Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .bumpy/bundled-deps-and-no-bump-comment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@varlock/bumpy': minor
---

Change detection is now `package.json`-field-aware: when `package.json` is the only changed file in a package, bumpy diffs it against the base branch and only requires a bump file if a publish-affecting field changed. The new `ignoredPackageJsonFields` option (default `["devDependencies"]`) controls which fields are ignored, so a dev-only dependency bump (e.g. Dependabot) no longer requires a bump file — unless the changed dep matches the package's `releaseTriggeringDevDeps`.

`ci check` no longer posts a "you're good to go" comment while exiting 1. When the check fails because changed packages have no bump file, the comment now matches the failing status, lists the uncovered packages, and points at an empty bump file (`bumpy add --empty`) to acknowledge an intentional no-release.

Add a per-package `releaseTriggeringDevDeps` option: names/globs of `devDependencies` that affect a package's published output (most often because they're bundled in). A change to one requires a release, and a listed internal workspace dep's own releases cascade with a patch bump — shorthand for a `cascadeFrom` rule of `{ trigger: 'patch', bumpAs: 'patch' }`.
78 changes: 62 additions & 16 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,32 @@ Bumpy is configured via `.bumpy/_config.json`, created by `bumpy init`. Per-pack
| `dependencyBumpRules` | `object` | see below | Controls how bumps propagate through dependency types |
| `versionCommitMessage` | `string` | — | Customize the version commit message (see below) |
| `changedFilePatterns` | `string[]` | `["**"]` | Glob patterns to filter which changed files count toward marking a package as changed |
| `ignoredPackageJsonFields` | `string[]` | `["devDependencies"]` | `package.json` fields whose change alone doesn't require a bump file (see below) |
| `publish` | `object` | see below | Publishing pipeline config |
| `gitUser` | `{ name, email }` | bumpy-bot | Git identity for CI commits |
| `versionPr` | `{ title, branch, preamble }` | see below | Customize the version PR |
| `allowCustomCommands` | `boolean \| string[]` | `false` | Allow per-package custom commands from `package.json` (see below) |
| `packages` | `object` | `{}` | Per-package config overrides (keyed by package name) |
| `channels` | `object` | `{}` | Prerelease channels, keyed by channel name (see below) |

### Change detection and `package.json` fields

A package is "changed" (and so needs a bump file) when a changed file inside it matches `changedFilePatterns`. `package.json` is a special case: editing it shouldn't always demand a release — a `devDependencies` bump from Dependabot, for example, doesn't affect what consumers install.

So when `package.json` is the **only** changed file in a package, bumpy diffs it against the base branch and only flags the package if a field **outside** `ignoredPackageJsonFields` changed. The default ignore list is `["devDependencies"]`, meaning dev-only dependency updates don't require a bump file. Every other field — `dependencies`, `exports`, `bin`, `files`, `description`, `scripts`, etc. — still counts.

One exception keeps this safe: a changed `devDependencies` entry that matches the package's [`releaseTriggeringDevDeps`](#release-triggering-devdependencies) **does** flag the package, since such a dep affects the published output.

To relax additional fields (e.g. treat `scripts` changes as non-releasing too), extend the list:

```json
{
"ignoredPackageJsonFields": ["devDependencies", "scripts"]
}
```

bumpy errs toward requiring a bump file whenever it can't compare cleanly — a brand-new `package.json`, or one it can't parse.

### Dependency bump rules

Controls how a version bump in one package propagates to packages that depend on it. Set globally in `dependencyBumpRules` or per-package.
Expand Down Expand Up @@ -132,19 +151,20 @@ Per-package settings can be defined in two places:

`package.json` settings take precedence over global config.

