@peiyanlu/release 0.0.5 → 0.0.7
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 +1 -1
- package/README.md +43 -1
- package/dist/cli.mjs +338 -282
- package/dist/index.d.mts +47 -6
- package/dist/index.mjs +320 -3
- package/package.json +4 -4
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -65,6 +65,18 @@ pnpm release minor --ci
|
|
|
65
65
|
| `--show-changelog` | 打印生成的 Changelog 并退出 |
|
|
66
66
|
| `--ci` | 启用 CI 模式:禁用交互提示,缺少必要参数时直接失败;GitHub Actions 等 CI/CD 环境中默认启用 |
|
|
67
67
|
|
|
68
|
+
### Prepare 相关参数
|
|
69
|
+
|
|
70
|
+
| 参数 | 说明 |
|
|
71
|
+
|-------------|--------------------------|
|
|
72
|
+
| `--prepare` | 仅仅变更版本、提交代码、创建 Tag、推送到远程 |
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
pnpm release minor --prepare --ci --package pkg-a
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
> 后续可以通过 tag 进行 npm 发布和创建 GitHub release
|
|
79
|
+
|
|
68
80
|
### Monorepo / CI 相关参数
|
|
69
81
|
|
|
70
82
|
| 参数 | 说明 |
|
|
@@ -72,7 +84,7 @@ pnpm release minor --ci
|
|
|
72
84
|
| `-p, --package <pkg>` | 指定要发布的子包名称(仅用于 Monorepo 的 CI 场景) |
|
|
73
85
|
|
|
74
86
|
```bash
|
|
75
|
-
pnpm release minor --ci --package
|
|
87
|
+
pnpm release minor --ci --package pkg-a
|
|
76
88
|
```
|
|
77
89
|
|
|
78
90
|
### npm 发布相关参数
|
|
@@ -268,6 +280,36 @@ GitHub Release 可基于 Git Tag 自动创建,并支持附带:
|
|
|
268
280
|
|
|
269
281
|
工具默认配置偏向于 **单仓库(Single Repo)** 项目,但是可以通过配置使其支持 **Monorepo**
|
|
270
282
|
|
|
283
|
+
## 流程拆分
|
|
284
|
+
|
|
285
|
+
### 本地准备 Release(仅创建 Tag)
|
|
286
|
+
|
|
287
|
+
在本地完成版本确认与变更准备:
|
|
288
|
+
|
|
289
|
+
- 确定下一个版本
|
|
290
|
+
- 更新 `package.json` / `CHANGELOG`
|
|
291
|
+
- 创建发布提交
|
|
292
|
+
- 创建并推送 git 标签
|
|
293
|
+
|
|
294
|
+
### CI/CD 自动发布(由 Tag 触发)
|
|
295
|
+
|
|
296
|
+
当 Tag 推送到远端后,CI/CD 自动执行:
|
|
297
|
+
|
|
298
|
+
- 发布到 npm
|
|
299
|
+
- 创建 GItHub release
|
|
300
|
+
|
|
301
|
+
```ts
|
|
302
|
+
// publishCI.ts
|
|
303
|
+
import { publishTagToNpm } from '@peiyanlu/release'
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
await publishTagToNpm({
|
|
307
|
+
gitTag: '1.0.16',
|
|
308
|
+
getPkgDir: () => '.',
|
|
309
|
+
defaultPackage: '.',
|
|
310
|
+
})
|
|
311
|
+
```
|
|
312
|
+
|
|
271
313
|
## Dry Run
|
|
272
314
|
|
|
273
315
|
```bash
|
package/dist/cli.mjs
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
import path, { join, resolve } from "node:path";
|
|
2
2
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
3
|
-
import { bumpPackageVersion, coloredChangeset, eol, execAsync, getAllRemotes, getAuthenticatedUser, getCurrentBranch, getGithubReleaseUrl, getGithubUrl, getLatestTag, getLog, getPackageInfo, getPackageUrl, getPublishedVersion, getRemote, getStatus, gitAddAll, gitAddTracked, gitCommit, gitTagAnnotated, hasWriteAccess, isGitRepo, isWorkingDirClean, parseGitHubRepo, pingRegistry, publishPackage, pushBranch, pushTag, readJsonFile, resolveChangelogRange
|
|
3
|
+
import { bumpPackageVersion, coloredChangeset, eol, execAsync, getAllRemotes, getAuthenticatedUser, getCurrentBranch, getGithubReleaseUrl, getGithubUrl, getLatestTag, getLog, getPackageInfo, getPackageUrl, getPublishedVersion, getRemote, getStatus, gitAddAll, gitAddTracked, gitCommit, gitTagAnnotated, hasWriteAccess, isGitRepo, isPrerelease, isValid, isWorkingDirClean, parseGitHubRepo, parseVersion, pingRegistry, publishPackage, pushBranch, pushTag, readJsonFile, resolveChangelogRange } from "@peiyanlu/cli-utils";
|
|
4
4
|
import { dim, green, red, rgb, underline, yellow } from "ansis";
|
|
5
5
|
import { program } from "commander";
|
|
6
6
|
import { createReadStream, createWriteStream, existsSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
7
7
|
import { cancel, confirm, group, intro, isCancel, log, outro, select, tasks, text } from "@clack/prompts";
|
|
8
8
|
import { publint } from "publint";
|
|
9
9
|
import { formatMessage } from "publint/utils";
|
|
10
|
-
import {
|
|
11
|
-
import { castArray, isPlainObject, isUndefined } from "@peiyanlu/ts-utils";
|
|
10
|
+
import { inc, lt, neq } from "semver";
|
|
11
|
+
import { castArray, isFunction, isPlainObject, isUndefined } from "@peiyanlu/ts-utils";
|
|
12
12
|
import { spawnSync } from "node:child_process";
|
|
13
|
-
import { Octokit } from "@octokit/rest";
|
|
14
|
-
import { contentType } from "mime-types";
|
|
15
|
-
import open from "open";
|
|
16
|
-
import { glob } from "tinyglobby";
|
|
17
13
|
import { inspect } from "node:util";
|
|
18
14
|
import { ConventionalChangelog } from "conventional-changelog";
|
|
19
15
|
import createPreset from "conventional-changelog-conventionalcommits";
|
|
20
16
|
import { writeFile } from "node:fs/promises";
|
|
21
17
|
import { finished } from "node:stream/promises";
|
|
18
|
+
import { Octokit } from "@octokit/rest";
|
|
19
|
+
import { contentType } from "mime-types";
|
|
20
|
+
import open from "open";
|
|
21
|
+
import { glob } from "tinyglobby";
|
|
22
22
|
|
|
23
23
|
//#region node_modules/.pnpm/tsdown@0.18.4_publint@0.3.17_typescript@5.9.3/node_modules/tsdown/esm-shims.js
|
|
24
24
|
const getFilename = () => fileURLToPath(import.meta.url);
|
|
@@ -55,7 +55,6 @@ const resolveConfig = async (cwd) => {
|
|
|
55
55
|
config: await loadConfig(configFile)
|
|
56
56
|
};
|
|
57
57
|
};
|
|
58
|
-
const isFunction = (v) => typeof v === "function";
|
|
59
58
|
const mergeConfig = (defaults, overrides) => {
|
|
60
59
|
if (!overrides) return structuredClone(defaults);
|
|
61
60
|
if (Object.keys(defaults).length === 0) return structuredClone(overrides);
|
|
@@ -78,7 +77,9 @@ const n = (b) => b ? "(dry run)" : "";
|
|
|
78
77
|
const t = () => green(`${Math.floor(process.uptime())}s`);
|
|
79
78
|
const MSG = {
|
|
80
79
|
INTRO: (dryRun) => `🚀 Starting release ${n(dryRun)}`,
|
|
80
|
+
INTRO_PREPARE: (dryRun) => `🚀 Starting prepare release ${n(dryRun)}`,
|
|
81
81
|
OUTRO: (dryRun) => `🎉 Release finished successfully in ${t()} ${n(dryRun)}`,
|
|
82
|
+
OUTRO_PREPARE: (dryRun) => `🎉 Prepare release finished successfully in ${t()} ${n(dryRun)}`,
|
|
82
83
|
LOG: {
|
|
83
84
|
SHOW_VERSION: (version) => `🎉 Released ${green(version)}`,
|
|
84
85
|
SHOW_CHANGELOG: "🎉 Changelog collected",
|
|
@@ -93,6 +94,10 @@ const MSG = {
|
|
|
93
94
|
NPM: {
|
|
94
95
|
CHECKING: "Checking npm registry",
|
|
95
96
|
CHECKED: (registry, msg$1) => `NPM registry: ${underline(dim(registry))} ${underline(dim(msg$1))}`
|
|
97
|
+
},
|
|
98
|
+
GITHUB: {
|
|
99
|
+
CHECKING: "Checking github repository",
|
|
100
|
+
CHECKED: (repository, msg$1) => `GitHub repository: ${underline(dim(repository))} ${underline(dim(msg$1))}`
|
|
96
101
|
}
|
|
97
102
|
},
|
|
98
103
|
TASK: {
|
|
@@ -151,123 +156,6 @@ const MSG = {
|
|
|
151
156
|
}
|
|
152
157
|
};
|
|
153
158
|
|
|
154
|
-
//#endregion
|
|
155
|
-
//#region src/github/release.ts
|
|
156
|
-
const truncateBody = (body) => {
|
|
157
|
-
if (body && body.length >= 124e3) return body.substring(0, 124e3) + "...";
|
|
158
|
-
return body;
|
|
159
|
-
};
|
|
160
|
-
const uploadAssets = async (ctx, config) => {
|
|
161
|
-
const { github: { assets } } = config;
|
|
162
|
-
const { github: { isReleased, owner, repo, uploadUrl, releaseId, token }, pkg: { name } } = ctx;
|
|
163
|
-
if (!assets?.length || !isReleased) return;
|
|
164
|
-
const octokit = new Octokit({ auth: token });
|
|
165
|
-
const files = await glob(assets);
|
|
166
|
-
await Promise.all(files.map((file) => {
|
|
167
|
-
return octokit.repos.uploadReleaseAsset({
|
|
168
|
-
owner,
|
|
169
|
-
repo,
|
|
170
|
-
name,
|
|
171
|
-
url: uploadUrl,
|
|
172
|
-
release_id: releaseId,
|
|
173
|
-
data: createReadStream(file),
|
|
174
|
-
headers: {
|
|
175
|
-
"content-type": contentType(file) || "application/octet-stream",
|
|
176
|
-
"content-length": statSync(file).size
|
|
177
|
-
}
|
|
178
|
-
});
|
|
179
|
-
}));
|
|
180
|
-
};
|
|
181
|
-
const createWebRelease = async (ctx, config) => {
|
|
182
|
-
const { pkg: { toPreRelease }, github: { owner, repo, changelog, releaseName }, git: { currentTag } } = ctx;
|
|
183
|
-
const { github: { prerelease: prerelease$1 } } = config;
|
|
184
|
-
const github = getGithubUrl(owner, repo);
|
|
185
|
-
const url = new URL(`${github}/releases/new`);
|
|
186
|
-
url.searchParams.set("tag", currentTag);
|
|
187
|
-
url.searchParams.set("title", releaseName);
|
|
188
|
-
url.searchParams.set("body", truncateBody(changelog));
|
|
189
|
-
url.searchParams.set("prelease", String(toPreRelease || prerelease$1));
|
|
190
|
-
const isWindows = process.platform === "win32";
|
|
191
|
-
await open(url.toString(), { wait: isWindows });
|
|
192
|
-
};
|
|
193
|
-
const createCiRelease = async (ctx, config) => {
|
|
194
|
-
const { pkg: { toPreRelease }, github: { owner, repo, token, changelog, releaseName }, git: { currentTag } } = ctx;
|
|
195
|
-
const { github: { prerelease: prerelease$1, draft, autoGenerate } } = config;
|
|
196
|
-
const octokit = new Octokit({ auth: token });
|
|
197
|
-
try {
|
|
198
|
-
const { data } = await octokit.repos.createRelease({
|
|
199
|
-
owner,
|
|
200
|
-
repo,
|
|
201
|
-
tag_name: currentTag,
|
|
202
|
-
target_commitish: void 0,
|
|
203
|
-
name: releaseName,
|
|
204
|
-
body: autoGenerate ? "" : truncateBody(changelog),
|
|
205
|
-
draft: draft ?? false,
|
|
206
|
-
prerelease: (toPreRelease || prerelease$1) ?? false,
|
|
207
|
-
discussion_category_name: void 0,
|
|
208
|
-
generate_release_notes: autoGenerate,
|
|
209
|
-
make_latest: "true"
|
|
210
|
-
});
|
|
211
|
-
const { html_url, upload_url, id, discussion_url } = data;
|
|
212
|
-
Object.assign(ctx.github, {
|
|
213
|
-
isReleased: true,
|
|
214
|
-
releaseId: id,
|
|
215
|
-
releaseUrl: html_url,
|
|
216
|
-
uploadUrl: upload_url,
|
|
217
|
-
discussionUrl: discussion_url
|
|
218
|
-
});
|
|
219
|
-
} catch (e) {
|
|
220
|
-
if (e?.status === 422) throw new Error(MSG.ERROR.GITHUB_TAG_EXIT(currentTag));
|
|
221
|
-
throw e;
|
|
222
|
-
}
|
|
223
|
-
};
|
|
224
|
-
const parseGithubUrl = (ctx) => {
|
|
225
|
-
const { git: { remoteUrl } } = ctx;
|
|
226
|
-
const [owner, repo] = parseGitHubRepo(remoteUrl);
|
|
227
|
-
Object.assign(ctx.github, {
|
|
228
|
-
owner,
|
|
229
|
-
repo
|
|
230
|
-
});
|
|
231
|
-
};
|
|
232
|
-
const createRelease = async (ctx, config) => {
|
|
233
|
-
const { github: { owner, repo }, isCI } = ctx;
|
|
234
|
-
const { github: { tokenRef, release, skipChecks } } = config;
|
|
235
|
-
if (!release) return;
|
|
236
|
-
const token = tokenRef ? process.env[tokenRef] : process.env.GITHUB_TOKEN;
|
|
237
|
-
if (!token) {
|
|
238
|
-
Object.assign(ctx.github, { isWeb: true });
|
|
239
|
-
if (isCI && process.env.GITHUB_ACTIONS) throw new Error(MSG.ERROR.GITHUB_TOKEN(tokenRef));
|
|
240
|
-
}
|
|
241
|
-
Object.assign(ctx.github, { token });
|
|
242
|
-
const octokit = new Octokit({ auth: token });
|
|
243
|
-
if (token && !skipChecks) if (process.env.GITHUB_ACTIONS) Object.assign(ctx.github, { username: process.env.GITHUB_ACTOR });
|
|
244
|
-
else {
|
|
245
|
-
try {
|
|
246
|
-
const { data: { login: username } } = await octokit.users.getAuthenticated();
|
|
247
|
-
Object.assign(ctx.github, { username });
|
|
248
|
-
} catch (e) {
|
|
249
|
-
throw new Error(MSG.ERROR.GITHUB_AUTH);
|
|
250
|
-
}
|
|
251
|
-
try {
|
|
252
|
-
const { github: { username } } = ctx;
|
|
253
|
-
await octokit.repos.checkCollaborator({
|
|
254
|
-
owner,
|
|
255
|
-
repo,
|
|
256
|
-
username
|
|
257
|
-
});
|
|
258
|
-
} catch (e) {
|
|
259
|
-
throw new Error(MSG.ERROR.GITHUB_USER);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
const { github: { isWeb }, dryRun } = ctx;
|
|
263
|
-
if (dryRun) return true;
|
|
264
|
-
if (isWeb) await createWebRelease(ctx, config);
|
|
265
|
-
else if (isCI) {
|
|
266
|
-
await createCiRelease(ctx, config);
|
|
267
|
-
await uploadAssets(ctx, config);
|
|
268
|
-
}
|
|
269
|
-
};
|
|
270
|
-
|
|
271
159
|
//#endregion
|
|
272
160
|
//#region src/git/commit.ts
|
|
273
161
|
const gitCheck = async (ctx, config) => {
|
|
@@ -289,7 +177,11 @@ const gitCheck = async (ctx, config) => {
|
|
|
289
177
|
remoteName,
|
|
290
178
|
remoteUrl
|
|
291
179
|
});
|
|
292
|
-
|
|
180
|
+
const [owner, repo] = parseGitHubRepo(remoteUrl);
|
|
181
|
+
Object.assign(ctx.github, {
|
|
182
|
+
owner,
|
|
183
|
+
repo
|
|
184
|
+
});
|
|
293
185
|
};
|
|
294
186
|
const commitAndTag = async (ctx, config) => {
|
|
295
187
|
const { git: { remoteName, currentTag, commitMessage, tagMessage }, dryRun } = ctx;
|
|
@@ -406,37 +298,13 @@ const formatTemplate = async (ctx, config) => {
|
|
|
406
298
|
Object.assign(ctx.github, { releaseName });
|
|
407
299
|
};
|
|
408
300
|
|
|
409
|
-
//#endregion
|
|
410
|
-
//#region src/version/bump.ts
|
|
411
|
-
const isPreRelease = (version) => {
|
|
412
|
-
return Boolean(prerelease(version));
|
|
413
|
-
};
|
|
414
|
-
const isValid = (version) => {
|
|
415
|
-
return Boolean(valid(version));
|
|
416
|
-
};
|
|
417
|
-
const parseVersion = (raw) => {
|
|
418
|
-
const next = isValid(raw) ? raw : coerce(raw)?.toString();
|
|
419
|
-
if (!next) return {};
|
|
420
|
-
const { prerelease: prerelease$1 } = parse(next);
|
|
421
|
-
const toPreRelease = Boolean(prerelease$1.length);
|
|
422
|
-
const [preId, preBase] = prerelease$1.map(String);
|
|
423
|
-
return {
|
|
424
|
-
next,
|
|
425
|
-
toPreRelease,
|
|
426
|
-
preId,
|
|
427
|
-
preBase
|
|
428
|
-
};
|
|
429
|
-
};
|
|
430
|
-
const getCIVersion = (current) => {
|
|
431
|
-
return inc(current, Release.PATCH) ?? "";
|
|
432
|
-
};
|
|
433
|
-
|
|
434
301
|
//#endregion
|
|
435
302
|
//#region src/version/prompts.ts
|
|
436
303
|
let Release = /* @__PURE__ */ function(Release$1) {
|
|
437
304
|
Release$1["MAJOR"] = "major";
|
|
438
305
|
Release$1["MINOR"] = "minor";
|
|
439
306
|
Release$1["PATCH"] = "patch";
|
|
307
|
+
Release$1["RELEASE"] = "release";
|
|
440
308
|
return Release$1;
|
|
441
309
|
}({});
|
|
442
310
|
let PreRelease = /* @__PURE__ */ function(PreRelease$1) {
|
|
@@ -456,76 +324,45 @@ let PreId = /* @__PURE__ */ function(PreId$1) {
|
|
|
456
324
|
PreId$1["DEV"] = "dev";
|
|
457
325
|
return PreId$1;
|
|
458
326
|
}({});
|
|
327
|
+
const createVersionMap = (current) => ({
|
|
328
|
+
patch: inc(current, Release.PATCH),
|
|
329
|
+
minor: inc(current, Release.MINOR),
|
|
330
|
+
major: inc(current, Release.MAJOR),
|
|
331
|
+
release: inc(current, Release.RELEASE),
|
|
332
|
+
prerelease: inc(current, PreRelease.RELEASE),
|
|
333
|
+
prepatch: inc(current, PreRelease.PATCH, PreId.BETA),
|
|
334
|
+
preminor: inc(current, PreRelease.MINOR, PreId.BETA),
|
|
335
|
+
premajor: inc(current, PreRelease.MAJOR, PreId.BETA)
|
|
336
|
+
});
|
|
337
|
+
const option = (label, value, hint) => ({
|
|
338
|
+
label,
|
|
339
|
+
value,
|
|
340
|
+
hint: hint ?? value
|
|
341
|
+
});
|
|
459
342
|
const runVersionPrompts = async (ctx, config) => {
|
|
460
343
|
const { pkg: { current, fromPreRelease } } = ctx;
|
|
461
344
|
const selectVersion = async () => {
|
|
462
|
-
const
|
|
463
|
-
const
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
{
|
|
476
|
-
label: "Pre-Patch",
|
|
477
|
-
value: prepatch,
|
|
478
|
-
hint: prepatch
|
|
479
|
-
},
|
|
480
|
-
{
|
|
481
|
-
label: "Pre-Minor",
|
|
482
|
-
value: preminor,
|
|
483
|
-
hint: preminor
|
|
484
|
-
},
|
|
485
|
-
{
|
|
486
|
-
label: "Pre-Major",
|
|
487
|
-
value: premajor,
|
|
488
|
-
hint: premajor
|
|
489
|
-
}
|
|
490
|
-
];
|
|
491
|
-
const release = [
|
|
492
|
-
{
|
|
493
|
-
label: "Patch",
|
|
494
|
-
value: patch,
|
|
495
|
-
hint: patch
|
|
496
|
-
},
|
|
497
|
-
{
|
|
498
|
-
label: "Minor",
|
|
499
|
-
value: minor,
|
|
500
|
-
hint: minor
|
|
501
|
-
},
|
|
502
|
-
{
|
|
503
|
-
label: "Major",
|
|
504
|
-
value: major,
|
|
505
|
-
hint: major
|
|
506
|
-
}
|
|
345
|
+
const versions = createVersionMap(current);
|
|
346
|
+
const options = [
|
|
347
|
+
...fromPreRelease ? [option("Pre-Release", "prerelease", versions.prerelease)] : [],
|
|
348
|
+
option("Patch", "patch", versions.patch),
|
|
349
|
+
option("Minor", "minor", versions.minor),
|
|
350
|
+
option("Major", "major", versions.major),
|
|
351
|
+
...!fromPreRelease ? [
|
|
352
|
+
option("Pre-Patch", "prepatch", versions.prepatch),
|
|
353
|
+
option("Pre-Minor", "preminor", versions.preminor),
|
|
354
|
+
option("Pre-Major", "premajor", versions.premajor)
|
|
355
|
+
] : [],
|
|
356
|
+
option("As-Is", "ignore", current),
|
|
357
|
+
option("Custom", "custom", "custom specified")
|
|
507
358
|
];
|
|
508
|
-
const
|
|
509
|
-
label: "As-Is",
|
|
510
|
-
value: "ignore",
|
|
511
|
-
hint: current
|
|
512
|
-
}, {
|
|
513
|
-
label: "Custom",
|
|
514
|
-
value: "custom",
|
|
515
|
-
hint: "custom specified"
|
|
516
|
-
}];
|
|
517
|
-
const version = await select({
|
|
359
|
+
const type = await select({
|
|
518
360
|
message: question(MSG.PROMPT.SELECT_VERSION, "version"),
|
|
519
|
-
options
|
|
520
|
-
|
|
521
|
-
...release,
|
|
522
|
-
...fromPreRelease ? [] : pre,
|
|
523
|
-
...custom
|
|
524
|
-
],
|
|
525
|
-
initialValue: fromPreRelease ? prerelease$1 : patch
|
|
361
|
+
options,
|
|
362
|
+
initialValue: fromPreRelease ? "prerelease" : "patch"
|
|
526
363
|
});
|
|
527
|
-
abortSinglePrompt(
|
|
528
|
-
return
|
|
364
|
+
abortSinglePrompt(type);
|
|
365
|
+
return type;
|
|
529
366
|
};
|
|
530
367
|
const inputVersion = async () => {
|
|
531
368
|
const version = await text({
|
|
@@ -538,10 +375,12 @@ const runVersionPrompts = async (ctx, config) => {
|
|
|
538
375
|
abortSinglePrompt(version);
|
|
539
376
|
return version;
|
|
540
377
|
};
|
|
541
|
-
const
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
378
|
+
const resolveNextVersion = async (type, current$1) => {
|
|
379
|
+
if (type === "ignore") return current$1;
|
|
380
|
+
if (type === "custom") return inputVersion();
|
|
381
|
+
return createVersionMap(current$1)[type];
|
|
382
|
+
};
|
|
383
|
+
return await resolveNextVersion(await selectVersion(), current);
|
|
545
384
|
};
|
|
546
385
|
|
|
547
386
|
//#endregion
|
|
@@ -644,8 +483,7 @@ const createDefaultConfig = (isCI) => {
|
|
|
644
483
|
},
|
|
645
484
|
npm: {
|
|
646
485
|
publish: isCI,
|
|
647
|
-
|
|
648
|
-
pushArgs: [],
|
|
486
|
+
publishArgs: [],
|
|
649
487
|
skipChecks: false
|
|
650
488
|
},
|
|
651
489
|
github: {
|
|
@@ -758,11 +596,8 @@ const defaultTypes = [
|
|
|
758
596
|
|
|
759
597
|
//#endregion
|
|
760
598
|
//#region src/git/changelog.ts
|
|
761
|
-
const
|
|
599
|
+
const createGenerator = async ({ getPkgDir, tagPrefix }) => {
|
|
762
600
|
const pkgDir = getPkgDir();
|
|
763
|
-
const infile = join(pkgDir, "CHANGELOG.md");
|
|
764
|
-
if (!existsSync(infile)) await writeFile(infile, "");
|
|
765
|
-
const originalChangelog = readFileSync(infile, "utf-8");
|
|
766
601
|
const preset = await createPreset({ types: defaultTypes.map((t$1) => ({
|
|
767
602
|
...t$1,
|
|
768
603
|
hidden: false
|
|
@@ -815,17 +650,30 @@ const generateChangelog = async ({ getPkgDir, tagPrefix }) => {
|
|
|
815
650
|
{{> commit root=@root}}
|
|
816
651
|
{{/each}}
|
|
817
652
|
{{/each}}`.trim() + eol(2);
|
|
818
|
-
|
|
819
|
-
|
|
653
|
+
return new ConventionalChangelog().readPackage(`${pkgDir}/package.json`).config(preset).options({ releaseCount: 1 }).commits({ path: pkgDir }).tags({ prefix: tagPrefix });
|
|
654
|
+
};
|
|
655
|
+
const getChangelog = async ({ getPkgDir, tagPrefix }) => {
|
|
656
|
+
const generator = await createGenerator({
|
|
657
|
+
getPkgDir,
|
|
658
|
+
tagPrefix
|
|
659
|
+
});
|
|
820
660
|
let changelog = "";
|
|
821
|
-
for await (const chunk of generator.write())
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
661
|
+
for await (const chunk of generator.write()) changelog += chunk;
|
|
662
|
+
return changelog;
|
|
663
|
+
};
|
|
664
|
+
const generateChangelog = async ({ getPkgDir, tagPrefix }) => {
|
|
665
|
+
const infile = join(getPkgDir(), "CHANGELOG.md");
|
|
666
|
+
if (!existsSync(infile)) await writeFile(infile, "");
|
|
667
|
+
const originalChangelog = readFileSync(infile, "utf-8");
|
|
668
|
+
const generator = await createGenerator({
|
|
669
|
+
getPkgDir,
|
|
670
|
+
tagPrefix
|
|
671
|
+
});
|
|
672
|
+
const writeStream = createWriteStream(infile);
|
|
673
|
+
for await (const chunk of generator.write()) writeStream.write(chunk);
|
|
825
674
|
writeStream.write(originalChangelog);
|
|
826
675
|
writeStream.end();
|
|
827
676
|
await finished(writeStream);
|
|
828
|
-
return changelog;
|
|
829
677
|
};
|
|
830
678
|
|
|
831
679
|
//#endregion
|
|
@@ -887,6 +735,172 @@ const runGithubPrompts = async (ctx, config) => {
|
|
|
887
735
|
return res;
|
|
888
736
|
};
|
|
889
737
|
|
|
738
|
+
//#endregion
|
|
739
|
+
//#region src/github/release.ts
|
|
740
|
+
const truncateBody = (body) => {
|
|
741
|
+
if (body && body.length >= 124e3) return body.substring(0, 124e3) + "...";
|
|
742
|
+
return body;
|
|
743
|
+
};
|
|
744
|
+
const getReleaseByTagSafe = async (octokit, options) => {
|
|
745
|
+
try {
|
|
746
|
+
const { data } = await octokit.repos.getReleaseByTag({ ...options });
|
|
747
|
+
return data;
|
|
748
|
+
} catch (e) {
|
|
749
|
+
if (e?.status === 404) return null;
|
|
750
|
+
throw e;
|
|
751
|
+
}
|
|
752
|
+
};
|
|
753
|
+
const createCRelease = async (octokit, options) => {
|
|
754
|
+
const { owner, repo, tag, name, body, draft, prerelease, generateReleaseNotes, makeLatest, targetCommitish, discussionCategoryName } = options;
|
|
755
|
+
const { data } = await octokit.repos.createRelease({
|
|
756
|
+
owner,
|
|
757
|
+
repo,
|
|
758
|
+
tag_name: tag,
|
|
759
|
+
name,
|
|
760
|
+
body,
|
|
761
|
+
draft,
|
|
762
|
+
prerelease,
|
|
763
|
+
generate_release_notes: generateReleaseNotes,
|
|
764
|
+
make_latest: makeLatest,
|
|
765
|
+
target_commitish: targetCommitish,
|
|
766
|
+
discussion_category_name: discussionCategoryName
|
|
767
|
+
});
|
|
768
|
+
return data;
|
|
769
|
+
};
|
|
770
|
+
const updateCRelease = async (octokit, options) => {
|
|
771
|
+
const { owner, repo, tag, name, body, draft, prerelease, generateReleaseNotes, makeLatest, targetCommitish, discussionCategoryName, releaseId } = options;
|
|
772
|
+
const { data } = await octokit.repos.updateRelease({
|
|
773
|
+
owner,
|
|
774
|
+
repo,
|
|
775
|
+
release_id: releaseId,
|
|
776
|
+
tag_name: tag,
|
|
777
|
+
name,
|
|
778
|
+
body,
|
|
779
|
+
draft,
|
|
780
|
+
prerelease,
|
|
781
|
+
generate_release_notes: generateReleaseNotes,
|
|
782
|
+
make_latest: makeLatest,
|
|
783
|
+
target_commitish: targetCommitish,
|
|
784
|
+
discussion_category_name: discussionCategoryName
|
|
785
|
+
});
|
|
786
|
+
return data;
|
|
787
|
+
};
|
|
788
|
+
const createCReleaseByTag = async (octokit, options) => {
|
|
789
|
+
const existing = await getReleaseByTagSafe(octokit, options);
|
|
790
|
+
if (!existing) return createCRelease(octokit, { ...options });
|
|
791
|
+
const { draft, id: releaseId } = existing;
|
|
792
|
+
if (!draft) throw new Error(`Release for tag "${options.tag}" already exists and is not a draft`);
|
|
793
|
+
return updateCRelease(octokit, {
|
|
794
|
+
releaseId,
|
|
795
|
+
...options
|
|
796
|
+
});
|
|
797
|
+
};
|
|
798
|
+
const uploadAssets = async (octokit, options) => {
|
|
799
|
+
const { owner, repo, name, uploadUrl, releaseId, assets } = options;
|
|
800
|
+
const files = await glob(assets);
|
|
801
|
+
await Promise.all(files.map((file) => {
|
|
802
|
+
return octokit.repos.uploadReleaseAsset({
|
|
803
|
+
owner,
|
|
804
|
+
repo,
|
|
805
|
+
name,
|
|
806
|
+
url: uploadUrl,
|
|
807
|
+
release_id: releaseId,
|
|
808
|
+
data: createReadStream(file),
|
|
809
|
+
headers: {
|
|
810
|
+
"content-type": contentType(file) || "application/octet-stream",
|
|
811
|
+
"content-length": statSync(file).size
|
|
812
|
+
}
|
|
813
|
+
});
|
|
814
|
+
}));
|
|
815
|
+
};
|
|
816
|
+
const createWebRelease = async (options) => {
|
|
817
|
+
const { owner, repo, tag, title, body, prerelease } = options;
|
|
818
|
+
const url = new URL(`${getGithubUrl(owner, repo)}/releases/new`);
|
|
819
|
+
Object.entries({
|
|
820
|
+
tag,
|
|
821
|
+
title,
|
|
822
|
+
body,
|
|
823
|
+
prerelease
|
|
824
|
+
}).forEach(([key, value]) => {
|
|
825
|
+
url.searchParams.set(key, value);
|
|
826
|
+
});
|
|
827
|
+
const isWindows = process.platform === "win32";
|
|
828
|
+
await open(url.toString(), { wait: isWindows });
|
|
829
|
+
};
|
|
830
|
+
const githubCheck = async (ctx, config) => {
|
|
831
|
+
const { github: { owner, repo }, isCI } = ctx;
|
|
832
|
+
const { github: { tokenRef, skipChecks } } = config;
|
|
833
|
+
const token = tokenRef ? process.env[tokenRef] : process.env.GITHUB_TOKEN;
|
|
834
|
+
if (!token) {
|
|
835
|
+
Object.assign(ctx.github, { isWeb: true });
|
|
836
|
+
if (isCI && process.env.GITHUB_ACTIONS) throw new Error(MSG.ERROR.GITHUB_TOKEN(tokenRef));
|
|
837
|
+
return "";
|
|
838
|
+
}
|
|
839
|
+
Object.assign(ctx.github, { token });
|
|
840
|
+
if (skipChecks) return "";
|
|
841
|
+
let username = "";
|
|
842
|
+
if (process.env.GITHUB_ACTIONS) username = process.env.GITHUB_ACTOR;
|
|
843
|
+
else {
|
|
844
|
+
const octokit = new Octokit({ auth: token });
|
|
845
|
+
try {
|
|
846
|
+
const { data: { login } } = await octokit.users.getAuthenticated();
|
|
847
|
+
username = login;
|
|
848
|
+
} catch (e) {
|
|
849
|
+
throw new Error(MSG.ERROR.GITHUB_AUTH);
|
|
850
|
+
}
|
|
851
|
+
try {
|
|
852
|
+
await octokit.repos.checkCollaborator({
|
|
853
|
+
owner,
|
|
854
|
+
repo,
|
|
855
|
+
username
|
|
856
|
+
});
|
|
857
|
+
} catch (e) {
|
|
858
|
+
throw new Error(MSG.ERROR.GITHUB_USER);
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
return username;
|
|
862
|
+
};
|
|
863
|
+
const createRelease = async (ctx, config) => {
|
|
864
|
+
const { pkg: { name, toPreRelease }, git: { currentTag }, isCI, dryRun } = ctx;
|
|
865
|
+
const { github: { isWeb, token, owner, repo, changelog, releaseName } } = ctx;
|
|
866
|
+
const { github: { release, assets, prerelease, autoGenerate, draft } } = config;
|
|
867
|
+
if (!release || dryRun) return;
|
|
868
|
+
const octokit = new Octokit({ auth: token });
|
|
869
|
+
if (isWeb) await createWebRelease({
|
|
870
|
+
owner,
|
|
871
|
+
repo,
|
|
872
|
+
tag: currentTag,
|
|
873
|
+
title: releaseName,
|
|
874
|
+
body: truncateBody(changelog),
|
|
875
|
+
prerelease: String(toPreRelease || prerelease)
|
|
876
|
+
});
|
|
877
|
+
else if (isCI) {
|
|
878
|
+
const res = await createCReleaseByTag(octokit, {
|
|
879
|
+
owner,
|
|
880
|
+
repo,
|
|
881
|
+
tag: currentTag,
|
|
882
|
+
name: releaseName,
|
|
883
|
+
body: autoGenerate ? "" : truncateBody(changelog),
|
|
884
|
+
draft,
|
|
885
|
+
prerelease: toPreRelease || prerelease,
|
|
886
|
+
generateReleaseNotes: autoGenerate,
|
|
887
|
+
makeLatest: "true"
|
|
888
|
+
});
|
|
889
|
+
if (!res) return;
|
|
890
|
+
if (assets.length) {
|
|
891
|
+
const { id: releaseId, upload_url: uploadUrl } = res;
|
|
892
|
+
await uploadAssets(octokit, {
|
|
893
|
+
owner,
|
|
894
|
+
repo,
|
|
895
|
+
name,
|
|
896
|
+
uploadUrl,
|
|
897
|
+
releaseId,
|
|
898
|
+
assets
|
|
899
|
+
});
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
};
|
|
903
|
+
|
|
890
904
|
//#endregion
|
|
891
905
|
//#region src/npm/prompts.ts
|
|
892
906
|
const runNpmPublishPrompts = async (ctx, config) => {
|
|
@@ -921,11 +935,11 @@ const runNpmOptPrompts = async (ctx, config) => {
|
|
|
921
935
|
//#endregion
|
|
922
936
|
//#region src/npm/publish.ts
|
|
923
937
|
const resolvePublishTag = async (pkgName, version) => {
|
|
924
|
-
const {
|
|
925
|
-
if (
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
938
|
+
const { isPrerelease: isPrerelease$1, preId } = parseVersion(version);
|
|
939
|
+
if (isPrerelease$1) return preId || "next";
|
|
940
|
+
const active = await getPublishedVersion(pkgName);
|
|
941
|
+
if (!active) return "latest";
|
|
942
|
+
return lt(version, active) ? "previous" : "latest";
|
|
929
943
|
};
|
|
930
944
|
const isOtpError = (err) => err instanceof Error && /one-time password|otp/i.test(err.message);
|
|
931
945
|
const npmCheck = async (ctx, config) => {
|
|
@@ -946,7 +960,7 @@ const npmCheck = async (ctx, config) => {
|
|
|
946
960
|
};
|
|
947
961
|
const publishNpm = async (ctx, config) => {
|
|
948
962
|
const { dryRun, selectedPkg, npm: { otp, tag } } = ctx;
|
|
949
|
-
const { npm: { publish,
|
|
963
|
+
const { npm: { publish, publishArgs }, getPkgDir } = config;
|
|
950
964
|
if (!publish) return;
|
|
951
965
|
const otpArgs = otp ? ["--otp", otp] : [];
|
|
952
966
|
const dryRunArg = dryRun ? ["--dry-run"] : [];
|
|
@@ -954,7 +968,7 @@ const publishNpm = async (ctx, config) => {
|
|
|
954
968
|
tag,
|
|
955
969
|
args: [
|
|
956
970
|
...otpArgs,
|
|
957
|
-
...
|
|
971
|
+
...publishArgs,
|
|
958
972
|
...dryRunArg
|
|
959
973
|
],
|
|
960
974
|
cwd: getPkgDir(selectedPkg)
|
|
@@ -964,7 +978,27 @@ const publishNpm = async (ctx, config) => {
|
|
|
964
978
|
//#endregion
|
|
965
979
|
//#region src/action.ts
|
|
966
980
|
var Action = class {
|
|
967
|
-
async
|
|
981
|
+
async handleRelease(cmdArgs, options) {
|
|
982
|
+
const { ctx, config } = await this.createContext(cmdArgs, options);
|
|
983
|
+
await this.checkTask(ctx, config);
|
|
984
|
+
await this.bumpTask(ctx, config);
|
|
985
|
+
await this.changelogTask(ctx, config);
|
|
986
|
+
await this.gitTask(ctx, config);
|
|
987
|
+
await this.npmTask(ctx, config);
|
|
988
|
+
await this.githubTask(ctx, config);
|
|
989
|
+
if (ctx.dryRun) gitRollback(ctx);
|
|
990
|
+
taskEnd(MSG.OUTRO(ctx.dryRun));
|
|
991
|
+
}
|
|
992
|
+
async handlePrepareRelease(cmdArgs, options) {
|
|
993
|
+
const { ctx, config } = await this.createContext(cmdArgs, options, true);
|
|
994
|
+
await this.checkTask(ctx, config);
|
|
995
|
+
await this.bumpTask(ctx, config);
|
|
996
|
+
await this.changelogTask(ctx, config);
|
|
997
|
+
await this.gitTask(ctx, config);
|
|
998
|
+
if (ctx.dryRun) gitRollback(ctx);
|
|
999
|
+
taskEnd(MSG.OUTRO_PREPARE(ctx.dryRun));
|
|
1000
|
+
}
|
|
1001
|
+
async createContext(cmdArgs, options, prepare = false) {
|
|
968
1002
|
const { version: cVersion, name: cName } = readJsonFile(join(__dirname, "../package.json"));
|
|
969
1003
|
const { otp, package: defPkg, ...others } = options;
|
|
970
1004
|
const { showChangelog, showRelease, ci, dryRun } = Object.fromEntries(Object.entries(others).map(([k, v]) => [k, Boolean(v)]));
|
|
@@ -974,7 +1008,7 @@ var Action = class {
|
|
|
974
1008
|
info(MSG.INFO.TOOL(cName, cVersion));
|
|
975
1009
|
info(MSG.INFO.CONFIG(configPath));
|
|
976
1010
|
console.log();
|
|
977
|
-
intro(MSG.INTRO(dryRun));
|
|
1011
|
+
intro(prepare ? MSG.INTRO_PREPARE(dryRun) : MSG.INTRO(dryRun));
|
|
978
1012
|
const { isMonorepo, packages, getPkgDir } = config;
|
|
979
1013
|
if (isMonorepo) {
|
|
980
1014
|
if (ci) {
|
|
@@ -1015,7 +1049,7 @@ var Action = class {
|
|
|
1015
1049
|
pkg: {
|
|
1016
1050
|
name: pkgName,
|
|
1017
1051
|
isPrivate: pkgPrivate,
|
|
1018
|
-
fromPreRelease:
|
|
1052
|
+
fromPreRelease: isPrerelease(pkgVersion),
|
|
1019
1053
|
current: pkgVersion,
|
|
1020
1054
|
next: nextVersion,
|
|
1021
1055
|
toPreRelease: false,
|
|
@@ -1028,33 +1062,42 @@ var Action = class {
|
|
|
1028
1062
|
const formated = formatMessage(message, pkg$1);
|
|
1029
1063
|
formated && msg("PKG", formated);
|
|
1030
1064
|
}
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
await this.npmTask(ctx, config);
|
|
1036
|
-
await this.githubTask(ctx, config);
|
|
1037
|
-
if (dryRun) gitRollback(ctx);
|
|
1038
|
-
taskEnd(MSG.OUTRO(dryRun));
|
|
1065
|
+
return {
|
|
1066
|
+
ctx,
|
|
1067
|
+
config
|
|
1068
|
+
};
|
|
1039
1069
|
}
|
|
1040
1070
|
async checkTask(ctx, config) {
|
|
1041
|
-
const {
|
|
1042
|
-
await tasks([
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
title: MSG.CHECK.NPM.CHECKING,
|
|
1051
|
-
task: async () => {
|
|
1052
|
-
const msg$1 = await npmCheck(ctx, config);
|
|
1053
|
-
const { pkg: { publishConfig: { registry } } } = ctx;
|
|
1054
|
-
return success(MSG.CHECK.NPM.CHECKED(registry, msg$1), dryRun);
|
|
1071
|
+
const { dryRun } = ctx;
|
|
1072
|
+
await tasks([
|
|
1073
|
+
{
|
|
1074
|
+
title: MSG.CHECK.GIT.CHECKING,
|
|
1075
|
+
task: async () => {
|
|
1076
|
+
await gitCheck(ctx, config);
|
|
1077
|
+
const { git: { remoteName, remoteUrl } } = ctx;
|
|
1078
|
+
return success(MSG.CHECK.GIT.CHECKED(remoteName, remoteUrl), dryRun);
|
|
1079
|
+
}
|
|
1055
1080
|
},
|
|
1056
|
-
|
|
1057
|
-
|
|
1081
|
+
{
|
|
1082
|
+
title: MSG.CHECK.NPM.CHECKING,
|
|
1083
|
+
task: async () => {
|
|
1084
|
+
const msg$1 = await npmCheck(ctx, config);
|
|
1085
|
+
const { pkg: { publishConfig: { registry } } } = ctx;
|
|
1086
|
+
return success(MSG.CHECK.NPM.CHECKED(registry, msg$1), dryRun);
|
|
1087
|
+
},
|
|
1088
|
+
enabled: !ctx.noNpm
|
|
1089
|
+
},
|
|
1090
|
+
{
|
|
1091
|
+
title: MSG.CHECK.GITHUB.CHECKING,
|
|
1092
|
+
task: async () => {
|
|
1093
|
+
const msg$1 = await githubCheck(ctx, config);
|
|
1094
|
+
const { github: { owner, repo } } = ctx;
|
|
1095
|
+
const repository = getGithubUrl(owner, repo);
|
|
1096
|
+
return success(MSG.CHECK.GITHUB.CHECKED(repository, msg$1), dryRun);
|
|
1097
|
+
},
|
|
1098
|
+
enabled: !ctx.noGitHub
|
|
1099
|
+
}
|
|
1100
|
+
]).catch(abortOnError);
|
|
1058
1101
|
}
|
|
1059
1102
|
async bumpTask(ctx, config) {
|
|
1060
1103
|
const { pkg: { current }, dryRun, isCI, showRelease, showChangelog, selectedPkg } = ctx;
|
|
@@ -1064,8 +1107,15 @@ var Action = class {
|
|
|
1064
1107
|
return !next$1 && isIncrement$1;
|
|
1065
1108
|
};
|
|
1066
1109
|
if (need(ctx)) {
|
|
1067
|
-
const
|
|
1068
|
-
|
|
1110
|
+
const nextVersion = (isCI ? inc(current, "patch") : void 0) || await runVersionPrompts(ctx, config);
|
|
1111
|
+
ctx.isIncrement = neq(current, nextVersion);
|
|
1112
|
+
const { version: next$1, isPrerelease: toPreRelease, preId, preBase } = parseVersion(nextVersion);
|
|
1113
|
+
Object.assign(ctx.pkg, {
|
|
1114
|
+
next: next$1,
|
|
1115
|
+
toPreRelease,
|
|
1116
|
+
preId,
|
|
1117
|
+
preBase
|
|
1118
|
+
});
|
|
1069
1119
|
}
|
|
1070
1120
|
const { pkg: { next }, isIncrement } = ctx;
|
|
1071
1121
|
if (showRelease) taskEnd(MSG.LOG.SHOW_VERSION(next));
|
|
@@ -1081,9 +1131,15 @@ var Action = class {
|
|
|
1081
1131
|
}]).catch((err) => abortOnError(err, ctx));
|
|
1082
1132
|
await runLifeCycleHook(hooks, "after:bump", dryRun);
|
|
1083
1133
|
const { from, to } = await resolveChangelogRange(isIncrement, isMonorepo ? `${tagPrefix?.(selectedPkg)}*` : "*");
|
|
1084
|
-
const
|
|
1085
|
-
|
|
1086
|
-
|
|
1134
|
+
const commits = await getLog(from, to, getPkgDir(selectedPkg));
|
|
1135
|
+
const changelog = await getChangelog({
|
|
1136
|
+
getPkgDir: () => getPkgDir(selectedPkg),
|
|
1137
|
+
tagPrefix: tagPrefix?.(selectedPkg)
|
|
1138
|
+
});
|
|
1139
|
+
Object.assign(ctx.github, { changelog });
|
|
1140
|
+
if (commits) {
|
|
1141
|
+
msg("GIT", `Changelog(${from}...${to}):${eol(2)}` + changelog.trimEnd());
|
|
1142
|
+
msg("GIT", `Commits(${from}...${to}):${eol(2)}` + commits);
|
|
1087
1143
|
if (showChangelog) taskEnd(MSG.LOG.SHOW_CHANGELOG);
|
|
1088
1144
|
} else msg("GIT", MSG.LOG.CHANGELOG_EMPTY);
|
|
1089
1145
|
}
|
|
@@ -1093,12 +1149,11 @@ var Action = class {
|
|
|
1093
1149
|
await tasks([{
|
|
1094
1150
|
title: MSG.TASK.CHANGELOG.START,
|
|
1095
1151
|
task: async () => {
|
|
1096
|
-
|
|
1152
|
+
await generateChangelog({
|
|
1097
1153
|
getPkgDir: () => getPkgDir(selectedPkg),
|
|
1098
1154
|
tagPrefix: tagPrefix?.(selectedPkg)
|
|
1099
1155
|
});
|
|
1100
|
-
await
|
|
1101
|
-
Object.assign(ctx.github, { changelog });
|
|
1156
|
+
await gitAddAll();
|
|
1102
1157
|
return success(MSG.TASK.CHANGELOG.END, dryRun);
|
|
1103
1158
|
},
|
|
1104
1159
|
enabled: isIncrement
|
|
@@ -1165,8 +1220,10 @@ var Action = class {
|
|
|
1165
1220
|
//#endregion
|
|
1166
1221
|
//#region src/cli.ts
|
|
1167
1222
|
const pkg = readJsonFile(join(__dirname, "..", "package.json"));
|
|
1168
|
-
program.name("release").description(pkg.description).version(pkg.version, "-v, --version", "Print the tool version and exit.").usage("[release-type] [options]").argument("[release-type]", "Version bump type: patch | minor | major").option("-n, --dry-run", "Run in dry mode
|
|
1169
|
-
|
|
1223
|
+
program.name("release").description(pkg.description).version(pkg.version, "-v, --version", "Print the tool version and exit.").usage("[release-type] [options]").argument("[release-type]", "Version bump type: patch | minor | major").option("-n, --dry-run", "Run in dry mode", false).option("-p, --package <pkg>", "Specify package name (CI only).", "").option("--otp <code>", "One-time password for npm publish.", "").option("--prepare", "Prepare a release.", false).option("--ci", "Enable CI mode.", false).option("--show-changelog", "Print changelog and exit.", false).option("--show-release", "Print release version and exit.", false).helpOption("-h, --help", "Display help information.").action(async (releaseType, options) => {
|
|
1224
|
+
const { prepare } = options;
|
|
1225
|
+
const action = new Action();
|
|
1226
|
+
prepare ? await action.handlePrepareRelease(releaseType, options) : await action.handleRelease(releaseType, options);
|
|
1170
1227
|
});
|
|
1171
1228
|
program.command("init").description("Create a release configuration file").option("-f, --force", "Overwrite existing config file", false).option("-m, --monorepo", "Mono-repo project", false).action((options) => {
|
|
1172
1229
|
const { force = false, monorepo = false } = options;
|
|
@@ -1174,9 +1231,10 @@ program.command("init").description("Create a release configuration file").optio
|
|
|
1174
1231
|
const { type } = readJsonFile(join(cwd, "package.json"));
|
|
1175
1232
|
const isESM = type === "module";
|
|
1176
1233
|
const isTS = existsSync(join(cwd, "tsconfig.json"));
|
|
1177
|
-
const
|
|
1234
|
+
const infile = `release.config.${isESM ? "" : "m"}${isTS ? "ts" : "js"}`;
|
|
1235
|
+
const configPath = join(cwd, infile);
|
|
1178
1236
|
if (existsSync(configPath) && !force) {
|
|
1179
|
-
console.error(`${red`Error:`} ${yellow
|
|
1237
|
+
console.error(`${red`Error:`} ${yellow(infile)} already exists.\nUse ${green`--force`} to overwrite.`);
|
|
1180
1238
|
process.exit(1);
|
|
1181
1239
|
}
|
|
1182
1240
|
const monoConfigToString = () => `{
|
|
@@ -1186,10 +1244,8 @@ program.command("init").description("Create a release configuration file").optio
|
|
|
1186
1244
|
toTag: (pkg, version) => \`\${ pkg }@\${ version }\`,
|
|
1187
1245
|
tagPrefix: (pkg) => \`\${ pkg }@\`,
|
|
1188
1246
|
}`;
|
|
1189
|
-
writeFileSync(configPath, `import { defineConfig } from '@peiyanlu/release'
|
|
1190
|
-
|
|
1191
|
-
export default defineConfig(${monorepo ? monoConfigToString() : "{}"})`, "utf-8");
|
|
1192
|
-
console.log(`${green`✔ release.config.ts`} created successfully`);
|
|
1247
|
+
writeFileSync(configPath, [`import { defineConfig } from '@peiyanlu/release'`, `export default defineConfig(${monorepo ? monoConfigToString() : "{}"})`].join(eol(2)), "utf-8");
|
|
1248
|
+
console.log(`${green`✔`} ${infile} created successfully`);
|
|
1193
1249
|
});
|
|
1194
1250
|
program.parse(process.argv);
|
|
1195
1251
|
program.on("command:*", () => {
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { DeepPartial, DeepRequired } from "@peiyanlu/ts-utils";
|
|
2
|
+
import { ConventionalChangelog } from "conventional-changelog";
|
|
2
3
|
import { log } from "@clack/prompts";
|
|
3
4
|
|
|
4
5
|
//#region src/types.d.ts
|
|
@@ -88,14 +89,10 @@ interface ReleaseConfig {
|
|
|
88
89
|
* 是否启用 npm 发布流程
|
|
89
90
|
*/
|
|
90
91
|
publish: boolean | undefined;
|
|
91
|
-
/**
|
|
92
|
-
* npm 发布目标路径,对应 `npm publish <package-spec>`
|
|
93
|
-
*/
|
|
94
|
-
publishPath: string;
|
|
95
92
|
/**
|
|
96
93
|
* 额外的 npm publish 参数(透传给 npm CLI)
|
|
97
94
|
*/
|
|
98
|
-
|
|
95
|
+
publishArgs: string[];
|
|
99
96
|
/**
|
|
100
97
|
* 是否跳过发布前检查(如 ping、whoami 等校验)
|
|
101
98
|
*/
|
|
@@ -155,4 +152,48 @@ declare function defineConfig(config: UserConfigFn): UserConfigFn;
|
|
|
155
152
|
declare function defineConfig(config: UserConfigExport): UserConfigExport;
|
|
156
153
|
declare const mergeConfig: <T extends object>(defaults: DeepRequired<T>, overrides?: DeepPartial<T>) => DeepRequired<T>;
|
|
157
154
|
//#endregion
|
|
158
|
-
|
|
155
|
+
//#region src/git/changelog.d.ts
|
|
156
|
+
interface Options {
|
|
157
|
+
/** @example () => `packages/${pkg}` */
|
|
158
|
+
getPkgDir: () => string;
|
|
159
|
+
/** @example `${pkg}@` */
|
|
160
|
+
tagPrefix?: string;
|
|
161
|
+
}
|
|
162
|
+
declare const createGenerator: ({
|
|
163
|
+
getPkgDir,
|
|
164
|
+
tagPrefix
|
|
165
|
+
}: Options) => Promise<ConventionalChangelog>;
|
|
166
|
+
declare const getChangelog: ({
|
|
167
|
+
getPkgDir,
|
|
168
|
+
tagPrefix
|
|
169
|
+
}: Options) => Promise<string>;
|
|
170
|
+
declare const generateChangelog: ({
|
|
171
|
+
getPkgDir,
|
|
172
|
+
tagPrefix
|
|
173
|
+
}: Options) => Promise<void>;
|
|
174
|
+
//#endregion
|
|
175
|
+
//#region src/index.d.ts
|
|
176
|
+
interface PublishTagOptions {
|
|
177
|
+
/**
|
|
178
|
+
* 支持通过命令行参数提供
|
|
179
|
+
* @example v1.0.0; pkg@1.0.1;
|
|
180
|
+
* @example my-cli v1.0.0;
|
|
181
|
+
*/
|
|
182
|
+
gitTag?: string;
|
|
183
|
+
/** @defaults `@` */
|
|
184
|
+
tagSeparator?: string;
|
|
185
|
+
/**
|
|
186
|
+
* monorepo 仓库子包不使用 `pkg@version` 格式创建 tag 时需要指定
|
|
187
|
+
* @example pkg-demo
|
|
188
|
+
*/
|
|
189
|
+
defaultPackage?: string;
|
|
190
|
+
/**
|
|
191
|
+
* @example (pkg) => `packages/${pkg}`; (pkg) => '.';
|
|
192
|
+
*/
|
|
193
|
+
getPkgDir?: (pkg: string) => string;
|
|
194
|
+
/** npm Trusted Publishing(OIDC) */
|
|
195
|
+
provenance?: boolean;
|
|
196
|
+
}
|
|
197
|
+
declare const publishTagToNpm: (options: PublishTagOptions) => Promise<void>;
|
|
198
|
+
//#endregion
|
|
199
|
+
export { type Logger, type UserConfig, createGenerator, defineConfig, generateChangelog, getChangelog, mergeConfig, publishTagToNpm };
|
package/dist/index.mjs
CHANGED
|
@@ -1,10 +1,113 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { eol, getPackageInfo, getPublishedVersion, parseVersion, publishPackage } from "@peiyanlu/cli-utils";
|
|
2
|
+
import { blue, cyan, dim, green, magenta, red, underline, yellow } from "ansis";
|
|
3
|
+
import { lt } from "semver";
|
|
4
|
+
import { isFunction, isPlainObject } from "@peiyanlu/ts-utils";
|
|
5
|
+
import { createWriteStream, existsSync, readFileSync } from "node:fs";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { ConventionalChangelog } from "conventional-changelog";
|
|
8
|
+
import createPreset from "conventional-changelog-conventionalcommits";
|
|
9
|
+
import { writeFile } from "node:fs/promises";
|
|
10
|
+
import { finished } from "node:stream/promises";
|
|
2
11
|
|
|
12
|
+
//#region src/messages.ts
|
|
13
|
+
const n = (b) => b ? "(dry run)" : "";
|
|
14
|
+
const t = () => green(`${Math.floor(process.uptime())}s`);
|
|
15
|
+
const MSG = {
|
|
16
|
+
INTRO: (dryRun) => `🚀 Starting release ${n(dryRun)}`,
|
|
17
|
+
INTRO_PREPARE: (dryRun) => `🚀 Starting prepare release ${n(dryRun)}`,
|
|
18
|
+
OUTRO: (dryRun) => `🎉 Release finished successfully in ${t()} ${n(dryRun)}`,
|
|
19
|
+
OUTRO_PREPARE: (dryRun) => `🎉 Prepare release finished successfully in ${t()} ${n(dryRun)}`,
|
|
20
|
+
LOG: {
|
|
21
|
+
SHOW_VERSION: (version) => `🎉 Released ${green(version)}`,
|
|
22
|
+
SHOW_CHANGELOG: "🎉 Changelog collected",
|
|
23
|
+
CHANGELOG_EMPTY: "No commits found since last release",
|
|
24
|
+
CHANGES_EMPTY: "Working tree clean. No files changed"
|
|
25
|
+
},
|
|
26
|
+
CHECK: {
|
|
27
|
+
GIT: {
|
|
28
|
+
CHECKING: "Checking Git repository",
|
|
29
|
+
CHECKED: (name, url) => `Git remote resolved: ${url ? `${dim(name)} → ${underline(dim(url))}` : "not found"}`
|
|
30
|
+
},
|
|
31
|
+
NPM: {
|
|
32
|
+
CHECKING: "Checking npm registry",
|
|
33
|
+
CHECKED: (registry, msg) => `NPM registry: ${underline(dim(registry))} ${underline(dim(msg))}`
|
|
34
|
+
},
|
|
35
|
+
GITHUB: {
|
|
36
|
+
CHECKING: "Checking github repository",
|
|
37
|
+
CHECKED: (repository, msg) => `GitHub repository: ${underline(dim(repository))} ${underline(dim(msg))}`
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
TASK: {
|
|
41
|
+
VERSION: {
|
|
42
|
+
START: "Bumping version",
|
|
43
|
+
END: (to) => `Version bumped: ${underline(dim(to))}`
|
|
44
|
+
},
|
|
45
|
+
CHANGELOG: {
|
|
46
|
+
START: "Generating changelog",
|
|
47
|
+
END: `Changelog generated`
|
|
48
|
+
},
|
|
49
|
+
NPM: {
|
|
50
|
+
START: "Publishing npm package",
|
|
51
|
+
END: (url) => `NPM package published: ${underline(dim(url))}`
|
|
52
|
+
},
|
|
53
|
+
GIT: {
|
|
54
|
+
START: "Creating git commit, tag & push",
|
|
55
|
+
END: "Git commit, tag & push completed"
|
|
56
|
+
},
|
|
57
|
+
GITHUB: {
|
|
58
|
+
START: "Creating GitHub release",
|
|
59
|
+
END: (url) => `GitHub release created: ${underline(dim(url))}`
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
PROMPT: {
|
|
63
|
+
SELECT_PACKAGE: "Select package release:",
|
|
64
|
+
SELECT_VERSION: "Select version bump:",
|
|
65
|
+
INPUT_VERSION: "Enter custom version:",
|
|
66
|
+
NPM_PUBLISH: (pkg) => `Publish to npm ${underline(dim`(${pkg})`)}?`,
|
|
67
|
+
NPM_OTP: "Enter npm one-time password (OTP):",
|
|
68
|
+
GITHUB_RELEASE: (title) => `Create a new GitHub release ${underline(dim`(${title})`)}?`,
|
|
69
|
+
GIT_COMMIT: (msg) => `Create git commit ${underline(dim`(${msg})`)}?`,
|
|
70
|
+
GIT_TAG: (msg) => `Create git tag ${underline(dim`(${msg})`)}?`,
|
|
71
|
+
GIT_PUSH: "Push commits and tags to remote?"
|
|
72
|
+
},
|
|
73
|
+
ERROR: {
|
|
74
|
+
GIT_WORKDIR: `[git] Working dir must be clean`,
|
|
75
|
+
GIT_REGISTRY: (name) => `[git] Repository not found for ${dim(name)}`,
|
|
76
|
+
GIT_REMOTE: (name) => `[git] Remote "${dim(name)}" not found`,
|
|
77
|
+
GITHUB_TAG_EXIT: (tag) => `[github] Release or tag "${tag}" already exists`,
|
|
78
|
+
GITHUB_TOKEN: (tokenRef) => `[github] Missing GitHub token. Please set ${yellow(tokenRef ?? "GITHUB_TOKEN")}`,
|
|
79
|
+
GITHUB_AUTH: "[github] Invalid GitHub token or insufficient permissions",
|
|
80
|
+
GITHUB_USER: "[github] User does not have permission to create releases",
|
|
81
|
+
NPM_REGISTRY: (registry) => `[npm] Unable to reach npm registry ${underline(dim(registry))}`,
|
|
82
|
+
NPM_AUTH: `[npm] Not authenticated with npm. Please run ${yellow("npm login")} and try again.`,
|
|
83
|
+
NPM_USER: (user, name) => `[npm] User ${dim(user)} is not a collaborator of ${dim(name)}.`
|
|
84
|
+
},
|
|
85
|
+
ABORT: {
|
|
86
|
+
CANCEL: "Operation cancelled",
|
|
87
|
+
MONOREPO_CI_NO_PACKAGE: "CI mode requires a target package in monorepo. Please specify it via \"--package <pkg>\"",
|
|
88
|
+
MONOREPO_NO_PACKAGES: "Monorepo detected, but no packages found. Please configure \"config.packages\""
|
|
89
|
+
},
|
|
90
|
+
INFO: {
|
|
91
|
+
TOOL: (name, version) => `${name} ${dim(`v${version}`)}`,
|
|
92
|
+
CONFIG: (path) => `config file: ${underline(dim(path || "not found"))}`
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
//#endregion
|
|
97
|
+
//#region src/npm/publish.ts
|
|
98
|
+
const resolvePublishTag = async (pkgName, version) => {
|
|
99
|
+
const { isPrerelease, preId } = parseVersion(version);
|
|
100
|
+
if (isPrerelease) return preId || "next";
|
|
101
|
+
const active = await getPublishedVersion(pkgName);
|
|
102
|
+
if (!active) return "latest";
|
|
103
|
+
return lt(version, active) ? "previous" : "latest";
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
//#endregion
|
|
3
107
|
//#region src/config.ts
|
|
4
108
|
function defineConfig(config) {
|
|
5
109
|
return config;
|
|
6
110
|
}
|
|
7
|
-
const isFunction = (v) => typeof v === "function";
|
|
8
111
|
const mergeConfig = (defaults, overrides) => {
|
|
9
112
|
if (!overrides) return structuredClone(defaults);
|
|
10
113
|
if (Object.keys(defaults).length === 0) return structuredClone(overrides);
|
|
@@ -22,4 +125,218 @@ const mergeConfig = (defaults, overrides) => {
|
|
|
22
125
|
};
|
|
23
126
|
|
|
24
127
|
//#endregion
|
|
25
|
-
|
|
128
|
+
//#region src/git/changetype.ts
|
|
129
|
+
const defaultTypes = [
|
|
130
|
+
{
|
|
131
|
+
type: "feat",
|
|
132
|
+
section: "✨ 新功能",
|
|
133
|
+
description: "新增功能"
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
type: "feature",
|
|
137
|
+
section: "✨ 新功能",
|
|
138
|
+
description: "新增功能"
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
type: "fix",
|
|
142
|
+
section: "🐛 Bug 修复",
|
|
143
|
+
description: "修复 bug"
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
type: "perf",
|
|
147
|
+
section: "⚡ 性能优化",
|
|
148
|
+
description: "提升性能"
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
type: "revert",
|
|
152
|
+
section: "⏪ 回退",
|
|
153
|
+
description: "回退到之前版本"
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
type: "docs",
|
|
157
|
+
section: "📝 文档",
|
|
158
|
+
description: "文档更新"
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
type: "style",
|
|
162
|
+
section: "💄 样式调整",
|
|
163
|
+
description: "样式或格式修改"
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
type: "chore",
|
|
167
|
+
section: "🎫 其他更新",
|
|
168
|
+
description: "非功能性更改"
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
type: "refactor",
|
|
172
|
+
section: "♻ 代码重构",
|
|
173
|
+
description: "重构代码"
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
type: "test",
|
|
177
|
+
section: "✅ 测试",
|
|
178
|
+
description: "测试更新"
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
type: "build",
|
|
182
|
+
section: "👷 构建系统",
|
|
183
|
+
description: "构建工具和系统修改"
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
type: "ci",
|
|
187
|
+
section: "🔧 CI 配置",
|
|
188
|
+
description: "持续集成配置"
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
type: "config",
|
|
192
|
+
section: "🔨 配置",
|
|
193
|
+
description: "配置文件更新"
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
type: "deps",
|
|
197
|
+
section: "🔗 依赖更新",
|
|
198
|
+
description: "依赖版本变更"
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
type: "security",
|
|
202
|
+
section: "🔒 安全修复",
|
|
203
|
+
description: "安全性更新"
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
type: "i18n",
|
|
207
|
+
section: "🌐 国际化",
|
|
208
|
+
description: "国际化相关更新"
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
type: "ux",
|
|
212
|
+
section: "🖥️ 用户体验",
|
|
213
|
+
description: "用户体验改进"
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
type: "hotfix",
|
|
217
|
+
section: "🔥 热修复",
|
|
218
|
+
description: "紧急修复"
|
|
219
|
+
}
|
|
220
|
+
];
|
|
221
|
+
|
|
222
|
+
//#endregion
|
|
223
|
+
//#region src/git/changelog.ts
|
|
224
|
+
const createGenerator = async ({ getPkgDir, tagPrefix }) => {
|
|
225
|
+
const pkgDir = getPkgDir();
|
|
226
|
+
const preset = await createPreset({ types: defaultTypes.map((t$1) => ({
|
|
227
|
+
...t$1,
|
|
228
|
+
hidden: false
|
|
229
|
+
})) });
|
|
230
|
+
preset.writer ??= {};
|
|
231
|
+
preset.writer.headerPartial = `
|
|
232
|
+
## {{#if isPatch~}} <small> {{~/if~}}
|
|
233
|
+
{{#if @root.linkCompare~}}
|
|
234
|
+
[{{version}}](
|
|
235
|
+
{{~#if @root.repository~}}
|
|
236
|
+
{{~#if @root.host}}
|
|
237
|
+
{{~@root.host}}/
|
|
238
|
+
{{~/if}}
|
|
239
|
+
{{~#if @root.owner}}
|
|
240
|
+
{{~@root.owner}}/
|
|
241
|
+
{{~/if}}
|
|
242
|
+
{{~@root.repository}}
|
|
243
|
+
{{~else}}
|
|
244
|
+
{{~@root.repoUrl}}
|
|
245
|
+
{{~/if~}}
|
|
246
|
+
/compare/{{previousTag}}...{{currentTag}})
|
|
247
|
+
{{~else}}
|
|
248
|
+
{{~version}}
|
|
249
|
+
{{~/if}}
|
|
250
|
+
{{~#if title}} "{{title}}"
|
|
251
|
+
{{~/if}}
|
|
252
|
+
{{~#if date}} ({{date}})
|
|
253
|
+
{{~/if}}
|
|
254
|
+
{{~#if isPatch~}} </small> {{~/if}}
|
|
255
|
+
`.trim() + eol();
|
|
256
|
+
preset.writer.mainTemplate = `
|
|
257
|
+
{{> header}}
|
|
258
|
+
{{#if noteGroups}}
|
|
259
|
+
{{#each noteGroups}}
|
|
260
|
+
|
|
261
|
+
### ⚠ {{title}}
|
|
262
|
+
|
|
263
|
+
{{#each notes}}
|
|
264
|
+
* {{#if commit.scope}}**{{commit.scope}}:** {{/if}}{{commit.subject}} {{#if commit.hash}}([{{commit.shortHash}}](https://github.com/{{@root.owner}}/{{@root.repository}}/commit/{{commit.hash}})){{/if}}
|
|
265
|
+
{{/each}}
|
|
266
|
+
{{/each}}
|
|
267
|
+
{{/if}}
|
|
268
|
+
{{#each commitGroups}}
|
|
269
|
+
|
|
270
|
+
{{#if title}}
|
|
271
|
+
### {{title}}
|
|
272
|
+
|
|
273
|
+
{{/if}}
|
|
274
|
+
{{#each commits}}
|
|
275
|
+
{{> commit root=@root}}
|
|
276
|
+
{{/each}}
|
|
277
|
+
{{/each}}`.trim() + eol(2);
|
|
278
|
+
return new ConventionalChangelog().readPackage(`${pkgDir}/package.json`).config(preset).options({ releaseCount: 1 }).commits({ path: pkgDir }).tags({ prefix: tagPrefix });
|
|
279
|
+
};
|
|
280
|
+
const getChangelog = async ({ getPkgDir, tagPrefix }) => {
|
|
281
|
+
const generator = await createGenerator({
|
|
282
|
+
getPkgDir,
|
|
283
|
+
tagPrefix
|
|
284
|
+
});
|
|
285
|
+
let changelog = "";
|
|
286
|
+
for await (const chunk of generator.write()) changelog += chunk;
|
|
287
|
+
return changelog;
|
|
288
|
+
};
|
|
289
|
+
const generateChangelog = async ({ getPkgDir, tagPrefix }) => {
|
|
290
|
+
const infile = join(getPkgDir(), "CHANGELOG.md");
|
|
291
|
+
if (!existsSync(infile)) await writeFile(infile, "");
|
|
292
|
+
const originalChangelog = readFileSync(infile, "utf-8");
|
|
293
|
+
const generator = await createGenerator({
|
|
294
|
+
getPkgDir,
|
|
295
|
+
tagPrefix
|
|
296
|
+
});
|
|
297
|
+
const writeStream = createWriteStream(infile);
|
|
298
|
+
for await (const chunk of generator.write()) writeStream.write(chunk);
|
|
299
|
+
writeStream.write(originalChangelog);
|
|
300
|
+
writeStream.end();
|
|
301
|
+
await finished(writeStream);
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
//#endregion
|
|
305
|
+
//#region src/index.ts
|
|
306
|
+
const publishTagToNpm = async (options) => {
|
|
307
|
+
const { gitTag, defaultPackage, tagSeparator = "@", getPkgDir = () => ".", provenance } = options;
|
|
308
|
+
const tag = process.argv.slice(2)[0] || gitTag;
|
|
309
|
+
if (!tag) throw new Error(`❌ No git tag specified.`);
|
|
310
|
+
console.log(`\n📦 Start publishing from git tag: ${cyan(tag)}`);
|
|
311
|
+
let pkgName = defaultPackage;
|
|
312
|
+
let version;
|
|
313
|
+
if (tag.includes(tagSeparator)) {
|
|
314
|
+
[pkgName, version] = tag.split(tagSeparator);
|
|
315
|
+
console.log(`🔍 Parsed tag → package: "${yellow(pkgName)}", version: "${green(version)}"`);
|
|
316
|
+
} else {
|
|
317
|
+
version = tag;
|
|
318
|
+
console.log(`🔍 Parsed tag → version only: "${green(version)}"`);
|
|
319
|
+
}
|
|
320
|
+
if (version.startsWith("v")) {
|
|
321
|
+
version = version.slice(1);
|
|
322
|
+
console.log(`✂️ Normalized version (strip v): ${version}`);
|
|
323
|
+
}
|
|
324
|
+
if (!pkgName) throw new Error(`❌ Package name should be specified in tag "${tag}" when defaultPackage is not set`);
|
|
325
|
+
console.log(`📁 Resolving package info for "${magenta(pkgName)}"...`);
|
|
326
|
+
const { pkg, pkgDir } = getPackageInfo(pkgName, getPkgDir);
|
|
327
|
+
console.log(`📄 package.json → name: ${yellow(pkg.name)}, version: ${green(pkg.version)}`);
|
|
328
|
+
console.log(`📂 Package directory: ${underline(pkgDir)}`);
|
|
329
|
+
if (pkg.version !== version) throw new Error(`❌ Package version from tag "${version}" mismatches with current version "${pkg.version}"`);
|
|
330
|
+
const publishTag = await resolvePublishTag(pkg.name, version);
|
|
331
|
+
console.log(`🏷 Resolved npm dist-tag: "${blue(publishTag)}" (version ${green(version)})`);
|
|
332
|
+
console.log(`🚀 Publishing "${red`${pkg.name}@${version}`}" to npm...`);
|
|
333
|
+
await publishPackage({
|
|
334
|
+
tag: publishTag,
|
|
335
|
+
args: provenance ? ["--provenance"] : [],
|
|
336
|
+
cwd: pkgDir
|
|
337
|
+
});
|
|
338
|
+
console.log(`🎉 Successfully published ${red`${pkg.name}@${version}`} with dist-tag "${blue(publishTag)}"\n`);
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
//#endregion
|
|
342
|
+
export { createGenerator, defineConfig, generateChangelog, getChangelog, mergeConfig, publishTagToNpm };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peiyanlu/release",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"description": "Release project tool.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -21,15 +21,15 @@
|
|
|
21
21
|
"dev": "tsdown -w",
|
|
22
22
|
"build": "tsdown",
|
|
23
23
|
"prepublish": "tsdown",
|
|
24
|
-
"release": "
|
|
24
|
+
"release": "node index.js",
|
|
25
25
|
"test": "vitest run",
|
|
26
26
|
"test:cov": "vitest run --coverage"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@clack/prompts": "^0.11.0",
|
|
30
30
|
"@octokit/rest": "^22.0.1",
|
|
31
|
-
"@peiyanlu/cli-utils": "^0.0.
|
|
32
|
-
"@peiyanlu/ts-utils": "^0.0.
|
|
31
|
+
"@peiyanlu/cli-utils": "^0.0.7",
|
|
32
|
+
"@peiyanlu/ts-utils": "^0.0.5",
|
|
33
33
|
"ansis": "^4.2.0",
|
|
34
34
|
"commander": "^14.0.2",
|
|
35
35
|
"conventional-changelog": "^7.1.1",
|