#!/usr/bin/env node /* * upload-road-graph.mjs — put the built road graph on R2 (D-01, fork path). * --------------------------------------------------------------------------- * A MAINTAINER-TIME REPO TOOL — NOT a runtime dependency; nothing in bot/src, * web/src, or routing/src imports it. It shells the wrangler CLI (already the * repo's deploy tool) so a fork needs zero new installs: * * npx wrangler r2 object put araharah-graphs/kl-roadgraph.v1.bin * --file routing/data/kl-roadgraph.v1.bin --remote * * THE FORK CONTRACT (docs/ROAD-GRAPH.md is the prose): your graph, YOUR * bucket. Create it first (`npx wrangler r2 bucket create araharah-graphs` * — or your own name, and point bot/wrangler.jsonc's ROAD_GRAPH binding at * it), build the binary with build-road-graph.mjs, then run this. The * Worker side reads the binding, never a bucket name — the name here and in * wrangler.jsonc must simply agree. * * PRE-FLIGHT: the binary must EXIST and clear the enforced 1 MB floor (the * 80 MB ceiling is build-road-graph.mjs's gate — this script re-checks both * cheaply so a stale artifact never uploads silently). * * Usage: * node routing/scripts/upload-road-graph.mjs * * Exit codes: 0 uploaded; 1 pre-flight failure or wrangler failure. */ import { execFileSync } from "node:child_process"; import { existsSync, statSync } from "node:fs"; import { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; const here = dirname(fileURLToPath(import.meta.url)); // routing/scripts const GRAPH_PATH = resolve(here, "..", "data", "kl-roadgraph.v1.bin"); /** Must agree with bot/wrangler.jsonc's ROAD_GRAPH binding bucket_name. */ const BUCKET = "araharah-graphs"; /** Must agree with worker-router.ts's GRAPH_KEY. */ const KEY = "kl-roadgraph.v1.bin"; /** The enforced floor — an at/below-floor file is a broken build, never an upload. */ const MIN_BINARY_BYTES = 1 * 1024 * 1024; const MAX_BINARY_BYTES = 80 * 1024 * 1024; function fail(message) { console.error(`upload-road-graph: ${message}`); process.exit(1); } if (!existsSync(GRAPH_PATH)) { fail(`no graph at ${GRAPH_PATH} — build it first: node routing/scripts/build-road-graph.mjs`); } const bytes = statSync(GRAPH_PATH).size; if (bytes <= MIN_BINARY_BYTES || bytes > MAX_BINARY_BYTES) { fail( `${GRAPH_PATH} is ${bytes} bytes — outside the enforced 1-80 MB envelope (D-01). ` + `Rebuild and heed the stats gate: node routing/scripts/build-road-graph.mjs`, ); } console.log(`uploading ${GRAPH_PATH} (${(bytes / (1024 * 1024)).toFixed(1)} MB) -> r2://${BUCKET}/${KEY}`); try { execFileSync( "npx", ["wrangler", "r2", "object", "put", `${BUCKET}/${KEY}`, "--file", GRAPH_PATH, "--remote", "--content-type", "application/octet-stream"], { stdio: "inherit" }, ); } catch (err) { fail( `wrangler failed (${err.status ?? "no status"}). If the bucket does not exist yet: ` + `npx wrangler r2 bucket create ${BUCKET} — and if R2 is not enabled on the account, ` + `activate it once in the Cloudflare dashboard (R2 -> Get started).`, ); } console.log(`uploaded r2://${BUCKET}/${KEY}`); console.log("next: npm run dev -w bot # /api/route loads it through the ROAD_GRAPH binding (memoized per isolate)"); console.log(`verify: npx wrangler r2 object get ${BUCKET}/${KEY} --file /tmp/opencode/graph-roundtrip.bin --remote && node routing/scripts/build-road-graph.mjs --check /tmp/opencode/graph-roundtrip.bin`); process.exit(0);