Files
upstream-tldraw/internal/scripts/publish-patch.ts
upstream-mirror a774ac673a
Some checks failed
Deploy analytics worker / Deploy analytics worker to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
i18n upload strings / Upload latest strings (push) Failing after 1s
Deploy bemo / Deploy bemo to ${{ (github.ref == 'refs/heads/bemo-production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
Deploy grafana resources / ${{ github.event_name == 'push' && 'Deploy grafana resources' || 'Validate grafana resources (dry run)' }} (push) Failing after 0s
End to end tests (dotcom) / Check for relevant changes (push) Failing after 0s
End to end tests (examples) / Check for relevant changes (push) Failing after 0s
End to end tests (mermaid readability) / Check for relevant changes (push) Failing after 1s
End to end tests (perf) / Check for relevant changes (push) Failing after 0s
Publish editor extensions / Publish editor extensions (push) Failing after 1s
Publish SDK packages / Remove the publish-packages label (push) Has been skipped
Checks / Tests & checks (push) Has been cancelled
Checks / Build all projects (push) Has been cancelled
Deploy templates / Deploy templates (push) Has been cancelled
End to end tests (dotcom) / End to end tests (dotcom) (push) Has been cancelled
End to end tests (mermaid readability) / End to end tests (mermaid readability) (push) Has been cancelled
End to end tests (perf) / End to end tests (perf) (push) Has been cancelled
Deploy .com / Deploy dotcom to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Has been cancelled
End to end tests (examples) / End to end tests (examples) (push) Has been cancelled
Publish SDK packages / Publish SDK packages (push) Has been cancelled
Publish SDK packages / Tell user released version (push) Has been cancelled
Publish SDK packages / Publishes code templates to separate repositories (push) Has been cancelled
Publish SDK packages / Deploys the template demos with the new SDK version (push) Has been cancelled
snapshot of https://github.com/tldraw/tldraw.git (main, 2026-09-23; blobs > 131072B omitted)
2026-09-23 16:12:52 +05:00

139 lines
5.4 KiB
TypeScript

import { appendFileSync, readFileSync } from 'node:fs'
import { Octokit } from '@octokit/rest'
import { parse } from 'semver'
import { extractChangelog } from './extract-draft-changelog'
import { getAnyPackageDiff } from './lib/didAnyPackageChange'
import { exec } from './lib/exec'
import { nicelog } from './lib/nicelog'
import {
getLatestTldrawVersionFromNpm,
publish,
publishProductionDocsAndExamplesAndBemo,
setAllVersions,
triggerBumpVersionsWorkflow,
} from './lib/publishing'
import { uploadStaticAssets } from './lib/upload-static-assets'
async function main() {
const currentBranch = (await exec('git', ['rev-parse', '--abbrev-ref', 'HEAD'])).toString().trim()
const match = currentBranch.match(/^v(\d+)\.(\d+)\.x$/)
if (!match) {
throw new Error('Branch name does not match expected format: v{major}.{minor}.x')
}
const [major, minor] = match.slice(1).map(Number)
const latestVersionInBranch = await getLatestTldrawVersionFromNpm({
versionPrefix: `${major}.${minor}`,
})
// Check if this commit is already tagged with the initial release for this major.minor version
const expectedInitialTag = `v${latestVersionInBranch.major}.${latestVersionInBranch.minor}.0`
const tagsAtThisCommit = (await exec('git', ['tag', '--points-at', 'HEAD']))
.toString()
.trim()
.split('\n')
if (tagsAtThisCommit.includes(expectedInitialTag)) {
// Skip release if this is the initial release commit for this major.minor version
nicelog('Initial release commit, skipping patch release')
return
} else {
nicelog('Not an initial release commit, continuing with patch release')
}
const latestVersion = await getLatestTldrawVersionFromNpm()
const isLatestVersion = latestVersion.compare(latestVersionInBranch) === 0
if (isLatestVersion) {
await publishProductionDocsAndExamplesAndBemo()
}
// Ensure asset directories exist before comparing package contents.
// CI may skip postinstall (and thus refresh-assets) when install-state.gz is cached.
await exec('yarn', ['refresh-assets', '--force'])
// Skip releasing a new version if the package contents are identical.
// This may happen when cherry-picking docs-only changes.
if (!(await getAnyPackageDiff(latestVersionInBranch.format()))) {
nicelog('No packages have changed, skipping release')
return
}
if (process.env.GITHUB_OUTPUT) {
appendFileSync(process.env.GITHUB_OUTPUT, `is_latest_version=${isLatestVersion}\n`)
}
// Capture the previous tag BEFORE calling .inc(): semver's SemVer.prototype.inc()
// mutates the instance in place, so reading latestVersionInBranch.format() afterwards
// would return the new version and leave prevTag === tag, producing an empty changelog.
const prevTag = `v${latestVersionInBranch.format()}`
// Determine the version to bump from. Normally this is the latest version
// published on npm for this branch. But if a previous patch release was
// interrupted after the "v X.Y.Z [skip ci]" version-bump commit was pushed
// but before npm publish completed, the local package.json files (and the
// matching git tag) will be ahead of npm. In that case we bump from the
// local version so we sidestep the orphaned version+tag entirely instead
// of trying to re-create them - both `git commit` (no staged changes) and
// `git push --follow-tags` (tag already exists at a different commit on
// origin) would otherwise fail and permanently wedge the branch.
const localTldrawVersion = parse(
JSON.parse(readFileSync('packages/tldraw/package.json', 'utf8')).version
)
const baseVersion =
localTldrawVersion && localTldrawVersion.compare(latestVersionInBranch) > 0
? localTldrawVersion
: latestVersionInBranch
if (baseVersion !== latestVersionInBranch) {
nicelog(
`Local package version ${baseVersion.format()} is ahead of latest npm version ` +
`${latestVersionInBranch.format()}; bumping from local to skip past orphaned version.`
)
}
// .inc() mutates baseVersion; reparse so callers downstream of us see the
// original instance unchanged.
const nextVersion = parse(baseVersion.format())!.inc('patch').format()
nicelog('Releasing version', nextVersion)
await setAllVersions(nextVersion, { stageChanges: true })
const tag = `v${nextVersion}`
// create and push a new tag
await exec('git', ['commit', '-m', `${tag} [skip ci]`])
await exec('git', ['tag', '-a', tag, '-m', tag, '-f'])
await exec('git', ['push', '--follow-tags'])
// Generate changelog and create GitHub release
nicelog('Generating changelog...')
const changelog = await extractChangelog(prevTag, 'HEAD')
nicelog('Creating GitHub release...')
const octokit = new Octokit({ auth: process.env.GH_TOKEN })
await octokit.repos.createRelease({
owner: 'tldraw',
repo: 'tldraw',
tag_name: tag,
name: tag,
body: changelog,
draft: false,
prerelease: false,
})
await uploadStaticAssets(nextVersion)
// if we're on the latest version, publish to npm under 'latest' tag.
// otherwise we don't want to overwrite the latest tag, so we publish under 'revision'.
// semver rules will still be respected because there's no prerelease tag in the version,
// so clients will get the updated version if they have a range like ^1.0.0
await publish(isLatestVersion ? 'latest' : 'revision')
// If this is the latest version, trigger version bump on main branch to sync all package versions
if (isLatestVersion) {
nicelog('This is the latest version, triggering version bump on main branch')
await triggerBumpVersionsWorkflow(process.env.GH_TOKEN!)
}
}
main()