@coze-arch/rush-publish-plugin 0.0.3 → 0.0.5-alpha.0f1107

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