@wjwjq/release-helper 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.prettierignore +13 -0
- package/.prettierrc.json +19 -0
- package/README.md +29 -0
- package/bin/index.js +7 -0
- package/dist/cli.js +56 -0
- package/dist/deploy/nginx/README.md +1 -0
- package/dist/deploy/nginx/pkg/nginx-arm-ssl1.1.1.tar.gz +0 -0
- package/dist/deploy/nginx/pkg/nginx-x86_64-ssl1.0.2.tar.gz +0 -0
- package/dist/deploy/nginx/pkg/nginx-x86_64-ssl1.1.1.tar.gz +0 -0
- package/dist/deploy/nginx/pkg/nginx-x86_64-ssl3.0.7.tar.gz +0 -0
- package/dist/deploy/nginx/script/common.sh +178 -0
- package/dist/deploy/nginx/script/compile.sh +11 -0
- package/dist/deploy/nginx/script/fix.sh +24 -0
- package/dist/deploy/nginx/script/https.sh +79 -0
- package/dist/deploy/nginx/script/install.sh +99 -0
- package/dist/deploy/nginx/script/prompt.sh +10 -0
- package/dist/deploy/nginx/script/upgrade.sh +7 -0
- package/dist/deploy/pkg/nginx/nginx.service.tpl +33 -0
- package/dist/deploy/script/common.sh +183 -0
- package/dist/deploy/script/install.sh +51 -0
- package/dist/deploy/script/nginx.sh +156 -0
- package/dist/deploy/script/prompt.sh +98 -0
- package/dist/deploy/script/readme.md +10 -0
- package/dist/deploy/script/upgrade.sh +50 -0
- package/dist/logger.js +20 -0
- package/dist/pack.js +111 -0
- package/dist/prepare.js +75 -0
- package/dist/publish.js +238 -0
- package/dist/release.js +200 -0
- package/package.json +71 -0
- package/rollup.config.js +78 -0
- package/src/.release/README.md +23 -0
- package/src/.release/doc//351/203/250/347/275/262/346/211/213/345/206/214.md +426 -0
- package/src/.release/nginx/ca/ca.crt +32 -0
- package/src/.release/nginx/ca/ca.key +54 -0
- package/src/.release/nginx/ca/client.crt +100 -0
- package/src/.release/nginx/ca/client.csr +16 -0
- package/src/.release/nginx/ca/client.p12 +0 -0
- package/src/.release/nginx/ca/client.pem +30 -0
- package/src/.release/nginx/ca/server.crt +101 -0
- package/src/.release/nginx/ca/server.csr +17 -0
- package/src/.release/nginx/ca/server.key +27 -0
- package/src/.release/nginx/ca/server.pem +30 -0
- package/src/.release/nginx/nginx.conf +179 -0
- package/src/.release/release.conf.yaml +9 -0
- package/src/cli.ts +99 -0
- package/src/deploy/nginx/README.md +1 -0
- package/src/deploy/nginx/pkg/nginx-arm-ssl1.1.1.tar.gz +0 -0
- package/src/deploy/nginx/pkg/nginx-x86_64-ssl1.0.2.tar.gz +0 -0
- package/src/deploy/nginx/pkg/nginx-x86_64-ssl1.1.1.tar.gz +0 -0
- package/src/deploy/nginx/pkg/nginx-x86_64-ssl3.0.7.tar.gz +0 -0
- package/src/deploy/nginx/script/common.sh +178 -0
- package/src/deploy/nginx/script/compile.sh +11 -0
- package/src/deploy/nginx/script/fix.sh +24 -0
- package/src/deploy/nginx/script/https.sh +79 -0
- package/src/deploy/nginx/script/install.sh +99 -0
- package/src/deploy/nginx/script/prompt.sh +10 -0
- package/src/deploy/nginx/script/upgrade.sh +7 -0
- package/src/deploy/pkg/nginx/nginx.service.tpl +33 -0
- package/src/deploy/script/common.sh +183 -0
- package/src/deploy/script/install.sh +51 -0
- package/src/deploy/script/nginx.sh +156 -0
- package/src/deploy/script/prompt.sh +98 -0
- package/src/deploy/script/readme.md +10 -0
- package/src/deploy/script/upgrade.sh +50 -0
- package/src/logger.ts +18 -0
- package/src/pack.ts +141 -0
- package/src/prepare.ts +105 -0
- package/src/publish.ts +308 -0
- package/src/release.ts +292 -0
- package/tsconfig.json +14 -0
package/dist/prepare.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import path, { dirname } from 'path';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import YAML from 'yaml';
|
|
4
|
+
import * as inquirer from '@inquirer/prompts';
|
|
5
|
+
import { execa } from 'execa';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import { logger } from './logger.js';
|
|
8
|
+
import 'picocolors';
|
|
9
|
+
|
|
10
|
+
const __work_dir = process.cwd();
|
|
11
|
+
const __releaseDir = path.resolve(__work_dir, ".release");
|
|
12
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
13
|
+
const __dirname = dirname(__filename);
|
|
14
|
+
const releaseConfFileName = "release.conf.yaml";
|
|
15
|
+
const __releaseConfPath = path.resolve(__releaseDir, releaseConfFileName);
|
|
16
|
+
function parseConf(file) {
|
|
17
|
+
try {
|
|
18
|
+
const conf = fs.readFileSync(file, "utf-8");
|
|
19
|
+
return YAML.parse(conf);
|
|
20
|
+
} catch (error) {
|
|
21
|
+
return {};
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const releaseConf = Object.assign({
|
|
25
|
+
/** 打包脚本 默认npm run build */
|
|
26
|
+
buildCmd: "npm run build",
|
|
27
|
+
/** 打包后资源 相对工作区所在路径 默认dist/*/
|
|
28
|
+
assetsDir: "dist"
|
|
29
|
+
}, parseConf(__releaseConfPath));
|
|
30
|
+
const $ = execa({ encoding: "utf8" });
|
|
31
|
+
async function prepare() {
|
|
32
|
+
const copyFiles = () => {
|
|
33
|
+
fs.cpSync(path.resolve(__dirname, ".release"), __releaseDir, { recursive: true });
|
|
34
|
+
};
|
|
35
|
+
if (!fs.existsSync(__releaseDir)) {
|
|
36
|
+
fs.mkdirSync(__releaseDir);
|
|
37
|
+
copyFiles();
|
|
38
|
+
logger.tip(`\u8BF7\u6CE8\u610F\u4FEE\u6539.release/${releaseConfFileName} \u914D\u7F6E\u4FE1\u606F`);
|
|
39
|
+
} else {
|
|
40
|
+
const shouldRegenerate = await inquirer.confirm(
|
|
41
|
+
{
|
|
42
|
+
message: ".release\u914D\u7F6E\u5DF2\u5B58\u5728\uFF0C\u662F\u5426\u91CD\u65B0\u751F\u6210\uFF1F"
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
if (shouldRegenerate) {
|
|
46
|
+
copyFiles();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
async function checkEnvInfo() {
|
|
51
|
+
const { stdout: isGitRepo } = await $(`git rev-parse --is-inside-work-tree`);
|
|
52
|
+
if (isGitRepo !== "true") {
|
|
53
|
+
logger.error("\u5F53\u524D\u9879\u76EE\u4E0D\u662Fgit\u4ED3\u5E93");
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
if (!fs.existsSync(path.resolve(__releaseDir, "nginx/nginx.conf"))) {
|
|
57
|
+
logger.error(".release\u76EE\u5F55\u4E0B\u4E0D\u5B58\u5728nginx/nginx.conf \u6587\u4EF6\uFF0C \u8BF7\u4F7F\u7528 release-helper init \u521D\u59CB\u5316\u751F\u6210");
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
if (!fs.existsSync(__releaseConfPath)) {
|
|
61
|
+
logger.error(`.release\u76EE\u5F55\u4E0B\u4E0D\u5B58\u5728${releaseConfFileName}\u6587\u4EF6\uFF0C\u8BF7\u4F7F\u7528 release-helper init \u521D\u59CB\u5316\u751F\u6210`);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
const { host, token } = releaseConf;
|
|
65
|
+
if (!host) {
|
|
66
|
+
logger.error(`The host field is required in the configuration file!`);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
if (!token) {
|
|
70
|
+
logger.error(`The token field is required in the configuration file!`);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export { $, __dirname, __releaseConfPath, __releaseDir, __work_dir, checkEnvInfo, prepare, releaseConf, releaseConfFileName };
|
package/dist/publish.js
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { URL } from 'url';
|
|
4
|
+
import crypto from 'crypto';
|
|
5
|
+
import mime from 'mime';
|
|
6
|
+
import * as inquirer from '@inquirer/prompts';
|
|
7
|
+
import { setTimeout } from 'node:timers/promises';
|
|
8
|
+
import { Gitlab } from '@gitbeaker/rest';
|
|
9
|
+
import { $, releaseConf, __releaseDir } from './prepare.js';
|
|
10
|
+
import { logger } from './logger.js';
|
|
11
|
+
import 'yaml';
|
|
12
|
+
import 'execa';
|
|
13
|
+
import 'node:url';
|
|
14
|
+
import 'picocolors';
|
|
15
|
+
|
|
16
|
+
async function isGitFlowRepo() {
|
|
17
|
+
try {
|
|
18
|
+
let { stdout } = await $("git config", ["--get-regexp", `^gitflow.`]);
|
|
19
|
+
return stdout.toString().length > 0;
|
|
20
|
+
} catch (error) {
|
|
21
|
+
console.error("\u{1F680} ~ doGitFlowRelease ~ error:", error);
|
|
22
|
+
}
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
async function getRepo() {
|
|
26
|
+
const res = await $`git remote -v `.pipe` sed -n '1p'`.pipe`awk -F ' ' '{print $2}'`.pipe`sed -e 's/.git//'`;
|
|
27
|
+
const repo = res.stdout.replace("\n", "");
|
|
28
|
+
if (repo.startsWith("git@")) {
|
|
29
|
+
const ps2 = repo.split("/");
|
|
30
|
+
return {
|
|
31
|
+
isHttpRepo: false,
|
|
32
|
+
repo: repo.split(":")[1],
|
|
33
|
+
repoName: ps2[ps2.length - 1].replace(".git", "")
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const url = new URL(repo);
|
|
37
|
+
const ps = url.pathname.split("/");
|
|
38
|
+
return {
|
|
39
|
+
isHttpRepo: true,
|
|
40
|
+
repo: url.pathname,
|
|
41
|
+
origin: url.origin,
|
|
42
|
+
repoName: ps[ps.length - 1]
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
async function getChangeLog(host, newVersion, userSelectedLatestTag, markdownUrls) {
|
|
46
|
+
async function getLog() {
|
|
47
|
+
let output2 = "";
|
|
48
|
+
try {
|
|
49
|
+
const isGitFlow = await isGitFlowRepo();
|
|
50
|
+
const { stdout } = await $`git tag --sort=-creatordate`;
|
|
51
|
+
const tags = stdout.split("\n");
|
|
52
|
+
const latestTag = tags[0];
|
|
53
|
+
let tagRange = "";
|
|
54
|
+
if (isGitFlow) {
|
|
55
|
+
if (tags.length >= 2) {
|
|
56
|
+
const secondLastTag = userSelectedLatestTag !== "" ? userSelectedLatestTag : tags[1];
|
|
57
|
+
tagRange = `${secondLastTag}..${latestTag}`;
|
|
58
|
+
} else if (tags.length === 1) {
|
|
59
|
+
tagRange = latestTag;
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
if (tags.length >= 1) {
|
|
63
|
+
tagRange = `${latestTag}..HEAD`;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
let logCmd = `git log ${tagRange} --format=%B%H----DELIMITER---- --abbrev-commit`;
|
|
67
|
+
const { stdout: r } = await $(logCmd);
|
|
68
|
+
output2 = r;
|
|
69
|
+
} catch (error) {
|
|
70
|
+
console.error("\u{1F680} ~ getLog ~ error:", error);
|
|
71
|
+
const { stdout: r } = await $`git log --format=%B%H----DELIMITER----`;
|
|
72
|
+
output2 = r;
|
|
73
|
+
}
|
|
74
|
+
return output2;
|
|
75
|
+
}
|
|
76
|
+
const output = await getLog();
|
|
77
|
+
const commitsArray = output.split("----DELIMITER----\n").map((commit) => {
|
|
78
|
+
const [message, sha] = commit.split("\n").filter((msg) => msg != "" && !msg.startsWith("Merge"));
|
|
79
|
+
return { sha, message };
|
|
80
|
+
}).filter((commit) => Boolean(commit.sha));
|
|
81
|
+
let currentChangelog = "";
|
|
82
|
+
const { repo } = await getRepo();
|
|
83
|
+
let newChangelog = ``;
|
|
84
|
+
let features = [];
|
|
85
|
+
let Bugfixes = [];
|
|
86
|
+
commitsArray.forEach((commit) => {
|
|
87
|
+
const commitSha = `([${commit.sha.substring(0, 6)}](${host}/${repo}/-/commit/${commit.sha}))`;
|
|
88
|
+
if (commit.message.startsWith("feat:") || commit.message.startsWith("feat\uFF1A")) {
|
|
89
|
+
const feats = commit.message.replace(/feat[:|:]/, "").replace(/;|;/g, "$").split("$");
|
|
90
|
+
features = features.concat(feats.map((f) => `* ${f.replace(/^\s+/, "")} ${commitSha}
|
|
91
|
+
`));
|
|
92
|
+
}
|
|
93
|
+
if (commit.message.startsWith("fix:") || commit.message.startsWith("fix\uFF1A")) {
|
|
94
|
+
const fixes = commit.message.replace(/fix[:|:]/, "").replace(/;|;/g, "$").split("$");
|
|
95
|
+
Bugfixes = Bugfixes.concat(fixes.map((f) => `* ${f.replace(/^\s+/, "")} ${commitSha}
|
|
96
|
+
`));
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
if (features.length) {
|
|
100
|
+
newChangelog += `# Features
|
|
101
|
+
`;
|
|
102
|
+
features.forEach((feature) => {
|
|
103
|
+
newChangelog += feature;
|
|
104
|
+
});
|
|
105
|
+
newChangelog += "\n";
|
|
106
|
+
}
|
|
107
|
+
if (Bugfixes.length) {
|
|
108
|
+
newChangelog += `# Bugfixes
|
|
109
|
+
`;
|
|
110
|
+
Bugfixes.forEach((bugfix) => {
|
|
111
|
+
newChangelog += bugfix;
|
|
112
|
+
});
|
|
113
|
+
newChangelog += "\n";
|
|
114
|
+
}
|
|
115
|
+
console.log("\u{1F680} ~ getChangeLog ~features & Bugfixes:", features, Bugfixes);
|
|
116
|
+
newChangelog += `# Artifacts
|
|
117
|
+
`;
|
|
118
|
+
newChangelog += `## tar\u5305
|
|
119
|
+
`;
|
|
120
|
+
newChangelog += `${markdownUrls.tar}
|
|
121
|
+
`;
|
|
122
|
+
newChangelog += `## md5
|
|
123
|
+
`;
|
|
124
|
+
newChangelog += `${markdownUrls.md5}
|
|
125
|
+
`;
|
|
126
|
+
newChangelog += `## \u6587\u6863
|
|
127
|
+
`;
|
|
128
|
+
newChangelog += `${markdownUrls.doc}
|
|
129
|
+
`;
|
|
130
|
+
return `${newChangelog}${currentChangelog}`;
|
|
131
|
+
}
|
|
132
|
+
async function getGitTags() {
|
|
133
|
+
const { stdout } = await $`git tag --sort=-creatordate`;
|
|
134
|
+
return stdout.split("\n");
|
|
135
|
+
}
|
|
136
|
+
async function selectLatestTag(targetVersion) {
|
|
137
|
+
let latestTag = "";
|
|
138
|
+
const tags = await getGitTags();
|
|
139
|
+
if (tags.length >= 2) {
|
|
140
|
+
const ac = new AbortController();
|
|
141
|
+
const prompt = inquirer.select(
|
|
142
|
+
{
|
|
143
|
+
message: "\u8BF7\u9009\u62E9\u53D1\u5E03\u4FE1\u606F\u4ECE\u54EA\u6B21tag\u5F00\u59CB\u751F\u6210\uFF0C\uFF08\u9ED8\u8BA4\uFF1A \u6700\u8FD1\u4E00\u6B21\uFF0C\u7279\u522B\u9488\u5BF9\u67D0\u4E3B\u7248\u672C\u4E4B\u95F4\u6709\u5C0F\u7248\u672C\u8FC7\u6E21\u7684\u60C5\u51B5\uFF09(timing out in 10 seconds)\uFF1A",
|
|
144
|
+
default: tags[0],
|
|
145
|
+
choices: tags.filter((item) => item !== targetVersion).map((tag) => ({ value: tag }))
|
|
146
|
+
}
|
|
147
|
+
);
|
|
148
|
+
prompt.finally(() => {
|
|
149
|
+
ac.abort();
|
|
150
|
+
}).catch(() => {
|
|
151
|
+
});
|
|
152
|
+
const defaultValue = setTimeout(1e4, "timeout", { signal: ac.signal }).then(() => {
|
|
153
|
+
prompt.cancel();
|
|
154
|
+
return tags[0];
|
|
155
|
+
});
|
|
156
|
+
latestTag = await Promise.race([defaultValue, prompt]);
|
|
157
|
+
}
|
|
158
|
+
return latestTag;
|
|
159
|
+
}
|
|
160
|
+
async function publish(params) {
|
|
161
|
+
const {
|
|
162
|
+
version,
|
|
163
|
+
pkgName,
|
|
164
|
+
appName,
|
|
165
|
+
remoteBranch,
|
|
166
|
+
userSelectedLatestTag = ""
|
|
167
|
+
} = params || {};
|
|
168
|
+
try {
|
|
169
|
+
const { repo } = await getRepo();
|
|
170
|
+
const { host, token } = releaseConf;
|
|
171
|
+
logger.tip("start to publish");
|
|
172
|
+
const api = new Gitlab({
|
|
173
|
+
host,
|
|
174
|
+
token,
|
|
175
|
+
rejectUnauthorized: false
|
|
176
|
+
});
|
|
177
|
+
const projects = await api.Projects.all();
|
|
178
|
+
const proj = projects.find((item) => item.path_with_namespace === repo);
|
|
179
|
+
if (proj === void 0) {
|
|
180
|
+
throw Error(`The target repo not founded!`);
|
|
181
|
+
}
|
|
182
|
+
const id = proj.id;
|
|
183
|
+
const assetFile = path.resolve(__releaseDir, pkgName);
|
|
184
|
+
const file = fs.readFileSync(assetFile);
|
|
185
|
+
const hash = crypto.createHash("md5");
|
|
186
|
+
hash.update(file);
|
|
187
|
+
const md5 = hash.digest("hex");
|
|
188
|
+
const md5FileName = `${appName}_${version}.md5.txt`;
|
|
189
|
+
const docPath = path.join(__releaseDir, "doc");
|
|
190
|
+
const replacedConstants = [["\\$APP_NAME\\$", appName], ["\\$VERSION\\$", version], ["\\$TAR_PKG\\$", pkgName]];
|
|
191
|
+
const docUploadsPromises = fs.readdirSync(docPath).map((docFIleName) => {
|
|
192
|
+
const docFilePath = path.resolve(docPath, docFIleName);
|
|
193
|
+
const stats = fs.statSync(docFilePath);
|
|
194
|
+
if (stats.isFile()) {
|
|
195
|
+
let content = fs.readFileSync(docFilePath).toString();
|
|
196
|
+
const doc = replacedConstants.reduce((prev, [reg, value]) => {
|
|
197
|
+
return prev.replace(new RegExp(reg, "g"), value);
|
|
198
|
+
}, content);
|
|
199
|
+
return api.Projects.uploadForReference(id, { content: new Blob([doc], { type: mime.getType(docFIleName) }), filename: docFIleName });
|
|
200
|
+
}
|
|
201
|
+
}).filter((item) => item);
|
|
202
|
+
logger.info("start to upload resources");
|
|
203
|
+
const [tarUploads, md5Uploads, ...docUploads] = await Promise.all([
|
|
204
|
+
api.Projects.uploadForReference(id, { content: new Blob([file], { type: "application/gzip" }), filename: pkgName }),
|
|
205
|
+
api.Projects.uploadForReference(id, { content: new Blob([md5], { type: "text/plain" }), filename: md5FileName }),
|
|
206
|
+
...docUploadsPromises
|
|
207
|
+
]);
|
|
208
|
+
const description = await getChangeLog(host, version, userSelectedLatestTag, { tar: tarUploads.markdown, md5: md5Uploads.markdown, doc: docUploads.map((item) => item.markdown).join("\n") });
|
|
209
|
+
logger.tip("start tag and release to git repo");
|
|
210
|
+
const tags = await api.Tags.all(id);
|
|
211
|
+
if (tags.find((r) => r.name === version) === void 0) {
|
|
212
|
+
await api.Tags.create(id, version, remoteBranch);
|
|
213
|
+
}
|
|
214
|
+
const releases = await api.ProjectReleases.all(id);
|
|
215
|
+
let res;
|
|
216
|
+
if (releases.find((r) => r.name === version)) {
|
|
217
|
+
res = await api.ProjectReleases.edit(id, version, {
|
|
218
|
+
description
|
|
219
|
+
});
|
|
220
|
+
} else {
|
|
221
|
+
res = await api.ProjectReleases.create(id, {
|
|
222
|
+
tag_name: version,
|
|
223
|
+
description
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
await api.ReleaseLinks.create(id, version, pkgName, `${host}${tarUploads.full_path}`, { linkType: "package" });
|
|
227
|
+
await api.ReleaseLinks.create(id, version, md5FileName, `${host}${md5Uploads.full_path}`, { linkType: "other" });
|
|
228
|
+
await Promise.all(docUploads.map((doc) => api.ReleaseLinks.create(id, version, doc.alt, `${host}${doc.full_path}`, { linkType: "other" })));
|
|
229
|
+
fs.unlinkSync(assetFile);
|
|
230
|
+
logger.success(`Successfully released at: ${res._links.self} `);
|
|
231
|
+
} catch (error) {
|
|
232
|
+
logger.error("\u{1F680} ~ file: publish.js ~ error:");
|
|
233
|
+
console.error(error);
|
|
234
|
+
return Promise.reject(error);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export { getGitTags, isGitFlowRepo, publish, selectLatestTag };
|
package/dist/release.js
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import * as inquirer from '@inquirer/prompts';
|
|
2
|
+
import process from 'process';
|
|
3
|
+
import pc from 'picocolors';
|
|
4
|
+
import { pack } from './pack.js';
|
|
5
|
+
import { getGitTags, selectLatestTag, publish, isGitFlowRepo } from './publish.js';
|
|
6
|
+
import { checkEnvInfo, $, releaseConf } from './prepare.js';
|
|
7
|
+
import { logger } from './logger.js';
|
|
8
|
+
import 'fs';
|
|
9
|
+
import 'path';
|
|
10
|
+
import 'tar';
|
|
11
|
+
import 'url';
|
|
12
|
+
import 'crypto';
|
|
13
|
+
import 'mime';
|
|
14
|
+
import 'node:timers/promises';
|
|
15
|
+
import '@gitbeaker/rest';
|
|
16
|
+
import 'yaml';
|
|
17
|
+
import 'execa';
|
|
18
|
+
import 'node:url';
|
|
19
|
+
|
|
20
|
+
async function release(fromPublishCmd = false) {
|
|
21
|
+
try {
|
|
22
|
+
await checkEnvInfo();
|
|
23
|
+
await hasUncommittedChanges();
|
|
24
|
+
let { stdout: originalBranch } = await $(`git branch --show-current`);
|
|
25
|
+
let version = await inquirer.input({
|
|
26
|
+
message: "\u8BF7\u8F93\u5165\u7248\u672C\u53F7\uFF1A"
|
|
27
|
+
});
|
|
28
|
+
if (version == "") {
|
|
29
|
+
logger.error("\u7248\u672C\u53F7\u4E0D\u80FD\u4E3A\u7A7A\uFF01");
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (/^\d/.test(version)) {
|
|
33
|
+
version = `v${version}`;
|
|
34
|
+
}
|
|
35
|
+
const tags = await getGitTags();
|
|
36
|
+
if (tags.some((tag) => tag === version)) {
|
|
37
|
+
logger.error("\u7248\u672C\u53F7\u5DF2\u5B58\u5728\uFF0C\u8BF7\u66F4\u6362\u7248\u672C\u53F7\uFF01");
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
let remoteTargetBranch = "master";
|
|
41
|
+
if (fromPublishCmd) {
|
|
42
|
+
remoteTargetBranch = await doCustomSpecifyBranchRelease();
|
|
43
|
+
} else {
|
|
44
|
+
const releaseWay = await inquirer.select(
|
|
45
|
+
{
|
|
46
|
+
message: "\u8BF7\u9009\u62E9\u53D1\u5E03\u65B9\u5F0F\uFF1A",
|
|
47
|
+
default: "1",
|
|
48
|
+
choices: [
|
|
49
|
+
{
|
|
50
|
+
name: "Git flow\u6D41\u7A0B\u53D1\u5E03",
|
|
51
|
+
value: "1",
|
|
52
|
+
description: "\u9075\u5FAAGit flow\u6D41\u7A0B\uFF0C\u9700\u4FDD\u8BC1\u4ED3\u5E93\u5DF2\u521D\u59CB\u5316\u4E3Agit flow\u6A21\u5F0F, \u4EE5master\u4E3A\u8FDC\u7A0B\u5206\u652F\uFF0C\u6253Tag"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: "\u81EA\u5B9A\u4E49\u5206\u652F\u6253\u5305\u53D1\u5E03",
|
|
56
|
+
value: "2",
|
|
57
|
+
description: "\u81EA\u5B9A\u4E49\u6307\u5B9A\u8FDC\u7A0B\u5206\u652F\uFF0C\u6253Tag\u7684\u65B9\u5F0F"
|
|
58
|
+
}
|
|
59
|
+
]
|
|
60
|
+
}
|
|
61
|
+
);
|
|
62
|
+
if (releaseWay === "1") {
|
|
63
|
+
remoteTargetBranch = await doGitFlowRelease(version);
|
|
64
|
+
} else if (releaseWay === "2") {
|
|
65
|
+
remoteTargetBranch = await doCustomSpecifyBranchRelease();
|
|
66
|
+
} else {
|
|
67
|
+
logger.error(`\u53D1\u5E03\u65B9\u5F0F\u4E0D\u80FD\u4E3A\u7A7A`);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (remoteTargetBranch == null) {
|
|
71
|
+
logger.error(`\u76EE\u6807\u53D1\u5E03\u5206\u652F\u4E0D\u80FD\u4E3A\u7A7A`);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
logger.tip(`start build and pack`);
|
|
76
|
+
await execGitCheckout(remoteTargetBranch);
|
|
77
|
+
await execGitPullOrigin(remoteTargetBranch);
|
|
78
|
+
const userSelectedLatestTag = await selectLatestTag(version);
|
|
79
|
+
await execWithPipe(releaseConf.buildCmd);
|
|
80
|
+
const source = await pack(version);
|
|
81
|
+
if (source === void 0) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
await publish({ ...source, remoteBranch: remoteTargetBranch, userSelectedLatestTag });
|
|
85
|
+
logger.info(pc.green("done"));
|
|
86
|
+
await execGitCheckout(originalBranch);
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error("Error:", error);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async function doGitFlowRelease(version) {
|
|
92
|
+
const isGitFlow = await isGitFlowRepo();
|
|
93
|
+
if (!isGitFlow) {
|
|
94
|
+
logger.error("\u5F53\u524D\u4ED3\u5E93 \u975Egit flow\u4ED3\u5E93\uFF0C\u8BF7\u4F7F\u7528git flow init \u521D\u59CB\u5316\u540E\uFF0C\u518D\u6267\u884C\u6B64\u547D\u4EE4");
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
let { stdout: currentBranch } = await $(`git branch --show-current`);
|
|
98
|
+
const releaseBranch = `release/${version}`;
|
|
99
|
+
if (currentBranch !== "develop" && currentBranch !== releaseBranch) {
|
|
100
|
+
const { stdout } = await $("git branch -a");
|
|
101
|
+
const branches = stdout.split("\n").map((branch) => branch.trim().replace(/^\* /, ""));
|
|
102
|
+
const preReleaseBranch = branches.includes(releaseBranch) ? releaseBranch : "develop";
|
|
103
|
+
await execGitCheckout(preReleaseBranch);
|
|
104
|
+
}
|
|
105
|
+
if (currentBranch === "develop") {
|
|
106
|
+
await execGitPullOrigin(`develop`);
|
|
107
|
+
const flowReleaseRes = await execWithPipe(`git flow release start ${version}`);
|
|
108
|
+
if (!(flowReleaseRes.stdout.indexOf(`A new branch '${releaseBranch}' was created`) > -1)) {
|
|
109
|
+
logger.error("\u6267\u884C git flow release \u5931\u8D25");
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
try {
|
|
114
|
+
await execGitPullOrigin(releaseBranch);
|
|
115
|
+
} catch (error) {
|
|
116
|
+
console.error("\u{1F680} ~ doGitFlowRelease ~ error:", error);
|
|
117
|
+
if (!(error && error.toString().indexOf(`fatal: couldn't find remote ref ${releaseBranch}`) > -1)) {
|
|
118
|
+
logger.error(`\u540C\u6B65\u5206\u652F\uFF1A ${releaseBranch} \u5931\u8D25`);
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
try {
|
|
124
|
+
const npmVersionRes = await $(`npm version ${version} --no-git-tag-version`);
|
|
125
|
+
if (npmVersionRes.stderr) {
|
|
126
|
+
logger.error("\u6DFB\u52A0\u7248\u672C\u4FE1\u606F\u5230package.json\u5931\u8D25");
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
logger.info(pc.green(`\u6DFB\u52A0 ${version} \u7248\u672C\u4FE1\u606F\u5230package.json \u6210\u529F`));
|
|
130
|
+
} catch (error) {
|
|
131
|
+
if (error && error.toString().indexOf("npm error Version not changed") == -1) {
|
|
132
|
+
logger.error("\u6DFB\u52A0\u7248\u672C\u4FE1\u606F\u5230package.json\u5931\u8D25");
|
|
133
|
+
process.exit(1);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const hasUncommitted = await hasUncommittedChanges(true);
|
|
137
|
+
if (hasUncommitted) {
|
|
138
|
+
await $(`git add -A`);
|
|
139
|
+
await $("git commit", ["-m", `Release version: ${version}`, "--no-verify"]);
|
|
140
|
+
}
|
|
141
|
+
await execWithPipe(`git flow release finish`, [version, "-m", `Release version: ${version}`]);
|
|
142
|
+
await $(`git push --all`);
|
|
143
|
+
await $(`git push --tags`);
|
|
144
|
+
return "master";
|
|
145
|
+
}
|
|
146
|
+
async function doCustomSpecifyBranchRelease() {
|
|
147
|
+
const { stdout } = await $("git branch -r");
|
|
148
|
+
const branches = stdout.split("\n").map((branch) => branch.trim()).filter((item) => !item.startsWith("origin/HEAD")).map((branch) => ({ value: branch.replace("origin/", "") }));
|
|
149
|
+
const selectedBranch = await inquirer.select({
|
|
150
|
+
message: "\u8BF7\u9009\u62E9\u76EE\u6807\u5206\u652F, \u5206\u652F\u672A\u627E\u5230\uFF0C\u8BF7\u5148\u5C06\u672C\u5730\u5206\u652F\u63A8\u5230\u8FDC\u7AEF\uFF0C\u518D\u6267\u884C\u6B64\u547D\u4EE4",
|
|
151
|
+
choices: branches
|
|
152
|
+
});
|
|
153
|
+
return selectedBranch;
|
|
154
|
+
}
|
|
155
|
+
async function hasUncommittedChanges(boolReturned = false) {
|
|
156
|
+
const { stdout: uncommitted } = await $`git status --porcelain`;
|
|
157
|
+
if (boolReturned) {
|
|
158
|
+
return !!uncommitted.toString().length;
|
|
159
|
+
}
|
|
160
|
+
if (uncommitted.toString().length) {
|
|
161
|
+
logger.error("\u5F53\u524D\u5206\u652F\u5B58\u5728\u5C1A\u672A\u63D0\u4EA4\u7684\u6587\u4EF6\uFF0C\u8BF7\u63D0\u4EA4\u540E\u518D\u8FD0\u884C\u6B64\u547D\u4EE4");
|
|
162
|
+
return Promise.reject(true);
|
|
163
|
+
}
|
|
164
|
+
return Promise.resolve(false);
|
|
165
|
+
}
|
|
166
|
+
async function execGitPullOrigin(remoteTargetBranch) {
|
|
167
|
+
logger.tip(`${remoteTargetBranch} syncing...`);
|
|
168
|
+
const syncReleaseBranchRes = $("git", ["pull", "origin", remoteTargetBranch]);
|
|
169
|
+
syncReleaseBranchRes.stdout.pipe(process.stdout);
|
|
170
|
+
syncReleaseBranchRes.stderr.pipe(process.stderr);
|
|
171
|
+
const res = await syncReleaseBranchRes;
|
|
172
|
+
if (res.stdout.indexOf("fatal:") > -1 || res.stderr.indexOf("fatal:") > -1) {
|
|
173
|
+
logger.error(`\u540C\u6B65${remoteTargetBranch} \u5931\u8D25`);
|
|
174
|
+
process.exit(1);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
async function execWithPipe(...args) {
|
|
178
|
+
const execRes = $(...args);
|
|
179
|
+
execRes.stdout.pipe(process.stdout);
|
|
180
|
+
execRes.stderr.pipe(process.stderr);
|
|
181
|
+
const result = await execRes;
|
|
182
|
+
return result;
|
|
183
|
+
}
|
|
184
|
+
async function execGitCheckout(remoteTargetBranch) {
|
|
185
|
+
await hasUncommittedChanges();
|
|
186
|
+
const { stdout: prevCurrentBranch } = await $(`git branch --show-current`);
|
|
187
|
+
if (prevCurrentBranch !== remoteTargetBranch) {
|
|
188
|
+
const execRes = $(`git checkout ${remoteTargetBranch}`);
|
|
189
|
+
execRes.stdout.pipe(process.stdout);
|
|
190
|
+
execRes.stderr.pipe(process.stderr);
|
|
191
|
+
const result = await execRes;
|
|
192
|
+
const { stdout: currentBranch } = await $(`git branch --show-current`);
|
|
193
|
+
if (currentBranch !== remoteTargetBranch) {
|
|
194
|
+
logger.error(`\u76EE\u6807\u53D1\u5E03\u5206\u652F[${remoteTargetBranch}] checkout \u5931\u8D25`);
|
|
195
|
+
return Promise.reject(result);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export { release };
|
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wjwjq/release-helper",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "generate deployment package for frontend, include nginx...",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.js",
|
|
7
|
+
"repository": {
|
|
8
|
+
"url": "http://10.17.5.2:90/cd_front/release-helper"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"localpack": "pnpm link --global",
|
|
12
|
+
"postinstall": "release-helper init",
|
|
13
|
+
"build": "rollup -c",
|
|
14
|
+
"prepublish": "pnpm build"
|
|
15
|
+
},
|
|
16
|
+
"bin": {
|
|
17
|
+
"release-helper": "./bin/index.js"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"frontend",
|
|
21
|
+
"deployment",
|
|
22
|
+
"nginx"
|
|
23
|
+
],
|
|
24
|
+
"author": "wjwjq",
|
|
25
|
+
"license": "ISC",
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"@gitbeaker/rest": "^40.0.3",
|
|
28
|
+
"@inquirer/prompts": "^5.0.6",
|
|
29
|
+
"cac": "^6.7.14",
|
|
30
|
+
"execa": "^9.2.0",
|
|
31
|
+
"fs-extra": "^11.2.0",
|
|
32
|
+
"mime": "^4.0.3",
|
|
33
|
+
"picocolors": "^1.0.1",
|
|
34
|
+
"tar": "^7.4.0",
|
|
35
|
+
"tsx": "^4.15.6",
|
|
36
|
+
"yaml": "^2.4.5"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@gitbeaker/rest": "^40.0.3",
|
|
40
|
+
"@inquirer/prompts": "^5.0.6",
|
|
41
|
+
"cac": "^6.7.14",
|
|
42
|
+
"execa": "^9.2.0",
|
|
43
|
+
"fs-extra": "^11.2.0",
|
|
44
|
+
"mime": "^4.0.3",
|
|
45
|
+
"picocolors": "^1.0.1",
|
|
46
|
+
"tar": "^7.4.0",
|
|
47
|
+
"tsx": "^4.15.6",
|
|
48
|
+
"yaml": "^2.4.5"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@rollup/plugin-commonjs": "^26.0.1",
|
|
52
|
+
"@rollup/plugin-json": "^6.1.0",
|
|
53
|
+
"@rollup/plugin-node-resolve": "^15.2.3",
|
|
54
|
+
"@types/node": "^20.14.6",
|
|
55
|
+
"eslint": "^9.5.0",
|
|
56
|
+
"eslint-plugin-import-x": "^0.5.1",
|
|
57
|
+
"eslint-plugin-n": "^17.9.0",
|
|
58
|
+
"eslint-plugin-regexp": "^2.6.0",
|
|
59
|
+
"glob": "^10.4.2",
|
|
60
|
+
"prettier": "3.3.2",
|
|
61
|
+
"rollup": "^4.18.0",
|
|
62
|
+
"rollup-plugin-copy": "^3.5.0",
|
|
63
|
+
"rollup-plugin-dts": "^6.1.1",
|
|
64
|
+
"rollup-plugin-esbuild": "^6.1.1",
|
|
65
|
+
"typescript": "^5.5.2"
|
|
66
|
+
},
|
|
67
|
+
"type": "module",
|
|
68
|
+
"engines": {
|
|
69
|
+
"node": "^18.20.0 || >=20.0.0"
|
|
70
|
+
}
|
|
71
|
+
}
|
package/rollup.config.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import nodeResolvePlugin from '@rollup/plugin-node-resolve'
|
|
2
|
+
import esbuildPlugin from 'rollup-plugin-esbuild'
|
|
3
|
+
import dtsPlugin from 'rollup-plugin-dts'
|
|
4
|
+
import commonjsPlugin from '@rollup/plugin-commonjs'
|
|
5
|
+
import jsonPlugin from '@rollup/plugin-json';
|
|
6
|
+
import copyPlugin from 'rollup-plugin-copy'
|
|
7
|
+
import { globSync } from 'glob';
|
|
8
|
+
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
11
|
+
import fs from 'fs';
|
|
12
|
+
|
|
13
|
+
const pkg = JSON.parse(
|
|
14
|
+
fs.readFileSync(new URL('./package.json', import.meta.url)).toString(),
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
function createConfig({ dts, esm } = {}) {
|
|
18
|
+
let file = 'dist/index.js'
|
|
19
|
+
if (dts) {
|
|
20
|
+
file = file.replace('.js', '.d.ts')
|
|
21
|
+
}
|
|
22
|
+
if (esm) {
|
|
23
|
+
file = file.replace('.js', '.mjs')
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
input: Object.fromEntries(
|
|
27
|
+
globSync('src/**/*.ts').map(file => [
|
|
28
|
+
// This remove `src/` as well as the file extension from each
|
|
29
|
+
// file, so e.g. src/nested/foo.js becomes nested/foo
|
|
30
|
+
path.relative(
|
|
31
|
+
'src',
|
|
32
|
+
file.slice(0, file.length - path.extname(file).length)
|
|
33
|
+
),
|
|
34
|
+
// This expands the relative paths to absolute paths, so e.g.
|
|
35
|
+
// src/nested/foo becomes /project/src/nested/foo.js
|
|
36
|
+
fileURLToPath(new URL(file, import.meta.url))
|
|
37
|
+
])
|
|
38
|
+
),
|
|
39
|
+
output: {
|
|
40
|
+
format: dts || esm ? 'esm' : 'cjs',
|
|
41
|
+
dir: 'dist'
|
|
42
|
+
},
|
|
43
|
+
external: [...Object.keys(pkg.dependencies)],
|
|
44
|
+
plugins: [
|
|
45
|
+
jsonPlugin(),
|
|
46
|
+
nodeResolvePlugin({
|
|
47
|
+
// mainFields: dts ? ['types', 'typings'] : ['module', 'main'],
|
|
48
|
+
// extensions: dts ? ['.d.ts', '.ts'] : ['.js', '.json', '.mjs'],
|
|
49
|
+
// customResolveOptions: {
|
|
50
|
+
// moduleDirectories: dts
|
|
51
|
+
// ? ['node_modules/@types', 'node_modules']
|
|
52
|
+
// : ['node_modules'],
|
|
53
|
+
// },
|
|
54
|
+
|
|
55
|
+
// modulesOnly: true
|
|
56
|
+
}),
|
|
57
|
+
!dts && commonjsPlugin(),
|
|
58
|
+
!dts &&
|
|
59
|
+
esbuildPlugin({
|
|
60
|
+
target: 'es2022',
|
|
61
|
+
}),
|
|
62
|
+
dts && dtsPlugin(),
|
|
63
|
+
copyPlugin({
|
|
64
|
+
targets: [
|
|
65
|
+
{ src: 'src/deploy', dest: 'dist' },
|
|
66
|
+
{ src: 'src/.release', dest: 'dist' },
|
|
67
|
+
]
|
|
68
|
+
})
|
|
69
|
+
].filter(Boolean),
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export default [
|
|
74
|
+
createConfig(),
|
|
75
|
+
// createConfig({ dts: true }),
|
|
76
|
+
createConfig({ esm: true }),
|
|
77
|
+
]
|
|
78
|
+
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# .release 目录说明
|
|
2
|
+
|
|
3
|
+
## doc 目录
|
|
4
|
+
|
|
5
|
+
用于release时附带的文档,文档中可以使用指定变量占位,后续执行release-helper release时会自动遍历,并替换所有变量
|
|
6
|
+
|
|
7
|
+
可使用变量及说明:
|
|
8
|
+
|
|
9
|
+
| **占位符** | **含义** |
|
|
10
|
+
| ------------- | ---------------------------------------------------- |
|
|
11
|
+
| \$APP\_NAME\$ | 应用名称(自动读取package.json中name字段值) |
|
|
12
|
+
| \$VERSION\$ | 用户指定的版本号 |
|
|
13
|
+
| \$PKG_NAME\$ | tar.gz包名称(值为\$APP_NAME\$\_\$VERSION\$.tar.gz) |
|
|
14
|
+
|
|
15
|
+
## nginx 目录
|
|
16
|
+
|
|
17
|
+
用于存放服务器端nginx的启动配置文件等,部分占位说明及占位符请勿删除,会在服务器端安装后更新时,由脚本自动替换
|
|
18
|
+
|
|
19
|
+
文件说明:
|
|
20
|
+
| **文件或目录** | **说明** |
|
|
21
|
+
| -------------- | --------------------------------------------------------- |
|
|
22
|
+
| ca | nginx https使用到的证书,变更时需对应修改nginx.conf中的值 |
|
|
23
|
+
| nginx.conf | nginx启动时 需要的配置文件 |
|