import * as github from '@actions/github' import { appendFile } from 'fs/promises' import { join } from 'path' import { env } from 'process' import { exec } from './exec' export function getDeployInfo() { const githubPrNumber = process.env.GITHUB_REF?.match(/refs\/pull\/(\d+)\/merge/)?.[1] let previewId = process.env.TLDRAW_PREVIEW_ID if (!previewId && env.TLDRAW_ENV === 'preview') { if (githubPrNumber) { previewId = `pr-${githubPrNumber}` } else { throw new Error( 'If running preview deploys from outside of a PR action, TLDRAW_PREVIEW_ID env var must be set' ) } } const sha: string | undefined = // if the event is 'pull_request', github.context.sha is an ephemeral merge commit // while the actual commit we want to create the deployment for is the 'head' of the PR. github.context.eventName === 'pull_request' ? github.context.payload.pull_request?.head.sha : github.context.sha if (!sha) { throw new Error('Could not determine the SHA of the commit to deploy') } return { githubPrNumber, previewId, sha, } } // Creates a github 'deployment', which creates a 'View Deployment' button in the PR timeline. export async function createGithubDeployment( env: { GH_TOKEN: string; TLDRAW_ENV: string }, { app, deploymentUrl, inspectUrl, sha, }: { app: string; deploymentUrl: string; inspectUrl?: string; sha: string } ) { const client = github.getOctokit(env.GH_TOKEN) const deployment = await client.rest.repos.createDeployment({ owner: 'tldraw', repo: 'tldraw', ref: sha, payload: { web_url: deploymentUrl }, environment: `${app}-${env.TLDRAW_ENV}`, transient_environment: env.TLDRAW_ENV === 'preview', production_environment: env.TLDRAW_ENV === 'production', required_contexts: [], auto_merge: false, task: 'deploy', }) await client.rest.repos.createDeploymentStatus({ owner: 'tldraw', repo: 'tldraw', deployment_id: (deployment.data as any).id, state: 'success', environment_url: deploymentUrl, log_url: inspectUrl, }) } /** Deploy a worker to wrangler, returning the deploy ID */ export async function wranglerDeploy({ location, dryRun, env, vars, sentry, }: { location: string dryRun: boolean env: string vars: Record sentry?: { authToken: string project: string release?: string } }) { const varsArray = [] for (const [key, value] of Object.entries(vars)) { varsArray.push('--var', `${key}:${value}`) } const out = await exec( 'yarn', [ 'wrangler', 'deploy', dryRun ? '--dry-run' : null, '--env', env, '--outdir', '.wrangler/dist', ...varsArray, ], { pwd: location, env: { NODE_ENV: 'production', // wrangler needs CI=1 set to prevent it from trying to do interactive prompts CI: '1', }, } ) if (dryRun) return const versionMatch = out.match(/Current Version ID: (.+)/) if (!versionMatch) { throw new Error('Could not find the deploy ID in wrangler output') } const workerNameMatch = out.match(/Uploaded ([^ ]+)/) if (!workerNameMatch) { throw new Error('Could not find the worker name in wrangler output') } if (sentry) { const release = sentry.release ?? `${workerNameMatch[1]}.${versionMatch[1]}` const sentryEnv = { SENTRY_AUTH_TOKEN: sentry.authToken, SENTRY_ORG: 'tldraw', SENTRY_PROJECT: sentry.project, } // create a sentry release: await exec('yarn', ['run', '-T', 'sentry-cli', 'releases', 'new', release], { pwd: location, env: sentryEnv, }) // upload sourcemaps to the release: await exec( 'yarn', [ 'run', '-T', 'sentry-cli', 'releases', 'files', release, 'upload-sourcemaps', '.wrangler/dist', ], { pwd: location, env: sentryEnv, } ) } } export async function setWranglerPreviewConfig( location: string, { name, customDomain }: { name: string; customDomain?: string } ) { await appendFile( join(location, 'wrangler.toml'), ` [env.preview] name = "${name}" ${customDomain ? `routes = [ { pattern = "${customDomain}", custom_domain = true} ]` : ''} ` ) }