@peiyanlu/release 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/LICENSE +21 -0
- package/README.md +111 -0
- package/dist/cli.mjs +1354 -0
- package/dist/index.cjs +27 -0
- package/dist/index.d.cts +51 -0
- package/dist/index.d.mts +51 -0
- package/dist/index.mjs +25 -0
- package/index.js +3 -0
- package/package.json +79 -0
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,1354 @@
|
|
|
1
|
+
import { execAsync, isGitRepo, readJsonFile } from "@peiyanlu/cli-utils";
|
|
2
|
+
import { blue, dim, gray, green, red, rgb, yellow } from "ansis";
|
|
3
|
+
import { program } from "commander";
|
|
4
|
+
import path, { dirname, join, resolve } from "node:path";
|
|
5
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
6
|
+
import { cancel, confirm, group, intro, isCancel, log, outro, select, tasks, text } from "@clack/prompts";
|
|
7
|
+
import { inspect, styleText } from "node:util";
|
|
8
|
+
import { clean, coerce, gt, gte, inc, parse, prerelease, valid } from "semver";
|
|
9
|
+
import { createReadStream, existsSync, statSync } from "node:fs";
|
|
10
|
+
import { EOL } from "node:os";
|
|
11
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
12
|
+
import { Octokit } from "@octokit/rest";
|
|
13
|
+
import { contentType } from "mime-types";
|
|
14
|
+
import open from "open";
|
|
15
|
+
import { glob } from "tinyglobby";
|
|
16
|
+
import { spawn } from "node:child_process";
|
|
17
|
+
|
|
18
|
+
//#region node_modules/.pnpm/tsdown@0.18.2_typescript@5.9.3/node_modules/tsdown/esm-shims.js
|
|
19
|
+
const getFilename = () => fileURLToPath(import.meta.url);
|
|
20
|
+
const getDirname = () => path.dirname(getFilename());
|
|
21
|
+
const __dirname$1 = /* @__PURE__ */ getDirname();
|
|
22
|
+
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/config.ts
|
|
25
|
+
const findConfigFile = (cwd = process.cwd()) => {
|
|
26
|
+
for (const ext of [
|
|
27
|
+
"ts",
|
|
28
|
+
"mts",
|
|
29
|
+
"cts",
|
|
30
|
+
"js",
|
|
31
|
+
"mjs",
|
|
32
|
+
"cjs"
|
|
33
|
+
]) {
|
|
34
|
+
const path$1 = resolve(cwd, `release.config.${ext}`);
|
|
35
|
+
if (existsSync(path$1)) return path$1;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
const loadConfig = async (file) => {
|
|
39
|
+
const { href } = pathToFileURL(file);
|
|
40
|
+
const mod = await import(href);
|
|
41
|
+
const temp = mod.default ?? mod;
|
|
42
|
+
return typeof temp === "function" ? temp() : temp;
|
|
43
|
+
};
|
|
44
|
+
const resolveConfig = async (cwd, log$1) => {
|
|
45
|
+
const configFile = findConfigFile(cwd);
|
|
46
|
+
if (!configFile) return {};
|
|
47
|
+
log$1?.(configFile);
|
|
48
|
+
return loadConfig(configFile);
|
|
49
|
+
};
|
|
50
|
+
const toString = (self) => Object.prototype.toString.call(self);
|
|
51
|
+
const isPlainObject = (v) => toString(v) === "[object Object]";
|
|
52
|
+
const isFunction = (v) => toString(v) === "[object Function]";
|
|
53
|
+
const mergeConfig = (defaults, overrides) => {
|
|
54
|
+
if (!overrides) return structuredClone(defaults);
|
|
55
|
+
if (Object.keys(defaults).length === 0) return structuredClone(overrides);
|
|
56
|
+
const result = {};
|
|
57
|
+
for (const key in defaults) {
|
|
58
|
+
const defaultValue = defaults[key];
|
|
59
|
+
const overrideValue = overrides[key];
|
|
60
|
+
if (overrideValue === void 0) if (isFunction(defaultValue)) result[key] = defaultValue;
|
|
61
|
+
else result[key] = structuredClone(defaultValue);
|
|
62
|
+
else if (isPlainObject(defaultValue) && isPlainObject(overrideValue)) result[key] = mergeConfig(defaultValue, overrideValue);
|
|
63
|
+
else if (isFunction(overrideValue)) result[key] = overrideValue;
|
|
64
|
+
else result[key] = structuredClone(overrideValue);
|
|
65
|
+
}
|
|
66
|
+
return result;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
//#endregion
|
|
70
|
+
//#region src/utils.ts
|
|
71
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
72
|
+
const __dirname = dirname(__filename);
|
|
73
|
+
function joinUrl(input) {
|
|
74
|
+
const temps = Array.isArray(input) ? input : [...arguments];
|
|
75
|
+
if (temps.length === 0) return "";
|
|
76
|
+
const result = [];
|
|
77
|
+
const parts = [...temps];
|
|
78
|
+
/** 协议正则 */
|
|
79
|
+
const PROTOCOL_RE = /^[^/:]+:\/*$/;
|
|
80
|
+
const FILE_PROTOCOL_RE = /^file:\/\/\//;
|
|
81
|
+
if (PROTOCOL_RE.test(parts[0]) && parts.length > 1) {
|
|
82
|
+
parts[1] = parts[0] + parts[1];
|
|
83
|
+
parts.shift();
|
|
84
|
+
}
|
|
85
|
+
if (FILE_PROTOCOL_RE.test(parts[0])) parts[0] = parts[0].replace(/^([^/:]+):\/*/, "$1:///");
|
|
86
|
+
else parts[0] = parts[0].replace(/^([^/:]+):\/*/, "$1://");
|
|
87
|
+
parts.forEach((part, index) => {
|
|
88
|
+
if (!part) return;
|
|
89
|
+
let segment = part;
|
|
90
|
+
if (index > 0) segment = segment.replace(/^\/+/, "");
|
|
91
|
+
if (index < parts.length - 1) segment = segment.replace(/\/+$/, "");
|
|
92
|
+
else segment = segment.replace(/\/+$/, "/");
|
|
93
|
+
result.push(segment);
|
|
94
|
+
});
|
|
95
|
+
let url = result.join("/");
|
|
96
|
+
url = url.replace(/\/(\?|&|#[^!])/g, "$1");
|
|
97
|
+
const [base, ...queryParts] = url.split("?");
|
|
98
|
+
url = base + (queryParts.length ? "?" + queryParts.join("&") : "");
|
|
99
|
+
return url;
|
|
100
|
+
}
|
|
101
|
+
const isUndefined = (val) => {
|
|
102
|
+
return void 0 === val;
|
|
103
|
+
};
|
|
104
|
+
const eol = (n$1 = 1) => EOL.repeat(n$1);
|
|
105
|
+
const underline = (text$1) => {
|
|
106
|
+
return styleText(["dim", "underline"], text$1);
|
|
107
|
+
};
|
|
108
|
+
const castArray = (arr) => {
|
|
109
|
+
return Array.isArray(arr) ? arr : [arr];
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
//#endregion
|
|
113
|
+
//#region src/messages.ts
|
|
114
|
+
const n = (b) => {
|
|
115
|
+
return b ? " (dry run)" : "";
|
|
116
|
+
};
|
|
117
|
+
const MSG = {
|
|
118
|
+
INTRO: (pkgName, dryRun) => `🚀 Starting release ${yellow(pkgName)}${n(dryRun)}`,
|
|
119
|
+
OUTRO: (dryRun) => `🎉 Release finished successfully${n(dryRun)}`,
|
|
120
|
+
LOG: {
|
|
121
|
+
SHOW_VERSION: (version) => `🎉 Released ${green(version)}`,
|
|
122
|
+
SHOW_CHANGELOG: "🎉 Changelog collected",
|
|
123
|
+
CHANGELOG_EMPTY: "No commits found since last release",
|
|
124
|
+
CHANGES_EMPTY: "Working tree clean. No files changed"
|
|
125
|
+
},
|
|
126
|
+
CHECK: {
|
|
127
|
+
GIT: {
|
|
128
|
+
CHECKING: "Checking Git repository",
|
|
129
|
+
CHECKED: (name, url) => `Git remote resolved: ${dim(name)} → ${underline(url)}`
|
|
130
|
+
},
|
|
131
|
+
NPM: {
|
|
132
|
+
CHECKING: "Checking npm registry",
|
|
133
|
+
CHECKED: (registry, msg$1) => `NPM registry: ${underline(registry)}${msg$1 ? ` ${msg$1}` : ""}`
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
TASK: {
|
|
137
|
+
VERSION: {
|
|
138
|
+
START: "Bumping version",
|
|
139
|
+
END: (version) => `Version bumped to ${green(version)}`
|
|
140
|
+
},
|
|
141
|
+
NPM: {
|
|
142
|
+
START: "Publishing npm package",
|
|
143
|
+
END: "NPM package published"
|
|
144
|
+
},
|
|
145
|
+
GIT: {
|
|
146
|
+
START: "Creating git commit, tag & push",
|
|
147
|
+
END: "Git commit, tag & push completed"
|
|
148
|
+
},
|
|
149
|
+
GITHUB: {
|
|
150
|
+
START: "Creating GitHub release",
|
|
151
|
+
END: "GitHub release created"
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
PROMPT: {
|
|
155
|
+
SELECT_VERSION: "Select version bump:",
|
|
156
|
+
INPUT_VERSION: "Enter custom version:",
|
|
157
|
+
NPM_PUBLISH: (pkg$1) => `Publish to npm ${underline(`(${pkg$1})`)}?`,
|
|
158
|
+
NPM_OTP: "Enter npm one-time password (OTP):",
|
|
159
|
+
GITHUB_RELEASE: (title) => `Create a new GitHub release ${underline(`(${title})`)}?`,
|
|
160
|
+
GIT_COMMIT: (msg$1) => `Create git commit ${underline(`(${msg$1})`)}?`,
|
|
161
|
+
GIT_TAG: (msg$1) => `Create git tag ${underline(`(${msg$1})`)}?`,
|
|
162
|
+
GIT_PUSH: "Push commits and tags to remote?"
|
|
163
|
+
},
|
|
164
|
+
ERROR: {
|
|
165
|
+
GIT_REGISTRY: (name) => `[git] Repository not found for ${dim(name)}`,
|
|
166
|
+
GIT_REMOTE: (name) => `[git] Remote "${dim(name)}" not found`,
|
|
167
|
+
GITHUB_TAG_EXIT: (tag) => `[github] Release or tag "${tag}" already exists`,
|
|
168
|
+
GITHUB_TOKEN: (tokenRef) => `[github] Missing GitHub token. Please set ${yellow(tokenRef ?? "GITHUB_TOKEN")}`,
|
|
169
|
+
GITHUB_AUTH: "[github] Invalid GitHub token or insufficient permissions",
|
|
170
|
+
GITHUB_USER: "[github] User does not have permission to create releases",
|
|
171
|
+
NPM_REGISTRY: (registry) => `[npm] Unable to reach npm registry ${underline(registry)}`,
|
|
172
|
+
NPM_AUTH: `[npm] Not authenticated with npm. Please run ${yellow("npm login")} and try again.`,
|
|
173
|
+
NPM_USER: (user, name) => `[npm] User ${dim(user)} is not a collaborator of ${dim(name)}.`
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
//#endregion
|
|
178
|
+
//#region src/prompts.ts
|
|
179
|
+
const abortTask = (msg$1) => {
|
|
180
|
+
cancel(`${red("✕")} ${msg$1}`);
|
|
181
|
+
process.exit(0);
|
|
182
|
+
};
|
|
183
|
+
const abortOnCancel = (value) => {
|
|
184
|
+
if (isCancel(value)) abortTask("Operation cancelled.");
|
|
185
|
+
};
|
|
186
|
+
const abortOnError = (err) => {
|
|
187
|
+
const msg$1 = err.message.replace(/^\[(github|npm|git)]/, (a) => red(a));
|
|
188
|
+
log.message(`\n${msg$1}\n`);
|
|
189
|
+
process.exit(1);
|
|
190
|
+
};
|
|
191
|
+
const taskEnd = (msg$1) => {
|
|
192
|
+
outro(msg$1);
|
|
193
|
+
process.exit(0);
|
|
194
|
+
};
|
|
195
|
+
const msg = (prefix, msg$1) => {
|
|
196
|
+
log.message(gray`${rgb(33, 91, 184)(`[${prefix}]`)} ${msg$1}`);
|
|
197
|
+
};
|
|
198
|
+
const prompt = (msg$1) => {
|
|
199
|
+
return `${blue`?`} ${msg$1}`;
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
//#endregion
|
|
203
|
+
//#region src/version/bump.ts
|
|
204
|
+
const isPreRelease = (version) => {
|
|
205
|
+
return Boolean(prerelease(version));
|
|
206
|
+
};
|
|
207
|
+
const isValid = (version) => {
|
|
208
|
+
return Boolean(valid(version));
|
|
209
|
+
};
|
|
210
|
+
const parseVersion = (raw) => {
|
|
211
|
+
const next = isValid(raw) ? raw : coerce(raw)?.toString();
|
|
212
|
+
if (!next) return {};
|
|
213
|
+
const { prerelease: prerelease$1 } = parse(next);
|
|
214
|
+
const toPreRelease = Boolean(prerelease$1.length);
|
|
215
|
+
const [preId, preBase] = prerelease$1.map(String);
|
|
216
|
+
return {
|
|
217
|
+
next,
|
|
218
|
+
toPreRelease,
|
|
219
|
+
preId,
|
|
220
|
+
preBase
|
|
221
|
+
};
|
|
222
|
+
};
|
|
223
|
+
const getCIVersion = (current) => {
|
|
224
|
+
return inc(current, Release.PATCH);
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
//#endregion
|
|
228
|
+
//#region src/version/prompts.ts
|
|
229
|
+
let Release = /* @__PURE__ */ function(Release$1) {
|
|
230
|
+
Release$1["MAJOR"] = "major";
|
|
231
|
+
Release$1["MINOR"] = "minor";
|
|
232
|
+
Release$1["PATCH"] = "patch";
|
|
233
|
+
return Release$1;
|
|
234
|
+
}({});
|
|
235
|
+
let PreRelease = /* @__PURE__ */ function(PreRelease$1) {
|
|
236
|
+
PreRelease$1["MAJOR"] = "premajor";
|
|
237
|
+
PreRelease$1["MINOR"] = "preminor";
|
|
238
|
+
PreRelease$1["PATCH"] = "prepatch";
|
|
239
|
+
PreRelease$1["RELEASE"] = "prerelease";
|
|
240
|
+
return PreRelease$1;
|
|
241
|
+
}({});
|
|
242
|
+
let PreId = /* @__PURE__ */ function(PreId$1) {
|
|
243
|
+
PreId$1["ALPHA"] = "alpha";
|
|
244
|
+
PreId$1["BETA"] = "beta";
|
|
245
|
+
PreId$1["RC"] = "rc";
|
|
246
|
+
PreId$1["NET"] = "next";
|
|
247
|
+
PreId$1["CANARY"] = "canary";
|
|
248
|
+
PreId$1["NIGHT"] = "nightly";
|
|
249
|
+
PreId$1["DEV"] = "dev";
|
|
250
|
+
return PreId$1;
|
|
251
|
+
}({});
|
|
252
|
+
const diff = (from, to) => {
|
|
253
|
+
const a = from.split(".");
|
|
254
|
+
return to.split(".").map((v, i) => v === a[i] ? dim(v) : green(v)).join(".");
|
|
255
|
+
};
|
|
256
|
+
const runVersionPrompts = async (ctx, config) => {
|
|
257
|
+
const { pkg: { current, fromPreRelease } } = ctx;
|
|
258
|
+
const selectVersion = async () => {
|
|
259
|
+
const patch = inc(current, Release.PATCH);
|
|
260
|
+
const minor = inc(current, Release.MINOR);
|
|
261
|
+
const major = inc(current, Release.MAJOR);
|
|
262
|
+
const prerelease$1 = inc(current, PreRelease.RELEASE);
|
|
263
|
+
const prepatch = inc(current, PreRelease.PATCH, PreId.BETA);
|
|
264
|
+
const preminor = inc(current, PreRelease.MINOR, PreId.BETA);
|
|
265
|
+
const premajor = inc(current, PreRelease.MAJOR, PreId.BETA);
|
|
266
|
+
const pr = [{
|
|
267
|
+
label: "Pre-Release",
|
|
268
|
+
value: prerelease$1,
|
|
269
|
+
hint: prerelease$1
|
|
270
|
+
}];
|
|
271
|
+
const pre = [
|
|
272
|
+
{
|
|
273
|
+
label: "Pre-Patch",
|
|
274
|
+
value: prepatch,
|
|
275
|
+
hint: prepatch
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
label: "Pre-Minor",
|
|
279
|
+
value: preminor,
|
|
280
|
+
hint: preminor
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
label: "Pre-Major",
|
|
284
|
+
value: premajor,
|
|
285
|
+
hint: premajor
|
|
286
|
+
}
|
|
287
|
+
];
|
|
288
|
+
const release = [
|
|
289
|
+
{
|
|
290
|
+
label: "Patch",
|
|
291
|
+
value: patch,
|
|
292
|
+
hint: patch
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
label: "Minor",
|
|
296
|
+
value: minor,
|
|
297
|
+
hint: minor
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
label: "Major",
|
|
301
|
+
value: major,
|
|
302
|
+
hint: major
|
|
303
|
+
}
|
|
304
|
+
];
|
|
305
|
+
const custom = [{
|
|
306
|
+
label: "As-Is",
|
|
307
|
+
value: "ignore",
|
|
308
|
+
hint: current
|
|
309
|
+
}, {
|
|
310
|
+
label: "Custom",
|
|
311
|
+
value: "custom",
|
|
312
|
+
hint: "custom specified"
|
|
313
|
+
}];
|
|
314
|
+
const version = await select({
|
|
315
|
+
message: prompt(MSG.PROMPT.SELECT_VERSION),
|
|
316
|
+
options: [
|
|
317
|
+
...fromPreRelease ? pr : [],
|
|
318
|
+
...release,
|
|
319
|
+
...fromPreRelease ? [] : pre,
|
|
320
|
+
...custom
|
|
321
|
+
],
|
|
322
|
+
initialValue: fromPreRelease ? prerelease$1 : patch
|
|
323
|
+
});
|
|
324
|
+
abortOnCancel(version);
|
|
325
|
+
return version;
|
|
326
|
+
};
|
|
327
|
+
const inputVersion = async () => {
|
|
328
|
+
const version = await text({
|
|
329
|
+
message: prompt(MSG.PROMPT.INPUT_VERSION),
|
|
330
|
+
initialValue: current,
|
|
331
|
+
validate: (val) => {
|
|
332
|
+
if (!isValid(val)) return "Invalid version";
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
abortOnCancel(version);
|
|
336
|
+
return version;
|
|
337
|
+
};
|
|
338
|
+
const type = await selectVersion();
|
|
339
|
+
const next = type === "custom" ? await inputVersion() : type === "ignore" ? current : type;
|
|
340
|
+
const parsed = parseVersion(next);
|
|
341
|
+
Object.assign(ctx.pkg, { ...parsed });
|
|
342
|
+
ctx.isIncrement = gte(next, current);
|
|
343
|
+
log.message(gray`
|
|
344
|
+
from ${dim(current)}
|
|
345
|
+
to ${diff(current, next)}
|
|
346
|
+
`);
|
|
347
|
+
return { next };
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
//#endregion
|
|
351
|
+
//#region src/defaults.ts
|
|
352
|
+
const createDefaultContext = () => {
|
|
353
|
+
return {
|
|
354
|
+
ci: false,
|
|
355
|
+
release: false,
|
|
356
|
+
noNpm: false,
|
|
357
|
+
dryRun: false,
|
|
358
|
+
changelog: false,
|
|
359
|
+
increment: "",
|
|
360
|
+
isIncrement: false,
|
|
361
|
+
noGithub: false,
|
|
362
|
+
pkg: {
|
|
363
|
+
current: "",
|
|
364
|
+
next: "",
|
|
365
|
+
name: "",
|
|
366
|
+
isPrivate: false,
|
|
367
|
+
fromPreRelease: false,
|
|
368
|
+
toPreRelease: false,
|
|
369
|
+
publishConfig: {
|
|
370
|
+
access: "public",
|
|
371
|
+
registry: "https://registry.npmjs.org",
|
|
372
|
+
publicPath: ""
|
|
373
|
+
},
|
|
374
|
+
preId: PreId.BETA,
|
|
375
|
+
preBase: "0"
|
|
376
|
+
},
|
|
377
|
+
npm: {
|
|
378
|
+
username: "",
|
|
379
|
+
otp: ""
|
|
380
|
+
},
|
|
381
|
+
git: {
|
|
382
|
+
remoteName: "origin",
|
|
383
|
+
remoteUrl: "",
|
|
384
|
+
latestTag: "",
|
|
385
|
+
previousTag: "",
|
|
386
|
+
currentTag: "",
|
|
387
|
+
isCommitted: false,
|
|
388
|
+
isTagged: false
|
|
389
|
+
},
|
|
390
|
+
github: {
|
|
391
|
+
username: "",
|
|
392
|
+
repo: "",
|
|
393
|
+
changelog: "",
|
|
394
|
+
owner: "",
|
|
395
|
+
isWeb: false,
|
|
396
|
+
skipChecks: false,
|
|
397
|
+
isReleased: false,
|
|
398
|
+
releaseId: 0,
|
|
399
|
+
releaseUrl: "",
|
|
400
|
+
uploadUrl: "",
|
|
401
|
+
discussionUrl: "",
|
|
402
|
+
token: ""
|
|
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
|
+
git: {
|
|
419
|
+
commit: isCI,
|
|
420
|
+
tag: isCI,
|
|
421
|
+
push: isCI,
|
|
422
|
+
commitMessage: "chore(release): ${version}",
|
|
423
|
+
tagMessage: "Release ${version}",
|
|
424
|
+
tagName: "${version}"
|
|
425
|
+
},
|
|
426
|
+
npm: {
|
|
427
|
+
publish: isCI,
|
|
428
|
+
tag: "",
|
|
429
|
+
allowSameVersion: false
|
|
430
|
+
},
|
|
431
|
+
github: {
|
|
432
|
+
release: isCI,
|
|
433
|
+
releaseName: "Release ${version}",
|
|
434
|
+
prerelease: false,
|
|
435
|
+
draft: false,
|
|
436
|
+
tokenRef: "GITHUB_TOKEN",
|
|
437
|
+
assets: []
|
|
438
|
+
}
|
|
439
|
+
};
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
//#endregion
|
|
443
|
+
//#region src/git/commit.ts
|
|
444
|
+
const spawnAsync = (cmd, args, options) => {
|
|
445
|
+
return new Promise((resolve$1) => {
|
|
446
|
+
const { trim, error, dryRun, ...others } = options ?? {};
|
|
447
|
+
const fullCmd = [cmd, ...args].join(" ");
|
|
448
|
+
const dim$1 = (text$1) => styleText(["dim"], text$1);
|
|
449
|
+
const red$1 = (text$1) => styleText(["red"], text$1);
|
|
450
|
+
if (dryRun) {
|
|
451
|
+
console.log(`${dim$1("[dry-run]")} ${fullCmd}`);
|
|
452
|
+
return resolve$1(void 0);
|
|
453
|
+
}
|
|
454
|
+
const child = spawn(cmd, args, { ...others });
|
|
455
|
+
let stdout = "";
|
|
456
|
+
child.stdout?.setEncoding("utf-8");
|
|
457
|
+
child.stdout?.on("data", (data) => stdout += trim ? data.trim() : data);
|
|
458
|
+
let stderr = "";
|
|
459
|
+
child.stderr?.setEncoding("utf-8");
|
|
460
|
+
child.stderr?.on("data", (data) => stderr += trim ? data.trim() : data);
|
|
461
|
+
child.on("close", (code) => {
|
|
462
|
+
if (stderr) {
|
|
463
|
+
const err = `${red$1("spawnAsync")} ${dim$1(fullCmd)} ${stderr}`;
|
|
464
|
+
switch (error) {
|
|
465
|
+
case "log":
|
|
466
|
+
console.error(err);
|
|
467
|
+
break;
|
|
468
|
+
case "throw": throw new Error(err);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
resolve$1(0 === code ? stdout : void 0);
|
|
472
|
+
});
|
|
473
|
+
});
|
|
474
|
+
};
|
|
475
|
+
const runGit = async (args, trim = true) => {
|
|
476
|
+
return spawnAsync("git", args, { trim });
|
|
477
|
+
};
|
|
478
|
+
const getBranchName = () => {
|
|
479
|
+
return runGit([
|
|
480
|
+
"rev-parse",
|
|
481
|
+
"--abbrev-ref",
|
|
482
|
+
"HEAD"
|
|
483
|
+
]);
|
|
484
|
+
};
|
|
485
|
+
const getRemoteForBranch = (branch) => {
|
|
486
|
+
return runGit([
|
|
487
|
+
"config",
|
|
488
|
+
"--get",
|
|
489
|
+
`branch.${branch}.remote`
|
|
490
|
+
]);
|
|
491
|
+
};
|
|
492
|
+
const getStatus = () => {
|
|
493
|
+
return runGit([
|
|
494
|
+
"status",
|
|
495
|
+
"--short",
|
|
496
|
+
"--untracked-files=no"
|
|
497
|
+
], false);
|
|
498
|
+
};
|
|
499
|
+
const gitRestore = () => {
|
|
500
|
+
return runGit(["restore", "."]);
|
|
501
|
+
};
|
|
502
|
+
const coloredChangeset = (log$1) => {
|
|
503
|
+
const colorStatusChar = (ch) => {
|
|
504
|
+
switch (ch) {
|
|
505
|
+
case "M": return `\x1b[33m${ch}\x1b[0m`;
|
|
506
|
+
case "A": return `\x1b[32m${ch}\x1b[0m`;
|
|
507
|
+
case "D": return `\x1b[31m${ch}\x1b[0m`;
|
|
508
|
+
default: return ch;
|
|
509
|
+
}
|
|
510
|
+
};
|
|
511
|
+
const colorStatus = (status) => status.split("").map(colorStatusChar).join("");
|
|
512
|
+
return log$1.split("\n").filter(Boolean).map((line) => {
|
|
513
|
+
const status = line.slice(0, 2);
|
|
514
|
+
const file = line.slice(3);
|
|
515
|
+
return `${colorStatus(status)} ${styleText(["dim"], file)}`;
|
|
516
|
+
}).join("\n");
|
|
517
|
+
};
|
|
518
|
+
const getRemote = async () => {
|
|
519
|
+
const branch = await getBranchName();
|
|
520
|
+
const remoteName = branch && await getRemoteForBranch(branch) || "origin";
|
|
521
|
+
return {
|
|
522
|
+
remoteName,
|
|
523
|
+
remoteUrl: await runGit([
|
|
524
|
+
"remote",
|
|
525
|
+
"get-url",
|
|
526
|
+
remoteName
|
|
527
|
+
]).catch((_) => runGit([
|
|
528
|
+
"config",
|
|
529
|
+
"--get",
|
|
530
|
+
`remote.${remoteName}.url`
|
|
531
|
+
]))
|
|
532
|
+
};
|
|
533
|
+
};
|
|
534
|
+
const parseGitHubRepo = (url) => {
|
|
535
|
+
const match = url.trim().match(/github(?:\.com)?[:/](.+?)\/(.+?)(\/.+?)?(\.git)?$/);
|
|
536
|
+
return match ? match.slice(1, 3) : [];
|
|
537
|
+
};
|
|
538
|
+
const getLatestTag = async (match = "*", exclude = "*-beta.*") => {
|
|
539
|
+
return runGit([
|
|
540
|
+
"describe",
|
|
541
|
+
"--tags",
|
|
542
|
+
"--abbrev=0",
|
|
543
|
+
`--match=${match}`,
|
|
544
|
+
`--exclude=${exclude}`
|
|
545
|
+
]);
|
|
546
|
+
};
|
|
547
|
+
const getPreviousTag = async (current) => {
|
|
548
|
+
return runGit([
|
|
549
|
+
"describe",
|
|
550
|
+
"--tags",
|
|
551
|
+
"--abbrev=0",
|
|
552
|
+
`${await runGit([
|
|
553
|
+
"rev-list",
|
|
554
|
+
"--tags",
|
|
555
|
+
current || "--skip=1",
|
|
556
|
+
"--max-count=1"
|
|
557
|
+
])}^`
|
|
558
|
+
]);
|
|
559
|
+
};
|
|
560
|
+
const resolveChangelogRange = async (isIncrement) => {
|
|
561
|
+
const latestTag = await getLatestTag();
|
|
562
|
+
const previousTag = await getPreviousTag(latestTag);
|
|
563
|
+
if (!latestTag) return {
|
|
564
|
+
from: "",
|
|
565
|
+
to: "HEAD"
|
|
566
|
+
};
|
|
567
|
+
if (!isIncrement && previousTag) return {
|
|
568
|
+
from: previousTag,
|
|
569
|
+
to: `${latestTag}^1`
|
|
570
|
+
};
|
|
571
|
+
return {
|
|
572
|
+
from: latestTag,
|
|
573
|
+
to: "HEAD"
|
|
574
|
+
};
|
|
575
|
+
};
|
|
576
|
+
const getLog = async (print, isIncrement) => {
|
|
577
|
+
const cmd = ["log", `--pretty=format:${print ? "* %s (%h)" : "%s %h %H"}`];
|
|
578
|
+
const { from, to } = await resolveChangelogRange(isIncrement);
|
|
579
|
+
if (from) cmd.push(`${from}...${to}`);
|
|
580
|
+
return runGit(cmd, false);
|
|
581
|
+
};
|
|
582
|
+
const normalizeTag = (tag) => clean(tag) ?? tag;
|
|
583
|
+
const resolveTag$1 = async (ctx, config) => {
|
|
584
|
+
const { git: { tagName: tagTemplate } } = config;
|
|
585
|
+
const latestTag = await getLatestTag() ?? "";
|
|
586
|
+
const currentTag = format(tagTemplate || ((latestTag || "").match(/^v/) ? "v${version}" : "${version}"), ctx);
|
|
587
|
+
Object.assign(ctx.git, {
|
|
588
|
+
latestTag,
|
|
589
|
+
currentTag
|
|
590
|
+
});
|
|
591
|
+
return true;
|
|
592
|
+
};
|
|
593
|
+
const gitCheck = async (ctx, config) => {
|
|
594
|
+
const { pkg: { name } } = ctx;
|
|
595
|
+
if (!await isGitRepo()) throw new Error(MSG.ERROR.GIT_REGISTRY(name));
|
|
596
|
+
const { remoteName, remoteUrl } = await getRemote();
|
|
597
|
+
if (!remoteUrl) throw new Error(MSG.ERROR.GIT_REMOTE(name));
|
|
598
|
+
Object.assign(ctx.git, {
|
|
599
|
+
remoteName,
|
|
600
|
+
remoteUrl
|
|
601
|
+
});
|
|
602
|
+
parseGithubUrl(ctx);
|
|
603
|
+
};
|
|
604
|
+
const hasUpstreamBranch = async () => {
|
|
605
|
+
const branch = await runGit([
|
|
606
|
+
"for-each-ref",
|
|
607
|
+
"--format=%(upstream:short)",
|
|
608
|
+
await runGit(["symbolic-ref", "HEAD"])
|
|
609
|
+
]);
|
|
610
|
+
return Boolean(branch);
|
|
611
|
+
};
|
|
612
|
+
const getUpstreamArgs = async (remoteName) => {
|
|
613
|
+
const hasUpstream = await hasUpstreamBranch();
|
|
614
|
+
const branch = await getBranchName();
|
|
615
|
+
if (!hasUpstream) return remoteName ? [
|
|
616
|
+
"--set-upstream",
|
|
617
|
+
remoteName,
|
|
618
|
+
branch
|
|
619
|
+
] : [
|
|
620
|
+
"--set-upstream",
|
|
621
|
+
"origin",
|
|
622
|
+
branch
|
|
623
|
+
];
|
|
624
|
+
if (remoteName) return [remoteName];
|
|
625
|
+
return [];
|
|
626
|
+
};
|
|
627
|
+
const format = (str, ctx) => {
|
|
628
|
+
const { pkg: { next } } = ctx;
|
|
629
|
+
return str.replace("${version}", next);
|
|
630
|
+
};
|
|
631
|
+
const commitAndTag = async (ctx, config) => {
|
|
632
|
+
const { git: { currentTag, remoteName }, dryRun } = ctx;
|
|
633
|
+
const { git: { commit, tag, push, tagMessage, commitMessage } } = config;
|
|
634
|
+
if (!commit) return;
|
|
635
|
+
const args = dryRun ? ["--dry-run"] : [];
|
|
636
|
+
await runGit([
|
|
637
|
+
"add",
|
|
638
|
+
".",
|
|
639
|
+
"--all",
|
|
640
|
+
...args
|
|
641
|
+
]);
|
|
642
|
+
await runGit([
|
|
643
|
+
"commit",
|
|
644
|
+
"--message",
|
|
645
|
+
format(commitMessage, ctx),
|
|
646
|
+
...args
|
|
647
|
+
]);
|
|
648
|
+
if (!dryRun) Object.assign(ctx.git, { isCommitted: true });
|
|
649
|
+
if (tag) {
|
|
650
|
+
await runGit([
|
|
651
|
+
"tag",
|
|
652
|
+
"--annotate",
|
|
653
|
+
"--message",
|
|
654
|
+
format(tagMessage, ctx),
|
|
655
|
+
currentTag,
|
|
656
|
+
...args
|
|
657
|
+
]);
|
|
658
|
+
if (!dryRun) Object.assign(ctx.git, { isTagged: true });
|
|
659
|
+
}
|
|
660
|
+
if (push) await runGit([
|
|
661
|
+
"push",
|
|
662
|
+
...await getUpstreamArgs(remoteName),
|
|
663
|
+
"--follow-tags",
|
|
664
|
+
...args
|
|
665
|
+
]);
|
|
666
|
+
};
|
|
667
|
+
const gitRollback = async (ctx) => {
|
|
668
|
+
const { git: { currentTag, isCommitted, isTagged } } = ctx;
|
|
669
|
+
await gitRestore();
|
|
670
|
+
if (isTagged) await runGit([
|
|
671
|
+
"tag",
|
|
672
|
+
"--delete",
|
|
673
|
+
currentTag
|
|
674
|
+
]);
|
|
675
|
+
await runGit([
|
|
676
|
+
"reset",
|
|
677
|
+
"--hard",
|
|
678
|
+
isCommitted ? "HEAD~1" : "HEAD"
|
|
679
|
+
]);
|
|
680
|
+
};
|
|
681
|
+
|
|
682
|
+
//#endregion
|
|
683
|
+
//#region src/github/release.ts
|
|
684
|
+
const getGithubUrl = (owner, repo) => {
|
|
685
|
+
return `https://github.com/${owner}/${repo}`;
|
|
686
|
+
};
|
|
687
|
+
const truncateBody = (body) => {
|
|
688
|
+
if (body && body.length >= 124e3) return body.substring(0, 124e3) + "...";
|
|
689
|
+
return body;
|
|
690
|
+
};
|
|
691
|
+
const uploadAssets = async (ctx, config) => {
|
|
692
|
+
const { github: { assets } } = config;
|
|
693
|
+
const { github: { isReleased, owner, repo, uploadUrl, releaseId, token }, pkg: { name } } = ctx;
|
|
694
|
+
if (!assets?.length || !isReleased) return;
|
|
695
|
+
const octokit = new Octokit({ auth: token });
|
|
696
|
+
const files = await glob(assets);
|
|
697
|
+
await Promise.all(files.map((file) => {
|
|
698
|
+
return octokit.repos.uploadReleaseAsset({
|
|
699
|
+
owner,
|
|
700
|
+
repo,
|
|
701
|
+
name,
|
|
702
|
+
url: uploadUrl,
|
|
703
|
+
release_id: releaseId,
|
|
704
|
+
data: createReadStream(file),
|
|
705
|
+
headers: {
|
|
706
|
+
"content-type": contentType(file) || "application/octet-stream",
|
|
707
|
+
"content-length": statSync(file).size
|
|
708
|
+
}
|
|
709
|
+
});
|
|
710
|
+
}));
|
|
711
|
+
};
|
|
712
|
+
const createWebRelease = async (ctx, config) => {
|
|
713
|
+
const { pkg: { next, toPreRelease }, github: { owner, repo, changelog } } = ctx;
|
|
714
|
+
const { github: { prerelease: prerelease$1, releaseName } } = config;
|
|
715
|
+
const github = getGithubUrl(owner, repo);
|
|
716
|
+
const url = new URL(`${github}/releases/new`);
|
|
717
|
+
url.searchParams.set("tag", next);
|
|
718
|
+
url.searchParams.set("title", format(releaseName, ctx) ?? `Release ${next}`);
|
|
719
|
+
url.searchParams.set("body", truncateBody(changelog));
|
|
720
|
+
url.searchParams.set("prelease", String(toPreRelease || prerelease$1));
|
|
721
|
+
const isWindows = process.platform === "win32";
|
|
722
|
+
await open(url.toString(), { wait: isWindows });
|
|
723
|
+
};
|
|
724
|
+
const createCiRelease = async (ctx, config) => {
|
|
725
|
+
const { pkg: { next, toPreRelease }, github: { owner, repo, token, changelog } } = ctx;
|
|
726
|
+
const { github: { prerelease: prerelease$1, releaseName, draft } } = config;
|
|
727
|
+
const octokit = new Octokit({ auth: token });
|
|
728
|
+
try {
|
|
729
|
+
const { data } = await octokit.repos.createRelease({
|
|
730
|
+
owner,
|
|
731
|
+
repo,
|
|
732
|
+
tag_name: next,
|
|
733
|
+
name: format(releaseName, ctx) ?? `Release ${next}`,
|
|
734
|
+
body: truncateBody(changelog),
|
|
735
|
+
draft: draft ?? false,
|
|
736
|
+
prerelease: (toPreRelease || prerelease$1) ?? false
|
|
737
|
+
});
|
|
738
|
+
const { html_url, upload_url, id, discussion_url } = data;
|
|
739
|
+
Object.assign(ctx.github, {
|
|
740
|
+
isReleased: true,
|
|
741
|
+
releaseId: id,
|
|
742
|
+
releaseUrl: html_url,
|
|
743
|
+
uploadUrl: upload_url,
|
|
744
|
+
discussionUrl: discussion_url
|
|
745
|
+
});
|
|
746
|
+
} catch (e) {
|
|
747
|
+
if (e?.status === 422) throw new Error(MSG.ERROR.GITHUB_TAG_EXIT(next));
|
|
748
|
+
throw e;
|
|
749
|
+
}
|
|
750
|
+
};
|
|
751
|
+
const parseGithubUrl = (ctx) => {
|
|
752
|
+
const { git: { remoteUrl } } = ctx;
|
|
753
|
+
const [owner, repo] = parseGitHubRepo(remoteUrl);
|
|
754
|
+
Object.assign(ctx.github, {
|
|
755
|
+
owner,
|
|
756
|
+
repo
|
|
757
|
+
});
|
|
758
|
+
};
|
|
759
|
+
const createRelease = async (ctx, config) => {
|
|
760
|
+
const { github: { owner, repo, skipChecks }, ci } = ctx;
|
|
761
|
+
const { github: { tokenRef, release } } = config;
|
|
762
|
+
if (!release) return;
|
|
763
|
+
const token = tokenRef ? process.env[tokenRef] : process.env.GITHUB_TOKEN;
|
|
764
|
+
if (!token) {
|
|
765
|
+
Object.assign(ctx.github, { isWeb: true });
|
|
766
|
+
if (ci && process.env.GITHUB_ACTIONS) throw new Error(MSG.ERROR.GITHUB_TOKEN(tokenRef));
|
|
767
|
+
}
|
|
768
|
+
Object.assign(ctx.github, { token });
|
|
769
|
+
const octokit = new Octokit({ auth: token });
|
|
770
|
+
if (token && !skipChecks) if (process.env.GITHUB_ACTIONS) Object.assign(ctx.github, { username: process.env.GITHUB_ACTOR });
|
|
771
|
+
else {
|
|
772
|
+
try {
|
|
773
|
+
const { data: { login: username } } = await octokit.users.getAuthenticated();
|
|
774
|
+
Object.assign(ctx.github, { username });
|
|
775
|
+
} catch (e) {
|
|
776
|
+
throw new Error(MSG.ERROR.GITHUB_AUTH);
|
|
777
|
+
}
|
|
778
|
+
try {
|
|
779
|
+
const { github: { username } } = ctx;
|
|
780
|
+
await octokit.repos.checkCollaborator({
|
|
781
|
+
owner,
|
|
782
|
+
repo,
|
|
783
|
+
username
|
|
784
|
+
});
|
|
785
|
+
} catch (e) {
|
|
786
|
+
throw new Error(MSG.ERROR.GITHUB_USER);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
const { github: { isWeb }, dryRun } = ctx;
|
|
790
|
+
if (dryRun) return true;
|
|
791
|
+
if (isWeb) await createWebRelease(ctx, config);
|
|
792
|
+
else if (ci) {
|
|
793
|
+
await createCiRelease(ctx, config);
|
|
794
|
+
await uploadAssets(ctx, config);
|
|
795
|
+
}
|
|
796
|
+
};
|
|
797
|
+
const getGithubReleaseUrl = (ctx) => {
|
|
798
|
+
const { github: { owner, repo }, git: { currentTag } } = ctx;
|
|
799
|
+
return `${getGithubUrl(owner, repo)}/releases/tag/${normalizeTag(currentTag)}`;
|
|
800
|
+
};
|
|
801
|
+
|
|
802
|
+
//#endregion
|
|
803
|
+
//#region src/git/changetype.ts
|
|
804
|
+
const defaultTypes = [
|
|
805
|
+
{
|
|
806
|
+
type: "feat",
|
|
807
|
+
section: "✨ 新功能",
|
|
808
|
+
description: "新增功能"
|
|
809
|
+
},
|
|
810
|
+
{
|
|
811
|
+
type: "fix",
|
|
812
|
+
section: "🐛 Bug 修复",
|
|
813
|
+
description: "修复 bug"
|
|
814
|
+
},
|
|
815
|
+
{
|
|
816
|
+
type: "chore",
|
|
817
|
+
section: "🎫 其他更新",
|
|
818
|
+
description: "非功能性更改"
|
|
819
|
+
},
|
|
820
|
+
{
|
|
821
|
+
type: "docs",
|
|
822
|
+
section: "📝 文档",
|
|
823
|
+
description: "文档更新"
|
|
824
|
+
},
|
|
825
|
+
{
|
|
826
|
+
type: "style",
|
|
827
|
+
section: "💄 样式调整",
|
|
828
|
+
description: "样式或格式修改"
|
|
829
|
+
},
|
|
830
|
+
{
|
|
831
|
+
type: "refactor",
|
|
832
|
+
section: "♻ 代码重构",
|
|
833
|
+
description: "重构代码"
|
|
834
|
+
},
|
|
835
|
+
{
|
|
836
|
+
type: "perf",
|
|
837
|
+
section: "⚡ 性能优化",
|
|
838
|
+
description: "提升性能"
|
|
839
|
+
},
|
|
840
|
+
{
|
|
841
|
+
type: "test",
|
|
842
|
+
section: "✅ 测试",
|
|
843
|
+
description: "测试更新"
|
|
844
|
+
},
|
|
845
|
+
{
|
|
846
|
+
type: "revert",
|
|
847
|
+
section: "⏪ 回退",
|
|
848
|
+
description: "回退到之前版本"
|
|
849
|
+
},
|
|
850
|
+
{
|
|
851
|
+
type: "build",
|
|
852
|
+
section: "👷 构建系统",
|
|
853
|
+
description: "构建工具和系统修改"
|
|
854
|
+
},
|
|
855
|
+
{
|
|
856
|
+
type: "ci",
|
|
857
|
+
section: "🔧 CI 配置",
|
|
858
|
+
description: "持续集成配置"
|
|
859
|
+
},
|
|
860
|
+
{
|
|
861
|
+
type: "config",
|
|
862
|
+
section: "🔨 配置",
|
|
863
|
+
description: "配置文件更新"
|
|
864
|
+
},
|
|
865
|
+
{
|
|
866
|
+
type: "deps",
|
|
867
|
+
section: "🔗 依赖更新",
|
|
868
|
+
description: "依赖版本变更"
|
|
869
|
+
},
|
|
870
|
+
{
|
|
871
|
+
type: "security",
|
|
872
|
+
section: "🔒 安全修复",
|
|
873
|
+
description: "安全性更新"
|
|
874
|
+
},
|
|
875
|
+
{
|
|
876
|
+
type: "i18n",
|
|
877
|
+
section: "🌐 国际化",
|
|
878
|
+
description: "国际化相关更新"
|
|
879
|
+
},
|
|
880
|
+
{
|
|
881
|
+
type: "ux",
|
|
882
|
+
section: "🖥️ 用户体验",
|
|
883
|
+
description: "用户体验改进"
|
|
884
|
+
},
|
|
885
|
+
{
|
|
886
|
+
type: "hotfix",
|
|
887
|
+
section: "🔥 热修复",
|
|
888
|
+
description: "紧急修复"
|
|
889
|
+
},
|
|
890
|
+
{
|
|
891
|
+
type: "break",
|
|
892
|
+
section: "💥 破坏性变更",
|
|
893
|
+
description: "破坏性变更"
|
|
894
|
+
}
|
|
895
|
+
];
|
|
896
|
+
const findType = (type) => {
|
|
897
|
+
return defaultTypes.find((item) => item.type === type)?.section ?? type;
|
|
898
|
+
};
|
|
899
|
+
|
|
900
|
+
//#endregion
|
|
901
|
+
//#region src/git/changelog.ts
|
|
902
|
+
const getCommits = async (isIncrement) => {
|
|
903
|
+
return (await getLog(false, isIncrement))?.split("\n").filter(Boolean);
|
|
904
|
+
};
|
|
905
|
+
const parseCommit = (message) => {
|
|
906
|
+
const match = message.match(/^(\w+):\s(.+)\s(\w{7})\s(\w{40})$/);
|
|
907
|
+
if (!match) return;
|
|
908
|
+
return {
|
|
909
|
+
type: match[1],
|
|
910
|
+
description: match[2],
|
|
911
|
+
hash: match[3],
|
|
912
|
+
fullHash: match[4]
|
|
913
|
+
};
|
|
914
|
+
};
|
|
915
|
+
const classify = (commits) => {
|
|
916
|
+
const sections = {};
|
|
917
|
+
for (const msg$1 of commits) {
|
|
918
|
+
const parsed = parseCommit(msg$1);
|
|
919
|
+
if (!parsed) continue;
|
|
920
|
+
const { type, description, hash, fullHash } = parsed;
|
|
921
|
+
(sections[type] ??= []).push([
|
|
922
|
+
description,
|
|
923
|
+
hash,
|
|
924
|
+
fullHash
|
|
925
|
+
]);
|
|
926
|
+
}
|
|
927
|
+
return sections;
|
|
928
|
+
};
|
|
929
|
+
const renderChangelog = async (ctx, sections) => {
|
|
930
|
+
const { pkg: { next: to }, github: { owner, repo }, git: { latestTag } } = ctx;
|
|
931
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
932
|
+
const github = getGithubUrl(owner, repo);
|
|
933
|
+
const eol$1 = (n$1) => EOL.repeat(n$1);
|
|
934
|
+
let md;
|
|
935
|
+
if (!latestTag) md = `## ${to} (${date})${eol$1(2)}`;
|
|
936
|
+
else md = `## [${to}](${github}/compare/${latestTag}...${to}) (${date})${eol$1(2)}`;
|
|
937
|
+
for (const [type, items] of Object.entries(sections)) {
|
|
938
|
+
if (!items.length) continue;
|
|
939
|
+
md += `### ${findType(type)}${eol$1(2)}`;
|
|
940
|
+
for (const [desc, hash, fullHash] of items) md += `* **${type}** ${desc} [${hash}](${github}/commit/${fullHash})${eol$1(1)}`;
|
|
941
|
+
md += eol$1(2);
|
|
942
|
+
}
|
|
943
|
+
if (latestTag) {
|
|
944
|
+
md += `**Full Changelog**: ${github}/compare/${latestTag}...${to}`;
|
|
945
|
+
md += eol$1(3);
|
|
946
|
+
}
|
|
947
|
+
return md;
|
|
948
|
+
};
|
|
949
|
+
const generateChangelog = async (ctx) => {
|
|
950
|
+
const { isIncrement } = ctx;
|
|
951
|
+
const commits = await getCommits(isIncrement);
|
|
952
|
+
if (!commits) return "";
|
|
953
|
+
const changelog = await renderChangelog(ctx, classify(commits));
|
|
954
|
+
Object.assign(ctx.github, { changelog });
|
|
955
|
+
return changelog;
|
|
956
|
+
};
|
|
957
|
+
const updateChangelog = async (newContent) => {
|
|
958
|
+
const file = "CHANGELOG.md";
|
|
959
|
+
const title = `# Changelog${eol(3)}`;
|
|
960
|
+
await writeFile(file, `${(existsSync(file) && await readFile(file, "utf-8") || title).replace(/* @__PURE__ */ new RegExp(`^${title}`), `${title}${newContent}`).trim()}${eol()}`);
|
|
961
|
+
await runGit(["add", file]);
|
|
962
|
+
};
|
|
963
|
+
|
|
964
|
+
//#endregion
|
|
965
|
+
//#region src/git/prompts.ts
|
|
966
|
+
const runGitPrompts = async (ctx, config) => {
|
|
967
|
+
const { git: { currentTag }, configFileExists: cfe, dryRun } = ctx;
|
|
968
|
+
const { git: { commit, tag, push, commitMessage } } = config;
|
|
969
|
+
const logs = [];
|
|
970
|
+
const cMsg = format(commitMessage, ctx);
|
|
971
|
+
const shouldPrompt = (val) => isUndefined(val) || dryRun;
|
|
972
|
+
const { log: _, ...reset } = await group({
|
|
973
|
+
commit: () => {
|
|
974
|
+
return shouldPrompt(commit) ? confirm({ message: prompt(MSG.PROMPT.GIT_COMMIT(cMsg)) }) : (() => {
|
|
975
|
+
logs.push(yellow`git.commit`);
|
|
976
|
+
return Promise.resolve(commit);
|
|
977
|
+
})();
|
|
978
|
+
},
|
|
979
|
+
tag: () => {
|
|
980
|
+
return shouldPrompt(tag) ? confirm({ message: prompt(MSG.PROMPT.GIT_TAG(currentTag)) }) : (() => {
|
|
981
|
+
logs.push(yellow`git.tag`);
|
|
982
|
+
return Promise.resolve(tag);
|
|
983
|
+
})();
|
|
984
|
+
},
|
|
985
|
+
push: () => {
|
|
986
|
+
return shouldPrompt(push) ? confirm({ message: prompt(MSG.PROMPT.GIT_PUSH) }) : (() => {
|
|
987
|
+
logs.push(yellow`git.push`);
|
|
988
|
+
return Promise.resolve(push);
|
|
989
|
+
})();
|
|
990
|
+
},
|
|
991
|
+
log: () => {
|
|
992
|
+
if (logs.length) msg("GIT", `Using ${cfe ? "user" : "default"} config: ${logs.join(", ")}`);
|
|
993
|
+
return Promise.resolve();
|
|
994
|
+
}
|
|
995
|
+
}, { onCancel: async ({ results }) => {
|
|
996
|
+
await gitRollback(ctx);
|
|
997
|
+
abortTask("Operation cancelled.");
|
|
998
|
+
} });
|
|
999
|
+
Object.assign(config.git, reset);
|
|
1000
|
+
};
|
|
1001
|
+
|
|
1002
|
+
//#endregion
|
|
1003
|
+
//#region src/github/prompts.ts
|
|
1004
|
+
const runGithubPrompts = async (ctx, config) => {
|
|
1005
|
+
const { configFileExists: cfe, dryRun } = ctx;
|
|
1006
|
+
const { github: { release, releaseName } } = config;
|
|
1007
|
+
const logs = [];
|
|
1008
|
+
const title = format(releaseName, ctx);
|
|
1009
|
+
const shouldPrompt = (val) => isUndefined(val) || dryRun;
|
|
1010
|
+
const res = await group({
|
|
1011
|
+
release: () => shouldPrompt(release) ? confirm({ message: prompt(MSG.PROMPT.GITHUB_RELEASE(title)) }) : (() => {
|
|
1012
|
+
logs.push(yellow`github.release`);
|
|
1013
|
+
return Promise.resolve(release);
|
|
1014
|
+
})(),
|
|
1015
|
+
log: () => {
|
|
1016
|
+
if (logs.length) msg("GITHUB", `Using ${cfe ? "user" : "default"} config: ${logs.join(", ")}`);
|
|
1017
|
+
return Promise.resolve();
|
|
1018
|
+
}
|
|
1019
|
+
}, { onCancel: async ({ results }) => {
|
|
1020
|
+
await gitRollback(ctx);
|
|
1021
|
+
abortTask("Operation cancelled.");
|
|
1022
|
+
} });
|
|
1023
|
+
Object.assign(config.github, { release: res.release });
|
|
1024
|
+
return res;
|
|
1025
|
+
};
|
|
1026
|
+
|
|
1027
|
+
//#endregion
|
|
1028
|
+
//#region src/npm/prompts.ts
|
|
1029
|
+
const runNpmPublishPrompts = async (ctx, config) => {
|
|
1030
|
+
const { pkg: { name, next }, configFileExists: cfe, dryRun } = ctx;
|
|
1031
|
+
const { npm: { publish } } = config;
|
|
1032
|
+
const logs = [];
|
|
1033
|
+
const target = `${name}@${next}`;
|
|
1034
|
+
const shouldPrompt = (val) => isUndefined(val) || dryRun;
|
|
1035
|
+
const res = await group({
|
|
1036
|
+
publish: () => shouldPrompt(publish) ? confirm({ message: prompt(MSG.PROMPT.NPM_PUBLISH(target)) }) : (() => {
|
|
1037
|
+
logs.push(yellow`npm.publish`);
|
|
1038
|
+
return Promise.resolve(publish);
|
|
1039
|
+
})(),
|
|
1040
|
+
log: () => {
|
|
1041
|
+
if (logs.length) msg("NPM", `Using ${cfe ? "user" : "default"} config: ${logs.join(", ")}`);
|
|
1042
|
+
return Promise.resolve();
|
|
1043
|
+
}
|
|
1044
|
+
}, { onCancel: async ({ results }) => {
|
|
1045
|
+
await gitRollback(ctx);
|
|
1046
|
+
abortTask("Operation cancelled.");
|
|
1047
|
+
} });
|
|
1048
|
+
Object.assign(config.npm, { publish: res.publish });
|
|
1049
|
+
return res;
|
|
1050
|
+
};
|
|
1051
|
+
const runNpmOptPrompts = async (ctx, config) => {
|
|
1052
|
+
const res = await group({ otp: () => text({ message: MSG.PROMPT.NPM_OTP }) }, { onCancel: async ({ results }) => {
|
|
1053
|
+
await gitRollback(ctx);
|
|
1054
|
+
abortTask("Operation cancelled.");
|
|
1055
|
+
} });
|
|
1056
|
+
Object.assign(ctx.npm, { otp: res.otp });
|
|
1057
|
+
return res;
|
|
1058
|
+
};
|
|
1059
|
+
|
|
1060
|
+
//#endregion
|
|
1061
|
+
//#region src/npm/publish.ts
|
|
1062
|
+
const DEFAULT_TAG = "latest";
|
|
1063
|
+
const DEFAULT_TAG_PRERELEASE = "next";
|
|
1064
|
+
const runNpm = (args) => {
|
|
1065
|
+
return execAsync(["npm", ...args].join(" "));
|
|
1066
|
+
};
|
|
1067
|
+
const getRegistry = (publishConfig = {}) => {
|
|
1068
|
+
return (publishConfig.registry ? [publishConfig.registry] : Object.keys(publishConfig).filter((key) => key.endsWith("registry")).map((key) => publishConfig[key]))[0];
|
|
1069
|
+
};
|
|
1070
|
+
const isRegistryUp = async (registry) => {
|
|
1071
|
+
return void 0 !== await runNpm(["ping", ...registry ? [`--registry ${registry}`] : []]);
|
|
1072
|
+
};
|
|
1073
|
+
const isAuthenticated = (registry) => {
|
|
1074
|
+
return runNpm(["whoami", ...registry ? [`--registry ${registry}`] : []]);
|
|
1075
|
+
};
|
|
1076
|
+
const getLatestRegistryVersion = (name, tag, registry) => {
|
|
1077
|
+
const registryArg = registry ? [`--registry ${registry}`] : [];
|
|
1078
|
+
return runNpm([
|
|
1079
|
+
"show",
|
|
1080
|
+
`${name}@${tag}`,
|
|
1081
|
+
"version",
|
|
1082
|
+
...registryArg
|
|
1083
|
+
]);
|
|
1084
|
+
};
|
|
1085
|
+
const getRegistryTags = async (name, registry) => {
|
|
1086
|
+
const res = await runNpm([
|
|
1087
|
+
"view",
|
|
1088
|
+
name,
|
|
1089
|
+
"dist-tags",
|
|
1090
|
+
"--json",
|
|
1091
|
+
...registry ? [`--registry ${registry}`] : []
|
|
1092
|
+
]);
|
|
1093
|
+
return Object.keys(JSON.parse(res || "{}"));
|
|
1094
|
+
};
|
|
1095
|
+
const bump = (version, allowSameVersion, args = []) => {
|
|
1096
|
+
allowSameVersion && args.push(`--allow-same-version`);
|
|
1097
|
+
return runNpm([
|
|
1098
|
+
"version",
|
|
1099
|
+
version,
|
|
1100
|
+
"--no-git-tag-version",
|
|
1101
|
+
"--workspaces=false",
|
|
1102
|
+
...args
|
|
1103
|
+
]);
|
|
1104
|
+
};
|
|
1105
|
+
const guessPreReleaseTag = async (name) => {
|
|
1106
|
+
return (await getRegistryTags(name)).filter((tag) => DEFAULT_TAG !== tag)[0] ?? DEFAULT_TAG_PRERELEASE;
|
|
1107
|
+
};
|
|
1108
|
+
const resolveTag = async (ctx, config) => {
|
|
1109
|
+
const { npm: { tag } } = config;
|
|
1110
|
+
const { pkg: { name, current } } = ctx;
|
|
1111
|
+
const { toPreRelease, preId } = parseVersion(current);
|
|
1112
|
+
if (!toPreRelease) return DEFAULT_TAG;
|
|
1113
|
+
else return tag || preId || await guessPreReleaseTag(name);
|
|
1114
|
+
};
|
|
1115
|
+
const isCollaborator = async (ctx) => {
|
|
1116
|
+
const { name, publishConfig } = ctx.pkg;
|
|
1117
|
+
const registry = getRegistry(publishConfig);
|
|
1118
|
+
const registryArg = registry ? [`--registry ${registry}`] : [];
|
|
1119
|
+
let npmVersion = await runNpm(["--version"]);
|
|
1120
|
+
let accessCommand;
|
|
1121
|
+
if (gt(npmVersion, "9.0.0")) accessCommand = [
|
|
1122
|
+
"access",
|
|
1123
|
+
"list",
|
|
1124
|
+
"collaborators",
|
|
1125
|
+
"--json"
|
|
1126
|
+
];
|
|
1127
|
+
else accessCommand = ["access", "ls-collaborators"];
|
|
1128
|
+
const res = await runNpm([
|
|
1129
|
+
...accessCommand,
|
|
1130
|
+
name,
|
|
1131
|
+
...registryArg
|
|
1132
|
+
]);
|
|
1133
|
+
if (res) {
|
|
1134
|
+
const permissions = JSON.parse(res ?? "{}")[ctx.npm.username];
|
|
1135
|
+
return permissions && permissions.includes("write");
|
|
1136
|
+
}
|
|
1137
|
+
log.error(`Unable to verify if user ${ctx.npm.username} is a collaborator for ${name}.`);
|
|
1138
|
+
};
|
|
1139
|
+
const getPackageUrl = (ctx) => {
|
|
1140
|
+
const { name } = ctx.pkg;
|
|
1141
|
+
return joinUrl([
|
|
1142
|
+
"https://www.npmjs.com",
|
|
1143
|
+
"/package",
|
|
1144
|
+
name
|
|
1145
|
+
]);
|
|
1146
|
+
};
|
|
1147
|
+
const npmCheck = async (ctx, config) => {
|
|
1148
|
+
const { pkg: { name, publishConfig }, npm: { username } } = ctx;
|
|
1149
|
+
const registry = getRegistry(publishConfig);
|
|
1150
|
+
const tag = await resolveTag(ctx, config);
|
|
1151
|
+
const getUser = async () => {
|
|
1152
|
+
const username$1 = await isAuthenticated(registry);
|
|
1153
|
+
if (!username$1) return false;
|
|
1154
|
+
Object.assign(ctx.npm, { username: username$1 });
|
|
1155
|
+
return true;
|
|
1156
|
+
};
|
|
1157
|
+
if (!await isRegistryUp()) throw new Error(MSG.ERROR.NPM_REGISTRY(registry));
|
|
1158
|
+
if (!await getUser()) throw new Error(MSG.ERROR.NPM_AUTH);
|
|
1159
|
+
const latestPublish = await getLatestRegistryVersion(name, tag, registry);
|
|
1160
|
+
if (latestPublish) {
|
|
1161
|
+
if (!await isCollaborator(ctx)) throw new Error(MSG.ERROR.NPM_USER(username, name));
|
|
1162
|
+
}
|
|
1163
|
+
return dim(latestPublish ? `(latest is ${latestPublish})` : "(new publish)");
|
|
1164
|
+
};
|
|
1165
|
+
const isOtpError = (err) => /one-time password|otp/i.test(err.message);
|
|
1166
|
+
const publishNpm = async (ctx, config) => {
|
|
1167
|
+
const { dryRun, npm: { otp } } = ctx;
|
|
1168
|
+
const { npm: { publish, tag } } = config;
|
|
1169
|
+
if (!publish) return;
|
|
1170
|
+
const args = ["publish"];
|
|
1171
|
+
if (tag) args.push("--tag", tag);
|
|
1172
|
+
if (otp) args.push("--otp", otp);
|
|
1173
|
+
if (dryRun) args.push("--dry-run");
|
|
1174
|
+
return runNpm(args);
|
|
1175
|
+
};
|
|
1176
|
+
|
|
1177
|
+
//#endregion
|
|
1178
|
+
//#region src/action.ts
|
|
1179
|
+
const info = (msg$1) => {
|
|
1180
|
+
console.log(`${rgb(33, 91, 184)(`ℹ`)} ${gray(msg$1)}`);
|
|
1181
|
+
};
|
|
1182
|
+
const success = (msg$1, dryRun) => {
|
|
1183
|
+
return `${(dryRun ? rgb(123, 115, 66) : green)("✔")} ${msg$1}`;
|
|
1184
|
+
};
|
|
1185
|
+
const runLifeCycleHook = async (hooks, key, dryRun) => {
|
|
1186
|
+
const handler = hooks[key];
|
|
1187
|
+
if (typeof handler === "function") {
|
|
1188
|
+
if (!dryRun) await handler();
|
|
1189
|
+
log.success(success(`${dim`run`} ${inspect(handler)}`, dryRun));
|
|
1190
|
+
return;
|
|
1191
|
+
}
|
|
1192
|
+
return Promise.allSettled(castArray(handler).map(async (hook) => {
|
|
1193
|
+
if (!dryRun) await execAsync(hook).catch(console.error);
|
|
1194
|
+
handler && log.success(success(hook.replace("echo", dim("echo")), dryRun));
|
|
1195
|
+
}));
|
|
1196
|
+
};
|
|
1197
|
+
var Action = class {
|
|
1198
|
+
async handle(cmdArgs, options) {
|
|
1199
|
+
const { version: cVersion, name: cName } = readJsonFile(join(__dirname$1, "../package.json"));
|
|
1200
|
+
info(`${cName} ${dim(`v${cVersion}`)} `);
|
|
1201
|
+
const { otp, ...others } = options;
|
|
1202
|
+
const { showChangelog, showRelease, ci, dryRun } = Object.fromEntries(Object.entries(others).map(([k, v]) => [k, Boolean(v)]));
|
|
1203
|
+
const { version: pkgVersion, private: pkgPrivate, name: pkgName, publishConfig = {} } = readJsonFile("package.json");
|
|
1204
|
+
const nextVersion = inc(pkgVersion, cmdArgs) ?? "";
|
|
1205
|
+
const local = await resolveConfig(process.cwd(), (path$1) => {
|
|
1206
|
+
info(`config file: ${underline(path$1)}`);
|
|
1207
|
+
});
|
|
1208
|
+
const config = mergeConfig(createDefaultConfig(ci ? true : void 0), local);
|
|
1209
|
+
const ctx = mergeConfig(createDefaultContext(), {
|
|
1210
|
+
configFileExists: Object.keys(local).length > 0,
|
|
1211
|
+
dryRun,
|
|
1212
|
+
npm: { otp: String(otp) },
|
|
1213
|
+
increment: cmdArgs,
|
|
1214
|
+
isIncrement: true,
|
|
1215
|
+
ci: [
|
|
1216
|
+
ci,
|
|
1217
|
+
showChangelog,
|
|
1218
|
+
showRelease,
|
|
1219
|
+
process.env.GITHUB_ACTIONS,
|
|
1220
|
+
process.env.CI
|
|
1221
|
+
].some((k) => Boolean(k)),
|
|
1222
|
+
pkg: {
|
|
1223
|
+
name: pkgName,
|
|
1224
|
+
isPrivate: pkgPrivate,
|
|
1225
|
+
fromPreRelease: isPreRelease(pkgVersion),
|
|
1226
|
+
current: pkgVersion,
|
|
1227
|
+
next: "",
|
|
1228
|
+
toPreRelease: false,
|
|
1229
|
+
publishConfig: { ...publishConfig }
|
|
1230
|
+
}
|
|
1231
|
+
});
|
|
1232
|
+
console.log();
|
|
1233
|
+
intro(MSG.INTRO(pkgName, dryRun));
|
|
1234
|
+
const { hooks } = config;
|
|
1235
|
+
await tasks([{
|
|
1236
|
+
title: MSG.CHECK.GIT.CHECKING,
|
|
1237
|
+
task: async () => {
|
|
1238
|
+
await gitCheck(ctx, config);
|
|
1239
|
+
const { git: { remoteName, remoteUrl } } = ctx;
|
|
1240
|
+
return success(MSG.CHECK.GIT.CHECKED(remoteName, remoteUrl), dryRun);
|
|
1241
|
+
}
|
|
1242
|
+
}, {
|
|
1243
|
+
title: MSG.CHECK.NPM.CHECKING,
|
|
1244
|
+
task: async () => {
|
|
1245
|
+
const msg$1 = await npmCheck(ctx, config);
|
|
1246
|
+
const { pkg: { publishConfig: { registry } } } = ctx;
|
|
1247
|
+
return success(MSG.CHECK.NPM.CHECKED(registry, msg$1), dryRun);
|
|
1248
|
+
}
|
|
1249
|
+
}]).catch(abortOnError);
|
|
1250
|
+
{
|
|
1251
|
+
const { pkg: { current, next }, isIncrement, ci: ci$1 } = ctx;
|
|
1252
|
+
if (ci$1) {
|
|
1253
|
+
if (!next && isIncrement) {
|
|
1254
|
+
const next$1 = nextVersion || getCIVersion(current);
|
|
1255
|
+
if (next$1) {
|
|
1256
|
+
const parsed = parseVersion(next$1);
|
|
1257
|
+
Object.assign(ctx.pkg, { ...parsed });
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
if (showRelease) {
|
|
1261
|
+
const { pkg: { next: next$1 } } = ctx;
|
|
1262
|
+
taskEnd(MSG.LOG.SHOW_VERSION(next$1));
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
{
|
|
1267
|
+
const logStr = await getLog(true, ctx.isIncrement);
|
|
1268
|
+
if (logStr) {
|
|
1269
|
+
msg("GIT", `Changelog:${eol(2)}` + dim(logStr));
|
|
1270
|
+
if (showChangelog) taskEnd(MSG.LOG.SHOW_CHANGELOG);
|
|
1271
|
+
} else msg("GIT", MSG.LOG.CHANGELOG_EMPTY);
|
|
1272
|
+
}
|
|
1273
|
+
{
|
|
1274
|
+
const { pkg: { next }, isIncrement } = ctx;
|
|
1275
|
+
if (!next && isIncrement) await runVersionPrompts(ctx, config);
|
|
1276
|
+
await runLifeCycleHook(hooks, "before:bump", dryRun);
|
|
1277
|
+
await tasks([{
|
|
1278
|
+
title: MSG.TASK.VERSION.START,
|
|
1279
|
+
task: async () => {
|
|
1280
|
+
const { pkg: { next: next$1 }, isIncrement: isIncrement$1 } = ctx;
|
|
1281
|
+
await bump(next$1, isIncrement$1);
|
|
1282
|
+
await resolveTag$1(ctx, config);
|
|
1283
|
+
await updateChangelog(await generateChangelog(ctx));
|
|
1284
|
+
return success(MSG.TASK.VERSION.END(next$1), dryRun);
|
|
1285
|
+
}
|
|
1286
|
+
}]).catch(abortOnError);
|
|
1287
|
+
await runLifeCycleHook(hooks, "after:bump", dryRun);
|
|
1288
|
+
}
|
|
1289
|
+
{
|
|
1290
|
+
const changeset = await getStatus();
|
|
1291
|
+
if (changeset) msg("GIT", `Changes:${eol(2)}` + coloredChangeset(changeset));
|
|
1292
|
+
else msg("GIT", MSG.LOG.CHANGES_EMPTY);
|
|
1293
|
+
}
|
|
1294
|
+
{
|
|
1295
|
+
const { pkg: { isPrivate }, ci: ci$1 } = ctx;
|
|
1296
|
+
if (!isPrivate) {
|
|
1297
|
+
await runNpmPublishPrompts(ctx, config);
|
|
1298
|
+
await runLifeCycleHook(hooks, "before:publish", dryRun);
|
|
1299
|
+
await tasks([{
|
|
1300
|
+
title: MSG.TASK.NPM.START,
|
|
1301
|
+
task: async () => {
|
|
1302
|
+
await publishNpm(ctx, config).catch(async (err) => {
|
|
1303
|
+
if (isOtpError(err) && !ci$1) {
|
|
1304
|
+
await runNpmOptPrompts(ctx, config);
|
|
1305
|
+
await publishNpm(ctx, config);
|
|
1306
|
+
} else throw err;
|
|
1307
|
+
});
|
|
1308
|
+
const url = getPackageUrl(ctx);
|
|
1309
|
+
return success(MSG.TASK.NPM.END + `: ${url}`, dryRun);
|
|
1310
|
+
}
|
|
1311
|
+
}]).catch(abortOnError);
|
|
1312
|
+
await runLifeCycleHook(hooks, "after:publish", dryRun);
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
await runGitPrompts(ctx, config);
|
|
1316
|
+
await runLifeCycleHook(hooks, "before:push", dryRun);
|
|
1317
|
+
await tasks([{
|
|
1318
|
+
title: MSG.TASK.GIT.START,
|
|
1319
|
+
task: async () => {
|
|
1320
|
+
await commitAndTag(ctx, config);
|
|
1321
|
+
return success(MSG.TASK.GIT.END, dryRun);
|
|
1322
|
+
}
|
|
1323
|
+
}]).catch(abortOnError);
|
|
1324
|
+
await runLifeCycleHook(hooks, "after:push", dryRun);
|
|
1325
|
+
await runGithubPrompts(ctx, config);
|
|
1326
|
+
await runLifeCycleHook(hooks, "before:release", dryRun);
|
|
1327
|
+
await tasks([{
|
|
1328
|
+
title: MSG.TASK.GITHUB.START,
|
|
1329
|
+
task: async () => {
|
|
1330
|
+
await createRelease(ctx, config);
|
|
1331
|
+
const url = getGithubReleaseUrl(ctx);
|
|
1332
|
+
return success(MSG.TASK.GITHUB.END + `: ${url}`, dryRun);
|
|
1333
|
+
}
|
|
1334
|
+
}]).catch(abortOnError);
|
|
1335
|
+
await runLifeCycleHook(hooks, "after:release", dryRun);
|
|
1336
|
+
if (dryRun) await gitRollback(ctx);
|
|
1337
|
+
taskEnd(MSG.OUTRO(dryRun));
|
|
1338
|
+
}
|
|
1339
|
+
};
|
|
1340
|
+
|
|
1341
|
+
//#endregion
|
|
1342
|
+
//#region src/cli.ts
|
|
1343
|
+
const pkg = readJsonFile(join(__dirname, "..", "package.json"));
|
|
1344
|
+
program.name(pkg.name).description(pkg.description).version(pkg.version, "-v, --version", "Output the current version.").usage("[ARGUMENT] [OPTION]...").argument("[argument]", "patch|minor|major").option("-n, --dry-run", "Report actions that would be performed without writing out results.", false).option("--ci", "CI mode", false).option("--otp", "npm publish otp", "").option("--show-changelog", "Skip npm publish", false).option("--show-release", "Skip github release", false).action(async (argName, options) => {
|
|
1345
|
+
await new Action().handle(argName, options);
|
|
1346
|
+
}).helpOption("-h, --help", "Output usage information.").parse(process.argv);
|
|
1347
|
+
program.on("command:*", () => {
|
|
1348
|
+
console.error(`\n${red`Error`} Invalid command: ${red`%s`}`, program.args.join(" "));
|
|
1349
|
+
console.log(`See ${red`--help`} for a list of available commands.\n`);
|
|
1350
|
+
process.exit(1);
|
|
1351
|
+
});
|
|
1352
|
+
|
|
1353
|
+
//#endregion
|
|
1354
|
+
export { };
|