@varlock/bumpy 1.13.2 → 1.14.0-rc.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.
Files changed (28) hide show
  1. package/README.md +3 -1
  2. package/config-schema.json +43 -0
  3. package/dist/{add-Dt1hddMt.mjs → add-C9rU_89s.mjs} +4 -4
  4. package/dist/{apply-release-plan-DD2R7SL2.mjs → apply-release-plan-DxTsUSqa.mjs} +11 -2
  5. package/dist/{bump-file-B7hmXZlB.mjs → bump-file-mRJeReRJ.mjs} +43 -8
  6. package/dist/{changelog-CbaET5V6.mjs → changelog-DuFhnJRO.mjs} +3 -3
  7. package/dist/{changelog-github-DXDnWkrB.mjs → changelog-github-jLOtwuWj.mjs} +2 -2
  8. package/dist/channels-CFXZkyGd.mjs +75 -0
  9. package/dist/{check-Dvi0DIqC.mjs → check-DIl9Dz68.mjs} +18 -6
  10. package/dist/{ci-B7gF6CFP.mjs → ci-ChYmDuwy.mjs} +376 -23
  11. package/dist/cli.mjs +30 -15
  12. package/dist/{config-D_4GYDJi.mjs → config-0we4ISZX.mjs} +5 -1
  13. package/dist/{generate-DohUlhu3.mjs → generate-B2OMt_64.mjs} +3 -3
  14. package/dist/{git-BWPimLgc.mjs → git-DAWj8LyV.mjs} +12 -1
  15. package/dist/index.d.mts +46 -4
  16. package/dist/index.mjs +7 -7
  17. package/dist/prerelease-B2PVfXkm.mjs +205 -0
  18. package/dist/{publish-VYBhDYFM.mjs → publish-Ak6jmwi_.mjs} +105 -14
  19. package/dist/{publish-pipeline-BPedWvKS.mjs → publish-pipeline-BD8mLbL9.mjs} +2 -2
  20. package/dist/{release-plan-mK7iGeGq.mjs → release-plan-C84pcBi-.mjs} +12 -17
  21. package/dist/status-DIzi-Iai.mjs +232 -0
  22. package/dist/{types-Bkh-igOJ.mjs → types-lpiG-Zxh.mjs} +1 -0
  23. package/dist/version-CMJUopVj.mjs +192 -0
  24. package/package.json +1 -1
  25. package/dist/status-DxzKPM8d.mjs +0 -129
  26. package/dist/version-Cm0nRAFF.mjs +0 -123
  27. /package/dist/{commit-message-CSWVKPJ-.mjs → commit-message-BwsowSds.mjs} +0 -0
  28. /package/dist/{init-BCkm6Nfa.mjs → init-DkAY5hjc.mjs} +0 -0
@@ -1,9 +1,9 @@
1
1
  import { n as log, t as colorize } from "./logger-BgksGFuf.mjs";
2
2
  import { t as ensureDir } from "./fs-CBXKZhoU.mjs";
3
- import { a as loadConfig, r as getBumpyDir } from "./config-D_4GYDJi.mjs";
4
- import { a as writeBumpFile, o as discoverPackages } from "./bump-file-B7hmXZlB.mjs";
3
+ import { a as loadConfig, r as getBumpyDir } from "./config-0we4ISZX.mjs";
4
+ import { o as writeBumpFile, s as discoverPackages } from "./bump-file-mRJeReRJ.mjs";
5
5
  import { s as tryRunArgs } from "./shell-C8KgKnMQ.mjs";
6
- import { c as getFilesChangedInCommit, i as getBranchCommits } from "./git-BWPimLgc.mjs";
6
+ import { c as getFilesChangedInCommit, i as getBranchCommits } from "./git-DAWj8LyV.mjs";
7
7
  import { n as slugify, t as randomName } from "./names-COooXAFg.mjs";
8
8
  import { relative } from "node:path";
9
9
  //#region src/commands/generate.ts
@@ -269,5 +269,16 @@ function getFileStatuses(dir, opts) {
269
269
  }
270
270
  return statuses;
271
271
  }
272
+ /** Get all tags matching a pattern */
273
+ function listTags(pattern, opts) {
274
+ const result = tryRunArgs([
275
+ "git",
276
+ "tag",
277
+ "-l",
278
+ pattern
279
+ ], opts);
280
+ if (!result) return [];
281
+ return result.split("\n").filter(Boolean);
282
+ }
272
283
  //#endregion
273
- export { getChangedFiles as a, getFilesChangedInCommit as c, tagExists as d, withGitToken as f, getBranchCommits as i, hasUncommittedChanges as l, forcePushTag as n, getCurrentBranch as o, getBaseCompareRef as r, getFileStatuses as s, createTag as t, readFileAtRef as u };
284
+ export { getChangedFiles as a, getFilesChangedInCommit as c, readFileAtRef as d, tagExists as f, getBranchCommits as i, hasUncommittedChanges as l, forcePushTag as n, getCurrentBranch as o, withGitToken as p, getBaseCompareRef as r, getFileStatuses as s, createTag as t, listTags as u };
package/dist/index.d.mts CHANGED
@@ -53,9 +53,30 @@ interface PublishConfig {
53
53
  */
54
54
  npmStaged: boolean;
55
55
  }
