Files
wehub-resource-sync 426e9eeabd
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
ci / test (push) Has been cancelled
ci / lint-and-format (push) Has been cancelled
ci / build (push) Has been cancelled
ci / dev-startup (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
Markdown Links / Relative Markdown Links (push) Has been cancelled
Quality (Extended) / Homepage Build (PR smoke) (push) Has been cancelled
Quality (Extended) / Comment-only diff guard (push) Has been cancelled
Quality (Extended) / Format + Type Safety Ratchet (push) Has been cancelled
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Has been cancelled
Quality (Extended) / Develop Gate (lint) (push) Has been cancelled
Chat shell gestures / Chat shell gesture + parity e2e (push) Has been cancelled
Cloud Gateway Discord / Test (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Has been cancelled
Build Agent Image / build-and-push (push) Has been cancelled
Dev Smoke / bun run dev onboarding chat (push) Has been cancelled
Dev Smoke / Vite HMR dependency-level smoke (push) Has been cancelled
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Has been cancelled
Publish @elizaos/example-code / check_npm (push) Has been cancelled
Publish @elizaos/example-code / publish_npm (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / verify_version (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / publish_npm (push) Has been cancelled
Sandbox Live Smoke / Sandbox live smoke (push) Has been cancelled
Snap Build & Test / Build Snap (amd64) (push) Has been cancelled
Snap Build & Test / Build Snap (arm64) (push) Has been cancelled
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Has been cancelled
Cloud Gateway Webhook / Test (push) Has been cancelled
Cloud Tests / lint-and-types (push) Has been cancelled
Cloud Tests / unit-tests (push) Has been cancelled
Cloud Tests / integration-tests (push) Has been cancelled
Cloud Tests / e2e-tests (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Apps Worker (Product 2) / Determine environment (push) Has been cancelled
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Has been cancelled
Deploy Eliza Provisioning Worker / Determine environment (push) Has been cancelled
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Has been cancelled
Dev Smoke / Classify changed paths (push) Has been cancelled
supply-chain / sbom (push) Has been cancelled
supply-chain / vulnerability-scan (push) Has been cancelled
Build, Push & Deploy to Phala Cloud / build-and-push (push) Has been cancelled
Test Packaging / Validate Packaging Configs (push) Has been cancelled
Test Packaging / Build & Test PyPI Package (push) Has been cancelled
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Has been cancelled
Test Packaging / Pack & Test JS Tarballs (push) Has been cancelled
UI Fixture E2E / ui-fixture-e2e (push) Has been cancelled
UI Fixture E2E / fixture-e2e (push) Has been cancelled
UI Story Gate / story-gate (push) Has been cancelled
vault-ci / test (macos-latest) (push) Has been cancelled
vault-ci / test (ubuntu-latest) (push) Has been cancelled
vault-ci / test (windows-latest) (push) Has been cancelled
vault-ci / app-core wiring tests (push) Has been cancelled
verify-patches / verify patches/CHECKSUMS.sha256 (push) Has been cancelled
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Has been cancelled
Voice Benchmark Smoke / voice bench smoke summary (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Has been cancelled
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:43:05 +08:00

643 lines
17 KiB
JavaScript

#!/usr/bin/env node
// Drives repo automation dev all with explicit CLI and CI behavior.
import { execFileSync, spawn } from "node:child_process";
import { existsSync } from "node:fs";
import net from "node:net";
import process from "node:process";
import { resolveDevAllSkipPlugins } from "./lib/script-metadata.mjs";
const args = new Set(process.argv.slice(2));
const dryRun = args.has("--dry-run");
const fullPrepare =
args.has("--full-prepare") || process.env.DEV_ALL_FULL_PREPARE === "1";
const forcePrepare =
args.has("--force-prepare") || process.env.DEV_ALL_FORCE_PREPARE === "1";
const skipPrepare =
args.has("--no-prepare") || process.env.DEV_ALL_SKIP_PREPARE === "1";
const skipCloudDb =
args.has("--no-cloud-db") || process.env.DEV_ALL_SKIP_CLOUD_DB === "1";
const buildNativePlugins =
args.has("--build-native-plugins") ||
process.env.DEV_ALL_BUILD_NATIVE_PLUGINS === "1";
const enableTestAuth =
args.has("--test-auth") || process.env.DEV_ALL_ENABLE_TEST_AUTH !== "0";
const repoRoot = process.cwd();
const bunBin =
process.env.BUN_BIN ||
(process.env.npm_execpath?.includes("bun")
? process.env.npm_execpath
: undefined) ||
(process.env.BUN_INSTALL
? `${process.env.BUN_INSTALL}/bin/bun`
: undefined) ||
"bun";
const bunBinDir = bunBin.includes("/")
? bunBin.slice(0, bunBin.lastIndexOf("/"))
: "";
const childPath = [bunBinDir, process.env.PATH].filter(Boolean).join(":");
function envDefault(key, value) {
return process.env[key]?.trim() || value;
}
const ports = {
agentApi: envDefault("DEV_ALL_AGENT_API_PORT", "31337"),
frontend: envDefault("DEV_ALL_FRONTEND_PORT", "2138"),
homepage: envDefault("DEV_ALL_HOMEPAGE_PORT", "4444"),
osHomepage: envDefault("DEV_ALL_OS_HOMEPAGE_PORT", "4455"),
cloudWeb: envDefault("DEV_ALL_CLOUD_WEB_PORT", "3000"),
cloudApi: envDefault("DEV_ALL_CLOUD_API_PORT", "8787"),
cloudDb: envDefault("DEV_ALL_CLOUD_DB_PORT", "55432"),
};
const urls = {
agentApi: `http://127.0.0.1:${ports.agentApi}`,
frontend: `http://localhost:${ports.frontend}`,
homepage: `http://localhost:${ports.homepage}`,
osHomepage: `http://localhost:${ports.osHomepage}`,
cloudWeb: `http://localhost:${ports.cloudWeb}`,
cloudApi: `http://localhost:${ports.cloudApi}`,
cloudDb: `postgresql://postgres@127.0.0.1:${ports.cloudDb}/postgres`,
};
const localTestAuthSecret = envDefault(
"PLAYWRIGHT_TEST_AUTH_SECRET",
"playwright-local-auth-secret",
);
const serviceStartupTimeoutMs = Number.parseInt(
envDefault("DEV_ALL_SERVICE_STARTUP_TIMEOUT_MS", "120000"),
10,
);
const packagedCloudAvailable = existsSync(
`${repoRoot}/packages/cloud/api/package.json`,
);
const cloudMode = packagedCloudAvailable ? "packages" : "legacy";
const commonEnv = {
...process.env,
NODE_ENV: "development",
PATH: childPath,
ELIZA_DEV_SOURCE: envDefault("ELIZA_DEV_SOURCE", "1"),
ELIZA_DEV_NO_WATCH: envDefault("ELIZA_DEV_NO_WATCH", "0"),
};
const cloudSharedEnv = {
...commonEnv,
API_DEV_PORT: ports.cloudApi,
DATABASE_URL: envDefault("DATABASE_URL", urls.cloudDb),
ELIZA_CLOUD_LOCAL_APP_URL: urls.cloudWeb,
ELIZA_CLOUD_LOCAL_API_URL: urls.cloudApi,
NEXT_PUBLIC_APP_URL: urls.cloudWeb,
NEXT_PUBLIC_API_URL: urls.cloudApi,
NEXT_PUBLIC_ELIZA_APP_URL: urls.homepage,
NEXT_PUBLIC_ELIZA_API_URL: urls.cloudApi,
NEXT_PUBLIC_ELIZA_PROXY_URL: urls.cloudWeb,
NEXT_PUBLIC_STEWARD_API_URL: `${urls.cloudApi}/steward`,
VITE_ELIZA_APP_URL: urls.homepage,
VITE_ELIZA_CLOUD_URL: urls.cloudWeb,
VITE_ELIZA_OS_URL: urls.osHomepage,
...(enableTestAuth
? {
AGENT_TEST_BOOTSTRAP_ADMIN: "true",
PLAYWRIGHT_TEST_AUTH: "true",
PLAYWRIGHT_TEST_AUTH_SECRET: localTestAuthSecret,
RATE_LIMIT_DISABLED: "true",
}
: {}),
VITE_API_PROXY_TARGET: urls.cloudApi,
VITE_ALLOWED_HOSTS: [
"localhost",
"127.0.0.1",
"::1",
process.env.VITE_ALLOWED_HOSTS,
]
.filter(Boolean)
.join(","),
};
const agentEnv = {
...commonEnv,
API_PORT: ports.agentApi,
SERVER_PORT: ports.agentApi,
ELIZA_PORT: ports.agentApi,
ELIZA_API_PORT: ports.agentApi,
ELIZA_UI_ENABLE: "true",
ELIZA_API_BIND: envDefault("ELIZA_API_BIND", "127.0.0.1"),
ELIZAOS_CLOUD_BASE_URL: envDefault(
"ELIZAOS_CLOUD_BASE_URL",
`${urls.cloudApi}/api/v1`,
),
ELIZA_CLOUD_URL: urls.cloudWeb,
ELIZA_WALLET_OS_STORE: envDefault("ELIZA_WALLET_OS_STORE", "0"),
ELIZA_ALLOW_NO_PROVIDER: envDefault("ELIZA_ALLOW_NO_PROVIDER", "1"),
ELIZA_DISABLE_LOCAL_EMBEDDINGS: envDefault(
"ELIZA_DISABLE_LOCAL_EMBEDDINGS",
"true",
),
// Plugins that opt out of the dev-all agent stack declare
// `elizaos.scripts.devStack.skipInDevAll` in their own package.json; resolved
// through the discovery seam so no plugin names live in this file.
ELIZA_SKIP_PLUGINS: [
process.env.ELIZA_SKIP_PLUGINS,
...resolveDevAllSkipPlugins({ repoRoot }),
]
.filter(Boolean)
.join(","),
PGLITE_DATA_DIR: envDefault(
"DEV_ALL_AGENT_PGLITE_DATA_DIR",
`${repoRoot}/.eliza/agent-pglite`,
),
EVM_PRIVATE_KEY: "",
SOLANA_PRIVATE_KEY: "",
};
const frontendEnv = {
...commonEnv,
PORT: ports.frontend,
ELIZA_UI_PORT: ports.frontend,
ELIZA_API_PORT: ports.agentApi,
ELIZA_PORT: ports.agentApi,
VITE_ELIZA_CLOUD_BASE: urls.cloudWeb,
VITE_ELIZA_IOS_API_BASE: urls.cloudApi,
VITE_ELIZACLOUD_API_URL: urls.cloudApi,
VITE_ASSET_BASE_URL: envDefault(
"VITE_ASSET_BASE_URL",
"https://blob.elizacloud.ai",
),
VITE_ELIZA_APP_URL: urls.homepage,
VITE_ELIZA_CLOUD_URL: urls.cloudWeb,
VITE_ELIZA_OS_URL: urls.osHomepage,
ELIZA_APP_VITE_NO_DISCOVERY: envDefault("ELIZA_APP_VITE_NO_DISCOVERY", "1"),
...(enableTestAuth ? { VITE_PLAYWRIGHT_TEST_AUTH: "true" } : {}),
};
const homepageEnv = {
...commonEnv,
PORT: ports.homepage,
VITE_ELIZACLOUD_API_URL: urls.cloudApi,
VITE_ELIZA_APP_URL: urls.homepage,
VITE_ELIZA_CLOUD_URL: urls.cloudWeb,
VITE_ELIZA_OS_URL: urls.osHomepage,
...(enableTestAuth ? { VITE_PLAYWRIGHT_TEST_AUTH: "true" } : {}),
};
const osHomepageEnv = {
...commonEnv,
PORT: ports.osHomepage,
VITE_ELIZACLOUD_API_URL: urls.cloudApi,
VITE_ELIZA_APP_URL: urls.homepage,
VITE_ELIZA_CLOUD_URL: urls.cloudWeb,
VITE_ELIZA_OS_URL: urls.osHomepage,
...(enableTestAuth ? { VITE_PLAYWRIGHT_TEST_AUTH: "true" } : {}),
};
const cloudDbEnv = {
...cloudSharedEnv,
PGLITE_PORT: ports.cloudDb,
PGLITE_HOST: "127.0.0.1",
};
const cloudApiService = packagedCloudAvailable
? { cwd: "packages/cloud/api", command: [bunBin, "run", "dev"] }
: { cwd: "cloud", command: [bunBin, "run", "dev:api"] };
const cloudWebService = packagedCloudAvailable
? {
cwd: "packages/app",
command: [
bunBin,
"run",
"dev",
"--",
"--host",
"0.0.0.0",
"--port",
ports.cloudWeb,
"--strictPort",
],
}
: {
cwd: "cloud",
command: [
bunBin,
"run",
"dev:web",
"--",
"--host",
"0.0.0.0",
"--port",
ports.cloudWeb,
"--strictPort",
],
};
const services = [
!skipCloudDb && {
name: "cloud-db",
cwd: ".",
command: [bunBin, "run", "db:cloud:pglite"],
env: cloudDbEnv,
},
{ name: "cloud-api", ...cloudApiService, env: cloudSharedEnv },
{
name: "cloud-web",
...cloudWebService,
env: { ...cloudSharedEnv, PORT: ports.cloudWeb },
},
{
name: "agent",
cwd: ".",
command: ["node", "packages/scripts/dev-agent-watch.mjs"],
env: agentEnv,
},
{
name: "frontend",
cwd: "packages/app",
command: [
bunBin,
"run",
"dev",
"--",
"--host",
"0.0.0.0",
"--port",
ports.frontend,
"--strictPort",
],
env: frontendEnv,
},
{
name: "homepage",
cwd: "packages/homepage",
command: [
bunBin,
"run",
"dev",
"--",
"--host",
"0.0.0.0",
"--port",
ports.homepage,
"--strictPort",
],
env: homepageEnv,
},
{
name: "os-homepage",
cwd: "packages/os/homepage",
command: [
bunBin,
"run",
"dev",
"--",
"--host",
"0.0.0.0",
"--port",
ports.osHomepage,
"--strictPort",
],
env: osHomepageEnv,
},
].filter(Boolean);
const cloudDevVarsCommand = packagedCloudAvailable
? [bunBin, "run", "packages/scripts/cloud/admin/sync-api-dev-vars.ts"]
: [bunBin, "run", "--cwd", "cloud", "packages/scripts/sync-api-dev-vars.ts"];
function packageDistReady(packageDir, requiredFiles) {
if (forcePrepare) return false;
return requiredFiles.every((file) =>
existsSync(`${repoRoot}/${packageDir}/${file}`),
);
}
function buildDefaultPrepareCommands() {
const generateCommands = [
{
label: "shared i18n keywords",
cwd: "packages/shared",
command: [bunBin, "run", "build:i18n"],
env: commonEnv,
},
{
label: "cloud dev vars",
cwd: ".",
command: cloudDevVarsCommand,
env: cloudSharedEnv,
},
];
const buildCommands = [];
if (!packageDistReady("packages/shared", ["dist/index.js"])) {
buildCommands.push({
label: "shared package build",
cwd: "packages/shared",
command: [bunBin, "run", "build:dist"],
env: commonEnv,
});
}
if (
!packageDistReady("packages/ui", [
"dist/index.js",
"dist/cloud-ui/index.css",
])
) {
buildCommands.push({
label: "ui package build",
cwd: "packages/ui",
command: [bunBin, "run", "build:dist"],
env: commonEnv,
});
}
const stages = [generateCommands, buildCommands];
if (buildNativePlugins) {
stages.push([
{
label: "app native plugin build",
cwd: "packages/app",
command: [bunBin, "run", "plugin:build"],
env: frontendEnv,
},
]);
}
return stages.filter((stage) => stage.length > 0);
}
const prepareCommands = fullPrepare
? [
[
{
label: "dev:prepare",
cwd: ".",
command: [bunBin, "run", "dev:prepare"],
env: commonEnv,
},
],
]
: buildDefaultPrepareCommands();
function printPlan() {
console.log("[dev:all] local stack");
console.log(` agent API: ${urls.agentApi}`);
console.log(` frontend: ${urls.frontend}`);
console.log(` app home: ${urls.homepage}`);
console.log(` OS home: ${urls.osHomepage}`);
console.log(` cloud web: ${urls.cloudWeb}`);
console.log(` cloud API: ${urls.cloudApi}`);
console.log(` cloud src: ${cloudMode}`);
if (!skipCloudDb) console.log(` cloud DB: ${urls.cloudDb}`);
console.log(
` test auth: ${enableTestAuth ? "enabled (local only)" : "disabled"}`,
);
console.log("");
}
function canConnect(host, port) {
return new Promise((resolve) => {
const socket = net.createConnection({ host, port: Number(port) });
socket.once("connect", () => {
socket.destroy();
resolve(true);
});
socket.once("error", () => {
socket.destroy();
resolve(false);
});
socket.setTimeout(750, () => {
socket.destroy();
resolve(false);
});
});
}
async function waitForPort(label, host, port) {
const startedAt = Date.now();
while (Date.now() - startedAt < serviceStartupTimeoutMs) {
if (await canConnect(host, port)) return;
await new Promise((resolve) => setTimeout(resolve, 500));
}
throw new Error(`[dev:all] ${label} did not start on ${host}:${port}`);
}
function describePortOwner(port) {
try {
return execFileSync("lsof", ["-nP", `-iTCP:${port}`, "-sTCP:LISTEN"], {
encoding: "utf8",
}).trim();
} catch {
return "";
}
}
async function assertPortsAvailable() {
const checks = [
!skipCloudDb && ["cloud DB", "127.0.0.1", ports.cloudDb],
["cloud API", "127.0.0.1", ports.cloudApi],
["cloud web", "127.0.0.1", ports.cloudWeb],
["agent API", "127.0.0.1", ports.agentApi],
["frontend", "127.0.0.1", ports.frontend],
["app home", "127.0.0.1", ports.homepage],
["OS home", "127.0.0.1", ports.osHomepage],
].filter(Boolean);
const occupied = [];
for (const [label, host, port] of checks) {
if (await canConnect(host, port)) {
occupied.push({ label, port, owner: describePortOwner(port) });
}
}
if (occupied.length > 0) {
const details = occupied
.map(
({ label, port, owner }) =>
` ${label} port ${port} is already in use${owner ? `\n${owner}` : ""}`,
)
.join("\n\n");
throw new Error(`[dev:all] cannot start; ports are occupied:\n${details}`);
}
}
function runOnce(label, cwd, command, env) {
return new Promise((resolve, reject) => {
console.log(`[dev:all] ${label}: ${command.join(" ")}`);
if (dryRun) return resolve();
const child = spawn(command[0], command.slice(1), {
cwd: cwd === "." ? repoRoot : `${repoRoot}/${cwd}`,
env,
stdio: "inherit",
});
child.on("error", reject);
child.on("exit", (code, signal) => {
if (code === 0) resolve();
else reject(new Error(`${label} exited with ${signal ?? code}`));
});
});
}
async function runPrepareStage(stage) {
await Promise.all(
stage.map(({ label, cwd, command, env }) =>
runOnce(label, cwd, command, env),
),
);
}
function prefixStream(stream, label, target) {
let pending = "";
stream.setEncoding("utf8");
stream.on("data", (chunk) => {
pending += chunk;
const lines = pending.split(/\r?\n/);
pending = lines.pop() ?? "";
for (const line of lines) target.write(`[${label}] ${line}\n`);
});
stream.on("end", () => {
if (pending) target.write(`[${label}] ${pending}\n`);
});
}
function startService(service) {
console.log(
`[dev:all] starting ${service.name}: ${service.command.join(" ")}`,
);
if (dryRun) return null;
const child = spawn(service.command[0], service.command.slice(1), {
cwd: service.cwd === "." ? repoRoot : `${repoRoot}/${service.cwd}`,
env: service.env,
detached: true,
stdio: ["inherit", "pipe", "pipe"],
});
child.serviceName = service.name;
prefixStream(child.stdout, service.name, process.stdout);
prefixStream(child.stderr, service.name, process.stderr);
child.on("error", (error) => {
console.error(
`[dev:all] ${service.name} failed to start: ${error.message}`,
);
});
return child;
}
function stopChildren(children) {
for (const child of children) {
if (!child) continue;
signalProcessGroup(child.pid, "SIGTERM");
}
}
function signalProcessGroup(pid, signal) {
try {
process.kill(-pid, signal);
return;
} catch {
// Fall back to walking descendants when the process group is gone.
}
signalProcessTree(pid, signal);
}
function childPids(pid) {
try {
return execFileSync("pgrep", ["-P", String(pid)], {
encoding: "utf8",
})
.split(/\s+/)
.filter(Boolean)
.map((value) => Number.parseInt(value, 10))
.filter(Number.isFinite);
} catch {
return [];
}
}
function signalProcessTree(pid, signal) {
for (const childPid of childPids(pid)) {
signalProcessTree(childPid, signal);
}
try {
process.kill(pid, signal);
} catch {
// Process already exited.
}
}
function stopChildrenAndExit(children, code) {
stopChildren(children);
setTimeout(() => {
for (const child of children) {
if (child) signalProcessGroup(child.pid, "SIGKILL");
}
process.exit(code);
}, 5000).unref();
}
async function main() {
printPlan();
if (!dryRun) await assertPortsAvailable();
if (!skipPrepare) {
if (!fullPrepare) {
console.log(
"[dev:all] using fast source prepare (pass --force-prepare to refresh dist, --full-prepare for Turbo)",
);
if (!buildNativePlugins) {
console.log(
"[dev:all] skipping native plugin prebuilds (pass --build-native-plugins if needed)",
);
}
}
for (const stage of prepareCommands) {
await runPrepareStage(stage);
}
} else {
console.log("[dev:all] skipping prepare steps");
}
if (dryRun) return;
await assertPortsAvailable();
const children = [];
const cloudDbService = services.find(
(service) => service.name === "cloud-db",
);
if (cloudDbService) {
const cloudDb = startService(cloudDbService);
if (cloudDb) children.push(cloudDb);
console.log(`[dev:all] waiting for cloud DB on 127.0.0.1:${ports.cloudDb}`);
await waitForPort("cloud DB", "127.0.0.1", ports.cloudDb);
}
for (const service of services) {
if (service.name === "cloud-db") continue;
const child = startService(service);
if (child) children.push(child);
}
let shuttingDown = false;
const shutdown = (signal) => {
if (shuttingDown) return;
shuttingDown = true;
console.log(`[dev:all] ${signal} received; stopping services`);
stopChildren(children);
setTimeout(() => {
for (const child of children) {
if (child) signalProcessGroup(child.pid, "SIGKILL");
}
process.exit(0);
}, 5000).unref();
};
process.on("SIGINT", () => shutdown("SIGINT"));
process.on("SIGTERM", () => shutdown("SIGTERM"));
for (const child of children) {
child.on("exit", (code, signal) => {
if (shuttingDown) return;
console.error(
`[dev:all] service ${child.serviceName ?? "unknown"} exited; stopping stack (${signal ?? code})`,
);
shuttingDown = true;
stopChildrenAndExit(children, typeof code === "number" ? code : 1);
});
}
}
main().catch((error) => {
console.error(
`[dev:all] ${error instanceof Error ? error.message : String(error)}`,
);
process.exit(1);
});