123 lines
3.6 KiB
JavaScript
123 lines
3.6 KiB
JavaScript
#!/usr/bin/env node
|
|||
|
|
/**
|
||
|
|
* One-shot local bootstrap (any OS):
|
||
|
|
* 1. Copy .env.example → .env if missing
|
||
|
|
* 2. Fill empty TOKEN_SIGNING_SECRET / APP_ENCRYPTION_KEY (local only)
|
||
|
|
* 3. docker compose up -d (Postgres)
|
||
|
|
* 4. wait for healthy DB
|
||
|
|
* 5. goose migrate + sqlc
|
||
|
|
*
|
||
|
|
* Usage: node scripts/setup.mjs
|
||
|
|
* Then: npm install && npm run dev
|
||
|
|
*/
|
||
|
|
import { spawnSync } from "node:child_process";
|
||
|
|
import crypto from "node:crypto";
|
||
|
|
import fs from "node:fs";
|
||
|
|
import path from "node:path";
|
||
|
|
import {
|
||
|
|
dotenvPath,
|
||
|
|
parseEnvFile,
|
||
|
|
readRootEnvFile,
|
||
|
|
repoRoot,
|
||
|
|
writeRootEnvFile,
|
||
|
|
} from "./root-env.mjs";
|
||
|
|
|
||
|
|
function requireCmd(bin, hint, versionArgs = ["--version"]) {
|
||
|
|
const r = spawnSync(bin, versionArgs, {
|
||
|
|
encoding: "utf8",
|
||
|
|
shell: process.platform === "win32",
|
||
|
|
});
|
||
|
|
if (r.error || r.status !== 0) {
|
||
|
|
console.error(`[setup] missing prerequisite: ${bin}`);
|
||
|
|
if (hint) console.error(` ${hint}`);
|
||
|
|
process.exit(1);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function runNodeScript(rel) {
|
||
|
|
const script = path.join(repoRoot, rel);
|
||
|
|
const r = spawnSync(process.execPath, [script], {
|
||
|
|
cwd: repoRoot,
|
||
|
|
stdio: "inherit",
|
||
|
|
env: process.env,
|
||
|
|
});
|
||
|
|
if (r.status !== 0) process.exit(r.status ?? 1);
|
||
|
|
}
|
||
|
|
|
||
|
|
function runDockerCompose(args) {
|
||
|
|
const r = spawnSync("docker", ["compose", ...args], {
|
||
|
|
cwd: repoRoot,
|
||
|
|
stdio: "inherit",
|
||
|
|
shell: process.platform === "win32",
|
||
|
|
});
|
||
|
|
if (r.status !== 0) process.exit(r.status ?? 1);
|
||
|
|
}
|
||
|
|
|
||
|
|
function ensureEnvFile() {
|
||
|
|
const dest = dotenvPath();
|
||
|
|
if (fs.existsSync(dest)) {
|
||
|
|
console.log(`[setup] using existing ${path.relative(repoRoot, dest)}`);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const example = path.join(repoRoot, ".env.example");
|
||
|
|
if (!fs.existsSync(example)) {
|
||
|
|
console.error("[setup] .env.example missing");
|
||
|
|
process.exit(1);
|
||
|
|
}
|
||
|
|
fs.copyFileSync(example, dest);
|
||
|
|
console.log(`[setup] created ${path.relative(repoRoot, dest)} from .env.example`);
|
||
|
|
}
|
||
|
|
|
||
|
|
function fillEmptySecrets() {
|
||
|
|
const text = readRootEnvFile();
|
||
|
|
if (text == null) return;
|
||
|
|
const parsed = parseEnvFile(text);
|
||
|
|
const need = [];
|
||
|
|
for (const key of ["TOKEN_SIGNING_SECRET", "APP_ENCRYPTION_KEY"]) {
|
||
|
|
if (!parsed[key] || !String(parsed[key]).trim()) need.push(key);
|
||
|
|
}
|
||
|
|
if (need.length === 0) return;
|
||
|
|
|
||
|
|
let next = text;
|
||
|
|
for (const key of need) {
|
||
|
|
const secret = crypto.randomBytes(32).toString("hex");
|
||
|
|
const lineRe = new RegExp(`^(\\s*${key}\\s*=\\s*).*$`, "m");
|
||
|
|
if (lineRe.test(next)) {
|
||
|
|
next = next.replace(lineRe, `$1${secret}`);
|
||
|
|
} else {
|
||
|
|
next = `${next.trimEnd()}\n${key}=${secret}\n`;
|
||
|
|
}
|
||
|
|
console.log(`[setup] generated local ${key} (not committed)`);
|
||
|
|
}
|
||
|
|
writeRootEnvFile(next);
|
||
|
|
}
|
||
|
|
|
||
|
|
console.log("==> Descrybe local setup");
|
||
|
|
requireCmd("node", "Install Node.js 20+ from https://nodejs.org/");
|
||
|
|
// Go uses `go version`, not `go --version`.
|
||
|
|
requireCmd("go", "Install Go 1.25+ from https://go.dev/dl/", ["version"]);
|
||
|
|
requireCmd("docker", "Install Docker Desktop / Docker Engine with Compose v2");
|
||
|
|
|
||
|
|
ensureEnvFile();
|
||
|
|
fillEmptySecrets();
|
||
|
|
|
||
|
|
console.log("==> docker compose up -d (Postgres on host :5433)");
|
||
|
|
runDockerCompose(["up", "-d"]);
|
||
|
|
|
||
|
|
console.log("==> wait for Postgres");
|
||
|
|
runNodeScript("scripts/wait-postgres.mjs");
|
||
|
|
|
||
|
|
console.log("==> migrate");
|
||
|
|
runNodeScript("scripts/migrate.mjs");
|
||
|
|
|
||
|
|
console.log("");
|
||
|
|
console.log("Setup complete.");
|
||
|
|
console.log("Next:");
|
||
|
|
console.log(" npm install");
|
||
|
|
console.log(" npm run dev # API :28471 + web :28472 + worker");
|
||
|
|
console.log(" npm run seed # optional demo login");
|
||
|
|
console.log(" npm run health # /healthz + /readyz (needs worker)");
|
||
|
|
console.log("");
|
||
|
|
console.log("Web: http://localhost:28472");
|
||
|
|
console.log("API: http://localhost:28471");
|