@peiyanlu/release 1.2.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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, 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.2.0";
27
+ var version = "1.2.1";
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))} ${underline(dim(msg))}`
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, msg) => `GitHub repository: ${underline(dim(repository))} ${underline(dim(msg))}`
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 }, skipGit, skipGithub } = config;
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) if (requireRemote) throw new Error(MSG.ERROR.GIT_REMOTE(remoteName));
181
- else {
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: { remoteName, currentTag, commitMessage, tagMessage }, dryRun } = ctx;
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({ types });
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,33 @@ const createWebRelease = async (options) => {
824
817
  }).forEach(([key, value]) => {
825
818
  url.searchParams.set(key, value);
826
819
  });
827
- const isWindows = process.platform === "win32";
828
- await open(url.toString(), { wait: isWindows });
820
+ await open(url.toString());
829
821
  };
822
+ const isGitHubActions = Boolean(process.env.GITHUB_ACTIONS);
830
823
  const githubCheck = async (ctx, config) => {
831
- const { github: { owner, repo }, isCI } = ctx;
824
+ const { git: { remoteUrl } } = ctx;
832
825
  const { github: { tokenRef, skipChecks } } = config;
826
+ if (!remoteUrl || isEmpty(parseGitHubRepo(remoteUrl))) {
827
+ ctx.noGitHub = true;
828
+ return;
829
+ }
830
+ const [owner, repo] = parseGitHubRepo(remoteUrl);
831
+ const url = getGithubUrl(owner, repo);
832
+ Object.assign(ctx.github, {
833
+ owner,
834
+ repo,
835
+ url
836
+ });
833
837
  const token = tokenRef ? process.env[tokenRef] : process.env.GITHUB_TOKEN;
834
838
  if (!token) {
835
839
  Object.assign(ctx.github, { isWeb: true });
836
- if (isCI && process.env.GITHUB_ACTIONS) throw new Error(MSG.ERROR.GITHUB_TOKEN(tokenRef));
837
- return "";
840
+ if (isGitHubActions) throw new Error(MSG.ERROR.GITHUB_TOKEN(tokenRef));
841
+ return;
838
842
  }
839
843
  Object.assign(ctx.github, { token });
840
- if (skipChecks) return "";
841
- let username = "";
842
- if (process.env.GITHUB_ACTIONS) username = process.env.GITHUB_ACTOR;
843
- else {
844
+ if (!isGitHubActions && !skipChecks) {
844
845
  const octokit = new Octokit({ auth: token });
846
+ let username = "";
845
847
  try {
846
848
  const { data: { login } } = await octokit.users.getAuthenticated();
847
849
  username = login;
@@ -858,7 +860,6 @@ const githubCheck = async (ctx, config) => {
858
860
  throw new Error(MSG.ERROR.GITHUB_USER);
859
861
  }
860
862
  }
861
- return username;
862
863
  };
863
864
  const createRelease = async (ctx, config) => {
864
865
  const { pkg: { name, toPreRelease }, git: { currentTag }, isCI, dryRun } = ctx;
@@ -903,11 +904,11 @@ const createRelease = async (ctx, config) => {
903
904
  //#endregion
904
905
  //#region src/npm/prompts.ts
905
906
  const runNpmPublishPrompts = async (ctx, config) => {
906
- const { pkg: { name, next }, configFileExists: cfe, dryRun } = ctx;
907
+ const { pkg: { name, next }, configFileExists: cfe, dryRun, isCI } = ctx;
907
908
  const { npm: { publish } } = config;
908
909
  const logs = [];
909
910
  const target = `${name}@${next}`;
910
- const shouldPrompt = (val) => isUndefined(val) || dryRun;
911
+ const shouldPrompt = (val) => isUndefined(val) || dryRun && !isCI;
911
912
  const res = await group({
912
913
  publish: () => shouldPrompt(publish) ? confirm({ message: question(MSG.PROMPT.NPM_PUBLISH(target), "npm") }) : (() => {
913
914
  logs.push(yellow`npm.publish`);
@@ -1132,7 +1133,7 @@ var Action = class {
1132
1133
  console.log();
1133
1134
  const { otp, package: defPkg, prepare, showChangelog, showRelease, ci, dryRun, onlyChangelog } = options;
1134
1135
  intro(prepare ? MSG.INTRO_PREPARE(dryRun) : MSG.INTRO(dryRun));
1135
- const { isMonorepo, packages, getPkgDir, skipNpm } = config;
1136
+ const { isMonorepo, packages, getPkgDir, skipGit, skipNpm, skipGithub } = config;
1136
1137
  if (isMonorepo) {
1137
1138
  if (ci) {
1138
1139
  if (!defPkg) abortTask(MSG.ABORT.MONOREPO_CI_NO_PACKAGE);
@@ -1177,9 +1178,12 @@ var Action = class {
1177
1178
  current: pkgVersion,
1178
1179
  next: nextVersion,
1179
1180
  toPreRelease: false,
1180
- publishConfig: { ...publishConfig }
1181
+ publishConfig: { ...publishConfig },
1182
+ scope: isEmpty(selectedPkg) ? void 0 : selectedPkg
1181
1183
  },
1182
- noNpm: pkgPrivate || skipNpm
1184
+ noGit: skipGit,
1185
+ noNpm: pkgPrivate || skipNpm,
1186
+ noGitHub: skipGit || skipGithub
1183
1187
  });
1184
1188
  const { messages } = await publint({ pkgDir });
1185
1189
  for (const message of messages) {
@@ -1220,8 +1224,9 @@ var Action = class {
1220
1224
  return resolveConfig(cwd);
1221
1225
  }
1222
1226
  async printChangelog(ctx, config) {
1223
- const { showChangelog, selectedPkg, noGit, isIncrement } = ctx;
1227
+ const { showChangelog, selectedPkg, noGit, isIncrement, pkg: { scope } } = ctx;
1224
1228
  const { isMonorepo, getPkgDir, changelog: { tagPrefix, releaseCount, includeHidden, transformTypes } } = config;
1229
+ const { github: { shortNotes } } = config;
1225
1230
  if (noGit) return;
1226
1231
  const match = isMonorepo ? `${tagPrefix?.(selectedPkg)}*` : "*";
1227
1232
  const { from, to } = isZero(releaseCount) ? {
@@ -1232,11 +1237,13 @@ var Action = class {
1232
1237
  const changelog = await getChangelog({
1233
1238
  getPkgDir: () => getPkgDir(selectedPkg),
1234
1239
  tagPrefix: tagPrefix?.(selectedPkg),
1240
+ scope: isEmpty(nonNullable([scope])) ? void 0 : nonNullable([scope]),
1235
1241
  releaseCount,
1236
1242
  includeHidden,
1237
1243
  transformTypes
1238
1244
  });
1239
- Object.assign(ctx.github, { changelog });
1245
+ const short = `Please refer to [CHANGELOG.md](${getPkgDir(selectedPkg)}/CHANGELOG.md) for details.`;
1246
+ Object.assign(ctx.github, { changelog: shortNotes ? short : changelog });
1240
1247
  if (commits) {
1241
1248
  msg("GIT", `Changelog(${from}...${to}):${eol(2)}` + changelog);
1242
1249
  msg("GIT", `Commits(${from}...${to}):${eol(2)}` + commits);
@@ -1252,7 +1259,8 @@ var Action = class {
1252
1259
  await gitCheck(ctx, config);
1253
1260
  const { git: { remoteName, remoteUrl } } = ctx;
1254
1261
  return success(MSG.CHECK.GIT.CHECKED(remoteName, remoteUrl), dryRun);
1255
- }
1262
+ },
1263
+ enabled: !ctx.noGit
1256
1264
  },
1257
1265
  {
1258
1266
  title: MSG.CHECK.NPM.CHECKING,
@@ -1266,10 +1274,10 @@ var Action = class {
1266
1274
  {
1267
1275
  title: MSG.CHECK.GITHUB.CHECKING,
1268
1276
  task: async () => {
1269
- const msg = await githubCheck(ctx, config);
1277
+ await githubCheck(ctx, config);
1270
1278
  const { github: { owner, repo } } = ctx;
1271
1279
  const repository = getGithubUrl(owner, repo);
1272
- return success(MSG.CHECK.GITHUB.CHECKED(repository, msg), dryRun);
1280
+ return success(MSG.CHECK.GITHUB.CHECKED(repository), dryRun);
1273
1281
  },
1274
1282
  enabled: !(ctx.noGitHub || onlyChangelog)
1275
1283
  }
@@ -1291,7 +1299,7 @@ var Action = class {
1291
1299
  });
1292
1300
  if (res) msg("BUMP", res.reason);
1293
1301
  const inferred = res?.releaseType;
1294
- const nextVersion = (isCI ? inferred ?? inc(current, "patch") : void 0) || await runVersionPrompts(ctx, config, inferred);
1302
+ const nextVersion = (isCI ? inc(current, inferred ?? "patch") : void 0) || await runVersionPrompts(ctx, config, inferred);
1295
1303
  ctx.isIncrement = neq(current, nextVersion);
1296
1304
  const { version: next, isPrerelease: toPreRelease, preId, preBase } = parseVersion(nextVersion);
1297
1305
  Object.assign(ctx.pkg, {
@@ -1318,7 +1326,7 @@ var Action = class {
1318
1326
  await this.printChangelog(ctx, config);
1319
1327
  }
1320
1328
  async changelogTask(ctx, config) {
1321
- const { isIncrement, dryRun, selectedPkg, noGit, onlyChangelog } = ctx;
1329
+ const { isIncrement, dryRun, selectedPkg, noGit, onlyChangelog, pkg: { scope } } = ctx;
1322
1330
  const { getPkgDir, changelog: { tagPrefix, releaseCount, includeHidden, transformTypes } } = config;
1323
1331
  if (noGit) return;
1324
1332
  await tasks([{
@@ -1327,6 +1335,7 @@ var Action = class {
1327
1335
  await generateChangelog({
1328
1336
  getPkgDir: () => getPkgDir(selectedPkg),
1329
1337
  tagPrefix: tagPrefix?.(selectedPkg),
1338
+ scope: isEmpty(nonNullable([scope])) ? void 0 : nonNullable([scope]),
1330
1339
  releaseCount,
1331
1340
  includeHidden,
1332
1341
  transformTypes
@@ -1399,6 +1408,45 @@ var Action = class {
1399
1408
  }
1400
1409
  };
1401
1410
  //#endregion
1411
+ //#region src/monorepo/packages.ts
1412
+ const scanWorkspacePackages = (cwd) => {
1413
+ const workspaceFile = join(cwd, "pnpm-workspace.yaml");
1414
+ if (!existsSync(workspaceFile)) return [];
1415
+ const { packages = [] } = parse(readFileSync(workspaceFile, "utf8"));
1416
+ return globSync(packages.map((pattern) => normalizePath(join(pattern, "package.json"))), {
1417
+ cwd,
1418
+ absolute: false
1419
+ });
1420
+ };
1421
+ const getPackages = (cwd) => {
1422
+ return scanWorkspacePackages(cwd).reduce(((acc, file) => {
1423
+ const workspace = dirname(dirname(file));
1424
+ (acc[workspace] ??= []).push(basename(dirname(file)));
1425
+ return acc;
1426
+ }), {});
1427
+ };
1428
+ const monoConfig = (packages = [], workspace = "packages") => {
1429
+ const subs = isEmpty(packages) ? "[]" : `[ ${[...packages].map((k) => `'${k}'`).join(", ")} ]`;
1430
+ return dedent(`
1431
+ import { defineConfig } from '@peiyanlu/release'
1432
+ ${newline(1)}
1433
+ export default defineConfig({
1434
+ isMonorepo: true,
1435
+ packages: ${subs},
1436
+ getPkgDir: (pkg) => \`${workspace}/\${ pkg }\`,
1437
+ toTag: (pkg, version) => \`\${ pkg }@\${ version }\`,
1438
+ changelog: {
1439
+ tagPrefix: (pkg) => \`\${ pkg }@\`,
1440
+ },
1441
+ })
1442
+ `);
1443
+ };
1444
+ const generateMonoConfig = (cwd = process.cwd()) => {
1445
+ const entries = Object.entries(getPackages(cwd));
1446
+ const [workspace, packages] = entries.length === 1 ? entries[0] : [];
1447
+ return monoConfig(packages, workspace);
1448
+ };
1449
+ //#endregion
1402
1450
  //#region src/cli.ts