| Option | Type | Description |
| --------------------- | -------------------------- | ---------------------------------------------------------------------------- |
| `managed` | `boolean` | Opt this package in or out of versioning |
| `access` | `"public" \| "restricted"` | Override the global access level |
| `publishCommand` | `string \| string[]` | Custom command(s) to publish this package (replaces npm publish) |
| `buildCommand` | `string` | Command to run before publishing |
| `registry` | `string` | Custom npm registry URL |
| `skipNpmPublish` | `boolean` | Don't publish to npm (still creates git tags) |
| `checkPublished` | `string` | Custom command that outputs the currently published version |
| `changedFilePatterns` | `string[]` | Glob patterns for changed-file detection (replaces root setting, not merged) |
| `dependencyBumpRules` | `object` | Per-package override for dependency propagation rules |
| `cascadeTo` | `object` | Explicit cascade targets — glob pattern mapped to `{ trigger, bumpAs }` |
| `cascadeFrom` | `object` | Explicit cascade sources — glob pattern mapped to `{ trigger, bumpAs }` |
| Option | Type | Description |
| -------------------------- | -------------------------- | -------------------------------------------------------------------------------------- |
| `managed` | `boolean` | Opt this package in or out of versioning |
| `access` | `"public" \| "restricted"` | Override the global access level |
| `publishCommand` | `string \| string[]` | Custom command(s) to publish this package (replaces npm publish) |
| `buildCommand` | `string` | Command to run before publishing |
| `registry` | `string` | Custom npm registry URL |
| `skipNpmPublish` | `boolean` | Don't publish to npm (still creates git tags) |
| `checkPublished` | `string` | Custom command that outputs the currently published version |
| `changedFilePatterns` | `string[]` | Glob patterns for changed-file detection (replaces root setting, not merged) |
| `dependencyBumpRules` | `object` | Per-package override for dependency propagation rules |
| `cascadeTo` | `object` | Explicit cascade targets — glob pattern mapped to `{ trigger, bumpAs }` |
| `cascadeFrom` | `object` | Explicit cascade sources — glob pattern mapped to `{ trigger, bumpAs }` |
| `releaseTriggeringDevDeps` | `string[]` | devDependencies that affect published output — a change requires a release (see below) |

### Custom commands and `allowCustomCommands`

