@varlock/bumpy 1.13.1 → 1.14.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/README.md +23 -140
  2. package/config-schema.json +43 -0
  3. package/dist/{add-5how2kia.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-CsF0zh8r.mjs → check-DIl9Dz68.mjs} +18 -6
  10. package/dist/{ci-CIamssoq.mjs → ci-hO7tAbCN.mjs} +391 -23
  11. package/dist/cli.mjs +32 -17
  12. package/dist/{config-D_4GYDJi.mjs → config-0we4ISZX.mjs} +5 -1
  13. package/dist/{generate-CvCvUaRV.mjs → generate-B2OMt_64.mjs} +3 -3
  14. package/dist/{git-DJJ64SW9.mjs → git-DAWj8LyV.mjs} +25 -4
  15. package/dist/index.d.mts +46 -4
  16. package/dist/index.mjs +7 -7
  17. package/dist/prerelease-Blnk8FE1.mjs +186 -0
  18. package/dist/{publish-h6rM58Cq.mjs → publish-Cz0e4KYT.mjs} +164 -19
  19. package/dist/{publish-pipeline-DSj14dW6.mjs → publish-pipeline-BD8mLbL9.mjs} +18 -3
  20. package/dist/{release-plan-mK7iGeGq.mjs → release-plan-C84pcBi-.mjs} +12 -17
  21. package/dist/status-CrMvvvNy.mjs +232 -0
  22. package/dist/{types-Bkh-igOJ.mjs → types-lpiG-Zxh.mjs} +1 -0
  23. package/dist/version-BUUf8vKC.mjs +192 -0
  24. package/package.json +1 -1
  25. package/dist/status-BbsDr6t7.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
package/dist/cli.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { n as log, t as colorize } from "./logger-BgksGFuf.mjs";
3
- import { n as findRoot } from "./config-D_4GYDJi.mjs";
3
+ import { n as findRoot } from "./config-0we4ISZX.mjs";
4
4
  //#region src/cli.ts
5
5
  const args = process.argv.slice(2);
6
6
  const command = args[0];
