91d86f9b21
The merge's "always pass adapter-resolved host/port to AppCLIEntry" made the adapter's 127.0.0.1/3080 shadow apps/cli/cordis.yml's webserver row — editing the yml port would have had no effect, a duplicated default. The adapter now assigns no host/port default: an absent --host/--port leaves the field undefined (WebInvocation.host?/port?), runWeb forwards each to AppCLIEntry only when present, and AppCLIEntry patches the webserver row only for an explicit flag. cordis.yml is the single source of the host/port default; the adapter still validates a flag when given. Removes the now-unused DEFAULT_WEB_PORT; LOOPBACK_HOST/ALL_INTERFACES_HOST stay as the allowed-value vocabulary (validation + the printed URL/LAN line).
48 lines
2.0 KiB
TypeScript
48 lines
2.0 KiB
TypeScript
/**
|
||
* `dsh web` — thin bin over the config-tree boot: run AppCLIEntry with the
|
||
* already-parsed host/port/dev, print the URL line, wire signals. All
|
||
* composition lives in cordis.yml; all boot glue lives in AppCLIEntry. The
|
||
* argument adapter validated host (loopback/all-interfaces) and port (0–65535).
|
||
*/
|
||
|
||
import { networkInterfaces } from 'node:os'
|
||
import { fileURLToPath } from 'node:url'
|
||
import { AppCLIEntry } from './app-cli-entry.ts'
|
||
import { ALL_INTERFACES_HOST, LOOPBACK_HOST } from './args.ts'
|
||
|
||
const CONFIG_PATH = fileURLToPath(new URL('../cordis.yml', import.meta.url))
|
||
|
||
/**
|
||
* Serve the browser UI from the shipped config tree. `host`/`port` are passed
|
||
* through only when the flag was given; absent, the `cordis.yml` value stands.
|
||
* @param host - the bind host ({@link LOOPBACK_HOST}/{@link ALL_INTERFACES_HOST}), or `undefined` to keep the config default.
|
||
* @param port - the listen port (`0` requests an OS-assigned port), or `undefined` to keep the config default.
|
||
* @param dev - mount the client HMR driver and watch plugin bundles for rebuilds.
|
||
*/
|
||
export async function runWeb(host: string | undefined, port: number | undefined, dev: boolean): Promise<void> {
|
||
const entry = new AppCLIEntry({
|
||
configPath: CONFIG_PATH,
|
||
dev,
|
||
...host !== undefined && { host },
|
||
...port !== undefined && { port },
|
||
})
|
||
const { ctx, port: boundPort } = await entry.run()
|
||
|
||
let exiting = false
|
||
const shutdown = (code: number): void => {
|
||
if (exiting) return
|
||
exiting = true
|
||
void Promise.resolve(ctx.fiber.dispose()).finally(() => { process.exit(code) })
|
||
}
|
||
|
||
const lanCandidate = host === ALL_INTERFACES_HOST
|
||
? Object.values(networkInterfaces()).flat()
|
||
.find(iface => iface !== undefined && iface.family === 'IPv4' && !iface.internal)
|
||
: undefined
|
||
const localUrl = `http://${LOOPBACK_HOST}:${boundPort}`
|
||
console.log(`dsh web: ${localUrl}${lanCandidate === undefined ? '' : ` (LAN: http://${lanCandidate.address}:${boundPort})`}`)
|
||
|
||
process.on('SIGTERM', () => { shutdown(0) })
|
||
process.on('SIGINT', () => { shutdown(130) })
|
||
}
|