// THE PORT DEFAULTS — the one copy. // // Every local server this repo starts has a default port, and a worktree adds // `index * OFFSET_STEP` to all of them (scripts/worktree.mjs, WORKTREES.md), so // the primary checkout keeps the numbers below and worktree #3 gets 33xx. // // Plain JS with JSDoc, like ytdlp/platformArgs.mjs, because the first reader is // scripts/worktree.mjs, which runs under bare `node`. TS callers (the CLI's // doctor, the playwright configs) import it as it is. // // WHO READS THE NUMBER, AND WHO CANNOT. `scripts/worktree.mjs` (the injector) // and `archilyzer doctor` import this table. Two kinds of place cannot, and // both are held to it by `ports.test.ts`, which reads them as text and fails // when one disagrees: // - a package.json script's shell default, `next dev --port ${EDITOR_PORT:-3001}`. // A script line is a shell command; it has no import. It keeps the default so // `pnpm --filter editor start` works with no wrapper at all, which is what a // primary checkout's operator types. // - the `--ports NAME:N` list a package's `e2e` script hands queue-lock.mjs. // Same reason, and the machine-global queue's argument syntax is shared with // checkouts on older code, so it does not change shape here. // Everything else — the playwright configs and the e2e helpers — reads it from // here or from the env the injector set. // // NOT HERE: the container's ports (docker-compose.yml, docker/entrypoint.sh, // docker/Caddyfile). They are the ports inside a container, where there is one // checkout and no worktree offset, and Caddy is the only thing that publishes. /** * @typedef {{ base: number, what: string }} PortDecl */ /** @type {Readonly>} */ export const PORTS = Object.freeze({ EDITOR_PORT: { base: 3001, what: "editor real dev/start (`pnpm dev:editor`)" }, PORT: { base: 3011, what: "editor test server + Playwright editor baseURL" }, EXPORT_PORT: { base: 3010, what: "export server launched by the editor e2e" }, EXPORT_DEV_PORT: { base: 3000, what: "export real dev (`pnpm dev:export`) and its built `out/` (`pnpm start:export`)" }, EXPORT_E2E_PORT: { base: 3020, what: "export's own Playwright suite" }, OLLAMA_STUB_PORT: { base: 11435, what: "digest-lane stub server in the editor e2e suite" }, HOMEPAGE_DEV_PORT: { base: 3030, what: "homepage real dev (`pnpm dev:homepage`)" }, HOMEPAGE_PORT: { base: 3031, what: "homepage static `serve out` (start:homepage)" }, HOMEPAGE_E2E_PORT: { base: 3040, what: "homepage's own Playwright suite" }, HUB_PORT: { base: 3041, what: "export's hub Playwright suite (e2e:hub)" }, REPORT_SITE_E2E_PORT: { base: 3042, what: "export's cited report-site Playwright suite (e2e:report)" }, UMTOOL_PORT: { base: 3050, what: "umtool real dev/start (`pnpm dev:umtool`)" }, UMTOOL_E2E_PORT: { base: 3051, what: "umtool's own Playwright suite" }, EDITOR_STUB_PORT: { base: 3052, what: "stub editor the umtool e2e suite fetches clips from" }, ORIGIN_B_PORT: { base: 4610, what: "export's two-origin suite: the member site (e2e:2origin)" }, HUB_A_PORT: { base: 4611, what: "export's two-origin suite: the hub (e2e:2origin)" }, }); /** Base ports (offset 0 == the primary checkout), name -> number. */ /** @type {Readonly>} */ export const PORT_BASES = Object.freeze( Object.fromEntries(Object.entries(PORTS).map(([k, v]) => [k, v.base])), ); /** A worktree's block is `index * OFFSET_STEP` above the bases. */ export const OFFSET_STEP = 100; /** * The port env map for a given offset, plus PLAYWRIGHT_BASE_URL (the editor * test server's URL). Does not consult process.env. * @param {number} offset * @returns {Record} */ export function portsForOffset(offset) { /** @type {Record} */ const env = {}; for (const [key, base] of Object.entries(PORT_BASES)) { env[key] = String(base + offset); } env.PLAYWRIGHT_BASE_URL = `http://localhost:${PORT_BASES.PORT + offset}`; return env; } /** * The port a process should use: the env's value when it is set (the injector * or the operator put it there), else the base. Throws for a name this table * does not declare and for a value that is not a port, so a typo cannot * quietly mean "undefined" or NaN. * @param {string} name * @param {Record} [env] * @returns {number} */ export function portFor(name, env = process.env) { const decl = PORTS[name]; if (!decl) throw new Error(`ports.mjs: no port named ${name}`); const raw = env[name]; if (raw != null && raw.trim() !== "") { const n = Number(raw); if (Number.isInteger(n) && n > 0 && n < 65536) return n; throw new Error(`ports.mjs: ${name}=${raw} is not a port`); } return decl.base; }