@peiyanlu/release 2.0.0 → 2.1.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,99 +1,30 @@
1
- import path, { join, resolve } from "node:path";
1
+ import path, { join, relative, resolve } from "node:path";
2
2
  import { fileURLToPath, pathToFileURL } from "node:url";
3
+ import { cancel, confirm, group, intro, isCancel, log, note, outro, select, text } from "@clack/prompts";
3
4
  import { newline } from "@conventional-changelog/template";
4
5
  import { gitAdd, gitAddSync, gitBranch, gitBranchCurrent, gitCheckoutBranch, gitCheckoutBranchForce, gitCommitMessage, gitConfigGet, gitConfigSet, gitFetch, gitLsRemote, gitPush, gitPushBranch, gitRevParse, gitTag, isGitRepo, isPrerelease, isValidVersion, isWorkingTreeClean } from "@peiyanlu/cli-utils";
5
6
  import { readJsonFileSync } from "@peiyanlu/node-utils";
6
7
  import { assert, equals, isString, isUndefined, omit, throwError } from "@peiyanlu/ts-utils";
8
+ import { blue, blueBright, cyan, cyanBright, dim, green, greenBright, magenta, magentaBright, red, redBright, yellow } from "ansis";
7
9
  import { program } from "commander";
8
10
  import Handlebars from "handlebars";
9
11
  import { existsSync, readFileSync, writeFileSync } from "node:fs";
10
12
  import { mkdir } from "node:fs/promises";
11
- import { cancel, confirm, group, intro, isCancel, log, note, outro, select, text } from "@clack/prompts";
12
- import { blue, blueBright, cyan, cyanBright, dim, green, greenBright, magenta, magentaBright, red, redBright, yellow } from "ansis";
13
- import { inc, lt } from "semver";
14
13
  import "conventional-changelog";
15
14
  import "conventional-changelog-conventionalcommits";
16
15
  import "conventional-recommended-bump";
17
- //#region node_modules/.pnpm/tsdown@0.23.1_publint@0.3.22_typescript@6.0.3/node_modules/tsdown/esm-shims.js
16
+ import { inc, lt } from "semver";
17
+ import "@octokit/rest";
18
+ import "open";
19
+ import "tinyglobby";
20
+ import "yaml";
21
+ import "@actions/github";
22
+ import "@actions/core";
23
+ // node_modules/.pnpm/tsdown@0.23.4_publint@0.3.25_typescript@6.0.3/node_modules/tsdown/esm-shims.js
18
24
  const getFilename = () => fileURLToPath(import.meta.url);
19
25
  const getDirname = () => path.dirname(getFilename());
20
26
  const __dirname = /* @__PURE__ */ getDirname();