@@ -25,13 +25,13 @@ async function main() {
25
25
  switch (command) {
26
26
  case "init": {
27
27
  const rootDir = await findRoot();
28
- const { initCommand } = await import("./init-BCkm6Nfa.mjs");
28
+ const { initCommand } = await import("./init-DkAY5hjc.mjs");
29
29
  await initCommand(rootDir, { force: flags.force === true });
30
30
  break;
31
31
  }
32
32
  case "add": {
33
33
  const rootDir = await findRoot();
34
- const { addCommand } = await import("./add-5how2kia.mjs");
34
+ const { addCommand } = await import("./add-C9rU_89s.mjs");
35
35
  await addCommand(rootDir, {
36
36
  packages: flags.packages,
37
37
  message: flags.message,
@@ -43,25 +43,29 @@ async function main() {
43
43
  }
44
44
  case "status": {
45
45
  const rootDir = await findRoot();
46
- const { statusCommand } = await import("./status-BbsDr6t7.mjs");
46
+ const { statusCommand } = await import("./status-CrMvvvNy.mjs");
47
47
  await statusCommand(rootDir, {
48
48
  json: flags.json === true,
49
49
  packagesOnly: flags.packages === true,
50
50
  bumpType: flags.bump,
51
51
  filter: flags.filter,
52
- verbose: flags.verbose === true
52
+ verbose: flags.verbose === true,
53
+ channel: flags.channel
53
54
  });
54
55
  break;
55
56
  }
56
57
  case "version": {
57
58
  const rootDir = await findRoot();
58
- const { versionCommand } = await import("./version-Cm0nRAFF.mjs");
59
- await versionCommand(rootDir, { commit: flags.commit === true });
59
+ const { versionCommand } = await import("./version-BUUf8vKC.mjs");
60
+ await versionCommand(rootDir, {
61
+ commit: flags.commit === true,
62
+ channel: flags.channel
63
+ });
60
64
  break;
61
65
  }
62
66
  case "generate": {
63
67
  const rootDir = await findRoot();
64
- const { generateCommand } = await import("./generate-CvCvUaRV.mjs");
68
+ const { generateCommand } = await import("./generate-B2OMt_64.mjs");
65
69
  await generateCommand(rootDir, {
66
70
  from: flags.from,
67
71
  dryRun: flags["dry-run"] === true,
@@ -71,7 +75,7 @@ async function main() {
71
75
  }
72
76
  case "check": {
73
77
  const rootDir = await findRoot();
74
- const { checkCommand } = await import("./check-CsF0zh8r.mjs").then((n) => n.t);
78
+ const { checkCommand } = await import("./check-DIl9Dz68.mjs").then((n) => n.t);
75
79
  const hookValue = flags.hook;
76
80
  if (hookValue && hookValue !== "pre-commit" && hookValue !== "pre-push") {
77
81
  log.error(`Invalid --hook value "${hookValue}". Expected "pre-commit" or "pre-push".`);
@@ -80,7 +84,8 @@ async function main() {
80
84
  await checkCommand(rootDir, {
81
85
  strict: flags.strict === true,
82
86
  noFail: flags["no-fail"] === true,
83
- hook: hookValue
87
+ hook: hookValue,
88
+ base: flags.base
84
89
  });
85
90
  break;
86
91
  }
@@ -89,17 +94,17 @@ async function main() {
89
94
  const subcommand = args[1];
90
95
  const ciFlags = parseFlags(args.slice(2));
91
96
  if (subcommand === "check") {
92
- const { ciCheckCommand } = await import("./ci-CIamssoq.mjs");
97
+ const { ciCheckCommand } = await import("./ci-hO7tAbCN.mjs");
93
98
  await ciCheckCommand(rootDir, {
94
99
  comment: ciFlags.comment !== void 0 ? ciFlags.comment === true : void 0,
95
100
  strict: ciFlags.strict === true,
96
101
  noFail: ciFlags["no-fail"] === true
97
102
  });
98
103
  } else if (subcommand === "plan") {
99
- const { ciPlanCommand } = await import("./ci-CIamssoq.mjs");
104
+ const { ciPlanCommand } = await import("./ci-hO7tAbCN.mjs");
100
105
  await ciPlanCommand(rootDir);
101
106
  } else if (subcommand === "release") {
102
- const { ciReleaseCommand } = await import("./ci-CIamssoq.mjs");
107
+ const { ciReleaseCommand } = await import("./ci-hO7tAbCN.mjs");
103
108
  const expectModeFlag = ciFlags["expect-mode"];
104
109
  const autoPublishFlag = ciFlags["auto-publish"] === true;
105
110
  if (expectModeFlag !== void 0 && expectModeFlag !== "version-pr" && expectModeFlag !== "publish") {
@@ -127,12 +132,13 @@ async function main() {
127
132
  }
128
133
  case "publish": {
129
134
  const rootDir = await findRoot();
130
- const { publishCommand } = await import("./publish-h6rM58Cq.mjs");
135
+ const { publishCommand } = await import("./publish-Cz0e4KYT.mjs");
131
136
  await publishCommand(rootDir, {
132
137
  dryRun: flags["dry-run"] === true,
133
138
  tag: flags.tag,
134
139
  noPush: flags["no-push"] === true,
135
- filter: flags.filter
140
+ filter: flags.filter,
141
+ channel: flags.channel
136
142
  });
137
143
  break;
138
144
  }
@@ -151,7 +157,7 @@ async function main() {
151
157
  }
152
158
  case "--version":
153
159
  case "-v":
154
- console.log(`bumpy 1.13.1`);
160
+ console.log(`bumpy 1.13.2`);
155
161
  break;
156
162
  case "help":
157
163
  case "--help":
@@ -171,7 +177,7 @@ async function main() {
171
177
  }
172
178
  function printHelp() {
173
179
  console.log(`
174
- ${colorize(`🐸 bumpy v1.13.1`, "bold")} - Modern monorepo versioning
180
+ ${colorize(`🐸 bumpy v1.13.2`, "bold")} - Modern monorepo versioning
175
181
 
176
182
  Usage: bumpy <command> [options]
177
183
 
@@ -186,8 +192,11 @@ function printHelp() {
186
192
  --strict Fail if any changed package is uncovered (default: only fail if no bump files at all)
187
193
  --no-fail Warn only, never exit 1
188
194
  --hook <context> Hook context: "pre-commit" or "pre-push" (controls which bump files count)
195
+ --base <branch> Branch to compare against (default: baseBranch; use the channel branch for channel PRs)
189
196
  version [--commit] Apply bump files and bump versions
197
+ (on a channel branch: moves pending bump files into .bumpy/<channel>/)
190
198
  publish Publish versioned packages
199
+ (on a channel branch: derives prerelease versions and publishes to the channel dist-tag)
191
200
  ci check PR check — report pending releases, comment on PR
192
201
  ci plan Report what ci release would do (JSON + GitHub Actions outputs)
193
202
  ci release Release — create version PR or auto-publish
@@ -211,12 +220,18 @@ function printHelp() {
211
220
  --bump <types> Filter by bump type (e.g., "major", "minor,patch")
212
221
  --filter <names> Filter by package name/glob (e.g., "@myorg/*")
213
222
  --verbose Show bump file details
223
+ --channel <name> Show channel status (default: inferred from the current branch)
214
224
 
215
225
  Publish options:
216
226
  --dry-run Preview without publishing
217
227
  --tag <tag> npm dist-tag (e.g., "next", "beta")
218
228
  --no-push Skip pushing git tags to remote
219
229
  --filter <names> Publish only matching packages (e.g., "@myorg/*")
230
+ --channel <name> Publish a prerelease channel (default: inferred from the current branch)
231
+
232
+ Version options:
233
+ --commit Create a git commit with the version changes
234
+ --channel <name> Channel override (default: inferred from the current branch)
220
235
 
221
236
  CI check options:
222
237
  --comment Force PR comment on/off (auto-detected in CI)
@@ -1,6 +1,6 @@
1
1
  import { a as __exportAll } from "./logger-BgksGFuf.mjs";
2
2
  import { a as readJson, n as exists, o as readJsonc } from "./fs-CBXKZhoU.mjs";
3
- import { l as normalizeCascadeConfig, r as DEFAULT_CONFIG } from "./types-Bkh-igOJ.mjs";
3
+ import { l as normalizeCascadeConfig, r as DEFAULT_CONFIG } from "./types-lpiG-Zxh.mjs";
4
4
  import { resolve } from "node:path";
5
5
  //#region src/core/config.ts
6
6
  var config_exports = /* @__PURE__ */ __exportAll({
@@ -89,6 +89,10 @@ function mergeConfig(defaults, user) {
89
89
  packages: {
90
90
  ...defaults.packages,
91
91
  ...user.packages
92
+ },
93
+ channels: {
94
+ ...defaults.channels,
95
+ ...user.channels
92
96
  }
93
97
  };
94
98
  }
@@ -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 { r as getBranchCommits, s as getFilesChangedInCommit } from "./git-DJJ64SW9.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
@@ -8,13 +8,23 @@ function createTag(tag, opts) {
8
8
  tag
9
9
  ], opts);
10
10
  }
11
- /** Push tags to remote, using BUMPY_GH_TOKEN if available */
12
- function pushWithTags(opts) {
11
+ /**
12
+ * Force-push a single tag to origin, using BUMPY_GH_TOKEN if available.
13
+ *
14
+ * Force is required because `gh release create --draft --target SHA` creates
15
+ * the tag on the remote at draft-creation time. If a previous publish attempt
16
+ * failed and HEAD has since moved, the remote tag points at the stale SHA —
17
+ * `git push --tags` would reject. The caller is responsible for ensuring the
18
+ * local tag is at the correct SHA (i.e. only call after a successful publish).
19
+ */
20
+ function forcePushTag(tag, opts) {
13
21
  withGitToken(opts?.cwd, () => {
14
22
  runArgs([
15
23
  "git",
16
24
  "push",
17
- "--tags"
25
+ "origin",
26
+ `refs/tags/${tag}`,
27
+ "--force"
18
28
  ], opts);
19
29
  });
20
30
  }
@@ -259,5 +269,16 @@ function getFileStatuses(dir, opts) {
259
269
  }
260
270
  return statuses;
261
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
+ }
262
283
  //#endregion
263
- export { getCurrentBranch as a, hasUncommittedChanges as c, tagExists as d, withGitToken as f, getChangedFiles as i, pushWithTags as l, getBaseCompareRef as n, getFileStatuses as o, getBranchCommits as r, getFilesChangedInCommit 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-DSj14dW6.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,186 @@
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
+ /** One-line summary of a channel plan's versions, for PR titles and commit messages */
179
+ function formatChannelVersionSummary(releases) {
180
+ if (releases.length === 0) return "";
181
+ const lead = releases.filter((r) => !r.isDependencyBump && !r.isCascadeBump && !r.isGroupBump)[0] ?? releases[0];
182
+ const rest = releases.length - 1;
183
+ return rest > 0 ? `${lead.name}@${lead.newVersion} (+${rest} more)` : `${lead.name}@${lead.newVersion}`;
184
+ }
185
+ //#endregion
186
+ export { formatChannelVersionSummary as n, writeChannelVersionsInPlace as r, buildChannelReleasePlan as t };