From d6fc1fc1396200dbf44ea1c0a64ec65f2c6ffd37 Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Thu, 5 Nov 2026 15:19:22 +0000 Subject: [PATCH] fix(version): Keep auto and pre below 1.0.0 on breaking changes --- docs/src/content/docs/versioning.md | 3 ++ src/__tests__/prepare-dry-run.e2e.test.ts | 41 +++++++++++++++++++++++ src/commands/prepare.ts | 27 ++++++++++----- 3 files changed, 62 insertions(+), 9 deletions(-) diff --git a/docs/src/content/docs/versioning.md b/docs/src/content/docs/versioning.md index 9a319d00..4c854f8f 100644 --- a/docs/src/content/docs/versioning.md +++ b/docs/src/content/docs/versioning.md @@ -63,6 +63,9 @@ Craft reads it from the files of the first configured target that [supports version bumping](#supported-targets) and declares a semver version above `0.0.0`. Without one, they increment `0.0.0`. +Before `1.0.0`, `auto` and `pre` treat breaking changes as a minor bump, so +`0.5.0` becomes `0.6.0`. Pass `1.0.0` or `major` to release `1.0.0`. + After a prerelease, bump types and `auto` promote it to a stable release: `2.1.0-alpha.3` becomes `2.1.0`, or a higher version when the bump needs one. To be safe, `auto` doesn't promote a prerelease to a major release: after diff --git a/src/__tests__/prepare-dry-run.e2e.test.ts b/src/__tests__/prepare-dry-run.e2e.test.ts index e08d970e..b89e48bb 100644 --- a/src/__tests__/prepare-dry-run.e2e.test.ts +++ b/src/__tests__/prepare-dry-run.e2e.test.ts @@ -778,6 +778,47 @@ targets: [] expect(output).not.toContain('Add foo feature in'); }, 60000); + test.each([ + ['auto', '0.6.0'], + ['pre', '0.6.0-alpha.0'], + ['major', '1.0.0'], + ])( + 'a breaking change before 1.0.0 with %s releases %s', + async (versionArg, expected) => { + tempDir = await createTestRepo(); + // eslint-disable-next-line no-restricted-syntax -- Test setup needs direct git access + const git = simpleGit(tempDir); + await writeFile( + join(tempDir, '.craft.yml'), + `minVersion: "3.9.0" +github: + owner: test-owner + repo: test-repo +changelog: + policy: auto +preReleaseCommand: "" +targets: [] +`, + ); + await git.add('.'); + await git.commit('chore: Enable automatic versioning'); + await git.addTag('0.5.0'); + await writeFile(join(tempDir, 'baz.ts'), 'export const baz = 3;'); + await git.add('.'); + await git.commit('feat!: Remove the old baz API'); + await git.push('origin', (await git.status()).current!); + + const { stdout, stderr } = await execFileAsync( + CLI_BIN, + ['prepare', versionArg, '--dry-run', '--no-input'], + { cwd: tempDir, env: { ...CLI_ENV, GITHUB_TOKEN: '' } }, + ); + + expect(stdout + stderr).toContain(`Version bump: 0.5.0 -> ${expected}`); + }, + 60000, + ); + test('prepares a workspace changelog without changing the root changelog', async () => { tempDir = await createTestRepo(); // eslint-disable-next-line no-restricted-syntax -- Test setup needs direct git access diff --git a/src/commands/prepare.ts b/src/commands/prepare.ts index 235ae6e2..af225487 100644 --- a/src/commands/prepare.ts +++ b/src/commands/prepare.ts @@ -715,6 +715,15 @@ async function resolveVersion( const latestTag = await getLatestTag(git, getGitTagPrefix()); + // Calculate new version from the latest tag. Before the first tag, start + // from the version in the release unit's project files, so a workspace at + // `1.0.0-dev.0` releases `1.0.0` instead of a bump of `0.0.0`. Bumping a + // prerelease promotes it, so `2.0.0-alpha.3` releases `2.0.0`. + const currentVersion = + (latestTag && getVersion(latestTag)) || + (await readProjectVersion(config.targets || [], getReleaseUnitDir())) || + '0.0.0'; + // Determine bump type - either from arg or from commit analysis let bumpType: BumpType; if (version === 'auto' || version === 'pre') { @@ -726,19 +735,19 @@ async function resolveVersion( ); validateBumpType(changelogResult); bumpType = changelogResult.bumpType; + if (bumpType === 'major' && semver.major(currentVersion) === 0) { + // Before 1.0.0, breaking changes bump the minor version. Releasing + // 1.0.0 is a decision, so it takes an explicit `major` or version. + logger.info( + `${currentVersion} is below 1.0.0, so the breaking changes bump ` + + 'the minor version. Pass "major" to release 1.0.0.', + ); + bumpType = 'minor'; + } } else { bumpType = version as BumpType; } - // Calculate new version from the latest tag. Before the first tag, start - // from the version in the release unit's project files, so a workspace at - // `1.0.0-dev.0` releases `1.0.0` instead of a bump of `0.0.0`. Bumping a - // prerelease promotes it, so `2.0.0-alpha.3` releases `2.0.0`. - const currentVersion = - (latestTag && getVersion(latestTag)) || - (await readProjectVersion(config.targets || [], getReleaseUnitDir())) || - '0.0.0'; - let newVersion = calculateNextVersion(currentVersion, bumpType); if (version === 'pre') { // `pre` cuts a prerelease of the version `auto` would release.