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:
183
internal/scripts/lib/didAnyPackageChange.ts
Normal file
183
internal/scripts/lib/didAnyPackageChange.ts
Normal file
@@ -0,0 +1,183 @@
|
||||
import assert from 'assert'
|
||||
import { writeFileSync } from 'fs'
|
||||
import * as diff from 'diff'
|
||||
import kleur from 'kleur'
|
||||
import * as tar from 'tar'
|
||||
import tmp from 'tmp'
|
||||
import { exec } from './exec'
|
||||
import { nicelog } from './nicelog'
|
||||
import { PackageDetails, getAllPackageDetails } from './publishing'
|
||||
|
||||
async function getPackageFirstDiff(
|
||||
pkg: PackageDetails,
|
||||
publishedVersion: string
|
||||
): Promise<Diff | null> {
|
||||
assert(process.env.TLDRAW_BEMO_URL, 'TLDRAW_BEMO_URL env var must be set')
|
||||
const dir = tmp.dirSync({ unsafeCleanup: true })
|
||||
const dirPath = dir.name
|
||||
try {
|
||||
const unscopedName = pkg.name.replace('@tldraw/', '')
|
||||
const url = `https://registry.npmjs.org/${pkg.name}/-/${unscopedName}-${publishedVersion}.tgz`
|
||||
const res = await fetch(url)
|
||||
if (res.status === 404) {
|
||||
// The package is not published at this version. This happens when a
|
||||
// new package was added to the monorepo since the last release, or
|
||||
// when a previous publish was interrupted after the version-bump
|
||||
// commit landed but before npm publish completed. In either case
|
||||
// treat it as an added package so the release proceeds instead of
|
||||
// failing the entire workflow.
|
||||
nicelog(
|
||||
`No published tarball for ${pkg.name}@${publishedVersion} (${url}); treating as added.`
|
||||
)
|
||||
return { type: 'added', packageName: pkg.name, filePath: pkg.name }
|
||||
}
|
||||
if (res.status >= 400) {
|
||||
throw new Error(`Failed to fetch ${url}: ${res.status}`)
|
||||
}
|
||||
const publishedTarballPath = `${dirPath}/published-package.tgz`
|
||||
writeFileSync(publishedTarballPath, Buffer.from(await res.arrayBuffer()))
|
||||
const publishedManifest = getTarballManifestSync(publishedTarballPath)
|
||||
|
||||
const localTarballPath = `${dirPath}/local-package.tgz`
|
||||
await exec('yarn', ['pack', '--out', localTarballPath], { pwd: pkg.dir, env: process.env })
|
||||
|
||||
const localManifest = getTarballManifestSync(localTarballPath)
|
||||
|
||||
return getManifestFirstDiff(pkg.name, publishedManifest, localManifest)
|
||||
} finally {
|
||||
dir.removeCallback()
|
||||
}
|
||||
}
|
||||
|
||||
type Diff =
|
||||
| {
|
||||
type: 'added' | 'removed'
|
||||
packageName: string
|
||||
filePath: string
|
||||
}
|
||||
| {
|
||||
type: 'changed'
|
||||
packageName: string
|
||||
filePath: string
|
||||
diff: string
|
||||
}
|
||||
|
||||
// In the `tldraw` package these files are generated during publishing from the
|
||||
// docs content in `apps/docs/content` (see `generate-tldraw-package-docs.ts`),
|
||||
// not from SDK source. A docs-only change therefore alters them, which would
|
||||
// otherwise make this diff check report a package change and cut a spurious SDK
|
||||
// patch release. Ignore them for the `tldraw` package so that docs-only patches
|
||||
// don't trigger a new SDK version. Other packages ship a hand-authored DOCS.md
|
||||
// as real source, so we only skip these for `tldraw`.
|
||||
//
|
||||
// `generate-tldraw-package-docs.ts` writes these files to the package root, so
|
||||
// in the tarball they live at exactly `package/<name>` (tarball entry paths are
|
||||
// prefixed with `package/`). Match the full path rather than the basename so a
|
||||
// same-named file in some other folder still counts as a real change.
|
||||
const GENERATED_DOCS_FILES = new Set(['package/DOCS.md', 'package/RELEASE_NOTES.md'])
|
||||
const GENERATED_DOCS_PACKAGE = 'tldraw'
|
||||
|
||||
function isGeneratedDocsFile(packageName: string, filePath: string) {
|
||||
if (packageName !== GENERATED_DOCS_PACKAGE) return false
|
||||
return GENERATED_DOCS_FILES.has(filePath)
|
||||
}
|
||||
|
||||
function getManifestFirstDiff(
|
||||
packageName: string,
|
||||
a: Record<string, Buffer>,
|
||||
b: Record<string, Buffer>
|
||||
): Diff | null {
|
||||
const aKeys = Object.keys(a).filter((key) => !isGeneratedDocsFile(packageName, key))
|
||||
const bKeys = Object.keys(b).filter((key) => !isGeneratedDocsFile(packageName, key))
|
||||
for (const key of aKeys) {
|
||||
if (!bKeys.includes(key)) {
|
||||
return { type: 'removed', packageName, filePath: key }
|
||||
}
|
||||
if (!a[key].equals(b[key])) {
|
||||
return {
|
||||
type: 'changed',
|
||||
packageName,
|
||||
filePath: key,
|
||||
diff: diff.createTwoFilesPatch(key, key, a[key].toString(), b[key].toString()),
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const key of bKeys) {
|
||||
if (!aKeys.includes(key)) {
|
||||
return { type: 'added', packageName, filePath: key }
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function getTarballManifestSync(tarballPath: string) {
|
||||
const manifest: Record<string, Buffer> = {}
|
||||
tar.list({
|
||||
file: tarballPath,
|
||||
onentry: (entry) => {
|
||||
entry.on('data', (data) => {
|
||||
// we could hash these to reduce memory but it's probably fine
|
||||
const existing = manifest[entry.path]
|
||||
if (existing) {
|
||||
manifest[entry.path] = Buffer.concat([existing, data])
|
||||
} else {
|
||||
manifest[entry.path] = data
|
||||
}
|
||||
})
|
||||
},
|
||||
sync: true,
|
||||
})
|
||||
|
||||
return manifest
|
||||
}
|
||||
|
||||
export function formatDiff(diff: Diff) {
|
||||
let message = kleur.cyan().bold(diff.packageName) + ' changed:'
|
||||
switch (diff.type) {
|
||||
case 'added':
|
||||
message += `\n New file added ${kleur.green().bold(diff.filePath)}`
|
||||
break
|
||||
case 'removed':
|
||||
message += `\n File removed ${kleur.red().bold(diff.filePath)}`
|
||||
break
|
||||
case 'changed':
|
||||
message += `\n File changed ${kleur.yellow().bold(diff.filePath)}\n\n`
|
||||
message += diff.diff
|
||||
.split('\n')
|
||||
.map((line) => {
|
||||
if (line.startsWith('+')) {
|
||||
return kleur.green(line)
|
||||
} else if (line.startsWith('-')) {
|
||||
return kleur.red(line)
|
||||
} else {
|
||||
return line
|
||||
}
|
||||
})
|
||||
.join('\n')
|
||||
|
||||
break
|
||||
}
|
||||
return message
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare each local package against the published tarball at
|
||||
* `publishedVersion` on npm and return the first detected difference, or null
|
||||
* if every package is identical to its published counterpart.
|
||||
*
|
||||
* Callers should pass the latest version that's actually published on npm
|
||||
* (e.g. from `npm show <pkg> version`) rather than the version in the local
|
||||
* `package.json`. The local version can drift ahead of npm if a previous
|
||||
* publish was interrupted after the version-bump commit was pushed; using the
|
||||
* known-published version keeps the diff check robust to that state.
|
||||
*/
|
||||
export async function getAnyPackageDiff(publishedVersion: string): Promise<Diff | null> {
|
||||
const details = await getAllPackageDetails()
|
||||
for (const pkg of Object.values(details)) {
|
||||
const diff = await getPackageFirstDiff(pkg, publishedVersion)
|
||||
if (diff) {
|
||||
return diff
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
Reference in New Issue
Block a user