diff --git a/scripts/lib/cloudflare-probes.mjs b/scripts/lib/cloudflare-probes.mjs index d2e8015..a6d2506 100644 --- a/scripts/lib/cloudflare-probes.mjs +++ b/scripts/lib/cloudflare-probes.mjs @@ -26,7 +26,7 @@ */ import { execFileSync } from "node:child_process"; import { existsSync, readFileSync, readdirSync } from "node:fs"; -import path from "node:path"; +import path, { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; const MODULE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", ".."); @@ -56,6 +56,27 @@ function readNamespaceId() { return stripped.match(/kv_namespaces[\s\S]*?"id"\s*:\s*"([0-9a-f]+)"/)?.[1] ?? null; } +/** Read Cloudflare credentials from bot/.dev.vars (gitignored; D-15: the + * values are used for the probe's wrangler calls — never printed, never + * logged, never written anywhere). */ +function readDevVarsCredentials() { + const out = {}; + try { + const raw = readFileSync(resolve(dirname(WRANGLER_JSONC), ".dev.vars"), "utf8"); + for (const line of raw.split("\n")) { + const eq = line.indexOf("="); + if (eq <= 0 || line.trimStart().startsWith("#")) continue; + const key = line.slice(0, eq).trim(); + if (key === "CLOUDFLARE_API_TOKEN" || key === "CLOUDFLARE_ACCOUNT_ID") { + out[key] = line.slice(eq + 1).trim(); + } + } + } catch { + // no .dev.vars — the caller reports the missing-credentials class + } + return out; +} + /** Read the deployed Mini App origin from bot/wrangler.jsonc vars (public * configuration — never a secret). */ function readMiniAppUrl() { @@ -86,11 +107,20 @@ function wranglerAvailable() { export function runKvExpiryProbe(opts = {}) { const id = "kv-expiry"; const missing = []; - if (!process.env.CLOUDFLARE_API_TOKEN || !process.env.CLOUDFLARE_ACCOUNT_ID) missing.push("Cloudflare credentials"); + // Credentials: process.env first, then bot/.dev.vars (gitignored, D-15 — + // the launch-gate's credential home; never echoed, never committed). + const devVars = readDevVarsCredentials(); + const apiToken = process.env.CLOUDFLARE_API_TOKEN ?? devVars.CLOUDFLARE_API_TOKEN; + const accountId = process.env.CLOUDFLARE_ACCOUNT_ID ?? devVars.CLOUDFLARE_ACCOUNT_ID; + if (!apiToken || !accountId) missing.push("Cloudflare credentials (bot/.dev.vars or env)"); const namespaceId = opts.namespaceId ?? readNamespaceId(); if (!namespaceId) missing.push("KV namespace id (bot/wrangler.jsonc)"); if (!wranglerAvailable()) missing.push("wrangler CLI"); if (missing.length > 0) return notRun(id, `missing ${missing.join(" + ")}`); + if (!opts.envOverride) { // the recorded probes run through wrangler with the env set + process.env.CLOUDFLARE_API_TOKEN = apiToken; + process.env.CLOUDFLARE_ACCOUNT_ID = accountId; + } /** @type {Array<{name: string, expiration?: number, metadata?: unknown}>} */ const keys = [];