cross-release-cli 0.0.1-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 rainbowatcher
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,87 @@
1
+ # Cross Release CLI
2
+
3
+ ## Usage
4
+
5
+ 1. install through package manager
6
+
7
+ ```shell
8
+ # pnpm
9
+ pnpm i cross-release -g
10
+ ```
11
+
12
+ otherwise you can use you favorite package manager instead
13
+
14
+ ```shell
15
+ # npm
16
+ npm i cross-release -g
17
+ # yarn
18
+ yarn add cross-release --global
19
+ ```
20
+
21
+ 2. add to your package.json
22
+
23
+ ```json
24
+ {
25
+ "scripts": {
26
+ "release": "cross-release"
27
+ }
28
+ }
29
+ ```
30
+
31
+ 3. run release script
32
+
33
+ ```shell
34
+ pnpm run release
35
+ ```
36
+
37
+ ## Command line
38
+
39
+ | short | long | description | default |
40
+ | ----- | --------------- | ------------------------------------------------- | -------------------------- |
41
+ | -r | --recursive | Run the command for each project in the workspace | `false` |
42
+ | -d | --dry | Dry run | `false` |
43
+ | -D | --dir [dir] | Set working directory | `project root` |
44
+ | -p | --publish | Publish the project | `false` |
45
+ | -c | --commit | Commit current changes | `false` |
46
+ | -p | --push | Push the project to remote | `false` |
47
+ | -t | --tag | Create a tag for current version | `false` |
48
+ | -e | --exclude [dir] | Folders to exclude from search | `["node_modules", ".git"]` |
49
+ | -y | --yes | Answer yes to all prompts | `false` |
50
+
51
+ ## Configuration
52
+
53
+ You can specify various runtime settings by using the "package.json" file. Here are some examples that cover all the parameters:
54
+
55
+ ```json
56
+ {
57
+ "...": "...",
58
+ "cross-release": {
59
+ // show the help message
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
+ "shouldPublish": false,
69
+ "shouldPush": false,
70
+ "shouldTag": false,
71
+ // default exclude folders are `["node_modules", ".git"]`, your config will be append within it
72
+ "excludes": ["path/to/exclude"],
73
+ "dir": "/path/to/run",
74
+ "commit": {
75
+ // Whether to invoke git pre-commit and commit-msg hook
76
+ "shouldVerify": true,
77
+ // Whether to stage all un-staged files or stage only changed files
78
+ "shouldStageAll": false,
79
+ // the symbol '%s' will be replace to the version number that you specified
80
+ "template": "chore: release v%s"
81
+ },
82
+ "push": {
83
+ "shouldFollowTags": false
84
+ }
85
+ }
86
+ }
87
+ ```
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ "use strict"
3
+ require("../dist/index.cjs")
package/dist/index.cjs ADDED
@@ -0,0 +1,441 @@
1
+ 'use strict';
2
+
3
+ var path$1 = require('path');
4
+ var prompts = require('@clack/prompts');
5
+ var crossBump = require('cross-bump');
6
+ var colorette = require('colorette');
7
+ var isUnicodeSupported = require('is-unicode-supported');
8
+ var path = require('node:path');
9
+ var fs = require('node:fs');
10
+ var cac = require('cac');
11
+ var defu = require('defu');
12
+ var execa = require('execa');
13
+
14
+ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
15
+
16
+ function _interopNamespace(e) {
17
+ if (e && e.__esModule) return e;
18
+ var n = Object.create(null);
19
+ if (e) {
20
+ Object.keys(e).forEach(function (k) {
21
+ if (k !== 'default') {
22
+ var d = Object.getOwnPropertyDescriptor(e, k);
23
+ Object.defineProperty(n, k, d.get ? d : {
24
+ enumerable: true,
25
+ get: function () { return e[k]; }
26
+ });
27
+ }
28
+ });
29
+ }
30
+ n["default"] = e;
31
+ return Object.freeze(n);
32
+ }
33
+
34
+ var path__default = /*#__PURE__*/_interopDefaultLegacy(path$1);
35
+ var isUnicodeSupported__default = /*#__PURE__*/_interopDefaultLegacy(isUnicodeSupported);
36
+ var path__namespace = /*#__PURE__*/_interopNamespace(path);
37
+ var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
38
+ var cac__default = /*#__PURE__*/_interopDefaultLegacy(cac);
39
+
40
+ var version = "0.0.0";
41
+
42
+ const configDefaults = {
43
+ showHelp: false,
44
+ showVersion: false,
45
+ version: "",
46
+ isAllYes: false,
47
+ isDry: false,
48
+ isRecursive: false,
49
+ shouldCommit: false,
50
+ shouldPublish: false,
51
+ shouldPush: false,
52
+ shouldTag: false,
53
+ excludes: ["node_modules", ".git"],
54
+ dir: process.cwd(),
55
+ commit: {
56
+ shouldVerify: true,
57
+ shouldStageAll: false,
58
+ template: "chore: release v%s"
59
+ },
60
+ push: {
61
+ isTagOnly: false
62
+ }
63
+ };
64
+ function loadUserConfig(overrides) {
65
+ const file = fs__namespace.readFileSync(path__namespace.resolve(process.cwd(), "package.json"), "utf-8");
66
+ const userConfig = JSON.parse(file)["cross-release"];
67
+ return defu.defu(overrides, userConfig, configDefaults);
68
+ }
69
+ function parseOptions() {
70
+ const cli = cac__default["default"]("cross-release").version(version).usage("[flags] version").option("-r, --recursive", "Run the command for each project in the workspace (default: false)").option("-d, --dry", "Dry run (default: false)").option("-D, --dir <dir>", "Set working directory (default: project root)").option("-p, --publish", "Publish the project (default: false)").option("-c, --commit", "Commit current changes (default: false)").option("-p, --push", "Push the project to remote (default: false)").option("-t, --tag", "Create a tag for current version (default: false)").option("-e, --exclude [dir]", "Folders to exclude from search (default: [node_modules, .git])").option("-y, --yes", "Answer yes to all prompts (default: false)").help().parse();
71
+ const { args, options } = cli;
72
+ const parsedArgs = loadUserConfig({
73
+ dir: options.dir,
74
+ excludes: options.exclude,
75
+ isRecursive: options.recursive,
76
+ isDry: options.dry,
77
+ isAllYes: options.yes,
78
+ showHelp: options.help,
79
+ showVersion: options.version,
80
+ shouldCommit: options.commit,
81
+ shouldPublish: options.publish,
82
+ shouldPush: options.push,
83
+ shouldTag: options.tag,
84
+ version: args[0]
85
+ });
86
+ return parsedArgs;
87
+ }
88
+
89
+ async function gitTag(tagName, options) {
90
+ const s = prompts.spinner();
91
+ s.start("creating tag");
92
+ const { isForce = false, isDel = false, message } = options || {};
93
+ const args = [];
94
+ if (isDel) {
95
+ args.push("--delete");
96
+ } else {
97
+ if (!message || (message == null ? void 0 : message.length) === 0) {
98
+ prompts.log.warn("no message provided, is recommended to provide a message for create an annotated tag");
99
+ } else {
100
+ args.push(
101
+ // Create an annotated tag, which is recommended for releases.
102
+ // See https://git-scm.com/docs/git-tag
103
+ "--annotate",
104
+ // Use the same commit message for the tag
105
+ "--message",
106
+ // formatMessageString(template, nextVersion),
107
+ message
108
+ );
109
+ }
110
+ }
111
+ if (isForce)
112
+ args.push("--force");
113
+ args.push(tagName);
114
+ if (!process.env.DRY) {
115
+ try {
116
+ const { command } = await execa.execa("git", ["tag", ...args]);
117
+ console.log(command);
118
+ } catch (e) {
119
+ s.stop(colorette.red(e.shortMessage));
120
+ return false;
121
+ }
122
+ }
123
+ s.stop(`create git tag: ${colorette.blue(tagName)}`);
124
+ return true;
125
+ }
126
+ async function gitCommit(message, options) {
127
+ const s = prompts.spinner();
128
+ s.start("committing");
129
+ const { modifiedFiles = [], shouldStageAll = true, shouldVerify = true } = options || {};
130
+ const args = [];
131
+ if (process.env.DRY) {
132
+ args.push("--dry-run");
133
+ }
134
+ args.push("--message", `"${message}"`);
135
+ if (modifiedFiles.length) {
136
+ await gitAdd(modifiedFiles);
137
+ args.push(...modifiedFiles);
138
+ } else if (shouldStageAll) {
139
+ args.push("--all");
140
+ }
141
+ if (!shouldVerify) {
142
+ args.push("--no-verify");
143
+ }
144
+ try {
145
+ await execa.execa("git", ["commit", ...args]);
146
+ s.stop(`commit message: ${colorette.green(message)}`);
147
+ } catch (e) {
148
+ s.stop(colorette.red(e.shortMessage));
149
+ return false;
150
+ }
151
+ return true;
152
+ }
153
+ async function gitPush(options) {
154
+ const { shouldFollowTags = true, remote, branch } = options || {};
155
+ const s = prompts.spinner();
156
+ s.start("pushing");
157
+ const originUrl = await gitOriginUrl();
158
+ const args = [];
159
+ if (shouldFollowTags) {
160
+ args.push("--follow-tags");
161
+ }
162
+ if (remote) {
163
+ args.push(remote);
164
+ if (branch) {
165
+ args.push(branch);
166
+ }
167
+ }
168
+ if (process.env.DRY) {
169
+ args.push("--dry-run");
170
+ }
171
+ try {
172
+ await execa.execa("git", ["push", ...args]);
173
+ s.stop(`pushed to repo: ${colorette.gray(originUrl)}`);
174
+ } catch (e) {
175
+ s.stop(colorette.red(e.shortMessage));
176
+ return false;
177
+ }
178
+ return true;
179
+ }
180
+ async function gitOriginUrl() {
181
+ return (await execa.execa("git", ["remote", "get-url", "origin"])).stdout.trim();
182
+ }
183
+ async function gitAdd(files) {
184
+ const args = ["-A"];
185
+ if (process.env.DRY) {
186
+ args.push("--dry-run");
187
+ }
188
+ args.push("--", ...files);
189
+ try {
190
+ await execa.execa("git", ["add", ...args]);
191
+ } catch (e) {
192
+ return false;
193
+ }
194
+ return true;
195
+ }
196
+
197
+ var ExitCode = /* @__PURE__ */ ((ExitCode2) => {
198
+ ExitCode2[ExitCode2["Success"] = 0] = "Success";
199
+ ExitCode2[ExitCode2["FatalError"] = 1] = "FatalError";
200
+ ExitCode2[ExitCode2["InvalidArgument"] = 9] = "InvalidArgument";
201
+ return ExitCode2;
202
+ })(ExitCode || {});
203
+
204
+ var __accessCheck = (obj, member, msg) => {
205
+ if (!member.has(obj))
206
+ throw TypeError("Cannot " + msg);
207
+ };
208
+ var __privateAdd = (obj, member, value) => {
209
+ if (member.has(obj))
210
+ throw TypeError("Cannot add the same private member more than once");
211
+ member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
212
+ };
213
+ var __privateMethod = (obj, member, method) => {
214
+ __accessCheck(obj, member, "access private method");
215
+ return method;
216
+ };
217
+ 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;
218
+ function message(msg) {
219
+ const bar = isUnicodeSupported__default["default"]() ? "\u2502" : "|";
220
+ console.log(`${colorette.gray(bar)} ${msg}`);
221
+ }
222
+ function handleUserCancel() {
223
+ prompts.cancel("User cancel");
224
+ process.exit(ExitCode.InvalidArgument);
225
+ }
226
+ async function chooseVersion(projectVersion) {
227
+ var _a;
228
+ const versionObj = crossBump.parseVersion(projectVersion);
229
+ const {
230
+ nextMajor,
231
+ nextMinor,
232
+ nextPatch,
233
+ nextRelease,
234
+ nextPreMajor,
235
+ nextPreMinor,
236
+ nextPrePatch
237
+ } = crossBump.getNextVersions(versionObj != null ? versionObj : void 0);
238
+ const C_CUSTOM = "custom";
239
+ const versions = [
240
+ { label: "custom...", value: C_CUSTOM },
241
+ { label: `next (${nextRelease})`, value: nextRelease },
242
+ { label: `patch (${nextPatch})`, value: nextPatch },
243
+ { label: `minor (${nextMinor})`, value: nextMinor },
244
+ { label: `major (${nextMajor})`, value: nextMajor },
245
+ { label: `pre-patch (${nextPrePatch})`, value: nextPrePatch },
246
+ { label: `pre-minor (${nextPreMinor})`, value: nextPreMinor },
247
+ { label: `pre-major (${nextPreMajor})`, value: nextPreMajor }
248
+ ];
249
+ const selectedValue = await prompts.select({
250
+ message: `Pick a project version. (current: ${projectVersion})`,
251
+ options: versions,
252
+ initialValue: (_a = versions[1].value) != null ? _a : C_CUSTOM
253
+ });
254
+ if (!selectedValue || selectedValue === C_CUSTOM) {
255
+ return await prompts.text({
256
+ message: "Input your custom version number",
257
+ placeholder: "version number",
258
+ validate: (value) => {
259
+ if (!crossBump.isVersionValid(value)) {
260
+ return "Invalid";
261
+ }
262
+ }
263
+ });
264
+ } else {
265
+ return selectedValue;
266
+ }
267
+ }
268
+ class App {
269
+ constructor() {
270
+ __privateAdd(this, _checkDryRun);
271
+ __privateAdd(this, _getNextVersion);
272
+ __privateAdd(this, _getProjects);
273
+ __privateAdd(this, _confirmReleaseOptions);
274
+ __privateAdd(this, _addTask);
275
+ __privateAdd(this, _start);
276
+ __privateAdd(this, _done);
277
+ __privateAdd(this, _check);
278
+ this.currentVersion = "";
279
+ this.nextVersion = "";
280
+ this.modifiedFiles = [];
281
+ // TODO: record the task execution status to rollback when execution fails
282
+ this.taskQueue = [];
283
+ this.taskStatus = "pending";
284
+ this.options = parseOptions();
285
+ }
286
+ async run() {
287
+ __privateMethod(this, _start, start_fn).call(this);
288
+ await __privateMethod(this, _getNextVersion, getNextVersion_fn).call(this);
289
+ await __privateMethod(this, _getProjects, getProjects_fn).call(this);
290
+ await __privateMethod(this, _confirmReleaseOptions, confirmReleaseOptions_fn).call(this);
291
+ for await (const task of this.taskQueue) {
292
+ if (this.taskStatus === "failed") {
293
+ break;
294
+ } else {
295
+ await task.exec();
296
+ }
297
+ }
298
+ __privateMethod(this, _done, done_fn).call(this);
299
+ }
300
+ /**
301
+ * Accepts a message string template (e.g. "release %s" or "This is the %s release").
302
+ * If the template contains any "%s" placeholders, then they are replaced with the version number;
303
+ * otherwise, the version number is appended to the string.
304
+ */
305
+ formatMessageString(template, nextVersion) {
306
+ if (template.includes("%s")) {
307
+ return template.replace(/%s/g, nextVersion);
308
+ } else {
309
+ return template + nextVersion;
310
+ }
311
+ }
312
+ }
313
+ _checkDryRun = new WeakSet();
314
+ checkDryRun_fn = function() {
315
+ if (this.options.isDry) {
316
+ prompts.log.message(colorette.bgBlue(" DRY RUN "));
317
+ process.env.DRY = "true";
318
+ }
319
+ };
320
+ _getNextVersion = new WeakSet();
321
+ getNextVersion_fn = async function() {
322
+ const { dir, excludes, version } = this.options;
323
+ const projectFile = await crossBump.findProjectFiles(dir, excludes);
324
+ if (!projectFile.length) {
325
+ throw new Error("can't found any project file in the project root");
326
+ }
327
+ const projectVersion = await crossBump.getProjectVersion(projectFile[0]);
328
+ this.currentVersion = projectVersion || "";
329
+ if (crossBump.isVersionValid(version)) {
330
+ this.nextVersion = version;
331
+ } else {
332
+ const nextVersion = await chooseVersion(projectVersion);
333
+ if (prompts.isCancel(nextVersion)) {
334
+ handleUserCancel();
335
+ } else {
336
+ this.nextVersion = nextVersion;
337
+ }
338
+ }
339
+ };
340
+ _getProjects = new WeakSet();
341
+ getProjects_fn = async function() {
342
+ const { options: { dir, excludes, isRecursive }, nextVersion, currentVersion } = this;
343
+ const projectFiles = await crossBump.findProjectFiles(dir, excludes, isRecursive);
344
+ __privateMethod(this, _addTask, addTask_fn).call(this, {
345
+ name: "upgradeVersion",
346
+ exec: () => {
347
+ return Promise.all(projectFiles.map(async (projectFile) => {
348
+ await crossBump.upgradeProjectVersion(nextVersion, projectFile).then(() => {
349
+ this.modifiedFiles.push(projectFile.path);
350
+ message(`upgrade version to ${colorette.blue(nextVersion)} for ${colorette.gray(path__default["default"].relative(dir, projectFile.path))}`);
351
+ }).catch((e) => {
352
+ this.taskStatus = "failed";
353
+ prompts.log.error(String(e));
354
+ });
355
+ }));
356
+ }
357
+ });
358
+ };
359
+ _confirmReleaseOptions = new WeakSet();
360
+ confirmReleaseOptions_fn = async function() {
361
+ const { isAllYes, commit } = this.options;
362
+ const confirmAndSet = async (optionName, message3, taskName, execFn) => {
363
+ if (isAllYes) {
364
+ this.options[optionName] = true;
365
+ } else if (!this.options[optionName]) {
366
+ const confirmation = await prompts.confirm({ message: message3 });
367
+ if (!prompts.isCancel(confirmation)) {
368
+ this.options[optionName] = confirmation;
369
+ } else {
370
+ handleUserCancel();
371
+ }
372
+ }
373
+ if (this.options[optionName]) {
374
+ __privateMethod(this, _addTask, addTask_fn).call(this, {
375
+ name: taskName,
376
+ exec: execFn
377
+ // rollback: rollbackFn,
378
+ });
379
+ }
380
+ };
381
+ const message2 = this.formatMessageString(commit.template, this.nextVersion);
382
+ await confirmAndSet(
383
+ "shouldCommit",
384
+ "should commit this release?",
385
+ "commit",
386
+ async () => {
387
+ __privateMethod(this, _check, check_fn).call(this, await gitCommit(message2, { modifiedFiles: this.modifiedFiles, shouldStageAll: false }));
388
+ }
389
+ // async () => {
390
+ // if (!process.env.DRY) {
391
+ // await gitReset({ files: this.modifiedFiles })
392
+ // }
393
+ // log.info("rollback: git reset")
394
+ // },
395
+ );
396
+ const tagName = `v${this.nextVersion}`;
397
+ await confirmAndSet(
398
+ "shouldTag",
399
+ "should create a tag for this release?",
400
+ "tag",
401
+ async () => {
402
+ __privateMethod(this, _check, check_fn).call(this, await gitTag(tagName, { message: message2 }));
403
+ }
404
+ // async () => {
405
+ // await gitTag(tagName, { isDel: true })
406
+ // log.info("rollback: git tag")
407
+ // },
408
+ );
409
+ await confirmAndSet(
410
+ "shouldPush",
411
+ "should push to remote repository?",
412
+ "push",
413
+ async () => {
414
+ __privateMethod(this, _check, check_fn).call(this, await gitPush({ shouldFollowTags: this.options.push.isTagOnly }));
415
+ }
416
+ // () => undefined,
417
+ );
418
+ };
419
+ _addTask = new WeakSet();
420
+ addTask_fn = function(task) {
421
+ const expect = this.taskQueue.length + 1;
422
+ return this.taskQueue.push(task) === expect;
423
+ };
424
+ _start = new WeakSet();
425
+ start_fn = function() {
426
+ prompts.intro("Cross release");
427
+ __privateMethod(this, _checkDryRun, checkDryRun_fn).call(this);
428
+ };
429
+ _done = new WeakSet();
430
+ done_fn = function() {
431
+ prompts.outro("Done");
432
+ };
433
+ _check = new WeakSet();
434
+ check_fn = function(status) {
435
+ if (!status) {
436
+ this.taskStatus = "failed";
437
+ }
438
+ };
439
+ var app = new App();
440
+
441
+ void app.run();
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "cross-release-cli",
3
+ "version": "0.0.1-alpha.1",
4
+ "description": "command line app for cross language bump utility",
5
+ "author": {
6
+ "name": "rainbowatcher",
7
+ "email": "rainbow-w@qq.com",
8
+ "url": "https://github.com/rainbowatcher"
9
+ },
10
+ "license": "MIT",
11
+ "homepage": "https://github.com/rainbowatcher/cross-release/blob/main/packages/cross-release-cli/README.md",
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/rainbowatcher/cross-release.git"
15
+ },
16
+ "bugs": {
17
+ "url": "https://github.com/rainbowatcher/cross-release/issues"
18
+ },
19
+ "main": "dist/index.cjs",
20
+ "bin": {
21
+ "cross-release": "./bin/cross-release.js"
22
+ },
23
+ "files": [
24
+ "dist",
25
+ "bin"
26
+ ],
27
+ "dependencies": {
28
+ "@clack/prompts": "^0.7.0",
29
+ "cac": "^6.7.14",
30
+ "colorette": "^2.0.20",
31
+ "debug": "^4.3.4",
32
+ "defu": "^6.1.2",
33
+ "execa": "^8.0.1",
34
+ "is-unicode-supported": "^1.3.0",
35
+ "cross-bump": "0.0.1-alpha.1"
36
+ },
37
+ "scripts": {
38
+ "build": "pkgroll"
39
+ }
40
+ }