@peiyanlu/release 0.0.3 → 0.0.5
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 +147 -20
- package/dist/cli.mjs +302 -452
- package/dist/index.d.mts +35 -43
- package/package.json +14 -11
- package/dist/index.cjs +0 -26
- package/dist/index.d.cts +0 -166
package/dist/cli.mjs
CHANGED
|
@@ -1,21 +1,26 @@
|
|
|
1
1
|
import path, { join, resolve } from "node:path";
|
|
2
2
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
3
|
-
import { eol, execAsync, isGitRepo, parseGitHubRepo, readJsonFile,
|
|
4
|
-
import { dim,
|
|
3
|
+
import { bumpPackageVersion, coloredChangeset, eol, execAsync, getAllRemotes, getAuthenticatedUser, getCurrentBranch, getGithubReleaseUrl, getGithubUrl, getLatestTag, getLog, getPackageInfo, getPackageUrl, getPublishedVersion, getRemote, getStatus, gitAddAll, gitAddTracked, gitCommit, gitTagAnnotated, hasWriteAccess, isGitRepo, isWorkingDirClean, parseGitHubRepo, pingRegistry, publishPackage, pushBranch, pushTag, readJsonFile, resolveChangelogRange, runGit } from "@peiyanlu/cli-utils";
|
|
4
|
+
import { dim, green, red, rgb, underline, yellow } from "ansis";
|
|
5
5
|
import { program } from "commander";
|
|
6
|
+
import { createReadStream, createWriteStream, existsSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
6
7
|
import { cancel, confirm, group, intro, isCancel, log, outro, select, tasks, text } from "@clack/prompts";
|
|
7
|
-
import {
|
|
8
|
+
import { publint } from "publint";
|
|
9
|
+
import { formatMessage } from "publint/utils";
|
|
10
|
+
import { coerce, eq, inc, lt, parse, prerelease, valid } from "semver";
|
|
8
11
|
import { castArray, isPlainObject, isUndefined } from "@peiyanlu/ts-utils";
|
|
9
|
-
import { createReadStream, existsSync, statSync } from "node:fs";
|
|
10
12
|
import { spawnSync } from "node:child_process";
|
|
11
|
-
import { inspect, styleText } from "node:util";
|
|
12
13
|
import { Octokit } from "@octokit/rest";
|
|
13
14
|
import { contentType } from "mime-types";
|
|
14
15
|
import open from "open";
|
|
15
16
|
import { glob } from "tinyglobby";
|
|
16
|
-
import {
|
|
17
|
+
import { inspect } from "node:util";
|
|
18
|
+
import { ConventionalChangelog } from "conventional-changelog";
|
|
19
|
+
import createPreset from "conventional-changelog-conventionalcommits";
|
|
20
|
+
import { writeFile } from "node:fs/promises";
|
|
21
|
+
import { finished } from "node:stream/promises";
|
|
17
22
|
|
|
18
|
-
//#region node_modules/.pnpm/tsdown@0.18.
|
|
23
|
+
//#region node_modules/.pnpm/tsdown@0.18.4_publint@0.3.17_typescript@5.9.3/node_modules/tsdown/esm-shims.js
|
|
19
24
|
const getFilename = () => fileURLToPath(import.meta.url);
|
|
20
25
|
const getDirname = () => path.dirname(getFilename());
|
|
21
26
|
const __dirname = /* @__PURE__ */ getDirname();
|
|
@@ -26,10 +31,8 @@ const findConfigFile = (cwd = process.cwd()) => {
|
|
|
26
31
|
for (const ext of [
|
|
27
32
|
"ts",
|
|
28
33
|
"mts",
|
|
29
|
-
"cts",
|
|
30
34
|
"js",
|
|
31
|
-
"mjs"
|
|
32
|
-
"cjs"
|
|
35
|
+
"mjs"
|
|
33
36
|
]) {
|
|
34
37
|
const path$1 = resolve(cwd, `release.config.${ext}`);
|
|
35
38
|
if (existsSync(path$1)) return path$1;
|
|
@@ -41,11 +44,16 @@ const loadConfig = async (file) => {
|
|
|
41
44
|
const temp = mod.default ?? mod;
|
|
42
45
|
return typeof temp === "function" ? temp() : temp;
|
|
43
46
|
};
|
|
44
|
-
const resolveConfig = async (cwd
|
|
47
|
+
const resolveConfig = async (cwd) => {
|
|
45
48
|
const configFile = findConfigFile(cwd);
|
|
46
|
-
if (!configFile) return {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
+
if (!configFile) return {
|
|
50
|
+
configPath: "",
|
|
51
|
+
config: {}
|
|
52
|
+
};
|
|
53
|
+
return {
|
|
54
|
+
configPath: configFile,
|
|
55
|
+
config: await loadConfig(configFile)
|
|
56
|
+
};
|
|
49
57
|
};
|
|
50
58
|
const isFunction = (v) => typeof v === "function";
|
|
51
59
|
const mergeConfig = (defaults, overrides) => {
|
|
@@ -69,8 +77,8 @@ const mergeConfig = (defaults, overrides) => {
|
|
|
69
77
|
const n = (b) => b ? "(dry run)" : "";
|
|
70
78
|
const t = () => green(`${Math.floor(process.uptime())}s`);
|
|
71
79
|
const MSG = {
|
|
72
|
-
INTRO: (
|
|
73
|
-
OUTRO: (dryRun) => `🎉 Release finished successfully in ${t()} ${n(dryRun)}
|
|
80
|
+
INTRO: (dryRun) => `🚀 Starting release ${n(dryRun)}`,
|
|
81
|
+
OUTRO: (dryRun) => `🎉 Release finished successfully in ${t()} ${n(dryRun)}`,
|
|
74
82
|
LOG: {
|
|
75
83
|
SHOW_VERSION: (version) => `🎉 Released ${green(version)}`,
|
|
76
84
|
SHOW_CHANGELOG: "🎉 Changelog collected",
|
|
@@ -80,11 +88,11 @@ const MSG = {
|
|
|
80
88
|
CHECK: {
|
|
81
89
|
GIT: {
|
|
82
90
|
CHECKING: "Checking Git repository",
|
|
83
|
-
CHECKED: (name, url) => `Git remote resolved: ${dim(name)} → ${underline(dim(url))}`
|
|
91
|
+
CHECKED: (name, url) => `Git remote resolved: ${url ? `${dim(name)} → ${underline(dim(url))}` : "not found"}`
|
|
84
92
|
},
|
|
85
93
|
NPM: {
|
|
86
94
|
CHECKING: "Checking npm registry",
|
|
87
|
-
CHECKED: (registry, msg$1) => `NPM registry: ${underline(dim(registry))}
|
|
95
|
+
CHECKED: (registry, msg$1) => `NPM registry: ${underline(dim(registry))} ${underline(dim(msg$1))}`
|
|
88
96
|
}
|
|
89
97
|
},
|
|
90
98
|
TASK: {
|
|
@@ -110,6 +118,7 @@ const MSG = {
|
|
|
110
118
|
}
|
|
111
119
|
},
|
|
112
120
|
PROMPT: {
|
|
121
|
+
SELECT_PACKAGE: "Select package release:",
|
|
113
122
|
SELECT_VERSION: "Select version bump:",
|
|
114
123
|
INPUT_VERSION: "Enter custom version:",
|
|
115
124
|
NPM_PUBLISH: (pkg$1) => `Publish to npm ${underline(dim`(${pkg$1})`)}?`,
|
|
@@ -130,14 +139,20 @@ const MSG = {
|
|
|
130
139
|
NPM_REGISTRY: (registry) => `[npm] Unable to reach npm registry ${underline(dim(registry))}`,
|
|
131
140
|
NPM_AUTH: `[npm] Not authenticated with npm. Please run ${yellow("npm login")} and try again.`,
|
|
132
141
|
NPM_USER: (user, name) => `[npm] User ${dim(user)} is not a collaborator of ${dim(name)}.`
|
|
142
|
+
},
|
|
143
|
+
ABORT: {
|
|
144
|
+
CANCEL: "Operation cancelled",
|
|
145
|
+
MONOREPO_CI_NO_PACKAGE: "CI mode requires a target package in monorepo. Please specify it via \"--package <pkg>\"",
|
|
146
|
+
MONOREPO_NO_PACKAGES: "Monorepo detected, but no packages found. Please configure \"config.packages\""
|
|
147
|
+
},
|
|
148
|
+
INFO: {
|
|
149
|
+
TOOL: (name, version) => `${name} ${dim(`v${version}`)}`,
|
|
150
|
+
CONFIG: (path$1) => `config file: ${underline(dim(path$1 || "not found"))}`
|
|
133
151
|
}
|
|
134
152
|
};
|
|
135
153
|
|
|
136
154
|
//#endregion
|
|
137
155
|
//#region src/github/release.ts
|
|
138
|
-
const getGithubUrl = (owner, repo) => {
|
|
139
|
-
return `https://github.com/${owner}/${repo}`;
|
|
140
|
-
};
|
|
141
156
|
const truncateBody = (body) => {
|
|
142
157
|
if (body && body.length >= 124e3) return body.substring(0, 124e3) + "...";
|
|
143
158
|
return body;
|
|
@@ -164,28 +179,28 @@ const uploadAssets = async (ctx, config) => {
|
|
|
164
179
|
}));
|
|
165
180
|
};
|
|
166
181
|
const createWebRelease = async (ctx, config) => {
|
|
167
|
-
const { pkg: {
|
|
182
|
+
const { pkg: { toPreRelease }, github: { owner, repo, changelog, releaseName }, git: { currentTag } } = ctx;
|
|
168
183
|
const { github: { prerelease: prerelease$1 } } = config;
|
|
169
184
|
const github = getGithubUrl(owner, repo);
|
|
170
185
|
const url = new URL(`${github}/releases/new`);
|
|
171
|
-
url.searchParams.set("tag",
|
|
172
|
-
url.searchParams.set("title", releaseName
|
|
186
|
+
url.searchParams.set("tag", currentTag);
|
|
187
|
+
url.searchParams.set("title", releaseName);
|
|
173
188
|
url.searchParams.set("body", truncateBody(changelog));
|
|
174
189
|
url.searchParams.set("prelease", String(toPreRelease || prerelease$1));
|
|
175
190
|
const isWindows = process.platform === "win32";
|
|
176
191
|
await open(url.toString(), { wait: isWindows });
|
|
177
192
|
};
|
|
178
193
|
const createCiRelease = async (ctx, config) => {
|
|
179
|
-
const { pkg: {
|
|
194
|
+
const { pkg: { toPreRelease }, github: { owner, repo, token, changelog, releaseName }, git: { currentTag } } = ctx;
|
|
180
195
|
const { github: { prerelease: prerelease$1, draft, autoGenerate } } = config;
|
|
181
196
|
const octokit = new Octokit({ auth: token });
|
|
182
197
|
try {
|
|
183
198
|
const { data } = await octokit.repos.createRelease({
|
|
184
199
|
owner,
|
|
185
200
|
repo,
|
|
186
|
-
tag_name:
|
|
201
|
+
tag_name: currentTag,
|
|
187
202
|
target_commitish: void 0,
|
|
188
|
-
name: releaseName
|
|
203
|
+
name: releaseName,
|
|
189
204
|
body: autoGenerate ? "" : truncateBody(changelog),
|
|
190
205
|
draft: draft ?? false,
|
|
191
206
|
prerelease: (toPreRelease || prerelease$1) ?? false,
|
|
@@ -202,7 +217,7 @@ const createCiRelease = async (ctx, config) => {
|
|
|
202
217
|
discussionUrl: discussion_url
|
|
203
218
|
});
|
|
204
219
|
} catch (e) {
|
|
205
|
-
if (e?.status === 422) throw new Error(MSG.ERROR.GITHUB_TAG_EXIT(
|
|
220
|
+
if (e?.status === 422) throw new Error(MSG.ERROR.GITHUB_TAG_EXIT(currentTag));
|
|
206
221
|
throw e;
|
|
207
222
|
}
|
|
208
223
|
};
|
|
@@ -252,133 +267,9 @@ const createRelease = async (ctx, config) => {
|
|
|
252
267
|
await uploadAssets(ctx, config);
|
|
253
268
|
}
|
|
254
269
|
};
|
|
255
|
-
const getGithubReleaseUrl = (ctx) => {
|
|
256
|
-
const { github: { owner, repo }, git: { currentTag } } = ctx;
|
|
257
|
-
return `${getGithubUrl(owner, repo)}/releases/tag/${normalizeTag(currentTag)}`;
|
|
258
|
-
};
|
|
259
270
|
|
|
260
271
|
//#endregion
|
|
261
272
|
//#region src/git/commit.ts
|
|
262
|
-
const getBranchName = () => {
|
|
263
|
-
return runGit([
|
|
264
|
-
"rev-parse",
|
|
265
|
-
"--abbrev-ref",
|
|
266
|
-
"HEAD"
|
|
267
|
-
]);
|
|
268
|
-
};
|
|
269
|
-
const getRemoteForBranch = (branch) => {
|
|
270
|
-
return runGit([
|
|
271
|
-
"config",
|
|
272
|
-
"--get",
|
|
273
|
-
`branch.${branch}.remote`
|
|
274
|
-
]);
|
|
275
|
-
};
|
|
276
|
-
const getStatus = () => {
|
|
277
|
-
return runGit([
|
|
278
|
-
"status",
|
|
279
|
-
"--short",
|
|
280
|
-
"--untracked-files=no"
|
|
281
|
-
], { trim: false });
|
|
282
|
-
};
|
|
283
|
-
const coloredChangeset = (log$1) => {
|
|
284
|
-
const colorStatusChar = (ch) => {
|
|
285
|
-
switch (ch) {
|
|
286
|
-
case "M": return `\x1b[33m${ch}\x1b[0m`;
|
|
287
|
-
case "A": return `\x1b[32m${ch}\x1b[0m`;
|
|
288
|
-
case "D": return `\x1b[31m${ch}\x1b[0m`;
|
|
289
|
-
default: return ch;
|
|
290
|
-
}
|
|
291
|
-
};
|
|
292
|
-
const colorStatus = (status) => status.split("").map(colorStatusChar).join("");
|
|
293
|
-
return log$1.split("\n").filter(Boolean).map((line) => {
|
|
294
|
-
const status = line.slice(0, 2);
|
|
295
|
-
const file = line.slice(3);
|
|
296
|
-
return `${colorStatus(status)} ${styleText(["dim"], file)}`;
|
|
297
|
-
}).join("\n");
|
|
298
|
-
};
|
|
299
|
-
const getRemote = async () => {
|
|
300
|
-
const branch = await getBranchName();
|
|
301
|
-
const remoteName = branch && await getRemoteForBranch(branch) || "origin";
|
|
302
|
-
return {
|
|
303
|
-
remoteName,
|
|
304
|
-
remoteUrl: await runGit([
|
|
305
|
-
"remote",
|
|
306
|
-
"get-url",
|
|
307
|
-
remoteName
|
|
308
|
-
]).catch((_) => runGit([
|
|
309
|
-
"config",
|
|
310
|
-
"--get",
|
|
311
|
-
`remote.${remoteName}.url`
|
|
312
|
-
]))
|
|
313
|
-
};
|
|
314
|
-
};
|
|
315
|
-
const getLatestTag = async (match = "*", exclude = "*-beta.*") => {
|
|
316
|
-
return runGit([
|
|
317
|
-
"describe",
|
|
318
|
-
"--tags",
|
|
319
|
-
"--abbrev=0",
|
|
320
|
-
`--match=${match}`,
|
|
321
|
-
`--exclude=${exclude}`
|
|
322
|
-
]);
|
|
323
|
-
};
|
|
324
|
-
const getPreviousTag = async (current) => {
|
|
325
|
-
return runGit([
|
|
326
|
-
"describe",
|
|
327
|
-
"--tags",
|
|
328
|
-
"--abbrev=0",
|
|
329
|
-
`${await runGit([
|
|
330
|
-
"rev-list",
|
|
331
|
-
"--tags",
|
|
332
|
-
current || "--skip=1",
|
|
333
|
-
"--max-count=1"
|
|
334
|
-
])}^`
|
|
335
|
-
]);
|
|
336
|
-
};
|
|
337
|
-
const resolveChangelogRange = async (isIncrement) => {
|
|
338
|
-
const latestTag = await getLatestTag();
|
|
339
|
-
const previousTag = await getPreviousTag(latestTag);
|
|
340
|
-
if (!latestTag) return {
|
|
341
|
-
from: "",
|
|
342
|
-
to: "HEAD"
|
|
343
|
-
};
|
|
344
|
-
if (!isIncrement && previousTag) return {
|
|
345
|
-
from: previousTag,
|
|
346
|
-
to: `${latestTag}^1`
|
|
347
|
-
};
|
|
348
|
-
return {
|
|
349
|
-
from: latestTag,
|
|
350
|
-
to: "HEAD"
|
|
351
|
-
};
|
|
352
|
-
};
|
|
353
|
-
const getLog = async (print, isIncrement) => {
|
|
354
|
-
const cmd = ["log", `--pretty=format:${print ? "* %s (%h)" : "%s %h %H"}`];
|
|
355
|
-
const { from, to } = await resolveChangelogRange(isIncrement);
|
|
356
|
-
if (from) cmd.push(`${from}...${to}`);
|
|
357
|
-
return runGit(cmd, { trim: false });
|
|
358
|
-
};
|
|
359
|
-
const isWorkingDirClean = async () => {
|
|
360
|
-
return (await runGit(["status", "-s"]))?.length === 0;
|
|
361
|
-
};
|
|
362
|
-
const hasUpstreamBranch = async () => {
|
|
363
|
-
const branch = await runGit([
|
|
364
|
-
"for-each-ref",
|
|
365
|
-
"--format=%(upstream:short)",
|
|
366
|
-
await runGit(["symbolic-ref", "HEAD"])
|
|
367
|
-
]);
|
|
368
|
-
return Boolean(branch);
|
|
369
|
-
};
|
|
370
|
-
const getUpstreamArgs = async (remoteName) => {
|
|
371
|
-
const hasUpstream = await hasUpstreamBranch();
|
|
372
|
-
const branch = await getBranchName();
|
|
373
|
-
if (!hasUpstream) return [
|
|
374
|
-
"--set-upstream",
|
|
375
|
-
remoteName || "origin",
|
|
376
|
-
branch
|
|
377
|
-
];
|
|
378
|
-
if (remoteName) return [remoteName];
|
|
379
|
-
return [];
|
|
380
|
-
};
|
|
381
|
-
const normalizeTag = (tag) => clean(tag) ?? tag;
|
|
382
273
|
const gitCheck = async (ctx, config) => {
|
|
383
274
|
const { pkg: { name } } = ctx;
|
|
384
275
|
const { git: { requireRepository, requireRemote, requireWorkDirClean } } = config;
|
|
@@ -405,42 +296,25 @@ const commitAndTag = async (ctx, config) => {
|
|
|
405
296
|
const { git: { commit, tag, push, addUntrackedFiles, commitArgs, tagArgs, pushArgs } } = config;
|
|
406
297
|
if (!commit) return;
|
|
407
298
|
const args = dryRun ? ["--dry-run"] : [];
|
|
408
|
-
await
|
|
409
|
-
|
|
410
|
-
".",
|
|
411
|
-
addUntrackedFiles ? "--all" : "--update",
|
|
412
|
-
...args
|
|
413
|
-
]);
|
|
414
|
-
await runGit([
|
|
415
|
-
"commit",
|
|
416
|
-
"--message",
|
|
417
|
-
commitMessage,
|
|
418
|
-
...commitArgs,
|
|
419
|
-
...args
|
|
420
|
-
]);
|
|
299
|
+
addUntrackedFiles ? await gitAddAll() : await gitAddTracked(args);
|
|
300
|
+
await gitCommit(commitMessage, [...commitArgs, ...args]);
|
|
421
301
|
if (!dryRun) Object.assign(ctx.git, { isCommitted: true });
|
|
422
302
|
if (tag) {
|
|
423
|
-
await
|
|
424
|
-
"tag",
|
|
425
|
-
"--annotate",
|
|
426
|
-
"--message",
|
|
427
|
-
tagMessage,
|
|
428
|
-
currentTag,
|
|
429
|
-
...tagArgs,
|
|
430
|
-
...args
|
|
431
|
-
]);
|
|
303
|
+
await gitTagAnnotated(currentTag, tagMessage, [...tagArgs, ...args]);
|
|
432
304
|
if (!dryRun) Object.assign(ctx.git, { isTagged: true });
|
|
433
305
|
}
|
|
434
|
-
if (push)
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
306
|
+
if (push) {
|
|
307
|
+
const remotes = await getAllRemotes();
|
|
308
|
+
for (const remoteName$1 of remotes) {
|
|
309
|
+
await pushTag(remoteName$1, currentTag, args);
|
|
310
|
+
await pushBranch(remoteName$1, await getCurrentBranch(), [...pushArgs, ...args]);
|
|
311
|
+
}
|
|
312
|
+
if (!dryRun) Object.assign(ctx.git, { isPushed: true });
|
|
313
|
+
}
|
|
441
314
|
};
|
|
442
|
-
const gitRollback
|
|
443
|
-
const { git: { currentTag, isCommitted, isTagged } } = ctx;
|
|
315
|
+
const gitRollback = (ctx) => {
|
|
316
|
+
const { git: { currentTag, isCommitted, isTagged, isPushed } } = ctx;
|
|
317
|
+
if (isPushed) return;
|
|
444
318
|
spawnSync("git", ["restore", "."]);
|
|
445
319
|
if (isTagged) spawnSync("git", [
|
|
446
320
|
"tag",
|
|
@@ -454,16 +328,41 @@ const gitRollback$1 = (ctx) => {
|
|
|
454
328
|
]);
|
|
455
329
|
};
|
|
456
330
|
|
|
331
|
+
//#endregion
|
|
332
|
+
//#region src/prompts.ts
|
|
333
|
+
const abortTask = (msg$1) => {
|
|
334
|
+
cancel(`${red("✕")} ${msg$1}`);
|
|
335
|
+
process.exit(0);
|
|
336
|
+
};
|
|
337
|
+
const abortSinglePrompt = (value) => {
|
|
338
|
+
if (isCancel(value)) abortTask(MSG.ABORT.CANCEL);
|
|
339
|
+
};
|
|
340
|
+
const abortGroupPrompt = (ctx) => {
|
|
341
|
+
ctx && gitRollback(ctx);
|
|
342
|
+
abortTask(MSG.ABORT.CANCEL);
|
|
343
|
+
};
|
|
344
|
+
const abortOnError = (err, ctx) => {
|
|
345
|
+
ctx && gitRollback(ctx);
|
|
346
|
+
const msg$1 = err.message.replace(/^\[(github|npm|git)]/, (a) => red(a));
|
|
347
|
+
log.message(`\n${msg$1}\n`);
|
|
348
|
+
process.exit(1);
|
|
349
|
+
};
|
|
350
|
+
const taskEnd = (msg$1) => {
|
|
351
|
+
outro(msg$1);
|
|
352
|
+
process.exit(0);
|
|
353
|
+
};
|
|
354
|
+
|
|
457
355
|
//#endregion
|
|
458
356
|
//#region src/utils.ts
|
|
459
357
|
const info = (msg$1) => {
|
|
460
|
-
console.log(`${rgb(33, 91, 184)(`i`)} ${
|
|
358
|
+
console.log(`${rgb(33, 91, 184)(`i`)} ${msg$1}`);
|
|
461
359
|
};
|
|
462
360
|
const msg = (prefix, msg$1) => {
|
|
463
|
-
log.message(
|
|
361
|
+
log.message(`${rgb(33, 91, 184)(`[${prefix}]`)} ${dim(msg$1)}`);
|
|
464
362
|
};
|
|
465
363
|
const question = (msg$1, type) => {
|
|
466
364
|
return `${{
|
|
365
|
+
package: yellow,
|
|
467
366
|
version: green,
|
|
468
367
|
git: rgb(220, 94, 62),
|
|
469
368
|
npm: rgb(186, 70, 61),
|
|
@@ -476,7 +375,7 @@ const success = (msg$1, dryRun) => {
|
|
|
476
375
|
const runLifeCycleHook = async (hooks, key, dryRun) => {
|
|
477
376
|
const handler = hooks[key];
|
|
478
377
|
if (typeof handler === "function") {
|
|
479
|
-
if (!dryRun) await handler();
|
|
378
|
+
if (!dryRun) await handler(log);
|
|
480
379
|
log.success(success(`${dim`run`} ${inspect(handler)}`, dryRun));
|
|
481
380
|
return;
|
|
482
381
|
}
|
|
@@ -485,33 +384,19 @@ const runLifeCycleHook = async (hooks, key, dryRun) => {
|
|
|
485
384
|
log.success(success(hook.replace(/^echo$/, dim("echo")), dryRun));
|
|
486
385
|
}));
|
|
487
386
|
};
|
|
488
|
-
const gitRollback = (ctx) => {
|
|
489
|
-
const { git: { currentTag, isCommitted, isTagged } } = ctx;
|
|
490
|
-
spawnSync("git", ["restore", "."]);
|
|
491
|
-
if (isTagged) spawnSync("git", [
|
|
492
|
-
"tag",
|
|
493
|
-
"--delete",
|
|
494
|
-
currentTag
|
|
495
|
-
]);
|
|
496
|
-
spawnSync("git", [
|
|
497
|
-
"reset",
|
|
498
|
-
"--hard",
|
|
499
|
-
isCommitted ? "HEAD~1" : "HEAD"
|
|
500
|
-
]);
|
|
501
|
-
};
|
|
502
387
|
const diff = (from, to, separator = ".") => {
|
|
503
388
|
const a = from.split(separator);
|
|
504
389
|
return to.split(separator).map((v, i) => v === a[i] ? dim(v) : green(v)).join(separator);
|
|
505
390
|
};
|
|
506
391
|
const formatTemplate = async (ctx, config) => {
|
|
507
|
-
const { pkg: { next } } = ctx;
|
|
508
|
-
const { git: { commitMessage: cm, tagMessage: tm
|
|
509
|
-
const f = (s, v) => s.replace("${
|
|
392
|
+
const { pkg: { next }, selectedPkg } = ctx;
|
|
393
|
+
const { git: { commitMessage: cm, tagMessage: tm }, github: { releaseName: rm }, toTag } = config;
|
|
394
|
+
const f = (s, v) => s.replace("${tag}", v);
|
|
510
395
|
const latestTag = await getLatestTag() ?? "";
|
|
511
|
-
const currentTag =
|
|
512
|
-
const commitMessage = f(cm,
|
|
513
|
-
const tagMessage = f(tm,
|
|
514
|
-
const releaseName = f(rm,
|
|
396
|
+
const currentTag = toTag(selectedPkg, next);
|
|
397
|
+
const commitMessage = f(cm, currentTag);
|
|
398
|
+
const tagMessage = f(tm, currentTag);
|
|
399
|
+
const releaseName = f(rm, currentTag);
|
|
515
400
|
Object.assign(ctx.git, {
|
|
516
401
|
latestTag,
|
|
517
402
|
currentTag,
|
|
@@ -521,30 +406,6 @@ const formatTemplate = async (ctx, config) => {
|
|
|
521
406
|
Object.assign(ctx.github, { releaseName });
|
|
522
407
|
};
|
|
523
408
|
|
|
524
|
-
//#endregion
|
|
525
|
-
//#region src/prompts.ts
|
|
526
|
-
const abortTask = (msg$1) => {
|
|
527
|
-
cancel(`${red("✕")} ${msg$1}`);
|
|
528
|
-
process.exit(0);
|
|
529
|
-
};
|
|
530
|
-
const abortSinglePrompt = (value) => {
|
|
531
|
-
if (isCancel(value)) abortTask("Operation cancelled.");
|
|
532
|
-
};
|
|
533
|
-
const abortGroupPrompt = (ctx) => {
|
|
534
|
-
ctx && gitRollback(ctx);
|
|
535
|
-
abortTask("Operation cancelled.");
|
|
536
|
-
};
|
|
537
|
-
const abortOnError = (err, ctx) => {
|
|
538
|
-
ctx && gitRollback(ctx);
|
|
539
|
-
const msg$1 = err.message.replace(/^\[(github|npm|git)]/, (a) => red(a));
|
|
540
|
-
log.message(`\n${msg$1}\n`);
|
|
541
|
-
process.exit(1);
|
|
542
|
-
};
|
|
543
|
-
const taskEnd = (msg$1) => {
|
|
544
|
-
outro(msg$1);
|
|
545
|
-
process.exit(0);
|
|
546
|
-
};
|
|
547
|
-
|
|
548
409
|
//#endregion
|
|
549
410
|
//#region src/version/bump.ts
|
|
550
411
|
const isPreRelease = (version) => {
|
|
@@ -679,7 +540,7 @@ const runVersionPrompts = async (ctx, config) => {
|
|
|
679
540
|
};
|
|
680
541
|
const type = await selectVersion();
|
|
681
542
|
const next = type === "custom" ? await inputVersion() : type === "ignore" ? current : type;
|
|
682
|
-
ctx.isIncrement =
|
|
543
|
+
ctx.isIncrement = !eq(next, current);
|
|
683
544
|
return { next };
|
|
684
545
|
};
|
|
685
546
|
|
|
@@ -698,6 +559,7 @@ const createDefaultContext = () => {
|
|
|
698
559
|
increment: "",
|
|
699
560
|
isIncrement: false,
|
|
700
561
|
configFileExists: false,
|
|
562
|
+
selectedPkg: "",
|
|
701
563
|
pkg: {
|
|
702
564
|
current: "",
|
|
703
565
|
next: "",
|
|
@@ -725,9 +587,9 @@ const createDefaultContext = () => {
|
|
|
725
587
|
currentTag: "",
|
|
726
588
|
isCommitted: false,
|
|
727
589
|
isTagged: false,
|
|
590
|
+
isPushed: false,
|
|
728
591
|
commitMessage: "",
|
|
729
|
-
tagMessage: ""
|
|
730
|
-
tagName: ""
|
|
592
|
+
tagMessage: ""
|
|
731
593
|
},
|
|
732
594
|
github: {
|
|
733
595
|
username: "",
|
|
@@ -757,14 +619,22 @@ const createDefaultConfig = (isCI) => {
|
|
|
757
619
|
"before:release": "",
|
|
758
620
|
"after:release": ""
|
|
759
621
|
},
|
|
622
|
+
isMonorepo: false,
|
|
623
|
+
packages: [],
|
|
624
|
+
getPkgDir(pkg$1) {
|
|
625
|
+
return ".";
|
|
626
|
+
},
|
|
627
|
+
toTag(pkg$1, version) {
|
|
628
|
+
return `v${version}`;
|
|
629
|
+
},
|
|
630
|
+
tagPrefix: void 0,
|
|
760
631
|
git: {
|
|
761
632
|
commit: isCI,
|
|
762
633
|
tag: isCI,
|
|
763
634
|
push: isCI,
|
|
764
|
-
commitMessage: "chore(release): ${
|
|
635
|
+
commitMessage: "chore(release): ${tag}",
|
|
765
636
|
commitArgs: [],
|
|
766
|
-
tagMessage: "
|
|
767
|
-
tagName: "${version}",
|
|
637
|
+
tagMessage: "${tag}",
|
|
768
638
|
tagArgs: [],
|
|
769
639
|
pushArgs: [],
|
|
770
640
|
addUntrackedFiles: false,
|
|
@@ -780,17 +650,13 @@ const createDefaultConfig = (isCI) => {
|
|
|
780
650
|
},
|
|
781
651
|
github: {
|
|
782
652
|
release: isCI,
|
|
783
|
-
releaseName: "
|
|
653
|
+
releaseName: "${tag}",
|
|
784
654
|
autoGenerate: false,
|
|
785
655
|
prerelease: false,
|
|
786
656
|
draft: false,
|
|
787
657
|
tokenRef: "GITHUB_TOKEN",
|
|
788
658
|
assets: [],
|
|
789
659
|
skipChecks: false
|
|
790
|
-
},
|
|
791
|
-
changelog: {
|
|
792
|
-
infile: "CHANGELOG.md",
|
|
793
|
-
types: []
|
|
794
660
|
}
|
|
795
661
|
};
|
|
796
662
|
};
|
|
@@ -803,15 +669,25 @@ const defaultTypes = [
|
|
|
803
669
|
section: "✨ 新功能",
|
|
804
670
|
description: "新增功能"
|
|
805
671
|
},
|
|
672
|
+
{
|
|
673
|
+
type: "feature",
|
|
674
|
+
section: "✨ 新功能",
|
|
675
|
+
description: "新增功能"
|
|
676
|
+
},
|
|
806
677
|
{
|
|
807
678
|
type: "fix",
|
|
808
679
|
section: "🐛 Bug 修复",
|
|
809
680
|
description: "修复 bug"
|
|
810
681
|
},
|
|
811
682
|
{
|
|
812
|
-
type: "
|
|
813
|
-
section: "
|
|
814
|
-
description: "
|
|
683
|
+
type: "perf",
|
|
684
|
+
section: "⚡ 性能优化",
|
|
685
|
+
description: "提升性能"
|
|
686
|
+
},
|
|
687
|
+
{
|
|
688
|
+
type: "revert",
|
|
689
|
+
section: "⏪ 回退",
|
|
690
|
+
description: "回退到之前版本"
|
|
815
691
|
},
|
|
816
692
|
{
|
|
817
693
|
type: "docs",
|
|
@@ -823,26 +699,21 @@ const defaultTypes = [
|
|
|
823
699
|
section: "💄 样式调整",
|
|
824
700
|
description: "样式或格式修改"
|
|
825
701
|
},
|
|
702
|
+
{
|
|
703
|
+
type: "chore",
|
|
704
|
+
section: "🎫 其他更新",
|
|
705
|
+
description: "非功能性更改"
|
|
706
|
+
},
|
|
826
707
|
{
|
|
827
708
|
type: "refactor",
|
|
828
709
|
section: "♻ 代码重构",
|
|
829
710
|
description: "重构代码"
|
|
830
711
|
},
|
|
831
|
-
{
|
|
832
|
-
type: "perf",
|
|
833
|
-
section: "⚡ 性能优化",
|
|
834
|
-
description: "提升性能"
|
|
835
|
-
},
|
|
836
712
|
{
|
|
837
713
|
type: "test",
|
|
838
714
|
section: "✅ 测试",
|
|
839
715
|
description: "测试更新"
|
|
840
716
|
},
|
|
841
|
-
{
|
|
842
|
-
type: "revert",
|
|
843
|
-
section: "⏪ 回退",
|
|
844
|
-
description: "回退到之前版本"
|
|
845
|
-
},
|
|
846
717
|
{
|
|
847
718
|
type: "build",
|
|
848
719
|
section: "👷 构建系统",
|
|
@@ -882,94 +753,80 @@ const defaultTypes = [
|
|
|
882
753
|
type: "hotfix",
|
|
883
754
|
section: "🔥 热修复",
|
|
884
755
|
description: "紧急修复"
|
|
885
|
-
},
|
|
886
|
-
{
|
|
887
|
-
type: "break",
|
|
888
|
-
section: "💥 破坏性变更",
|
|
889
|
-
description: "破坏性变更"
|
|
890
756
|
}
|
|
891
757
|
];
|
|
892
|
-
const mergeTypes = (types) => {
|
|
893
|
-
types.forEach((item) => {
|
|
894
|
-
const index = defaultTypes.findIndex((it) => it.type === item.type);
|
|
895
|
-
if (index >= 0) defaultTypes.splice(index, 1, { ...item });
|
|
896
|
-
else defaultTypes.push({ ...item });
|
|
897
|
-
});
|
|
898
|
-
};
|
|
899
|
-
const findType = (type) => {
|
|
900
|
-
return defaultTypes.find((item) => item.type === type)?.section ?? type;
|
|
901
|
-
};
|
|
902
758
|
|
|
903
759
|
//#endregion
|
|
904
760
|
//#region src/git/changelog.ts
|
|
905
|
-
const
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
const
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
761
|
+
const generateChangelog = async ({ getPkgDir, tagPrefix }) => {
|
|
762
|
+
const pkgDir = getPkgDir();
|
|
763
|
+
const infile = join(pkgDir, "CHANGELOG.md");
|
|
764
|
+
if (!existsSync(infile)) await writeFile(infile, "");
|
|
765
|
+
const originalChangelog = readFileSync(infile, "utf-8");
|
|
766
|
+
const preset = await createPreset({ types: defaultTypes.map((t$1) => ({
|
|
767
|
+
...t$1,
|
|
768
|
+
hidden: false
|
|
769
|
+
})) });
|
|
770
|
+
preset.writer ??= {};
|
|
771
|
+
preset.writer.headerPartial = `
|
|
772
|
+
## {{#if isPatch~}} <small> {{~/if~}}
|
|
773
|
+
{{#if @root.linkCompare~}}
|
|
774
|
+
[{{version}}](
|
|
775
|
+
{{~#if @root.repository~}}
|
|
776
|
+
{{~#if @root.host}}
|
|
777
|
+
{{~@root.host}}/
|
|
778
|
+
{{~/if}}
|
|
779
|
+
{{~#if @root.owner}}
|
|
780
|
+
{{~@root.owner}}/
|
|
781
|
+
{{~/if}}
|
|
782
|
+
{{~@root.repository}}
|
|
783
|
+
{{~else}}
|
|
784
|
+
{{~@root.repoUrl}}
|
|
785
|
+
{{~/if~}}
|
|
786
|
+
/compare/{{previousTag}}...{{currentTag}})
|
|
787
|
+
{{~else}}
|
|
788
|
+
{{~version}}
|
|
789
|
+
{{~/if}}
|
|
790
|
+
{{~#if title}} "{{title}}"
|
|
791
|
+
{{~/if}}
|
|
792
|
+
{{~#if date}} ({{date}})
|
|
793
|
+
{{~/if}}
|
|
794
|
+
{{~#if isPatch~}} </small> {{~/if}}
|
|
795
|
+
`.trim() + eol();
|
|
796
|
+
preset.writer.mainTemplate = `
|
|
797
|
+
{{> header}}
|
|
798
|
+
{{#if noteGroups}}
|
|
799
|
+
{{#each noteGroups}}
|
|
800
|
+
|
|
801
|
+
### ⚠ {{title}}
|
|
802
|
+
|
|
803
|
+
{{#each notes}}
|
|
804
|
+
* {{#if commit.scope}}**{{commit.scope}}:** {{/if}}{{commit.subject}} {{#if commit.hash}}([{{commit.shortHash}}](https://github.com/{{@root.owner}}/{{@root.repository}}/commit/{{commit.hash}})){{/if}}
|
|
805
|
+
{{/each}}
|
|
806
|
+
{{/each}}
|
|
807
|
+
{{/if}}
|
|
808
|
+
{{#each commitGroups}}
|
|
809
|
+
|
|
810
|
+
{{#if title}}
|
|
811
|
+
### {{title}}
|
|
812
|
+
|
|
813
|
+
{{/if}}
|
|
814
|
+
{{#each commits}}
|
|
815
|
+
{{> commit root=@root}}
|
|
816
|
+
{{/each}}
|
|
817
|
+
{{/each}}`.trim() + eol(2);
|
|
818
|
+
const generator = new ConventionalChangelog().readPackage(`${pkgDir}/package.json`).config(preset).options({ releaseCount: 1 }).commits({ path: pkgDir }).tags({ prefix: tagPrefix });
|
|
819
|
+
const writeStream = createWriteStream(infile);
|
|
820
|
+
let changelog = "";
|
|
821
|
+
for await (const chunk of generator.write()) {
|
|
822
|
+
changelog += chunk;
|
|
823
|
+
writeStream.write(chunk);
|
|
929
824
|
}
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
const { github: { owner, repo }, git: { latestTag, currentTag } } = ctx;
|
|
934
|
-
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
935
|
-
const github = getGithubUrl(owner, repo);
|
|
936
|
-
return [
|
|
937
|
-
latestTag ? `## [${currentTag}](${github}/compare/${latestTag}...${currentTag}) (${date})` : `## ${currentTag} (${date})`,
|
|
938
|
-
Object.entries(sections).map((entry) => {
|
|
939
|
-
const [type, items] = entry;
|
|
940
|
-
return [`### ${findType(type)}`, items.map((item) => {
|
|
941
|
-
const [desc, hash, fullHash] = item;
|
|
942
|
-
return `* **${type}** ${desc} [${hash}](${github}/commit/${fullHash})`;
|
|
943
|
-
}).join(eol(1))].join(eol(2));
|
|
944
|
-
}).join(eol(2)),
|
|
945
|
-
latestTag ? `**Full Changelog**: ${github}/compare/${latestTag}...${currentTag}` : ""
|
|
946
|
-
].join(eol(3));
|
|
947
|
-
};
|
|
948
|
-
const generateChangelog = async (ctx) => {
|
|
949
|
-
const { isIncrement } = ctx;
|
|
950
|
-
const commits = await getCommits(isIncrement);
|
|
951
|
-
if (!commits) return "";
|
|
952
|
-
const changelog = await renderChangelog(ctx, classify(commits));
|
|
953
|
-
Object.assign(ctx.github, { changelog });
|
|
825
|
+
writeStream.write(originalChangelog);
|
|
826
|
+
writeStream.end();
|
|
827
|
+
await finished(writeStream);
|
|
954
828
|
return changelog;
|
|
955
829
|
};
|
|
956
|
-
const updateChangelog = async (file, newContent) => {
|
|
957
|
-
const infile = file || "CHANGELOG.md";
|
|
958
|
-
const target = join(process.cwd(), infile);
|
|
959
|
-
const SHORT_TITLE = "# Changelog";
|
|
960
|
-
const exist = (existsSync(target) ? await readFile(target, "utf-8") : "").replace(/\r\n/g, "\n");
|
|
961
|
-
await writeFile(target, [
|
|
962
|
-
SHORT_TITLE,
|
|
963
|
-
newContent,
|
|
964
|
-
exist.startsWith(SHORT_TITLE) ? exist.slice(11) : exist
|
|
965
|
-
].map((k) => k.trim()).join(eol(4)) + eol());
|
|
966
|
-
await runGit(["add", infile]);
|
|
967
|
-
};
|
|
968
|
-
const writeChangelog = async (ctx, config) => {
|
|
969
|
-
const { changelog: { infile, types } } = config;
|
|
970
|
-
mergeTypes(types);
|
|
971
|
-
await updateChangelog(infile, await generateChangelog(ctx));
|
|
972
|
-
};
|
|
973
830
|
|
|
974
831
|
//#endregion
|
|
975
832
|
//#region src/git/prompts.ts
|
|
@@ -1063,108 +920,45 @@ const runNpmOptPrompts = async (ctx, config) => {
|
|
|
1063
920
|
|
|
1064
921
|
//#endregion
|
|
1065
922
|
//#region src/npm/publish.ts
|
|
1066
|
-
const
|
|
1067
|
-
return (publishConfig.registry ? [publishConfig.registry] : Object.keys(publishConfig).filter((key) => key.endsWith("registry")).map((key) => publishConfig[key]))[0];
|
|
1068
|
-
};
|
|
1069
|
-
const isRegistryUp = async (registry) => {
|
|
1070
|
-
return void 0 !== await runNpm(["ping", ...registry ? ["--registry", registry] : []]);
|
|
1071
|
-
};
|
|
1072
|
-
const isAuthenticated = (registry) => {
|
|
1073
|
-
return runNpm(["whoami", ...registry ? ["--registry", registry] : []]);
|
|
1074
|
-
};
|
|
1075
|
-
const getLatestRegistryVersion = (name, tag, registry) => {
|
|
1076
|
-
const registryArg = registry ? ["--registry", registry] : [];
|
|
1077
|
-
return runNpm([
|
|
1078
|
-
"show",
|
|
1079
|
-
`${name}@${tag}`,
|
|
1080
|
-
"version",
|
|
1081
|
-
...registryArg
|
|
1082
|
-
]);
|
|
1083
|
-
};
|
|
1084
|
-
const bump = (version, allowSameVersion, args = []) => {
|
|
1085
|
-
allowSameVersion && args.push(`--allow-same-version`);
|
|
1086
|
-
return runNpm([
|
|
1087
|
-
"version",
|
|
1088
|
-
version,
|
|
1089
|
-
"--no-git-tag-version",
|
|
1090
|
-
"--workspaces=false",
|
|
1091
|
-
...args
|
|
1092
|
-
]);
|
|
1093
|
-
};
|
|
1094
|
-
const resolveTag = async (version) => {
|
|
923
|
+
const resolvePublishTag = async (pkgName, version) => {
|
|
1095
924
|
const { toPreRelease, preId } = parseVersion(version);
|
|
1096
|
-
if (!toPreRelease)
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
const { pkg: { name, publishConfig }, npm: { username } } = ctx;
|
|
1101
|
-
const registry = getRegistry(publishConfig);
|
|
1102
|
-
const registryArg = registry ? ["--registry", registry] : [];
|
|
1103
|
-
let npmVersion = await runNpm(["--version"]);
|
|
1104
|
-
let accessCommand;
|
|
1105
|
-
if (gt(npmVersion, "9.0.0")) accessCommand = [
|
|
1106
|
-
"access",
|
|
1107
|
-
"list",
|
|
1108
|
-
"collaborators",
|
|
1109
|
-
"--json"
|
|
1110
|
-
];
|
|
1111
|
-
else accessCommand = ["access", "ls-collaborators"];
|
|
1112
|
-
const res = await runNpm([
|
|
1113
|
-
...accessCommand,
|
|
1114
|
-
name,
|
|
1115
|
-
...registryArg
|
|
1116
|
-
]);
|
|
1117
|
-
if (res) {
|
|
1118
|
-
const permissions = JSON.parse(res ?? "{}")[username];
|
|
1119
|
-
return permissions && permissions.includes("write");
|
|
1120
|
-
}
|
|
1121
|
-
log.error(`Unable to verify if user ${username} is a collaborator for ${name}.`);
|
|
1122
|
-
};
|
|
1123
|
-
const getPackageUrl = (ctx) => {
|
|
1124
|
-
const { pkg: { name, next } } = ctx;
|
|
1125
|
-
return `https://www.npmjs.com/package/${name}/v/${next}`;
|
|
925
|
+
if (!toPreRelease) {
|
|
926
|
+
const active = await getPublishedVersion(pkgName);
|
|
927
|
+
return active && lt(version, active) ? "previous" : "latest";
|
|
928
|
+
} else return preId || "prerelease";
|
|
1126
929
|
};
|
|
930
|
+
const isOtpError = (err) => err instanceof Error && /one-time password|otp/i.test(err.message);
|
|
1127
931
|
const npmCheck = async (ctx, config) => {
|
|
1128
|
-
const { pkg: { name, current, publishConfig
|
|
932
|
+
const { pkg: { name, current, publishConfig: { registry } } } = ctx;
|
|
1129
933
|
const { npm: { skipChecks } } = config;
|
|
1130
|
-
const
|
|
1131
|
-
const tag = await resolveTag(current);
|
|
934
|
+
const tag = await resolvePublishTag(name, current);
|
|
1132
935
|
Object.assign(ctx.npm, { tag });
|
|
1133
936
|
if (skipChecks) return "";
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
if (!await getUser()) throw new Error(MSG.ERROR.NPM_AUTH);
|
|
1142
|
-
const latestPublish = await getLatestRegistryVersion(name, tag, registry);
|
|
1143
|
-
if (latestPublish) {
|
|
1144
|
-
if (!await isCollaborator(ctx)) throw new Error(MSG.ERROR.NPM_USER(username, name));
|
|
937
|
+
const [pinged, username] = await Promise.all([pingRegistry(registry), getAuthenticatedUser(registry)]);
|
|
938
|
+
if (!pinged) throw new Error(MSG.ERROR.NPM_REGISTRY(registry));
|
|
939
|
+
if (!username) throw new Error(MSG.ERROR.NPM_AUTH);
|
|
940
|
+
Object.assign(ctx.npm, { username });
|
|
941
|
+
const latest = await getPublishedVersion(`${name}@${tag}`);
|
|
942
|
+
if (latest) {
|
|
943
|
+
if (!await hasWriteAccess(name, username, registry)) throw new Error(MSG.ERROR.NPM_USER(username, name));
|
|
1145
944
|
}
|
|
1146
|
-
return
|
|
945
|
+
return latest ? `(${tag} → ${latest})` : `(${tag} → no published version)`;
|
|
1147
946
|
};
|
|
1148
|
-
const isOtpError = (err) => /one-time password|otp/i.test(err.message);
|
|
1149
947
|
const publishNpm = async (ctx, config) => {
|
|
1150
|
-
const { dryRun, npm: { otp, tag }
|
|
1151
|
-
const { npm: { publish,
|
|
1152
|
-
const registry = getRegistry(publishConfig);
|
|
948
|
+
const { dryRun, selectedPkg, npm: { otp, tag } } = ctx;
|
|
949
|
+
const { npm: { publish, pushArgs }, getPkgDir } = config;
|
|
1153
950
|
if (!publish) return;
|
|
1154
951
|
const otpArgs = otp ? ["--otp", otp] : [];
|
|
1155
952
|
const dryRunArg = dryRun ? ["--dry-run"] : [];
|
|
1156
|
-
|
|
1157
|
-
return runNpm([
|
|
1158
|
-
"publish",
|
|
1159
|
-
publishPath,
|
|
1160
|
-
"--tag",
|
|
953
|
+
return publishPackage({
|
|
1161
954
|
tag,
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
955
|
+
args: [
|
|
956
|
+
...otpArgs,
|
|
957
|
+
...pushArgs,
|
|
958
|
+
...dryRunArg
|
|
959
|
+
],
|
|
960
|
+
cwd: getPkgDir(selectedPkg)
|
|
961
|
+
});
|
|
1168
962
|
};
|
|
1169
963
|
|
|
1170
964
|
//#endregion
|
|
@@ -1172,17 +966,38 @@ const publishNpm = async (ctx, config) => {
|
|
|
1172
966
|
var Action = class {
|
|
1173
967
|
async handle(cmdArgs, options) {
|
|
1174
968
|
const { version: cVersion, name: cName } = readJsonFile(join(__dirname, "../package.json"));
|
|
1175
|
-
|
|
1176
|
-
const { otp, ...others } = options;
|
|
969
|
+
const { otp, package: defPkg, ...others } = options;
|
|
1177
970
|
const { showChangelog, showRelease, ci, dryRun } = Object.fromEntries(Object.entries(others).map(([k, v]) => [k, Boolean(v)]));
|
|
1178
971
|
process.env["dryRun"] = String(dryRun);
|
|
1179
|
-
const {
|
|
1180
|
-
const nextVersion = inc(pkgVersion, cmdArgs) ?? "";
|
|
1181
|
-
const local = await resolveConfig(process.cwd(), (path$1) => {
|
|
1182
|
-
info(`config file: ${underline(dim(path$1))}`);
|
|
1183
|
-
});
|
|
972
|
+
const { configPath, config: local } = await resolveConfig(process.cwd());
|
|
1184
973
|
const config = mergeConfig(createDefaultConfig(ci ? true : void 0), local);
|
|
974
|
+
info(MSG.INFO.TOOL(cName, cVersion));
|
|
975
|
+
info(MSG.INFO.CONFIG(configPath));
|
|
976
|
+
console.log();
|
|
977
|
+
intro(MSG.INTRO(dryRun));
|
|
978
|
+
const { isMonorepo, packages, getPkgDir } = config;
|
|
979
|
+
if (isMonorepo) {
|
|
980
|
+
if (ci) {
|
|
981
|
+
if (!defPkg) abortTask(MSG.ABORT.MONOREPO_CI_NO_PACKAGE);
|
|
982
|
+
} else if (packages.length < 1) abortTask(MSG.ABORT.MONOREPO_NO_PACKAGES);
|
|
983
|
+
}
|
|
984
|
+
const selectPackage = async () => {
|
|
985
|
+
const pkg$2 = await select({
|
|
986
|
+
message: question(MSG.PROMPT.SELECT_PACKAGE, "package"),
|
|
987
|
+
options: packages.map((pkg$3) => ({
|
|
988
|
+
label: pkg$3,
|
|
989
|
+
value: pkg$3
|
|
990
|
+
}))
|
|
991
|
+
});
|
|
992
|
+
abortSinglePrompt(pkg$2);
|
|
993
|
+
return pkg$2;
|
|
994
|
+
};
|
|
995
|
+
const selectedPkg = !isMonorepo ? "" : ci ? defPkg : packages.length === 1 ? packages[0] : await selectPackage();
|
|
996
|
+
const { pkg: pkg$1, pkgDir } = getPackageInfo(selectedPkg, getPkgDir);
|
|
997
|
+
const { version: pkgVersion, private: pkgPrivate = false, name: pkgName, publishConfig = {} } = pkg$1;
|
|
998
|
+
const nextVersion = inc(pkgVersion, cmdArgs) ?? "";
|
|
1185
999
|
const ctx = mergeConfig(createDefaultContext(), {
|
|
1000
|
+
selectedPkg,
|
|
1186
1001
|
configFileExists: Object.keys(local).length > 0,
|
|
1187
1002
|
dryRun,
|
|
1188
1003
|
showRelease,
|
|
@@ -1208,15 +1023,18 @@ var Action = class {
|
|
|
1208
1023
|
},
|
|
1209
1024
|
noNpm: pkgPrivate
|
|
1210
1025
|
});
|
|
1211
|
-
|
|
1212
|
-
|
|
1026
|
+
const { messages } = await publint({ pkgDir });
|
|
1027
|
+
for (const message of messages) {
|
|
1028
|
+
const formated = formatMessage(message, pkg$1);
|
|
1029
|
+
formated && msg("PKG", formated);
|
|
1030
|
+
}
|
|
1213
1031
|
await this.checkTask(ctx, config);
|
|
1214
1032
|
await this.bumpTask(ctx, config);
|
|
1215
1033
|
await this.changelogTask(ctx, config);
|
|
1216
1034
|
await this.gitTask(ctx, config);
|
|
1217
1035
|
await this.npmTask(ctx, config);
|
|
1218
1036
|
await this.githubTask(ctx, config);
|
|
1219
|
-
if (dryRun) gitRollback
|
|
1037
|
+
if (dryRun) gitRollback(ctx);
|
|
1220
1038
|
taskEnd(MSG.OUTRO(dryRun));
|
|
1221
1039
|
}
|
|
1222
1040
|
async checkTask(ctx, config) {
|
|
@@ -1239,8 +1057,8 @@ var Action = class {
|
|
|
1239
1057
|
}]).catch(abortOnError);
|
|
1240
1058
|
}
|
|
1241
1059
|
async bumpTask(ctx, config) {
|
|
1242
|
-
const { pkg: { current }, dryRun, isCI, showRelease, showChangelog } = ctx;
|
|
1243
|
-
const { hooks } = config;
|
|
1060
|
+
const { pkg: { current }, dryRun, isCI, showRelease, showChangelog, selectedPkg } = ctx;
|
|
1061
|
+
const { isMonorepo, hooks, getPkgDir, tagPrefix } = config;
|
|
1244
1062
|
const need = (ctx$1) => {
|
|
1245
1063
|
const { pkg: { next: next$1 }, isIncrement: isIncrement$1 } = ctx$1;
|
|
1246
1064
|
return !next$1 && isIncrement$1;
|
|
@@ -1255,32 +1073,39 @@ var Action = class {
|
|
|
1255
1073
|
await tasks([{
|
|
1256
1074
|
title: MSG.TASK.VERSION.START,
|
|
1257
1075
|
task: async () => {
|
|
1258
|
-
await
|
|
1076
|
+
await bumpPackageVersion(next, [], getPkgDir(selectedPkg));
|
|
1259
1077
|
await formatTemplate(ctx, config);
|
|
1260
|
-
const to = `(${current}...${diff(current, next)})`;
|
|
1261
|
-
return success(MSG.TASK.VERSION.END(to), dryRun);
|
|
1078
|
+
const to$1 = `(${current}...${diff(current, next)})`;
|
|
1079
|
+
return success(MSG.TASK.VERSION.END(to$1), dryRun);
|
|
1262
1080
|
}
|
|
1263
1081
|
}]).catch((err) => abortOnError(err, ctx));
|
|
1264
1082
|
await runLifeCycleHook(hooks, "after:bump", dryRun);
|
|
1265
|
-
const
|
|
1083
|
+
const { from, to } = await resolveChangelogRange(isIncrement, isMonorepo ? `${tagPrefix?.(selectedPkg)}*` : "*");
|
|
1084
|
+
const logStr = await getLog(from, to, getPkgDir(selectedPkg));
|
|
1266
1085
|
if (logStr) {
|
|
1267
|
-
msg("GIT", `Changelog:${eol(2)}` +
|
|
1086
|
+
msg("GIT", `Changelog(${from}...${to}):${eol(2)}` + logStr);
|
|
1268
1087
|
if (showChangelog) taskEnd(MSG.LOG.SHOW_CHANGELOG);
|
|
1269
1088
|
} else msg("GIT", MSG.LOG.CHANGELOG_EMPTY);
|
|
1270
|
-
const changeset = await getStatus();
|
|
1271
|
-
if (changeset) msg("GIT", `Changes:${eol(2)}` + coloredChangeset(changeset));
|
|
1272
|
-
else msg("GIT", MSG.LOG.CHANGES_EMPTY);
|
|
1273
1089
|
}
|
|
1274
1090
|
async changelogTask(ctx, config) {
|
|
1275
|
-
const { isIncrement, dryRun } = ctx;
|
|
1091
|
+
const { isIncrement, dryRun, selectedPkg } = ctx;
|
|
1092
|
+
const { getPkgDir, tagPrefix } = config;
|
|
1276
1093
|
await tasks([{
|
|
1277
1094
|
title: MSG.TASK.CHANGELOG.START,
|
|
1278
1095
|
task: async () => {
|
|
1279
|
-
await
|
|
1096
|
+
const changelog = await generateChangelog({
|
|
1097
|
+
getPkgDir: () => getPkgDir(selectedPkg),
|
|
1098
|
+
tagPrefix: tagPrefix?.(selectedPkg)
|
|
1099
|
+
});
|
|
1100
|
+
await runGit(["add", `${getPkgDir(selectedPkg)}/CHANGELOG.md`]);
|
|
1101
|
+
Object.assign(ctx.github, { changelog });
|
|
1280
1102
|
return success(MSG.TASK.CHANGELOG.END, dryRun);
|
|
1281
1103
|
},
|
|
1282
1104
|
enabled: isIncrement
|
|
1283
1105
|
}]).catch((err) => abortOnError(err, ctx));
|
|
1106
|
+
const changeset = await getStatus();
|
|
1107
|
+
if (changeset) msg("GIT", `Changes:${eol(2)}` + coloredChangeset(changeset));
|
|
1108
|
+
else msg("GIT", MSG.LOG.CHANGES_EMPTY);
|
|
1284
1109
|
}
|
|
1285
1110
|
async gitTask(ctx, config) {
|
|
1286
1111
|
const { noGit, dryRun } = ctx;
|
|
@@ -1298,7 +1123,7 @@ var Action = class {
|
|
|
1298
1123
|
await runLifeCycleHook(hooks, "after:push", dryRun);
|
|
1299
1124
|
}
|
|
1300
1125
|
async npmTask(ctx, config) {
|
|
1301
|
-
const { noNpm, dryRun, isCI } = ctx;
|
|
1126
|
+
const { noNpm, dryRun, isCI, pkg: { name, next } } = ctx;
|
|
1302
1127
|
const { hooks } = config;
|
|
1303
1128
|
if (noNpm) return;
|
|
1304
1129
|
await runNpmPublishPrompts(ctx, config);
|
|
@@ -1312,7 +1137,7 @@ var Action = class {
|
|
|
1312
1137
|
await publishNpm(ctx, config);
|
|
1313
1138
|
} else throw err;
|
|
1314
1139
|
});
|
|
1315
|
-
const url = getPackageUrl(
|
|
1140
|
+
const url = getPackageUrl(name, next);
|
|
1316
1141
|
return success(MSG.TASK.NPM.END(url), dryRun);
|
|
1317
1142
|
}
|
|
1318
1143
|
}]).catch((err) => abortOnError(err, ctx));
|
|
@@ -1327,8 +1152,9 @@ var Action = class {
|
|
|
1327
1152
|
await tasks([{
|
|
1328
1153
|
title: MSG.TASK.GITHUB.START,
|
|
1329
1154
|
task: async () => {
|
|
1155
|
+
const { github: { owner, repo }, git: { currentTag } } = ctx;
|
|
1330
1156
|
await createRelease(ctx, config);
|
|
1331
|
-
const url = getGithubReleaseUrl(
|
|
1157
|
+
const url = getGithubReleaseUrl(owner, repo, currentTag);
|
|
1332
1158
|
return success(MSG.TASK.GITHUB.END(url), dryRun);
|
|
1333
1159
|
}
|
|
1334
1160
|
}]).catch((err) => abortOnError(err, ctx));
|
|
@@ -1339,9 +1165,33 @@ var Action = class {
|
|
|
1339
1165
|
//#endregion
|
|
1340
1166
|
//#region src/cli.ts
|
|
1341
1167
|
const pkg = readJsonFile(join(__dirname, "..", "package.json"));
|
|
1342
|
-
program.name(
|
|
1343
|
-
await new Action().handle(
|
|
1344
|
-
})
|
|
1168
|
+
program.name("release").description(pkg.description).version(pkg.version, "-v, --version", "Print the tool version and exit.").usage("[release-type] [options]").argument("[release-type]", "Version bump type: patch | minor | major").option("-n, --dry-run", "Run in dry mode: show what would be released without making any changes.", false).option("-p, --package <pkg>", "Specify package name for mono-repo release (CI only).", "").option("--otp <code>", "One-time password for npm publish (used for 2FA).", "").option("--ci", "Enable CI mode: disable prompts and fail on missing required options.", false).option("--show-changelog", "Print the generated changelog and exit.", false).option("--show-release", "Print the version that would be released and exit.", false).helpOption("-h, --help", "Display help information.").action(async (releaseType, options) => {
|
|
1169
|
+
await new Action().handle(releaseType, options);
|
|
1170
|
+
});
|
|
1171
|
+
program.command("init").description("Create a release configuration file").option("-f, --force", "Overwrite existing config file", false).option("-m, --monorepo", "Mono-repo project", false).action((options) => {
|
|
1172
|
+
const { force = false, monorepo = false } = options;
|
|
1173
|
+
const cwd = process.cwd();
|
|
1174
|
+
const { type } = readJsonFile(join(cwd, "package.json"));
|
|
1175
|
+
const isESM = type === "module";
|
|
1176
|
+
const isTS = existsSync(join(cwd, "tsconfig.json"));
|
|
1177
|
+
const configPath = join(cwd, `release.config.${isESM ? "" : "m"}${isTS ? "ts" : "js"}`);
|
|
1178
|
+
if (existsSync(configPath) && !force) {
|
|
1179
|
+
console.error(`${red`Error:`} ${yellow`release.config.ts`} already exists.\nUse ${green`--force`} to overwrite.`);
|
|
1180
|
+
process.exit(1);
|
|
1181
|
+
}
|
|
1182
|
+
const monoConfigToString = () => `{
|
|
1183
|
+
isMonorepo: true,
|
|
1184
|
+
packages: [ 'demo-a', 'demo-b' ],
|
|
1185
|
+
getPkgDir: (pkg) => \`packages/\${ pkg }\`,
|
|
1186
|
+
toTag: (pkg, version) => \`\${ pkg }@\${ version }\`,
|
|
1187
|
+
tagPrefix: (pkg) => \`\${ pkg }@\`,
|
|
1188
|
+
}`;
|
|
1189
|
+
writeFileSync(configPath, `import { defineConfig } from '@peiyanlu/release'
|
|
1190
|
+
|
|
1191
|
+
export default defineConfig(${monorepo ? monoConfigToString() : "{}"})`, "utf-8");
|
|
1192
|
+
console.log(`${green`✔ release.config.ts`} created successfully`);
|
|
1193
|
+
});
|
|
1194
|
+
program.parse(process.argv);
|
|
1345
1195
|
program.on("command:*", () => {
|
|
1346
1196
|
console.error(`\n${red`Error`} Invalid command: ${red`%s`}`, program.args.join(" "));
|
|
1347
1197
|
console.log(`See ${red`--help`} for a list of available commands.\n`);
|