(getConfig: () => Config)
| 482 | } |
| 483 | |
| 484 | export function uiCommand(getConfig: () => Config): Command { |
| 485 | return new Command('ui') |
| 486 | .description('Serve the OpenKnowledge React editor UI') |
| 487 | .option( |
| 488 | '-p, --port <port>', |
| 489 | `UI port (default: $PORT env or ${DEFAULT_UI_PORT}, kernel-allocated fallback if busy)`, |
| 490 | ) |
| 491 | .option( |
| 492 | '-H, --host <host>', |
| 493 | 'UI host. Default: two-socket loopback bind (`[::1]` + `127.0.0.1`) so cross-family collisions fail loud. Pass an explicit host (e.g. `127.0.0.1`, `0.0.0.0`) to bind a single socket on that host.', |
| 494 | ) |
| 495 | .action(async (opts: { port?: string; host?: string }) => { |
| 496 | const { dim } = await import('../ui/colors.ts'); |
| 497 | const { UiLockCollisionError } = await import('@inkeep/open-knowledge-server'); |
| 498 | const { resolveLockDir } = await import('@inkeep/open-knowledge-server'); |
| 499 | const config = getConfig(); |
| 500 | const host = opts.host; |
| 501 | |
| 502 | let resolved: ResolvedRequestedPort; |
| 503 | try { |
| 504 | resolved = resolveRequestedPort(opts.port, process.env.PORT); |
| 505 | } catch (err) { |
| 506 | console.error(err instanceof Error ? err.message : String(err)); |
| 507 | process.exitCode = 1; |
| 508 | return; |
| 509 | } |
| 510 | const requestedPort = resolved.port; |
| 511 | |
| 512 | try { |
| 513 | const handle = await startUiServer({ |
| 514 | config, |
| 515 | cwd: process.cwd(), |
| 516 | port: requestedPort, |
| 517 | fallbackToKernel: resolved.fallbackToKernel, |
| 518 | host, |
| 519 | }); |
| 520 | const displayHost = |
| 521 | host === undefined || host === '::' || host === '0.0.0.0' ? 'localhost' : host; |
| 522 | console.log(`${dim('[ui]')} listening on http://${displayHost}:${handle.port}`); |
| 523 | |
| 524 | let shuttingDown = false; |
| 525 | const shutdown = (signal: NodeJS.Signals) => { |
| 526 | if (shuttingDown) return; |
| 527 | shuttingDown = true; |
| 528 | console.log(dim(`\n[ui] Shutting down (${signal})`)); |
| 529 | handle.detachSafetyNet(); |
| 530 | const finish = () => { |
| 531 | try { |
| 532 | handle.release(); |
| 533 | } finally { |
| 534 | process.exit(process.exitCode ?? 0); |
| 535 | } |
| 536 | }; |
| 537 | handle.drainUpgradeSockets(); |
| 538 | closeHttpServers(handle.httpServers).then(finish, finish); |
| 539 | setTimeout(finish, 2000).unref(); |
| 540 | }; |
| 541 | process.once('SIGINT', () => shutdown('SIGINT')); |
no test coverage detected