Skip to content

feat(publish): Add built-in development version bump after release - #950

Merged
BYK merged 4 commits into
masterfrom
feat/post-release-bump
Nov 2, 2026
Merged

BYK merged 4 commits into
masterfrom
feat/post-release-bump

Conversation

@sentry-junior

@sentry-junior sentry-junior Bot commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

Closes #949.

Many repos use postReleaseCommand for one job: write the next development version, commit, pull with rebase, and push. In almost every case the script just re-runs the repo's own pre-release bump with a dev version. This PR builds that into Craft as an opt-in setting:

postReleaseBump: true            # defaults: minor + -dev.0
postReleaseBump: patch           # shorthand for { bump: patch }
postReleaseBump:
  bump: minor                    # patch | minor | major | calver
  version: '{{version}}-dev.0'   # template; {{version}} = next version

Behavior

  • Computing the dev version:
    • bump picks the next version: a semver patch/minor/major increment, or calver, the first version of the next CalVer period under versioning.calver.format (e.g. 26.10.1 → 26.11.0, 26.12.3 → 27.1.0).
    • The default is calver when versioning.policy is calver, otherwise minor.
    • version is a template, rendered with renderTemplateSafe ({{version}} = next version). The default {{version}}-dev.0 matches npm version preminor --preid=dev. A value with no placeholder, such as nightly, is used as is.
  • Writing it: Craft reuses runPreReleaseCommand, the step craft prepare uses: the preReleaseCommand when set (with CRAFT_NEW_VERSION set to the dev version), otherwise the automatic target bumps in the selected workspace. It then commits meta: Bump new development version + #skip-changelog with --all, runs git pull --rebase (aborting the rebase if that fails), and pushes to the merge target.
  • Refactors:
    • runPreReleaseCommand moves from commands/prepare.ts to utils/preReleaseCommand.ts, so publish doesn't import prepare, which already imports publish.
    • The pull logic is now a shared pullWithRebase helper.
  • When the bump is skipped:
    • The release branch wasn't merged: noMerge, a partial-target publish, or a merge or push failure. A BranchCleanupError still counts as merged.
    • The released version is a pre-release.
    • Nothing wrote a version.
    • Dry runs only log the version they would write.
  • A failed bump warns and reports to Sentry, but doesn't fail the release, the same way a failed merge is handled.
  • Schema: true and the bump-type shorthand normalize to the object form, so publish only handles one shape. The dev marker should stay a pre-release (-dev.0): +dev build metadata is ignored when ordering versions, dropped by npm, and rejected by PyPI.
  • Workspaces inherit the setting. A workspace can opt out with postReleaseBump: false.
  • Order: the bump runs before postReleaseCommand.

Who can switch

A survey of getsentry repos found these post-release scripts. Each one re-runs the repo's bump script with a dev version and then commits and pushes:

repo config
craft postReleaseBump: true → #951
sentry, snuba (calver) postReleaseBump: { version: '{{version}}.dev0' } → 26.11.0.dev0 after 26.10.1, same as today
self-hosted postReleaseBump: { version: nightly }
sentry-godot postReleaseBump: true (2.5.0-dev.0, vs. -dev from its current script). That script doesn't seem to run: main is still 2.4.0 after the 2.4.0 release, and it calls 'pwsh.\scripts\bump-version.ps1'.
toolkit postReleaseBump: true re-runs bump-version.ts in pre mode. Its frontmatter sync only happens in --post mode today, so it either needs a small script change or #948

Migration PRs follow once this ships in 3.7.0.

Checks

  • tsc --noEmit, pnpm lint, and prettier --check all pass.
  • Tests: vitest run src/commands/__tests__ src/__tests__/config.test.ts src/utils/__tests__/calver.test.ts: 235 passed. That covers:
    • publish: semver, CalVer, template, and literal versions; passing the preReleaseCommand through; commit/pull/push; and every skip and failure path.
    • getNextCalVerPeriod: month and day formats, year rollover, and versions that don't match.
    • schema: true, false, the object form, the bump-type shorthand and how each normalizes, and invalid values such as preminor.
  • In my sandbox, the full suite had 4 failing files on both this branch and master. The causes are sandbox-specific: a forced Git identity breaks commits in tests, npm env vars leak in, and the default pnpm is v12. CI is the source of truth.

via @BYK.

--

View Session in Sentry

