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
164 lines
4.7 KiB
JavaScript
164 lines
4.7 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Fetches tldraw stable release notes directly from GitHub
|
|
* (apps/docs/content/releases/vX.Y.Z.mdx on `tldraw/tldraw` main) and
|
|
* concatenates them into a single text file filtered by version range.
|
|
* GitHub is the source of truth, so the output reflects whatever is on
|
|
* main — not whatever tldraw.dev has deployed.
|
|
*
|
|
* `next.mdx` is intentionally NOT included here. The skill fetches it
|
|
* separately, conditionally on a pre-release target, into its own file
|
|
* (`references/tldraw-next.mdx`).
|
|
*
|
|
* Usage:
|
|
* node fetch-release-notes.mjs <from-version> [to-version] > output.txt
|
|
*
|
|
* Honors a `GITHUB_TOKEN` env var to avoid the unauthenticated API rate limit.
|
|
*/
|
|
|
|
const REPO = process.env.TLDRAW_RELEASES_REPO || 'tldraw/tldraw'
|
|
const RELEASES_PATH = 'apps/docs/content/releases'
|
|
const BRANCH = process.env.TLDRAW_RELEASES_BRANCH || 'main'
|
|
|
|
const [, , fromArg, toArg] = process.argv
|
|
|
|
if (!fromArg) {
|
|
process.stderr.write(
|
|
'Usage: node fetch-release-notes.mjs <from-version> [to-version]\n' +
|
|
' e.g.: node fetch-release-notes.mjs 4.0.0 > releases.txt\n'
|
|
)
|
|
process.exit(1)
|
|
}
|
|
|
|
function parseVersion(str) {
|
|
const m = String(str)
|
|
.trim()
|
|
.replace(/^v/, '')
|
|
.match(/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?/)
|
|
if (!m) return null
|
|
return { main: [+m[1], +m[2], +m[3]], pre: m[4] || null }
|
|
}
|
|
|
|
function comparePrerelease(a, b) {
|
|
if (!a && !b) return 0
|
|
if (!a) return 1
|
|
if (!b) return -1
|
|
const aParts = a.split('.')
|
|
const bParts = b.split('.')
|
|
for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
|
|
const ap = aParts[i]
|
|
const bp = bParts[i]
|
|
if (ap === undefined) return -1
|
|
if (bp === undefined) return 1
|
|
const aNum = /^\d+$/.test(ap)
|
|
const bNum = /^\d+$/.test(bp)
|
|
if (aNum && bNum) {
|
|
const d = +ap - +bp
|
|
if (d !== 0) return d
|
|
} else if (aNum !== bNum) {
|
|
return aNum ? -1 : 1
|
|
} else if (ap !== bp) {
|
|
return ap < bp ? -1 : 1
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
function compareVersions(a, b) {
|
|
for (let i = 0; i < 3; i++) {
|
|
if (a.main[i] !== b.main[i]) return a.main[i] - b.main[i]
|
|
}
|
|
return comparePrerelease(a.pre, b.pre)
|
|
}
|
|
|
|
const fromVer = parseVersion(fromArg)
|
|
if (!fromVer) {
|
|
process.stderr.write(`Invalid from-version: ${fromArg}\n`)
|
|
process.exit(1)
|
|
}
|
|
|
|
const toVer = toArg ? parseVersion(toArg) : null
|
|
if (toArg && !toVer) {
|
|
process.stderr.write(`Invalid to-version: ${toArg}\n`)
|
|
process.exit(1)
|
|
}
|
|
|
|
const headers = {
|
|
'User-Agent': 'tldraw-migrate-skill',
|
|
Accept: 'application/vnd.github.v3+json',
|
|
}
|
|
if (process.env.GITHUB_TOKEN) {
|
|
headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`
|
|
}
|
|
|
|
const apiUrl = `https://api.github.com/repos/${REPO}/contents/${RELEASES_PATH}?ref=${BRANCH}`
|
|
const listing = await fetch(apiUrl, { headers })
|
|
.then(async (r) => {
|
|
if (!r.ok) {
|
|
const body = await r.text().catch(() => '')
|
|
throw new Error(`HTTP ${r.status} ${r.statusText}: ${body.slice(0, 200)}`)
|
|
}
|
|
return r.json()
|
|
})
|
|
.catch((err) => {
|
|
process.stderr.write(`Failed to list ${REPO}/${RELEASES_PATH}: ${err.message}\n`)
|
|
process.exit(1)
|
|
})
|
|
|
|
if (!Array.isArray(listing)) {
|
|
process.stderr.write(`Unexpected GitHub API response: ${JSON.stringify(listing).slice(0, 200)}\n`)
|
|
process.exit(1)
|
|
}
|
|
|
|
const files = listing
|
|
.filter((f) => f.type === 'file' && f.name.endsWith('.mdx') && f.name !== 'next.mdx')
|
|
.map((f) => ({
|
|
name: f.name,
|
|
url: f.download_url,
|
|
version: parseVersion(f.name.replace(/\.mdx$/, '')),
|
|
}))
|
|
.filter((f) => f.version !== null)
|
|
|
|
const relevant = files
|
|
.filter((f) => {
|
|
const afterFrom = compareVersions(f.version, fromVer) > 0
|
|
const beforeTo = toVer ? compareVersions(f.version, toVer) <= 0 : true
|
|
return afterFrom && beforeTo
|
|
})
|
|
.sort((a, b) => compareVersions(a.version, b.version))
|
|
|
|
if (relevant.length === 0) {
|
|
process.stderr.write(`No release notes found between ${fromArg} and ${toArg || 'latest'}\n`)
|
|
process.exit(0)
|
|
}
|
|
|
|
const sections = ['# tldraw SDK releases', '', '--------', '']
|
|
const failed = []
|
|
|
|
for (const file of relevant) {
|
|
const text = await fetch(file.url, { headers: { 'User-Agent': 'tldraw-migrate-skill' } })
|
|
.then(async (r) => {
|
|
if (!r.ok) throw new Error(`HTTP ${r.status} ${r.statusText}`)
|
|
return r.text()
|
|
})
|
|
.catch((err) => {
|
|
process.stderr.write(`Warning: failed to fetch ${file.name}: ${err.message}\n`)
|
|
failed.push(file.name)
|
|
return null
|
|
})
|
|
if (text === null) continue
|
|
|
|
const body = text.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n+/, '').trim()
|
|
sections.push(`# ${file.name.replace(/\.mdx$/, '')}`, '', body, '', '--------', '')
|
|
}
|
|
|
|
process.stdout.write(sections.join('\n'))
|
|
|
|
if (failed.length > 0) {
|
|
process.stderr.write(
|
|
`\nERROR: ${failed.length} of ${relevant.length} release files failed to fetch (${failed.join(', ')}). ` +
|
|
`Output is incomplete — re-run, or set GITHUB_TOKEN to avoid the unauthenticated rate limit.\n`
|
|
)
|
|
process.exit(2)
|
|
}
|