Skip to content

Commit

Permalink
fix: refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
uanid committed Jun 11, 2024
1 parent 5a9a493 commit 207a237
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 36 deletions.
29 changes: 29 additions & 0 deletions lib/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type {VerifyConditionsContext} from "semantic-release";
import {execSync} from "node:child_process";
import gitUrlParse from 'git-url-parse';
import urlJoin from "url-join";
import axios, {type AxiosInstance, type InternalAxiosRequestConfig} from "axios";

export interface UserConfig {
gitlabUrl: string | undefined;
Expand Down Expand Up @@ -75,6 +76,34 @@ function getProjectId(origin: string): string {
return path;
}

export function newAxiosInstance(config: PluginConfig): AxiosInstance {
const instance = axios.create({});
// do not throw axios error
instance.interceptors.request.use(
async (request: InternalAxiosRequestConfig): Promise<InternalAxiosRequestConfig> => {
request.headers.set('PRIVATE-TOKEN', config.gitlabToken);
request.headers.set('Content-Type', 'application/json');
return request;
},
async (error: any) => {
if (error instanceof axios.AxiosError) {
return error;
} else {
return Promise.reject(error);
}
});

// do not throw axios error
instance.interceptors.response.use(undefined, async (error: any) => {
if (error instanceof axios.AxiosError) {
return error;
} else {
return Promise.reject(error);
}
});
return instance;
}

export const isUserConfig = typia.createIs<UserConfig>();
export const assertUserConfig = typia.createAssert<UserConfig>();
export const isPluginConfig = typia.createIs<PluginConfig>();
Expand Down
102 changes: 82 additions & 20 deletions lib/publish.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,90 @@
import {resolvePluginConfig, type UserConfig} from "./plugin.js";
import type {PrepareContext} from "semantic-release";
import {newAxiosInstance, type PluginConfig, resolvePluginConfig, type UserConfig} from "./plugin.js";
import type {PublishContext} from "semantic-release";
import fs from "node:fs/promises";
import path from "node:path";
import os from "node:os";
import axios, {AxiosError} from "axios";
import {AxiosError, type AxiosResponse} from "axios";
import urlJoin from "url-join";
import {execSync} from "child_process";
import {template} from "lodash-es";

export async function publish(userConfig: UserConfig, context: PrepareContext): Promise<void> {
export async function publish(userConfig: UserConfig, context: PublishContext): Promise<void> {
const config = resolvePluginConfig(userConfig, context);
if (config.assets.length === 0) {
context.logger.log("No assets defined to publish.");
return;
}

const body = await makeCommitRequestBody(config, context);
context.logger.log(`Extract ${body.actions.length} assets.`);

const instance = newAxiosInstance(config);
const response: AxiosResponse | AxiosError = await instance.post(urlJoin(config.gitlabBaseUrl, 'repository', 'commits'), body);

if (response instanceof AxiosError) {
if (!isDuplicatedBranchError(response)) {
context.logger.error(`Failed to commit assets: ${JSON.stringify(response.response?.data)}, Status ${response.response?.status}`);
throw response;
}

// Append assets to existing branch
body.start_branch = body.branch;

const response2: AxiosResponse | AxiosError = await instance.post(urlJoin(config.gitlabBaseUrl, 'repository', 'commits'), body);
if (response2 instanceof AxiosError) {
context.logger.error(`Failed to commit assets: ${JSON.stringify(response2.response?.data)}, Status ${response2.response?.status}`);
throw response2;
}
if (response2.status !== 201) {
context.logger.error(`Failed to commit assets: ${JSON.stringify(response2.data)}, Status ${response.status}`);
throw new Error(`Failed to commit assets: ${JSON.stringify(response2.data)}, Status ${response.status}`);
}
return;
}
if (response.status !== 201) {
context.logger.error(`Failed to commit assets: ${JSON.stringify(response.data)}, Status ${response.status}`);
throw new Error(`Failed to commit assets: ${JSON.stringify(response.data)}, Status ${response.status}`);
}
}

function isDuplicatedBranchError(error: AxiosError): boolean {
if (!error.response) {
return false;
}
if (error.response.status !== 400) {
return false;
}
if (!error.response.data) {
return false;
}
if (typeof error.response.data !== "object") {
return false;
}
if (!("message" in error.response.data)) {
return false;
}
if (typeof error.response.data.message !== "string") {
return false;
}
return error.response.data.message.includes("already exists.");
}

async function makeCommitRequestBody(config: PluginConfig, context: PublishContext) {
const publishConfig = resolvePublishConfig();

const commitMessage = template(config.commitTitle)({
branch: context.branch,
lastRelease: context.lastRelease,
nextRelease: context.nextRelease
});

// https://docs.gitlab.com/ee/api/commits.html#create-a-commit-with-multiple-files-and-actions
const body = {
branch: context.branch.name,
commit_message: config.commitTitle,
branch: `assets/${publishConfig.commitSha.substring(0, 8)}`,
start_branch: context.branch.name,
commit_message: commitMessage,
actions: [],
};
const repositoryDir = execSync("git rev-parse --show-toplevel", {encoding: "utf-8", cwd: context.cwd}).trim();
for (const asset of config.assets) {
const assetPath = resolve(asset.path, context.cwd);
if (!await fileExists(assetPath)) {
Expand All @@ -31,25 +95,23 @@ export async function publish(userConfig: UserConfig, context: PrepareContext):

body.actions.push({
action: "update",
file_path: path.relative(repositoryDir, assetPath),
file_path: path.relative(publishConfig.repositoryDir, assetPath),
encoding: "text",
content: assetContent,
} as never);
}
return body;
}

try {
const instance = axios.create({});
await instance.post(urlJoin(config.gitlabBaseUrl, 'repository', 'commits'), body, {
headers: {"PRIVATE-TOKEN": config.gitlabToken, "Content-Type": "application/json"},
});
} catch (e) {
if (!(e instanceof AxiosError)) {
throw e;
}
interface PublishConfig {
repositoryDir: string;
commitSha: string;
}

context.logger.error(`Failed to commit assets: ${JSON.stringify(e.response?.data)}, Status ${e.response?.status}`);
throw e;
}
function resolvePublishConfig(): PublishConfig {
const repositoryDir = execSync("git rev-parse --show-toplevel", {encoding: "utf-8"}).trim();
const commitSha = execSync("git rev-parse HEAD", {encoding: "utf-8"}).trim();
return {repositoryDir, commitSha};
}

export async function fileExists(path: string): Promise<boolean> {
Expand Down
22 changes: 9 additions & 13 deletions lib/verifyConditions.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
import {resolvePluginConfig, type UserConfig} from "./plugin.js";
import {newAxiosInstance, resolvePluginConfig, type UserConfig} from "./plugin.js";
import type {PrepareContext} from "semantic-release";
import axios, {AxiosError} from "axios";
import {AxiosError, type AxiosResponse} from "axios";

export async function verifyConditions(userConfig: UserConfig, context: PrepareContext): Promise<void> {
const config = resolvePluginConfig(userConfig, context);
context.logger.log(`Resolved Plugin Config: ${JSON.stringify(config)}`);

try {
const instance = axios.create({});
await instance.get(config.gitlabBaseUrl, {
headers: {"PRIVATE-TOKEN": config.gitlabToken, "Content-Type": "application/json"},
});
} catch (e) {
if (!(e instanceof AxiosError)) {
throw e;
}
const instance = newAxiosInstance(config);

context.logger.error(`Failed to verify GitLab connection with error: ${JSON.stringify(e.response?.data)}. Please check your GitLab configuration and token.`);
throw e;
const response: AxiosResponse | AxiosError = await instance.get(config.gitlabBaseUrl);
if (response instanceof AxiosError) {
context.logger.error(`Failed to verify GitLab connection with error: ${JSON.stringify(response.response?.data)}. Please check your GitLab configuration and token.`);
throw response;
} else {
context.logger.log(`Verified GitLab connection with status: ${response.status}`);
}
}
24 changes: 21 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"@semantic-release/npm": "^12.0.1",
"@semantic-release/release-notes-generator": "^14.0.0",
"@types/git-url-parse": "^9.0.3",
"@types/lodash-es": "^4.17.12",
"@types/node": "^20.14.2",
"semantic-release": "^24.0.0",
"ts-node": "^10.9.2",
Expand All @@ -26,11 +27,13 @@
"dependencies": {
"axios": "^1.7.2",
"git-url-parse": "^14.0.0",
"lodash-es": "^4.17.21",
"typia": "^6.0.6",
"url-join": "^5.0.0"
},
"files": [
"dist/*",
"lib/*",
"package.json",
"README.md",
"CHANGELOG.md"
Expand Down

0 comments on commit 207a237

Please sign in to comment.