scripts/verify-cloudflare-bindings.ts
Rama main · réplica de lectura del repositorio original.
import { execFileSync } from "node:child_process";
import { readFileSync } from "node:fs";
type WranglerConfig = {
name?: string;
main?: string;
compatibility_date?: string;
compatibility_flags?: string[];
observability?: { enabled?: boolean };
upload_source_maps?: boolean;
triggers?: { crons?: string[] };
send_email?: Array<{ name: string }>;
durable_objects?: { bindings?: Array<{ name: string; class_name: string }> };
r2_buckets?: Array<{ binding: string; bucket_name: string }>;
queues?: {
producers?: Array<{ binding: string; queue: string }>;
consumers?: Array<{ queue: string; dead_letter_queue?: string }>;
};
d1_databases?: Array<{ binding: string; database_name: string; database_id: string }>;
env?: Record<string, Partial<WranglerConfig>>;
};
type VerificationConfig = {
configEnv: "production" | "dev";
worker: string;
r2: string;
queue: string;
dlq: string;
d1: string;
d1Id: string;
emailSendingDomain: string;
routingDomain: string;
routingAddress: string;
doBinding: string;
r2Binding: string;
queueBinding: string;
d1Binding: string;
emailBinding: string;
};
function stripJsonc(input: string): string {
return input.replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, "");
}
function runWrangler(args: string[]): string {
return execFileSync("pnpm", ["wrangler", ...args], {
encoding: "utf8",
stdio: ["ignore", "pipe", "pipe"],
});
}
function assert(condition: unknown, message: string): asserts condition {
if (!condition) {
throw new Error(message);
}
}
function parseArgs(argv: string[]): Record<string, string> {
const args: Record<string, string> = {};
for (let i = 0; i < argv.length; i += 1) {
const arg = argv[i];
if (!arg?.startsWith("--")) continue;
const [rawKey, inlineValue] = arg.slice(2).split("=", 2);
if (!rawKey) continue;
if (inlineValue !== undefined) {
args[rawKey] = inlineValue;
continue;
}
const next = argv[i + 1];
if (!next || next.startsWith("--")) {
args[rawKey] = "true";
continue;
}
args[rawKey] = next;
i += 1;
}
return args;
}
function resolveString(
args: Record<string, string>,
argName: string,
envName: string,
fallback: string,
): string {
return args[argName] ?? process.env[envName] ?? fallback;
}
const args = parseArgs(process.argv.slice(2));
const configEnvArg = args.env ?? process.env.CF_VERIFY_ENV ?? "dev";
assert(
configEnvArg === "dev" || configEnvArg === "production",
`Invalid env ${configEnvArg}; expected "dev" or "production"`,
);
const defaultConfigByEnv: Record<
VerificationConfig["configEnv"],
Omit<VerificationConfig, "configEnv">
> = {
dev: {
worker: "reccado-dev",
r2: "inbox-mcp-raw-dev-eu",
queue: "inbox-mcp-inbound-dev",
dlq: "inbox-mcp-inbound-dlq-dev",
d1: "inbox-mcp-index-dev-eu",
// Placeholder — pass your real dev D1 id via CF_VERIFY_D1_ID (or --d1-id).
d1Id: "00000000-0000-0000-0000-000000000000",
emailSendingDomain: "mail.example.com",
routingDomain: "example.com",
routingAddress: "test@example.com",
doBinding: "MAILBOX_DO",
r2Binding: "MAIL_OBJECTS",
queueBinding: "INBOUND_EMAIL_QUEUE",
d1Binding: "INDEX_DB",
emailBinding: "EMAIL",
},
production: {
worker: "reccado",
r2: "inbox-mcp-raw",
queue: "inbox-mcp-inbound",
dlq: "inbox-mcp-inbound-dlq",
d1: "inbox-mcp-index",
d1Id: "<your-prod-d1-database-id>",
emailSendingDomain: "mail.example.com",
routingDomain: "example.com",
routingAddress: "test@example.com",
doBinding: "MAILBOX_DO",
r2Binding: "MAIL_OBJECTS",
queueBinding: "INBOUND_EMAIL_QUEUE",
d1Binding: "INDEX_DB",
emailBinding: "EMAIL",
},
};
const defaults = defaultConfigByEnv[configEnvArg];
const required: VerificationConfig = {
configEnv: configEnvArg,
worker: resolveString(args, "worker", "CF_VERIFY_WORKER", defaults.worker),
r2: resolveString(args, "r2", "CF_VERIFY_R2_BUCKET", defaults.r2),
queue: resolveString(args, "queue", "CF_VERIFY_QUEUE", defaults.queue),
dlq: resolveString(args, "dlq", "CF_VERIFY_DLQ", defaults.dlq),
d1: resolveString(args, "d1", "CF_VERIFY_D1_NAME", defaults.d1),
d1Id: resolveString(args, "d1-id", "CF_VERIFY_D1_ID", defaults.d1Id),
emailSendingDomain: resolveString(
args,
"email-sending-domain",
"CF_VERIFY_EMAIL_SENDING_DOMAIN",
defaults.emailSendingDomain,
),
routingDomain: resolveString(
args,
"routing-domain",
"CF_VERIFY_ROUTING_DOMAIN",
defaults.routingDomain,
),
routingAddress: resolveString(
args,
"routing-address",
"CF_VERIFY_ROUTING_ADDRESS",
defaults.routingAddress,
),
doBinding: resolveString(args, "do-binding", "CF_VERIFY_DO_BINDING", defaults.doBinding),
r2Binding: resolveString(args, "r2-binding", "CF_VERIFY_R2_BINDING", defaults.r2Binding),
queueBinding: resolveString(
args,
"queue-binding",
"CF_VERIFY_QUEUE_BINDING",
defaults.queueBinding,
),
d1Binding: resolveString(args, "d1-binding", "CF_VERIFY_D1_BINDING", defaults.d1Binding),
emailBinding: resolveString(
args,
"email-binding",
"CF_VERIFY_EMAIL_BINDING",
defaults.emailBinding,
),
};
// The repo ships a placeholder D1 id (public template). Fail early with an actionable message
// instead of querying Cloudflare for a UUID no real account can have.
// Catches the dev UUID-zero, empty, and the textual `<your-...-d1-database-id>` prod placeholder.
if (
required.d1Id === "00000000-0000-0000-0000-000000000000" ||
required.d1Id === "" ||
/^<.*>$/.test(required.d1Id)
) {
console.error(
"verify:cf: INDEX_DB database_id is a placeholder. Pass your real id via CF_VERIFY_D1_ID " +
"(or --d1-id), e.g. CF_VERIFY_D1_ID=<uuid> pnpm verify:cf.",
);
process.exit(1);
}
const config = JSON.parse(stripJsonc(readFileSync("wrangler.jsonc", "utf8"))) as WranglerConfig;
const envConfig = required.configEnv === "dev" ? (config.env?.dev ?? {}) : {};
const merged: WranglerConfig = { ...config, ...envConfig };
assert(config.main === "src/server.ts", "wrangler main must be src/server.ts");
assert(config.compatibility_date === "2026-06-30", "compatibility_date mismatch");
assert(config.compatibility_flags?.includes("nodejs_compat"), "nodejs_compat flag missing");
assert(config.observability?.enabled === true, "observability.enabled missing");
if (required.configEnv === "dev") {
assert(envConfig.name === required.worker, "env.dev.name mismatch");
} else {
assert(config.name === required.worker, "top-level worker name mismatch");
}
assert(merged.triggers?.crons?.length, "Cron trigger missing");
assert(
merged.send_email?.some((binding) => binding.name === required.emailBinding),
"send_email binding missing",
);
assert(
merged.durable_objects?.bindings?.some(
(binding) =>
binding.name === required.doBinding && binding.class_name === "MailboxDurableObject",
),
"Durable Object binding missing",
);
assert(
merged.r2_buckets?.some(
(bucket) => bucket.binding === required.r2Binding && bucket.bucket_name === required.r2,
),
"R2 binding missing",
);
assert(
merged.queues?.producers?.some(
(producer) => producer.binding === required.queueBinding && producer.queue === required.queue,
),
"Queue producer binding missing",
);
assert(
merged.queues?.consumers?.some(
(consumer) => consumer.queue === required.queue && consumer.dead_letter_queue === required.dlq,
),
"Queue consumer/DLQ binding missing",
);
assert(
merged.d1_databases?.some(
(db) =>
db.binding === required.d1Binding &&
db.database_name === required.d1 &&
db.database_id === required.d1Id,
),
"D1 binding missing",
);
const buckets = runWrangler(["r2", "bucket", "list"]);
assert(buckets.includes(required.r2), `R2 bucket ${required.r2} missing`);
const queues = runWrangler(["queues", "list"]);
assert(queues.includes(required.queue), `Queue ${required.queue} missing`);
assert(queues.includes(required.dlq), `DLQ ${required.dlq} missing`);
const d1s = runWrangler(["d1", "list"]);
assert(d1s.includes(required.d1), `D1 ${required.d1} missing`);
assert(d1s.includes(required.d1Id), `D1 id ${required.d1Id} missing`);
const emailSending = runWrangler(["email", "sending", "list"]);
assert(emailSending.includes(required.emailSendingDomain), "Email Sending domain missing");
assert(emailSending.includes("yes"), "Email Sending domain is not enabled");
const routingRules = runWrangler(["email", "routing", "rules", "list", required.routingDomain]);
assert(routingRules.includes(required.routingAddress), "Email Routing test alias missing");
assert(routingRules.includes(required.worker), "Email Routing worker action missing");
console.log(
JSON.stringify(
{
ok: true,
env: required.configEnv,
worker: required.worker,
resources: {
r2: required.r2,
queue: required.queue,
dlq: required.dlq,
d1: required.d1,
d1Id: required.d1Id,
emailSendingDomain: required.emailSendingDomain,
routingDomain: required.routingDomain,
routingAddress: required.routingAddress,
cron: merged.triggers?.crons,
emailBinding: required.emailBinding,
},
},
null,
2,
),
);