From e7a2afeec02ceca009b34fbd118e6de3babaa6ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Sat, 9 May 2026 12:10:20 -0400 Subject: [PATCH] Add Cloudflare public cache publisher --- cloudflare/package.json | 2 + cloudflare/scripts/publish_public_cache.mjs | 154 ++++++++++++++++++++ 2 files changed, 156 insertions(+) create mode 100644 cloudflare/scripts/publish_public_cache.mjs diff --git a/cloudflare/package.json b/cloudflare/package.json index cc4fa70..d3b9b05 100644 --- a/cloudflare/package.json +++ b/cloudflare/package.json @@ -4,7 +4,9 @@ "private": true, "type": "module", "scripts": { + "build": "tsc -p tsconfig.json --outDir dist", "check": "tsc --noEmit -p tsconfig.json", + "public-cache:publish": "npm run build && node scripts/publish_public_cache.mjs", "test": "tsc -p tsconfig.test.json --outDir dist && node --test dist/tests/*.test.js" }, "devDependencies": { diff --git a/cloudflare/scripts/publish_public_cache.mjs b/cloudflare/scripts/publish_public_cache.mjs new file mode 100644 index 0000000..7252466 --- /dev/null +++ b/cloudflare/scripts/publish_public_cache.mjs @@ -0,0 +1,154 @@ +#!/usr/bin/env node +import { spawn } from "node:child_process"; +import { readFile, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join, resolve } from "node:path"; + +import { + parsePluginRegistryDocument, + pluginRegistryKvKey, +} from "../dist/src/plugin_registry.js"; +import { + parseReleaseManifestDocument, + releaseManifestKvKey, +} from "../dist/src/release_manifests.js"; +import { + parsePublicSigningKeysDocument, + publicSigningKeysKvKey, +} from "../dist/src/signing_keys.js"; + +const DOCUMENTS = [ + { + flag: "--signing-keys", + label: "public signing keys", + keyFor: publicSigningKeysKvKey, + parse: parsePublicSigningKeysDocument, + }, + { + flag: "--plugin-registry", + label: "plugin registry", + keyFor: pluginRegistryKvKey, + parse: parsePluginRegistryDocument, + }, + { + flag: "--release-manifest", + label: "release manifest", + keyFor: releaseManifestKvKey, + parse: parseReleaseManifestDocument, + }, +]; + +async function main() { + const options = parseArgs(process.argv.slice(2)); + const publications = DOCUMENTS.flatMap((document) => { + const path = options.documents.get(document.flag); + return path === undefined ? [] : [{ ...document, path }]; + }); + + if (publications.length === 0) { + throw new Error("Provide at least one public cache document path."); + } + + for (const publication of publications) { + await publishDocument(options.environment, publication); + } +} + +function parseArgs(args) { + const options = { + environment: undefined, + documents: new Map(), + }; + const flags = new Set(DOCUMENTS.map((document) => document.flag)); + + for (let index = 0; index < args.length; index += 1) { + const arg = args[index]; + if (arg === "--help" || arg === "-h") { + printUsage(); + process.exit(0); + } + if (arg === "--environment") { + options.environment = requiredValue(args, index, arg); + index += 1; + continue; + } + if (flags.has(arg)) { + options.documents.set(arg, requiredValue(args, index, arg)); + index += 1; + continue; + } + throw new Error(`Unknown argument: ${arg}`); + } + + if (options.environment === undefined) { + throw new Error("Missing required --environment value."); + } + + return options; +} + +function requiredValue(args, index, flag) { + const value = args[index + 1]; + if (value === undefined || value.startsWith("--")) { + throw new Error(`Missing value for ${flag}.`); + } + return value; +} + +async function publishDocument(environment, publication) { + const sourcePath = resolve(publication.path); + const raw = await readFile(sourcePath, "utf8"); + const document = publication.parse(raw); + const kvKey = publication.keyFor(environment); + const tempPath = join(tmpdir(), `ely-public-cache-${process.pid}-${Date.now()}.json`); + const normalized = `${JSON.stringify(document, null, 2)}\n`; + + await writeFile(tempPath, normalized, { mode: 0o600 }); + try { + await runWrangler([ + "kv", + "key", + "put", + kvKey, + "--binding", + "ELY_KV", + "--remote", + "--path", + tempPath, + ]); + console.log(`Published ${publication.label} to ${kvKey}`); + } finally { + await rm(tempPath, { force: true }); + } +} + +function runWrangler(args) { + return new Promise((resolveProcess, rejectProcess) => { + const command = process.platform === "win32" ? "npx.cmd" : "npx"; + const child = spawn(command, ["wrangler", ...args], { + stdio: "inherit", + }); + + child.on("error", rejectProcess); + child.on("exit", (code) => { + if (code === 0) { + resolveProcess(); + return; + } + rejectProcess(new Error(`wrangler exited with code ${code}`)); + }); + }); +} + +function printUsage() { + console.log(`Usage: + npm run public-cache:publish -- --environment production \\ + --signing-keys ./secure/signing-keys.json \\ + --plugin-registry ./secure/plugin-registry.json \\ + --release-manifest ./secure/release-manifest.json`); +} + +main().catch((error) => { + console.error(error instanceof Error ? error.message : String(error)); + process.exit(1); +});