@deot/dev-releaser 2.9.9 → 2.9.10

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.
Files changed (3) hide show
  1. package/dist/index.cjs +520 -591
  2. package/dist/index.js +496 -571
  3. package/package.json +6 -6
package/dist/index.js CHANGED
@@ -1,574 +1,499 @@
1
- import chalk from 'chalk';
2
- import { Locals, Logger, Shell, Utils } from '@deot/dev-shared';
3
- import * as path from 'node:path';
4
- import { createRequire } from 'node:module';
5
- import fs from 'fs-extra';
6
- import { CommitParser } from 'conventional-commits-parser';
7
- import semver from 'semver';
8
- import { confirm, input } from '@inquirer/prompts';
9
-
10
- const cwd = process.cwd();
11
- const require$ = createRequire(cwd);
12
- const HASH = "-hash-";
13
- const SUFFIX = "🐒💨🙊";
14
- const parserOptions = {
15
- noteKeywords: ["BREAKING CHANGE", "Breaking Change"]
1
+ import chalk from "chalk";
2
+ import { Locals, Logger, Shell, Utils } from "@deot/dev-shared";
3
+ import * as path from "node:path";
4
+ import { createRequire } from "node:module";
5
+ import fs from "fs-extra";
6
+ import { CommitParser } from "conventional-commits-parser";
7
+ import semver from "semver";
8
+ import { confirm, input } from "@inquirer/prompts";
9
+ //#region packages/releaser/src/release.ts
10
+ var require$ = createRequire(process.cwd());
11
+ var HASH = "-hash-";
12
+ var SUFFIX = "🐒💨🙊";
13
+ var reBreaking = new RegExp(`(${{ noteKeywords: ["BREAKING CHANGE", "Breaking Change"] }.noteKeywords.join(")|(")})`);
14
+ var commitParser = new CommitParser();
15
+ var Release = class {
16
+ packageDir;
17
+ packageName;
18
+ packageFolderName;
19
+ packageOptions;
20
+ packageRelation;
21
+ config;
22
+ changeLog;
23
+ version;
24
+ commits;
25
+ commandOptions;
26
+ constructor(config, commandOptions) {
27
+ const { packageDir, packageRelation } = Locals.impl();
28
+ if (typeof config === "string") {
29
+ const packageFolderName = config;
30
+ config = {
31
+ dir: path.resolve(packageDir, packageFolderName),
32
+ name: packageFolderName
33
+ };
34
+ }
35
+ this.packageDir = config.dir;
36
+ this.packageName = Locals.getPackageName(config.name);
37
+ this.packageFolderName = config.name;
38
+ this.packageOptions = require$(`${this.packageDir}/package.json`);
39
+ this.packageRelation = packageRelation[this.packageName] || [];
40
+ this.config = config;
41
+ this.commits = [];
42
+ this.changeLog = "";
43
+ this.version = "";
44
+ this.commandOptions = commandOptions;
45
+ }
46
+ async parseCommits() {
47
+ const { workspace } = Locals.impl();
48
+ const { packageFolderName, commandOptions } = this;
49
+ const [latestTag] = await this.getTags();
50
+ Logger.log(chalk.yellow(`Last Release Tag`) + `: ${latestTag || "<none>"}`);
51
+ const params = [
52
+ "--no-pager",
53
+ "log",
54
+ `${latestTag}..HEAD`,
55
+ `--format=%B%n${HASH}%n%H${SUFFIX}`
56
+ ];
57
+ let { stdout } = await Shell.exec("git", params);
58
+ let skipGetLog = false;
59
+ if (latestTag) {
60
+ const log1 = await Shell.exec("git", ["rev-parse", latestTag]);
61
+ const log2 = await Shell.exec("git", [
62
+ "--no-pager",
63
+ "log",
64
+ "-1",
65
+ "--format=%H"
66
+ ]);
67
+ if (log1.stdout === log2.stdout) skipGetLog = true;
68
+ }
69
+ if (!skipGetLog && !stdout) {
70
+ if (latestTag) params.splice(2, 1, `${latestTag}`);
71
+ else params.splice(2, 1, "HEAD");
72
+ ({stdout} = await Shell.exec("git", params));
73
+ }
74
+ const allowTypes = [
75
+ "feat",
76
+ `fix`,
77
+ `break change`,
78
+ `style`,
79
+ `perf`,
80
+ `types`,
81
+ `refactor`,
82
+ `chore`
83
+ ];
84
+ const rePlugin = new RegExp(`^(${allowTypes.join("|")})${workspace ? `\\(([,\\w-]+(,))?${packageFolderName}((,)[,\\w-]+)?\\)` : "(\\(.+\\))?"}: .*`, "i");
85
+ const reAll = workspace && new RegExp(`^(${allowTypes.join("|")})\\(\\*\\): .*`, "i");
86
+ const allCommits = commandOptions.commits || stdout.split(SUFFIX);
87
+ const commits = allCommits.filter((commit) => {
88
+ const chunk = commit.trim();
89
+ return chunk && (rePlugin.test(chunk) || reAll && reAll.test(chunk));
90
+ }).map((commit) => {
91
+ const node = commitParser.parse(commit);
92
+ const body = node.body || node.footer;
93
+ if (!node.type) node.type = commitParser.parse(node.header?.replace(/\(.+\)!?:/, ":") || "").type;
94
+ if (!node.hash) node.hash = commit.split(HASH).pop()?.trim();
95
+ node.breaking = reBreaking.test(body) || /!:/.test(node.header);
96
+ node.effect = false;
97
+ node.custom = false;
98
+ return node;
99
+ });
100
+ if (!commits.length) Logger.log(chalk.red(`No Commits Found.`));
101
+ else Logger.log(chalk.yellow(`Found `) + chalk.bold(`${allCommits.length}`) + ` Commits, ` + chalk.bold(`${commits.length}`) + " Commits Valid");
102
+ const { skipUpdatePackage } = commandOptions;
103
+ if (commits.length && skipUpdatePackage) {
104
+ let skip = false;
105
+ if (typeof skipUpdatePackage === "boolean" && skipUpdatePackage) skip = await confirm({
106
+ message: `Skip Update(${this.packageName}@${this.packageOptions.version}):`,
107
+ default: true
108
+ });
109
+ else if (typeof skipUpdatePackage === "string" && (skipUpdatePackage === "*" || skipUpdatePackage.split(",").includes(this.packageName))) skip = true;
110
+ if (skip) {
111
+ Logger.log(chalk.red(`Skipping Update\n`));
112
+ return;
113
+ }
114
+ }
115
+ await this.updateVersion();
116
+ await this.updateCommits(commits);
117
+ const { forceUpdatePackage } = commandOptions;
118
+ if (!commits.length && forceUpdatePackage) {
119
+ let force = false;
120
+ if (typeof forceUpdatePackage === "boolean" && forceUpdatePackage) force = await confirm({
121
+ message: `Force Update(${this.packageName}@${this.packageOptions.version}):`,
122
+ default: true
123
+ });
124
+ else if (typeof forceUpdatePackage === "string" && (forceUpdatePackage === "*" || forceUpdatePackage.split(",").includes(this.packageName))) force = true;
125
+ if (force) {
126
+ const versionChanged = `\`${this.packageOptions.version}\` -> \`${this.version}\``;
127
+ this.commits = [{
128
+ type: "chore",
129
+ header: `chore(${this.packageFolderName || "release"}): force-publish ${versionChanged}`,
130
+ hash: "",
131
+ effect: false,
132
+ breaking: false,
133
+ custom: true
134
+ }];
135
+ this.changeLog = `### Force Update Package\n\n- ${versionChanged}`.trim();
136
+ }
137
+ }
138
+ }
139
+ async getTags() {
140
+ const { packageName } = this;
141
+ const params = [
142
+ "tag",
143
+ "--list",
144
+ `'${packageName}@*'`,
145
+ "--sort",
146
+ "-v:refname"
147
+ ];
148
+ const { stdout } = await Shell.exec("git", params);
149
+ return stdout.split("\n");
150
+ }
151
+ rebuildChangeLog(commits) {
152
+ const { packageDir } = this;
153
+ const { homepage, workspace } = Locals.impl();
154
+ const logPath = path.resolve(packageDir, "./CHANGELOG.md");
155
+ const logFile = fs.existsSync(logPath) ? fs.readFileSync(logPath, "utf-8") : "";
156
+ const notes = {
157
+ breaking: [],
158
+ features: [],
159
+ fixes: [],
160
+ updates: []
161
+ };
162
+ /**
163
+ * (close #1) -> issues
164
+ * close (#1) -> issues
165
+ * (#1) -> pull request
166
+ *
167
+ * JS支持后行断言
168
+ * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions/Assertions
169
+ */
170
+ const closeRegxp = /\(?(closes? )\(?#((\d+))\)/gi;
171
+ const pullRegxp = /(?<!closes? )\((#(\d+))\)/gi;
172
+ for (const commit of commits) {
173
+ const { effect, breaking, hash, header, type } = commit;
174
+ /**
175
+ * 确保当log一样时,commit hash判断log是不一致的
176
+ * close: commit下自行提交(log可能会相同)需添加ref
177
+ * pr: 由github pr生成一条提交记录(唯一的, 无需添加ref)
178
+ */
179
+ const ref = !hash || pullRegxp.test(header) ? "" : ` ([${hash?.substring(0, 7)}](${homepage}/commit/${hash}))`;
180
+ let message = header?.trim();
181
+ if (workspace && !effect) message = message.replace(/\(.+\)!?:/, ":");
182
+ message = message.replace(pullRegxp, `[$1](${homepage}/pull/$2)`).replace(closeRegxp, `[$1$2](${homepage}/issues/$2)`) + ref;
183
+ if (breaking) notes.breaking.push(message);
184
+ else if (type === "fix") notes.fixes.push(message);
185
+ else if (type === "feat") notes.features.push(message);
186
+ else notes.updates.push(message);
187
+ }
188
+ Object.keys(notes).forEach((i) => {
189
+ notes[i] = notes[i].filter((j) => {
190
+ return !logFile.includes(j);
191
+ });
192
+ });
193
+ const parts = [
194
+ notes.breaking.length ? `### Breaking Changes\n\n- ${notes.breaking.join("\n- ")}`.trim() : "",
195
+ notes.fixes.length ? `### Bugfixes\n\n- ${notes.fixes.join("\n- ")}`.trim() : "",
196
+ notes.features.length ? `### Features\n\n- ${notes.features.join("\n- ")}`.trim() : "",
197
+ notes.updates.length ? `### Updates\n\n- ${notes.updates.join("\n- ")}`.trim() : ""
198
+ ].filter(Boolean);
199
+ const newLog = parts.join("\n\n");
200
+ return !parts.length || logFile.includes(newLog) ? "" : newLog;
201
+ }
202
+ async updateVersion() {
203
+ const { packageOptions, commits, commandOptions } = this;
204
+ const { version } = packageOptions;
205
+ let newVersion = "";
206
+ if (commandOptions.customVersion) {
207
+ newVersion = commandOptions.customVersion;
208
+ if (!/\d+.\d+.\d+/.test(newVersion) || version === newVersion) newVersion = await input({
209
+ message: `Custom Update Version(${this.packageName}@${version}):`,
210
+ default: "",
211
+ validate: (answer) => {
212
+ if (!/\d+.\d+.\d+/.test(answer)) return "Version Should Be Like x.x.x";
213
+ if (answer === version) return "Version Should Be Diff Than Before";
214
+ return true;
215
+ }
216
+ });
217
+ } else {
218
+ const intersection = [
219
+ commandOptions.major && "major",
220
+ commandOptions.minor && "minor",
221
+ commandOptions.patch && "patch"
222
+ ].filter((i) => !!i);
223
+ if (intersection.length) newVersion = semver.inc(version, intersection[0]) || "";
224
+ else {
225
+ const types = new Set(commits.map(({ type }) => type));
226
+ const level = commits.some((commit) => !!commit.breaking) ? "major" : types.has("feat") ? "minor" : "patch";
227
+ newVersion = semver.inc(version, level) || "";
228
+ }
229
+ }
230
+ this.version = newVersion;
231
+ }
232
+ isChanged() {
233
+ return !!this.commits.length;
234
+ }
235
+ async updateCommits(commits, source) {
236
+ if (!commits.length) return;
237
+ const { packageName } = this;
238
+ const olds = this.commits.map((i) => JSON.stringify({
239
+ ...i,
240
+ effect: false
241
+ }));
242
+ const newCommits = commits.filter((i) => {
243
+ return !olds.includes(JSON.stringify({
244
+ ...i,
245
+ effect: false
246
+ }));
247
+ }).map((j) => {
248
+ return {
249
+ ...j,
250
+ effect: !!source
251
+ };
252
+ });
253
+ if (newCommits.length && this.commits.length) this.commits = this.commits.filter((i) => !i.custom);
254
+ const commits$ = this.commits.concat(newCommits);
255
+ if (source) Logger.log(chalk.magenta(`MERGE COMMITS: `) + chalk.bold(`${commits.length}`) + " Commits. merge " + chalk.yellow(source) + " into " + chalk.green(packageName));
256
+ else Logger.log(``);
257
+ const changeLog = this.rebuildChangeLog(commits$);
258
+ if (changeLog) {
259
+ this.commits = commits$;
260
+ this.changeLog = changeLog;
261
+ } else if (commits.length) Logger.log(chalk.red(`${commits.length} Commits Already Exists.`));
262
+ }
263
+ async updatePackageOptions(relationVerisons = {}) {
264
+ if (!this.isChanged()) return;
265
+ const { packageDir, packageOptions, commandOptions } = this;
266
+ const { dependencies, devDependencies } = packageOptions;
267
+ const newVersion = this.version;
268
+ Logger.log(chalk.yellow(`New Version: `) + `${newVersion}`);
269
+ packageOptions.version = newVersion;
270
+ if (Object.keys(this.packageRelation).length) for (const packageName$ in relationVerisons) {
271
+ const newVersion$ = relationVerisons[packageName$];
272
+ if (dependencies?.[packageName$]) dependencies[packageName$] = newVersion$;
273
+ if (devDependencies?.[packageName$]) devDependencies[packageName$] = newVersion$;
274
+ }
275
+ if (commandOptions.dryRun) {
276
+ Logger.log(chalk.yellow(`Skipping package.json Update`));
277
+ return;
278
+ }
279
+ Logger.log(chalk.yellow(`Updating `) + "package.json");
280
+ fs.outputFileSync(`${packageDir}/package.json`, JSON.stringify(packageOptions, null, 2));
281
+ }
282
+ async updateChangelog() {
283
+ if (!this.isChanged()) return;
284
+ const { packageName, packageDir, packageOptions, commandOptions } = this;
285
+ const title = `# ${packageName} ChangeLog`;
286
+ const [date] = (/* @__PURE__ */ new Date()).toISOString().split("T");
287
+ const logPath = path.resolve(packageDir, "./CHANGELOG.md");
288
+ const logFile = fs.existsSync(logPath) ? fs.readFileSync(logPath, "utf-8") : "";
289
+ const oldNotes = logFile.startsWith(title) ? logFile.slice(title.length).trim() : logFile;
290
+ const newLog = [
291
+ `## v${packageOptions.version}`,
292
+ `_${date}_`,
293
+ this.changeLog
294
+ ].filter(Boolean).join("\n\n");
295
+ if (commandOptions.dryRun) {
296
+ Logger.log(chalk.yellow(`New ChangeLog:`) + `\n${newLog}`);
297
+ return;
298
+ }
299
+ Logger.log(chalk.yellow(`Updating `) + `CHANGELOG.md`);
300
+ let content = [
301
+ title,
302
+ newLog,
303
+ oldNotes
304
+ ].filter(Boolean).join("\n\n");
305
+ if (!content.endsWith("\n")) content += "\n";
306
+ fs.writeFileSync(logPath, content, "utf-8");
307
+ }
308
+ async test() {
309
+ if (!this.isChanged()) return;
310
+ const { commandOptions } = this;
311
+ if (commandOptions.dryRun) {
312
+ Logger.log(chalk.yellow("Skipping Test"));
313
+ return;
314
+ } else Logger.log(chalk.yellow("Test..."));
315
+ await Shell.exec(`npm run test -- --package-name ${this.packageName}${commandOptions.coverage ? "" : " --no-coverage"}`);
316
+ }
317
+ async build() {
318
+ if (!this.isChanged()) return;
319
+ const { commandOptions } = this;
320
+ if (commandOptions.dryRun) {
321
+ Logger.log(chalk.yellow("Skipping Build"));
322
+ return;
323
+ } else Logger.log(chalk.yellow("Build..."));
324
+ await Shell.exec(`npm run build -- --package-name ${this.packageName}`);
325
+ }
326
+ async publish() {
327
+ const { commandOptions } = this;
328
+ if (!this.isChanged() || !commandOptions.publish) return;
329
+ const { packageDir, packageName } = this;
330
+ Logger.log(chalk.magenta(`PUBLISH: `) + packageName);
331
+ if (commandOptions.dryRun) {
332
+ Logger.log(chalk.yellow(`Skipping Publish`));
333
+ return;
334
+ }
335
+ Logger.log(chalk.cyan(`\n Publishing to NPM`));
336
+ await Shell.spawn("npm", [
337
+ "publish",
338
+ "--no-git-checks",
339
+ "--access",
340
+ "public"
341
+ ], { cwd: packageDir });
342
+ }
343
+ async tag() {
344
+ const { commandOptions } = this;
345
+ if (!this.isChanged() || !commandOptions.tag) return;
346
+ const { packageDir, packageName, packageOptions } = this;
347
+ Logger.log(chalk.magenta(`TAG: `) + packageName);
348
+ if (commandOptions.dryRun) {
349
+ Logger.log(chalk.yellow(`Skipping Git Tag`));
350
+ return;
351
+ }
352
+ const tagName = `${packageName}@${packageOptions.version}`;
353
+ Logger.log(chalk.blue(`\n Tagging`) + chalk.grey(`${tagName}`));
354
+ await Shell.spawn("git", ["tag", tagName], { cwd: packageDir });
355
+ }
356
+ /**
357
+ * 清理tags,仅保留最后一个tag. 相当于tags仅用于记录commit开始的位置
358
+ * git push以后执行。即时删除失败了,也不会产生不必要的影响
359
+ */
360
+ async cleanTagsAndKeepLastTag() {
361
+ const { commandOptions } = this;
362
+ if (!commandOptions.keepLastTag) return;
363
+ let tags = await this.getTags();
364
+ tags = tags.slice(1).filter((i) => !!i).reverse();
365
+ if (!tags.length) return;
366
+ const { packageName } = this;
367
+ Logger.log(chalk.magenta(`CLEAN TAGS: `) + packageName);
368
+ if (commandOptions.dryRun) {
369
+ Logger.log(chalk.yellow(`Skipping Tags Clean`));
370
+ return;
371
+ }
372
+ await tags.reduce((preProcess, tag) => {
373
+ preProcess = preProcess.then(() => Shell.spawn("git", [
374
+ "push",
375
+ "origin",
376
+ "--delete",
377
+ tag
378
+ ])).then(() => Shell.spawn("git", [
379
+ "tag",
380
+ "--delete",
381
+ tag
382
+ ]));
383
+ return preProcess;
384
+ }, Promise.resolve());
385
+ }
386
+ async process() {
387
+ const { workspace } = Locals.impl();
388
+ const { packageName, packageDir, packageFolderName } = this;
389
+ if (!packageDir || !fs.pathExists(packageDir)) throw new RangeError(`Could not find directory for package: ${packageFolderName}`);
390
+ Logger.log(chalk.cyan(`Releasing ${packageName}`) + " from " + chalk.grey(`${workspace}/${packageFolderName}`));
391
+ await this.parseCommits();
392
+ return this;
393
+ }
16
394
  };
17
- const reBreaking = new RegExp(`(${parserOptions.noteKeywords.join(")|(")})`);
18
- const commitParser = new CommitParser();
19
- class Release {
20
- packageDir;
21
- packageName;
22
- packageFolderName;
23
- packageOptions;
24
- packageRelation;
25
- config;
26
- changeLog;
27
- version;
28
- commits;
29
- commandOptions;
30
- constructor(config, commandOptions) {
31
- const { packageDir, packageRelation } = Locals.impl();
32
- if (typeof config === "string") {
33
- const packageFolderName = config;
34
- const packageDir$ = path.resolve(packageDir, packageFolderName);
35
- config = {
36
- dir: packageDir$,
37
- name: packageFolderName
38
- };
39
- }
40
- this.packageDir = config.dir;
41
- this.packageName = Locals.getPackageName(config.name);
42
- this.packageFolderName = config.name;
43
- this.packageOptions = require$(`${this.packageDir}/package.json`);
44
- this.packageRelation = packageRelation[this.packageName] || [];
45
- this.config = config;
46
- this.commits = [];
47
- this.changeLog = "";
48
- this.version = "";
49
- this.commandOptions = commandOptions;
50
- }
51
- async parseCommits() {
52
- const { workspace } = Locals.impl();
53
- const { packageFolderName, commandOptions } = this;
54
- const [latestTag] = await this.getTags();
55
- Logger.log(chalk.yellow(`Last Release Tag`) + `: ${latestTag || "<none>"}`);
56
- const params = ["--no-pager", "log", `${latestTag}..HEAD`, `--format=%B%n${HASH}%n%H${SUFFIX}`];
57
- let {
58
- stdout
59
- } = await Shell.exec("git", params);
60
- let skipGetLog = false;
61
- if (latestTag) {
62
- const log1 = await Shell.exec("git", ["rev-parse", latestTag]);
63
- const log2 = await Shell.exec("git", ["--no-pager", "log", "-1", "--format=%H"]);
64
- if (log1.stdout === log2.stdout) {
65
- skipGetLog = true;
66
- }
67
- }
68
- if (!skipGetLog && !stdout) {
69
- if (latestTag) {
70
- params.splice(2, 1, `${latestTag}`);
71
- } else {
72
- params.splice(2, 1, "HEAD");
73
- }
74
- ({ stdout } = await Shell.exec("git", params));
75
- }
76
- const allowTypes = ["feat", `fix`, `break change`, `style`, `perf`, `types`, `refactor`, `chore`];
77
- const rePlugin = new RegExp(`^(${allowTypes.join("|")})${workspace ? `\\(([,\\w-]+(,))?${packageFolderName}((,)[,\\w-]+)?\\)` : "(\\(.+\\))?"}: .*`, "i");
78
- const reAll = workspace && new RegExp(`^(${allowTypes.join("|")})\\(\\*\\): .*`, "i");
79
- const allCommits = commandOptions.commits || stdout.split(SUFFIX);
80
- const commits = allCommits.filter((commit) => {
81
- const chunk = commit.trim();
82
- return chunk && (rePlugin.test(chunk) || reAll && reAll.test(chunk));
83
- }).map((commit) => {
84
- const node = commitParser.parse(commit);
85
- const body = node.body || node.footer;
86
- if (!node.type) node.type = commitParser.parse(node.header?.replace(/\(.+\)!?:/, ":") || "").type;
87
- if (!node.hash) node.hash = commit.split(HASH).pop()?.trim();
88
- node.breaking = reBreaking.test(body) || /!:/.test(node.header);
89
- node.effect = false;
90
- node.custom = false;
91
- return node;
92
- });
93
- if (!commits.length) {
94
- Logger.log(chalk.red(`No Commits Found.`));
95
- } else {
96
- Logger.log(
97
- chalk.yellow(`Found `) + chalk.bold(`${allCommits.length}`) + ` Commits, ` + chalk.bold(`${commits.length}`) + " Commits Valid"
98
- );
99
- }
100
- const { skipUpdatePackage } = commandOptions;
101
- if (commits.length && skipUpdatePackage) {
102
- let skip = false;
103
- if (typeof skipUpdatePackage === "boolean" && skipUpdatePackage) {
104
- skip = await confirm({
105
- message: `Skip Update(${this.packageName}@${this.packageOptions.version}):`,
106
- default: true
107
- });
108
- } else if (typeof skipUpdatePackage === "string" && (skipUpdatePackage === "*" || skipUpdatePackage.split(",").includes(this.packageName))) {
109
- skip = true;
110
- }
111
- if (skip) {
112
- Logger.log(chalk.red(`Skipping Update
113
- `));
114
- return;
115
- }
116
- }
117
- await this.updateVersion();
118
- await this.updateCommits(commits);
119
- const { forceUpdatePackage } = commandOptions;
120
- if (!commits.length && forceUpdatePackage) {
121
- let force = false;
122
- if (typeof forceUpdatePackage === "boolean" && forceUpdatePackage) {
123
- force = await confirm({
124
- message: `Force Update(${this.packageName}@${this.packageOptions.version}):`,
125
- default: true
126
- });
127
- } else if (typeof forceUpdatePackage === "string" && (forceUpdatePackage === "*" || forceUpdatePackage.split(",").includes(this.packageName))) {
128
- force = true;
129
- }
130
- if (force) {
131
- const oldVersion = this.packageOptions.version;
132
- const versionChanged = `\`${oldVersion}\` -> \`${this.version}\``;
133
- this.commits = [
134
- {
135
- type: "chore",
136
- header: `chore(${this.packageFolderName || "release"}): force-publish ${versionChanged}`,
137
- hash: "",
138
- effect: false,
139
- breaking: false,
140
- custom: true
141
- }
142
- ];
143
- this.changeLog = `### Force Update Package
144
-
145
- - ${versionChanged}`.trim();
146
- }
147
- }
148
- }
149
- async getTags() {
150
- const { packageName } = this;
151
- const params = ["tag", "--list", `'${packageName}@*'`, "--sort", "-v:refname"];
152
- const { stdout } = await Shell.exec("git", params);
153
- return stdout.split("\n");
154
- }
155
- rebuildChangeLog(commits) {
156
- const { packageDir } = this;
157
- const { homepage, workspace } = Locals.impl();
158
- const logPath = path.resolve(packageDir, "./CHANGELOG.md");
159
- const logFile = fs.existsSync(logPath) ? fs.readFileSync(logPath, "utf-8") : "";
160
- const notes = {
161
- breaking: [],
162
- features: [],
163
- fixes: [],
164
- updates: []
165
- };
166
- const closeRegxp = /\(?(closes? )\(?#((\d+))\)/ig;
167
- const pullRegxp = /(?<!closes? )\((#(\d+))\)/ig;
168
- for (const commit of commits) {
169
- const { effect, breaking, hash, header, type } = commit;
170
- const ref = !hash || pullRegxp.test(header) ? "" : ` ([${hash?.substring(0, 7)}](${homepage}/commit/${hash}))`;
171
- let message = header?.trim();
172
- if (workspace && !effect) {
173
- message = message.replace(/\(.+\)!?:/, ":");
174
- }
175
- message = message.replace(pullRegxp, `[$1](${homepage}/pull/$2)`).replace(closeRegxp, `[$1$2](${homepage}/issues/$2)`) + ref;
176
- if (breaking) {
177
- notes.breaking.push(message);
178
- } else if (type === "fix") {
179
- notes.fixes.push(message);
180
- } else if (type === "feat") {
181
- notes.features.push(message);
182
- } else {
183
- notes.updates.push(message);
184
- }
185
- }
186
- Object.keys(notes).forEach((i) => {
187
- notes[i] = notes[i].filter((j) => {
188
- return !logFile.includes(j);
189
- });
190
- });
191
- const parts = [
192
- notes.breaking.length ? `### Breaking Changes
193
-
194
- - ${notes.breaking.join("\n- ")}`.trim() : "",
195
- notes.fixes.length ? `### Bugfixes
196
-
197
- - ${notes.fixes.join("\n- ")}`.trim() : "",
198
- notes.features.length ? `### Features
199
-
200
- - ${notes.features.join("\n- ")}`.trim() : "",
201
- notes.updates.length ? `### Updates
202
-
203
- - ${notes.updates.join("\n- ")}`.trim() : ""
204
- ].filter(Boolean);
205
- const newLog = parts.join("\n\n");
206
- return !parts.length || logFile.includes(newLog) ? "" : newLog;
207
- }
208
- async updateVersion() {
209
- const { packageOptions, commits, commandOptions } = this;
210
- const { version } = packageOptions;
211
- let newVersion = "";
212
- if (commandOptions.customVersion) {
213
- newVersion = commandOptions.customVersion;
214
- if (!/\d+.\d+.\d+/.test(newVersion) || version === newVersion) {
215
- newVersion = await input(
216
- {
217
- message: `Custom Update Version(${this.packageName}@${version}):`,
218
- default: "",
219
- validate: (answer) => {
220
- if (!/\d+.\d+.\d+/.test(answer)) {
221
- return "Version Should Be Like x.x.x";
222
- }
223
- if (answer === version) {
224
- return "Version Should Be Diff Than Before";
225
- }
226
- return true;
227
- }
228
- }
229
- );
230
- }
231
- } else {
232
- const intersection = [
233
- commandOptions.major && "major",
234
- commandOptions.minor && "minor",
235
- commandOptions.patch && "patch"
236
- ].filter((i) => !!i);
237
- if (intersection.length) {
238
- newVersion = semver.inc(version, intersection[0]) || "";
239
- } else {
240
- const types = new Set(commits.map(({
241
- type
242
- }) => type));
243
- const breaking = commits.some((commit) => !!commit.breaking);
244
- const level = breaking ? "major" : types.has("feat") ? "minor" : "patch";
245
- newVersion = semver.inc(version, level) || "";
246
- }
247
- }
248
- this.version = newVersion;
249
- }
250
- isChanged() {
251
- return !!this.commits.length;
252
- }
253
- async updateCommits(commits, source) {
254
- if (!commits.length) return;
255
- const { packageName } = this;
256
- const olds = this.commits.map((i) => JSON.stringify({ ...i, effect: false }));
257
- const newCommits = commits.filter((i) => {
258
- return !olds.includes(JSON.stringify({ ...i, effect: false }));
259
- }).map((j) => {
260
- return {
261
- ...j,
262
- effect: !!source
263
- };
264
- });
265
- if (newCommits.length && this.commits.length) {
266
- this.commits = this.commits.filter((i) => !i.custom);
267
- }
268
- const commits$ = this.commits.concat(newCommits);
269
- if (source) {
270
- Logger.log(
271
- chalk.magenta(`MERGE COMMITS: `) + chalk.bold(`${commits.length}`) + ` Commits. merge ` + chalk.yellow(source) + " into " + chalk.green(packageName)
272
- );
273
- } else {
274
- Logger.log(``);
275
- }
276
- const changeLog = this.rebuildChangeLog(commits$);
277
- if (changeLog) {
278
- this.commits = commits$;
279
- this.changeLog = changeLog;
280
- } else if (commits.length) {
281
- Logger.log(chalk.red(`${commits.length} Commits Already Exists.`));
282
- }
283
- }
284
- async updatePackageOptions(relationVerisons = {}) {
285
- if (!this.isChanged()) return;
286
- const { packageDir, packageOptions, commandOptions } = this;
287
- const { dependencies, devDependencies } = packageOptions;
288
- const newVersion = this.version;
289
- Logger.log(chalk.yellow(`New Version: `) + `${newVersion}`);
290
- packageOptions.version = newVersion;
291
- if (Object.keys(this.packageRelation).length) {
292
- for (const packageName$ in relationVerisons) {
293
- const newVersion$ = relationVerisons[packageName$];
294
- if (dependencies?.[packageName$]) {
295
- dependencies[packageName$] = newVersion$;
296
- }
297
- if (devDependencies?.[packageName$]) {
298
- devDependencies[packageName$] = newVersion$;
299
- }
300
- }
301
- }
302
- if (commandOptions.dryRun) {
303
- Logger.log(chalk.yellow(`Skipping package.json Update`));
304
- return;
305
- }
306
- Logger.log(chalk.yellow(`Updating `) + "package.json");
307
- fs.outputFileSync(`${packageDir}/package.json`, JSON.stringify(packageOptions, null, 2));
308
- }
309
- async updateChangelog() {
310
- if (!this.isChanged()) return;
311
- const { packageName, packageDir, packageOptions, commandOptions } = this;
312
- const title = `# ${packageName} ChangeLog`;
313
- const [date] = (/* @__PURE__ */ new Date()).toISOString().split("T");
314
- const logPath = path.resolve(packageDir, "./CHANGELOG.md");
315
- const logFile = fs.existsSync(logPath) ? fs.readFileSync(logPath, "utf-8") : "";
316
- const oldNotes = logFile.startsWith(title) ? logFile.slice(title.length).trim() : logFile;
317
- const parts = [
318
- `## v${packageOptions.version}`,
319
- `_${date}_`,
320
- this.changeLog
321
- ].filter(Boolean);
322
- const newLog = parts.join("\n\n");
323
- if (commandOptions.dryRun) {
324
- Logger.log(chalk.yellow(`New ChangeLog:`) + `
325
- ${newLog}`);
326
- return;
327
- }
328
- Logger.log(chalk.yellow(`Updating `) + `CHANGELOG.md`);
329
- let content = [title, newLog, oldNotes].filter(Boolean).join("\n\n");
330
- if (!content.endsWith("\n")) content += "\n";
331
- fs.writeFileSync(logPath, content, "utf-8");
332
- }
333
- async test() {
334
- if (!this.isChanged()) return;
335
- const { commandOptions } = this;
336
- if (commandOptions.dryRun) {
337
- Logger.log(chalk.yellow("Skipping Test"));
338
- return;
339
- } else {
340
- Logger.log(chalk.yellow("Test..."));
341
- }
342
- await Shell.exec(`npm run test -- --package-name ${this.packageName}${commandOptions.coverage ? "" : " --no-coverage"}`);
343
- }
344
- async build() {
345
- if (!this.isChanged()) return;
346
- const { commandOptions } = this;
347
- if (commandOptions.dryRun) {
348
- Logger.log(chalk.yellow("Skipping Build"));
349
- return;
350
- } else {
351
- Logger.log(chalk.yellow("Build..."));
352
- }
353
- await Shell.exec(`npm run build -- --package-name ${this.packageName}`);
354
- }
355
- async publish() {
356
- const { commandOptions } = this;
357
- if (!this.isChanged() || !commandOptions.publish) return;
358
- const { packageDir, packageName } = this;
359
- Logger.log(chalk.magenta(`PUBLISH: `) + packageName);
360
- if (commandOptions.dryRun) {
361
- Logger.log(chalk.yellow(`Skipping Publish`));
362
- return;
363
- }
364
- Logger.log(chalk.cyan(`
365
- Publishing to NPM`));
366
- await Shell.spawn(
367
- "npm",
368
- ["publish", "--no-git-checks", "--access", "public"],
369
- {
370
- cwd: packageDir
371
- }
372
- );
373
- }
374
- async tag() {
375
- const { commandOptions } = this;
376
- if (!this.isChanged() || !commandOptions.tag) return;
377
- const { packageDir, packageName, packageOptions } = this;
378
- Logger.log(chalk.magenta(`TAG: `) + packageName);
379
- if (commandOptions.dryRun) {
380
- Logger.log(chalk.yellow(`Skipping Git Tag`));
381
- return;
382
- }
383
- const tagName = `${packageName}@${packageOptions.version}`;
384
- Logger.log(chalk.blue(`
385
- Tagging`) + chalk.grey(`${tagName}`));
386
- await Shell.spawn(
387
- "git",
388
- ["tag", tagName],
389
- {
390
- cwd: packageDir
391
- }
392
- );
393
- }
394
- /**
395
- * 清理tags,仅保留最后一个tag. 相当于tags仅用于记录commit开始的位置
396
- * git push以后执行。即时删除失败了,也不会产生不必要的影响
397
- */
398
- async cleanTagsAndKeepLastTag() {
399
- const { commandOptions } = this;
400
- if (!commandOptions.keepLastTag) return;
401
- let tags = await this.getTags();
402
- tags = tags.slice(1).filter((i) => !!i).reverse();
403
- if (!tags.length) return;
404
- const { packageName } = this;
405
- Logger.log(chalk.magenta(`CLEAN TAGS: `) + packageName);
406
- if (commandOptions.dryRun) {
407
- Logger.log(chalk.yellow(`Skipping Tags Clean`));
408
- return;
409
- }
410
- await tags.reduce(
411
- (preProcess, tag) => {
412
- preProcess = preProcess.then(() => Shell.spawn("git", ["push", "origin", "--delete", tag])).then(() => Shell.spawn("git", ["tag", "--delete", tag]));
413
- return preProcess;
414
- },
415
- Promise.resolve()
416
- );
417
- }
418
- async process() {
419
- const { workspace } = Locals.impl();
420
- const { packageName, packageDir, packageFolderName } = this;
421
- if (!packageDir || !fs.pathExists(packageDir)) {
422
- throw new RangeError(`Could not find directory for package: ${packageFolderName}`);
423
- }
424
- Logger.log(chalk.cyan(`Releasing ${packageName}`) + " from " + chalk.grey(`${workspace}/${packageFolderName}`));
425
- await this.parseCommits();
426
- return this;
427
- }
428
- }
429
- const release = (options, commandOptions) => {
430
- return new Release(options, commandOptions);
395
+ var release = (options, commandOptions) => {
396
+ return new Release(options, commandOptions);
431
397
  };
432
-
433
- const run = (options) => Utils.autoCatch(async () => {
434
- options = {
435
- coverage: true,
436
- dryRun: true,
437
- tag: true,
438
- publish: true,
439
- commit: true,
440
- push: true,
441
- keepLastTag: false,
442
- ...options
443
- };
444
- const locals = Locals.impl();
445
- options.forceUpdateName = Locals.getRealPackageName(options.forceUpdateName);
446
- options.skipUpdateName = Locals.getRealPackageName(options.skipUpdateName);
447
- if (options.dryRun) {
448
- Logger.log(
449
- chalk.magenta(`DRY RUN: `) + "No files will be modified.",
450
- options
451
- );
452
- }
453
- let inputs = [];
454
- if (locals.workspace) {
455
- inputs = locals.normalizePackageFolderNames;
456
- } else {
457
- inputs = [""];
458
- }
459
- const instances = {};
460
- await inputs.reduce(
461
- (preProcess, packageFolderName) => {
462
- preProcess = preProcess.then(() => release(packageFolderName, options).process()).then((instance) => {
463
- instances[packageFolderName] = instance;
464
- });
465
- return preProcess;
466
- },
467
- Promise.resolve()
468
- );
469
- Logger.log(chalk.blue(`---------------------
470
- `));
471
- let message = `chore(release): publish
472
-
473
- `;
474
- const relationVerisons = {};
475
- await inputs.reduce(
476
- (preProcess, packageFolderName) => {
477
- const instance = instances[packageFolderName];
478
- instance.packageRelation.forEach((i) => {
479
- const packageFolderName$ = Locals.getPackageFolderName(i);
480
- const instance$ = instances[packageFolderName$];
481
- if (instance$.commits.length > 0) {
482
- instance.updateCommits(instance$.commits, instance$.packageName);
483
- }
484
- });
485
- if (instance.commits.length) {
486
- preProcess = preProcess.then(() => Logger.log(chalk.magenta(`CHANGED: `) + instance.packageName)).then(() => instance.test()).then(() => instance.build()).then(() => instance.updatePackageOptions(relationVerisons)).then(() => instance.updateChangelog()).then(() => {
487
- message += `- ${instance.packageName}@${instance.packageOptions.version}
488
- `;
489
- relationVerisons[instance.packageName] = `^${instance.packageOptions.version}`;
490
- });
491
- }
492
- return preProcess;
493
- },
494
- Promise.resolve()
495
- );
496
- Logger.log(chalk.blue(`
497
- ---------------------
498
- `));
499
- const isChanged = Object.keys(relationVerisons).length;
500
- if (!isChanged) {
501
- Logger.log(chalk.magenta(`COMMIT: `) + "Nothing Chanaged Found.");
502
- } else if (!options.commit) {
503
- Logger.log(chalk.magenta(`COMMIT: `) + "Disabled.");
504
- } else if (options.dryRun) {
505
- Logger.log(chalk.magenta(`COMMIT: `) + chalk.yellow(`Skipping Git Commit`) + `
506
- ${message}`);
507
- } else {
508
- Logger.log(chalk.magenta(`CHANGED: `) + `pnpm-lock.yaml`);
509
- await Shell.spawn("npx", ["pnpm", "install", "--lockfile-only"]);
510
- Logger.log(chalk.magenta(`COMMIT: `) + `CHANGELOG.md, package.json, pnpm-lock.yaml`);
511
- await Shell.spawn("git", ["add", process.cwd()]);
512
- await Shell.spawn("git", ["commit", "--m", `'${message}'`]);
513
- }
514
- if ((options.keepLastTag || options.push) && !options.dryRun) {
515
- Logger.log(chalk.yellow("Git Fetch..."));
516
- await Shell.spawn("git", ["fetch", "--prune", "--prune-tags"]);
517
- }
518
- Logger.log(chalk.blue(`
519
- ---------------------
520
- `));
521
- await inputs.reduce(
522
- (preProcess, packageFolderName) => {
523
- const instance = instances[packageFolderName];
524
- preProcess = preProcess.then(() => instance.publish()).then(() => instance.tag());
525
- return preProcess;
526
- },
527
- Promise.resolve()
528
- );
529
- Logger.log(chalk.blue(`
530
- ---------------------
531
- `));
532
- if (!isChanged) {
533
- Logger.log(chalk.magenta(`PUSH: `) + "Nothing Chanaged.");
534
- } else if (!options.push) {
535
- Logger.log(chalk.magenta(`PUSH: `) + "Push Disabled.");
536
- } else if (options.dryRun) {
537
- Logger.log(chalk.magenta(`PUSH: `) + "Skipping Git Push");
538
- } else {
539
- Logger.log(chalk.yellow("Git Pull/Push..."));
540
- await Shell.spawn("git", ["pull", "--rebase"]);
541
- await Shell.spawn("git", ["push"]);
542
- await Shell.spawn("git", ["push", "--tags"]);
543
- }
544
- Logger.log(chalk.blue(`
545
- ---------------------
546
- `));
547
- await inputs.reduce(
548
- (preProcess, packageFolderName) => {
549
- const instance = instances[packageFolderName];
550
- preProcess = preProcess.then(() => instance.cleanTagsAndKeepLastTag());
551
- return preProcess;
552
- },
553
- Promise.resolve()
554
- );
555
- Logger.log(chalk.magenta(`FINISH: `) + chalk.green(`Release Successed.`));
556
- if (options.dryRun) {
557
- Logger.log(
558
- chalk.green("NO DRY RUN WAY: ") + chalk.grey(`npm run release -- --no-dry-run
559
- `)
560
- );
561
- }
562
- }, {
563
- onError: (e) => {
564
- if (typeof e === "number" && e === 1) {
565
- Logger.error("发布失败");
566
- } else {
567
- Logger.error(e);
568
- }
569
- Logger.log(chalk.magenta(`FINISH: `) + chalk.red(`Release Failed.`));
570
- process.exit(1);
571
- }
572
- });
573
-
398
+ //#endregion
399
+ //#region packages/releaser/src/index.ts
400
+ var run = (options) => Utils.autoCatch(async () => {
401
+ options = {
402
+ coverage: true,
403
+ dryRun: true,
404
+ tag: true,
405
+ publish: true,
406
+ commit: true,
407
+ push: true,
408
+ keepLastTag: false,
409
+ ...options
410
+ };
411
+ const locals = Locals.impl();
412
+ options.forceUpdateName = Locals.getRealPackageName(options.forceUpdateName);
413
+ options.skipUpdateName = Locals.getRealPackageName(options.skipUpdateName);
414
+ if (options.dryRun) Logger.log(chalk.magenta(`DRY RUN: `) + "No files will be modified.", options);
415
+ let inputs = [];
416
+ if (locals.workspace) inputs = locals.normalizePackageFolderNames;
417
+ else inputs = [""];
418
+ const instances = {};
419
+ await inputs.reduce((preProcess, packageFolderName) => {
420
+ preProcess = preProcess.then(() => release(packageFolderName, options).process()).then((instance) => {
421
+ instances[packageFolderName] = instance;
422
+ });
423
+ return preProcess;
424
+ }, Promise.resolve());
425
+ Logger.log(chalk.blue(`---------------------\n`));
426
+ let message = `chore(release): publish\n\n`;
427
+ const relationVerisons = {};
428
+ await inputs.reduce((preProcess, packageFolderName) => {
429
+ const instance = instances[packageFolderName];
430
+ instance.packageRelation.forEach((i) => {
431
+ const instance$ = instances[Locals.getPackageFolderName(i)];
432
+ if (instance$.commits.length > 0) instance.updateCommits(instance$.commits, instance$.packageName);
433
+ });
434
+ if (instance.commits.length) preProcess = preProcess.then(() => Logger.log(chalk.magenta(`CHANGED: `) + instance.packageName)).then(() => instance.test()).then(() => instance.build()).then(() => instance.updatePackageOptions(relationVerisons)).then(() => instance.updateChangelog()).then(() => {
435
+ message += `- ${instance.packageName}@${instance.packageOptions.version}\n`;
436
+ relationVerisons[instance.packageName] = `^${instance.packageOptions.version}`;
437
+ });
438
+ return preProcess;
439
+ }, Promise.resolve());
440
+ Logger.log(chalk.blue(`\n---------------------\n`));
441
+ const isChanged = Object.keys(relationVerisons).length;
442
+ if (!isChanged) Logger.log(chalk.magenta(`COMMIT: `) + "Nothing Chanaged Found.");
443
+ else if (!options.commit) Logger.log(chalk.magenta(`COMMIT: `) + "Disabled.");
444
+ else if (options.dryRun) Logger.log(chalk.magenta(`COMMIT: `) + chalk.yellow(`Skipping Git Commit`) + `\n${message}`);
445
+ else {
446
+ Logger.log(chalk.magenta(`CHANGED: `) + `pnpm-lock.yaml`);
447
+ await Shell.spawn("npx", [
448
+ "pnpm",
449
+ "install",
450
+ "--lockfile-only"
451
+ ]);
452
+ Logger.log(chalk.magenta(`COMMIT: `) + `CHANGELOG.md, package.json, pnpm-lock.yaml`);
453
+ await Shell.spawn("git", ["add", process.cwd()]);
454
+ await Shell.spawn("git", [
455
+ "commit",
456
+ "--m",
457
+ `'${message}'`
458
+ ]);
459
+ }
460
+ if ((options.keepLastTag || options.push) && !options.dryRun) {
461
+ Logger.log(chalk.yellow("Git Fetch..."));
462
+ await Shell.spawn("git", [
463
+ "fetch",
464
+ "--prune",
465
+ "--prune-tags"
466
+ ]);
467
+ }
468
+ Logger.log(chalk.blue(`\n---------------------\n`));
469
+ await inputs.reduce((preProcess, packageFolderName) => {
470
+ const instance = instances[packageFolderName];
471
+ preProcess = preProcess.then(() => instance.publish()).then(() => instance.tag());
472
+ return preProcess;
473
+ }, Promise.resolve());
474
+ Logger.log(chalk.blue(`\n---------------------\n`));
475
+ if (!isChanged) Logger.log(chalk.magenta(`PUSH: `) + "Nothing Chanaged.");
476
+ else if (!options.push) Logger.log(chalk.magenta(`PUSH: `) + "Push Disabled.");
477
+ else if (options.dryRun) Logger.log(chalk.magenta(`PUSH: `) + "Skipping Git Push");
478
+ else {
479
+ Logger.log(chalk.yellow("Git Pull/Push..."));
480
+ await Shell.spawn("git", ["pull", "--rebase"]);
481
+ await Shell.spawn("git", ["push"]);
482
+ await Shell.spawn("git", ["push", "--tags"]);
483
+ }
484
+ Logger.log(chalk.blue(`\n---------------------\n`));
485
+ await inputs.reduce((preProcess, packageFolderName) => {
486
+ const instance = instances[packageFolderName];
487
+ preProcess = preProcess.then(() => instance.cleanTagsAndKeepLastTag());
488
+ return preProcess;
489
+ }, Promise.resolve());
490
+ Logger.log(chalk.magenta(`FINISH: `) + chalk.green(`Release Successed.`));
491
+ if (options.dryRun) Logger.log(chalk.green("NO DRY RUN WAY: ") + chalk.grey(`npm run release -- --no-dry-run\n`));
492
+ }, { onError: (e) => {
493
+ if (typeof e === "number" && e === 1) Logger.error("发布失败");
494
+ else Logger.error(e);
495
+ Logger.log(chalk.magenta(`FINISH: `) + chalk.red(`Release Failed.`));
496
+ process.exit(1);
497
+ } });
498
+ //#endregion
574
499
  export { run };