21
- //#endregion
22
- //#region package.json
23
- var package_default = {
24
- name: "@peiyanlu/release",
25
- version: "2.0.0",
26
- description: "Tool to automate version bumps, changelogs, and releases.",
27
- license: "MIT",
28
- type: "module",
29
- "private": false,
30
- bin: { "release": "index.js" },
31
- exports: {
32
- ".": {
33
- "types": "./dist/index.d.mts",
34
- "default": "./dist/index.mjs"
35
- },
36
- "./package.json": "./package.json"
37
- },
38
- scripts: {
39
- "dev": "tsdown -w",
40
- "build": "tsdown",
41
- "prepublishOnly": "tsdown",
42
- "release": "node index.js",
43
- "release:dry": "node index.js -C -n",
44
- "test": "vitest run",
45
- "test:cov": "vitest run --coverage"
46
- },
47
- dependencies: {
48
- "@actions/core": "^3.0.1",
49
- "@actions/github": "^9.1.1",
50
- "@clack/prompts": "^1.7.0",
51
- "@conventional-changelog/template": "^1.2.1",
52
- "@octokit/rest": "^22.0.1",
53
- "@peiyanlu/cli-utils": "^0.0.15",
54
- "@peiyanlu/node-utils": "^0.0.4",
55
- "@peiyanlu/ts-utils": "^0.0.12",
56
- "ansis": "^4.3.1",
57
- "commander": "^15.0.0",
58
- "conventional-changelog": "^8.1.0",
59
- "conventional-changelog-conventionalcommits": "^10.2.1",
60
- "conventional-recommended-bump": "^12.1.0",
61
- "open": "^11.0.0",
62
- "publint": "^0.3.22",
63
- "semver": "^7.8.5",
64
- "tinyglobby": "^0.2.17",
65
- "yaml": "^2.9.0",
66
- "handlebars": "^4.7.9"
67
- },
68
- devDependencies: {
69
- "@peiyanlu/test-tools": "^0.0.7",
70
- "@types/node": "^24.13.3",
71
- "@types/semver": "^7.7.1",
72
- "@vitest/coverage-v8": "^4.1.10",
73
- "tsdown": "^0.23.1",
74
- "typescript": "^6.0.3",
75
- "vitest": "^4.1.10"
76
- },
77
- author: {
78
- "name": "YanluPei",
79
- "email": "peiyanlu@vip.qq.com"
80
- },
81
- engines: { "node": ">=20" },
82
- keywords: ["typescript", "cli"],
83
- files: ["dist", "template"],
84
- publishConfig: {
85
- "access": "public",
86
- "registry": "https://registry.npmjs.org"
87
- },
88
- repository: {
89
- "type": "git",
90
- "url": "git+https://github.com/peiyanlu/release.git"
91
- },
92
- bugs: { "url": "https://github.com/peiyanlu/release/issues" },
93
- homepage: "https://github.com/peiyanlu/release#readme"
94
- };
95
- //#endregion
96
- //#region src/config/index.ts
27
+ // src/config/index.ts
97
28
  const loadJavaScriptFile = async (file) => {
98
29
  const { href } = pathToFileURL(file);
99
30
  const mod = await import(href);
@@ -122,8 +53,9 @@ const loadConfig = async (cwd = process.cwd()) => {
122
53
  config: await loadJavaScriptFile(file)
123
54
  };
124
55
  };
125
- //#endregion
126
- //#region src/core/logger.ts
56
+ // src/core/utils.ts
57
+ const when = (condition, ...value) => condition ? [...value] : [];
58
+ // src/core/logger.ts
127
59
  const defaultPrinter = ({ dryRun, stage, message }) => {
128
60
  console.log(`${dryRun ? `[dry-run]` : ""}[${stage}]:`, message);
129
61
  };
@@ -203,11 +135,7 @@ var Logger = class {
203
135
  return color;
204
136
  }
205
137
  };
206
- //#endregion
207
- //#region src/core/utils.ts
208
- const when = (condition, ...value) => condition ? [...value] : [];
209
- //#endregion
210
- //#region src/core/releaser.ts
138
+ // src/core/releaser.ts
211
139
  var Releaser = class {
212
140
  options;
213
141
  project;
@@ -285,7 +213,7 @@ var Releaser = class {
285
213
  logger.info("Skipping checkout.");
286
214
  return;
287
215
  }
288
- logger.info(`Checking out branch ${headBranch}...`);
216
+ logger.info(`Checking out branch "${headBranch}"...`);
289
217
  this.state.headBranch = headBranch;
290
218
  this.state.baseBranch = await this.getBaseBranch();
291
219
  if (fetch) {
@@ -300,10 +228,10 @@ var Releaser = class {
300
228
  ]);
301
229
  }
302
230
  if (force) {
303
- logger.verbose(`Force checking out branch ${headBranch}...`);
231
+ logger.verbose(`Force checking out branch "${headBranch}"...`);
304
232
  if (!dryRun) await gitCheckoutBranchForce(headBranch);
305
233
  } else {
306
- logger.verbose(`Checking out branch ${headBranch}...`);
234
+ logger.verbose(`Checking out branch "${headBranch}"...`);
307
235
  if (!dryRun) await gitCheckoutBranch(headBranch);
308
236
  }
