Files
descrybe/scripts/setup.mjs
T
greeneclipse 8580c996c3 Initial commit of Descrybe v2 without local scratch artifacts.
Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
2026-08-09 22:47:43 +02:00

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");