Expand Down Expand Up @@ -222,15 +242,41 @@ Or with custom trigger/bumpAs:
}
```

### Example: cascade from a bundled dependency (consumer-side)
### Release-triggering devDependencies

By default a `devDependencies` change doesn't require a release — it's usually dev tooling (a linter, a type package, a test runner). But sometimes a dependency that affects your **published output** lives under `devDependencies`. `releaseTriggeringDevDeps` marks those, so a change to one requires a release (and, for internal workspace deps, its own releases cascade to you):

```json
{
"name": "@myorg/astro-integration",
"bumpy": {
"releaseTriggeringDevDeps": ["@myorg/vite-integration", "nanoid"]
}
}
```

**The usual reason is bundling.** A build step (tsup, tsdown, esbuild, rolldown/rollup, Vite, `bun build`, webpack, …) inlines imports into `dist/`, so consumers get a self-contained artifact. A bundled dependency isn't installed from the registry at consume time — its code is copied into your output — so it's conventionally declared under `devDependencies` (you don't want consumers to also install it). Taken to the extreme, a fully-bundled package can have **no runtime `dependencies` at all**; every library it imports sits in `devDependencies`. (bumpy itself is built this way with tsdown.) Other cases that fit: a dependency whose output you commit and ship (codegen), or a re-exported types-only package.

`releaseTriggeringDevDeps` declares intent — "a change to this dep changes what I publish" — and that drives **two** behaviors:

1. **Propagation** — when the dep gets its **own release**, this package is cascaded a **patch** bump (shorthand for a `cascadeFrom` rule of `{ "trigger": "patch", "bumpAs": "patch" }`). This only applies to **internal workspace** deps, since bumpy only releases packages in your workspace.
2. **Change detection** — when the dep's version is edited in **this** package's `package.json` (e.g. a Dependabot PR, or a manual bump), this package is flagged as changed and needs a bump file — even though `devDependencies` edits are normally ignored (see [Change detection](#change-detection-and-packagejson-fields)). This applies to **any** listed dep, internal or external.

So for an **internal** workspace dep, both paths fire; for an **external** dep (e.g. a published npm package you inline), only change detection applies — listing it is still useful, and is a harmless no-op for propagation.

#### Internal workspace deps: release-relevant or not

This is exactly the knob for "which `devDependencies` affect my published output." An internal workspace package listed under `devDependencies` is, by default, treated as dev-only — its releases don't cascade ([`dependencyBumpRules.devDependencies` is `false`](#dependency-bump-rules)) and bumping its range doesn't flag you. Add it to `releaseTriggeringDevDeps` to flip both: now its releases republish you, and editing its range flags you. Leave it out and it stays dev-only. No global setting is involved — it's per-dependency, per-consumer.

#### Proportional bumps

When a package bundles a devDependency into its published output, use `cascadeFrom` so bumps to the dependency also trigger a release of the consumer:
If you re-export the dependency's API and want **proportional** bumps (a minor in the dep → a minor here), use `cascadeFrom` directly instead — an explicit `cascadeFrom` rule for the same source takes precedence over the `releaseTriggeringDevDeps` patch default:

```json
{
"name": "@myorg/astro-integration",
"bumpy": {
"cascadeFrom": ["@myorg/vite-integration"]
"cascadeFrom": { "@myorg/vite-integration": { "trigger": "patch", "bumpAs": "match" } }
}
}
```
Expand Down
2 changes: 1 addition & 1 deletion docs/differences-from-changesets.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Bumpy splits propagation into three phases inside an iterative loop:
Key differences from changesets:

- Out-of-range peer dep bumps match the triggering bump level (not always major) — a minor bump on `core` → minor bump on `plugin`, not major
- Dev deps never propagate by default (configurable per-package for bundled devDeps)
- Dev deps never propagate by default (opt specific ones in per-package via `releaseTriggeringDevDeps`, e.g. bundled deps)
- `cascadeTo` config for source-side "when I change, cascade to these packages"
- Per-bump-file `none` to acknowledge changes without triggering a direct bump
- Warns about `^0.x` caret range gotchas and `workspace:*` on peer deps
Expand Down
4 changes: 3 additions & 1 deletion docs/version-propagation.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@ The bump type applied to the dependent depends on the dependency type:
| `peerDependencies` | matches the triggering bump | Proportional — a minor bump on the dep → minor bump on the dependent |
| `dependencies` | `patch` | Internal detail — consumers don't see it |
| `optionalDependencies` | `patch` | Internal detail — consumers don't see it |
| `devDependencies` | _(skipped)_ | Doesn't affect published consumers |
| `devDependencies` | _(skipped)_ | Doesn't affect published consumers |

For peer deps, "matches the triggering bump" means if `core` gets a minor bump that breaks the range, `plugin` also gets a minor bump. This keeps version bumps proportional — especially important for `0.x` packages where `^` ranges cause minor bumps to go out of range frequently.

> † `devDependencies` are skipped because they normally don't ship to consumers. The exception is a dependency that affects your published output — most often one **bundled** in by a build step (tsup, tsdown, esbuild, rolldown/rollup, Vite, `bun build`, …), declared under `devDependencies` since it isn't runtime-resolved. List those under `releaseTriggeringDevDeps` (or use `cascadeFrom`) so any bump to them republishes this package. See [Configuration](./configuration.md#release-triggering-devdependencies).

This phase is a **safety net** — it cannot be skipped. It ensures that published packages always have valid dependency ranges.

### `workspace:` protocol resolution
Expand Down
11 changes: 11 additions & 0 deletions packages/bumpy/config-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@
"items": { "type": "string" },
"default": ["**"]
},
"ignoredPackageJsonFields": {
"type": "array",
"description": "Top-level package.json fields whose change alone does not mark a package as changed. When package.json is the only changed file, it's diffed against the base branch and changes confined to these fields are ignored. Default: ['devDependencies']. A changed devDependencies entry matching the package's releaseTriggeringDevDeps still counts.",
"items": { "type": "string" },
"default": ["devDependencies"]
},
"fixed": {
"type": "array",
"description": "Package groups that always bump together to the same version. Each element is an array of package name globs.",
Expand Down Expand Up @@ -366,6 +372,11 @@
"cascadeFrom": {
"description": "Explicit cascade sources — when a matching package is bumped, cascade the bump to this package.",
"$ref": "#/$defs/cascadeConfig"
},
"releaseTriggeringDevDeps": {
"type": "array",
"description": "Names (or globs) of devDependencies that are release-relevant: a change to one requires a release of this package, and (for internal workspace deps) its own releases cascade here. devDependencies are ignored for versioning by default — this opts specific entries back in. The usual reason is a dep bundled into the published output (inlined by tsup/tsdown/esbuild/rollup/…). Shorthand for a cascadeFrom rule of { trigger: 'patch', bumpAs: 'patch' }; an explicit cascadeFrom for the same source takes precedence.",
"items": { "type": "string" }
}
},
"additionalProperties": false
Expand Down
9 changes: 9 additions & 0 deletions packages/bumpy/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,14 @@
"picomatch": "^4.0.4",
"semver": "^7.7.2",
"tsdown": "catalog:"
},
"bumpy": {
"releaseTriggeringDevDeps": [
"@clack/prompts",
"js-yaml",
"picocolors",
"picomatch",
"semver"
]
}
}
Loading