cross-release-cli 0.1.0 → 0.2.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/app.js CHANGED
@@ -1,619 +1,674 @@
1
- // src/app.ts
2
- import process4 from "node:process";
3
- import {
4
- cancel,
5
- confirm,
6
- intro,
7
- isCancel,
8
- log as log2,
9
- outro
10
- } from "@clack/prompts";
11
- import {
12
- findProjectFiles,
13
- getProjectVersion,
14
- isVersionValid as isVersionValid2,
15
- upgradeProjectVersion
16
- } from "cross-bump";
1
+ import process from "node:process";
2
+ import { cancel, confirm, intro, isCancel, log, outro, select, spinner, text } from "@clack/prompts";
3
+ import { DEFAULT_IGNORED_GLOBS, findProjectFiles, getGitignores, getNextVersions, getProjectVersion, isVersionValid, parseVersion, upgradeProjectVersion } from "cross-bump";
17
4
  import { execaSync, parseCommandString } from "execa";
18
5
  import isUnicodeSupported from "is-unicode-supported";
19
- import color2 from "picocolors";
20
-
21
- // src/cli.ts
6
+ import color from "picocolors";
22
7
  import path from "node:path";
23
- import { toAbsolute as toAbsolute2 } from "@rainbowatcher/path-extra";
24
- import { Command } from "commander";
25
- import { DEFAULT_IGNORED_GLOBS as DEFAULT_IGNORED_GLOBS2, getGitignores } from "cross-bump";
26
- import { defu as defu2 } from "defu";
27
-
28
- // package.json
29
- var version = "0.1.0-alpha.4";
30
-
31
- // src/constants.ts
32
- import process from "node:process";
33
- import { DEFAULT_IGNORED_GLOBS } from "cross-bump";
34
- var CONFIG_DEFAULT = {
35
- all: false,
36
- commit: {
37
- stageAll: false,
38
- template: "chore: release v%s",
39
- verify: true
40
- },
41
- cwd: process.cwd(),
42
- debug: false,
43
- dry: false,
44
- exclude: DEFAULT_IGNORED_GLOBS,
45
- execute: [],
46
- main: "javascript",
47
- push: {
48
- followTags: false
49
- },
50
- recursive: false,
51
- tag: {
52
- template: "v%s"
53
- },
54
- yes: false
55
- };
56
-
57
- // src/util/config.ts
58
- import process2 from "node:process";
59
- import { isFileSync } from "@rainbowatcher/fs-extra";
60
8
  import { toAbsolute } from "@rainbowatcher/path-extra";
61
- import defu from "defu";
9
+ import cac from "cac";
10
+ import { isFileSync } from "@rainbowatcher/fs-extra";
62
11
  import { loadConfig } from "unconfig";
63
-
64
- // src/util/debug.ts
65
12
  import debug from "debug";
13
+ import { Objects } from "@rainbowatcher/common";
14
+ import { z } from "zod";
15
+
16
+ //#region src/util/debug.ts
66
17
  function createDebug(ns) {
67
- return debug(`cross-release-cli:${ns}`);
18
+ return debug(`cross-release-cli:${ns}`);
68
19
  }
