@peiyanlu/release 1.1.2 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -14
- package/dist/cli.mjs +211 -57
- package/dist/index.d.mts +40 -9
- package/dist/index.mjs +1 -1
- package/package.json +11 -26
package/README.md
CHANGED
|
@@ -1,19 +1,15 @@
|
|
|
1
1
|
# Release Project
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
<a href="https://www.npmjs.com/package/@peiyanlu/release" target="_blank">
|
|
14
|
-
<img src="https://img.shields.io/npm/dm/@peiyanlu/release.svg" alt="NPM Downloads" />
|
|
15
|
-
</a>
|
|
16
|
-
</p>
|
|
3
|
+
|
|
4
|
+
[](https://nodejs.org/api/esm.html)
|
|
5
|
+
[](https://npmjs.com/package/@peiyanlu/release)
|
|
6
|
+
[](https://npmjs.com/package/@peiyanlu/release)
|
|
7
|
+
[](https://npmjs.com/package/@peiyanlu/release)
|
|
8
|
+
[](https://nodejs.org)
|
|
9
|
+
[](https://packagephobia.com/result?p=@peiyanlu/release)
|
|
10
|
+
[](https://libraries.io/npm/%40peiyanlu%2Frelease)
|
|
11
|
+
[](https://github.com/peiyanlu/release/actions)
|
|
12
|
+
[](https://coveralls.io/github/peiyanlu/release?branch=master)
|
|
17
13
|
|
|
18
14
|
🚀 **一个用于自动化版本管理与包发布的通用 CLI 工具**
|
|
19
15
|
|
|
@@ -282,6 +278,23 @@ COMMIT_TYPES:
|
|
|
282
278
|
|
|
283
279
|
适用于 CI/CD 场景下的**安全、无 Token** 自动化发布。
|
|
284
280
|
|
|
281
|
+
支持配置发布前清理:
|
|
282
|
+
|
|
283
|
+
- 移除 `package.json` 中对发布无意义的字段
|
|
284
|
+
- `README.md` 只保留主要内容
|
|
285
|
+
|
|
286
|
+
```ts
|
|
287
|
+
// 默认配置
|
|
288
|
+
export default defineConfig({
|
|
289
|
+
npm: {
|
|
290
|
+
cleanup: {
|
|
291
|
+
packageJson: true,
|
|
292
|
+
readme: false,
|
|
293
|
+
removeTempDir: true,
|
|
294
|
+
}
|
|
295
|
+
},
|
|
296
|
+
})
|
|
297
|
+
```
|
|
285
298
|
|
|
286
299
|
## GitHub Releases
|
|
287
300
|
|
|
@@ -320,10 +333,13 @@ GitHub Release 可基于 Git Tag 自动创建,并支持附带:
|
|
|
320
333
|
### 示例用法
|
|
321
334
|
|
|
322
335
|
- 发布到 npm
|
|
336
|
+
|
|
323
337
|
```ts
|
|
324
338
|
// publishCI.ts
|
|
325
339
|
|
|
326
340
|
import { publishTagToNpm } from '@peiyanlu/release'
|
|
341
|
+
|
|
342
|
+
|
|
327
343
|
await publishTagToNpm({})
|
|
328
344
|
```
|
|
329
345
|
|
|
@@ -353,6 +369,7 @@ jobs:
|
|
|
353
369
|
```
|
|
354
370
|
|
|
355
371
|
- 创建 GitHub Release
|
|
372
|
+
|
|
356
373
|
```yaml
|
|
357
374
|
# .github/workflows/release.yml
|
|
358
375
|
|
package/dist/cli.mjs
CHANGED
|
@@ -1,29 +1,30 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { bold, compareUrl, each, heading, link, list, newline, segments, small, words } from "@conventional-changelog/template";
|
|
2
|
+
import { bumpPackageVersion, coloredStatus, eol, execAsync, getAuthenticatedUser, getGithubReleaseUrl, getGithubUrl, getLatestTag, getLogSince, getPackageInfo, getPackageUrl, getPublishedVersion, getRemoteList, getRemoteNames, getShortStatus, gitAddAll, gitAddSync, gitAddTracked, gitBranchCurrent, gitCommitMessage, gitPushBranch, gitPushTag, gitResetHardSync, gitTagAnnotated, gitTagDeleteSync, hasWriteAccess, isGitRepo, isOtpError, isPrerelease, isValidVersion, isWorkingTreeClean, parseGitHubRepo, parseVersion, pingRegistry, publishPackage, registryArg, resolveChangelogRange, resolvePublishTag, runNpm } from "@peiyanlu/cli-utils";
|
|
3
|
+
import { copyDir, createMatcher, createTempDir, editFile, readJsonFile, readJsonFileSync, writeJsonFile } from "@peiyanlu/node-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
|
+
import { join, resolve } from "node:path";
|
|
7
8
|
import { cancel, confirm, group, intro, isCancel, log, outro, select, tasks, text } from "@clack/prompts";
|
|
8
|
-
import { castArray, isFunction, isPlainObject, isUndefined, isZero,
|
|
9
|
+
import { castArray, isBoolean, isEmptyObject, isFunction, isNotEmpty, isNumber, isPlainObject, isUndefined, isZero, omit, partition, unset } from "@peiyanlu/ts-utils";
|
|
9
10
|
import { publint } from "publint";
|
|
10
11
|
import { formatMessage } from "publint/utils";
|
|
11
12
|
import { inc, neq } from "semver";
|
|
13
|
+
import { pathToFileURL } from "node:url";
|
|
12
14
|
import { inspect } from "node:util";
|
|
13
15
|
import { ConventionalChangelog } from "conventional-changelog";
|
|
14
16
|
import createPreset from "conventional-changelog-conventionalcommits";
|
|
15
17
|
import { Bumper } from "conventional-recommended-bump";
|
|
16
18
|
import { writeFile } from "node:fs/promises";
|
|
17
19
|
import { finished } from "node:stream/promises";
|
|
18
|
-
import { bold, compareUrl, each, heading, link, list, newline, segments, small, words } from "@conventional-changelog/template";
|
|
19
20
|
import { Octokit } from "@octokit/rest";
|
|
20
21
|
import { contentType } from "mime-types";
|
|
21
22
|
import open from "open";
|
|
22
23
|
import { glob } from "tinyglobby";
|
|
23
|
-
//#region
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
//#region package.json
|
|
25
|
+
var name = "@peiyanlu/release";
|
|
26
|
+
var version = "1.2.0";
|
|
27
|
+
var description = "Release project tool.";
|
|
27
28
|
//#endregion
|
|
28
29
|
//#region src/config.ts
|
|
29
30
|
const findConfigFile = (cwd = process.cwd()) => {
|
|
@@ -133,7 +134,7 @@ const MSG = {
|
|
|
133
134
|
},
|
|
134
135
|
ERROR: {
|
|
135
136
|
GIT_WORKDIR: `[git] Working tree must be clean`,
|
|
136
|
-
GIT_REGISTRY: (name) => `[git] Repository not found for ${dim(name)}`,
|
|
137
|
+
GIT_REGISTRY: (name) => `[git] Repository not found for package "${dim(name)}"`,
|
|
137
138
|
GIT_REMOTE: (name) => `[git] Remote "${dim(name)}" not found`,
|
|
138
139
|
GITHUB_TAG_EXIT: (tag) => `[github] Release for tag "${tag}" already exists and is not a draft`,
|
|
139
140
|
GITHUB_TOKEN: (tokenRef) => `[github] Missing GitHub token. Please set ${yellow(tokenRef ?? "GITHUB_TOKEN")}`,
|
|
@@ -141,7 +142,6 @@ const MSG = {
|
|
|
141
142
|
GITHUB_USER: "[github] User does not have permission to create releases",
|
|
142
143
|
NPM_REGISTRY: (registry) => `[npm] Unable to reach npm registry ${underline(dim(registry))}`,
|
|
143
144
|
NPM_AUTH: `[npm] Not authenticated with npm. Please run ${yellow("npm login")} and try again`,
|
|
144
|
-
NPM_PERMISSION: `[npm] Missing publish permission. Please use a token with publish access and try again`,
|
|
145
145
|
NPM_USER: (user, name) => `[npm] User ${dim(user)} is not a collaborator of ${dim(name)}`
|
|
146
146
|
},
|
|
147
147
|
ABORT: {
|
|
@@ -175,8 +175,9 @@ const gitCheck = async (ctx, config) => {
|
|
|
175
175
|
ctx.noGitHub = true;
|
|
176
176
|
return;
|
|
177
177
|
}
|
|
178
|
-
const
|
|
179
|
-
|
|
178
|
+
const remote = (await getRemoteList())[0];
|
|
179
|
+
const { name: remoteName = "origin", url: remoteUrl } = remote ?? {};
|
|
180
|
+
if (!remote || !remoteUrl) if (requireRemote) throw new Error(MSG.ERROR.GIT_REMOTE(remoteName));
|
|
180
181
|
else {
|
|
181
182
|
ctx.noGitHub = true;
|
|
182
183
|
return;
|
|
@@ -186,9 +187,11 @@ const gitCheck = async (ctx, config) => {
|
|
|
186
187
|
remoteUrl
|
|
187
188
|
});
|
|
188
189
|
const [owner, repo] = parseGitHubRepo(remoteUrl);
|
|
190
|
+
const url = getGithubUrl(owner, repo);
|
|
189
191
|
Object.assign(ctx.github, {
|
|
190
192
|
owner,
|
|
191
|
-
repo
|
|
193
|
+
repo,
|
|
194
|
+
url
|
|
192
195
|
});
|
|
193
196
|
};
|
|
194
197
|
const commitAndTag = async (ctx, config) => {
|
|
@@ -401,6 +404,7 @@ const createDefaultContext = () => {
|
|
|
401
404
|
tagMessage: ""
|
|
402
405
|
},
|
|
403
406
|
github: {
|
|
407
|
+
url: "",
|
|
404
408
|
username: "",
|
|
405
409
|
repo: "",
|
|
406
410
|
changelog: "",
|
|
@@ -464,7 +468,12 @@ const createDefaultConfig = (isCI) => {
|
|
|
464
468
|
npm: {
|
|
465
469
|
publish: isCI,
|
|
466
470
|
publishArgs: [],
|
|
467
|
-
skipChecks: false
|
|
471
|
+
skipChecks: false,
|
|
472
|
+
cleanup: {
|
|
473
|
+
packageJson: true,
|
|
474
|
+
readme: false,
|
|
475
|
+
removeTempDir: true
|
|
476
|
+
}
|
|
468
477
|
},
|
|
469
478
|
github: {
|
|
470
479
|
release: isCI,
|
|
@@ -922,21 +931,138 @@ const runNpmOptPrompts = async (ctx, config) => {
|
|
|
922
931
|
return res;
|
|
923
932
|
};
|
|
924
933
|
//#endregion
|
|
934
|
+
//#region src/npm/cleanup.ts
|
|
935
|
+
const PUBLISH_CONFIG_FIELDS = [
|
|
936
|
+
"bin",
|
|
937
|
+
"main",
|
|
938
|
+
"exports",
|
|
939
|
+
"types",
|
|
940
|
+
"typings",
|
|
941
|
+
"module",
|
|
942
|
+
"browser",
|
|
943
|
+
"esnext",
|
|
944
|
+
"es2015",
|
|
945
|
+
"unpkg",
|
|
946
|
+
"umd:main",
|
|
947
|
+
"typesVersions",
|
|
948
|
+
"cpu",
|
|
949
|
+
"os",
|
|
950
|
+
"engines"
|
|
951
|
+
];
|
|
952
|
+
const IGNORE_FIELDS = [
|
|
953
|
+
"babel",
|
|
954
|
+
"browserslist",
|
|
955
|
+
"c8",
|
|
956
|
+
"commitlint",
|
|
957
|
+
"devDependencies",
|
|
958
|
+
"eslintConfig",
|
|
959
|
+
"eslintIgnore",
|
|
960
|
+
"husky",
|
|
961
|
+
"jest",
|
|
962
|
+
"lint-staged",
|
|
963
|
+
"nano-staged",
|
|
964
|
+
"pre-commit",
|
|
965
|
+
"prettier",
|
|
966
|
+
"pwmetrics",
|
|
967
|
+
"remarkConfig",
|
|
968
|
+
"renovate",
|
|
969
|
+
"resolutions",
|
|
970
|
+
"sharec",
|
|
971
|
+
"simple-git-hooks",
|
|
972
|
+
"simple-pre-commit",
|
|
973
|
+
"size-limit",
|
|
974
|
+
"typeCoverage",
|
|
975
|
+
"yaspeller",
|
|
976
|
+
"pnpm",
|
|
977
|
+
"packageManager"
|
|
978
|
+
];
|
|
979
|
+
const KEEP_SCRIPT_NAMES = [
|
|
980
|
+
"preinstall",
|
|
981
|
+
"install",
|
|
982
|
+
"postinstall"
|
|
983
|
+
];
|
|
984
|
+
const IGNORE_FILES = [
|
|
985
|
+
".DS_Store",
|
|
986
|
+
".git",
|
|
987
|
+
".vscode",
|
|
988
|
+
".idea",
|
|
989
|
+
"node_modules"
|
|
990
|
+
];
|
|
991
|
+
const clearPackageJSON = async (dir, options) => {
|
|
992
|
+
const file = join(dir, "package.json");
|
|
993
|
+
const json = await readJsonFile(file);
|
|
994
|
+
const { ignoreFields = [], keepScripts = [] } = options ?? {};
|
|
995
|
+
const mergePublishConfig = (json) => {
|
|
996
|
+
if (!json.publishConfig) return json;
|
|
997
|
+
const publishConfig = { ...json.publishConfig };
|
|
998
|
+
PUBLISH_CONFIG_FIELDS.forEach((field) => {
|
|
999
|
+
if (publishConfig[field]) {
|
|
1000
|
+
json[field] = publishConfig[field];
|
|
1001
|
+
delete publishConfig[field];
|
|
1002
|
+
}
|
|
1003
|
+
});
|
|
1004
|
+
return {
|
|
1005
|
+
...json,
|
|
1006
|
+
publishConfig
|
|
1007
|
+
};
|
|
1008
|
+
};
|
|
1009
|
+
const removeIgnoredFields = (json) => {
|
|
1010
|
+
const [matched, unmatched] = partition([...ignoreFields, ...IGNORE_FIELDS], (i) => i.includes("."));
|
|
1011
|
+
matched.forEach((field) => unset(json, field));
|
|
1012
|
+
return omit(json, unmatched);
|
|
1013
|
+
};
|
|
1014
|
+
const cleanScripts = (json) => {
|
|
1015
|
+
if (!json.scripts) return json;
|
|
1016
|
+
const keep = [...keepScripts, ...KEEP_SCRIPT_NAMES];
|
|
1017
|
+
const excludes = Object.keys(json.scripts).filter((k) => !keep.includes(k));
|
|
1018
|
+
json.scripts = omit(json.scripts, excludes);
|
|
1019
|
+
return json;
|
|
1020
|
+
};
|
|
1021
|
+
const removeEmptyObjects = (json) => {
|
|
1022
|
+
return omit(json, Object.entries(json).filter(([k, v]) => isEmptyObject(v)).map(([k, v]) => k));
|
|
1023
|
+
};
|
|
1024
|
+
await writeJsonFile(file, removeEmptyObjects(cleanScripts(removeIgnoredFields(structuredClone(mergePublishConfig(json))))));
|
|
1025
|
+
};
|
|
1026
|
+
const cleanReadme = async (dir, homepage) => {
|
|
1027
|
+
const file = join(dir, "README.md");
|
|
1028
|
+
homepage && await editFile(file, (str) => {
|
|
1029
|
+
return str.split(/^##\s+\S.*$/m)[0] + `## Docs
|
|
1030
|
+
|
|
1031
|
+
Read full docs **[here](${homepage})**.`;
|
|
1032
|
+
});
|
|
1033
|
+
};
|
|
1034
|
+
//#endregion
|
|
925
1035
|
//#region src/npm/publish.ts
|
|
1036
|
+
const canPublish = async (registry, cwd) => {
|
|
1037
|
+
const res = await runNpm([
|
|
1038
|
+
"publish",
|
|
1039
|
+
"--dry-run",
|
|
1040
|
+
"--access",
|
|
1041
|
+
"public",
|
|
1042
|
+
...registryArg(registry)
|
|
1043
|
+
], {
|
|
1044
|
+
error: "throw",
|
|
1045
|
+
cwd
|
|
1046
|
+
}).catch((err) => err);
|
|
1047
|
+
const allowedErr = (err) => {
|
|
1048
|
+
return [/previously published versions/i, /cannot publish over/i].some((reg) => reg.test(err.message));
|
|
1049
|
+
};
|
|
1050
|
+
if (!(res instanceof Error) || allowedErr(res)) return true;
|
|
1051
|
+
throw res;
|
|
1052
|
+
};
|
|
926
1053
|
const npmCheck = async (ctx, config) => {
|
|
927
|
-
const { pkg: { name, current, publishConfig: { registry } } } = ctx;
|
|
928
|
-
const { npm: { skipChecks } } = config;
|
|
1054
|
+
const { pkg: { name, current, publishConfig: { registry } }, selectedPkg } = ctx;
|
|
1055
|
+
const { npm: { skipChecks }, getPkgDir } = config;
|
|
929
1056
|
const tag = await resolvePublishTag(name, current);
|
|
930
1057
|
Object.assign(ctx.npm, { tag });
|
|
931
1058
|
if (skipChecks) return "";
|
|
932
|
-
const [pinged, username
|
|
1059
|
+
const [pinged, username] = await Promise.all([
|
|
933
1060
|
pingRegistry(registry),
|
|
934
1061
|
getAuthenticatedUser(registry),
|
|
935
|
-
canPublish(registry)
|
|
1062
|
+
canPublish(registry, getPkgDir(selectedPkg))
|
|
936
1063
|
]);
|
|
937
1064
|
if (!pinged) throw new Error(MSG.ERROR.NPM_REGISTRY(registry));
|
|
938
1065
|
if (!username) throw new Error(MSG.ERROR.NPM_AUTH);
|
|
939
|
-
if (!canPublished) throw new Error(MSG.ERROR.NPM_PERMISSION);
|
|
940
1066
|
Object.assign(ctx.npm, { username });
|
|
941
1067
|
const latest = await getPublishedVersion(`${name}@${tag}`);
|
|
942
1068
|
if (latest) {
|
|
@@ -945,18 +1071,34 @@ const npmCheck = async (ctx, config) => {
|
|
|
945
1071
|
return latest ? `(${tag} → ${latest})` : `(${tag} → no published version)`;
|
|
946
1072
|
};
|
|
947
1073
|
const publishNpm = async (ctx, config) => {
|
|
948
|
-
const { dryRun, selectedPkg, npm: { otp, tag } } = ctx;
|
|
949
|
-
const { npm: { publish, publishArgs }, getPkgDir } = config;
|
|
1074
|
+
const { dryRun, selectedPkg, npm: { otp, tag }, github: { url } } = ctx;
|
|
1075
|
+
const { npm: { publish, publishArgs, cleanup: { packageJson, readme, removeTempDir } }, getPkgDir } = config;
|
|
950
1076
|
if (!publish) return;
|
|
951
1077
|
const otpArgs = otp ? ["--otp", otp] : [];
|
|
952
1078
|
const dryRunArg = dryRun ? ["--dry-run"] : [];
|
|
953
|
-
|
|
1079
|
+
const args = [
|
|
1080
|
+
...otpArgs,
|
|
1081
|
+
...publishArgs,
|
|
1082
|
+
...dryRunArg
|
|
1083
|
+
];
|
|
1084
|
+
if (packageJson || readme) {
|
|
1085
|
+
const parent = resolve(getPkgDir(selectedPkg));
|
|
1086
|
+
const temp = await createTempDir(parent);
|
|
1087
|
+
await copyDir(parent, temp.path, { ignore: [(name) => {
|
|
1088
|
+
return [temp.name, ...IGNORE_FILES].map((pattern) => createMatcher(pattern)).some((matcher) => matcher(name));
|
|
1089
|
+
}] });
|
|
1090
|
+
const options = typeof packageJson === "boolean" ? void 0 : packageJson;
|
|
1091
|
+
packageJson && await clearPackageJSON(temp.path, options);
|
|
1092
|
+
readme && await cleanReadme(temp.path, `${url}#readme`);
|
|
1093
|
+
await publishPackage({
|
|
1094
|
+
tag,
|
|
1095
|
+
args,
|
|
1096
|
+
cwd: temp.path
|
|
1097
|
+
});
|
|
1098
|
+
removeTempDir && await temp.remove();
|
|
1099
|
+
} else await publishPackage({
|
|
954
1100
|
tag,
|
|
955
|
-
args
|
|
956
|
-
...otpArgs,
|
|
957
|
-
...publishArgs,
|
|
958
|
-
...dryRunArg
|
|
959
|
-
],
|
|
1101
|
+
args,
|
|
960
1102
|
cwd: getPkgDir(selectedPkg)
|
|
961
1103
|
});
|
|
962
1104
|
};
|
|
@@ -975,7 +1117,7 @@ var Action = class {
|
|
|
975
1117
|
taskEnd(MSG.OUTRO(ctx.dryRun));
|
|
976
1118
|
}
|
|
977
1119
|
async handlePrepareRelease(cmdArgs, options) {
|
|
978
|
-
const { ctx, config } = await this.createContext(cmdArgs, options
|
|
1120
|
+
const { ctx, config } = await this.createContext(cmdArgs, options);
|
|
979
1121
|
await this.checkTask(ctx, config);
|
|
980
1122
|
await this.bumpTask(ctx, config);
|
|
981
1123
|
await this.changelogTask(ctx, config);
|
|
@@ -983,27 +1125,12 @@ var Action = class {
|
|
|
983
1125
|
if (ctx.dryRun) gitRollback(ctx);
|
|
984
1126
|
taskEnd(MSG.OUTRO_PREPARE(ctx.dryRun));
|
|
985
1127
|
}
|
|
986
|
-
async createContext(cmdArgs, options
|
|
987
|
-
const {
|
|
988
|
-
|
|
989
|
-
const { showChangelog, showRelease, ci, dryRun, onlyChangelog } = mapObject(others, (k, v) => [k, Boolean(v)]);
|
|
990
|
-
process.env["dryRun"] = String(dryRun);
|
|
991
|
-
const { configFile, config: local } = await resolveConfig(process.cwd());
|
|
992
|
-
const config = mergeConfig(createDefaultConfig(ci ? true : void 0), local);
|
|
993
|
-
info(MSG.INFO.TOOL(cName, cVersion));
|
|
1128
|
+
async createContext(cmdArgs, options) {
|
|
1129
|
+
const { configFile, config } = await this.createConfig(options);
|
|
1130
|
+
info(MSG.INFO.TOOL(name, version));
|
|
994
1131
|
info(MSG.INFO.CONFIG(configFile));
|
|
995
|
-
{
|
|
996
|
-
const { requireCleanWorkingTree, skipGit, skipNpm, skipGithub, isMonorepo, includeHidden } = mapObject(others, (k, v) => [k, Boolean(v)]);
|
|
997
|
-
const count = Number(releaseCount);
|
|
998
|
-
config.changelog.releaseCount = isNaN(count) ? 0 : count;
|
|
999
|
-
config.changelog.includeHidden = includeHidden;
|
|
1000
|
-
config.git.requireCleanWorkingTree = requireCleanWorkingTree;
|
|
1001
|
-
config.skipGit = skipGit;
|
|
1002
|
-
config.skipNpm = skipNpm;
|
|
1003
|
-
config.skipGithub = skipGithub;
|
|
1004
|
-
config.isMonorepo = isMonorepo;
|
|
1005
|
-
}
|
|
1006
1132
|
console.log();
|
|
1133
|
+
const { otp, package: defPkg, prepare, showChangelog, showRelease, ci, dryRun, onlyChangelog } = options;
|
|
1007
1134
|
intro(prepare ? MSG.INTRO_PREPARE(dryRun) : MSG.INTRO(dryRun));
|
|
1008
1135
|
const { isMonorepo, packages, getPkgDir, skipNpm } = config;
|
|
1009
1136
|
if (isMonorepo) {
|
|
@@ -1028,7 +1155,7 @@ var Action = class {
|
|
|
1028
1155
|
const nextVersion = inc(pkgVersion, cmdArgs) ?? "";
|
|
1029
1156
|
const ctx = mergeConfig(createDefaultContext(), {
|
|
1030
1157
|
selectedPkg,
|
|
1031
|
-
configFileExists:
|
|
1158
|
+
configFileExists: isNotEmpty(configFile),
|
|
1032
1159
|
dryRun,
|
|
1033
1160
|
showRelease,
|
|
1034
1161
|
showChangelog,
|
|
@@ -1042,7 +1169,7 @@ var Action = class {
|
|
|
1042
1169
|
showRelease,
|
|
1043
1170
|
process.env.GITHUB_ACTIONS,
|
|
1044
1171
|
process.env.CI
|
|
1045
|
-
].some(
|
|
1172
|
+
].some(Boolean),
|
|
1046
1173
|
pkg: {
|
|
1047
1174
|
name: pkgName,
|
|
1048
1175
|
isPrivate: pkgPrivate,
|
|
@@ -1064,6 +1191,34 @@ var Action = class {
|
|
|
1064
1191
|
config
|
|
1065
1192
|
};
|
|
1066
1193
|
}
|
|
1194
|
+
async createConfig(options) {
|
|
1195
|
+
const { ci, requireCleanWorkingTree, skipGit, skipNpm, skipGithub, isMonorepo, includeHidden, releaseCount } = options;
|
|
1196
|
+
const toFalse = (value) => {
|
|
1197
|
+
return isBoolean(value) && !value ? value : void 0;
|
|
1198
|
+
};
|
|
1199
|
+
const toNumber = (value) => {
|
|
1200
|
+
return isNumber(value) ? value : void 0;
|
|
1201
|
+
};
|
|
1202
|
+
const defaultConfig = createDefaultConfig(ci ? true : void 0);
|
|
1203
|
+
const { configFile, config: local } = await this.resolveUserConfig(process.cwd());
|
|
1204
|
+
return {
|
|
1205
|
+
configFile,
|
|
1206
|
+
config: mergeConfig(mergeConfig(defaultConfig, local), {
|
|
1207
|
+
changelog: {
|
|
1208
|
+
releaseCount: toNumber(releaseCount),
|
|
1209
|
+
includeHidden
|
|
1210
|
+
},
|
|
1211
|
+
git: { requireCleanWorkingTree: toFalse(requireCleanWorkingTree) },
|
|
1212
|
+
skipGit,
|
|
1213
|
+
skipNpm,
|
|
1214
|
+
skipGithub,
|
|
1215
|
+
isMonorepo
|
|
1216
|
+
})
|
|
1217
|
+
};
|
|
1218
|
+
}
|
|
1219
|
+
async resolveUserConfig(cwd = process.cwd()) {
|
|
1220
|
+
return resolveConfig(cwd);
|
|
1221
|
+
}
|
|
1067
1222
|
async printChangelog(ctx, config) {
|
|
1068
1223
|
const { showChangelog, selectedPkg, noGit, isIncrement } = ctx;
|
|
1069
1224
|
const { isMonorepo, getPkgDir, changelog: { tagPrefix, releaseCount, includeHidden, transformTypes } } = config;
|
|
@@ -1245,16 +1400,15 @@ var Action = class {
|
|
|
1245
1400
|
};
|
|
1246
1401
|
//#endregion
|
|
1247
1402
|
//#region src/cli.ts
|
|
1248
|
-
|
|
1249
|
-
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("-m, --is-monorepo", "Mono-repo project", false).option("-p, --package <pkg>", "Specify package name (Mono-repo 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).option("-r, --release-count <count>", "Release count for release.", (value) => Number(value), 1).option("--include-hidden", "Include hidden commit types in the changelog.", false).option("--only-changelog", "Only update the changelog.", false).option("-C, --no-require-clean-working-tree", "Allow releasing with uncommitted changes.", true).option("--skip-git", "Skip all Git-related checks and operations.", false).option("--skip-npm", "Skip all npm-related checks and operations.", false).option("--skip-github", "Skip all GitHub-related checks and operations.", false).helpOption("-h, --help", "Display help information.").action(async (releaseType, options) => {
|
|
1403
|
+
program.name("release").description(description).version(version, "-v, --version", "Print the tool version and exit.").usage("[release-type] [options]").argument("[release-type]", "Version bump type: patch | minor | major").enablePositionalOptions().option("-n, --dry-run", "Run in dry mode", false).option("-p, --package <pkg>", "Specify package name (Mono-repo 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).option("--only-changelog", "Only update the changelog.", false).option("-r, --release-count <count>", "Release count for release.", (val) => Number(val)).option("--include-hidden", "Include hidden commit types in the changelog.").option("-C, --no-require-clean-working-tree", "Allow releasing with uncommitted changes.").option("--skip-git", "Skip all Git-related checks and operations.").option("--skip-npm", "Skip all npm-related checks and operations.").option("--skip-github", "Skip all GitHub-related checks and operations.").option("-m, --is-monorepo", "Mono-repo project").helpOption("-h, --help", "Display help information.").action(async (releaseType, options) => {
|
|
1250
1404
|
const { prepare } = options;
|
|
1251
1405
|
const action = new Action();
|
|
1252
1406
|
prepare ? await action.handlePrepareRelease(releaseType, options) : await action.handleRelease(releaseType, options);
|
|
1253
1407
|
});
|
|
1254
|
-
program.command("init").description("Create a release configuration file").option("-f, --force", "Overwrite existing config file", false).option("-m, --monorepo", "Mono-repo project", false).option("-a, --add", "Automatically stage the config file.", false).action((options) => {
|
|
1408
|
+
program.command("init").description("Create a release configuration file").option("-f, --force", "Overwrite existing config file", false).option("-m, --monorepo", "Mono-repo project", false).option("-a, --add", "Automatically stage the config file.", false).action(async (options) => {
|
|
1255
1409
|
const { force = false, monorepo = false, add = false } = options;
|
|
1256
1410
|
const cwd = process.cwd();
|
|
1257
|
-
const { type } =
|
|
1411
|
+
const { type } = readJsonFileSync(join(cwd, "package.json"));
|
|
1258
1412
|
const isESM = type === "module";
|
|
1259
1413
|
const isTS = existsSync(join(cwd, "tsconfig.json"));
|
|
1260
1414
|
const infile = `release.config.${isESM ? "" : "m"}${isTS ? "ts" : "js"}`;
|
|
@@ -1265,15 +1419,15 @@ program.command("init").description("Create a release configuration file").optio
|
|
|
1265
1419
|
}
|
|
1266
1420
|
const monoConfigToString = () => `{
|
|
1267
1421
|
isMonorepo: true,
|
|
1268
|
-
packages: [
|
|
1422
|
+
packages: [],
|
|
1269
1423
|
getPkgDir: (pkg) => \`packages/\${ pkg }\`,
|
|
1270
1424
|
toTag: (pkg, version) => \`\${ pkg }@\${ version }\`,
|
|
1271
1425
|
changelog: {
|
|
1272
1426
|
tagPrefix: (pkg) => \`\${ pkg }@\`,
|
|
1273
1427
|
},
|
|
1274
1428
|
}`;
|
|
1275
|
-
const content = [`import { defineConfig } from '@peiyanlu/release'`, `export default defineConfig(${monorepo ? monoConfigToString() : "{}"})`].join(eol(
|
|
1276
|
-
writeFileSync(configFile, content, "utf-8");
|
|
1429
|
+
const content = [`import { defineConfig } from '@peiyanlu/release'`, `export default defineConfig(${monorepo ? monoConfigToString() : "{}"})`].join(eol(3));
|
|
1430
|
+
writeFileSync(configFile, content + newline(), "utf-8");
|
|
1277
1431
|
console.log(`Wrote to ${underline(dim(configFile))}`);
|
|
1278
1432
|
if (add) gitAddSync([infile]);
|
|
1279
1433
|
console.log(`\n${content}`);
|
package/dist/index.d.mts
CHANGED
|
@@ -1,22 +1,21 @@
|
|
|
1
|
-
import { DeepPartial, DeepRequired } from "@peiyanlu/ts-utils";
|
|
1
|
+
import { DeepPartial, DeepRequired, Simplify } from "@peiyanlu/ts-utils";
|
|
2
2
|
import { ConventionalChangelog } from "conventional-changelog";
|
|
3
3
|
import { CommitType as CommitType$1 } from "conventional-changelog-conventionalcommits";
|
|
4
4
|
import { log } from "@clack/prompts";
|
|
5
|
-
|
|
6
5
|
//#region src/types.d.ts
|
|
7
6
|
interface CommitType {
|
|
8
7
|
type: string;
|
|
9
8
|
section?: string;
|
|
10
|
-
effect?:
|
|
9
|
+
effect?: "bump" | "changelog" | "hidden";
|
|
11
10
|
scope?: string;
|
|
12
11
|
description?: string;
|
|
13
12
|
}
|
|
14
13
|
type Logger = typeof log;
|
|
15
|
-
type HookTiming =
|
|
16
|
-
type HookAction =
|
|
14
|
+
type HookTiming = "before" | "after";
|
|
15
|
+
type HookAction = "bump" | "publish" | "push" | "release";
|
|
17
16
|
type ReleaseHookKey = `${HookTiming}:${HookAction}`;
|
|
18
17
|
type ReleaseHookValue = string | string[] | ((logger: Logger) => Promise<void> | void);
|
|
19
|
-
type HookConfig = { [key in ReleaseHookKey]: ReleaseHookValue };
|
|
18
|
+
type HookConfig = { [key in ReleaseHookKey]: ReleaseHookValue; };
|
|
20
19
|
interface ReleaseConfig {
|
|
21
20
|
/**
|
|
22
21
|
* 生命周期钩子配置
|
|
@@ -129,11 +128,42 @@ interface ReleaseConfig {
|
|
|
129
128
|
publishArgs: string[];
|
|
130
129
|
/**
|
|
131
130
|
* 是否跳过发布前检查(如 ping、whoami 等校验)
|
|
131
|
+
*
|
|
132
|
+
* 启用后可减少发布前的网络请求,但可能无法提前发现登录状态异常或 Registry 不可用等问题。
|
|
132
133
|
*/
|
|
133
134
|
skipChecks: boolean;
|
|
135
|
+
/**
|
|
136
|
+
* 发布前的文件清理配置
|
|
137
|
+
*
|
|
138
|
+
* 用于控制复制到临时发布目录后的清理行为,不会修改源项目文件。
|
|
139
|
+
*/
|
|
140
|
+
cleanup: {
|
|
141
|
+
/**
|
|
142
|
+
* 是否清理 package.json
|
|
143
|
+
*
|
|
144
|
+
* 包括移除开发相关字段、应用 publishConfig,并保留发布所需内容。
|
|
145
|
+
*/
|
|
146
|
+
packageJson: boolean | {
|
|
147
|
+
/** 需要忽略的字段,支持 `a.b.c` */
|
|
148
|
+
ignoreFields: string[];
|
|
149
|
+
/** 需要保留的脚本,默认:`preinstall | install | postinstall` */
|
|
150
|
+
keepScripts: string[];
|
|
151
|
+
};
|
|
152
|
+
/**
|
|
153
|
+
* 是否清理 README.md
|
|
154
|
+
*
|
|
155
|
+
* 仅保留主要内容,并移除或精简不需要随包发布的文档内容。
|
|
156
|
+
*/
|
|
157
|
+
readme: boolean;
|
|
158
|
+
/**
|
|
159
|
+
* 是否在发布之后删除临时目录
|
|
160
|
+
*/
|
|
161
|
+
removeTempDir: boolean;
|
|
162
|
+
};
|
|
134
163
|
};
|
|
135
164
|
/**
|
|
136
165
|
* GitHub Release 相关配置
|
|
166
|
+
*
|
|
137
167
|
* 控制是否创建 Release、发布方式以及上传资源。
|
|
138
168
|
*/
|
|
139
169
|
github: {
|
|
@@ -201,9 +231,10 @@ interface ParsePresetOptions {
|
|
|
201
231
|
/** 对默认 commit types 进行转换 */
|
|
202
232
|
transformTypes?: (types: CommitType$1[]) => CommitType$1[];
|
|
203
233
|
}
|
|
204
|
-
|
|
205
|
-
declare const
|
|
206
|
-
declare const
|
|
234
|
+
type GOptions = Simplify<GenerateOptions & ParsePresetOptions>;
|
|
235
|
+
declare const createGenerator: (options: GOptions) => Promise<ConventionalChangelog>;
|
|
236
|
+
declare const getChangelog: (options: GOptions) => Promise<string>;
|
|
237
|
+
declare const generateChangelog: (options: GOptions) => Promise<void>;
|
|
207
238
|
//#endregion
|
|
208
239
|
//#region src/npm/publish.d.ts
|
|
209
240
|
interface PublishTagOptions {
|
package/dist/index.mjs
CHANGED
|
@@ -8,6 +8,7 @@ import "conventional-recommended-bump";
|
|
|
8
8
|
import { writeFile } from "node:fs/promises";
|
|
9
9
|
import { finished } from "node:stream/promises";
|
|
10
10
|
import { bold, compareUrl, each, heading, link, list, newline, segments, small, words } from "@conventional-changelog/template";
|
|
11
|
+
import "@peiyanlu/node-utils";
|
|
11
12
|
import { blue, cyan, green, magenta, red, underline, yellow } from "ansis";
|
|
12
13
|
//#region src/config.ts
|
|
13
14
|
function defineConfig(config) {
|
|
@@ -218,7 +219,6 @@ const publishTagToNpm = async (options) => {
|
|
|
218
219
|
[pkgName, version] = tag.split(tagSeparator);
|
|
219
220
|
console.log(`🔍 Parsed tag → package: "${yellow(pkgName)}", version: "${green(version)}"`);
|
|
220
221
|
} else {
|
|
221
|
-
pkgName = ".";
|
|
222
222
|
version = tag;
|
|
223
223
|
console.log(`🔍 Parsed tag → version only: "${green(version)}"`);
|
|
224
224
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peiyanlu/release",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "Release project tool.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -17,42 +17,27 @@
|
|
|
17
17
|
},
|
|
18
18
|
"./package.json": "./package.json"
|
|
19
19
|
},
|
|
20
|
-
"scripts": {
|
|
21
|
-
"dev": "tsdown -w",
|
|
22
|
-
"build": "tsdown",
|
|
23
|
-
"prepublishOnly": "tsdown",
|
|
24
|
-
"release": "node index.js",
|
|
25
|
-
"test": "vitest run",
|
|
26
|
-
"test:cov": "vitest run --coverage"
|
|
27
|
-
},
|
|
28
20
|
"dependencies": {
|
|
29
21
|
"@actions/core": "^3.0.1",
|
|
30
22
|
"@actions/github": "^9.1.1",
|
|
31
|
-
"@clack/prompts": "^1.
|
|
23
|
+
"@clack/prompts": "^1.7.0",
|
|
24
|
+
"@conventional-changelog/template": "^1.2.1",
|
|
32
25
|
"@octokit/rest": "^22.0.1",
|
|
33
|
-
"@peiyanlu/cli-utils": "^0.0.
|
|
34
|
-
"@peiyanlu/
|
|
26
|
+
"@peiyanlu/cli-utils": "^0.0.15",
|
|
27
|
+
"@peiyanlu/node-utils": "^0.0.3",
|
|
28
|
+
"@peiyanlu/ts-utils": "^0.0.12",
|
|
35
29
|
"ansis": "^4.3.1",
|
|
36
30
|
"commander": "^15.0.0",
|
|
37
|
-
"conventional-changelog": "^8.
|
|
38
|
-
"conventional-changelog-conventionalcommits": "^10.2.
|
|
39
|
-
"conventional-recommended-bump": "^12.
|
|
40
|
-
"@conventional-changelog/template": "^1.2.0",
|
|
31
|
+
"conventional-changelog": "^8.1.0",
|
|
32
|
+
"conventional-changelog-conventionalcommits": "^10.2.1",
|
|
33
|
+
"conventional-recommended-bump": "^12.1.0",
|
|
41
34
|
"mime-types": "^3.0.2",
|
|
35
|
+
"minimatch": "^10.2.5",
|
|
42
36
|
"open": "^11.0.0",
|
|
43
|
-
"publint": "^0.3.
|
|
37
|
+
"publint": "^0.3.22",
|
|
44
38
|
"semver": "^7.8.5",
|
|
45
39
|
"tinyglobby": "^0.2.17"
|
|
46
40
|
},
|
|
47
|
-
"devDependencies": {
|
|
48
|
-
"@types/mime-types": "^3.0.1",
|
|
49
|
-
"@types/node": "^24.12.4",
|
|
50
|
-
"@types/semver": "^7.7.1",
|
|
51
|
-
"@vitest/coverage-v8": "^4.1.8",
|
|
52
|
-
"tsdown": "^0.22.3",
|
|
53
|
-
"typescript": "^6.0.3",
|
|
54
|
-
"vitest": "^4.1.9"
|
|
55
|
-
},
|
|
56
41
|
"author": {
|
|
57
42
|
"name": "YanluPei",
|
|
58
43
|
"email": "peiyanlu@vip.qq.com"
|