[docs] Sync docs deploy with npm deploy (#3153)

This PR makes it so that our docs deployment process is tied to, and
mirrors, the npm deployment process.

From here on:

- Commits to main get deployed to staging.tldraw.dev
- Commits to a special protected branch called `docs-production` get
deployed to www.tldraw.dev
- Whenever we create a new npm 'latest' release we reset the HEAD of
docs-production to point to the tagged commit for that release.
- If we make a docs change that we want to appear on tldraw.dev ASAP
without waiting for the next npm release, we'll have to follow the same
process as for creating a patch release i.e merge a cherry-pick PR
targeting the latest release branch e.g. `v2.0.x`. This will not cause
another npm patch release unless the cherry-picked changes touch source
files, e.g. updating TSDoc comments.


### Change Type


- [x] `docs` — Changes to the documentation, examples, or templates.
- [x] `tools` — Changes to infrastructure, CI, internal scripts,
debugging tools, etc.
This commit is contained in:
David Sheldrick 2024-03-14 14:43:32 +00:00 committed by GitHub
parent 4d8dab843e
commit 44a3ea7363
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 137 additions and 14 deletions

View file

@ -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<string, Buffer>, b: Record<string, Buffer>) {
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<Record<string, Buffer>> {
const manifest: Record<string, Buffer> = {}
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
}

View file

@ -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"
}
}

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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