56
+ interface ChannelConfig {
57
+ /** Branch that triggers this channel (required) */
58
+ branch: string;
59
+ /** Version suffix (preid), e.g. "rc" → 1.2.0-rc.0. Defaults to the channel name. */
60
+ preid?: string;
61
+ /** npm dist-tag for publishes. Defaults to the channel name. */
62
+ tag?: string;
63
+ /** Release PR overrides for this channel */
64
+ versionPr?: {
65
+ title?: string;
66
+ branch?: string; /** Enable auto-merge on the release PR */
67
+ automerge?: boolean;
68
+ };
69
+ }
56
70
  interface BumpyConfig {
57
71
  baseBranch: string;
58
72
  access: 'public' | 'restricted';
73
+ /**
74
+ * Prerelease channels, keyed by channel name. Each maps a long-lived branch
75
+ * to a prerelease line (version suffix + npm dist-tag). Shipped bump files
76
+ * are tracked in `.bumpy/<name>/`. Prerelease versions are never committed —
77
+ * they are derived from bump files, the registry, and git tags at publish time.
78
+ */
79
+ channels: Record<string, ChannelConfig>;
59
80
  /**
60
81
  * Customize the commit message used when versioning.
61
82
  * A string starting with "./" is treated as a path to a module that exports
@@ -136,6 +157,8 @@ interface BumpFile {
136
157
  id: string;
137
158
  releases: BumpFileRelease[];
138
159
  summary: string;
160
+ /** Channel directory this file lives in (`.bumpy/<channel>/`), if any. Undefined = `.bumpy/` root. */
161
+ channel?: string;
139
162
  }
