@peiyanlu/release 0.0.14 → 0.0.16
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 +6 -2
- package/dist/cli.mjs +110 -98
- package/dist/index.d.mts +24 -13
- package/dist/index.mjs +36 -29
- package/package.json +9 -8
package/README.md
CHANGED
|
@@ -122,7 +122,9 @@ export default defineConfig({
|
|
|
122
122
|
packages: [],
|
|
123
123
|
getPkgDir: (pkg) => `.`,
|
|
124
124
|
toTag: (pkg: string, version: string) => `v${ version }`,
|
|
125
|
-
|
|
125
|
+
changelog: {
|
|
126
|
+
tagPrefix: undefined,
|
|
127
|
+
},
|
|
126
128
|
|
|
127
129
|
git: {
|
|
128
130
|
commit: true,
|
|
@@ -157,7 +159,9 @@ export default defineConfig({
|
|
|
157
159
|
packages: [ 'demo-a', 'demo-b' ],
|
|
158
160
|
getPkgDir: (pkg) => `packages/${ pkg }`,
|
|
159
161
|
toTag: (pkg: string, version: string) => `${ pkg }@${ version }`,
|
|
160
|
-
|
|
162
|
+
changelog: {
|
|
163
|
+
tagPrefix: (pkg: string) => `${ pkg }@`,
|
|
164
|
+
},
|
|
161
165
|
|
|
162
166
|
git: {
|
|
163
167
|
commit: true,
|
package/dist/cli.mjs
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import path, { join, resolve } from "node:path";
|
|
2
2
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
3
|
-
import { bumpPackageVersion,
|
|
3
|
+
import { bumpPackageVersion, canPublish, coloredStatus, eol, execAsync, getAuthenticatedUser, getGithubReleaseUrl, getGithubUrl, getLatestTag, getLogSince, getPackageInfo, getPackageUrl, getPublishedVersion, getRemoteList, getRemoteNames, getShortStatus, gitAddAll, gitAddTracked, gitBranchCurrent, gitCommitMessage, gitPushBranch, gitPushTag, gitResetHardSync, gitTagAnnotated, gitTagDeleteSync, hasWriteAccess, isGitRepo, isOtpError, isPrerelease, isValidVersion, isWorkingTreeClean, parseGitHubRepo, parseVersion, pingRegistry, publishPackage, readJsonFile, resolveChangelogRange, resolvePublishTag } from "@peiyanlu/cli-utils";
|
|
4
4
|
import { dim, green, red, rgb, underline, yellow } from "ansis";
|
|
5
5
|
import { program } from "commander";
|
|
6
6
|
import { createReadStream, createWriteStream, existsSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
7
7
|
import { cancel, confirm, group, intro, isCancel, log, outro, select, tasks, text } from "@clack/prompts";
|
|
8
|
+
import { castArray, isFunction, isPlainObject, isUndefined, isZero } from "@peiyanlu/ts-utils";
|
|
8
9
|
import { publint } from "publint";
|
|
9
10
|
import { formatMessage } from "publint/utils";
|
|
10
11
|
import { inc, neq } from "semver";
|
|
11
|
-
import { castArray, isFunction, isPlainObject, isUndefined } from "@peiyanlu/ts-utils";
|
|
12
|
-
import { spawnSync } from "node:child_process";
|
|
13
12
|
import { inspect } from "node:util";
|
|
14
13
|
import { ConventionalChangelog } from "conventional-changelog";
|
|
15
14
|
import createPreset from "conventional-changelog-conventionalcommits";
|
|
@@ -19,7 +18,7 @@ import { Octokit } from "@octokit/rest";
|
|
|
19
18
|
import { contentType } from "mime-types";
|
|
20
19
|
import open from "open";
|
|
21
20
|
import { glob } from "tinyglobby";
|
|
22
|
-
//#region node_modules/.pnpm/tsdown@0.22.
|
|
21
|
+
//#region node_modules/.pnpm/tsdown@0.22.3_publint@0.3.21_typescript@6.0.3/node_modules/tsdown/esm-shims.js
|
|
23
22
|
const getFilename = () => fileURLToPath(import.meta.url);
|
|
24
23
|
const getDirname = () => path.dirname(getFilename());
|
|
25
24
|
const __dirname = /* @__PURE__ */ getDirname();
|
|
@@ -45,22 +44,21 @@ const loadConfig = async (file) => {
|
|
|
45
44
|
const resolveConfig = async (cwd) => {
|
|
46
45
|
const configFile = findConfigFile(cwd);
|
|
47
46
|
if (!configFile) return {
|
|
48
|
-
|
|
47
|
+
configFile: "",
|
|
49
48
|
config: {}
|
|
50
49
|
};
|
|
51
50
|
return {
|
|
52
|
-
|
|
51
|
+
configFile,
|
|
53
52
|
config: await loadConfig(configFile)
|
|
54
53
|
};
|
|
55
54
|
};
|
|
56
55
|
const mergeConfig = (defaults, overrides) => {
|
|
57
|
-
if (!overrides) return structuredClone(defaults);
|
|
58
|
-
if (Object.keys(defaults).length === 0) return structuredClone(overrides);
|
|
59
56
|
const result = {};
|
|
60
|
-
for (const key
|
|
57
|
+
for (const key of Object.keys(defaults)) {
|
|
61
58
|
const defaultValue = defaults[key];
|
|
62
59
|
const overrideValue = overrides[key];
|
|
63
|
-
if (overrideValue
|
|
60
|
+
if (isUndefined(overrideValue)) if (isFunction(defaultValue)) result[key] = defaultValue;
|
|
61
|
+
else if (isPlainObject(defaultValue)) result[key] = mergeConfig(defaultValue, {});
|
|
64
62
|
else result[key] = structuredClone(defaultValue);
|
|
65
63
|
else if (isPlainObject(defaultValue) && isPlainObject(overrideValue)) result[key] = mergeConfig(defaultValue, overrideValue);
|
|
66
64
|
else if (isFunction(overrideValue)) result[key] = overrideValue;
|
|
@@ -134,13 +132,14 @@ const MSG = {
|
|
|
134
132
|
GIT_WORKDIR: `[git] Working dir must be clean`,
|
|
135
133
|
GIT_REGISTRY: (name) => `[git] Repository not found for ${dim(name)}`,
|
|
136
134
|
GIT_REMOTE: (name) => `[git] Remote "${dim(name)}" not found`,
|
|
137
|
-
GITHUB_TAG_EXIT: (tag) => `[github] Release
|
|
135
|
+
GITHUB_TAG_EXIT: (tag) => `[github] Release for tag "${tag}" already exists and is not a draft`,
|
|
138
136
|
GITHUB_TOKEN: (tokenRef) => `[github] Missing GitHub token. Please set ${yellow(tokenRef ?? "GITHUB_TOKEN")}`,
|
|
139
137
|
GITHUB_AUTH: "[github] Invalid GitHub token or insufficient permissions",
|
|
140
138
|
GITHUB_USER: "[github] User does not have permission to create releases",
|
|
141
139
|
NPM_REGISTRY: (registry) => `[npm] Unable to reach npm registry ${underline(dim(registry))}`,
|
|
142
|
-
NPM_AUTH: `[npm] Not authenticated with npm. Please run ${yellow("npm login")} and try again
|
|
143
|
-
|
|
140
|
+
NPM_AUTH: `[npm] Not authenticated with npm. Please run ${yellow("npm login")} and try again`,
|
|
141
|
+
NPM_PERMISSION: `[npm] Missing publish permission. Please use a token with publish access and try again`,
|
|
142
|
+
NPM_USER: (user, name) => `[npm] User ${dim(user)} is not a collaborator of ${dim(name)}`
|
|
144
143
|
},
|
|
145
144
|
ABORT: {
|
|
146
145
|
CANCEL: "Operation cancelled",
|
|
@@ -168,12 +167,12 @@ const gitCheck = async (ctx, config) => {
|
|
|
168
167
|
ctx.noGitHub = true;
|
|
169
168
|
return;
|
|
170
169
|
}
|
|
171
|
-
if (requireWorkDirClean && !await
|
|
170
|
+
if (requireWorkDirClean && !await isWorkingTreeClean()) throw new Error(MSG.ERROR.GIT_WORKDIR);
|
|
172
171
|
if (ignoreGithub) {
|
|
173
172
|
ctx.noGitHub = true;
|
|
174
173
|
return;
|
|
175
174
|
}
|
|
176
|
-
const { remoteName, remoteUrl } = await
|
|
175
|
+
const { name: remoteName, url: remoteUrl } = (await getRemoteList())[0];
|
|
177
176
|
if (!remoteUrl) if (requireRemote) throw new Error(MSG.ERROR.GIT_REMOTE(name));
|
|
178
177
|
else {
|
|
179
178
|
ctx.noGitHub = true;
|
|
@@ -194,18 +193,18 @@ const commitAndTag = async (ctx, config) => {
|
|
|
194
193
|
const { git: { commit, tag, push, addUntrackedFiles, commitArgs, tagArgs, pushArgs } } = config;
|
|
195
194
|
if (!commit) return;
|
|
196
195
|
const args = dryRun ? ["--dry-run"] : [];
|
|
197
|
-
addUntrackedFiles ? await gitAddAll() : await gitAddTracked(args);
|
|
198
|
-
await
|
|
196
|
+
addUntrackedFiles ? await gitAddAll(args) : await gitAddTracked(args);
|
|
197
|
+
await gitCommitMessage(commitMessage, [...commitArgs, ...args]);
|
|
199
198
|
if (!dryRun) Object.assign(ctx.git, { isCommitted: true });
|
|
200
199
|
if (tag) {
|
|
201
200
|
await gitTagAnnotated(currentTag, tagMessage, [...tagArgs, ...args]);
|
|
202
201
|
if (!dryRun) Object.assign(ctx.git, { isTagged: true });
|
|
203
202
|
}
|
|
204
203
|
if (push) {
|
|
205
|
-
const remotes = await
|
|
204
|
+
const remotes = await getRemoteNames();
|
|
206
205
|
for (const remoteName of remotes) {
|
|
207
|
-
await
|
|
208
|
-
await
|
|
206
|
+
await gitPushTag(remoteName, currentTag, args);
|
|
207
|
+
await gitPushBranch(remoteName, await gitBranchCurrent(), [...pushArgs, ...args]);
|
|
209
208
|
}
|
|
210
209
|
if (!dryRun) Object.assign(ctx.git, { isPushed: true });
|
|
211
210
|
}
|
|
@@ -213,40 +212,8 @@ const commitAndTag = async (ctx, config) => {
|
|
|
213
212
|
const gitRollback = (ctx) => {
|
|
214
213
|
const { git: { currentTag, isCommitted, isTagged, isPushed } } = ctx;
|
|
215
214
|
if (isPushed) return;
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
"tag",
|
|
219
|
-
"--delete",
|
|
220
|
-
currentTag
|
|
221
|
-
]);
|
|
222
|
-
spawnSync("git", [
|
|
223
|
-
"reset",
|
|
224
|
-
"--hard",
|
|
225
|
-
isCommitted ? "HEAD~1" : "HEAD"
|
|
226
|
-
]);
|
|
227
|
-
};
|
|
228
|
-
//#endregion
|
|
229
|
-
//#region src/prompts.ts
|
|
230
|
-
const abortTask = (msg) => {
|
|
231
|
-
cancel(`${red("✕")} ${msg}`);
|
|
232
|
-
process.exit(0);
|
|
233
|
-
};
|
|
234
|
-
const abortSinglePrompt = (value) => {
|
|
235
|
-
if (isCancel(value)) abortTask(MSG.ABORT.CANCEL);
|
|
236
|
-
};
|
|
237
|
-
const abortGroupPrompt = (ctx) => {
|
|
238
|
-
ctx && gitRollback(ctx);
|
|
239
|
-
abortTask(MSG.ABORT.CANCEL);
|
|
240
|
-
};
|
|
241
|
-
const abortOnError = (err, ctx, exit = true) => {
|
|
242
|
-
ctx && gitRollback(ctx);
|
|
243
|
-
const msg = err.message.replace(/^\[(github|npm|git)]/, (a) => red(a));
|
|
244
|
-
log.message(`\n${msg}\n`);
|
|
245
|
-
exit && process.exit(1);
|
|
246
|
-
};
|
|
247
|
-
const taskEnd = (msg) => {
|
|
248
|
-
outro(msg);
|
|
249
|
-
process.exit(0);
|
|
215
|
+
if (isTagged) gitTagDeleteSync(currentTag);
|
|
216
|
+
gitResetHardSync(isCommitted ? 1 : 0);
|
|
250
217
|
};
|
|
251
218
|
//#endregion
|
|
252
219
|
//#region src/utils.ts
|
|
@@ -301,6 +268,27 @@ const formatTemplate = async (ctx, config) => {
|
|
|
301
268
|
});
|
|
302
269
|
Object.assign(ctx.github, { releaseName });
|
|
303
270
|
};
|
|
271
|
+
const abortTask = (msg) => {
|
|
272
|
+
cancel(`${red("✕")} ${msg}`);
|
|
273
|
+
process.exit(0);
|
|
274
|
+
};
|
|
275
|
+
const abortSinglePrompt = (value) => {
|
|
276
|
+
if (isCancel(value)) abortTask(MSG.ABORT.CANCEL);
|
|
277
|
+
};
|
|
278
|
+
const abortGroupPrompt = (ctx) => {
|
|
279
|
+
ctx && gitRollback(ctx);
|
|
280
|
+
abortTask(MSG.ABORT.CANCEL);
|
|
281
|
+
};
|
|
282
|
+
const abortOnError = (err, ctx, exit = true) => {
|
|
283
|
+
ctx && gitRollback(ctx);
|
|
284
|
+
const msg = err.message.replace(/^\[(github|npm|git)]/, (a) => red(a));
|
|
285
|
+
log.message(`\n${msg}\n`);
|
|
286
|
+
exit && process.exit(1);
|
|
287
|
+
};
|
|
288
|
+
const taskEnd = (msg) => {
|
|
289
|
+
outro(msg);
|
|
290
|
+
process.exit(0);
|
|
291
|
+
};
|
|
304
292
|
//#endregion
|
|
305
293
|
//#region src/version/prompts.ts
|
|
306
294
|
const createVersionMap = (current) => ({
|
|
@@ -348,7 +336,7 @@ const runVersionPrompts = async (ctx, config) => {
|
|
|
348
336
|
message: question(MSG.PROMPT.INPUT_VERSION, "version"),
|
|
349
337
|
initialValue: current,
|
|
350
338
|
validate: (val) => {
|
|
351
|
-
if (!
|
|
339
|
+
if (isUndefined(val) || !isValidVersion(val)) return "Invalid version";
|
|
352
340
|
}
|
|
353
341
|
});
|
|
354
342
|
abortSinglePrompt(version);
|
|
@@ -444,8 +432,11 @@ const createDefaultConfig = (isCI) => {
|
|
|
444
432
|
toTag(pkg, version) {
|
|
445
433
|
return `v${version}`;
|
|
446
434
|
},
|
|
447
|
-
|
|
448
|
-
|
|
435
|
+
changelog: {
|
|
436
|
+
tagPrefix(pkg) {
|
|
437
|
+
return "";
|
|
438
|
+
},
|
|
439
|
+
releaseCount: 1
|
|
449
440
|
},
|
|
450
441
|
ignoreGit: false,
|
|
451
442
|
ignoreNpm: false,
|
|
@@ -521,42 +512,42 @@ const defaultTypes = [
|
|
|
521
512
|
},
|
|
522
513
|
{
|
|
523
514
|
type: "chore",
|
|
524
|
-
section: "
|
|
515
|
+
section: "🧹 其他更新",
|
|
525
516
|
description: "非功能性更改"
|
|
526
517
|
},
|
|
527
518
|
{
|
|
528
519
|
type: "refactor",
|
|
529
|
-
section: "
|
|
520
|
+
section: "♻️ 代码重构",
|
|
530
521
|
description: "重构代码"
|
|
531
522
|
},
|
|
532
523
|
{
|
|
533
524
|
type: "test",
|
|
534
|
-
section: "
|
|
525
|
+
section: "🧪 测试",
|
|
535
526
|
description: "测试更新"
|
|
536
527
|
},
|
|
537
528
|
{
|
|
538
529
|
type: "build",
|
|
539
|
-
section: "
|
|
530
|
+
section: "🏗️ 构建系统",
|
|
540
531
|
description: "构建工具和系统修改"
|
|
541
532
|
},
|
|
542
533
|
{
|
|
543
534
|
type: "ci",
|
|
544
|
-
section: "
|
|
535
|
+
section: "🤖 CI",
|
|
545
536
|
description: "持续集成配置"
|
|
546
537
|
},
|
|
547
538
|
{
|
|
548
539
|
type: "config",
|
|
549
|
-
section: "
|
|
540
|
+
section: "⚙️ 配置",
|
|
550
541
|
description: "配置文件更新"
|
|
551
542
|
},
|
|
552
543
|
{
|
|
553
544
|
type: "deps",
|
|
554
|
-
section: "
|
|
545
|
+
section: "📦 依赖更新",
|
|
555
546
|
description: "依赖版本变更"
|
|
556
547
|
},
|
|
557
548
|
{
|
|
558
549
|
type: "security",
|
|
559
|
-
section: "
|
|
550
|
+
section: "🛡️ 安全修复",
|
|
560
551
|
description: "安全性更新"
|
|
561
552
|
},
|
|
562
553
|
{
|
|
@@ -566,13 +557,18 @@ const defaultTypes = [
|
|
|
566
557
|
},
|
|
567
558
|
{
|
|
568
559
|
type: "ux",
|
|
569
|
-
section: "
|
|
560
|
+
section: "🎨 用户体验",
|
|
570
561
|
description: "用户体验改进"
|
|
571
562
|
},
|
|
572
563
|
{
|
|
573
564
|
type: "hotfix",
|
|
574
|
-
section: "
|
|
565
|
+
section: "🚑 热修复",
|
|
575
566
|
description: "紧急修复"
|
|
567
|
+
},
|
|
568
|
+
{
|
|
569
|
+
type: "tool",
|
|
570
|
+
section: "🛠️ 工具链",
|
|
571
|
+
description: "工具链变更"
|
|
576
572
|
}
|
|
577
573
|
];
|
|
578
574
|
//#endregion
|
|
@@ -583,8 +579,7 @@ const parsePreset = async () => {
|
|
|
583
579
|
hidden: false
|
|
584
580
|
})) });
|
|
585
581
|
preset.writer ??= {};
|
|
586
|
-
preset.writer.headerPartial =
|
|
587
|
-
## {{#if isPatch~}} <small> {{~/if~}}
|
|
582
|
+
preset.writer.headerPartial = `## {{#if isPatch~}} <small> {{~/if~}}
|
|
588
583
|
{{#if @root.linkCompare~}}
|
|
589
584
|
[{{version}}](
|
|
590
585
|
{{~#if @root.repository~}}
|
|
@@ -608,8 +603,7 @@ const parsePreset = async () => {
|
|
|
608
603
|
{{~/if}}
|
|
609
604
|
{{~#if isPatch~}} </small> {{~/if}}
|
|
610
605
|
`.trim() + eol();
|
|
611
|
-
preset.writer.mainTemplate = `
|
|
612
|
-
{{> header}}
|
|
606
|
+
preset.writer.mainTemplate = `{{> header}}
|
|
613
607
|
{{#if noteGroups}}
|
|
614
608
|
{{#each noteGroups}}
|
|
615
609
|
|
|
@@ -632,32 +626,37 @@ const parsePreset = async () => {
|
|
|
632
626
|
{{/each}}`.trim();
|
|
633
627
|
return preset;
|
|
634
628
|
};
|
|
635
|
-
const createGenerator = async ({ getPkgDir, tagPrefix }) => {
|
|
629
|
+
const createGenerator = async ({ getPkgDir, tagPrefix, releaseCount = 1 }) => {
|
|
636
630
|
const pkgDir = getPkgDir();
|
|
637
631
|
const preset = await parsePreset();
|
|
638
|
-
return new ConventionalChangelog().readPackage(`${pkgDir}/package.json`).config(preset).options({ releaseCount
|
|
632
|
+
return new ConventionalChangelog().readPackage(`${pkgDir}/package.json`).config(preset).options({ releaseCount }).commits({ path: pkgDir }).tags({ prefix: tagPrefix });
|
|
639
633
|
};
|
|
640
|
-
const getChangelog = async ({ getPkgDir, tagPrefix }) => {
|
|
634
|
+
const getChangelog = async ({ getPkgDir, tagPrefix, releaseCount = 1 }) => {
|
|
641
635
|
const generator = await createGenerator({
|
|
642
636
|
getPkgDir,
|
|
643
|
-
tagPrefix
|
|
637
|
+
tagPrefix,
|
|
638
|
+
releaseCount
|
|
644
639
|
});
|
|
645
640
|
let changelog = "";
|
|
646
641
|
for await (const chunk of generator.write()) changelog += chunk;
|
|
647
|
-
return changelog;
|
|
642
|
+
return changelog.trimEnd();
|
|
648
643
|
};
|
|
649
|
-
const generateChangelog = async ({ getPkgDir, tagPrefix }) => {
|
|
644
|
+
const generateChangelog = async ({ getPkgDir, tagPrefix, releaseCount = 1 }) => {
|
|
650
645
|
const infile = join(getPkgDir(), "CHANGELOG.md");
|
|
651
646
|
if (!existsSync(infile)) await writeFile(infile, "");
|
|
652
647
|
const exist = readFileSync(infile, "utf-8");
|
|
648
|
+
const writeStream = createWriteStream(infile);
|
|
653
649
|
const generator = await createGenerator({
|
|
654
650
|
getPkgDir,
|
|
655
|
-
tagPrefix
|
|
651
|
+
tagPrefix,
|
|
652
|
+
releaseCount
|
|
656
653
|
});
|
|
657
|
-
const writeStream = createWriteStream(infile);
|
|
658
654
|
for await (const chunk of generator.write()) writeStream.write(chunk);
|
|
659
|
-
|
|
660
|
-
|
|
655
|
+
const override = isZero(releaseCount);
|
|
656
|
+
if (!(isZero(exist.trim().length) || override)) {
|
|
657
|
+
writeStream.write(eol());
|
|
658
|
+
writeStream.write(exist);
|
|
659
|
+
}
|
|
661
660
|
writeStream.end();
|
|
662
661
|
await finished(writeStream);
|
|
663
662
|
};
|
|
@@ -776,7 +775,7 @@ const createCReleaseByTag = async (octokit, options) => {
|
|
|
776
775
|
const existing = await getReleaseByTagSafe(octokit, options);
|
|
777
776
|
if (!existing) return createCRelease(octokit, { ...options });
|
|
778
777
|
const { draft, id: releaseId } = existing;
|
|
779
|
-
if (!draft) throw new Error(
|
|
778
|
+
if (!draft) throw new Error(MSG.ERROR.GITHUB_TAG_EXIT(options.tag));
|
|
780
779
|
return updateCRelease(octokit, {
|
|
781
780
|
releaseId,
|
|
782
781
|
...options
|
|
@@ -925,9 +924,14 @@ const npmCheck = async (ctx, config) => {
|
|
|
925
924
|
const tag = await resolvePublishTag(name, current);
|
|
926
925
|
Object.assign(ctx.npm, { tag });
|
|
927
926
|
if (skipChecks) return "";
|
|
928
|
-
const [pinged, username] = await Promise.all([
|
|
927
|
+
const [pinged, username, canPublished] = await Promise.all([
|
|
928
|
+
pingRegistry(registry),
|
|
929
|
+
getAuthenticatedUser(registry),
|
|
930
|
+
canPublish(registry)
|
|
931
|
+
]);
|
|
929
932
|
if (!pinged) throw new Error(MSG.ERROR.NPM_REGISTRY(registry));
|
|
930
933
|
if (!username) throw new Error(MSG.ERROR.NPM_AUTH);
|
|
934
|
+
if (!canPublished) throw new Error(MSG.ERROR.NPM_PERMISSION);
|
|
931
935
|
Object.assign(ctx.npm, { username });
|
|
932
936
|
const latest = await getPublishedVersion(`${name}@${tag}`);
|
|
933
937
|
if (latest) {
|
|
@@ -979,10 +983,10 @@ var Action = class {
|
|
|
979
983
|
const { otp, package: defPkg, ...others } = options;
|
|
980
984
|
const { showChangelog, showRelease, ci, dryRun } = Object.fromEntries(Object.entries(others).map(([k, v]) => [k, Boolean(v)]));
|
|
981
985
|
process.env["dryRun"] = String(dryRun);
|
|
982
|
-
const {
|
|
986
|
+
const { configFile, config: local } = await resolveConfig(process.cwd());
|
|
983
987
|
const config = mergeConfig(createDefaultConfig(ci ? true : void 0), local);
|
|
984
988
|
info(MSG.INFO.TOOL(cName, cVersion));
|
|
985
|
-
info(MSG.INFO.CONFIG(
|
|
989
|
+
info(MSG.INFO.CONFIG(configFile));
|
|
986
990
|
console.log();
|
|
987
991
|
intro(prepare ? MSG.INTRO_PREPARE(dryRun) : MSG.INTRO(dryRun));
|
|
988
992
|
const { isMonorepo, packages, getPkgDir, ignoreNpm } = config;
|
|
@@ -1077,7 +1081,7 @@ var Action = class {
|
|
|
1077
1081
|
}
|
|
1078
1082
|
async bumpTask(ctx, config) {
|
|
1079
1083
|
const { pkg: { current }, dryRun, isCI, showRelease, showChangelog, selectedPkg, noGit } = ctx;
|
|
1080
|
-
const { isMonorepo, hooks, getPkgDir, tagPrefix } = config;
|
|
1084
|
+
const { isMonorepo, hooks, getPkgDir, changelog: { tagPrefix, releaseCount } } = config;
|
|
1081
1085
|
const need = (ctx) => {
|
|
1082
1086
|
const { pkg: { next }, isIncrement } = ctx;
|
|
1083
1087
|
return !next && isIncrement;
|
|
@@ -1107,37 +1111,43 @@ var Action = class {
|
|
|
1107
1111
|
}]).catch((err) => abortOnError(err, ctx));
|
|
1108
1112
|
await runLifeCycleHook(hooks, "after:bump", dryRun);
|
|
1109
1113
|
if (noGit) return;
|
|
1110
|
-
const
|
|
1111
|
-
const
|
|
1114
|
+
const match = isMonorepo ? `${tagPrefix?.(selectedPkg)}*` : "*";
|
|
1115
|
+
const { from, to } = isZero(releaseCount) ? {
|
|
1116
|
+
from: "",
|
|
1117
|
+
to: "HEAD"
|
|
1118
|
+
} : await resolveChangelogRange(isIncrement, match);
|
|
1119
|
+
const commits = await getLogSince(from, to, getPkgDir(selectedPkg));
|
|
1112
1120
|
const changelog = await getChangelog({
|
|
1113
1121
|
getPkgDir: () => getPkgDir(selectedPkg),
|
|
1114
|
-
tagPrefix: tagPrefix?.(selectedPkg)
|
|
1122
|
+
tagPrefix: tagPrefix?.(selectedPkg),
|
|
1123
|
+
releaseCount
|
|
1115
1124
|
});
|
|
1116
1125
|
Object.assign(ctx.github, { changelog });
|
|
1117
1126
|
if (commits) {
|
|
1118
|
-
msg("GIT", `Changelog(${from}...${to}):${eol(2)}` + changelog
|
|
1127
|
+
msg("GIT", `Changelog(${from}...${to}):${eol(2)}` + changelog);
|
|
1119
1128
|
msg("GIT", `Commits(${from}...${to}):${eol(2)}` + commits);
|
|
1120
1129
|
if (showChangelog) taskEnd(MSG.LOG.SHOW_CHANGELOG);
|
|
1121
1130
|
} else msg("GIT", MSG.LOG.CHANGELOG_EMPTY);
|
|
1122
1131
|
}
|
|
1123
1132
|
async changelogTask(ctx, config) {
|
|
1124
1133
|
const { isIncrement, dryRun, selectedPkg, noGit } = ctx;
|
|
1125
|
-
const { getPkgDir, tagPrefix } = config;
|
|
1134
|
+
const { getPkgDir, changelog: { tagPrefix, releaseCount } } = config;
|
|
1126
1135
|
if (noGit) return;
|
|
1127
1136
|
await tasks([{
|
|
1128
1137
|
title: MSG.TASK.CHANGELOG.START,
|
|
1129
1138
|
task: async () => {
|
|
1130
1139
|
await generateChangelog({
|
|
1131
1140
|
getPkgDir: () => getPkgDir(selectedPkg),
|
|
1132
|
-
tagPrefix: tagPrefix?.(selectedPkg)
|
|
1141
|
+
tagPrefix: tagPrefix?.(selectedPkg),
|
|
1142
|
+
releaseCount
|
|
1133
1143
|
});
|
|
1134
1144
|
await gitAddAll();
|
|
1135
1145
|
return success(MSG.TASK.CHANGELOG.END, dryRun);
|
|
1136
1146
|
},
|
|
1137
1147
|
enabled: isIncrement
|
|
1138
1148
|
}]).catch((err) => abortOnError(err, ctx));
|
|
1139
|
-
const changeset = await
|
|
1140
|
-
if (changeset) msg("GIT", `Changes:${eol(2)}` +
|
|
1149
|
+
const changeset = await getShortStatus();
|
|
1150
|
+
if (changeset) msg("GIT", `Changes:${eol(2)}` + coloredStatus(changeset));
|
|
1141
1151
|
else msg("GIT", MSG.LOG.CHANGES_EMPTY);
|
|
1142
1152
|
}
|
|
1143
1153
|
async gitTask(ctx, config) {
|
|
@@ -1212,8 +1222,8 @@ program.command("init").description("Create a release configuration file").optio
|
|
|
1212
1222
|
const isESM = type === "module";
|
|
1213
1223
|
const isTS = existsSync(join(cwd, "tsconfig.json"));
|
|
1214
1224
|
const infile = `release.config.${isESM ? "" : "m"}${isTS ? "ts" : "js"}`;
|
|
1215
|
-
const
|
|
1216
|
-
if (existsSync(
|
|
1225
|
+
const configFile = join(cwd, infile);
|
|
1226
|
+
if (existsSync(configFile) && !force) {
|
|
1217
1227
|
console.error(`${red`Error:`} ${yellow(infile)} already exists.\nUse ${green`--force`} to overwrite.`);
|
|
1218
1228
|
process.exit(1);
|
|
1219
1229
|
}
|
|
@@ -1222,9 +1232,11 @@ program.command("init").description("Create a release configuration file").optio
|
|
|
1222
1232
|
packages: [ 'demo-a', 'demo-b' ],
|
|
1223
1233
|
getPkgDir: (pkg) => \`packages/\${ pkg }\`,
|
|
1224
1234
|
toTag: (pkg, version) => \`\${ pkg }@\${ version }\`,
|
|
1225
|
-
|
|
1235
|
+
changelog: {
|
|
1236
|
+
tagPrefix: (pkg) => \`\${ pkg }@\`,
|
|
1237
|
+
},
|
|
1226
1238
|
}`;
|
|
1227
|
-
writeFileSync(
|
|
1239
|
+
writeFileSync(configFile, [`import { defineConfig } from '@peiyanlu/release'`, `export default defineConfig(${monorepo ? monoConfigToString() : "{}"})`].join(eol(2)), "utf-8");
|
|
1228
1240
|
console.log(`${green`✔`} ${infile} created successfully`);
|
|
1229
1241
|
});
|
|
1230
1242
|
program.parse(process.argv);
|
package/dist/index.d.mts
CHANGED
|
@@ -23,14 +23,20 @@ interface ReleaseConfig {
|
|
|
23
23
|
getPkgDir: (pkg: string) => string;
|
|
24
24
|
/** 使用 monorepo 时使用 toTag,例如 `${pkg}@${version}`,默认为 `v${version}` 作为单仓库 */
|
|
25
25
|
toTag: (pkg: string, version: string) => string;
|
|
26
|
-
/**
|
|
27
|
-
|
|
26
|
+
/**
|
|
27
|
+
* CHANGELOG 相关配置
|
|
28
|
+
* 影响生成的日志数量、日志标题(monorepo)。
|
|
29
|
+
*/
|
|
30
|
+
changelog: {
|
|
31
|
+
/** 作为 CHANGELOG 标题的 Tag 前缀,多包场景下需要 `${pkg}@` */tagPrefix: ((pkg: string) => string); /** 生成多少个版本的变更日志,详见 {@link Options} */
|
|
32
|
+
releaseCount: number;
|
|
33
|
+
};
|
|
28
34
|
/** 忽略 git、github 环境检查以及后续操作 */
|
|
29
|
-
ignoreGit: boolean
|
|
35
|
+
ignoreGit: boolean;
|
|
30
36
|
/** 忽略 npm 环境检查以及后续操作 */
|
|
31
|
-
ignoreNpm: boolean
|
|
37
|
+
ignoreNpm: boolean;
|
|
32
38
|
/** 忽略 github 环境检查以及后续操作 */
|
|
33
|
-
ignoreGithub: boolean
|
|
39
|
+
ignoreGithub: boolean;
|
|
34
40
|
/**
|
|
35
41
|
* Git 相关配置
|
|
36
42
|
* 影响提交、打 tag、推送等本地/远程 Git 行为。
|
|
@@ -156,27 +162,32 @@ declare function defineConfig(config: UserConfigFnObject): UserConfigFnObject;
|
|
|
156
162
|
declare function defineConfig(config: UserConfigFnPromise): UserConfigFnPromise;
|
|
157
163
|
declare function defineConfig(config: UserConfigFn): UserConfigFn;
|
|
158
164
|
declare function defineConfig(config: UserConfigExport): UserConfigExport;
|
|
159
|
-
declare const mergeConfig: <T extends object>(defaults: DeepRequired<T>, overrides
|
|
165
|
+
declare const mergeConfig: <T extends object>(defaults: DeepRequired<T>, overrides: DeepPartial<T>) => DeepRequired<T>;
|
|
160
166
|
//#endregion
|
|
161
167
|
//#region src/git/changelog.d.ts
|
|
162
|
-
interface
|
|
168
|
+
interface GenerateOptions {
|
|
163
169
|
/** @example () => `packages/${pkg}` */
|
|
164
170
|
getPkgDir: () => string;
|
|
165
171
|
/** @example `${pkg}@` */
|
|
166
172
|
tagPrefix?: string;
|
|
173
|
+
/** 生成多少个变更日志,详见 {@link Options} */
|
|
174
|
+
releaseCount?: number;
|
|
167
175
|
}
|
|
168
176
|
declare const createGenerator: ({
|
|
169
177
|
getPkgDir,
|
|
170
|
-
tagPrefix
|
|
171
|
-
|
|
178
|
+
tagPrefix,
|
|
179
|
+
releaseCount
|
|
180
|
+
}: GenerateOptions) => Promise<ConventionalChangelog>;
|
|
172
181
|
declare const getChangelog: ({
|
|
173
182
|
getPkgDir,
|
|
174
|
-
tagPrefix
|
|
175
|
-
|
|
183
|
+
tagPrefix,
|
|
184
|
+
releaseCount
|
|
185
|
+
}: GenerateOptions) => Promise<string>;
|
|
176
186
|
declare const generateChangelog: ({
|
|
177
187
|
getPkgDir,
|
|
178
|
-
tagPrefix
|
|
179
|
-
|
|
188
|
+
tagPrefix,
|
|
189
|
+
releaseCount
|
|
190
|
+
}: GenerateOptions) => Promise<void>;
|
|
180
191
|
//#endregion
|
|
181
192
|
//#region src/npm/publish.d.ts
|
|
182
193
|
interface PublishTagOptions {
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isFunction, isPlainObject } from "@peiyanlu/ts-utils";
|
|
1
|
+
import { isFunction, isPlainObject, isUndefined, isZero } from "@peiyanlu/ts-utils";
|
|
2
2
|
import { createWriteStream, existsSync, readFileSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { eol, getPackageInfo, publishPackage, resolvePublishTag } from "@peiyanlu/cli-utils";
|
|
@@ -12,13 +12,12 @@ function defineConfig(config) {
|
|
|
12
12
|
return config;
|
|
13
13
|
}
|
|
14
14
|
const mergeConfig = (defaults, overrides) => {
|
|
15
|
-
if (!overrides) return structuredClone(defaults);
|
|
16
|
-
if (Object.keys(defaults).length === 0) return structuredClone(overrides);
|
|
17
15
|
const result = {};
|
|
18
|
-
for (const key
|
|
16
|
+
for (const key of Object.keys(defaults)) {
|
|
19
17
|
const defaultValue = defaults[key];
|
|
20
18
|
const overrideValue = overrides[key];
|
|
21
|
-
if (overrideValue
|
|
19
|
+
if (isUndefined(overrideValue)) if (isFunction(defaultValue)) result[key] = defaultValue;
|
|
20
|
+
else if (isPlainObject(defaultValue)) result[key] = mergeConfig(defaultValue, {});
|
|
22
21
|
else result[key] = structuredClone(defaultValue);
|
|
23
22
|
else if (isPlainObject(defaultValue) && isPlainObject(overrideValue)) result[key] = mergeConfig(defaultValue, overrideValue);
|
|
24
23
|
else if (isFunction(overrideValue)) result[key] = overrideValue;
|
|
@@ -66,42 +65,42 @@ const defaultTypes = [
|
|
|
66
65
|
},
|
|
67
66
|
{
|
|
68
67
|
type: "chore",
|
|
69
|
-
section: "
|
|
68
|
+
section: "🧹 其他更新",
|
|
70
69
|
description: "非功能性更改"
|
|
71
70
|
},
|
|
72
71
|
{
|
|
73
72
|
type: "refactor",
|
|
74
|
-
section: "
|
|
73
|
+
section: "♻️ 代码重构",
|
|
75
74
|
description: "重构代码"
|
|
76
75
|
},
|
|
77
76
|
{
|
|
78
77
|
type: "test",
|
|
79
|
-
section: "
|
|
78
|
+
section: "🧪 测试",
|
|
80
79
|
description: "测试更新"
|
|
81
80
|
},
|
|
82
81
|
{
|
|
83
82
|
type: "build",
|
|
84
|
-
section: "
|
|
83
|
+
section: "🏗️ 构建系统",
|
|
85
84
|
description: "构建工具和系统修改"
|
|
86
85
|
},
|
|
87
86
|
{
|
|
88
87
|
type: "ci",
|
|
89
|
-
section: "
|
|
88
|
+
section: "🤖 CI",
|
|
90
89
|
description: "持续集成配置"
|
|
91
90
|
},
|
|
92
91
|
{
|
|
93
92
|
type: "config",
|
|
94
|
-
section: "
|
|
93
|
+
section: "⚙️ 配置",
|
|
95
94
|
description: "配置文件更新"
|
|
96
95
|
},
|
|
97
96
|
{
|
|
98
97
|
type: "deps",
|
|
99
|
-
section: "
|
|
98
|
+
section: "📦 依赖更新",
|
|
100
99
|
description: "依赖版本变更"
|
|
101
100
|
},
|
|
102
101
|
{
|
|
103
102
|
type: "security",
|
|
104
|
-
section: "
|
|
103
|
+
section: "🛡️ 安全修复",
|
|
105
104
|
description: "安全性更新"
|
|
106
105
|
},
|
|
107
106
|
{
|
|
@@ -111,13 +110,18 @@ const defaultTypes = [
|
|
|
111
110
|
},
|
|
112
111
|
{
|
|
113
112
|
type: "ux",
|
|
114
|
-
section: "
|
|
113
|
+
section: "🎨 用户体验",
|
|
115
114
|
description: "用户体验改进"
|
|
116
115
|
},
|
|
117
116
|
{
|
|
118
117
|
type: "hotfix",
|
|
119
|
-
section: "
|
|
118
|
+
section: "🚑 热修复",
|
|
120
119
|
description: "紧急修复"
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
type: "tool",
|
|
123
|
+
section: "🛠️ 工具链",
|
|
124
|
+
description: "工具链变更"
|
|
121
125
|
}
|
|
122
126
|
];
|
|
123
127
|
//#endregion
|
|
@@ -128,8 +132,7 @@ const parsePreset = async () => {
|
|
|
128
132
|
hidden: false
|
|
129
133
|
})) });
|
|
130
134
|
preset.writer ??= {};
|
|
131
|
-
preset.writer.headerPartial =
|
|
132
|
-
## {{#if isPatch~}} <small> {{~/if~}}
|
|
135
|
+
preset.writer.headerPartial = `## {{#if isPatch~}} <small> {{~/if~}}
|
|
133
136
|
{{#if @root.linkCompare~}}
|
|
134
137
|
[{{version}}](
|
|
135
138
|
{{~#if @root.repository~}}
|
|
@@ -153,8 +156,7 @@ const parsePreset = async () => {
|
|
|
153
156
|
{{~/if}}
|
|
154
157
|
{{~#if isPatch~}} </small> {{~/if}}
|
|
155
158
|
`.trim() + eol();
|
|
156
|
-
preset.writer.mainTemplate = `
|
|
157
|
-
{{> header}}
|
|
159
|
+
preset.writer.mainTemplate = `{{> header}}
|
|
158
160
|
{{#if noteGroups}}
|
|
159
161
|
{{#each noteGroups}}
|
|
160
162
|
|
|
@@ -177,32 +179,37 @@ const parsePreset = async () => {
|
|
|
177
179
|
{{/each}}`.trim();
|
|
178
180
|
return preset;
|
|
179
181
|
};
|
|
180
|
-
const createGenerator = async ({ getPkgDir, tagPrefix }) => {
|
|
182
|
+
const createGenerator = async ({ getPkgDir, tagPrefix, releaseCount = 1 }) => {
|
|
181
183
|
const pkgDir = getPkgDir();
|
|
182
184
|
const preset = await parsePreset();
|
|
183
|
-
return new ConventionalChangelog().readPackage(`${pkgDir}/package.json`).config(preset).options({ releaseCount
|
|
185
|
+
return new ConventionalChangelog().readPackage(`${pkgDir}/package.json`).config(preset).options({ releaseCount }).commits({ path: pkgDir }).tags({ prefix: tagPrefix });
|
|
184
186
|
};
|
|
185
|
-
const getChangelog = async ({ getPkgDir, tagPrefix }) => {
|
|
187
|
+
const getChangelog = async ({ getPkgDir, tagPrefix, releaseCount = 1 }) => {
|
|
186
188
|
const generator = await createGenerator({
|
|
187
189
|
getPkgDir,
|
|
188
|
-
tagPrefix
|
|
190
|
+
tagPrefix,
|
|
191
|
+
releaseCount
|
|
189
192
|
});
|
|
190
193
|
let changelog = "";
|
|
191
194
|
for await (const chunk of generator.write()) changelog += chunk;
|
|
192
|
-
return changelog;
|
|
195
|
+
return changelog.trimEnd();
|
|
193
196
|
};
|
|
194
|
-
const generateChangelog = async ({ getPkgDir, tagPrefix }) => {
|
|
197
|
+
const generateChangelog = async ({ getPkgDir, tagPrefix, releaseCount = 1 }) => {
|
|
195
198
|
const infile = join(getPkgDir(), "CHANGELOG.md");
|
|
196
199
|
if (!existsSync(infile)) await writeFile(infile, "");
|
|
197
200
|
const exist = readFileSync(infile, "utf-8");
|
|
201
|
+
const writeStream = createWriteStream(infile);
|
|
198
202
|
const generator = await createGenerator({
|
|
199
203
|
getPkgDir,
|
|
200
|
-
tagPrefix
|
|
204
|
+
tagPrefix,
|
|
205
|
+
releaseCount
|
|
201
206
|
});
|
|
202
|
-
const writeStream = createWriteStream(infile);
|
|
203
207
|
for await (const chunk of generator.write()) writeStream.write(chunk);
|
|
204
|
-
|
|
205
|
-
|
|
208
|
+
const override = isZero(releaseCount);
|
|
209
|
+
if (!(isZero(exist.trim().length) || override)) {
|
|
210
|
+
writeStream.write(eol());
|
|
211
|
+
writeStream.write(exist);
|
|
212
|
+
}
|
|
206
213
|
writeStream.end();
|
|
207
214
|
await finished(writeStream);
|
|
208
215
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peiyanlu/release",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.16",
|
|
4
4
|
"description": "Release project tool.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"scripts": {
|
|
21
21
|
"dev": "tsdown -w",
|
|
22
22
|
"build": "tsdown",
|
|
23
|
-
"
|
|
23
|
+
"prepublishOnly": "tsdown",
|
|
24
24
|
"release": "node index.js",
|
|
25
25
|
"test": "vitest run",
|
|
26
26
|
"test:cov": "vitest run --coverage"
|
|
@@ -30,16 +30,17 @@
|
|
|
30
30
|
"@actions/github": "^9.1.1",
|
|
31
31
|
"@clack/prompts": "^1.5.0",
|
|
32
32
|
"@octokit/rest": "^22.0.1",
|
|
33
|
-
"@peiyanlu/cli-utils": "^0.0.
|
|
34
|
-
"@peiyanlu/ts-utils": "^0.0.
|
|
33
|
+
"@peiyanlu/cli-utils": "^0.0.12",
|
|
34
|
+
"@peiyanlu/ts-utils": "^0.0.7",
|
|
35
35
|
"ansis": "^4.3.1",
|
|
36
36
|
"commander": "^15.0.0",
|
|
37
|
-
"conventional-changelog": "^7.2.
|
|
37
|
+
"conventional-changelog": "^7.2.1",
|
|
38
38
|
"conventional-changelog-conventionalcommits": "^9.3.1",
|
|
39
|
+
"conventional-changelog-writer": "^8.3.0",
|
|
39
40
|
"mime-types": "^3.0.2",
|
|
40
41
|
"open": "^11.0.0",
|
|
41
42
|
"publint": "^0.3.21",
|
|
42
|
-
"semver": "^7.8.
|
|
43
|
+
"semver": "^7.8.5",
|
|
43
44
|
"tinyglobby": "^0.2.17"
|
|
44
45
|
},
|
|
45
46
|
"devDependencies": {
|
|
@@ -47,9 +48,9 @@
|
|
|
47
48
|
"@types/node": "^24.12.4",
|
|
48
49
|
"@types/semver": "^7.7.1",
|
|
49
50
|
"@vitest/coverage-v8": "^4.1.8",
|
|
50
|
-
"tsdown": "^0.22.
|
|
51
|
+
"tsdown": "^0.22.3",
|
|
51
52
|
"typescript": "^6.0.3",
|
|
52
|
-
"vitest": "^4.1.
|
|
53
|
+
"vitest": "^4.1.9"
|
|
53
54
|
},
|
|
54
55
|
"author": {
|
|
55
56
|
"name": "YanluPei",
|