309
237
  });
@@ -579,8 +507,7 @@ var Releaser = class {
579
507
  return gitBranchCurrent();
580
508
  }
581
509
  };
582
- //#endregion
583
- //#region src/releaser-cli/prompts.ts
510
+ // src/releaser-cli/prompts.ts
584
511
  const abortTask = (msg) => {
585
512
  cancel(`${red("✕")} ${msg ?? "Release cancelled."}`);
586
513
  process.exit(0);
@@ -609,7 +536,8 @@ const createVersionOptions = async (project, options = {}, current, tagPrefix) =
609
536
  {
610
537
  label: "Custom",
611
538
  value: "custom",
612
- hint: "Specify an exact version"
539
+ hint: "Specify an exact version",
540
+ bump: { firstRelease }
613
541
  },
614
542
  {
615
543
  label: "As-is",
@@ -740,10 +668,8 @@ const runBumpPrompts = async (project, options = {}) => {
740
668
  if (isUndefined(val) || !isValidVersion(val)) return "Invalid version.";
741
669
  }
742
670
  }));
743
- else {
744
- const selected = find(type);
745
- Object.assign(promptsRes, selected?.bump);
746
- }
671
+ const selected = find(type);
672
+ Object.assign(promptsRes, selected?.bump);
747
673
  return promptsRes;
748
674
  };