69
- function isDebugEnable(options) {
70
- if (options.debug) {
71
- debug.enable("cross-release-cli:*");
72
- }
20
+ function setupDebug(options) {
21
+ if (options.debug) debug.enable("cross-release-cli:*");
73
22
  }
74
23
 
75
- // src/util/config.ts
76
- var debug2 = createDebug("config");
24
+ //#endregion
25
+ //#region src/config.ts
26
+ const debug$4 = createDebug("config");
77
27
  function resolveAltOptions(opts, key, defaultValue) {
78
- const value = opts[key];
79
- const _defaultValue = defaultValue ?? {};
80
- return typeof value === "boolean" ? value ? _defaultValue : {} : { ..._defaultValue, ...value };
28
+ const value = opts[key];
29
+ const _defaultValue = defaultValue ?? {};
30
+ return typeof value === "boolean" ? value ? _defaultValue : {} : {
31
+ ..._defaultValue,
32
+ ...value
33
+ };
34
+ }
35
+ function loadUserSpecifiedConfigFile(configPath) {
36
+ const absConfigPath = toAbsolute(configPath);
37
+ if (!isFileSync(absConfigPath)) throw new Error(`${absConfigPath} is not a valid file.`);
38
+ const { config, sources } = loadConfig.sync({ sources: [{ files: absConfigPath }] });
39
+ debug$4("load specified config file: %O", sources);
40
+ return config;
81
41
  }
82
- async function loadUserSpecifiedConfigFile(configPath, currentOpts) {
83
- const absConfigPath = toAbsolute(configPath);
84
- if (!isFileSync(absConfigPath)) {
85
- throw new Error(`${absConfigPath} is not a valid file.`);
86
- }
87
- const { config, sources } = await loadConfig({
88
- sources: [{
89
- files: absConfigPath
90
- }]
91
- });
92
- debug2("load specified config file:", sources);
93
- return defu({ config: toAbsolute(currentOpts.config ?? "") }, currentOpts, config);
42
+ function loadDefaultConfigFile(cwd = process.cwd()) {
43
+ const { config, sources } = loadConfig.sync({
44
+ cwd,
45
+ sources: [{ files: "cross-release.config" }, {
46
+ extensions: ["json"],
47
+ files: "package",
48
+ rewrite(config$1) {
49
+ return config$1["cross-release"];
50
+ }
51
+ }]
52
+ }) ?? {};
53
+ debug$4("load user config", sources);
54
+ debug$4("user config: %O", config);
55
+ return config;
94
56
  }
95
- async function loadUserConfig(cwd = process2.cwd()) {
96
- const { config: userConfig, sources } = await loadConfig({
97
- cwd,
98
- sources: [
99
- { files: "cross-release.config" },
100
- {
101
- extensions: ["json"],
102
- files: "package",
103
- rewrite(config) {
104
- return config["cross-release"];
105
- }
106
- }
107
- ]
108
- });
109
- debug2("load user config", sources);
110
- debug2("user config:", userConfig);
111
- return userConfig;
57
+ function loadUserConfig(opts) {
58
+ let userConfig;
59
+ if (opts.config) userConfig = loadUserSpecifiedConfigFile(opts.config);
60
+ else userConfig = loadDefaultConfigFile(opts.cwd);
61
+ return userConfig;
112
62
  }
113
63
 
114
- // src/cli.ts
115
- var debug3 = createDebug("cli");
116
- function createCliProgram() {
117
- const cli = new Command("cross-release");
118
- cli.configureHelp({
119
- subcommandTerm: (cmd) => `${cmd.name()} ${cmd.usage()}`
120
- });
121
- cli.name("cross-release").version(version).description("A release tool that support multi programming language").usage("[version] [options]").option("-a, --all", "Add all changed files to staged", CONFIG_DEFAULT.commit.stageAll).option("-c, --config [file]", "Config file (auto detect by default)").option("-D, --dry", "Dry run", CONFIG_DEFAULT.dry).option("-d, --debug", "Enable debug mode", CONFIG_DEFAULT.debug).option("-e, --exclude [dir...]", "Folders to exclude from search", CONFIG_DEFAULT.exclude).option("-m, --main", "Base project language [e.g. java, rust, javascript]", CONFIG_DEFAULT.main).option("-r, --recursive", "Run the command for each project in the workspace", CONFIG_DEFAULT.recursive).option("-x, --execute [command...]", "Execute the command", CONFIG_DEFAULT.execute).option("-y, --yes", "Answer yes to all prompts", CONFIG_DEFAULT.yes).option("--cwd [dir]", "Set working directory", CONFIG_DEFAULT.cwd).option("--no-commit", "Skip committing changes").option("--no-push", "Skip pushing").option("--no-tag", "Skip tagging").option("-h, --help", "Display this message");
122
- return cli;
64
+ //#endregion
65
+ //#region src/constants.ts
66
+ /**
67
+ * CLI exit codes.
68
+ *
69
+ * @see https://nodejs.org/api/process.html#process_exit_codes
70
+ */
71
+ let ExitCode = /* @__PURE__ */ function(ExitCode$1) {
72
+ ExitCode$1[ExitCode$1["Canceled"] = 2] = "Canceled";
73
+ ExitCode$1[ExitCode$1["FatalError"] = 1] = "FatalError";
74
+ ExitCode$1[ExitCode$1["GitDirty"] = 3] = "GitDirty";
75
+ ExitCode$1[ExitCode$1["InvalidArgument"] = 9] = "InvalidArgument";
76
+ ExitCode$1[ExitCode$1["Success"] = 0] = "Success";
77
+ return ExitCode$1;
78
+ }({});
79
+ const CONFIG_DEFAULT = {
80
+ commit: {
81
+ signoff: true,
82
+ stageAll: false,
83
+ template: "chore: release v%s",
84
+ verify: true
85
+ },
86
+ cwd: process.cwd(),
87
+ debug: false,
88
+ dry: false,
89
+ exclude: DEFAULT_IGNORED_GLOBS,
90
+ execute: [],
91
+ main: "javascript",
92
+ push: {
93
+ branch: void 0,
94
+ followTags: true,
95
+ remote: void 0
96
+ },
97
+ recursive: false,
98
+ tag: { template: "v%s" },
99
+ yes: false
100
+ };
101
+
102
+ //#endregion
103
+ //#region src/util/array.ts
104
+ function toArray(maybeArr) {
105
+ if (!maybeArr) return [];
106
+ return Array.isArray(maybeArr) ? maybeArr : [maybeArr];
107
+ }
108
+
109
+ //#endregion
110
+ //#region src/util/merge.ts
111
+ function merge(target, ...sources) {
112
+ let result = target;
113
+ const composer = (left, right, key) => {
114
+ if ([
115
+ "commit",
116
+ "push",
117
+ "tag"
118
+ ].includes(key) && right === true) return left;
119
+ };
120
+ for (const source of sources) result = Objects.mergeWith(result, source, composer);
121
+ return result;
122
+ }
123
+
124
+ //#endregion
125
+ //#region src/zod.ts
126
+ const cliOptions = z.object({
127
+ commit: z.union([z.object({
128
+ signoff: z.boolean(),
129
+ stageAll: z.boolean(),
130
+ template: z.string(),
131
+ verify: z.boolean()
132
+ }), z.boolean()]).describe("Indicates whether to commit the changes."),
133
+ config: z.string().optional(),
134
+ cwd: z.string(),
135
+ debug: z.boolean(),
136
+ dry: z.boolean(),
137
+ exclude: z.array(z.string()),
138
+ execute: z.array(z.string()),
139
+ main: z.string(),
140
+ push: z.union([z.object({
141
+ branch: z.string().optional(),
142
+ followTags: z.boolean(),
143
+ remote: z.string().optional()
144
+ }), z.boolean()]),
145
+ recursive: z.boolean(),
146
+ tag: z.union([z.object({ template: z.string() }), z.boolean()]),
147
+ version: z.string().optional(),
148
+ yes: z.boolean()
149
+ });
150
+
151
+ //#endregion
152
+ //#region package.json
153
+ var version = "0.1.0";
154
+
155
+ //#endregion
156
+ //#region src/cli.ts
157
+ const debug$3 = createDebug("cli");
158
+ function createCliProgram(argv) {
159
+ const cli = cac("cross-release").usage("A release tool that support multi programming language").version(version).usage("[version] [options]").option("-c, --config [file]", "Config file (auto detect by default)").option("-D, --dry", "Dry run").option("-d, --debug", "Enable debug mode").option("-e, --exclude [dir...]", "Folders to exclude from search").option("-m, --main [lang]", "Base project language [e.g. java, rust, javascript]").option("-r, --recursive", "Run the command for each project in the workspace").option("-x, --execute [command...]", "Execute the command").option("-y, --yes", "Answer yes to all prompts").option("--cwd [dir]", "Set working directory").option("--commit", "Committing changes").option("--commit.signoff", "Pushing Commit with signoff").option("--commit.stageAll", "Stage all changes before pushing").option("--commit.template <template>", "Template for commit message").option("--commit.verify", "Verify commit message").option("--push", "Pushing Commit to remote").option("--push.followTags", "Pushing with follow tags").option("--push.branch <branch>", "Branch name to push").option("--push.followTags", "pushing with follow tags").option("--tag", "Tagging for release").option("--tag.template <template>", "Template for tag message").option("-h, --help", "Display this message").help();
160
+ return cli.parse(argv);
123
161
  }
124
- function toCliReleaseOptions(cli) {
125
- const { args } = cli;
126
- const options = cli.opts();
127
- if (options.help) {
128
- cli.help();
129
- }
130
- return {
131
- ...options,
132
- // combine user cli exclude option with default
133
- exclude: options.exclude?.length ? [...DEFAULT_IGNORED_GLOBS2, ...options.exclude] : DEFAULT_IGNORED_GLOBS2,
134
- ...args.length > 0 ? { version: args[0] } : {}
135
- };
162
+ function argvToReleaseOptions(cli) {
163
+ const { args, options } = cli;
164
+ const opts = {
165
+ commit: options.commit,
166
+ config: options.config,
167
+ cwd: options.cwd,
168
+ debug: options.debug,
169
+ dry: options.dry,
170
+ exclude: toArray(options.exclude),
171
+ execute: toArray(options.execute),
172
+ main: options.main,
173
+ push: options.push,
174
+ recursive: options.recursive,
175
+ tag: options.tag,
176
+ version: options.version,
177
+ yes: options.yes,
178
+ ...args.length > 0 ? { version: args[0] } : {}
179
+ };
180
+ debug$3("cli options: %O", opts);
181
+ return opts;
136
182
  }
137
- async function resolveOptions(cli) {
138
- const cliOptions = toCliReleaseOptions(cli);
139
- let userConfig;
140
- if (cliOptions.config) {
141
- userConfig = await loadUserSpecifiedConfigFile(cliOptions.config, cliOptions);
142
- } else {
143
- userConfig = await loadUserConfig(cliOptions.cwd);
144
- }
145
- const parsedArgs = defu2(cliOptions, userConfig);
146
- isDebugEnable(parsedArgs);
147
- const set = getGitignores(parsedArgs.cwd);
148
- for (const i of parsedArgs.exclude) set.add(i);
149
- parsedArgs.exclude = [...set];
150
- const shouldBeAbsolute = ["cwd", "config"];
151
- for (const key of shouldBeAbsolute) {
152
- if (!parsedArgs[key]) continue;
153
- if (key === "cwd") {
154
- const { cwd } = parsedArgs;
155
- parsedArgs.cwd = toAbsolute2(cwd);
156
- }
157
- parsedArgs[key] = path.resolve(parsedArgs.cwd, parsedArgs[key]);
158
- }
159
- debug3("parsedArgs:", parsedArgs);
160
- return parsedArgs;
183
+ function pathToAbs(opts) {
184
+ const shouldBeAbsolute = ["cwd", "config"];
185
+ for (const key of shouldBeAbsolute) {
186
+ if (!opts[key]) continue;
187
+ if (key === "cwd") opts.cwd = toAbsolute(opts.cwd);
188
+ opts[key] = path.resolve(opts.cwd, opts[key]);
189
+ }
190
+ }
191
+ function resolveGitIgnore(opts) {
192
+ const ignoresSet = getGitignores(opts.cwd);
193
+ for (const i of opts.exclude) ignoresSet.add(i);
194
+ opts.exclude = [...ignoresSet];
195
+ }
196
+ function validateOptions(cli) {
197
+ const result = cliOptions.safeParse(cli);
198
+ if (!result.success) {
199
+ const formatted = result.error.format();
200
+ let errorMsg = "";
201
+ for (const [key, val] of Object.entries(formatted)) {
202
+ if (key === "_errors") continue;
203
+ errorMsg = `${val._errors[0]} for key \`${key}\``;
204
+ }
205
+ console.error(errorMsg);
206
+ process.exit(ExitCode.FatalError);
207
+ }
208
+ }
209
+ function resolveAppOptions(cli) {
210
+ const opts = argvToReleaseOptions(cli);
211
+ const userConfig = loadUserConfig(opts);
212
+ const crOptions = merge(CONFIG_DEFAULT, userConfig, opts);
213
+ validateOptions(crOptions);
214
+ setupDebug(crOptions);
215
+ resolveGitIgnore(crOptions);
216
+ pathToAbs(crOptions);
217
+ debug$3("resolved app options: %O", crOptions);
218
+ return crOptions;
161
219
  }
162
220
 
163
- // src/git.ts
164
- import process3 from "node:process";
165
- import { log, spinner } from "@clack/prompts";
166
- import { execaSync as createExeca } from "execa";
167
- import color from "picocolors";
168
- var debug4 = createDebug("git");
169
- var execa = createExeca({ all: true, reject: false });
221
+ //#endregion
222
+ //#region src/git.ts
223
+ const debug$2 = createDebug("git");
224
+ const execa = execaSync({
225
+ all: true,
226
+ reject: false
227
+ });
170
228
  function gitTag(options) {
171
- const {
172
- cwd = process3.cwd(),
173
- del = false,
174
- dry = false,
175
- force = false,
176
- message: message2,
177
- tagName: name
178
- } = options ?? {};
179
- const s = spinner();
180
- s.start("creating tag...");
181
- const args = [];
182
- if (del) {
183
- args.push("--delete");
184
- } else {
185
- if (!message2 || message2?.length === 0) {
186
- log.warn("no message provided, is recommended to provide a message for create an annotated tag");
187
- } else {
188
- args.push(
189
- // Create an annotated tag, which is recommended for releases.
190
- // See https://git-scm.com/docs/git-tag
191
- "--annotate",
192
- // Use the same commit message for the tag
193
- "--message",
194
- // formatMessageString(template, nextVersion),
195
- message2
196
- );
197
- }
198
- }
199
- if (force) args.push("--force");
200
- args.push(name);
201
- debug4(`command: git tag ${args.join(" ")}`);
202
- if (!dry) {
203
- const { all, exitCode, failed, shortMessage } = execa("git", ["tag", ...args], { cwd });
204
- debug4("git tag stdout:", all);
205
- if (failed) {
206
- s.stop(color.red(shortMessage), exitCode);
207
- return false;
208
- }
209
- }
210
- s.stop(`create git tag: ${color.blue(name)}`);
211
- return true;
229
+ const { cwd = process.cwd(), del = false, dry = false, force = false, message: message$1, tagName: name } = options ?? {};
230
+ const s = spinner();
231
+ s.start("creating tag...");
232
+ const args = [];
233
+ if (del) args.push("--delete");
234
+ else if (!message$1 || message$1?.length === 0) log.warn("no message provided, is recommended to provide a message for create an annotated tag");
235
+ else args.push(
236
+ // Create an annotated tag, which is recommended for releases.
237
+ // See https://git-scm.com/docs/git-tag
238
+ "--annotate",
239
+ // Use the same commit message for the tag
240
+ "--message",
241
+ // formatMessageString(template, nextVersion),
242
+ message$1
243
+ );
244
+ if (force) args.push("--force");
245
+ args.push(name);
246
+ debug$2(`command: git tag ${args.join(" ")}`);
247
+ if (!dry) {
248
+ const { all, exitCode, failed, shortMessage } = execa("git", ["tag", ...args], { cwd });
249
+ debug$2("git tag stdout:", all);
250
+ if (failed) {
251
+ s.stop(color.red(shortMessage), exitCode);
252
+ return false;
253
+ }
254
+ }
255
+ s.stop(`create git tag: ${color.blue(name)}`);
256
+ return true;
212
257
  }
213
258
  function gitCommit(options) {
214
- const {
215
- cwd = process3.cwd(),
216
- dry = false,
217
- message: message2,
218
- modifiedFiles = [],
219
- stageAll,
220
- verify
221
- } = options ?? {};
222
- const s = spinner();
223
- s.start("committing...");
224
- const args = [];
225
- args.push("--message", message2);
226
- !verify && args.push("--no-verify");
227
- if (!stageAll && modifiedFiles.length > 0) {
228
- args.push("--", ...modifiedFiles);
229
- } else {
230
- args.push("--all");
231
- }
232
- debug4(`command: git commit ${args.join(" ")}`);
233
- if (!dry) {
234
- const { all, exitCode, failed, shortMessage } = execa("git", ["commit", ...args], { cwd });
235
- debug4("git commit stdout: %s", all);
236
- if (failed) {
237
- s.stop(color.red(shortMessage), exitCode);
238
- return false;
239
- }
240
- }
241
- s.stop(`commit message: ${color.green(message2)}`);
242
- return true;
259
+ const { cwd = process.cwd(), dry = false, message: message$1, modifiedFiles = [], signoff, stageAll, verify } = options ?? {};
260
+ const s = spinner();
261
+ s.start("committing...");
262
+ const args = [];
263
+ args.push("--message", message$1);
264
+ !verify && args.push("--no-verify");
265
+ if (!stageAll && modifiedFiles.length > 0) args.push("--", ...modifiedFiles);
266
+ else args.push("--all");
267
+ if (signoff) args.push("--signoff");
268
+ debug$2(`command: git commit ${args.join(" ")}`);
269
+ if (!dry) {
270
+ const { all, exitCode, failed, shortMessage } = execa("git", ["commit", ...args], { cwd });
271
+ debug$2("git commit stdout: %s", all);
272
+ if (failed) {
273
+ s.stop(color.red(shortMessage), exitCode);
274
+ return false;
275
+ }
276
+ }
277
+ s.stop(`commit message: ${color.green(message$1)}`);
278
+ return true;
243
279
  }
244
280
  function gitPush(options = {}) {
245
- const {
246
- branch,
247
- cwd = process3.cwd(),
248
- dry,
249
- followTags = true,
250
- remote
251
- } = options;
252
- const s = spinner();
253
- s.start("pushing...");
254
- const args = [];
255
- if (remote) {
256
- args.push(remote);
257
- if (branch) {
258
- args.push(branch);
259
- }
260
- }
261
- followTags && args.push("--follow-tags");
262
- debug4(`command: git push ${args.join(" ")}`);
263
- if (!dry) {
264
- const { all, exitCode, failed, shortMessage } = execa("git", ["push", ...args], { cwd });
265
- debug4("git push stdout: %s", all);
266
- if (failed) {
267
- s.stop(color.red(shortMessage), exitCode);
268
- return false;
269
- }
270
- }
271
- const originUrl = gitOriginUrl();
272
- s.stop(`pushed to repo: ${color.underline(originUrl)}`);
273
- return true;
281
+ const { branch, cwd = process.cwd(), dry, followTags = true, remote } = options;
282
+ const s = spinner();
283
+ s.start("pushing...");
284
+ const args = [];
285
+ if (remote) {
286
+ args.push(remote);
287
+ if (branch) args.push(branch);
288
+ }
289
+ followTags && args.push("--follow-tags");
290
+ debug$2(`command: git push ${args.join(" ")}`);
291
+ if (!dry) {
292
+ const { all, exitCode, failed, shortMessage } = execa("git", ["push", ...args], { cwd });
293
+ debug$2("git push stdout: %s", all);
294
+ if (failed) {
295
+ s.stop(color.red(shortMessage), exitCode);
296
+ return false;
297
+ }
298
+ }
299
+ const originUrl = gitOriginUrl();
300
+ s.stop(`pushed to repo: ${color.underline(originUrl)}`);
301
+ return true;
274
302
  }
275
303
  function gitOriginUrl() {
276
- const command = execa("git", ["remote", "get-url", "origin"]);
277
- return command.stdout.trim();
304
+ const command = execa("git", [
305
+ "remote",
306
+ "get-url",
307
+ "origin"
308
+ ]);
309
+ return command.stdout.trim();
278
310
  }
279
311
  function gitAdd(options = {}) {
280
- const {
281
- all = false,
282
- cwd = process3.cwd(),
283
- dry = false,
284
- files = []
285
- } = options;
286
- const args = [];
287
- if (all) {
288
- args.push("--all");
289
- } else if (files.length > 0) {
290
- args.push("--", ...files);
291
- }
292
- debug4("command: git add", args.join(" "));
293
- if (!dry) {
294
- const { all: all2, failed } = execa("git", ["add", ...args], { cwd });
295
- debug4("git add stdout:", all2);
296
- if (failed) {
297
- return false;
298
- }
299
- }
300
- return true;
312
+ const { all = false, cwd = process.cwd(), dry = false, files = [] } = options;
313
+ const args = [];
314
+ if (all) args.push("--all");
315
+ else if (files.length > 0) args.push("--", ...files);
316
+ debug$2("command: git add", args.join(" "));
317
+ if (!dry) {
318
+ const { all: all$1, failed } = execa("git", ["add", ...args], { cwd });
319
+ debug$2("git add stdout:", all$1);
320
+ if (failed) return false;
321
+ }
322
+ return true;
301
323
  }
302
324
  function isGitClean(options = {}) {
303
- const { cwd = process3.cwd() } = options;
304
- const args = ["diff-index", "--quiet", "HEAD", "--"];
305
- const { failed, message: message2 } = execa("git", args, { cwd });
306
- if (message2?.includes("bad revision")) {
307
- return true;
308
- }
309
- return !failed;
325
+ const { cwd = process.cwd() } = options;
326
+ const args = [
327
+ "diff-index",
328
+ "--quiet",
329
+ "HEAD",
330
+ "--"
331
+ ];
332
+ const { failed, message: message$1 } = execa("git", args, { cwd });
333
+ if (message$1?.includes("bad revision")) return true;
334
+ return !failed;
310
335
  }
336
+ /**
337
+ * `-z`: use NUL termination instead of newline
338
+ * @see https://git-scm.com/docs/diff
339
+ */
311
340
  function getStagedFiles(opts = {}) {
312
- const { cwd } = opts;
313
- let stagedArr = [];
314
- const args = ["--name-only", "--staged", "-z", "--diff-filter=ACMR"];
315
- debug4("command: git diff", args.join(" "));
316
- const { all, failed } = execa("git", ["diff", ...args], { cwd });
317
- if (!failed) {
318
- stagedArr = all.replace(/\0$/, "").split("\0");
319
- }
320
- return stagedArr;
341
+ const { cwd } = opts;
342
+ let stagedArr = [];
343
+ const args = [
344
+ "--name-only",
345
+ "--staged",
346
+ "-z",
347
+ "--diff-filter=ACMR"
348
+ ];
349
+ debug$2("command: git diff", args.join(" "));
350
+ const { all, failed } = execa("git", ["diff", ...args], { cwd });
351
+ if (!failed) stagedArr = all.replace(/\0$/, "").split("\0");
352
+ return stagedArr;
321
353
  }
322
354
 
323
- // src/prompt.ts
324
- import { select, text } from "@clack/prompts";
325
- import { getNextVersions, isVersionValid, parseVersion } from "cross-bump";
355
+ //#endregion
356
+ //#region src/prompt.ts
357
+ /**
358
+ * Generates the version to be chosen based on command line arguments and project version.
359
+ *
360
+ * @param argv - The command line arguments.
361
+ * @param currentVersion - The current project version.
362
+ * @return The chosen version.
363
+ */
326
364
  async function chooseVersion(currentVersion) {
327
- const versionObj = parseVersion(currentVersion);
328
- const {
329
- nextMajor,
330
- nextMinor,
331
- nextPatch,
332
- nextPreMajor,
333
- nextPreMinor,
334
- nextPrePatch,
335
- nextRelease
336
- } = getNextVersions(versionObj ?? void 0);
337
- const C_CUSTOM = "custom";
338
- const versions = [
339
- { label: "custom...", value: C_CUSTOM },
340
- { label: `next (${nextRelease})`, value: nextRelease },
341
- { label: `keep (${currentVersion})`, value: currentVersion ?? "" },
342
- { label: `patch (${nextPatch})`, value: nextPatch },
343
- { label: `minor (${nextMinor})`, value: nextMinor },
344
- { label: `major (${nextMajor})`, value: nextMajor },
345
- { label: `pre-patch (${nextPrePatch})`, value: nextPrePatch },
346
- { label: `pre-minor (${nextPreMinor})`, value: nextPreMinor },
347
- { label: `pre-major (${nextPreMajor})`, value: nextPreMajor }
348
- ];
349
- const selectedValue = await select({
350
- initialValue: versions[1].value ?? C_CUSTOM,
351
- message: `Pick a project version. (current: ${currentVersion})`,
352
- options: versions
353
- });
354
- if (!selectedValue || selectedValue === C_CUSTOM) {
355
- return await text({
356
- message: "Input your custom version number",
357
- placeholder: "version number",
358
- validate: (value) => {
359
- if (!isVersionValid(value)) {
360
- return "Invalid";
361
- }
362
- }
363
- });
364
- } else {
365
- return selectedValue;
366
- }
365
+ const versionObj = parseVersion(currentVersion);
366
+ const { nextMajor, nextMinor, nextPatch, nextPreMajor, nextPreMinor, nextPrePatch, nextRelease } = getNextVersions(versionObj ?? void 0);
367
+ const C_CUSTOM = "custom";
368
+ const versions = [
369
+ {
370
+ label: "custom...",
371
+ value: C_CUSTOM
372
+ },
373
+ {
374
+ label: `next (${nextRelease})`,
375
+ value: nextRelease
376
+ },
377
+ {
378
+ label: `keep (${currentVersion})`,
379
+ value: currentVersion ?? ""
380
+ },
381
+ {
382
+ label: `patch (${nextPatch})`,
383
+ value: nextPatch
384
+ },
385
+ {
386
+ label: `minor (${nextMinor})`,
387
+ value: nextMinor
388
+ },
389
+ {
390
+ label: `major (${nextMajor})`,
391
+ value: nextMajor
392
+ },
393
+ {
394
+ label: `pre-patch (${nextPrePatch})`,
395
+ value: nextPrePatch
396
+ },
397
+ {
398
+ label: `pre-minor (${nextPreMinor})`,
399
+ value: nextPreMinor
400
+ },
401
+ {
402
+ label: `pre-major (${nextPreMajor})`,
403
+ value: nextPreMajor
404
+ }
405
+ ];
406
+ const selectedValue = await select({
407
+ initialValue: versions[1].value ?? C_CUSTOM,
408
+ message: `Pick a project version. (current: ${currentVersion})`,
409
+ options: versions
410
+ });
411
+ if (!selectedValue || selectedValue === C_CUSTOM) return await text({
412
+ message: "Input your custom version number",
413
+ placeholder: "version number",
414
+ validate: (value) => {
415
+ if (!isVersionValid(value)) return "Invalid";
416
+ }
417
+ });
418
+ else return selectedValue;
367
419
  }
368
420
 
369
- // src/util/str.ts
421
+ //#endregion
422
+ //#region src/util/str.ts
423
+ /**
424
+ * Accepts a message string template (e.g. "release %s" or "This is the %s release").
425
+ * If the template contains any "%s" placeholders, then they are replaced with the version number;
426
+ * otherwise, the version number is appended to the string.
427
+ */
370
428
  function formatMessageString(template, nextVersion) {
371
- return template?.includes("%s") ? template.replaceAll("%s", nextVersion) : template + nextVersion;
429
+ return template?.includes("%s") ? template.replaceAll("%s", nextVersion) : template + nextVersion;
372
430
  }
373
431
 
374
- // src/app.ts
375
- var debug5 = createDebug("app");
432
+ //#endregion
433
+ //#region src/app.ts
434
+ const debug$1 = createDebug("app");
376
435
  function message(msg) {
377
- const bar = isUnicodeSupported() ? "\u2502" : "|";
378
- console.log(`${color2.gray(bar)} ${msg}`);
436
+ const bar = isUnicodeSupported() ? "" : "|";
437
+ console.log(`${color.gray(bar)} ${msg}`);
379
438
  }
439
+ /**
440
+ * Return the original result if it is not a cancellation symbol. exit process when detect cancel signal
441
+ */
380
442
  function handleUserCancel(result) {
381
- if (isCancel(result)) {
382
- cancel("User cancel");
383
- process4.exit(2 /* Canceled */);
384
- }
385
- return result;
443
+ if (isCancel(result)) {
444
+ cancel("User cancel");
445
+ process.exit(ExitCode.Canceled);
446
+ }
447
+ return result;
386
448
  }
387
- var App = class _App {
388
- _currentVersion = "";
389
- _modifiedFiles = [];
390
- _nextVersion = "";
391
- _options;
392
- _projectFiles = [];
393
- _taskQueue = [];
394
- _taskStatus = "pending";
395
- constructor(opts) {
396
- this._options = opts;
397
- }
398
- static async create(argv = process4.argv) {
399
- const cli = createCliProgram().parse(argv);
400
- const opts = await resolveOptions(cli);
401
- return new _App(opts);
402
- }
403
- #addTask(task, idx) {
404
- const expect = this._taskQueue.length + 1;
405
- if (idx) {
406
- this._taskQueue.splice(idx, 0, task);
407
- } else {
408
- this._taskQueue.push(task);
409
- }
410
- return this._taskQueue.length === expect;
411
- }
412
- #check(status) {
413
- if (Array.isArray(status)) {
414
- if (status.some((s) => !s)) {
415
- this._taskStatus = "failed";
416
- }
417
- } else if (!status) {
418
- this._taskStatus = "failed";
419
- }
420
- }
421
- #checkDryRun() {
422
- if (this._options.dry) {
423
- log2.message(color2.bgBlue(" DRY RUN "));
424
- process4.env.DRY = "true";
425
- }
426
- }
427
- #done() {
428
- if (this._taskStatus === "failed") {
429
- outro(color2.red("Error"));
430
- process4.exit(1 /* FatalError */);
431
- } else {
432
- outro("Done");
433
- this._taskStatus = "finished";
434
- }
435
- }
436
- #start() {
437
- intro("Cross release");
438
- this.#checkDryRun();
439
- this._taskStatus = "running";
440
- }
441
- checkGitClean() {
442
- const { cwd } = this._options;
443
- if (!isGitClean({ cwd })) {
444
- log2.warn("git is not clean, please commit or stash your changes before release");
445
- this.#done();
446
- process4.exit(3 /* GitDirty */);
447
- }
448
- }
449
- async confirmReleaseOptions() {
450
- const { all, cwd, dry, yes } = this._options;
451
- const confirmTask = async (name, message2, exec) => {
452
- if (yes) {
453
- if (!this._options[name]) return;
454
- this._options[name] = true;
455
- } else if (this._options[name]) {
456
- const confirmation = await confirm({ message: message2 });
457
- this._options[name] = handleUserCancel(confirmation);
458
- }
459
- if (this._options[name]) {
460
- this.#addTask({ exec, name });
461
- }
462
- };
463
- let commitMessage;
464
- if (this._options.commit) {
465
- const {
466
- stageAll,
467
- template,
468
- verify
469
- } = resolveAltOptions(this._options, "commit", {
470
- ...CONFIG_DEFAULT.commit,
471
- stageAll: all
472
- });
473
- const _all = stageAll ?? all;
474
- this.#addTask({
475
- exec: () => {
476
- return gitAdd({
477
- all: _all,
478
- cwd,
479
- dry,
480
- files: this._modifiedFiles
481
- });
482
- },
483
- name: "add"
484
- });
485
- commitMessage = formatMessageString(template, this._nextVersion);
486
- await confirmTask("commit", "should commit?", () => {
487
- debug5("staged files: %O", getStagedFiles({ cwd }));
488
- return gitCommit({
489
- cwd,
490
- dry,
491
- message: commitMessage,
492
- modifiedFiles: _all ? void 0 : this._modifiedFiles,
493
- stageAll: _all,
494
- verify
495
- });
496
- });
497
- }
498
- if (this._options.tag && commitMessage !== void 0) {
499
- const { template: tagTpt } = resolveAltOptions(this._options, "tag", CONFIG_DEFAULT.tag);
500
- await confirmTask("tag", "should create tag?", () => {
501
- const tagName = formatMessageString(tagTpt, this._nextVersion);
502
- return gitTag({
503
- cwd,
504
- dry,
505
- message: commitMessage,
506
- tagName
507
- });
508
- });
509
- }
510
- if (this._options.push) {
511
- const { followTags } = resolveAltOptions(this._options, "push", CONFIG_DEFAULT.push);
512
- await confirmTask("push", "should push to remote?", () => {
513
- return gitPush({ cwd, dry, followTags });
514
- });
515
- }
516
- }
517
- async executeTasks() {
518
- debug5("taskQueue:", this._taskQueue);
519
- for await (const task of this._taskQueue) {
520
- if (this._taskStatus === "failed") break;
521
- this.#check(await task.exec());
522
- }
523
- }
524
- resolveExecutes() {
525
- const { cwd, execute } = this._options;
526
- const indexBeforeCommit = this._taskQueue.findIndex((t) => t.name === "commit") - 1;
527
- const index = indexBeforeCommit === -1 ? this._taskQueue.length : indexBeforeCommit;
528
- for (const command of execute) {
529
- if (!command) continue;
530
- const [cmd, ...args] = parseCommandString(command);
531
- if (!cmd) continue;
532
- const exec = () => {
533
- debug5("exec command: %s %s", cmd, args.join(" "));
534
- const { exitCode, failed, stdout } = execaSync(cmd, args, { cwd, reject: false });
535
- debug5("exec stdout:", stdout, exitCode);
536
- if (failed) {
537
- log2.error(`exec: ${command}`);
538
- return false;
539
- } else {
540
- log2.success(`exec: ${command}`);
541
- return true;
542
- }
543
- };
544
- this.#addTask({ exec, name: "anonymous" }, index);
545
- }
546
- }
547
- async resolveNextVersion() {
548
- const { main, version: version2 } = this._options;
549
- const mainProjectFile = this._projectFiles.find((file) => file.category === main);
550
- if (!mainProjectFile) {
551
- throw new Error(`can't found ${main} project file in the project root`);
552
- }
553
- const projectVersion = await getProjectVersion(mainProjectFile);
554
- this._currentVersion = projectVersion ?? "";
555
- if (isVersionValid2(version2)) {
556
- this._nextVersion = version2;
557
- log2.info(`current version: ${this._currentVersion}, next version: ${color2.blue(this._nextVersion)}`);
558
- } else {
559
- const nextVersion = await chooseVersion(this._currentVersion);
560
- this._nextVersion = handleUserCancel(nextVersion);
561
- }
562
- }
563
- resolveProjectFiles() {
564
- const { cwd, exclude, recursive } = this._options;
565
- const projectFiles = findProjectFiles(cwd, exclude, recursive);
566
- if (projectFiles.length === 0) {
567
- console.error("can't found any project file in the project root");
568
- process4.exit(1);
569
- }
570
- debug5(`found ${projectFiles.length} project files`);
571
- this._projectFiles = projectFiles;
572
- }
573
- resolveProjects() {
574
- const { _nextVersion, _projectFiles } = this;
575
- this.#addTask({
576
- exec: async () => {
577
- return await Promise.all(_projectFiles.map(async (projectFile) => {
578
- try {
579
- await upgradeProjectVersion(_nextVersion, projectFile);
580
- this._modifiedFiles.push(projectFile.path);
581
- message(`upgrade to ${color2.blue(_nextVersion)} for ${color2.gray(projectFile.path)}`);
582
- } catch (error) {
583
- log2.error(String(error));
584
- return false;
585
- }
586
- return true;
587
- }));
588
- },
589
- name: "upgradeVersion"
590
- });
591
- }
592
- async run() {
593
- this.#start();
594
- this.checkGitClean();
595
- this.resolveProjectFiles();
596
- await this.resolveNextVersion();
597
- this.resolveProjects();
598
- await this.confirmReleaseOptions();
599
- this.resolveExecutes();
600
- await this.executeTasks();
601
- this.#done();
602
- }
603
- get currentVersion() {
604
- return this._currentVersion;
605
- }
606
- get nextVersion() {
607
- return this._nextVersion;
608
- }
609
- get options() {
610
- return this._options;
611
- }
612
- get projectFiles() {
613
- return this._projectFiles;
614
- }
449
+ var App = class {
450
+ _currentVersion = "";
451
+ _modifiedFiles = [];
452
+ _nextVersion = "";
453
+ _options;
454
+ _projectFiles = [];
455
+ _taskQueue = [];
456
+ _taskStatus = "pending";
457
+ constructor(argv = process.argv) {
458
+ const cli = createCliProgram(argv);
459
+ const opts = resolveAppOptions(cli);
460
+ this._options = opts;
461
+ }
462
+ #addTask(task, idx) {
463
+ const expect = this._taskQueue.length + 1;
464
+ if (idx) this._taskQueue.splice(idx, 0, task);
465
+ else this._taskQueue.push(task);
466
+ return this._taskQueue.length === expect;
467
+ }
468
+ #check(status) {
469
+ if (Array.isArray(status)) {
470
+ if (status.some((s) => !s)) this._taskStatus = "failed";
471
+ } else if (!status) this._taskStatus = "failed";
472
+ }
473
+ #checkDryRun() {
474
+ if (this._options.dry) {
475
+ log.message(color.bgBlue(" DRY RUN "));
476
+ process.env.DRY = "true";
477
+ }
478
+ }
479
+ #done() {
480
+ if (this._taskStatus === "failed") {
481
+ outro(color.red("Error"));
482
+ process.exit(ExitCode.FatalError);
483
+ } else {
484
+ outro("Done");
485
+ this._taskStatus = "finished";
486
+ }
487
+ }
488
+ #start() {
489
+ intro("Cross release");
490
+ this.#checkDryRun();
491
+ this._taskStatus = "running";
492
+ }
493
+ checkGitClean() {
494
+ const { cwd } = this._options;
495
+ const commit = resolveAltOptions(this._options, "commit");
496
+ const isClean = isGitClean({ cwd });
497
+ if (!isClean && !commit.stageAll) {
498
+ log.warn("git is not clean, please commit or stash your changes before release");
499
+ this.#done();
500
+ process.exit(ExitCode.GitDirty);
501
+ }
502
+ }
503
+ async confirmReleaseOptions() {
504
+ const { cwd, dry, yes } = this._options;
505
+ const confirmTask = async (name, message$1, exec) => {
506
+ if (yes) {
507
+ if (!this._options[name]) return;
508
+ this._options[name] = true;
509
+ } else if (this._options[name]) {
510
+ const confirmation = await confirm({ message: message$1 });
511
+ this._options[name] = handleUserCancel(confirmation);
512
+ }
513
+ if (this._options[name]) this.#addTask({
514
+ exec,
515
+ name
516
+ });
517
+ };
518
+ let commitMessage;
519
+ if (this._options.commit) {
520
+ const { stageAll, template, verify } = resolveAltOptions(this._options, "commit", { ...CONFIG_DEFAULT.commit });
521
+ this.#addTask({
522
+ exec: () => {
523
+ return gitAdd({
524
+ all: stageAll,
525
+ cwd,
526
+ dry,
527
+ files: this._modifiedFiles
528
+ });
529
+ },
530
+ name: "add"
531
+ });
532
+ commitMessage = formatMessageString(template, this._nextVersion);
533
+ await confirmTask("commit", "should commit?", () => {
534
+ debug$1("staged files: %O", getStagedFiles({ cwd }));
535
+ return gitCommit({
536
+ cwd,
537
+ dry,
538
+ message: commitMessage,
539
+ modifiedFiles: stageAll ? void 0 : this._modifiedFiles,
540
+ stageAll,
541
+ verify
542
+ });
543
+ });
544
+ }
545
+ if (this._options.tag && commitMessage !== void 0) {
546
+ const { template: tagTpt } = resolveAltOptions(this.options, "tag");
547
+ await confirmTask("tag", "should create tag?", () => {
548
+ const tagName = formatMessageString(tagTpt, this._nextVersion);
549
+ return gitTag({
550
+ cwd,
551
+ dry,
552
+ message: commitMessage,
553
+ tagName
554
+ });
555
+ });
556
+ }
557
+ if (this._options.push) {
558
+ const { followTags } = resolveAltOptions(this._options, "push", CONFIG_DEFAULT.push);
559
+ await confirmTask("push", "should push to remote?", () => {
560
+ return gitPush({
561
+ cwd,
562
+ dry,
563
+ followTags
564
+ });
565
+ });
566
+ }
567
+ }
568
+ async executeTasks() {
569
+ debug$1("taskQueue:", this._taskQueue);
570
+ for (const task of this._taskQueue) {
571
+ if (this._taskStatus === "failed") break;
572
+ this.#check(await task.exec());
573
+ }
574
+ }
575
+ resolveExecutes() {
576
+ const { cwd, execute } = this._options;
577
+ const indexBeforeCommit = this._taskQueue.findIndex((t) => t.name === "commit") - 1;
578
+ const index = indexBeforeCommit === -1 ? this._taskQueue.length : indexBeforeCommit;
579
+ for (const command of execute) {
580
+ if (!command) continue;
581
+ const [cmd, ...args] = parseCommandString(command);
582
+ if (!cmd) continue;
583
+ const exec = () => {
584
+ debug$1("exec command: %s %s", cmd, args.join(" "));
585
+ const { exitCode, failed, stdout } = execaSync(cmd, args, {
586
+ cwd,
587
+ reject: false
588
+ });
589
+ debug$1("exec stdout:", stdout, exitCode);
590
+ if (failed) {
591
+ log.error(`exec: ${command}`);
592
+ return false;
593
+ } else {
594
+ log.success(`exec: ${command}`);
595
+ return true;
596
+ }
597
+ };
598
+ this.#addTask({
599
+ exec,
600
+ name: "anonymous"
601
+ }, index);
602
+ }
603
+ }
604
+ async resolveNextVersion() {
605
+ const { main, version: version$1 } = this._options;
606
+ const mainProjectFile = this._projectFiles.find((file) => file.category === main);
607
+ if (!mainProjectFile) throw new Error(`can't found ${main} project file in the project root`);
608
+ const projectVersion = await getProjectVersion(mainProjectFile);
609
+ this._currentVersion = projectVersion ?? "";
610
+ if (isVersionValid(version$1)) {
611
+ this._nextVersion = version$1;
612
+ log.info(`current version: ${this._currentVersion}, next version: ${color.blue(this._nextVersion)}`);
613
+ } else {
614
+ const nextVersion = await chooseVersion(this._currentVersion);
615
+ this._nextVersion = handleUserCancel(nextVersion);
616
+ }
617
+ }
618
+ resolveProjectFiles() {
619
+ const { cwd, exclude, recursive } = this._options;
620
+ const projectFiles = findProjectFiles(cwd, exclude, recursive);
621
+ if (projectFiles.length === 0) {
622
+ console.error("can't found any project file in the project root");
623
+ process.exit(ExitCode.FatalError);
624
+ }
625
+ debug$1(`found ${projectFiles.length} project files`);
626
+ this._projectFiles = projectFiles;
627
+ }
628
+ resolveProjects() {
629
+ const { _nextVersion, _projectFiles } = this;
630
+ this.#addTask({
631
+ exec: async () => {
632
+ return await Promise.all(_projectFiles.map(async (projectFile) => {
633
+ try {
634
+ await upgradeProjectVersion(_nextVersion, projectFile);
635
+ this._modifiedFiles.push(projectFile.path);
636
+ message(`upgrade to ${color.blue(_nextVersion)} for ${color.gray(projectFile.path)}`);
637
+ } catch (error) {
638
+ log.error(String(error));
639
+ return false;
640
+ }
641
+ return true;
642
+ }));
643
+ },
644
+ name: "upgradeVersion"
645
+ });
646
+ }
647
+ async run() {
648
+ this.#start();
649
+ this.checkGitClean();
650
+ this.resolveProjectFiles();
651
+ await this.resolveNextVersion();
652
+ this.resolveProjects();
653
+ await this.confirmReleaseOptions();
654
+ this.resolveExecutes();
655
+ await this.executeTasks();
656
+ this.#done();
657
+ }
658
+ get currentVersion() {
659
+ return this._currentVersion;
660
+ }
661
+ get nextVersion() {
662
+ return this._nextVersion;
663
+ }
664
+ get options() {
665
+ return this._options;
666
+ }
667
+ get projectFiles() {
668
+ return this._projectFiles;
669
+ }
615
670
  };
616
671
  var app_default = App;
617
- export {
618
- app_default as default
619
- };
672
+
673
+ //#endregion
674
+ export { app_default as default };