@qlover/fe-release 5.0.6 → 5.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -28,7 +28,7 @@ var import_commander = require("commander");
28
28
 
29
29
  // package.json
30
30
  var description = "A tool for releasing front-end projects, supporting multiple release modes and configurations, simplifying the release process and improving efficiency.";
31
- var version = "5.0.6";
31
+ var version = "5.3.1";
32
32
 
33
33
  // src/cli.ts
34
34
  var import_semver = __toESM(require("semver"), 1);
@@ -82,6 +82,10 @@ var releaseJson = {
82
82
  branchName: "release/${repoName}-${releaseId}",
83
83
  releaseTagName: "release-tag-${count}-patch-${releaseId}",
84
84
  commitMessage: "chore(release): ${spaces}",
85
+ /** GitHub release title template (createRelease mode) */
86
+ releaseName: "${name}@${version}",
87
+ /** Path prefixes skipped when creating GitHub releases */
88
+ ignoreReleasePaths: [],
85
89
  label: {
86
90
  name: "CI-Release",
87
91
  color: "1A7F37",
@@ -1606,6 +1610,22 @@ var ChangesetVersion = class extends import_scripts_context4.ScriptPlugin {
1606
1610
  get ignoreNonUpdatedPackages() {
1607
1611
  return !!this.config.ignoreNonUpdatedPackages;
1608
1612
  }
1613
+ get useTrustedPublishing() {
1614
+ return !!(this.config.useTrustedPublishing || this.context.parameters.changesetVersion?.useTrustedPublishing);
1615
+ }
1616
+ /**
1617
+ * True when publish should use npm Trusted Publishing (OIDC) instead of NPM_TOKEN.
1618
+ */
1619
+ isTrustedPublishingEnv() {
1620
+ if (this.useTrustedPublishing) {
1621
+ return true;
1622
+ }
1623
+ const envFlag = this.context.getEnv("FE_RELEASE_TRUSTED_PUBLISHING");
1624
+ if (envFlag === "1" || envFlag === "true") {
1625
+ return true;
1626
+ }
1627
+ return Boolean(this.context.getEnv("ACTIONS_ID_TOKEN_REQUEST_URL"));
1628
+ }
1609
1629
  shouldProcessWorkspace(workspace) {
1610
1630
  return shouldProcessWorkspace(workspace, this.ignoreNonUpdatedPackages);
1611
1631
  }
@@ -1627,15 +1647,26 @@ var ChangesetVersion = class extends import_scripts_context4.ScriptPlugin {
1627
1647
  }
1628
1648
  }
1629
1649
  /**
1630
- * Ensure NPM_TOKEN is available and configured before changeset publish.
1650
+ * Configure npm auth before changeset publish.
1631
1651
  *
1632
- * Only required for `publish` / `both` modes. Version-only runs (release PR)
1633
- * do not need an npm auth token.
1652
+ * Uses Trusted Publishing (OIDC) when detected; otherwise requires `NPM_TOKEN`.
1653
+ * Version-only runs (release PR) do not need npm auth.
1634
1654
  */
1635
1655
  async validateNpmToken() {
1656
+ if (this.isTrustedPublishingEnv()) {
1657
+ this.logger.info(
1658
+ "Using npm Trusted Publishing (OIDC); skip NPM_TOKEN auth setup"
1659
+ );
1660
+ if (this.context.dryRun) {
1661
+ this.logDryRun("Would publish via npm Trusted Publishing (OIDC)");
1662
+ }
1663
+ return;
1664
+ }
1636
1665
  const npmToken = this.context.getEnv("NPM_TOKEN");
1637
1666
  if (!npmToken) {
1638
- throw new Error("NPM_TOKEN is not set");
1667
+ throw new Error(
1668
+ "NPM_TOKEN is not set. Enable Trusted Publishing (OIDC) or provide NPM_TOKEN."
1669
+ );
1639
1670
  }
1640
1671
  if (this.context.dryRun) {
1641
1672
  this.logDryRun(
@@ -2745,7 +2776,7 @@ var GithubManager = class {
2745
2776
  */
2746
2777
  getOctokitReleaseOptions(options) {
2747
2778
  const {
2748
- releaseName,
2779
+ releaseName = releaseJson.github.releaseName,
2749
2780
  draft = false,
2750
2781
  preRelease = false,
2751
2782
  autoGenerate = false,
@@ -2754,7 +2785,7 @@ var GithubManager = class {
2754
2785
  discussionCategoryName = void 0
2755
2786
  } = this.context.getParameters("github");
2756
2787
  const name = releaseName;
2757
- const body = autoGenerate ? "" : this.truncateBody(String(releaseNotes));
2788
+ const body = autoGenerate ? "" : this.truncateBody(String(releaseNotes ?? ""));
2758
2789
  return {
2759
2790
  name,
2760
2791
  make_latest: makeLatest.toString(),
@@ -3518,6 +3549,7 @@ var ReleaseFormatter = class {
3518
3549
  var Github = class extends GitBase {
3519
3550
  releaseFormatter;
3520
3551
  githubManager;
3552
+ pather = new Pather();
3521
3553
  constructor(context, props = {}) {
3522
3554
  super(context, "github", props);
3523
3555
  this.releaseFormatter = new ReleaseFormatter(
@@ -3529,6 +3561,9 @@ var Github = class extends GitBase {
3529
3561
  get mode() {
3530
3562
  return this.config.mode || releaseJson.github.mode;
3531
3563
  }
3564
+ get ignoreReleasePaths() {
3565
+ return this.config.ignoreReleasePaths ?? [...releaseJson.github.ignoreReleasePaths];
3566
+ }
3532
3567
  async onBefore() {
3533
3568
  await super.onBefore();
3534
3569
  await this.githubManager.validateToken();
@@ -3554,6 +3589,9 @@ var Github = class extends GitBase {
3554
3589
  if (workspace.dependencyRelease) {
3555
3590
  return workspace;
3556
3591
  }
3592
+ if (this.mode === "createRelease" && this.isIgnoredReleasePath(workspace)) {
3593
+ return workspace;
3594
+ }
3557
3595
  return githubChangelog.enrichWorkspaceChangelog(workspace);
3558
3596
  })
3559
3597
  )
@@ -3562,6 +3600,13 @@ var Github = class extends GitBase {
3562
3600
  }
3563
3601
  async onSuccess() {
3564
3602
  const workspaces = this.context.requireWorkspaces();
3603
+ if (this.mode === "createRelease") {
3604
+ await this.step({
3605
+ label: "Create GitHub Releases",
3606
+ task: () => this.createGithubReleases(workspaces)
3607
+ });
3608
+ return;
3609
+ }
3565
3610
  const releaseBranchResult = await this.step({
3566
3611
  label: "Create Release Branch",
3567
3612
  task: () => this.createReleaseBranch(workspaces)
@@ -3578,6 +3623,63 @@ var Github = class extends GitBase {
3578
3623
  }
3579
3624
  await this.handleReleasePullRequest(workspaces, releaseBranchResult);
3580
3625
  }
3626
+ /**
3627
+ * Whether this workspace path is under an ignoreReleasePaths entry.
3628
+ */
3629
+ isIgnoredReleasePath(workspace) {
3630
+ const ignorePaths = this.ignoreReleasePaths;
3631
+ if (ignorePaths.length === 0) {
3632
+ return false;
3633
+ }
3634
+ return ignorePaths.some(
3635
+ (ignorePath) => this.pather.isSubPath(workspace.path, ignorePath)
3636
+ );
3637
+ }
3638
+ /**
3639
+ * Workspaces that should receive a GitHub Release in createRelease mode.
3640
+ *
3641
+ * Requires `tagName`. Skips dependency-release packages and paths matched
3642
+ * by {@link GithubProps.ignoreReleasePaths}. Private packages are included
3643
+ * when they have a tag.
3644
+ */
3645
+ getReleaseWorkspaces(workspaces) {
3646
+ return workspaces.filter((workspace) => {
3647
+ if (!workspace.tagName) {
3648
+ return false;
3649
+ }
3650
+ if (workspace.dependencyRelease) {
3651
+ return false;
3652
+ }
3653
+ if (this.isIgnoredReleasePath(workspace)) {
3654
+ this.logger.info(
3655
+ `Skip GitHub Release for ignored path: ${workspace.path} (${workspace.tagName})`
3656
+ );
3657
+ return false;
3658
+ }
3659
+ return true;
3660
+ });
3661
+ }
3662
+ /**
3663
+ * Create a GitHub Release for each eligible workspace.
3664
+ *
3665
+ * Release notes use the enriched workspace changelog from `onExec`.
3666
+ */
3667
+ async createGithubReleases(workspaces) {
3668
+ const releaseWorkspaces = this.getReleaseWorkspaces(workspaces);
3669
+ if (releaseWorkspaces.length === 0) {
3670
+ this.logger.warn("No workspaces eligible for GitHub Release creation");
3671
+ return;
3672
+ }
3673
+ this.logger.info(
3674
+ `Creating GitHub Releases (${releaseWorkspaces.length}):
3675
+ ${releaseWorkspaces.map((w) => ` ${w.tagName}`).join("\n")}`
3676
+ );
3677
+ await Promise.all(
3678
+ releaseWorkspaces.map(
3679
+ (workspace) => this.githubManager.createRelease(workspace)
3680
+ )
3681
+ );
3682
+ }
3581
3683
  /**
3582
3684
  * Create the release PR and optionally auto-merge it.
3583
3685
  *
@@ -3907,6 +4009,9 @@ function programArgs() {
3907
4009
  ).option(
3908
4010
  "--changelog.ignore-non-updated-packages",
3909
4011
  "Alias of --changesetVersion.ignore-non-updated-packages"
4012
+ ).option(
4013
+ "--changesetVersion.use-trusted-publishing",
4014
+ "Use npm Trusted Publishing (OIDC) instead of NPM_TOKEN (fe-config: release.changesetVersion.useTrustedPublishing)"
3910
4015
  ).option(
3911
4016
  "--changesetVersion.mode <mode>",
3912
4017
  "ChangesetVersion mode: version (default), publish, or both (fe-config: release.changesetVersion.mode)",
@@ -3933,6 +4038,22 @@ function programArgs() {
3933
4038
  ).option(
3934
4039
  "--workspaces.skip",
3935
4040
  "Skip the Workspaces plugin"
4041
+ ).option(
4042
+ "--github.mode <mode>",
4043
+ "Github mode: createPR (default) or createRelease (fe-config: release.github.mode)",
4044
+ (value) => {
4045
+ if (value !== "createPR" && value !== "createRelease") {
4046
+ throw new Error(
4047
+ 'Invalid github.mode. Must be "createPR" or "createRelease"'
4048
+ );
4049
+ }
4050
+ return value;
4051
+ },
4052
+ "createPR"
4053
+ ).option(
4054
+ "--github.ignore-release-paths <paths>",
4055
+ "Path prefixes to skip when creating GitHub Releases, comma-separated (fe-config: release.github.ignoreReleasePaths)",
4056
+ splitWithComma
3936
4057
  ).option(
3937
4058
  "--github.skip [lifecycle]",
3938
4059
  "Skip the Github plugin (no value) or a lifecycle such as onSuccess"
package/dist/cli.js CHANGED
@@ -5,7 +5,7 @@ import { Command } from "commander";
5
5
 
6
6
  // package.json
7
7
  var description = "A tool for releasing front-end projects, supporting multiple release modes and configurations, simplifying the release process and improving efficiency.";
8
- var version = "5.0.6";
8
+ var version = "5.3.1";
9
9
 
10
10
  // src/cli.ts
11
11
  import semver from "semver";
@@ -59,6 +59,10 @@ var releaseJson = {
59
59
  branchName: "release/${repoName}-${releaseId}",
60
60
  releaseTagName: "release-tag-${count}-patch-${releaseId}",
61
61
  commitMessage: "chore(release): ${spaces}",
62
+ /** GitHub release title template (createRelease mode) */
63
+ releaseName: "${name}@${version}",
64
+ /** Path prefixes skipped when creating GitHub releases */
65
+ ignoreReleasePaths: [],
62
66
  label: {
63
67
  name: "CI-Release",
64
68
  color: "1A7F37",
@@ -1586,6 +1590,22 @@ var ChangesetVersion = class extends ScriptPlugin2 {
1586
1590
  get ignoreNonUpdatedPackages() {
1587
1591
  return !!this.config.ignoreNonUpdatedPackages;
1588
1592
  }
1593
+ get useTrustedPublishing() {
1594
+ return !!(this.config.useTrustedPublishing || this.context.parameters.changesetVersion?.useTrustedPublishing);
1595
+ }
1596
+ /**
1597
+ * True when publish should use npm Trusted Publishing (OIDC) instead of NPM_TOKEN.
1598
+ */
1599
+ isTrustedPublishingEnv() {
1600
+ if (this.useTrustedPublishing) {
1601
+ return true;
1602
+ }
1603
+ const envFlag = this.context.getEnv("FE_RELEASE_TRUSTED_PUBLISHING");
1604
+ if (envFlag === "1" || envFlag === "true") {
1605
+ return true;
1606
+ }
1607
+ return Boolean(this.context.getEnv("ACTIONS_ID_TOKEN_REQUEST_URL"));
1608
+ }
1589
1609
  shouldProcessWorkspace(workspace) {
1590
1610
  return shouldProcessWorkspace(workspace, this.ignoreNonUpdatedPackages);
1591
1611
  }
@@ -1607,15 +1627,26 @@ var ChangesetVersion = class extends ScriptPlugin2 {
1607
1627
  }
1608
1628
  }
1609
1629
  /**
1610
- * Ensure NPM_TOKEN is available and configured before changeset publish.
1630
+ * Configure npm auth before changeset publish.
1611
1631
  *
1612
- * Only required for `publish` / `both` modes. Version-only runs (release PR)
1613
- * do not need an npm auth token.
1632
+ * Uses Trusted Publishing (OIDC) when detected; otherwise requires `NPM_TOKEN`.
1633
+ * Version-only runs (release PR) do not need npm auth.
1614
1634
  */
1615
1635
  async validateNpmToken() {
1636
+ if (this.isTrustedPublishingEnv()) {
1637
+ this.logger.info(
1638
+ "Using npm Trusted Publishing (OIDC); skip NPM_TOKEN auth setup"
1639
+ );
1640
+ if (this.context.dryRun) {
1641
+ this.logDryRun("Would publish via npm Trusted Publishing (OIDC)");
1642
+ }
1643
+ return;
1644
+ }
1616
1645
  const npmToken = this.context.getEnv("NPM_TOKEN");
1617
1646
  if (!npmToken) {
1618
- throw new Error("NPM_TOKEN is not set");
1647
+ throw new Error(
1648
+ "NPM_TOKEN is not set. Enable Trusted Publishing (OIDC) or provide NPM_TOKEN."
1649
+ );
1619
1650
  }
1620
1651
  if (this.context.dryRun) {
1621
1652
  this.logDryRun(
@@ -2725,7 +2756,7 @@ var GithubManager = class {
2725
2756
  */
2726
2757
  getOctokitReleaseOptions(options) {
2727
2758
  const {
2728
- releaseName,
2759
+ releaseName = releaseJson.github.releaseName,
2729
2760
  draft = false,
2730
2761
  preRelease = false,
2731
2762
  autoGenerate = false,
@@ -2734,7 +2765,7 @@ var GithubManager = class {
2734
2765
  discussionCategoryName = void 0
2735
2766
  } = this.context.getParameters("github");
2736
2767
  const name = releaseName;
2737
- const body = autoGenerate ? "" : this.truncateBody(String(releaseNotes));
2768
+ const body = autoGenerate ? "" : this.truncateBody(String(releaseNotes ?? ""));
2738
2769
  return {
2739
2770
  name,
2740
2771
  make_latest: makeLatest.toString(),
@@ -3498,6 +3529,7 @@ var ReleaseFormatter = class {
3498
3529
  var Github = class extends GitBase {
3499
3530
  releaseFormatter;
3500
3531
  githubManager;
3532
+ pather = new Pather();
3501
3533
  constructor(context, props = {}) {
3502
3534
  super(context, "github", props);
3503
3535
  this.releaseFormatter = new ReleaseFormatter(
@@ -3509,6 +3541,9 @@ var Github = class extends GitBase {
3509
3541
  get mode() {
3510
3542
  return this.config.mode || releaseJson.github.mode;
3511
3543
  }
3544
+ get ignoreReleasePaths() {
3545
+ return this.config.ignoreReleasePaths ?? [...releaseJson.github.ignoreReleasePaths];
3546
+ }
3512
3547
  async onBefore() {
3513
3548
  await super.onBefore();
3514
3549
  await this.githubManager.validateToken();
@@ -3534,6 +3569,9 @@ var Github = class extends GitBase {
3534
3569
  if (workspace.dependencyRelease) {
3535
3570
  return workspace;
3536
3571
  }
3572
+ if (this.mode === "createRelease" && this.isIgnoredReleasePath(workspace)) {
3573
+ return workspace;
3574
+ }
3537
3575
  return githubChangelog.enrichWorkspaceChangelog(workspace);
3538
3576
  })
3539
3577
  )
@@ -3542,6 +3580,13 @@ var Github = class extends GitBase {
3542
3580
  }
3543
3581
  async onSuccess() {
3544
3582
  const workspaces = this.context.requireWorkspaces();
3583
+ if (this.mode === "createRelease") {
3584
+ await this.step({
3585
+ label: "Create GitHub Releases",
3586
+ task: () => this.createGithubReleases(workspaces)
3587
+ });
3588
+ return;
3589
+ }
3545
3590
  const releaseBranchResult = await this.step({
3546
3591
  label: "Create Release Branch",
3547
3592
  task: () => this.createReleaseBranch(workspaces)
@@ -3558,6 +3603,63 @@ var Github = class extends GitBase {
3558
3603
  }
3559
3604
  await this.handleReleasePullRequest(workspaces, releaseBranchResult);
3560
3605
  }
3606
+ /**
3607
+ * Whether this workspace path is under an ignoreReleasePaths entry.
3608
+ */
3609
+ isIgnoredReleasePath(workspace) {
3610
+ const ignorePaths = this.ignoreReleasePaths;
3611
+ if (ignorePaths.length === 0) {
3612
+ return false;
3613
+ }
3614
+ return ignorePaths.some(
3615
+ (ignorePath) => this.pather.isSubPath(workspace.path, ignorePath)
3616
+ );
3617
+ }
3618
+ /**
3619
+ * Workspaces that should receive a GitHub Release in createRelease mode.
3620
+ *
3621
+ * Requires `tagName`. Skips dependency-release packages and paths matched
3622
+ * by {@link GithubProps.ignoreReleasePaths}. Private packages are included
3623
+ * when they have a tag.
3624
+ */
3625
+ getReleaseWorkspaces(workspaces) {
3626
+ return workspaces.filter((workspace) => {
3627
+ if (!workspace.tagName) {
3628
+ return false;
3629
+ }
3630
+ if (workspace.dependencyRelease) {
3631
+ return false;
3632
+ }
3633
+ if (this.isIgnoredReleasePath(workspace)) {
3634
+ this.logger.info(
3635
+ `Skip GitHub Release for ignored path: ${workspace.path} (${workspace.tagName})`
3636
+ );
3637
+ return false;
3638
+ }
3639
+ return true;
3640
+ });
3641
+ }
3642
+ /**
3643
+ * Create a GitHub Release for each eligible workspace.
3644
+ *
3645
+ * Release notes use the enriched workspace changelog from `onExec`.
3646
+ */
3647
+ async createGithubReleases(workspaces) {
3648
+ const releaseWorkspaces = this.getReleaseWorkspaces(workspaces);
3649
+ if (releaseWorkspaces.length === 0) {
3650
+ this.logger.warn("No workspaces eligible for GitHub Release creation");
3651
+ return;
3652
+ }
3653
+ this.logger.info(
3654
+ `Creating GitHub Releases (${releaseWorkspaces.length}):
3655
+ ${releaseWorkspaces.map((w) => ` ${w.tagName}`).join("\n")}`
3656
+ );
3657
+ await Promise.all(
3658
+ releaseWorkspaces.map(
3659
+ (workspace) => this.githubManager.createRelease(workspace)
3660
+ )
3661
+ );
3662
+ }
3561
3663
  /**
3562
3664
  * Create the release PR and optionally auto-merge it.
3563
3665
  *
@@ -3887,6 +3989,9 @@ function programArgs() {
3887
3989
  ).option(
3888
3990
  "--changelog.ignore-non-updated-packages",
3889
3991
  "Alias of --changesetVersion.ignore-non-updated-packages"
3992
+ ).option(
3993
+ "--changesetVersion.use-trusted-publishing",
3994
+ "Use npm Trusted Publishing (OIDC) instead of NPM_TOKEN (fe-config: release.changesetVersion.useTrustedPublishing)"
3890
3995
  ).option(
3891
3996
  "--changesetVersion.mode <mode>",
3892
3997
  "ChangesetVersion mode: version (default), publish, or both (fe-config: release.changesetVersion.mode)",
@@ -3913,6 +4018,22 @@ function programArgs() {
3913
4018
  ).option(
3914
4019
  "--workspaces.skip",
3915
4020
  "Skip the Workspaces plugin"
4021
+ ).option(
4022
+ "--github.mode <mode>",
4023
+ "Github mode: createPR (default) or createRelease (fe-config: release.github.mode)",
4024
+ (value) => {
4025
+ if (value !== "createPR" && value !== "createRelease") {
4026
+ throw new Error(
4027
+ 'Invalid github.mode. Must be "createPR" or "createRelease"'
4028
+ );
4029
+ }
4030
+ return value;
4031
+ },
4032
+ "createPR"
4033
+ ).option(
4034
+ "--github.ignore-release-paths <paths>",
4035
+ "Path prefixes to skip when creating GitHub Releases, comma-separated (fe-config: release.github.ignoreReleasePaths)",
4036
+ splitWithComma
3916
4037
  ).option(
3917
4038
  "--github.skip [lifecycle]",
3918
4039
  "Skip the Github plugin (no value) or a lifecycle such as onSuccess"
package/dist/index.cjs CHANGED
@@ -79,6 +79,10 @@ var releaseJson = {
79
79
  branchName: "release/${repoName}-${releaseId}",
80
80
  releaseTagName: "release-tag-${count}-patch-${releaseId}",
81
81
  commitMessage: "chore(release): ${spaces}",
82
+ /** GitHub release title template (createRelease mode) */
83
+ releaseName: "${name}@${version}",
84
+ /** Path prefixes skipped when creating GitHub releases */
85
+ ignoreReleasePaths: [],
82
86
  label: {
83
87
  name: "CI-Release",
84
88
  color: "1A7F37",
@@ -1611,6 +1615,22 @@ var ChangesetVersion = class extends import_scripts_context4.ScriptPlugin {
1611
1615
  get ignoreNonUpdatedPackages() {
1612
1616
  return !!this.config.ignoreNonUpdatedPackages;
1613
1617
  }
1618
+ get useTrustedPublishing() {
1619
+ return !!(this.config.useTrustedPublishing || this.context.parameters.changesetVersion?.useTrustedPublishing);
1620
+ }
1621
+ /**
1622
+ * True when publish should use npm Trusted Publishing (OIDC) instead of NPM_TOKEN.
1623
+ */
1624
+ isTrustedPublishingEnv() {
1625
+ if (this.useTrustedPublishing) {
1626
+ return true;
1627
+ }
1628
+ const envFlag = this.context.getEnv("FE_RELEASE_TRUSTED_PUBLISHING");
1629
+ if (envFlag === "1" || envFlag === "true") {
1630
+ return true;
1631
+ }
1632
+ return Boolean(this.context.getEnv("ACTIONS_ID_TOKEN_REQUEST_URL"));
1633
+ }
1614
1634
  shouldProcessWorkspace(workspace) {
1615
1635
  return shouldProcessWorkspace(workspace, this.ignoreNonUpdatedPackages);
1616
1636
  }
@@ -1632,15 +1652,26 @@ var ChangesetVersion = class extends import_scripts_context4.ScriptPlugin {
1632
1652
  }
1633
1653
  }
1634
1654
  /**
1635
- * Ensure NPM_TOKEN is available and configured before changeset publish.
1655
+ * Configure npm auth before changeset publish.
1636
1656
  *
1637
- * Only required for `publish` / `both` modes. Version-only runs (release PR)
1638
- * do not need an npm auth token.
1657
+ * Uses Trusted Publishing (OIDC) when detected; otherwise requires `NPM_TOKEN`.
1658
+ * Version-only runs (release PR) do not need npm auth.
1639
1659
  */
1640
1660
  async validateNpmToken() {
1661
+ if (this.isTrustedPublishingEnv()) {
1662
+ this.logger.info(
1663
+ "Using npm Trusted Publishing (OIDC); skip NPM_TOKEN auth setup"
1664
+ );
1665
+ if (this.context.dryRun) {
1666
+ this.logDryRun("Would publish via npm Trusted Publishing (OIDC)");
1667
+ }
1668
+ return;
1669
+ }
1641
1670
  const npmToken = this.context.getEnv("NPM_TOKEN");
1642
1671
  if (!npmToken) {
1643
- throw new Error("NPM_TOKEN is not set");
1672
+ throw new Error(
1673
+ "NPM_TOKEN is not set. Enable Trusted Publishing (OIDC) or provide NPM_TOKEN."
1674
+ );
1644
1675
  }
1645
1676
  if (this.context.dryRun) {
1646
1677
  this.logDryRun(
@@ -2750,7 +2781,7 @@ var GithubManager = class {
2750
2781
  */
2751
2782
  getOctokitReleaseOptions(options) {
2752
2783
  const {
2753
- releaseName,
2784
+ releaseName = releaseJson.github.releaseName,
2754
2785
  draft = false,
2755
2786
  preRelease = false,
2756
2787
  autoGenerate = false,
@@ -2759,7 +2790,7 @@ var GithubManager = class {
2759
2790
  discussionCategoryName = void 0
2760
2791
  } = this.context.getParameters("github");
2761
2792
  const name = releaseName;
2762
- const body = autoGenerate ? "" : this.truncateBody(String(releaseNotes));
2793
+ const body = autoGenerate ? "" : this.truncateBody(String(releaseNotes ?? ""));
2763
2794
  return {
2764
2795
  name,
2765
2796
  make_latest: makeLatest.toString(),
@@ -3523,6 +3554,7 @@ var ReleaseFormatter = class {
3523
3554
  var Github = class extends GitBase {
3524
3555
  releaseFormatter;
3525
3556
  githubManager;
3557
+ pather = new Pather();
3526
3558
  constructor(context, props = {}) {
3527
3559
  super(context, "github", props);
3528
3560
  this.releaseFormatter = new ReleaseFormatter(
@@ -3534,6 +3566,9 @@ var Github = class extends GitBase {
3534
3566
  get mode() {
3535
3567
  return this.config.mode || releaseJson.github.mode;
3536
3568
  }
3569
+ get ignoreReleasePaths() {
3570
+ return this.config.ignoreReleasePaths ?? [...releaseJson.github.ignoreReleasePaths];
3571
+ }
3537
3572
  async onBefore() {
3538
3573
  await super.onBefore();
3539
3574
  await this.githubManager.validateToken();
@@ -3559,6 +3594,9 @@ var Github = class extends GitBase {
3559
3594
  if (workspace.dependencyRelease) {
3560
3595
  return workspace;
3561
3596
  }
3597
+ if (this.mode === "createRelease" && this.isIgnoredReleasePath(workspace)) {
3598
+ return workspace;
3599
+ }
3562
3600
  return githubChangelog.enrichWorkspaceChangelog(workspace);
3563
3601
  })
3564
3602
  )
@@ -3567,6 +3605,13 @@ var Github = class extends GitBase {
3567
3605
  }
3568
3606
  async onSuccess() {
3569
3607
  const workspaces = this.context.requireWorkspaces();
3608
+ if (this.mode === "createRelease") {
3609
+ await this.step({
3610
+ label: "Create GitHub Releases",
3611
+ task: () => this.createGithubReleases(workspaces)
3612
+ });
3613
+ return;
3614
+ }
3570
3615
  const releaseBranchResult = await this.step({
3571
3616
  label: "Create Release Branch",
3572
3617
  task: () => this.createReleaseBranch(workspaces)
@@ -3583,6 +3628,63 @@ var Github = class extends GitBase {
3583
3628
  }
3584
3629
  await this.handleReleasePullRequest(workspaces, releaseBranchResult);
3585
3630
  }
3631
+ /**
3632
+ * Whether this workspace path is under an ignoreReleasePaths entry.
3633
+ */
3634
+ isIgnoredReleasePath(workspace) {
3635
+ const ignorePaths = this.ignoreReleasePaths;
3636
+ if (ignorePaths.length === 0) {
3637
+ return false;
3638
+ }
3639
+ return ignorePaths.some(
3640
+ (ignorePath) => this.pather.isSubPath(workspace.path, ignorePath)
3641
+ );
3642
+ }
3643
+ /**
3644
+ * Workspaces that should receive a GitHub Release in createRelease mode.
3645
+ *
3646
+ * Requires `tagName`. Skips dependency-release packages and paths matched
3647
+ * by {@link GithubProps.ignoreReleasePaths}. Private packages are included
3648
+ * when they have a tag.
3649
+ */
3650
+ getReleaseWorkspaces(workspaces) {
3651
+ return workspaces.filter((workspace) => {
3652
+ if (!workspace.tagName) {
3653
+ return false;
3654
+ }
3655
+ if (workspace.dependencyRelease) {
3656
+ return false;
3657
+ }
3658
+ if (this.isIgnoredReleasePath(workspace)) {
3659
+ this.logger.info(
3660
+ `Skip GitHub Release for ignored path: ${workspace.path} (${workspace.tagName})`
3661
+ );
3662
+ return false;
3663
+ }
3664
+ return true;
3665
+ });
3666
+ }
3667
+ /**
3668
+ * Create a GitHub Release for each eligible workspace.
3669
+ *
3670
+ * Release notes use the enriched workspace changelog from `onExec`.
3671
+ */
3672
+ async createGithubReleases(workspaces) {
3673
+ const releaseWorkspaces = this.getReleaseWorkspaces(workspaces);
3674
+ if (releaseWorkspaces.length === 0) {
3675
+ this.logger.warn("No workspaces eligible for GitHub Release creation");
3676
+ return;
3677
+ }
3678
+ this.logger.info(
3679
+ `Creating GitHub Releases (${releaseWorkspaces.length}):
3680
+ ${releaseWorkspaces.map((w) => ` ${w.tagName}`).join("\n")}`
3681
+ );
3682
+ await Promise.all(
3683
+ releaseWorkspaces.map(
3684
+ (workspace) => this.githubManager.createRelease(workspace)
3685
+ )
3686
+ );
3687
+ }
3586
3688
  /**
3587
3689
  * Create the release PR and optionally auto-merge it.
3588
3690
  *
package/dist/index.d.ts CHANGED
@@ -253,7 +253,7 @@ interface ReleaseFormatterConfig {
253
253
 
254
254
  /**
255
255
  * @module Github
256
- * @description GitHub changelog enrichment and release PR plugin
256
+ * @description GitHub changelog enrichment, release PR, and GitHub Release plugin
257
257
  *
258
258
  * Third plugin in the default release pipeline (after {@link Workspaces} and
259
259
  * {@link ChangesetVersion}). Extends {@link GitBase} for git operations and
@@ -262,33 +262,48 @@ interface ReleaseFormatterConfig {
262
262
  * Pipeline phases:
263
263
  * - **onBefore**: validate GitHub token; seed {@link ReleaseFormatter} context
264
264
  * - **onExec**: enrich workspace changelogs with PR/commit links via {@link GithubChangelog}
265
- * - **onSuccess**: create release branch, commit, push, and open PR (unless skipped)
265
+ * - **onSuccess**:
266
+ * - `createPR` — create release branch, commit, push, and open PR (unless skipped)
267
+ * - `createRelease` — create a GitHub Release per workspace (tag + changelog body)
266
268
  *
267
- * Release branch flow:
269
+ * Release branch flow (`createPR`):
268
270
  * 1. `ReleaseFormatter.getReleaseBranch()` — derive branch and tag names from templates
269
271
  * 2. Create branch from `sourceBranch`, commit version/changelog changes, push
270
272
  * 3. Open PR with formatted title/body and optional labels
271
273
  * 4. Auto-merge when `autoMergeReleasePr` is enabled
272
274
  *
275
+ * GitHub Release flow (`createRelease`):
276
+ * 1. Filter workspaces with `tagName`, skipping `dependencyRelease` and
277
+ * paths matched by {@link GithubProps.ignoreReleasePaths}
278
+ * 2. Call {@link GithubManager.createRelease} with workspace changelog as notes
279
+ *
273
280
  * @example Skip PR creation (local dry-run)
274
281
  * ```bash
275
282
  * fe-release --github.skip-create-release-pr --dry-run
276
283
  * ```
277
284
  *
285
+ * @example Publish + GitHub Releases (ignore examples)
286
+ * ```bash
287
+ * fe-release --changesetVersion.mode publish \
288
+ * --github.mode createRelease \
289
+ * --github.ignore-release-paths examples
290
+ * ```
291
+ *
278
292
  * @example fe-config label and merge settings
279
293
  * ```json
280
294
  * {
281
295
  * "release": {
282
296
  * "github": {
283
297
  * "autoMergeReleasePr": false,
284
- * "label": { "name": "CI-Release" }
298
+ * "label": { "name": "CI-Release" },
299
+ * "ignoreReleasePaths": ["examples"]
285
300
  * }
286
301
  * }
287
302
  * }
288
303
  * ```
289
304
  */
290
305
 
291
- type GithubMode = 'createPR';
306
+ type GithubMode = 'createPR' | 'createRelease';
292
307
  type GithubLabel = {
293
308
  /**
294
309
  * Hexadecimal color code for label appearance
@@ -345,8 +360,8 @@ interface GithubProps extends ReleaseFormatterConfig, GitBaseProps {
345
360
  /**
346
361
  * Plugin work mode
347
362
  *
348
- * Currently only `createPR` is supported: enrich changelogs in `onExec`,
349
- * then create release branch and PR in `onSuccess`.
363
+ * - `createPR`: enrich changelogs in `onExec`, then create release branch and PR in `onSuccess`
364
+ * - `createRelease`: enrich changelogs in `onExec`, then create GitHub Releases in `onSuccess`
350
365
  *
351
366
  * @default `'createPR'`
352
367
  */
@@ -423,6 +438,22 @@ interface GithubProps extends ReleaseFormatterConfig, GitBaseProps {
423
438
  * @default false
424
439
  */
425
440
  skipCreateReleasePr?: boolean;
441
+ /**
442
+ * Workspace path prefixes to skip when creating GitHub Releases.
443
+ *
444
+ * Matched with segment-aware path containment (e.g. `examples` skips
445
+ * `examples/react-seed`). Applies only in `createRelease` mode.
446
+ *
447
+ * CLI: `--github.ignore-release-paths`
448
+ * fe-config: `release.github.ignoreReleasePaths`
449
+ *
450
+ * @default `[]`
451
+ * @example
452
+ * ```json
453
+ * { "ignoreReleasePaths": ["examples", "apps/docs"] }
454
+ * ```
455
+ */
456
+ ignoreReleasePaths?: string[];
426
457
  /**
427
458
  * Configuration for release pull request labels
428
459
  *
@@ -850,7 +881,8 @@ interface ChangeLogInterface {
850
881
  * Changesets CLI for monorepo version bumps.
851
882
  *
852
883
  * Pipeline phases:
853
- * - **onBefore**: validate `.changeset` directory; validate `NPM_TOKEN` when mode includes publish
884
+ * - **onBefore**: validate `.changeset` directory; configure npm auth when mode includes publish
885
+ * (`NPM_TOKEN` for token auth, or skip when Trusted Publishing / OIDC is detected)
854
886
  * - **onExec**: generate per-workspace git changelogs (skips `dependencyRelease`
855
887
  * when `ignoreNonUpdatedPackages` is enabled)
856
888
  * - **onSuccess**: run version and/or publish flow based on `mode`
@@ -963,6 +995,17 @@ interface ChangesetVersionProps extends GitChangelogOptions, ScriptPluginProps {
963
995
  * @default false
964
996
  */
965
997
  ignoreNonUpdatedPackages?: boolean;
998
+ /**
999
+ * Use npm Trusted Publishing (OIDC) instead of `NPM_TOKEN`.
1000
+ *
1001
+ * When enabled (or when GitHub Actions OIDC env is detected), publish skips
1002
+ * writing `_authToken` to npm config so the CLI can exchange OIDC credentials.
1003
+ *
1004
+ * CLI: `--changesetVersion.use-trusted-publishing`
1005
+ *
1006
+ * @default false
1007
+ */
1008
+ useTrustedPublishing?: boolean;
966
1009
  }
967
1010
  /**
968
1011
  * Manages changelog generation, changeset file creation, and Changesets CLI execution.
@@ -977,14 +1020,19 @@ declare class ChangesetVersion extends ScriptPlugin<ReleaseContext, ChangesetVer
977
1020
  get changesetConfigPath(): string;
978
1021
  protected get mode(): ChangesetVersionMode;
979
1022
  protected get ignoreNonUpdatedPackages(): boolean;
1023
+ protected get useTrustedPublishing(): boolean;
1024
+ /**
1025
+ * True when publish should use npm Trusted Publishing (OIDC) instead of NPM_TOKEN.
1026
+ */
1027
+ protected isTrustedPublishingEnv(): boolean;
980
1028
  protected shouldProcessWorkspace(workspace: WorkspaceInterface): boolean;
981
1029
  protected getProcessableWorkspaces(workspaces: WorkspaceInterface[]): WorkspaceInterface[];
982
1030
  onBefore(): Promise<void>;
983
1031
  /**
984
- * Ensure NPM_TOKEN is available and configured before changeset publish.
1032
+ * Configure npm auth before changeset publish.
985
1033
  *
986
- * Only required for `publish` / `both` modes. Version-only runs (release PR)
987
- * do not need an npm auth token.
1034
+ * Uses Trusted Publishing (OIDC) when detected; otherwise requires `NPM_TOKEN`.
1035
+ * Version-only runs (release PR) do not need npm auth.
988
1036
  */
989
1037
  protected validateNpmToken(): Promise<void>;
990
1038
  onExec(_context: ReleaseContext): Promise<void>;
@@ -1465,6 +1513,10 @@ declare const releaseJson: {
1465
1513
  readonly branchName: "release/${repoName}-${releaseId}";
1466
1514
  readonly releaseTagName: "release-tag-${count}-patch-${releaseId}";
1467
1515
  readonly commitMessage: "chore(release): ${spaces}";
1516
+ /** GitHub release title template (createRelease mode) */
1517
+ readonly releaseName: "${name}@${version}";
1518
+ /** Path prefixes skipped when creating GitHub releases */
1519
+ readonly ignoreReleasePaths: readonly string[];
1468
1520
  readonly label: {
1469
1521
  readonly name: "CI-Release";
1470
1522
  readonly color: "1A7F37";
package/dist/index.js CHANGED
@@ -27,6 +27,10 @@ var releaseJson = {
27
27
  branchName: "release/${repoName}-${releaseId}",
28
28
  releaseTagName: "release-tag-${count}-patch-${releaseId}",
29
29
  commitMessage: "chore(release): ${spaces}",
30
+ /** GitHub release title template (createRelease mode) */
31
+ releaseName: "${name}@${version}",
32
+ /** Path prefixes skipped when creating GitHub releases */
33
+ ignoreReleasePaths: [],
30
34
  label: {
31
35
  name: "CI-Release",
32
36
  color: "1A7F37",
@@ -1562,6 +1566,22 @@ var ChangesetVersion = class extends ScriptPlugin2 {
1562
1566
  get ignoreNonUpdatedPackages() {
1563
1567
  return !!this.config.ignoreNonUpdatedPackages;
1564
1568
  }
1569
+ get useTrustedPublishing() {
1570
+ return !!(this.config.useTrustedPublishing || this.context.parameters.changesetVersion?.useTrustedPublishing);
1571
+ }
1572
+ /**
1573
+ * True when publish should use npm Trusted Publishing (OIDC) instead of NPM_TOKEN.
1574
+ */
1575
+ isTrustedPublishingEnv() {
1576
+ if (this.useTrustedPublishing) {
1577
+ return true;
1578
+ }
1579
+ const envFlag = this.context.getEnv("FE_RELEASE_TRUSTED_PUBLISHING");
1580
+ if (envFlag === "1" || envFlag === "true") {
1581
+ return true;
1582
+ }
1583
+ return Boolean(this.context.getEnv("ACTIONS_ID_TOKEN_REQUEST_URL"));
1584
+ }
1565
1585
  shouldProcessWorkspace(workspace) {
1566
1586
  return shouldProcessWorkspace(workspace, this.ignoreNonUpdatedPackages);
1567
1587
  }
@@ -1583,15 +1603,26 @@ var ChangesetVersion = class extends ScriptPlugin2 {
1583
1603
  }
1584
1604
  }
1585
1605
  /**
1586
- * Ensure NPM_TOKEN is available and configured before changeset publish.
1606
+ * Configure npm auth before changeset publish.
1587
1607
  *
1588
- * Only required for `publish` / `both` modes. Version-only runs (release PR)
1589
- * do not need an npm auth token.
1608
+ * Uses Trusted Publishing (OIDC) when detected; otherwise requires `NPM_TOKEN`.
1609
+ * Version-only runs (release PR) do not need npm auth.
1590
1610
  */
1591
1611
  async validateNpmToken() {
1612
+ if (this.isTrustedPublishingEnv()) {
1613
+ this.logger.info(
1614
+ "Using npm Trusted Publishing (OIDC); skip NPM_TOKEN auth setup"
1615
+ );
1616
+ if (this.context.dryRun) {
1617
+ this.logDryRun("Would publish via npm Trusted Publishing (OIDC)");
1618
+ }
1619
+ return;
1620
+ }
1592
1621
  const npmToken = this.context.getEnv("NPM_TOKEN");
1593
1622
  if (!npmToken) {
1594
- throw new Error("NPM_TOKEN is not set");
1623
+ throw new Error(
1624
+ "NPM_TOKEN is not set. Enable Trusted Publishing (OIDC) or provide NPM_TOKEN."
1625
+ );
1595
1626
  }
1596
1627
  if (this.context.dryRun) {
1597
1628
  this.logDryRun(
@@ -2701,7 +2732,7 @@ var GithubManager = class {
2701
2732
  */
2702
2733
  getOctokitReleaseOptions(options) {
2703
2734
  const {
2704
- releaseName,
2735
+ releaseName = releaseJson.github.releaseName,
2705
2736
  draft = false,
2706
2737
  preRelease = false,
2707
2738
  autoGenerate = false,
@@ -2710,7 +2741,7 @@ var GithubManager = class {
2710
2741
  discussionCategoryName = void 0
2711
2742
  } = this.context.getParameters("github");
2712
2743
  const name = releaseName;
2713
- const body = autoGenerate ? "" : this.truncateBody(String(releaseNotes));
2744
+ const body = autoGenerate ? "" : this.truncateBody(String(releaseNotes ?? ""));
2714
2745
  return {
2715
2746
  name,
2716
2747
  make_latest: makeLatest.toString(),
@@ -3474,6 +3505,7 @@ var ReleaseFormatter = class {
3474
3505
  var Github = class extends GitBase {
3475
3506
  releaseFormatter;
3476
3507
  githubManager;
3508
+ pather = new Pather();
3477
3509
  constructor(context, props = {}) {
3478
3510
  super(context, "github", props);
3479
3511
  this.releaseFormatter = new ReleaseFormatter(
@@ -3485,6 +3517,9 @@ var Github = class extends GitBase {
3485
3517
  get mode() {
3486
3518
  return this.config.mode || releaseJson.github.mode;
3487
3519
  }
3520
+ get ignoreReleasePaths() {
3521
+ return this.config.ignoreReleasePaths ?? [...releaseJson.github.ignoreReleasePaths];
3522
+ }
3488
3523
  async onBefore() {
3489
3524
  await super.onBefore();
3490
3525
  await this.githubManager.validateToken();
@@ -3510,6 +3545,9 @@ var Github = class extends GitBase {
3510
3545
  if (workspace.dependencyRelease) {
3511
3546
  return workspace;
3512
3547
  }
3548
+ if (this.mode === "createRelease" && this.isIgnoredReleasePath(workspace)) {
3549
+ return workspace;
3550
+ }
3513
3551
  return githubChangelog.enrichWorkspaceChangelog(workspace);
3514
3552
  })
3515
3553
  )
@@ -3518,6 +3556,13 @@ var Github = class extends GitBase {
3518
3556
  }
3519
3557
  async onSuccess() {
3520
3558
  const workspaces = this.context.requireWorkspaces();
3559
+ if (this.mode === "createRelease") {
3560
+ await this.step({
3561
+ label: "Create GitHub Releases",
3562
+ task: () => this.createGithubReleases(workspaces)
3563
+ });
3564
+ return;
3565
+ }
3521
3566
  const releaseBranchResult = await this.step({
3522
3567
  label: "Create Release Branch",
3523
3568
  task: () => this.createReleaseBranch(workspaces)
@@ -3534,6 +3579,63 @@ var Github = class extends GitBase {
3534
3579
  }
3535
3580
  await this.handleReleasePullRequest(workspaces, releaseBranchResult);
3536
3581
  }
3582
+ /**
3583
+ * Whether this workspace path is under an ignoreReleasePaths entry.
3584
+ */
3585
+ isIgnoredReleasePath(workspace) {
3586
+ const ignorePaths = this.ignoreReleasePaths;
3587
+ if (ignorePaths.length === 0) {
3588
+ return false;
3589
+ }
3590
+ return ignorePaths.some(
3591
+ (ignorePath) => this.pather.isSubPath(workspace.path, ignorePath)
3592
+ );
3593
+ }
3594
+ /**
3595
+ * Workspaces that should receive a GitHub Release in createRelease mode.
3596
+ *
3597
+ * Requires `tagName`. Skips dependency-release packages and paths matched
3598
+ * by {@link GithubProps.ignoreReleasePaths}. Private packages are included
3599
+ * when they have a tag.
3600
+ */
3601
+ getReleaseWorkspaces(workspaces) {
3602
+ return workspaces.filter((workspace) => {
3603
+ if (!workspace.tagName) {
3604
+ return false;
3605
+ }
3606
+ if (workspace.dependencyRelease) {
3607
+ return false;
3608
+ }
3609
+ if (this.isIgnoredReleasePath(workspace)) {
3610
+ this.logger.info(
3611
+ `Skip GitHub Release for ignored path: ${workspace.path} (${workspace.tagName})`
3612
+ );
3613
+ return false;
3614
+ }
3615
+ return true;
3616
+ });
3617
+ }
3618
+ /**
3619
+ * Create a GitHub Release for each eligible workspace.
3620
+ *
3621
+ * Release notes use the enriched workspace changelog from `onExec`.
3622
+ */
3623
+ async createGithubReleases(workspaces) {
3624
+ const releaseWorkspaces = this.getReleaseWorkspaces(workspaces);
3625
+ if (releaseWorkspaces.length === 0) {
3626
+ this.logger.warn("No workspaces eligible for GitHub Release creation");
3627
+ return;
3628
+ }
3629
+ this.logger.info(
3630
+ `Creating GitHub Releases (${releaseWorkspaces.length}):
3631
+ ${releaseWorkspaces.map((w) => ` ${w.tagName}`).join("\n")}`
3632
+ );
3633
+ await Promise.all(
3634
+ releaseWorkspaces.map(
3635
+ (workspace) => this.githubManager.createRelease(workspace)
3636
+ )
3637
+ );
3638
+ }
3537
3639
  /**
3538
3640
  * Create the release PR and optionally auto-merge it.
3539
3641
  *
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@qlover/fe-release",
3
3
  "description": "A tool for releasing front-end projects, supporting multiple release modes and configurations, simplifying the release process and improving efficiency.",
4
- "version": "5.0.6",
4
+ "version": "5.3.1",
5
5
  "type": "module",
6
6
  "private": false,
7
7
  "homepage": "https://github.com/qlover/fe-base/tree/master/packages/fe-release",
@@ -59,8 +59,8 @@
59
59
  "lodash-es": "^4.17.22",
60
60
  "p-limit": "^6.2.0",
61
61
  "semver": "^7.5.4",
62
- "@qlover/env-loader": "1.0.2",
63
- "@qlover/fe-corekit": "3.4.4",
62
+ "@qlover/env-loader": "1.1.0",
63
+ "@qlover/fe-corekit": "3.4.6",
64
64
  "@qlover/logger": "1.2.1",
65
65
  "@qlover/scripts-context": "2.3.3"
66
66
  },