@peiyanlu/release 1.2.1 → 1.3.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/dist/cli.mjs +117 -72
- package/dist/index.d.mts +18 -12
- package/dist/index.mjs +14 -5
- package/package.json +4 -3
package/dist/cli.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { bold, compareUrl, each, heading, link, list, newline, segments, small, words } from "@conventional-changelog/template";
|
|
2
2
|
import { bumpPackageVersion, coloredStatus, eol, execAsync, getAuthenticatedUser, getGithubReleaseUrl, getGithubUrl, getLatestTag, getLogSince, getPackageInfo, getPackageUrl, getPublishedVersion, getRemoteList, getRemoteNames, getShortStatus, gitAddAll, gitAddSync, gitAddTracked, gitBranchCurrent, gitCommitMessage, gitPushBranch, gitPushTag, gitResetHardSync, gitTagAnnotated, gitTagDeleteSync, hasWriteAccess, isGitRepo, isOtpError, isPrerelease, isValidVersion, isWorkingTreeClean, parseGitHubRepo, parseVersion, pingRegistry, publishPackage, registryArg, resolveChangelogRange, resolvePublishTag, runNpm } from "@peiyanlu/cli-utils";
|
|
3
|
-
import { copyDir, createMatcher, createTempDir, editFile, readJsonFile, readJsonFileSync, writeJsonFile } from "@peiyanlu/node-utils";
|
|
3
|
+
import { copyDir, createMatcher, createTempDir, editFile, normalizePath, readJsonFile, readJsonFileSync, writeJsonFile } from "@peiyanlu/node-utils";
|
|
4
|
+
import { castArray, dedent, isBoolean, isEmpty, isEmptyObject, isFunction, isNotEmpty, isNumber, isPlainObject, isUndefined, isZero, nonNullable, omit, partition, sleep, unset } from "@peiyanlu/ts-utils";
|
|
4
5
|
import { dim, green, red, rgb, underline, yellow } from "ansis";
|
|
5
6
|
import { program } from "commander";
|
|
6
7
|
import { createReadStream, createWriteStream, existsSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
7
|
-
import { join, resolve } from "node:path";
|
|
8
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
8
9
|
import { cancel, confirm, group, intro, isCancel, log, outro, select, tasks, text } from "@clack/prompts";
|
|
9
|
-
import { castArray, isBoolean, isEmptyObject, isFunction, isNotEmpty, isNumber, isPlainObject, isUndefined, isZero, omit, partition, unset } from "@peiyanlu/ts-utils";
|
|
10
10
|
import { publint } from "publint";
|
|
11
11
|
import { formatMessage } from "publint/utils";
|
|
12
12
|
import { inc, neq } from "semver";
|
|
@@ -20,10 +20,11 @@ import { finished } from "node:stream/promises";
|
|
|
20
20
|
import { Octokit } from "@octokit/rest";
|
|
21
21
|
import { contentType } from "mime-types";
|
|
22
22
|
import open from "open";
|
|
23
|
-
import { glob } from "tinyglobby";
|
|
23
|
+
import { glob, globSync } from "tinyglobby";
|
|
24
|
+
import { parse } from "yaml";
|
|
24
25
|
//#region package.json
|
|
25
26
|
var name = "@peiyanlu/release";
|
|
26
|
-
var version = "1.
|
|
27
|
+
var version = "1.3.0";
|
|
27
28
|
var description = "Release project tool.";
|
|
28
29
|
//#endregion
|
|
29
30
|
//#region src/config.ts
|
|
@@ -92,11 +93,11 @@ const MSG = {
|
|
|
92
93
|
},
|
|
93
94
|
NPM: {
|
|
94
95
|
CHECKING: "Checking npm registry",
|
|
95
|
-
CHECKED: (registry, msg) => `NPM registry: ${underline(dim(registry))} ${
|
|
96
|
+
CHECKED: (registry, msg) => `NPM registry: ${underline(dim(registry))} ${dim(msg)}`
|
|
96
97
|
},
|
|
97
98
|
GITHUB: {
|
|
98
99
|
CHECKING: "Checking github repository",
|
|
99
|
-
CHECKED: (repository
|
|
100
|
+
CHECKED: (repository) => `GitHub repository: ${underline(dim(repository))}`
|
|
100
101
|
}
|
|
101
102
|
},
|
|
102
103
|
TASK: {
|
|
@@ -158,44 +159,25 @@ const MSG = {
|
|
|
158
159
|
//#region src/git/commit.ts
|
|
159
160
|
const gitCheck = async (ctx, config) => {
|
|
160
161
|
const { pkg: { name } } = ctx;
|
|
161
|
-
const { git: { requireRepository, requireRemote, requireCleanWorkingTree }
|
|
162
|
-
if (skipGit) {
|
|
163
|
-
ctx.noGit = true;
|
|
164
|
-
ctx.noGitHub = true;
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
162
|
+
const { git: { requireRepository, requireRemote, requireCleanWorkingTree } } = config;
|
|
167
163
|
if (!await isGitRepo()) if (requireRepository) throw new Error(MSG.ERROR.GIT_REGISTRY(name));
|
|
168
164
|
else {
|
|
169
165
|
ctx.noGit = true;
|
|
170
|
-
ctx.noGitHub = true;
|
|
171
166
|
return;
|
|
172
167
|
}
|
|
173
168
|
if (requireCleanWorkingTree && !await isWorkingTreeClean()) throw new Error(MSG.ERROR.GIT_WORKDIR);
|
|
174
|
-
if (skipGithub) {
|
|
175
|
-
ctx.noGitHub = true;
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
169
|
const remote = (await getRemoteList())[0];
|
|
179
170
|
const { name: remoteName = "origin", url: remoteUrl } = remote ?? {};
|
|
180
|
-
if (!remote || !remoteUrl)
|
|
181
|
-
|
|
182
|
-
ctx.noGitHub = true;
|
|
183
|
-
return;
|
|
171
|
+
if (!remote || !remoteUrl) {
|
|
172
|
+
if (requireRemote) throw new Error(MSG.ERROR.GIT_REMOTE(remoteName));
|
|
184
173
|
}
|
|
185
174
|
Object.assign(ctx.git, {
|
|
186
175
|
remoteName,
|
|
187
176
|
remoteUrl
|
|
188
177
|
});
|
|
189
|
-
const [owner, repo] = parseGitHubRepo(remoteUrl);
|
|
190
|
-
const url = getGithubUrl(owner, repo);
|
|
191
|
-
Object.assign(ctx.github, {
|
|
192
|
-
owner,
|
|
193
|
-
repo,
|
|
194
|
-
url
|
|
195
|
-
});
|
|
196
178
|
};
|
|
197
179
|
const commitAndTag = async (ctx, config) => {
|
|
198
|
-
const { git: {
|
|
180
|
+
const { git: { currentTag, commitMessage, tagMessage }, dryRun } = ctx;
|
|
199
181
|
const { git: { commit, tag, push, addUntrackedFiles, commitArgs, tagArgs, pushArgs } } = config;
|
|
200
182
|
if (!commit) return;
|
|
201
183
|
const args = dryRun ? ["--dry-run"] : [];
|
|
@@ -384,7 +366,8 @@ const createDefaultContext = () => {
|
|
|
384
366
|
registry: "https://registry.npmjs.org"
|
|
385
367
|
},
|
|
386
368
|
preId: "beta",
|
|
387
|
-
preBase: "0"
|
|
369
|
+
preBase: "0",
|
|
370
|
+
scope: void 0
|
|
388
371
|
},
|
|
389
372
|
npm: {
|
|
390
373
|
username: "",
|
|
@@ -483,7 +466,8 @@ const createDefaultConfig = (isCI) => {
|
|
|
483
466
|
draft: false,
|
|
484
467
|
tokenRef: "GITHUB_TOKEN",
|
|
485
468
|
assets: [],
|
|
486
|
-
skipChecks: false
|
|
469
|
+
skipChecks: false,
|
|
470
|
+
shortNotes: false
|
|
487
471
|
}
|
|
488
472
|
};
|
|
489
473
|
};
|
|
@@ -613,6 +597,10 @@ function headerPartial(context) {
|
|
|
613
597
|
const fullTitle = words(linkCompare && !isLikelyHash(previousTag) ? link(version, compareUrl(context)) : version, title && `"${title}"`, date && `(${date})`);
|
|
614
598
|
return heading(2, isPatch ? small(fullTitle) : fullTitle);
|
|
615
599
|
}
|
|
600
|
+
function preamblePartial(contest) {
|
|
601
|
+
const { preamble, commitGroups, noteGroups } = contest;
|
|
602
|
+
return segments(preamble, isEmpty(preamble) && isEmpty(commitGroups) && isEmpty(noteGroups) && "Version bump without any changes.");
|
|
603
|
+
}
|
|
616
604
|
function template(context) {
|
|
617
605
|
const { headerPartial, preamblePartial, commitPartial, footerPartial, noteGroups, commitGroups } = context;
|
|
618
606
|
return segments(headerPartial(context), preamblePartial(context), each(noteGroups, (group) => segments(heading(3, words("⚠", group.title)), list(group.notes, (note) => {
|
|
@@ -622,14 +610,18 @@ function template(context) {
|
|
|
622
610
|
//#endregion
|
|
623
611
|
//#region src/git/changelog.ts
|
|
624
612
|
const parsePreset = (options = {}) => {
|
|
625
|
-
const { includeHidden, transformTypes } = options;
|
|
613
|
+
const { includeHidden, transformTypes, scope } = options;
|
|
626
614
|
let types = [...defaultTypes];
|
|
627
615
|
if (includeHidden) types.forEach((type) => {
|
|
628
616
|
if (type.effect === "hidden") type.effect = "changelog";
|
|
629
617
|
});
|
|
630
618
|
types = transformTypes?.(types) ?? types;
|
|
631
|
-
const preset = createPreset({
|
|
619
|
+
const preset = createPreset({
|
|
620
|
+
types,
|
|
621
|
+
scope
|
|
622
|
+
});
|
|
632
623
|
preset.writer ??= {};
|
|
624
|
+
preset.writer.preamblePartial = preamblePartial;
|
|
633
625
|
preset.writer.headerPartial = headerPartial;
|
|
634
626
|
preset.writer.template = template;
|
|
635
627
|
return preset;
|
|
@@ -645,11 +637,12 @@ const inferReleaseType = async (options) => {
|
|
|
645
637
|
} : releaseType;
|
|
646
638
|
};
|
|
647
639
|
const createGenerator = async (options) => {
|
|
648
|
-
const { getPkgDir, tagPrefix, releaseCount = 1, includeHidden, transformTypes } = options;
|
|
640
|
+
const { getPkgDir, tagPrefix, releaseCount = 1, includeHidden, transformTypes, scope } = options;
|
|
649
641
|
const pkgDir = getPkgDir();
|
|
650
642
|
return new ConventionalChangelog().readPackage(`${pkgDir}/package.json`).config(parsePreset({
|
|
651
643
|
includeHidden,
|
|
652
|
-
transformTypes
|
|
644
|
+
transformTypes,
|
|
645
|
+
scope
|
|
653
646
|
})).options({ releaseCount }).commits({ path: pkgDir }).tags({ prefix: tagPrefix });
|
|
654
647
|
};
|
|
655
648
|
const getChangelog = async (options) => {
|
|
@@ -677,10 +670,10 @@ const generateChangelog = async (options) => {
|
|
|
677
670
|
//#endregion
|
|
678
671
|
//#region src/git/prompts.ts
|
|
679
672
|
const runGitPrompts = async (ctx, config) => {
|
|
680
|
-
const { git: { currentTag, commitMessage }, configFileExists: cfe, dryRun } = ctx;
|
|
673
|
+
const { git: { currentTag, commitMessage }, configFileExists: cfe, dryRun, isCI } = ctx;
|
|
681
674
|
const { git: { commit, tag, push } } = config;
|
|
682
675
|
const logs = [];
|
|
683
|
-
const shouldPrompt = (val) => isUndefined(val) || dryRun;
|
|
676
|
+
const shouldPrompt = (val) => isUndefined(val) || dryRun && !isCI;
|
|
684
677
|
const { log: _, ...reset } = await group({
|
|
685
678
|
commit: () => {
|
|
686
679
|
return shouldPrompt(commit) ? confirm({ message: question(MSG.PROMPT.GIT_COMMIT(commitMessage), "git") }) : (() => {
|
|
@@ -716,10 +709,10 @@ const runGitPrompts = async (ctx, config) => {
|
|
|
716
709
|
//#endregion
|
|
717
710
|
//#region src/github/prompts.ts
|
|
718
711
|
const runGithubPrompts = async (ctx, config) => {
|
|
719
|
-
const { configFileExists: cfe, dryRun, github: { releaseName } } = ctx;
|
|
712
|
+
const { configFileExists: cfe, dryRun, github: { releaseName }, isCI } = ctx;
|
|
720
713
|
const { github: { release } } = config;
|
|
721
714
|
const logs = [];
|
|
722
|
-
const shouldPrompt = (val) => isUndefined(val) || dryRun;
|
|
715
|
+
const shouldPrompt = (val) => isUndefined(val) || dryRun && !isCI;
|
|
723
716
|
const res = await group({
|
|
724
717
|
release: () => shouldPrompt(release) ? confirm({ message: question(MSG.PROMPT.GITHUB_RELEASE(releaseName), "github") }) : (() => {
|
|
725
718
|
logs.push(yellow`github.release`);
|
|
@@ -824,24 +817,34 @@ const createWebRelease = async (options) => {
|
|
|
824
817
|
}).forEach(([key, value]) => {
|
|
825
818
|
url.searchParams.set(key, value);
|
|
826
819
|
});
|
|
827
|
-
|
|
828
|
-
await
|
|
820
|
+
await open(url.toString());
|
|
821
|
+
await sleep(1e3 * 1.5);
|
|
829
822
|
};
|
|
823
|
+
const isGitHubActions = Boolean(process.env.GITHUB_ACTIONS);
|
|
830
824
|
const githubCheck = async (ctx, config) => {
|
|
831
|
-
const {
|
|
825
|
+
const { git: { remoteUrl } } = ctx;
|
|
832
826
|
const { github: { tokenRef, skipChecks } } = config;
|
|
827
|
+
if (!remoteUrl || isEmpty(parseGitHubRepo(remoteUrl))) {
|
|
828
|
+
ctx.noGitHub = true;
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
const [owner, repo] = parseGitHubRepo(remoteUrl);
|
|
832
|
+
const url = getGithubUrl(owner, repo);
|
|
833
|
+
Object.assign(ctx.github, {
|
|
834
|
+
owner,
|
|
835
|
+
repo,
|
|
836
|
+
url
|
|
837
|
+
});
|
|
833
838
|
const token = tokenRef ? process.env[tokenRef] : process.env.GITHUB_TOKEN;
|
|
834
839
|
if (!token) {
|
|
835
840
|
Object.assign(ctx.github, { isWeb: true });
|
|
836
|
-
if (
|
|
837
|
-
return
|
|
841
|
+
if (isGitHubActions) throw new Error(MSG.ERROR.GITHUB_TOKEN(tokenRef));
|
|
842
|
+
return;
|
|
838
843
|
}
|
|
839
844
|
Object.assign(ctx.github, { token });
|
|
840
|
-
if (skipChecks)
|
|
841
|
-
let username = "";
|
|
842
|
-
if (process.env.GITHUB_ACTIONS) username = process.env.GITHUB_ACTOR;
|
|
843
|
-
else {
|
|
845
|
+
if (!isGitHubActions && !skipChecks) {
|
|
844
846
|
const octokit = new Octokit({ auth: token });
|
|
847
|
+
let username = "";
|
|
845
848
|
try {
|
|
846
849
|
const { data: { login } } = await octokit.users.getAuthenticated();
|
|
847
850
|
username = login;
|
|
@@ -858,7 +861,6 @@ const githubCheck = async (ctx, config) => {
|
|
|
858
861
|
throw new Error(MSG.ERROR.GITHUB_USER);
|
|
859
862
|
}
|
|
860
863
|
}
|
|
861
|
-
return username;
|
|
862
864
|
};
|
|
863
865
|
const createRelease = async (ctx, config) => {
|
|
864
866
|
const { pkg: { name, toPreRelease }, git: { currentTag }, isCI, dryRun } = ctx;
|
|
@@ -903,11 +905,11 @@ const createRelease = async (ctx, config) => {
|
|
|
903
905
|
//#endregion
|
|
904
906
|
//#region src/npm/prompts.ts
|
|
905
907
|
const runNpmPublishPrompts = async (ctx, config) => {
|
|
906
|
-
const { pkg: { name, next }, configFileExists: cfe, dryRun } = ctx;
|
|
908
|
+
const { pkg: { name, next }, configFileExists: cfe, dryRun, isCI } = ctx;
|
|
907
909
|
const { npm: { publish } } = config;
|
|
908
910
|
const logs = [];
|
|
909
911
|
const target = `${name}@${next}`;
|
|
910
|
-
const shouldPrompt = (val) => isUndefined(val) || dryRun;
|
|
912
|
+
const shouldPrompt = (val) => isUndefined(val) || dryRun && !isCI;
|
|
911
913
|
const res = await group({
|
|
912
914
|
publish: () => shouldPrompt(publish) ? confirm({ message: question(MSG.PROMPT.NPM_PUBLISH(target), "npm") }) : (() => {
|
|
913
915
|
logs.push(yellow`npm.publish`);
|
|
@@ -1132,7 +1134,7 @@ var Action = class {
|
|
|
1132
1134
|
console.log();
|
|
1133
1135
|
const { otp, package: defPkg, prepare, showChangelog, showRelease, ci, dryRun, onlyChangelog } = options;
|
|
1134
1136
|
intro(prepare ? MSG.INTRO_PREPARE(dryRun) : MSG.INTRO(dryRun));
|
|
1135
|
-
const { isMonorepo, packages, getPkgDir, skipNpm } = config;
|
|
1137
|
+
const { isMonorepo, packages, getPkgDir, skipGit, skipNpm, skipGithub } = config;
|
|
1136
1138
|
if (isMonorepo) {
|
|
1137
1139
|
if (ci) {
|
|
1138
1140
|
if (!defPkg) abortTask(MSG.ABORT.MONOREPO_CI_NO_PACKAGE);
|
|
@@ -1177,9 +1179,12 @@ var Action = class {
|
|
|
1177
1179
|
current: pkgVersion,
|
|
1178
1180
|
next: nextVersion,
|
|
1179
1181
|
toPreRelease: false,
|
|
1180
|
-
publishConfig: { ...publishConfig }
|
|
1182
|
+
publishConfig: { ...publishConfig },
|
|
1183
|
+
scope: isEmpty(selectedPkg) ? void 0 : selectedPkg
|
|
1181
1184
|
},
|
|
1182
|
-
|
|
1185
|
+
noGit: skipGit,
|
|
1186
|
+
noNpm: pkgPrivate || skipNpm,
|
|
1187
|
+
noGitHub: skipGit || skipGithub
|
|
1183
1188
|
});
|
|
1184
1189
|
const { messages } = await publint({ pkgDir });
|
|
1185
1190
|
for (const message of messages) {
|
|
@@ -1220,8 +1225,9 @@ var Action = class {
|
|
|
1220
1225
|
return resolveConfig(cwd);
|
|
1221
1226
|
}
|
|
1222
1227
|
async printChangelog(ctx, config) {
|
|
1223
|
-
const { showChangelog, selectedPkg, noGit, isIncrement } = ctx;
|
|
1228
|
+
const { showChangelog, selectedPkg, noGit, isIncrement, pkg: { scope } } = ctx;
|
|
1224
1229
|
const { isMonorepo, getPkgDir, changelog: { tagPrefix, releaseCount, includeHidden, transformTypes } } = config;
|
|
1230
|
+
const { github: { shortNotes } } = config;
|
|
1225
1231
|
if (noGit) return;
|
|
1226
1232
|
const match = isMonorepo ? `${tagPrefix?.(selectedPkg)}*` : "*";
|
|
1227
1233
|
const { from, to } = isZero(releaseCount) ? {
|
|
@@ -1232,11 +1238,13 @@ var Action = class {
|
|
|
1232
1238
|
const changelog = await getChangelog({
|
|
1233
1239
|
getPkgDir: () => getPkgDir(selectedPkg),
|
|
1234
1240
|
tagPrefix: tagPrefix?.(selectedPkg),
|
|
1241
|
+
scope: isEmpty(nonNullable([scope])) ? void 0 : nonNullable([scope]),
|
|
1235
1242
|
releaseCount,
|
|
1236
1243
|
includeHidden,
|
|
1237
1244
|
transformTypes
|
|
1238
1245
|
});
|
|
1239
|
-
|
|
1246
|
+
const short = `Please refer to [CHANGELOG.md](${getPkgDir(selectedPkg)}/CHANGELOG.md) for details.`;
|
|
1247
|
+
Object.assign(ctx.github, { changelog: shortNotes ? short : changelog });
|
|
1240
1248
|
if (commits) {
|
|
1241
1249
|
msg("GIT", `Changelog(${from}...${to}):${eol(2)}` + changelog);
|
|
1242
1250
|
msg("GIT", `Commits(${from}...${to}):${eol(2)}` + commits);
|
|
@@ -1252,7 +1260,8 @@ var Action = class {
|
|
|
1252
1260
|
await gitCheck(ctx, config);
|
|
1253
1261
|
const { git: { remoteName, remoteUrl } } = ctx;
|
|
1254
1262
|
return success(MSG.CHECK.GIT.CHECKED(remoteName, remoteUrl), dryRun);
|
|
1255
|
-
}
|
|
1263
|
+
},
|
|
1264
|
+
enabled: !ctx.noGit
|
|
1256
1265
|
},
|
|
1257
1266
|
{
|
|
1258
1267
|
title: MSG.CHECK.NPM.CHECKING,
|
|
@@ -1266,10 +1275,10 @@ var Action = class {
|
|
|
1266
1275
|
{
|
|
1267
1276
|
title: MSG.CHECK.GITHUB.CHECKING,
|
|
1268
1277
|
task: async () => {
|
|
1269
|
-
|
|
1278
|
+
await githubCheck(ctx, config);
|
|
1270
1279
|
const { github: { owner, repo } } = ctx;
|
|
1271
1280
|
const repository = getGithubUrl(owner, repo);
|
|
1272
|
-
return success(MSG.CHECK.GITHUB.CHECKED(repository
|
|
1281
|
+
return success(MSG.CHECK.GITHUB.CHECKED(repository), dryRun);
|
|
1273
1282
|
},
|
|
1274
1283
|
enabled: !(ctx.noGitHub || onlyChangelog)
|
|
1275
1284
|
}
|
|
@@ -1291,7 +1300,7 @@ var Action = class {
|
|
|
1291
1300
|
});
|
|
1292
1301
|
if (res) msg("BUMP", res.reason);
|
|
1293
1302
|
const inferred = res?.releaseType;
|
|
1294
|
-
const nextVersion = (isCI ?
|
|
1303
|
+
const nextVersion = (isCI ? inc(current, inferred ?? "patch") : void 0) || await runVersionPrompts(ctx, config, inferred);
|
|
1295
1304
|
ctx.isIncrement = neq(current, nextVersion);
|
|
1296
1305
|
const { version: next, isPrerelease: toPreRelease, preId, preBase } = parseVersion(nextVersion);
|
|
1297
1306
|
Object.assign(ctx.pkg, {
|
|
@@ -1318,7 +1327,7 @@ var Action = class {
|
|
|
1318
1327
|
await this.printChangelog(ctx, config);
|
|
1319
1328
|
}
|
|
1320
1329
|
async changelogTask(ctx, config) {
|
|
1321
|
-
const { isIncrement, dryRun, selectedPkg, noGit, onlyChangelog } = ctx;
|
|
1330
|
+
const { isIncrement, dryRun, selectedPkg, noGit, onlyChangelog, pkg: { scope } } = ctx;
|
|
1322
1331
|
const { getPkgDir, changelog: { tagPrefix, releaseCount, includeHidden, transformTypes } } = config;
|
|
1323
1332
|
if (noGit) return;
|
|
1324
1333
|
await tasks([{
|
|
@@ -1327,6 +1336,7 @@ var Action = class {
|
|
|
1327
1336
|
await generateChangelog({
|
|
1328
1337
|
getPkgDir: () => getPkgDir(selectedPkg),
|
|
1329
1338
|
tagPrefix: tagPrefix?.(selectedPkg),
|
|
1339
|
+
scope: isEmpty(nonNullable([scope])) ? void 0 : nonNullable([scope]),
|
|
1330
1340
|
releaseCount,
|
|
1331
1341
|
includeHidden,
|
|
1332
1342
|
transformTypes
|
|
@@ -1399,6 +1409,45 @@ var Action = class {
|
|
|
1399
1409
|
}
|
|
1400
1410
|
};
|
|
1401
1411
|
//#endregion
|
|
1412
|
+
//#region src/monorepo/packages.ts
|
|
1413
|
+
const scanWorkspacePackages = (cwd) => {
|
|
1414
|
+
const workspaceFile = join(cwd, "pnpm-workspace.yaml");
|
|
1415
|
+
if (!existsSync(workspaceFile)) return [];
|
|
1416
|
+
const { packages = [] } = parse(readFileSync(workspaceFile, "utf8"));
|
|
1417
|
+
return globSync(packages.map((pattern) => normalizePath(join(pattern, "package.json"))), {
|
|
1418
|
+
cwd,
|
|
1419
|
+
absolute: false
|
|
1420
|
+
});
|
|
1421
|
+
};
|
|
1422
|
+
const getPackages = (cwd) => {
|
|
1423
|
+
return scanWorkspacePackages(cwd).reduce(((acc, file) => {
|
|
1424
|
+
const workspace = dirname(dirname(file));
|
|
1425
|
+
(acc[workspace] ??= []).push(basename(dirname(file)));
|
|
1426
|
+
return acc;
|
|
1427
|
+
}), {});
|
|
1428
|
+
};
|
|
1429
|
+
const monoConfig = (packages = [], workspace = "packages") => {
|
|
1430
|
+
const subs = isEmpty(packages) ? "[]" : `[ ${[...packages].map((k) => `'${k}'`).join(", ")} ]`;
|
|
1431
|
+
return dedent(`
|
|
1432
|
+
import { defineConfig } from '@peiyanlu/release'
|
|
1433
|
+
${newline(1)}
|
|
1434
|
+
export default defineConfig({
|
|
1435
|
+
isMonorepo: true,
|
|
1436
|
+
packages: ${subs},
|
|
1437
|
+
getPkgDir: (pkg) => \`${workspace}/\${ pkg }\`,
|
|
1438
|
+
toTag: (pkg, version) => \`\${ pkg }@\${ version }\`,
|
|
1439
|
+
changelog: {
|
|
1440
|
+
tagPrefix: (pkg) => \`\${ pkg }@\`,
|
|
1441
|
+
},
|
|
1442
|
+
})
|
|
1443
|
+
`);
|
|
1444
|
+
};
|
|
1445
|
+
const generateMonoConfig = (cwd = process.cwd()) => {
|
|
1446
|
+
const entries = Object.entries(getPackages(cwd));
|
|
1447
|
+
const [workspace, packages] = entries.length === 1 ? entries[0] : [];
|
|
1448
|
+
return monoConfig(packages, workspace);
|
|
1449
|
+
};
|
|
1450
|
+
//#endregion
|
|
1402
1451
|
//#region src/cli.ts
|
|
1403
1452
|
program.name("release").description(description).version(version, "-v, --version", "Print the tool version and exit.").usage("[release-type] [options]").argument("[release-type]", "Version bump type: patch | minor | major").enablePositionalOptions().option("-n, --dry-run", "Run in dry mode", false).option("-p, --package <pkg>", "Specify package name (Mono-repo CI only).", "").option("--otp <code>", "One-time password for npm publish.", "").option("--prepare", "Prepare a release.", false).option("--ci", "Enable CI mode.", false).option("--show-changelog", "Print changelog and exit.", false).option("--show-release", "Print release version and exit.", false).option("--only-changelog", "Only update the changelog.", false).option("-r, --release-count <count>", "Release count for release.", (val) => Number(val)).option("--include-hidden", "Include hidden commit types in the changelog.").option("-C, --no-require-clean-working-tree", "Allow releasing with uncommitted changes.").option("--skip-git", "Skip all Git-related checks and operations.").option("--skip-npm", "Skip all npm-related checks and operations.").option("--skip-github", "Skip all GitHub-related checks and operations.").option("-m, --is-monorepo", "Mono-repo project").helpOption("-h, --help", "Display help information.").action(async (releaseType, options) => {
|
|
1404
1453
|
const { prepare } = options;
|
|
@@ -1417,16 +1466,12 @@ program.command("init").description("Create a release configuration file").optio
|
|
|
1417
1466
|
console.error(`${red`Error:`} ${yellow(infile)} already exists.\nUse ${green`--force`} to overwrite.`);
|
|
1418
1467
|
process.exit(1);
|
|
1419
1468
|
}
|
|
1420
|
-
const
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
tagPrefix: (pkg) => \`\${ pkg }@\`,
|
|
1427
|
-
},
|
|
1428
|
-
}`;
|
|
1429
|
-
const content = [`import { defineConfig } from '@peiyanlu/release'`, `export default defineConfig(${monorepo ? monoConfigToString() : "{}"})`].join(eol(3));
|
|
1469
|
+
const config = () => dedent(`
|
|
1470
|
+
import { defineConfig } from '@peiyanlu/release'
|
|
1471
|
+
${newline(1)}
|
|
1472
|
+
export default defineConfig({})
|
|
1473
|
+
`);
|
|
1474
|
+
const content = monorepo ? generateMonoConfig(process.cwd()) : config();
|
|
1430
1475
|
writeFileSync(configFile, content + newline(), "utf-8");
|
|
1431
1476
|
console.log(`Wrote to ${underline(dim(configFile))}`);
|
|
1432
1477
|
if (add) gitAddSync([infile]);
|
package/dist/index.d.mts
CHANGED
|
@@ -19,20 +19,20 @@ type HookConfig = { [key in ReleaseHookKey]: ReleaseHookValue; };
|
|
|
19
19
|
interface ReleaseConfig {
|
|
20
20
|
/**
|
|
21
21
|
* 生命周期钩子配置
|
|
22
|
-
* 用于在 Release 关键阶段注入自定义逻辑(如 before/after hooks
|
|
22
|
+
* 用于在 Release 关键阶段注入自定义逻辑(如 before/after hooks)
|
|
23
23
|
*/
|
|
24
24
|
hooks: HookConfig;
|
|
25
25
|
/** 是否是 monorepo */
|
|
26
26
|
isMonorepo: boolean;
|
|
27
|
-
/** 使用 monorepo 时发布的包列表,后续回调的 `pkg
|
|
27
|
+
/** 使用 monorepo 时发布的包列表,后续回调的 `pkg`,默认为 `[]` 作为单仓库 */
|
|
28
28
|
packages: string[];
|
|
29
|
-
/** 使用 monorepo 时使用 getPkgDir,例如 `packages/${pkg}
|
|
29
|
+
/** 使用 monorepo 时使用 getPkgDir,例如 `packages/${pkg}`,默认为 `.` 作为单仓库 */
|
|
30
30
|
getPkgDir: (pkg: string) => string;
|
|
31
31
|
/** 使用 monorepo 时使用 toTag,例如 `${pkg}@${version}`,默认为 `v${version}` 作为单仓库 */
|
|
32
32
|
toTag: (pkg: string, version: string) => string;
|
|
33
33
|
/**
|
|
34
34
|
* CHANGELOG 相关配置
|
|
35
|
-
* 影响生成的日志数量、日志标题(monorepo
|
|
35
|
+
* 影响生成的日志数量、日志标题(monorepo)
|
|
36
36
|
*/
|
|
37
37
|
changelog: {
|
|
38
38
|
/**
|
|
@@ -60,7 +60,7 @@ interface ReleaseConfig {
|
|
|
60
60
|
skipGithub: boolean;
|
|
61
61
|
/**
|
|
62
62
|
* Git 相关配置
|
|
63
|
-
* 影响提交、打 tag、推送等本地/远程 Git
|
|
63
|
+
* 影响提交、打 tag、推送等本地/远程 Git 行为
|
|
64
64
|
*/
|
|
65
65
|
git: {
|
|
66
66
|
/**
|
|
@@ -115,7 +115,7 @@ interface ReleaseConfig {
|
|
|
115
115
|
};
|
|
116
116
|
/**
|
|
117
117
|
* NPM 发布相关配置
|
|
118
|
-
* 影响 npm publish
|
|
118
|
+
* 影响 npm publish 的目标路径、参数与前置校验
|
|
119
119
|
*/
|
|
120
120
|
npm: {
|
|
121
121
|
/**
|
|
@@ -129,19 +129,19 @@ interface ReleaseConfig {
|
|
|
129
129
|
/**
|
|
130
130
|
* 是否跳过发布前检查(如 ping、whoami 等校验)
|
|
131
131
|
*
|
|
132
|
-
* 启用后可减少发布前的网络请求,但可能无法提前发现登录状态异常或 Registry
|
|
132
|
+
* 启用后可减少发布前的网络请求,但可能无法提前发现登录状态异常或 Registry 不可用等问题
|
|
133
133
|
*/
|
|
134
134
|
skipChecks: boolean;
|
|
135
135
|
/**
|
|
136
136
|
* 发布前的文件清理配置
|
|
137
137
|
*
|
|
138
|
-
*
|
|
138
|
+
* 用于控制复制到临时发布目录后的清理行为,不会修改源项目文件
|
|
139
139
|
*/
|
|
140
140
|
cleanup: {
|
|
141
141
|
/**
|
|
142
142
|
* 是否清理 package.json
|
|
143
143
|
*
|
|
144
|
-
* 包括移除开发相关字段、应用 publishConfig
|
|
144
|
+
* 包括移除开发相关字段、应用 publishConfig,并保留发布所需内容
|
|
145
145
|
*/
|
|
146
146
|
packageJson: boolean | {
|
|
147
147
|
/** 需要忽略的字段,支持 `a.b.c` */
|
|
@@ -152,7 +152,7 @@ interface ReleaseConfig {
|
|
|
152
152
|
/**
|
|
153
153
|
* 是否清理 README.md
|
|
154
154
|
*
|
|
155
|
-
*
|
|
155
|
+
* 仅保留主要内容,并移除或精简不需要随包发布的文档内容
|
|
156
156
|
*/
|
|
157
157
|
readme: boolean;
|
|
158
158
|
/**
|
|
@@ -164,7 +164,7 @@ interface ReleaseConfig {
|
|
|
164
164
|
/**
|
|
165
165
|
* GitHub Release 相关配置
|
|
166
166
|
*
|
|
167
|
-
* 控制是否创建 Release
|
|
167
|
+
* 控制是否创建 Release、发布方式以及上传资源
|
|
168
168
|
*/
|
|
169
169
|
github: {
|
|
170
170
|
/**
|
|
@@ -196,9 +196,13 @@ interface ReleaseConfig {
|
|
|
196
196
|
*/
|
|
197
197
|
assets: string[];
|
|
198
198
|
/**
|
|
199
|
-
*
|
|
199
|
+
* 是否跳过本地 GitHub 发布前检查
|
|
200
200
|
*/
|
|
201
201
|
skipChecks: boolean;
|
|
202
|
+
/**
|
|
203
|
+
* 是否使用简短的 Release Notes,内容为指向 CHANGELOG.md 的链接
|
|
204
|
+
*/
|
|
205
|
+
shortNotes: boolean;
|
|
202
206
|
};
|
|
203
207
|
}
|
|
204
208
|
//#endregion
|
|
@@ -230,6 +234,8 @@ interface ParsePresetOptions {
|
|
|
230
234
|
includeHidden?: boolean;
|
|
231
235
|
/** 对默认 commit types 进行转换 */
|
|
232
236
|
transformTypes?: (types: CommitType$1[]) => CommitType$1[];
|
|
237
|
+
/** commit scope */
|
|
238
|
+
scope?: string | string[];
|
|
233
239
|
}
|
|
234
240
|
type GOptions = Simplify<GenerateOptions & ParsePresetOptions>;
|
|
235
241
|
declare const createGenerator: (options: GOptions) => Promise<ConventionalChangelog>;
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isFunction, isPlainObject, isUndefined, isZero } from "@peiyanlu/ts-utils";
|
|
1
|
+
import { isEmpty, 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";
|
|
@@ -154,6 +154,10 @@ function headerPartial(context) {
|
|
|
154
154
|
const fullTitle = words(linkCompare && !isLikelyHash(previousTag) ? link(version, compareUrl(context)) : version, title && `"${title}"`, date && `(${date})`);
|
|
155
155
|
return heading(2, isPatch ? small(fullTitle) : fullTitle);
|
|
156
156
|
}
|
|
157
|
+
function preamblePartial(contest) {
|
|
158
|
+
const { preamble, commitGroups, noteGroups } = contest;
|
|
159
|
+
return segments(preamble, isEmpty(preamble) && isEmpty(commitGroups) && isEmpty(noteGroups) && "Version bump without any changes.");
|
|
160
|
+
}
|
|
157
161
|
function template(context) {
|
|
158
162
|
const { headerPartial, preamblePartial, commitPartial, footerPartial, noteGroups, commitGroups } = context;
|
|
159
163
|
return segments(headerPartial(context), preamblePartial(context), each(noteGroups, (group) => segments(heading(3, words("⚠", group.title)), list(group.notes, (note) => {
|
|
@@ -163,24 +167,29 @@ function template(context) {
|
|
|
163
167
|
//#endregion
|
|
164
168
|
//#region src/git/changelog.ts
|
|
165
169
|
const parsePreset = (options = {}) => {
|
|
166
|
-
const { includeHidden, transformTypes } = options;
|
|
170
|
+
const { includeHidden, transformTypes, scope } = options;
|
|
167
171
|
let types = [...defaultTypes];
|
|
168
172
|
if (includeHidden) types.forEach((type) => {
|
|
169
173
|
if (type.effect === "hidden") type.effect = "changelog";
|
|
170
174
|
});
|
|
171
175
|
types = transformTypes?.(types) ?? types;
|
|
172
|
-
const preset = createPreset({
|
|
176
|
+
const preset = createPreset({
|
|
177
|
+
types,
|
|
178
|
+
scope
|
|
179
|
+
});
|
|
173
180
|
preset.writer ??= {};
|
|
181
|
+
preset.writer.preamblePartial = preamblePartial;
|
|
174
182
|
preset.writer.headerPartial = headerPartial;
|
|
175
183
|
preset.writer.template = template;
|
|
176
184
|
return preset;
|
|
177
185
|
};
|
|
178
186
|
const createGenerator = async (options) => {
|
|
179
|
-
const { getPkgDir, tagPrefix, releaseCount = 1, includeHidden, transformTypes } = options;
|
|
187
|
+
const { getPkgDir, tagPrefix, releaseCount = 1, includeHidden, transformTypes, scope } = options;
|
|
180
188
|
const pkgDir = getPkgDir();
|
|
181
189
|
return new ConventionalChangelog().readPackage(`${pkgDir}/package.json`).config(parsePreset({
|
|
182
190
|
includeHidden,
|
|
183
|
-
transformTypes
|
|
191
|
+
transformTypes,
|
|
192
|
+
scope
|
|
184
193
|
})).options({ releaseCount }).commits({ path: pkgDir }).tags({ prefix: tagPrefix });
|
|
185
194
|
};
|
|
186
195
|
const getChangelog = async (options) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peiyanlu/release",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "Release project tool.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"@conventional-changelog/template": "^1.2.1",
|
|
25
25
|
"@octokit/rest": "^22.0.1",
|
|
26
26
|
"@peiyanlu/cli-utils": "^0.0.15",
|
|
27
|
-
"@peiyanlu/node-utils": "^0.0.
|
|
27
|
+
"@peiyanlu/node-utils": "^0.0.4",
|
|
28
28
|
"@peiyanlu/ts-utils": "^0.0.12",
|
|
29
29
|
"ansis": "^4.3.1",
|
|
30
30
|
"commander": "^15.0.0",
|
|
@@ -36,7 +36,8 @@
|
|
|
36
36
|
"open": "^11.0.0",
|
|
37
37
|
"publint": "^0.3.22",
|
|
38
38
|
"semver": "^7.8.5",
|
|
39
|
-
"tinyglobby": "^0.2.17"
|
|
39
|
+
"tinyglobby": "^0.2.17",
|
|
40
|
+
"yaml": "^2.9.0"
|
|
40
41
|
},
|
|
41
42
|
"author": {
|
|
42
43
|
"name": "YanluPei",
|