Add an opt-in postReleaseBump setting. After craft publish merges the
release branch, Craft bumps the release unit to the next development
version (default preminor with preid dev) through the same target logic
as the automatic pre-release bump, commits it with #skip-changelog, pulls
with rebase, and pushes to the merge target.

The bump is skipped after a pre-release or when the release branch isn't
merged, and a failure warns without failing the release. Workspaces
inherit the setting and can opt out with postReleaseBump: false.

Closes #949

Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>
@github-actions

github-actions Bot commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-11-02 10:19 UTC

…alVer and templates

postReleaseBump now writes the development version with the same step
craft prepare uses: the preReleaseCommand when set, otherwise the
automatic target bumps. Repos whose post-release scripts just re-run
their bump script with a dev version (sentry, snuba, self-hosted,
sentry-godot) can switch to it.

Replace bump/preid with bump (patch, minor, major, calver) and a version
template ('{{version}}-dev.0' by default). calver moves to the first
version of the next CalVer period, and the default for the calver
policy. A template without {{version}}, such as 'nightly', is used as
is. postReleaseBump: true enables the defaults.

Move runPreReleaseCommand to src/utils/preReleaseCommand.ts so publish
can use it without importing the prepare command.

Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 80bdb6f. Configure here.

Comment thread src/utils/calver.ts Resolved
postReleaseBump: patch (or minor, major, calver) is shorthand for
{ bump: patch } with the default -dev.0 template. The schema normalizes
true and the bump-type shorthand to the object form, so publish only
deals with one shape. Document why the dev marker should be a
pre-release suffix rather than +build metadata.

Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>
Comment thread src/commands/publish.ts Resolved
The development version reaches spawned commands as an argument, and
spawnProcess expands whole-argument ${VAR} values from the full process
env. A crafted template such as '${NPM_TOKEN}' could write a secret into
a version file that is then committed and pushed. Allow only letters,
digits, '.', '_', '+', '-' and the {{version}} placeholder, and replace
the placeholder directly instead of rendering it with mustache.
@BYK
BYK merged commit 96b7395 into master Nov 2, 2026
29 checks passed
@BYK
BYK deleted the feat/post-release-bump branch November 2, 2026 10:18
BYK added a commit that referenced this pull request Nov 2, 2026
The skip magic word was matched as a plain substring anywhere in a
commit message or PR body. #950's squash commit described the
post-release bump commit in backticks, so the feature dropped itself:
`craft prepare auto` found zero commits since 3.6.0 and the [3.7.0
release
failed](https://github.com/getsentry/craft/actions/runs/41427876670/job/125222689992)
with "no commits found since the last release".

Now mentions inside Markdown inline code spans (including ones
hard-wrapped across lines, as in squash commits) and fenced code blocks
are ignored. The word still works anywhere in plain prose, so the
documented "anywhere in your commit message or PR body" behavior holds,
and Craft's own `meta: Bump new development version` commits are still
skipped. Both checks (the early git commit-body filter and
`shouldExcludePR` for PR bodies) share the new helper.

Once this merges, the next auto release will count #950 again (verified
against its actual commit message), so a plain re-run should get 3.7.0
out with #950 in the changelog.

<!-- junior-request-attribution:start -->
via @BYK.
<!-- junior-request-attribution:end -->

<!-- junior-session-footer:start -->
<!-- junior-conversation-id:local%3Aweb%3Af8508ca3568070a0389d1c35 -->

--

