#!/usr/bin/env bun /** * Generates the list of hostnames / zones the plugin worker is routed on from * cloudflare_workers/plugin/wrangler.jsonc. The /updates edge cache purge uses * it to purge only the zones that can hold plugin cache entries. * * Run `bun run generate:plugin-route-hosts` after changing plugin routes; * tests/plugin-route-hosts.unit.test.ts fails while the file is stale. */ import { readFileSync, writeFileSync } from 'node:fs' import { dirname, resolve } from 'node:path' import { fileURLToPath } from 'node:url' const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..') export const WRANGLER_PATH = resolve(ROOT, 'cloudflare_workers/plugin/wrangler.jsonc') export const GENERATED_PATH = resolve(ROOT, 'supabase/functions/_backend/plugin_runtime/utils/pluginRouteHosts.generated.ts') /** Strips // and /* *\/ comments and trailing commas outside of strings. */ export function parseJsonc(text: string): unknown { let out = '' let inString = false for (let i = 0; i < text.length; i++) { const char = text[i] if (inString) { out += char if (char === '\\') { out += text[++i] ?? '' continue } if (char === '"') inString = false continue } if (char === '"') { inString = true out += char continue } if (char === '/' && text[i + 1] === '/') { while (i < text.length && text[i] !== '\n') i++ out += '\n' continue } if (char === '/' && text[i + 1] === '*') { i += 2 while (i < text.length && !(text[i] === '*' && text[i + 1] === '/')) i++ i++ continue } out += char } return JSON.parse(stripTrailingCommas(out)) } /** Drops commas before `}` / `]`, outside strings only (string values stay intact). */ function stripTrailingCommas(text: string) { let out = '' let inString = false for (let i = 0; i < text.length; i++) { const char = text[i] if (inString) { out += char if (char === '\\') { out += text[++i] ?? '' continue } if (char === '"') inString = false continue } if (char === '"') { inString = true out += char continue } if (char === ',') { let next = i + 1 while (next < text.length && /\s/.test(text[next])) next++ if (text[next] === '}' || text[next] === ']') continue } out += char } return out } interface Route { pattern?: string, zone_name?: string } export function collectPluginRouteHosts(config: unknown) { const hosts = new Set() const zoneNames = new Set() const envs = Object.values((config as { env?: Record }).env ?? {}) for (const env of envs) { for (const route of env.routes ?? []) { if (route.pattern) hosts.add(route.pattern.split('/')[0].toLowerCase()) if (route.zone_name) zoneNames.add(route.zone_name.toLowerCase()) } } return { hosts: [...hosts].sort(), zoneNames: [...zoneNames].sort() } } function renderList(values: string[]) { return `[\n${values.map(value => ` '${value}',\n`).join('')}]` } export function renderGenerated(config: unknown) { const { hosts, zoneNames } = collectPluginRouteHosts(config) return `// Generated by scripts/generate-plugin-route-hosts.ts from // cloudflare_workers/plugin/wrangler.jsonc. Do not edit by hand. /** Hostnames the plugin worker is routed on (custom domains and route patterns). */ export const PLUGIN_ROUTE_HOSTS: readonly string[] = ${renderList(hosts)} /** Zones named explicitly by plugin routes (custom hostnames resolve through them). */ export const PLUGIN_ROUTE_ZONE_NAMES: readonly string[] = ${renderList(zoneNames)} ` } if (import.meta.main) { writeFileSync(GENERATED_PATH, renderGenerated(parseJsonc(readFileSync(WRANGLER_PATH, 'utf8')))) console.log(`wrote ${GENERATED_PATH}`) }