@varlock/bumpy 1.9.2 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@varlock/bumpy",
3
- "version": "1.9.2",
3
+ "version": "1.10.0",
4
4
  "description": "Modern monorepo versioning and changelog tool",
5
5
  "keywords": [
6
6
  "bump",
@@ -1,371 +0,0 @@
1
- import { n as log, o as __require, t as colorize } from "./logger-BgksGFuf.mjs";
2
- import { a as loadConfig } from "./config-48u1NbKv.mjs";
3
- import { n as detectWorkspaces } from "./package-manager-BQPwXwu5.mjs";
4
- import { s as discoverWorkspace } from "./bump-file-B_7P2UZO.mjs";
5
- import { a as DependencyGraph } from "./release-plan-s1o52Rc-.mjs";
6
- import { r as runArgsAsync, s as tryRunArgs } from "./shell-C8KgKnMQ.mjs";
7
- import { i as loadFormatter, n as generateChangelogEntry } from "./changelog-LaYJ7aUa.mjs";
8
- import { c as listTags, l as pushWithTags, s as hasUncommittedChanges } from "./git-CpJqzpp-.mjs";
9
- import { t as publishPackages } from "./publish-pipeline-DpmTVsnX.mjs";
10
- import { CI_PLAN_CACHE_PATH } from "./ci-CHIpKtvI.mjs";
11
- //#region src/core/github-release.ts
12
- /** Get the current HEAD commit SHA */
13
- function getHeadSha(rootDir) {
14
- return tryRunArgs([
15
- "git",
16
- "rev-parse",
17
- "HEAD"
18
- ], { cwd: rootDir });
19
- }
20
- /** Create individual GitHub releases for each published package */
21
- async function createIndividualReleases(releases, bumpFiles, rootDir, opts = {}) {
22
- if (!isGhAvailable()) {
23
- log.dim(" gh CLI not found — skipping GitHub releases");
24
- return;
25
- }
26
- const headSha = getHeadSha(rootDir);
27
- for (const release of releases) {
28
- const tag = `${release.name}@${release.newVersion}`;
29
- const body = opts.formatter ? await generateReleaseBody(release, bumpFiles, opts.formatter) : buildReleaseBody(release, bumpFiles);
30
- const title = `${release.name} v${release.newVersion}`;
31
- if (opts.dryRun) {
32
- log.dim(` Would create GitHub release: ${title}`);
33
- continue;
34
- }
35
- try {
36
- const args = [
37
- "gh",
38
- "release",
39
- "create",
40
- tag,
41
- "--title",
42
- title,
43
- "--notes",
44
- body
45
- ];
46
- if (headSha) args.push("--target", headSha);
47
- await runArgsAsync(args, { cwd: rootDir });
48
- log.dim(` Created GitHub release: ${title}`);
49
- } catch (err) {
50
- log.warn(` Failed to create GitHub release for ${tag}: ${err instanceof Error ? err.message : err}`);
51
- }
52
- }
53
- }
54
- /** Create a single aggregated GitHub release for all published packages */
55
- async function createAggregateRelease(releases, bumpFiles, rootDir, opts = {}) {
56
- if (!isGhAvailable()) {
57
- log.dim(" gh CLI not found — skipping GitHub release");
58
- return;
59
- }
60
- if (releases.length === 0) return;
61
- const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
62
- const { tag, title } = resolveAggregateTagAndTitle(date, listTags(`release-${date}*`, { cwd: rootDir }), opts.title);
63
- const body = opts.formatter ? await generateAggregateBody(releases, bumpFiles, opts.formatter) : buildAggregateBody(releases, bumpFiles);
64
- if (opts.dryRun) {
65
- log.dim(` Would create aggregate GitHub release: ${title}`);
66
- log.dim(` Tag: ${tag}`);
67
- return;
68
- }
69
- try {
70
- tryRunArgs([
71
- "git",
72
- "tag",
73
- tag
74
- ], { cwd: rootDir });
75
- const headSha = getHeadSha(rootDir);
76
- const args = [
77
- "gh",
78
- "release",
79
- "create",
80
- tag,
81
- "--title",
82
- title,
83
- "--notes",
84
- body
85
- ];
86
- if (headSha) args.push("--target", headSha);
87
- await runArgsAsync(args, { cwd: rootDir });
88
- log.success(`Created aggregate GitHub release: ${title}`);
89
- } catch (err) {
90
- log.warn(`Failed to create aggregate GitHub release: ${err instanceof Error ? err.message : err}`);
91
- }
92
- }
93
- /** Generate a release body for a single package using the changelog formatter */
94
- async function generateReleaseBody(release, bumpFiles, formatter) {
95
- return stripVersionHeading(await generateChangelogEntry(release, bumpFiles, formatter, void 0, "github-release")).trim() || "No changelog entries.";
96
- }
97
- /** Generate an aggregate release body using the changelog formatter */
98
- async function generateAggregateBody(releases, bumpFiles, formatter) {
99
- const lines = [];
100
- const groups = [
101
- ["Major Changes", releases.filter((r) => r.type === "major")],
102
- ["Minor Changes", releases.filter((r) => r.type === "minor")],
103
- ["Patch Changes", releases.filter((r) => r.type === "patch")]
104
- ];
105
- for (const [heading, group] of groups) {
106
- if (group.length === 0) continue;
107
- lines.push(`## ${heading}\n`);
108
- for (const release of group) {
109
- lines.push(`### ${release.name} v${release.newVersion}\n`);
110
- const body = stripVersionHeading(await generateChangelogEntry(release, bumpFiles, formatter, void 0, "github-release")).trim();
111
- if (body) lines.push(body);
112
- else if (release.isDependencyBump) {
113
- const sourceList = release.bumpSources.map((s) => `\`${s.name}\` v${s.newVersion}`).join(", ");
114
- lines.push(sourceList ? `- Updated dependency ${sourceList}` : "- Updated dependencies");
115
- } else if (release.isGroupBump) {
116
- const sourceList = release.bumpSources.map((s) => `\`${s.name}\` v${s.newVersion}`).join(", ");
117
- lines.push(sourceList ? `- Version bump from group with ${sourceList}` : "- Version bump from group");
118
- } else if (release.isCascadeBump) {
119
- const sourceList = release.bumpSources.map((s) => `\`${s.name}\` v${s.newVersion}`).join(", ");
120
- lines.push(sourceList ? `- Version bump from ${sourceList}` : "- Version bump via cascade rule");
121
- }
122
- lines.push("");
123
- }
124
- }
125
- return lines.join("\n").trim() || "No changelog entries.";
126
- }
127
- /** Strip the leading ## version heading and date sub-heading from a changelog entry */
128
- function stripVersionHeading(entry) {
129
- return entry.replace(/^## .+\n/, "").replace(/^<sub>.+<\/sub>\n/, "").replace(/^_.+_\n/, "");
130
- }
131
- function buildReleaseBody(release, bumpFiles) {
132
- const lines = [];
133
- const relevant = bumpFiles.filter((bf) => release.bumpFiles.includes(bf.id));
134
- if (relevant.length > 0) {
135
- for (const bf of relevant) if (bf.summary) lines.push(`- ${bf.summary.split("\n")[0]}`);
136
- }
137
- if (relevant.length === 0) {
138
- const sourceList = release.bumpSources.map((s) => `\`${s.name}\` v${s.newVersion}`).join(", ");
139
- if (release.isDependencyBump) lines.push(sourceList ? `- Updated dependency ${sourceList}` : "- Updated dependencies");
140
- else if (release.isGroupBump) lines.push(sourceList ? `- Version bump from group with ${sourceList}` : "- Version bump from group");
141
- else if (release.isCascadeBump) lines.push(sourceList ? `- Version bump from ${sourceList}` : "- Version bump via cascade rule");
142
- }
143
- return lines.join("\n") || "No changelog entries.";
144
- }
145
- function buildAggregateBody(releases, bumpFiles) {
146
- const lines = [];
147
- const groups = [
148
- ["Major Changes", releases.filter((r) => r.type === "major")],
149
- ["Minor Changes", releases.filter((r) => r.type === "minor")],
150
- ["Patch Changes", releases.filter((r) => r.type === "patch")]
151
- ];
152
- for (const [heading, group] of groups) {
153
- if (group.length === 0) continue;
154
- lines.push(`## ${heading}\n`);
155
- for (const release of group) {
156
- lines.push(`### ${release.name} v${release.newVersion}\n`);
157
- const relevant = bumpFiles.filter((bf) => release.bumpFiles.includes(bf.id));
158
- if (relevant.length > 0) {
159
- for (const bf of relevant) if (bf.summary) lines.push(`- ${bf.summary.split("\n")[0]}`);
160
- } else {
161
- const sourceList = release.bumpSources.map((s) => `\`${s.name}\` v${s.newVersion}`).join(", ");
162
- if (release.isDependencyBump) lines.push(sourceList ? `- Updated dependency ${sourceList}` : "- Updated dependencies");
163
- else if (release.isGroupBump) lines.push(sourceList ? `- Version bump from group with ${sourceList}` : "- Version bump from group");
164
- else if (release.isCascadeBump) lines.push(sourceList ? `- Version bump from ${sourceList}` : "- Version bump via cascade rule");
165
- }
166
- lines.push("");
167
- }
168
- }
169
- return lines.join("\n").trim() || "No changelog entries.";
170
- }
171
- /** Compute the aggregate release tag and title, appending -n suffix if a tag for the same date already exists */
172
- function resolveAggregateTagAndTitle(date, existingTags, titleTemplate) {
173
- const baseTag = `release-${date}`;
174
- const suffix = existingTags.length === 0 ? "" : `-${existingTags.length + 1}`;
175
- return {
176
- tag: `${baseTag}${suffix}`,
177
- title: (titleTemplate || "Release {{date}}").replace("{{date}}", `${date}${suffix}`)
178
- };
179
- }
180
- function isGhAvailable() {
181
- return tryRunArgs(["gh", "--version"]) !== null;
182
- }
183
- //#endregion
184
- //#region src/commands/publish.ts
185
- /**
186
- * Publish packages that have been versioned but not yet published.
187
- * Detects unpublished versions by comparing package.json versions against npm registry.
188
- */
189
- async function publishCommand(rootDir, opts) {
190
- const config = await loadConfig(rootDir);
191
- const { packages, catalogs } = await discoverWorkspace(rootDir, config);
192
- const { packageManager: detectedPm } = await detectWorkspaces(rootDir);
193
- const depGraph = new DependencyGraph(packages);
194
- if (!opts.dryRun && hasUncommittedChanges({ cwd: rootDir })) {
195
- log.warn("You have uncommitted changes. Commit or stash them before publishing.");
196
- process.exit(1);
197
- }
198
- let toPublish = await findUnpublishedWithCache(rootDir, packages, config);
199
- if (opts.filter) {
200
- const { matchGlob } = await import("./config-48u1NbKv.mjs").then((n) => n.t);
201
- const patterns = opts.filter.split(",").map((p) => p.trim());
202
- toPublish = toPublish.filter((r) => patterns.some((p) => matchGlob(r.name, p)));
203
- }
204
- if (toPublish.length === 0) {
205
- log.info("No unpublished packages found.");
206
- return;
207
- }
208
- const recoveredBumpFiles = opts.recoveredBumpFiles || [];
209
- 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);
210
- const releasePlan = {
211
- bumpFiles: recoveredBumpFiles,
212
- releases: toPublish,
213
- warnings: []
214
- };
215
- if (opts.dryRun) log.bold("Dry run — would publish:");
216
- else log.bold("Publishing:");
217
- for (const r of toPublish) console.log(` ${r.name}@${colorize(r.newVersion, "cyan")}`);
218
- console.log();
219
- const result = await publishPackages(releasePlan, packages, depGraph, config, rootDir, {
220
- dryRun: opts.dryRun,
221
- tag: opts.tag
222
- }, catalogs, detectedPm);
223
- if (result.published.length > 0) log.success(`🐸 Published ${result.published.length} package(s)`);
224
- if (result.skipped.length > 0) log.dim(`Skipped ${result.skipped.length}: ${result.skipped.map((s) => s.name).join(", ")}`);
225
- if (result.failed.length > 0) {
226
- log.error(`Failed ${result.failed.length}: ${result.failed.map((f) => `${f.name} (${f.error})`).join(", ")}`);
227
- process.exit(1);
228
- }
229
- if (!opts.dryRun && !opts.noPush && result.published.length > 0) try {
230
- log.step("Pushing tags...");
231
- pushWithTags({ cwd: rootDir });
232
- log.success("Pushed tags to remote");
233
- } catch (err) {
234
- log.warn(`Failed to push tags: ${err instanceof Error ? err.message : err}`);
235
- }
236
- if (result.published.length > 0) {
237
- const publishedReleases = releasePlan.releases.filter((r) => result.published.some((p) => p.name === r.name));
238
- const aggConfig = config.aggregateRelease;
239
- const isAggregate = aggConfig === true || typeof aggConfig === "object" && aggConfig.enabled;
240
- const aggTitle = typeof aggConfig === "object" ? aggConfig.title : void 0;
241
- const formatter = config.changelog !== false ? await loadFormatter(config.changelog, rootDir) : void 0;
242
- if (isAggregate) await createAggregateRelease(publishedReleases, releasePlan.bumpFiles, rootDir, {
243
- dryRun: opts.dryRun,
244
- title: aggTitle,
245
- formatter
246
- });
247
- else await createIndividualReleases(publishedReleases, releasePlan.bumpFiles, rootDir, {
248
- dryRun: opts.dryRun,
249
- formatter
250
- });
251
- }
252
- }
253
- /**
254
- * Try to load cached plan from `ci plan`. Returns the unpublished package names
255
- * if the cache is valid, or null to fall back to registry lookups.
256
- *
257
- * Validates that every cached package exists in the workspace with the same version,
258
- * so the cache can only filter — never fabricate — the set of packages.
259
- */
260
- function loadCachedPlan(rootDir, packages) {
261
- const cachePath = `${rootDir}/${CI_PLAN_CACHE_PATH}`;
262
- let raw;
263
- try {
264
- raw = __require("node:fs").readFileSync(cachePath, "utf-8");
265
- __require("node:fs").unlinkSync(cachePath);
266
- } catch {
267
- return null;
268
- }
269
- try {
270
- const cached = JSON.parse(raw);
271
- if (cached?.mode !== "publish" || !Array.isArray(cached.releases)) return null;
272
- const names = /* @__PURE__ */ new Set();
273
- for (const r of cached.releases) {
274
- if (typeof r.name !== "string" || typeof r.newVersion !== "string") return null;
275
- const pkg = packages.get(r.name);
276
- if (!pkg || pkg.version !== r.newVersion) {
277
- log.dim(" ci plan cache is stale — falling back to registry lookups");
278
- return null;
279
- }
280
- names.add(r.name);
281
- }
282
- log.dim(" Using cached plan from ci plan");
283
- return names;
284
- } catch {
285
- return null;
286
- }
287
- }
288
- /**
289
- * Find unpublished packages, using the ci plan cache if available.
290
- * Falls back to registry lookups if no cache or cache is invalid.
291
- */
292
- async function findUnpublishedWithCache(rootDir, packages, config) {
293
- const cachedNames = loadCachedPlan(rootDir, packages);
294
- if (cachedNames) {
295
- const unpublished = [];
296
- for (const name of cachedNames) {
297
- const pkg = packages.get(name);
298
- unpublished.push({
299
- name,
300
- type: "patch",
301
- oldVersion: pkg.version,
302
- newVersion: pkg.version,
303
- bumpFiles: [],
304
- isDependencyBump: false,
305
- isCascadeBump: false,
306
- isGroupBump: false,
307
- bumpSources: []
308
- });
309
- }
310
- return unpublished;
311
- }
312
- return findUnpublishedPackages(packages, config);
313
- }
314
- /**
315
- * Find packages whose current version is not yet published.
316
- *
317
- * Detection strategy (per package):
318
- * 1. Custom `checkPublished` command → run it, compare output to current version
319
- * 2. `skipNpmPublish` or custom `publishCommand` → check git tags
320
- * 3. Default → check npm registry via `npm info`
321
- */
322
- async function findUnpublishedPackages(packages, _config) {
323
- const unpublished = [];
324
- for (const [name, pkg] of packages) {
325
- if (pkg.private && !pkg.bumpy?.publishCommand) continue;
326
- if (pkg.version === "0.0.0") continue;
327
- if (!await checkIfPublished(name, pkg.version, pkg.bumpy)) unpublished.push({
328
- name,
329
- type: "patch",
330
- oldVersion: pkg.version,
331
- newVersion: pkg.version,
332
- bumpFiles: [],
333
- isDependencyBump: false,
334
- isCascadeBump: false,
335
- isGroupBump: false,
336
- bumpSources: []
337
- });
338
- }
339
- return unpublished;
340
- }
341
- async function checkIfPublished(name, version, pkgConfig) {
342
- const { runAsync, runArgsAsync, tryRunArgs } = await import("./shell-C8KgKnMQ.mjs").then((n) => n.a);
343
- if (pkgConfig?.checkPublished) try {
344
- return (await runAsync(pkgConfig.checkPublished)).trim() === version;
345
- } catch {
346
- return false;
347
- }
348
- if (pkgConfig?.skipNpmPublish || pkgConfig?.publishCommand) {
349
- const tag = `${name}@${version}`;
350
- return tryRunArgs([
351
- "git",
352
- "tag",
353
- "-l",
354
- tag
355
- ]) === tag;
356
- }
357
- try {
358
- const args = [
359
- "npm",
360
- "info",
361
- `${name}@${version}`,
362
- "version"
363
- ];
364
- if (pkgConfig?.registry) args.push("--registry", pkgConfig.registry);
365
- return await runArgsAsync(args) === version;
366
- } catch {
367
- return false;
368
- }
369
- }
370
- //#endregion
371
- export { findUnpublishedPackages, publishCommand };