Skip to content

Commit 3748941

Browse files
fix(release): skip past orphaned version bumps in patch-release flow (tldraw#8838)
In order to recover from a previously-interrupted patch release that wedges a release branch's CI, this PR makes the patch-release script bump from `max(latestPublishedVersion, localTldrawVersion)` instead of always from the latest version on npm. Follow-up to tldraw#8800. [The `v4.0.x` patch-release run on the test commit](https://github.com/tldraw/tldraw/actions/runs/25747452078/job/75614452558) got past the diff-check fix from tldraw#8800 but then died at: ``` → No staged files found. cmd: 'git commit -m v4.0.4 [skip ci]' ``` That branch is in this state: - npm `tldraw` latest = `4.0.3` - Local `package.json`s already say `4.0.4` (from the orphaned `2293cc1a v4.0.4 [skip ci]` commit on `v4.0.x`) - Tag `v4.0.4` already exists on origin pointing at that orphaned commit So when the script tried to bump npm's `4.0.3` → `4.0.4` and call `setAllVersions("4.0.4")`, every package.json was already at `4.0.4`, nothing was staged, and `git commit` exited 1. Even with that fixed, the next step (`git push --follow-tags`) would have failed because `v4.0.4` already exists on origin at a different commit. ### Fix Compute the bump base as the higher of (latest npm version, local `tldraw` version): - **Happy path** (local equals npm): identical to today - bump from npm's latest, behaviour unchanged. - **Broken-state path** (local ahead of npm): bump from the local version, which leapfrogs past the orphaned version+tag entirely. For `v4.0.x` that means publishing `4.0.5` instead of trying to re-create `4.0.4`. Both `git commit` (real changes from `4.0.4` -> `4.0.5`) and `git push --follow-tags` (fresh tag) work normally. Logs a notice when the leapfrog path is taken, so it's obvious in CI output what's happening. The `prevTag` used for changelog generation still points at the actual latest npm version (e.g. `v4.0.3`), so the GitHub release notes still cover all commits since the real previous release. ### Change type - [x] `bugfix` ### Test plan 1. Push to `v4.0.x` again - the workflow should publish `4.0.5` successfully (4.0.4 stays orphaned on the abandoned commit, which is fine). 2. Confirm normal release branches (`v4.5.x`, etc.) bump as before. In the steady state where local equals npm, `baseVersion === latestVersionInBranch` and `nextVersion` is still `latest + 1 patch`. ### Code changes | Section | LOC change | | --------------- | ---------- | | Config/tooling | +28 / -2 | Made with [Cursor](https://cursor.com) --------- Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent f9a046b commit 3748941

2 files changed

Lines changed: 29 additions & 3 deletions

File tree

internal/scripts/publish-patch.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { appendFileSync } from 'node:fs'
1+
import { appendFileSync, readFileSync } from 'node:fs'
22
import { Octokit } from '@octokit/rest'
3+
import { parse } from 'semver'
34
import { extractChangelog } from './extract-draft-changelog'
45
import { getAnyPackageDiff } from './lib/didAnyPackageChange'
56
import { exec } from './lib/exec'
@@ -66,7 +67,32 @@ async function main() {
6667
// would return the new version and leave prevTag === tag, producing an empty changelog.
6768
const prevTag = `v${latestVersionInBranch.format()}`
6869

69-
const nextVersion = latestVersionInBranch.inc('patch').format()
70+
// Determine the version to bump from. Normally this is the latest version
71+
// published on npm for this branch. But if a previous patch release was
72+
// interrupted after the "v X.Y.Z [skip ci]" version-bump commit was pushed
73+
// but before npm publish completed, the local package.json files (and the
74+
// matching git tag) will be ahead of npm. In that case we bump from the
75+
// local version so we sidestep the orphaned version+tag entirely instead
76+
// of trying to re-create them - both `git commit` (no staged changes) and
77+
// `git push --follow-tags` (tag already exists at a different commit on
78+
// origin) would otherwise fail and permanently wedge the branch.
79+
const localTldrawVersion = parse(
80+
JSON.parse(readFileSync('packages/tldraw/package.json', 'utf8')).version
81+
)
82+
const baseVersion =
83+
localTldrawVersion && localTldrawVersion.compare(latestVersionInBranch) > 0
84+
? localTldrawVersion
85+
: latestVersionInBranch
86+
if (baseVersion !== latestVersionInBranch) {
87+
nicelog(
88+
`Local package version ${baseVersion.format()} is ahead of latest npm version ` +
89+
`${latestVersionInBranch.format()}; bumping from local to skip past orphaned version.`
90+
)
91+
}
92+
93+
// .inc() mutates baseVersion; reparse so callers downstream of us see the
94+
// original instance unchanged.
95+
const nextVersion = parse(baseVersion.format())!.inc('patch').format()
7096
nicelog('Releasing version', nextVersion)
7197

7298
await setAllVersions(nextVersion, { stageChanges: true })

packages/editor/src/lib/license/LicenseManager.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { importPublicKey, str2ab } from '../utils/licensing'
66
const GRACE_PERIOD_DAYS = 30
77

88
export const FLAGS = {
9-
// -- MUTUALLY EXCLUSIVE FLAGS --
9+
// -- MUTUALLY EXCLUSIVE FLAGS
1010
// Annual means the license expires after a time period, usually 1 year.
1111
ANNUAL_LICENSE: 1,
1212
// Perpetual means the license never expires up to the max supported version.

0 commit comments

Comments
 (0)