PORT is read in one place, and it has no default
officer-url.mjs is now the only file in the tree that touches process.env.PORT.
Twenty-two others read it and supplied their own default; a value with
twenty-two sources is not configuration, it is twenty-two things to keep in sync,
and they had already drifted three ways.
It throws when PORT is unset rather than guessing. A default only covers the case
where .env was never loaded — which is not a machine anyone wants running,
because POSTGRES_URL is missing in the same breath. What the default bought was a
process that starts, binds somewhere unexpected, and fails later for a reason
that does not name the cause. Same posture as jwt.ts with JWT_SECRET.
It is .mjs, not .ts, and that is the whole reason this could be one file. pm2
launches officer-pty with node (ecosystem.config.cjs) and everything else with
bun; node cannot import TypeScript, so a .ts module would have left the pty
sidecar holding the only surviving copy of the default — precisely the thing
being removed. allowJs is already on, so the TS callers still get types. Verified
both runtimes import it, and that PUBLIC_URL-style overrides still work.
It also exports API_URL and OFFICER_API_URL, because nineteen sidecars were
independently building `ws://127.0.0.1:${PORT}` and two more were building the
http form. Those are one listener described in two protocols — no sidecar binds
anything — so they belong beside the port rather than being rediscovered per
file.
server.tsx now takes PORT as a number, so Number(PORT) at the serve site is gone.
Not typechecked (empty node_modules, frozen installs). Every edited file parses
under `bun build --no-bundle`; node and bun both load the new module; the unset
and non-numeric paths were exercised; the pm2 profile still loads.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
// Where the app is, and the only place that knows.
|
||||
//
|
||||
// ── Why this file is .mjs ──
|
||||
//
|
||||
// Every process is bun except `officer-pty`, which pm2 launches with node (see
|
||||
// ecosystem.config.cjs). Node cannot import TypeScript, so a .ts module here would have left the pty
|
||||
// sidecar with its own copy — which is exactly the thing this file exists to end. Plain JS is
|
||||
// importable by both, and `allowJs` in tsconfig.json means the TS callers still get types.
|
||||
//
|
||||
// ── Why there is no default ──
|
||||
//
|
||||
// There were twenty-two, and they disagreed: 5000 in the app and nineteen sidecars, 9010 in
|
||||
// user-instance.ts, 9000 in .env.example. Each was defensible where it was written and none was
|
||||
// visible from the others.
|
||||
//
|
||||
// A default is a guess at a value that .env always supplies. The one case it covers — PORT genuinely
|
||||
// unset — is not a machine anyone wants running: it means .env was not loaded, so POSTGRES_URL is
|
||||
// missing too and nothing works anyway. What a default buys there is a process that starts, binds
|
||||
// somewhere unexpected and fails later for a reason that does not name the cause.
|
||||
//
|
||||
// So: no default anywhere, and this throws. Same posture as jwt.ts with JWT_SECRET.
|
||||
|
||||
const raw = process.env.PORT;
|
||||
|
||||
if (!raw) {
|
||||
throw new Error(
|
||||
[
|
||||
'PORT is not set.',
|
||||
'',
|
||||
'Officer reads it from .env, which bun auto-loads from the working directory. If this is a pm2',
|
||||
'process, the `cwd` pin in ecosystem.profile.cjs is what puts it there — a PORT this empty',
|
||||
'usually means .env was never loaded at all, and POSTGRES_URL is missing too.',
|
||||
'',
|
||||
'Set PORT in .env (officer-setup writes 9000), or start from the repo root.',
|
||||
].join('\n'),
|
||||
);
|
||||
}
|
||||
|
||||
const parsed = Number(raw);
|
||||
|
||||
if (!Number.isInteger(parsed) || parsed < 1 || parsed > 65535) {
|
||||
throw new Error(`PORT must be an integer between 1 and 65535, but it is ${JSON.stringify(raw)}`);
|
||||
}
|
||||
|
||||
/** The port the app binds. Everything else addresses it; nothing else binds. */
|
||||
export const PORT = parsed;
|
||||
|
||||
// The app serves HTTP and WebSocket on ONE listener, so these are the same port in two protocols.
|
||||
// Sidecars append their own path — `/api/sidecar/register` for the registration socket.
|
||||
//
|
||||
// The env overrides are kept because they are explicit escape hatches rather than defaults: nothing
|
||||
// sets either today, and a value that is present is a deliberate act rather than a guess.
|
||||
export const API_URL = process.env.API_URL ?? `ws://127.0.0.1:${PORT}`;
|
||||
export const OFFICER_API_URL = process.env.OFFICER_API_URL ?? `http://127.0.0.1:${PORT}`;
|
||||
Reference in New Issue
Block a user