Files
edgeever/scripts/verify-deployment.mjs
T

105 lines
3.4 KiB
JavaScript

import { spawnSync } from "node:child_process";
import { resolve } from "node:path";
export const REQUIRED_TABLES = [
"users",
"sessions",
"workspaces",
"workspace_members",
"notebooks",
"memos",
"mobile_sync_changes",
];
export const buildSchemaVerificationSql = () =>
`SELECT name FROM sqlite_master WHERE type = 'table' AND name IN (${REQUIRED_TABLES.map((name) => `'${name}'`).join(", ")}) ORDER BY name`;
export const parseJsonOutput = (output, description) => {
if (!output.trim()) {
throw new Error(`Wrangler returned no JSON while checking ${description}.`);
}
try {
return JSON.parse(output);
} catch (error) {
throw new Error(
`Wrangler returned invalid JSON while checking ${description}: ${error instanceof Error ? error.message : String(error)}`,
);
}
};
export const parseD1Rows = (output) => {
// Some Wrangler versions emit an empty stdout for a successful D1 query
// with zero rows instead of the documented empty JSON result.
if (!output.trim()) {
return [];
}
const parsed = parseJsonOutput(output, "the remote D1 schema");
const results = Array.isArray(parsed) ? parsed : [parsed];
return results.flatMap((result) => result?.results ?? []);
};
export const parseSecretNames = (output) => {
const parsed = parseJsonOutput(output, "Worker Secrets");
const secrets = Array.isArray(parsed) ? parsed : parsed?.secrets ?? parsed?.result ?? [];
return new Set(secrets.map((secret) => secret?.name).filter(Boolean));
};
const runWrangler = (args, options = {}) => {
const result = spawnSync(
process.execPath,
[resolve("scripts/run-wrangler.mjs"), ...args],
{ encoding: "utf8", env: process.env },
);
if (result.status !== 0) {
if (result.stdout) process.stdout.write(result.stdout);
if (result.stderr) process.stderr.write(result.stderr);
throw new Error(`Wrangler verification command exited with status ${result.status ?? 1}.`);
}
if (!result.stdout.trim() && !options.allowEmptyOutput) {
if (result.stderr) process.stderr.write(result.stderr);
throw new Error(
"Wrangler completed without returning verification data. Check the D1 binding and deployment credentials, then retry.",
);
}
return result.stdout;
};
const main = () => {
const schemaOutput = runWrangler([
"d1",
"execute",
"DB",
"--remote",
"--command",
buildSchemaVerificationSql(),
"--json",
], { allowEmptyOutput: true });
const tableNames = new Set(parseD1Rows(schemaOutput).map((row) => row.name));
const missingTables = REQUIRED_TABLES.filter((table) => !tableNames.has(table));
if (missingTables.length > 0) {
throw new Error(
`Remote D1 migrations are incomplete; missing tables: ${missingTables.join(", ")}. Run bun run db:migrate:remote and retry deployment.`,
);
}
console.log(`[ok] remote D1 schema: ${REQUIRED_TABLES.length} required tables`);
const secretNames = parseSecretNames(runWrangler(["secret", "list", "--format", "json"]));
if (!secretNames.has("EDGE_EVER_AUTH_PASSWORD") && !secretNames.has("EDGE_EVER_AUTH_PASSWORD_HASH")) {
throw new Error("The deployed Worker has no EdgeEver authentication Secret.");
}
console.log("[ok] Worker authentication Secret is deployed");
};
if (import.meta.main) {
try {
main();
} catch (error) {
console.error(`[fail] ${error instanceof Error ? error.message : String(error)}`);
process.exit(1);
}
}