@varlock/bumpy 1.12.0 → 1.13.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/README.md
CHANGED
|
@@ -88,20 +88,40 @@ _examples use bun, but works with Node.js_
|
|
|
88
88
|
### PR check workflow
|
|
89
89
|
|
|
90
90
|
```yaml
|
|
91
|
-
# .github/workflows/bumpy-check.
|
|
91
|
+
# .github/workflows/bumpy-check.yaml
|
|
92
|
+
#
|
|
93
|
+
# ⚠️ Uses `pull_request_target` so fork PR comments work — runs with write
|
|
94
|
+
# perms and secrets, so it MUST NOT execute PR code (no `bun install`, no
|
|
95
|
+
# PR-defined scripts). Bumpy only reads files; its version is resolved from
|
|
96
|
+
# the base branch's package.json. See docs/github-actions.md for details.
|
|
92
97
|
name: Bumpy Check
|
|
93
|
-
on:
|
|
98
|
+
on: pull_request_target
|
|
99
|
+
|
|
100
|
+
permissions:
|
|
101
|
+
pull-requests: write
|
|
102
|
+
contents: read
|
|
94
103
|
|
|
95
104
|
jobs:
|
|
96
105
|
check:
|
|
97
106
|
runs-on: ubuntu-latest
|
|
98
|
-
permissions:
|
|
99
|
-
pull-requests: write
|
|
100
107
|
steps:
|
|
101
108
|
- uses: actions/checkout@v6
|
|
109
|
+
with:
|
|
110
|
+
ref: ${{ github.event.pull_request.head.sha }}
|
|
102
111
|
- uses: oven-sh/setup-bun@v2
|
|
103
|
-
|
|
104
|
-
|
|
112
|
+
|
|
113
|
+
# Resolve bumpy's version from the base branch (trusted) — not the PR's
|
|
114
|
+
# package.json (which a fork PR could swap to a malicious version).
|
|
115
|
+
# Change "main" to your base branch if different.
|
|
116
|
+
- name: Resolve bumpy version from base
|
|
117
|
+
run: |
|
|
118
|
+
git fetch origin main --depth=1
|
|
119
|
+
VERSION=$(git show "origin/main:package.json" \
|
|
120
|
+
| jq -r '.devDependencies["@varlock/bumpy"] // .dependencies["@varlock/bumpy"]' \
|
|
121
|
+
| sed 's/[\^~]//')
|
|
122
|
+
echo "BUMPY_VERSION=$VERSION" >> "$GITHUB_ENV"
|
|
123
|
+
|
|
124
|
+
- run: bunx "@varlock/bumpy@$BUMPY_VERSION" ci check
|
|
105
125
|
env:
|
|
106
126
|
GH_TOKEN: ${{ github.token }}
|
|
107
127
|
```
|
|
@@ -24,6 +24,10 @@ const LEVELS = [
|
|
|
24
24
|
* - Changed packages default to "patch", unchanged to "none"
|
|
25
25
|
* - Enter to confirm
|
|
26
26
|
* - Ctrl+C / Escape to cancel
|
|
27
|
+
*
|
|
28
|
+
* Renders a viewport that fits within the terminal so the list scrolls instead of
|
|
29
|
+
* overflowing — otherwise large package counts cause the redraw cursor-up to lose
|
|
30
|
+
* its anchor once content scrolls off-screen.
|
|
27
31
|
*/
|
|
28
32
|
async function bumpSelectPrompt(items) {
|
|
29
33
|
const changedEntries = items.map((item, idx) => ({
|
|
@@ -35,7 +39,44 @@ async function bumpSelectPrompt(items) {
|
|
|
35
39
|
idx
|
|
36
40
|
})).filter(({ item }) => !item.changed);
|
|
37
41
|
const displayOrder = [...changedEntries, ...unchangedEntries];
|
|
42
|
+
const rows = [];
|
|
43
|
+
const itemRowIndex = [];
|
|
44
|
+
{
|
|
45
|
+
let displayIdx = 0;
|
|
46
|
+
if (changedEntries.length > 0) {
|
|
47
|
+
rows.push({
|
|
48
|
+
kind: "header",
|
|
49
|
+
text: "Changed"
|
|
50
|
+
});
|
|
51
|
+
for (const { idx } of changedEntries) {
|
|
52
|
+
itemRowIndex.push(rows.length);
|
|
53
|
+
rows.push({
|
|
54
|
+
kind: "item",
|
|
55
|
+
itemIdx: idx,
|
|
56
|
+
displayIdx
|
|
57
|
+
});
|
|
58
|
+
displayIdx++;
|
|
59
|
+
}
|
|
60
|
+
if (unchangedEntries.length > 0) rows.push({ kind: "separator" });
|
|
61
|
+
}
|
|
62
|
+
if (unchangedEntries.length > 0) {
|
|
63
|
+
rows.push({
|
|
64
|
+
kind: "header",
|
|
65
|
+
text: "Unchanged"
|
|
66
|
+
});
|
|
67
|
+
for (const { idx } of unchangedEntries) {
|
|
68
|
+
itemRowIndex.push(rows.length);
|
|
69
|
+
rows.push({
|
|
70
|
+
kind: "item",
|
|
71
|
+
itemIdx: idx,
|
|
72
|
+
displayIdx
|
|
73
|
+
});
|
|
74
|
+
displayIdx++;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
38
78
|
let cursor = 0;
|
|
79
|
+
let scroll = 0;
|
|
39
80
|
const levels = items.map((item) => item.initialLevel !== void 0 ? item.initialLevel : item.changed ? "patch" : "skip");
|
|
40
81
|
return new Promise((resolve) => {
|
|
41
82
|
const { stdin, stdout } = process;
|
|
@@ -57,38 +98,77 @@ async function bumpSelectPrompt(items) {
|
|
|
57
98
|
if (selected.length === 0) lines.push(`${import_picocolors.default.dim("│")} ${import_picocolors.default.dim("(none selected)")}`);
|
|
58
99
|
else for (const { item, idx } of selected) lines.push(`${import_picocolors.default.dim("│")} ${import_picocolors.default.cyan(item.name)} ${import_picocolors.default.dim("→")} ${import_picocolors.default.bold(levels[idx])}`);
|
|
59
100
|
lines.push(import_picocolors.default.dim("│"));
|
|
101
|
+
const output = lines.join("\n") + "\n";
|
|
102
|
+
stdout.write(output);
|
|
103
|
+
renderedLines = lines.length;
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const headerChrome = [
|
|
107
|
+
`${import_picocolors.default.cyan("◆")} Select bump levels`,
|
|
108
|
+
`${import_picocolors.default.dim("│")} ${import_picocolors.default.dim("↑/↓ navigate · ←/→ change level · enter to confirm")}`,
|
|
109
|
+
`${import_picocolors.default.dim("│")} ${import_picocolors.default.dim("0 skip current · x skip all · r reset all to defaults")}`,
|
|
110
|
+
import_picocolors.default.dim("│")
|
|
111
|
+
];
|
|
112
|
+
const selectedCount = levels.filter((l) => l !== "skip").length;
|
|
113
|
+
const footerChrome = [
|
|
114
|
+
import_picocolors.default.dim("│"),
|
|
115
|
+
`${import_picocolors.default.dim("│")} ${import_picocolors.default.dim(`${selectedCount} package${selectedCount !== 1 ? "s" : ""} selected`)}`,
|
|
116
|
+
import_picocolors.default.dim("└")
|
|
117
|
+
];
|
|
118
|
+
const termRows = stdout.rows || 24;
|
|
119
|
+
const chromeLines = headerChrome.length + footerChrome.length;
|
|
120
|
+
const MIN_BODY = 3;
|
|
121
|
+
const availableBody = Math.max(MIN_BODY, termRows - chromeLines - 1);
|
|
122
|
+
let visibleRows;
|
|
123
|
+
let topIndicator = null;
|
|
124
|
+
let bottomIndicator = null;
|
|
125
|
+
let stickyHeader = null;
|
|
126
|
+
if (rows.length <= availableBody) {
|
|
127
|
+
visibleRows = rows;
|
|
128
|
+
scroll = 0;
|
|
60
129
|
} else {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
130
|
+
let windowSize = Math.max(MIN_BODY, availableBody - 2);
|
|
131
|
+
const focusedRowIdx = itemRowIndex[cursor];
|
|
132
|
+
const adjustScroll = () => {
|
|
133
|
+
if (focusedRowIdx < scroll) scroll = focusedRowIdx;
|
|
134
|
+
else if (focusedRowIdx >= scroll + windowSize) scroll = focusedRowIdx - windowSize + 1;
|
|
135
|
+
scroll = Math.max(0, Math.min(scroll, rows.length - windowSize));
|
|
136
|
+
};
|
|
137
|
+
adjustScroll();
|
|
138
|
+
const section = getCurrentSection(cursor, changedEntries.length, unchangedEntries.length);
|
|
139
|
+
if (section !== null && section.headerRowIdx < scroll) {
|
|
140
|
+
windowSize = Math.max(MIN_BODY, windowSize - 1);
|
|
141
|
+
adjustScroll();
|
|
142
|
+
stickyHeader = `${import_picocolors.default.dim("│")} ${import_picocolors.default.underline(section.name)}`;
|
|
73
143
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
144
|
+
visibleRows = rows.slice(scroll, scroll + windowSize);
|
|
145
|
+
const above = scroll;
|
|
146
|
+
const below = rows.length - (scroll + windowSize);
|
|
147
|
+
if (above > 0) topIndicator = `${import_picocolors.default.dim("│")} ${import_picocolors.default.dim(`▲ ${above} more`)}`;
|
|
148
|
+
if (below > 0) bottomIndicator = `${import_picocolors.default.dim("│")} ${import_picocolors.default.dim(`▼ ${below} more`)}`;
|
|
149
|
+
}
|
|
150
|
+
lines.push(...headerChrome);
|
|
151
|
+
if (topIndicator !== null) lines.push(topIndicator);
|
|
152
|
+
if (stickyHeader !== null) lines.push(stickyHeader);
|
|
153
|
+
for (const row of visibleRows) if (row.kind === "separator") lines.push(import_picocolors.default.dim("│"));
|
|
154
|
+
else if (row.kind === "header") lines.push(`${import_picocolors.default.dim("│")} ${import_picocolors.default.underline(row.text)}`);
|
|
155
|
+
else {
|
|
156
|
+
const item = items[row.itemIdx];
|
|
157
|
+
const isFocused = row.displayIdx === cursor;
|
|
158
|
+
lines.push(formatRow(item, levels[row.itemIdx], isFocused));
|
|
85
159
|
}
|
|
160
|
+
if (bottomIndicator !== null) lines.push(bottomIndicator);
|
|
161
|
+
lines.push(...footerChrome);
|
|
86
162
|
const output = lines.join("\n") + "\n";
|
|
87
163
|
stdout.write(output);
|
|
88
164
|
renderedLines = lines.length;
|
|
89
165
|
}
|
|
166
|
+
function onResize() {
|
|
167
|
+
render();
|
|
168
|
+
}
|
|
90
169
|
function cleanup() {
|
|
91
170
|
stdin.removeListener("keypress", onKeypress);
|
|
171
|
+
stdout.removeListener("resize", onResize);
|
|
92
172
|
rl.close();
|
|
93
173
|
stdout.write("\x1B[?25h");
|
|
94
174
|
if (stdin.isTTY) stdin.setRawMode(false);
|
|
@@ -141,8 +221,24 @@ async function bumpSelectPrompt(items) {
|
|
|
141
221
|
render();
|
|
142
222
|
}
|
|
143
223
|
stdin.on("keypress", onKeypress);
|
|
224
|
+
stdout.on("resize", onResize);
|
|
144
225
|
});
|
|
145
226
|
}
|
|
227
|
+
/** Returns the section the focused item is in, plus the row index of its header. */
|
|
228
|
+
function getCurrentSection(cursor, changedCount, unchangedCount) {
|
|
229
|
+
if (cursor < changedCount) {
|
|
230
|
+
if (changedCount === 0) return null;
|
|
231
|
+
return {
|
|
232
|
+
headerRowIdx: 0,
|
|
233
|
+
name: "Changed"
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
if (unchangedCount === 0) return null;
|
|
237
|
+
return {
|
|
238
|
+
headerRowIdx: changedCount > 0 ? changedCount + 2 : 0,
|
|
239
|
+
name: "Unchanged"
|
|
240
|
+
};
|
|
241
|
+
}
|
|
146
242
|
function formatRow(item, level, focused) {
|
|
147
243
|
return `${import_picocolors.default.dim("│")} ${focused ? import_picocolors.default.cyan("›") : " "} ${focused ? import_picocolors.default.cyan(item.name) : item.name} ${import_picocolors.default.dim(`(${item.version})`)} ${formatLevel(level, focused)}`;
|
|
148
244
|
}
|
|
@@ -8,7 +8,7 @@ import { f as withGitToken, i as getChangedFiles } from "./git-DJJ64SW9.mjs";
|
|
|
8
8
|
import { t as randomName } from "./names-COooXAFg.mjs";
|
|
9
9
|
import { n as findChangedPackages } from "./check-CsF0zh8r.mjs";
|
|
10
10
|
import { t as resolveCommitMessage } from "./commit-message-CSWVKPJ-.mjs";
|
|
11
|
-
import { appendFileSync, mkdirSync, writeFileSync } from "node:fs";
|
|
11
|
+
import { appendFileSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
12
12
|
import { createHash } from "node:crypto";
|
|
13
13
|
//#region src/commands/ci.ts
|
|
14
14
|
/**
|
|
@@ -157,7 +157,7 @@ async function ciPlanCommand(rootDir) {
|
|
|
157
157
|
packageNames: plan.releases.map((r) => r.name)
|
|
158
158
|
};
|
|
159
159
|
else {
|
|
160
|
-
const { findUnpublishedPackages } = await import("./publish-
|
|
160
|
+
const { findUnpublishedPackages } = await import("./publish-h6rM58Cq.mjs");
|
|
161
161
|
const unpublished = await findUnpublishedPackages(packages, config);
|
|
162
162
|
if (unpublished.length > 0) output = {
|
|
163
163
|
mode: "publish",
|
|
@@ -234,7 +234,7 @@ async function ciReleaseCommand(rootDir, opts) {
|
|
|
234
234
|
const msg = bumpFiles.length === 0 ? "No pending bump files — checking for unpublished packages..." : "Bump files found but no packages would be released — checking for unpublished packages...";
|
|
235
235
|
log.info(msg);
|
|
236
236
|
const recoveredBumpFiles = recoverDeletedBumpFiles(rootDir);
|
|
237
|
-
const { publishCommand } = await import("./publish-
|
|
237
|
+
const { publishCommand } = await import("./publish-h6rM58Cq.mjs");
|
|
238
238
|
await publishCommand(rootDir, {
|
|
239
239
|
tag: opts.tag,
|
|
240
240
|
recoveredBumpFiles
|
|
@@ -287,7 +287,7 @@ async function autoPublish(rootDir, config, plan, tag) {
|
|
|
287
287
|
], { cwd: rootDir });
|
|
288
288
|
}
|
|
289
289
|
log.step("Running bumpy publish...");
|
|
290
|
-
const { publishCommand } = await import("./publish-
|
|
290
|
+
const { publishCommand } = await import("./publish-h6rM58Cq.mjs");
|
|
291
291
|
await publishCommand(rootDir, { tag });
|
|
292
292
|
}
|
|
293
293
|
/**
|
|
@@ -721,6 +721,7 @@ async function postOrUpdatePrComment(prNumber, body, rootDir) {
|
|
|
721
721
|
}
|
|
722
722
|
} catch (err) {
|
|
723
723
|
log.warn(` Failed to comment on PR: ${err instanceof Error ? err.message : err}`);
|
|
724
|
+
if (process.env.GITHUB_EVENT_NAME === "pull_request" && isForkPr()) log.warn(" This PR is from a fork. Fork PRs running on `pull_request` get a read-only token\n and cannot post comments. Switch the workflow to `pull_request_target` —\n see https://bumpy.varlock.dev/docs/github-actions");
|
|
724
725
|
}
|
|
725
726
|
}
|
|
726
727
|
function detectPrBranch(rootDir) {
|
|
@@ -736,9 +737,14 @@ function detectPrBranch(rootDir) {
|
|
|
736
737
|
], { cwd: rootDir })?.trim() || null;
|
|
737
738
|
}
|
|
738
739
|
function detectPrNumber() {
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
740
|
+
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
741
|
+
if (eventName === "pull_request" || eventName === "pull_request_target") {
|
|
742
|
+
const num = readGitHubEventPayload()?.pull_request?.number;
|
|
743
|
+
if (typeof num === "number") return String(num);
|
|
744
|
+
if (eventName === "pull_request") {
|
|
745
|
+
const match = process.env.GITHUB_REF?.match(/refs\/pull\/(\d+)\//);
|
|
746
|
+
if (match) return match[1];
|
|
747
|
+
}
|
|
742
748
|
}
|
|
743
749
|
const envPr = process.env.BUMPY_PR_NUMBER || process.env.PR_NUMBER || null;
|
|
744
750
|
if (envPr && !/^\d+$/.test(envPr)) {
|
|
@@ -747,5 +753,21 @@ function detectPrNumber() {
|
|
|
747
753
|
}
|
|
748
754
|
return envPr;
|
|
749
755
|
}
|
|
756
|
+
function readGitHubEventPayload() {
|
|
757
|
+
const path = process.env.GITHUB_EVENT_PATH;
|
|
758
|
+
if (!path) return null;
|
|
759
|
+
try {
|
|
760
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
761
|
+
} catch {
|
|
762
|
+
return null;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
/** True when running on a PR whose head repo differs from the base repo (i.e. a fork). */
|
|
766
|
+
function isForkPr() {
|
|
767
|
+
const event = readGitHubEventPayload();
|
|
768
|
+
const headId = event?.pull_request?.head?.repo?.id;
|
|
769
|
+
const baseId = event?.pull_request?.base?.repo?.id;
|
|
770
|
+
return typeof headId === "number" && typeof baseId === "number" && headId !== baseId;
|
|
771
|
+
}
|
|
750
772
|
//#endregion
|
|
751
773
|
export { CI_PLAN_CACHE_PATH, ciCheckCommand, ciPlanCommand, ciReleaseCommand };
|
package/dist/cli.mjs
CHANGED
|
@@ -31,7 +31,7 @@ async function main() {
|
|
|
31
31
|
}
|
|
32
32
|
case "add": {
|
|
33
33
|
const rootDir = await findRoot();
|
|
34
|
-
const { addCommand } = await import("./add-
|
|
34
|
+
const { addCommand } = await import("./add-5how2kia.mjs");
|
|
35
35
|
await addCommand(rootDir, {
|
|
36
36
|
packages: flags.packages,
|
|
37
37
|
message: flags.message,
|
|
@@ -89,17 +89,17 @@ async function main() {
|
|
|
89
89
|
const subcommand = args[1];
|
|
90
90
|
const ciFlags = parseFlags(args.slice(2));
|
|
91
91
|
if (subcommand === "check") {
|
|
92
|
-
const { ciCheckCommand } = await import("./ci-
|
|
92
|
+
const { ciCheckCommand } = await import("./ci-CIamssoq.mjs");
|
|
93
93
|
await ciCheckCommand(rootDir, {
|
|
94
94
|
comment: ciFlags.comment !== void 0 ? ciFlags.comment === true : void 0,
|
|
95
95
|
strict: ciFlags.strict === true,
|
|
96
96
|
noFail: ciFlags["no-fail"] === true
|
|
97
97
|
});
|
|
98
98
|
} else if (subcommand === "plan") {
|
|
99
|
-
const { ciPlanCommand } = await import("./ci-
|
|
99
|
+
const { ciPlanCommand } = await import("./ci-CIamssoq.mjs");
|
|
100
100
|
await ciPlanCommand(rootDir);
|
|
101
101
|
} else if (subcommand === "release") {
|
|
102
|
-
const { ciReleaseCommand } = await import("./ci-
|
|
102
|
+
const { ciReleaseCommand } = await import("./ci-CIamssoq.mjs");
|
|
103
103
|
const expectModeFlag = ciFlags["expect-mode"];
|
|
104
104
|
const autoPublishFlag = ciFlags["auto-publish"] === true;
|
|
105
105
|
if (expectModeFlag !== void 0 && expectModeFlag !== "version-pr" && expectModeFlag !== "publish") {
|
|
@@ -127,7 +127,7 @@ async function main() {
|
|
|
127
127
|
}
|
|
128
128
|
case "publish": {
|
|
129
129
|
const rootDir = await findRoot();
|
|
130
|
-
const { publishCommand } = await import("./publish-
|
|
130
|
+
const { publishCommand } = await import("./publish-h6rM58Cq.mjs");
|
|
131
131
|
await publishCommand(rootDir, {
|
|
132
132
|
dryRun: flags["dry-run"] === true,
|
|
133
133
|
tag: flags.tag,
|
|
@@ -151,7 +151,7 @@ async function main() {
|
|
|
151
151
|
}
|
|
152
152
|
case "--version":
|
|
153
153
|
case "-v":
|
|
154
|
-
console.log(`bumpy 1.
|
|
154
|
+
console.log(`bumpy 1.13.1`);
|
|
155
155
|
break;
|
|
156
156
|
case "help":
|
|
157
157
|
case "--help":
|
|
@@ -171,7 +171,7 @@ async function main() {
|
|
|
171
171
|
}
|
|
172
172
|
function printHelp() {
|
|
173
173
|
console.log(`
|
|
174
|
-
${colorize(`🐸 bumpy v1.
|
|
174
|
+
${colorize(`🐸 bumpy v1.13.1`, "bold")} - Modern monorepo versioning
|
|
175
175
|
|
|
176
176
|
Usage: bumpy <command> [options]
|
|
177
177
|
|
|
@@ -7,7 +7,7 @@ import { r as runArgsAsync, s as tryRunArgs } from "./shell-C8KgKnMQ.mjs";
|
|
|
7
7
|
import { i as loadFormatter, n as generateChangelogEntry } from "./changelog-CbaET5V6.mjs";
|
|
8
8
|
import { c as hasUncommittedChanges, l as pushWithTags } from "./git-DJJ64SW9.mjs";
|
|
9
9
|
import { t as publishPackages } from "./publish-pipeline-DSj14dW6.mjs";
|
|
10
|
-
import { CI_PLAN_CACHE_PATH } from "./ci-
|
|
10
|
+
import { CI_PLAN_CACHE_PATH } from "./ci-CIamssoq.mjs";
|
|
11
11
|
//#region src/core/github-release.ts
|
|
12
12
|
/** Get the current HEAD commit SHA */
|
|
13
13
|
function getHeadSha(rootDir) {
|