#!/usr/bin/env node // Build the self-contained Cloudflare worker artifact. // // The output is what the Workers script-upload API takes: one module file plus // a manifest naming the compatibility settings, the bindings the module // expects, and the migration that creates the Durable Object class. A deploy // tool uploads these directly; it needs no wrangler and no checkout. // // node scripts/build-worker-bundle.mjs [outdir] // // Default outdir: dist/worker // // VERSION_SUFFIX (e.g. "-canary.ab12cd3") appends to the package version. A // canary build carries the suffix in its running version, not only in the // manifest: it reaches /xrpc/_health through core's VERSION constant, which // is what the account page and the update page compare against. The build // stamps the constant in the source, bundles, and restores the source. import { spawnSync } from 'node:child_process'; import { mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; import { dirname, join, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..'); const outdir = resolve(process.argv[2] ?? join(repoRoot, 'dist', 'worker')); const suffix = process.env.VERSION_SUFFIX ?? ''; const { version: packageVersion } = JSON.parse( readFileSync(join(repoRoot, 'packages/cloudflare/package.json'), 'utf8'), ); const version = `${packageVersion}${suffix}`; rmSync(outdir, { recursive: true, force: true }); mkdirSync(outdir, { recursive: true }); // The same constant scripts/sync-version.js maintains. const coreSource = join(repoRoot, 'packages/core/src/pds.js'); const versionPattern = /^const VERSION = '[^']*';$/m; let originalSource = null; if (suffix) { originalSource = readFileSync(coreSource, 'utf8'); if (!versionPattern.test(originalSource)) { throw new Error( 'no VERSION constant in packages/core/src/pds.js to stamp the suffix into', ); } writeFileSync( coreSource, originalSource.replace(versionPattern, `const VERSION = '${version}';`), ); } // Wrangler's dry run bundles exactly what deploy would upload. let result; try { result = spawnSync( 'npx', ['wrangler', 'deploy', '--dry-run', `--outdir=${outdir}`], { cwd: repoRoot, stdio: 'inherit' }, ); } finally { if (originalSource !== null) writeFileSync(coreSource, originalSource); } if (result.status !== 0) { process.exit(result.status ?? 1); } // The README wrangler writes into the outdir describes wrangler, not the // artifact. rmSync(join(outdir, 'README.md'), { force: true }); const wranglerToml = readFileSync(join(repoRoot, 'wrangler.toml'), 'utf8'); const compatibilityDate = wranglerToml.match( /compatibility_date\s*=\s*"([^"]+)"/, )?.[1]; if (!compatibilityDate) { throw new Error('compatibility_date not found in wrangler.toml'); } const manifest = { version, mainModule: 'index.js', compatibilityDate, compatibilityFlags: ['nodejs_compat'], durableObjects: [{ name: 'PDS', className: 'PDSDurableObject' }], // The migration for a first deploy. An existing worker keeps its migration // history; this tag applies only when the class does not exist yet. migrations: { new_tag: 'v1', new_sqlite_classes: ['PDSDurableObject'] }, r2Buckets: [{ binding: 'BLOBS' }], requiredVars: ['HOSTNAME'], requiredSecrets: ['JWT_SECRET', 'PDS_PASSWORD'], }; writeFileSync( join(outdir, 'manifest.json'), `${JSON.stringify(manifest, null, 2)}\n`, ); console.log(`\nArtifact in ${outdir}:`); console.log(' index.js the worker module'); console.log(' index.js.map its source map (not uploaded)'); console.log(' manifest.json compatibility, bindings, migration');