@peiyanlu/release 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -1,24 +1,24 @@
1
- import { execAsync, isGitRepo, readJsonFile } from "@peiyanlu/cli-utils";
2
- import { blue, dim, gray, green, red, rgb, yellow } from "ansis";
3
- import { program } from "commander";
4
- import path, { dirname, join, resolve } from "node:path";
1
+ import path, { join, resolve } from "node:path";
5
2
  import { fileURLToPath, pathToFileURL } from "node:url";
3
+ import { eol, execAsync, isGitRepo, parseGitHubRepo, readJsonFile, runGit, runNpm } from "@peiyanlu/cli-utils";
4
+ import { dim, gray, green, red, rgb, underline, yellow } from "ansis";
5
+ import { program } from "commander";
6
6
  import { cancel, confirm, group, intro, isCancel, log, outro, select, tasks, text } from "@clack/prompts";
7
- import { inspect, styleText } from "node:util";
8
7
  import { clean, coerce, gt, gte, inc, parse, prerelease, valid } from "semver";
8
+ import { castArray, isPlainObject, isUndefined } from "@peiyanlu/ts-utils";
9
9
  import { createReadStream, existsSync, statSync } from "node:fs";
10
- import { EOL } from "node:os";
11
- import { readFile, writeFile } from "node:fs/promises";
10
+ import { spawnSync } from "node:child_process";
11
+ import { inspect, styleText } from "node:util";
12
12
  import { Octokit } from "@octokit/rest";
13
13
  import { contentType } from "mime-types";
14
14
  import open from "open";
15
15
  import { glob } from "tinyglobby";
16
- import { spawn } from "node:child_process";
16
+ import { readFile, writeFile } from "node:fs/promises";
17
17
 
18
- //#region node_modules/.pnpm/tsdown@0.18.2_typescript@5.9.3/node_modules/tsdown/esm-shims.js
18
+ //#region node_modules/.pnpm/tsdown@0.18.4_typescript@5.9.3/node_modules/tsdown/esm-shims.js
19
19
  const getFilename = () => fileURLToPath(import.meta.url);
20
20
  const getDirname = () => path.dirname(getFilename());
21
- const __dirname$1 = /* @__PURE__ */ getDirname();
21
+ const __dirname = /* @__PURE__ */ getDirname();
22
22
 
23
23
  //#endregion
24
24
  //#region src/config.ts
@@ -47,9 +47,7 @@ const resolveConfig = async (cwd, log$1) => {
47
47
  log$1?.(configFile);
48
48
  return loadConfig(configFile);
49
49
  };
