265 lines
7.9 KiB
JavaScript
265 lines
7.9 KiB
JavaScript
import { spawnSync } from "node:child_process";
|
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
import { resolve } from "node:path";
|
|
|
|
const PLACEHOLDER_D1_ID = "00000000-0000-0000-0000-000000000000";
|
|
const UUID_PATTERN =
|
|
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
|
|
const wranglerArgs = process.argv.slice(2);
|
|
if (wranglerArgs.length === 0) {
|
|
console.error("Usage: bun scripts/run-wrangler.mjs <wrangler args...>");
|
|
process.exit(1);
|
|
}
|
|
|
|
const requestedInstance = process.env.EDGE_EVER_INSTANCE;
|
|
|
|
const loadLocalEnv = () => {
|
|
const envPath = resolve(".env.local");
|
|
if (!existsSync(envPath)) {
|
|
return;
|
|
}
|
|
|
|
const lines = readFileSync(envPath, "utf8").split(/\r?\n/);
|
|
for (const line of lines) {
|
|
const trimmed = line.trim();
|
|
if (!trimmed || trimmed.startsWith("#") || !trimmed.includes("=")) {
|
|
continue;
|
|
}
|
|
|
|
const separatorIndex = trimmed.indexOf("=");
|
|
const key = trimmed.slice(0, separatorIndex).trim();
|
|
let value = trimmed.slice(separatorIndex + 1).trim();
|
|
if (
|
|
(value.startsWith('"') && value.endsWith('"')) ||
|
|
(value.startsWith("'") && value.endsWith("'"))
|
|
) {
|
|
value = value.slice(1, -1);
|
|
}
|
|
|
|
// Bun expands $ references while auto-loading .env files. Values written
|
|
// by the deployment script escape literal dollars as \$.
|
|
value = value.replace(/\\\$/g, "$");
|
|
|
|
if (key) {
|
|
process.env[key] = value;
|
|
}
|
|
}
|
|
};
|
|
|
|
loadLocalEnv();
|
|
|
|
// An instance selected for this command must take precedence over the default
|
|
// stored in .env.local. Bun loads .env.local before this script starts, so
|
|
// capture the effective process value before the explicit reload above.
|
|
if (requestedInstance !== undefined) {
|
|
process.env.EDGE_EVER_INSTANCE = requestedInstance;
|
|
}
|
|
|
|
const baseConfigPath = resolve(process.env.WRANGLER_CONFIG ?? "wrangler.toml");
|
|
const instance = process.env.EDGE_EVER_INSTANCE?.trim();
|
|
const instanceKey = instance?.replace(/[^a-zA-Z0-9]/g, "_").toUpperCase();
|
|
const generatedConfigPath = resolve(
|
|
instanceKey
|
|
? `.wrangler.generated.${instanceKey.toLowerCase()}.toml`
|
|
: ".wrangler.generated.toml",
|
|
);
|
|
const generatedSecretsPath = resolve(
|
|
instanceKey
|
|
? `.env.wrangler.generated.${instanceKey.toLowerCase()}.secrets`
|
|
: ".env.wrangler.generated.secrets",
|
|
);
|
|
let config = readFileSync(baseConfigPath, "utf8");
|
|
let changed = false;
|
|
|
|
const replaceTomlValue = (source, key, value) => {
|
|
if (!value) {
|
|
return source;
|
|
}
|
|
|
|
const pattern = new RegExp(`(^${key}\\s*=\\s*")[^"]*(")`, "m");
|
|
if (!pattern.test(source)) {
|
|
throw new Error(`Cannot find ${key} in ${baseConfigPath}`);
|
|
}
|
|
|
|
changed = true;
|
|
return source.replace(pattern, `$1${value}$2`);
|
|
};
|
|
|
|
const tomlString = (value) => JSON.stringify(value);
|
|
|
|
const envValue = (name) => {
|
|
const scopedName = instanceKey ? `EDGE_EVER_${instanceKey}_${name}` : undefined;
|
|
return (scopedName ? process.env[scopedName] : undefined)?.trim()
|
|
|| process.env[`EDGE_EVER_${name}`]?.trim();
|
|
};
|
|
|
|
const workerName = envValue("WORKER_NAME");
|
|
if (workerName) {
|
|
config = replaceTomlValue(config, "name", workerName);
|
|
}
|
|
|
|
const workersDev = envValue("WORKERS_DEV");
|
|
if (workersDev) {
|
|
const normalized = workersDev.toLowerCase();
|
|
if (!["true", "false"].includes(normalized)) {
|
|
throw new Error("EDGE_EVER_WORKERS_DEV must be true or false.");
|
|
}
|
|
|
|
const pattern = /^workers_dev\s*=\s*(true|false)/m;
|
|
if (!pattern.test(config)) {
|
|
throw new Error(`Cannot find workers_dev in ${baseConfigPath}`);
|
|
}
|
|
|
|
changed = true;
|
|
config = config.replace(pattern, `workers_dev = ${normalized}`);
|
|
}
|
|
|
|
const d1DatabaseId = envValue("D1_DATABASE_ID");
|
|
if (d1DatabaseId) {
|
|
if (!UUID_PATTERN.test(d1DatabaseId)) {
|
|
throw new Error("EDGE_EVER_D1_DATABASE_ID must be a Cloudflare D1 UUID.");
|
|
}
|
|
|
|
config = replaceTomlValue(config, "database_id", d1DatabaseId);
|
|
}
|
|
|
|
config = replaceTomlValue(config, "database_name", envValue("D1_DATABASE_NAME"));
|
|
config = replaceTomlValue(config, "bucket_name", envValue("R2_BUCKET_NAME"));
|
|
config = replaceTomlValue(
|
|
config,
|
|
"preview_bucket_name",
|
|
envValue("R2_PREVIEW_BUCKET_NAME"),
|
|
);
|
|
|
|
const runtimeVars = {
|
|
EDGE_EVER_AUTH_USERNAME: envValue("AUTH_USERNAME"),
|
|
EDGE_EVER_SESSION_TTL_DAYS: envValue("SESSION_TTL_DAYS"),
|
|
EDGE_EVER_R2_BUCKET_NAME: envValue("R2_BUCKET_NAME"),
|
|
EDGE_EVER_DEMO_MODE: envValue("DEMO_MODE"),
|
|
EDGE_EVER_LOCAL_DEMO_SEED: envValue("LOCAL_DEMO_SEED"),
|
|
};
|
|
const runtimeVarLines = Object.entries(runtimeVars)
|
|
.filter(([, value]) => Boolean(value))
|
|
.map(([key, value]) => `${key} = ${tomlString(value)}`);
|
|
|
|
if (runtimeVarLines.length > 0) {
|
|
changed = true;
|
|
config = `${config.trimEnd()}
|
|
|
|
[vars]
|
|
${runtimeVarLines.join("\n")}
|
|
`;
|
|
}
|
|
|
|
const demoMode = envValue("DEMO_MODE")?.toLowerCase();
|
|
if (demoMode && !["true", "false"].includes(demoMode)) {
|
|
throw new Error("EDGE_EVER_DEMO_MODE must be true or false.");
|
|
}
|
|
|
|
const localDemoSeed = envValue("LOCAL_DEMO_SEED")?.toLowerCase();
|
|
if (localDemoSeed && !["true", "false"].includes(localDemoSeed)) {
|
|
throw new Error("EDGE_EVER_LOCAL_DEMO_SEED must be true or false.");
|
|
}
|
|
|
|
if (demoMode === "true") {
|
|
const demoResetCron = envValue("DEMO_RESET_CRON") || "0 19 * * *";
|
|
changed = true;
|
|
config = `${config.trimEnd()}
|
|
|
|
[triggers]
|
|
crons = [${tomlString(demoResetCron)}]
|
|
`;
|
|
}
|
|
|
|
const customDomain = envValue("CUSTOM_DOMAIN");
|
|
const routePattern = envValue("ROUTE_PATTERN") || customDomain;
|
|
if (routePattern) {
|
|
changed = true;
|
|
config = `${config.trimEnd()}
|
|
|
|
[[routes]]
|
|
pattern = "${routePattern}"
|
|
custom_domain = ${customDomain ? "true" : "false"}
|
|
`;
|
|
}
|
|
|
|
const isRemoteCommand =
|
|
wranglerArgs.includes("deploy") || wranglerArgs.includes("--remote");
|
|
if (isRemoteCommand && config.includes(`database_id = "${PLACEHOLDER_D1_ID}"`)) {
|
|
console.error(
|
|
[
|
|
"Missing Cloudflare D1 database id.",
|
|
instanceKey
|
|
? `Set EDGE_EVER_${instanceKey}_D1_DATABASE_ID or EDGE_EVER_D1_DATABASE_ID,`
|
|
: "Set EDGE_EVER_D1_DATABASE_ID,",
|
|
"or replace the database_id placeholder in wrangler.toml / WRANGLER_CONFIG.",
|
|
].join(" "),
|
|
);
|
|
process.exit(1);
|
|
}
|
|
|
|
const configPath = changed ? generatedConfigPath : baseConfigPath;
|
|
if (changed) {
|
|
writeFileSync(generatedConfigPath, config);
|
|
}
|
|
|
|
const isDeployCommand = wranglerArgs.includes("deploy");
|
|
const hasSecretsFileArg = wranglerArgs.some((arg) => arg === "--secrets-file" || arg.startsWith("--secrets-file="));
|
|
const authPassword = envValue("AUTH_PASSWORD");
|
|
const authPasswordHash = envValue("AUTH_PASSWORD_HASH");
|
|
const authSecrets = {
|
|
...(authPassword ? { EDGE_EVER_AUTH_PASSWORD: authPassword } : {}),
|
|
...(authPasswordHash ? { EDGE_EVER_AUTH_PASSWORD_HASH: authPasswordHash } : {}),
|
|
};
|
|
const finalWranglerArgs = [...wranglerArgs];
|
|
|
|
if (isDeployCommand && Object.keys(authSecrets).length > 0 && !hasSecretsFileArg) {
|
|
writeFileSync(generatedSecretsPath, `${JSON.stringify(authSecrets, null, 2)}\n`);
|
|
finalWranglerArgs.push("--secrets-file", generatedSecretsPath);
|
|
}
|
|
|
|
const localWrangler = resolve(
|
|
"node_modules",
|
|
".bin",
|
|
process.platform === "win32" ? "wrangler.cmd" : "wrangler",
|
|
);
|
|
const executable = existsSync(localWrangler)
|
|
? localWrangler
|
|
: process.platform === "win32"
|
|
? "wrangler.cmd"
|
|
: "wrangler";
|
|
const result = spawnSync(executable, ["--config", configPath, ...finalWranglerArgs], {
|
|
stdio: "inherit",
|
|
shell: process.platform === "win32",
|
|
});
|
|
|
|
if (result.status === 0 && isDeployCommand) {
|
|
for (const [secretName, secretValue] of Object.entries(authSecrets)) {
|
|
const secretResult = spawnSync(
|
|
executable,
|
|
["--config", configPath, "secret", "put", secretName],
|
|
{
|
|
input: secretValue,
|
|
stdio: ["pipe", "inherit", "inherit"],
|
|
shell: process.platform === "win32",
|
|
},
|
|
);
|
|
|
|
if (secretResult.error) {
|
|
throw secretResult.error;
|
|
}
|
|
|
|
if (secretResult.status !== 0) {
|
|
process.exit(secretResult.status ?? 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (result.error) {
|
|
console.error(result.error.message);
|
|
process.exit(1);
|
|
}
|
|
|
|
process.exit(result.status ?? 1);
|