145 lines
4.2 KiB
JavaScript
145 lines
4.2 KiB
JavaScript
import { spawn } from "node:child_process";
|
|
|
|
const composeArgs = ["compose", "-f", "apps/workshop/docker-compose.yml"];
|
|
const pocketBaseUrl = "http://127.0.0.1:8090";
|
|
const requiredCollections = ["submissions", "workshop_listings"];
|
|
const isWindows = process.platform === "win32";
|
|
const children = new Set();
|
|
let shuttingDown = false;
|
|
|
|
try {
|
|
process.loadEnvFile(".env");
|
|
} catch (error) {
|
|
if (error?.code !== "ENOENT") throw error;
|
|
// The Workshop has safe local defaults when no .env file exists. GitHub OAuth remains optional.
|
|
}
|
|
|
|
function start(command, args, options = {}) {
|
|
const child = spawn(command, args, {
|
|
stdio: "inherit",
|
|
shell: isWindows,
|
|
...options,
|
|
});
|
|
child.once("error", (error) => {
|
|
child.spawnError = error;
|
|
});
|
|
children.add(child);
|
|
child.once("close", () => children.delete(child));
|
|
return child;
|
|
}
|
|
|
|
function run(command, args, options = {}) {
|
|
return new Promise((resolve, reject) => {
|
|
const child = spawn(command, args, {
|
|
stdio: "inherit",
|
|
shell: isWindows,
|
|
...options,
|
|
});
|
|
child.once("error", reject);
|
|
child.once("exit", (code) => {
|
|
if (code === 0) resolve();
|
|
else reject(new Error(`${command} exited with code ${code ?? "unknown"}`));
|
|
});
|
|
});
|
|
}
|
|
|
|
async function endpointIsReady(path) {
|
|
try {
|
|
const response = await fetch(`${pocketBaseUrl}${path}`, {
|
|
signal: AbortSignal.timeout(2_000),
|
|
});
|
|
return response.ok;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async function waitForWorkshop(compose) {
|
|
const deadline = Date.now() + 120_000;
|
|
while (Date.now() < deadline) {
|
|
if (compose.spawnError) throw compose.spawnError;
|
|
if (compose.exitCode !== null) {
|
|
throw new Error(`PocketBase stopped before it became ready (exit ${compose.exitCode})`);
|
|
}
|
|
|
|
const checks = [
|
|
endpointIsReady("/api/health"),
|
|
...requiredCollections.map((collection) =>
|
|
endpointIsReady(`/api/collections/${collection}/records?page=1&perPage=1`),
|
|
),
|
|
];
|
|
if ((await Promise.all(checks)).every(Boolean)) return;
|
|
await new Promise((resolve) => setTimeout(resolve, 1_000));
|
|
}
|
|
|
|
throw new Error("PocketBase did not expose its health endpoint and required collections within 120 seconds");
|
|
}
|
|
|
|
async function shutdown(exitCode = 0) {
|
|
if (shuttingDown) return;
|
|
shuttingDown = true;
|
|
|
|
for (const child of children) {
|
|
if (child.exitCode === null) child.kill("SIGTERM");
|
|
}
|
|
|
|
try {
|
|
await run("docker", [...composeArgs, "stop", "workshop"], {
|
|
env: workshopEnvironment,
|
|
stdio: "ignore",
|
|
});
|
|
} catch (error) {
|
|
console.error("Could not stop the Workshop container cleanly:", error);
|
|
exitCode ||= 1;
|
|
}
|
|
|
|
process.exitCode = exitCode;
|
|
}
|
|
|
|
const workshopEnvironment = {
|
|
...process.env,
|
|
PB_EXPOSE_PORT: "8090",
|
|
PB_ALLOWED_ORIGINS: process.env.PB_ALLOWED_ORIGINS ?? "*",
|
|
WORKSHOP_PUBLIC_ORIGIN: process.env.WORKSHOP_PUBLIC_ORIGIN ?? "http://127.0.0.1:3003",
|
|
WORKSHOP_WEB_URL: process.env.WORKSHOP_WEB_URL ?? "http://127.0.0.1:3003/workshop",
|
|
};
|
|
|
|
for (const signal of ["SIGINT", "SIGTERM"]) {
|
|
process.once(signal, () => void shutdown());
|
|
}
|
|
|
|
const compose = start("docker", [...composeArgs, "up", "--build", "workshop"], {
|
|
env: workshopEnvironment,
|
|
});
|
|
|
|
try {
|
|
console.log("Waiting for PocketBase health and Workshop collections on http://127.0.0.1:8090 …");
|
|
await waitForWorkshop(compose);
|
|
console.log("Workshop is ready. Starting documentation on http://127.0.0.1:3003/workshop …");
|
|
|
|
const docs = start("pnpm", ["--filter", "@homarr/docs", "dev"], {
|
|
env: {
|
|
...process.env,
|
|
HOMARR_WEBSITE_URL: process.env.HOMARR_WEBSITE_URL ?? "http://127.0.0.1:3003",
|
|
WORKSHOP_API_URL: process.env.WORKSHOP_API_URL ?? pocketBaseUrl,
|
|
WORKSHOP_WEB_URL: process.env.WORKSHOP_WEB_URL ?? "http://127.0.0.1:3003/workshop",
|
|
DOCS_BASE_URL: process.env.DOCS_BASE_URL ?? "/",
|
|
},
|
|
});
|
|
|
|
const exitCode = await new Promise((resolve) => {
|
|
const handleError = (error) => {
|
|
console.error(error);
|
|
resolve(1);
|
|
};
|
|
compose.once("error", handleError);
|
|
docs.once("error", handleError);
|
|
compose.once("exit", (code) => resolve(code ?? 1));
|
|
docs.once("exit", (code) => resolve(code ?? 1));
|
|
});
|
|
await shutdown(exitCode);
|
|
} catch (error) {
|
|
console.error(error);
|
|
await shutdown(1);
|
|
}
|