[View Session in
Sentry](https://sentry.sentry.io/explore/conversations/local%3Aweb%3Af8508ca3568070a0389d1c35/?project=4510944073809921)

<!-- junior-session-footer:end -->

---------

Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com>
Co-authored-by: BYK <126780+BYK@users.noreply.github.com>
BYK added a commit that referenced this pull request Nov 2, 2026
Moves Craft's own release config to the built-in post-release bump from
#950. That's the follow-up for #949.

## Changes

- **`postReleaseCommand` → `postReleaseBump: true`.** The old command
ran `npm version preminor --preid=dev`, then committed with
`#skip-changelog`, pulled with rebase, and pushed. Now Craft computes
the default `{{version}}-dev.0` (`3.7.0` → `3.8.0-dev.0`, the same as
before) and writes it through the existing `preReleaseCommand`, which
runs `npm version $CRAFT_NEW_VERSION`. The commit message stays the
same.
- **`minVersion: '2.14.0'` → `'3.7.0'`.** An older Craft drops the
unknown `postReleaseBump` key without an error, so this makes it fail
loudly instead of skipping the bump. The only behavior gates between
those versions are:
- auto-bump (2.21.0): doesn't apply, since we keep `preReleaseCommand`.
  - workspaces (2.29.0): not used here.

One small behavior change: after a pre-release, Craft now skips the
bump. The old script bumped anyway.

## Merge order

This stays a draft until #950 is merged **and** shipped in 3.7.0. `craft
prepare` here runs the build artifact from `master`, but `publish` may
run a released Craft. Merging early could fail a release on the
`minVersion` check.

## Checks

- I didn't rebuild `dist/craft` for the final schema. The #950 config
tests cover `postReleaseBump: true`.
- `prettier --check .craft.yml` passes.

<!-- junior-request-attribution:start -->
via @BYK.
<!-- junior-request-attribution:end -->

<!-- junior-session-footer:start -->
<!-- junior-conversation-id:local%3Aweb%3Af8508ca3568070a0389d1c35 -->

--

[View Session in
Sentry](https://sentry.sentry.io/explore/conversations/local%3Aweb%3Af8508ca3568070a0389d1c35/?project=4510944073809921)

<!-- junior-session-footer:end -->

---------

Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com>
Co-authored-by: BYK <126780+BYK@users.noreply.github.com>
BYK added a commit that referenced this pull request Nov 2, 2026
…955)

Closes #948.

A release unit can now list extra files that automatic version bumping
writes the new version into. Until now, the only option was a custom
`preReleaseCommand`, which also turns off the automatic bump.

```yaml
minVersion: '3.8.0'
workspaces:
  packages/cli:
    versioning:
      policy: auto
      files:
        - path: packages/mcp-server/package.json          # json, field: version
        - path: packages/cli/plugins/sentry-cli/.claude-plugin/plugin.json
          stripPrerelease: true
        - path: packages/cli/plugins/sentry-cli/skills/sentry-cli/**/*.md   # frontmatter
        - path: crates/*/Cargo.toml                       # toml, field: package.version
        - path: src/sdk/_version.py
          format: regex
          pattern: '/__version__ = "([^"]+)"/'
```

### Formats

The formats cover every kind of file Craft's version-bumping targets
edit, plus `frontmatter`, and `regex` as an escape hatch. Craft infers
the format from the file name:

| `format` | Inferred for | Updates | Implementation |
| --- | --- | --- | --- |
| `msbuild` | `*.csproj`, `*.fsproj`, `*.vbproj`, `*.props`, `*.targets`
| Version properties, and clears `<VersionSuffix>` when there's a
`<VersionPrefix>` | The `nuget` target's code |
| `gem` | `*.gemspec`, `version.rb` | `spec.version = "…"`, `VERSION =
"…"` | The `gem` target's patterns |
| `mix` | `mix.exs` | `version: "…"`, `@version "…"` | The `hex`
target's patterns |
| `json` | `*.json` | String at `field` (default `version`) | New:
`jsonc-parser` replaces only the value's characters |
| `toml` | `*.toml` | String at `field`. Default: `package.version` in
`Cargo.toml`, `project.version` in `pyproject.toml`, otherwise `version`
| New: a table-aware line scanner that skips arrays of tables. Shared
with the `pypi` target |
| `yaml` | `*.yaml`, `*.yml` | Scalar at `field`; skips sequences and
block scalars | New: an indentation-aware scanner. The result is parsed
back with js-yaml to confirm the new version is at `field`. Shared with
the `pub-dev` target |
| `frontmatter` | `*.md`, `*.mdx` | Same as `yaml`, inside the `---`
frontmatter | Shares the `yaml` code |
| `regex` | never inferred | Capturing group of every match of a
`/source/flags` string (same syntax as `includeNames`) | New |

The gem, hex and nuget patterns moved to `src/utils/versionPatterns.ts`,
so those targets and `versioning.files` share one definition.
`NugetTarget.updateVersionInXml` now calls the shared
`setMsBuildVersion`. Behavior is unchanged.

The new TOML and YAML editors live in the same module, and the targets
that direct-edit those files now use them:

- `pypi` direct edit: replaced its regex, which rewrote the first
`version = ` line in any table, with the TOML editor on
`project.version`. Hatch, poetry and setuptools_scm are unchanged.
- `pub-dev`: edits `version` in place instead of loading and re-dumping
the whole pubspec with js-yaml, which dropped comments and formatting.
It falls back to the old rewrite only when there's no single top-level
`version` to edit, so a pubspec without one still gets it added.

`npm` (`npm version`) and `crates` (`cargo set-version`) keep their
CLIs: those also update `package-lock.json`, workspace members and
dependent crates' version requirements, which a one-file edit doesn't
cover. `json` has no target to share with, since only `npm` writes JSON.

### Design decisions

- **Location:** `versioning.files`, with `stripPrerelease` set on each
entry. Workspaces replace `versioning` as a whole, like every other
field except `github`, so a workspace that declares `versioning` also
replaces the top-level `files`. The workspace docs say so.
- **Paths are relative to the repository root**, same as
`changelog.includePaths`. Absolute paths and `..` are rejected using the
same safety check as workspace globs. Glob matches that resolve outside
the repository (symlinks) are dropped.
- **`preReleaseCommand` plus `versioning.files` is a config error.** The
check runs on the resolved config, so a workspace can't get `files` from
one level and a `preReleaseCommand` from another. `preReleaseCommand:
''` counts as a conflict too. An empty `files: []` is allowed.
- **`minVersion: 3.8.0` is required** (3.7.0 shipped without this
feature). Older Crafts silently drop unknown keys. This gate makes those
configs also set a minVersion that older Crafts refuse to run, instead
of releasing without updating the files.
- **Strict, all-or-nothing:** a path that matches no files, or a matched
file with nothing to update, fails `craft prepare` before any file is
written. Writes go through `safeFs`. In a dry run, the files are written
to the temporary copy of the repo (the path comes from the isolated git
client).
- **Updated files count as a bump:** they trigger the `--all` commit,
and a unit whose targets can't bump anything no longer gets the "nothing
to bump" warning or error.

- **`postReleaseBump` writes them too.** `postReleaseBump` (#950) writes
the development version with the same `runPreReleaseCommand` step, so it
now passes `versioning.files` as well. With `stripPrerelease`, a
`1.3.0-dev.0` development version is written as `1.3.0`. A template that
isn't semver, such as `version: nightly`, can't be stripped: the bump
fails with a warning, and the release itself still succeeds.

### Testing

- `src/utils/__tests__/versionFiles.test.ts` (21 tests): every format,
formatting preservation, default TOML fields, nested YAML keys that skip
sequences and block scalars, globs, CRLF, symlink escape, writing
nothing when a later entry fails, several entries for one file.
- `src/__tests__/config.test.ts`: format inference, schema errors, the
minVersion gate, unsafe paths, the `preReleaseCommand` conflict
including the inherited case.
- `runPreReleaseCommand` tests in
`src/commands/__tests__/prepare.test.ts`, plus a `postReleaseBump` test
in `publish-main.test.ts`. Existing gem, hex and nuget bump tests still
pass with the shared patterns. New `versionBump.test.ts` cases cover the
pypi edit staying in `[project]`, pubspec comments surviving, and a
pubspec without a version still getting one.
- `pnpm typecheck`, `pnpm lint` and `prettier --check .` are clean.
- Merged with `master` (3.7.1). `pnpm test`: 1883 passed. 19 failed, all
in `prepare-dry-run.e2e` plus one `git.test.ts` case, the same tests
that fail on unmodified `master` in my sandbox. They fail because the
sandbox rejects test-repo commits with custom git identities. The PR's
checks so far are CodeQL, Socket and Semgrep, with no test job, so those
tests have only run locally.

<!-- junior-request-attribution:start -->
via @BYK.
<!-- junior-request-attribution:end -->

<!-- junior-session-footer:start -->
<!-- junior-conversation-id:local%3Aweb%3A62122f49891ab1ec203dce05 -->

--

[View Session in
Sentry](https://sentry.sentry.io/explore/conversations/local%3Aweb%3A62122f49891ab1ec203dce05/?project=4510944073809921)

<!-- junior-session-footer:end -->

---------

Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com>
Co-authored-by: BYK <126780+BYK@users.noreply.github.com>

This branch was previously deployed

1 inactive deployment
false — 32a0c03a Deployed Nov 1, 2026 by sentry-junior[bot] via Build #3275
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Built-in development version bump after release

1 participant