snapshot of https://github.com/tldraw/tldraw.git (main, 2026-09-23; blobs > 131072B omitted)
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
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
This commit is contained in:
143
internal/scripts/publish-editor-extensions.ts
Normal file
143
internal/scripts/publish-editor-extensions.ts
Normal file
@@ -0,0 +1,143 @@
|
||||
import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'fs'
|
||||
import path from 'path'
|
||||
import { parse } from 'semver'
|
||||
import { exec } from './lib/exec'
|
||||
import { makeEnv } from './lib/makeEnv'
|
||||
import { nicelog } from './lib/nicelog'
|
||||
|
||||
// VSCE_PAT needs to be set. It is used by the vsce publish command.
|
||||
// OVSX_PAT needs to be set for Open VSX publishing (handled by publish script).
|
||||
const env = makeEnv(['VSCE_PAT', 'OVSX_PAT', 'TLDRAW_ENV'])
|
||||
|
||||
const EXTENSION_DIR = 'apps/vscode/extension'
|
||||
const DISTRIBUTION_DIR = 'apps/vscode/extension/release'
|
||||
const MAX_VERSION_CONFLICT_ATTEMPTS = 5
|
||||
const VERSION_CONFLICT_RETRY_DELAY_MS = 30_000
|
||||
|
||||
function isVersionConflictError(err: unknown): boolean {
|
||||
const message = err instanceof Error ? err.message : ''
|
||||
const stderr = typeof (err as any)?.stderr === 'string' ? (err as any).stderr : ''
|
||||
return message.includes('already exists') || stderr.includes('already exists')
|
||||
}
|
||||
|
||||
async function fetchMarketplaceVersion(): Promise<string> {
|
||||
await exec('yarn', ['get-info'], { pwd: EXTENSION_DIR })
|
||||
const extensionInfoJsonPath = path.join(EXTENSION_DIR, 'extension.json')
|
||||
if (!existsSync(extensionInfoJsonPath)) {
|
||||
throw new Error('Published extension info not found.')
|
||||
}
|
||||
const extensionInfoJson = JSON.parse(readFileSync(extensionInfoJsonPath, 'utf8'))
|
||||
const version = extensionInfoJson.versions[0].version
|
||||
if (!version) {
|
||||
throw new Error('Could not get the version of the published extension.')
|
||||
}
|
||||
return version
|
||||
}
|
||||
|
||||
async function bumpVersion(fromVersion?: string): Promise<string> {
|
||||
const currentVersion = fromVersion ?? (await fetchMarketplaceVersion())
|
||||
const semVer = parse(currentVersion)
|
||||
if (!semVer) {
|
||||
throw new Error(`Could not parse the published version: ${currentVersion}`)
|
||||
}
|
||||
const release = env.TLDRAW_ENV === 'production' ? 'minor' : 'patch'
|
||||
const nextVersion = semVer.inc(release).version
|
||||
nicelog(`Bumping extension version from ${currentVersion} to ${nextVersion}`)
|
||||
|
||||
const packageJsonPath = path.join(EXTENSION_DIR, 'package.json')
|
||||
if (!existsSync(packageJsonPath)) {
|
||||
throw new Error("Could not find the extension's package.json file.")
|
||||
}
|
||||
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'))
|
||||
packageJson.version = nextVersion
|
||||
writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, '\t') + '\n')
|
||||
return nextVersion
|
||||
}
|
||||
|
||||
async function copyExtensionToReleaseFolder(version: string) {
|
||||
if (!existsSync(DISTRIBUTION_DIR)) {
|
||||
mkdirSync(DISTRIBUTION_DIR, { recursive: true })
|
||||
}
|
||||
|
||||
const tempDir = path.join(EXTENSION_DIR, 'temp')
|
||||
if (!existsSync(tempDir)) {
|
||||
throw new Error(
|
||||
'Extension temp directory not found. Make sure packaging completed successfully.'
|
||||
)
|
||||
}
|
||||
|
||||
const files = readdirSync(tempDir)
|
||||
const vsixFile = files.find((file: string) => file.endsWith('.vsix'))
|
||||
if (!vsixFile) {
|
||||
throw new Error('No .vsix file found in temp directory.')
|
||||
}
|
||||
|
||||
const sourcePath = path.join(tempDir, vsixFile)
|
||||
const targetPath = path.join(DISTRIBUTION_DIR, 'tldraw-vscode.vsix')
|
||||
|
||||
nicelog(`Copying extension from ${sourcePath} to ${targetPath}`)
|
||||
copyFileSync(sourcePath, targetPath)
|
||||
|
||||
nicelog('Setting git user identity...')
|
||||
await exec('git', ['config', 'user.name', 'huppy-bot[bot]'])
|
||||
await exec('git', ['config', 'user.email', '128400622+huppy-bot[bot]@users.noreply.github.com'])
|
||||
|
||||
nicelog('Committing extension to git...')
|
||||
await exec('git', ['add', '-f', targetPath])
|
||||
await exec('git', ['commit', '-m', `Add VSCode extension v${version} [skip ci]`])
|
||||
nicelog('Pushing changes to remote repository...')
|
||||
await exec('git', ['push'])
|
||||
}
|
||||
|
||||
async function main() {
|
||||
if (env.TLDRAW_ENV !== 'production' && env.TLDRAW_ENV !== 'staging') {
|
||||
throw new Error('Workflow triggered from a branch other than main or production.')
|
||||
}
|
||||
|
||||
await exec('yarn', ['lazy', 'run', 'build', '--filter=packages/*'])
|
||||
|
||||
// When two pushes to main happen in quick succession, the concurrency group serializes
|
||||
// the runs but `vsce show` can lag a fresh publish by 10+ minutes, so both runs compute
|
||||
// the same next version. The "already exists" rejection is authoritative, so on conflict
|
||||
// bump past the rejected version locally instead of re-fetching the stale listing.
|
||||
let conflictedVersion: string | undefined
|
||||
for (let attempt = 1; attempt <= MAX_VERSION_CONFLICT_ATTEMPTS; attempt++) {
|
||||
const version = await bumpVersion(conflictedVersion)
|
||||
|
||||
try {
|
||||
switch (env.TLDRAW_ENV) {
|
||||
case 'production':
|
||||
await exec('yarn', ['package'], { pwd: EXTENSION_DIR })
|
||||
await exec('yarn', ['publish'], { pwd: EXTENSION_DIR })
|
||||
await copyExtensionToReleaseFolder(version)
|
||||
return
|
||||
case 'staging':
|
||||
await exec('yarn', ['package', '--pre-release'], { pwd: EXTENSION_DIR })
|
||||
await exec('yarn', ['publish', '--pre-release'], { pwd: EXTENSION_DIR })
|
||||
return
|
||||
}
|
||||
} catch (err) {
|
||||
// Exit code 75 from the publish script means an earlier upload may have succeeded.
|
||||
if ((err as { code?: number })?.code === 75) {
|
||||
throw new Error(
|
||||
`Publishing version ${version} stopped because an earlier Marketplace upload may have succeeded. Open VSX has not been attempted. Check Marketplace and complete this release at the same version; do not rerun the workflow blindly, as it computes a new version.`,
|
||||
{ cause: err }
|
||||
)
|
||||
}
|
||||
if (isVersionConflictError(err) && attempt < MAX_VERSION_CONFLICT_ATTEMPTS) {
|
||||
conflictedVersion = version
|
||||
nicelog(
|
||||
`Version ${version} already exists (attempt ${attempt}/${MAX_VERSION_CONFLICT_ATTEMPTS}), bumping past it and retrying in ${VERSION_CONFLICT_RETRY_DELAY_MS / 1000}s...`
|
||||
)
|
||||
await new Promise((resolve) => setTimeout(resolve, VERSION_CONFLICT_RETRY_DELAY_MS))
|
||||
continue
|
||||
}
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(async (err) => {
|
||||
console.error(err)
|
||||
process.exit(1)
|
||||
})
|
||||
Reference in New Issue
Block a user