From 989ad69aafa8fe177c8bea8ce541122da11feb11 Mon Sep 17 00:00:00 2001 From: Theo Ephraim Date: Mon, 4 May 2026 14:49:21 -0700 Subject: [PATCH 1/3] Fix `bumpy add` interactive prompt: distinguish skip vs none, respect existing bump files - Add 'skip' level to distinguish "not included" from explicit "none" in bump files - Load existing branch bump files so already-covered packages default to skip - Fix keypress listener cleanup to prevent ghost handlers on subsequent prompts - Fix emitKeypressEvents ordering - Only offer cascade options for actual version bumps, not 'none' --- .bumpy/fix-add-command.md | 5 +++ packages/bumpy/src/commands/add.ts | 35 +++++++++++----- packages/bumpy/src/prompts/bump-select.ts | 49 ++++++++++++++--------- 3 files changed, 61 insertions(+), 28 deletions(-) create mode 100644 .bumpy/fix-add-command.md diff --git a/.bumpy/fix-add-command.md b/.bumpy/fix-add-command.md new file mode 100644 index 0000000..31b8a89 --- /dev/null +++ b/.bumpy/fix-add-command.md @@ -0,0 +1,5 @@ +--- +'@varlock/bumpy': patch +--- + +Fix add command: distinguish skip vs none, respect existing bump files, fix prompt rendering diff --git a/packages/bumpy/src/commands/add.ts b/packages/bumpy/src/commands/add.ts index 356c23c..da7e267 100644 --- a/packages/bumpy/src/commands/add.ts +++ b/packages/bumpy/src/commands/add.ts @@ -4,7 +4,7 @@ import { log } from '../utils/logger.ts'; import { p, unwrap } from '../utils/clack.ts'; import { ensureDir, exists } from '../utils/fs.ts'; import { randomName, slugify } from '../utils/names.ts'; -import { writeBumpFile } from '../core/bump-file.ts'; +import { writeBumpFile, readBumpFiles, filterBranchBumpFiles } from '../core/bump-file.ts'; import picomatch from 'picomatch'; import { getBumpyDir, loadConfig, loadPackageConfig, matchGlob } from '../core/config.ts'; import { discoverPackages, discoverWorkspace } from '../core/workspace.ts'; @@ -12,7 +12,7 @@ import { findChangedPackages } from './check.ts'; import { DependencyGraph } from '../core/dep-graph.ts'; import { getChangedFiles } from '../core/git.ts'; import { bumpSelectPrompt } from '../prompts/bump-select.ts'; -import type { BumpSelectItem } from '../prompts/bump-select.ts'; +import type { BumpSelectItem, BumpLevel } from '../prompts/bump-select.ts'; import type { BumpType, BumpTypeWithNone, BumpFileRelease, BumpFileReleaseCascade } from '../types.ts'; interface AddOptions { @@ -117,12 +117,29 @@ export async function addCommand(rootDir: string, opts: AddOptions): Promise(); + for (const bf of branchBumpFiles) { + for (const release of bf.releases) { + alreadyCoveredPackages.set(release.name, release.type === 'none' ? 'none' : release.type); + } + } + // Build items for the bump select prompt - const bumpSelectItems: BumpSelectItem[] = [...pkgs.values()].map((pkg) => ({ - name: pkg.name, - version: pkg.version, - changed: changedPackageNames.has(pkg.name), - })); + const bumpSelectItems: BumpSelectItem[] = [...pkgs.values()].map((pkg) => { + const item: BumpSelectItem = { + name: pkg.name, + version: pkg.version, + changed: changedPackageNames.has(pkg.name), + }; + // If already covered by an existing bump file, default to skip + if (alreadyCoveredPackages.has(pkg.name)) { + item.initialLevel = 'skip'; + } + return item; + }); const bumpSelectResult = await bumpSelectPrompt(bumpSelectItems); if (typeof bumpSelectResult === 'symbol') { @@ -140,8 +157,8 @@ export async function addCommand(rootDir: string, opts: AddOptions): Promise (item.changed ? 'patch' : 'none')); + const levels: BumpLevel[] = items.map((item) => + item.initialLevel !== undefined ? item.initialLevel : item.changed ? 'patch' : 'skip', + ); return new Promise((resolve) => { const { stdin, stdout } = process; @@ -55,9 +60,9 @@ export async function bumpSelectPrompt(items: BumpSelectItem[]): Promise levels[idx] !== 'none'); + const selected = displayOrder.filter(({ idx }) => levels[idx] !== 'skip'); if (selected.length === 0) { - lines.push(`${pc.dim('│')} ${pc.dim('(none)')}`); + lines.push(`${pc.dim('│')} ${pc.dim('(none selected)')}`); } else { for (const { item, idx } of selected) { lines.push(`${pc.dim('│')} ${pc.cyan(item.name)} ${pc.dim('→')} ${pc.bold(levels[idx])}`); @@ -67,7 +72,7 @@ export async function bumpSelectPrompt(items: BumpSelectItem[]): Promise l !== 'none').length; + const selectedCount = levels.filter((l) => l !== 'skip').length; lines.push(`${pc.dim('│')} ${pc.dim(`${selectedCount} package${selectedCount !== 1 ? 's' : ''} selected`)}`); lines.push(`${pc.dim('└')}`); } @@ -103,6 +108,7 @@ export async function bumpSelectPrompt(items: BumpSelectItem[]): Promise { + function onKeypress(_str: string | undefined, key: readline.Key) { if (!key) return; if (key.name === 'escape' || (key.ctrl && key.name === 'c')) { @@ -138,7 +147,7 @@ export async function bumpSelectPrompt(items: BumpSelectItem[]): Promise { if (l === level) { + if (l === 'skip') return pc.bold(pc.dim('[skip]')); if (l === 'none') return pc.bold(pc.dim('[none]')); if (l === 'major') return pc.bold(pc.red(`[${l}]`)); if (l === 'minor') return pc.bold(pc.yellow(`[${l}]`)); From 14406d42d1e49e095f7fe4a6acc0ef096bbf95d3 Mon Sep 17 00:00:00 2001 From: Theo Ephraim Date: Mon, 4 May 2026 14:58:37 -0700 Subject: [PATCH 2/3] Remove cascade prompt from interactive add flow Cascades are an advanced feature better authored directly in bump files. --- packages/bumpy/src/commands/add.ts | 70 ++---------------------------- 1 file changed, 3 insertions(+), 67 deletions(-) diff --git a/packages/bumpy/src/commands/add.ts b/packages/bumpy/src/commands/add.ts index da7e267..af22433 100644 --- a/packages/bumpy/src/commands/add.ts +++ b/packages/bumpy/src/commands/add.ts @@ -6,14 +6,13 @@ import { ensureDir, exists } from '../utils/fs.ts'; import { randomName, slugify } from '../utils/names.ts'; import { writeBumpFile, readBumpFiles, filterBranchBumpFiles } from '../core/bump-file.ts'; import picomatch from 'picomatch'; -import { getBumpyDir, loadConfig, loadPackageConfig, matchGlob } from '../core/config.ts'; +import { getBumpyDir, loadConfig, loadPackageConfig } from '../core/config.ts'; import { discoverPackages, discoverWorkspace } from '../core/workspace.ts'; import { findChangedPackages } from './check.ts'; -import { DependencyGraph } from '../core/dep-graph.ts'; import { getChangedFiles } from '../core/git.ts'; import { bumpSelectPrompt } from '../prompts/bump-select.ts'; import type { BumpSelectItem, BumpLevel } from '../prompts/bump-select.ts'; -import type { BumpType, BumpTypeWithNone, BumpFileRelease, BumpFileReleaseCascade } from '../types.ts'; +import type { BumpTypeWithNone, BumpFileRelease } from '../types.ts'; interface AddOptions { packages?: string; // "pkg-a:minor,pkg-b:patch" @@ -23,12 +22,6 @@ interface AddOptions { none?: boolean; } -const CASCADE_CHOICES: { label: string; value: BumpType }[] = [ - { label: 'patch', value: 'patch' }, - { label: 'minor', value: 'minor' }, - { label: 'major', value: 'major' }, -]; - export async function addCommand(rootDir: string, opts: AddOptions): Promise { const config = await loadConfig(rootDir); const bumpyDir = getBumpyDir(rootDir); @@ -86,8 +79,6 @@ export async function addCommand(rootDir: string, opts: AddOptions): Promise 0 || cascadeTargets) { - const wantCascade = unwrap( - await p.confirm({ - message: `${pc.cyan(name)} has ${pc.bold(String(dependents.length))} dependents. Specify explicit cascades?`, - initialValue: false, - }), - ); - - if (wantCascade) { - const allTargets = new Set(); - for (const d of dependents) allTargets.add(d.name); - if (cascadeTargets) { - for (const pattern of Object.keys(cascadeTargets)) { - for (const [pName] of pkgs) { - if (matchGlob(pName, pattern)) allTargets.add(pName); - } - } - } - - const cascadeSelected = unwrap( - await p.multiselect({ - message: 'Which packages should cascade?', - options: [...allTargets].map((n) => ({ label: n, value: n })), - required: false, - }), - ); - - if (cascadeSelected.length > 0) { - const cascadeBump = unwrap( - await p.select({ - message: 'Cascade bump type', - options: CASCADE_CHOICES, - }), - ); - const cascade: Record = {}; - for (const target of cascadeSelected) { - cascade[target] = cascadeBump; - } - (release as BumpFileReleaseCascade).cascade = cascade; - } - } - } - } - - releases.push(release); - } + releases = bumpSelections.map(({ name, type }) => ({ name, type }) as BumpFileRelease); summary = unwrap( await p.text({ From 7bae690beaf3389f4f0257a8872fa39d9542ef37 Mon Sep 17 00:00:00 2001 From: Theo Ephraim Date: Mon, 4 May 2026 15:03:10 -0700 Subject: [PATCH 3/3] Update bump file summary to include cascade prompt removal --- .bumpy/fix-add-command.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.bumpy/fix-add-command.md b/.bumpy/fix-add-command.md index 31b8a89..13fe5ec 100644 --- a/.bumpy/fix-add-command.md +++ b/.bumpy/fix-add-command.md @@ -2,4 +2,4 @@ '@varlock/bumpy': patch --- -Fix add command: distinguish skip vs none, respect existing bump files, fix prompt rendering +Fix `bumpy add` interactive prompt: distinguish skip vs none, respect existing bump files, fix prompt rendering, remove cascade prompt