1403
1451
  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
1452
  const { prepare } = options;
@@ -1417,16 +1465,12 @@ program.command("init").description("Create a release configuration file").optio
1417
1465
  console.error(`${red`Error:`} ${yellow(infile)} already exists.\nUse ${green`--force`} to overwrite.`);
1418
1466
  process.exit(1);
1419
1467
  }
1420
- const monoConfigToString = () => `{
1421
- isMonorepo: true,
1422
- packages: [],
1423
- getPkgDir: (pkg) => \`packages/\${ pkg }\`,
1424
- toTag: (pkg, version) => \`\${ pkg }@\${ version }\`,
1425
- changelog: {
1426
- tagPrefix: (pkg) => \`\${ pkg }@\`,
1427
- },
1428
- }`;
1429
- const content = [`import { defineConfig } from '@peiyanlu/release'`, `export default defineConfig(${monorepo ? monoConfigToString() : "{}"})`].join(eol(3));
1468
+ const config = () => dedent(`
1469
+ import { defineConfig } from '@peiyanlu/release'
1470
+ ${newline(1)}
1471
+ export default defineConfig({})
1472
+ `);
1473
+ const content = monorepo ? generateMonoConfig(process.cwd()) : config();
1430
1474
  writeFileSync(configFile, content + newline(), "utf-8");
1431
1475
  console.log(`Wrote to ${underline(dim(configFile))}`);
1432
1476
  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
- * 是否跳过 GitHub 发布前检查
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({ types });
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.2.1",
3
+ "version": "1.3.0",
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.3",
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",