#!/usr/bin/env node import { startServer, previewOptions, DEFAULT_BOARD } from './serve.mjs'; import { busCaller } from './bus.mjs'; import { views } from '../../bus/src/views.mjs'; import { loadSystem, socketPath, REPO, rolesDir } from '../../cli/src/config.mjs'; import { configDir } from '../../business/src/business.mjs'; import { consoleReads, redactor } from './reads.mjs'; import { statSync } from 'node:fs'; import { isAbsolute } from 'node:path'; import { ID_PATTERN } from '../../business/src/vocabulary.mjs'; const args = process.argv.slice(2); const usage = 'node packages/webui/src/cli.mjs serve [--port N] [--checkout PATH] [--board http://127.0.0.1:7331] [--business ID] [--public-host NAME --auth-header HEADER --allow-user ID [--send]]'; try { if (args.length === 1 && ['--help', '-h'].includes(args[0])) { console.log(usage); process.exit(0); } if (args.shift() !== 'serve') throw new Error(usage); const options = { board: DEFAULT_BOARD, port: 7330 }, preview = {}; let sendOn = false; while (args.length) { const flag = args.shift(); // Row 76 (#1570): the one flag without a value. if (flag === '--send') { sendOn = true; continue; } const value = args.shift(); if (!value) throw new Error(`missing value for ${flag}`); if (flag === '--port') { if (!/^\d+$/.test(value) || Number(value) > 65535) throw new Error('port must be 0..65535'); options.port = Number(value); } else if (flag === '--board') options.board = value; // Row 77 (#1571): the canonical checkout the queue read runs from, when // serve runs from a pinned copy; everything else loads from the copy. else if (flag === '--checkout') { let dir = false; try { dir = isAbsolute(value) && statSync(value).isDirectory(); } catch { /* refused below */ } if (!dir) throw new Error('--checkout must be the absolute path of a directory'); options.checkout = value; } else if (flag === '--business') { if (!ID_PATTERN.test(value)) throw new Error('business must be a business id'); options.business = value; } // Row 74 (#1568): the preview mode, all three or none, checked before any config is read. else if (flag === '--public-host') preview.publicHost = value; else if (flag === '--auth-header') preview.authHeader = value; else if (flag === '--allow-user') preview.allowUser = value; else throw new Error(`unknown option: ${flag}`); } options.preview = previewOptions(preview); if (sendOn && !options.preview) throw new Error('--send needs the preview mode (--public-host, --auth-header and --allow-user)'); // The bus views (slice 1 S5) need the system config. Without it the // board still serves and the bus routes answer not-configured. // The Queue, Business and Settings reads (row 54) need no bus; without // the system config, Business can't resolve authority and Settings has // no notifier. A refusal printed here names no path (redactor). // --send without a bus refuses to start rather than serve a composer that can't send. let bus = null, send = null, busLine, system = null; try { system = loadSystem(); const { dataRoot } = system; const call = busCaller({ dataRoot, socket: socketPath(dataRoot), business: options.business ?? null }); bus = views({ call }); if (sendOn) send = args => call('message.send', args); busLine = `Bus: ${options.business ?? 'the running bus host\'s business'}`; } catch (err) { const why = redactor({ configDir: configDir() })(err.message); if (sendOn) throw new Error(`--send needs the bus configured (${why})`); busLine = `Bus: not configured (${why})`; } const reads = consoleReads({ root: REPO, queueRoot: options.checkout ?? REPO, system, configDir: configDir(), rolesDir: rolesDir(), business: options.business ?? null }); const server = await startServer({ board: options.board, port: options.port, bus, reads, preview: options.preview, send }); const previewLine = options.preview ? `\nPreview: https://${options.preview.host}/ through the proxy, signed-in user from ${options.preview.header}, ${send ? 'reads and human messages to roles (--send); one line per send below' : 'read-only'}` : ''; const queueLine = options.checkout ? '\nQueue: read from the --checkout directory' : ''; console.log(`Mosaic Console: http://127.0.0.1:${server.address().port}/\nBoard: ${options.board}\n${busLine}${queueLine}${previewLine}\nCtrl-C stops this server.`); for (const signal of ['SIGINT', 'SIGTERM']) process.once(signal, () => server.close()); } catch (err) { console.error(`refused: ${err.message}`); process.exitCode = 2; }