feat(cli): add gateway start/stop/status lifecycle commands
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,10 +1,12 @@
|
|||||||
import { createInterface } from 'node:readline';
|
import { createInterface } from 'node:readline';
|
||||||
import { writeFileSync } from 'node:fs';
|
import { spawn } from 'node:child_process';
|
||||||
import { resolve } from 'node:path';
|
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
|
||||||
|
import { dirname, resolve } from 'node:path';
|
||||||
import type { Command } from 'commander';
|
import type { Command } from 'commander';
|
||||||
import {
|
import {
|
||||||
DEFAULT_LOCAL_CONFIG,
|
DEFAULT_LOCAL_CONFIG,
|
||||||
DEFAULT_TEAM_CONFIG,
|
DEFAULT_TEAM_CONFIG,
|
||||||
|
loadConfig,
|
||||||
type MosaicConfig,
|
type MosaicConfig,
|
||||||
type StorageTier,
|
type StorageTier,
|
||||||
} from '@mosaic/config';
|
} from '@mosaic/config';
|
||||||
@@ -64,6 +66,37 @@ async function runInit(opts: { tier?: string; output: string }): Promise<void> {
|
|||||||
console.log(' 2. Run: pnpm --filter @mosaic/gateway exec tsx src/main.ts');
|
console.log(' 2. Run: pnpm --filter @mosaic/gateway exec tsx src/main.ts');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const PID_FILE = resolve(process.cwd(), '.mosaic/gateway.pid');
|
||||||
|
|
||||||
|
function writePidFile(pid: number): void {
|
||||||
|
const dir = dirname(PID_FILE);
|
||||||
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
||||||
|
writeFileSync(PID_FILE, String(pid));
|
||||||
|
}
|
||||||
|
|
||||||
|
function readPidFile(): number | null {
|
||||||
|
if (!existsSync(PID_FILE)) return null;
|
||||||
|
const raw = readFileSync(PID_FILE, 'utf-8').trim();
|
||||||
|
const pid = Number(raw);
|
||||||
|
return Number.isFinite(pid) ? pid : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isProcessRunning(pid: number): boolean {
|
||||||
|
try {
|
||||||
|
process.kill(pid, 0);
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function printConfigSummary(config: MosaicConfig): void {
|
||||||
|
console.log(` Tier: ${config.tier}`);
|
||||||
|
console.log(` Storage: ${config.storage.type}`);
|
||||||
|
console.log(` Queue: ${config.queue.type}`);
|
||||||
|
console.log(` Memory: ${config.memory.type}`);
|
||||||
|
}
|
||||||
|
|
||||||
export function registerGatewayCommand(program: Command): void {
|
export function registerGatewayCommand(program: Command): void {
|
||||||
const gateway = program.command('gateway').description('Gateway management commands');
|
const gateway = program.command('gateway').description('Gateway management commands');
|
||||||
|
|
||||||
@@ -75,4 +108,91 @@ export function registerGatewayCommand(program: Command): void {
|
|||||||
.action(async (opts: { tier?: string; output: string }) => {
|
.action(async (opts: { tier?: string; output: string }) => {
|
||||||
await runInit(opts);
|
await runInit(opts);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
gateway
|
||||||
|
.command('start')
|
||||||
|
.description('Start the Mosaic gateway process')
|
||||||
|
.option('--port <port>', 'Port to listen on (overrides config)')
|
||||||
|
.option('--daemon', 'Run in background and write PID to .mosaic/gateway.pid')
|
||||||
|
.action((opts: { port?: string; daemon?: boolean }) => {
|
||||||
|
const config = loadConfig();
|
||||||
|
const port = opts.port ?? '4000';
|
||||||
|
|
||||||
|
console.log('Starting gateway…');
|
||||||
|
printConfigSummary(config);
|
||||||
|
console.log(` Port: ${port}`);
|
||||||
|
|
||||||
|
const entryPoint = resolve(process.cwd(), 'apps/gateway/src/main.ts');
|
||||||
|
const env = { ...process.env, GATEWAY_PORT: port };
|
||||||
|
|
||||||
|
if (opts.daemon) {
|
||||||
|
const child = spawn('npx', ['tsx', entryPoint], {
|
||||||
|
env,
|
||||||
|
stdio: 'ignore',
|
||||||
|
detached: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
child.unref();
|
||||||
|
|
||||||
|
if (child.pid) {
|
||||||
|
writePidFile(child.pid);
|
||||||
|
console.log(`\nGateway started in background (PID ${child.pid})`);
|
||||||
|
console.log(`PID file: ${PID_FILE}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const child = spawn('npx', ['tsx', entryPoint], {
|
||||||
|
env,
|
||||||
|
stdio: 'inherit',
|
||||||
|
});
|
||||||
|
|
||||||
|
child.on('exit', (code) => {
|
||||||
|
process.exit(code ?? 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
gateway
|
||||||
|
.command('stop')
|
||||||
|
.description('Stop the running gateway process')
|
||||||
|
.action(() => {
|
||||||
|
const pid = readPidFile();
|
||||||
|
|
||||||
|
if (pid === null) {
|
||||||
|
console.error('No PID file found at', PID_FILE);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isProcessRunning(pid)) {
|
||||||
|
console.log(`Process ${pid} is not running. Removing stale PID file.`);
|
||||||
|
unlinkSync(PID_FILE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
process.kill(pid, 'SIGTERM');
|
||||||
|
unlinkSync(PID_FILE);
|
||||||
|
console.log(`Gateway stopped (PID ${pid})`);
|
||||||
|
});
|
||||||
|
|
||||||
|
gateway
|
||||||
|
.command('status')
|
||||||
|
.description('Show gateway process status')
|
||||||
|
.action(() => {
|
||||||
|
const config = loadConfig();
|
||||||
|
const pid = readPidFile();
|
||||||
|
|
||||||
|
if (pid !== null && isProcessRunning(pid)) {
|
||||||
|
console.log('Gateway: running');
|
||||||
|
console.log(` PID: ${pid}`);
|
||||||
|
} else {
|
||||||
|
console.log('Gateway: stopped');
|
||||||
|
if (pid !== null) {
|
||||||
|
console.log(` (stale PID file for ${pid})`);
|
||||||
|
unlinkSync(PID_FILE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('');
|
||||||
|
console.log('Config:');
|
||||||
|
printConfigSummary(config);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user