|
| 1 | +import type { Argv } from 'yargs'; |
| 2 | + |
| 3 | +import { handleDaemonFlush } from './flush.ts'; |
| 4 | +import { handleInspect } from './inspect.ts'; |
| 5 | +import { handleInvoke } from './invoke.ts'; |
| 6 | +import { handleLaunch } from './launch.ts'; |
| 7 | +import { handleDaemonLogs } from './logs.ts'; |
| 8 | +import { handleDaemonPid } from './pid.ts'; |
| 9 | +import { handleDaemonRestart } from './restart.ts'; |
| 10 | +import { handleDaemonStart } from './start.ts'; |
| 11 | +import { handleDaemonStatus } from './status.ts'; |
| 12 | +import { handleDaemonStop } from './stop.ts'; |
| 13 | +import type { DaemonCommandsConfig } from './types.ts'; |
| 14 | +import { handleUrlIssue } from './url-issue.ts'; |
| 15 | +import { handleUrlRedeem } from './url-redeem.ts'; |
| 16 | +import { handleView } from './view.ts'; |
| 17 | + |
| 18 | +/** |
| 19 | + * Run a daemon command handler and exit the process on completion. |
| 20 | + * Errors propagate to yargs' fail handler; successful completion exits with 0. |
| 21 | + * |
| 22 | + * @param fn - The async handler to run. |
| 23 | + * @returns A promise that exits the process on success. |
| 24 | + */ |
| 25 | +async function runAndExit(fn: () => Promise<void>): Promise<void> { |
| 26 | + await fn(); |
| 27 | + // eslint-disable-next-line n/no-process-exit |
| 28 | + process.exit(0); |
| 29 | +} |
| 30 | + |
| 31 | +/** |
| 32 | + * Register all daemon subcommands on the given yargs instance. |
| 33 | + * Captures config in closure so individual handlers receive injected dependencies. |
| 34 | + * Every handler exits the process with code 0 after completing successfully. |
| 35 | + * |
| 36 | + * @param yargs - The yargs instance to extend (the `daemon` subcommand builder). |
| 37 | + * @param config - Injected configuration (logger, getMethodSpecs, daemonProcessPath). |
| 38 | + * @returns The extended yargs instance. |
| 39 | + */ |
| 40 | +export function registerDaemonCommands( |
| 41 | + yargs: Argv, |
| 42 | + config: DaemonCommandsConfig, |
| 43 | +): Argv { |
| 44 | + const { logger, getMethodSpecs, daemonProcessPath } = config; |
| 45 | + |
| 46 | + return yargs |
| 47 | + .command( |
| 48 | + 'start', |
| 49 | + 'Start the background kernel daemon', |
| 50 | + (yg) => yg, |
| 51 | + async () => |
| 52 | + runAndExit(async () => { |
| 53 | + try { |
| 54 | + await handleDaemonStart(daemonProcessPath, logger); |
| 55 | + } catch (error) { |
| 56 | + if ( |
| 57 | + error instanceof Error && |
| 58 | + error.message.startsWith('Daemon already running') |
| 59 | + ) { |
| 60 | + logger.info(error.message); |
| 61 | + } else { |
| 62 | + throw error; |
| 63 | + } |
| 64 | + } |
| 65 | + }), |
| 66 | + ) |
| 67 | + .command( |
| 68 | + 'stop', |
| 69 | + 'Stop the background kernel daemon', |
| 70 | + (yg) => yg, |
| 71 | + async () => runAndExit(async () => handleDaemonStop(logger)), |
| 72 | + ) |
| 73 | + .command( |
| 74 | + 'status', |
| 75 | + 'Show daemon status', |
| 76 | + (yg) => yg, |
| 77 | + async () => runAndExit(async () => handleDaemonStatus(logger)), |
| 78 | + ) |
| 79 | + .command( |
| 80 | + 'restart', |
| 81 | + 'Restart the background kernel daemon', |
| 82 | + (yg) => |
| 83 | + yg.option('flush', { |
| 84 | + type: 'boolean', |
| 85 | + default: false, |
| 86 | + describe: 'Flush the daemon database before restarting', |
| 87 | + }), |
| 88 | + async (args) => |
| 89 | + runAndExit(async () => |
| 90 | + handleDaemonRestart(daemonProcessPath, logger, { |
| 91 | + flush: args.flush, |
| 92 | + }), |
| 93 | + ), |
| 94 | + ) |
| 95 | + .command( |
| 96 | + 'flush', |
| 97 | + 'Delete the daemon database (daemon must be stopped)', |
| 98 | + (yg) => yg, |
| 99 | + async () => runAndExit(async () => handleDaemonFlush(logger)), |
| 100 | + ) |
| 101 | + .command( |
| 102 | + 'pid', |
| 103 | + 'Print the daemon process ID', |
| 104 | + (yg) => yg, |
| 105 | + async () => runAndExit(async () => handleDaemonPid(logger)), |
| 106 | + ) |
| 107 | + .command( |
| 108 | + 'logs', |
| 109 | + 'Print the daemon log file', |
| 110 | + (yg) => yg, |
| 111 | + async () => runAndExit(async () => handleDaemonLogs(logger)), |
| 112 | + ) |
| 113 | + .command( |
| 114 | + 'launch <path>', |
| 115 | + 'Launch a .bundle or subcluster.json via the daemon', |
| 116 | + (yg) => |
| 117 | + yg.positional('path', { |
| 118 | + type: 'string', |
| 119 | + demandOption: true, |
| 120 | + describe: 'Path to a .bundle or subcluster.json file', |
| 121 | + }), |
| 122 | + async (args) => |
| 123 | + runAndExit(async () => handleLaunch(args.path, getMethodSpecs, logger)), |
| 124 | + ) |
| 125 | + .command( |
| 126 | + 'view', |
| 127 | + 'View kernel state as JSON', |
| 128 | + (yg) => yg, |
| 129 | + async () => runAndExit(async () => handleView(getMethodSpecs, logger)), |
| 130 | + ) |
| 131 | + .command( |
| 132 | + 'invoke <kref> <method> [args..]', |
| 133 | + 'Invoke a method on a kernel object via the daemon', |
| 134 | + (yg) => |
| 135 | + yg |
| 136 | + .positional('kref', { |
| 137 | + type: 'string', |
| 138 | + demandOption: true, |
| 139 | + describe: 'The kernel reference (e.g. ko1)', |
| 140 | + }) |
| 141 | + .positional('method', { |
| 142 | + type: 'string', |
| 143 | + demandOption: true, |
| 144 | + describe: 'The method name to invoke', |
| 145 | + }) |
| 146 | + .positional('args', { |
| 147 | + type: 'string', |
| 148 | + array: true, |
| 149 | + default: [] as string[], |
| 150 | + describe: 'Arguments to pass (JSON-parsed if possible)', |
| 151 | + }), |
| 152 | + async (args) => |
| 153 | + runAndExit(async () => |
| 154 | + handleInvoke( |
| 155 | + args.kref, |
| 156 | + args.method, |
| 157 | + args.args ?? [], |
| 158 | + getMethodSpecs, |
| 159 | + logger, |
| 160 | + ), |
| 161 | + ), |
| 162 | + ) |
| 163 | + .command( |
| 164 | + 'inspect <kref>', |
| 165 | + 'Inspect a kernel object (methods, guard, schema)', |
| 166 | + (yg) => |
| 167 | + yg.positional('kref', { |
| 168 | + type: 'string', |
| 169 | + demandOption: true, |
| 170 | + describe: 'The kernel reference (e.g. ko1)', |
| 171 | + }), |
| 172 | + async (args) => |
| 173 | + runAndExit(async () => |
| 174 | + handleInspect(args.kref, getMethodSpecs, logger), |
| 175 | + ), |
| 176 | + ) |
| 177 | + .command('url [command]', 'Issue and redeem OCAP URLs', (yg) => |
| 178 | + yg |
| 179 | + .command( |
| 180 | + 'issue <kref>', |
| 181 | + 'Issue an OCAP URL for a kernel object', |
| 182 | + (yg2) => |
| 183 | + yg2.positional('kref', { |
| 184 | + type: 'string', |
| 185 | + demandOption: true, |
| 186 | + describe: 'The kernel reference (e.g. ko1)', |
| 187 | + }), |
| 188 | + async (args) => |
| 189 | + runAndExit(async () => |
| 190 | + handleUrlIssue(args.kref, getMethodSpecs, logger), |
| 191 | + ), |
| 192 | + ) |
| 193 | + .command( |
| 194 | + 'redeem <url>', |
| 195 | + 'Redeem an OCAP URL to get its kernel reference', |
| 196 | + (yg2) => |
| 197 | + yg2.positional('url', { |
| 198 | + type: 'string', |
| 199 | + demandOption: true, |
| 200 | + describe: 'The OCAP URL to redeem', |
| 201 | + }), |
| 202 | + async (args) => |
| 203 | + runAndExit(async () => |
| 204 | + handleUrlRedeem(args.url, getMethodSpecs, logger), |
| 205 | + ), |
| 206 | + ) |
| 207 | + .demandCommand(1, 'Specify a url subcommand: issue or redeem'), |
| 208 | + ); |
| 209 | +} |
| 210 | + |
| 211 | +export { handleDaemonStart } from './start.ts'; |
| 212 | +export type { DaemonCommandsConfig } from './types.ts'; |
0 commit comments