@coze-arch/rush-publish-plugin 0.0.4 → 0.0.5-alpha.1ae01f

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 CHANGED
@@ -13,6 +13,9 @@ var prompts = require('@inquirer/prompts');
13
13
  var dayjs = require('dayjs');
14
14
  var ChangeFile = require('@rushstack/rush-sdk/lib/api/ChangeFile');
15
15
 
16
+ // Copyright (c) 2025 coze-dev
17
+ // SPDX-License-Identifier: MIT
18
+
16
19
  /**
17
20
  * 日志工具
18
21
  *
@@ -142,6 +145,34 @@ const logger = {
142
145
  },
143
146
  };
144
147
 
148
+ // Copyright (c) 2025 coze-dev
149
+ // SPDX-License-Identifier: MIT
150
+
151
+ /**
152
+ * 默认的 NPM registry 地址
153
+ */
154
+ const DEFAULT_NPM_REGISTRY = 'https://registry.npmjs.org';
155
+
156
+ /**
157
+ * 默认的 Git 分支名前缀
158
+ */
159
+ const DEFAULT_BRANCH_PREFIX = 'bump';
160
+
161
+ /**
162
+ * 默认允许发布正式版本的分支列表
163
+ */
164
+ const DEFAULT_ALLOW_BRANCHES = ['main', 'feat/auto-publish'];
165
+
166
+ // Copyright (c) 2025 coze-dev
167
+ // SPDX-License-Identifier: MIT
168
+
169
+
170
+
171
+
172
+
173
+
174
+
175
+
145
176
  class ExecError extends Error {
146
177
 
147
178
 
@@ -168,6 +199,10 @@ const exec = (
168
199
  });
169
200
  });
170
201
 
202
+ // Copyright (c) 2025 coze-dev
203
+ // SPDX-License-Identifier: MIT
204
+
205
+
171
206
  const serializeFilesName = (output) =>
172
207
  output
173
208
  .split('\n')
@@ -197,6 +232,15 @@ const getCurrentBranchName = async () => {
197
232
  return stdout.trim();
198
233
  };
199
234
 
235
+ /**
236
+ * 获取当前 commit hash
237
+ * @returns commit hash
238
+ */
239
+ const getCurrentCommitHash = async () => {
240
+ const { stdout } = await exec('git rev-parse HEAD');
241
+ return stdout.trim();
242
+ };
243
+
200
244
  const isMainBranch = async () => {
201
245
  const currentBranchName = await getCurrentBranchName();
202
246
  return currentBranchName === 'main';
@@ -238,6 +282,88 @@ const getCurrentOrigin = async (
238
282
  }
239
283
  };
240
284
 