50
- const toString = (self) => Object.prototype.toString.call(self);
51
- const isPlainObject = (v) => toString(v) === "[object Object]";
52
- const isFunction = (v) => toString(v) === "[object Function]";
50
+ const isFunction = (v) => typeof v === "function";
53
51
  const mergeConfig = (defaults, overrides) => {
54
52
  if (!overrides) return structuredClone(defaults);
55
53
  if (Object.keys(defaults).length === 0) return structuredClone(overrides);
@@ -66,57 +64,13 @@ const mergeConfig = (defaults, overrides) => {
66
64
  return result;
67
65
  };
68
66
 
69
- //#endregion
70
- //#region src/utils.ts
71
- const __filename = fileURLToPath(import.meta.url);
72
- const __dirname = dirname(__filename);
73
- function joinUrl(input) {
74
- const temps = Array.isArray(input) ? input : [...arguments];
75
- if (temps.length === 0) return "";
76
- const result = [];
77
- const parts = [...temps];
78
- /** 协议正则 */
79
- const PROTOCOL_RE = /^[^/:]+:\/*$/;
80
- const FILE_PROTOCOL_RE = /^file:\/\/\//;
81
- if (PROTOCOL_RE.test(parts[0]) && parts.length > 1) {
82
- parts[1] = parts[0] + parts[1];
83
- parts.shift();
84
- }
85
- if (FILE_PROTOCOL_RE.test(parts[0])) parts[0] = parts[0].replace(/^([^/:]+):\/*/, "$1:///");
86
- else parts[0] = parts[0].replace(/^([^/:]+):\/*/, "$1://");
87
- parts.forEach((part, index) => {
88
- if (!part) return;
89
- let segment = part;
90
- if (index > 0) segment = segment.replace(/^\/+/, "");
91
- if (index < parts.length - 1) segment = segment.replace(/\/+$/, "");
92
- else segment = segment.replace(/\/+$/, "/");
93
- result.push(segment);
94
- });
95
- let url = result.join("/");
96
- url = url.replace(/\/(\?|&|#[^!])/g, "$1");
97
- const [base, ...queryParts] = url.split("?");
98
- url = base + (queryParts.length ? "?" + queryParts.join("&") : "");
99
- return url;
100
- }
101
- const isUndefined = (val) => {
102
- return void 0 === val;
103
- };
104
- const eol = (n$1 = 1) => EOL.repeat(n$1);
105
- const underline = (text$1) => {
106
- return styleText(["dim", "underline"], text$1);
107
- };
108
- const castArray = (arr) => {
109
- return Array.isArray(arr) ? arr : [arr];
110
- };
111
-
112
67
  //#endregion
113
68
  //#region src/messages.ts
114
- const n = (b) => {
115
- return b ? " (dry run)" : "";
116
- };
69
+ const n = (b) => b ? "(dry run)" : "";
70
+ const t = () => green(`${Math.floor(process.uptime())}s`);
117
71
  const MSG = {
118
- INTRO: (pkgName, dryRun) => `🚀 Starting release ${yellow(pkgName)}${n(dryRun)}`,
119
- OUTRO: (dryRun) => `🎉 Release finished successfully${n(dryRun)}`,
72
+ INTRO: (pkgName, dryRun) => `🚀 Starting release ${yellow(pkgName)} ${n(dryRun)}`,
73
+ OUTRO: (dryRun) => `🎉 Release finished successfully in ${t()} ${n(dryRun)}.`,
120
74
  LOG: {
121
75
  SHOW_VERSION: (version) => `🎉 Released ${green(version)}`,
122
76
  SHOW_CHANGELOG: "🎉 Changelog collected",
@@ -126,21 +80,25 @@ const MSG = {
126
80
  CHECK: {
127
81
  GIT: {
128
82
  CHECKING: "Checking Git repository",
129
- CHECKED: (name, url) => `Git remote resolved: ${dim(name)} → ${underline(url)}`
83
+ CHECKED: (name, url) => `Git remote resolved: ${dim(name)} → ${underline(dim(url))}`
130
84
  },
131
85
  NPM: {
132
86
  CHECKING: "Checking npm registry",
133
- CHECKED: (registry, msg$1) => `NPM registry: ${underline(registry)}${msg$1 ? ` ${msg$1}` : ""}`
87
+ CHECKED: (registry, msg$1) => `NPM registry: ${underline(dim(registry))}${msg$1 ? ` ${msg$1}` : ""}`
134
88
  }
135
89
  },
136
90
  TASK: {
137
91
  VERSION: {
138
92
  START: "Bumping version",
139
- END: (version) => `Version bumped to ${green(version)}`
93
+ END: (to) => `Version bumped: ${underline(dim(to))}`
94
+ },
95
+ CHANGELOG: {
96
+ START: "Generating changelog",
97
+ END: `Changelog generated`
140
98
  },
141
99
  NPM: {
142
100
  START: "Publishing npm package",
143
- END: "NPM package published"
101
+ END: (url) => `NPM package published: ${underline(dim(url))}`
144
102
  },
145
103
  GIT: {
146
104
  START: "Creating git commit, tag & push",
@@ -148,333 +106,159 @@ const MSG = {
148
106
  },
149
107
  GITHUB: {
150
108
  START: "Creating GitHub release",
151
- END: "GitHub release created"
109
+ END: (url) => `GitHub release created: ${underline(dim(url))}`
152
110
  }
153
111
  },
154
112
  PROMPT: {
155
113
  SELECT_VERSION: "Select version bump:",
156
114
  INPUT_VERSION: "Enter custom version:",
157
- NPM_PUBLISH: (pkg$1) => `Publish to npm ${underline(`(${pkg$1})`)}?`,
115
+ NPM_PUBLISH: (pkg$1) => `Publish to npm ${underline(dim`(${pkg$1})`)}?`,
158
116
  NPM_OTP: "Enter npm one-time password (OTP):",
159
- GITHUB_RELEASE: (title) => `Create a new GitHub release ${underline(`(${title})`)}?`,
160
- GIT_COMMIT: (msg$1) => `Create git commit ${underline(`(${msg$1})`)}?`,
161
- GIT_TAG: (msg$1) => `Create git tag ${underline(`(${msg$1})`)}?`,
117
+ GITHUB_RELEASE: (title) => `Create a new GitHub release ${underline(dim`(${title})`)}?`,
118
+ GIT_COMMIT: (msg$1) => `Create git commit ${underline(dim`(${msg$1})`)}?`,
119
+ GIT_TAG: (msg$1) => `Create git tag ${underline(dim`(${msg$1})`)}?`,
162
120
  GIT_PUSH: "Push commits and tags to remote?"
163
121
  },
164
122
  ERROR: {
123
+ GIT_WORKDIR: `[git] Working dir must be clean`,
165
124
  GIT_REGISTRY: (name) => `[git] Repository not found for ${dim(name)}`,
166
125
  GIT_REMOTE: (name) => `[git] Remote "${dim(name)}" not found`,
167
126
  GITHUB_TAG_EXIT: (tag) => `[github] Release or tag "${tag}" already exists`,
168
127
  GITHUB_TOKEN: (tokenRef) => `[github] Missing GitHub token. Please set ${yellow(tokenRef ?? "GITHUB_TOKEN")}`,
169
128
  GITHUB_AUTH: "[github] Invalid GitHub token or insufficient permissions",
170
129
  GITHUB_USER: "[github] User does not have permission to create releases",
171
- NPM_REGISTRY: (registry) => `[npm] Unable to reach npm registry ${underline(registry)}`,
130
+ NPM_REGISTRY: (registry) => `[npm] Unable to reach npm registry ${underline(dim(registry))}`,
172
131
  NPM_AUTH: `[npm] Not authenticated with npm. Please run ${yellow("npm login")} and try again.`,
173
132
  NPM_USER: (user, name) => `[npm] User ${dim(user)} is not a collaborator of ${dim(name)}.`
174
133
  }
175
134
  };
176
135
 
177
136
  //#endregion
178
- //#region src/prompts.ts
179
- const abortTask = (msg$1) => {
180
- cancel(`${red("✕")} ${msg$1}`);
181
- process.exit(0);
182
- };
183
- const abortOnCancel = (value) => {
184
- if (isCancel(value)) abortTask("Operation cancelled.");
185
- };
186
- const abortOnError = (err) => {
187
- const msg$1 = err.message.replace(/^\[(github|npm|git)]/, (a) => red(a));
188
- log.message(`\n${msg$1}\n`);
189
- process.exit(1);
190
- };
191
- const taskEnd = (msg$1) => {
192
- outro(msg$1);
193
- process.exit(0);
194
- };
195
- const msg = (prefix, msg$1) => {
196
- log.message(gray`${rgb(33, 91, 184)(`[${prefix}]`)} ${msg$1}`);
197
- };
198
- const prompt = (msg$1) => {
199
- return `${blue`?`} ${msg$1}`;
200
- };
201
-
202
- //#endregion
203
- //#region src/version/bump.ts
204
- const isPreRelease = (version) => {
205
- return Boolean(prerelease(version));
206
- };
207
- const isValid = (version) => {
208
- return Boolean(valid(version));
137
+ //#region src/github/release.ts
138
+ const getGithubUrl = (owner, repo) => {
139
+ return `https://github.com/${owner}/${repo}`;
209
140
  };
210
- const parseVersion = (raw) => {
211
- const next = isValid(raw) ? raw : coerce(raw)?.toString();
212
- if (!next) return {};
213
- const { prerelease: prerelease$1 } = parse(next);
214
- const toPreRelease = Boolean(prerelease$1.length);
215
- const [preId, preBase] = prerelease$1.map(String);
216
- return {
217
- next,
218
- toPreRelease,
219
- preId,
220
- preBase
221
- };
141
+ const truncateBody = (body) => {
142
+ if (body && body.length >= 124e3) return body.substring(0, 124e3) + "...";
143
+ return body;
222
144
  };
223
- const getCIVersion = (current) => {
224
- return inc(current, Release.PATCH);
145
+ const uploadAssets = async (ctx, config) => {
146
+ const { github: { assets } } = config;
147
+ const { github: { isReleased, owner, repo, uploadUrl, releaseId, token }, pkg: { name } } = ctx;
148
+ if (!assets?.length || !isReleased) return;
149
+ const octokit = new Octokit({ auth: token });
150
+ const files = await glob(assets);
151
+ await Promise.all(files.map((file) => {
152
+ return octokit.repos.uploadReleaseAsset({
153
+ owner,
154
+ repo,
155
+ name,
156
+ url: uploadUrl,
157
+ release_id: releaseId,
158
+ data: createReadStream(file),
159
+ headers: {
160
+ "content-type": contentType(file) || "application/octet-stream",
161
+ "content-length": statSync(file).size
162
+ }
163
+ });
164
+ }));
225
165
  };
226
-
227
- //#endregion
228
- //#region src/version/prompts.ts
229
- let Release = /* @__PURE__ */ function(Release$1) {
230
- Release$1["MAJOR"] = "major";
231
- Release$1["MINOR"] = "minor";
232
- Release$1["PATCH"] = "patch";
233
- return Release$1;
234
- }({});
235
- let PreRelease = /* @__PURE__ */ function(PreRelease$1) {
236
- PreRelease$1["MAJOR"] = "premajor";
237
- PreRelease$1["MINOR"] = "preminor";
238
- PreRelease$1["PATCH"] = "prepatch";
239
- PreRelease$1["RELEASE"] = "prerelease";
240
- return PreRelease$1;
241
- }({});
242
- let PreId = /* @__PURE__ */ function(PreId$1) {
243
- PreId$1["ALPHA"] = "alpha";
244
- PreId$1["BETA"] = "beta";
245
- PreId$1["RC"] = "rc";
246
- PreId$1["NET"] = "next";
247
- PreId$1["CANARY"] = "canary";
248
- PreId$1["NIGHT"] = "nightly";
249
- PreId$1["DEV"] = "dev";
250
- return PreId$1;
251
- }({});
252
- const diff = (from, to) => {
253
- const a = from.split(".");
254
- return to.split(".").map((v, i) => v === a[i] ? dim(v) : green(v)).join(".");
166
+ const createWebRelease = async (ctx, config) => {
167
+ const { pkg: { next, toPreRelease }, github: { owner, repo, changelog, releaseName } } = ctx;
168
+ const { github: { prerelease: prerelease$1 } } = config;
169
+ const github = getGithubUrl(owner, repo);
170
+ const url = new URL(`${github}/releases/new`);
171
+ url.searchParams.set("tag", next);
172
+ url.searchParams.set("title", releaseName ?? `Release ${next}`);
173
+ url.searchParams.set("body", truncateBody(changelog));
174
+ url.searchParams.set("prelease", String(toPreRelease || prerelease$1));
175
+ const isWindows = process.platform === "win32";
176
+ await open(url.toString(), { wait: isWindows });
255
177
  };
256
- const runVersionPrompts = async (ctx, config) => {
257
- const { pkg: { current, fromPreRelease } } = ctx;
258
- const selectVersion = async () => {
259
- const patch = inc(current, Release.PATCH);
260
- const minor = inc(current, Release.MINOR);
261
- const major = inc(current, Release.MAJOR);
262
- const prerelease$1 = inc(current, PreRelease.RELEASE);
263
- const prepatch = inc(current, PreRelease.PATCH, PreId.BETA);
264
- const preminor = inc(current, PreRelease.MINOR, PreId.BETA);
265
- const premajor = inc(current, PreRelease.MAJOR, PreId.BETA);
266
- const pr = [{
267
- label: "Pre-Release",
268
- value: prerelease$1,
269
- hint: prerelease$1
270
- }];
271
- const pre = [
272
- {
273
- label: "Pre-Patch",
274
- value: prepatch,
275
- hint: prepatch
276
- },
277
- {
278
- label: "Pre-Minor",
279
- value: preminor,
280
- hint: preminor
281
- },
282
- {
283
- label: "Pre-Major",
284
- value: premajor,
285
- hint: premajor
286
- }
287
- ];
288
- const release = [
289
- {
290
- label: "Patch",
291
- value: patch,
292
- hint: patch
293
- },
294
- {
295
- label: "Minor",
296
- value: minor,
297
- hint: minor
298
- },
299
- {
300
- label: "Major",
301
- value: major,
302
- hint: major
303
- }
304
- ];
305
- const custom = [{
306
- label: "As-Is",
307
- value: "ignore",
308
- hint: current
309
- }, {
310
- label: "Custom",
311
- value: "custom",
312
- hint: "custom specified"
313
- }];
314
- const version = await select({
315
- message: prompt(MSG.PROMPT.SELECT_VERSION),
316
- options: [
317
- ...fromPreRelease ? pr : [],
318
- ...release,
319
- ...fromPreRelease ? [] : pre,
320
- ...custom
321
- ],
322
- initialValue: fromPreRelease ? prerelease$1 : patch
178
+ const createCiRelease = async (ctx, config) => {
179
+ const { pkg: { next, toPreRelease }, github: { owner, repo, token, changelog, releaseName } } = ctx;
180
+ const { github: { prerelease: prerelease$1, draft, autoGenerate } } = config;
181
+ const octokit = new Octokit({ auth: token });
182
+ try {
183
+ const { data } = await octokit.repos.createRelease({
184
+ owner,
185
+ repo,
186
+ tag_name: next,
187
+ target_commitish: void 0,
188
+ name: releaseName ?? `Release ${next}`,
189
+ body: autoGenerate ? "" : truncateBody(changelog),
190
+ draft: draft ?? false,
191
+ prerelease: (toPreRelease || prerelease$1) ?? false,
192
+ discussion_category_name: void 0,
193
+ generate_release_notes: autoGenerate,
194
+ make_latest: "true"
323
195
  });
324
- abortOnCancel(version);
325
- return version;
326
- };
327
- const inputVersion = async () => {
328
- const version = await text({
329
- message: prompt(MSG.PROMPT.INPUT_VERSION),
330
- initialValue: current,
331
- validate: (val) => {
332
- if (!isValid(val)) return "Invalid version";
333
- }
196
+ const { html_url, upload_url, id, discussion_url } = data;
197
+ Object.assign(ctx.github, {
198
+ isReleased: true,
199
+ releaseId: id,
200
+ releaseUrl: html_url,
201
+ uploadUrl: upload_url,
202
+ discussionUrl: discussion_url
334
203
  });
335
- abortOnCancel(version);
336
- return version;
337
- };
338
- const type = await selectVersion();
339
- const next = type === "custom" ? await inputVersion() : type === "ignore" ? current : type;
340
- const parsed = parseVersion(next);
341
- Object.assign(ctx.pkg, { ...parsed });
342
- ctx.isIncrement = gte(next, current);
343
- log.message(gray`
344
- from ${dim(current)}
345
- to ${diff(current, next)}
346
- `);
347
- return { next };
204
+ } catch (e) {
205
+ if (e?.status === 422) throw new Error(MSG.ERROR.GITHUB_TAG_EXIT(next));
206
+ throw e;
207
+ }
348
208
  };
349
-
350
- //#endregion
351
- //#region src/defaults.ts
352
- const createDefaultContext = () => {
353
- return {
354
- ci: false,
355
- release: false,
356
- noNpm: false,
357
- dryRun: false,
358
- changelog: false,
359
- increment: "",
360
- isIncrement: false,
361
- noGithub: false,
362
- pkg: {
363
- current: "",
364
- next: "",
365
- name: "",
366
- isPrivate: false,
367
- fromPreRelease: false,
368
- toPreRelease: false,
369
- publishConfig: {
370
- access: "public",
371
- registry: "https://registry.npmjs.org",
372
- publicPath: ""
373
- },
374
- preId: PreId.BETA,
375
- preBase: "0"
376
- },
377
- npm: {
378
- username: "",
379
- otp: ""
380
- },
381
- git: {
382
- remoteName: "origin",
383
- remoteUrl: "",
384
- latestTag: "",
385
- previousTag: "",
386
- currentTag: "",
387
- isCommitted: false,
388
- isTagged: false
389
- },
390
- github: {
391
- username: "",
392
- repo: "",
393
- changelog: "",
394
- owner: "",
395
- isWeb: false,
396
- skipChecks: false,
397
- isReleased: false,
398
- releaseId: 0,
399
- releaseUrl: "",
400
- uploadUrl: "",
401
- discussionUrl: "",
402
- token: ""
209
+ const parseGithubUrl = (ctx) => {
210
+ const { git: { remoteUrl } } = ctx;
211
+ const [owner, repo] = parseGitHubRepo(remoteUrl);
212
+ Object.assign(ctx.github, {
213
+ owner,
214
+ repo
215
+ });
216
+ };
217
+ const createRelease = async (ctx, config) => {
218
+ const { github: { owner, repo }, isCI } = ctx;
219
+ const { github: { tokenRef, release, skipChecks } } = config;
220
+ if (!release) return;
221
+ const token = tokenRef ? process.env[tokenRef] : process.env.GITHUB_TOKEN;
222
+ if (!token) {
223
+ Object.assign(ctx.github, { isWeb: true });
224
+ if (isCI && process.env.GITHUB_ACTIONS) throw new Error(MSG.ERROR.GITHUB_TOKEN(tokenRef));
225
+ }
226
+ Object.assign(ctx.github, { token });
227
+ const octokit = new Octokit({ auth: token });
228
+ if (token && !skipChecks) if (process.env.GITHUB_ACTIONS) Object.assign(ctx.github, { username: process.env.GITHUB_ACTOR });
229
+ else {
230
+ try {
231
+ const { data: { login: username } } = await octokit.users.getAuthenticated();
232
+ Object.assign(ctx.github, { username });
233
+ } catch (e) {
234
+ throw new Error(MSG.ERROR.GITHUB_AUTH);
235
+ }
236
+ try {
237
+ const { github: { username } } = ctx;
238
+ await octokit.repos.checkCollaborator({
239
+ owner,
240
+ repo,
241
+ username
242
+ });
243
+ } catch (e) {
244
+ throw new Error(MSG.ERROR.GITHUB_USER);
403
245
  }
404
- };
246
+ }
247
+ const { github: { isWeb }, dryRun } = ctx;
248
+ if (dryRun) return true;
249
+ if (isWeb) await createWebRelease(ctx, config);
250
+ else if (isCI) {
251
+ await createCiRelease(ctx, config);
252
+ await uploadAssets(ctx, config);
253
+ }
405
254
  };
406
- const createDefaultConfig = (isCI) => {
407
- return {
408
- hooks: {
409
- "before:bump": "",
410
- "after:bump": "",
411
- "before:publish": "",
412
- "after:publish": "",
413
- "before:push": "",
414
- "after:push": "",
415
- "before:release": "",
416
- "after:release": ""
417
- },
418
- git: {
419
- commit: isCI,
420
- tag: isCI,
421
- push: isCI,
422
- commitMessage: "chore(release): ${version}",
423
- tagMessage: "Release ${version}",
424
- tagName: "${version}"
425
- },
426
- npm: {
427
- publish: isCI,
428
- tag: "",
429
- allowSameVersion: false
430
- },
431
- github: {
432
- release: isCI,
433
- releaseName: "Release ${version}",
434
- prerelease: false,
435
- draft: false,
436
- tokenRef: "GITHUB_TOKEN",
437
- assets: []
438
- }
439
- };
255
+ const getGithubReleaseUrl = (ctx) => {
256
+ const { github: { owner, repo }, git: { currentTag } } = ctx;
257
+ return `${getGithubUrl(owner, repo)}/releases/tag/${normalizeTag(currentTag)}`;
440
258
  };
441
259
 
442
260
  //#endregion
443
261
  //#region src/git/commit.ts
444
- const spawnAsync = (cmd, args, options) => {
445
- return new Promise((resolve$1) => {
446
- const { trim, error, dryRun, ...others } = options ?? {};
447
- const fullCmd = [cmd, ...args].join(" ");
448
- const dim$1 = (text$1) => styleText(["dim"], text$1);
449
- const red$1 = (text$1) => styleText(["red"], text$1);
450
- if (dryRun) {
451
- console.log(`${dim$1("[dry-run]")} ${fullCmd}`);
452
- return resolve$1(void 0);
453
- }
454
- const child = spawn(cmd, args, { ...others });
455
- let stdout = "";
456
- child.stdout?.setEncoding("utf-8");
457
- child.stdout?.on("data", (data) => stdout += trim ? data.trim() : data);
458
- let stderr = "";
459
- child.stderr?.setEncoding("utf-8");
460
- child.stderr?.on("data", (data) => stderr += trim ? data.trim() : data);
461
- child.on("close", (code) => {
462
- if (stderr) {
463
- const err = `${red$1("spawnAsync")} ${dim$1(fullCmd)} ${stderr}`;
464
- switch (error) {
465
- case "log":
466
- console.error(err);
467
- break;
468
- case "throw": throw new Error(err);
469
- }
470
- }
471
- resolve$1(0 === code ? stdout : void 0);
472
- });
473
- });
474
- };
475
- const runGit = async (args, trim = true) => {
476
- return spawnAsync("git", args, { trim });
477
- };
478
262
  const getBranchName = () => {
479
263
  return runGit([
480
264
  "rev-parse",
@@ -494,10 +278,7 @@ const getStatus = () => {
494
278
  "status",
495
279
  "--short",
496
280
  "--untracked-files=no"
497
- ], false);
498
- };
499
- const gitRestore = () => {
500
- return runGit(["restore", "."]);
281
+ ], { trim: false });
501
282
  };
502
283
  const coloredChangeset = (log$1) => {
503
284
  const colorStatusChar = (ch) => {
@@ -531,10 +312,6 @@ const getRemote = async () => {
531
312
  ]))
532
313
  };
533
314
  };
534
- const parseGitHubRepo = (url) => {
535
- const match = url.trim().match(/github(?:\.com)?[:/](.+?)\/(.+?)(\/.+?)?(\.git)?$/);
536
- return match ? match.slice(1, 3) : [];
537
- };
538
315
  const getLatestTag = async (match = "*", exclude = "*-beta.*") => {
539
316
  return runGit([
540
317
  "describe",
@@ -577,29 +354,10 @@ const getLog = async (print, isIncrement) => {
577
354
  const cmd = ["log", `--pretty=format:${print ? "* %s (%h)" : "%s %h %H"}`];
578
355
  const { from, to } = await resolveChangelogRange(isIncrement);
579
356
  if (from) cmd.push(`${from}...${to}`);
580
- return runGit(cmd, false);
581
- };
582
- const normalizeTag = (tag) => clean(tag) ?? tag;
583
- const resolveTag$1 = async (ctx, config) => {
584
- const { git: { tagName: tagTemplate } } = config;
585
- const latestTag = await getLatestTag() ?? "";
586
- const currentTag = format(tagTemplate || ((latestTag || "").match(/^v/) ? "v${version}" : "${version}"), ctx);
587
- Object.assign(ctx.git, {
588
- latestTag,
589
- currentTag
590
- });
591
- return true;
357
+ return runGit(cmd, { trim: false });
592
358
  };
593
- const gitCheck = async (ctx, config) => {
594
- const { pkg: { name } } = ctx;
595
- if (!await isGitRepo()) throw new Error(MSG.ERROR.GIT_REGISTRY(name));
596
- const { remoteName, remoteUrl } = await getRemote();
597
- if (!remoteUrl) throw new Error(MSG.ERROR.GIT_REMOTE(name));
598
- Object.assign(ctx.git, {
599
- remoteName,
600
- remoteUrl
601
- });
602
- parseGithubUrl(ctx);
359
+ const isWorkingDirClean = async () => {
360
+ return (await runGit(["status", "-s"]))?.length === 0;
603
361
  };
604
362
  const hasUpstreamBranch = async () => {
605
363
  const branch = await runGit([
@@ -612,37 +370,52 @@ const hasUpstreamBranch = async () => {
612
370
  const getUpstreamArgs = async (remoteName) => {
613
371
  const hasUpstream = await hasUpstreamBranch();
614
372
  const branch = await getBranchName();
615
- if (!hasUpstream) return remoteName ? [
616
- "--set-upstream",
617
- remoteName,
618
- branch
619
- ] : [
373
+ if (!hasUpstream) return [
620
374
  "--set-upstream",
621
- "origin",
375
+ remoteName || "origin",
622
376
  branch
623
377
  ];
624
378
  if (remoteName) return [remoteName];
625
379
  return [];
626
380
  };
627
- const format = (str, ctx) => {
628
- const { pkg: { next } } = ctx;
629
- return str.replace("${version}", next);
381
+ const normalizeTag = (tag) => clean(tag) ?? tag;
382
+ const gitCheck = async (ctx, config) => {
383
+ const { pkg: { name } } = ctx;
384
+ const { git: { requireRepository, requireRemote, requireWorkDirClean } } = config;
385
+ if (!await isGitRepo()) if (requireRepository) throw new Error(MSG.ERROR.GIT_REGISTRY(name));
386
+ else {
387
+ ctx.noGit = true;
388
+ return;
389
+ }
390
+ if (requireWorkDirClean && !await isWorkingDirClean()) throw new Error(MSG.ERROR.GIT_WORKDIR);
391
+ const { remoteName, remoteUrl } = await getRemote();
392
+ if (!remoteUrl) if (requireRemote) throw new Error(MSG.ERROR.GIT_REMOTE(name));
393
+ else {
394
+ ctx.noGitHub = true;
395
+ return;
396
+ }
397
+ Object.assign(ctx.git, {
398
+ remoteName,
399
+ remoteUrl
400
+ });
401
+ parseGithubUrl(ctx);
630
402
  };
631
403
  const commitAndTag = async (ctx, config) => {
632
- const { git: { currentTag, remoteName }, dryRun } = ctx;
633
- const { git: { commit, tag, push, tagMessage, commitMessage } } = config;
404
+ const { git: { remoteName, currentTag, commitMessage, tagMessage }, dryRun } = ctx;
405
+ const { git: { commit, tag, push, addUntrackedFiles, commitArgs, tagArgs, pushArgs } } = config;
634
406
  if (!commit) return;
635
407
  const args = dryRun ? ["--dry-run"] : [];
636
408
  await runGit([
637
409
  "add",
638
410
  ".",
639
- "--all",
411
+ addUntrackedFiles ? "--all" : "--update",
640
412
  ...args
641
413
  ]);
642
414
  await runGit([
643
415
  "commit",
644
416
  "--message",
645
- format(commitMessage, ctx),
417
+ commitMessage,
418
+ ...commitArgs,
646
419
  ...args
647
420
  ]);
648
421
  if (!dryRun) Object.assign(ctx.git, { isCommitted: true });
@@ -651,8 +424,9 @@ const commitAndTag = async (ctx, config) => {
651
424
  "tag",
652
425
  "--annotate",
653
426
  "--message",
654
- format(tagMessage, ctx),
427
+ tagMessage,
655
428
  currentTag,
429
+ ...tagArgs,
656
430
  ...args
657
431
  ]);
658
432
  if (!dryRun) Object.assign(ctx.git, { isTagged: true });
@@ -661,18 +435,19 @@ const commitAndTag = async (ctx, config) => {
661
435
  "push",
662
436
  ...await getUpstreamArgs(remoteName),
663
437
  "--follow-tags",
438
+ ...pushArgs,
664
439
  ...args
665
440
  ]);
666
441
  };
667
- const gitRollback = async (ctx) => {
442
+ const gitRollback$1 = (ctx) => {
668
443
  const { git: { currentTag, isCommitted, isTagged } } = ctx;
669
- await gitRestore();
670
- if (isTagged) await runGit([
444
+ spawnSync("git", ["restore", "."]);
445
+ if (isTagged) spawnSync("git", [
671
446
  "tag",
672
447
  "--delete",
673
448
  currentTag
674
449
  ]);
675
- await runGit([
450
+ spawnSync("git", [
676
451
  "reset",
677
452
  "--hard",
678
453
  isCommitted ? "HEAD~1" : "HEAD"
@@ -680,123 +455,344 @@ const gitRollback = async (ctx) => {
680
455
  };
681
456
 
682
457
  //#endregion
683
- //#region src/github/release.ts
684
- const getGithubUrl = (owner, repo) => {
685
- return `https://github.com/${owner}/${repo}`;
458
+ //#region src/utils.ts
459
+ const info = (msg$1) => {
460
+ console.log(`${rgb(33, 91, 184)(`i`)} ${gray(msg$1)}`);
686
461
  };
687
- const truncateBody = (body) => {
688
- if (body && body.length >= 124e3) return body.substring(0, 124e3) + "...";
689
- return body;
462
+ const msg = (prefix, msg$1) => {
463
+ log.message(gray`${rgb(33, 91, 184)(`[${prefix}]`)} ${msg$1}`);
690
464
  };
691
- const uploadAssets = async (ctx, config) => {
692
- const { github: { assets } } = config;
693
- const { github: { isReleased, owner, repo, uploadUrl, releaseId, token }, pkg: { name } } = ctx;
694
- if (!assets?.length || !isReleased) return;
695
- const octokit = new Octokit({ auth: token });
696
- const files = await glob(assets);
697
- await Promise.all(files.map((file) => {
698
- return octokit.repos.uploadReleaseAsset({
699
- owner,
700
- repo,
701
- name,
702
- url: uploadUrl,
703
- release_id: releaseId,
704
- data: createReadStream(file),
705
- headers: {
706
- "content-type": contentType(file) || "application/octet-stream",
707
- "content-length": statSync(file).size
465
+ const question = (msg$1, type) => {
466
+ return `${{
467
+ version: green,
468
+ git: rgb(220, 94, 62),
469
+ npm: rgb(186, 70, 61),
470
+ github: rgb(64, 110, 228)
471
+ }[type]`?`} ${msg$1}`;
472
+ };
473
+ const success = (msg$1, dryRun) => {
474
+ return `${(dryRun ? rgb(123, 115, 66) : green)("✔")} ${msg$1}`;
475
+ };
476
+ const runLifeCycleHook = async (hooks, key, dryRun) => {
477
+ const handler = hooks[key];
478
+ if (typeof handler === "function") {
479
+ if (!dryRun) await handler();
480
+ log.success(success(`${dim`run`} ${inspect(handler)}`, dryRun));
481
+ return;
482
+ }
483
+ return Promise.allSettled(castArray(handler).filter(Boolean).map(async (hook) => {
484
+ if (!dryRun) await execAsync(hook).catch(log.error);
485
+ log.success(success(hook.replace(/^echo$/, dim("echo")), dryRun));
486
+ }));
487
+ };
488
+ const gitRollback = (ctx) => {
489
+ const { git: { currentTag, isCommitted, isTagged } } = ctx;
490
+ spawnSync("git", ["restore", "."]);
491
+ if (isTagged) spawnSync("git", [
492
+ "tag",
493
+ "--delete",
494
+ currentTag
495
+ ]);
496
+ spawnSync("git", [
497
+ "reset",
498
+ "--hard",
499
+ isCommitted ? "HEAD~1" : "HEAD"
500
+ ]);
501
+ };
502
+ const diff = (from, to, separator = ".") => {
503
+ const a = from.split(separator);
504
+ return to.split(separator).map((v, i) => v === a[i] ? dim(v) : green(v)).join(separator);
505
+ };
506
+ const formatTemplate = async (ctx, config) => {
507
+ const { pkg: { next } } = ctx;
508
+ const { git: { commitMessage: cm, tagMessage: tm, tagName }, github: { releaseName: rm } } = config;
509
+ const f = (s, v) => s.replace("${version}", v);
510
+ const latestTag = await getLatestTag() ?? "";
511
+ const currentTag = f(tagName || (latestTag.match(/^v/) ? "v${version}" : "${version}"), next);
512
+ const commitMessage = f(cm, next);
513
+ const tagMessage = f(tm, next);
514
+ const releaseName = f(rm, next);
515
+ Object.assign(ctx.git, {
516
+ latestTag,
517
+ currentTag,
518
+ commitMessage,
519
+ tagMessage
520
+ });
521
+ Object.assign(ctx.github, { releaseName });
522
+ };
523
+
524
+ //#endregion
525
+ //#region src/prompts.ts
526
+ const abortTask = (msg$1) => {
527
+ cancel(`${red("✕")} ${msg$1}`);
528
+ process.exit(0);
529
+ };
530
+ const abortSinglePrompt = (value) => {
531
+ if (isCancel(value)) abortTask("Operation cancelled.");
532
+ };
533
+ const abortGroupPrompt = (ctx) => {
534
+ ctx && gitRollback(ctx);
535
+ abortTask("Operation cancelled.");
536
+ };
537
+ const abortOnError = (err, ctx) => {
538
+ ctx && gitRollback(ctx);
539
+ const msg$1 = err.message.replace(/^\[(github|npm|git)]/, (a) => red(a));
540
+ log.message(`\n${msg$1}\n`);
541
+ process.exit(1);
542
+ };
543
+ const taskEnd = (msg$1) => {
544
+ outro(msg$1);
545
+ process.exit(0);
546
+ };
547
+
548
+ //#endregion
549
+ //#region src/version/bump.ts
550
+ const isPreRelease = (version) => {
551
+ return Boolean(prerelease(version));
552
+ };
553
+ const isValid = (version) => {
554
+ return Boolean(valid(version));
555
+ };
556
+ const parseVersion = (raw) => {
557
+ const next = isValid(raw) ? raw : coerce(raw)?.toString();
558
+ if (!next) return {};
559
+ const { prerelease: prerelease$1 } = parse(next);
560
+ const toPreRelease = Boolean(prerelease$1.length);
561
+ const [preId, preBase] = prerelease$1.map(String);
562
+ return {
563
+ next,
564
+ toPreRelease,
565
+ preId,
566
+ preBase
567
+ };
568
+ };
569
+ const getCIVersion = (current) => {
570
+ return inc(current, Release.PATCH) ?? "";
571
+ };
572
+
573
+ //#endregion
574
+ //#region src/version/prompts.ts
575
+ let Release = /* @__PURE__ */ function(Release$1) {
576
+ Release$1["MAJOR"] = "major";
577
+ Release$1["MINOR"] = "minor";
578
+ Release$1["PATCH"] = "patch";
579
+ return Release$1;
580
+ }({});
581
+ let PreRelease = /* @__PURE__ */ function(PreRelease$1) {
582
+ PreRelease$1["MAJOR"] = "premajor";
583
+ PreRelease$1["MINOR"] = "preminor";
584
+ PreRelease$1["PATCH"] = "prepatch";
585
+ PreRelease$1["RELEASE"] = "prerelease";
586
+ return PreRelease$1;
587
+ }({});
588
+ let PreId = /* @__PURE__ */ function(PreId$1) {
589
+ PreId$1["ALPHA"] = "alpha";
590
+ PreId$1["BETA"] = "beta";
591
+ PreId$1["RC"] = "rc";
592
+ PreId$1["NET"] = "next";
593
+ PreId$1["CANARY"] = "canary";
594
+ PreId$1["NIGHT"] = "nightly";
595
+ PreId$1["DEV"] = "dev";
596
+ return PreId$1;
597
+ }({});
598
+ const runVersionPrompts = async (ctx, config) => {
599
+ const { pkg: { current, fromPreRelease } } = ctx;
600
+ const selectVersion = async () => {
601
+ const patch = inc(current, Release.PATCH);
602
+ const minor = inc(current, Release.MINOR);
603
+ const major = inc(current, Release.MAJOR);
604
+ const prerelease$1 = inc(current, PreRelease.RELEASE);
605
+ const prepatch = inc(current, PreRelease.PATCH, PreId.BETA);
606
+ const preminor = inc(current, PreRelease.MINOR, PreId.BETA);
607
+ const premajor = inc(current, PreRelease.MAJOR, PreId.BETA);
608
+ const pr = [{
609
+ label: "Pre-Release",
610
+ value: prerelease$1,
611
+ hint: prerelease$1
612
+ }];
613
+ const pre = [
614
+ {
615
+ label: "Pre-Patch",
616
+ value: prepatch,
617
+ hint: prepatch
618
+ },
619
+ {
620
+ label: "Pre-Minor",
621
+ value: preminor,
622
+ hint: preminor
623
+ },
624
+ {
625
+ label: "Pre-Major",
626
+ value: premajor,
627
+ hint: premajor
628
+ }
629
+ ];
630
+ const release = [
631
+ {
632
+ label: "Patch",
633
+ value: patch,
634
+ hint: patch
635
+ },
636
+ {
637
+ label: "Minor",
638
+ value: minor,
639
+ hint: minor
640
+ },
641
+ {
642
+ label: "Major",
643
+ value: major,
644
+ hint: major
708
645
  }
646
+ ];
647
+ const custom = [{
648
+ label: "As-Is",
649
+ value: "ignore",
650
+ hint: current
651
+ }, {
652
+ label: "Custom",
653
+ value: "custom",
654
+ hint: "custom specified"
655
+ }];
656
+ const version = await select({
657
+ message: question(MSG.PROMPT.SELECT_VERSION, "version"),
658
+ options: [
659
+ ...fromPreRelease ? pr : [],
660
+ ...release,
661
+ ...fromPreRelease ? [] : pre,
662
+ ...custom
663
+ ],
664
+ initialValue: fromPreRelease ? prerelease$1 : patch
709
665
  });
710
- }));
711
- };
712
- const createWebRelease = async (ctx, config) => {
713
- const { pkg: { next, toPreRelease }, github: { owner, repo, changelog } } = ctx;
714
- const { github: { prerelease: prerelease$1, releaseName } } = config;
715
- const github = getGithubUrl(owner, repo);
716
- const url = new URL(`${github}/releases/new`);
717
- url.searchParams.set("tag", next);
718
- url.searchParams.set("title", format(releaseName, ctx) ?? `Release ${next}`);
719
- url.searchParams.set("body", truncateBody(changelog));
720
- url.searchParams.set("prelease", String(toPreRelease || prerelease$1));
721
- const isWindows = process.platform === "win32";
722
- await open(url.toString(), { wait: isWindows });
723
- };
724
- const createCiRelease = async (ctx, config) => {
725
- const { pkg: { next, toPreRelease }, github: { owner, repo, token, changelog } } = ctx;
726
- const { github: { prerelease: prerelease$1, releaseName, draft } } = config;
727
- const octokit = new Octokit({ auth: token });
728
- try {
729
- const { data } = await octokit.repos.createRelease({
730
- owner,
731
- repo,
732
- tag_name: next,
733
- name: format(releaseName, ctx) ?? `Release ${next}`,
734
- body: truncateBody(changelog),
735
- draft: draft ?? false,
736
- prerelease: (toPreRelease || prerelease$1) ?? false
737
- });
738
- const { html_url, upload_url, id, discussion_url } = data;
739
- Object.assign(ctx.github, {
740
- isReleased: true,
741
- releaseId: id,
742
- releaseUrl: html_url,
743
- uploadUrl: upload_url,
744
- discussionUrl: discussion_url
666
+ abortSinglePrompt(version);
667
+ return version;
668
+ };
669
+ const inputVersion = async () => {
670
+ const version = await text({
671
+ message: question(MSG.PROMPT.INPUT_VERSION, "version"),
672
+ initialValue: current,
673
+ validate: (val) => {
674
+ if (!isValid(val)) return "Invalid version";
675
+ }
745
676
  });
746
- } catch (e) {
747
- if (e?.status === 422) throw new Error(MSG.ERROR.GITHUB_TAG_EXIT(next));
748
- throw e;
749
- }
750
- };
751
- const parseGithubUrl = (ctx) => {
752
- const { git: { remoteUrl } } = ctx;
753
- const [owner, repo] = parseGitHubRepo(remoteUrl);
754
- Object.assign(ctx.github, {
755
- owner,
756
- repo
757
- });
677
+ abortSinglePrompt(version);
678
+ return version;
679
+ };
680
+ const type = await selectVersion();
681
+ const next = type === "custom" ? await inputVersion() : type === "ignore" ? current : type;
682
+ ctx.isIncrement = gte(next, current);
683
+ return { next };
758
684
  };
759
- const createRelease = async (ctx, config) => {
760
- const { github: { owner, repo, skipChecks }, ci } = ctx;
761
- const { github: { tokenRef, release } } = config;
762
- if (!release) return;
763
- const token = tokenRef ? process.env[tokenRef] : process.env.GITHUB_TOKEN;
764
- if (!token) {
765
- Object.assign(ctx.github, { isWeb: true });
766
- if (ci && process.env.GITHUB_ACTIONS) throw new Error(MSG.ERROR.GITHUB_TOKEN(tokenRef));
767
- }
768
- Object.assign(ctx.github, { token });
769
- const octokit = new Octokit({ auth: token });
770
- if (token && !skipChecks) if (process.env.GITHUB_ACTIONS) Object.assign(ctx.github, { username: process.env.GITHUB_ACTOR });
771
- else {
772
- try {
773
- const { data: { login: username } } = await octokit.users.getAuthenticated();
774
- Object.assign(ctx.github, { username });
775
- } catch (e) {
776
- throw new Error(MSG.ERROR.GITHUB_AUTH);
777
- }
778
- try {
779
- const { github: { username } } = ctx;
780
- await octokit.repos.checkCollaborator({
781
- owner,
782
- repo,
783
- username
784
- });
785
- } catch (e) {
786
- throw new Error(MSG.ERROR.GITHUB_USER);
685
+
686
+ //#endregion
687
+ //#region src/defaults.ts
688
+ const createDefaultContext = () => {
689
+ return {
690
+ isCI: false,
691
+ release: false,
692
+ showRelease: false,
693
+ showChangelog: false,
694
+ dryRun: false,
695
+ noGit: false,
696
+ noNpm: false,
697
+ noGitHub: false,
698
+ increment: "",
699
+ isIncrement: false,
700
+ configFileExists: false,
701
+ pkg: {
702
+ current: "",
703
+ next: "",
704
+ name: "",
705
+ isPrivate: false,
706
+ fromPreRelease: false,
707
+ toPreRelease: false,
708
+ publishConfig: {
709
+ access: "public",
710
+ registry: "https://registry.npmjs.org"
711
+ },
712
+ preId: PreId.BETA,
713
+ preBase: "0"
714
+ },
715
+ npm: {
716
+ username: "",
717
+ otp: "",
718
+ tag: "latest"
719
+ },
720
+ git: {
721
+ remoteName: "origin",
722
+ remoteUrl: "",
723
+ latestTag: "",
724
+ previousTag: "",
725
+ currentTag: "",
726
+ isCommitted: false,
727
+ isTagged: false,
728
+ commitMessage: "",
729
+ tagMessage: "",
730
+ tagName: ""
731
+ },
732
+ github: {
733
+ username: "",
734
+ repo: "",
735
+ changelog: "",
736
+ owner: "",
737
+ isWeb: false,
738
+ isReleased: false,
739
+ releaseId: 0,
740
+ releaseUrl: "",
741
+ uploadUrl: "",
742
+ discussionUrl: "",
743
+ token: "",
744
+ releaseName: ""
787
745
  }
788
- }
789
- const { github: { isWeb }, dryRun } = ctx;
790
- if (dryRun) return true;
791
- if (isWeb) await createWebRelease(ctx, config);
792
- else if (ci) {
793
- await createCiRelease(ctx, config);
794
- await uploadAssets(ctx, config);
795
- }
746
+ };
796
747
  };
797
- const getGithubReleaseUrl = (ctx) => {
798
- const { github: { owner, repo }, git: { currentTag } } = ctx;
799
- return `${getGithubUrl(owner, repo)}/releases/tag/${normalizeTag(currentTag)}`;
748
+ const createDefaultConfig = (isCI) => {
749
+ return {
750
+ hooks: {
751
+ "before:bump": "",
752
+ "after:bump": "",
753
+ "before:publish": "",
754
+ "after:publish": "",
755
+ "before:push": "",
756
+ "after:push": "",
757
+ "before:release": "",
758
+ "after:release": ""
759
+ },
760
+ git: {
761
+ commit: isCI,
762
+ tag: isCI,
763
+ push: isCI,
764
+ commitMessage: "chore(release): ${version}",
765
+ commitArgs: [],
766
+ tagMessage: "Release ${version}",
767
+ tagName: "${version}",
768
+ tagArgs: [],
769
+ pushArgs: [],
770
+ addUntrackedFiles: false,
771
+ requireRemote: true,
772
+ requireRepository: true,
773
+ requireWorkDirClean: true
774
+ },
775
+ npm: {
776
+ publish: isCI,
777
+ publishPath: ".",
778
+ pushArgs: [],
779
+ skipChecks: false
780
+ },
781
+ github: {
782
+ release: isCI,
783
+ releaseName: "Release ${version}",
784
+ autoGenerate: false,
785
+ prerelease: false,
786
+ draft: false,
787
+ tokenRef: "GITHUB_TOKEN",
788
+ assets: [],
789
+ skipChecks: false
790
+ },
791
+ changelog: {
792
+ infile: "CHANGELOG.md",
793
+ types: []
794
+ }
795
+ };
800
796
  };
801
797
 
802
798
  //#endregion
@@ -893,6 +889,13 @@ const defaultTypes = [
893
889
  description: "破坏性变更"
894
890
  }
895
891
  ];
892
+ const mergeTypes = (types) => {
893
+ types.forEach((item) => {
894
+ const index = defaultTypes.findIndex((it) => it.type === item.type);
895
+ if (index >= 0) defaultTypes.splice(index, 1, { ...item });
896
+ else defaultTypes.push({ ...item });
897
+ });
898
+ };
896
899
  const findType = (type) => {
897
900
  return defaultTypes.find((item) => item.type === type)?.section ?? type;
898
901
  };
@@ -927,24 +930,20 @@ const classify = (commits) => {
927
930
  return sections;
928
931
  };
929
932
  const renderChangelog = async (ctx, sections) => {
930
- const { pkg: { next: to }, github: { owner, repo }, git: { latestTag } } = ctx;
933
+ const { github: { owner, repo }, git: { latestTag, currentTag } } = ctx;
931
934
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
932
935
  const github = getGithubUrl(owner, repo);
933
- const eol$1 = (n$1) => EOL.repeat(n$1);
934
- let md;
935
- if (!latestTag) md = `## ${to} (${date})${eol$1(2)}`;
936
- else md = `## [${to}](${github}/compare/${latestTag}...${to}) (${date})${eol$1(2)}`;
937
- for (const [type, items] of Object.entries(sections)) {
938
- if (!items.length) continue;
939
- md += `### ${findType(type)}${eol$1(2)}`;
940
- for (const [desc, hash, fullHash] of items) md += `* **${type}** ${desc} [${hash}](${github}/commit/${fullHash})${eol$1(1)}`;
941
- md += eol$1(2);
942
- }
943
- if (latestTag) {
944
- md += `**Full Changelog**: ${github}/compare/${latestTag}...${to}`;
945
- md += eol$1(3);
946
- }
947
- return md;
936
+ return [
937
+ latestTag ? `## [${currentTag}](${github}/compare/${latestTag}...${currentTag}) (${date})` : `## ${currentTag} (${date})`,
938
+ Object.entries(sections).map((entry) => {
939
+ const [type, items] = entry;
940
+ return [`### ${findType(type)}`, items.map((item) => {
941
+ const [desc, hash, fullHash] = item;
942
+ return `* **${type}** ${desc} [${hash}](${github}/commit/${fullHash})`;
943
+ }).join(eol(1))].join(eol(2));
944
+ }).join(eol(2)),
945
+ latestTag ? `**Full Changelog**: ${github}/compare/${latestTag}...${currentTag}` : ""
946
+ ].join(eol(3));
948
947
  };
949
948
  const generateChangelog = async (ctx) => {
950
949
  const { isIncrement } = ctx;
@@ -954,36 +953,46 @@ const generateChangelog = async (ctx) => {
954
953
  Object.assign(ctx.github, { changelog });
955
954
  return changelog;
956
955
  };
957
- const updateChangelog = async (newContent) => {
958
- const file = "CHANGELOG.md";
959
- const title = `# Changelog${eol(3)}`;
960
- await writeFile(file, `${(existsSync(file) && await readFile(file, "utf-8") || title).replace(/* @__PURE__ */ new RegExp(`^${title}`), `${title}${newContent}`).trim()}${eol()}`);
961
- await runGit(["add", file]);
956
+ const updateChangelog = async (file, newContent) => {
957
+ const infile = file || "CHANGELOG.md";
958
+ const target = join(process.cwd(), infile);
959
+ const SHORT_TITLE = "# Changelog";
960
+ const exist = (existsSync(target) ? await readFile(target, "utf-8") : "").replace(/\r\n/g, "\n");
961
+ await writeFile(target, [
962
+ SHORT_TITLE,
963
+ newContent,
964
+ exist.startsWith(SHORT_TITLE) ? exist.slice(11) : exist
965
+ ].map((k) => k.trim()).join(eol(4)) + eol());
966
+ await runGit(["add", infile]);
967
+ };
968
+ const writeChangelog = async (ctx, config) => {
969
+ const { changelog: { infile, types } } = config;
970
+ mergeTypes(types);
971
+ await updateChangelog(infile, await generateChangelog(ctx));
962
972
  };
963
973
 
964
974
  //#endregion
965
975
  //#region src/git/prompts.ts
966
976
  const runGitPrompts = async (ctx, config) => {
967
- const { git: { currentTag }, configFileExists: cfe, dryRun } = ctx;
968
- const { git: { commit, tag, push, commitMessage } } = config;
977
+ const { git: { currentTag, commitMessage }, configFileExists: cfe, dryRun } = ctx;
978
+ const { git: { commit, tag, push } } = config;
969
979
  const logs = [];
970
- const cMsg = format(commitMessage, ctx);
971
980
  const shouldPrompt = (val) => isUndefined(val) || dryRun;
972
981
  const { log: _, ...reset } = await group({
973
982
  commit: () => {
974
- return shouldPrompt(commit) ? confirm({ message: prompt(MSG.PROMPT.GIT_COMMIT(cMsg)) }) : (() => {
983
+ return shouldPrompt(commit) ? confirm({ message: question(MSG.PROMPT.GIT_COMMIT(commitMessage), "git") }) : (() => {
975
984
  logs.push(yellow`git.commit`);
976
985
  return Promise.resolve(commit);
977
986
  })();
978
987
  },
979
988
  tag: () => {
980
- return shouldPrompt(tag) ? confirm({ message: prompt(MSG.PROMPT.GIT_TAG(currentTag)) }) : (() => {
989
+ return shouldPrompt(tag) ? confirm({ message: question(MSG.PROMPT.GIT_TAG(currentTag), "git") }) : (() => {
981
990
  logs.push(yellow`git.tag`);
982
991
  return Promise.resolve(tag);
983
992
  })();
984
993
  },
985
994
  push: () => {
986
- return shouldPrompt(push) ? confirm({ message: prompt(MSG.PROMPT.GIT_PUSH) }) : (() => {
995
+ return shouldPrompt(push) ? confirm({ message: question(MSG.PROMPT.GIT_PUSH, "git") }) : (() => {
987
996
  logs.push(yellow`git.push`);
988
997
  return Promise.resolve(push);
989
998
  })();
@@ -992,9 +1001,8 @@ const runGitPrompts = async (ctx, config) => {
992
1001
  if (logs.length) msg("GIT", `Using ${cfe ? "user" : "default"} config: ${logs.join(", ")}`);
993
1002
  return Promise.resolve();
994
1003
  }
995
- }, { onCancel: async ({ results }) => {
996
- await gitRollback(ctx);
997
- abortTask("Operation cancelled.");
1004
+ }, { onCancel: ({ results }) => {
1005
+ abortGroupPrompt(ctx);
998
1006
  } });
999
1007
  Object.assign(config.git, reset);
1000
1008
  };
@@ -1002,13 +1010,12 @@ const runGitPrompts = async (ctx, config) => {
1002
1010
  //#endregion
1003
1011
  //#region src/github/prompts.ts
1004
1012
  const runGithubPrompts = async (ctx, config) => {
1005
- const { configFileExists: cfe, dryRun } = ctx;
1006
- const { github: { release, releaseName } } = config;
1013
+ const { configFileExists: cfe, dryRun, github: { releaseName } } = ctx;
1014
+ const { github: { release } } = config;
1007
1015
  const logs = [];
1008
- const title = format(releaseName, ctx);
1009
1016
  const shouldPrompt = (val) => isUndefined(val) || dryRun;
1010
1017
  const res = await group({
1011
- release: () => shouldPrompt(release) ? confirm({ message: prompt(MSG.PROMPT.GITHUB_RELEASE(title)) }) : (() => {
1018
+ release: () => shouldPrompt(release) ? confirm({ message: question(MSG.PROMPT.GITHUB_RELEASE(releaseName), "github") }) : (() => {
1012
1019
  logs.push(yellow`github.release`);
1013
1020
  return Promise.resolve(release);
1014
1021
  })(),
@@ -1016,9 +1023,8 @@ const runGithubPrompts = async (ctx, config) => {
1016
1023
  if (logs.length) msg("GITHUB", `Using ${cfe ? "user" : "default"} config: ${logs.join(", ")}`);
1017
1024
  return Promise.resolve();
1018
1025
  }
1019
- }, { onCancel: async ({ results }) => {
1020
- await gitRollback(ctx);
1021
- abortTask("Operation cancelled.");
1026
+ }, { onCancel: ({ results }) => {
1027
+ abortGroupPrompt(ctx);
1022
1028
  } });
1023
1029
  Object.assign(config.github, { release: res.release });
1024
1030
  return res;
@@ -1033,7 +1039,7 @@ const runNpmPublishPrompts = async (ctx, config) => {
1033
1039
  const target = `${name}@${next}`;
1034
1040
  const shouldPrompt = (val) => isUndefined(val) || dryRun;
1035
1041
  const res = await group({
1036
- publish: () => shouldPrompt(publish) ? confirm({ message: prompt(MSG.PROMPT.NPM_PUBLISH(target)) }) : (() => {
1042
+ publish: () => shouldPrompt(publish) ? confirm({ message: question(MSG.PROMPT.NPM_PUBLISH(target), "npm") }) : (() => {
1037
1043
  logs.push(yellow`npm.publish`);
1038
1044
  return Promise.resolve(publish);
1039
1045
  })(),
@@ -1042,16 +1048,14 @@ const runNpmPublishPrompts = async (ctx, config) => {
1042
1048
  return Promise.resolve();
1043
1049
  }
1044
1050
  }, { onCancel: async ({ results }) => {
1045
- await gitRollback(ctx);
1046
- abortTask("Operation cancelled.");
1051
+ abortGroupPrompt(ctx);
1047
1052
  } });
1048
1053
  Object.assign(config.npm, { publish: res.publish });
1049
1054
  return res;
1050
1055
  };
1051
1056
  const runNpmOptPrompts = async (ctx, config) => {
1052
- const res = await group({ otp: () => text({ message: MSG.PROMPT.NPM_OTP }) }, { onCancel: async ({ results }) => {
1053
- await gitRollback(ctx);
1054
- abortTask("Operation cancelled.");
1057
+ const res = await group({ otp: () => text({ message: MSG.PROMPT.NPM_OTP }) }, { onCancel: ({ results }) => {
1058
+ abortGroupPrompt(ctx);
1055
1059
  } });
1056
1060
  Object.assign(ctx.npm, { otp: res.otp });
1057
1061
  return res;
@@ -1059,22 +1063,17 @@ const runNpmOptPrompts = async (ctx, config) => {
1059
1063
 
1060
1064
  //#endregion
1061
1065
  //#region src/npm/publish.ts
1062
- const DEFAULT_TAG = "latest";
1063
- const DEFAULT_TAG_PRERELEASE = "next";
1064
- const runNpm = (args) => {
1065
- return execAsync(["npm", ...args].join(" "));
1066
- };
1067
1066
  const getRegistry = (publishConfig = {}) => {
1068
1067
  return (publishConfig.registry ? [publishConfig.registry] : Object.keys(publishConfig).filter((key) => key.endsWith("registry")).map((key) => publishConfig[key]))[0];
1069
1068
  };
1070
1069
  const isRegistryUp = async (registry) => {
1071
- return void 0 !== await runNpm(["ping", ...registry ? [`--registry ${registry}`] : []]);
1070
+ return void 0 !== await runNpm(["ping", ...registry ? ["--registry", registry] : []]);
1072
1071
  };
1073
1072
  const isAuthenticated = (registry) => {
1074
- return runNpm(["whoami", ...registry ? [`--registry ${registry}`] : []]);
1073
+ return runNpm(["whoami", ...registry ? ["--registry", registry] : []]);
1075
1074
  };
1076
1075
  const getLatestRegistryVersion = (name, tag, registry) => {
1077
- const registryArg = registry ? [`--registry ${registry}`] : [];
1076
+ const registryArg = registry ? ["--registry", registry] : [];
1078
1077
  return runNpm([
1079
1078
  "show",
1080
1079
  `${name}@${tag}`,
@@ -1082,16 +1081,6 @@ const getLatestRegistryVersion = (name, tag, registry) => {
1082
1081
  ...registryArg
1083
1082
  ]);
1084
1083
  };
1085
- const getRegistryTags = async (name, registry) => {
1086
- const res = await runNpm([
1087
- "view",
1088
- name,
1089
- "dist-tags",
1090
- "--json",
1091
- ...registry ? [`--registry ${registry}`] : []
1092
- ]);
1093
- return Object.keys(JSON.parse(res || "{}"));
1094
- };
1095
1084
  const bump = (version, allowSameVersion, args = []) => {
1096
1085
  allowSameVersion && args.push(`--allow-same-version`);
1097
1086
  return runNpm([
@@ -1102,20 +1091,15 @@ const bump = (version, allowSameVersion, args = []) => {
1102
1091
  ...args
1103
1092
  ]);
1104
1093
  };
1105
- const guessPreReleaseTag = async (name) => {
1106
- return (await getRegistryTags(name)).filter((tag) => DEFAULT_TAG !== tag)[0] ?? DEFAULT_TAG_PRERELEASE;
1107
- };
1108
- const resolveTag = async (ctx, config) => {
1109
- const { npm: { tag } } = config;
1110
- const { pkg: { name, current } } = ctx;
1111
- const { toPreRelease, preId } = parseVersion(current);
1112
- if (!toPreRelease) return DEFAULT_TAG;
1113
- else return tag || preId || await guessPreReleaseTag(name);
1094
+ const resolveTag = async (version) => {
1095
+ const { toPreRelease, preId } = parseVersion(version);
1096
+ if (!toPreRelease) return "latest";
1097
+ else return preId || "next";
1114
1098
  };
1115
1099
  const isCollaborator = async (ctx) => {
1116
- const { name, publishConfig } = ctx.pkg;
1100
+ const { pkg: { name, publishConfig }, npm: { username } } = ctx;
1117
1101
  const registry = getRegistry(publishConfig);
1118
- const registryArg = registry ? [`--registry ${registry}`] : [];
1102
+ const registryArg = registry ? ["--registry", registry] : [];
1119
1103
  let npmVersion = await runNpm(["--version"]);
1120
1104
  let accessCommand;
1121
1105
  if (gt(npmVersion, "9.0.0")) accessCommand = [
@@ -1131,88 +1115,82 @@ const isCollaborator = async (ctx) => {
1131
1115
  ...registryArg
1132
1116
  ]);
1133
1117
  if (res) {
1134
- const permissions = JSON.parse(res ?? "{}")[ctx.npm.username];
1118
+ const permissions = JSON.parse(res ?? "{}")[username];
1135
1119
  return permissions && permissions.includes("write");
1136
1120
  }
1137
- log.error(`Unable to verify if user ${ctx.npm.username} is a collaborator for ${name}.`);
1121
+ log.error(`Unable to verify if user ${username} is a collaborator for ${name}.`);
1138
1122
  };
1139
1123
  const getPackageUrl = (ctx) => {
1140
- const { name } = ctx.pkg;
1141
- return joinUrl([
1142
- "https://www.npmjs.com",
1143
- "/package",
1144
- name
1145
- ]);
1124
+ const { pkg: { name, next } } = ctx;
1125
+ return `https://www.npmjs.com/package/${name}/v/${next}`;
1146
1126
  };
1147
1127
  const npmCheck = async (ctx, config) => {
1148
- const { pkg: { name, publishConfig }, npm: { username } } = ctx;
1128
+ const { pkg: { name, current, publishConfig }, npm: { username } } = ctx;
1129
+ const { npm: { skipChecks } } = config;
1149
1130
  const registry = getRegistry(publishConfig);
1150
- const tag = await resolveTag(ctx, config);
1131
+ const tag = await resolveTag(current);
1132
+ Object.assign(ctx.npm, { tag });
1133
+ if (skipChecks) return "";
1134
+ if (!await isRegistryUp()) throw new Error(MSG.ERROR.NPM_REGISTRY(registry));
1151
1135
  const getUser = async () => {
1152
1136
  const username$1 = await isAuthenticated(registry);
1153
1137
  if (!username$1) return false;
1154
1138
  Object.assign(ctx.npm, { username: username$1 });
1155
1139
  return true;
1156
1140
  };
1157
- if (!await isRegistryUp()) throw new Error(MSG.ERROR.NPM_REGISTRY(registry));
1158
1141
  if (!await getUser()) throw new Error(MSG.ERROR.NPM_AUTH);
1159
1142
  const latestPublish = await getLatestRegistryVersion(name, tag, registry);
1160
1143
  if (latestPublish) {
1161
1144
  if (!await isCollaborator(ctx)) throw new Error(MSG.ERROR.NPM_USER(username, name));
1162
1145
  }
1163
- return dim(latestPublish ? `(latest is ${latestPublish})` : "(new publish)");
1146
+ return underline(dim(latestPublish ? `(latest is ${latestPublish})` : "(new publish)"));
1164
1147
  };
1165
1148
  const isOtpError = (err) => /one-time password|otp/i.test(err.message);
1166
1149
  const publishNpm = async (ctx, config) => {
1167
- const { dryRun, npm: { otp } } = ctx;
1168
- const { npm: { publish, tag } } = config;
1150
+ const { dryRun, npm: { otp, tag }, pkg: { publishConfig } } = ctx;
1151
+ const { npm: { publish, publishPath, pushArgs } } = config;
1152
+ const registry = getRegistry(publishConfig);
1169
1153
  if (!publish) return;
1170
- const args = ["publish"];
1171
- if (tag) args.push("--tag", tag);
1172
- if (otp) args.push("--otp", otp);
1173
- if (dryRun) args.push("--dry-run");
1174
- return runNpm(args);
1154
+ const otpArgs = otp ? ["--otp", otp] : [];
1155
+ const dryRunArg = dryRun ? ["--dry-run"] : [];
1156
+ const registryArg = registry ? ["--registry", registry] : [];
1157
+ return runNpm([
1158
+ "publish",
1159
+ publishPath,
1160
+ "--tag",
1161
+ tag,
1162
+ "--workspaces=false",
1163
+ ...otpArgs,
1164
+ ...pushArgs,
1165
+ ...registryArg,
1166
+ ...dryRunArg
1167
+ ]);
1175
1168
  };
1176
1169
 
1177
1170
  //#endregion
1178
1171
  //#region src/action.ts
1179
- const info = (msg$1) => {
1180
- console.log(`${rgb(33, 91, 184)(`ℹ`)} ${gray(msg$1)}`);
1181
- };
1182
- const success = (msg$1, dryRun) => {
1183
- return `${(dryRun ? rgb(123, 115, 66) : green)("✔")} ${msg$1}`;
1184
- };
1185
- const runLifeCycleHook = async (hooks, key, dryRun) => {
1186
- const handler = hooks[key];
1187
- if (typeof handler === "function") {
1188
- if (!dryRun) await handler();
1189
- log.success(success(`${dim`run`} ${inspect(handler)}`, dryRun));
1190
- return;
1191
- }
1192
- return Promise.allSettled(castArray(handler).map(async (hook) => {
1193
- if (!dryRun) await execAsync(hook).catch(console.error);
1194
- handler && log.success(success(hook.replace("echo", dim("echo")), dryRun));
1195
- }));
1196
- };
1197
1172
  var Action = class {
1198
1173
  async handle(cmdArgs, options) {
1199
- const { version: cVersion, name: cName } = readJsonFile(join(__dirname$1, "../package.json"));
1174
+ const { version: cVersion, name: cName } = readJsonFile(join(__dirname, "../package.json"));
1200
1175
  info(`${cName} ${dim(`v${cVersion}`)} `);
1201
1176
  const { otp, ...others } = options;
1202
1177
  const { showChangelog, showRelease, ci, dryRun } = Object.fromEntries(Object.entries(others).map(([k, v]) => [k, Boolean(v)]));
1178
+ process.env["dryRun"] = String(dryRun);
1203
1179
  const { version: pkgVersion, private: pkgPrivate, name: pkgName, publishConfig = {} } = readJsonFile("package.json");
1204
1180
  const nextVersion = inc(pkgVersion, cmdArgs) ?? "";
1205
1181
  const local = await resolveConfig(process.cwd(), (path$1) => {
1206
- info(`config file: ${underline(path$1)}`);
1182
+ info(`config file: ${underline(dim(path$1))}`);
1207
1183
  });
1208
1184
  const config = mergeConfig(createDefaultConfig(ci ? true : void 0), local);
1209
1185
  const ctx = mergeConfig(createDefaultContext(), {
1210
1186
  configFileExists: Object.keys(local).length > 0,
1211
1187
  dryRun,
1188
+ showRelease,
1189
+ showChangelog,
1212
1190
  npm: { otp: String(otp) },
1213
1191
  increment: cmdArgs,
1214
1192
  isIncrement: true,
1215
- ci: [
1193
+ isCI: [
1216
1194
  ci,
1217
1195
  showChangelog,
1218
1196
  showRelease,
@@ -1224,14 +1202,25 @@ var Action = class {
1224
1202
  isPrivate: pkgPrivate,
1225
1203
  fromPreRelease: isPreRelease(pkgVersion),
1226
1204
  current: pkgVersion,
1227
- next: "",
1205
+ next: nextVersion,
1228
1206
  toPreRelease: false,
1229
1207
  publishConfig: { ...publishConfig }
1230
- }
1208
+ },
1209
+ noNpm: pkgPrivate
1231
1210
  });
1232
1211
  console.log();
1233
1212
  intro(MSG.INTRO(pkgName, dryRun));
1234
- const { hooks } = config;
1213
+ await this.checkTask(ctx, config);
1214
+ await this.bumpTask(ctx, config);
1215
+ await this.changelogTask(ctx, config);
1216
+ await this.gitTask(ctx, config);
1217
+ await this.npmTask(ctx, config);
1218
+ await this.githubTask(ctx, config);
1219
+ if (dryRun) gitRollback$1(ctx);
1220
+ taskEnd(MSG.OUTRO(dryRun));
1221
+ }
1222
+ async checkTask(ctx, config) {
1223
+ const { noNpm, dryRun } = ctx;
1235
1224
  await tasks([{
1236
1225
  title: MSG.CHECK.GIT.CHECKING,
1237
1226
  task: async () => {
@@ -1245,73 +1234,58 @@ var Action = class {
1245
1234
  const msg$1 = await npmCheck(ctx, config);
1246
1235
  const { pkg: { publishConfig: { registry } } } = ctx;
1247
1236
  return success(MSG.CHECK.NPM.CHECKED(registry, msg$1), dryRun);
1248
- }
1237
+ },
1238
+ enabled: !noNpm
1249
1239
  }]).catch(abortOnError);
1250
- {
1251
- const { pkg: { current, next }, isIncrement, ci: ci$1 } = ctx;
1252
- if (ci$1) {
1253
- if (!next && isIncrement) {
1254
- const next$1 = nextVersion || getCIVersion(current);
1255
- if (next$1) {
1256
- const parsed = parseVersion(next$1);
1257
- Object.assign(ctx.pkg, { ...parsed });
1258
- }
1259
- }
1260
- if (showRelease) {
1261
- const { pkg: { next: next$1 } } = ctx;
1262
- taskEnd(MSG.LOG.SHOW_VERSION(next$1));
1263
- }
1264
- }
1265
- }
1266
- {
1267
- const logStr = await getLog(true, ctx.isIncrement);
1268
- if (logStr) {
1269
- msg("GIT", `Changelog:${eol(2)}` + dim(logStr));
1270
- if (showChangelog) taskEnd(MSG.LOG.SHOW_CHANGELOG);
1271
- } else msg("GIT", MSG.LOG.CHANGELOG_EMPTY);
1272
- }
1273
- {
1274
- const { pkg: { next }, isIncrement } = ctx;
1275
- if (!next && isIncrement) await runVersionPrompts(ctx, config);
1276
- await runLifeCycleHook(hooks, "before:bump", dryRun);
1277
- await tasks([{
1278
- title: MSG.TASK.VERSION.START,
1279
- task: async () => {
1280
- const { pkg: { next: next$1 }, isIncrement: isIncrement$1 } = ctx;
1281
- await bump(next$1, isIncrement$1);
1282
- await resolveTag$1(ctx, config);
1283
- await updateChangelog(await generateChangelog(ctx));
1284
- return success(MSG.TASK.VERSION.END(next$1), dryRun);
1285
- }
1286
- }]).catch(abortOnError);
1287
- await runLifeCycleHook(hooks, "after:bump", dryRun);
1288
- }
1289
- {
1290
- const changeset = await getStatus();
1291
- if (changeset) msg("GIT", `Changes:${eol(2)}` + coloredChangeset(changeset));
1292
- else msg("GIT", MSG.LOG.CHANGES_EMPTY);
1240
+ }
1241
+ async bumpTask(ctx, config) {
1242
+ const { pkg: { current }, dryRun, isCI, showRelease, showChangelog } = ctx;
1243
+ const { hooks } = config;
1244
+ const need = (ctx$1) => {
1245
+ const { pkg: { next: next$1 }, isIncrement: isIncrement$1 } = ctx$1;
1246
+ return !next$1 && isIncrement$1;
1247
+ };
1248
+ if (need(ctx)) {
1249
+ const parsed = parseVersion(isCI && getCIVersion(current) || (await runVersionPrompts(ctx, config)).next);
1250
+ Object.assign(ctx.pkg, { ...parsed });
1293
1251
  }
1294
- {
1295
- const { pkg: { isPrivate }, ci: ci$1 } = ctx;
1296
- if (!isPrivate) {
1297
- await runNpmPublishPrompts(ctx, config);
1298
- await runLifeCycleHook(hooks, "before:publish", dryRun);
1299
- await tasks([{
1300
- title: MSG.TASK.NPM.START,
1301
- task: async () => {
1302
- await publishNpm(ctx, config).catch(async (err) => {
1303
- if (isOtpError(err) && !ci$1) {
1304
- await runNpmOptPrompts(ctx, config);
1305
- await publishNpm(ctx, config);
1306
- } else throw err;
1307
- });
1308
- const url = getPackageUrl(ctx);
1309
- return success(MSG.TASK.NPM.END + `: ${url}`, dryRun);
1310
- }
1311
- }]).catch(abortOnError);
1312
- await runLifeCycleHook(hooks, "after:publish", dryRun);
1252
+ const { pkg: { next }, isIncrement } = ctx;
1253
+ if (showRelease) taskEnd(MSG.LOG.SHOW_VERSION(next));
1254
+ await runLifeCycleHook(hooks, "before:bump", dryRun);
1255
+ await tasks([{
1256
+ title: MSG.TASK.VERSION.START,
1257
+ task: async () => {
1258
+ await bump(next, isIncrement);
1259
+ await formatTemplate(ctx, config);
1260
+ const to = `(${current}...${diff(current, next)})`;
1261
+ return success(MSG.TASK.VERSION.END(to), dryRun);
1313
1262
  }
1314
- }
1263
+ }]).catch((err) => abortOnError(err, ctx));
1264
+ await runLifeCycleHook(hooks, "after:bump", dryRun);
1265
+ const logStr = (await getLog(true, isIncrement))?.split("\n").filter((line) => /^(\*\s+)?(\w+):\s(.+)\s\((\w{7})\)$/.test(line)).join("\n");
1266
+ if (logStr) {
1267
+ msg("GIT", `Changelog:${eol(2)}` + dim(logStr));
1268
+ if (showChangelog) taskEnd(MSG.LOG.SHOW_CHANGELOG);
1269
+ } else msg("GIT", MSG.LOG.CHANGELOG_EMPTY);
1270
+ const changeset = await getStatus();
1271
+ if (changeset) msg("GIT", `Changes:${eol(2)}` + coloredChangeset(changeset));
1272
+ else msg("GIT", MSG.LOG.CHANGES_EMPTY);
1273
+ }
1274
+ async changelogTask(ctx, config) {
1275
+ const { isIncrement, dryRun } = ctx;
1276
+ await tasks([{
1277
+ title: MSG.TASK.CHANGELOG.START,
1278
+ task: async () => {
1279
+ await writeChangelog(ctx, config);
1280
+ return success(MSG.TASK.CHANGELOG.END, dryRun);
1281
+ },
1282
+ enabled: isIncrement
1283
+ }]).catch((err) => abortOnError(err, ctx));
1284
+ }
1285
+ async gitTask(ctx, config) {
1286
+ const { noGit, dryRun } = ctx;
1287
+ const { hooks } = config;
1288
+ if (noGit) return;
1315
1289
  await runGitPrompts(ctx, config);
1316
1290
  await runLifeCycleHook(hooks, "before:push", dryRun);
1317
1291
  await tasks([{
@@ -1320,8 +1294,34 @@ var Action = class {
1320
1294
  await commitAndTag(ctx, config);
1321
1295
  return success(MSG.TASK.GIT.END, dryRun);
1322
1296
  }
1323
- }]).catch(abortOnError);
1297
+ }]).catch((err) => abortOnError(err, ctx));
1324
1298
  await runLifeCycleHook(hooks, "after:push", dryRun);
1299
+ }
1300
+ async npmTask(ctx, config) {
1301
+ const { noNpm, dryRun, isCI } = ctx;
1302
+ const { hooks } = config;
1303
+ if (noNpm) return;
1304
+ await runNpmPublishPrompts(ctx, config);
1305
+ await runLifeCycleHook(hooks, "before:publish", dryRun);
1306
+ await tasks([{
1307
+ title: MSG.TASK.NPM.START,
1308
+ task: async () => {
1309
+ await publishNpm(ctx, config).catch(async (err) => {
1310
+ if (isOtpError(err) && !isCI) {
1311
+ await runNpmOptPrompts(ctx, config);
1312
+ await publishNpm(ctx, config);
1313
+ } else throw err;
1314
+ });
1315
+ const url = getPackageUrl(ctx);
1316
+ return success(MSG.TASK.NPM.END(url), dryRun);
1317
+ }
1318
+ }]).catch((err) => abortOnError(err, ctx));
1319
+ await runLifeCycleHook(hooks, "after:publish", dryRun);
1320
+ }
1321
+ async githubTask(ctx, config) {
1322
+ const { noGitHub, dryRun } = ctx;
1323
+ const { hooks } = config;
1324
+ if (noGitHub) return;
1325
1325
  await runGithubPrompts(ctx, config);
1326
1326
  await runLifeCycleHook(hooks, "before:release", dryRun);
1327
1327
  await tasks([{
@@ -1329,12 +1329,10 @@ var Action = class {
1329
1329
  task: async () => {
1330
1330
  await createRelease(ctx, config);
1331
1331
  const url = getGithubReleaseUrl(ctx);
1332
- return success(MSG.TASK.GITHUB.END + `: ${url}`, dryRun);
1332
+ return success(MSG.TASK.GITHUB.END(url), dryRun);
1333
1333
  }
1334
- }]).catch(abortOnError);
1334
+ }]).catch((err) => abortOnError(err, ctx));
1335
1335
  await runLifeCycleHook(hooks, "after:release", dryRun);
1336
- if (dryRun) await gitRollback(ctx);
1337
- taskEnd(MSG.OUTRO(dryRun));
1338
1336
  }
1339
1337
  };
1340
1338