UNPKG

@bluecadet/launchpad-cli

Version:
96 lines 4.31 kB
import { fork } from "node:child_process"; import chalk from "chalk"; import { fromPromise, okAsync } from "neverthrow"; import { cliLogger } from "../utils/cli-logger.js"; import { handleFatalError, loadConfigAndEnv } from "../utils/command-utils.js"; import { withDaemonOrController } from "../utils/controller-execution.js"; import { isDetached, isValidChildLogMessage, isValidReadyMessage, sendReadyMessage, } from "../utils/detached-messaging.js"; import { onTerminate } from "../utils/on-terminate.js"; export function start(argv) { if (argv.detach) { return startDetached(argv); } return startForeground(argv); } function startDetached(_argv) { return fromPromise(new Promise((resolve, reject) => { const filteredArgv = process.argv .slice(1) // Skip the first argument (node executable) .filter((arg) => arg !== "--detach" && arg !== "-d"); // Remove detach flags const [mod, ...args] = filteredArgv; cliLogger.info("Starting Launchpad in background..."); const child = fork(mod, args, { detached: true, stdio: "ignore", // Ignore stdin, pipe stdout/stderr, keep IPC channel env: { ...process.env, LAUNCHPAD_IS_DETACHED: "1", // Indicate to the child process that it's detached }, }); cliLogger.verbose(`Launched detached process with PID: ${child.pid}`); child.on("error", (error) => { reject(error); }); child.on("message", (message) => { if (isValidChildLogMessage(message)) { const { level, payload } = message; cliLogger.fromPayload(level, payload); } else if (isValidReadyMessage(message)) { cliLogger.info("Launchpad started successfully in background."); child.unref(); // Allow the parent to exit independently child.disconnect(); // Close IPC channel resolve(); } else { cliLogger.warn("Unknown message from detached process:", message); } }); child.on("exit", (code) => { reject(new Error(`Detached process exited with code ${code}`)); }); }), (error) => error).andTee(() => { cliLogger.info(`Launchpad started in background. Use '${chalk.cyan("launchpad stop")}' to stop it.`); }); } function startForeground(argv) { return loadConfigAndEnv(argv) .mapErr((error) => handleFatalError(error)) .andThen(({ dir, config }) => { return withDaemonOrController(dir, config.controller, { mode: "persistent", ifDaemon: (_client, pid) => { // Daemon already running cliLogger.error(`Launchpad is already running (PID: ${pid})`); cliLogger.error("Stop it with: launchpad stop"); process.exit(1); }, otherwise: (controller) => { if (isDetached) { process.title = "launchpad"; } onTerminate(() => { controller.stop().match(() => process.exit(0), () => process.exit(1)); }); // Listen for shutdown events from IPC or plugins controller.getEventBus().on("system:shutdown", ({ code }) => { controller.stop().match(() => process.exit(code ?? 0), () => process.exit(1)); }); const plugins = config.plugins ?? []; return plugins .reduce((chain, plugin) => chain.andThen(() => controller.registerPlugin(plugin)), okAsync(undefined)) .andTee(() => { controller.setWorkflows(config.workflows ?? {}); }) .andThen(() => controller.runWorkflow("start")) .andTee(() => { if (isDetached) { sendReadyMessage(); } cliLogger.info("Launchpad started in persistent mode. Press Ctrl+C to stop."); }); }, }).orElse((error) => handleFatalError(error)); }); } //# sourceMappingURL=start.js.map