tldraw/scripts/deploy-dotcom.ts

407 lines
13 KiB
TypeScript
Raw Normal View History

import { GetObjectCommand, ListObjectsV2Command, S3Client } from '@aws-sdk/client-s3'
import { Upload } from '@aws-sdk/lib-storage'
import assert from 'assert'
import { execSync } from 'child_process'
import { existsSync, readdirSync, writeFileSync } from 'fs'
import path from 'path'
import { PassThrough } from 'stream'
import * as tar from 'tar'
import {
createGithubDeployment,
getDeployInfo,
setWranglerPreviewConfig,
wranglerDeploy,
} from './lib/deploy'
import { Discord } from './lib/discord'
import { exec } from './lib/exec'
import { makeEnv } from './lib/makeEnv'
import { nicelog } from './lib/nicelog'
const worker = path.relative(process.cwd(), path.resolve(__dirname, '../apps/dotcom-worker'))
const healthWorker = path.relative(process.cwd(), path.resolve(__dirname, '../apps/health-worker'))
const assetUpload = path.relative(
process.cwd(),
path.resolve(__dirname, '../apps/dotcom-asset-upload')
)
const dotcom = path.relative(process.cwd(), path.resolve(__dirname, '../apps/dotcom'))
// Do not use `process.env` directly in this script. Add your variable to `makeEnv` and use it via
// `env` instead. This makes sure that all required env vars are present.
const env = makeEnv([
'APP_ORIGIN',
'ASSET_UPLOAD',
assets: make option to transform urls dynamically / LOD (#3827) this is take #2 of this PR https://github.com/tldraw/tldraw/pull/3764 This continues the idea kicked off in https://github.com/tldraw/tldraw/pull/3684 to explore LOD and takes it in a different direction. Several things here to call out: - our dotcom version would start to use Cloudflare's image transforms - we don't rewrite non-image assets - we debounce zooming so that we're not swapping out images while zooming (it creates jank) - we load different images based on steps of .25 (maybe we want to make this more, like 0.33). Feels like 0.5 might be a bit too much but we can play around with it. - we take into account network connection speed. if you're on 3g, for example, we have the size of the image. - dpr is taken into account - in our case, Cloudflare handles it. But if it wasn't Cloudflare, we could add it to our width equation. - we use Cloudflare's `fit=scale-down` setting to never scale _up_ an image. - we don't swap the image in until we've finished loading it programatically (to avoid a blank image while it loads) TODO - [x] We need to enable Cloudflare's pricing on image transforms btw @steveruizok 😉 - this won't work quite yet until we do that. ### Change Type <!-- ❗ Please select a 'Scope' label ❗️ --> - [x] `sdk` — Changes the tldraw SDK - [ ] `dotcom` — Changes the tldraw.com web app - [ ] `docs` — Changes to the documentation, examples, or templates. - [ ] `vs code` — Changes to the vscode plugin - [ ] `internal` — Does not affect user-facing stuff <!-- ❗ Please select a 'Type' label ❗️ --> - [ ] `bugfix` — Bug fix - [x] `feature` — New feature - [ ] `improvement` — Improving existing features - [ ] `chore` — Updating dependencies, other boring stuff - [ ] `galaxy brain` — Architectural changes - [ ] `tests` — Changes to any test code - [ ] `tools` — Changes to infrastructure, CI, internal scripts, debugging tools, etc. - [ ] `dunno` — I don't know ### Test Plan 1. Test images on staging, small, medium, large, mega 2. Test videos on staging - [x] Unit Tests - [ ] End to end tests ### Release Notes - Assets: make option to transform urls dynamically to provide different sized images on demand.
2024-06-11 14:17:09 +00:00
'ASSET_BUCKET_ORIGIN',
'CLOUDFLARE_ACCOUNT_ID',
'CLOUDFLARE_API_TOKEN',
'DISCORD_DEPLOY_WEBHOOK_URL',
'DISCORD_HEALTH_WEBHOOK_URL',
'HEALTH_WORKER_UPDOWN_WEBHOOK_PATH',
'GC_MAPS_API_KEY',
'RELEASE_COMMIT_HASH',
'SENTRY_AUTH_TOKEN',
'SENTRY_DSN',
'SENTRY_CSP_REPORT_URI',
'SUPABASE_LITE_ANON_KEY',
'SUPABASE_LITE_URL',
'TLDRAW_ENV',
'VERCEL_PROJECT_ID',
'VERCEL_ORG_ID',
'VERCEL_TOKEN',
'WORKER_SENTRY_DSN',
'MULTIPLAYER_SERVER',
'GH_TOKEN',
'R2_ACCESS_KEY_ID',
'R2_ACCESS_KEY_SECRET',
])
const discord = new Discord({
webhookUrl: env.DISCORD_DEPLOY_WEBHOOK_URL,
shouldNotify: env.TLDRAW_ENV === 'production',
totalSteps: 8,
})
const { previewId, sha } = getDeployInfo()
const sentryReleaseName = `${env.TLDRAW_ENV}-${previewId ? previewId + '-' : ''}-${sha}`
async function main() {
assert(
env.TLDRAW_ENV === 'staging' || env.TLDRAW_ENV === 'production' || env.TLDRAW_ENV === 'preview',
'TLDRAW_ENV must be staging or production or preview'
)
await discord.message(`--- **${env.TLDRAW_ENV} dotcom deploy pre-flight** ---`)
await discord.step('setting up deploy', async () => {
// make sure the tldraw .css files are built:
await exec('yarn', ['lazy', 'prebuild'])
// link to vercel and supabase projects:
await vercelCli('link', ['--project', env.VERCEL_PROJECT_ID])
})
// deploy pre-flight steps:
// 1. get the dotcom app ready to go (env vars and pre-build)
await discord.step('building dotcom app', async () => {
await createSentryRelease()
await prepareDotcomApp()
await uploadSourceMaps()
await coalesceWithPreviousAssets(`${dotcom}/.vercel/output/static/assets`)
})
await discord.step('cloudflare deploy dry run', async () => {
await deployAssetUploadWorker({ dryRun: true })
await deployHealthWorker({ dryRun: true })
await deployTlsyncWorker({ dryRun: true })
})
// --- point of no return! do the deploy for real --- //
await discord.message(`--- **pre-flight complete, starting real dotcom deploy** ---`)
// 2. deploy the cloudflare workers:
await discord.step('deploying asset uploader to cloudflare', async () => {
await deployAssetUploadWorker({ dryRun: false })
})
await discord.step('deploying multiplayer worker to cloudflare', async () => {
await deployTlsyncWorker({ dryRun: false })
})
await discord.step('deploying health worker to cloudflare', async () => {
await deployHealthWorker({ dryRun: false })
})
// 3. deploy the pre-build dotcom app:
const { deploymentUrl, inspectUrl } = await discord.step(
'deploying dotcom app to vercel',
async () => {
return await deploySpa()
}
)
let deploymentAlias = null as null | string
if (previewId) {
const aliasDomain = `${previewId}-preview-deploy.tldraw.com`
await discord.step('aliasing preview deployment', async () => {
await vercelCli('alias', ['set', deploymentUrl, aliasDomain])
})
deploymentAlias = `https://${aliasDomain}`
}
nicelog('Creating deployment for', deploymentUrl)
await createGithubDeployment(env, {
app: 'dotcom',
deploymentUrl: deploymentAlias ?? deploymentUrl,
inspectUrl,
sha,
})
await discord.message(`**Deploy complete!**`)
}
async function prepareDotcomApp() {
// pre-build the app:
await exec('yarn', ['build-app'], {
env: {
NEXT_PUBLIC_TLDRAW_RELEASE_INFO: `${env.RELEASE_COMMIT_HASH} ${new Date().toISOString()}`,
ASSET_UPLOAD: previewId
? `https://${previewId}-tldraw-assets.tldraw.workers.dev`
: env.ASSET_UPLOAD,
MULTIPLAYER_SERVER: previewId
? `https://${previewId}-tldraw-multiplayer.tldraw.workers.dev`
: env.MULTIPLAYER_SERVER,
NEXT_PUBLIC_CONTROL_SERVER: 'https://control.tldraw.com',
NEXT_PUBLIC_GC_API_KEY: env.GC_MAPS_API_KEY,
SENTRY_AUTH_TOKEN: env.SENTRY_AUTH_TOKEN,
SENTRY_ORG: 'tldraw',
SENTRY_PROJECT: 'lite',
SUPABASE_KEY: env.SUPABASE_LITE_ANON_KEY,
SUPABASE_URL: env.SUPABASE_LITE_URL,
TLDRAW_ENV: env.TLDRAW_ENV,
},
})
}
let didUpdateAssetUploadWorker = false
async function deployAssetUploadWorker({ dryRun }: { dryRun: boolean }) {
if (previewId && !didUpdateAssetUploadWorker) {
await setWranglerPreviewConfig(assetUpload, { name: `${previewId}-tldraw-assets` })
didUpdateAssetUploadWorker = true
}
await wranglerDeploy({
location: assetUpload,
dryRun,
env: env.TLDRAW_ENV,
vars: {},
})
}
let didUpdateTlsyncWorker = false
async function deployTlsyncWorker({ dryRun }: { dryRun: boolean }) {
const workerId = `${previewId ?? env.TLDRAW_ENV}-tldraw-multiplayer`
if (previewId && !didUpdateTlsyncWorker) {
await setWranglerPreviewConfig(worker, { name: workerId })
didUpdateTlsyncWorker = true
}
await wranglerDeploy({
location: worker,
dryRun,
env: env.TLDRAW_ENV,
vars: {
SUPABASE_URL: env.SUPABASE_LITE_URL,
SUPABASE_KEY: env.SUPABASE_LITE_ANON_KEY,
SENTRY_DSN: env.WORKER_SENTRY_DSN,
TLDRAW_ENV: env.TLDRAW_ENV,
APP_ORIGIN: env.APP_ORIGIN,
WORKER_NAME: workerId,
},
})
}
let didUpdateHealthWorker = false
async function deployHealthWorker({ dryRun }: { dryRun: boolean }) {
if (previewId && !didUpdateHealthWorker) {
await setWranglerPreviewConfig(healthWorker, { name: `${previewId}-tldraw-health` })
didUpdateHealthWorker = true
}
await wranglerDeploy({
location: healthWorker,
dryRun,
env: env.TLDRAW_ENV,
vars: {
DISCORD_HEALTH_WEBHOOK_URL: env.DISCORD_HEALTH_WEBHOOK_URL,
HEALTH_WORKER_UPDOWN_WEBHOOK_PATH: env.HEALTH_WORKER_UPDOWN_WEBHOOK_PATH,
},
})
}
type ExecOpts = NonNullable<Parameters<typeof exec>[2]>
async function vercelCli(command: string, args: string[], opts?: ExecOpts) {
return exec(
'yarn',
[
'run',
'-T',
'vercel',
command,
'--token',
env.VERCEL_TOKEN,
'--scope',
env.VERCEL_ORG_ID,
'--yes',
...args,
],
{
...opts,
env: {
// specify org id via args instead of via env vars because otherwise it gets upset
// that there's no project id either
VERCEL_ORG_ID: env.VERCEL_ORG_ID,
VERCEL_PROJECT_ID: env.VERCEL_PROJECT_ID,
...opts?.env,
},
}
)
}
async function deploySpa(): Promise<{ deploymentUrl: string; inspectUrl: string }> {
// both 'staging' and 'production' are deployed to vercel as 'production' deploys
// in separate 'projects'
const prod = env.TLDRAW_ENV !== 'preview'
const out = await vercelCli('deploy', ['--prebuilt', ...(prod ? ['--prod'] : [])], {
pwd: dotcom,
})
const previewURL = out.match(/Preview: (https:\/\/\S*)/)?.[1]
const inspectUrl = out.match(/Inspect: (https:\/\/\S*)/)?.[1]
const productionURL = out.match(/Production: (https:\/\/\S*)/)?.[1]
const deploymentUrl = previewURL ?? productionURL
if (!deploymentUrl) {
throw new Error('Could not find deployment URL in vercel output ' + out)
}
if (!inspectUrl) {
throw new Error('Could not find inspect URL in vercel output ' + out)
}
return { deploymentUrl, inspectUrl }
}
const sentryEnv = {
SENTRY_AUTH_TOKEN: env.SENTRY_AUTH_TOKEN,
SENTRY_ORG: 'tldraw',
SENTRY_PROJECT: 'lite',
}
const execSentry = (command: string, args: string[]) =>
exec(`yarn`, ['run', '-T', 'sentry-cli', command, ...args], { env: sentryEnv })
async function createSentryRelease() {
await execSentry('releases', ['new', sentryReleaseName])
if (!existsSync(`${dotcom}/sentry-release-name.ts`)) {
throw new Error('sentry-release-name.ts does not exist')
}
writeFileSync(
`${dotcom}/sentry-release-name.ts`,
`// This file is replaced during deployments to point to a meaningful release name in Sentry.` +
`// DO NOT MESS WITH THIS LINE OR THE ONE BELOW IT. I WILL FIND YOU\n` +
`export const sentryReleaseName = '${sentryReleaseName}'`
)
}
async function uploadSourceMaps() {
const sourceMapDir = `${dotcom}/dist/assets`
await execSentry('sourcemaps', ['upload', '--release', sentryReleaseName, sourceMapDir])
execSync('rm -rf ./.next/static/chunks/**/*.map')
}
const R2_URL = `https://c34edc4e76350954b63adebde86d5eb1.r2.cloudflarestorage.com`
const R2_BUCKET = `dotcom-deploy-assets-cache`
const R2 = new S3Client({
region: 'auto',
endpoint: R2_URL,
credentials: {
accessKeyId: env.R2_ACCESS_KEY_ID,
secretAccessKey: env.R2_ACCESS_KEY_SECRET,
},
})
/**
* When we run a vite prod build it creates a folder in the output dir called assets in which
* every file includes a hash of its contents in the filename. These files include files that
* are 'imported' by the js bundle, e.g. svg and css files, along with the js bundle itself
* (split into chunks).
*
* By default, when we deploy a new version of the app it will replace the previous versions
* of the files with new versions. This is problematic when we make a new deploy because if
* existing users have tldraw open in tabs while we make the new deploy, they might still try
* to fetch .js chunks or images which are no longer present on the server and may not have
* been cached by vercel's CDN in their location (or at all).
*
* To avoid this, we keep track of the assets from previous deploys in R2 and include them in the
* new deploy. This way, if a user has an old version of the app open in a tab, they will still
* be able to fetch the old assets from the previous deploy.
*/
async function coalesceWithPreviousAssets(assetsDir: string) {
nicelog('Saving assets to R2 bucket')
const objectKey = `${previewId ?? env.TLDRAW_ENV}/${new Date().toISOString()}+${sha}.tar.gz`
const pack = tar.c({ gzip: true, cwd: assetsDir }, readdirSync(assetsDir))
// Need to pipe through a PassThrough here because the tar stream is not a first class node stream
// and AWS's sdk expects a node stream (it checks `Body instanceof streams.Readable`)
const Body = new PassThrough()
pack.pipe(Body)
await new Upload({
client: R2,
params: {
Bucket: R2_BUCKET,
Key: objectKey,
Body,
},
}).done()
nicelog('Extracting previous assets from R2 bucket')
const { Contents } = await R2.send(
new ListObjectsV2Command({
Bucket: R2_BUCKET,
Prefix: `${previewId ?? env.TLDRAW_ENV}/`,
})
)
const [mostRecent, ...others] =
// filter out the one we just uploaded
Contents?.filter((obj) => obj.Key !== objectKey).sort(
(a, b) => (b.LastModified?.getTime() ?? 0) - (a.LastModified?.getTime() ?? 0)
) ?? []
if (!mostRecent) {
nicelog('No previous assets found')
return
}
// Always include the assets from the directly previous build, but also if there
// have been more deploys in the last two weeks, include those too.
const twoWeeks = 1000 * 60 * 60 * 24 * 14
const recentOthers = others.filter(
(o) => (o.LastModified?.getTime() ?? 0) > Date.now() - twoWeeks
)
const objectsToFetch = [mostRecent, ...recentOthers]
nicelog(
`Fetching ${objectsToFetch.length} previous assets from R2 bucket:`,
objectsToFetch.map((k) => k.Key)
)
for (const obj of objectsToFetch) {
const { Body } = await R2.send(
new GetObjectCommand({
Bucket: R2_BUCKET,
Key: obj.Key,
})
)
if (!Body) {
throw new Error(`Could not fetch object ${obj.Key}`)
}
// pipe into untar
// `keep-existing` is important here because we don't want to overwrite the new assets
// if they have the same name as the old assets becuase they will have different sentry debugIds
// and it will mess up the inline source viewer on sentry errors.
const out = tar.x({ cwd: assetsDir, 'keep-existing': true })
for await (const chunk of Body?.transformToWebStream() as any as AsyncIterable<Uint8Array>) {
Bump the npm_and_yarn group across 1 directory with 2 updates (#3505) Bumps the npm_and_yarn group with 2 updates in the / directory: [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) and [tar](https://github.com/isaacs/node-tar). Updates `vite` from 5.2.8 to 5.2.9 <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md">vite's changelog</a>.</em></p> <blockquote> <h2><!-- raw HTML omitted -->5.2.9 (2024-04-15)<!-- raw HTML omitted --></h2> <ul> <li>fix: <code>fsp.rm</code> removing files does not take effect (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/16032">#16032</a>) (<a href="https://github.com/vitejs/vite/commit/b05c405">b05c405</a>), closes <a href="https://redirect.github.com/vitejs/vite/issues/16032">#16032</a></li> <li>fix: fix accumulated stacks in error overlay (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/16393">#16393</a>) (<a href="https://github.com/vitejs/vite/commit/102c2fd">102c2fd</a>), closes <a href="https://redirect.github.com/vitejs/vite/issues/16393">#16393</a></li> <li>fix(deps): update all non-major dependencies (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/16376">#16376</a>) (<a href="https://github.com/vitejs/vite/commit/58a2938">58a2938</a>), closes <a href="https://redirect.github.com/vitejs/vite/issues/16376">#16376</a></li> <li>chore: update region comment (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/16380">#16380</a>) (<a href="https://github.com/vitejs/vite/commit/77562c3">77562c3</a>), closes <a href="https://redirect.github.com/vitejs/vite/issues/16380">#16380</a></li> <li>perf: reduce size of injected __vite__mapDeps code (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/16184">#16184</a>) (<a href="https://github.com/vitejs/vite/commit/c0ec6be">c0ec6be</a>), closes <a href="https://redirect.github.com/vitejs/vite/issues/16184">#16184</a></li> <li>perf(css): only replace empty chunk if imported (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/16349">#16349</a>) (<a href="https://github.com/vitejs/vite/commit/e2658ad">e2658ad</a>), closes <a href="https://redirect.github.com/vitejs/vite/issues/16349">#16349</a></li> </ul> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/vitejs/vite/commit/a77707d69ca53d21e6c7ae9256683ecd3f1d721e"><code>a77707d</code></a> release: v5.2.9</li> <li><a href="https://github.com/vitejs/vite/commit/102c2fd5ad32a607f2b14dd728e8a802b7ddce34"><code>102c2fd</code></a> fix: fix accumulated stacks in error overlay (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/16393">#16393</a>)</li> <li><a href="https://github.com/vitejs/vite/commit/58a2938a9766981fdc2ed89bec8ff1c96cae0716"><code>58a2938</code></a> fix(deps): update all non-major dependencies (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/16376">#16376</a>)</li> <li><a href="https://github.com/vitejs/vite/commit/77562c3ff2005c7ca7fc3749214c76d019fff4e3"><code>77562c3</code></a> chore: update region comment (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/16380">#16380</a>)</li> <li><a href="https://github.com/vitejs/vite/commit/b05c405f6884f9612fd8b6c1e7587a553cf58baf"><code>b05c405</code></a> fix: <code>fsp.rm</code> removing files does not take effect (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/16032">#16032</a>)</li> <li><a href="https://github.com/vitejs/vite/commit/e2658ad6fe81278069d75d0b3b9c260c3021b922"><code>e2658ad</code></a> perf(css): only replace empty chunk if imported (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/16349">#16349</a>)</li> <li><a href="https://github.com/vitejs/vite/commit/c0ec6bea69b6160553f4a5b30652dcef891788fc"><code>c0ec6be</code></a> perf: reduce size of injected __vite__mapDeps code (<a href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/16184">#16184</a>)</li> <li>See full diff in <a href="https://github.com/vitejs/vite/commits/v5.2.9/packages/vite">compare view</a></li> </ul> </details> <br /> Updates `tar` from 6.2.1 to 7.0.1 <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/isaacs/node-tar/blob/main/CHANGELOG.md">tar's changelog</a>.</em></p> <blockquote> <h1>Changelog</h1> <h2>7.0</h2> <ul> <li>Rewrite in TypeScript, provide ESM and CommonJS hybrid interface</li> <li>Add tree-shake friendly exports, like <code>import('tar/create')</code> and <code>import('tar/read-entry')</code> to get individual functions or classes.</li> <li>Add <code>chmod</code> option that defaults to false, and deprecate <code>noChmod</code>. That is, reverse the default option regarding explicitly setting file system modes to match tar entry settings.</li> <li>Add <code>processUmask</code> option to avoid having to call <code>process.umask()</code> when <code>chmod: true</code> (or <code>noChmod: false</code>) is set.</li> </ul> <h2>6.2</h2> <ul> <li>Add support for brotli compression</li> <li>Add <code>maxDepth</code> option to prevent extraction into excessively deep folders.</li> </ul> <h2>6.1</h2> <ul> <li>remove dead link to benchmarks (<a href="https://redirect.github.com/isaacs/node-tar/issues/313">#313</a>) (<a href="https://github.com/yetzt"><code>@​yetzt</code></a>)</li> <li>add examples/explanation of using tar.t (<a href="https://github.com/isaacs"><code>@​isaacs</code></a>)</li> <li>ensure close event is emited after stream has ended (<a href="https://github.com/webark"><code>@​webark</code></a>)</li> <li>replace deprecated String.prototype.substr() (<a href="https://github.com/CommanderRoot"><code>@​CommanderRoot</code></a>, <a href="https://github.com/lukekarrys"><code>@​lukekarrys</code></a>)</li> </ul> <h2>6.0</h2> <ul> <li>Drop support for node 6 and 8</li> <li>fix symlinks and hardlinks on windows being packed with <code>\</code>-style path targets</li> </ul> <h2>5.0</h2> <ul> <li>Address unpack race conditions using path reservations</li> <li>Change large-numbers errors from TypeError to Error</li> <li>Add <code>TAR_*</code> error codes</li> <li>Raise <code>TAR_BAD_ARCHIVE</code> warning/error when there are no valid entries found in an archive</li> <li>do not treat ignored entries as an invalid archive</li> <li>drop support for node v4</li> <li>unpack: conditionally use a file mapping to write files on Windows</li> <li>Set more portable 'mode' value in portable mode</li> <li>Set <code>portable</code> gzip option in portable mode</li> </ul> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/isaacs/node-tar/commit/d99fce38ebf5175cce4c6623c53f4b17d6d31157"><code>d99fce3</code></a> 7.0.1</li> <li><a href="https://github.com/isaacs/node-tar/commit/af043922c09d12d28e21ecb463c7c9ff375cbf47"><code>af04392</code></a> Do not apply linkpath,global from global pax header</li> <li><a href="https://github.com/isaacs/node-tar/commit/b0fbdea4631f9f65f15786fb5333695a9164a536"><code>b0fbdea</code></a> 7.0.0</li> <li><a href="https://github.com/isaacs/node-tar/commit/957da7506cc594f24f54b884305718927194fb73"><code>957da75</code></a> remove old lib folder</li> <li><a href="https://github.com/isaacs/node-tar/commit/9a260c2dbaf9090c34872944393dfd854940c7c6"><code>9a260c2</code></a> test verifying <a href="https://redirect.github.com/isaacs/node-tar/issues/398">#398</a> is fixed</li> <li><a href="https://github.com/isaacs/node-tar/commit/2d89a4edc3dd76aef0bde3a9913cdb4f9c9d3b77"><code>2d89a4e</code></a> Properly handle long linkpath in PaxHeader</li> <li><a href="https://github.com/isaacs/node-tar/commit/314ec7e64245f86663c8ca20fad05ebb5a390d80"><code>314ec7e</code></a> list: close file even if no error thrown</li> <li><a href="https://github.com/isaacs/node-tar/commit/b3afdbb26496fe42110b3708b8f75c4ca4c853b7"><code>b3afdbb</code></a> unpack test: use modern tap features</li> <li><a href="https://github.com/isaacs/node-tar/commit/23304160811cceb1a1949d3915d5a2a818726ec6"><code>2330416</code></a> test: code style, prefer () to _ for empty fns</li> <li><a href="https://github.com/isaacs/node-tar/commit/ae9ce7ec2adb6300155cb6a46f8c9ea601330d81"><code>ae9ce7e</code></a> test: fix normalize-unicode coverage on linux</li> <li>Additional commits viewable in <a href="https://github.com/isaacs/node-tar/compare/v6.2.1...v7.0.1">compare view</a></li> </ul> </details> <br /> Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore <dependency name> major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore <dependency name> minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore <dependency name>` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore <dependency name>` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore <dependency name> <ignore condition>` will remove the ignore condition of the specified dependency and ignore conditions You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/tldraw/tldraw/network/alerts). </details> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Mime Čuvalo <mimecuvalo@gmail.com> Co-authored-by: Steve Ruiz <steveruizok@gmail.com>
2024-04-21 12:39:38 +00:00
out.write(Buffer.from(chunk.buffer))
}
out.end()
}
}
main().catch(async (err) => {
// don't notify discord on preview builds
if (env.TLDRAW_ENV !== 'preview') {
await discord.message(`${Discord.AT_TEAM_MENTION} Deploy failed: ${err.stack}`, {
always: true,
})
}
console.error(err)
process.exit(1)
})