285
+ /**
286
+ * 解析 Git 远程仓库 URL,提取主机和仓库路径
287
+ * 支持 HTTP/HTTPS 和 SSH 格式
288
+ * @param gitUrl Git 仓库 URL
289
+ * @returns 包含主机和仓库路径的对象,解析失败返回 null
290
+ */
291
+ const parseGitRemoteUrl = (
292
+ gitUrl,
293
+ ) => {
294
+ if (!gitUrl) {
295
+ return null;
296
+ }
297
+
298
+ const trimmedUrl = gitUrl.trim().replace(/\.git$/, '');
299
+
300
+ // 匹配 SSH 格式: git@github.com:owner/repo.git
301
+ const sshMatch = trimmedUrl.match(/^git@([^:]+):(.+)$/);
302
+ if (sshMatch) {
303
+ const host = sshMatch[1];
304
+ const path = sshMatch[2];
305
+ return {
306
+ host,
307
+ path,
308
+ fullUrl: `https://${host}/${path}`,
309
+ };
310
+ }
311
+
312
+ // 匹配 HTTP/HTTPS 格式: https://github.com/owner/repo.git
313
+ const httpMatch = trimmedUrl.match(/^https?:\/\/([^/]+)\/(.+)$/);
314
+ if (httpMatch) {
315
+ const host = httpMatch[1];
316
+ const path = httpMatch[2];
317
+ return {
318
+ host,
319
+ path,
320
+ fullUrl: `https://${host}/${path}`,
321
+ };
322
+ }
323
+
324
+ return null;
325
+ };
326
+
327
+ /**
328
+ * 根据仓库信息构建 MR/PR 创建链接
329
+ * @param repoInfo 仓库信息
330
+ * @param branchName 分支名称
331
+ * @returns MR/PR 创建链接,如果无法构建则返回 null
332
+ */
333
+ const buildMergeRequestUrl = (
334
+ repoInfo,
335
+ branchName,
336
+ ) => {
337
+ const { host, fullUrl } = repoInfo;
338
+
339
+ // GitHub
340
+ if (host.includes('github.com')) {
341
+ return `${fullUrl}/compare/${branchName}?expand=1`;
342
+ }
343
+
344
+ // GitLab
345
+ if (host.includes('gitlab')) {
346
+ return `${fullUrl}/-/merge_requests/new?merge_request[source_branch]=${branchName}`;
347
+ }
348
+
349
+ // Gitee
350
+ if (host.includes('gitee.com')) {
351
+ return `${fullUrl}/compare/${branchName}...master`;
352
+ }
353
+
354
+ // Bitbucket
355
+ if (host.includes('bitbucket')) {
356
+ return `${fullUrl}/pull-requests/new?source=${branchName}`;
357
+ }
358
+
359
+ // 无法识别的平台
360
+ return null;
361
+ };
362
+
363
+ // Copyright (c) 2025 coze-dev
364
+ // SPDX-License-Identifier: MIT
365
+
366
+
241
367
  const readJsonFile = async ( path) => {
242
368
  const content = await fs.readFile(path, 'utf8');
243
369
  return JSON.parse(content) ;
@@ -259,7 +385,11 @@ const isDirExists = async (path) =>
259
385
  .then(() => true)
260
386
  .catch(() => false);
261
387
 
262
- const getRushConfiguration = (() => {
388
+ // Copyright (c) 2025 coze-dev
389
+ // SPDX-License-Identifier: MIT
390
+
391
+
392
+ const getRushConfiguration$1 = (() => {
263
393
  let rushConfiguration = null;
264
394
  return (useCache = true) => {
265
395
  if (!useCache) {
@@ -271,19 +401,32 @@ const getRushConfiguration = (() => {
271
401
  };
272
402
  })();
273
403
 
404
+ // Copyright (c) 2025 coze-dev
405
+ // SPDX-License-Identifier: MIT
406
+
407
+
408
+
409
+
410
+
411
+
412
+
413
+
414
+
274
415
  /**
275
416
  * 更新依赖版本
276
417
  */
277
418
  const updateDependencyVersions = async (
278
419
  packageJson,
279
420
  ) => {
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;
421
+ const rushConfiguration = getRushConfiguration$1();
422
+ const depTypes = ['dependencies', 'peerDependencies'];
423
+ for (const depType of depTypes) {
424
+ if (packageJson[depType]) {
425
+ for (const [dep, ver] of Object.entries(packageJson[depType])) {
426
+ const project = rushConfiguration.getProjectByName(dep);
427
+ if (/^workspace:/.test(ver) && project) {
428
+ packageJson[depType][dep] = project.packageJson.version;
429
+ }
287
430
  }
288
431
  }
289
432
  }
@@ -331,7 +474,12 @@ const publishPackage = async (
331
474
  : version.includes('beta')
332
475
  ? 'beta'
333
476
  : 'latest';
334
- const args = [`NPM_AUTH_TOKEN=${token}`, 'npm', 'publish', `--tag ${tag}`];
477
+ const setToken = `npm config set //bnpm.byted.org/:_authToken ${token}`;
478
+ await exec(setToken, {
479
+ cwd: project.projectFolder,
480
+ });
481
+
482
+ const args = [`NODE_AUTH_TOKEN=${token}`, 'npm', 'publish', `--tag ${tag}`];
335
483
  if (dryRun) {
336
484
  args.push('--dry-run');
337
485
  }
@@ -406,33 +554,51 @@ const calReleasePlan = (releaseManifests) => {
406
554
  const checkReleasePlan = (
407
555
  releaseManifests,
408
556
  branchName,
557
+ allowBranches = ['main', 'feat/auto-publish'],
409
558
  ) => {
410
559
  const releasePlan = calReleasePlan(releaseManifests);
411
560
  if (
412
561
  releasePlan === ReleaseType.LATEST &&
413
- !['main', 'feat/auto-publish'].includes(branchName)
562
+ !allowBranches.includes(branchName)
414
563
  ) {
415
- throw new Error('For LATEST release, should be on main branch only.');
564
+ throw new Error(
565
+ `For LATEST release, should be on one of these branches: ${allowBranches.join(', ')}. Current Branch: ${branchName}`,
566
+ );
416
567
  }
417
568
  return true;
418
569
  };
419
570
 
571
+ // Copyright (c) 2025 coze-dev
572
+ // SPDX-License-Identifier: MIT
573
+
574
+
575
+
420
576
  /**
421
577
  * 构建发布依赖树
422
578
  */
423
579
  function buildReleaseManifest(
424
580
  packages,
425
581
  ) {
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
- });
582
+ const rushConfiguration = getRushConfiguration$1();
583
+ return packages
584
+ .map(pkg => {
585
+ const project = rushConfiguration.getProjectByName(pkg.packageName);
586
+ if (!project) {
587
+ throw new Error(`Cannot find project: ${pkg.packageName}`);
588
+ }
589
+ if (project.shouldPublish) {
590
+ return { project, version: project.packageJson.version };
591
+ }
592
+ return undefined;
593
+ })
594
+ .filter(Boolean) ;
434
595
  }
435
596
 
597
+ // Copyright (c) 2025 coze-dev
598
+ // SPDX-License-Identifier: MIT
599
+
600
+
601
+
436
602
  /**
437
603
  * 从 git tag 中解析需要发布的包信息
438
604
  */
@@ -461,11 +627,37 @@ const getPackagesToPublish = async (
461
627
  return packages;
462
628
  };
463
629
 
630
+ // Copyright (c) 2025 coze-dev
631
+ // SPDX-License-Identifier: MIT
632
+
633
+
464
634
  async function release(options) {
465
- const { commit, dryRun = false, registry } = options;
635
+ const {
636
+ dryRun = false,
637
+ registry,
638
+ packages,
639
+ allowBranches = DEFAULT_ALLOW_BRANCHES,
640
+ } = options;
641
+ let { commit } = options;
642
+ const hasPassedCommit = !!options.commit;
466
643
 
467
644
  // 1. 获取需要发布的包列表
468
- const packagesToPublish = await getPackagesToPublish(commit);
645
+ let packagesToPublish;
646
+ if (packages) {
647
+ // 直接使用传入的包列表
648
+ packagesToPublish = packages;
649
+ logger.info('Using provided package list');
650
+ } else {
651
+ // 从 git tags 获取包列表
652
+ if (!hasPassedCommit) {
653
+ commit = await getCurrentCommitHash();
654
+ logger.info('Using current commit');
655
+ }
656
+ // 此时 commit 必定有值(要么传入了,要么刚获取了)
657
+ const commitHash = commit ;
658
+ packagesToPublish = await getPackagesToPublish(commitHash);
659
+ }
660
+
469
661
  if (packagesToPublish.length === 0) {
470
662
  logger.warn('No packages to publish');
471
663
  return;
@@ -481,8 +673,16 @@ async function release(options) {
481
673
  false,
482
674
  );
483
675
  const branchName = await getCurrentBranchName();
484
- checkReleasePlan(releaseManifests, branchName);
485
- await exec(`git checkout ${commit}`);
676
+ checkReleasePlan(releaseManifests, branchName, allowBranches);
677
+
678
+ // 只有在指定了 commit 且与当前 HEAD 不同时才切换
679
+ if (hasPassedCommit) {
680
+ const currentHead = await getCurrentCommitHash();
681
+ if (currentHead !== commit) {
682
+ logger.info(`Checking out commit: ${commit}`);
683
+ await exec(`git checkout ${commit}`);
684
+ }
685
+ }
486
686
 
487
687
  await releasePackages(releaseManifests, { dryRun, registry });
488
688
  logger.success('All packages published successfully!');
@@ -498,18 +698,19 @@ const installAction$2 = (program) => {
498
698
  program
499
699
  .command('release')
500
700
  .description('Release packages based on git tags.')
501
- .requiredOption('--commit <string>', '需要执行发布的 commit id')
701
+ .option('--commit <string>', '需要执行发布的 commit id (默认使用当前 HEAD)')
502
702
  .option('--dry-run', '是否只执行不真实发布', false)
503
703
  .option(
504
704
  '-r, --registry <string>',
505
- '发布到的 registry',
506
- 'https://registry.npmjs.org',
705
+ `发布到的 registry (默认: ${DEFAULT_NPM_REGISTRY})`,
706
+ DEFAULT_NPM_REGISTRY,
707
+ )
708
+ .option(
709
+ '--allow-branches <branches...>',
710
+ `允许发布正式版本的分支列表 (默认: ${DEFAULT_ALLOW_BRANCHES.join(', ')})`,
507
711
  )
508
712
  .action(async (options) => {
509
713
  try {
510
- if (!options.commit) {
511
- throw new Error('请提供需要发布的 commit id');
512
- }
513
714
  if (!process.env.NPM_AUTH_TOKEN) {
514
715
  throw new Error('请设置 NPM_AUTH_TOKEN 环境变量');
515
716
  }
@@ -522,7 +723,14 @@ const installAction$2 = (program) => {
522
723
  });
523
724
  };
524
725
 
525
- const GIT_REPO_URL_REGEX = /git@github\.com:([^\/]+)\/([^\.]+)\.git/;
726
+ // Copyright (c) 2025 coze-dev
727
+ // SPDX-License-Identifier: MIT
728
+
729
+ const GIT_REPO_URL_REGEX = /git@.+:([^\/]+)\/([^\.]+)\.git/;
730
+
731
+ // Copyright (c) 2025 coze-dev
732
+ // SPDX-License-Identifier: MIT
733
+
526
734
 
527
735
  /**
528
736
  * 生成指定长度的随机字符串(使用 crypto 模块)
@@ -544,6 +752,10 @@ var BumpType; (function (BumpType) {
544
752
  const MAJOR = 'major'; BumpType["MAJOR"] = MAJOR;
545
753
  })(BumpType || (BumpType = {}));
546
754
 
755
+ // Copyright (c) 2025 coze-dev
756
+ // SPDX-License-Identifier: MIT
757
+
758
+
547
759
  /**
548
760
  * 获取更新类型的描述
549
761
  */
@@ -592,6 +804,16 @@ const requestBumpType = async () => {
592
804
  }
593
805
  };
594
806
 
807
+ // Copyright (c) 2025 coze-dev
808
+ // SPDX-License-Identifier: MIT
809
+
810
+
811
+
812
+
813
+
814
+
815
+
816
+
595
817
  /**
596
818
  * 根据当前版本和发布类型计算新版本号
597
819
  */
@@ -718,28 +940,24 @@ const generatePublishManifest = async (
718
940
  };
719
941
  };
720
942
 
943
+ // Copyright (c) 2025 coze-dev
944
+ // SPDX-License-Identifier: MIT
945
+
946
+
947
+
948
+
949
+
950
+
951
+
721
952
  async function commitChanges({
722
- sessionId,
723
953
  files,
724
954
  cwd,
725
- publishManifests,
726
955
  branchName,
727
- createTags,
728
956
  }) {
729
957
  await exec(`git add ${files.join(' ')}`, { cwd });
730
958
  await exec(`git commit -m "chore: Publish ${branchName}" -n`, { cwd });
731
959
 
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 };
960
+ return { effects: [branchName], branchName };
743
961
  }
744
962
 
745
963
 
@@ -754,83 +972,307 @@ async function push({ refs, cwd, repoUrl }) {
754
972
  });
755
973
  }
756
974
 
975
+ // Copyright (c) 2025 coze-dev
976
+ // SPDX-License-Identifier: MIT
977
+
978
+
979
+
980
+
981
+
982
+
983
+
984
+
985
+
986
+
987
+
988
+
989
+
990
+
991
+ /**
992
+ * 获取或创建发布分支
993
+ */
994
+ const getOrCreateBranch = async (params
995
+
996
+
997
+
998
+
999
+ ) => {
1000
+ const { bumpPolicy, sessionId, branchPrefix, cwd } = params;
1001
+
1002
+ if (bumpPolicy === BumpType.BETA) {
1003
+ return await getCurrentBranchName();
1004
+ }
1005
+
1006
+ const date = dayjs().format('YYYYMMDD');
1007
+ const branchName = `${branchPrefix}/${date}-${sessionId}`;
1008
+ await exec(`git checkout -b ${branchName}`, { cwd });
1009
+ return branchName;
1010
+ };
1011
+
1012
+ /**
1013
+ * 处理 Beta 发布流程:询问并执行 git push
1014
+ */
1015
+ const handleBetaPublish = async (
1016
+ branchName,
1017
+ cwd,
1018
+ ) => {
1019
+ logger.success(`Changes have been committed to branch "${branchName}".`);
1020
+
1021
+ const shouldPush = await prompts.confirm({
1022
+ message: 'Do you want to push the changes now?',
1023
+ default: true,
1024
+ });
1025
+
1026
+ if (shouldPush) {
1027
+ logger.info('Pushing changes to remote...');
1028
+ await exec('git push', { cwd });
1029
+ logger.success('Changes pushed successfully!');
1030
+ } else {
1031
+ logger.info('Please run "git push" manually when you are ready.');
1032
+ }
1033
+ };
1034
+
1035
+ /**
1036
+ * 显示手动创建 MR 的提示消息
1037
+ */
1038
+ const showManualMrMessage = (
1039
+ branchName,
1040
+ repositoryUrl,
1041
+ ) => {
1042
+ const baseMessage = `Please create a merge request from branch "${branchName}" to the main branch`;
1043
+ const suffix =
1044
+ 'The release will be triggered after the merge request is merged.';
1045
+
1046
+ if (repositoryUrl) {
1047
+ logger.success(
1048
+ `Repository: ${repositoryUrl}\n${baseMessage}.\n${suffix}`,
1049
+ false,
1050
+ );
1051
+ } else {
1052
+ logger.success(`${baseMessage} in your repository.\n${suffix}`);
1053
+ }
1054
+ };
1055
+
1056
+ /**
1057
+ * 显示 MR/PR 创建链接并打开浏览器
1058
+ */
1059
+ const showMrLinkAndOpenBrowser = async (mrUrl) => {
1060
+ const log = [
1061
+ '************************************************',
1062
+ '*',
1063
+ `* Please create MR/PR: ${mrUrl}`,
1064
+ '*',
1065
+ '* The release will be triggered after the MR is merged.',
1066
+ '*',
1067
+ '************************************************',
1068
+ ];
1069
+ logger.success(log.join('\n'), false);
1070
+
1071
+ const open = await import('open');
1072
+ await open.default(mrUrl);
1073
+ };
1074
+
1075
+ /**
1076
+ * 处理正式版本发布流程:提示创建 MR/PR
1077
+ */
1078
+ const handleProductionPublish = async (
1079
+ branchName,
1080
+ cwd,
1081
+ ) => {
1082
+ const originUrl = await getCurrentOrigin(cwd);
1083
+ if (!originUrl) {
1084
+ showManualMrMessage(branchName);
1085
+ return;
1086
+ }
1087
+
1088
+ const repoInfo = parseGitRemoteUrl(originUrl);
1089
+ if (!repoInfo) {
1090
+ showManualMrMessage(branchName);
1091
+ return;
1092
+ }
1093
+
1094
+ const mrUrl = buildMergeRequestUrl(repoInfo, branchName);
1095
+ if (!mrUrl) {
1096
+ showManualMrMessage(branchName, repoInfo.fullUrl);
1097
+ return;
1098
+ }
1099
+
1100
+ await showMrLinkAndOpenBrowser(mrUrl);
1101
+ };
1102
+
1103
+ /**
1104
+ * 根据发布类型处理后续提示
1105
+ */
1106
+ const handlePostPushPrompts = async (
1107
+ bumpPolicy,
1108
+ branchName,
1109
+ cwd,
1110
+ ) => {
1111
+ const isAlphaPublish = bumpPolicy === BumpType.ALPHA;
1112
+ const isBetaPublish = bumpPolicy === BumpType.BETA;
1113
+
1114
+ // Alpha 发布:不需要提示(会自动触发 CI 发布)
1115
+ if (isAlphaPublish) {
1116
+ return;
1117
+ }
1118
+
1119
+ // Beta 发布:询问用户是否立即执行 git push
1120
+ if (isBetaPublish) {
1121
+ await handleBetaPublish(branchName, cwd);
1122
+ return;
1123
+ }
1124
+
1125
+ // 正式版本发布:提示创建 MR/PR
1126
+ await handleProductionPublish(branchName, cwd);
1127
+ };
1128
+
757
1129
  const pushToRemote = async (options) => {
758
1130
  const {
759
1131
  sessionId,
760
1132
  changedFiles,
761
1133
  cwd,
762
- publishManifests,
763
1134
  bumpPolicy,
764
1135
  skipCommit,
765
1136
  skipPush,
766
1137
  repoUrl,
1138
+ branchPrefix = 'release',
767
1139
  } = options;
1140
+
768
1141
  if (skipCommit) {
769
1142
  return;
770
1143
  }
771
1144
 
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
- );
1145
+ // 1. 获取或创建发布分支
1146
+ const branchName = await getOrCreateBranch({
1147
+ bumpPolicy,
1148
+ sessionId,
1149
+ branchPrefix,
1150
+ cwd,
1151
+ });
786
1152
 
787
- // 4. 创建并推送发布分支
1153
+ // 2. 提交变更
788
1154
  const { effects } = await commitChanges({
789
- sessionId,
790
1155
  files: changedFiles,
791
1156
  cwd,
792
- publishManifests,
793
1157
  branchName,
794
- // 只有 alpha、beta 需要创建 tag,正式发布会在 .github/workflows/common-pr-checks.yml 创建并发布tag
795
- createTags: isTestPublish,
796
1158
  });
1159
+
797
1160
  if (skipPush) {
798
1161
  return;
799
1162
  }
1163
+
1164
+ // 3. 推送变更
800
1165
  await push({
801
1166
  refs: effects,
802
1167
  cwd,
803
- repoUrl: actualRepoUrl,
1168
+ repoUrl,
804
1169
  });
805
1170
 
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];
1171
+ // 4. 根据发布类型显示不同提示
1172
+ await handlePostPushPrompts(bumpPolicy, branchName, cwd);
1173
+ };
813
1174
 
814
- if (isTestPublish) {
815
- logger.success(
816
- `Please refer to https://github.com/${repoOwner}/${repoName}/actions/workflows/release.yml for the release progress.`,
1175
+ // Copyright (c) 2025 coze-dev
1176
+ // SPDX-License-Identifier: MIT
1177
+
1178
+
1179
+ const getRushConfiguration = (() => {
1180
+ const cachedRushConfigs = new Map();
1181
+ return (startingFolder) => {
1182
+ startingFolder = startingFolder || process.cwd();
1183
+ const possibleRushFile = rushSdk.RushConfiguration.tryFindRushJsonLocation({
1184
+ startingFolder,
1185
+ });
1186
+ if (!possibleRushFile) {
1187
+ throw new Error(
1188
+ `rush.json not found from starting folder: ${startingFolder}`,
1189
+ );
1190
+ }
1191
+ if (cachedRushConfigs.has(possibleRushFile)) {
1192
+ return cachedRushConfigs.get(possibleRushFile) ;
1193
+ }
1194
+
1195
+ const rushConfig =
1196
+ rushSdk.RushConfiguration.loadFromConfigurationFile(possibleRushFile);
1197
+ cachedRushConfigs.set(startingFolder, rushConfig);
1198
+ return rushConfig;
1199
+ };
1200
+ })();
1201
+
1202
+ const lookupTo = (to) => {
1203
+ const cached = new Map();
1204
+ const config = getRushConfiguration();
1205
+ const core = (pkgName) => {
1206
+ if (cached.has(pkgName)) {
1207
+ return cached.get(pkgName);
1208
+ }
1209
+ const result = [pkgName];
1210
+ cached.set(pkgName, result);
1211
+ const projects = config.projects.filter(p => p.packageName === pkgName);
1212
+ if (projects.length === 0) {
1213
+ throw new Error(`Project ${pkgName} not found`);
1214
+ }
1215
+ const project = projects[0];
1216
+ const deps = Array.from(project.dependencyProjects.values()).map(
1217
+ p => p.packageName,
817
1218
  );
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);
1219
+ result.push(...deps);
1220
+ deps.forEach(dep => {
1221
+ const subPkgs = core(dep);
1222
+ if (subPkgs) {
1223
+ result.push(...subPkgs);
1224
+ }
1225
+ });
1226
+ return result;
1227
+ };
1228
+ const result = core(to);
1229
+ return [...new Set(result)];
1230
+ };
1231
+
1232
+ const lookupFrom = (from) => {
1233
+ const cached = new Map();
1234
+ const config = getRushConfiguration();
1235
+ const core = (pkgName) => {
1236
+ if (cached.has(pkgName)) {
1237
+ return cached.get(pkgName);
1238
+ }
1239
+ const result = new Set();
1240
+ cached.set(pkgName, result);
1241
+ const projects = config.projects.filter(p => p.packageName === pkgName);
1242
+ if (projects.length === 0) {
1243
+ throw new Error(`Project ${pkgName} not found`);
1244
+ }
1245
+ const project = projects[0];
1246
+ const deps = Array.from([
1247
+ ...project.dependencyProjects.values(),
1248
+ ...project.consumingProjects.values(),
1249
+ ]).map(p => p.packageName);
1250
+ deps.forEach(dep => {
1251
+ result.add(dep);
1252
+ const subPkgs = cached.has(dep) ? cached.get(dep) : core(dep);
1253
+ if (subPkgs) {
1254
+ subPkgs.forEach(p => {
1255
+ result.add(p);
1256
+ });
1257
+ }
1258
+ });
1259
+ return result;
1260
+ };
1261
+ const result = core(from);
1262
+ return [...new Set(result)];
1263
+ };
1264
+
1265
+ const lookupOnly = (packageName) => {
1266
+ const config = getRushConfiguration();
1267
+ const projects = config.projects.filter(p => p.packageName === packageName);
1268
+ if (projects.length === 0) {
1269
+ throw new Error(`Project ${packageName} not found`);
830
1270
  }
1271
+ return projects[0];
831
1272
  };
832
1273
 
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; }
1274
+ function _optionalChain$2(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; }
1275
+
834
1276
 
835
1277
 
836
1278
  var RetrievePattern; (function (RetrievePattern) {
@@ -843,10 +1285,9 @@ const retrievePackages = (
843
1285
  pattern,
844
1286
  packages,
845
1287
  ) => {
846
- const rushConfiguration = getRushConfiguration();
847
1288
  const matchedPackages = new Set();
848
1289
  packages.forEach(pkg => {
849
- const project = rushConfiguration.getProjectByName(pkg);
1290
+ const project = lookupOnly(pkg);
850
1291
  if (!project) {
851
1292
  throw new Error(`Package "${pkg}" not found in rush configuration`);
852
1293
  }
@@ -858,16 +1299,15 @@ const retrievePackages = (
858
1299
  const matched = [];
859
1300
  switch (pattern) {
860
1301
  case 'to': {
861
- matched.push(project.dependencyProjects);
1302
+ matched.push(...lookupTo(pkg));
862
1303
  break;
863
1304
  }
864
1305
  case 'from': {
865
- matched.push(project.consumingProjects);
866
- matched.push(project.dependencyProjects);
1306
+ matched.push(...lookupFrom(pkg));
867
1307
  break;
868
1308
  }
869
1309
  case 'only': {
870
- // do nothing
1310
+ matched.push(pkg);
871
1311
  break;
872
1312
  }
873
1313
  default: {
@@ -875,50 +1315,81 @@ const retrievePackages = (
875
1315
  }
876
1316
  }
877
1317
 
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);
1318
+ matched.forEach(pkgName => {
1319
+ matchedPackages.add(pkgName);
1320
+ });
886
1321
  });
887
- return matchedPackages;
1322
+
1323
+ return [...matchedPackages];
888
1324
  };
889
1325
 
890
1326
  const validateAndGetPackages = (options) => {
891
1327
  const retrievePatterns = Object.values(RetrievePattern);
892
- if (retrievePatterns.every(pattern => (_optionalChain$1([options, 'access', _ => _[pattern], 'optionalAccess', _2 => _2.length]) || 0) <= 0)) {
1328
+ if (retrievePatterns.every(pattern => (_optionalChain$2([options, 'access', _ => _[pattern], 'optionalAccess', _2 => _2.length]) || 0) <= 0)) {
893
1329
  throw new Error('No packages to publish');
894
1330
  }
895
- return retrievePatterns.reduce((acc, pattern) => {
1331
+ const res = retrievePatterns.reduce((acc, pattern) => {
896
1332
  const packages = options[pattern];
897
1333
  if (!packages || packages.length <= 0) {
898
1334
  return acc;
899
1335
  }
900
1336
  const placeholders = retrievePackages(pattern , packages);
901
- return new Set([...acc, ...placeholders]);
1337
+ placeholders.forEach(pkgName => {
1338
+ acc.add(pkgName);
1339
+ });
1340
+ return acc;
902
1341
  }, new Set());
1342
+ const result = [...res]
1343
+ .map(pkgName => {
1344
+ const p = lookupOnly(pkgName);
1345
+ if (p && p.shouldPublish) {
1346
+ return p;
1347
+ }
1348
+ return undefined;
1349
+ })
1350
+ .filter(Boolean) ;
1351
+ return result;
903
1352
  };
904
1353
 
1354
+ 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; }// Copyright (c) 2025 coze-dev
1355
+
1356
+
1357
+
1358
+
1359
+
1360
+
1361
+
1362
+
905
1363
  const confirmForPublish = async (
906
1364
  publishManifest,
907
1365
  dryRun,
1366
+ options,
908
1367
  ) => {
909
- console.log(chalk.gray('Will publish the following packages:'));
1368
+ logger.info(chalk.gray('Will publish the following packages:'), false);
910
1369
  publishManifest.forEach(manifest => {
911
- const msg = `${manifest.project.packageName}: ${chalk.bgGreen(`${manifest.currentVersion} -> ${chalk.bold(manifest.newVersion)}`)}`;
912
- console.log(`- ${msg}`);
1370
+ const versionChange = `${manifest.currentVersion} -> ${chalk.bold(manifest.newVersion)}`;
1371
+ const msg = `${manifest.project.packageName}: ${chalk.bgGreen(versionChange)}`;
1372
+ logger.info(`- ${msg}`, false);
913
1373
  });
1374
+
1375
+ // Release 模式的额外提示
1376
+ if (_optionalChain$1([options, 'optionalAccess', _ => _.isReleaseMode])) {
1377
+ logger.info('', false);
1378
+ logger.warn(chalk.yellow.bold('⚠️ Release Mode Enabled:'), false);
1379
+ const registryMsg = ` Packages will be published directly to: ${chalk.bold(options.registry || 'default registry')}`;
1380
+ logger.warn(chalk.yellow(registryMsg), false);
1381
+ }
1382
+
914
1383
  if (dryRun) {
915
1384
  return false;
916
1385
  }
917
1386
 
918
- console.log('\n');
1387
+ logger.info('', false);
919
1388
  try {
920
1389
  const result = await prompts.confirm({
921
- message: 'Are you sure to publish?',
1390
+ message: _optionalChain$1([options, 'optionalAccess', _2 => _2.isReleaseMode])
1391
+ ? 'Are you sure to publish directly?'
1392
+ : 'Are you sure to publish?',
922
1393
  default: true,
923
1394
  });
924
1395
  return result;
@@ -927,7 +1398,8 @@ const confirmForPublish = async (
927
1398
  }
928
1399
  };
929
1400
 
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; }
1401
+ 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
1402
+
931
1403
 
932
1404
 
933
1405
 
@@ -1132,6 +1604,11 @@ const generateChangelog$1 = ({
1132
1604
  };
1133
1605
  };
1134
1606
 
1607
+ // Copyright (c) 2025 coze-dev
1608
+ // SPDX-License-Identifier: MIT
1609
+
1610
+
1611
+
1135
1612
  const deleteFiles = async (files) => {
1136
1613
  await Promise.all(
1137
1614
  files.map(async file => {
@@ -1187,7 +1664,7 @@ const readPreviousChangelog = async (
1187
1664
 
1188
1665
  const generateChangelogForProject = async (manifest) => {
1189
1666
  const { project, newVersion } = manifest;
1190
- const rushConfiguration = getRushConfiguration();
1667
+ const rushConfiguration = getRushConfiguration$1();
1191
1668
  const { changesFolder } = rushConfiguration;
1192
1669
  const changedFolderOfPkg = path.resolve(changesFolder, project.packageName);
1193
1670
  const changelogJsonPath = path.resolve(
@@ -1229,6 +1706,12 @@ const generateChangelog = async (
1229
1706
  return modifiedFiles.flat();
1230
1707
  };
1231
1708
 
1709
+ // Copyright (c) 2025 coze-dev
1710
+ // SPDX-License-Identifier: MIT
1711
+
1712
+
1713
+
1714
+
1232
1715
  const updatePackageVersion = async (
1233
1716
  project,
1234
1717
  newVersion,
@@ -1257,6 +1740,10 @@ const applyPublishManifest = async (
1257
1740
  return modifiedFiles;
1258
1741
  };
1259
1742
 
1743
+ // Copyright (c) 2025 coze-dev
1744
+ // SPDX-License-Identifier: MIT
1745
+
1746
+
1260
1747
  // 针对不同类型的发布,对应不同 sideEffects:
1261
1748
  // 1. alpha: 直接创建并push 分支,触发 CI,执行发布;
1262
1749
  // 2. beta: 本分支直接切换版本号,并发布
@@ -1264,15 +1751,18 @@ const applyPublishManifest = async (
1264
1751
 
1265
1752
  const publish = async (options) => {
1266
1753
  const sessionId = randomHash(6);
1267
- const rushConfiguration = getRushConfiguration();
1754
+ const rushConfiguration = getRushConfiguration$1();
1268
1755
  const rushFolder = rushConfiguration.rushJsonFolder;
1269
- if (process.env.SKIP_UNCOMMITTED_CHECK !== 'true') {
1756
+ if (
1757
+ process.env.SKIP_UNCOMMITTED_CHECK !== 'true' &&
1758
+ options.release !== true
1759
+ ) {
1270
1760
  await ensureNotUncommittedChanges();
1271
1761
  }
1272
1762
 
1273
1763
  // 1. 验证并获取需要发布的包列表
1274
1764
  const packagesToPublish = validateAndGetPackages(options);
1275
- if (packagesToPublish.size === 0) {
1765
+ if (packagesToPublish.length === 0) {
1276
1766
  logger.error(
1277
1767
  'No packages to publish, should specify some package by `--to` or `--from` or `--only`',
1278
1768
  );
@@ -1291,7 +1781,11 @@ const publish = async (options) => {
1291
1781
  const isBetaPublish = [BumpType.BETA, BumpType.ALPHA].includes(
1292
1782
  bumpPolicy ,
1293
1783
  );
1294
- if (isBetaPublish === false && (await isMainBranch()) === false) {
1784
+ if (
1785
+ process.env.SKIP_BRANCH_CHECK !== 'true' &&
1786
+ isBetaPublish === false &&
1787
+ (await isMainBranch()) === false
1788
+ ) {
1295
1789
  // 只允许在主分支发布
1296
1790
  logger.error(
1297
1791
  'You are not in main branch, please switch to main branch and try again.',
@@ -1302,6 +1796,10 @@ const publish = async (options) => {
1302
1796
  const continuePublish = await confirmForPublish(
1303
1797
  publishManifests,
1304
1798
  !!options.dryRun,
1799
+ {
1800
+ isReleaseMode: !!options.release,
1801
+ registry: options.registry || DEFAULT_NPM_REGISTRY,
1802
+ },
1305
1803
  );
1306
1804
 
1307
1805
  if (!continuePublish) {
@@ -1317,17 +1815,46 @@ const publish = async (options) => {
1317
1815
  await Promise.all(postHandles.map(handle => handle(publishManifests)))
1318
1816
  ).flat();
1319
1817
 
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
- });
1818
+ // 4. 创建并推送发布分支 或 直接发布
1819
+ let shouldRelease = false;
1820
+ if (options.release) {
1821
+ // 验证 release 模式的前置条件
1822
+ if (isBetaPublish === false) {
1823
+ logger.error(
1824
+ 'Direct release (--release) is only allowed for alpha or beta versions.',
1825
+ );
1826
+ logger.error(`Current bump type is: ${bumpPolicy}`);
1827
+ logger.warn('Falling back to normal publish mode...');
1828
+ } else {
1829
+ shouldRelease = true;
1830
+ }
1831
+ }
1832
+
1833
+ if (shouldRelease) {
1834
+ // Release 模式:直接发布
1835
+ logger.info('Running in direct release mode...');
1836
+ logger.info('Starting package release...');
1837
+ const registry = options.registry || DEFAULT_NPM_REGISTRY;
1838
+ // 将 PublishManifest[] 转换为 PackageToPublish[]
1839
+ const packages = publishManifests.map(manifest => ({
1840
+ packageName: manifest.project.packageName,
1841
+ version: manifest.newVersion,
1842
+ }));
1843
+ await release({ dryRun: !!options.dryRun, registry, packages });
1844
+ } else {
1845
+ // 普通模式:创建并推送发布分支
1846
+ await pushToRemote({
1847
+ bumpPolicy: bumpPolicy ,
1848
+ sessionId,
1849
+ changedFiles,
1850
+ cwd: rushFolder,
1851
+ skipCommit: !!options.skipCommit,
1852
+ skipPush: !!options.skipPush,
1853
+ repoUrl: options.repoUrl,
1854
+ branchPrefix: options.branchPrefix,
1855
+ });
1856
+ }
1857
+
1331
1858
  logger.success('Publish success.');
1332
1859
  };
1333
1860
 
@@ -1358,6 +1885,21 @@ const installAction$1 = (program) => {
1358
1885
  'Git repository URL (e.g. git@github.com:coze-dev/coze-js.git)',
1359
1886
  undefined,
1360
1887
  )
1888
+ .option(
1889
+ '--branch-prefix <prefix>',
1890
+ `Git branch name prefix (default: ${DEFAULT_BRANCH_PREFIX})`,
1891
+ DEFAULT_BRANCH_PREFIX,
1892
+ )
1893
+ .option(
1894
+ '-l, --release',
1895
+ 'Directly publish packages (only for alpha/beta versions)',
1896
+ false,
1897
+ )
1898
+ .option(
1899
+ '--registry <url>',
1900
+ `NPM registry URL (default: ${DEFAULT_NPM_REGISTRY})`,
1901
+ DEFAULT_NPM_REGISTRY,
1902
+ )
1361
1903
  .action(async (options) => {
1362
1904
  try {
1363
1905
  const repoUrl = options.repoUrl || (await getCurrentOrigin());
@@ -1365,8 +1907,9 @@ const installAction$1 = (program) => {
1365
1907
  throw new Error('Git repository URL is required');
1366
1908
  }
1367
1909
  if (!GIT_REPO_URL_REGEX.test(repoUrl)) {
1910
+ const expectedFormat = 'git@github.com:${org}/${repo}.git';
1368
1911
  throw new Error(
1369
- `Invalid git repository URL: ${repoUrl}, it should be follow the format: git@github.com:\${org}/\${repo}.git`,
1912
+ `Invalid git repository URL: ${repoUrl}, it should follow the format: ${expectedFormat}`,
1370
1913
  );
1371
1914
  }
1372
1915
  const normalizeOptions = {
@@ -1381,6 +1924,10 @@ const installAction$1 = (program) => {
1381
1924
  });
1382
1925
  };
1383
1926
 
1927
+ // Copyright (c) 2025 coze-dev
1928
+ // SPDX-License-Identifier: MIT
1929
+
1930
+
1384
1931
  const whoAmI = async () => {
1385
1932
  const [name, email] = await Promise.all([
1386
1933
  exec('git config user.name', { cwd: __dirname, silent: true }),
@@ -1392,6 +1939,10 @@ const whoAmI = async () => {
1392
1939
  };
1393
1940
  };
1394
1941
 
1942
+ // Copyright (c) 2025 coze-dev
1943
+ // SPDX-License-Identifier: MIT
1944
+
1945
+
1395
1946
  // 这两个包没有 module 导出
1396
1947
  // eslint-disable-next-line @typescript-eslint/no-require-imports
1397
1948
  const { sync } = require('conventional-commits-parser');
@@ -1408,7 +1959,7 @@ const collectShouldUpdateChangesProjects = async (
1408
1959
 
1409
1960
  ) => {
1410
1961
  const changedFiles = await getChangedFilesFromCached();
1411
- const rushConfiguration = getRushConfiguration();
1962
+ const rushConfiguration = getRushConfiguration$1();
1412
1963
  const lookup = rushConfiguration.getProjectLookupForRoot(
1413
1964
  rushConfiguration.rushJsonFolder,
1414
1965
  );
@@ -1520,7 +2071,7 @@ async function generateAllChangesFile(
1520
2071
  comment,
1521
2072
  patchType,
1522
2073
  ) {
1523
- const rushConfiguration = getRushConfiguration();
2074
+ const rushConfiguration = getRushConfiguration$1();
1524
2075
  const needUpdateProjects = await collectShouldUpdateChangesProjects();
1525
2076
  const { email } = await whoAmI();
1526
2077
 
@@ -1544,9 +2095,13 @@ async function generateAllChangesFile(
1544
2095
  }
1545
2096
  }
1546
2097
 
2098
+ // Copyright (c) 2025 coze-dev
2099
+ // SPDX-License-Identifier: MIT
2100
+
2101
+
1547
2102
  const amendCommit = async () => {
1548
2103
  const changedFiles = await getChangedFilesFromCached();
1549
- const rushConfiguration = getRushConfiguration();
2104
+ const rushConfiguration = getRushConfiguration$1();
1550
2105
 
1551
2106
  const relativeChangesFolder = path.relative(
1552
2107
  rushConfiguration.rushJsonFolder,
@@ -1561,6 +2116,10 @@ const amendCommit = async () => {
1561
2116
  }
1562
2117
  };
1563
2118
 
2119
+ // Copyright (c) 2025 coze-dev
2120
+ // SPDX-License-Identifier: MIT
2121
+
2122
+
1564
2123
  const generateChangeFiles = async (options) => {
1565
2124
  // CI 环境的提交不做处理
1566
2125
  if (options.ci || process.env.CI === 'true') {
@@ -1573,7 +2132,7 @@ const generateChangeFiles = async (options) => {
1573
2132
  try {
1574
2133
  let { commitMsg } = options;
1575
2134
  if (!commitMsg) {
1576
- const rushConfiguration = getRushConfiguration();
2135
+ const rushConfiguration = getRushConfiguration$1();
1577
2136
  commitMsg = await fs.readFile(
1578
2137
  path.resolve(rushConfiguration.rushJsonFolder, '.git/COMMIT_EDITMSG'),
1579
2138
  'utf-8',
@@ -1606,6 +2165,10 @@ const installAction = (program) => {
1606
2165
  });
1607
2166
  };
1608
2167
 
2168
+ // Copyright (c) 2025 coze-dev
2169
+ // SPDX-License-Identifier: MIT
2170
+
2171
+
1609
2172
  const main = () => {
1610
2173
  const packageJson = JSON.parse(
1611
2174
  fs$1.readFileSync(path.resolve(__dirname, '../package.json'), 'utf8'),