@qlover/fe-release 5.0.5 → 5.1.0
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 +186 -14
- package/dist/cli.js +186 -14
- package/dist/index.cjs +169 -13
- package/dist/index.d.ts +67 -11
- package/dist/index.js +169 -13
- package/package.json +2 -2
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
|
|
31
|
+
var version = "5.1.0";
|
|
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",
|
|
@@ -592,6 +596,9 @@ function workspaceVersionSummary(workspace) {
|
|
|
592
596
|
function shouldProcessWorkspace(workspace, ignoreNonUpdatedPackages) {
|
|
593
597
|
return !(ignoreNonUpdatedPackages && workspace.dependencyRelease);
|
|
594
598
|
}
|
|
599
|
+
function isPrivateWorkspace(workspace) {
|
|
600
|
+
return workspace.packageJson?.private === true;
|
|
601
|
+
}
|
|
595
602
|
function readJson(path) {
|
|
596
603
|
const packageJsonContent = (0, import_fs.readFileSync)(path, "utf-8");
|
|
597
604
|
return JSON.parse(packageJsonContent);
|
|
@@ -1733,9 +1740,18 @@ ${workspaces?.map((workspace) => workspace.toString()).join("\n")}`
|
|
|
1733
1740
|
this.logDryRun("Would run: changeset publish, publish packages and tags");
|
|
1734
1741
|
const preview = this.mergeWorkspaces(workspaces);
|
|
1735
1742
|
this.context.setWorkspaces(preview);
|
|
1736
|
-
const previewTags =
|
|
1737
|
-
|
|
1743
|
+
const previewTags = this.collectPushableReleaseTags(preview);
|
|
1744
|
+
const privateTags = preview.filter(
|
|
1745
|
+
(workspace) => this.shouldProcessWorkspace(workspace) && !workspace.dependencyRelease && isPrivateWorkspace(workspace) && !!workspace.tagName
|
|
1738
1746
|
).map((workspace) => workspace.tagName);
|
|
1747
|
+
if (privateTags.length > 0) {
|
|
1748
|
+
this.logDryRun(
|
|
1749
|
+
`Would create local tag(s) for private package(s):
|
|
1750
|
+
${[
|
|
1751
|
+
...new Set(privateTags)
|
|
1752
|
+
].map((tag) => ` ${tag}`).join("\n")}`
|
|
1753
|
+
);
|
|
1754
|
+
}
|
|
1739
1755
|
this.logDryRun(
|
|
1740
1756
|
previewTags.length === 0 ? "Would push release tag(s) from workspace.tagName: none" : `Would push release tag(s) from workspace.tagName:
|
|
1741
1757
|
${previewTags.map((tag) => ` ${tag}`).join("\n")}`
|
|
@@ -1749,37 +1765,106 @@ ${previewTags.map((tag) => ` ${tag}`).join("\n")}`
|
|
|
1749
1765
|
if (workspaces.length > 0) {
|
|
1750
1766
|
this.syncWorkspaces(workspaces);
|
|
1751
1767
|
}
|
|
1768
|
+
const syncedWorkspaces = this.context.workspaces ?? workspaces;
|
|
1769
|
+
await this.step({
|
|
1770
|
+
label: "Create Private Package Tags",
|
|
1771
|
+
task: () => this.ensurePrivateReleaseTags(syncedWorkspaces)
|
|
1772
|
+
});
|
|
1752
1773
|
await this.step({
|
|
1753
1774
|
label: "Push Release Tags",
|
|
1754
|
-
task: () => this.pushWorkspaceReleaseTags(
|
|
1775
|
+
task: () => this.pushWorkspaceReleaseTags(syncedWorkspaces)
|
|
1755
1776
|
});
|
|
1756
1777
|
}
|
|
1757
1778
|
/**
|
|
1758
|
-
*
|
|
1779
|
+
* Collect tag names that should be pushed after `changeset publish`.
|
|
1759
1780
|
*
|
|
1760
|
-
*
|
|
1761
|
-
*
|
|
1781
|
+
* Includes public packages (tagged by changesets) and private packages
|
|
1782
|
+
* (tagged by {@link ensurePrivateReleaseTags}). Skips dependency-release
|
|
1783
|
+
* workspaces.
|
|
1762
1784
|
*/
|
|
1763
|
-
|
|
1785
|
+
collectPushableReleaseTags(workspaces) {
|
|
1764
1786
|
const tags = workspaces.filter(
|
|
1765
1787
|
(workspace) => this.shouldProcessWorkspace(workspace) && !workspace.dependencyRelease && !!workspace.tagName
|
|
1766
1788
|
).map((workspace) => workspace.tagName);
|
|
1767
|
-
|
|
1789
|
+
return [...new Set(tags)];
|
|
1790
|
+
}
|
|
1791
|
+
/**
|
|
1792
|
+
* List local git tag names (short refs under `refs/tags/`).
|
|
1793
|
+
*/
|
|
1794
|
+
async listLocalTags() {
|
|
1795
|
+
const output = await this.shell.exec(
|
|
1796
|
+
'git for-each-ref --format="%(refname:short)" refs/tags/',
|
|
1797
|
+
{ dryRun: false, silent: true }
|
|
1798
|
+
);
|
|
1799
|
+
const tags = output.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
1800
|
+
return new Set(tags);
|
|
1801
|
+
}
|
|
1802
|
+
/**
|
|
1803
|
+
* Create local git tags for private packages after `changeset publish`.
|
|
1804
|
+
*
|
|
1805
|
+
* Changesets skips npm publish and tagging for `private: true` packages.
|
|
1806
|
+
* Release still needs those tags as changelog baselines, so create them here.
|
|
1807
|
+
*/
|
|
1808
|
+
async ensurePrivateReleaseTags(workspaces) {
|
|
1809
|
+
const privateWorkspaces = workspaces.filter(
|
|
1810
|
+
(workspace) => this.shouldProcessWorkspace(workspace) && !workspace.dependencyRelease && isPrivateWorkspace(workspace) && !!workspace.tagName
|
|
1811
|
+
);
|
|
1812
|
+
if (privateWorkspaces.length === 0) {
|
|
1813
|
+
return;
|
|
1814
|
+
}
|
|
1815
|
+
const localTags = await this.listLocalTags();
|
|
1816
|
+
for (const workspace of privateWorkspaces) {
|
|
1817
|
+
const tagName = workspace.tagName;
|
|
1818
|
+
if (localTags.has(tagName)) {
|
|
1819
|
+
this.logger.debug(`Private package tag already exists: ${tagName}`);
|
|
1820
|
+
continue;
|
|
1821
|
+
}
|
|
1822
|
+
this.logger.info(
|
|
1823
|
+
`Create local release tag for private package: ${tagName}`
|
|
1824
|
+
);
|
|
1825
|
+
await this.shell.exec(["git", "tag", tagName]);
|
|
1826
|
+
localTags.add(tagName);
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
/**
|
|
1830
|
+
* Push release tags from synced workspaces' `tagName` fields.
|
|
1831
|
+
*
|
|
1832
|
+
* After {@link syncWorkspaces} and {@link ensurePrivateReleaseTags}, push
|
|
1833
|
+
* tags that exist as local refs. Missing public-package tags are skipped
|
|
1834
|
+
* (changeset may have skipped publish); they are not invented here.
|
|
1835
|
+
*/
|
|
1836
|
+
async pushWorkspaceReleaseTags(workspaces) {
|
|
1837
|
+
const uniqueTags = this.collectPushableReleaseTags(workspaces);
|
|
1768
1838
|
if (uniqueTags.length === 0) {
|
|
1769
1839
|
this.logger.warn(
|
|
1770
1840
|
"No workspace.tagName found after publish sync; skip tag push"
|
|
1771
1841
|
);
|
|
1772
1842
|
return;
|
|
1773
1843
|
}
|
|
1844
|
+
const localTags = await this.listLocalTags();
|
|
1845
|
+
const existingTags = uniqueTags.filter((tag) => localTags.has(tag));
|
|
1846
|
+
const missingTags = uniqueTags.filter((tag) => !localTags.has(tag));
|
|
1847
|
+
if (missingTags.length > 0) {
|
|
1848
|
+
this.logger.warn(
|
|
1849
|
+
`Skip push for missing local tag(s):
|
|
1850
|
+
${missingTags.map((tag) => ` ${tag}`).join("\n")}`
|
|
1851
|
+
);
|
|
1852
|
+
}
|
|
1853
|
+
if (existingTags.length === 0) {
|
|
1854
|
+
this.logger.warn(
|
|
1855
|
+
"No local release tags found to push after publish; skip tag push"
|
|
1856
|
+
);
|
|
1857
|
+
return;
|
|
1858
|
+
}
|
|
1774
1859
|
this.logger.info(
|
|
1775
|
-
`Pushing ${
|
|
1776
|
-
${
|
|
1860
|
+
`Pushing ${existingTags.length} release tag(s) to origin:
|
|
1861
|
+
${existingTags.map((tag) => ` ${tag}`).join("\n")}`
|
|
1777
1862
|
);
|
|
1778
1863
|
await this.shell.exec([
|
|
1779
1864
|
"git",
|
|
1780
1865
|
"push",
|
|
1781
1866
|
"origin",
|
|
1782
|
-
...
|
|
1867
|
+
...existingTags.map((tag) => `"refs/tags/${tag}"`)
|
|
1783
1868
|
]);
|
|
1784
1869
|
}
|
|
1785
1870
|
syncWorkspaces(workspaces) {
|
|
@@ -2664,7 +2749,7 @@ var GithubManager = class {
|
|
|
2664
2749
|
*/
|
|
2665
2750
|
getOctokitReleaseOptions(options) {
|
|
2666
2751
|
const {
|
|
2667
|
-
releaseName,
|
|
2752
|
+
releaseName = releaseJson.github.releaseName,
|
|
2668
2753
|
draft = false,
|
|
2669
2754
|
preRelease = false,
|
|
2670
2755
|
autoGenerate = false,
|
|
@@ -2673,7 +2758,7 @@ var GithubManager = class {
|
|
|
2673
2758
|
discussionCategoryName = void 0
|
|
2674
2759
|
} = this.context.getParameters("github");
|
|
2675
2760
|
const name = releaseName;
|
|
2676
|
-
const body = autoGenerate ? "" : this.truncateBody(String(releaseNotes));
|
|
2761
|
+
const body = autoGenerate ? "" : this.truncateBody(String(releaseNotes ?? ""));
|
|
2677
2762
|
return {
|
|
2678
2763
|
name,
|
|
2679
2764
|
make_latest: makeLatest.toString(),
|
|
@@ -3437,6 +3522,7 @@ var ReleaseFormatter = class {
|
|
|
3437
3522
|
var Github = class extends GitBase {
|
|
3438
3523
|
releaseFormatter;
|
|
3439
3524
|
githubManager;
|
|
3525
|
+
pather = new Pather();
|
|
3440
3526
|
constructor(context, props = {}) {
|
|
3441
3527
|
super(context, "github", props);
|
|
3442
3528
|
this.releaseFormatter = new ReleaseFormatter(
|
|
@@ -3448,6 +3534,9 @@ var Github = class extends GitBase {
|
|
|
3448
3534
|
get mode() {
|
|
3449
3535
|
return this.config.mode || releaseJson.github.mode;
|
|
3450
3536
|
}
|
|
3537
|
+
get ignoreReleasePaths() {
|
|
3538
|
+
return this.config.ignoreReleasePaths ?? [...releaseJson.github.ignoreReleasePaths];
|
|
3539
|
+
}
|
|
3451
3540
|
async onBefore() {
|
|
3452
3541
|
await super.onBefore();
|
|
3453
3542
|
await this.githubManager.validateToken();
|
|
@@ -3473,6 +3562,9 @@ var Github = class extends GitBase {
|
|
|
3473
3562
|
if (workspace.dependencyRelease) {
|
|
3474
3563
|
return workspace;
|
|
3475
3564
|
}
|
|
3565
|
+
if (this.mode === "createRelease" && this.isIgnoredReleasePath(workspace)) {
|
|
3566
|
+
return workspace;
|
|
3567
|
+
}
|
|
3476
3568
|
return githubChangelog.enrichWorkspaceChangelog(workspace);
|
|
3477
3569
|
})
|
|
3478
3570
|
)
|
|
@@ -3481,6 +3573,13 @@ var Github = class extends GitBase {
|
|
|
3481
3573
|
}
|
|
3482
3574
|
async onSuccess() {
|
|
3483
3575
|
const workspaces = this.context.requireWorkspaces();
|
|
3576
|
+
if (this.mode === "createRelease") {
|
|
3577
|
+
await this.step({
|
|
3578
|
+
label: "Create GitHub Releases",
|
|
3579
|
+
task: () => this.createGithubReleases(workspaces)
|
|
3580
|
+
});
|
|
3581
|
+
return;
|
|
3582
|
+
}
|
|
3484
3583
|
const releaseBranchResult = await this.step({
|
|
3485
3584
|
label: "Create Release Branch",
|
|
3486
3585
|
task: () => this.createReleaseBranch(workspaces)
|
|
@@ -3497,6 +3596,63 @@ var Github = class extends GitBase {
|
|
|
3497
3596
|
}
|
|
3498
3597
|
await this.handleReleasePullRequest(workspaces, releaseBranchResult);
|
|
3499
3598
|
}
|
|
3599
|
+
/**
|
|
3600
|
+
* Whether this workspace path is under an ignoreReleasePaths entry.
|
|
3601
|
+
*/
|
|
3602
|
+
isIgnoredReleasePath(workspace) {
|
|
3603
|
+
const ignorePaths = this.ignoreReleasePaths;
|
|
3604
|
+
if (ignorePaths.length === 0) {
|
|
3605
|
+
return false;
|
|
3606
|
+
}
|
|
3607
|
+
return ignorePaths.some(
|
|
3608
|
+
(ignorePath) => this.pather.isSubPath(workspace.path, ignorePath)
|
|
3609
|
+
);
|
|
3610
|
+
}
|
|
3611
|
+
/**
|
|
3612
|
+
* Workspaces that should receive a GitHub Release in createRelease mode.
|
|
3613
|
+
*
|
|
3614
|
+
* Requires `tagName`. Skips dependency-release packages and paths matched
|
|
3615
|
+
* by {@link GithubProps.ignoreReleasePaths}. Private packages are included
|
|
3616
|
+
* when they have a tag.
|
|
3617
|
+
*/
|
|
3618
|
+
getReleaseWorkspaces(workspaces) {
|
|
3619
|
+
return workspaces.filter((workspace) => {
|
|
3620
|
+
if (!workspace.tagName) {
|
|
3621
|
+
return false;
|
|
3622
|
+
}
|
|
3623
|
+
if (workspace.dependencyRelease) {
|
|
3624
|
+
return false;
|
|
3625
|
+
}
|
|
3626
|
+
if (this.isIgnoredReleasePath(workspace)) {
|
|
3627
|
+
this.logger.info(
|
|
3628
|
+
`Skip GitHub Release for ignored path: ${workspace.path} (${workspace.tagName})`
|
|
3629
|
+
);
|
|
3630
|
+
return false;
|
|
3631
|
+
}
|
|
3632
|
+
return true;
|
|
3633
|
+
});
|
|
3634
|
+
}
|
|
3635
|
+
/**
|
|
3636
|
+
* Create a GitHub Release for each eligible workspace.
|
|
3637
|
+
*
|
|
3638
|
+
* Release notes use the enriched workspace changelog from `onExec`.
|
|
3639
|
+
*/
|
|
3640
|
+
async createGithubReleases(workspaces) {
|
|
3641
|
+
const releaseWorkspaces = this.getReleaseWorkspaces(workspaces);
|
|
3642
|
+
if (releaseWorkspaces.length === 0) {
|
|
3643
|
+
this.logger.warn("No workspaces eligible for GitHub Release creation");
|
|
3644
|
+
return;
|
|
3645
|
+
}
|
|
3646
|
+
this.logger.info(
|
|
3647
|
+
`Creating GitHub Releases (${releaseWorkspaces.length}):
|
|
3648
|
+
${releaseWorkspaces.map((w) => ` ${w.tagName}`).join("\n")}`
|
|
3649
|
+
);
|
|
3650
|
+
await Promise.all(
|
|
3651
|
+
releaseWorkspaces.map(
|
|
3652
|
+
(workspace) => this.githubManager.createRelease(workspace)
|
|
3653
|
+
)
|
|
3654
|
+
);
|
|
3655
|
+
}
|
|
3500
3656
|
/**
|
|
3501
3657
|
* Create the release PR and optionally auto-merge it.
|
|
3502
3658
|
*
|
|
@@ -3852,6 +4008,22 @@ function programArgs() {
|
|
|
3852
4008
|
).option(
|
|
3853
4009
|
"--workspaces.skip",
|
|
3854
4010
|
"Skip the Workspaces plugin"
|
|
4011
|
+
).option(
|
|
4012
|
+
"--github.mode <mode>",
|
|
4013
|
+
"Github mode: createPR (default) or createRelease (fe-config: release.github.mode)",
|
|
4014
|
+
(value) => {
|
|
4015
|
+
if (value !== "createPR" && value !== "createRelease") {
|
|
4016
|
+
throw new Error(
|
|
4017
|
+
'Invalid github.mode. Must be "createPR" or "createRelease"'
|
|
4018
|
+
);
|
|
4019
|
+
}
|
|
4020
|
+
return value;
|
|
4021
|
+
},
|
|
4022
|
+
"createPR"
|
|
4023
|
+
).option(
|
|
4024
|
+
"--github.ignore-release-paths <paths>",
|
|
4025
|
+
"Path prefixes to skip when creating GitHub Releases, comma-separated (fe-config: release.github.ignoreReleasePaths)",
|
|
4026
|
+
splitWithComma
|
|
3855
4027
|
).option(
|
|
3856
4028
|
"--github.skip [lifecycle]",
|
|
3857
4029
|
"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
|
|
8
|
+
var version = "5.1.0";
|
|
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",
|
|
@@ -572,6 +576,9 @@ function workspaceVersionSummary(workspace) {
|
|
|
572
576
|
function shouldProcessWorkspace(workspace, ignoreNonUpdatedPackages) {
|
|
573
577
|
return !(ignoreNonUpdatedPackages && workspace.dependencyRelease);
|
|
574
578
|
}
|
|
579
|
+
function isPrivateWorkspace(workspace) {
|
|
580
|
+
return workspace.packageJson?.private === true;
|
|
581
|
+
}
|
|
575
582
|
function readJson(path) {
|
|
576
583
|
const packageJsonContent = readFileSync(path, "utf-8");
|
|
577
584
|
return JSON.parse(packageJsonContent);
|
|
@@ -1713,9 +1720,18 @@ ${workspaces?.map((workspace) => workspace.toString()).join("\n")}`
|
|
|
1713
1720
|
this.logDryRun("Would run: changeset publish, publish packages and tags");
|
|
1714
1721
|
const preview = this.mergeWorkspaces(workspaces);
|
|
1715
1722
|
this.context.setWorkspaces(preview);
|
|
1716
|
-
const previewTags =
|
|
1717
|
-
|
|
1723
|
+
const previewTags = this.collectPushableReleaseTags(preview);
|
|
1724
|
+
const privateTags = preview.filter(
|
|
1725
|
+
(workspace) => this.shouldProcessWorkspace(workspace) && !workspace.dependencyRelease && isPrivateWorkspace(workspace) && !!workspace.tagName
|
|
1718
1726
|
).map((workspace) => workspace.tagName);
|
|
1727
|
+
if (privateTags.length > 0) {
|
|
1728
|
+
this.logDryRun(
|
|
1729
|
+
`Would create local tag(s) for private package(s):
|
|
1730
|
+
${[
|
|
1731
|
+
...new Set(privateTags)
|
|
1732
|
+
].map((tag) => ` ${tag}`).join("\n")}`
|
|
1733
|
+
);
|
|
1734
|
+
}
|
|
1719
1735
|
this.logDryRun(
|
|
1720
1736
|
previewTags.length === 0 ? "Would push release tag(s) from workspace.tagName: none" : `Would push release tag(s) from workspace.tagName:
|
|
1721
1737
|
${previewTags.map((tag) => ` ${tag}`).join("\n")}`
|
|
@@ -1729,37 +1745,106 @@ ${previewTags.map((tag) => ` ${tag}`).join("\n")}`
|
|
|
1729
1745
|
if (workspaces.length > 0) {
|
|
1730
1746
|
this.syncWorkspaces(workspaces);
|
|
1731
1747
|
}
|
|
1748
|
+
const syncedWorkspaces = this.context.workspaces ?? workspaces;
|
|
1749
|
+
await this.step({
|
|
1750
|
+
label: "Create Private Package Tags",
|
|
1751
|
+
task: () => this.ensurePrivateReleaseTags(syncedWorkspaces)
|
|
1752
|
+
});
|
|
1732
1753
|
await this.step({
|
|
1733
1754
|
label: "Push Release Tags",
|
|
1734
|
-
task: () => this.pushWorkspaceReleaseTags(
|
|
1755
|
+
task: () => this.pushWorkspaceReleaseTags(syncedWorkspaces)
|
|
1735
1756
|
});
|
|
1736
1757
|
}
|
|
1737
1758
|
/**
|
|
1738
|
-
*
|
|
1759
|
+
* Collect tag names that should be pushed after `changeset publish`.
|
|
1739
1760
|
*
|
|
1740
|
-
*
|
|
1741
|
-
*
|
|
1761
|
+
* Includes public packages (tagged by changesets) and private packages
|
|
1762
|
+
* (tagged by {@link ensurePrivateReleaseTags}). Skips dependency-release
|
|
1763
|
+
* workspaces.
|
|
1742
1764
|
*/
|
|
1743
|
-
|
|
1765
|
+
collectPushableReleaseTags(workspaces) {
|
|
1744
1766
|
const tags = workspaces.filter(
|
|
1745
1767
|
(workspace) => this.shouldProcessWorkspace(workspace) && !workspace.dependencyRelease && !!workspace.tagName
|
|
1746
1768
|
).map((workspace) => workspace.tagName);
|
|
1747
|
-
|
|
1769
|
+
return [...new Set(tags)];
|
|
1770
|
+
}
|
|
1771
|
+
/**
|
|
1772
|
+
* List local git tag names (short refs under `refs/tags/`).
|
|
1773
|
+
*/
|
|
1774
|
+
async listLocalTags() {
|
|
1775
|
+
const output = await this.shell.exec(
|
|
1776
|
+
'git for-each-ref --format="%(refname:short)" refs/tags/',
|
|
1777
|
+
{ dryRun: false, silent: true }
|
|
1778
|
+
);
|
|
1779
|
+
const tags = output.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
1780
|
+
return new Set(tags);
|
|
1781
|
+
}
|
|
1782
|
+
/**
|
|
1783
|
+
* Create local git tags for private packages after `changeset publish`.
|
|
1784
|
+
*
|
|
1785
|
+
* Changesets skips npm publish and tagging for `private: true` packages.
|
|
1786
|
+
* Release still needs those tags as changelog baselines, so create them here.
|
|
1787
|
+
*/
|
|
1788
|
+
async ensurePrivateReleaseTags(workspaces) {
|
|
1789
|
+
const privateWorkspaces = workspaces.filter(
|
|
1790
|
+
(workspace) => this.shouldProcessWorkspace(workspace) && !workspace.dependencyRelease && isPrivateWorkspace(workspace) && !!workspace.tagName
|
|
1791
|
+
);
|
|
1792
|
+
if (privateWorkspaces.length === 0) {
|
|
1793
|
+
return;
|
|
1794
|
+
}
|
|
1795
|
+
const localTags = await this.listLocalTags();
|
|
1796
|
+
for (const workspace of privateWorkspaces) {
|
|
1797
|
+
const tagName = workspace.tagName;
|
|
1798
|
+
if (localTags.has(tagName)) {
|
|
1799
|
+
this.logger.debug(`Private package tag already exists: ${tagName}`);
|
|
1800
|
+
continue;
|
|
1801
|
+
}
|
|
1802
|
+
this.logger.info(
|
|
1803
|
+
`Create local release tag for private package: ${tagName}`
|
|
1804
|
+
);
|
|
1805
|
+
await this.shell.exec(["git", "tag", tagName]);
|
|
1806
|
+
localTags.add(tagName);
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
/**
|
|
1810
|
+
* Push release tags from synced workspaces' `tagName` fields.
|
|
1811
|
+
*
|
|
1812
|
+
* After {@link syncWorkspaces} and {@link ensurePrivateReleaseTags}, push
|
|
1813
|
+
* tags that exist as local refs. Missing public-package tags are skipped
|
|
1814
|
+
* (changeset may have skipped publish); they are not invented here.
|
|
1815
|
+
*/
|
|
1816
|
+
async pushWorkspaceReleaseTags(workspaces) {
|
|
1817
|
+
const uniqueTags = this.collectPushableReleaseTags(workspaces);
|
|
1748
1818
|
if (uniqueTags.length === 0) {
|
|
1749
1819
|
this.logger.warn(
|
|
1750
1820
|
"No workspace.tagName found after publish sync; skip tag push"
|
|
1751
1821
|
);
|
|
1752
1822
|
return;
|
|
1753
1823
|
}
|
|
1824
|
+
const localTags = await this.listLocalTags();
|
|
1825
|
+
const existingTags = uniqueTags.filter((tag) => localTags.has(tag));
|
|
1826
|
+
const missingTags = uniqueTags.filter((tag) => !localTags.has(tag));
|
|
1827
|
+
if (missingTags.length > 0) {
|
|
1828
|
+
this.logger.warn(
|
|
1829
|
+
`Skip push for missing local tag(s):
|
|
1830
|
+
${missingTags.map((tag) => ` ${tag}`).join("\n")}`
|
|
1831
|
+
);
|
|
1832
|
+
}
|
|
1833
|
+
if (existingTags.length === 0) {
|
|
1834
|
+
this.logger.warn(
|
|
1835
|
+
"No local release tags found to push after publish; skip tag push"
|
|
1836
|
+
);
|
|
1837
|
+
return;
|
|
1838
|
+
}
|
|
1754
1839
|
this.logger.info(
|
|
1755
|
-
`Pushing ${
|
|
1756
|
-
${
|
|
1840
|
+
`Pushing ${existingTags.length} release tag(s) to origin:
|
|
1841
|
+
${existingTags.map((tag) => ` ${tag}`).join("\n")}`
|
|
1757
1842
|
);
|
|
1758
1843
|
await this.shell.exec([
|
|
1759
1844
|
"git",
|
|
1760
1845
|
"push",
|
|
1761
1846
|
"origin",
|
|
1762
|
-
...
|
|
1847
|
+
...existingTags.map((tag) => `"refs/tags/${tag}"`)
|
|
1763
1848
|
]);
|
|
1764
1849
|
}
|
|
1765
1850
|
syncWorkspaces(workspaces) {
|
|
@@ -2644,7 +2729,7 @@ var GithubManager = class {
|
|
|
2644
2729
|
*/
|
|
2645
2730
|
getOctokitReleaseOptions(options) {
|
|
2646
2731
|
const {
|
|
2647
|
-
releaseName,
|
|
2732
|
+
releaseName = releaseJson.github.releaseName,
|
|
2648
2733
|
draft = false,
|
|
2649
2734
|
preRelease = false,
|
|
2650
2735
|
autoGenerate = false,
|
|
@@ -2653,7 +2738,7 @@ var GithubManager = class {
|
|
|
2653
2738
|
discussionCategoryName = void 0
|
|
2654
2739
|
} = this.context.getParameters("github");
|
|
2655
2740
|
const name = releaseName;
|
|
2656
|
-
const body = autoGenerate ? "" : this.truncateBody(String(releaseNotes));
|
|
2741
|
+
const body = autoGenerate ? "" : this.truncateBody(String(releaseNotes ?? ""));
|
|
2657
2742
|
return {
|
|
2658
2743
|
name,
|
|
2659
2744
|
make_latest: makeLatest.toString(),
|
|
@@ -3417,6 +3502,7 @@ var ReleaseFormatter = class {
|
|
|
3417
3502
|
var Github = class extends GitBase {
|
|
3418
3503
|
releaseFormatter;
|
|
3419
3504
|
githubManager;
|
|
3505
|
+
pather = new Pather();
|
|
3420
3506
|
constructor(context, props = {}) {
|
|
3421
3507
|
super(context, "github", props);
|
|
3422
3508
|
this.releaseFormatter = new ReleaseFormatter(
|
|
@@ -3428,6 +3514,9 @@ var Github = class extends GitBase {
|
|
|
3428
3514
|
get mode() {
|
|
3429
3515
|
return this.config.mode || releaseJson.github.mode;
|
|
3430
3516
|
}
|
|
3517
|
+
get ignoreReleasePaths() {
|
|
3518
|
+
return this.config.ignoreReleasePaths ?? [...releaseJson.github.ignoreReleasePaths];
|
|
3519
|
+
}
|
|
3431
3520
|
async onBefore() {
|
|
3432
3521
|
await super.onBefore();
|
|
3433
3522
|
await this.githubManager.validateToken();
|
|
@@ -3453,6 +3542,9 @@ var Github = class extends GitBase {
|
|
|
3453
3542
|
if (workspace.dependencyRelease) {
|
|
3454
3543
|
return workspace;
|
|
3455
3544
|
}
|
|
3545
|
+
if (this.mode === "createRelease" && this.isIgnoredReleasePath(workspace)) {
|
|
3546
|
+
return workspace;
|
|
3547
|
+
}
|
|
3456
3548
|
return githubChangelog.enrichWorkspaceChangelog(workspace);
|
|
3457
3549
|
})
|
|
3458
3550
|
)
|
|
@@ -3461,6 +3553,13 @@ var Github = class extends GitBase {
|
|
|
3461
3553
|
}
|
|
3462
3554
|
async onSuccess() {
|
|
3463
3555
|
const workspaces = this.context.requireWorkspaces();
|
|
3556
|
+
if (this.mode === "createRelease") {
|
|
3557
|
+
await this.step({
|
|
3558
|
+
label: "Create GitHub Releases",
|
|
3559
|
+
task: () => this.createGithubReleases(workspaces)
|
|
3560
|
+
});
|
|
3561
|
+
return;
|
|
3562
|
+
}
|
|
3464
3563
|
const releaseBranchResult = await this.step({
|
|
3465
3564
|
label: "Create Release Branch",
|
|
3466
3565
|
task: () => this.createReleaseBranch(workspaces)
|
|
@@ -3477,6 +3576,63 @@ var Github = class extends GitBase {
|
|
|
3477
3576
|
}
|
|
3478
3577
|
await this.handleReleasePullRequest(workspaces, releaseBranchResult);
|
|
3479
3578
|
}
|
|
3579
|
+
/**
|
|
3580
|
+
* Whether this workspace path is under an ignoreReleasePaths entry.
|
|
3581
|
+
*/
|
|
3582
|
+
isIgnoredReleasePath(workspace) {
|
|
3583
|
+
const ignorePaths = this.ignoreReleasePaths;
|
|
3584
|
+
if (ignorePaths.length === 0) {
|
|
3585
|
+
return false;
|
|
3586
|
+
}
|
|
3587
|
+
return ignorePaths.some(
|
|
3588
|
+
(ignorePath) => this.pather.isSubPath(workspace.path, ignorePath)
|
|
3589
|
+
);
|
|
3590
|
+
}
|
|
3591
|
+
/**
|
|
3592
|
+
* Workspaces that should receive a GitHub Release in createRelease mode.
|
|
3593
|
+
*
|
|
3594
|
+
* Requires `tagName`. Skips dependency-release packages and paths matched
|
|
3595
|
+
* by {@link GithubProps.ignoreReleasePaths}. Private packages are included
|
|
3596
|
+
* when they have a tag.
|
|
3597
|
+
*/
|
|
3598
|
+
getReleaseWorkspaces(workspaces) {
|
|
3599
|
+
return workspaces.filter((workspace) => {
|
|
3600
|
+
if (!workspace.tagName) {
|
|
3601
|
+
return false;
|
|
3602
|
+
}
|
|
3603
|
+
if (workspace.dependencyRelease) {
|
|
3604
|
+
return false;
|
|
3605
|
+
}
|
|
3606
|
+
if (this.isIgnoredReleasePath(workspace)) {
|
|
3607
|
+
this.logger.info(
|
|
3608
|
+
`Skip GitHub Release for ignored path: ${workspace.path} (${workspace.tagName})`
|
|
3609
|
+
);
|
|
3610
|
+
return false;
|
|
3611
|
+
}
|
|
3612
|
+
return true;
|
|
3613
|
+
});
|
|
3614
|
+
}
|
|
3615
|
+
/**
|
|
3616
|
+
* Create a GitHub Release for each eligible workspace.
|
|
3617
|
+
*
|
|
3618
|
+
* Release notes use the enriched workspace changelog from `onExec`.
|
|
3619
|
+
*/
|
|
3620
|
+
async createGithubReleases(workspaces) {
|
|
3621
|
+
const releaseWorkspaces = this.getReleaseWorkspaces(workspaces);
|
|
3622
|
+
if (releaseWorkspaces.length === 0) {
|
|
3623
|
+
this.logger.warn("No workspaces eligible for GitHub Release creation");
|
|
3624
|
+
return;
|
|
3625
|
+
}
|
|
3626
|
+
this.logger.info(
|
|
3627
|
+
`Creating GitHub Releases (${releaseWorkspaces.length}):
|
|
3628
|
+
${releaseWorkspaces.map((w) => ` ${w.tagName}`).join("\n")}`
|
|
3629
|
+
);
|
|
3630
|
+
await Promise.all(
|
|
3631
|
+
releaseWorkspaces.map(
|
|
3632
|
+
(workspace) => this.githubManager.createRelease(workspace)
|
|
3633
|
+
)
|
|
3634
|
+
);
|
|
3635
|
+
}
|
|
3480
3636
|
/**
|
|
3481
3637
|
* Create the release PR and optionally auto-merge it.
|
|
3482
3638
|
*
|
|
@@ -3832,6 +3988,22 @@ function programArgs() {
|
|
|
3832
3988
|
).option(
|
|
3833
3989
|
"--workspaces.skip",
|
|
3834
3990
|
"Skip the Workspaces plugin"
|
|
3991
|
+
).option(
|
|
3992
|
+
"--github.mode <mode>",
|
|
3993
|
+
"Github mode: createPR (default) or createRelease (fe-config: release.github.mode)",
|
|
3994
|
+
(value) => {
|
|
3995
|
+
if (value !== "createPR" && value !== "createRelease") {
|
|
3996
|
+
throw new Error(
|
|
3997
|
+
'Invalid github.mode. Must be "createPR" or "createRelease"'
|
|
3998
|
+
);
|
|
3999
|
+
}
|
|
4000
|
+
return value;
|
|
4001
|
+
},
|
|
4002
|
+
"createPR"
|
|
4003
|
+
).option(
|
|
4004
|
+
"--github.ignore-release-paths <paths>",
|
|
4005
|
+
"Path prefixes to skip when creating GitHub Releases, comma-separated (fe-config: release.github.ignoreReleasePaths)",
|
|
4006
|
+
splitWithComma
|
|
3835
4007
|
).option(
|
|
3836
4008
|
"--github.skip [lifecycle]",
|
|
3837
4009
|
"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",
|
|
@@ -597,6 +601,9 @@ function workspaceVersionSummary(workspace) {
|
|
|
597
601
|
function shouldProcessWorkspace(workspace, ignoreNonUpdatedPackages) {
|
|
598
602
|
return !(ignoreNonUpdatedPackages && workspace.dependencyRelease);
|
|
599
603
|
}
|
|
604
|
+
function isPrivateWorkspace(workspace) {
|
|
605
|
+
return workspace.packageJson?.private === true;
|
|
606
|
+
}
|
|
600
607
|
function readJson(path) {
|
|
601
608
|
const packageJsonContent = (0, import_fs.readFileSync)(path, "utf-8");
|
|
602
609
|
return JSON.parse(packageJsonContent);
|
|
@@ -1738,9 +1745,18 @@ ${workspaces?.map((workspace) => workspace.toString()).join("\n")}`
|
|
|
1738
1745
|
this.logDryRun("Would run: changeset publish, publish packages and tags");
|
|
1739
1746
|
const preview = this.mergeWorkspaces(workspaces);
|
|
1740
1747
|
this.context.setWorkspaces(preview);
|
|
1741
|
-
const previewTags =
|
|
1742
|
-
|
|
1748
|
+
const previewTags = this.collectPushableReleaseTags(preview);
|
|
1749
|
+
const privateTags = preview.filter(
|
|
1750
|
+
(workspace) => this.shouldProcessWorkspace(workspace) && !workspace.dependencyRelease && isPrivateWorkspace(workspace) && !!workspace.tagName
|
|
1743
1751
|
).map((workspace) => workspace.tagName);
|
|
1752
|
+
if (privateTags.length > 0) {
|
|
1753
|
+
this.logDryRun(
|
|
1754
|
+
`Would create local tag(s) for private package(s):
|
|
1755
|
+
${[
|
|
1756
|
+
...new Set(privateTags)
|
|
1757
|
+
].map((tag) => ` ${tag}`).join("\n")}`
|
|
1758
|
+
);
|
|
1759
|
+
}
|
|
1744
1760
|
this.logDryRun(
|
|
1745
1761
|
previewTags.length === 0 ? "Would push release tag(s) from workspace.tagName: none" : `Would push release tag(s) from workspace.tagName:
|
|
1746
1762
|
${previewTags.map((tag) => ` ${tag}`).join("\n")}`
|
|
@@ -1754,37 +1770,106 @@ ${previewTags.map((tag) => ` ${tag}`).join("\n")}`
|
|
|
1754
1770
|
if (workspaces.length > 0) {
|
|
1755
1771
|
this.syncWorkspaces(workspaces);
|
|
1756
1772
|
}
|
|
1773
|
+
const syncedWorkspaces = this.context.workspaces ?? workspaces;
|
|
1774
|
+
await this.step({
|
|
1775
|
+
label: "Create Private Package Tags",
|
|
1776
|
+
task: () => this.ensurePrivateReleaseTags(syncedWorkspaces)
|
|
1777
|
+
});
|
|
1757
1778
|
await this.step({
|
|
1758
1779
|
label: "Push Release Tags",
|
|
1759
|
-
task: () => this.pushWorkspaceReleaseTags(
|
|
1780
|
+
task: () => this.pushWorkspaceReleaseTags(syncedWorkspaces)
|
|
1760
1781
|
});
|
|
1761
1782
|
}
|
|
1762
1783
|
/**
|
|
1763
|
-
*
|
|
1784
|
+
* Collect tag names that should be pushed after `changeset publish`.
|
|
1764
1785
|
*
|
|
1765
|
-
*
|
|
1766
|
-
*
|
|
1786
|
+
* Includes public packages (tagged by changesets) and private packages
|
|
1787
|
+
* (tagged by {@link ensurePrivateReleaseTags}). Skips dependency-release
|
|
1788
|
+
* workspaces.
|
|
1767
1789
|
*/
|
|
1768
|
-
|
|
1790
|
+
collectPushableReleaseTags(workspaces) {
|
|
1769
1791
|
const tags = workspaces.filter(
|
|
1770
1792
|
(workspace) => this.shouldProcessWorkspace(workspace) && !workspace.dependencyRelease && !!workspace.tagName
|
|
1771
1793
|
).map((workspace) => workspace.tagName);
|
|
1772
|
-
|
|
1794
|
+
return [...new Set(tags)];
|
|
1795
|
+
}
|
|
1796
|
+
/**
|
|
1797
|
+
* List local git tag names (short refs under `refs/tags/`).
|
|
1798
|
+
*/
|
|
1799
|
+
async listLocalTags() {
|
|
1800
|
+
const output = await this.shell.exec(
|
|
1801
|
+
'git for-each-ref --format="%(refname:short)" refs/tags/',
|
|
1802
|
+
{ dryRun: false, silent: true }
|
|
1803
|
+
);
|
|
1804
|
+
const tags = output.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
1805
|
+
return new Set(tags);
|
|
1806
|
+
}
|
|
1807
|
+
/**
|
|
1808
|
+
* Create local git tags for private packages after `changeset publish`.
|
|
1809
|
+
*
|
|
1810
|
+
* Changesets skips npm publish and tagging for `private: true` packages.
|
|
1811
|
+
* Release still needs those tags as changelog baselines, so create them here.
|
|
1812
|
+
*/
|
|
1813
|
+
async ensurePrivateReleaseTags(workspaces) {
|
|
1814
|
+
const privateWorkspaces = workspaces.filter(
|
|
1815
|
+
(workspace) => this.shouldProcessWorkspace(workspace) && !workspace.dependencyRelease && isPrivateWorkspace(workspace) && !!workspace.tagName
|
|
1816
|
+
);
|
|
1817
|
+
if (privateWorkspaces.length === 0) {
|
|
1818
|
+
return;
|
|
1819
|
+
}
|
|
1820
|
+
const localTags = await this.listLocalTags();
|
|
1821
|
+
for (const workspace of privateWorkspaces) {
|
|
1822
|
+
const tagName = workspace.tagName;
|
|
1823
|
+
if (localTags.has(tagName)) {
|
|
1824
|
+
this.logger.debug(`Private package tag already exists: ${tagName}`);
|
|
1825
|
+
continue;
|
|
1826
|
+
}
|
|
1827
|
+
this.logger.info(
|
|
1828
|
+
`Create local release tag for private package: ${tagName}`
|
|
1829
|
+
);
|
|
1830
|
+
await this.shell.exec(["git", "tag", tagName]);
|
|
1831
|
+
localTags.add(tagName);
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
/**
|
|
1835
|
+
* Push release tags from synced workspaces' `tagName` fields.
|
|
1836
|
+
*
|
|
1837
|
+
* After {@link syncWorkspaces} and {@link ensurePrivateReleaseTags}, push
|
|
1838
|
+
* tags that exist as local refs. Missing public-package tags are skipped
|
|
1839
|
+
* (changeset may have skipped publish); they are not invented here.
|
|
1840
|
+
*/
|
|
1841
|
+
async pushWorkspaceReleaseTags(workspaces) {
|
|
1842
|
+
const uniqueTags = this.collectPushableReleaseTags(workspaces);
|
|
1773
1843
|
if (uniqueTags.length === 0) {
|
|
1774
1844
|
this.logger.warn(
|
|
1775
1845
|
"No workspace.tagName found after publish sync; skip tag push"
|
|
1776
1846
|
);
|
|
1777
1847
|
return;
|
|
1778
1848
|
}
|
|
1849
|
+
const localTags = await this.listLocalTags();
|
|
1850
|
+
const existingTags = uniqueTags.filter((tag) => localTags.has(tag));
|
|
1851
|
+
const missingTags = uniqueTags.filter((tag) => !localTags.has(tag));
|
|
1852
|
+
if (missingTags.length > 0) {
|
|
1853
|
+
this.logger.warn(
|
|
1854
|
+
`Skip push for missing local tag(s):
|
|
1855
|
+
${missingTags.map((tag) => ` ${tag}`).join("\n")}`
|
|
1856
|
+
);
|
|
1857
|
+
}
|
|
1858
|
+
if (existingTags.length === 0) {
|
|
1859
|
+
this.logger.warn(
|
|
1860
|
+
"No local release tags found to push after publish; skip tag push"
|
|
1861
|
+
);
|
|
1862
|
+
return;
|
|
1863
|
+
}
|
|
1779
1864
|
this.logger.info(
|
|
1780
|
-
`Pushing ${
|
|
1781
|
-
${
|
|
1865
|
+
`Pushing ${existingTags.length} release tag(s) to origin:
|
|
1866
|
+
${existingTags.map((tag) => ` ${tag}`).join("\n")}`
|
|
1782
1867
|
);
|
|
1783
1868
|
await this.shell.exec([
|
|
1784
1869
|
"git",
|
|
1785
1870
|
"push",
|
|
1786
1871
|
"origin",
|
|
1787
|
-
...
|
|
1872
|
+
...existingTags.map((tag) => `"refs/tags/${tag}"`)
|
|
1788
1873
|
]);
|
|
1789
1874
|
}
|
|
1790
1875
|
syncWorkspaces(workspaces) {
|
|
@@ -2669,7 +2754,7 @@ var GithubManager = class {
|
|
|
2669
2754
|
*/
|
|
2670
2755
|
getOctokitReleaseOptions(options) {
|
|
2671
2756
|
const {
|
|
2672
|
-
releaseName,
|
|
2757
|
+
releaseName = releaseJson.github.releaseName,
|
|
2673
2758
|
draft = false,
|
|
2674
2759
|
preRelease = false,
|
|
2675
2760
|
autoGenerate = false,
|
|
@@ -2678,7 +2763,7 @@ var GithubManager = class {
|
|
|
2678
2763
|
discussionCategoryName = void 0
|
|
2679
2764
|
} = this.context.getParameters("github");
|
|
2680
2765
|
const name = releaseName;
|
|
2681
|
-
const body = autoGenerate ? "" : this.truncateBody(String(releaseNotes));
|
|
2766
|
+
const body = autoGenerate ? "" : this.truncateBody(String(releaseNotes ?? ""));
|
|
2682
2767
|
return {
|
|
2683
2768
|
name,
|
|
2684
2769
|
make_latest: makeLatest.toString(),
|
|
@@ -3442,6 +3527,7 @@ var ReleaseFormatter = class {
|
|
|
3442
3527
|
var Github = class extends GitBase {
|
|
3443
3528
|
releaseFormatter;
|
|
3444
3529
|
githubManager;
|
|
3530
|
+
pather = new Pather();
|
|
3445
3531
|
constructor(context, props = {}) {
|
|
3446
3532
|
super(context, "github", props);
|
|
3447
3533
|
this.releaseFormatter = new ReleaseFormatter(
|
|
@@ -3453,6 +3539,9 @@ var Github = class extends GitBase {
|
|
|
3453
3539
|
get mode() {
|
|
3454
3540
|
return this.config.mode || releaseJson.github.mode;
|
|
3455
3541
|
}
|
|
3542
|
+
get ignoreReleasePaths() {
|
|
3543
|
+
return this.config.ignoreReleasePaths ?? [...releaseJson.github.ignoreReleasePaths];
|
|
3544
|
+
}
|
|
3456
3545
|
async onBefore() {
|
|
3457
3546
|
await super.onBefore();
|
|
3458
3547
|
await this.githubManager.validateToken();
|
|
@@ -3478,6 +3567,9 @@ var Github = class extends GitBase {
|
|
|
3478
3567
|
if (workspace.dependencyRelease) {
|
|
3479
3568
|
return workspace;
|
|
3480
3569
|
}
|
|
3570
|
+
if (this.mode === "createRelease" && this.isIgnoredReleasePath(workspace)) {
|
|
3571
|
+
return workspace;
|
|
3572
|
+
}
|
|
3481
3573
|
return githubChangelog.enrichWorkspaceChangelog(workspace);
|
|
3482
3574
|
})
|
|
3483
3575
|
)
|
|
@@ -3486,6 +3578,13 @@ var Github = class extends GitBase {
|
|
|
3486
3578
|
}
|
|
3487
3579
|
async onSuccess() {
|
|
3488
3580
|
const workspaces = this.context.requireWorkspaces();
|
|
3581
|
+
if (this.mode === "createRelease") {
|
|
3582
|
+
await this.step({
|
|
3583
|
+
label: "Create GitHub Releases",
|
|
3584
|
+
task: () => this.createGithubReleases(workspaces)
|
|
3585
|
+
});
|
|
3586
|
+
return;
|
|
3587
|
+
}
|
|
3489
3588
|
const releaseBranchResult = await this.step({
|
|
3490
3589
|
label: "Create Release Branch",
|
|
3491
3590
|
task: () => this.createReleaseBranch(workspaces)
|
|
@@ -3502,6 +3601,63 @@ var Github = class extends GitBase {
|
|
|
3502
3601
|
}
|
|
3503
3602
|
await this.handleReleasePullRequest(workspaces, releaseBranchResult);
|
|
3504
3603
|
}
|
|
3604
|
+
/**
|
|
3605
|
+
* Whether this workspace path is under an ignoreReleasePaths entry.
|
|
3606
|
+
*/
|
|
3607
|
+
isIgnoredReleasePath(workspace) {
|
|
3608
|
+
const ignorePaths = this.ignoreReleasePaths;
|
|
3609
|
+
if (ignorePaths.length === 0) {
|
|
3610
|
+
return false;
|
|
3611
|
+
}
|
|
3612
|
+
return ignorePaths.some(
|
|
3613
|
+
(ignorePath) => this.pather.isSubPath(workspace.path, ignorePath)
|
|
3614
|
+
);
|
|
3615
|
+
}
|
|
3616
|
+
/**
|
|
3617
|
+
* Workspaces that should receive a GitHub Release in createRelease mode.
|
|
3618
|
+
*
|
|
3619
|
+
* Requires `tagName`. Skips dependency-release packages and paths matched
|
|
3620
|
+
* by {@link GithubProps.ignoreReleasePaths}. Private packages are included
|
|
3621
|
+
* when they have a tag.
|
|
3622
|
+
*/
|
|
3623
|
+
getReleaseWorkspaces(workspaces) {
|
|
3624
|
+
return workspaces.filter((workspace) => {
|
|
3625
|
+
if (!workspace.tagName) {
|
|
3626
|
+
return false;
|
|
3627
|
+
}
|
|
3628
|
+
if (workspace.dependencyRelease) {
|
|
3629
|
+
return false;
|
|
3630
|
+
}
|
|
3631
|
+
if (this.isIgnoredReleasePath(workspace)) {
|
|
3632
|
+
this.logger.info(
|
|
3633
|
+
`Skip GitHub Release for ignored path: ${workspace.path} (${workspace.tagName})`
|
|
3634
|
+
);
|
|
3635
|
+
return false;
|
|
3636
|
+
}
|
|
3637
|
+
return true;
|
|
3638
|
+
});
|
|
3639
|
+
}
|
|
3640
|
+
/**
|
|
3641
|
+
* Create a GitHub Release for each eligible workspace.
|
|
3642
|
+
*
|
|
3643
|
+
* Release notes use the enriched workspace changelog from `onExec`.
|
|
3644
|
+
*/
|
|
3645
|
+
async createGithubReleases(workspaces) {
|
|
3646
|
+
const releaseWorkspaces = this.getReleaseWorkspaces(workspaces);
|
|
3647
|
+
if (releaseWorkspaces.length === 0) {
|
|
3648
|
+
this.logger.warn("No workspaces eligible for GitHub Release creation");
|
|
3649
|
+
return;
|
|
3650
|
+
}
|
|
3651
|
+
this.logger.info(
|
|
3652
|
+
`Creating GitHub Releases (${releaseWorkspaces.length}):
|
|
3653
|
+
${releaseWorkspaces.map((w) => ` ${w.tagName}`).join("\n")}`
|
|
3654
|
+
);
|
|
3655
|
+
await Promise.all(
|
|
3656
|
+
releaseWorkspaces.map(
|
|
3657
|
+
(workspace) => this.githubManager.createRelease(workspace)
|
|
3658
|
+
)
|
|
3659
|
+
);
|
|
3660
|
+
}
|
|
3505
3661
|
/**
|
|
3506
3662
|
* Create the release PR and optionally auto-merge it.
|
|
3507
3663
|
*
|
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
|
|
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**:
|
|
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
|
-
*
|
|
349
|
-
* then create
|
|
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
|
*
|
|
@@ -856,9 +887,10 @@ interface ChangeLogInterface {
|
|
|
856
887
|
* - **onSuccess**: run version and/or publish flow based on `mode`
|
|
857
888
|
*
|
|
858
889
|
* Publish flow (`mode: 'publish'` or second half of `'both'`):
|
|
859
|
-
* 1. Run `changeset publish` (npm publish + local `git tag`
|
|
890
|
+
* 1. Run `changeset publish` (npm publish + local `git tag` for public packages)
|
|
860
891
|
* 2. Sync workspaces from disk so `tagName` is set from the release version
|
|
861
|
-
* 3.
|
|
892
|
+
* 3. Create local tags for `private` packages (changesets skips tagging them)
|
|
893
|
+
* 4. Push `workspace.tagName` refs that exist locally to `origin`
|
|
862
894
|
*
|
|
863
895
|
* Version flow (`mode: 'version'` or first half of `'both'`):
|
|
864
896
|
* 1. Write `.changeset/*.md` files for directly changed packages only
|
|
@@ -992,11 +1024,31 @@ declare class ChangesetVersion extends ScriptPlugin<ReleaseContext, ChangesetVer
|
|
|
992
1024
|
protected logDryRun(message: string): void;
|
|
993
1025
|
protected runVersionFlow(): Promise<void>;
|
|
994
1026
|
protected runChangesetPublish(): Promise<void>;
|
|
1027
|
+
/**
|
|
1028
|
+
* Collect tag names that should be pushed after `changeset publish`.
|
|
1029
|
+
*
|
|
1030
|
+
* Includes public packages (tagged by changesets) and private packages
|
|
1031
|
+
* (tagged by {@link ensurePrivateReleaseTags}). Skips dependency-release
|
|
1032
|
+
* workspaces.
|
|
1033
|
+
*/
|
|
1034
|
+
protected collectPushableReleaseTags(workspaces: WorkspaceInterface[]): string[];
|
|
1035
|
+
/**
|
|
1036
|
+
* List local git tag names (short refs under `refs/tags/`).
|
|
1037
|
+
*/
|
|
1038
|
+
protected listLocalTags(): Promise<Set<string>>;
|
|
1039
|
+
/**
|
|
1040
|
+
* Create local git tags for private packages after `changeset publish`.
|
|
1041
|
+
*
|
|
1042
|
+
* Changesets skips npm publish and tagging for `private: true` packages.
|
|
1043
|
+
* Release still needs those tags as changelog baselines, so create them here.
|
|
1044
|
+
*/
|
|
1045
|
+
protected ensurePrivateReleaseTags(workspaces: WorkspaceInterface[]): Promise<void>;
|
|
995
1046
|
/**
|
|
996
1047
|
* Push release tags from synced workspaces' `tagName` fields.
|
|
997
1048
|
*
|
|
998
|
-
*
|
|
999
|
-
*
|
|
1049
|
+
* After {@link syncWorkspaces} and {@link ensurePrivateReleaseTags}, push
|
|
1050
|
+
* tags that exist as local refs. Missing public-package tags are skipped
|
|
1051
|
+
* (changeset may have skipped publish); they are not invented here.
|
|
1000
1052
|
*/
|
|
1001
1053
|
protected pushWorkspaceReleaseTags(workspaces: WorkspaceInterface[]): Promise<void>;
|
|
1002
1054
|
protected syncWorkspaces(workspaces: WorkspaceInterface[]): void;
|
|
@@ -1444,6 +1496,10 @@ declare const releaseJson: {
|
|
|
1444
1496
|
readonly branchName: "release/${repoName}-${releaseId}";
|
|
1445
1497
|
readonly releaseTagName: "release-tag-${count}-patch-${releaseId}";
|
|
1446
1498
|
readonly commitMessage: "chore(release): ${spaces}";
|
|
1499
|
+
/** GitHub release title template (createRelease mode) */
|
|
1500
|
+
readonly releaseName: "${name}@${version}";
|
|
1501
|
+
/** Path prefixes skipped when creating GitHub releases */
|
|
1502
|
+
readonly ignoreReleasePaths: readonly string[];
|
|
1447
1503
|
readonly label: {
|
|
1448
1504
|
readonly name: "CI-Release";
|
|
1449
1505
|
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",
|
|
@@ -548,6 +552,9 @@ function workspaceVersionSummary(workspace) {
|
|
|
548
552
|
function shouldProcessWorkspace(workspace, ignoreNonUpdatedPackages) {
|
|
549
553
|
return !(ignoreNonUpdatedPackages && workspace.dependencyRelease);
|
|
550
554
|
}
|
|
555
|
+
function isPrivateWorkspace(workspace) {
|
|
556
|
+
return workspace.packageJson?.private === true;
|
|
557
|
+
}
|
|
551
558
|
function readJson(path) {
|
|
552
559
|
const packageJsonContent = readFileSync(path, "utf-8");
|
|
553
560
|
return JSON.parse(packageJsonContent);
|
|
@@ -1689,9 +1696,18 @@ ${workspaces?.map((workspace) => workspace.toString()).join("\n")}`
|
|
|
1689
1696
|
this.logDryRun("Would run: changeset publish, publish packages and tags");
|
|
1690
1697
|
const preview = this.mergeWorkspaces(workspaces);
|
|
1691
1698
|
this.context.setWorkspaces(preview);
|
|
1692
|
-
const previewTags =
|
|
1693
|
-
|
|
1699
|
+
const previewTags = this.collectPushableReleaseTags(preview);
|
|
1700
|
+
const privateTags = preview.filter(
|
|
1701
|
+
(workspace) => this.shouldProcessWorkspace(workspace) && !workspace.dependencyRelease && isPrivateWorkspace(workspace) && !!workspace.tagName
|
|
1694
1702
|
).map((workspace) => workspace.tagName);
|
|
1703
|
+
if (privateTags.length > 0) {
|
|
1704
|
+
this.logDryRun(
|
|
1705
|
+
`Would create local tag(s) for private package(s):
|
|
1706
|
+
${[
|
|
1707
|
+
...new Set(privateTags)
|
|
1708
|
+
].map((tag) => ` ${tag}`).join("\n")}`
|
|
1709
|
+
);
|
|
1710
|
+
}
|
|
1695
1711
|
this.logDryRun(
|
|
1696
1712
|
previewTags.length === 0 ? "Would push release tag(s) from workspace.tagName: none" : `Would push release tag(s) from workspace.tagName:
|
|
1697
1713
|
${previewTags.map((tag) => ` ${tag}`).join("\n")}`
|
|
@@ -1705,37 +1721,106 @@ ${previewTags.map((tag) => ` ${tag}`).join("\n")}`
|
|
|
1705
1721
|
if (workspaces.length > 0) {
|
|
1706
1722
|
this.syncWorkspaces(workspaces);
|
|
1707
1723
|
}
|
|
1724
|
+
const syncedWorkspaces = this.context.workspaces ?? workspaces;
|
|
1725
|
+
await this.step({
|
|
1726
|
+
label: "Create Private Package Tags",
|
|
1727
|
+
task: () => this.ensurePrivateReleaseTags(syncedWorkspaces)
|
|
1728
|
+
});
|
|
1708
1729
|
await this.step({
|
|
1709
1730
|
label: "Push Release Tags",
|
|
1710
|
-
task: () => this.pushWorkspaceReleaseTags(
|
|
1731
|
+
task: () => this.pushWorkspaceReleaseTags(syncedWorkspaces)
|
|
1711
1732
|
});
|
|
1712
1733
|
}
|
|
1713
1734
|
/**
|
|
1714
|
-
*
|
|
1735
|
+
* Collect tag names that should be pushed after `changeset publish`.
|
|
1715
1736
|
*
|
|
1716
|
-
*
|
|
1717
|
-
*
|
|
1737
|
+
* Includes public packages (tagged by changesets) and private packages
|
|
1738
|
+
* (tagged by {@link ensurePrivateReleaseTags}). Skips dependency-release
|
|
1739
|
+
* workspaces.
|
|
1718
1740
|
*/
|
|
1719
|
-
|
|
1741
|
+
collectPushableReleaseTags(workspaces) {
|
|
1720
1742
|
const tags = workspaces.filter(
|
|
1721
1743
|
(workspace) => this.shouldProcessWorkspace(workspace) && !workspace.dependencyRelease && !!workspace.tagName
|
|
1722
1744
|
).map((workspace) => workspace.tagName);
|
|
1723
|
-
|
|
1745
|
+
return [...new Set(tags)];
|
|
1746
|
+
}
|
|
1747
|
+
/**
|
|
1748
|
+
* List local git tag names (short refs under `refs/tags/`).
|
|
1749
|
+
*/
|
|
1750
|
+
async listLocalTags() {
|
|
1751
|
+
const output = await this.shell.exec(
|
|
1752
|
+
'git for-each-ref --format="%(refname:short)" refs/tags/',
|
|
1753
|
+
{ dryRun: false, silent: true }
|
|
1754
|
+
);
|
|
1755
|
+
const tags = output.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
1756
|
+
return new Set(tags);
|
|
1757
|
+
}
|
|
1758
|
+
/**
|
|
1759
|
+
* Create local git tags for private packages after `changeset publish`.
|
|
1760
|
+
*
|
|
1761
|
+
* Changesets skips npm publish and tagging for `private: true` packages.
|
|
1762
|
+
* Release still needs those tags as changelog baselines, so create them here.
|
|
1763
|
+
*/
|
|
1764
|
+
async ensurePrivateReleaseTags(workspaces) {
|
|
1765
|
+
const privateWorkspaces = workspaces.filter(
|
|
1766
|
+
(workspace) => this.shouldProcessWorkspace(workspace) && !workspace.dependencyRelease && isPrivateWorkspace(workspace) && !!workspace.tagName
|
|
1767
|
+
);
|
|
1768
|
+
if (privateWorkspaces.length === 0) {
|
|
1769
|
+
return;
|
|
1770
|
+
}
|
|
1771
|
+
const localTags = await this.listLocalTags();
|
|
1772
|
+
for (const workspace of privateWorkspaces) {
|
|
1773
|
+
const tagName = workspace.tagName;
|
|
1774
|
+
if (localTags.has(tagName)) {
|
|
1775
|
+
this.logger.debug(`Private package tag already exists: ${tagName}`);
|
|
1776
|
+
continue;
|
|
1777
|
+
}
|
|
1778
|
+
this.logger.info(
|
|
1779
|
+
`Create local release tag for private package: ${tagName}`
|
|
1780
|
+
);
|
|
1781
|
+
await this.shell.exec(["git", "tag", tagName]);
|
|
1782
|
+
localTags.add(tagName);
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
/**
|
|
1786
|
+
* Push release tags from synced workspaces' `tagName` fields.
|
|
1787
|
+
*
|
|
1788
|
+
* After {@link syncWorkspaces} and {@link ensurePrivateReleaseTags}, push
|
|
1789
|
+
* tags that exist as local refs. Missing public-package tags are skipped
|
|
1790
|
+
* (changeset may have skipped publish); they are not invented here.
|
|
1791
|
+
*/
|
|
1792
|
+
async pushWorkspaceReleaseTags(workspaces) {
|
|
1793
|
+
const uniqueTags = this.collectPushableReleaseTags(workspaces);
|
|
1724
1794
|
if (uniqueTags.length === 0) {
|
|
1725
1795
|
this.logger.warn(
|
|
1726
1796
|
"No workspace.tagName found after publish sync; skip tag push"
|
|
1727
1797
|
);
|
|
1728
1798
|
return;
|
|
1729
1799
|
}
|
|
1800
|
+
const localTags = await this.listLocalTags();
|
|
1801
|
+
const existingTags = uniqueTags.filter((tag) => localTags.has(tag));
|
|
1802
|
+
const missingTags = uniqueTags.filter((tag) => !localTags.has(tag));
|
|
1803
|
+
if (missingTags.length > 0) {
|
|
1804
|
+
this.logger.warn(
|
|
1805
|
+
`Skip push for missing local tag(s):
|
|
1806
|
+
${missingTags.map((tag) => ` ${tag}`).join("\n")}`
|
|
1807
|
+
);
|
|
1808
|
+
}
|
|
1809
|
+
if (existingTags.length === 0) {
|
|
1810
|
+
this.logger.warn(
|
|
1811
|
+
"No local release tags found to push after publish; skip tag push"
|
|
1812
|
+
);
|
|
1813
|
+
return;
|
|
1814
|
+
}
|
|
1730
1815
|
this.logger.info(
|
|
1731
|
-
`Pushing ${
|
|
1732
|
-
${
|
|
1816
|
+
`Pushing ${existingTags.length} release tag(s) to origin:
|
|
1817
|
+
${existingTags.map((tag) => ` ${tag}`).join("\n")}`
|
|
1733
1818
|
);
|
|
1734
1819
|
await this.shell.exec([
|
|
1735
1820
|
"git",
|
|
1736
1821
|
"push",
|
|
1737
1822
|
"origin",
|
|
1738
|
-
...
|
|
1823
|
+
...existingTags.map((tag) => `"refs/tags/${tag}"`)
|
|
1739
1824
|
]);
|
|
1740
1825
|
}
|
|
1741
1826
|
syncWorkspaces(workspaces) {
|
|
@@ -2620,7 +2705,7 @@ var GithubManager = class {
|
|
|
2620
2705
|
*/
|
|
2621
2706
|
getOctokitReleaseOptions(options) {
|
|
2622
2707
|
const {
|
|
2623
|
-
releaseName,
|
|
2708
|
+
releaseName = releaseJson.github.releaseName,
|
|
2624
2709
|
draft = false,
|
|
2625
2710
|
preRelease = false,
|
|
2626
2711
|
autoGenerate = false,
|
|
@@ -2629,7 +2714,7 @@ var GithubManager = class {
|
|
|
2629
2714
|
discussionCategoryName = void 0
|
|
2630
2715
|
} = this.context.getParameters("github");
|
|
2631
2716
|
const name = releaseName;
|
|
2632
|
-
const body = autoGenerate ? "" : this.truncateBody(String(releaseNotes));
|
|
2717
|
+
const body = autoGenerate ? "" : this.truncateBody(String(releaseNotes ?? ""));
|
|
2633
2718
|
return {
|
|
2634
2719
|
name,
|
|
2635
2720
|
make_latest: makeLatest.toString(),
|
|
@@ -3393,6 +3478,7 @@ var ReleaseFormatter = class {
|
|
|
3393
3478
|
var Github = class extends GitBase {
|
|
3394
3479
|
releaseFormatter;
|
|
3395
3480
|
githubManager;
|
|
3481
|
+
pather = new Pather();
|
|
3396
3482
|
constructor(context, props = {}) {
|
|
3397
3483
|
super(context, "github", props);
|
|
3398
3484
|
this.releaseFormatter = new ReleaseFormatter(
|
|
@@ -3404,6 +3490,9 @@ var Github = class extends GitBase {
|
|
|
3404
3490
|
get mode() {
|
|
3405
3491
|
return this.config.mode || releaseJson.github.mode;
|
|
3406
3492
|
}
|
|
3493
|
+
get ignoreReleasePaths() {
|
|
3494
|
+
return this.config.ignoreReleasePaths ?? [...releaseJson.github.ignoreReleasePaths];
|
|
3495
|
+
}
|
|
3407
3496
|
async onBefore() {
|
|
3408
3497
|
await super.onBefore();
|
|
3409
3498
|
await this.githubManager.validateToken();
|
|
@@ -3429,6 +3518,9 @@ var Github = class extends GitBase {
|
|
|
3429
3518
|
if (workspace.dependencyRelease) {
|
|
3430
3519
|
return workspace;
|
|
3431
3520
|
}
|
|
3521
|
+
if (this.mode === "createRelease" && this.isIgnoredReleasePath(workspace)) {
|
|
3522
|
+
return workspace;
|
|
3523
|
+
}
|
|
3432
3524
|
return githubChangelog.enrichWorkspaceChangelog(workspace);
|
|
3433
3525
|
})
|
|
3434
3526
|
)
|
|
@@ -3437,6 +3529,13 @@ var Github = class extends GitBase {
|
|
|
3437
3529
|
}
|
|
3438
3530
|
async onSuccess() {
|
|
3439
3531
|
const workspaces = this.context.requireWorkspaces();
|
|
3532
|
+
if (this.mode === "createRelease") {
|
|
3533
|
+
await this.step({
|
|
3534
|
+
label: "Create GitHub Releases",
|
|
3535
|
+
task: () => this.createGithubReleases(workspaces)
|
|
3536
|
+
});
|
|
3537
|
+
return;
|
|
3538
|
+
}
|
|
3440
3539
|
const releaseBranchResult = await this.step({
|
|
3441
3540
|
label: "Create Release Branch",
|
|
3442
3541
|
task: () => this.createReleaseBranch(workspaces)
|
|
@@ -3453,6 +3552,63 @@ var Github = class extends GitBase {
|
|
|
3453
3552
|
}
|
|
3454
3553
|
await this.handleReleasePullRequest(workspaces, releaseBranchResult);
|
|
3455
3554
|
}
|
|
3555
|
+
/**
|
|
3556
|
+
* Whether this workspace path is under an ignoreReleasePaths entry.
|
|
3557
|
+
*/
|
|
3558
|
+
isIgnoredReleasePath(workspace) {
|
|
3559
|
+
const ignorePaths = this.ignoreReleasePaths;
|
|
3560
|
+
if (ignorePaths.length === 0) {
|
|
3561
|
+
return false;
|
|
3562
|
+
}
|
|
3563
|
+
return ignorePaths.some(
|
|
3564
|
+
(ignorePath) => this.pather.isSubPath(workspace.path, ignorePath)
|
|
3565
|
+
);
|
|
3566
|
+
}
|
|
3567
|
+
/**
|
|
3568
|
+
* Workspaces that should receive a GitHub Release in createRelease mode.
|
|
3569
|
+
*
|
|
3570
|
+
* Requires `tagName`. Skips dependency-release packages and paths matched
|
|
3571
|
+
* by {@link GithubProps.ignoreReleasePaths}. Private packages are included
|
|
3572
|
+
* when they have a tag.
|
|
3573
|
+
*/
|
|
3574
|
+
getReleaseWorkspaces(workspaces) {
|
|
3575
|
+
return workspaces.filter((workspace) => {
|
|
3576
|
+
if (!workspace.tagName) {
|
|
3577
|
+
return false;
|
|
3578
|
+
}
|
|
3579
|
+
if (workspace.dependencyRelease) {
|
|
3580
|
+
return false;
|
|
3581
|
+
}
|
|
3582
|
+
if (this.isIgnoredReleasePath(workspace)) {
|
|
3583
|
+
this.logger.info(
|
|
3584
|
+
`Skip GitHub Release for ignored path: ${workspace.path} (${workspace.tagName})`
|
|
3585
|
+
);
|
|
3586
|
+
return false;
|
|
3587
|
+
}
|
|
3588
|
+
return true;
|
|
3589
|
+
});
|
|
3590
|
+
}
|
|
3591
|
+
/**
|
|
3592
|
+
* Create a GitHub Release for each eligible workspace.
|
|
3593
|
+
*
|
|
3594
|
+
* Release notes use the enriched workspace changelog from `onExec`.
|
|
3595
|
+
*/
|
|
3596
|
+
async createGithubReleases(workspaces) {
|
|
3597
|
+
const releaseWorkspaces = this.getReleaseWorkspaces(workspaces);
|
|
3598
|
+
if (releaseWorkspaces.length === 0) {
|
|
3599
|
+
this.logger.warn("No workspaces eligible for GitHub Release creation");
|
|
3600
|
+
return;
|
|
3601
|
+
}
|
|
3602
|
+
this.logger.info(
|
|
3603
|
+
`Creating GitHub Releases (${releaseWorkspaces.length}):
|
|
3604
|
+
${releaseWorkspaces.map((w) => ` ${w.tagName}`).join("\n")}`
|
|
3605
|
+
);
|
|
3606
|
+
await Promise.all(
|
|
3607
|
+
releaseWorkspaces.map(
|
|
3608
|
+
(workspace) => this.githubManager.createRelease(workspace)
|
|
3609
|
+
)
|
|
3610
|
+
);
|
|
3611
|
+
}
|
|
3456
3612
|
/**
|
|
3457
3613
|
* Create the release PR and optionally auto-merge it.
|
|
3458
3614
|
*
|
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
|
|
4
|
+
"version": "5.1.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
7
7
|
"homepage": "https://github.com/qlover/fe-base/tree/master/packages/fe-release",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"p-limit": "^6.2.0",
|
|
61
61
|
"semver": "^7.5.4",
|
|
62
62
|
"@qlover/env-loader": "1.0.2",
|
|
63
|
-
"@qlover/fe-corekit": "3.
|
|
63
|
+
"@qlover/fe-corekit": "3.4.4",
|
|
64
64
|
"@qlover/logger": "1.2.1",
|
|
65
65
|
"@qlover/scripts-context": "2.3.3"
|
|
66
66
|
},
|