cross-release-cli 0.0.1-alpha.5 → 0.1.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -29
- package/bin/cross-release.mjs +7 -0
- package/dist/app.d.ts +29 -0
- package/dist/app.js +616 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +7 -0
- package/dist/types.d.ts +127 -0
- package/package.json +16 -12
- package/bin/cross-release.js +0 -4
- package/dist/index.mjs +0 -404
package/README.md
CHANGED
|
@@ -22,9 +22,9 @@ yarn add cross-release --global
|
|
|
22
22
|
|
|
23
23
|
```json
|
|
24
24
|
{
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
"scripts": {
|
|
26
|
+
"release": "cross-release"
|
|
27
|
+
}
|
|
28
28
|
}
|
|
29
29
|
```
|
|
30
30
|
|
|
@@ -54,33 +54,33 @@ You can specify various runtime settings by using the "package.json" file. Here
|
|
|
54
54
|
|
|
55
55
|
```json
|
|
56
56
|
{
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
"...": "...",
|
|
58
|
+
"cross-release": {
|
|
59
59
|
// show the help message
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
60
|
+
"showHelp": false,
|
|
61
|
+
// show the version about cross-release
|
|
62
|
+
"showVersion": false,
|
|
63
|
+
"version": "",
|
|
64
|
+
"isAllYes": false,
|
|
65
|
+
"isDry": false,
|
|
66
|
+
"isRecursive": false,
|
|
67
|
+
"shouldCommit": false,
|
|
68
|
+
"shouldPush": false,
|
|
69
|
+
"shouldTag": false,
|
|
70
|
+
// default exclude folders are `["node_modules", ".git"]`, your config will be append within it
|
|
71
|
+
"excludes": ["path/to/exclude"],
|
|
72
|
+
"dir": "/path/to/run",
|
|
73
|
+
"commit": {
|
|
74
|
+
// Whether to invoke git pre-commit and commit-msg hook
|
|
75
|
+
"shouldVerify": true,
|
|
76
|
+
// Whether to stage all un-staged files or stage only changed files
|
|
77
|
+
"shouldStageAll": false,
|
|
78
|
+
// the symbol '%s' will be replace to the version number that you specified
|
|
79
|
+
"template": "chore: release v%s"
|
|
80
|
+
},
|
|
81
|
+
"push": {
|
|
82
|
+
"shouldFollowTags": false
|
|
83
|
+
}
|
|
83
84
|
}
|
|
84
|
-
}
|
|
85
85
|
}
|
|
86
86
|
```
|
package/dist/app.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { ProjectFile } from 'cross-bump';
|
|
2
|
+
import { ReleaseOptions } from './types.js';
|
|
3
|
+
|
|
4
|
+
declare class App {
|
|
5
|
+
#private;
|
|
6
|
+
private _currentVersion;
|
|
7
|
+
private _modifiedFiles;
|
|
8
|
+
private _nextVersion;
|
|
9
|
+
private _options;
|
|
10
|
+
private _projectFiles;
|
|
11
|
+
private _taskQueue;
|
|
12
|
+
private _taskStatus;
|
|
13
|
+
private constructor();
|
|
14
|
+
static create(argv?: string[]): Promise<App>;
|
|
15
|
+
checkGitClean(): void;
|
|
16
|
+
confirmReleaseOptions(): Promise<void>;
|
|
17
|
+
executeTasks(): Promise<void>;
|
|
18
|
+
resolveExecutes(): void;
|
|
19
|
+
resolveNextVersion(): Promise<void>;
|
|
20
|
+
resolveProjectFiles(): void;
|
|
21
|
+
resolveProjects(): void;
|
|
22
|
+
run(): Promise<void>;
|
|
23
|
+
get currentVersion(): string;
|
|
24
|
+
get nextVersion(): string;
|
|
25
|
+
get options(): ReleaseOptions;
|
|
26
|
+
get projectFiles(): ProjectFile[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export { App as default };
|
package/dist/app.js
ADDED
|
@@ -0,0 +1,616 @@
|
|
|
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";
|
|
17
|
+
import { execaSync, parseCommandString } from "execa";
|
|
18
|
+
import isUnicodeSupported from "is-unicode-supported";
|
|
19
|
+
import color2 from "picocolors";
|
|
20
|
+
|
|
21
|
+
// src/cli.ts
|
|
22
|
+
import path from "node:path";
|
|
23
|
+
import { toAbsolute as toAbsolute2 } from "@rainbowatcher/path-extra";
|
|
24
|
+
import { Command } from "commander";
|
|
25
|
+
import { getGitignores } from "cross-bump";
|
|
26
|
+
import { defu as defu2 } from "defu";
|
|
27
|
+
|
|
28
|
+
// package.json
|
|
29
|
+
var version = "0.1.0-alpha.1";
|
|
30
|
+
|
|
31
|
+
// src/constants.ts
|
|
32
|
+
import process from "node:process";
|
|
33
|
+
var CONFIG_DEFAULT = {
|
|
34
|
+
all: false,
|
|
35
|
+
commit: {
|
|
36
|
+
stageAll: false,
|
|
37
|
+
template: "chore: release v%s",
|
|
38
|
+
verify: true
|
|
39
|
+
},
|
|
40
|
+
cwd: process.cwd(),
|
|
41
|
+
debug: false,
|
|
42
|
+
dry: false,
|
|
43
|
+
exclude: ["node_modules", ".git"],
|
|
44
|
+
execute: [],
|
|
45
|
+
main: "javascript",
|
|
46
|
+
push: {
|
|
47
|
+
followTags: false
|
|
48
|
+
},
|
|
49
|
+
recursive: false,
|
|
50
|
+
tag: {
|
|
51
|
+
template: "v%s"
|
|
52
|
+
},
|
|
53
|
+
yes: false
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// src/util/config.ts
|
|
57
|
+
import process2 from "node:process";
|
|
58
|
+
import { isFileSync } from "@rainbowatcher/fs-extra";
|
|
59
|
+
import { toAbsolute } from "@rainbowatcher/path-extra";
|
|
60
|
+
import defu from "defu";
|
|
61
|
+
import { loadConfig } from "unconfig";
|
|
62
|
+
|
|
63
|
+
// src/util/debug.ts
|
|
64
|
+
import debug from "debug";
|
|
65
|
+
function createDebug(ns) {
|
|
66
|
+
return debug(`cross-release-cli:${ns}`);
|
|
67
|
+
}
|
|
68
|
+
function isDebugEnable(options) {
|
|
69
|
+
if (options.debug) {
|
|
70
|
+
debug.enable("cross-release-cli:*");
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// src/util/config.ts
|
|
75
|
+
var debug2 = createDebug("config");
|
|
76
|
+
function resolveAltOptions(opts, key, defaultValue) {
|
|
77
|
+
const value = opts[key];
|
|
78
|
+
const _defaultValue = defaultValue ?? {};
|
|
79
|
+
return typeof value === "boolean" ? value ? _defaultValue : {} : { ..._defaultValue, ...value };
|
|
80
|
+
}
|
|
81
|
+
async function loadUserSpecifiedConfigFile(configPath, currentOpts) {
|
|
82
|
+
const absConfigPath = toAbsolute(configPath);
|
|
83
|
+
if (!isFileSync(absConfigPath)) {
|
|
84
|
+
throw new Error(`${absConfigPath} is not a valid file.`);
|
|
85
|
+
}
|
|
86
|
+
const { config, sources } = await loadConfig({
|
|
87
|
+
sources: [{
|
|
88
|
+
files: absConfigPath
|
|
89
|
+
}]
|
|
90
|
+
});
|
|
91
|
+
debug2("load specified config file:", sources);
|
|
92
|
+
return defu({ config: toAbsolute(currentOpts.config ?? "") }, currentOpts, config);
|
|
93
|
+
}
|
|
94
|
+
async function loadUserConfig(cwd = process2.cwd()) {
|
|
95
|
+
const { config: userConfig, sources } = await loadConfig({
|
|
96
|
+
cwd,
|
|
97
|
+
sources: [
|
|
98
|
+
{ files: "cross-release.config" },
|
|
99
|
+
{
|
|
100
|
+
extensions: ["json"],
|
|
101
|
+
files: "package",
|
|
102
|
+
rewrite(config) {
|
|
103
|
+
return config["cross-release"];
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
]
|
|
107
|
+
});
|
|
108
|
+
debug2("load user config", sources);
|
|
109
|
+
debug2("user config:", userConfig);
|
|
110
|
+
return userConfig;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// src/cli.ts
|
|
114
|
+
var debug3 = createDebug("cli");
|
|
115
|
+
function createCliProgram() {
|
|
116
|
+
const cli = new Command("cross-release");
|
|
117
|
+
cli.configureHelp({
|
|
118
|
+
subcommandTerm: (cmd) => `${cmd.name()} ${cmd.usage()}`
|
|
119
|
+
});
|
|
120
|
+
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");
|
|
121
|
+
return cli;
|
|
122
|
+
}
|
|
123
|
+
function toCliReleaseOptions(cli) {
|
|
124
|
+
const { args } = cli;
|
|
125
|
+
const options = cli.opts();
|
|
126
|
+
if (options.help) {
|
|
127
|
+
cli.help();
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
...options,
|
|
131
|
+
...args.length > 0 ? { version: args[0] } : {}
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
async function resolveOptions(cli) {
|
|
135
|
+
const cliOptions = toCliReleaseOptions(cli);
|
|
136
|
+
let userConfig;
|
|
137
|
+
if (cliOptions.config) {
|
|
138
|
+
userConfig = await loadUserSpecifiedConfigFile(cliOptions.config, cliOptions);
|
|
139
|
+
} else {
|
|
140
|
+
userConfig = await loadUserConfig();
|
|
141
|
+
}
|
|
142
|
+
const parsedArgs = defu2(cliOptions, userConfig);
|
|
143
|
+
isDebugEnable(parsedArgs);
|
|
144
|
+
const set = getGitignores(parsedArgs.cwd);
|
|
145
|
+
for (const i of parsedArgs.exclude) set.add(i);
|
|
146
|
+
parsedArgs.exclude = [...set];
|
|
147
|
+
const shouldBeAbsolute = ["cwd", "config"];
|
|
148
|
+
for (const key of shouldBeAbsolute) {
|
|
149
|
+
if (!parsedArgs[key]) continue;
|
|
150
|
+
if (key === "cwd") {
|
|
151
|
+
const { cwd } = parsedArgs;
|
|
152
|
+
parsedArgs.cwd = toAbsolute2(cwd);
|
|
153
|
+
}
|
|
154
|
+
parsedArgs[key] = path.resolve(parsedArgs.cwd, parsedArgs[key]);
|
|
155
|
+
}
|
|
156
|
+
debug3("parsedArgs:", parsedArgs);
|
|
157
|
+
return parsedArgs;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// src/git.ts
|
|
161
|
+
import process3 from "node:process";
|
|
162
|
+
import { log, spinner } from "@clack/prompts";
|
|
163
|
+
import { execaSync as createExeca } from "execa";
|
|
164
|
+
import color from "picocolors";
|
|
165
|
+
var debug4 = createDebug("git");
|
|
166
|
+
var execa = createExeca({ all: true, reject: false });
|
|
167
|
+
function gitTag(options) {
|
|
168
|
+
const {
|
|
169
|
+
cwd = process3.cwd(),
|
|
170
|
+
del = false,
|
|
171
|
+
dry = false,
|
|
172
|
+
force = false,
|
|
173
|
+
message: message2,
|
|
174
|
+
tagName: name
|
|
175
|
+
} = options ?? {};
|
|
176
|
+
const s = spinner();
|
|
177
|
+
s.start("creating tag...");
|
|
178
|
+
const args = [];
|
|
179
|
+
if (del) {
|
|
180
|
+
args.push("--delete");
|
|
181
|
+
} else {
|
|
182
|
+
if (!message2 || message2?.length === 0) {
|
|
183
|
+
log.warn("no message provided, is recommended to provide a message for create an annotated tag");
|
|
184
|
+
} else {
|
|
185
|
+
args.push(
|
|
186
|
+
// Create an annotated tag, which is recommended for releases.
|
|
187
|
+
// See https://git-scm.com/docs/git-tag
|
|
188
|
+
"--annotate",
|
|
189
|
+
// Use the same commit message for the tag
|
|
190
|
+
"--message",
|
|
191
|
+
// formatMessageString(template, nextVersion),
|
|
192
|
+
message2
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (force) args.push("--force");
|
|
197
|
+
args.push(name);
|
|
198
|
+
debug4(`command: git tag ${args.join(" ")}`);
|
|
199
|
+
if (!dry) {
|
|
200
|
+
const { exitCode, failed, shortMessage, stderr, stdout } = execa("git", ["tag", ...args], { cwd });
|
|
201
|
+
debug4("git tag stdout:", stdout, stderr);
|
|
202
|
+
if (failed) {
|
|
203
|
+
s.stop(color.red(shortMessage), exitCode);
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
s.stop(`create git tag: ${color.blue(name)}`);
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
function gitCommit(options) {
|
|
211
|
+
const {
|
|
212
|
+
cwd = process3.cwd(),
|
|
213
|
+
dry = false,
|
|
214
|
+
message: message2,
|
|
215
|
+
modifiedFiles = [],
|
|
216
|
+
stageAll,
|
|
217
|
+
verify
|
|
218
|
+
} = options ?? {};
|
|
219
|
+
const s = spinner();
|
|
220
|
+
s.start("committing...");
|
|
221
|
+
const args = [];
|
|
222
|
+
args.push("--message", message2);
|
|
223
|
+
!verify && args.push("--no-verify");
|
|
224
|
+
if (!stageAll && modifiedFiles.length > 0) {
|
|
225
|
+
args.push("--", ...modifiedFiles);
|
|
226
|
+
} else {
|
|
227
|
+
args.push("--all");
|
|
228
|
+
}
|
|
229
|
+
debug4(`command: git commit ${args.join(" ")}`);
|
|
230
|
+
if (!dry) {
|
|
231
|
+
const { exitCode, failed, shortMessage, stderr, stdout } = execa("git", ["commit", ...args], { cwd });
|
|
232
|
+
debug4("git commit stdout:", stdout, stderr);
|
|
233
|
+
if (failed) {
|
|
234
|
+
s.stop(color.red(shortMessage), exitCode);
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
s.stop(`commit message: ${color.green(message2)}`);
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
241
|
+
function gitPush(options = {}) {
|
|
242
|
+
const {
|
|
243
|
+
branch,
|
|
244
|
+
cwd = process3.cwd(),
|
|
245
|
+
dry,
|
|
246
|
+
followTags = true,
|
|
247
|
+
remote
|
|
248
|
+
} = options;
|
|
249
|
+
const s = spinner();
|
|
250
|
+
s.start("pushing...");
|
|
251
|
+
const args = [];
|
|
252
|
+
if (remote) {
|
|
253
|
+
args.push(remote);
|
|
254
|
+
if (branch) {
|
|
255
|
+
args.push(branch);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
followTags && args.push("--follow-tags");
|
|
259
|
+
debug4(`command: git push ${args.join(" ")}`);
|
|
260
|
+
if (!dry) {
|
|
261
|
+
const { exitCode, failed, shortMessage, stderr, stdout } = execa("git", ["push", ...args], { cwd });
|
|
262
|
+
debug4("git push stdout:", stdout, stderr);
|
|
263
|
+
if (failed) {
|
|
264
|
+
s.stop(color.red(shortMessage), exitCode);
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
const originUrl = gitOriginUrl();
|
|
269
|
+
s.stop(`pushed to repo: ${color.underline(originUrl)}`);
|
|
270
|
+
return true;
|
|
271
|
+
}
|
|
272
|
+
function gitOriginUrl() {
|
|
273
|
+
const command = execa("git", ["remote", "get-url", "origin"]);
|
|
274
|
+
return command.stdout.trim();
|
|
275
|
+
}
|
|
276
|
+
function gitAdd(options = {}) {
|
|
277
|
+
const {
|
|
278
|
+
all = false,
|
|
279
|
+
cwd = process3.cwd(),
|
|
280
|
+
dry = false,
|
|
281
|
+
files = []
|
|
282
|
+
} = options;
|
|
283
|
+
const args = [];
|
|
284
|
+
if (all) {
|
|
285
|
+
args.push("-A");
|
|
286
|
+
} else if (files.length > 0) {
|
|
287
|
+
args.push("--", ...files);
|
|
288
|
+
}
|
|
289
|
+
debug4("command: git add", args.join(" "));
|
|
290
|
+
if (!dry) {
|
|
291
|
+
const { failed, stderr, stdout } = execa("git", ["add", ...args], { cwd });
|
|
292
|
+
debug4("git add stdout:", stdout, stderr);
|
|
293
|
+
if (failed) {
|
|
294
|
+
return false;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
debug4("add files:", files);
|
|
298
|
+
return true;
|
|
299
|
+
}
|
|
300
|
+
function isGitClean(options = {}) {
|
|
301
|
+
const { cwd = process3.cwd() } = options;
|
|
302
|
+
const args = ["diff-index", "--quiet", "HEAD", "--"];
|
|
303
|
+
const { failed, message: message2 } = execa("git", args, { cwd });
|
|
304
|
+
if (message2?.includes("bad revision")) {
|
|
305
|
+
return true;
|
|
306
|
+
}
|
|
307
|
+
return !failed;
|
|
308
|
+
}
|
|
309
|
+
function getStagedFiles(opts = {}) {
|
|
310
|
+
const { cwd } = opts;
|
|
311
|
+
let stagedArr = [];
|
|
312
|
+
const args = ["--name-only", "--staged", "-z", "--diff-filter=ACMR"];
|
|
313
|
+
debug4("command: git diff", args.join(" "));
|
|
314
|
+
const { all, failed } = execa("git", ["diff", ...args], { cwd });
|
|
315
|
+
if (!failed) {
|
|
316
|
+
stagedArr = all.replace(/\0$/, "").split("\0");
|
|
317
|
+
}
|
|
318
|
+
return stagedArr;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// src/prompt.ts
|
|
322
|
+
import { select, text } from "@clack/prompts";
|
|
323
|
+
import { getNextVersions, isVersionValid, parseVersion } from "cross-bump";
|
|
324
|
+
async function chooseVersion(currentVersion) {
|
|
325
|
+
const versionObj = parseVersion(currentVersion);
|
|
326
|
+
const {
|
|
327
|
+
nextMajor,
|
|
328
|
+
nextMinor,
|
|
329
|
+
nextPatch,
|
|
330
|
+
nextPreMajor,
|
|
331
|
+
nextPreMinor,
|
|
332
|
+
nextPrePatch,
|
|
333
|
+
nextRelease
|
|
334
|
+
} = getNextVersions(versionObj ?? void 0);
|
|
335
|
+
const C_CUSTOM = "custom";
|
|
336
|
+
const versions = [
|
|
337
|
+
{ label: "custom...", value: C_CUSTOM },
|
|
338
|
+
{ label: `next (${nextRelease})`, value: nextRelease },
|
|
339
|
+
{ label: `keep (${currentVersion})`, value: currentVersion ?? "" },
|
|
340
|
+
{ label: `patch (${nextPatch})`, value: nextPatch },
|
|
341
|
+
{ label: `minor (${nextMinor})`, value: nextMinor },
|
|
342
|
+
{ label: `major (${nextMajor})`, value: nextMajor },
|
|
343
|
+
{ label: `pre-patch (${nextPrePatch})`, value: nextPrePatch },
|
|
344
|
+
{ label: `pre-minor (${nextPreMinor})`, value: nextPreMinor },
|
|
345
|
+
{ label: `pre-major (${nextPreMajor})`, value: nextPreMajor }
|
|
346
|
+
];
|
|
347
|
+
const selectedValue = await select({
|
|
348
|
+
initialValue: versions[1].value ?? C_CUSTOM,
|
|
349
|
+
message: `Pick a project version. (current: ${currentVersion})`,
|
|
350
|
+
options: versions
|
|
351
|
+
});
|
|
352
|
+
if (!selectedValue || selectedValue === C_CUSTOM) {
|
|
353
|
+
return await text({
|
|
354
|
+
message: "Input your custom version number",
|
|
355
|
+
placeholder: "version number",
|
|
356
|
+
validate: (value) => {
|
|
357
|
+
if (!isVersionValid(value)) {
|
|
358
|
+
return "Invalid";
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
} else {
|
|
363
|
+
return selectedValue;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// src/util/str.ts
|
|
368
|
+
function formatMessageString(template, nextVersion) {
|
|
369
|
+
return template?.includes("%s") ? template.replaceAll("%s", nextVersion) : template + nextVersion;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// src/app.ts
|
|
373
|
+
var debug5 = createDebug("app");
|
|
374
|
+
function message(msg) {
|
|
375
|
+
const bar = isUnicodeSupported() ? "\u2502" : "|";
|
|
376
|
+
console.log(`${color2.gray(bar)} ${msg}`);
|
|
377
|
+
}
|
|
378
|
+
function handleUserCancel(result) {
|
|
379
|
+
if (isCancel(result)) {
|
|
380
|
+
cancel("User cancel");
|
|
381
|
+
process4.exit(2 /* Canceled */);
|
|
382
|
+
}
|
|
383
|
+
return result;
|
|
384
|
+
}
|
|
385
|
+
var App = class _App {
|
|
386
|
+
_currentVersion = "";
|
|
387
|
+
_modifiedFiles = [];
|
|
388
|
+
_nextVersion = "";
|
|
389
|
+
_options;
|
|
390
|
+
_projectFiles = [];
|
|
391
|
+
_taskQueue = [];
|
|
392
|
+
_taskStatus = "pending";
|
|
393
|
+
constructor(opts) {
|
|
394
|
+
this._options = opts;
|
|
395
|
+
}
|
|
396
|
+
static async create(argv = process4.argv) {
|
|
397
|
+
const cli = createCliProgram().parse(argv);
|
|
398
|
+
const opts = await resolveOptions(cli);
|
|
399
|
+
return new _App(opts);
|
|
400
|
+
}
|
|
401
|
+
#addTask(task, idx) {
|
|
402
|
+
const expect = this._taskQueue.length + 1;
|
|
403
|
+
if (idx) {
|
|
404
|
+
this._taskQueue.splice(idx, 0, task);
|
|
405
|
+
} else {
|
|
406
|
+
this._taskQueue.push(task);
|
|
407
|
+
}
|
|
408
|
+
return this._taskQueue.length === expect;
|
|
409
|
+
}
|
|
410
|
+
#check(status) {
|
|
411
|
+
if (Array.isArray(status)) {
|
|
412
|
+
if (status.some((s) => !s)) {
|
|
413
|
+
this._taskStatus = "failed";
|
|
414
|
+
}
|
|
415
|
+
} else if (!status) {
|
|
416
|
+
this._taskStatus = "failed";
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
#checkDryRun() {
|
|
420
|
+
if (this._options.dry) {
|
|
421
|
+
log2.message(color2.bgBlue(" DRY RUN "));
|
|
422
|
+
process4.env.DRY = "true";
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
#done() {
|
|
426
|
+
if (this._taskStatus === "failed") {
|
|
427
|
+
outro(color2.red("Error"));
|
|
428
|
+
process4.exit(1 /* FatalError */);
|
|
429
|
+
} else {
|
|
430
|
+
outro("Done");
|
|
431
|
+
this._taskStatus = "finished";
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
#start() {
|
|
435
|
+
intro("Cross release");
|
|
436
|
+
this.#checkDryRun();
|
|
437
|
+
this._taskStatus = "running";
|
|
438
|
+
}
|
|
439
|
+
checkGitClean() {
|
|
440
|
+
const { cwd } = this._options;
|
|
441
|
+
if (!isGitClean({ cwd })) {
|
|
442
|
+
log2.warn("git is not clean, please commit or stash your changes before release");
|
|
443
|
+
this.#done();
|
|
444
|
+
process4.exit(3 /* GitDirty */);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
async confirmReleaseOptions() {
|
|
448
|
+
const { all, cwd, dry, yes } = this._options;
|
|
449
|
+
const confirmTask = async (name, message2, exec) => {
|
|
450
|
+
if (yes) {
|
|
451
|
+
if (!this._options[name]) return;
|
|
452
|
+
this._options[name] = true;
|
|
453
|
+
} else if (this._options[name]) {
|
|
454
|
+
const confirmation = await confirm({ message: message2 });
|
|
455
|
+
this._options[name] = handleUserCancel(confirmation);
|
|
456
|
+
}
|
|
457
|
+
if (this._options[name]) {
|
|
458
|
+
this.#addTask({ exec, name });
|
|
459
|
+
}
|
|
460
|
+
};
|
|
461
|
+
let commitMessage;
|
|
462
|
+
if (this._options.commit) {
|
|
463
|
+
const {
|
|
464
|
+
stageAll,
|
|
465
|
+
template,
|
|
466
|
+
verify
|
|
467
|
+
} = resolveAltOptions(this._options, "commit", {
|
|
468
|
+
...CONFIG_DEFAULT.commit,
|
|
469
|
+
stageAll: all
|
|
470
|
+
});
|
|
471
|
+
this.#addTask({
|
|
472
|
+
exec: () => {
|
|
473
|
+
return gitAdd({
|
|
474
|
+
all: stageAll === false ? all : stageAll,
|
|
475
|
+
cwd,
|
|
476
|
+
dry,
|
|
477
|
+
files: this._modifiedFiles
|
|
478
|
+
});
|
|
479
|
+
},
|
|
480
|
+
name: "add"
|
|
481
|
+
});
|
|
482
|
+
commitMessage = formatMessageString(template, this._nextVersion);
|
|
483
|
+
await confirmTask("commit", "should commit?", () => {
|
|
484
|
+
debug5("staged files: %O", getStagedFiles({ cwd }));
|
|
485
|
+
return gitCommit({
|
|
486
|
+
cwd,
|
|
487
|
+
dry,
|
|
488
|
+
message: commitMessage,
|
|
489
|
+
modifiedFiles: this._modifiedFiles,
|
|
490
|
+
// stageAll,
|
|
491
|
+
verify
|
|
492
|
+
});
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
if (this._options.tag && commitMessage !== void 0) {
|
|
496
|
+
const { template: tagTpt } = resolveAltOptions(this._options, "tag", CONFIG_DEFAULT.tag);
|
|
497
|
+
await confirmTask("tag", "should create tag?", () => {
|
|
498
|
+
const tagName = formatMessageString(tagTpt, this._nextVersion);
|
|
499
|
+
return gitTag({
|
|
500
|
+
cwd,
|
|
501
|
+
dry,
|
|
502
|
+
message: commitMessage,
|
|
503
|
+
tagName
|
|
504
|
+
});
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
if (this._options.push) {
|
|
508
|
+
const { followTags } = resolveAltOptions(this._options, "push", CONFIG_DEFAULT.push);
|
|
509
|
+
await confirmTask("push", "should push to remote?", () => {
|
|
510
|
+
return gitPush({ cwd, dry, followTags });
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
async executeTasks() {
|
|
515
|
+
debug5("taskQueue:", this._taskQueue);
|
|
516
|
+
for await (const task of this._taskQueue) {
|
|
517
|
+
if (this._taskStatus === "failed") break;
|
|
518
|
+
this.#check(await task.exec());
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
resolveExecutes() {
|
|
522
|
+
const { cwd, execute } = this._options;
|
|
523
|
+
const indexBeforeCommit = this._taskQueue.findIndex((t) => t.name === "commit") - 1;
|
|
524
|
+
const index = indexBeforeCommit === -1 ? this._taskQueue.length : indexBeforeCommit;
|
|
525
|
+
for (const command of execute) {
|
|
526
|
+
if (!command) continue;
|
|
527
|
+
const [cmd, ...args] = parseCommandString(command);
|
|
528
|
+
if (!cmd) continue;
|
|
529
|
+
const exec = () => {
|
|
530
|
+
debug5("exec command: %s %s", cmd, args.join(" "));
|
|
531
|
+
const { exitCode, failed, stdout } = execaSync(cmd, args, { cwd, reject: false });
|
|
532
|
+
debug5("exec stdout:", stdout, exitCode);
|
|
533
|
+
if (failed) {
|
|
534
|
+
log2.error(`exec: ${command}`);
|
|
535
|
+
return false;
|
|
536
|
+
} else {
|
|
537
|
+
log2.success(`exec: ${command}`);
|
|
538
|
+
return true;
|
|
539
|
+
}
|
|
540
|
+
};
|
|
541
|
+
this.#addTask({ exec, name: "anonymous" }, index);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
async resolveNextVersion() {
|
|
545
|
+
const { main, version: version2 } = this._options;
|
|
546
|
+
const mainProjectFile = this._projectFiles.find((file) => file.category === main);
|
|
547
|
+
if (!mainProjectFile) {
|
|
548
|
+
throw new Error(`can't found ${main} project file in the project root`);
|
|
549
|
+
}
|
|
550
|
+
const projectVersion = await getProjectVersion(mainProjectFile);
|
|
551
|
+
this._currentVersion = projectVersion ?? "";
|
|
552
|
+
if (isVersionValid2(version2)) {
|
|
553
|
+
this._nextVersion = version2;
|
|
554
|
+
log2.info(`current version: ${this._currentVersion}, next version: ${color2.blue(this._nextVersion)}`);
|
|
555
|
+
} else {
|
|
556
|
+
const nextVersion = await chooseVersion(this._currentVersion);
|
|
557
|
+
this._nextVersion = handleUserCancel(nextVersion);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
resolveProjectFiles() {
|
|
561
|
+
const { cwd, exclude, recursive } = this._options;
|
|
562
|
+
const projectFiles = findProjectFiles(cwd, exclude, recursive);
|
|
563
|
+
if (projectFiles.length === 0) {
|
|
564
|
+
console.error("can't found any project file in the project root");
|
|
565
|
+
process4.exit(1);
|
|
566
|
+
}
|
|
567
|
+
debug5(`found ${projectFiles.length} project files`);
|
|
568
|
+
this._projectFiles = projectFiles;
|
|
569
|
+
}
|
|
570
|
+
resolveProjects() {
|
|
571
|
+
const { _nextVersion, _projectFiles } = this;
|
|
572
|
+
this.#addTask({
|
|
573
|
+
exec: async () => {
|
|
574
|
+
return await Promise.all(_projectFiles.map(async (projectFile) => {
|
|
575
|
+
try {
|
|
576
|
+
await upgradeProjectVersion(_nextVersion, projectFile);
|
|
577
|
+
this._modifiedFiles.push(projectFile.path);
|
|
578
|
+
message(`upgrade to ${color2.blue(_nextVersion)} for ${color2.gray(projectFile.path)}`);
|
|
579
|
+
} catch (error) {
|
|
580
|
+
log2.error(String(error));
|
|
581
|
+
return false;
|
|
582
|
+
}
|
|
583
|
+
return true;
|
|
584
|
+
}));
|
|
585
|
+
},
|
|
586
|
+
name: "upgradeVersion"
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
async run() {
|
|
590
|
+
this.#start();
|
|
591
|
+
this.checkGitClean();
|
|
592
|
+
this.resolveProjectFiles();
|
|
593
|
+
await this.resolveNextVersion();
|
|
594
|
+
this.resolveProjects();
|
|
595
|
+
await this.confirmReleaseOptions();
|
|
596
|
+
this.resolveExecutes();
|
|
597
|
+
await this.executeTasks();
|
|
598
|
+
this.#done();
|
|
599
|
+
}
|
|
600
|
+
get currentVersion() {
|
|
601
|
+
return this._currentVersion;
|
|
602
|
+
}
|
|
603
|
+
get nextVersion() {
|
|
604
|
+
return this._nextVersion;
|
|
605
|
+
}
|
|
606
|
+
get options() {
|
|
607
|
+
return this._options;
|
|
608
|
+
}
|
|
609
|
+
get projectFiles() {
|
|
610
|
+
return this._projectFiles;
|
|
611
|
+
}
|
|
612
|
+
};
|
|
613
|
+
var app_default = App;
|
|
614
|
+
export {
|
|
615
|
+
app_default as default
|
|
616
|
+
};
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { ProjectCategory } from 'cross-bump';
|
|
2
|
+
|
|
3
|
+
type Arrayable<T> = T | T[];
|
|
4
|
+
type CliPrimitive = boolean | string | string[];
|
|
5
|
+
type ExcludeType<T, U> = {
|
|
6
|
+
[K in keyof T]: T[K] extends U ? T[K] : Exclude<T[K], U>;
|
|
7
|
+
};
|
|
8
|
+
type KeysOf<T, KeyType = string> = keyof {
|
|
9
|
+
[K in keyof T as T[K] extends KeyType ? K : never]: T[K];
|
|
10
|
+
};
|
|
11
|
+
type ResolvedOptions<T> = T extends boolean ? never : NonNullable<T>;
|
|
12
|
+
type Status = "failed" | "finished" | "pending" | "running";
|
|
13
|
+
type ExtractBooleanKeys<T> = keyof Pick<T, {
|
|
14
|
+
[K in keyof T]: T[K] extends boolean | Record<string, unknown> ? K : never;
|
|
15
|
+
}[keyof T]>;
|
|
16
|
+
type Task = {
|
|
17
|
+
exec: () => boolean | boolean[] | Promise<boolean | boolean[]>;
|
|
18
|
+
name: string;
|
|
19
|
+
};
|
|
20
|
+
type ReleaseOptionsDefault = Omit<ExcludeType<ReleaseOptions, CliPrimitive>, "config" | "version">;
|
|
21
|
+
type DefineConfigOptions = Partial<Omit<ReleaseOptions, "config">>;
|
|
22
|
+
type CliReleaseOptions = ExcludeType<ReleaseOptions, Record<string, unknown>>;
|
|
23
|
+
type ReleaseOptions = {
|
|
24
|
+
/**
|
|
25
|
+
* Wethere add all changed files to staged, shorthand for @type {CommitOptions.stageAll}
|
|
26
|
+
*/
|
|
27
|
+
all: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Indicates whether to commit the changes.
|
|
30
|
+
* @default false
|
|
31
|
+
*/
|
|
32
|
+
commit: boolean | CommitOptions;
|
|
33
|
+
/**
|
|
34
|
+
* Specifies the path to the configuration file.
|
|
35
|
+
*/
|
|
36
|
+
config: string;
|
|
37
|
+
/**
|
|
38
|
+
* The directory path where the operation will be performed.
|
|
39
|
+
* @default process.cwd()
|
|
40
|
+
*/
|
|
41
|
+
cwd: string;
|
|
42
|
+
/**
|
|
43
|
+
* Enable debug log
|
|
44
|
+
*/
|
|
45
|
+
debug: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Whether the operation is being run in a dry-run mode (simulated execution).
|
|
48
|
+
*/
|
|
49
|
+
dry: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* The list of directories to exclude from the search.
|
|
52
|
+
* @default ["node_modules", ".git"]
|
|
53
|
+
*/
|
|
54
|
+
exclude: string[];
|
|
55
|
+
/**
|
|
56
|
+
* The command to execute before pushing.
|
|
57
|
+
*/
|
|
58
|
+
execute: string[];
|
|
59
|
+
/**
|
|
60
|
+
* Specifies the main project category.
|
|
61
|
+
*/
|
|
62
|
+
main: ProjectCategory;
|
|
63
|
+
/**
|
|
64
|
+
* Whether push changes to remote and push options
|
|
65
|
+
* @default false
|
|
66
|
+
*/
|
|
67
|
+
push: boolean | PushOptions;
|
|
68
|
+
/**
|
|
69
|
+
* Specifies whether the operation should be performed recursively.
|
|
70
|
+
* @default false
|
|
71
|
+
*/
|
|
72
|
+
recursive: boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Indicates whether to create a tag for a release.
|
|
75
|
+
* @default false
|
|
76
|
+
*/
|
|
77
|
+
tag: boolean | TagOptions;
|
|
78
|
+
/**
|
|
79
|
+
* The version string associated with the command or operation.
|
|
80
|
+
*/
|
|
81
|
+
version: string;
|
|
82
|
+
/**
|
|
83
|
+
* Whether all prompts requiring user input will be answered with "yes".
|
|
84
|
+
* @default false
|
|
85
|
+
*/
|
|
86
|
+
yes: boolean;
|
|
87
|
+
};
|
|
88
|
+
type CommitOptions = {
|
|
89
|
+
/**
|
|
90
|
+
* Whether to stage all files or only modified files.
|
|
91
|
+
*/
|
|
92
|
+
stageAll?: boolean;
|
|
93
|
+
/**
|
|
94
|
+
* The template string for the commit message. if the template contains any "%s" placeholders,
|
|
95
|
+
* then they are replaced with the version number;
|
|
96
|
+
*/
|
|
97
|
+
template?: string;
|
|
98
|
+
/**
|
|
99
|
+
* Whether to enable git pre-commit and commit-msg hook.
|
|
100
|
+
* @default true
|
|
101
|
+
*/
|
|
102
|
+
verify?: boolean;
|
|
103
|
+
};
|
|
104
|
+
type PushOptions = {
|
|
105
|
+
/**
|
|
106
|
+
* The branch name
|
|
107
|
+
*/
|
|
108
|
+
branch?: string;
|
|
109
|
+
/**
|
|
110
|
+
* Whether to follow tags
|
|
111
|
+
*/
|
|
112
|
+
followTags?: boolean;
|
|
113
|
+
/**
|
|
114
|
+
* The remote name
|
|
115
|
+
*/
|
|
116
|
+
remote?: string;
|
|
117
|
+
};
|
|
118
|
+
type TagOptions = {
|
|
119
|
+
/**
|
|
120
|
+
* The template for tag name, same as @type {CommitOptions.template}
|
|
121
|
+
* if the template contains any "%s" placeholders,
|
|
122
|
+
* then they are replaced with the version number;
|
|
123
|
+
*/
|
|
124
|
+
template?: string;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export type { Arrayable, CliPrimitive, CliReleaseOptions, CommitOptions, DefineConfigOptions, ExcludeType, ExtractBooleanKeys, KeysOf, PushOptions, ReleaseOptions, ReleaseOptionsDefault, ResolvedOptions, Status, TagOptions, Task };
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cross-release-cli",
|
|
3
|
-
"
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.1.0-alpha.2",
|
|
4
5
|
"description": "command line app for cross language bump utility",
|
|
5
6
|
"author": {
|
|
6
7
|
"name": "rainbowatcher",
|
|
@@ -16,26 +17,29 @@
|
|
|
16
17
|
"bugs": {
|
|
17
18
|
"url": "https://github.com/rainbowatcher/cross-release/issues"
|
|
18
19
|
},
|
|
19
|
-
"main": "dist/index.
|
|
20
|
+
"main": "dist/index.js",
|
|
20
21
|
"bin": {
|
|
21
22
|
"cross-release": "./bin/cross-release.js"
|
|
22
23
|
},
|
|
23
24
|
"files": [
|
|
24
|
-
"
|
|
25
|
-
"
|
|
25
|
+
"bin",
|
|
26
|
+
"dist"
|
|
26
27
|
],
|
|
27
28
|
"dependencies": {
|
|
28
29
|
"@clack/prompts": "^0.7.0",
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
30
|
+
"@rainbowatcher/fs-extra": "^0.2.3",
|
|
31
|
+
"@rainbowatcher/path-extra": "^0.2.3",
|
|
32
|
+
"commander": "^12.1.0",
|
|
33
|
+
"debug": "^4.3.6",
|
|
34
|
+
"defu": "^6.1.4",
|
|
35
|
+
"execa": "^9.3.1",
|
|
36
|
+
"is-unicode-supported": "^2.0.0",
|
|
37
|
+
"picocolors": "^1.1.0",
|
|
38
|
+
"unconfig": "^0.5.5",
|
|
39
|
+
"cross-bump": "0.1.0-alpha.2"
|
|
36
40
|
},
|
|
37
41
|
"scripts": {
|
|
38
42
|
"clean": "rimraf dist/*",
|
|
39
|
-
"build": "
|
|
43
|
+
"build": "tsup"
|
|
40
44
|
}
|
|
41
45
|
}
|
package/bin/cross-release.js
DELETED
package/dist/index.mjs
DELETED
|
@@ -1,404 +0,0 @@
|
|
|
1
|
-
import path$1 from 'path';
|
|
2
|
-
import { spinner, log, isCancel, intro, outro, cancel, select, text, confirm } from '@clack/prompts';
|
|
3
|
-
import { findProjectFiles, getProjectVersion, isVersionValid, upgradeProjectVersion, parseVersion, getNextVersions } from 'cross-bump';
|
|
4
|
-
import { red, blue, green, gray, bgBlue } from 'colorette';
|
|
5
|
-
import isUnicodeSupported from 'is-unicode-supported';
|
|
6
|
-
import * as path from 'node:path';
|
|
7
|
-
import * as fs from 'node:fs';
|
|
8
|
-
import cac from 'cac';
|
|
9
|
-
import { defu } from 'defu';
|
|
10
|
-
import { execa } from 'execa';
|
|
11
|
-
import debug$1 from 'debug';
|
|
12
|
-
|
|
13
|
-
var version = "0.0.1-alpha.5";
|
|
14
|
-
|
|
15
|
-
const configDefaults = {
|
|
16
|
-
showHelp: false,
|
|
17
|
-
showVersion: false,
|
|
18
|
-
version: "",
|
|
19
|
-
isAllYes: false,
|
|
20
|
-
isDry: false,
|
|
21
|
-
isRecursive: false,
|
|
22
|
-
shouldCommit: false,
|
|
23
|
-
shouldPush: false,
|
|
24
|
-
shouldTag: false,
|
|
25
|
-
excludes: ["node_modules", ".git"],
|
|
26
|
-
dir: process.cwd(),
|
|
27
|
-
main: "javascript",
|
|
28
|
-
commit: {
|
|
29
|
-
shouldVerify: true,
|
|
30
|
-
shouldStageAll: true,
|
|
31
|
-
template: "chore: release v%s"
|
|
32
|
-
},
|
|
33
|
-
push: {
|
|
34
|
-
shouldFollowTags: false
|
|
35
|
-
}
|
|
36
|
-
};
|
|
37
|
-
function loadUserConfig(overrides) {
|
|
38
|
-
const file = fs.readFileSync(path.resolve(process.cwd(), "package.json"), "utf-8");
|
|
39
|
-
const userConfig = JSON.parse(file)["cross-release"];
|
|
40
|
-
return defu(overrides, userConfig, configDefaults);
|
|
41
|
-
}
|
|
42
|
-
function parseOptions() {
|
|
43
|
-
const cli = cac("cross-release").version(version).usage("[flags] version").option("-r, --recursive", "Run the command for each project in the workspace (default: false)").option("-c, --commit", "Commit current changes (default: false)").option("-d, --dry", "Dry run (default: false)").option("-e, --exclude [dir]", "Folders to exclude from search (default: [node_modules, .git])").option("-m, --main", "Base project language (e.g. java, rust, javascript default: javascript)").option("-D, --dir [dir]", "Set working directory (default: project root)").option("-p, --push", "Push the project to remote (default: false)").option("-t, --tag", "Create a tag for current version (default: false)").option("-y, --yes", "Answer yes to all prompts (default: false)").help().parse();
|
|
44
|
-
const { args, options } = cli;
|
|
45
|
-
const parsedArgs = loadUserConfig({
|
|
46
|
-
dir: options.dir,
|
|
47
|
-
excludes: options.exclude,
|
|
48
|
-
isRecursive: options.recursive,
|
|
49
|
-
isDry: options.dry,
|
|
50
|
-
isAllYes: options.yes,
|
|
51
|
-
showHelp: options.help,
|
|
52
|
-
showVersion: options.version,
|
|
53
|
-
shouldCommit: options.commit,
|
|
54
|
-
shouldPush: options.push,
|
|
55
|
-
shouldTag: options.tag,
|
|
56
|
-
version: args[0]
|
|
57
|
-
});
|
|
58
|
-
return parsedArgs;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function createDebug(ns) {
|
|
62
|
-
return debug$1(`cross-release-cli:${ns}`);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const debug = createDebug("git");
|
|
66
|
-
async function gitTag(tagName, options) {
|
|
67
|
-
const s = spinner();
|
|
68
|
-
s.start("creating tag");
|
|
69
|
-
const { isForce = false, isDel = false, message } = options || {};
|
|
70
|
-
const args = [];
|
|
71
|
-
if (isDel) {
|
|
72
|
-
args.push("--delete");
|
|
73
|
-
} else {
|
|
74
|
-
if (!message || message?.length === 0) {
|
|
75
|
-
log.warn("no message provided, is recommended to provide a message for create an annotated tag");
|
|
76
|
-
} else {
|
|
77
|
-
args.push(
|
|
78
|
-
// Create an annotated tag, which is recommended for releases.
|
|
79
|
-
// See https://git-scm.com/docs/git-tag
|
|
80
|
-
"--annotate",
|
|
81
|
-
// Use the same commit message for the tag
|
|
82
|
-
"--message",
|
|
83
|
-
// formatMessageString(template, nextVersion),
|
|
84
|
-
message
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
if (isForce)
|
|
89
|
-
args.push("--force");
|
|
90
|
-
args.push(tagName);
|
|
91
|
-
if (!process.env.DRY) {
|
|
92
|
-
try {
|
|
93
|
-
const { command } = await execa("git", ["tag", ...args]);
|
|
94
|
-
debug(`command: ${command}`);
|
|
95
|
-
} catch (e) {
|
|
96
|
-
s.stop(red(e.shortMessage));
|
|
97
|
-
return false;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
s.stop(`create git tag: ${blue(tagName)}`);
|
|
101
|
-
return true;
|
|
102
|
-
}
|
|
103
|
-
async function gitCommit(message, options) {
|
|
104
|
-
const s = spinner();
|
|
105
|
-
s.start("committing");
|
|
106
|
-
const { modifiedFiles = [], shouldStageAll, shouldVerify } = options || {};
|
|
107
|
-
const args = [];
|
|
108
|
-
if (process.env.DRY) {
|
|
109
|
-
args.push("--dry-run");
|
|
110
|
-
}
|
|
111
|
-
args.push("--message", message);
|
|
112
|
-
if (!shouldStageAll && modifiedFiles.length) {
|
|
113
|
-
args.push("--", ...modifiedFiles);
|
|
114
|
-
} else {
|
|
115
|
-
args.push("--all");
|
|
116
|
-
}
|
|
117
|
-
if (!shouldVerify) {
|
|
118
|
-
args.push("--no-verify");
|
|
119
|
-
}
|
|
120
|
-
try {
|
|
121
|
-
const { command } = await execa("git", ["commit", ...args]);
|
|
122
|
-
debug(`command: ${command}`);
|
|
123
|
-
s.stop(`commit message: ${green(message)}`);
|
|
124
|
-
} catch (e) {
|
|
125
|
-
s.stop(red(e.shortMessage));
|
|
126
|
-
return false;
|
|
127
|
-
}
|
|
128
|
-
return true;
|
|
129
|
-
}
|
|
130
|
-
async function gitPush(options) {
|
|
131
|
-
const { shouldFollowTags = true, remote, branch } = options || {};
|
|
132
|
-
const s = spinner();
|
|
133
|
-
s.start("pushing");
|
|
134
|
-
const originUrl = await gitOriginUrl();
|
|
135
|
-
const args = [];
|
|
136
|
-
if (shouldFollowTags) {
|
|
137
|
-
args.push("--follow-tags");
|
|
138
|
-
}
|
|
139
|
-
if (remote) {
|
|
140
|
-
args.push(remote);
|
|
141
|
-
if (branch) {
|
|
142
|
-
args.push(branch);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
if (process.env.DRY) {
|
|
146
|
-
args.push("--dry-run");
|
|
147
|
-
}
|
|
148
|
-
try {
|
|
149
|
-
const { command } = await execa("git", ["push", ...args]);
|
|
150
|
-
debug(`command: ${command}`);
|
|
151
|
-
s.stop(`pushed to repo: ${gray(originUrl)}`);
|
|
152
|
-
} catch (e) {
|
|
153
|
-
s.stop(red(e.shortMessage));
|
|
154
|
-
return false;
|
|
155
|
-
}
|
|
156
|
-
return true;
|
|
157
|
-
}
|
|
158
|
-
async function gitOriginUrl() {
|
|
159
|
-
return (await execa("git", ["remote", "get-url", "origin"])).stdout.trim();
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
var ExitCode = /* @__PURE__ */ ((ExitCode2) => {
|
|
163
|
-
ExitCode2[ExitCode2["Success"] = 0] = "Success";
|
|
164
|
-
ExitCode2[ExitCode2["FatalError"] = 1] = "FatalError";
|
|
165
|
-
ExitCode2[ExitCode2["InvalidArgument"] = 9] = "InvalidArgument";
|
|
166
|
-
return ExitCode2;
|
|
167
|
-
})(ExitCode || {});
|
|
168
|
-
|
|
169
|
-
var __accessCheck = (obj, member, msg) => {
|
|
170
|
-
if (!member.has(obj))
|
|
171
|
-
throw TypeError("Cannot " + msg);
|
|
172
|
-
};
|
|
173
|
-
var __privateAdd = (obj, member, value) => {
|
|
174
|
-
if (member.has(obj))
|
|
175
|
-
throw TypeError("Cannot add the same private member more than once");
|
|
176
|
-
member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
177
|
-
};
|
|
178
|
-
var __privateMethod = (obj, member, method) => {
|
|
179
|
-
__accessCheck(obj, member, "access private method");
|
|
180
|
-
return method;
|
|
181
|
-
};
|
|
182
|
-
var _checkDryRun, checkDryRun_fn, _getNextVersion, getNextVersion_fn, _getProjects, getProjects_fn, _confirmReleaseOptions, confirmReleaseOptions_fn, _addTask, addTask_fn, _start, start_fn, _done, done_fn, _check, check_fn;
|
|
183
|
-
function message(msg) {
|
|
184
|
-
const bar = isUnicodeSupported() ? "\u2502" : "|";
|
|
185
|
-
console.log(`${gray(bar)} ${msg}`);
|
|
186
|
-
}
|
|
187
|
-
function handleUserCancel() {
|
|
188
|
-
cancel("User cancel");
|
|
189
|
-
process.exit(ExitCode.InvalidArgument);
|
|
190
|
-
}
|
|
191
|
-
async function chooseVersion(currentVersion) {
|
|
192
|
-
const versionObj = parseVersion(currentVersion);
|
|
193
|
-
const {
|
|
194
|
-
nextMajor,
|
|
195
|
-
nextMinor,
|
|
196
|
-
nextPatch,
|
|
197
|
-
nextRelease,
|
|
198
|
-
nextPreMajor,
|
|
199
|
-
nextPreMinor,
|
|
200
|
-
nextPrePatch
|
|
201
|
-
} = getNextVersions(versionObj ?? void 0);
|
|
202
|
-
const C_CUSTOM = "custom";
|
|
203
|
-
const versions = [
|
|
204
|
-
{ label: "custom...", value: C_CUSTOM },
|
|
205
|
-
{ label: `next (${nextRelease})`, value: nextRelease },
|
|
206
|
-
{ label: `keep (${currentVersion})`, value: currentVersion || "" },
|
|
207
|
-
{ label: `patch (${nextPatch})`, value: nextPatch },
|
|
208
|
-
{ label: `minor (${nextMinor})`, value: nextMinor },
|
|
209
|
-
{ label: `major (${nextMajor})`, value: nextMajor },
|
|
210
|
-
{ label: `pre-patch (${nextPrePatch})`, value: nextPrePatch },
|
|
211
|
-
{ label: `pre-minor (${nextPreMinor})`, value: nextPreMinor },
|
|
212
|
-
{ label: `pre-major (${nextPreMajor})`, value: nextPreMajor }
|
|
213
|
-
];
|
|
214
|
-
const selectedValue = await select({
|
|
215
|
-
message: `Pick a project version. (current: ${currentVersion})`,
|
|
216
|
-
options: versions,
|
|
217
|
-
initialValue: versions[1].value ?? C_CUSTOM
|
|
218
|
-
});
|
|
219
|
-
if (!selectedValue || selectedValue === C_CUSTOM) {
|
|
220
|
-
return await text({
|
|
221
|
-
message: "Input your custom version number",
|
|
222
|
-
placeholder: "version number",
|
|
223
|
-
validate: (value) => {
|
|
224
|
-
if (!isVersionValid(value)) {
|
|
225
|
-
return "Invalid";
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
});
|
|
229
|
-
} else {
|
|
230
|
-
return selectedValue;
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
class App {
|
|
234
|
-
constructor() {
|
|
235
|
-
__privateAdd(this, _checkDryRun);
|
|
236
|
-
__privateAdd(this, _getNextVersion);
|
|
237
|
-
__privateAdd(this, _getProjects);
|
|
238
|
-
__privateAdd(this, _confirmReleaseOptions);
|
|
239
|
-
__privateAdd(this, _addTask);
|
|
240
|
-
__privateAdd(this, _start);
|
|
241
|
-
__privateAdd(this, _done);
|
|
242
|
-
__privateAdd(this, _check);
|
|
243
|
-
this.currentVersion = "";
|
|
244
|
-
this.nextVersion = "";
|
|
245
|
-
this.modifiedFiles = [];
|
|
246
|
-
this.taskQueue = [];
|
|
247
|
-
this.taskStatus = "pending";
|
|
248
|
-
this.options = parseOptions();
|
|
249
|
-
}
|
|
250
|
-
async run() {
|
|
251
|
-
__privateMethod(this, _start, start_fn).call(this);
|
|
252
|
-
await __privateMethod(this, _getNextVersion, getNextVersion_fn).call(this);
|
|
253
|
-
await __privateMethod(this, _getProjects, getProjects_fn).call(this);
|
|
254
|
-
await __privateMethod(this, _confirmReleaseOptions, confirmReleaseOptions_fn).call(this);
|
|
255
|
-
for await (const task of this.taskQueue) {
|
|
256
|
-
if (this.taskStatus === "failed") {
|
|
257
|
-
break;
|
|
258
|
-
} else {
|
|
259
|
-
await task.exec();
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
__privateMethod(this, _done, done_fn).call(this);
|
|
263
|
-
}
|
|
264
|
-
/**
|
|
265
|
-
* Accepts a message string template (e.g. "release %s" or "This is the %s release").
|
|
266
|
-
* If the template contains any "%s" placeholders, then they are replaced with the version number;
|
|
267
|
-
* otherwise, the version number is appended to the string.
|
|
268
|
-
*/
|
|
269
|
-
formatMessageString(template, nextVersion) {
|
|
270
|
-
if (template.includes("%s")) {
|
|
271
|
-
return template.replace(/%s/g, nextVersion);
|
|
272
|
-
} else {
|
|
273
|
-
return template + nextVersion;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
_checkDryRun = new WeakSet();
|
|
278
|
-
checkDryRun_fn = function() {
|
|
279
|
-
if (this.options.isDry) {
|
|
280
|
-
log.message(bgBlue(" DRY RUN "));
|
|
281
|
-
process.env.DRY = "true";
|
|
282
|
-
}
|
|
283
|
-
};
|
|
284
|
-
_getNextVersion = new WeakSet();
|
|
285
|
-
getNextVersion_fn = async function() {
|
|
286
|
-
const { dir, excludes, version } = this.options;
|
|
287
|
-
const projectFiles = await findProjectFiles(dir, excludes);
|
|
288
|
-
if (!projectFiles.length) {
|
|
289
|
-
throw new Error("can't found any project file in the project root");
|
|
290
|
-
}
|
|
291
|
-
const mainProjectFile = projectFiles.find((file) => file.category === this.options.main);
|
|
292
|
-
if (!mainProjectFile) {
|
|
293
|
-
throw new Error(`can't found ${this.options.main} project file in the project root`);
|
|
294
|
-
}
|
|
295
|
-
const projectVersion = await getProjectVersion(mainProjectFile);
|
|
296
|
-
this.currentVersion = projectVersion || "";
|
|
297
|
-
if (isVersionValid(version)) {
|
|
298
|
-
this.nextVersion = version;
|
|
299
|
-
} else {
|
|
300
|
-
const nextVersion = await chooseVersion(this.currentVersion);
|
|
301
|
-
if (isCancel(nextVersion)) {
|
|
302
|
-
handleUserCancel();
|
|
303
|
-
} else {
|
|
304
|
-
this.nextVersion = nextVersion;
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
};
|
|
308
|
-
_getProjects = new WeakSet();
|
|
309
|
-
getProjects_fn = async function() {
|
|
310
|
-
const { options: { dir, excludes, isRecursive }, nextVersion } = this;
|
|
311
|
-
const projectFiles = await findProjectFiles(dir, excludes, isRecursive);
|
|
312
|
-
__privateMethod(this, _addTask, addTask_fn).call(this, {
|
|
313
|
-
name: "upgradeVersion",
|
|
314
|
-
exec: () => {
|
|
315
|
-
return Promise.all(projectFiles.map(async (projectFile) => {
|
|
316
|
-
try {
|
|
317
|
-
await upgradeProjectVersion(nextVersion, projectFile);
|
|
318
|
-
this.modifiedFiles.push(projectFile.path);
|
|
319
|
-
message(`upgrade to ${blue(nextVersion)} for ${gray(path$1.relative(dir, projectFile.path))}`);
|
|
320
|
-
} catch (e) {
|
|
321
|
-
this.taskStatus = "failed";
|
|
322
|
-
log.error(String(e));
|
|
323
|
-
}
|
|
324
|
-
}));
|
|
325
|
-
}
|
|
326
|
-
});
|
|
327
|
-
};
|
|
328
|
-
_confirmReleaseOptions = new WeakSet();
|
|
329
|
-
confirmReleaseOptions_fn = async function() {
|
|
330
|
-
const { isAllYes, commit } = this.options;
|
|
331
|
-
const confirmAndSet = async (optionName, message3, taskName, execFn) => {
|
|
332
|
-
if (isAllYes) {
|
|
333
|
-
this.options[optionName] = true;
|
|
334
|
-
} else if (!this.options[optionName]) {
|
|
335
|
-
const confirmation = await confirm({ message: message3 });
|
|
336
|
-
if (!isCancel(confirmation)) {
|
|
337
|
-
this.options[optionName] = confirmation;
|
|
338
|
-
} else {
|
|
339
|
-
handleUserCancel();
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
if (this.options[optionName]) {
|
|
343
|
-
__privateMethod(this, _addTask, addTask_fn).call(this, {
|
|
344
|
-
name: taskName,
|
|
345
|
-
exec: execFn
|
|
346
|
-
});
|
|
347
|
-
}
|
|
348
|
-
};
|
|
349
|
-
const message2 = this.formatMessageString(commit.template, this.nextVersion);
|
|
350
|
-
await confirmAndSet(
|
|
351
|
-
"shouldCommit",
|
|
352
|
-
"should commit?",
|
|
353
|
-
"commit",
|
|
354
|
-
async () => {
|
|
355
|
-
__privateMethod(this, _check, check_fn).call(this, await gitCommit(message2, {
|
|
356
|
-
modifiedFiles: this.modifiedFiles,
|
|
357
|
-
shouldStageAll: commit.shouldStageAll,
|
|
358
|
-
shouldVerify: commit.shouldVerify
|
|
359
|
-
}));
|
|
360
|
-
}
|
|
361
|
-
);
|
|
362
|
-
const tagName = `v${this.nextVersion}`;
|
|
363
|
-
await confirmAndSet(
|
|
364
|
-
"shouldTag",
|
|
365
|
-
"should create tag?",
|
|
366
|
-
"tag",
|
|
367
|
-
async () => {
|
|
368
|
-
__privateMethod(this, _check, check_fn).call(this, await gitTag(tagName, { message: message2 }));
|
|
369
|
-
}
|
|
370
|
-
);
|
|
371
|
-
await confirmAndSet(
|
|
372
|
-
"shouldPush",
|
|
373
|
-
"should push to remote?",
|
|
374
|
-
"push",
|
|
375
|
-
async () => {
|
|
376
|
-
__privateMethod(this, _check, check_fn).call(this, await gitPush({ shouldFollowTags: this.options.push.shouldFollowTags }));
|
|
377
|
-
}
|
|
378
|
-
);
|
|
379
|
-
};
|
|
380
|
-
_addTask = new WeakSet();
|
|
381
|
-
addTask_fn = function(task) {
|
|
382
|
-
const expect = this.taskQueue.length + 1;
|
|
383
|
-
return this.taskQueue.push(task) === expect;
|
|
384
|
-
};
|
|
385
|
-
_start = new WeakSet();
|
|
386
|
-
start_fn = function() {
|
|
387
|
-
intro("Cross release");
|
|
388
|
-
__privateMethod(this, _checkDryRun, checkDryRun_fn).call(this);
|
|
389
|
-
this.taskStatus = "running";
|
|
390
|
-
};
|
|
391
|
-
_done = new WeakSet();
|
|
392
|
-
done_fn = function() {
|
|
393
|
-
outro("Done");
|
|
394
|
-
this.taskStatus = "finished";
|
|
395
|
-
};
|
|
396
|
-
_check = new WeakSet();
|
|
397
|
-
check_fn = function(status) {
|
|
398
|
-
if (!status) {
|
|
399
|
-
this.taskStatus = "failed";
|
|
400
|
-
}
|
|
401
|
-
};
|
|
402
|
-
var app = new App();
|
|
403
|
-
|
|
404
|
-
void app.run();
|