@releasekit/release 0.6.1 → 0.7.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/chunk-6RGD5BSP.js +73 -0
- package/dist/{chunk-VAYTUKM7.js → chunk-PTL6YXWN.js} +428 -4
- package/dist/cli.js +4 -34
- package/dist/dispatcher.js +4 -2
- package/dist/index.js +2 -4
- package/docs/ci-setup.md +9 -9
- package/package.json +4 -4
- package/dist/chunk-4SIZK7PB.js +0 -46
- package/dist/chunk-H4NI763A.js +0 -438
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EXIT_CODES,
|
|
3
|
+
runPreview,
|
|
4
|
+
runRelease
|
|
5
|
+
} from "./chunk-PTL6YXWN.js";
|
|
6
|
+
|
|
7
|
+
// src/preview-command.ts
|
|
8
|
+
import { Command } from "commander";
|
|
9
|
+
function createPreviewCommand() {
|
|
10
|
+
return new Command("preview").description("Post a release preview comment on the current pull request").option("-c, --config <path>", "Path to config file").option("--project-dir <path>", "Project directory", process.cwd()).option("--pr <number>", "PR number (auto-detected from GitHub Actions)").option("--repo <owner/repo>", "Repository (auto-detected from GITHUB_REPOSITORY)").option("-p, --prerelease [identifier]", "Force prerelease preview (auto-detected by default)").option("--stable", "Force stable release preview (graduation from prerelease)", false).option(
|
|
11
|
+
"-d, --dry-run",
|
|
12
|
+
"Print the comment to stdout without posting (GitHub context not available in dry-run mode)",
|
|
13
|
+
false
|
|
14
|
+
).action(async (opts) => {
|
|
15
|
+
try {
|
|
16
|
+
await runPreview({
|
|
17
|
+
config: opts.config,
|
|
18
|
+
projectDir: opts.projectDir,
|
|
19
|
+
pr: opts.pr,
|
|
20
|
+
repo: opts.repo,
|
|
21
|
+
prerelease: opts.prerelease,
|
|
22
|
+
stable: opts.stable,
|
|
23
|
+
dryRun: opts.dryRun
|
|
24
|
+
});
|
|
25
|
+
} catch (error) {
|
|
26
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
27
|
+
process.exit(EXIT_CODES.GENERAL_ERROR);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// src/release-command.ts
|
|
33
|
+
import { Command as Command2, Option } from "commander";
|
|
34
|
+
function createReleaseCommand() {
|
|
35
|
+
return new Command2("release").description("Run the full release pipeline").option("-c, --config <path>", "Path to config file").option("-d, --dry-run", "Preview all steps without side effects", false).option("-b, --bump <type>", "Force bump type (patch|minor|major)").option("-p, --prerelease [identifier]", "Create prerelease version").option("-s, --sync", "Use synchronized versioning across all packages", false).option("-t, --target <packages>", "Target specific packages (comma-separated)").option("--branch <name>", "Override the git branch used for push").addOption(new Option("--npm-auth <method>", "NPM auth method").choices(["auto", "oidc", "token"]).default("auto")).option("--skip-notes", "Skip changelog generation", false).option("--skip-publish", "Skip registry publishing and git operations", false).option("--skip-git", "Skip git commit/tag/push", false).option("--skip-github-release", "Skip GitHub release creation", false).option("--skip-verification", "Skip post-publish verification", false).option("-j, --json", "Output results as JSON", false).option("-v, --verbose", "Verbose logging", false).option("-q, --quiet", "Suppress non-error output", false).option("--project-dir <path>", "Project directory", process.cwd()).action(async (opts) => {
|
|
36
|
+
const options = {
|
|
37
|
+
config: opts.config,
|
|
38
|
+
dryRun: opts.dryRun,
|
|
39
|
+
bump: opts.bump,
|
|
40
|
+
prerelease: opts.prerelease,
|
|
41
|
+
sync: opts.sync,
|
|
42
|
+
target: opts.target,
|
|
43
|
+
branch: opts.branch,
|
|
44
|
+
npmAuth: opts.npmAuth,
|
|
45
|
+
skipNotes: opts.skipNotes,
|
|
46
|
+
skipPublish: opts.skipPublish,
|
|
47
|
+
skipGit: opts.skipGit,
|
|
48
|
+
skipGithubRelease: opts.skipGithubRelease,
|
|
49
|
+
skipVerification: opts.skipVerification,
|
|
50
|
+
json: opts.json,
|
|
51
|
+
verbose: opts.verbose,
|
|
52
|
+
quiet: opts.quiet,
|
|
53
|
+
projectDir: opts.projectDir
|
|
54
|
+
};
|
|
55
|
+
try {
|
|
56
|
+
const result = await runRelease(options);
|
|
57
|
+
if (options.json && result) {
|
|
58
|
+
console.log(JSON.stringify(result, null, 2));
|
|
59
|
+
}
|
|
60
|
+
if (!result) {
|
|
61
|
+
process.exit(0);
|
|
62
|
+
}
|
|
63
|
+
} catch (error) {
|
|
64
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
65
|
+
process.exit(EXIT_CODES.GENERAL_ERROR);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export {
|
|
71
|
+
createPreviewCommand,
|
|
72
|
+
createReleaseCommand
|
|
73
|
+
};
|
|
@@ -645,14 +645,438 @@ async function runPublishStep(versionOutput, options, releaseNotes, additionalFi
|
|
|
645
645
|
return runPipeline(versionOutput, config, publishOptions);
|
|
646
646
|
}
|
|
647
647
|
|
|
648
|
+
// src/preview-context.ts
|
|
649
|
+
import * as fs4 from "fs";
|
|
650
|
+
function resolvePreviewContext(opts) {
|
|
651
|
+
const token = process.env.GITHUB_TOKEN;
|
|
652
|
+
if (!token) {
|
|
653
|
+
throw new Error("GITHUB_TOKEN environment variable is required");
|
|
654
|
+
}
|
|
655
|
+
const prNumber = resolvePRNumber(opts.pr);
|
|
656
|
+
const { owner, repo } = resolveRepo(opts.repo);
|
|
657
|
+
return { prNumber, owner, repo, token };
|
|
658
|
+
}
|
|
659
|
+
function resolvePRNumber(cliValue) {
|
|
660
|
+
if (cliValue) {
|
|
661
|
+
const num = Number.parseInt(cliValue, 10);
|
|
662
|
+
if (Number.isNaN(num) || num <= 0) {
|
|
663
|
+
throw new Error(`Invalid PR number: ${cliValue}`);
|
|
664
|
+
}
|
|
665
|
+
return num;
|
|
666
|
+
}
|
|
667
|
+
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
668
|
+
if (eventPath && fs4.existsSync(eventPath)) {
|
|
669
|
+
try {
|
|
670
|
+
const event = JSON.parse(fs4.readFileSync(eventPath, "utf-8"));
|
|
671
|
+
if (event.pull_request?.number) {
|
|
672
|
+
return event.pull_request.number;
|
|
673
|
+
}
|
|
674
|
+
} catch {
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
throw new Error("Could not determine PR number. Use --pr <number> or run in a GitHub Actions pull_request workflow.");
|
|
678
|
+
}
|
|
679
|
+
function resolveRepo(cliValue) {
|
|
680
|
+
const repoStr = cliValue ?? process.env.GITHUB_REPOSITORY;
|
|
681
|
+
if (!repoStr) {
|
|
682
|
+
throw new Error("Could not determine repository. Use --repo <owner/repo> or run in a GitHub Actions workflow.");
|
|
683
|
+
}
|
|
684
|
+
const parts = repoStr.split("/");
|
|
685
|
+
if (parts.length !== 2 || !parts[0] || !parts[1]) {
|
|
686
|
+
throw new Error(`Invalid repository format: ${repoStr}. Expected "owner/repo".`);
|
|
687
|
+
}
|
|
688
|
+
return { owner: parts[0], repo: parts[1] };
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// src/preview-detect.ts
|
|
692
|
+
import * as fs5 from "fs";
|
|
693
|
+
import * as path4 from "path";
|
|
694
|
+
function detectPrerelease(packagePaths, projectDir) {
|
|
695
|
+
const paths = packagePaths.length > 0 ? packagePaths.map((p) => path4.join(projectDir, p, "package.json")) : [path4.join(projectDir, "package.json")];
|
|
696
|
+
for (const pkgPath of paths) {
|
|
697
|
+
if (!fs5.existsSync(pkgPath)) continue;
|
|
698
|
+
try {
|
|
699
|
+
const pkg = JSON.parse(fs5.readFileSync(pkgPath, "utf-8"));
|
|
700
|
+
const result = parsePrerelease(pkg.version);
|
|
701
|
+
if (result.isPrerelease) return result;
|
|
702
|
+
} catch {
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
return { isPrerelease: false };
|
|
706
|
+
}
|
|
707
|
+
function parsePrerelease(version) {
|
|
708
|
+
if (!version) return { isPrerelease: false };
|
|
709
|
+
const match = version.match(/-([a-zA-Z0-9][a-zA-Z0-9-]*)(?:\.\d+)*(?:\+[^\s]+)?$/);
|
|
710
|
+
if (match) {
|
|
711
|
+
return { isPrerelease: true, identifier: match[1] };
|
|
712
|
+
}
|
|
713
|
+
return { isPrerelease: false };
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// src/preview-format.ts
|
|
717
|
+
var MARKER = "<!-- releasekit-preview -->";
|
|
718
|
+
var FOOTER = "*Updated automatically by [ReleaseKit](https://github.com/goosewobbler/releasekit)*";
|
|
719
|
+
var TYPE_LABELS = {
|
|
720
|
+
added: "Added",
|
|
721
|
+
changed: "Changed",
|
|
722
|
+
fixed: "Fixed",
|
|
723
|
+
security: "Security",
|
|
724
|
+
docs: "Documentation",
|
|
725
|
+
chore: "Chores",
|
|
726
|
+
test: "Tests",
|
|
727
|
+
feat: "Features",
|
|
728
|
+
fix: "Bug Fixes",
|
|
729
|
+
perf: "Performance",
|
|
730
|
+
refactor: "Refactoring",
|
|
731
|
+
style: "Styles",
|
|
732
|
+
build: "Build",
|
|
733
|
+
ci: "CI",
|
|
734
|
+
revert: "Reverts"
|
|
735
|
+
};
|
|
736
|
+
function getNoChangesMessage(strategy) {
|
|
737
|
+
switch (strategy) {
|
|
738
|
+
case "manual":
|
|
739
|
+
return "Run the release workflow manually if a release is needed.";
|
|
740
|
+
case "direct":
|
|
741
|
+
return "Merging this PR will not trigger a release.";
|
|
742
|
+
case "standing-pr":
|
|
743
|
+
return "Merging this PR will not affect the release PR.";
|
|
744
|
+
case "scheduled":
|
|
745
|
+
return "These changes will not be included in the next scheduled release.";
|
|
746
|
+
default:
|
|
747
|
+
return "";
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
function getIntroMessage(strategy, standingPrNumber) {
|
|
751
|
+
switch (strategy) {
|
|
752
|
+
case "direct":
|
|
753
|
+
return "This PR will trigger the following release when merged:";
|
|
754
|
+
case "standing-pr":
|
|
755
|
+
return standingPrNumber ? `These changes will be added to the release PR (#${standingPrNumber}) when merged:` : "Merging this PR will create a new release PR with the following changes:";
|
|
756
|
+
case "scheduled":
|
|
757
|
+
return "These changes will be included in the next scheduled release:";
|
|
758
|
+
default:
|
|
759
|
+
return "If released, this PR would include:";
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
function getLabelBanner(labelContext) {
|
|
763
|
+
if (!labelContext) return [];
|
|
764
|
+
if (labelContext.trigger === "commit") {
|
|
765
|
+
if (labelContext.skip) {
|
|
766
|
+
return ["> **Warning:** This PR is marked to skip release.", ""];
|
|
767
|
+
}
|
|
768
|
+
if (labelContext.bumpLabel === "major") {
|
|
769
|
+
return ["> **Important:** This PR is labeled for a **major** release.", ""];
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
if (labelContext.trigger === "label") {
|
|
773
|
+
if (labelContext.noBumpLabel) {
|
|
774
|
+
const labels = labelContext.labels;
|
|
775
|
+
const labelExamples = labels ? `\`${labels.patch}\`, \`${labels.minor}\`, or \`${labels.major}\`` : "a release label (e.g., `release:patch`, `release:minor`, `release:major`)";
|
|
776
|
+
return ["> No release label detected.", `> **Note:** Add ${labelExamples} to trigger a release.`, ""];
|
|
777
|
+
}
|
|
778
|
+
if (labelContext.bumpLabel) {
|
|
779
|
+
return [`> This PR is labeled for a **${labelContext.bumpLabel}** release.`, ""];
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
return [];
|
|
783
|
+
}
|
|
784
|
+
function formatPreviewComment(result, options) {
|
|
785
|
+
const strategy = options?.strategy ?? "direct";
|
|
786
|
+
const labelContext = options?.labelContext;
|
|
787
|
+
const lines = [MARKER, ""];
|
|
788
|
+
const banner = getLabelBanner(labelContext);
|
|
789
|
+
if (!result) {
|
|
790
|
+
lines.push("<details>", "<summary><b>Release Preview</b> \u2014 no release</summary>", "");
|
|
791
|
+
lines.push(...banner);
|
|
792
|
+
if (!labelContext?.noBumpLabel) {
|
|
793
|
+
lines.push(`> **Note:** No releasable changes detected. ${getNoChangesMessage(strategy)}`);
|
|
794
|
+
}
|
|
795
|
+
lines.push("", "---", FOOTER, "</details>");
|
|
796
|
+
return lines.join("\n");
|
|
797
|
+
}
|
|
798
|
+
const { versionOutput } = result;
|
|
799
|
+
const pkgCount = versionOutput.updates.length;
|
|
800
|
+
const pkgSummary = pkgCount === 1 ? `${versionOutput.updates[0]?.packageName} ${versionOutput.updates[0]?.newVersion}` : `${pkgCount} packages`;
|
|
801
|
+
lines.push("<details>", `<summary><b>Release Preview</b> \u2014 ${pkgSummary}</summary>`, "");
|
|
802
|
+
lines.push(...banner);
|
|
803
|
+
lines.push(getIntroMessage(strategy, options?.standingPrNumber), "");
|
|
804
|
+
lines.push("### Packages", "");
|
|
805
|
+
lines.push("| Package | Version |", "|---------|---------|");
|
|
806
|
+
for (const update of versionOutput.updates) {
|
|
807
|
+
lines.push(`| \`${update.packageName}\` | ${update.newVersion} |`);
|
|
808
|
+
}
|
|
809
|
+
lines.push("");
|
|
810
|
+
const sharedEntries = versionOutput.sharedEntries?.length ? versionOutput.sharedEntries : void 0;
|
|
811
|
+
const hasPackageChangelogs = versionOutput.changelogs.some((cl) => cl.entries.length > 0);
|
|
812
|
+
if (sharedEntries || hasPackageChangelogs) {
|
|
813
|
+
lines.push("### Changelog", "");
|
|
814
|
+
if (sharedEntries) {
|
|
815
|
+
lines.push("<details>", "<summary><b>Project-wide changes</b></summary>", "");
|
|
816
|
+
lines.push(...renderEntries(sharedEntries));
|
|
817
|
+
lines.push("</details>", "");
|
|
818
|
+
}
|
|
819
|
+
for (const changelog of versionOutput.changelogs) {
|
|
820
|
+
if (changelog.entries.length > 0) {
|
|
821
|
+
lines.push(...formatPackageChangelog(changelog));
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
if (versionOutput.tags.length > 0) {
|
|
826
|
+
lines.push("### Tags", "");
|
|
827
|
+
for (const tag of versionOutput.tags) {
|
|
828
|
+
lines.push(`- \`${tag}\``);
|
|
829
|
+
}
|
|
830
|
+
lines.push("");
|
|
831
|
+
}
|
|
832
|
+
lines.push("---", FOOTER, "</details>");
|
|
833
|
+
return lines.join("\n");
|
|
834
|
+
}
|
|
835
|
+
function renderEntries(entries) {
|
|
836
|
+
const lines = [];
|
|
837
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
838
|
+
for (const entry of entries) {
|
|
839
|
+
if (!grouped.has(entry.type)) grouped.set(entry.type, []);
|
|
840
|
+
grouped.get(entry.type)?.push(entry);
|
|
841
|
+
}
|
|
842
|
+
const renderedTypes = /* @__PURE__ */ new Set();
|
|
843
|
+
for (const type of Object.keys(TYPE_LABELS)) {
|
|
844
|
+
const group = grouped.get(type);
|
|
845
|
+
if (group && group.length > 0) {
|
|
846
|
+
lines.push(...formatEntryGroup(type, group));
|
|
847
|
+
renderedTypes.add(type);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
for (const [type, group] of grouped) {
|
|
851
|
+
if (!renderedTypes.has(type) && group.length > 0) {
|
|
852
|
+
lines.push(...formatEntryGroup(type, group));
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
return lines;
|
|
856
|
+
}
|
|
857
|
+
function formatPackageChangelog(changelog) {
|
|
858
|
+
const lines = [];
|
|
859
|
+
const prevVersion = changelog.previousVersion ?? "N/A";
|
|
860
|
+
const summary = `<b>${changelog.packageName}</b> ${prevVersion} \u2192 ${changelog.version}`;
|
|
861
|
+
lines.push("<details>", `<summary>${summary}</summary>`, "");
|
|
862
|
+
lines.push(...renderEntries(changelog.entries));
|
|
863
|
+
lines.push("</details>", "");
|
|
864
|
+
return lines;
|
|
865
|
+
}
|
|
866
|
+
function formatEntryGroup(type, entries) {
|
|
867
|
+
const label = TYPE_LABELS[type] ?? capitalize(type);
|
|
868
|
+
const lines = [`#### ${label}`, ""];
|
|
869
|
+
for (const entry of entries) {
|
|
870
|
+
let line = `- ${entry.description}`;
|
|
871
|
+
if (entry.scope) {
|
|
872
|
+
line += ` (\`${entry.scope}\`)`;
|
|
873
|
+
}
|
|
874
|
+
if (entry.issueIds && entry.issueIds.length > 0) {
|
|
875
|
+
line += ` ${entry.issueIds.join(", ")}`;
|
|
876
|
+
}
|
|
877
|
+
lines.push(line);
|
|
878
|
+
}
|
|
879
|
+
lines.push("");
|
|
880
|
+
return lines;
|
|
881
|
+
}
|
|
882
|
+
function capitalize(str) {
|
|
883
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
// src/preview-github.ts
|
|
887
|
+
import { Octokit } from "@octokit/rest";
|
|
888
|
+
function createOctokit(token) {
|
|
889
|
+
return new Octokit({ auth: token });
|
|
890
|
+
}
|
|
891
|
+
async function findPreviewComment(octokit, owner, repo, prNumber) {
|
|
892
|
+
const iterator = octokit.paginate.iterator(octokit.rest.issues.listComments, {
|
|
893
|
+
owner,
|
|
894
|
+
repo,
|
|
895
|
+
issue_number: prNumber,
|
|
896
|
+
per_page: 100
|
|
897
|
+
});
|
|
898
|
+
for await (const response of iterator) {
|
|
899
|
+
for (const comment of response.data) {
|
|
900
|
+
if (comment.body?.startsWith(MARKER)) {
|
|
901
|
+
return comment.id;
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
return null;
|
|
906
|
+
}
|
|
907
|
+
async function fetchPRLabels(octokit, owner, repo, prNumber) {
|
|
908
|
+
const { data } = await octokit.rest.issues.get({
|
|
909
|
+
owner,
|
|
910
|
+
repo,
|
|
911
|
+
issue_number: prNumber
|
|
912
|
+
});
|
|
913
|
+
return (data.labels ?? []).map((label) => typeof label === "string" ? label : label.name ?? "");
|
|
914
|
+
}
|
|
915
|
+
async function postOrUpdateComment(octokit, owner, repo, prNumber, body) {
|
|
916
|
+
const existingId = await findPreviewComment(octokit, owner, repo, prNumber);
|
|
917
|
+
if (existingId) {
|
|
918
|
+
await octokit.rest.issues.updateComment({
|
|
919
|
+
owner,
|
|
920
|
+
repo,
|
|
921
|
+
comment_id: existingId,
|
|
922
|
+
body
|
|
923
|
+
});
|
|
924
|
+
} else {
|
|
925
|
+
await octokit.rest.issues.createComment({
|
|
926
|
+
owner,
|
|
927
|
+
repo,
|
|
928
|
+
issue_number: prNumber,
|
|
929
|
+
body
|
|
930
|
+
});
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
// src/preview.ts
|
|
935
|
+
var DEFAULT_LABELS = {
|
|
936
|
+
stable: "release:stable",
|
|
937
|
+
prerelease: "release:prerelease",
|
|
938
|
+
skip: "release:skip",
|
|
939
|
+
major: "release:major",
|
|
940
|
+
minor: "release:minor",
|
|
941
|
+
patch: "release:patch"
|
|
942
|
+
};
|
|
943
|
+
async function runPreview(options) {
|
|
944
|
+
const ciConfig = loadCIConfig({ cwd: options.projectDir, configPath: options.config });
|
|
945
|
+
if (ciConfig?.prPreview === false) {
|
|
946
|
+
info("PR preview is disabled in config (ci.prPreview: false)");
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
let context;
|
|
950
|
+
let octokit;
|
|
951
|
+
if (!options.dryRun) {
|
|
952
|
+
try {
|
|
953
|
+
context = resolvePreviewContext({ pr: options.pr, repo: options.repo });
|
|
954
|
+
octokit = createOctokit(context.token);
|
|
955
|
+
} catch (error2) {
|
|
956
|
+
warn(`Cannot post PR comment: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
const { options: effectiveOptions, labelContext } = await applyLabelOverrides(options, ciConfig, context, octokit);
|
|
960
|
+
const strategy = ciConfig?.releaseStrategy ?? "direct";
|
|
961
|
+
let result = null;
|
|
962
|
+
if (!labelContext.noBumpLabel) {
|
|
963
|
+
const releaseConfig = loadConfig({ cwd: effectiveOptions.projectDir, configPath: effectiveOptions.config });
|
|
964
|
+
const prereleaseFlag = resolvePrerelease(
|
|
965
|
+
effectiveOptions,
|
|
966
|
+
releaseConfig.version?.packages ?? [],
|
|
967
|
+
effectiveOptions.projectDir
|
|
968
|
+
);
|
|
969
|
+
info("Analyzing release...");
|
|
970
|
+
result = await runRelease({
|
|
971
|
+
config: effectiveOptions.config,
|
|
972
|
+
dryRun: true,
|
|
973
|
+
sync: false,
|
|
974
|
+
bump: effectiveOptions.bump,
|
|
975
|
+
prerelease: prereleaseFlag,
|
|
976
|
+
skipNotes: true,
|
|
977
|
+
skipPublish: true,
|
|
978
|
+
skipGit: true,
|
|
979
|
+
skipGithubRelease: true,
|
|
980
|
+
skipVerification: true,
|
|
981
|
+
json: false,
|
|
982
|
+
verbose: false,
|
|
983
|
+
quiet: true,
|
|
984
|
+
projectDir: effectiveOptions.projectDir
|
|
985
|
+
});
|
|
986
|
+
} else {
|
|
987
|
+
info("No release label detected \u2014 skipping version analysis");
|
|
988
|
+
}
|
|
989
|
+
const commentBody = formatPreviewComment(result, { strategy, labelContext });
|
|
990
|
+
if (!context || !octokit) {
|
|
991
|
+
console.log(commentBody);
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
994
|
+
info(`Posting preview comment on PR #${context.prNumber}...`);
|
|
995
|
+
await postOrUpdateComment(octokit, context.owner, context.repo, context.prNumber, commentBody);
|
|
996
|
+
success(`Preview comment posted on PR #${context.prNumber}`);
|
|
997
|
+
}
|
|
998
|
+
function resolvePrerelease(options, packagePaths, projectDir) {
|
|
999
|
+
if (options.stable) {
|
|
1000
|
+
return void 0;
|
|
1001
|
+
}
|
|
1002
|
+
if (options.prerelease !== void 0) {
|
|
1003
|
+
return options.prerelease;
|
|
1004
|
+
}
|
|
1005
|
+
const detected = detectPrerelease(packagePaths, projectDir);
|
|
1006
|
+
if (detected.isPrerelease) {
|
|
1007
|
+
info(`Detected prerelease version (identifier: ${detected.identifier})`);
|
|
1008
|
+
return detected.identifier;
|
|
1009
|
+
}
|
|
1010
|
+
return void 0;
|
|
1011
|
+
}
|
|
1012
|
+
async function applyLabelOverrides(options, ciConfig, context, existingOctokit) {
|
|
1013
|
+
const trigger = ciConfig?.releaseTrigger ?? "label";
|
|
1014
|
+
const labels = ciConfig?.labels ?? DEFAULT_LABELS;
|
|
1015
|
+
const defaultLabelContext = { trigger, skip: false, noBumpLabel: false };
|
|
1016
|
+
if (!context) {
|
|
1017
|
+
return {
|
|
1018
|
+
options,
|
|
1019
|
+
labelContext: { ...defaultLabelContext, noBumpLabel: trigger === "label" && !options.bump, labels }
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
1022
|
+
let prLabels;
|
|
1023
|
+
const octokitToUse = existingOctokit ?? createOctokit(context.token);
|
|
1024
|
+
try {
|
|
1025
|
+
prLabels = await fetchPRLabels(octokitToUse, context.owner, context.repo, context.prNumber);
|
|
1026
|
+
} catch {
|
|
1027
|
+
warn("Could not fetch PR labels \u2014 skipping label-driven overrides");
|
|
1028
|
+
return {
|
|
1029
|
+
options,
|
|
1030
|
+
labelContext: { ...defaultLabelContext, noBumpLabel: trigger === "label", labels }
|
|
1031
|
+
};
|
|
1032
|
+
}
|
|
1033
|
+
const result = { ...options };
|
|
1034
|
+
const labelContext = { trigger, skip: false, noBumpLabel: false, labels };
|
|
1035
|
+
if (trigger === "commit") {
|
|
1036
|
+
if (prLabels.includes(labels.skip)) {
|
|
1037
|
+
info(`PR label "${labels.skip}" detected \u2014 release will be skipped`);
|
|
1038
|
+
labelContext.skip = true;
|
|
1039
|
+
}
|
|
1040
|
+
if (!labelContext.skip && prLabels.includes(labels.major)) {
|
|
1041
|
+
info(`PR label "${labels.major}" detected \u2014 forcing major release`);
|
|
1042
|
+
labelContext.bumpLabel = "major";
|
|
1043
|
+
result.bump = "major";
|
|
1044
|
+
}
|
|
1045
|
+
} else {
|
|
1046
|
+
if (prLabels.includes(labels.major)) {
|
|
1047
|
+
info(`PR label "${labels.major}" detected \u2014 major release`);
|
|
1048
|
+
labelContext.bumpLabel = "major";
|
|
1049
|
+
result.bump = "major";
|
|
1050
|
+
} else if (prLabels.includes(labels.minor)) {
|
|
1051
|
+
info(`PR label "${labels.minor}" detected \u2014 minor release`);
|
|
1052
|
+
labelContext.bumpLabel = "minor";
|
|
1053
|
+
result.bump = "minor";
|
|
1054
|
+
} else if (prLabels.includes(labels.patch)) {
|
|
1055
|
+
info(`PR label "${labels.patch}" detected \u2014 patch release`);
|
|
1056
|
+
labelContext.bumpLabel = "patch";
|
|
1057
|
+
result.bump = "patch";
|
|
1058
|
+
} else {
|
|
1059
|
+
labelContext.noBumpLabel = true;
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
if (!options.stable && options.prerelease === void 0) {
|
|
1063
|
+
if (prLabels.includes(labels.stable)) {
|
|
1064
|
+
info(`PR label "${labels.stable}" detected \u2014 using stable release preview`);
|
|
1065
|
+
result.stable = true;
|
|
1066
|
+
} else if (prLabels.includes(labels.prerelease)) {
|
|
1067
|
+
info(`PR label "${labels.prerelease}" detected \u2014 using prerelease preview`);
|
|
1068
|
+
result.prerelease = true;
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
return { options: result, labelContext: { ...labelContext, labels } };
|
|
1072
|
+
}
|
|
1073
|
+
|
|
648
1074
|
export {
|
|
649
1075
|
readPackageVersion,
|
|
650
1076
|
error,
|
|
651
|
-
warn,
|
|
652
1077
|
info,
|
|
653
1078
|
success,
|
|
654
1079
|
EXIT_CODES,
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
runRelease
|
|
1080
|
+
runRelease,
|
|
1081
|
+
runPreview
|
|
658
1082
|
};
|
package/dist/cli.js
CHANGED
|
@@ -1,48 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
createPreviewCommand,
|
|
3
4
|
createReleaseCommand
|
|
4
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-6RGD5BSP.js";
|
|
5
6
|
import {
|
|
6
|
-
runPreview
|
|
7
|
-
} from "./chunk-H4NI763A.js";
|
|
8
|
-
import {
|
|
9
|
-
EXIT_CODES,
|
|
10
7
|
readPackageVersion
|
|
11
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-PTL6YXWN.js";
|
|
12
9
|
|
|
13
10
|
// src/cli.ts
|
|
14
11
|
import { realpathSync } from "fs";
|
|
15
12
|
import { fileURLToPath } from "url";
|
|
16
|
-
import { Command as Command2 } from "commander";
|
|
17
|
-
|
|
18
|
-
// src/preview-command.ts
|
|
19
13
|
import { Command } from "commander";
|
|
20
|
-
function createPreviewCommand() {
|
|
21
|
-
return new Command("preview").description("Post a release preview comment on the current pull request").option("-c, --config <path>", "Path to config file").option("--project-dir <path>", "Project directory", process.cwd()).option("--pr <number>", "PR number (auto-detected from GitHub Actions)").option("--repo <owner/repo>", "Repository (auto-detected from GITHUB_REPOSITORY)").option("-p, --prerelease [identifier]", "Force prerelease preview (auto-detected by default)").option("--stable", "Force stable release preview (graduation from prerelease)", false).option(
|
|
22
|
-
"-d, --dry-run",
|
|
23
|
-
"Print the comment to stdout without posting (GitHub context not available in dry-run mode)",
|
|
24
|
-
false
|
|
25
|
-
).action(async (opts) => {
|
|
26
|
-
try {
|
|
27
|
-
await runPreview({
|
|
28
|
-
config: opts.config,
|
|
29
|
-
projectDir: opts.projectDir,
|
|
30
|
-
pr: opts.pr,
|
|
31
|
-
repo: opts.repo,
|
|
32
|
-
prerelease: opts.prerelease,
|
|
33
|
-
stable: opts.stable,
|
|
34
|
-
dryRun: opts.dryRun
|
|
35
|
-
});
|
|
36
|
-
} catch (error) {
|
|
37
|
-
console.error(error instanceof Error ? error.message : String(error));
|
|
38
|
-
process.exit(EXIT_CODES.GENERAL_ERROR);
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// src/cli.ts
|
|
44
14
|
function createReleaseProgram() {
|
|
45
|
-
return new
|
|
15
|
+
return new Command().name("releasekit-release").description("Unified release pipeline: version, changelog, and publish").version(readPackageVersion(import.meta.url)).addCommand(createReleaseCommand(), { isDefault: true }).addCommand(createPreviewCommand());
|
|
46
16
|
}
|
|
47
17
|
var isMain = (() => {
|
|
48
18
|
try {
|
package/dist/dispatcher.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
createPreviewCommand,
|
|
3
4
|
createReleaseCommand
|
|
4
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-6RGD5BSP.js";
|
|
5
6
|
import {
|
|
6
7
|
EXIT_CODES,
|
|
7
8
|
error,
|
|
8
9
|
info,
|
|
9
10
|
readPackageVersion,
|
|
10
11
|
success
|
|
11
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-PTL6YXWN.js";
|
|
12
13
|
|
|
13
14
|
// src/dispatcher.ts
|
|
14
15
|
import { realpathSync } from "fs";
|
|
@@ -71,6 +72,7 @@ function createInitCommand() {
|
|
|
71
72
|
function createDispatcherProgram() {
|
|
72
73
|
const program = new Command2().name("releasekit").description("Unified release pipeline: version, changelog, and publish").version(readPackageVersion(import.meta.url));
|
|
73
74
|
program.addCommand(createReleaseCommand(), { isDefault: true });
|
|
75
|
+
program.addCommand(createPreviewCommand());
|
|
74
76
|
program.addCommand(createInitCommand());
|
|
75
77
|
program.addCommand(createVersionCommand());
|
|
76
78
|
program.addCommand(createNotesCommand());
|
package/dist/index.js
CHANGED
package/docs/ci-setup.md
CHANGED
|
@@ -32,11 +32,11 @@ jobs:
|
|
|
32
32
|
release:
|
|
33
33
|
runs-on: ubuntu-latest
|
|
34
34
|
steps:
|
|
35
|
-
- uses: actions/checkout@
|
|
35
|
+
- uses: actions/checkout@v6
|
|
36
36
|
with:
|
|
37
37
|
fetch-depth: 0
|
|
38
38
|
|
|
39
|
-
- uses: actions/setup-node@
|
|
39
|
+
- uses: actions/setup-node@v6
|
|
40
40
|
with:
|
|
41
41
|
node-version: '20'
|
|
42
42
|
registry-url: 'https://registry.npmjs.org'
|
|
@@ -84,11 +84,11 @@ jobs:
|
|
|
84
84
|
release:
|
|
85
85
|
runs-on: ubuntu-latest
|
|
86
86
|
steps:
|
|
87
|
-
- uses: actions/checkout@
|
|
87
|
+
- uses: actions/checkout@v6
|
|
88
88
|
with:
|
|
89
89
|
fetch-depth: 0
|
|
90
90
|
|
|
91
|
-
- uses: actions/setup-node@
|
|
91
|
+
- uses: actions/setup-node@v6
|
|
92
92
|
with:
|
|
93
93
|
node-version: '20'
|
|
94
94
|
registry-url: 'https://registry.npmjs.org'
|
|
@@ -147,11 +147,11 @@ jobs:
|
|
|
147
147
|
preview:
|
|
148
148
|
runs-on: ubuntu-latest
|
|
149
149
|
steps:
|
|
150
|
-
- uses: actions/checkout@
|
|
150
|
+
- uses: actions/checkout@v6
|
|
151
151
|
with:
|
|
152
152
|
fetch-depth: 0
|
|
153
153
|
|
|
154
|
-
- uses: actions/setup-node@
|
|
154
|
+
- uses: actions/setup-node@v6
|
|
155
155
|
with:
|
|
156
156
|
node-version: '20'
|
|
157
157
|
|
|
@@ -182,7 +182,7 @@ permissions:
|
|
|
182
182
|
id-token: write # grants the OIDC token
|
|
183
183
|
|
|
184
184
|
steps:
|
|
185
|
-
- uses: actions/setup-node@
|
|
185
|
+
- uses: actions/setup-node@v6
|
|
186
186
|
with:
|
|
187
187
|
node-version: '20'
|
|
188
188
|
registry-url: 'https://registry.npmjs.org'
|
|
@@ -221,11 +221,11 @@ jobs:
|
|
|
221
221
|
prerelease:
|
|
222
222
|
runs-on: ubuntu-latest
|
|
223
223
|
steps:
|
|
224
|
-
- uses: actions/checkout@
|
|
224
|
+
- uses: actions/checkout@v6
|
|
225
225
|
with:
|
|
226
226
|
fetch-depth: 0
|
|
227
227
|
|
|
228
|
-
- uses: actions/setup-node@
|
|
228
|
+
- uses: actions/setup-node@v6
|
|
229
229
|
with:
|
|
230
230
|
node-version: '20'
|
|
231
231
|
registry-url: 'https://registry.npmjs.org'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@releasekit/release",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "Unified release pipeline: version, changelog, and publish in a single command",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -50,9 +50,9 @@
|
|
|
50
50
|
"commander": "^14.0.3",
|
|
51
51
|
"smol-toml": "^1.6.1",
|
|
52
52
|
"zod": "^4.3.6",
|
|
53
|
-
"@releasekit/
|
|
54
|
-
"@releasekit/
|
|
55
|
-
"@releasekit/
|
|
53
|
+
"@releasekit/version": "0.7.1",
|
|
54
|
+
"@releasekit/publish": "0.7.1",
|
|
55
|
+
"@releasekit/notes": "0.7.1"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
58
|
"@biomejs/biome": "^2.4.6",
|
package/dist/chunk-4SIZK7PB.js
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
EXIT_CODES,
|
|
3
|
-
runRelease
|
|
4
|
-
} from "./chunk-VAYTUKM7.js";
|
|
5
|
-
|
|
6
|
-
// src/release-command.ts
|
|
7
|
-
import { Command, Option } from "commander";
|
|
8
|
-
function createReleaseCommand() {
|
|
9
|
-
return new Command("release").description("Run the full release pipeline").option("-c, --config <path>", "Path to config file").option("-d, --dry-run", "Preview all steps without side effects", false).option("-b, --bump <type>", "Force bump type (patch|minor|major)").option("-p, --prerelease [identifier]", "Create prerelease version").option("-s, --sync", "Use synchronized versioning across all packages", false).option("-t, --target <packages>", "Target specific packages (comma-separated)").option("--branch <name>", "Override the git branch used for push").addOption(new Option("--npm-auth <method>", "NPM auth method").choices(["auto", "oidc", "token"]).default("auto")).option("--skip-notes", "Skip changelog generation", false).option("--skip-publish", "Skip registry publishing and git operations", false).option("--skip-git", "Skip git commit/tag/push", false).option("--skip-github-release", "Skip GitHub release creation", false).option("--skip-verification", "Skip post-publish verification", false).option("-j, --json", "Output results as JSON", false).option("-v, --verbose", "Verbose logging", false).option("-q, --quiet", "Suppress non-error output", false).option("--project-dir <path>", "Project directory", process.cwd()).action(async (opts) => {
|
|
10
|
-
const options = {
|
|
11
|
-
config: opts.config,
|
|
12
|
-
dryRun: opts.dryRun,
|
|
13
|
-
bump: opts.bump,
|
|
14
|
-
prerelease: opts.prerelease,
|
|
15
|
-
sync: opts.sync,
|
|
16
|
-
target: opts.target,
|
|
17
|
-
branch: opts.branch,
|
|
18
|
-
npmAuth: opts.npmAuth,
|
|
19
|
-
skipNotes: opts.skipNotes,
|
|
20
|
-
skipPublish: opts.skipPublish,
|
|
21
|
-
skipGit: opts.skipGit,
|
|
22
|
-
skipGithubRelease: opts.skipGithubRelease,
|
|
23
|
-
skipVerification: opts.skipVerification,
|
|
24
|
-
json: opts.json,
|
|
25
|
-
verbose: opts.verbose,
|
|
26
|
-
quiet: opts.quiet,
|
|
27
|
-
projectDir: opts.projectDir
|
|
28
|
-
};
|
|
29
|
-
try {
|
|
30
|
-
const result = await runRelease(options);
|
|
31
|
-
if (options.json && result) {
|
|
32
|
-
console.log(JSON.stringify(result, null, 2));
|
|
33
|
-
}
|
|
34
|
-
if (!result) {
|
|
35
|
-
process.exit(0);
|
|
36
|
-
}
|
|
37
|
-
} catch (error) {
|
|
38
|
-
console.error(error instanceof Error ? error.message : String(error));
|
|
39
|
-
process.exit(EXIT_CODES.GENERAL_ERROR);
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export {
|
|
45
|
-
createReleaseCommand
|
|
46
|
-
};
|
package/dist/chunk-H4NI763A.js
DELETED
|
@@ -1,438 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
info,
|
|
3
|
-
loadCIConfig,
|
|
4
|
-
loadConfig,
|
|
5
|
-
runRelease,
|
|
6
|
-
success,
|
|
7
|
-
warn
|
|
8
|
-
} from "./chunk-VAYTUKM7.js";
|
|
9
|
-
|
|
10
|
-
// src/preview-context.ts
|
|
11
|
-
import * as fs from "fs";
|
|
12
|
-
function resolvePreviewContext(opts) {
|
|
13
|
-
const token = process.env.GITHUB_TOKEN;
|
|
14
|
-
if (!token) {
|
|
15
|
-
throw new Error("GITHUB_TOKEN environment variable is required");
|
|
16
|
-
}
|
|
17
|
-
const prNumber = resolvePRNumber(opts.pr);
|
|
18
|
-
const { owner, repo } = resolveRepo(opts.repo);
|
|
19
|
-
return { prNumber, owner, repo, token };
|
|
20
|
-
}
|
|
21
|
-
function resolvePRNumber(cliValue) {
|
|
22
|
-
if (cliValue) {
|
|
23
|
-
const num = Number.parseInt(cliValue, 10);
|
|
24
|
-
if (Number.isNaN(num) || num <= 0) {
|
|
25
|
-
throw new Error(`Invalid PR number: ${cliValue}`);
|
|
26
|
-
}
|
|
27
|
-
return num;
|
|
28
|
-
}
|
|
29
|
-
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
30
|
-
if (eventPath && fs.existsSync(eventPath)) {
|
|
31
|
-
try {
|
|
32
|
-
const event = JSON.parse(fs.readFileSync(eventPath, "utf-8"));
|
|
33
|
-
if (event.pull_request?.number) {
|
|
34
|
-
return event.pull_request.number;
|
|
35
|
-
}
|
|
36
|
-
} catch {
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
throw new Error("Could not determine PR number. Use --pr <number> or run in a GitHub Actions pull_request workflow.");
|
|
40
|
-
}
|
|
41
|
-
function resolveRepo(cliValue) {
|
|
42
|
-
const repoStr = cliValue ?? process.env.GITHUB_REPOSITORY;
|
|
43
|
-
if (!repoStr) {
|
|
44
|
-
throw new Error("Could not determine repository. Use --repo <owner/repo> or run in a GitHub Actions workflow.");
|
|
45
|
-
}
|
|
46
|
-
const parts = repoStr.split("/");
|
|
47
|
-
if (parts.length !== 2 || !parts[0] || !parts[1]) {
|
|
48
|
-
throw new Error(`Invalid repository format: ${repoStr}. Expected "owner/repo".`);
|
|
49
|
-
}
|
|
50
|
-
return { owner: parts[0], repo: parts[1] };
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// src/preview-detect.ts
|
|
54
|
-
import * as fs2 from "fs";
|
|
55
|
-
import * as path from "path";
|
|
56
|
-
function detectPrerelease(packagePaths, projectDir) {
|
|
57
|
-
const paths = packagePaths.length > 0 ? packagePaths.map((p) => path.join(projectDir, p, "package.json")) : [path.join(projectDir, "package.json")];
|
|
58
|
-
for (const pkgPath of paths) {
|
|
59
|
-
if (!fs2.existsSync(pkgPath)) continue;
|
|
60
|
-
try {
|
|
61
|
-
const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf-8"));
|
|
62
|
-
const result = parsePrerelease(pkg.version);
|
|
63
|
-
if (result.isPrerelease) return result;
|
|
64
|
-
} catch {
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
return { isPrerelease: false };
|
|
68
|
-
}
|
|
69
|
-
function parsePrerelease(version) {
|
|
70
|
-
if (!version) return { isPrerelease: false };
|
|
71
|
-
const match = version.match(/-([a-zA-Z0-9][a-zA-Z0-9-]*)(?:\.\d+)*(?:\+[^\s]+)?$/);
|
|
72
|
-
if (match) {
|
|
73
|
-
return { isPrerelease: true, identifier: match[1] };
|
|
74
|
-
}
|
|
75
|
-
return { isPrerelease: false };
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// src/preview-format.ts
|
|
79
|
-
var MARKER = "<!-- releasekit-preview -->";
|
|
80
|
-
var FOOTER = "*Updated automatically by [ReleaseKit](https://github.com/goosewobbler/releasekit)*";
|
|
81
|
-
var TYPE_LABELS = {
|
|
82
|
-
added: "Added",
|
|
83
|
-
changed: "Changed",
|
|
84
|
-
fixed: "Fixed",
|
|
85
|
-
security: "Security",
|
|
86
|
-
docs: "Documentation",
|
|
87
|
-
chore: "Chores",
|
|
88
|
-
test: "Tests",
|
|
89
|
-
feat: "Features",
|
|
90
|
-
fix: "Bug Fixes",
|
|
91
|
-
perf: "Performance",
|
|
92
|
-
refactor: "Refactoring",
|
|
93
|
-
style: "Styles",
|
|
94
|
-
build: "Build",
|
|
95
|
-
ci: "CI",
|
|
96
|
-
revert: "Reverts"
|
|
97
|
-
};
|
|
98
|
-
function getNoChangesMessage(strategy) {
|
|
99
|
-
switch (strategy) {
|
|
100
|
-
case "manual":
|
|
101
|
-
return "Run the release workflow manually if a release is needed.";
|
|
102
|
-
case "direct":
|
|
103
|
-
return "Merging this PR will not trigger a release.";
|
|
104
|
-
case "standing-pr":
|
|
105
|
-
return "Merging this PR will not affect the release PR.";
|
|
106
|
-
case "scheduled":
|
|
107
|
-
return "These changes will not be included in the next scheduled release.";
|
|
108
|
-
default:
|
|
109
|
-
return "";
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
function getIntroMessage(strategy, standingPrNumber) {
|
|
113
|
-
switch (strategy) {
|
|
114
|
-
case "direct":
|
|
115
|
-
return "This PR will trigger the following release when merged:";
|
|
116
|
-
case "standing-pr":
|
|
117
|
-
return standingPrNumber ? `These changes will be added to the release PR (#${standingPrNumber}) when merged:` : "Merging this PR will create a new release PR with the following changes:";
|
|
118
|
-
case "scheduled":
|
|
119
|
-
return "These changes will be included in the next scheduled release:";
|
|
120
|
-
default:
|
|
121
|
-
return "If released, this PR would include:";
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
function getLabelBanner(labelContext) {
|
|
125
|
-
if (!labelContext) return [];
|
|
126
|
-
if (labelContext.trigger === "commit") {
|
|
127
|
-
if (labelContext.skip) {
|
|
128
|
-
return ["> **Warning:** This PR is marked to skip release.", ""];
|
|
129
|
-
}
|
|
130
|
-
if (labelContext.bumpLabel === "major") {
|
|
131
|
-
return ["> **Important:** This PR is labeled for a **major** release.", ""];
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
if (labelContext.trigger === "label") {
|
|
135
|
-
if (labelContext.noBumpLabel) {
|
|
136
|
-
const labels = labelContext.labels;
|
|
137
|
-
const labelExamples = labels ? `\`${labels.patch}\`, \`${labels.minor}\`, or \`${labels.major}\`` : "a release label (e.g., `release:patch`, `release:minor`, `release:major`)";
|
|
138
|
-
return ["> No release label detected.", `> **Note:** Add ${labelExamples} to trigger a release.`, ""];
|
|
139
|
-
}
|
|
140
|
-
if (labelContext.bumpLabel) {
|
|
141
|
-
return [`> This PR is labeled for a **${labelContext.bumpLabel}** release.`, ""];
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
return [];
|
|
145
|
-
}
|
|
146
|
-
function formatPreviewComment(result, options) {
|
|
147
|
-
const strategy = options?.strategy ?? "direct";
|
|
148
|
-
const labelContext = options?.labelContext;
|
|
149
|
-
const lines = [MARKER, ""];
|
|
150
|
-
const banner = getLabelBanner(labelContext);
|
|
151
|
-
if (!result) {
|
|
152
|
-
lines.push("<details>", "<summary><b>Release Preview</b> \u2014 no release</summary>", "");
|
|
153
|
-
lines.push(...banner);
|
|
154
|
-
if (!labelContext?.noBumpLabel) {
|
|
155
|
-
lines.push(`> **Note:** No releasable changes detected. ${getNoChangesMessage(strategy)}`);
|
|
156
|
-
}
|
|
157
|
-
lines.push("", "---", FOOTER, "</details>");
|
|
158
|
-
return lines.join("\n");
|
|
159
|
-
}
|
|
160
|
-
const { versionOutput } = result;
|
|
161
|
-
const pkgCount = versionOutput.updates.length;
|
|
162
|
-
const pkgSummary = pkgCount === 1 ? `${versionOutput.updates[0]?.packageName} ${versionOutput.updates[0]?.newVersion}` : `${pkgCount} packages`;
|
|
163
|
-
lines.push("<details>", `<summary><b>Release Preview</b> \u2014 ${pkgSummary}</summary>`, "");
|
|
164
|
-
lines.push(...banner);
|
|
165
|
-
lines.push(getIntroMessage(strategy, options?.standingPrNumber), "");
|
|
166
|
-
lines.push("### Packages", "");
|
|
167
|
-
lines.push("| Package | Version |", "|---------|---------|");
|
|
168
|
-
for (const update of versionOutput.updates) {
|
|
169
|
-
lines.push(`| \`${update.packageName}\` | ${update.newVersion} |`);
|
|
170
|
-
}
|
|
171
|
-
lines.push("");
|
|
172
|
-
const sharedEntries = versionOutput.sharedEntries?.length ? versionOutput.sharedEntries : void 0;
|
|
173
|
-
const hasPackageChangelogs = versionOutput.changelogs.some((cl) => cl.entries.length > 0);
|
|
174
|
-
if (sharedEntries || hasPackageChangelogs) {
|
|
175
|
-
lines.push("### Changelog", "");
|
|
176
|
-
if (sharedEntries) {
|
|
177
|
-
lines.push("<details>", "<summary><b>Project-wide changes</b></summary>", "");
|
|
178
|
-
lines.push(...renderEntries(sharedEntries));
|
|
179
|
-
lines.push("</details>", "");
|
|
180
|
-
}
|
|
181
|
-
for (const changelog of versionOutput.changelogs) {
|
|
182
|
-
if (changelog.entries.length > 0) {
|
|
183
|
-
lines.push(...formatPackageChangelog(changelog));
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
if (versionOutput.tags.length > 0) {
|
|
188
|
-
lines.push("### Tags", "");
|
|
189
|
-
for (const tag of versionOutput.tags) {
|
|
190
|
-
lines.push(`- \`${tag}\``);
|
|
191
|
-
}
|
|
192
|
-
lines.push("");
|
|
193
|
-
}
|
|
194
|
-
lines.push("---", FOOTER, "</details>");
|
|
195
|
-
return lines.join("\n");
|
|
196
|
-
}
|
|
197
|
-
function renderEntries(entries) {
|
|
198
|
-
const lines = [];
|
|
199
|
-
const grouped = /* @__PURE__ */ new Map();
|
|
200
|
-
for (const entry of entries) {
|
|
201
|
-
if (!grouped.has(entry.type)) grouped.set(entry.type, []);
|
|
202
|
-
grouped.get(entry.type)?.push(entry);
|
|
203
|
-
}
|
|
204
|
-
const renderedTypes = /* @__PURE__ */ new Set();
|
|
205
|
-
for (const type of Object.keys(TYPE_LABELS)) {
|
|
206
|
-
const group = grouped.get(type);
|
|
207
|
-
if (group && group.length > 0) {
|
|
208
|
-
lines.push(...formatEntryGroup(type, group));
|
|
209
|
-
renderedTypes.add(type);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
for (const [type, group] of grouped) {
|
|
213
|
-
if (!renderedTypes.has(type) && group.length > 0) {
|
|
214
|
-
lines.push(...formatEntryGroup(type, group));
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
return lines;
|
|
218
|
-
}
|
|
219
|
-
function formatPackageChangelog(changelog) {
|
|
220
|
-
const lines = [];
|
|
221
|
-
const prevVersion = changelog.previousVersion ?? "N/A";
|
|
222
|
-
const summary = `<b>${changelog.packageName}</b> ${prevVersion} \u2192 ${changelog.version}`;
|
|
223
|
-
lines.push("<details>", `<summary>${summary}</summary>`, "");
|
|
224
|
-
lines.push(...renderEntries(changelog.entries));
|
|
225
|
-
lines.push("</details>", "");
|
|
226
|
-
return lines;
|
|
227
|
-
}
|
|
228
|
-
function formatEntryGroup(type, entries) {
|
|
229
|
-
const label = TYPE_LABELS[type] ?? capitalize(type);
|
|
230
|
-
const lines = [`#### ${label}`, ""];
|
|
231
|
-
for (const entry of entries) {
|
|
232
|
-
let line = `- ${entry.description}`;
|
|
233
|
-
if (entry.scope) {
|
|
234
|
-
line += ` (\`${entry.scope}\`)`;
|
|
235
|
-
}
|
|
236
|
-
if (entry.issueIds && entry.issueIds.length > 0) {
|
|
237
|
-
line += ` ${entry.issueIds.join(", ")}`;
|
|
238
|
-
}
|
|
239
|
-
lines.push(line);
|
|
240
|
-
}
|
|
241
|
-
lines.push("");
|
|
242
|
-
return lines;
|
|
243
|
-
}
|
|
244
|
-
function capitalize(str) {
|
|
245
|
-
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// src/preview-github.ts
|
|
249
|
-
import { Octokit } from "@octokit/rest";
|
|
250
|
-
function createOctokit(token) {
|
|
251
|
-
return new Octokit({ auth: token });
|
|
252
|
-
}
|
|
253
|
-
async function findPreviewComment(octokit, owner, repo, prNumber) {
|
|
254
|
-
const iterator = octokit.paginate.iterator(octokit.rest.issues.listComments, {
|
|
255
|
-
owner,
|
|
256
|
-
repo,
|
|
257
|
-
issue_number: prNumber,
|
|
258
|
-
per_page: 100
|
|
259
|
-
});
|
|
260
|
-
for await (const response of iterator) {
|
|
261
|
-
for (const comment of response.data) {
|
|
262
|
-
if (comment.body?.startsWith(MARKER)) {
|
|
263
|
-
return comment.id;
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
return null;
|
|
268
|
-
}
|
|
269
|
-
async function fetchPRLabels(octokit, owner, repo, prNumber) {
|
|
270
|
-
const { data } = await octokit.rest.issues.get({
|
|
271
|
-
owner,
|
|
272
|
-
repo,
|
|
273
|
-
issue_number: prNumber
|
|
274
|
-
});
|
|
275
|
-
return (data.labels ?? []).map((label) => typeof label === "string" ? label : label.name ?? "");
|
|
276
|
-
}
|
|
277
|
-
async function postOrUpdateComment(octokit, owner, repo, prNumber, body) {
|
|
278
|
-
const existingId = await findPreviewComment(octokit, owner, repo, prNumber);
|
|
279
|
-
if (existingId) {
|
|
280
|
-
await octokit.rest.issues.updateComment({
|
|
281
|
-
owner,
|
|
282
|
-
repo,
|
|
283
|
-
comment_id: existingId,
|
|
284
|
-
body
|
|
285
|
-
});
|
|
286
|
-
} else {
|
|
287
|
-
await octokit.rest.issues.createComment({
|
|
288
|
-
owner,
|
|
289
|
-
repo,
|
|
290
|
-
issue_number: prNumber,
|
|
291
|
-
body
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
// src/preview.ts
|
|
297
|
-
var DEFAULT_LABELS = {
|
|
298
|
-
stable: "release:stable",
|
|
299
|
-
prerelease: "release:prerelease",
|
|
300
|
-
skip: "release:skip",
|
|
301
|
-
major: "release:major",
|
|
302
|
-
minor: "release:minor",
|
|
303
|
-
patch: "release:patch"
|
|
304
|
-
};
|
|
305
|
-
async function runPreview(options) {
|
|
306
|
-
const ciConfig = loadCIConfig({ cwd: options.projectDir, configPath: options.config });
|
|
307
|
-
if (ciConfig?.prPreview === false) {
|
|
308
|
-
info("PR preview is disabled in config (ci.prPreview: false)");
|
|
309
|
-
return;
|
|
310
|
-
}
|
|
311
|
-
let context;
|
|
312
|
-
let octokit;
|
|
313
|
-
if (!options.dryRun) {
|
|
314
|
-
try {
|
|
315
|
-
context = resolvePreviewContext({ pr: options.pr, repo: options.repo });
|
|
316
|
-
octokit = createOctokit(context.token);
|
|
317
|
-
} catch (error) {
|
|
318
|
-
warn(`Cannot post PR comment: ${error instanceof Error ? error.message : String(error)}`);
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
const { options: effectiveOptions, labelContext } = await applyLabelOverrides(options, ciConfig, context, octokit);
|
|
322
|
-
const strategy = ciConfig?.releaseStrategy ?? "direct";
|
|
323
|
-
let result = null;
|
|
324
|
-
if (!labelContext.noBumpLabel) {
|
|
325
|
-
const releaseConfig = loadConfig({ cwd: effectiveOptions.projectDir, configPath: effectiveOptions.config });
|
|
326
|
-
const prereleaseFlag = resolvePrerelease(
|
|
327
|
-
effectiveOptions,
|
|
328
|
-
releaseConfig.version?.packages ?? [],
|
|
329
|
-
effectiveOptions.projectDir
|
|
330
|
-
);
|
|
331
|
-
info("Analyzing release...");
|
|
332
|
-
result = await runRelease({
|
|
333
|
-
config: effectiveOptions.config,
|
|
334
|
-
dryRun: true,
|
|
335
|
-
sync: false,
|
|
336
|
-
bump: effectiveOptions.bump,
|
|
337
|
-
prerelease: prereleaseFlag,
|
|
338
|
-
skipNotes: true,
|
|
339
|
-
skipPublish: true,
|
|
340
|
-
skipGit: true,
|
|
341
|
-
skipGithubRelease: true,
|
|
342
|
-
skipVerification: true,
|
|
343
|
-
json: false,
|
|
344
|
-
verbose: false,
|
|
345
|
-
quiet: true,
|
|
346
|
-
projectDir: effectiveOptions.projectDir
|
|
347
|
-
});
|
|
348
|
-
} else {
|
|
349
|
-
info("No release label detected \u2014 skipping version analysis");
|
|
350
|
-
}
|
|
351
|
-
const commentBody = formatPreviewComment(result, { strategy, labelContext });
|
|
352
|
-
if (!context || !octokit) {
|
|
353
|
-
console.log(commentBody);
|
|
354
|
-
return;
|
|
355
|
-
}
|
|
356
|
-
info(`Posting preview comment on PR #${context.prNumber}...`);
|
|
357
|
-
await postOrUpdateComment(octokit, context.owner, context.repo, context.prNumber, commentBody);
|
|
358
|
-
success(`Preview comment posted on PR #${context.prNumber}`);
|
|
359
|
-
}
|
|
360
|
-
function resolvePrerelease(options, packagePaths, projectDir) {
|
|
361
|
-
if (options.stable) {
|
|
362
|
-
return void 0;
|
|
363
|
-
}
|
|
364
|
-
if (options.prerelease !== void 0) {
|
|
365
|
-
return options.prerelease;
|
|
366
|
-
}
|
|
367
|
-
const detected = detectPrerelease(packagePaths, projectDir);
|
|
368
|
-
if (detected.isPrerelease) {
|
|
369
|
-
info(`Detected prerelease version (identifier: ${detected.identifier})`);
|
|
370
|
-
return detected.identifier;
|
|
371
|
-
}
|
|
372
|
-
return void 0;
|
|
373
|
-
}
|
|
374
|
-
async function applyLabelOverrides(options, ciConfig, context, existingOctokit) {
|
|
375
|
-
const trigger = ciConfig?.releaseTrigger ?? "label";
|
|
376
|
-
const labels = ciConfig?.labels ?? DEFAULT_LABELS;
|
|
377
|
-
const defaultLabelContext = { trigger, skip: false, noBumpLabel: false };
|
|
378
|
-
if (!context) {
|
|
379
|
-
return {
|
|
380
|
-
options,
|
|
381
|
-
labelContext: { ...defaultLabelContext, noBumpLabel: trigger === "label" && !options.bump, labels }
|
|
382
|
-
};
|
|
383
|
-
}
|
|
384
|
-
let prLabels;
|
|
385
|
-
const octokitToUse = existingOctokit ?? createOctokit(context.token);
|
|
386
|
-
try {
|
|
387
|
-
prLabels = await fetchPRLabels(octokitToUse, context.owner, context.repo, context.prNumber);
|
|
388
|
-
} catch {
|
|
389
|
-
warn("Could not fetch PR labels \u2014 skipping label-driven overrides");
|
|
390
|
-
return {
|
|
391
|
-
options,
|
|
392
|
-
labelContext: { ...defaultLabelContext, noBumpLabel: trigger === "label", labels }
|
|
393
|
-
};
|
|
394
|
-
}
|
|
395
|
-
const result = { ...options };
|
|
396
|
-
const labelContext = { trigger, skip: false, noBumpLabel: false, labels };
|
|
397
|
-
if (trigger === "commit") {
|
|
398
|
-
if (prLabels.includes(labels.skip)) {
|
|
399
|
-
info(`PR label "${labels.skip}" detected \u2014 release will be skipped`);
|
|
400
|
-
labelContext.skip = true;
|
|
401
|
-
}
|
|
402
|
-
if (!labelContext.skip && prLabels.includes(labels.major)) {
|
|
403
|
-
info(`PR label "${labels.major}" detected \u2014 forcing major release`);
|
|
404
|
-
labelContext.bumpLabel = "major";
|
|
405
|
-
result.bump = "major";
|
|
406
|
-
}
|
|
407
|
-
} else {
|
|
408
|
-
if (prLabels.includes(labels.major)) {
|
|
409
|
-
info(`PR label "${labels.major}" detected \u2014 major release`);
|
|
410
|
-
labelContext.bumpLabel = "major";
|
|
411
|
-
result.bump = "major";
|
|
412
|
-
} else if (prLabels.includes(labels.minor)) {
|
|
413
|
-
info(`PR label "${labels.minor}" detected \u2014 minor release`);
|
|
414
|
-
labelContext.bumpLabel = "minor";
|
|
415
|
-
result.bump = "minor";
|
|
416
|
-
} else if (prLabels.includes(labels.patch)) {
|
|
417
|
-
info(`PR label "${labels.patch}" detected \u2014 patch release`);
|
|
418
|
-
labelContext.bumpLabel = "patch";
|
|
419
|
-
result.bump = "patch";
|
|
420
|
-
} else {
|
|
421
|
-
labelContext.noBumpLabel = true;
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
if (!options.stable && options.prerelease === void 0) {
|
|
425
|
-
if (prLabels.includes(labels.stable)) {
|
|
426
|
-
info(`PR label "${labels.stable}" detected \u2014 using stable release preview`);
|
|
427
|
-
result.stable = true;
|
|
428
|
-
} else if (prLabels.includes(labels.prerelease)) {
|
|
429
|
-
info(`PR label "${labels.prerelease}" detected \u2014 using prerelease preview`);
|
|
430
|
-
result.prerelease = true;
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
return { options: result, labelContext: { ...labelContext, labels } };
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
export {
|
|
437
|
-
runPreview
|
|
438
|
-
};
|