140
163
  interface WorkspacePackage {
141
164
  name: string;
@@ -219,8 +242,15 @@ interface ReadBumpFilesResult {
219
242
  bumpFiles: BumpFile[];
220
243
  errors: string[];
221
244
  }
222
- /** Read all bump files from .bumpy/ directory, sorted by git creation order */
223
- declare function readBumpFiles(rootDir: string): Promise<ReadBumpFilesResult>;
245
+ interface ReadBumpFilesOptions {
246
+ /**
247
+ * Channel names whose `.bumpy/<name>/` subdirectories should also be read.
248
+ * Files from those dirs get their `channel` field set; root files don't.
249
+ */
250
+ channels?: string[];
251
+ }
252
+ /** Read all bump files from .bumpy/ (and optionally channel subdirs), sorted by git creation order */
253
+ declare function readBumpFiles(rootDir: string, opts?: ReadBumpFilesOptions): Promise<ReadBumpFilesResult>;
224
254
  interface BumpFileParseResult {
225
255
  bumpFile: BumpFile | null;
226
256
  errors: string[];
@@ -240,7 +270,19 @@ declare function writeBumpFile(rootDir: string, filename: string, releases: Bump
240
270
  * Phase B — enforce fixed/linked group constraints
241
271
  * Phase C — apply cascades and proactive propagation rules
242
272
  */
243
- declare function assembleReleasePlan(bumpFiles: BumpFile[], packages: Map<string, WorkspacePackage>, depGraph: DependencyGraph, config: BumpyConfig): ReleasePlan;
273
+ interface AssemblePlanOptions {
274
+ /**
275
+ * Prerelease preid for channel plans (e.g. "rc"). When set, Phase A checks range
276
+ * satisfaction against `<target>-<preid>.0` instead of the stable target. Since a
277
+ * prerelease never satisfies a normal range (`^1.0.0` doesn't match `1.5.0-rc.0`),
278
+ * every dependent of a bumped package goes out of range and joins the cycle —
279
+ * required so consumers can actually install the cycle together from the dist-tag.
280
+ * Planned versions remain stable targets; the prerelease suffix is applied later
281
+ * from the registry floor.
282
+ */
283
+ prereleasePreid?: string;
284
+ }
285
+ declare function assembleReleasePlan(bumpFiles: BumpFile[], packages: Map<string, WorkspacePackage>, depGraph: DependencyGraph, config: BumpyConfig, opts?: AssemblePlanOptions): ReleasePlan;
244
286
  //#endregion
245
287
  //#region src/core/apply-release-plan.d.ts
246
288
  /** Apply the release plan: bump versions, update changelogs, delete bump files */
@@ -322,4 +364,4 @@ interface PublishResult {
322
364
  */
323
365
  declare function publishPackages(releasePlan: ReleasePlan, packages: Map<string, WorkspacePackage>, depGraph: DependencyGraph, config: BumpyConfig, rootDir: string, opts?: PublishOptions, catalogs?: CatalogMap, detectedPm?: PackageManager): Promise<PublishResult>;
324
366
  //#endregion
325
- export { BUMP_LEVELS, BumpFile, type BumpFileParseResult, BumpFileRelease, BumpFileReleaseCascade, BumpFileReleaseSimple, BumpType, BumpTypeWithNone, BumpyConfig, CascadeConfig, CascadeRule, type ChangelogContext, type ChangelogFormatter, DEFAULT_BUMP_RULES, DEFAULT_CONFIG, DEFAULT_PUBLISH_CONFIG, DEP_TYPES, DepType, DependencyBumpRule, DependencyGraph, DependentInfo, type GithubChangelogOptions, PackageConfig, PackageManager, PlannedRelease, PublishConfig, type ReadBumpFilesResult, ReleasePlan, WorkspacePackage, applyReleasePlan, assembleReleasePlan, bumpLevel, bumpVersion, defaultFormatter, discoverPackages, findRoot, generateChangelogEntry, getBumpyDir, hasCascade, loadConfig, loadFormatter, matchGlob, maxBump, normalizeCascadeConfig, parseBumpFile, prependToChangelog, publishPackages, readBumpFiles, satisfies, stripProtocol, writeBumpFile };
367
+ export { BUMP_LEVELS, BumpFile, type BumpFileParseResult, BumpFileRelease, BumpFileReleaseCascade, BumpFileReleaseSimple, BumpType, BumpTypeWithNone, BumpyConfig, CascadeConfig, CascadeRule, type ChangelogContext, type ChangelogFormatter, ChannelConfig, DEFAULT_BUMP_RULES, DEFAULT_CONFIG, DEFAULT_PUBLISH_CONFIG, DEP_TYPES, DepType, DependencyBumpRule, DependencyGraph, DependentInfo, type GithubChangelogOptions, PackageConfig, PackageManager, PlannedRelease, PublishConfig, type ReadBumpFilesResult, ReleasePlan, WorkspacePackage, applyReleasePlan, assembleReleasePlan, bumpLevel, bumpVersion, defaultFormatter, discoverPackages, findRoot, generateChangelogEntry, getBumpyDir, hasCascade, loadConfig, loadFormatter, matchGlob, maxBump, normalizeCascadeConfig, parseBumpFile, prependToChangelog, publishPackages, readBumpFiles, satisfies, stripProtocol, writeBumpFile };
package/dist/index.mjs CHANGED
@@ -1,8 +1,8 @@
1
- import { a as DEP_TYPES, c as maxBump, i as DEFAULT_PUBLISH_CONFIG, l as normalizeCascadeConfig, n as DEFAULT_BUMP_RULES, o as bumpLevel, r as DEFAULT_CONFIG, s as hasCascade, t as BUMP_LEVELS } from "./types-Bkh-igOJ.mjs";
2
- import { a as loadConfig, n as findRoot, r as getBumpyDir, s as matchGlob } from "./config-D_4GYDJi.mjs";
3
- import { a as writeBumpFile, n as parseBumpFile, o as discoverPackages, r as readBumpFiles } from "./bump-file-B7hmXZlB.mjs";
4
- import { a as DependencyGraph, i as stripProtocol, n as bumpVersion, r as satisfies, t as assembleReleasePlan } from "./release-plan-mK7iGeGq.mjs";
5
- import { a as prependToChangelog, i as loadFormatter, n as generateChangelogEntry, t as defaultFormatter } from "./changelog-CbaET5V6.mjs";
6
- import { t as applyReleasePlan } from "./apply-release-plan-DD2R7SL2.mjs";
7
- import { t as publishPackages } from "./publish-pipeline-BPedWvKS.mjs";
1
+ import { a as DEP_TYPES, c as maxBump, i as DEFAULT_PUBLISH_CONFIG, l as normalizeCascadeConfig, n as DEFAULT_BUMP_RULES, o as bumpLevel, r as DEFAULT_CONFIG, s as hasCascade, t as BUMP_LEVELS } from "./types-lpiG-Zxh.mjs";
2
+ import { a as loadConfig, n as findRoot, r as getBumpyDir, s as matchGlob } from "./config-0we4ISZX.mjs";
3
+ import { i as readBumpFiles, o as writeBumpFile, r as parseBumpFile, s as discoverPackages } from "./bump-file-mRJeReRJ.mjs";
4
+ import { i as stripProtocol, n as bumpVersion, o as DependencyGraph, r as satisfies, t as assembleReleasePlan } from "./release-plan-C84pcBi-.mjs";
5
+ import { a as prependToChangelog, i as loadFormatter, n as generateChangelogEntry, t as defaultFormatter } from "./changelog-DuFhnJRO.mjs";
6
+ import { t as applyReleasePlan } from "./apply-release-plan-DxTsUSqa.mjs";
7
+ import { t as publishPackages } from "./publish-pipeline-BD8mLbL9.mjs";
8
8
  export { BUMP_LEVELS, DEFAULT_BUMP_RULES, DEFAULT_CONFIG, DEFAULT_PUBLISH_CONFIG, DEP_TYPES, DependencyGraph, applyReleasePlan, assembleReleasePlan, bumpLevel, bumpVersion, defaultFormatter, discoverPackages, findRoot, generateChangelogEntry, getBumpyDir, hasCascade, loadConfig, loadFormatter, matchGlob, maxBump, normalizeCascadeConfig, parseBumpFile, prependToChangelog, publishPackages, readBumpFiles, satisfies, stripProtocol, writeBumpFile };
@@ -0,0 +1,205 @@
1
+ import { s as __toESM } from "./logger-BgksGFuf.mjs";
2
+ import { f as writeText, l as updateJsonFields, s as readText, u as updateJsonNestedField } from "./fs-CBXKZhoU.mjs";
3
+ import { a as require_semver } from "./release-plan-C84pcBi-.mjs";
4
+ import { r as runArgsAsync, s as tryRunArgs } from "./shell-C8KgKnMQ.mjs";
5
+ import { u as listTags } from "./git-DAWj8LyV.mjs";
6
+ import { resolve } from "node:path";
7
+ //#region src/core/prerelease.ts
8
+ var import_semver = /* @__PURE__ */ __toESM(require_semver(), 1);
9
+ /**
10
+ * Extract existing prerelease counters for a target+preid from a list of published versions.
11
+ * Only exact `<target>-<preid>.<N>` versions count — other preids and targets are ignored.
12
+ */
13
+ function extractPrereleaseCounters(versions, target, preid) {
14
+ const counters = [];
15
+ for (const v of versions) {
16
+ const parsed = import_semver.default.parse(v);
17
+ if (!parsed) continue;
18
+ if (`${parsed.major}.${parsed.minor}.${parsed.patch}` !== target) continue;
19
+ if (parsed.prerelease.length !== 2) continue;
20
+ if (parsed.prerelease[0] !== preid) continue;
21
+ const n = parsed.prerelease[1];
22
+ if (typeof n === "number") counters.push(n);
23
+ }
24
+ return counters;
25
+ }
26
+ /** Compute the next prerelease version: `<target>-<preid>.<max existing counter + 1>` */
27
+ function nextPrereleaseVersion(target, preid, existingCounters) {
28
+ return `${target}-${preid}.${existingCounters.length > 0 ? Math.max(...existingCounters) + 1 : 0}`;
29
+ }
30
+ /** Fetch all published versions of a package from the registry */
31
+ async function fetchPublishedVersions(name, registry) {
32
+ const args = [
33
+ "npm",
34
+ "info",
35
+ name,
36
+ "versions",
37
+ "--json"
38
+ ];
39
+ if (registry) args.push("--registry", registry);
40
+ try {
41
+ const raw = await runArgsAsync(args);
42
+ const parsed = JSON.parse(raw);
43
+ if (Array.isArray(parsed)) return parsed.filter((v) => typeof v === "string");
44
+ if (typeof parsed === "string") return [parsed];
45
+ return [];
46
+ } catch {
47
+ return [];
48
+ }
49
+ }
50
+ /** Fetch the gitHead recorded for a published version (set by npm publish from a git checkout) */
51
+ async function fetchGitHead(name, version, registry) {
52
+ const args = [
53
+ "npm",
54
+ "info",
55
+ `${name}@${version}`,
56
+ "gitHead"
57
+ ];
58
+ if (registry) args.push("--registry", registry);
59
+ try {
60
+ const sha = (await runArgsAsync(args)).trim();
61
+ return /^[0-9a-f]{40}$/.test(sha) ? sha : null;
62
+ } catch {
63
+ return null;
64
+ }
65
+ }
66
+ /** Whether a package publishes through the npm registry (vs custom command / git-tag tracking) */
67
+ function usesNpmRegistry(pkg) {
68
+ return !pkg.bumpy?.publishCommand && !pkg.bumpy?.skipNpmPublish && !pkg.private;
69
+ }
70
+ /** Query published prerelease state for one package at a target version */
71
+ async function getPublishedPrereleaseState(pkg, target, preid, rootDir) {
72
+ if (usesNpmRegistry(pkg)) {
73
+ const versions = await fetchPublishedVersions(pkg.name, pkg.bumpy?.registry);
74
+ return {
75
+ counters: extractPrereleaseCounters(versions, target, preid),
76
+ stablePublished: versions.includes(target)
77
+ };
78
+ }
79
+ return {
80
+ counters: extractPrereleaseCounters(listTags(`${pkg.name}@${target}-${preid}.*`, { cwd: rootDir }).map((t) => t.slice(pkg.name.length + 1)), target, preid),
81
+ stablePublished: listTags(`${pkg.name}@${target}`, { cwd: rootDir }).length > 0
82
+ };
83
+ }
84
+ /**
85
+ * Transform a stable release plan (targets computed from bump files) into a channel
86
+ * prerelease plan: each release's newVersion becomes `<target>-<preid>.<N>` with N
87
+ * derived from the registry floor.
88
+ *
89
+ * Idempotency: if a package's latest published prerelease for this target records a
90
+ * gitHead equal to HEAD (or, for non-npm packages, its tag points at HEAD), the package
91
+ * was already published from this exact commit — it is skipped rather than re-counted.
92
+ */
93
+ async function buildChannelReleasePlan(stablePlan, channel, packages, rootDir, opts = {}) {
94
+ const headSha = tryRunArgs([
95
+ "git",
96
+ "rev-parse",
97
+ "HEAD"
98
+ ], { cwd: rootDir });
99
+ const warnings = [...stablePlan.warnings];
100
+ const alreadyPublished = [];
101
+ const releases = [];
102
+ await Promise.all(stablePlan.releases.map(async (release) => {
103
+ const pkg = packages.get(release.name);
104
+ if (!pkg) return;
105
+ if (pkg.private && !pkg.bumpy?.publishCommand) return;
106
+ const target = release.newVersion;
107
+ const state = await getPublishedPrereleaseState(pkg, target, channel.preid, rootDir);
108
+ if (state.stablePublished) warnings.push(`${release.name}@${target} is already published as a stable release — merge ${target}'s release into the "${channel.branch}" branch so the cycle retargets.`);
109
+ if (!opts.forDisplay && state.counters.length > 0 && headSha) {
110
+ const latest = `${target}-${channel.preid}.${Math.max(...state.counters)}`;
111
+ if (usesNpmRegistry(pkg) ? await fetchGitHead(pkg.name, latest, pkg.bumpy?.registry) === headSha : tryRunArgs([
112
+ "git",
113
+ "rev-parse",
114
+ `refs/tags/${pkg.name}@${latest}`
115
+ ], { cwd: rootDir }) === headSha) {
116
+ alreadyPublished.push({
117
+ name: release.name,
118
+ version: latest
119
+ });
120
+ return;
121
+ }
122
+ }
123
+ releases.push({
124
+ ...release,
125
+ newVersion: nextPrereleaseVersion(target, channel.preid, state.counters)
126
+ });
127
+ }));
128
+ releases.sort((a, b) => a.name.localeCompare(b.name));
129
+ return {
130
+ plan: {
131
+ bumpFiles: stablePlan.bumpFiles,
132
+ releases,
133
+ warnings
134
+ },
135
+ alreadyPublished,
136
+ warnings
137
+ };
138
+ }
139
+ /**
140
+ * Transiently write computed prerelease versions (and exact pins for in-cycle deps)
141
+ * into the working tree's package.json files. Returns a restore function that puts
142
+ * the original contents back — call it in a `finally` after publishing.
143
+ *
144
+ * Versions must be on disk before build/pack so that:
145
+ * - PM pack picks up the prerelease version for the tarball
146
+ * - builds that bake in the version (banners, __VERSION__) see the right one
147
+ *
148
+ * In-cycle dependencies are pinned EXACTLY (`"1.2.0-rc.0"`, no range) so any
149
+ * combination of packages installed from the channel dist-tag resolves to the
150
+ * coherent set it was published with.
151
+ */
152
+ async function writeChannelVersionsInPlace(plan, packages) {
153
+ const releaseMap = new Map(plan.releases.map((r) => [r.name, r]));
154
+ const originals = /* @__PURE__ */ new Map();
155
+ for (const release of plan.releases) {
156
+ const pkg = packages.get(release.name);
157
+ if (!pkg) continue;
158
+ const pkgJsonPath = resolve(pkg.dir, "package.json");
159
+ originals.set(pkgJsonPath, await readText(pkgJsonPath));
160
+ await updateJsonFields(pkgJsonPath, { version: release.newVersion });
161
+ for (const depField of [
162
+ "dependencies",
163
+ "peerDependencies",
164
+ "optionalDependencies"
165
+ ]) {
166
+ const deps = pkg[depField];
167
+ for (const depName of Object.keys(deps)) {
168
+ const depRelease = releaseMap.get(depName);
169
+ if (!depRelease) continue;
170
+ await updateJsonNestedField(pkgJsonPath, depField, depName, depRelease.newVersion);
171
+ }
172
+ }
173
+ }
174
+ return async () => {
175
+ for (const [path, content] of originals) await writeText(path, content);
176
+ };
177
+ }
178
+ /**
179
+ * Derive display versions for a channel cycle without touching the registry:
180
+ * each target gets a wildcard counter (`1.2.0-rc.x`). Everything here comes from
181
+ * committed state (bump files + config), so PR titles/bodies and commit messages
182
+ * can never disagree with what eventually publishes. Unpublishable packages are
183
+ * dropped, mirroring the filter in `buildChannelReleasePlan`.
184
+ */
185
+ function channelDisplayPlan(stablePlan, channel, packages) {
186
+ const releases = stablePlan.releases.filter((r) => {
187
+ const pkg = packages.get(r.name);
188
+ return !!pkg && !(pkg.private && !pkg.bumpy?.publishCommand);
189
+ }).map((r) => ({
190
+ ...r,
191
+ newVersion: `${r.newVersion}-${channel.preid}.x`
192
+ }));
193
+ return {
194
+ ...stablePlan,
195
+ releases
196
+ };
197
+ }
198
+ /** One-line summary of a channel plan's versions, for PR titles and commit messages */
199
+ function formatChannelVersionSummary(releases) {
200
+ if (releases.length === 0) return "";
201
+ if (releases.length === 1) return `${releases[0].name}@${releases[0].newVersion}`;
202
+ return `${releases.length} packages`;
203
+ }
204
+ //#endregion
205
+ export { writeChannelVersionsInPlace as i, channelDisplayPlan as n, formatChannelVersionSummary as r, buildChannelReleasePlan as t };
@@ -1,14 +1,17 @@
1
- import { n as log, o as __require, t as colorize } from "./logger-BgksGFuf.mjs";
2
- import { a as loadConfig } from "./config-D_4GYDJi.mjs";
1
+ import { n as log, o as __require, s as __toESM, t as colorize } from "./logger-BgksGFuf.mjs";
2
+ import { a as loadConfig } from "./config-0we4ISZX.mjs";
3
3
  import { r as detectWorkspaces } from "./package-manager-Db_vTztt.mjs";
4
- import { s as discoverWorkspace } from "./bump-file-B7hmXZlB.mjs";
5
- import { a as DependencyGraph } from "./release-plan-mK7iGeGq.mjs";
4
+ import { c as discoverWorkspace, i as readBumpFiles } from "./bump-file-mRJeReRJ.mjs";
5
+ import { a as require_semver, o as DependencyGraph, t as assembleReleasePlan } from "./release-plan-C84pcBi-.mjs";
6
6
  import { r as runArgsAsync, s as tryRunArgs } from "./shell-C8KgKnMQ.mjs";
7
- import { i as loadFormatter, n as generateChangelogEntry } from "./changelog-CbaET5V6.mjs";
8
- import { d as tagExists, l as hasUncommittedChanges, n as forcePushTag } from "./git-BWPimLgc.mjs";
9
- import { n as willUseOidcExclusively, t as publishPackages } from "./publish-pipeline-BPedWvKS.mjs";
10
- import { CI_PLAN_CACHE_PATH } from "./ci-B7gF6CFP.mjs";
7
+ import { i as loadFormatter, n as generateChangelogEntry } from "./changelog-DuFhnJRO.mjs";
8
+ import { f as tagExists, l as hasUncommittedChanges, n as forcePushTag } from "./git-DAWj8LyV.mjs";
9
+ import { n as willUseOidcExclusively, t as publishPackages } from "./publish-pipeline-BD8mLbL9.mjs";
10
+ import { channelNames, resolveActiveChannel } from "./channels-CFXZkyGd.mjs";
11
+ import { i as writeChannelVersionsInPlace, t as buildChannelReleasePlan } from "./prerelease-B2PVfXkm.mjs";
12
+ import { CI_PLAN_CACHE_PATH } from "./ci-ChYmDuwy.mjs";
11
13
  //#region src/core/github-release.ts
14
+ var import_semver = /* @__PURE__ */ __toESM(require_semver(), 1);
12
15
  /** Get the current HEAD commit SHA */
13
16
  function getHeadSha(rootDir) {
14
17
  return tryRunArgs([
@@ -67,6 +70,7 @@ async function createIndividualReleases(releases, bumpFiles, rootDir, opts = {})
67
70
  "--notes",
68
71
  body
69
72
  ];
73
+ if (/-/.test(release.newVersion)) args.push("--prerelease");
70
74
  if (headSha) args.push("--target", headSha);
71
75
  await withReleaseToken(() => runArgsAsync(args, { cwd: rootDir }));
72
76
  log.dim(` Created GitHub release: ${title}`);
@@ -193,7 +197,7 @@ async function findReleaseByTag(tag, rootDir) {
193
197
  }
194
198
  }
195
199
  /** Create a draft GitHub release */
196
- async function createDraftRelease(tag, title, body, rootDir, targetSha) {
200
+ async function createDraftRelease(tag, title, body, rootDir, targetSha, opts) {
197
201
  const args = [
198
202
  "gh",
199
203
  "release",
@@ -205,6 +209,7 @@ async function createDraftRelease(tag, title, body, rootDir, targetSha) {
205
209
  body,
206
210
  "--draft"
207
211
  ];
212
+ if (opts?.prerelease) args.push("--prerelease");
208
213
  if (targetSha) args.push("--target", targetSha);
209
214
  await withReleaseToken(() => runArgsAsync(args, { cwd: rootDir }));
210
215
  }
@@ -293,7 +298,13 @@ async function finalizeSupersededDrafts(packageName, newVersion, rootDir) {
293
298
  //#region src/commands/publish.ts
294
299
  /**
295
300
  * Publish packages that have been versioned but not yet published.
296
- * Detects unpublished versions by comparing package.json versions against npm registry.
301
+ *
302
+ * On the base branch: detects unpublished versions by comparing package.json versions
303
+ * against the npm registry.
304
+ *
305
+ * On a channel branch: prerelease versions are never committed, so they are computed
306
+ * here — targets from the cycle's bump files, counters from the registry — written
307
+ * transiently into the working tree, published to the channel's dist-tag, and restored.
297
308
  */
298
309
  async function publishCommand(rootDir, opts) {
299
310
  const config = await loadConfig(rootDir);
@@ -304,7 +315,21 @@ async function publishCommand(rootDir, opts) {
304
315
  log.warn("You have uncommitted changes. Commit or stash them before publishing.");
305
316
  process.exit(1);
306
317
  }
318
+ const channel = resolveActiveChannel(rootDir, config, opts.channel);
319
+ if (channel) {
320
+ await publishChannel(rootDir, config, packages, catalogs, detectedPm, depGraph, channel, opts);
321
+ return;
322
+ }
307
323
  let toPublish = await findUnpublishedWithCache(rootDir, packages, config);
324
+ if (Object.keys(config.channels || {}).length > 0) {
325
+ const prereleases = toPublish.filter((r) => import_semver.default.prerelease(r.newVersion) !== null);
326
+ if (prereleases.length > 0) {
327
+ log.error("Refusing to publish prerelease versions outside a channel:");
328
+ for (const r of prereleases) log.error(` • ${r.name}@${r.newVersion}`);
329
+ log.error("Prerelease versions should never be committed — see https://bumpy.varlock.dev/docs/prereleases");
330
+ process.exit(1);
331
+ }
332
+ }
308
333
  if (opts.excludePackages && opts.excludePackages.size > 0) {
309
334
  const excluded = toPublish.filter((r) => opts.excludePackages.has(r.name));
310
335
  if (excluded.length > 0) {
@@ -313,7 +338,7 @@ async function publishCommand(rootDir, opts) {
313
338
  }
314
339
  }
315
340
  if (opts.filter) {
316
- const { matchGlob } = await import("./config-D_4GYDJi.mjs").then((n) => n.t);
341
+ const { matchGlob } = await import("./config-0we4ISZX.mjs").then((n) => n.t);
317
342
  const patterns = opts.filter.split(",").map((p) => p.trim());
318
343
  toPublish = toPublish.filter((r) => patterns.some((p) => matchGlob(r.name, p)));
319
344
  }
@@ -323,11 +348,77 @@ async function publishCommand(rootDir, opts) {
323
348
  }
324
349
  const recoveredBumpFiles = opts.recoveredBumpFiles || [];
325
350
  if (recoveredBumpFiles.length > 0) for (const release of toPublish) release.bumpFiles = recoveredBumpFiles.filter((bf) => bf.releases.some((r) => r.name === release.name)).map((bf) => bf.id);
326
- const releasePlan = {
351
+ await runPublishFlow(rootDir, config, packages, catalogs, detectedPm, depGraph, {
327
352
  bumpFiles: recoveredBumpFiles,
328
353
  releases: toPublish,
329
354
  warnings: []
330
- };
355
+ }, {
356
+ dryRun: opts.dryRun,
357
+ tag: opts.tag,
358
+ noPush: opts.noPush
359
+ });
360
+ }
361
+ /**
362
+ * Publish a prerelease cycle from a channel branch.
363
+ *
364
+ * The cycle = every bump file on the branch (pending at root or in other channels'
365
+ * dirs, plus shipped in this channel's dir). The whole cycle republishes together
366
+ * each time so the channel dist-tag always points at one coherent, exact-pinned set.
367
+ */
368
+ async function publishChannel(rootDir, config, packages, catalogs, detectedPm, depGraph, channel, opts) {
369
+ const { bumpFiles, errors: parseErrors } = await readBumpFiles(rootDir, { channels: channelNames(config) });
370
+ if (parseErrors.length > 0) {
371
+ for (const err of parseErrors) log.error(err);
372
+ process.exit(1);
373
+ }
374
+ if (bumpFiles.filter((bf) => bf.channel === channel.name).length === 0) {
375
+ log.info(`Nothing has shipped on channel "${channel.name}" yet (no bump files in .bumpy/${channel.name}/).\n Run \`bumpy version\` on the channel branch (or merge the release PR) first.`);
376
+ return;
377
+ }
378
+ log.bold(`Channel "${channel.name}" — preid "-${channel.preid}.N", dist-tag @${channel.tag}\n`);
379
+ const { plan, alreadyPublished, warnings } = await buildChannelReleasePlan(assembleReleasePlan(bumpFiles, packages, depGraph, config, { prereleasePreid: channel.preid }), channel, packages, rootDir);
380
+ for (const w of warnings) log.warn(w);
381
+ for (const skip of alreadyPublished) log.dim(` Skipping ${skip.name}@${skip.version} — already published from this commit`);
382
+ if (plan.releases.length === 0) {
383
+ log.info("All cycle packages already published from this commit.");
384
+ return;
385
+ }
386
+ let toPublish = plan.releases;
387
+ if (opts.filter) {
388
+ const { matchGlob } = await import("./config-0we4ISZX.mjs").then((n) => n.t);
389
+ const patterns = opts.filter.split(",").map((p) => p.trim());
390
+ toPublish = toPublish.filter((r) => patterns.some((p) => matchGlob(r.name, p)));
391
+ if (toPublish.length === 0) {
392
+ log.info("No cycle packages match the filter.");
393
+ return;
394
+ }
395
+ }
396
+ let restore = null;
397
+ if (!opts.dryRun) restore = await writeChannelVersionsInPlace(plan, packages);
398
+ try {
399
+ await runPublishFlow(rootDir, config, packages, catalogs, detectedPm, depGraph, {
400
+ bumpFiles: plan.bumpFiles,
401
+ releases: toPublish,
402
+ warnings: []
403
+ }, {
404
+ dryRun: opts.dryRun,
405
+ tag: opts.tag ?? channel.tag,
406
+ noPush: opts.noPush
407
+ });
408
+ } finally {
409
+ if (restore) {
410
+ await restore();
411
+ log.dim(" Restored package.json files (prerelease versions are not committed)");
412
+ }
413
+ }
414
+ }
415
+ /**
416
+ * The shared publish flow: OIDC checks, draft GitHub releases, topological publish,
417
+ * release metadata updates, tag pushes. Used by both the stable and channel paths.
418
+ * Mutates `releasePlan.releases` as packages are filtered out (already published, etc.).
419
+ */
420
+ async function runPublishFlow(rootDir, config, packages, catalogs, detectedPm, depGraph, releasePlan, opts) {
421
+ let toPublish = releasePlan.releases;
331
422
  if (opts.dryRun) log.bold("Dry run — would publish:");
332
423
  else log.bold("Publishing:");
333
424
  for (const r of toPublish) console.log(` ${r.name}@${colorize(r.newVersion, "cyan")}`);
@@ -381,7 +472,7 @@ async function publishCommand(rootDir, opts) {
381
472
  const title = `${release.name} v${release.newVersion}`;
382
473
  const headSha = getHeadSha(rootDir);
383
474
  try {
384
- await createDraftRelease(tag, title, body, rootDir, headSha || void 0);
475
+ await createDraftRelease(tag, title, body, rootDir, headSha || void 0, { prerelease: import_semver.default.prerelease(release.newVersion) !== null });
385
476
  log.dim(` Created draft release: ${title}`);
386
477
  releaseMetadataByPkg.set(release.name, {
387
478
  tag,
@@ -1,9 +1,9 @@
1
1
  import { n as log, t as colorize } from "./logger-BgksGFuf.mjs";
2
2
  import { a as readJson, u as updateJsonNestedField } from "./fs-CBXKZhoU.mjs";
3
3
  import { s as resolveCatalogDep } from "./package-manager-Db_vTztt.mjs";
4
- import { i as stripProtocol } from "./release-plan-mK7iGeGq.mjs";
4
+ import { i as stripProtocol } from "./release-plan-C84pcBi-.mjs";
5
5
  import { i as runAsync, o as sq, r as runArgsAsync, s as tryRunArgs } from "./shell-C8KgKnMQ.mjs";
6
- import { d as tagExists, t as createTag } from "./git-BWPimLgc.mjs";
6
+ import { f as tagExists, t as createTag } from "./git-DAWj8LyV.mjs";
7
7
  import { resolve } from "node:path";
8
8
  import { unlink } from "node:fs/promises";
9
9
  import { appendFileSync, existsSync, readFileSync, writeFileSync } from "node:fs";
@@ -1,6 +1,6 @@
1
1
  import { i as __commonJSMin, s as __toESM } from "./logger-BgksGFuf.mjs";
2
- import { c as maxBump, l as normalizeCascadeConfig, n as DEFAULT_BUMP_RULES, o as bumpLevel, s as hasCascade } from "./types-Bkh-igOJ.mjs";
3
- import { s as matchGlob } from "./config-D_4GYDJi.mjs";
2
+ import { c as maxBump, l as normalizeCascadeConfig, n as DEFAULT_BUMP_RULES, o as bumpLevel, s as hasCascade } from "./types-lpiG-Zxh.mjs";
3
+ import { s as matchGlob } from "./config-0we4ISZX.mjs";
4
4
  //#region src/core/dep-graph.ts
5
5
  var DependencyGraph = class {
6
6
  /** Map from package name → packages that depend on it */
@@ -1347,8 +1347,8 @@ var require_subset = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1347
1347
  module.exports = subset;
1348
1348
  }));
1349
1349
  //#endregion
1350
- //#region src/core/semver.ts
1351
- var import_semver = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
1350
+ //#region ../../node_modules/.bun/semver@7.7.4/node_modules/semver/index.js
1351
+ var require_semver = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1352
1352
  const internalRe = require_re();
1353
1353
  const constants = require_constants();
1354
1354
  const SemVer = require_semver$1();
@@ -1400,7 +1400,10 @@ var import_semver = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exp
1400
1400
  compareIdentifiers: identifiers.compareIdentifiers,
1401
1401
  rcompareIdentifiers: identifiers.rcompareIdentifiers
1402
1402
  };
1403
- })))(), 1);
1403
+ }));
1404
+ //#endregion
1405
+ //#region src/core/semver.ts
1406
+ var import_semver = /* @__PURE__ */ __toESM(require_semver(), 1);
1404
1407
  function bumpVersion(version, type) {
1405
1408
  const result = import_semver.default.inc(version, type);
1406
1409
  if (!result) throw new Error(`Failed to bump ${version} by ${type}`);
@@ -1433,16 +1436,7 @@ function stripProtocol(range) {
1433
1436
  }
1434
1437
  //#endregion
1435
1438
  //#region src/core/release-plan.ts
1436
- /**
1437
- * Build a release plan from pending bump files, the dependency graph, and config.
1438
- * This is the core algorithm of bumpy.
1439
- *
1440
- * The propagation loop runs three phases until stable:
1441
- * Phase A — fix out-of-range dependencies (always runs)
1442
- * Phase B — enforce fixed/linked group constraints
1443
- * Phase C — apply cascades and proactive propagation rules
1444
- */
1445
- function assembleReleasePlan(bumpFiles, packages, depGraph, config) {
1439
+ function assembleReleasePlan(bumpFiles, packages, depGraph, config, opts = {}) {
1446
1440
  if (bumpFiles.length === 0) return {
1447
1441
  bumpFiles: [],
1448
1442
  releases: [],
@@ -1485,11 +1479,12 @@ function assembleReleasePlan(bumpFiles, packages, depGraph, config) {
1485
1479
  for (const [pkgName, bump] of planned) {
1486
1480
  const pkg = packages.get(pkgName);
1487
1481
  const newVersion = bumpVersion(pkg.version, bump.type);
1482
+ const versionForRangeCheck = opts.prereleasePreid ? `${newVersion}-${opts.prereleasePreid}.0` : newVersion;
1488
1483
  const dependents = depGraph.getDependents(pkgName);
1489
1484
  for (const dep of dependents) {
1490
1485
  if (dep.depType === "devDependencies") continue;
1491
1486
  const currentVersion = pkg.version;
1492
- if (satisfies(newVersion, dep.versionRange, currentVersion)) continue;
1487
+ if (satisfies(versionForRangeCheck, dep.versionRange, currentVersion)) continue;
1493
1488
  let depBump;
1494
1489
  if (dep.depType === "peerDependencies") depBump = bump.type;
1495
1490
  else depBump = "patch";
@@ -1704,4 +1699,4 @@ function resolveRule(dependentName, depType, packages, config) {
1704
1699
  };
1705
1700
  }
1706
1701
  //#endregion
1707
- export { DependencyGraph as a, stripProtocol as i, bumpVersion as n, satisfies as r, assembleReleasePlan as t };
1702
+ export { require_semver as a, stripProtocol as i, bumpVersion as n, DependencyGraph as o, satisfies as r, assembleReleasePlan as t };