/* eslint-disable unicorn/no-abusive-eslint-disable -- server entry: argv/env side-effect imports + boot IIFE legitimately trip many rules; mirrors the framework's server/server.ts. */ /* eslint-disable */ //? Server entry. Parses positional argv first (so `config.ts` sees the //? port), loads env, runs side-effect imports for the major config //? registries, then hands off to `bootstrapLuckyStack` which auto-imports //? the `luckystack//` overlay folder and calls //? `createLuckyStackServer`. //? //? Argv shape: npm run server -- [port] //? Examples: //? npm run server (default preset, port 80) //? npm run server -- billing (one bundle, port 80) //? npm run server -- billing,vehicles 4001 (two bundles merged, port 4001) //? ───────── Datadog APM (optional, enable-later) ───────── //? Unlike Sentry / PostHog (wired from `luckystack/` overlays), Datadog can't be //? overlay-loaded: `dd-trace` MUST be the FIRST import in the process — before //? parseArgv and every framework module — because it patches Node core to //? instrument I/O. So you enable it by uncommenting here. To enable: //? 1. npm i dd-trace hot-shots //? 2. set DD_API_KEY (+ DD_SITE, DD_TRACE_AGENT_URL) in `.env.local` //? 3. uncomment the two lines below AND the registration block in the boot IIFE. // import tracer from 'dd-trace'; // tracer.init(); import '@luckystack/server/parseArgv'; import { loadEnvFiles } from '@luckystack/core'; loadEnvFiles(); //? Side-effect imports — populate the registries everything below reads from. //? config first (projectConfig), then the router topology (deploy.config + //? services.config). A base / single-instance scaffold PRUNES the two router //? imports (and their config files); `npx luckystack add router` / `remove router` //? re-add / drop them post-install. The pure `config.ports` import is always kept. import '../config'; import '../deploy.config'; import '../services.config'; import './utils/responseNormalizer'; import { ports } from '../config.ports'; import type { IncomingMessage, ServerResponse } from 'node:http'; import { bootstrapLuckyStack } from '@luckystack/server'; (async () => { //? Optional @luckystack/secret-manager (opt-in). Resolve `.env` pointers //? (NAME=BASE_V) against an external secret server before any framework //? code reads a secret. To enable: //? 1. npm i @luckystack/secret-manager //? 2. uncomment the `secretManager` slot in config.ts + set LUCKYSTACK_SECRET_MANAGER_URL //? 3. uncomment below. See docs/luckystack/ARCHITECTURE_SECRET_MANAGER.md. // const projectConfig = (await import('../config')).default; // if (projectConfig.secretManager?.url) { // const sm = await import('@luckystack/secret-manager'); // await sm.initSecretManager({ ...projectConfig.secretManager, source: 'remote' }); // } //? Datadog adapter registration — uncomment together with the dd-trace block //? at the top of this file. `tracer` is the dd-trace instance from there. // const { createDatadogAdapter, registerErrorTracker } = await import('@luckystack/error-tracking'); // const StatsD = (await import('hot-shots')).default; // registerErrorTracker(createDatadogAdapter({ tracer, statsd: new StatsD() })); //? Minimal static-file fallbacks. Most apps will swap these out for //? Vite-built static handlers in production; the server just needs SOME //? handler if you serve any static asset alongside `/api` and `/sync`. const noopServeFile = (_req: IncomingMessage, res: ServerResponse) => { res.writeHead(404, { 'Content-Type': 'text/plain' }); res.end('Not Found'); }; const noopServeFavicon = (res: ServerResponse) => { res.writeHead(204); res.end(); }; //? `loadGeneratedMaps` lets the framework wire production runtime maps //? without you having to maintain `server/prod/runtimeMaps.ts`. The first //? positional argv arg selects the preset(s) (comma-separated for multi- //? preset boots; defaults to `default`). Generated by //? `scripts/generateServerRequests.ts` at build time. const server = await bootstrapLuckyStack({ //? Single-instance backend port from config.ports.ts. Sits BELOW a positional //? argv port, so `npm run server -- ` still wins for a //? multi-instance boot behind the router. defaultPort: ports.backend, serveFile: noopServeFile, serveFavicon: noopServeFavicon, loadGeneratedMaps: (preset: string) => import(`./prod/generatedApis.${preset}`), }); await server.listen(); //? ── Post-listen work goes HERE, inside `afterListen` ────────────────── //? Anything that runs once the server is already accepting traffic — durable //? queue workers, warm-up passes, reconciliation jobs — belongs in this //? callback rather than after the `await` above. //? //? WHY: everything before `listen()` is genuinely fatal — a failure there //? means there is no server. Post-listen work is different: the server IS up //? and serving. A queue worker that cannot reach its database is a real //? problem, but killing a healthy process over it turns one broken dependency //? into a crash-loop (the supervisor restarts, the dependency is still down, //? repeat). `afterListen` logs the failure loudly and keeps serving. //? //? Need the old fail-fast behaviour for a specific task — say the process is //? useless without it — then throw inside the callback AND pass //? `{ fatal: true }`. //? //? Uncomment and fill in when you have post-listen work. It ships commented //? out on purpose: an empty callback trips `require-await` (async body with //? no await) or `no-empty-function`, both active here via //? `strictTypeChecked` + `stylisticTypeChecked`, so a live-but-empty example //? would hand every new project a lint error on day one. //? //? await server.afterListen(async () => { //? const { startWorkers } = await import('../src/jobs/workers'); //? await startWorkers(); //? }, { label: 'job workers' }); })().catch((err: unknown) => { //? Reached only for pre-listen failures: config, bootstrap, or the bind //? itself. By the time `afterListen` runs, the server is up and its failures //? are handled there — so this message is always literally true. console.error('[server] failed to start:', err); process.exit(1); });