749
675
  if (project.isMonorepoMode() && project.isIndependentMode) for await (const subProject of project.getProjects()) {
@@ -767,10 +693,10 @@ const getCliPromptOptions = async (options = {}) => {
767
693
  const shouldPrompt = (v) => !ci && (isUndefined(v) || dryRun);
768
694
  const res = await group({
769
695
  commit: () => options.skipGit ? Promise.resolve(false) : shouldPrompt(options.skipGit) ? confirm({ message: question("Commit release changes?", dryRun) }) : Promise.resolve(true),
770
- tag: () => options.skipGit ? Promise.resolve(false) : shouldPrompt(options.skipGit) ? confirm({ message: question("Create release tags?", dryRun) }) : Promise.resolve(true),
771
- push: () => options.skipGit ? Promise.resolve(false) : shouldPrompt(options.skipGit) ? confirm({ message: question("Push release changes?", dryRun) }) : Promise.resolve(true),
696
+ tag: ({ results: { commit } }) => !commit ? Promise.resolve(false) : shouldPrompt(options.skipGit) ? confirm({ message: question("Create release tags?", dryRun) }) : Promise.resolve(true),
697
+ push: ({ results: { commit } }) => !commit ? Promise.resolve(false) : shouldPrompt(options.skipGit) ? confirm({ message: question("Push release changes?", dryRun) }) : Promise.resolve(true),
772
698
  publish: () => options.skipPublish ? Promise.resolve(false) : shouldPrompt(options.skipPublish) ? confirm({ message: question("Publish project?", dryRun) }) : Promise.resolve(true),
773
- release: () => options.skipRelease || options.skipGit ? Promise.resolve(false) : shouldPrompt(options.skipRelease) ? confirm({ message: question("Create repository release?", dryRun) }) : Promise.resolve(true)
699
+ release: () => options.skipRelease ? Promise.resolve(false) : shouldPrompt(options.skipRelease) ? confirm({ message: question("Create repository release?", dryRun) }) : Promise.resolve(true)
774
700
  }, { onCancel: () => {
775
701
  abortTask();
776
702
  } });
@@ -785,8 +711,8 @@ const getCliPromptOptions = async (options = {}) => {
785
711
  const runConfirmPrompts = async (ci = false, dryRun = false) => {
786
712
  if (!(ci ? true : await singlePrompt(() => confirm({ message: question(red("Confirm start workflow?"), dryRun) })))) abortTask();
787
713
  };
788
- //#endregion
789
- //#region src/releaser-cli/releaser.ts
714
+ // src/releaser-cli/releaser.ts
715
+ const pkgJson$1 = readJsonFileSync(join(__dirname, "..", "package.json"));
790
716
  var ReleaserCli = class extends Releaser {
791
717
  constructor(options) {
792
718
  const { silent, dryRun, verbose } = options;
@@ -833,17 +759,22 @@ var ReleaserCli = class extends Releaser {
833
759
  }
834
760
  setCliOptions(options) {
835
761
  return this.enqueue(async () => {
836
- const { ci, dryRun, releaseType: as } = options;
762
+ const { ci, dryRun, releaseType: as, skipBump } = options;
837
763
  const { project, stepsOptions: { bump: bumpOpts } } = this;
838
- const bump = await runBumpPrompts(project, {
764
+ const version = await project.manifest.getVersion();
765
+ const bump = skipBump ? { version } : await runBumpPrompts(project, {
839
766
  bump: {
840
767
  ...bumpOpts,
841
768
  as
842
769
  },
843
770
  run: options
844
771
  });
845
- this.setOptions({ bump }).setSkippedSteps(await getCliPromptOptions(options));
772
+ const skippedSteps = await getCliPromptOptions(options);
846
773
  await runConfirmPrompts(ci, dryRun);
774
+ this.setOptions({ bump }).setSkippedSteps({
775
+ bump: version === bump.version && !bump.firstRelease,
776
+ ...skippedSteps
777
+ });
847
778
  });
848
779
  }
849
780
  setLegacyOptions() {
@@ -863,33 +794,9 @@ var ReleaserCli = class extends Releaser {
863
794
  this.setOptions(legacySteps);
864
795
  });
865
796
  }
866
- check() {
867
- return this.enqueue(async () => {
868
- const { project, hosting } = this;
869
- const logger = this.logger.createChild("check");
870
- logger.info("Checking release environment...");
871
- if (![
872
- "commit",
873
- "tag",
874
- "push"
875
- ].every((k) => this.shouldSkip(k))) {
876
- logger.verbose("Checking Git repository...");
877
- const isRepo = await isGitRepo();
878
- assert(isRepo, "Current directory is not a Git repository");
879
- logger.verbose("Checking working tree...");
880
- const isClean = await isWorkingTreeClean();
881
- assert(isClean, "Git working tree is not clean");
882
- logger.verbose("Checking remote repository...");
883
- const hasRemote = await gitConfigGet("remote.origin.url");
884
- assert(hasRemote, "Git remote \"origin\" is not configured");
885
- }
886
- if (!this.shouldSkip("publish")) await project.publishCheck({ logger });
887
- if (!this.shouldSkip("release")) await hosting?.releaseCheck({ logger });
888
- });
889
- }
890
797
  start() {
891
798
  return this.enqueue(async () => {
892
- const { name, version } = package_default;
799
+ const { name, version } = pkgJson$1;
893
800
  console.log(`${blue`i`} ${name} v${version}\n`);
894
801
  intro("🚀 Starting release");
895
802
  });
@@ -900,16 +807,19 @@ var ReleaserCli = class extends Releaser {
900
807
  process.exit(0);
901
808
  }
902
809
  async runAction(options) {
903
- await this.start().setLegacyOptions().setCliOptions(options).check().bump().commit().tag().push().publish().release().run();
810
+ await this.start().setLegacyOptions().setCliOptions(options).bump().commit().tag().push().publish().release().run();
904
811
  }
905
812
  };
906
- //#endregion
907
- //#region src/cli.ts
908
- program.name("release").description(package_default.description).version(package_default.version, "-v, --version", "Print the tool version and exit.").usage("[release-type] [options]").argument("[release-type]", `Release type. One of 'major', 'minor', 'patch', or 'prerelease'.`).enablePositionalOptions().option("-n, --dry-run", "Run in dry mode.").option("--ci", "Enable CI mode.").option("--skip-git", "Skip all Git-related checks and operations.").option("--skip-publish", "Skip the package publishing step.").option("--skip-release", "Skip the repository release step.").helpOption("-h, --help", "Display help information.").action(async (releaseType, options) => {
813
+ // src/cli.ts
814
+ const pkgJson = readJsonFileSync(join(__dirname, "..", "package.json"));
815
+ program.name("release").description(pkgJson.description).version(pkgJson.version, "-v, --version", "Print the tool version and exit.").usage("[release-type] [options]").argument("[release-type]", `Release type. One of 'major', 'minor', 'patch', or 'prerelease'.`).enablePositionalOptions().option("-n, --dry-run", "Run in dry mode.").option("--ci", "Enable CI mode.").option("--skip-bump", "Skip updating the project version.").option("--skip-git", "Skip Git commit, tag, and push operations. Git history is still read.").option("--skip-publish", "Skip publishing the project.").option("--skip-release", "Skip creating a release on the repository hosting service.").helpOption("-h, --help", "Display help information.").action(async (releaseType, options) => {
909
816
  const { config: { project, hosting, releaser } } = await loadConfig(process.cwd());
910
817
  const { dryRun } = options;
911
- if (!project) throwError("Project not found");
912
- if (!hosting) throwError("GitRepositoryHosting not found");
818
+ assert(await isGitRepo(), "Current directory is not a Git repository");
819
+ assert(await isWorkingTreeClean(), "Git working tree is not clean");
820
+ assert(await gitConfigGet("remote.origin.url"), "Git remote \"origin\" is not configured");
821
+ assert(project, "Project not found");
822
+ assert(hosting, "GitRepositoryHosting not found");
913
823
  await new ReleaserCli({
914
824
  ...releaser,
915
825
  project,
@@ -920,19 +830,80 @@ program.name("release").description(package_default.description).version(package
920
830
  ...options
921
831
  });
922
832
  });
923
- program.command("init").description("Create a release configuration file").option("--npm", "Use npm as the package manager", false).option("--pnpm", "Use pnpm as the package manager", false).option("--monorepo", "Configure a monorepo", false).option("--fixed", "Use fixed versioning for the monorepo", false).option("--github-workflow", "Create a GitHub Actions release workflow", false).option("--force", "Overwrite existing configuration or workflow files", false).option("--add", "Stage generated files with Git", false).action(async (options) => {
924
- const { npm, pnpm, monorepo, fixed, githubWorkflow, force, add } = options;
833
+ program.command("init").description("Initialize release configuration for your project.").option("--npm", "Use npm as the package manager", false).option("--pnpm", "Use pnpm as the package manager", true).option("--trusted", "Use npm Trusted Publishing", true).option("--monorepo", "Configure a monorepo", false).option("--fixed", "Use fixed versioning for the monorepo", false).option("--github-workflow", "Create a GitHub Actions release workflow", true).option("--force", "Overwrite existing configuration or workflow files", false).option("--add", "Stage generated files with Git", true).action(async (options, command) => {
834
+ const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
835
+ const wasProvided = (name) => "cli" === command.getOptionValueSource(name);
836
+ const ask = async (name, prompt, value, provided = wasProvided(name)) => {
837
+ if (!interactive || provided) return value;
838
+ const answer = await prompt();
839
+ if (isCancel(answer)) {
840
+ cancel("Initialization cancelled.");
841
+ process.exit(0);
842
+ }
843
+ return answer;
844
+ };
925
845
  const cwd = process.cwd();
926
846
  const tplDir = resolve(__dirname, "..", "template");
927
- const { dependencies, devDependencies } = readJsonFileSync(join(cwd, "package.json"));
847
+ const detectedPackageManager = existsSync(join(cwd, "pnpm-lock.yaml")) ? "pnpm" : existsSync(join(cwd, "package-lock.json")) ? "npm" : "none";
848
+ const defaultPackageManager = options.npm ? "npm" : options.pnpm ? "pnpm" : detectedPackageManager;
849
+ if (interactive) intro("🚀 Initialize release config");
850
+ const packageManager = await ask("npm", () => select({
851
+ message: "Package manager?",
852
+ options: [
853
+ {
854
+ label: "npm",
855
+ value: "npm"
856
+ },
857
+ {
858
+ label: "pnpm",
859
+ value: "pnpm"
860
+ },
861
+ {
862
+ label: "None",
863
+ value: "none"
864
+ }
865
+ ],
866
+ initialValue: defaultPackageManager
867
+ }), defaultPackageManager, wasProvided("npm") || wasProvided("pnpm"));
868
+ const npm = packageManager === "npm";
869
+ const pnpm = packageManager === "pnpm";
870
+ const monorepo = await ask("monorepo", () => confirm({
871
+ message: "Monorepo?",
872
+ initialValue: options.monorepo
873
+ }), options.monorepo);
874
+ const fixed = monorepo ? await ask("fixed", () => confirm({
875
+ message: "Fixed versioning?",
876
+ initialValue: options.fixed
877
+ }), options.fixed) : false;
878
+ const githubWorkflow = await ask("githubWorkflow", () => confirm({
879
+ message: "GitHub Actions workflow?",
880
+ initialValue: options.githubWorkflow
881
+ }), options.githubWorkflow);
882
+ const trusted = githubWorkflow ? await ask("trusted", () => confirm({
883
+ message: "npm Trusted Publishing?",
884
+ initialValue: options.trusted
885
+ }), options.trusted) : false;
886
+ const add = await ask("add", () => confirm({
887
+ message: "Stage files with Git?",
888
+ initialValue: options.add
889
+ }), options.add);
890
+ const pkg = readJsonFileSync(join(cwd, "package.json"));
928
891
  const isTS = existsSync(join(cwd, "tsconfig.json")) || Object.keys({
929
- ...dependencies,
930
- ...devDependencies
892
+ ...pkg.dependencies,
893
+ ...pkg.devDependencies
931
894
  }).includes("typescript");
932
895
  const configFile = join(cwd, `release.config.${isTS ? "ts" : "js"}`);
933
896
  const workflowFile = join(cwd, ".github/workflows", "release.yml");
934
- if (existsSync(configFile) && !force) throwError(`"${configFile}" already exists`);
935
- if (githubWorkflow && existsSync(workflowFile) && !force) throwError(`"${workflowFile}" already exists`);
897
+ const existingFiles = [...existsSync(configFile) ? [configFile] : [], ...githubWorkflow && existsSync(workflowFile) ? [workflowFile] : []];
898
+ let force = options.force;
899
+ if (existingFiles.length && !force) {
900
+ const files = existingFiles.map((p) => relative(cwd, p)).join(" | ");
901
+ force = await ask("force", () => confirm({
902
+ message: `Overwrite files? ${dim(files)}`,
903
+ initialValue: false
904
+ }), false);
905
+ if (!force) throwError(`Existing files: ${files}`);
906
+ }
936
907
  const template = readFileSync(join(tplDir, `release.config.hbs`), "utf-8");
937
908
  const configContent = Handlebars.compile(template)({
938
909
  monorepo,
@@ -943,12 +914,13 @@ program.command("init").description("Create a release configuration file").optio
943
914
  });
944
915
  writeFileSync(configFile, configContent.trimEnd() + newline(), "utf-8");
945
916
  if (githubWorkflow) {
946
- const workflowContent = readFileSync(join(tplDir, `release.text`), "utf-8");
947
917
  await mkdir(".github/workflows", { recursive: true });
918
+ const template = readFileSync(join(tplDir, `release.workflow.hbs`), "utf-8");
919
+ const workflowContent = Handlebars.compile(template)({ trusted });
948
920
  writeFileSync(workflowFile, workflowContent.trimEnd() + newline(), "utf-8");
949
921
  }
950
922
  if (add) gitAddSync(githubWorkflow ? [configFile, workflowFile] : [configFile]);
923
+ if (interactive) outro("✨ Release config ready");
951
924
  });
952
925
  program.parse(process.argv);
953
- //#endregion
954
926
  export {};