diff --git a/scripts/lib/didAnyPackageChange.ts b/scripts/lib/didAnyPackageChange.ts new file mode 100644 index 000000000..b51ea7fb2 --- /dev/null +++ b/scripts/lib/didAnyPackageChange.ts @@ -0,0 +1,84 @@ +import { writeFileSync } from 'fs' +import tar from 'tar' +import tmp from 'tmp' +import { exec } from './exec' +import { PackageDetails, getAllPackageDetails } from './publishing' + +async function hasPackageChanged(pkg: PackageDetails) { + const dir = tmp.dirSync({ unsafeCleanup: true }) + const dirPath = dir.name + try { + const version = pkg.version + + const unscopedName = pkg.name.replace('@tldraw/', '') + const url = `https://registry.npmjs.org/${pkg.name}/-/${unscopedName}-${version}.tgz` + const res = await fetch(url) + if (res.status >= 400) { + throw new Error(`Package not found at url ${url}: ${res.status}`) + } + const publishedTarballPath = `${dirPath}/published-package.tgz` + writeFileSync(publishedTarballPath, Buffer.from(await res.arrayBuffer())) + const publishedManifest = await getTarballManifest(publishedTarballPath) + + const localTarballPath = `${dirPath}/local-package.tgz` + await exec('yarn', ['pack', '--out', localTarballPath], { pwd: pkg.dir }) + + const localManifest = await getTarballManifest(localTarballPath) + + return !manifestsAreEqual(publishedManifest, localManifest) + } finally { + dir.removeCallback() + } +} + +function manifestsAreEqual(a: Record, b: Record) { + const aKeys = Object.keys(a) + const bKeys = Object.keys(b) + if (aKeys.length !== bKeys.length) { + return false + } + for (const key of aKeys) { + if (!bKeys.includes(key)) { + return false + } + if (!a[key].equals(b[key])) { + return false + } + } + return true +} + +function getTarballManifest(tarballPath: string): Promise> { + const manifest: Record = {} + return new Promise((resolve, reject) => + tar.list( + { + // @ts-expect-error bad typings + file: tarballPath, + onentry: (entry) => { + entry.on('data', (data) => { + // we could hash these to reduce memory but it's probably fine + manifest[entry.path] = data + }) + }, + }, + (err: any) => { + if (err) { + reject(err) + } else { + resolve(manifest) + } + } + ) + ) +} + +export async function didAnyPackageChange() { + const details = getAllPackageDetails() + for (const pkg of Object.values(details)) { + if (await hasPackageChanged(pkg)) { + return true + } + } + return false +} diff --git a/scripts/package.json b/scripts/package.json index 7d950bc6f..e1bf07393 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -32,7 +32,7 @@ "@aws-sdk/lib-storage": "^3.440.0", "@types/is-ci": "^3.0.0", "@types/node": "~20.11", - "@types/tar": "^6.1.7", + "@types/tar": "^6.1.11", "@typescript-eslint/utils": "^5.59.0", "ast-types": "^0.14.2", "cross-fetch": "^3.1.5", @@ -56,8 +56,10 @@ "dependencies": { "@octokit/rest": "^20.0.2", "@types/minimist": "^1.2.5", + "@types/tmp": "^0.2.6", "ignore": "^5.2.4", "minimist": "^1.2.8", - "tar": "^6.2.0" + "tar": "^6.2.0", + "tmp": "^0.2.3" } } diff --git a/scripts/publish-new.ts b/scripts/publish-new.ts index 41f212003..5dc619e90 100644 --- a/scripts/publish-new.ts +++ b/scripts/publish-new.ts @@ -125,6 +125,7 @@ async function main() { if (!isPrerelease) { const { major, minor } = parse(nextVersion)! await exec('git', ['push', 'origin', `${gitTag}:refs/heads/v${major}.${minor}.x`]) + await exec('git', ['push', 'origin', `${gitTag}:docs-production`, `--force`]) } // create a release on github diff --git a/scripts/publish-patch.ts b/scripts/publish-patch.ts index 848573d55..53592ef56 100644 --- a/scripts/publish-patch.ts +++ b/scripts/publish-patch.ts @@ -3,6 +3,7 @@ import fetch from 'cross-fetch' import glob from 'glob' import { assert } from 'node:console' import { appendFileSync } from 'node:fs' +import { didAnyPackageChange } from './lib/didAnyPackageChange' import { exec } from './lib/exec' import { generateAutoRcFile } from './lib/labels' import { nicelog } from './lib/nicelog' @@ -41,6 +42,17 @@ async function main() { return } + if (isLatestVersion) { + await exec('git', ['push', 'origin', `HEAD:docs-production`, '--force']) + } + + // Skip releasing a new version if the package contents are identical. + // This may happen when cherry-picking docs-only changes. + if (!(await didAnyPackageChange())) { + nicelog('No packages have changed, skipping release') + return + } + nicelog('Releasing version', nextVersion) await setAllVersions(nextVersion) diff --git a/scripts/vercel/should-build-docs.sh b/scripts/vercel/should-build-docs.sh new file mode 100755 index 000000000..a84166624 --- /dev/null +++ b/scripts/vercel/should-build-docs.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +set -eux + +if [[ "$VERCEL_ENV" == "production" ]] ; then + echo "Always build on production"; + exit 1; +fi + +## main is not production anymore, but we still always want to build it +if [[ "$VERCEL_GIT_COMMIT_REF" == "main" ]] ; then + echo "Always build on main"; + exit 1; +fi + +## on PR builds, only rebuild if the docs directory changed +git diff HEAD^ HEAD --quiet ./apps/docs + diff --git a/yarn.lock b/yarn.lock index 22803447b..f7f57330c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7351,7 +7351,8 @@ __metadata: "@types/is-ci": "npm:^3.0.0" "@types/minimist": "npm:^1.2.5" "@types/node": "npm:~20.11" - "@types/tar": "npm:^6.1.7" + "@types/tar": "npm:^6.1.11" + "@types/tmp": "npm:^0.2.6" "@typescript-eslint/utils": "npm:^5.59.0" ast-types: "npm:^0.14.2" cross-fetch: "npm:^3.1.5" @@ -7370,6 +7371,7 @@ __metadata: semver: "npm:^7.3.8" svgo: "npm:^3.0.2" tar: "npm:^6.2.0" + tmp: "npm:^0.2.3" typescript: "npm:^5.3.3" languageName: unknown linkType: soft @@ -8212,13 +8214,13 @@ __metadata: languageName: node linkType: hard -"@types/tar@npm:^6.1.7": - version: 6.1.10 - resolution: "@types/tar@npm:6.1.10" +"@types/tar@npm:^6.1.11": + version: 6.1.11 + resolution: "@types/tar@npm:6.1.11" dependencies: "@types/node": "npm:*" minipass: "npm:^4.0.0" - checksum: da525415a9bac9e81a1498d0b684dd7fa34f69a8568b54ab19660e99d5e7dcdeb2527a40059e1cfc697fe4bbcc18cd03a50c96356d84ab865345c2c48a9d88f6 + checksum: 0d54b8acbd7d2fc43bd1097eef5058604a6b0e3a394cf485038303ca3ef39ecb42451c7dc5a2b9b18420e137ef5b2c76ec504e94c2f45010b2c8e8c3a49d9de7 languageName: node linkType: hard @@ -8231,6 +8233,13 @@ __metadata: languageName: node linkType: hard +"@types/tmp@npm:^0.2.6": + version: 0.2.6 + resolution: "@types/tmp@npm:0.2.6" + checksum: e14a094c10569d3b56805552b21417860ef21060d969000d5d5b53604a78c2bdac216f064b03797d4b07a081e0141dd3ab22bc36923e75300eb1c023f7252cc7 + languageName: node + linkType: hard + "@types/tough-cookie@npm:*": version: 4.0.5 resolution: "@types/tough-cookie@npm:4.0.5" @@ -21513,7 +21522,7 @@ __metadata: languageName: node linkType: hard -"rimraf@npm:^3.0.0, rimraf@npm:^3.0.2": +"rimraf@npm:^3.0.2": version: 3.0.2 resolution: "rimraf@npm:3.0.2" dependencies: @@ -23015,12 +23024,10 @@ __metadata: languageName: node linkType: hard -"tmp@npm:^0.2.1": - version: 0.2.1 - resolution: "tmp@npm:0.2.1" - dependencies: - rimraf: "npm:^3.0.0" - checksum: 445148d72df3ce99356bc89a7857a0c5c3b32958697a14e50952c6f7cf0a8016e746ababe9a74c1aa52f04c526661992f14659eba34d3c6701d49ba2f3cf781b +"tmp@npm:^0.2.1, tmp@npm:^0.2.3": + version: 0.2.3 + resolution: "tmp@npm:0.2.3" + checksum: 7b13696787f159c9754793a83aa79a24f1522d47b87462ddb57c18ee93ff26c74cbb2b8d9138f571d2e0e765c728fb2739863a672b280528512c6d83d511c6fa languageName: node linkType: hard