@coze-arch/rush-publish-plugin 0.0.3 → 0.0.4
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/lib/index.js +1628 -25
- package/package.json +14 -6
- package/lib/action/change/action.js +0 -40
- package/lib/action/change/amend-commit.js +0 -23
- package/lib/action/change/helper.js +0 -126
- package/lib/action/change/index.js +0 -17
- package/lib/action/change/types.js +0 -3
- package/lib/action/publish/action.js +0 -68
- package/lib/action/publish/apply-new-version.js +0 -27
- package/lib/action/publish/changelog.js +0 -79
- package/lib/action/publish/confirm.js +0 -31
- package/lib/action/publish/const.js +0 -5
- package/lib/action/publish/git.js +0 -36
- package/lib/action/publish/index.js +0 -43
- package/lib/action/publish/packages.js +0 -67
- package/lib/action/publish/push-to-remote.js +0 -107
- package/lib/action/publish/request-bump-type.js +0 -56
- package/lib/action/publish/types.js +0 -12
- package/lib/action/publish/version.js +0 -111
- package/lib/action/release/action.js +0 -34
- package/lib/action/release/git.js +0 -28
- package/lib/action/release/index.js +0 -31
- package/lib/action/release/manifest.js +0 -18
- package/lib/action/release/package.js +0 -49
- package/lib/action/release/plan.js +0 -38
- package/lib/action/release/release.js +0 -50
- package/lib/action/release/types.js +0 -3
- package/lib/generate-changelog/generate-changelog.js +0 -127
- package/lib/types.js +0 -3
- package/lib/utils/exec.js +0 -24
- package/lib/utils/get-rush-config.js +0 -16
- package/lib/utils/git.js +0 -70
- package/lib/utils/random.js +0 -19
- package/lib/utils/whoami.js +0 -16
package/lib/index.js
CHANGED
|
@@ -1,28 +1,1631 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var path = require('path');
|
|
4
|
+
var fs$1 = require('fs');
|
|
5
|
+
var commander = require('commander');
|
|
6
|
+
var shelljs = require('shelljs');
|
|
7
|
+
var fs = require('fs/promises');
|
|
8
|
+
var rushSdk = require('@rushstack/rush-sdk');
|
|
9
|
+
var crypto = require('crypto');
|
|
10
|
+
var semver = require('semver');
|
|
11
|
+
var chalk = require('chalk');
|
|
12
|
+
var prompts = require('@inquirer/prompts');
|
|
13
|
+
var dayjs = require('dayjs');
|
|
14
|
+
var ChangeFile = require('@rushstack/rush-sdk/lib/api/ChangeFile');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 日志工具
|
|
18
|
+
*
|
|
19
|
+
* 提供统一的日志输出接口,支持不同级别的日志,包括彩色输出
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
// 日志级别枚举
|
|
23
|
+
var LogLevel; (function (LogLevel) {
|
|
24
|
+
const DEBUG = 0; LogLevel[LogLevel["DEBUG"] = DEBUG] = "DEBUG";
|
|
25
|
+
const INFO = 1; LogLevel[LogLevel["INFO"] = INFO] = "INFO";
|
|
26
|
+
const WARN = 2; LogLevel[LogLevel["WARN"] = WARN] = "WARN";
|
|
27
|
+
const ERROR = 3; LogLevel[LogLevel["ERROR"] = ERROR] = "ERROR";
|
|
28
|
+
const SUCCESS = 4; LogLevel[LogLevel["SUCCESS"] = SUCCESS] = "SUCCESS";
|
|
29
|
+
const NONE = 5; LogLevel[LogLevel["NONE"] = NONE] = "NONE"; // 用于完全禁用日志
|
|
30
|
+
})(LogLevel || (LogLevel = {}));
|
|
31
|
+
|
|
32
|
+
// ANSI 颜色代码
|
|
33
|
+
const colors = {
|
|
34
|
+
reset: '\x1b[0m',
|
|
35
|
+
debug: '\x1b[36m', // 青色
|
|
36
|
+
info: '\x1b[34m', // 蓝色
|
|
37
|
+
warn: '\x1b[33m', // 黄色
|
|
38
|
+
error: '\x1b[31m', // 红色
|
|
39
|
+
success: '\x1b[32m', // 绿色
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// 默认日志级别
|
|
43
|
+
let currentLogLevel = LogLevel.INFO;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 基础日志函数
|
|
47
|
+
* @param level 日志级别
|
|
48
|
+
* @param message 日志消息
|
|
49
|
+
* @param showPrefix 是否显示前缀,默认为 true
|
|
50
|
+
*/
|
|
51
|
+
function log(level, message, showPrefix = true) {
|
|
52
|
+
if (level < currentLogLevel) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let prefix = '';
|
|
57
|
+
let color = '';
|
|
58
|
+
|
|
59
|
+
switch (level) {
|
|
60
|
+
case LogLevel.DEBUG: {
|
|
61
|
+
prefix = '[DEBUG]';
|
|
62
|
+
color = colors.debug;
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
case LogLevel.WARN: {
|
|
66
|
+
prefix = '[WARN]';
|
|
67
|
+
color = colors.warn;
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
case LogLevel.ERROR: {
|
|
71
|
+
prefix = '[ERROR]';
|
|
72
|
+
color = colors.error;
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
case LogLevel.SUCCESS: {
|
|
76
|
+
prefix = '[SUCCESS]';
|
|
77
|
+
color = colors.success;
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
case LogLevel.INFO:
|
|
81
|
+
default: {
|
|
82
|
+
prefix = '[INFO]';
|
|
83
|
+
color = colors.info;
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 格式化日志前缀
|
|
89
|
+
const formattedPrefix = showPrefix ? `${color}${prefix}${colors.reset} ` : '';
|
|
90
|
+
|
|
91
|
+
// 输出日志
|
|
92
|
+
console.log(`${formattedPrefix}${message}`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 导出的日志工具
|
|
97
|
+
*/
|
|
98
|
+
const logger = {
|
|
99
|
+
/**
|
|
100
|
+
* 调试日志
|
|
101
|
+
* @param message 日志消息
|
|
102
|
+
* @param showPrefix 是否显示前缀,默认为 true
|
|
103
|
+
*/
|
|
104
|
+
debug(message, showPrefix = true) {
|
|
105
|
+
log(LogLevel.DEBUG, message, showPrefix);
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* 信息日志
|
|
110
|
+
* @param message 日志消息
|
|
111
|
+
* @param showPrefix 是否显示前缀,默认为 true
|
|
112
|
+
*/
|
|
113
|
+
info(message, showPrefix = true) {
|
|
114
|
+
log(LogLevel.INFO, message, showPrefix);
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* 警告日志
|
|
119
|
+
* @param message 日志消息
|
|
120
|
+
* @param showPrefix 是否显示前缀,默认为 true
|
|
121
|
+
*/
|
|
122
|
+
warn(message, showPrefix = true) {
|
|
123
|
+
log(LogLevel.WARN, message, showPrefix);
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* 错误日志
|
|
128
|
+
* @param message 日志消息
|
|
129
|
+
* @param showPrefix 是否显示前缀,默认为 true
|
|
130
|
+
*/
|
|
131
|
+
error(message, showPrefix = true) {
|
|
132
|
+
log(LogLevel.ERROR, message, showPrefix);
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* 成功日志
|
|
137
|
+
* @param message 日志消息
|
|
138
|
+
* @param showPrefix 是否显示前缀,默认为 true
|
|
139
|
+
*/
|
|
140
|
+
success(message, showPrefix = true) {
|
|
141
|
+
log(LogLevel.SUCCESS, message, showPrefix);
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
class ExecError extends Error {
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
constructor(result) {
|
|
150
|
+
super(result.stderr || result.stdout);
|
|
151
|
+
this.code = result.code;
|
|
152
|
+
this.stderr = result.stderr;
|
|
153
|
+
this.stdout = result.stdout;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const exec = (
|
|
158
|
+
cmd,
|
|
159
|
+
options = { silent: true },
|
|
160
|
+
) =>
|
|
161
|
+
new Promise((r, j) => {
|
|
162
|
+
shelljs.exec(cmd, options, (code, stdout, stderr) => {
|
|
163
|
+
if (code === 0) {
|
|
164
|
+
r({ code, stdout, stderr });
|
|
165
|
+
} else {
|
|
166
|
+
j(new ExecError({ code, stderr, stdout }));
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
const serializeFilesName = (output) =>
|
|
172
|
+
output
|
|
173
|
+
.split('\n')
|
|
174
|
+
.map(line => {
|
|
175
|
+
if (line) {
|
|
176
|
+
const trimmedLine = line.trim();
|
|
177
|
+
return trimmedLine;
|
|
178
|
+
}
|
|
179
|
+
return '';
|
|
180
|
+
})
|
|
181
|
+
.filter(line => line && line.length > 0);
|
|
182
|
+
|
|
183
|
+
const getChangedFilesFromCached = async () => {
|
|
184
|
+
const output = await exec('git diff --name-only --diff-filter=ACMR --cached');
|
|
185
|
+
if (!output) {
|
|
186
|
+
return [];
|
|
187
|
+
}
|
|
188
|
+
return serializeFilesName(output.stdout);
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* 获取当前分支名称
|
|
193
|
+
* @returns string
|
|
194
|
+
*/
|
|
195
|
+
const getCurrentBranchName = async () => {
|
|
196
|
+
const { stdout } = await exec('git rev-parse --abbrev-ref HEAD');
|
|
197
|
+
return stdout.trim();
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const isMainBranch = async () => {
|
|
201
|
+
const currentBranchName = await getCurrentBranchName();
|
|
202
|
+
return currentBranchName === 'main';
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const getChangedFiles = async () => {
|
|
206
|
+
const output = await exec('git diff --name-only --diff-filter=ACMR');
|
|
207
|
+
return serializeFilesName(output.stdout);
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* 确保没有未提交的变更
|
|
212
|
+
*/
|
|
213
|
+
const ensureNotUncommittedChanges = async () => {
|
|
214
|
+
const changedFiles = (
|
|
215
|
+
await Promise.all([getChangedFilesFromCached(), getChangedFiles()])
|
|
216
|
+
).flat();
|
|
217
|
+
if (changedFiles.length > 0) {
|
|
218
|
+
throw new Error(
|
|
219
|
+
'There are uncommitted changes in the working tree, please commit them first.',
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
return true;
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* 获取当前 Git 仓库设置的 origin 远程源地址
|
|
227
|
+
* @param cwd 当前工作目录
|
|
228
|
+
* @returns origin 远程源地址
|
|
229
|
+
*/
|
|
230
|
+
const getCurrentOrigin = async (
|
|
231
|
+
cwd = process.cwd(),
|
|
232
|
+
) => {
|
|
233
|
+
try {
|
|
234
|
+
const { stdout } = await exec('git remote get-url origin', { cwd });
|
|
235
|
+
return stdout.trim();
|
|
236
|
+
} catch (error) {
|
|
237
|
+
return undefined;
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
const readJsonFile = async ( path) => {
|
|
242
|
+
const content = await fs.readFile(path, 'utf8');
|
|
243
|
+
return JSON.parse(content) ;
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
const writeJsonFile = async (path, data) => {
|
|
247
|
+
await fs.writeFile(path, JSON.stringify(data, null, 2), 'utf8');
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
const isFileExists = async (path) =>
|
|
251
|
+
fs
|
|
252
|
+
.access(path)
|
|
253
|
+
.then(() => true)
|
|
254
|
+
.catch(() => false);
|
|
255
|
+
|
|
256
|
+
const isDirExists = async (path) =>
|
|
257
|
+
fs
|
|
258
|
+
.access(path)
|
|
259
|
+
.then(() => true)
|
|
260
|
+
.catch(() => false);
|
|
261
|
+
|
|
262
|
+
const getRushConfiguration = (() => {
|
|
263
|
+
let rushConfiguration = null;
|
|
264
|
+
return (useCache = true) => {
|
|
265
|
+
if (!useCache) {
|
|
266
|
+
rushConfiguration = null;
|
|
267
|
+
}
|
|
268
|
+
return (rushConfiguration ||= rushSdk.RushConfiguration.loadFromDefaultLocation({
|
|
269
|
+
startingFolder: process.cwd(),
|
|
270
|
+
}));
|
|
271
|
+
};
|
|
272
|
+
})();
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* 更新依赖版本
|
|
276
|
+
*/
|
|
277
|
+
const updateDependencyVersions = async (
|
|
278
|
+
packageJson,
|
|
279
|
+
) => {
|
|
280
|
+
const rushConfiguration = getRushConfiguration();
|
|
281
|
+
const { dependencies } = packageJson;
|
|
282
|
+
if (dependencies) {
|
|
283
|
+
for (const [dep, ver] of Object.entries(dependencies)) {
|
|
284
|
+
const project = rushConfiguration.getProjectByName(dep);
|
|
285
|
+
if (/^workspace:/.test(ver) && project) {
|
|
286
|
+
dependencies[dep] = project.packageJson.version;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return Promise.resolve(packageJson);
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
const applyCozePublishConfig = async (
|
|
294
|
+
packageJson,
|
|
295
|
+
) => {
|
|
296
|
+
const { cozePublishConfig } = packageJson;
|
|
297
|
+
if (cozePublishConfig) {
|
|
298
|
+
const keys = Object.keys(cozePublishConfig);
|
|
299
|
+
for (const key of keys) {
|
|
300
|
+
packageJson[key] = cozePublishConfig[key];
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return Promise.resolve(packageJson);
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
const applyPublishConfig = async (project) => {
|
|
307
|
+
const jobs = [
|
|
308
|
+
updateDependencyVersions,
|
|
309
|
+
applyCozePublishConfig,
|
|
310
|
+
];
|
|
311
|
+
const packageJsonPath = path.join(project.projectFolder, 'package.json');
|
|
312
|
+
let packageJson = await readJsonFile(packageJsonPath);
|
|
313
|
+
for (const job of jobs) {
|
|
314
|
+
packageJson = await job(packageJson);
|
|
315
|
+
}
|
|
316
|
+
await writeJsonFile(packageJsonPath, packageJson);
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* 发布包
|
|
321
|
+
*/
|
|
322
|
+
const publishPackage = async (
|
|
323
|
+
project,
|
|
324
|
+
releaseOptions,
|
|
325
|
+
) => {
|
|
326
|
+
const { dryRun, registry } = releaseOptions;
|
|
327
|
+
const token = process.env.NPM_AUTH_TOKEN;
|
|
328
|
+
const { version } = project.packageJson;
|
|
329
|
+
const tag = version.includes('alpha')
|
|
330
|
+
? 'alpha'
|
|
331
|
+
: version.includes('beta')
|
|
332
|
+
? 'beta'
|
|
333
|
+
: 'latest';
|
|
334
|
+
const args = [`NPM_AUTH_TOKEN=${token}`, 'npm', 'publish', `--tag ${tag}`];
|
|
335
|
+
if (dryRun) {
|
|
336
|
+
args.push('--dry-run');
|
|
337
|
+
}
|
|
338
|
+
if (registry) {
|
|
339
|
+
args.push(`--registry=${registry}`);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
await exec(args.join(' '), {
|
|
343
|
+
cwd: project.projectFolder,
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
logger.success(`- Published ${project.packageName}@${version}`);
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
const releasePackage = async (
|
|
350
|
+
releaseManifest,
|
|
351
|
+
releaseOptions,
|
|
352
|
+
) => {
|
|
353
|
+
const { project } = releaseManifest;
|
|
354
|
+
const { packageName } = project;
|
|
355
|
+
logger.info(`Preparing release for package: ${packageName}`);
|
|
356
|
+
await applyPublishConfig(project);
|
|
357
|
+
await publishPackage(project, releaseOptions);
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
const buildProjects = async (releaseManifests) => {
|
|
361
|
+
const packageNames = releaseManifests.map(
|
|
362
|
+
manifest => manifest.project.packageName,
|
|
363
|
+
);
|
|
364
|
+
const buildCommands = `rush build ${packageNames.map(name => `--to ${name}`).join(' ')}`;
|
|
365
|
+
await exec(buildCommands);
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
const releasePackages = async (
|
|
369
|
+
releaseManifests,
|
|
370
|
+
releaseOptions,
|
|
371
|
+
) => {
|
|
372
|
+
await buildProjects(releaseManifests);
|
|
373
|
+
await Promise.all(
|
|
374
|
+
releaseManifests.map(async manifest => {
|
|
375
|
+
await releasePackage(manifest, releaseOptions);
|
|
376
|
+
}),
|
|
377
|
+
);
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
var ReleaseType; (function (ReleaseType) {
|
|
381
|
+
const ALPHA = 'alpha'; ReleaseType["ALPHA"] = ALPHA;
|
|
382
|
+
const BETA = 'beta'; ReleaseType["BETA"] = BETA;
|
|
383
|
+
const LATEST = 'latest'; ReleaseType["LATEST"] = LATEST;
|
|
384
|
+
})(ReleaseType || (ReleaseType = {}));
|
|
385
|
+
|
|
386
|
+
const calReleaseType = (version) => {
|
|
387
|
+
const tag = version.includes('alpha')
|
|
388
|
+
? ReleaseType.ALPHA
|
|
389
|
+
: version.includes('beta')
|
|
390
|
+
? ReleaseType.BETA
|
|
391
|
+
: ReleaseType.LATEST;
|
|
392
|
+
return tag;
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
const calReleasePlan = (releaseManifests) => {
|
|
396
|
+
const plan = releaseManifests.map(r => calReleaseType(r.version));
|
|
397
|
+
if (plan.some(p => p === ReleaseType.LATEST)) {
|
|
398
|
+
return ReleaseType.LATEST;
|
|
399
|
+
}
|
|
400
|
+
if (plan.some(p => p === ReleaseType.BETA)) {
|
|
401
|
+
return ReleaseType.BETA;
|
|
402
|
+
}
|
|
403
|
+
return ReleaseType.ALPHA;
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
const checkReleasePlan = (
|
|
407
|
+
releaseManifests,
|
|
408
|
+
branchName,
|
|
409
|
+
) => {
|
|
410
|
+
const releasePlan = calReleasePlan(releaseManifests);
|
|
411
|
+
if (
|
|
412
|
+
releasePlan === ReleaseType.LATEST &&
|
|
413
|
+
!['main', 'feat/auto-publish'].includes(branchName)
|
|
414
|
+
) {
|
|
415
|
+
throw new Error('For LATEST release, should be on main branch only.');
|
|
416
|
+
}
|
|
417
|
+
return true;
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* 构建发布依赖树
|
|
422
|
+
*/
|
|
423
|
+
function buildReleaseManifest(
|
|
424
|
+
packages,
|
|
425
|
+
) {
|
|
426
|
+
const rushConfiguration = getRushConfiguration();
|
|
427
|
+
return packages.map(pkg => {
|
|
428
|
+
const project = rushConfiguration.getProjectByName(pkg.packageName);
|
|
429
|
+
if (!project) {
|
|
430
|
+
throw new Error(`Cannot find project: ${pkg.packageName}`);
|
|
431
|
+
}
|
|
432
|
+
return { project, version: project.packageJson.version };
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* 从 git tag 中解析需要发布的包信息
|
|
438
|
+
*/
|
|
439
|
+
const getPackagesToPublish = async (
|
|
440
|
+
commit,
|
|
441
|
+
) => {
|
|
442
|
+
// 获取指定 commit 的所有 tag
|
|
443
|
+
const { stdout } = await exec(`git tag --points-at ${commit}`);
|
|
444
|
+
const tags = stdout.split('\n').filter(Boolean);
|
|
445
|
+
|
|
446
|
+
// 解析符合 v/{packagename}@{version} 格式的 tag
|
|
447
|
+
const packagePattern = /^v\/(.+)@(.+)$/;
|
|
448
|
+
const packages = [];
|
|
449
|
+
|
|
450
|
+
for (const tag of tags) {
|
|
451
|
+
const match = tag.match(packagePattern);
|
|
452
|
+
if (match) {
|
|
453
|
+
const [, packageName, version] = match;
|
|
454
|
+
packages.push({
|
|
455
|
+
packageName,
|
|
456
|
+
version,
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
return packages;
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
async function release(options) {
|
|
465
|
+
const { commit, dryRun = false, registry } = options;
|
|
466
|
+
|
|
467
|
+
// 1. 获取需要发布的包列表
|
|
468
|
+
const packagesToPublish = await getPackagesToPublish(commit);
|
|
469
|
+
if (packagesToPublish.length === 0) {
|
|
470
|
+
logger.warn('No packages to publish');
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// 2. 构建发布依赖树
|
|
475
|
+
const releaseManifests = buildReleaseManifest(packagesToPublish);
|
|
476
|
+
logger.info('Release manifests:');
|
|
477
|
+
logger.info(
|
|
478
|
+
releaseManifests
|
|
479
|
+
.map(manifest => `${manifest.project.packageName}@${manifest.version}`)
|
|
480
|
+
.join(', '),
|
|
481
|
+
false,
|
|
482
|
+
);
|
|
483
|
+
const branchName = await getCurrentBranchName();
|
|
484
|
+
checkReleasePlan(releaseManifests, branchName);
|
|
485
|
+
await exec(`git checkout ${commit}`);
|
|
486
|
+
|
|
487
|
+
await releasePackages(releaseManifests, { dryRun, registry });
|
|
488
|
+
logger.success('All packages published successfully!');
|
|
489
|
+
logger.success(
|
|
490
|
+
releaseManifests
|
|
491
|
+
.map(manifest => `- ${manifest.project.packageName}@${manifest.version}`)
|
|
492
|
+
.join('\n'),
|
|
493
|
+
false,
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
const installAction$2 = (program) => {
|
|
498
|
+
program
|
|
499
|
+
.command('release')
|
|
500
|
+
.description('Release packages based on git tags.')
|
|
501
|
+
.requiredOption('--commit <string>', '需要执行发布的 commit id')
|
|
502
|
+
.option('--dry-run', '是否只执行不真实发布', false)
|
|
503
|
+
.option(
|
|
504
|
+
'-r, --registry <string>',
|
|
505
|
+
'发布到的 registry',
|
|
506
|
+
'https://registry.npmjs.org',
|
|
507
|
+
)
|
|
508
|
+
.action(async (options) => {
|
|
509
|
+
try {
|
|
510
|
+
if (!options.commit) {
|
|
511
|
+
throw new Error('请提供需要发布的 commit id');
|
|
512
|
+
}
|
|
513
|
+
if (!process.env.NPM_AUTH_TOKEN) {
|
|
514
|
+
throw new Error('请设置 NPM_AUTH_TOKEN 环境变量');
|
|
515
|
+
}
|
|
516
|
+
await release(options);
|
|
517
|
+
} catch (error) {
|
|
518
|
+
logger.error('Release failed!');
|
|
519
|
+
logger.error((error ).message);
|
|
520
|
+
process.exit(1);
|
|
521
|
+
}
|
|
522
|
+
});
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
const GIT_REPO_URL_REGEX = /git@github\.com:([^\/]+)\/([^\.]+)\.git/;
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* 生成指定长度的随机字符串(使用 crypto 模块)
|
|
529
|
+
* @param digit 字符串长度
|
|
530
|
+
* @returns 随机字符串
|
|
531
|
+
*/
|
|
532
|
+
function randomHash(digit) {
|
|
533
|
+
return crypto
|
|
534
|
+
.randomBytes(Math.ceil(digit / 2))
|
|
535
|
+
.toString('hex')
|
|
536
|
+
.slice(0, digit);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
var BumpType; (function (BumpType) {
|
|
540
|
+
const ALPHA = 'alpha'; BumpType["ALPHA"] = ALPHA;
|
|
541
|
+
const BETA = 'beta'; BumpType["BETA"] = BETA;
|
|
542
|
+
const PATCH = 'patch'; BumpType["PATCH"] = PATCH;
|
|
543
|
+
const MINOR = 'minor'; BumpType["MINOR"] = MINOR;
|
|
544
|
+
const MAJOR = 'major'; BumpType["MAJOR"] = MAJOR;
|
|
545
|
+
})(BumpType || (BumpType = {}));
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* 获取更新类型的描述
|
|
549
|
+
*/
|
|
550
|
+
const getTypeDescription = (type) => {
|
|
551
|
+
switch (type) {
|
|
552
|
+
case BumpType.MAJOR:
|
|
553
|
+
return `Major update, incompatible API changes, for example: ${chalk.green('1.1.1 -> 2.0.0')}`;
|
|
554
|
+
case BumpType.MINOR:
|
|
555
|
+
return `Minor update, backwards-compatible features, for example: ${chalk.green('1.1.1 -> 1.2.0')}`;
|
|
556
|
+
case BumpType.PATCH:
|
|
557
|
+
return `Patch update, backwards-compatible bug fixes, for example: ${chalk.green('1.1.1 -> 1.1.2')}`;
|
|
558
|
+
case BumpType.BETA:
|
|
559
|
+
return `Beta pre-release version, for example: ${chalk.green('1.1.1-beta.1')}`;
|
|
560
|
+
case BumpType.ALPHA:
|
|
561
|
+
return `Alpha pre-release version, for example: ${chalk.green('1.1.1-alpha.2597f3')}`;
|
|
562
|
+
default:
|
|
563
|
+
return '';
|
|
564
|
+
}
|
|
565
|
+
};
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* 让用户选择版本更新类型
|
|
569
|
+
*/
|
|
570
|
+
const requestBumpType = async () => {
|
|
571
|
+
const bumpTypesChoices = [
|
|
572
|
+
BumpType.ALPHA,
|
|
573
|
+
BumpType.BETA,
|
|
574
|
+
BumpType.PATCH,
|
|
575
|
+
BumpType.MINOR,
|
|
576
|
+
BumpType.MAJOR,
|
|
577
|
+
];
|
|
578
|
+
const choices = bumpTypesChoices.map(type => ({
|
|
579
|
+
name: `${chalk.bold(type.toUpperCase())}: ${getTypeDescription(type)}`,
|
|
580
|
+
value: type,
|
|
581
|
+
}));
|
|
582
|
+
|
|
583
|
+
try {
|
|
584
|
+
const selected = await prompts.select({
|
|
585
|
+
message: 'Select version bump type:',
|
|
586
|
+
choices,
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
return selected;
|
|
590
|
+
} catch (error) {
|
|
591
|
+
return null;
|
|
592
|
+
}
|
|
593
|
+
};
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* 根据当前版本和发布类型计算新版本号
|
|
597
|
+
*/
|
|
598
|
+
const calculateNewVersion = (
|
|
599
|
+
currentVersion,
|
|
600
|
+
bumpType,
|
|
601
|
+
sessionId,
|
|
602
|
+
) => {
|
|
603
|
+
// 解析当前版本
|
|
604
|
+
const parsed = semver.parse(currentVersion);
|
|
605
|
+
if (!parsed) {
|
|
606
|
+
throw new Error(`Invalid current version: ${currentVersion}`);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
const { major, minor, patch, prerelease } = parsed;
|
|
610
|
+
|
|
611
|
+
switch (bumpType) {
|
|
612
|
+
case BumpType.PATCH:
|
|
613
|
+
case BumpType.MINOR:
|
|
614
|
+
case BumpType.MAJOR: {
|
|
615
|
+
// 如果当前是预发布版本,去掉预发布标识
|
|
616
|
+
const cc =
|
|
617
|
+
prerelease.length > 0 ? `${major}.${minor}.${patch}` : currentVersion;
|
|
618
|
+
// 否则增加 patch 版本
|
|
619
|
+
return semver.inc(cc, bumpType) || currentVersion;
|
|
620
|
+
}
|
|
621
|
+
case BumpType.BETA: {
|
|
622
|
+
// 如果当前已经是 beta 版本,增加 beta 版本号
|
|
623
|
+
if (prerelease[0] === 'beta') {
|
|
624
|
+
const nextVersion = semver.inc(currentVersion, 'prerelease', 'beta');
|
|
625
|
+
return nextVersion || currentVersion;
|
|
626
|
+
}
|
|
627
|
+
// 否则基于当前版本创建新的 beta 版本
|
|
628
|
+
const baseVersion = `${major}.${minor}.${patch}`;
|
|
629
|
+
return `${baseVersion}-beta.1`;
|
|
630
|
+
}
|
|
631
|
+
case BumpType.ALPHA: {
|
|
632
|
+
// 否则基于当前版本创建新的 alpha 版本
|
|
633
|
+
const baseVersion = `${major}.${minor}.${patch}`;
|
|
634
|
+
// 生成随机哈希值
|
|
635
|
+
return `${baseVersion}-alpha.${sessionId || randomHash(6)}`;
|
|
636
|
+
}
|
|
637
|
+
default: {
|
|
638
|
+
throw new Error(
|
|
639
|
+
`Invalid bump type: ${bumpType}, should be one of ${Object.values(BumpType).join(', ')}`,
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
};
|
|
644
|
+
|
|
645
|
+
/**
|
|
646
|
+
* 生成新版本号
|
|
647
|
+
* 策略优先级:
|
|
648
|
+
* 1. 指定版本号
|
|
649
|
+
* 2. 指定发布类型
|
|
650
|
+
* 3. 交互式选择
|
|
651
|
+
*/
|
|
652
|
+
const generateNewVersionForPackage = (
|
|
653
|
+
project,
|
|
654
|
+
options,
|
|
655
|
+
) => {
|
|
656
|
+
const currentVersion = project.packageJson.version;
|
|
657
|
+
// 1. 如果指定了版本号,直接使用
|
|
658
|
+
if (options.version) {
|
|
659
|
+
return options.version;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
// 1. 如果指定了发布类型,计算新版本号
|
|
663
|
+
const { bumpType } = options;
|
|
664
|
+
if (!bumpType) {
|
|
665
|
+
throw new Error('Version selection was cancelled');
|
|
666
|
+
}
|
|
667
|
+
const newVersion = calculateNewVersion(
|
|
668
|
+
currentVersion,
|
|
669
|
+
bumpType,
|
|
670
|
+
options.sessionId,
|
|
671
|
+
);
|
|
672
|
+
|
|
673
|
+
return newVersion;
|
|
674
|
+
};
|
|
675
|
+
|
|
676
|
+
const calBumpPolicy = (options) => {
|
|
677
|
+
const { version, bumpType } = options;
|
|
678
|
+
if (version) {
|
|
679
|
+
return version;
|
|
680
|
+
}
|
|
681
|
+
return bumpType;
|
|
682
|
+
};
|
|
683
|
+
|
|
684
|
+
/**
|
|
685
|
+
* 生成发布清单
|
|
686
|
+
*/
|
|
687
|
+
const generatePublishManifest = async (
|
|
688
|
+
packages,
|
|
689
|
+
options,
|
|
690
|
+
|
|
691
|
+
|
|
692
|
+
|
|
693
|
+
) => {
|
|
694
|
+
const manifests = [];
|
|
695
|
+
const { version, bumpType } = options;
|
|
696
|
+
if (version && !semver.valid(version)) {
|
|
697
|
+
throw new Error(`Invalid version specified: ${version}`);
|
|
698
|
+
} else if (!bumpType) {
|
|
699
|
+
const newBumpType = await requestBumpType();
|
|
700
|
+
if (!newBumpType) {
|
|
701
|
+
throw new Error('Version selection was cancelled!');
|
|
702
|
+
}
|
|
703
|
+
options.bumpType = newBumpType;
|
|
704
|
+
}
|
|
705
|
+
for (const pkg of packages) {
|
|
706
|
+
const currentVersion = pkg.packageJson.version;
|
|
707
|
+
const newVersion = await generateNewVersionForPackage(pkg, options);
|
|
708
|
+
manifests.push({
|
|
709
|
+
project: pkg,
|
|
710
|
+
newVersion,
|
|
711
|
+
currentVersion,
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
const bumpPolicy = calBumpPolicy(options);
|
|
715
|
+
return {
|
|
716
|
+
manifests,
|
|
717
|
+
bumpPolicy,
|
|
718
|
+
};
|
|
719
|
+
};
|
|
720
|
+
|
|
721
|
+
async function commitChanges({
|
|
722
|
+
sessionId,
|
|
723
|
+
files,
|
|
724
|
+
cwd,
|
|
725
|
+
publishManifests,
|
|
726
|
+
branchName,
|
|
727
|
+
createTags,
|
|
728
|
+
}) {
|
|
729
|
+
await exec(`git add ${files.join(' ')}`, { cwd });
|
|
730
|
+
await exec(`git commit -m "chore: Publish ${branchName}" -n`, { cwd });
|
|
731
|
+
|
|
732
|
+
let tags = [];
|
|
733
|
+
if (createTags) {
|
|
734
|
+
tags = publishManifests.map(
|
|
735
|
+
m => `v/${m.project.packageName}@${m.newVersion}`,
|
|
736
|
+
);
|
|
737
|
+
await exec(
|
|
738
|
+
tags.map(tag => `git tag -a ${tag} -m "Bump type ${tag}"`).join(' && '),
|
|
739
|
+
{ cwd },
|
|
740
|
+
);
|
|
741
|
+
}
|
|
742
|
+
return { effects: [...tags, branchName], branchName };
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
|
|
746
|
+
|
|
747
|
+
|
|
748
|
+
|
|
749
|
+
|
|
750
|
+
|
|
751
|
+
async function push({ refs, cwd, repoUrl }) {
|
|
752
|
+
await exec(`git push ${repoUrl} ${refs.join(' ')} --no-verify`, {
|
|
753
|
+
cwd,
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
const pushToRemote = async (options) => {
|
|
758
|
+
const {
|
|
759
|
+
sessionId,
|
|
760
|
+
changedFiles,
|
|
761
|
+
cwd,
|
|
762
|
+
publishManifests,
|
|
763
|
+
bumpPolicy,
|
|
764
|
+
skipCommit,
|
|
765
|
+
skipPush,
|
|
766
|
+
repoUrl,
|
|
767
|
+
} = options;
|
|
768
|
+
if (skipCommit) {
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
// 获取仓库 URL
|
|
773
|
+
const actualRepoUrl = repoUrl;
|
|
774
|
+
|
|
775
|
+
let branchName;
|
|
776
|
+
if (bumpPolicy === BumpType.BETA) {
|
|
777
|
+
branchName = await getCurrentBranchName();
|
|
778
|
+
} else {
|
|
779
|
+
const date = dayjs().format('YYYYMMDD');
|
|
780
|
+
branchName = `release/${date}-${sessionId}`;
|
|
781
|
+
await exec(`git checkout -b ${branchName}`, { cwd });
|
|
782
|
+
}
|
|
783
|
+
const isTestPublish = [BumpType.ALPHA, BumpType.BETA].includes(
|
|
784
|
+
bumpPolicy ,
|
|
785
|
+
);
|
|
786
|
+
|
|
787
|
+
// 4. 创建并推送发布分支
|
|
788
|
+
const { effects } = await commitChanges({
|
|
789
|
+
sessionId,
|
|
790
|
+
files: changedFiles,
|
|
791
|
+
cwd,
|
|
792
|
+
publishManifests,
|
|
793
|
+
branchName,
|
|
794
|
+
// 只有 alpha、beta 需要创建 tag,正式发布会在 .github/workflows/common-pr-checks.yml 创建并发布tag
|
|
795
|
+
createTags: isTestPublish,
|
|
796
|
+
});
|
|
797
|
+
if (skipPush) {
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
await push({
|
|
801
|
+
refs: effects,
|
|
802
|
+
cwd,
|
|
803
|
+
repoUrl: actualRepoUrl,
|
|
804
|
+
});
|
|
805
|
+
|
|
806
|
+
// 从 git URL 提取组织和仓库名称,用于构建 GitHub 链接
|
|
807
|
+
const repoInfoMatch = actualRepoUrl.match(GIT_REPO_URL_REGEX);
|
|
808
|
+
if (!repoInfoMatch) {
|
|
809
|
+
throw new Error('Invalid git repository URL');
|
|
810
|
+
}
|
|
811
|
+
const repoOwner = repoInfoMatch[1];
|
|
812
|
+
const repoName = repoInfoMatch[2];
|
|
813
|
+
|
|
814
|
+
if (isTestPublish) {
|
|
815
|
+
logger.success(
|
|
816
|
+
`Please refer to https://github.com/${repoOwner}/${repoName}/actions/workflows/release.yml for the release progress.`,
|
|
817
|
+
);
|
|
818
|
+
} else {
|
|
819
|
+
const prUrl = `https://github.com/${repoOwner}/${repoName}/compare/${branchName}?expand=1`;
|
|
820
|
+
const log = [
|
|
821
|
+
'************************************************',
|
|
822
|
+
'*',
|
|
823
|
+
`* Please create PR: ${prUrl}`,
|
|
824
|
+
'*',
|
|
825
|
+
'************************************************',
|
|
826
|
+
];
|
|
827
|
+
logger.success(log.join('\n'), false);
|
|
828
|
+
const open = await import('open');
|
|
829
|
+
await open.default(prUrl);
|
|
830
|
+
}
|
|
831
|
+
};
|
|
832
|
+
|
|
833
|
+
function _optionalChain$1(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|
834
|
+
|
|
835
|
+
|
|
836
|
+
var RetrievePattern; (function (RetrievePattern) {
|
|
837
|
+
const TO = 'to'; RetrievePattern["TO"] = TO;
|
|
838
|
+
const FROM = 'from'; RetrievePattern["FROM"] = FROM;
|
|
839
|
+
const ONLY = 'only'; RetrievePattern["ONLY"] = ONLY;
|
|
840
|
+
})(RetrievePattern || (RetrievePattern = {}));
|
|
841
|
+
|
|
842
|
+
const retrievePackages = (
|
|
843
|
+
pattern,
|
|
844
|
+
packages,
|
|
845
|
+
) => {
|
|
846
|
+
const rushConfiguration = getRushConfiguration();
|
|
847
|
+
const matchedPackages = new Set();
|
|
848
|
+
packages.forEach(pkg => {
|
|
849
|
+
const project = rushConfiguration.getProjectByName(pkg);
|
|
850
|
+
if (!project) {
|
|
851
|
+
throw new Error(`Package "${pkg}" not found in rush configuration`);
|
|
852
|
+
}
|
|
853
|
+
if (!project.shouldPublish) {
|
|
854
|
+
throw new Error(
|
|
855
|
+
`Package "${pkg}" is not set to publish. if you want to publish it, please set the "shouldPublish" property to true in the \`rush.json\` file.`,
|
|
856
|
+
);
|
|
857
|
+
}
|
|
858
|
+
const matched = [];
|
|
859
|
+
switch (pattern) {
|
|
860
|
+
case 'to': {
|
|
861
|
+
matched.push(project.dependencyProjects);
|
|
862
|
+
break;
|
|
863
|
+
}
|
|
864
|
+
case 'from': {
|
|
865
|
+
matched.push(project.consumingProjects);
|
|
866
|
+
matched.push(project.dependencyProjects);
|
|
867
|
+
break;
|
|
868
|
+
}
|
|
869
|
+
case 'only': {
|
|
870
|
+
// do nothing
|
|
871
|
+
break;
|
|
872
|
+
}
|
|
873
|
+
default: {
|
|
874
|
+
throw new Error('Unexpected package selection state');
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
for (const matchedSet of matched) {
|
|
879
|
+
for (const p of matchedSet) {
|
|
880
|
+
if (p.shouldPublish) {
|
|
881
|
+
matchedPackages.add(p);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
matchedPackages.add(project);
|
|
886
|
+
});
|
|
887
|
+
return matchedPackages;
|
|
888
|
+
};
|
|
889
|
+
|
|
890
|
+
const validateAndGetPackages = (options) => {
|
|
891
|
+
const retrievePatterns = Object.values(RetrievePattern);
|
|
892
|
+
if (retrievePatterns.every(pattern => (_optionalChain$1([options, 'access', _ => _[pattern], 'optionalAccess', _2 => _2.length]) || 0) <= 0)) {
|
|
893
|
+
throw new Error('No packages to publish');
|
|
894
|
+
}
|
|
895
|
+
return retrievePatterns.reduce((acc, pattern) => {
|
|
896
|
+
const packages = options[pattern];
|
|
897
|
+
if (!packages || packages.length <= 0) {
|
|
898
|
+
return acc;
|
|
899
|
+
}
|
|
900
|
+
const placeholders = retrievePackages(pattern , packages);
|
|
901
|
+
return new Set([...acc, ...placeholders]);
|
|
902
|
+
}, new Set());
|
|
903
|
+
};
|
|
904
|
+
|
|
905
|
+
const confirmForPublish = async (
|
|
906
|
+
publishManifest,
|
|
907
|
+
dryRun,
|
|
908
|
+
) => {
|
|
909
|
+
console.log(chalk.gray('Will publish the following packages:'));
|
|
910
|
+
publishManifest.forEach(manifest => {
|
|
911
|
+
const msg = `${manifest.project.packageName}: ${chalk.bgGreen(`${manifest.currentVersion} -> ${chalk.bold(manifest.newVersion)}`)}`;
|
|
912
|
+
console.log(`- ${msg}`);
|
|
913
|
+
});
|
|
914
|
+
if (dryRun) {
|
|
915
|
+
return false;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
console.log('\n');
|
|
919
|
+
try {
|
|
920
|
+
const result = await prompts.confirm({
|
|
921
|
+
message: 'Are you sure to publish?',
|
|
922
|
+
default: true,
|
|
923
|
+
});
|
|
924
|
+
return result;
|
|
925
|
+
} catch (error) {
|
|
926
|
+
return false;
|
|
927
|
+
}
|
|
928
|
+
};
|
|
929
|
+
|
|
930
|
+
function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|
931
|
+
|
|
932
|
+
|
|
933
|
+
|
|
934
|
+
|
|
935
|
+
|
|
936
|
+
|
|
937
|
+
|
|
938
|
+
|
|
939
|
+
|
|
940
|
+
|
|
941
|
+
|
|
942
|
+
|
|
943
|
+
|
|
944
|
+
|
|
945
|
+
|
|
946
|
+
|
|
947
|
+
|
|
948
|
+
|
|
949
|
+
|
|
950
|
+
|
|
951
|
+
|
|
952
|
+
|
|
953
|
+
|
|
954
|
+
|
|
955
|
+
|
|
956
|
+
|
|
957
|
+
|
|
958
|
+
|
|
959
|
+
|
|
960
|
+
|
|
961
|
+
|
|
962
|
+
|
|
963
|
+
|
|
964
|
+
|
|
965
|
+
|
|
966
|
+
|
|
967
|
+
|
|
968
|
+
|
|
969
|
+
/**
|
|
970
|
+
* Convert change type to corresponding title
|
|
971
|
+
*/
|
|
972
|
+
const getChangeTypeTitle = (type) => {
|
|
973
|
+
switch (type) {
|
|
974
|
+
case 'major':
|
|
975
|
+
return '### Breaking Changes';
|
|
976
|
+
case 'minor':
|
|
977
|
+
return '### New Features';
|
|
978
|
+
case 'patch':
|
|
979
|
+
return '### Bug Fixes';
|
|
980
|
+
case 'dependency':
|
|
981
|
+
return '### Dependencies';
|
|
982
|
+
case 'none':
|
|
983
|
+
return '### Other Changes';
|
|
984
|
+
default:
|
|
985
|
+
return '';
|
|
986
|
+
}
|
|
987
|
+
};
|
|
988
|
+
|
|
989
|
+
/**
|
|
990
|
+
* Generate changelog for a single version
|
|
991
|
+
*/
|
|
992
|
+
const generateVersionChangelog = ({
|
|
993
|
+
version,
|
|
994
|
+
changes,
|
|
995
|
+
tag,
|
|
996
|
+
defaultChangelog,
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
|
|
1000
|
+
|
|
1001
|
+
|
|
1002
|
+
) => {
|
|
1003
|
+
// Group changes by type
|
|
1004
|
+
const groupedChanges =
|
|
1005
|
+
changes.length > 0
|
|
1006
|
+
? changes.reduce(
|
|
1007
|
+
(acc, change) => {
|
|
1008
|
+
const { type, comment, customFields } = change;
|
|
1009
|
+
if (!acc[type]) {
|
|
1010
|
+
acc[type] = [];
|
|
1011
|
+
}
|
|
1012
|
+
const node = acc[type];
|
|
1013
|
+
if (node.some(existing => existing.comment === comment) === false) {
|
|
1014
|
+
node.push({
|
|
1015
|
+
comment,
|
|
1016
|
+
customFields,
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
return acc;
|
|
1020
|
+
},
|
|
1021
|
+
{} ,
|
|
1022
|
+
)
|
|
1023
|
+
: { none: [{ comment: defaultChangelog }] };
|
|
1024
|
+
|
|
1025
|
+
return {
|
|
1026
|
+
version,
|
|
1027
|
+
tag,
|
|
1028
|
+
date: dayjs().toISOString(),
|
|
1029
|
+
comments: groupedChanges ,
|
|
1030
|
+
};
|
|
1031
|
+
};
|
|
1032
|
+
|
|
1033
|
+
/**
|
|
1034
|
+
* Convert changelog to Markdown format
|
|
1035
|
+
*/
|
|
1036
|
+
const changelogToMarkdown = (changelog) => {
|
|
1037
|
+
const lines = [];
|
|
1038
|
+
lines.push(`# ${changelog.name}`);
|
|
1039
|
+
lines.push('');
|
|
1040
|
+
|
|
1041
|
+
changelog.entries.forEach(entry => {
|
|
1042
|
+
lines.push(
|
|
1043
|
+
`## ${entry.version} - ${dayjs(entry.date).format('YYYY-MM-DD')}`,
|
|
1044
|
+
);
|
|
1045
|
+
lines.push('');
|
|
1046
|
+
|
|
1047
|
+
// Output different types of changes in fixed order
|
|
1048
|
+
const typeOrder = [
|
|
1049
|
+
'major',
|
|
1050
|
+
'minor',
|
|
1051
|
+
'patch',
|
|
1052
|
+
'dependency',
|
|
1053
|
+
'none',
|
|
1054
|
+
];
|
|
1055
|
+
|
|
1056
|
+
typeOrder.forEach(type => {
|
|
1057
|
+
const changes = entry.comments[type];
|
|
1058
|
+
if (_optionalChain([changes, 'optionalAccess', _ => _.length])) {
|
|
1059
|
+
lines.push(getChangeTypeTitle(type));
|
|
1060
|
+
lines.push('');
|
|
1061
|
+
changes.forEach(change => {
|
|
1062
|
+
lines.push(`- ${change.comment}`);
|
|
1063
|
+
// Add custom fields to changelog if they exist
|
|
1064
|
+
if (change.customFields) {
|
|
1065
|
+
Object.entries(change.customFields).forEach(([key, value]) => {
|
|
1066
|
+
lines.push(` - ${key}: ${value}`);
|
|
1067
|
+
});
|
|
1068
|
+
}
|
|
1069
|
+
});
|
|
1070
|
+
lines.push('');
|
|
1071
|
+
}
|
|
1072
|
+
});
|
|
1073
|
+
|
|
1074
|
+
lines.push('');
|
|
1075
|
+
});
|
|
1076
|
+
|
|
1077
|
+
return lines.join('\n');
|
|
1078
|
+
};
|
|
1079
|
+
|
|
1080
|
+
|
|
1081
|
+
|
|
1082
|
+
|
|
1083
|
+
|
|
1084
|
+
|
|
1085
|
+
|
|
1086
|
+
|
|
1087
|
+
|
|
1088
|
+
/**
|
|
1089
|
+
* Merge and generate changelog
|
|
1090
|
+
*/
|
|
1091
|
+
const generateChangelog$1 = ({
|
|
1092
|
+
commingChanges,
|
|
1093
|
+
previousChangelog,
|
|
1094
|
+
version,
|
|
1095
|
+
packageName,
|
|
1096
|
+
tag,
|
|
1097
|
+
defaultChangelog = 'Publish for noop',
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
|
|
1101
|
+
) => {
|
|
1102
|
+
// Create new changelog entry
|
|
1103
|
+
const newEntry = generateVersionChangelog({
|
|
1104
|
+
version,
|
|
1105
|
+
changes: commingChanges.flatMap(r => r.changes),
|
|
1106
|
+
tag: tag || 'HEAD',
|
|
1107
|
+
defaultChangelog,
|
|
1108
|
+
});
|
|
1109
|
+
|
|
1110
|
+
const allEntries = (
|
|
1111
|
+
previousChangelog ? [newEntry, ...previousChangelog.entries] : [newEntry]
|
|
1112
|
+
).sort((a, b) => {
|
|
1113
|
+
// Handle invalid version numbers
|
|
1114
|
+
if (!semver.valid(a.version) || !semver.valid(b.version)) {
|
|
1115
|
+
return 0;
|
|
1116
|
+
}
|
|
1117
|
+
// Use semver.rcompare for descending sort (newer versions first)
|
|
1118
|
+
return semver.rcompare(a.version, b.version);
|
|
1119
|
+
});
|
|
1120
|
+
|
|
1121
|
+
// Merge with existing changelog
|
|
1122
|
+
const changelog = {
|
|
1123
|
+
name: packageName,
|
|
1124
|
+
entries: allEntries,
|
|
1125
|
+
};
|
|
1126
|
+
|
|
1127
|
+
// Convert to markdown
|
|
1128
|
+
const markdown = changelogToMarkdown(changelog);
|
|
1129
|
+
return {
|
|
1130
|
+
changelog,
|
|
1131
|
+
report: markdown,
|
|
1132
|
+
};
|
|
1133
|
+
};
|
|
1134
|
+
|
|
1135
|
+
const deleteFiles = async (files) => {
|
|
1136
|
+
await Promise.all(
|
|
1137
|
+
files.map(async file => {
|
|
1138
|
+
await fs.unlink(file);
|
|
1139
|
+
}),
|
|
1140
|
+
);
|
|
1141
|
+
};
|
|
1142
|
+
|
|
1143
|
+
const readChangeFiles = async (changedFolderOfPkg) => {
|
|
1144
|
+
if (!(await isDirExists(changedFolderOfPkg))) {
|
|
1145
|
+
return [];
|
|
1146
|
+
}
|
|
1147
|
+
const changeFiles = (await fs.readdir(changedFolderOfPkg)).filter(r =>
|
|
1148
|
+
r.endsWith('.json'),
|
|
1149
|
+
);
|
|
1150
|
+
return changeFiles.map(r => path.resolve(changedFolderOfPkg, r));
|
|
1151
|
+
};
|
|
1152
|
+
|
|
1153
|
+
const readChanges = async (changeFiles) => {
|
|
1154
|
+
const changes = (
|
|
1155
|
+
await Promise.all(
|
|
1156
|
+
changeFiles.map(async r => {
|
|
1157
|
+
try {
|
|
1158
|
+
const res = await readJsonFile(r);
|
|
1159
|
+
return res;
|
|
1160
|
+
} catch (e) {
|
|
1161
|
+
return null;
|
|
1162
|
+
}
|
|
1163
|
+
}),
|
|
1164
|
+
)
|
|
1165
|
+
).filter(r => r !== null);
|
|
1166
|
+
return changes;
|
|
1167
|
+
};
|
|
1168
|
+
|
|
1169
|
+
const readPreviousChangelog = async (
|
|
1170
|
+
changelogJsonPath,
|
|
1171
|
+
packageName,
|
|
1172
|
+
) => {
|
|
1173
|
+
const defaultValue = {
|
|
1174
|
+
name: packageName,
|
|
1175
|
+
entries: [] ,
|
|
1176
|
+
};
|
|
1177
|
+
if (!(await isFileExists(changelogJsonPath))) {
|
|
1178
|
+
return defaultValue;
|
|
1179
|
+
}
|
|
1180
|
+
try {
|
|
1181
|
+
const changelog = await readJsonFile(changelogJsonPath);
|
|
1182
|
+
return changelog;
|
|
1183
|
+
} catch (e) {
|
|
1184
|
+
return defaultValue;
|
|
1185
|
+
}
|
|
1186
|
+
};
|
|
1187
|
+
|
|
1188
|
+
const generateChangelogForProject = async (manifest) => {
|
|
1189
|
+
const { project, newVersion } = manifest;
|
|
1190
|
+
const rushConfiguration = getRushConfiguration();
|
|
1191
|
+
const { changesFolder } = rushConfiguration;
|
|
1192
|
+
const changedFolderOfPkg = path.resolve(changesFolder, project.packageName);
|
|
1193
|
+
const changelogJsonPath = path.resolve(
|
|
1194
|
+
project.projectFolder,
|
|
1195
|
+
'CHANGELOG.json',
|
|
1196
|
+
);
|
|
1197
|
+
const changelogMarkdownPath = path.resolve(
|
|
1198
|
+
project.projectFolder,
|
|
1199
|
+
'CHANGELOG.md',
|
|
1200
|
+
);
|
|
1201
|
+
|
|
1202
|
+
const changeFiles = await readChangeFiles(changedFolderOfPkg);
|
|
1203
|
+
const changes = await readChanges(changeFiles);
|
|
1204
|
+
|
|
1205
|
+
const previousChangelog = await readPreviousChangelog(
|
|
1206
|
+
changelogJsonPath,
|
|
1207
|
+
project.packageName,
|
|
1208
|
+
);
|
|
1209
|
+
const { changelog, report: changelogMarkdown } = await generateChangelog$1({
|
|
1210
|
+
packageName: project.packageName,
|
|
1211
|
+
version: newVersion,
|
|
1212
|
+
commingChanges: changes,
|
|
1213
|
+
previousChangelog,
|
|
1214
|
+
});
|
|
1215
|
+
|
|
1216
|
+
// side effects
|
|
1217
|
+
await writeJsonFile(changelogJsonPath, changelog);
|
|
1218
|
+
await fs.writeFile(changelogMarkdownPath, changelogMarkdown);
|
|
1219
|
+
await deleteFiles(changeFiles);
|
|
1220
|
+
return [changelogJsonPath, changelogMarkdownPath, ...changeFiles];
|
|
1221
|
+
};
|
|
1222
|
+
|
|
1223
|
+
const generateChangelog = async (
|
|
1224
|
+
manifests,
|
|
1225
|
+
) => {
|
|
1226
|
+
const modifiedFiles = await Promise.all(
|
|
1227
|
+
manifests.map(manifest => generateChangelogForProject(manifest)),
|
|
1228
|
+
);
|
|
1229
|
+
return modifiedFiles.flat();
|
|
1230
|
+
};
|
|
1231
|
+
|
|
1232
|
+
const updatePackageVersion = async (
|
|
1233
|
+
project,
|
|
1234
|
+
newVersion,
|
|
1235
|
+
) => {
|
|
1236
|
+
const packageJsonPath = path.resolve(project.projectFolder, 'package.json');
|
|
1237
|
+
const packageJson = await readJsonFile(packageJsonPath);
|
|
1238
|
+
packageJson.version = newVersion;
|
|
1239
|
+
await writeJsonFile(packageJsonPath, packageJson);
|
|
1240
|
+
return packageJsonPath;
|
|
1241
|
+
};
|
|
1242
|
+
|
|
1243
|
+
const applyPublishManifest = async (
|
|
1244
|
+
manifests,
|
|
1245
|
+
) => {
|
|
1246
|
+
const modifiedFiles = await Promise.all(
|
|
1247
|
+
manifests.map(async manifest => {
|
|
1248
|
+
const { project, newVersion } = manifest;
|
|
1249
|
+
|
|
1250
|
+
const modifiedFile = await updatePackageVersion(project, newVersion);
|
|
1251
|
+
return modifiedFile;
|
|
1252
|
+
}),
|
|
1253
|
+
);
|
|
1254
|
+
logger.info(
|
|
1255
|
+
`Updated version for packages: ${manifests.map(m => m.project.packageName).join(', ')}`,
|
|
1256
|
+
);
|
|
1257
|
+
return modifiedFiles;
|
|
1258
|
+
};
|
|
1259
|
+
|
|
1260
|
+
// 针对不同类型的发布,对应不同 sideEffects:
|
|
1261
|
+
// 1. alpha: 直接创建并push 分支,触发 CI,执行发布;
|
|
1262
|
+
// 2. beta: 本分支直接切换版本号,并发布
|
|
1263
|
+
// 3. 正式版本:发起MR,MR 合入 main 后,触发发布
|
|
1264
|
+
|
|
1265
|
+
const publish = async (options) => {
|
|
1266
|
+
const sessionId = randomHash(6);
|
|
1267
|
+
const rushConfiguration = getRushConfiguration();
|
|
1268
|
+
const rushFolder = rushConfiguration.rushJsonFolder;
|
|
1269
|
+
if (process.env.SKIP_UNCOMMITTED_CHECK !== 'true') {
|
|
1270
|
+
await ensureNotUncommittedChanges();
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
// 1. 验证并获取需要发布的包列表
|
|
1274
|
+
const packagesToPublish = validateAndGetPackages(options);
|
|
1275
|
+
if (packagesToPublish.size === 0) {
|
|
1276
|
+
logger.error(
|
|
1277
|
+
'No packages to publish, should specify some package by `--to` or `--from` or `--only`',
|
|
1278
|
+
);
|
|
1279
|
+
return;
|
|
1280
|
+
}
|
|
1281
|
+
logger.debug(
|
|
1282
|
+
`Will publish the following packages:\n ${[...packagesToPublish].map(pkg => pkg.packageName).join('\n')}`,
|
|
1283
|
+
);
|
|
1284
|
+
|
|
1285
|
+
// 2. 生成发布清单
|
|
1286
|
+
const { manifests: publishManifests, bumpPolicy } =
|
|
1287
|
+
await generatePublishManifest(packagesToPublish, {
|
|
1288
|
+
...options,
|
|
1289
|
+
sessionId,
|
|
1290
|
+
});
|
|
1291
|
+
const isBetaPublish = [BumpType.BETA, BumpType.ALPHA].includes(
|
|
1292
|
+
bumpPolicy ,
|
|
1293
|
+
);
|
|
1294
|
+
if (isBetaPublish === false && (await isMainBranch()) === false) {
|
|
1295
|
+
// 只允许在主分支发布
|
|
1296
|
+
logger.error(
|
|
1297
|
+
'You are not in main branch, please switch to main branch and try again.',
|
|
1298
|
+
);
|
|
1299
|
+
return;
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
const continuePublish = await confirmForPublish(
|
|
1303
|
+
publishManifests,
|
|
1304
|
+
!!options.dryRun,
|
|
1305
|
+
);
|
|
1306
|
+
|
|
1307
|
+
if (!continuePublish) {
|
|
1308
|
+
return;
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
// 3. 应用更新,注意这里会修改文件,产生 sideEffect
|
|
1312
|
+
const postHandles = [applyPublishManifest];
|
|
1313
|
+
if (isBetaPublish === false) {
|
|
1314
|
+
postHandles.push(generateChangelog);
|
|
1315
|
+
}
|
|
1316
|
+
const changedFiles = (
|
|
1317
|
+
await Promise.all(postHandles.map(handle => handle(publishManifests)))
|
|
1318
|
+
).flat();
|
|
1319
|
+
|
|
1320
|
+
// 4. 创建并推送发布分支
|
|
1321
|
+
await pushToRemote({
|
|
1322
|
+
publishManifests,
|
|
1323
|
+
bumpPolicy: bumpPolicy ,
|
|
1324
|
+
sessionId,
|
|
1325
|
+
changedFiles,
|
|
1326
|
+
cwd: rushFolder,
|
|
1327
|
+
skipCommit: !!options.skipCommit,
|
|
1328
|
+
skipPush: !!options.skipPush,
|
|
1329
|
+
repoUrl: options.repoUrl,
|
|
1330
|
+
});
|
|
1331
|
+
logger.success('Publish success.');
|
|
1332
|
+
};
|
|
1333
|
+
|
|
1334
|
+
const installAction$1 = (program) => {
|
|
1335
|
+
program
|
|
1336
|
+
.command('pub')
|
|
1337
|
+
.description('Generate new version and create release branch.')
|
|
1338
|
+
.option('-v, --version <string>', 'Specify publish version')
|
|
1339
|
+
.option('-d, --dry-run', 'Enable dry run mode', false)
|
|
1340
|
+
.option(
|
|
1341
|
+
'-t, --to <packages...>',
|
|
1342
|
+
'Publish specified packages and their downstream dependencies',
|
|
1343
|
+
)
|
|
1344
|
+
.option(
|
|
1345
|
+
'-f, --from <packages...>',
|
|
1346
|
+
'Publish specified packages and their upstream/downstream dependencies',
|
|
1347
|
+
)
|
|
1348
|
+
.option('-o, --only <packages...>', 'Only publish specified packages')
|
|
1349
|
+
.option('-s, --skip-commit', 'Skip git commit', false)
|
|
1350
|
+
.option('-p, --skip-push', 'Skip git push', false)
|
|
1351
|
+
.option(
|
|
1352
|
+
'-b, --bump-type <type>',
|
|
1353
|
+
'Version bump type (alpha/beta/patch/minor/major)',
|
|
1354
|
+
/^(alpha|beta|patch|minor|major)$/,
|
|
1355
|
+
)
|
|
1356
|
+
.option(
|
|
1357
|
+
'--repo-url <url>',
|
|
1358
|
+
'Git repository URL (e.g. git@github.com:coze-dev/coze-js.git)',
|
|
1359
|
+
undefined,
|
|
1360
|
+
)
|
|
1361
|
+
.action(async (options) => {
|
|
1362
|
+
try {
|
|
1363
|
+
const repoUrl = options.repoUrl || (await getCurrentOrigin());
|
|
1364
|
+
if (!repoUrl) {
|
|
1365
|
+
throw new Error('Git repository URL is required');
|
|
1366
|
+
}
|
|
1367
|
+
if (!GIT_REPO_URL_REGEX.test(repoUrl)) {
|
|
1368
|
+
throw new Error(
|
|
1369
|
+
`Invalid git repository URL: ${repoUrl}, it should be follow the format: git@github.com:\${org}/\${repo}.git`,
|
|
1370
|
+
);
|
|
1371
|
+
}
|
|
1372
|
+
const normalizeOptions = {
|
|
1373
|
+
...options,
|
|
1374
|
+
repoUrl,
|
|
1375
|
+
};
|
|
1376
|
+
await publish(normalizeOptions);
|
|
1377
|
+
} catch (error) {
|
|
1378
|
+
logger.error('Publish failed!');
|
|
1379
|
+
logger.error((error ).message);
|
|
1380
|
+
}
|
|
1381
|
+
});
|
|
1382
|
+
};
|
|
1383
|
+
|
|
1384
|
+
const whoAmI = async () => {
|
|
1385
|
+
const [name, email] = await Promise.all([
|
|
1386
|
+
exec('git config user.name', { cwd: __dirname, silent: true }),
|
|
1387
|
+
exec('git config user.email', { cwd: __dirname, silent: true }),
|
|
1388
|
+
]);
|
|
1389
|
+
return {
|
|
1390
|
+
name: name.stdout.toString().replace('\n', ''),
|
|
1391
|
+
email: email.stdout.toString().replace('\n', ''),
|
|
1392
|
+
};
|
|
1393
|
+
};
|
|
1394
|
+
|
|
1395
|
+
// 这两个包没有 module 导出
|
|
1396
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
1397
|
+
const { sync } = require('conventional-commits-parser');
|
|
1398
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
1399
|
+
const defaultChangelogOpts = require('conventional-changelog-angular');
|
|
1400
|
+
|
|
1401
|
+
const VERSIONS = ['major', 'minor', 'patch'];
|
|
1402
|
+
|
|
1403
|
+
/**
|
|
1404
|
+
* 收集需要更新 changes 的包
|
|
1405
|
+
* @returns RushConfigurationProject[]
|
|
1406
|
+
*/
|
|
1407
|
+
const collectShouldUpdateChangesProjects = async (
|
|
1408
|
+
|
|
1409
|
+
) => {
|
|
1410
|
+
const changedFiles = await getChangedFilesFromCached();
|
|
1411
|
+
const rushConfiguration = getRushConfiguration();
|
|
1412
|
+
const lookup = rushConfiguration.getProjectLookupForRoot(
|
|
1413
|
+
rushConfiguration.rushJsonFolder,
|
|
1414
|
+
);
|
|
1415
|
+
|
|
1416
|
+
const relativeChangesFolder = path.relative(
|
|
1417
|
+
rushConfiguration.rushJsonFolder,
|
|
1418
|
+
rushConfiguration.changesFolder,
|
|
1419
|
+
);
|
|
1420
|
+
const ignorePackage = [];
|
|
1421
|
+
const shouldUpdateChangesProjects = new Set();
|
|
1422
|
+
|
|
1423
|
+
for (const file of changedFiles) {
|
|
1424
|
+
// 收集有 common/changes 下变更的包
|
|
1425
|
+
if (file.startsWith(relativeChangesFolder)) {
|
|
1426
|
+
const packageName = path.relative(
|
|
1427
|
+
relativeChangesFolder,
|
|
1428
|
+
path.dirname(file),
|
|
1429
|
+
);
|
|
1430
|
+
ignorePackage.push(packageName);
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
const project = lookup.findChildPath(file);
|
|
1434
|
+
// 按是否发布提取相关包信息,同时过滤掉changes包含在本次变更内的文件(该策略是手动更改优先级大于自动生成)
|
|
1435
|
+
if (project && !ignorePackage.includes(project.packageName)) {
|
|
1436
|
+
if (!shouldUpdateChangesProjects.has(project) && project.shouldPublish) {
|
|
1437
|
+
shouldUpdateChangesProjects.add(project);
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
return [...shouldUpdateChangesProjects];
|
|
1443
|
+
};
|
|
1444
|
+
|
|
1445
|
+
/**
|
|
1446
|
+
* 解析 commit-mag
|
|
1447
|
+
* @param message
|
|
1448
|
+
* @param parser
|
|
1449
|
+
* @param parserOpts
|
|
1450
|
+
* @returns
|
|
1451
|
+
*/
|
|
1452
|
+
// https://github.com/conventional-changelog/commitlint/blob/8b8a6e62f57511c0be05346d14959247851cdfeb/%40commitlint/parse/src/index.ts#L6
|
|
1453
|
+
async function parseCommit(
|
|
1454
|
+
message,
|
|
1455
|
+
parser = sync,
|
|
1456
|
+
parserOpts,
|
|
1457
|
+
) {
|
|
1458
|
+
const defaultOpts = (await defaultChangelogOpts).parserOpts;
|
|
1459
|
+
const opts = {
|
|
1460
|
+
...defaultOpts,
|
|
1461
|
+
...({}),
|
|
1462
|
+
fieldPattern: null,
|
|
1463
|
+
};
|
|
1464
|
+
const parsed = parser(message, opts) ;
|
|
1465
|
+
parsed.raw = message;
|
|
1466
|
+
return parsed;
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
/**
|
|
1470
|
+
* 生成可能更新的版本类型
|
|
1471
|
+
* @param commits
|
|
1472
|
+
* @returns
|
|
1473
|
+
*/
|
|
1474
|
+
// https://github.com/conventional-changelog/conventional-changelog/blob/master/packages/conventional-recommended-bump/index.js
|
|
1475
|
+
function whatBump(commits)
|
|
1476
|
+
|
|
1477
|
+
|
|
1478
|
+
|
|
1479
|
+
{
|
|
1480
|
+
const DEFAULT_LEVEL = 2;
|
|
1481
|
+
let level = DEFAULT_LEVEL;
|
|
1482
|
+
let breakings = 0;
|
|
1483
|
+
let features = 0;
|
|
1484
|
+
|
|
1485
|
+
commits.forEach(commit => {
|
|
1486
|
+
if (commit.notes.length > 0) {
|
|
1487
|
+
breakings += commit.notes.length;
|
|
1488
|
+
level = 0;
|
|
1489
|
+
} else if (commit.type === 'feat') {
|
|
1490
|
+
features += 1;
|
|
1491
|
+
if (level === DEFAULT_LEVEL) {
|
|
1492
|
+
level = 1;
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
});
|
|
1496
|
+
|
|
1497
|
+
return {
|
|
1498
|
+
level,
|
|
1499
|
+
releaseType: VERSIONS[level],
|
|
1500
|
+
reason:
|
|
1501
|
+
breakings === 1
|
|
1502
|
+
? `There is ${breakings} BREAKING CHANGE and ${features} features`
|
|
1503
|
+
: `There are ${breakings} BREAKING CHANGES and ${features} features`,
|
|
1504
|
+
};
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
async function analysisCommitMsg(
|
|
1508
|
+
msg,
|
|
1509
|
+
) {
|
|
1510
|
+
const parsedCommit = await parseCommit(msg);
|
|
1511
|
+
const bumpInfo = whatBump([parsedCommit]);
|
|
1512
|
+
|
|
1513
|
+
return { type: bumpInfo.releaseType, content: parsedCommit.subject || '' };
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
/**
|
|
1517
|
+
* 生成 changes
|
|
1518
|
+
*/
|
|
1519
|
+
async function generateAllChangesFile(
|
|
1520
|
+
comment,
|
|
1521
|
+
patchType,
|
|
1522
|
+
) {
|
|
1523
|
+
const rushConfiguration = getRushConfiguration();
|
|
1524
|
+
const needUpdateProjects = await collectShouldUpdateChangesProjects();
|
|
1525
|
+
const { email } = await whoAmI();
|
|
1526
|
+
|
|
1527
|
+
// 重新组织已有 change 文件和待新增的 change
|
|
1528
|
+
for (const project of needUpdateProjects) {
|
|
1529
|
+
const { packageName } = project;
|
|
1530
|
+
// TODO: ChangeFile 需要的 IChangeInfo 类型和当前规范存在属性差异,暂时先忽略 email
|
|
1531
|
+
const changeFile = new ChangeFile.ChangeFile(
|
|
1532
|
+
{ changes: [], packageName, email },
|
|
1533
|
+
rushConfiguration ,
|
|
1534
|
+
);
|
|
1535
|
+
changeFile.addChange({ packageName, comment, type: patchType });
|
|
1536
|
+
changeFile.writeSync();
|
|
1537
|
+
|
|
1538
|
+
const updateChangesPath = path.resolve(
|
|
1539
|
+
rushConfiguration.changesFolder,
|
|
1540
|
+
packageName,
|
|
1541
|
+
);
|
|
1542
|
+
await exec(`git add ${updateChangesPath}`);
|
|
1543
|
+
logger.success(`Success update ${packageName} changes file`);
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
const amendCommit = async () => {
|
|
1548
|
+
const changedFiles = await getChangedFilesFromCached();
|
|
1549
|
+
const rushConfiguration = getRushConfiguration();
|
|
1550
|
+
|
|
1551
|
+
const relativeChangesFolder = path.relative(
|
|
1552
|
+
rushConfiguration.rushJsonFolder,
|
|
1553
|
+
rushConfiguration.changesFolder,
|
|
1554
|
+
);
|
|
1555
|
+
|
|
1556
|
+
for (const file of changedFiles) {
|
|
1557
|
+
if (file.startsWith(relativeChangesFolder)) {
|
|
1558
|
+
await exec('git commit --amend --no-edit -n');
|
|
1559
|
+
break;
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
};
|
|
1563
|
+
|
|
1564
|
+
const generateChangeFiles = async (options) => {
|
|
1565
|
+
// CI 环境的提交不做处理
|
|
1566
|
+
if (options.ci || process.env.CI === 'true') {
|
|
1567
|
+
return;
|
|
1568
|
+
}
|
|
1569
|
+
if (options.amendCommit) {
|
|
1570
|
+
amendCommit();
|
|
1571
|
+
return;
|
|
1572
|
+
}
|
|
1573
|
+
try {
|
|
1574
|
+
let { commitMsg } = options;
|
|
1575
|
+
if (!commitMsg) {
|
|
1576
|
+
const rushConfiguration = getRushConfiguration();
|
|
1577
|
+
commitMsg = await fs.readFile(
|
|
1578
|
+
path.resolve(rushConfiguration.rushJsonFolder, '.git/COMMIT_EDITMSG'),
|
|
1579
|
+
'utf-8',
|
|
1580
|
+
);
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
const { content, type } = await analysisCommitMsg(commitMsg);
|
|
1584
|
+
if (!content) {
|
|
1585
|
+
logger.warn('Invalid subject');
|
|
1586
|
+
return;
|
|
1587
|
+
}
|
|
1588
|
+
generateAllChangesFile(content, type);
|
|
1589
|
+
} catch (e) {
|
|
1590
|
+
logger.error(`Generate changes file fail \n ${e}`);
|
|
1591
|
+
}
|
|
1592
|
+
};
|
|
1593
|
+
|
|
1594
|
+
const installAction = (program) => {
|
|
1595
|
+
program
|
|
1596
|
+
.command('change')
|
|
1597
|
+
.description('Generate changes in a simple way.')
|
|
1598
|
+
.option(
|
|
1599
|
+
'-c, --commit-msg <string>',
|
|
1600
|
+
'本次提交信息,默认读取 .git/COMMIT_EDITMSG',
|
|
1601
|
+
)
|
|
1602
|
+
.option('-a, --amend-commit', '是否 amend commit 阶段')
|
|
1603
|
+
.option('-i, --ci', '是否在 CI 环境')
|
|
1604
|
+
.action(async (options) => {
|
|
1605
|
+
await generateChangeFiles({ ...options });
|
|
1606
|
+
});
|
|
1607
|
+
};
|
|
1608
|
+
|
|
12
1609
|
const main = () => {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
1610
|
+
const packageJson = JSON.parse(
|
|
1611
|
+
fs$1.readFileSync(path.resolve(__dirname, '../package.json'), 'utf8'),
|
|
1612
|
+
);
|
|
1613
|
+
const program = new commander.Command();
|
|
1614
|
+
|
|
1615
|
+
program
|
|
1616
|
+
.name(packageJson.name)
|
|
1617
|
+
.description(packageJson.description)
|
|
1618
|
+
.version(packageJson.version)
|
|
1619
|
+
.showSuggestionAfterError(true)
|
|
1620
|
+
.showHelpAfterError(true);
|
|
1621
|
+
|
|
1622
|
+
const actions = [installAction, installAction$2, installAction$1];
|
|
1623
|
+
|
|
1624
|
+
actions.forEach(a => {
|
|
1625
|
+
a(program);
|
|
1626
|
+
});
|
|
1627
|
+
|
|
1628
|
+
program.parse();
|
|
26
1629
|
};
|
|
1630
|
+
|
|
27
1631
|
main();
|
|
28
|
-
//# sourceMappingURL=index.js.map
|