[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:
parent
4d8dab843e
commit
44a3ea7363
6 changed files with 137 additions and 14 deletions
84
scripts/lib/didAnyPackageChange.ts
Normal file
84
scripts/lib/didAnyPackageChange.ts
Normal 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
|
||||||
|
}
|
|
@ -32,7 +32,7 @@
|
||||||
"@aws-sdk/lib-storage": "^3.440.0",
|
"@aws-sdk/lib-storage": "^3.440.0",
|
||||||
"@types/is-ci": "^3.0.0",
|
"@types/is-ci": "^3.0.0",
|
||||||
"@types/node": "~20.11",
|
"@types/node": "~20.11",
|
||||||
"@types/tar": "^6.1.7",
|
"@types/tar": "^6.1.11",
|
||||||
"@typescript-eslint/utils": "^5.59.0",
|
"@typescript-eslint/utils": "^5.59.0",
|
||||||
"ast-types": "^0.14.2",
|
"ast-types": "^0.14.2",
|
||||||
"cross-fetch": "^3.1.5",
|
"cross-fetch": "^3.1.5",
|
||||||
|
@ -56,8 +56,10 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/rest": "^20.0.2",
|
"@octokit/rest": "^20.0.2",
|
||||||
"@types/minimist": "^1.2.5",
|
"@types/minimist": "^1.2.5",
|
||||||
|
"@types/tmp": "^0.2.6",
|
||||||
"ignore": "^5.2.4",
|
"ignore": "^5.2.4",
|
||||||
"minimist": "^1.2.8",
|
"minimist": "^1.2.8",
|
||||||
"tar": "^6.2.0"
|
"tar": "^6.2.0",
|
||||||
|
"tmp": "^0.2.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -125,6 +125,7 @@ async function main() {
|
||||||
if (!isPrerelease) {
|
if (!isPrerelease) {
|
||||||
const { major, minor } = parse(nextVersion)!
|
const { major, minor } = parse(nextVersion)!
|
||||||
await exec('git', ['push', 'origin', `${gitTag}:refs/heads/v${major}.${minor}.x`])
|
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
|
// create a release on github
|
||||||
|
|
|
@ -3,6 +3,7 @@ import fetch from 'cross-fetch'
|
||||||
import glob from 'glob'
|
import glob from 'glob'
|
||||||
import { assert } from 'node:console'
|
import { assert } from 'node:console'
|
||||||
import { appendFileSync } from 'node:fs'
|
import { appendFileSync } from 'node:fs'
|
||||||
|
import { didAnyPackageChange } from './lib/didAnyPackageChange'
|
||||||
import { exec } from './lib/exec'
|
import { exec } from './lib/exec'
|
||||||
import { generateAutoRcFile } from './lib/labels'
|
import { generateAutoRcFile } from './lib/labels'
|
||||||
import { nicelog } from './lib/nicelog'
|
import { nicelog } from './lib/nicelog'
|
||||||
|
@ -41,6 +42,17 @@ async function main() {
|
||||||
return
|
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)
|
nicelog('Releasing version', nextVersion)
|
||||||
|
|
||||||
await setAllVersions(nextVersion)
|
await setAllVersions(nextVersion)
|
||||||
|
|
17
scripts/vercel/should-build-docs.sh
Executable file
17
scripts/vercel/should-build-docs.sh
Executable 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
|
||||||
|
|
31
yarn.lock
31
yarn.lock
|
@ -7351,7 +7351,8 @@ __metadata:
|
||||||
"@types/is-ci": "npm:^3.0.0"
|
"@types/is-ci": "npm:^3.0.0"
|
||||||
"@types/minimist": "npm:^1.2.5"
|
"@types/minimist": "npm:^1.2.5"
|
||||||
"@types/node": "npm:~20.11"
|
"@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"
|
"@typescript-eslint/utils": "npm:^5.59.0"
|
||||||
ast-types: "npm:^0.14.2"
|
ast-types: "npm:^0.14.2"
|
||||||
cross-fetch: "npm:^3.1.5"
|
cross-fetch: "npm:^3.1.5"
|
||||||
|
@ -7370,6 +7371,7 @@ __metadata:
|
||||||
semver: "npm:^7.3.8"
|
semver: "npm:^7.3.8"
|
||||||
svgo: "npm:^3.0.2"
|
svgo: "npm:^3.0.2"
|
||||||
tar: "npm:^6.2.0"
|
tar: "npm:^6.2.0"
|
||||||
|
tmp: "npm:^0.2.3"
|
||||||
typescript: "npm:^5.3.3"
|
typescript: "npm:^5.3.3"
|
||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
|
@ -8212,13 +8214,13 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@types/tar@npm:^6.1.7":
|
"@types/tar@npm:^6.1.11":
|
||||||
version: 6.1.10
|
version: 6.1.11
|
||||||
resolution: "@types/tar@npm:6.1.10"
|
resolution: "@types/tar@npm:6.1.11"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/node": "npm:*"
|
"@types/node": "npm:*"
|
||||||
minipass: "npm:^4.0.0"
|
minipass: "npm:^4.0.0"
|
||||||
checksum: da525415a9bac9e81a1498d0b684dd7fa34f69a8568b54ab19660e99d5e7dcdeb2527a40059e1cfc697fe4bbcc18cd03a50c96356d84ab865345c2c48a9d88f6
|
checksum: 0d54b8acbd7d2fc43bd1097eef5058604a6b0e3a394cf485038303ca3ef39ecb42451c7dc5a2b9b18420e137ef5b2c76ec504e94c2f45010b2c8e8c3a49d9de7
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
@ -8231,6 +8233,13 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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:*":
|
"@types/tough-cookie@npm:*":
|
||||||
version: 4.0.5
|
version: 4.0.5
|
||||||
resolution: "@types/tough-cookie@npm:4.0.5"
|
resolution: "@types/tough-cookie@npm:4.0.5"
|
||||||
|
@ -21513,7 +21522,7 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"rimraf@npm:^3.0.0, rimraf@npm:^3.0.2":
|
"rimraf@npm:^3.0.2":
|
||||||
version: 3.0.2
|
version: 3.0.2
|
||||||
resolution: "rimraf@npm:3.0.2"
|
resolution: "rimraf@npm:3.0.2"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -23015,12 +23024,10 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"tmp@npm:^0.2.1":
|
"tmp@npm:^0.2.1, tmp@npm:^0.2.3":
|
||||||
version: 0.2.1
|
version: 0.2.3
|
||||||
resolution: "tmp@npm:0.2.1"
|
resolution: "tmp@npm:0.2.3"
|
||||||
dependencies:
|
checksum: 7b13696787f159c9754793a83aa79a24f1522d47b87462ddb57c18ee93ff26c74cbb2b8d9138f571d2e0e765c728fb2739863a672b280528512c6d83d511c6fa
|
||||||
rimraf: "npm:^3.0.0"
|
|
||||||
checksum: 445148d72df3ce99356bc89a7857a0c5c3b32958697a14e50952c6f7cf0a8016e746ababe9a74c1aa52f04c526661992f14659eba34d3c6701d49ba2f3cf781b
|
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue