@peiyanlu/release 1.3.0 → 2.0.0
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/README.md +138 -356
- package/dist/action.mjs +1036 -0
- package/dist/cli.mjs +878 -1411
- package/dist/index.d.mts +850 -253
- package/dist/index.mjs +2848 -149
- package/package.json +27 -12
- package/template/release.config.hbs +29 -0
- package/template/release.text +82 -0
package/dist/cli.mjs
CHANGED
|
@@ -1,1487 +1,954 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
1
|
+
import path, { join, resolve } from "node:path";
|
|
2
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
3
|
+
import { newline } from "@conventional-changelog/template";
|
|
4
|
+
import { gitAdd, gitAddSync, gitBranch, gitBranchCurrent, gitCheckoutBranch, gitCheckoutBranchForce, gitCommitMessage, gitConfigGet, gitConfigSet, gitFetch, gitLsRemote, gitPush, gitPushBranch, gitRevParse, gitTag, isGitRepo, isPrerelease, isValidVersion, isWorkingTreeClean } from "@peiyanlu/cli-utils";
|
|
5
|
+
import { readJsonFileSync } from "@peiyanlu/node-utils";
|
|
6
|
+
import { assert, equals, isString, isUndefined, omit, throwError } from "@peiyanlu/ts-utils";
|
|
6
7
|
import { program } from "commander";
|
|
7
|
-
import
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import { inc,
|
|
13
|
-
import
|
|
14
|
-
import
|
|
15
|
-
import
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
import { Octokit } from "@octokit/rest";
|
|
21
|
-
import { contentType } from "mime-types";
|
|
22
|
-
import open from "open";
|
|
23
|
-
import { glob, globSync } from "tinyglobby";
|
|
24
|
-
import { parse } from "yaml";
|
|
25
|
-
//#region package.json
|
|
26
|
-
var name = "@peiyanlu/release";
|
|
27
|
-
var version = "1.2.1";
|
|
28
|
-
var description = "Release project tool.";
|
|
8
|
+
import Handlebars from "handlebars";
|
|
9
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
10
|
+
import { mkdir } from "node:fs/promises";
|
|
11
|
+
import { cancel, confirm, group, intro, isCancel, log, note, outro, select, text } from "@clack/prompts";
|
|
12
|
+
import { blue, blueBright, cyan, cyanBright, dim, green, greenBright, magenta, magentaBright, red, redBright, yellow } from "ansis";
|
|
13
|
+
import { inc, lt } from "semver";
|
|
14
|
+
import "conventional-changelog";
|
|
15
|
+
import "conventional-changelog-conventionalcommits";
|
|
16
|
+
import "conventional-recommended-bump";
|
|
17
|
+
//#region node_modules/.pnpm/tsdown@0.23.1_publint@0.3.22_typescript@6.0.3/node_modules/tsdown/esm-shims.js
|
|
18
|
+
const getFilename = () => fileURLToPath(import.meta.url);
|
|
19
|
+
const getDirname = () => path.dirname(getFilename());
|
|
20
|
+
const __dirname = /* @__PURE__ */ getDirname();
|
|
29
21
|
//#endregion
|
|
30
|
-
//#region
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
22
|
+
//#region package.json
|
|
23
|
+
var package_default = {
|
|
24
|
+
name: "@peiyanlu/release",
|
|
25
|
+
version: "2.0.0",
|
|
26
|
+
description: "Tool to automate version bumps, changelogs, and releases.",
|
|
27
|
+
license: "MIT",
|
|
28
|
+
type: "module",
|
|
29
|
+
"private": false,
|
|
30
|
+
bin: { "release": "index.js" },
|
|
31
|
+
exports: {
|
|
32
|
+
".": {
|
|
33
|
+
"types": "./dist/index.d.mts",
|
|
34
|
+
"default": "./dist/index.mjs"
|
|
35
|
+
},
|
|
36
|
+
"./package.json": "./package.json"
|
|
37
|
+
},
|
|
38
|
+
scripts: {
|
|
39
|
+
"dev": "tsdown -w",
|
|
40
|
+
"build": "tsdown",
|
|
41
|
+
"prepublishOnly": "tsdown",
|
|
42
|
+
"release": "node index.js",
|
|
43
|
+
"release:dry": "node index.js -C -n",
|
|
44
|
+
"test": "vitest run",
|
|
45
|
+
"test:cov": "vitest run --coverage"
|
|
46
|
+
},
|
|
47
|
+
dependencies: {
|
|
48
|
+
"@actions/core": "^3.0.1",
|
|
49
|
+
"@actions/github": "^9.1.1",
|
|
50
|
+
"@clack/prompts": "^1.7.0",
|
|
51
|
+
"@conventional-changelog/template": "^1.2.1",
|
|
52
|
+
"@octokit/rest": "^22.0.1",
|
|
53
|
+
"@peiyanlu/cli-utils": "^0.0.15",
|
|
54
|
+
"@peiyanlu/node-utils": "^0.0.4",
|
|
55
|
+
"@peiyanlu/ts-utils": "^0.0.12",
|
|
56
|
+
"ansis": "^4.3.1",
|
|
57
|
+
"commander": "^15.0.0",
|
|
58
|
+
"conventional-changelog": "^8.1.0",
|
|
59
|
+
"conventional-changelog-conventionalcommits": "^10.2.1",
|
|
60
|
+
"conventional-recommended-bump": "^12.1.0",
|
|
61
|
+
"open": "^11.0.0",
|
|
62
|
+
"publint": "^0.3.22",
|
|
63
|
+
"semver": "^7.8.5",
|
|
64
|
+
"tinyglobby": "^0.2.17",
|
|
65
|
+
"yaml": "^2.9.0",
|
|
66
|
+
"handlebars": "^4.7.9"
|
|
67
|
+
},
|
|
68
|
+
devDependencies: {
|
|
69
|
+
"@peiyanlu/test-tools": "^0.0.7",
|
|
70
|
+
"@types/node": "^24.13.3",
|
|
71
|
+
"@types/semver": "^7.7.1",
|
|
72
|
+
"@vitest/coverage-v8": "^4.1.10",
|
|
73
|
+
"tsdown": "^0.23.1",
|
|
74
|
+
"typescript": "^6.0.3",
|
|
75
|
+
"vitest": "^4.1.10"
|
|
76
|
+
},
|
|
77
|
+
author: {
|
|
78
|
+
"name": "YanluPei",
|
|
79
|
+
"email": "peiyanlu@vip.qq.com"
|
|
80
|
+
},
|
|
81
|
+
engines: { "node": ">=20" },
|
|
82
|
+
keywords: ["typescript", "cli"],
|
|
83
|
+
files: ["dist", "template"],
|
|
84
|
+
publishConfig: {
|
|
85
|
+
"access": "public",
|
|
86
|
+
"registry": "https://registry.npmjs.org"
|
|
87
|
+
},
|
|
88
|
+
repository: {
|
|
89
|
+
"type": "git",
|
|
90
|
+
"url": "git+https://github.com/peiyanlu/release.git"
|
|
91
|
+
},
|
|
92
|
+
bugs: { "url": "https://github.com/peiyanlu/release/issues" },
|
|
93
|
+
homepage: "https://github.com/peiyanlu/release#readme"
|
|
41
94
|
};
|
|
42
|
-
|
|
95
|
+
//#endregion
|
|
96
|
+
//#region src/config/index.ts
|
|
97
|
+
const loadJavaScriptFile = async (file) => {
|
|
43
98
|
const { href } = pathToFileURL(file);
|
|
44
99
|
const mod = await import(href);
|
|
45
|
-
const temp = mod.default
|
|
100
|
+
const temp = "default" in mod ? mod.default : mod;
|
|
46
101
|
return typeof temp === "function" ? temp() : temp;
|
|
47
102
|
};
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
103
|
+
const findFile = (cwd, filename, extensions) => {
|
|
104
|
+
for (const ext of extensions) {
|
|
105
|
+
const path = resolve(cwd, `${filename}.${ext}`);
|
|
106
|
+
if (existsSync(path)) return path;
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
const loadConfig = async (cwd = process.cwd()) => {
|
|
110
|
+
const file = findFile(cwd, "release.config", [
|
|
111
|
+
"ts",
|
|
112
|
+
"mts",
|
|
113
|
+
"js",
|
|
114
|
+
"mjs"
|
|
115
|
+
]);
|
|
116
|
+
if (!file) return {
|
|
117
|
+
file: "",
|
|
52
118
|
config: {}
|
|
53
119
|
};
|
|
54
120
|
return {
|
|
55
|
-
|
|
56
|
-
config: await
|
|
121
|
+
file,
|
|
122
|
+
config: await loadJavaScriptFile(file)
|
|
57
123
|
};
|
|
58
124
|
};
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
125
|
+
//#endregion
|
|
126
|
+
//#region src/core/logger.ts
|
|
127
|
+
const defaultPrinter = ({ dryRun, stage, message }) => {
|
|
128
|
+
console.log(`${dryRun ? `[dry-run]` : ""}[${stage}]:`, message);
|
|
129
|
+
};
|
|
130
|
+
/** 一个子消息记录器,绑定到特定阶段 */
|
|
131
|
+
var ChildLogger = class {
|
|
132
|
+
parent;
|
|
133
|
+
stage;
|
|
134
|
+
/**
|
|
135
|
+
* 创建子日志记录器。
|
|
136
|
+
* @param parent - 要继承其选项的父日志记录器。
|
|
137
|
+
* @param stage - 此日志记录器所属的处理阶段。
|
|
138
|
+
*/
|
|
139
|
+
constructor(parent, stage) {
|
|
140
|
+
this.parent = parent;
|
|
141
|
+
this.stage = stage;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* 记录一条普通信息。
|
|
145
|
+
* @param message - 要记录的信息。
|
|
146
|
+
*/
|
|
147
|
+
info(message) {
|
|
148
|
+
this.parent.info(this.stage, message);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* 记录一条详细信息。
|
|
152
|
+
* @param message - 要记录的信息。
|
|
153
|
+
*/
|
|
154
|
+
verbose(message) {
|
|
155
|
+
this.parent.verbose(this.stage, message);
|
|
70
156
|
}
|
|
71
|
-
return result;
|
|
72
157
|
};
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
CHECKING: "Checking npm registry",
|
|
96
|
-
CHECKED: (registry, msg) => `NPM registry: ${underline(dim(registry))} ${dim(msg)}`
|
|
97
|
-
},
|
|
98
|
-
GITHUB: {
|
|
99
|
-
CHECKING: "Checking github repository",
|
|
100
|
-
CHECKED: (repository) => `GitHub repository: ${underline(dim(repository))}`
|
|
158
|
+
/** 消息记录器 */
|
|
159
|
+
var Logger = class {
|
|
160
|
+
options;
|
|
161
|
+
printer;
|
|
162
|
+
colors;
|
|
163
|
+
colorMap = /* @__PURE__ */ new Map();
|
|
164
|
+
constructor(options = {}) {
|
|
165
|
+
this.options = options;
|
|
166
|
+
const { colors = [], printer = defaultPrinter } = options;
|
|
167
|
+
this.colors = colors;
|
|
168
|
+
this.printer = printer;
|
|
169
|
+
}
|
|
170
|
+
info(stage, message) {
|
|
171
|
+
const { silent, dryRun } = this.options;
|
|
172
|
+
if (!silent) {
|
|
173
|
+
const color = this.getColor(stage);
|
|
174
|
+
this.printer({
|
|
175
|
+
dryRun,
|
|
176
|
+
stage,
|
|
177
|
+
message,
|
|
178
|
+
color
|
|
179
|
+
});
|
|
101
180
|
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
START: "Publishing npm package",
|
|
114
|
-
END: (url) => url ? `Published npm package: ${underline(dim(url))}` : "NPM publish skipped"
|
|
115
|
-
},
|
|
116
|
-
GIT: {
|
|
117
|
-
START: "Creating git commit, tag & push",
|
|
118
|
-
END: (push) => push ? "Git commit, tag & push completed" : "Git commit, tag & push skipped"
|
|
119
|
-
},
|
|
120
|
-
GITHUB: {
|
|
121
|
-
START: "Creating GitHub release",
|
|
122
|
-
END: (url) => url ? `Created GitHub release: ${underline(dim(url))}` : "GitHub release skipped"
|
|
181
|
+
}
|
|
182
|
+
verbose(stage, message) {
|
|
183
|
+
const { silent, verbose, dryRun } = this.options;
|
|
184
|
+
if (!silent && (verbose || dryRun)) {
|
|
185
|
+
const color = this.getColor(stage);
|
|
186
|
+
this.printer({
|
|
187
|
+
dryRun,
|
|
188
|
+
stage,
|
|
189
|
+
message,
|
|
190
|
+
color
|
|
191
|
+
});
|
|
123
192
|
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
},
|
|
136
|
-
ERROR: {
|
|
137
|
-
GIT_WORKDIR: `[git] Working tree must be clean`,
|
|
138
|
-
GIT_REGISTRY: (name) => `[git] Repository not found for package "${dim(name)}"`,
|
|
139
|
-
GIT_REMOTE: (name) => `[git] Remote "${dim(name)}" not found`,
|
|
140
|
-
GITHUB_TAG_EXIT: (tag) => `[github] Release for tag "${tag}" already exists and is not a draft`,
|
|
141
|
-
GITHUB_TOKEN: (tokenRef) => `[github] Missing GitHub token. Please set ${yellow(tokenRef ?? "GITHUB_TOKEN")}`,
|
|
142
|
-
GITHUB_AUTH: "[github] Invalid GitHub token or insufficient permissions",
|
|
143
|
-
GITHUB_USER: "[github] User does not have permission to create releases",
|
|
144
|
-
NPM_REGISTRY: (registry) => `[npm] Unable to reach npm registry ${underline(dim(registry))}`,
|
|
145
|
-
NPM_AUTH: `[npm] Not authenticated with npm. Please run ${yellow("npm login")} and try again`,
|
|
146
|
-
NPM_USER: (user, name) => `[npm] User ${dim(user)} is not a collaborator of ${dim(name)}`
|
|
147
|
-
},
|
|
148
|
-
ABORT: {
|
|
149
|
-
CANCEL: "Operation cancelled",
|
|
150
|
-
MONOREPO_CI_NO_PACKAGE: "CI mode requires a target package in monorepo. Please specify it via \"--package <pkg>\"",
|
|
151
|
-
MONOREPO_NO_PACKAGES: "Monorepo detected, but no packages found. Please configure \"config.packages\""
|
|
152
|
-
},
|
|
153
|
-
INFO: {
|
|
154
|
-
TOOL: (name, version) => `${name} ${dim(`v${version}`)}`,
|
|
155
|
-
CONFIG: (path) => `config file: ${underline(dim(path || "not found"))}`
|
|
193
|
+
}
|
|
194
|
+
createChild(stage) {
|
|
195
|
+
return new ChildLogger(this, stage);
|
|
196
|
+
}
|
|
197
|
+
getColor(stage) {
|
|
198
|
+
const cached = this.colorMap.get(stage);
|
|
199
|
+
if (cached) return cached;
|
|
200
|
+
const index = this.colorMap.size % this.colors.length;
|
|
201
|
+
const color = this.colors[index] ?? ((text) => text);
|
|
202
|
+
this.colorMap.set(stage, color);
|
|
203
|
+
return color;
|
|
156
204
|
}
|
|
157
205
|
};
|
|
158
206
|
//#endregion
|
|
159
|
-
//#region src/
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
207
|
+
//#region src/core/utils.ts
|
|
208
|
+
const when = (condition, ...value) => condition ? [...value] : [];
|
|
209
|
+
//#endregion
|
|
210
|
+
//#region src/core/releaser.ts
|
|
211
|
+
var Releaser = class {
|
|
212
|
+
options;
|
|
213
|
+
project;
|
|
214
|
+
hosting;
|
|
215
|
+
logger;
|
|
216
|
+
stepsOptions = {};
|
|
217
|
+
skippedSteps = {};
|
|
218
|
+
state = {};
|
|
219
|
+
queue = [];
|
|
220
|
+
constructor(options) {
|
|
221
|
+
this.options = options;
|
|
222
|
+
const { project, hosting, logger, ...log } = options;
|
|
223
|
+
this.project = project;
|
|
224
|
+
this.hosting = hosting;
|
|
225
|
+
this.logger = logger ?? new Logger(log);
|
|
167
226
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
if (!remote || !remoteUrl) {
|
|
172
|
-
if (requireRemote) throw new Error(MSG.ERROR.GIT_REMOTE(remoteName));
|
|
227
|
+
enqueue(fn) {
|
|
228
|
+
this.queue.push(fn);
|
|
229
|
+
return this;
|
|
173
230
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
};
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
if (!commit) return;
|
|
183
|
-
const args = dryRun ? ["--dry-run"] : [];
|
|
184
|
-
addUntrackedFiles ? await gitAddAll(args) : await gitAddTracked(args);
|
|
185
|
-
await gitCommitMessage(commitMessage, [...commitArgs, ...args]);
|
|
186
|
-
if (!dryRun) Object.assign(ctx.git, { isCommitted: true });
|
|
187
|
-
if (tag) {
|
|
188
|
-
await gitTagAnnotated(currentTag, tagMessage, [...tagArgs, ...args]);
|
|
189
|
-
if (!dryRun) Object.assign(ctx.git, { isTagged: true });
|
|
231
|
+
setOptions(options) {
|
|
232
|
+
const logger = this.logger.createChild("set-options");
|
|
233
|
+
logger.verbose("Setting options for steps:");
|
|
234
|
+
logger.verbose(options);
|
|
235
|
+
for (const [key, value] of Object.entries(options)) Object.assign(this.stepsOptions[key] ??= {}, value);
|
|
236
|
+
logger.verbose("Final options for steps:");
|
|
237
|
+
logger.verbose(this.stepsOptions);
|
|
238
|
+
return this;
|
|
190
239
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
240
|
+
setUser(options) {
|
|
241
|
+
return this.enqueue(async () => {
|
|
242
|
+
const { options: { dryRun }, stepsOptions: { setUser } } = this;
|
|
243
|
+
const { username, email } = {
|
|
244
|
+
...setUser,
|
|
245
|
+
...options
|
|
246
|
+
};
|
|
247
|
+
const logger = this.logger.createChild("set-user");
|
|
248
|
+
if (this.shouldSkip("setUser")) {
|
|
249
|
+
logger.info("Skipping set git user.");
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
logger.info("Setting git user...");
|
|
253
|
+
if (!username && !email) {
|
|
254
|
+
logger.info("No git user configuration provided.");
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
if (username) {
|
|
258
|
+
logger.verbose(`Setting git user.name to "${username}".`);
|
|
259
|
+
if (!dryRun) await gitConfigSet("user.name", username);
|
|
260
|
+
}
|
|
261
|
+
if (email) {
|
|
262
|
+
logger.verbose(`Setting git user.email to "${email}".`);
|
|
263
|
+
if (!dryRun) await gitConfigSet("user.email", email);
|
|
264
|
+
}
|
|
265
|
+
});
|
|
198
266
|
}
|
|
199
|
-
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
//#region src/utils.ts
|
|
208
|
-
const info = (msg) => {
|
|
209
|
-
console.log(`${rgb(33, 91, 184)(`i`)} ${msg}`);
|
|
210
|
-
};
|
|
211
|
-
const msg = (prefix, msg) => {
|
|
212
|
-
log.message(`${rgb(33, 91, 184)(`[${prefix}]`)} ${dim(msg)}`);
|
|
213
|
-
};
|
|
214
|
-
const question = (msg, type) => {
|
|
215
|
-
return `${{
|
|
216
|
-
package: yellow,
|
|
217
|
-
version: green,
|
|
218
|
-
git: rgb(220, 94, 62),
|
|
219
|
-
npm: rgb(186, 70, 61),
|
|
220
|
-
github: rgb(64, 110, 228)
|
|
221
|
-
}[type]`?`} ${msg}`;
|
|
222
|
-
};
|
|
223
|
-
const success = (msg, dryRun) => {
|
|
224
|
-
return `${(dryRun ? rgb(123, 115, 66) : green)("✔")} ${msg}`;
|
|
225
|
-
};
|
|
226
|
-
const runLifeCycleHook = async (hooks, key, dryRun) => {
|
|
227
|
-
const handler = hooks[key];
|
|
228
|
-
if (typeof handler === "function") {
|
|
229
|
-
if (!dryRun) await handler(log);
|
|
230
|
-
log.success(success(`${dim`run`} ${inspect(handler)}`, dryRun));
|
|
231
|
-
return;
|
|
267
|
+
setSkippedSteps(options) {
|
|
268
|
+
const logger = this.logger.createChild("set-skipped-steps");
|
|
269
|
+
logger.verbose("Setting skipped for steps:");
|
|
270
|
+
logger.verbose(options);
|
|
271
|
+
Object.assign(this.skippedSteps, options);
|
|
272
|
+
logger.verbose("Final skipped for steps:");
|
|
273
|
+
logger.verbose(this.skippedSteps);
|
|
274
|
+
return this;
|
|
232
275
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
}
|
|
266
|
-
const abortGroupPrompt = (ctx) => {
|
|
267
|
-
ctx && gitRollback(ctx);
|
|
268
|
-
abortTask(MSG.ABORT.CANCEL);
|
|
269
|
-
};
|
|
270
|
-
const abortOnError = (err, ctx, exit = true) => {
|
|
271
|
-
ctx && gitRollback(ctx);
|
|
272
|
-
const msg = err.message.replace(/^\[(github|npm|git)]/, (a) => red(a));
|
|
273
|
-
log.message(`\n${msg}\n`);
|
|
274
|
-
exit && process.exit(1);
|
|
275
|
-
};
|
|
276
|
-
const taskEnd = (msg) => {
|
|
277
|
-
outro(msg);
|
|
278
|
-
process.exit(0);
|
|
279
|
-
};
|
|
280
|
-
//#endregion
|
|
281
|
-
//#region src/version/prompts.ts
|
|
282
|
-
const createVersionMap = (current) => ({
|
|
283
|
-
patch: inc(current, "patch"),
|
|
284
|
-
minor: inc(current, "minor"),
|
|
285
|
-
major: inc(current, "major"),
|
|
286
|
-
release: inc(current, "release"),
|
|
287
|
-
prerelease: inc(current, "prerelease"),
|
|
288
|
-
prepatch: inc(current, "prepatch", "beta"),
|
|
289
|
-
preminor: inc(current, "preminor", "beta"),
|
|
290
|
-
premajor: inc(current, "premajor", "beta")
|
|
291
|
-
});
|
|
292
|
-
const option = (label, value, hint) => ({
|
|
293
|
-
label,
|
|
294
|
-
value,
|
|
295
|
-
hint: hint ?? value
|
|
296
|
-
});
|
|
297
|
-
const runVersionPrompts = async (ctx, config, inferred) => {
|
|
298
|
-
const { pkg: { current, fromPreRelease } } = ctx;
|
|
299
|
-
const selectVersion = async () => {
|
|
300
|
-
const versions = createVersionMap(current);
|
|
301
|
-
const options = [
|
|
302
|
-
...fromPreRelease ? [option("Pre-Release", "prerelease", versions.prerelease)] : [],
|
|
303
|
-
option("Patch", "patch", versions.patch),
|
|
304
|
-
option("Minor", "minor", versions.minor),
|
|
305
|
-
option("Major", "major", versions.major),
|
|
306
|
-
...!fromPreRelease ? [
|
|
307
|
-
option("Pre-Patch", "prepatch", versions.prepatch),
|
|
308
|
-
option("Pre-Minor", "preminor", versions.preminor),
|
|
309
|
-
option("Pre-Major", "premajor", versions.premajor)
|
|
310
|
-
] : [],
|
|
311
|
-
option("As-Is", "ignore", current),
|
|
312
|
-
option("Custom", "custom", "custom specified")
|
|
313
|
-
];
|
|
314
|
-
const type = await select({
|
|
315
|
-
message: question(MSG.PROMPT.SELECT_VERSION, "version"),
|
|
316
|
-
options,
|
|
317
|
-
initialValue: inferred ?? (fromPreRelease ? "prerelease" : "patch")
|
|
276
|
+
checkout(options) {
|
|
277
|
+
return this.enqueue(async () => {
|
|
278
|
+
const { options: { dryRun }, stepsOptions: { checkout } } = this;
|
|
279
|
+
const { branch: headBranch = "release-branch", fetch, force } = {
|
|
280
|
+
...checkout,
|
|
281
|
+
...options
|
|
282
|
+
};
|
|
283
|
+
const logger = this.logger.createChild("checkout");
|
|
284
|
+
if (this.shouldSkip("checkout")) {
|
|
285
|
+
logger.info("Skipping checkout.");
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
logger.info(`Checking out branch ${headBranch}...`);
|
|
289
|
+
this.state.headBranch = headBranch;
|
|
290
|
+
this.state.baseBranch = await this.getBaseBranch();
|
|
291
|
+
if (fetch) {
|
|
292
|
+
logger.verbose(`Fetching all commits and tags from the remote repository...`);
|
|
293
|
+
if (!dryRun) await gitFetch([
|
|
294
|
+
"--prune",
|
|
295
|
+
"--unshallow",
|
|
296
|
+
"--tags",
|
|
297
|
+
"--",
|
|
298
|
+
"origin",
|
|
299
|
+
"HEAD"
|
|
300
|
+
]);
|
|
301
|
+
}
|
|
302
|
+
if (force) {
|
|
303
|
+
logger.verbose(`Force checking out branch ${headBranch}...`);
|
|
304
|
+
if (!dryRun) await gitCheckoutBranchForce(headBranch);
|
|
305
|
+
} else {
|
|
306
|
+
logger.verbose(`Checking out branch ${headBranch}...`);
|
|
307
|
+
if (!dryRun) await gitCheckoutBranch(headBranch);
|
|
308
|
+
}
|
|
318
309
|
});
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
if (isUndefined(val) || !isValidVersion(val)) return "Invalid version";
|
|
310
|
+
}
|
|
311
|
+
bump(options) {
|
|
312
|
+
return this.enqueue(async () => {
|
|
313
|
+
const { project, options: { dryRun }, stepsOptions: { bump } } = this;
|
|
314
|
+
const logger = this.logger.createChild("bump");
|
|
315
|
+
if (this.shouldSkip("bump")) {
|
|
316
|
+
logger.info("Skipping bump.");
|
|
317
|
+
return;
|
|
328
318
|
}
|
|
319
|
+
logger.info("Bumping version...");
|
|
320
|
+
const done = await project.bump({
|
|
321
|
+
dryRun,
|
|
322
|
+
logger,
|
|
323
|
+
...bump,
|
|
324
|
+
...options
|
|
325
|
+
});
|
|
326
|
+
this.state.bump = done;
|
|
327
|
+
if (!done) logger.info("No version changes detected.");
|
|
329
328
|
});
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
const createDefaultContext = () => {
|
|
343
|
-
return {
|
|
344
|
-
isCI: false,
|
|
345
|
-
release: false,
|
|
346
|
-
showRelease: false,
|
|
347
|
-
showChangelog: false,
|
|
348
|
-
onlyChangelog: false,
|
|
349
|
-
dryRun: false,
|
|
350
|
-
noGit: false,
|
|
351
|
-
noNpm: false,
|
|
352
|
-
noGitHub: false,
|
|
353
|
-
increment: "",
|
|
354
|
-
isIncrement: false,
|
|
355
|
-
configFileExists: false,
|
|
356
|
-
selectedPkg: "",
|
|
357
|
-
pkg: {
|
|
358
|
-
current: "",
|
|
359
|
-
next: "",
|
|
360
|
-
name: "",
|
|
361
|
-
isPrivate: false,
|
|
362
|
-
fromPreRelease: false,
|
|
363
|
-
toPreRelease: false,
|
|
364
|
-
publishConfig: {
|
|
365
|
-
access: "public",
|
|
366
|
-
registry: "https://registry.npmjs.org"
|
|
367
|
-
},
|
|
368
|
-
preId: "beta",
|
|
369
|
-
preBase: "0",
|
|
370
|
-
scope: void 0
|
|
371
|
-
},
|
|
372
|
-
npm: {
|
|
373
|
-
username: "",
|
|
374
|
-
otp: "",
|
|
375
|
-
tag: "latest"
|
|
376
|
-
},
|
|
377
|
-
git: {
|
|
378
|
-
remoteName: "origin",
|
|
379
|
-
remoteUrl: "",
|
|
380
|
-
latestTag: "",
|
|
381
|
-
previousTag: "",
|
|
382
|
-
currentTag: "",
|
|
383
|
-
isCommitted: false,
|
|
384
|
-
isTagged: false,
|
|
385
|
-
isPushed: false,
|
|
386
|
-
commitMessage: "",
|
|
387
|
-
tagMessage: ""
|
|
388
|
-
},
|
|
389
|
-
github: {
|
|
390
|
-
url: "",
|
|
391
|
-
username: "",
|
|
392
|
-
repo: "",
|
|
393
|
-
changelog: "",
|
|
394
|
-
owner: "",
|
|
395
|
-
isWeb: false,
|
|
396
|
-
isReleased: false,
|
|
397
|
-
releaseId: 0,
|
|
398
|
-
releaseUrl: "",
|
|
399
|
-
uploadUrl: "",
|
|
400
|
-
discussionUrl: "",
|
|
401
|
-
token: "",
|
|
402
|
-
releaseName: ""
|
|
403
|
-
}
|
|
404
|
-
};
|
|
405
|
-
};
|
|
406
|
-
const createDefaultConfig = (isCI) => {
|
|
407
|
-
return {
|
|
408
|
-
hooks: {
|
|
409
|
-
"before:bump": "",
|
|
410
|
-
"after:bump": "",
|
|
411
|
-
"before:publish": "",
|
|
412
|
-
"after:publish": "",
|
|
413
|
-
"before:push": "",
|
|
414
|
-
"after:push": "",
|
|
415
|
-
"before:release": "",
|
|
416
|
-
"after:release": ""
|
|
417
|
-
},
|
|
418
|
-
isMonorepo: false,
|
|
419
|
-
packages: [],
|
|
420
|
-
getPkgDir(pkg) {
|
|
421
|
-
return ".";
|
|
422
|
-
},
|
|
423
|
-
toTag(pkg, version) {
|
|
424
|
-
return `v${version}`;
|
|
425
|
-
},
|
|
426
|
-
changelog: {
|
|
427
|
-
tagPrefix(pkg) {
|
|
428
|
-
return "";
|
|
429
|
-
},
|
|
430
|
-
releaseCount: 1,
|
|
431
|
-
includeHidden: false,
|
|
432
|
-
transformTypes: (t) => t
|
|
433
|
-
},
|
|
434
|
-
skipGit: false,
|
|
435
|
-
skipNpm: false,
|
|
436
|
-
skipGithub: false,
|
|
437
|
-
git: {
|
|
438
|
-
commit: isCI,
|
|
439
|
-
tag: isCI,
|
|
440
|
-
push: isCI,
|
|
441
|
-
commitMessage: "chore(release): ${tag}",
|
|
442
|
-
commitArgs: [],
|
|
443
|
-
tagMessage: "${tag}",
|
|
444
|
-
tagArgs: [],
|
|
445
|
-
pushArgs: [],
|
|
446
|
-
addUntrackedFiles: false,
|
|
447
|
-
requireRemote: true,
|
|
448
|
-
requireRepository: true,
|
|
449
|
-
requireCleanWorkingTree: true
|
|
450
|
-
},
|
|
451
|
-
npm: {
|
|
452
|
-
publish: isCI,
|
|
453
|
-
publishArgs: [],
|
|
454
|
-
skipChecks: false,
|
|
455
|
-
cleanup: {
|
|
456
|
-
packageJson: true,
|
|
457
|
-
readme: false,
|
|
458
|
-
removeTempDir: true
|
|
329
|
+
}
|
|
330
|
+
commit(options) {
|
|
331
|
+
return this.enqueue(async () => {
|
|
332
|
+
const { project, options: { dryRun }, state, stepsOptions: { commit } } = this;
|
|
333
|
+
const { sign, allowEmpty } = {
|
|
334
|
+
...commit,
|
|
335
|
+
...options
|
|
336
|
+
};
|
|
337
|
+
const logger = this.logger.createChild("commit");
|
|
338
|
+
if (this.shouldSkip("commit")) {
|
|
339
|
+
logger.info("Skipping commit.");
|
|
340
|
+
return;
|
|
459
341
|
}
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
342
|
+
logger.info(`Committing changes...`);
|
|
343
|
+
if (!state.bump) {
|
|
344
|
+
logger.info("No changes to commit.");
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
const files = project.changedFiles;
|
|
348
|
+
const message = await project.getCommitMessage();
|
|
349
|
+
logger.verbose(`Files to commit:`);
|
|
350
|
+
files.forEach((file) => {
|
|
351
|
+
logger.verbose(`- ${file}`);
|
|
352
|
+
});
|
|
353
|
+
logger.verbose(message.includes("\n") ? `Commit message:\n\n${message}` : `Commit message: ${message}`);
|
|
354
|
+
if (!dryRun) {
|
|
355
|
+
await gitAdd(["--", ...files]);
|
|
356
|
+
await gitCommitMessage(message, [
|
|
357
|
+
...when(sign, "--gpg-sign"),
|
|
358
|
+
...when(allowEmpty, "--allow-empty"),
|
|
359
|
+
"--no-verify"
|
|
360
|
+
]);
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
revert() {
|
|
365
|
+
return this.enqueue(async () => {
|
|
366
|
+
const { project, logger, options: { dryRun } } = this;
|
|
367
|
+
logger.info("revert", "Reverting version updates...");
|
|
368
|
+
if (!await project.revert({
|
|
369
|
+
dryRun,
|
|
370
|
+
logger: logger.createChild("revert")
|
|
371
|
+
})) {
|
|
372
|
+
logger.info("revert", "No version updates to revert.");
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
this.state.bump = false;
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
maintenanceBranch(options) {
|
|
379
|
+
return this.enqueue(async () => {
|
|
380
|
+
const { project, options: { dryRun }, stepsOptions: { maintenanceBranch } } = this;
|
|
381
|
+
const { enabled, force } = {
|
|
382
|
+
...maintenanceBranch,
|
|
383
|
+
...options
|
|
384
|
+
};
|
|
385
|
+
const logger = this.logger.createChild("maintenance-branch");
|
|
386
|
+
if (this.shouldSkip("maintenanceBranch")) {
|
|
387
|
+
logger.info("Skipping create maintenance branches.");
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
logger.info("Creating maintenance branches...");
|
|
391
|
+
if (!enabled) {
|
|
392
|
+
logger.info("Maintenance branch creation is disabled.");
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
logger.verbose("Fetching fresh tags from the remote repository...");
|
|
396
|
+
if (!dryRun) await gitFetch(["--tags"]);
|
|
397
|
+
const branches = await project.getMaintenanceBranches();
|
|
398
|
+
if (!branches.length) {
|
|
399
|
+
logger.info("No maintenance branches to create.");
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
logger.verbose("Branches to create:");
|
|
403
|
+
for (const { from, to } of branches) {
|
|
404
|
+
logger.verbose(`- ${to} from ${from}`);
|
|
405
|
+
if (dryRun) continue;
|
|
406
|
+
if ((await gitRevParse(["--verify", `refs/heads/${to}`]) || await gitLsRemote([
|
|
407
|
+
"--heads",
|
|
408
|
+
"origin",
|
|
409
|
+
to
|
|
410
|
+
])) && !force) {
|
|
411
|
+
logger.info(`Branch ${to} already exists, skipping.`);
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
const args = when(force, "--force");
|
|
415
|
+
await gitBranch([
|
|
416
|
+
...args,
|
|
417
|
+
"--",
|
|
418
|
+
to,
|
|
419
|
+
from
|
|
420
|
+
]);
|
|
421
|
+
await gitPushBranch("origin", to, ["--no-verify", ...args]);
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
tag(options) {
|
|
426
|
+
return this.enqueue(async () => {
|
|
427
|
+
const { project, options: { dryRun }, stepsOptions: { tag } } = this;
|
|
428
|
+
const { fetch, sign } = {
|
|
429
|
+
...tag,
|
|
430
|
+
...options
|
|
431
|
+
};
|
|
432
|
+
const logger = this.logger.createChild("tag");
|
|
433
|
+
if (this.shouldSkip("tag")) {
|
|
434
|
+
logger.info("Skipping tag.");
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
logger.info("Tagging version...");
|
|
438
|
+
if (fetch) {
|
|
439
|
+
logger.verbose("Fetching fresh tags from the remote repository...");
|
|
440
|
+
if (!dryRun) await gitFetch(["--tags"]);
|
|
441
|
+
}
|
|
442
|
+
const tags = await project.getTags();
|
|
443
|
+
this.state.tags = tags.length > 0;
|
|
444
|
+
if (!this.state.tags) {
|
|
445
|
+
logger.info("No version changes detected.");
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
logger.verbose(`Tags to create:`);
|
|
449
|
+
for (const tag of tags) {
|
|
450
|
+
logger.verbose(`- ${tag}`);
|
|
451
|
+
if (!dryRun) await gitTag([
|
|
452
|
+
sign ? "--sign" : "--annotate",
|
|
453
|
+
"--message",
|
|
454
|
+
tag,
|
|
455
|
+
tag
|
|
456
|
+
]);
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
push(options) {
|
|
461
|
+
return this.enqueue(async () => {
|
|
462
|
+
const { options: { dryRun }, state, stepsOptions: { push } } = this;
|
|
463
|
+
const { force } = {
|
|
464
|
+
...push,
|
|
465
|
+
...options
|
|
466
|
+
};
|
|
467
|
+
const logger = this.logger.createChild("push");
|
|
468
|
+
if (this.shouldSkip("push")) {
|
|
469
|
+
logger.info("Skipping push.");
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
const branch = await gitBranchCurrent();
|
|
473
|
+
logger.info(`Pushing changes to "${branch}"...`);
|
|
474
|
+
if (!state.bump && !state.tags) {
|
|
475
|
+
logger.info("Nothing to push.");
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
if (!dryRun) {
|
|
479
|
+
if (state.bump) await gitPushBranch("origin", branch, [...when(force, "--force"), "--no-verify"]);
|
|
480
|
+
if (state.tags) await gitPush([
|
|
481
|
+
"origin",
|
|
482
|
+
"--tags",
|
|
483
|
+
"--no-verify"
|
|
484
|
+
]);
|
|
485
|
+
}
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
publish(options) {
|
|
489
|
+
return this.enqueue(async () => {
|
|
490
|
+
const { project, options: { dryRun }, stepsOptions: { publish } } = this;
|
|
491
|
+
const logger = this.logger.createChild("publish");
|
|
492
|
+
if (this.shouldSkip("publish")) {
|
|
493
|
+
logger.info("Skipping publish.");
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
logger.info("Publishing...");
|
|
497
|
+
const tagsMap = await project.getDefaultPublishTag();
|
|
498
|
+
const name = await project.manifest.getName();
|
|
499
|
+
const tag = tagsMap.get(name);
|
|
500
|
+
await project.publish({
|
|
501
|
+
dryRun,
|
|
502
|
+
logger,
|
|
503
|
+
tag,
|
|
504
|
+
...publish,
|
|
505
|
+
...options
|
|
506
|
+
});
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
release(option) {
|
|
510
|
+
const { hosting } = this;
|
|
511
|
+
if (!hosting) throwError("Hosting service is not configured. Please set the hosting option.");
|
|
512
|
+
return this.enqueue(async () => {
|
|
513
|
+
const { project, options: { dryRun }, stepsOptions: { release } } = this;
|
|
514
|
+
const logger = this.logger.createChild("release");
|
|
515
|
+
if (this.shouldSkip("release")) {
|
|
516
|
+
logger.info("Skipping release.");
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
logger.info("Creating release...");
|
|
520
|
+
await hosting.createRelease({
|
|
521
|
+
project,
|
|
522
|
+
dryRun,
|
|
523
|
+
logger,
|
|
524
|
+
...release,
|
|
525
|
+
...option
|
|
526
|
+
});
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
pullRequest(option) {
|
|
530
|
+
const { hosting } = this;
|
|
531
|
+
if (!hosting) throwError("Hosting service is not configured. Please set the hosting option.");
|
|
532
|
+
return this.enqueue(async () => {
|
|
533
|
+
const { project, state, options: { dryRun }, stepsOptions: { pullRequest } } = this;
|
|
534
|
+
const logger = this.logger.createChild("pull-request");
|
|
535
|
+
if (this.shouldSkip("pullRequest")) {
|
|
536
|
+
logger.info("Skipping pull request.");
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
logger.info("Creating pull request...");
|
|
540
|
+
if (!state.bump) {
|
|
541
|
+
logger.info("No version changes detected, skipping pull request creation.");
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
if (!state.headBranch) {
|
|
545
|
+
logger.info("Head branch is not set, skipping pull request creation.");
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
await hosting.createPullRequest({
|
|
549
|
+
project,
|
|
550
|
+
dryRun,
|
|
551
|
+
logger,
|
|
552
|
+
from: state.headBranch,
|
|
553
|
+
to: await this.getBaseBranch(),
|
|
554
|
+
...pullRequest,
|
|
555
|
+
...option
|
|
556
|
+
});
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
async run(condition) {
|
|
560
|
+
let shouldRun = true;
|
|
561
|
+
if (condition) {
|
|
562
|
+
const logger = this.logger.createChild("run");
|
|
563
|
+
logger.info("Checking condition...");
|
|
564
|
+
shouldRun = await condition(this);
|
|
565
|
+
if (shouldRun) logger.info("Condition passed, running release steps...");
|
|
566
|
+
else logger.info("Condition failed, skipping release steps.");
|
|
471
567
|
}
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
const defaultTypes = [
|
|
477
|
-
{
|
|
478
|
-
type: "feat",
|
|
479
|
-
section: "✨ 新功能",
|
|
480
|
-
effect: "bump",
|
|
481
|
-
description: "新增功能"
|
|
482
|
-
},
|
|
483
|
-
{
|
|
484
|
-
type: "feature",
|
|
485
|
-
section: "✨ 新功能",
|
|
486
|
-
effect: "bump",
|
|
487
|
-
description: "新增功能"
|
|
488
|
-
},
|
|
489
|
-
{
|
|
490
|
-
type: "fix",
|
|
491
|
-
section: "🐛 Bug 修复",
|
|
492
|
-
effect: "bump",
|
|
493
|
-
description: "修复 bug"
|
|
494
|
-
},
|
|
495
|
-
{
|
|
496
|
-
type: "perf",
|
|
497
|
-
section: "⚡ 性能优化",
|
|
498
|
-
effect: "bump",
|
|
499
|
-
description: "提升性能"
|
|
500
|
-
},
|
|
501
|
-
{
|
|
502
|
-
type: "revert",
|
|
503
|
-
section: "⏪ 回退",
|
|
504
|
-
effect: "bump",
|
|
505
|
-
description: "回退到之前版本"
|
|
506
|
-
},
|
|
507
|
-
{
|
|
508
|
-
type: "docs",
|
|
509
|
-
section: "📝 文档",
|
|
510
|
-
effect: "changelog",
|
|
511
|
-
description: "文档更新"
|
|
512
|
-
},
|
|
513
|
-
{
|
|
514
|
-
type: "style",
|
|
515
|
-
section: "💄 样式调整",
|
|
516
|
-
effect: "hidden",
|
|
517
|
-
description: "样式或格式修改"
|
|
518
|
-
},
|
|
519
|
-
{
|
|
520
|
-
type: "chore",
|
|
521
|
-
section: "🧹 其他更新",
|
|
522
|
-
effect: "hidden",
|
|
523
|
-
description: "非功能性更改"
|
|
524
|
-
},
|
|
525
|
-
{
|
|
526
|
-
type: "refactor",
|
|
527
|
-
section: "♻️ 代码重构",
|
|
528
|
-
effect: "changelog",
|
|
529
|
-
description: "重构代码"
|
|
530
|
-
},
|
|
531
|
-
{
|
|
532
|
-
type: "test",
|
|
533
|
-
section: "🧪 测试",
|
|
534
|
-
effect: "hidden",
|
|
535
|
-
description: "测试更新"
|
|
536
|
-
},
|
|
537
|
-
{
|
|
538
|
-
type: "build",
|
|
539
|
-
section: "🏗️ 构建系统",
|
|
540
|
-
effect: "hidden",
|
|
541
|
-
description: "构建工具和系统修改"
|
|
542
|
-
},
|
|
543
|
-
{
|
|
544
|
-
type: "ci",
|
|
545
|
-
section: "🤖 CI",
|
|
546
|
-
effect: "hidden",
|
|
547
|
-
description: "持续集成配置"
|
|
548
|
-
},
|
|
549
|
-
{
|
|
550
|
-
type: "security",
|
|
551
|
-
section: "🛡️ 安全修复",
|
|
552
|
-
effect: "changelog",
|
|
553
|
-
description: "安全性更新"
|
|
554
|
-
},
|
|
555
|
-
{
|
|
556
|
-
type: "hotfix",
|
|
557
|
-
section: "🚑 热修复",
|
|
558
|
-
effect: "changelog",
|
|
559
|
-
description: "紧急修复"
|
|
560
|
-
},
|
|
561
|
-
{
|
|
562
|
-
type: "i18n",
|
|
563
|
-
section: "🌐 国际化",
|
|
564
|
-
effect: "changelog",
|
|
565
|
-
description: "国际化相关更新"
|
|
566
|
-
},
|
|
567
|
-
{
|
|
568
|
-
type: "ux",
|
|
569
|
-
section: "🎨 用户体验",
|
|
570
|
-
effect: "changelog",
|
|
571
|
-
description: "用户体验改进"
|
|
572
|
-
},
|
|
573
|
-
{
|
|
574
|
-
type: "deps",
|
|
575
|
-
section: "📦 依赖更新",
|
|
576
|
-
effect: "changelog",
|
|
577
|
-
description: "依赖版本变更"
|
|
578
|
-
},
|
|
579
|
-
{
|
|
580
|
-
type: "tool",
|
|
581
|
-
section: "🛠️ 工具链",
|
|
582
|
-
effect: "hidden",
|
|
583
|
-
description: "工具链变更"
|
|
584
|
-
},
|
|
585
|
-
{
|
|
586
|
-
type: "config",
|
|
587
|
-
section: "⚙️ 配置",
|
|
588
|
-
effect: "hidden",
|
|
589
|
-
description: "配置文件更新"
|
|
568
|
+
if (shouldRun) for (const fn of this.queue) await fn();
|
|
569
|
+
const logger = this.logger.createChild("done");
|
|
570
|
+
this.queue.length = 0;
|
|
571
|
+
logger.info(`Done!`);
|
|
590
572
|
}
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
return heading(2, isPatch ? small(fullTitle) : fullTitle);
|
|
599
|
-
}
|
|
600
|
-
function preamblePartial(contest) {
|
|
601
|
-
const { preamble, commitGroups, noteGroups } = contest;
|
|
602
|
-
return segments(preamble, isEmpty(preamble) && isEmpty(commitGroups) && isEmpty(noteGroups) && "Version bump without any changes.");
|
|
603
|
-
}
|
|
604
|
-
function template(context) {
|
|
605
|
-
const { headerPartial, preamblePartial, commitPartial, footerPartial, noteGroups, commitGroups } = context;
|
|
606
|
-
return segments(headerPartial(context), preamblePartial(context), each(noteGroups, (group) => segments(heading(3, words("⚠", group.title)), list(group.notes, (note) => {
|
|
607
|
-
return each([words(note.commit.scope && bold(`${note.commit.scope}:`), commitPartial(context, note.commit)), note.commit.subject.trim() !== note.text.trim() ? list([note.text], (t) => t) : ""], (t) => t, newline());
|
|
608
|
-
})), newline(2)), each(commitGroups, (group) => segments(group.title && heading(3, group.title), list(group.commits, (commit) => commitPartial(context, commit))), newline(2)), footerPartial(context));
|
|
609
|
-
}
|
|
610
|
-
//#endregion
|
|
611
|
-
//#region src/git/changelog.ts
|
|
612
|
-
const parsePreset = (options = {}) => {
|
|
613
|
-
const { includeHidden, transformTypes, scope } = options;
|
|
614
|
-
let types = [...defaultTypes];
|
|
615
|
-
if (includeHidden) types.forEach((type) => {
|
|
616
|
-
if (type.effect === "hidden") type.effect = "changelog";
|
|
617
|
-
});
|
|
618
|
-
types = transformTypes?.(types) ?? types;
|
|
619
|
-
const preset = createPreset({
|
|
620
|
-
types,
|
|
621
|
-
scope
|
|
622
|
-
});
|
|
623
|
-
preset.writer ??= {};
|
|
624
|
-
preset.writer.preamblePartial = preamblePartial;
|
|
625
|
-
preset.writer.headerPartial = headerPartial;
|
|
626
|
-
preset.writer.template = template;
|
|
627
|
-
return preset;
|
|
628
|
-
};
|
|
629
|
-
const inferReleaseType = async (options) => {
|
|
630
|
-
const preset = parsePreset(options);
|
|
631
|
-
const res = await new Bumper().config(preset).bump(preset.whatBump);
|
|
632
|
-
if (!("releaseType" in res)) return void 0;
|
|
633
|
-
const { releaseType, reason } = res;
|
|
634
|
-
return options?.json ? {
|
|
635
|
-
releaseType,
|
|
636
|
-
reason
|
|
637
|
-
} : releaseType;
|
|
638
|
-
};
|
|
639
|
-
const createGenerator = async (options) => {
|
|
640
|
-
const { getPkgDir, tagPrefix, releaseCount = 1, includeHidden, transformTypes, scope } = options;
|
|
641
|
-
const pkgDir = getPkgDir();
|
|
642
|
-
return new ConventionalChangelog().readPackage(`${pkgDir}/package.json`).config(parsePreset({
|
|
643
|
-
includeHidden,
|
|
644
|
-
transformTypes,
|
|
645
|
-
scope
|
|
646
|
-
})).options({ releaseCount }).commits({ path: pkgDir }).tags({ prefix: tagPrefix });
|
|
647
|
-
};
|
|
648
|
-
const getChangelog = async (options) => {
|
|
649
|
-
const generator = await createGenerator(options);
|
|
650
|
-
let changelog = "";
|
|
651
|
-
for await (const chunk of generator.write()) changelog += chunk;
|
|
652
|
-
return changelog.trimEnd();
|
|
653
|
-
};
|
|
654
|
-
const generateChangelog = async (options) => {
|
|
655
|
-
const { getPkgDir, releaseCount = 1 } = options;
|
|
656
|
-
const infile = join(getPkgDir(), "CHANGELOG.md");
|
|
657
|
-
if (!existsSync(infile)) await writeFile(infile, "");
|
|
658
|
-
const exist = readFileSync(infile, "utf-8");
|
|
659
|
-
const writeStream = createWriteStream(infile);
|
|
660
|
-
const generator = await createGenerator(options);
|
|
661
|
-
for await (const chunk of generator.write()) writeStream.write(chunk);
|
|
662
|
-
const override = isZero(releaseCount);
|
|
663
|
-
if (!(isZero(exist.trim().length) || override)) {
|
|
664
|
-
writeStream.write(eol());
|
|
665
|
-
writeStream.write(exist);
|
|
573
|
+
shouldSkip(step) {
|
|
574
|
+
return this.skippedSteps[step] === true;
|
|
575
|
+
}
|
|
576
|
+
async getBaseBranch() {
|
|
577
|
+
const { state: { baseBranch } } = this;
|
|
578
|
+
if (baseBranch) return baseBranch;
|
|
579
|
+
return gitBranchCurrent();
|
|
666
580
|
}
|
|
667
|
-
writeStream.end();
|
|
668
|
-
await finished(writeStream);
|
|
669
581
|
};
|
|
670
582
|
//#endregion
|
|
671
|
-
//#region src/
|
|
672
|
-
const
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
const
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
583
|
+
//#region src/releaser-cli/prompts.ts
|
|
584
|
+
const abortTask = (msg) => {
|
|
585
|
+
cancel(`${red("✕")} ${msg ?? "Release cancelled."}`);
|
|
586
|
+
process.exit(0);
|
|
587
|
+
};
|
|
588
|
+
const singlePrompt = async (fn, abortMsg) => {
|
|
589
|
+
const value = await fn();
|
|
590
|
+
if (isCancel(value)) abortTask(abortMsg);
|
|
591
|
+
return value;
|
|
592
|
+
};
|
|
593
|
+
const question = (msg, dryRun) => {
|
|
594
|
+
return `${(dryRun ? yellow : red)("?")} ${msg}`;
|
|
595
|
+
};
|
|
596
|
+
const getVersionHint = async (project, options = {}) => await project.getNextVersion(options) ?? "No version change";
|
|
597
|
+
const createVersionOptions = async (project, options = {}, current, tagPrefix) => {
|
|
598
|
+
const firstRelease = await project.isFirstRelease({ tagPrefix });
|
|
599
|
+
if (firstRelease) return [
|
|
600
|
+
{
|
|
601
|
+
label: "Initial release",
|
|
602
|
+
value: "initial",
|
|
603
|
+
hint: `Release ${current}`,
|
|
604
|
+
bump: {
|
|
605
|
+
version: current,
|
|
606
|
+
firstRelease
|
|
607
|
+
}
|
|
691
608
|
},
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
logs.push(yellow`git.push`);
|
|
697
|
-
return Promise.resolve(push);
|
|
698
|
-
})();
|
|
609
|
+
{
|
|
610
|
+
label: "Custom",
|
|
611
|
+
value: "custom",
|
|
612
|
+
hint: "Specify an exact version"
|
|
699
613
|
},
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
614
|
+
{
|
|
615
|
+
label: "As-is",
|
|
616
|
+
value: "ignore",
|
|
617
|
+
hint: `Do not update the version ${current}`,
|
|
618
|
+
bump: { version: current }
|
|
703
619
|
}
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
620
|
+
];
|
|
621
|
+
const fromPreRelease = isPrerelease(current);
|
|
622
|
+
const candidates = [
|
|
623
|
+
{
|
|
624
|
+
label: fromPreRelease ? "Release" : "Patch",
|
|
625
|
+
value: "patch",
|
|
626
|
+
hint: await getVersionHint(project, {
|
|
627
|
+
...options,
|
|
628
|
+
tagPrefix,
|
|
629
|
+
as: "patch"
|
|
630
|
+
}),
|
|
631
|
+
bump: { as: "patch" }
|
|
632
|
+
},
|
|
633
|
+
{
|
|
634
|
+
label: "Minor",
|
|
635
|
+
value: "minor",
|
|
636
|
+
hint: await getVersionHint(project, {
|
|
637
|
+
...options,
|
|
638
|
+
tagPrefix,
|
|
639
|
+
as: "minor"
|
|
640
|
+
}),
|
|
641
|
+
bump: { as: "minor" }
|
|
642
|
+
},
|
|
643
|
+
{
|
|
644
|
+
label: "Major",
|
|
645
|
+
value: "major",
|
|
646
|
+
hint: await getVersionHint(project, {
|
|
647
|
+
...options,
|
|
648
|
+
tagPrefix,
|
|
649
|
+
as: "major"
|
|
650
|
+
}),
|
|
651
|
+
bump: { as: "major" }
|
|
724
652
|
}
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
return body;
|
|
736
|
-
};
|
|
737
|
-
const getReleaseByTagSafe = async (octokit, options) => {
|
|
738
|
-
try {
|
|
739
|
-
const { data } = await octokit.repos.getReleaseByTag({ ...options });
|
|
740
|
-
return data;
|
|
741
|
-
} catch (e) {
|
|
742
|
-
if (e?.status === 404) return null;
|
|
743
|
-
throw e;
|
|
744
|
-
}
|
|
745
|
-
};
|
|
746
|
-
const createCRelease = async (octokit, options) => {
|
|
747
|
-
const { owner, repo, tag, name, body, draft, prerelease, generateReleaseNotes, makeLatest, targetCommitish, discussionCategoryName } = options;
|
|
748
|
-
const { data } = await octokit.repos.createRelease({
|
|
749
|
-
owner,
|
|
750
|
-
repo,
|
|
751
|
-
tag_name: tag,
|
|
752
|
-
name,
|
|
753
|
-
body,
|
|
754
|
-
draft,
|
|
755
|
-
prerelease,
|
|
756
|
-
generate_release_notes: generateReleaseNotes,
|
|
757
|
-
make_latest: makeLatest,
|
|
758
|
-
target_commitish: targetCommitish,
|
|
759
|
-
discussion_category_name: discussionCategoryName
|
|
653
|
+
];
|
|
654
|
+
if (fromPreRelease) candidates.unshift({
|
|
655
|
+
label: "Pre-release",
|
|
656
|
+
value: "prerelease",
|
|
657
|
+
hint: await getVersionHint(project, {
|
|
658
|
+
...options,
|
|
659
|
+
tagPrefix,
|
|
660
|
+
as: "prerelease"
|
|
661
|
+
}),
|
|
662
|
+
bump: { as: "prerelease" }
|
|
760
663
|
});
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
664
|
+
else for (const as of [
|
|
665
|
+
"patch",
|
|
666
|
+
"minor",
|
|
667
|
+
"major"
|
|
668
|
+
]) candidates.push({
|
|
669
|
+
label: `Pre-${as[0].toUpperCase()}${as.slice(1)}`,
|
|
670
|
+
value: `pre${as}`,
|
|
671
|
+
hint: await getVersionHint(project, {
|
|
672
|
+
...options,
|
|
673
|
+
tagPrefix,
|
|
674
|
+
as,
|
|
675
|
+
prerelease: "beta"
|
|
676
|
+
}),
|
|
677
|
+
bump: {
|
|
678
|
+
as,
|
|
679
|
+
prerelease: "beta"
|
|
680
|
+
}
|
|
778
681
|
});
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
...options
|
|
682
|
+
candidates.push({
|
|
683
|
+
label: "Custom",
|
|
684
|
+
value: "custom",
|
|
685
|
+
hint: "Specify an exact version"
|
|
686
|
+
}, {
|
|
687
|
+
label: "As-is",
|
|
688
|
+
value: "ignore",
|
|
689
|
+
hint: `Do not update the version ${current}`,
|
|
690
|
+
bump: { version: current }
|
|
789
691
|
});
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
const
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
headers: {
|
|
803
|
-
"content-type": contentType(file) || "application/octet-stream",
|
|
804
|
-
"content-length": statSync(file).size
|
|
805
|
-
}
|
|
692
|
+
return candidates;
|
|
693
|
+
};
|
|
694
|
+
const runBumpPrompts = async (project, options = {}) => {
|
|
695
|
+
const { bump: bumpOpts = {}, run = {} } = options;
|
|
696
|
+
const bump = { ...bumpOpts };
|
|
697
|
+
const { dryRun = false, ci = false } = run;
|
|
698
|
+
if (bump.version || bump.as || bump.snapshot) return bump;
|
|
699
|
+
const runVersionPrompts = async (project, tagPrefix) => {
|
|
700
|
+
const promptsRes = {};
|
|
701
|
+
const inferred = await project.inferReleaseType({
|
|
702
|
+
...bump,
|
|
703
|
+
tagPrefix
|
|
806
704
|
});
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
ctx.noGitHub = true;
|
|
828
|
-
return;
|
|
829
|
-
}
|
|
830
|
-
const [owner, repo] = parseGitHubRepo(remoteUrl);
|
|
831
|
-
const url = getGithubUrl(owner, repo);
|
|
832
|
-
Object.assign(ctx.github, {
|
|
833
|
-
owner,
|
|
834
|
-
repo,
|
|
835
|
-
url
|
|
836
|
-
});
|
|
837
|
-
const token = tokenRef ? process.env[tokenRef] : process.env.GITHUB_TOKEN;
|
|
838
|
-
if (!token) {
|
|
839
|
-
Object.assign(ctx.github, { isWeb: true });
|
|
840
|
-
if (isGitHubActions) throw new Error(MSG.ERROR.GITHUB_TOKEN(tokenRef));
|
|
841
|
-
return;
|
|
842
|
-
}
|
|
843
|
-
Object.assign(ctx.github, { token });
|
|
844
|
-
if (!isGitHubActions && !skipChecks) {
|
|
845
|
-
const octokit = new Octokit({ auth: token });
|
|
846
|
-
let username = "";
|
|
847
|
-
try {
|
|
848
|
-
const { data: { login } } = await octokit.users.getAuthenticated();
|
|
849
|
-
username = login;
|
|
850
|
-
} catch (e) {
|
|
851
|
-
throw new Error(MSG.ERROR.GITHUB_AUTH);
|
|
705
|
+
const name = await project.manifest.getName();
|
|
706
|
+
const current = await project.manifest.getVersion();
|
|
707
|
+
let useCustom = false;
|
|
708
|
+
let customVersion = current;
|
|
709
|
+
if (project.isMonorepoMode() && project.isFixedMode) {
|
|
710
|
+
const { version: maxVersion, packages } = await project.getMaxVersion();
|
|
711
|
+
if (lt(current, maxVersion)) {
|
|
712
|
+
useCustom = true;
|
|
713
|
+
customVersion = inc(maxVersion, inferred ?? "patch");
|
|
714
|
+
note(`
|
|
715
|
+
Current version: ${current}
|
|
716
|
+
Highest package version: ${maxVersion}
|
|
717
|
+
Packages at this version:\n${packages.map((name) => `- ${name}`).join("\n")}
|
|
718
|
+
|
|
719
|
+
Minimum release version: ${maxVersion}
|
|
720
|
+
Suggested release version: ${customVersion} (used in CI)
|
|
721
|
+
`.trim(), "Monorepo version mismatch", { format(line) {
|
|
722
|
+
return line.trim();
|
|
723
|
+
} });
|
|
724
|
+
}
|
|
852
725
|
}
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
})
|
|
859
|
-
|
|
860
|
-
|
|
726
|
+
const versionOptions = await createVersionOptions(project, bump, current, tagPrefix);
|
|
727
|
+
const find = (val) => versionOptions.find((option) => option.value === val);
|
|
728
|
+
const fromPreRelease = isPrerelease(current);
|
|
729
|
+
const initialValue = useCustom ? "custom" : inferred && find(inferred) ? inferred : fromPreRelease && find("prerelease") ? "prerelease" : "patch";
|
|
730
|
+
const type = ci ? initialValue : await singlePrompt(() => select({
|
|
731
|
+
message: question(`Select "${cyan`${name}`}" release version type:`, dryRun),
|
|
732
|
+
options: versionOptions,
|
|
733
|
+
initialValue
|
|
734
|
+
}));
|
|
735
|
+
if (equals(type, "custom")) promptsRes.version = ci ? customVersion : await singlePrompt(() => text({
|
|
736
|
+
message: question("Enter release version:", dryRun),
|
|
737
|
+
initialValue: customVersion,
|
|
738
|
+
placeholder: customVersion,
|
|
739
|
+
validate: (val) => {
|
|
740
|
+
if (isUndefined(val) || !isValidVersion(val)) return "Invalid version.";
|
|
741
|
+
}
|
|
742
|
+
}));
|
|
743
|
+
else {
|
|
744
|
+
const selected = find(type);
|
|
745
|
+
Object.assign(promptsRes, selected?.bump);
|
|
861
746
|
}
|
|
747
|
+
return promptsRes;
|
|
748
|
+
};
|
|
749
|
+
if (project.isMonorepoMode() && project.isIndependentMode) for await (const subProject of project.getProjects()) {
|
|
750
|
+
const name = await subProject.manifest.getName();
|
|
751
|
+
const scope = await project.getScope(name);
|
|
752
|
+
const tagPrefix = await project.getTagPrefix(scope);
|
|
753
|
+
const monoBump = bump;
|
|
754
|
+
(monoBump.byProject ??= {})[name] = {
|
|
755
|
+
...monoBump.byProject?.[name],
|
|
756
|
+
...await runVersionPrompts(subProject, tagPrefix)
|
|
757
|
+
};
|
|
862
758
|
}
|
|
863
|
-
|
|
864
|
-
const
|
|
865
|
-
|
|
866
|
-
const { github: { isWeb, token, owner, repo, changelog, releaseName } } = ctx;
|
|
867
|
-
const { github: { release, assets, prerelease, autoGenerate, draft } } = config;
|
|
868
|
-
if (!release || dryRun) return;
|
|
869
|
-
const octokit = new Octokit({ auth: token });
|
|
870
|
-
if (isWeb) await createWebRelease({
|
|
871
|
-
owner,
|
|
872
|
-
repo,
|
|
873
|
-
tag: currentTag,
|
|
874
|
-
title: releaseName,
|
|
875
|
-
body: truncateBody(changelog),
|
|
876
|
-
prerelease: String(toPreRelease || prerelease)
|
|
877
|
-
});
|
|
878
|
-
else if (isCI) {
|
|
879
|
-
const res = await createCReleaseByTag(octokit, {
|
|
880
|
-
owner,
|
|
881
|
-
repo,
|
|
882
|
-
tag: currentTag,
|
|
883
|
-
name: releaseName,
|
|
884
|
-
body: autoGenerate ? "" : truncateBody(changelog),
|
|
885
|
-
draft,
|
|
886
|
-
prerelease: toPreRelease || prerelease,
|
|
887
|
-
generateReleaseNotes: autoGenerate,
|
|
888
|
-
makeLatest: "true"
|
|
889
|
-
});
|
|
890
|
-
if (!res) return;
|
|
891
|
-
if (assets.length) {
|
|
892
|
-
const { id: releaseId, upload_url: uploadUrl } = res;
|
|
893
|
-
await uploadAssets(octokit, {
|
|
894
|
-
owner,
|
|
895
|
-
repo,
|
|
896
|
-
name,
|
|
897
|
-
uploadUrl,
|
|
898
|
-
releaseId,
|
|
899
|
-
assets
|
|
900
|
-
});
|
|
901
|
-
}
|
|
759
|
+
else {
|
|
760
|
+
const result = await runVersionPrompts(project);
|
|
761
|
+
Object.assign(bump, result);
|
|
902
762
|
}
|
|
763
|
+
return bump;
|
|
903
764
|
};
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
const
|
|
907
|
-
const { pkg: { name, next }, configFileExists: cfe, dryRun, isCI } = ctx;
|
|
908
|
-
const { npm: { publish } } = config;
|
|
909
|
-
const logs = [];
|
|
910
|
-
const target = `${name}@${next}`;
|
|
911
|
-
const shouldPrompt = (val) => isUndefined(val) || dryRun && !isCI;
|
|
765
|
+
const getCliPromptOptions = async (options = {}) => {
|
|
766
|
+
const { dryRun = false, ci = false } = options;
|
|
767
|
+
const shouldPrompt = (v) => !ci && (isUndefined(v) || dryRun);
|
|
912
768
|
const res = await group({
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
})(),
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
}
|
|
921
|
-
}, { onCancel: async ({ results }) => {
|
|
922
|
-
abortGroupPrompt(ctx);
|
|
923
|
-
} });
|
|
924
|
-
Object.assign(config.npm, { publish: res.publish });
|
|
925
|
-
return res;
|
|
926
|
-
};
|
|
927
|
-
const runNpmOptPrompts = async (ctx, config) => {
|
|
928
|
-
const res = await group({ otp: () => text({ message: MSG.PROMPT.NPM_OTP }) }, { onCancel: ({ results }) => {
|
|
929
|
-
abortGroupPrompt(ctx);
|
|
769
|
+
commit: () => options.skipGit ? Promise.resolve(false) : shouldPrompt(options.skipGit) ? confirm({ message: question("Commit release changes?", dryRun) }) : Promise.resolve(true),
|
|
770
|
+
tag: () => options.skipGit ? Promise.resolve(false) : shouldPrompt(options.skipGit) ? confirm({ message: question("Create release tags?", dryRun) }) : Promise.resolve(true),
|
|
771
|
+
push: () => options.skipGit ? Promise.resolve(false) : shouldPrompt(options.skipGit) ? confirm({ message: question("Push release changes?", dryRun) }) : Promise.resolve(true),
|
|
772
|
+
publish: () => options.skipPublish ? Promise.resolve(false) : shouldPrompt(options.skipPublish) ? confirm({ message: question("Publish project?", dryRun) }) : Promise.resolve(true),
|
|
773
|
+
release: () => options.skipRelease || options.skipGit ? Promise.resolve(false) : shouldPrompt(options.skipRelease) ? confirm({ message: question("Create repository release?", dryRun) }) : Promise.resolve(true)
|
|
774
|
+
}, { onCancel: () => {
|
|
775
|
+
abortTask();
|
|
930
776
|
} });
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
"bin",
|
|
938
|
-
"main",
|
|
939
|
-
"exports",
|
|
940
|
-
"types",
|
|
941
|
-
"typings",
|
|
942
|
-
"module",
|
|
943
|
-
"browser",
|
|
944
|
-
"esnext",
|
|
945
|
-
"es2015",
|
|
946
|
-
"unpkg",
|
|
947
|
-
"umd:main",
|
|
948
|
-
"typesVersions",
|
|
949
|
-
"cpu",
|
|
950
|
-
"os",
|
|
951
|
-
"engines"
|
|
952
|
-
];
|
|
953
|
-
const IGNORE_FIELDS = [
|
|
954
|
-
"babel",
|
|
955
|
-
"browserslist",
|
|
956
|
-
"c8",
|
|
957
|
-
"commitlint",
|
|
958
|
-
"devDependencies",
|
|
959
|
-
"eslintConfig",
|
|
960
|
-
"eslintIgnore",
|
|
961
|
-
"husky",
|
|
962
|
-
"jest",
|
|
963
|
-
"lint-staged",
|
|
964
|
-
"nano-staged",
|
|
965
|
-
"pre-commit",
|
|
966
|
-
"prettier",
|
|
967
|
-
"pwmetrics",
|
|
968
|
-
"remarkConfig",
|
|
969
|
-
"renovate",
|
|
970
|
-
"resolutions",
|
|
971
|
-
"sharec",
|
|
972
|
-
"simple-git-hooks",
|
|
973
|
-
"simple-pre-commit",
|
|
974
|
-
"size-limit",
|
|
975
|
-
"typeCoverage",
|
|
976
|
-
"yaspeller",
|
|
977
|
-
"pnpm",
|
|
978
|
-
"packageManager"
|
|
979
|
-
];
|
|
980
|
-
const KEEP_SCRIPT_NAMES = [
|
|
981
|
-
"preinstall",
|
|
982
|
-
"install",
|
|
983
|
-
"postinstall"
|
|
984
|
-
];
|
|
985
|
-
const IGNORE_FILES = [
|
|
986
|
-
".DS_Store",
|
|
987
|
-
".git",
|
|
988
|
-
".vscode",
|
|
989
|
-
".idea",
|
|
990
|
-
"node_modules"
|
|
991
|
-
];
|
|
992
|
-
const clearPackageJSON = async (dir, options) => {
|
|
993
|
-
const file = join(dir, "package.json");
|
|
994
|
-
const json = await readJsonFile(file);
|
|
995
|
-
const { ignoreFields = [], keepScripts = [] } = options ?? {};
|
|
996
|
-
const mergePublishConfig = (json) => {
|
|
997
|
-
if (!json.publishConfig) return json;
|
|
998
|
-
const publishConfig = { ...json.publishConfig };
|
|
999
|
-
PUBLISH_CONFIG_FIELDS.forEach((field) => {
|
|
1000
|
-
if (publishConfig[field]) {
|
|
1001
|
-
json[field] = publishConfig[field];
|
|
1002
|
-
delete publishConfig[field];
|
|
1003
|
-
}
|
|
1004
|
-
});
|
|
1005
|
-
return {
|
|
1006
|
-
...json,
|
|
1007
|
-
publishConfig
|
|
1008
|
-
};
|
|
1009
|
-
};
|
|
1010
|
-
const removeIgnoredFields = (json) => {
|
|
1011
|
-
const [matched, unmatched] = partition([...ignoreFields, ...IGNORE_FIELDS], (i) => i.includes("."));
|
|
1012
|
-
matched.forEach((field) => unset(json, field));
|
|
1013
|
-
return omit(json, unmatched);
|
|
1014
|
-
};
|
|
1015
|
-
const cleanScripts = (json) => {
|
|
1016
|
-
if (!json.scripts) return json;
|
|
1017
|
-
const keep = [...keepScripts, ...KEEP_SCRIPT_NAMES];
|
|
1018
|
-
const excludes = Object.keys(json.scripts).filter((k) => !keep.includes(k));
|
|
1019
|
-
json.scripts = omit(json.scripts, excludes);
|
|
1020
|
-
return json;
|
|
1021
|
-
};
|
|
1022
|
-
const removeEmptyObjects = (json) => {
|
|
1023
|
-
return omit(json, Object.entries(json).filter(([k, v]) => isEmptyObject(v)).map(([k, v]) => k));
|
|
777
|
+
return {
|
|
778
|
+
commit: !res.commit,
|
|
779
|
+
tag: !res.tag,
|
|
780
|
+
push: !res.push,
|
|
781
|
+
publish: !res.publish,
|
|
782
|
+
release: !res.release
|
|
1024
783
|
};
|
|
1025
|
-
await writeJsonFile(file, removeEmptyObjects(cleanScripts(removeIgnoredFields(structuredClone(mergePublishConfig(json))))));
|
|
1026
784
|
};
|
|
1027
|
-
const
|
|
1028
|
-
|
|
1029
|
-
homepage && await editFile(file, (str) => {
|
|
1030
|
-
return str.split(/^##\s+\S.*$/m)[0] + `## Docs
|
|
1031
|
-
|
|
1032
|
-
Read full docs **[here](${homepage})**.`;
|
|
1033
|
-
});
|
|
785
|
+
const runConfirmPrompts = async (ci = false, dryRun = false) => {
|
|
786
|
+
if (!(ci ? true : await singlePrompt(() => confirm({ message: question(red("Confirm start workflow?"), dryRun) })))) abortTask();
|
|
1034
787
|
};
|
|
1035
788
|
//#endregion
|
|
1036
|
-
//#region src/
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
canPublish(registry, getPkgDir(selectedPkg))
|
|
1064
|
-
]);
|
|
1065
|
-
if (!pinged) throw new Error(MSG.ERROR.NPM_REGISTRY(registry));
|
|
1066
|
-
if (!username) throw new Error(MSG.ERROR.NPM_AUTH);
|
|
1067
|
-
Object.assign(ctx.npm, { username });
|
|
1068
|
-
const latest = await getPublishedVersion(`${name}@${tag}`);
|
|
1069
|
-
if (latest) {
|
|
1070
|
-
if (!await hasWriteAccess(name, username, registry)) throw new Error(MSG.ERROR.NPM_USER(username, name));
|
|
1071
|
-
}
|
|
1072
|
-
return latest ? `(${tag} → ${latest})` : `(${tag} → no published version)`;
|
|
1073
|
-
};
|
|
1074
|
-
const publishNpm = async (ctx, config) => {
|
|
1075
|
-
const { dryRun, selectedPkg, npm: { otp, tag }, github: { url } } = ctx;
|
|
1076
|
-
const { npm: { publish, publishArgs, cleanup: { packageJson, readme, removeTempDir } }, getPkgDir } = config;
|
|
1077
|
-
if (!publish) return;
|
|
1078
|
-
const otpArgs = otp ? ["--otp", otp] : [];
|
|
1079
|
-
const dryRunArg = dryRun ? ["--dry-run"] : [];
|
|
1080
|
-
const args = [
|
|
1081
|
-
...otpArgs,
|
|
1082
|
-
...publishArgs,
|
|
1083
|
-
...dryRunArg
|
|
1084
|
-
];
|
|
1085
|
-
if (packageJson || readme) {
|
|
1086
|
-
const parent = resolve(getPkgDir(selectedPkg));
|
|
1087
|
-
const temp = await createTempDir(parent);
|
|
1088
|
-
await copyDir(parent, temp.path, { ignore: [(name) => {
|
|
1089
|
-
return [temp.name, ...IGNORE_FILES].map((pattern) => createMatcher(pattern)).some((matcher) => matcher(name));
|
|
1090
|
-
}] });
|
|
1091
|
-
const options = typeof packageJson === "boolean" ? void 0 : packageJson;
|
|
1092
|
-
packageJson && await clearPackageJSON(temp.path, options);
|
|
1093
|
-
readme && await cleanReadme(temp.path, `${url}#readme`);
|
|
1094
|
-
await publishPackage({
|
|
1095
|
-
tag,
|
|
1096
|
-
args,
|
|
1097
|
-
cwd: temp.path
|
|
789
|
+
//#region src/releaser-cli/releaser.ts
|
|
790
|
+
var ReleaserCli = class extends Releaser {
|
|
791
|
+
constructor(options) {
|
|
792
|
+
const { silent, dryRun, verbose } = options;
|
|
793
|
+
super({
|
|
794
|
+
logger: new Logger({
|
|
795
|
+
silent,
|
|
796
|
+
dryRun,
|
|
797
|
+
verbose,
|
|
798
|
+
colors: [
|
|
799
|
+
blue,
|
|
800
|
+
blueBright,
|
|
801
|
+
cyan,
|
|
802
|
+
cyanBright,
|
|
803
|
+
green,
|
|
804
|
+
greenBright,
|
|
805
|
+
magenta,
|
|
806
|
+
magentaBright,
|
|
807
|
+
red,
|
|
808
|
+
redBright
|
|
809
|
+
].sort(() => Math.random() - .5),
|
|
810
|
+
printer: ({ dryRun, stage, message, color }) => {
|
|
811
|
+
const msg = isString(message) ? message : JSON.stringify(message, null, 2);
|
|
812
|
+
log.message(dim`${dryRun ? yellow`[dry-run]` : ""}${color(`[${stage}]`)}: ${msg}`);
|
|
813
|
+
}
|
|
814
|
+
}),
|
|
815
|
+
...options
|
|
1098
816
|
});
|
|
1099
|
-
removeTempDir && await temp.remove();
|
|
1100
|
-
} else await publishPackage({
|
|
1101
|
-
tag,
|
|
1102
|
-
args,
|
|
1103
|
-
cwd: getPkgDir(selectedPkg)
|
|
1104
|
-
});
|
|
1105
|
-
};
|
|
1106
|
-
//#endregion
|
|
1107
|
-
//#region src/action.ts
|
|
1108
|
-
var Action = class {
|
|
1109
|
-
async handleRelease(cmdArgs, options) {
|
|
1110
|
-
const { ctx, config } = await this.createContext(cmdArgs, options);
|
|
1111
|
-
await this.checkTask(ctx, config);
|
|
1112
|
-
await this.bumpTask(ctx, config);
|
|
1113
|
-
await this.changelogTask(ctx, config);
|
|
1114
|
-
await this.gitTask(ctx, config);
|
|
1115
|
-
await this.npmTask(ctx, config);
|
|
1116
|
-
await this.githubTask(ctx, config);
|
|
1117
|
-
if (ctx.dryRun) gitRollback(ctx);
|
|
1118
|
-
taskEnd(MSG.OUTRO(ctx.dryRun));
|
|
1119
817
|
}
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
await this.changelogTask(ctx, config);
|
|
1125
|
-
await this.gitTask(ctx, config);
|
|
1126
|
-
if (ctx.dryRun) gitRollback(ctx);
|
|
1127
|
-
taskEnd(MSG.OUTRO_PREPARE(ctx.dryRun));
|
|
1128
|
-
}
|
|
1129
|
-
async createContext(cmdArgs, options) {
|
|
1130
|
-
const { configFile, config } = await this.createConfig(options);
|
|
1131
|
-
info(MSG.INFO.TOOL(name, version));
|
|
1132
|
-
info(MSG.INFO.CONFIG(configFile));
|
|
1133
|
-
console.log();
|
|
1134
|
-
const { otp, package: defPkg, prepare, showChangelog, showRelease, ci, dryRun, onlyChangelog } = options;
|
|
1135
|
-
intro(prepare ? MSG.INTRO_PREPARE(dryRun) : MSG.INTRO(dryRun));
|
|
1136
|
-
const { isMonorepo, packages, getPkgDir, skipGit, skipNpm, skipGithub } = config;
|
|
1137
|
-
if (isMonorepo) {
|
|
1138
|
-
if (ci) {
|
|
1139
|
-
if (!defPkg) abortTask(MSG.ABORT.MONOREPO_CI_NO_PACKAGE);
|
|
1140
|
-
} else if (packages.length < 1) abortTask(MSG.ABORT.MONOREPO_NO_PACKAGES);
|
|
1141
|
-
}
|
|
1142
|
-
const selectPackage = async () => {
|
|
1143
|
-
const pkg = await select({
|
|
1144
|
-
message: question(MSG.PROMPT.SELECT_PACKAGE, "package"),
|
|
1145
|
-
options: packages.map((pkg) => ({
|
|
1146
|
-
label: pkg,
|
|
1147
|
-
value: pkg
|
|
1148
|
-
}))
|
|
1149
|
-
});
|
|
1150
|
-
abortSinglePrompt(pkg);
|
|
1151
|
-
return pkg;
|
|
1152
|
-
};
|
|
1153
|
-
const selectedPkg = !isMonorepo ? "" : ci ? defPkg : packages.length === 1 ? packages[0] : await selectPackage();
|
|
1154
|
-
const { pkg, pkgDir } = getPackageInfo(selectedPkg, getPkgDir);
|
|
1155
|
-
const { version: pkgVersion, private: pkgPrivate = false, name: pkgName, publishConfig = {} } = pkg;
|
|
1156
|
-
const nextVersion = inc(pkgVersion, cmdArgs) ?? "";
|
|
1157
|
-
const ctx = mergeConfig(createDefaultContext(), {
|
|
1158
|
-
selectedPkg,
|
|
1159
|
-
configFileExists: isNotEmpty(configFile),
|
|
1160
|
-
dryRun,
|
|
1161
|
-
showRelease,
|
|
1162
|
-
showChangelog,
|
|
1163
|
-
onlyChangelog,
|
|
1164
|
-
npm: { otp: String(otp) },
|
|
1165
|
-
increment: cmdArgs,
|
|
1166
|
-
isIncrement: true,
|
|
1167
|
-
isCI: [
|
|
1168
|
-
ci,
|
|
1169
|
-
showChangelog,
|
|
1170
|
-
showRelease,
|
|
1171
|
-
process.env.GITHUB_ACTIONS,
|
|
1172
|
-
process.env.CI
|
|
1173
|
-
].some(Boolean),
|
|
1174
|
-
pkg: {
|
|
1175
|
-
name: pkgName,
|
|
1176
|
-
isPrivate: pkgPrivate,
|
|
1177
|
-
fromPreRelease: isPrerelease(pkgVersion),
|
|
1178
|
-
current: pkgVersion,
|
|
1179
|
-
next: nextVersion,
|
|
1180
|
-
toPreRelease: false,
|
|
1181
|
-
publishConfig: { ...publishConfig },
|
|
1182
|
-
scope: isEmpty(selectedPkg) ? void 0 : selectedPkg
|
|
1183
|
-
},
|
|
1184
|
-
noGit: skipGit,
|
|
1185
|
-
noNpm: pkgPrivate || skipNpm,
|
|
1186
|
-
noGitHub: skipGit || skipGithub
|
|
818
|
+
tag(options = {}) {
|
|
819
|
+
return super.tag({
|
|
820
|
+
fetch: true,
|
|
821
|
+
...options
|
|
1187
822
|
});
|
|
1188
|
-
const { messages } = await publint({ pkgDir });
|
|
1189
|
-
for (const message of messages) {
|
|
1190
|
-
const formated = formatMessage(message, pkg);
|
|
1191
|
-
formated && msg("PKG", formated);
|
|
1192
|
-
}
|
|
1193
|
-
return {
|
|
1194
|
-
ctx,
|
|
1195
|
-
config
|
|
1196
|
-
};
|
|
1197
823
|
}
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
};
|
|
1203
|
-
const toNumber = (value) => {
|
|
1204
|
-
return isNumber(value) ? value : void 0;
|
|
1205
|
-
};
|
|
1206
|
-
const defaultConfig = createDefaultConfig(ci ? true : void 0);
|
|
1207
|
-
const { configFile, config: local } = await this.resolveUserConfig(process.cwd());
|
|
1208
|
-
return {
|
|
1209
|
-
configFile,
|
|
1210
|
-
config: mergeConfig(mergeConfig(defaultConfig, local), {
|
|
1211
|
-
changelog: {
|
|
1212
|
-
releaseCount: toNumber(releaseCount),
|
|
1213
|
-
includeHidden
|
|
1214
|
-
},
|
|
1215
|
-
git: { requireCleanWorkingTree: toFalse(requireCleanWorkingTree) },
|
|
1216
|
-
skipGit,
|
|
1217
|
-
skipNpm,
|
|
1218
|
-
skipGithub,
|
|
1219
|
-
isMonorepo
|
|
1220
|
-
})
|
|
1221
|
-
};
|
|
1222
|
-
}
|
|
1223
|
-
async resolveUserConfig(cwd = process.cwd()) {
|
|
1224
|
-
return resolveConfig(cwd);
|
|
1225
|
-
}
|
|
1226
|
-
async printChangelog(ctx, config) {
|
|
1227
|
-
const { showChangelog, selectedPkg, noGit, isIncrement, pkg: { scope } } = ctx;
|
|
1228
|
-
const { isMonorepo, getPkgDir, changelog: { tagPrefix, releaseCount, includeHidden, transformTypes } } = config;
|
|
1229
|
-
const { github: { shortNotes } } = config;
|
|
1230
|
-
if (noGit) return;
|
|
1231
|
-
const match = isMonorepo ? `${tagPrefix?.(selectedPkg)}*` : "*";
|
|
1232
|
-
const { from, to } = isZero(releaseCount) ? {
|
|
1233
|
-
from: "",
|
|
1234
|
-
to: "HEAD"
|
|
1235
|
-
} : await resolveChangelogRange(isIncrement, match);
|
|
1236
|
-
const commits = await getLogSince(from, to, getPkgDir(selectedPkg));
|
|
1237
|
-
const changelog = await getChangelog({
|
|
1238
|
-
getPkgDir: () => getPkgDir(selectedPkg),
|
|
1239
|
-
tagPrefix: tagPrefix?.(selectedPkg),
|
|
1240
|
-
scope: isEmpty(nonNullable([scope])) ? void 0 : nonNullable([scope]),
|
|
1241
|
-
releaseCount,
|
|
1242
|
-
includeHidden,
|
|
1243
|
-
transformTypes
|
|
824
|
+
push(options = {}) {
|
|
825
|
+
return super.push({
|
|
826
|
+
force: true,
|
|
827
|
+
...options
|
|
1244
828
|
});
|
|
1245
|
-
const short = `Please refer to [CHANGELOG.md](${getPkgDir(selectedPkg)}/CHANGELOG.md) for details.`;
|
|
1246
|
-
Object.assign(ctx.github, { changelog: shortNotes ? short : changelog });
|
|
1247
|
-
if (commits) {
|
|
1248
|
-
msg("GIT", `Changelog(${from}...${to}):${eol(2)}` + changelog);
|
|
1249
|
-
msg("GIT", `Commits(${from}...${to}):${eol(2)}` + commits);
|
|
1250
|
-
if (showChangelog) taskEnd(MSG.LOG.SHOW_CHANGELOG);
|
|
1251
|
-
} else msg("GIT", MSG.LOG.CHANGELOG_EMPTY);
|
|
1252
829
|
}
|
|
1253
|
-
async
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
{
|
|
1257
|
-
title: MSG.CHECK.GIT.CHECKING,
|
|
1258
|
-
task: async () => {
|
|
1259
|
-
await gitCheck(ctx, config);
|
|
1260
|
-
const { git: { remoteName, remoteUrl } } = ctx;
|
|
1261
|
-
return success(MSG.CHECK.GIT.CHECKED(remoteName, remoteUrl), dryRun);
|
|
1262
|
-
},
|
|
1263
|
-
enabled: !ctx.noGit
|
|
1264
|
-
},
|
|
1265
|
-
{
|
|
1266
|
-
title: MSG.CHECK.NPM.CHECKING,
|
|
1267
|
-
task: async () => {
|
|
1268
|
-
const msg = await npmCheck(ctx, config);
|
|
1269
|
-
const { pkg: { publishConfig: { registry } } } = ctx;
|
|
1270
|
-
return success(MSG.CHECK.NPM.CHECKED(registry, msg), dryRun);
|
|
1271
|
-
},
|
|
1272
|
-
enabled: !(ctx.noNpm || onlyChangelog)
|
|
1273
|
-
},
|
|
1274
|
-
{
|
|
1275
|
-
title: MSG.CHECK.GITHUB.CHECKING,
|
|
1276
|
-
task: async () => {
|
|
1277
|
-
await githubCheck(ctx, config);
|
|
1278
|
-
const { github: { owner, repo } } = ctx;
|
|
1279
|
-
const repository = getGithubUrl(owner, repo);
|
|
1280
|
-
return success(MSG.CHECK.GITHUB.CHECKED(repository), dryRun);
|
|
1281
|
-
},
|
|
1282
|
-
enabled: !(ctx.noGitHub || onlyChangelog)
|
|
1283
|
-
}
|
|
1284
|
-
]).catch(abortOnError);
|
|
830
|
+
async run(...args) {
|
|
831
|
+
await super.run(...args);
|
|
832
|
+
this.end();
|
|
1285
833
|
}
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
includeHidden,
|
|
1297
|
-
transformTypes,
|
|
1298
|
-
json: true
|
|
1299
|
-
});
|
|
1300
|
-
if (res) msg("BUMP", res.reason);
|
|
1301
|
-
const inferred = res?.releaseType;
|
|
1302
|
-
const nextVersion = (isCI ? inc(current, inferred ?? "patch") : void 0) || await runVersionPrompts(ctx, config, inferred);
|
|
1303
|
-
ctx.isIncrement = neq(current, nextVersion);
|
|
1304
|
-
const { version: next, isPrerelease: toPreRelease, preId, preBase } = parseVersion(nextVersion);
|
|
1305
|
-
Object.assign(ctx.pkg, {
|
|
1306
|
-
next,
|
|
1307
|
-
toPreRelease,
|
|
1308
|
-
preId,
|
|
1309
|
-
preBase
|
|
834
|
+
setCliOptions(options) {
|
|
835
|
+
return this.enqueue(async () => {
|
|
836
|
+
const { ci, dryRun, releaseType: as } = options;
|
|
837
|
+
const { project, stepsOptions: { bump: bumpOpts } } = this;
|
|
838
|
+
const bump = await runBumpPrompts(project, {
|
|
839
|
+
bump: {
|
|
840
|
+
...bumpOpts,
|
|
841
|
+
as
|
|
842
|
+
},
|
|
843
|
+
run: options
|
|
1310
844
|
});
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
await runLifeCycleHook(hooks, "before:bump", dryRun);
|
|
1315
|
-
await tasks([{
|
|
1316
|
-
title: MSG.TASK.VERSION.START,
|
|
1317
|
-
task: async () => {
|
|
1318
|
-
await bumpPackageVersion(next, [], getPkgDir(selectedPkg));
|
|
1319
|
-
await formatTemplate(ctx, config);
|
|
1320
|
-
const to = `(${current}...${diff(current, next)})`;
|
|
1321
|
-
return success(MSG.TASK.VERSION.END(to), dryRun);
|
|
1322
|
-
},
|
|
1323
|
-
enabled: true
|
|
1324
|
-
}]).catch((err) => abortOnError(err, ctx));
|
|
1325
|
-
await runLifeCycleHook(hooks, "after:bump", dryRun);
|
|
1326
|
-
await this.printChangelog(ctx, config);
|
|
1327
|
-
}
|
|
1328
|
-
async changelogTask(ctx, config) {
|
|
1329
|
-
const { isIncrement, dryRun, selectedPkg, noGit, onlyChangelog, pkg: { scope } } = ctx;
|
|
1330
|
-
const { getPkgDir, changelog: { tagPrefix, releaseCount, includeHidden, transformTypes } } = config;
|
|
1331
|
-
if (noGit) return;
|
|
1332
|
-
await tasks([{
|
|
1333
|
-
title: MSG.TASK.CHANGELOG.START,
|
|
1334
|
-
task: async () => {
|
|
1335
|
-
await generateChangelog({
|
|
1336
|
-
getPkgDir: () => getPkgDir(selectedPkg),
|
|
1337
|
-
tagPrefix: tagPrefix?.(selectedPkg),
|
|
1338
|
-
scope: isEmpty(nonNullable([scope])) ? void 0 : nonNullable([scope]),
|
|
1339
|
-
releaseCount,
|
|
1340
|
-
includeHidden,
|
|
1341
|
-
transformTypes
|
|
1342
|
-
});
|
|
1343
|
-
await gitAddAll();
|
|
1344
|
-
return success(MSG.TASK.CHANGELOG.END, dryRun);
|
|
1345
|
-
},
|
|
1346
|
-
enabled: isIncrement
|
|
1347
|
-
}]).catch((err) => abortOnError(err, ctx));
|
|
1348
|
-
const changeset = await getShortStatus();
|
|
1349
|
-
if (changeset) msg("GIT", `Changes:${eol(2)}` + coloredStatus(changeset));
|
|
1350
|
-
else msg("GIT", MSG.LOG.CHANGES_EMPTY);
|
|
1351
|
-
if (onlyChangelog) taskEnd(MSG.LOG.ONLY_CHANGELOG);
|
|
845
|
+
this.setOptions({ bump }).setSkippedSteps(await getCliPromptOptions(options));
|
|
846
|
+
await runConfirmPrompts(ci, dryRun);
|
|
847
|
+
});
|
|
1352
848
|
}
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
849
|
+
setLegacyOptions() {
|
|
850
|
+
return this.enqueue(async () => {
|
|
851
|
+
const logger = this.logger.createChild("legacy-options");
|
|
852
|
+
const { config, file } = await loadConfig(process.cwd());
|
|
853
|
+
const legacySteps = omit(config, [
|
|
854
|
+
"project",
|
|
855
|
+
"hosting",
|
|
856
|
+
"releaser"
|
|
857
|
+
]);
|
|
858
|
+
if (file) {
|
|
859
|
+
logger.verbose(`Legacy config file: ${file}`);
|
|
860
|
+
logger.verbose("Legacy config for steps:");
|
|
861
|
+
logger.verbose(legacySteps);
|
|
1365
862
|
}
|
|
1366
|
-
|
|
1367
|
-
|
|
863
|
+
this.setOptions(legacySteps);
|
|
864
|
+
});
|
|
1368
865
|
}
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
866
|
+
check() {
|
|
867
|
+
return this.enqueue(async () => {
|
|
868
|
+
const { project, hosting } = this;
|
|
869
|
+
const logger = this.logger.createChild("check");
|
|
870
|
+
logger.info("Checking release environment...");
|
|
871
|
+
if (![
|
|
872
|
+
"commit",
|
|
873
|
+
"tag",
|
|
874
|
+
"push"
|
|
875
|
+
].every((k) => this.shouldSkip(k))) {
|
|
876
|
+
logger.verbose("Checking Git repository...");
|
|
877
|
+
const isRepo = await isGitRepo();
|
|
878
|
+
assert(isRepo, "Current directory is not a Git repository");
|
|
879
|
+
logger.verbose("Checking working tree...");
|
|
880
|
+
const isClean = await isWorkingTreeClean();
|
|
881
|
+
assert(isClean, "Git working tree is not clean");
|
|
882
|
+
logger.verbose("Checking remote repository...");
|
|
883
|
+
const hasRemote = await gitConfigGet("remote.origin.url");
|
|
884
|
+
assert(hasRemote, "Git remote \"origin\" is not configured");
|
|
1387
885
|
}
|
|
1388
|
-
|
|
1389
|
-
|
|
886
|
+
if (!this.shouldSkip("publish")) await project.publishCheck({ logger });
|
|
887
|
+
if (!this.shouldSkip("release")) await hosting?.releaseCheck({ logger });
|
|
888
|
+
});
|
|
1390
889
|
}
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
}
|
|
1406
|
-
}]).catch((err) => abortOnError(err, ctx, false));
|
|
1407
|
-
await runLifeCycleHook(hooks, "after:release", dryRun);
|
|
890
|
+
start() {
|
|
891
|
+
return this.enqueue(async () => {
|
|
892
|
+
const { name, version } = package_default;
|
|
893
|
+
console.log(`${blue`i`} ${name} v${version}\n`);
|
|
894
|
+
intro("🚀 Starting release");
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
end() {
|
|
898
|
+
const { options: { dryRun = false } } = this;
|
|
899
|
+
outro(`🎉 ${dryRun ? "Dry run complete" : "Release complete"}`);
|
|
900
|
+
process.exit(0);
|
|
901
|
+
}
|
|
902
|
+
async runAction(options) {
|
|
903
|
+
await this.start().setLegacyOptions().setCliOptions(options).check().bump().commit().tag().push().publish().release().run();
|
|
1408
904
|
}
|
|
1409
|
-
};
|
|
1410
|
-
//#endregion
|
|
1411
|
-
//#region src/monorepo/packages.ts
|
|
1412
|
-
const scanWorkspacePackages = (cwd) => {
|
|
1413
|
-
const workspaceFile = join(cwd, "pnpm-workspace.yaml");
|
|
1414
|
-
if (!existsSync(workspaceFile)) return [];
|
|
1415
|
-
const { packages = [] } = parse(readFileSync(workspaceFile, "utf8"));
|
|
1416
|
-
return globSync(packages.map((pattern) => normalizePath(join(pattern, "package.json"))), {
|
|
1417
|
-
cwd,
|
|
1418
|
-
absolute: false
|
|
1419
|
-
});
|
|
1420
|
-
};
|
|
1421
|
-
const getPackages = (cwd) => {
|
|
1422
|
-
return scanWorkspacePackages(cwd).reduce(((acc, file) => {
|
|
1423
|
-
const workspace = dirname(dirname(file));
|
|
1424
|
-
(acc[workspace] ??= []).push(basename(dirname(file)));
|
|
1425
|
-
return acc;
|
|
1426
|
-
}), {});
|
|
1427
|
-
};
|
|
1428
|
-
const monoConfig = (packages = [], workspace = "packages") => {
|
|
1429
|
-
const subs = isEmpty(packages) ? "[]" : `[ ${[...packages].map((k) => `'${k}'`).join(", ")} ]`;
|
|
1430
|
-
return dedent(`
|
|
1431
|
-
import { defineConfig } from '@peiyanlu/release'
|
|
1432
|
-
${newline(1)}
|
|
1433
|
-
export default defineConfig({
|
|
1434
|
-
isMonorepo: true,
|
|
1435
|
-
packages: ${subs},
|
|
1436
|
-
getPkgDir: (pkg) => \`${workspace}/\${ pkg }\`,
|
|
1437
|
-
toTag: (pkg, version) => \`\${ pkg }@\${ version }\`,
|
|
1438
|
-
changelog: {
|
|
1439
|
-
tagPrefix: (pkg) => \`\${ pkg }@\`,
|
|
1440
|
-
},
|
|
1441
|
-
})
|
|
1442
|
-
`);
|
|
1443
|
-
};
|
|
1444
|
-
const generateMonoConfig = (cwd = process.cwd()) => {
|
|
1445
|
-
const entries = Object.entries(getPackages(cwd));
|
|
1446
|
-
const [workspace, packages] = entries.length === 1 ? entries[0] : [];
|
|
1447
|
-
return monoConfig(packages, workspace);
|
|
1448
905
|
};
|
|
1449
906
|
//#endregion
|
|
1450
907
|
//#region src/cli.ts
|
|
1451
|
-
program.name("release").description(description).version(version, "-v, --version", "Print the tool version and exit.").usage("[release-type] [options]").argument("[release-type]",
|
|
1452
|
-
const {
|
|
1453
|
-
const
|
|
1454
|
-
|
|
908
|
+
program.name("release").description(package_default.description).version(package_default.version, "-v, --version", "Print the tool version and exit.").usage("[release-type] [options]").argument("[release-type]", `Release type. One of 'major', 'minor', 'patch', or 'prerelease'.`).enablePositionalOptions().option("-n, --dry-run", "Run in dry mode.").option("--ci", "Enable CI mode.").option("--skip-git", "Skip all Git-related checks and operations.").option("--skip-publish", "Skip the package publishing step.").option("--skip-release", "Skip the repository release step.").helpOption("-h, --help", "Display help information.").action(async (releaseType, options) => {
|
|
909
|
+
const { config: { project, hosting, releaser } } = await loadConfig(process.cwd());
|
|
910
|
+
const { dryRun } = options;
|
|
911
|
+
if (!project) throwError("Project not found");
|
|
912
|
+
if (!hosting) throwError("GitRepositoryHosting not found");
|
|
913
|
+
await new ReleaserCli({
|
|
914
|
+
...releaser,
|
|
915
|
+
project,
|
|
916
|
+
hosting,
|
|
917
|
+
dryRun
|
|
918
|
+
}).runAction({
|
|
919
|
+
releaseType,
|
|
920
|
+
...options
|
|
921
|
+
});
|
|
1455
922
|
});
|
|
1456
|
-
program.command("init").description("Create a release configuration file").option("
|
|
1457
|
-
const {
|
|
923
|
+
program.command("init").description("Create a release configuration file").option("--npm", "Use npm as the package manager", false).option("--pnpm", "Use pnpm as the package manager", false).option("--monorepo", "Configure a monorepo", false).option("--fixed", "Use fixed versioning for the monorepo", false).option("--github-workflow", "Create a GitHub Actions release workflow", false).option("--force", "Overwrite existing configuration or workflow files", false).option("--add", "Stage generated files with Git", false).action(async (options) => {
|
|
924
|
+
const { npm, pnpm, monorepo, fixed, githubWorkflow, force, add } = options;
|
|
1458
925
|
const cwd = process.cwd();
|
|
1459
|
-
const
|
|
1460
|
-
const
|
|
1461
|
-
const isTS = existsSync(join(cwd, "tsconfig.json"))
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
926
|
+
const tplDir = resolve(__dirname, "..", "template");
|
|
927
|
+
const { dependencies, devDependencies } = readJsonFileSync(join(cwd, "package.json"));
|
|
928
|
+
const isTS = existsSync(join(cwd, "tsconfig.json")) || Object.keys({
|
|
929
|
+
...dependencies,
|
|
930
|
+
...devDependencies
|
|
931
|
+
}).includes("typescript");
|
|
932
|
+
const configFile = join(cwd, `release.config.${isTS ? "ts" : "js"}`);
|
|
933
|
+
const workflowFile = join(cwd, ".github/workflows", "release.yml");
|
|
934
|
+
if (existsSync(configFile) && !force) throwError(`"${configFile}" already exists`);
|
|
935
|
+
if (githubWorkflow && existsSync(workflowFile) && !force) throwError(`"${workflowFile}" already exists`);
|
|
936
|
+
const template = readFileSync(join(tplDir, `release.config.hbs`), "utf-8");
|
|
937
|
+
const configContent = Handlebars.compile(template)({
|
|
938
|
+
monorepo,
|
|
939
|
+
fixed,
|
|
940
|
+
npm,
|
|
941
|
+
pnpm,
|
|
942
|
+
npmOrPnpm: npm || pnpm
|
|
943
|
+
});
|
|
944
|
+
writeFileSync(configFile, configContent.trimEnd() + newline(), "utf-8");
|
|
945
|
+
if (githubWorkflow) {
|
|
946
|
+
const workflowContent = readFileSync(join(tplDir, `release.text`), "utf-8");
|
|
947
|
+
await mkdir(".github/workflows", { recursive: true });
|
|
948
|
+
writeFileSync(workflowFile, workflowContent.trimEnd() + newline(), "utf-8");
|
|
1467
949
|
}
|
|
1468
|
-
|
|
1469
|
-
import { defineConfig } from '@peiyanlu/release'
|
|
1470
|
-
${newline(1)}
|
|
1471
|
-
export default defineConfig({})
|
|
1472
|
-
`);
|
|
1473
|
-
const content = monorepo ? generateMonoConfig(process.cwd()) : config();
|
|
1474
|
-
writeFileSync(configFile, content + newline(), "utf-8");
|
|
1475
|
-
console.log(`Wrote to ${underline(dim(configFile))}`);
|
|
1476
|
-
if (add) gitAddSync([infile]);
|
|
1477
|
-
console.log(`\n${content}`);
|
|
950
|
+
if (add) gitAddSync(githubWorkflow ? [configFile, workflowFile] : [configFile]);
|
|
1478
951
|
});
|
|
1479
|
-
program.addHelpText("afterAll", "\nCASE: release -C -r0 --include-hidden --only-changelog\n\nThanks for using!");
|
|
1480
952
|
program.parse(process.argv);
|
|
1481
|
-
program.on("command:*", () => {
|
|
1482
|
-
console.error(`\n${red`Error`} Invalid command: ${red`%s`}`, program.args.join(" "));
|
|
1483
|
-
console.log(`See ${red`--help`} for a list of available commands.\n`);
|
|
1484
|
-
process.exit(1);
|
|
1485
|
-
});
|
|
1486
953
|
//#endregion
|
|
1487
954
|
export {};
|