@bluecadet/launchpad-cli
Version:
CLI for @bluecadet/launchpad utilities
96 lines • 4.31 kB
JavaScript
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