@warlock.js/core 5.7.0 → 5.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/esm/cli/cli-commands.manager.mjs +2 -2
  3. package/esm/cli/cli-commands.manager.mjs.map +1 -1
  4. package/esm/cli/commands/doctor/checks/index.mjs +2 -0
  5. package/esm/cli/commands/doctor/checks/index.mjs.map +1 -1
  6. package/esm/cli/commands/doctor/checks/jwt-secret.check.mjs +55 -0
  7. package/esm/cli/commands/doctor/checks/jwt-secret.check.mjs.map +1 -0
  8. package/esm/cli/commands/generate/generators/controller.generator.mjs +4 -4
  9. package/esm/cli/commands/generate/generators/controller.generator.mjs.map +1 -1
  10. package/esm/cli/commands/generate/generators/migration.generator.mjs.map +1 -1
  11. package/esm/cli/commands/generate/generators/model.generator.mjs +4 -4
  12. package/esm/cli/commands/generate/generators/model.generator.mjs.map +1 -1
  13. package/esm/cli/commands/generate/generators/module.generator.mjs.map +1 -1
  14. package/esm/cli/commands/generate/generators/repository.generator.mjs +4 -4
  15. package/esm/cli/commands/generate/generators/repository.generator.mjs.map +1 -1
  16. package/esm/cli/commands/generate/generators/resource.generator.mjs +4 -4
  17. package/esm/cli/commands/generate/generators/resource.generator.mjs.map +1 -1
  18. package/esm/cli/commands/generate/generators/service.generator.mjs +4 -4
  19. package/esm/cli/commands/generate/generators/service.generator.mjs.map +1 -1
  20. package/esm/cli/commands/generate/templates/stubs.mjs +6 -3
  21. package/esm/cli/commands/generate/templates/stubs.mjs.map +1 -1
  22. package/esm/dev-server/files-orchestrator.mjs +1 -0
  23. package/esm/dev-server/files-orchestrator.mjs.map +1 -1
  24. package/esm/generations/stubs.mjs +6 -6
  25. package/esm/generations/stubs.mjs.map +1 -1
  26. package/esm/http/boot-port-preflight.d.mts +25 -1
  27. package/esm/http/boot-port-preflight.d.mts.map +1 -1
  28. package/esm/http/boot-port-preflight.mjs +51 -2
  29. package/esm/http/boot-port-preflight.mjs.map +1 -1
  30. package/esm/http/index.d.mts +1 -1
  31. package/esm/http/index.mjs +1 -1
  32. package/esm/index.d.mts +2 -2
  33. package/esm/index.mjs +2 -2
  34. package/esm/utils/load-environment.mjs +4 -1
  35. package/esm/utils/load-environment.mjs.map +1 -1
  36. package/esm/utils/recorded-environment-overrides.mjs +41 -0
  37. package/esm/utils/recorded-environment-overrides.mjs.map +1 -0
  38. package/package.json +12 -12
@@ -1,4 +1,18 @@
1
1
  //#region ../core/src/http/boot-port-preflight.d.ts
2
+ /**
3
+ * Whether the HTTP-port preflight should run for a command's `connectors`
4
+ * preload.
5
+ *
6
+ * The bind-and-release probe is only meaningful when this boot will actually
7
+ * start the http connector: `true` (a full boot — http starts in its late
8
+ * phase) or an explicit list that names `"http"`. A scoped list WITHOUT http —
9
+ * e.g. `warlock seed`'s `["database", "cache", "logger"]`, or `migrate` — never
10
+ * binds the port, so probing it is pointless and actively harmful: the probe
11
+ * collides with a dev server already listening on that port (and inherits a
12
+ * stray `HTTP_PORT`), so a read-only data command fails on a port it was never
13
+ * going to use. Finding f9ace89e.
14
+ */
15
+ declare function shouldPreflightHttpPort(connectors: readonly string[] | true): boolean;
2
16
  /**
3
17
  * Probe the port declared in `src/config/http.ts`, if there is one.
4
18
  *
@@ -41,6 +55,16 @@ declare function assertConfiguredHttpPortIsFree(): Promise<void>;
41
55
  * would otherwise have booted. `HttpConnector` still runs the real check.
42
56
  */
43
57
  declare function preflightConfiguredHttpPort(): Promise<void>;
58
+ /**
59
+ * The remedy lines, chosen by where `http.port` actually came from.
60
+ *
61
+ * The provenance is the detector's own finding from env-load, carried here via
62
+ * {@link getRecordedEnvironmentOverride} rather than recomputed — so this asks
63
+ * "did an ambient HTTP_PORT beat .env, and is that the value now colliding?"
64
+ * and, only when the answer is yes, replaces the config-edit advice with the
65
+ * one that will actually free the port.
66
+ */
67
+ declare function remedyLines(collidingPort: number): string[];
44
68
  //#endregion
45
- export { assertConfiguredHttpPortIsFree, preflightConfiguredHttpPort };
69
+ export { assertConfiguredHttpPortIsFree, preflightConfiguredHttpPort, remedyLines, shouldPreflightHttpPort };
46
70
  //# sourceMappingURL=boot-port-preflight.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"boot-port-preflight.d.mts","names":[],"sources":["../../../../../../../core/src/http/boot-port-preflight.ts"],"mappings":";;AAiCA;;;;AAA+D;AA4D/D;;;;AAA4D;;;;iBA5DtC,8BAAA,IAAkC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA4DzC,2BAAA,IAA+B,OAAO"}
1
+ {"version":3,"file":"boot-port-preflight.d.mts","names":[],"sources":["../../../../../../../core/src/http/boot-port-preflight.ts"],"mappings":";;AAyCA;;;;AAA4E;AAkB5E;;;;AAA+D;AA4D/D;;iBA9EgB,uBAAA,CAAwB,UAAoC;;AA8EhB;AAwD5D;;;;AAAiD;;;;;;;;iBApH3B,8BAAA,IAAkC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA4DzC,2BAAA,IAA+B,OAAO;;;;;;;;;;iBAwD5C,WAAA,CAAY,aAAqB"}
@@ -1,9 +1,17 @@
1
+ import { getRecordedEnvironmentOverride } from "../utils/recorded-environment-overrides.mjs";
1
2
  import { PortInUseError, assertPortIsAvailable } from "./port-preflight.mjs";
2
3
  import { resolveBindPort } from "./resolve-bind-port.mjs";
3
4
  import config from "@mongez/config";
4
5
 
5
6
  //#region ../core/src/http/boot-port-preflight.ts
6
7
  /**
8
+ * The env var whose value `src/config/http.ts` reads for `http.port`
9
+ * (`port: env("HTTP_PORT", …)`). When an ambient value for it beats `.env`,
10
+ * `http.port` resolves to the ambient value and editing the config literal
11
+ * changes nothing — the whole point of the provenance branch below.
12
+ */
13
+ const HTTP_PORT_ENV_KEY = "HTTP_PORT";
14
+ /**
7
15
  * Mirrors `dev-server/supervisor.ts`'s `BOOT_PRECONDITION_EXIT_CODE`. Kept as
8
16
  * its own literal here (rather than imported) so this file — reached by both
9
17
  * the dev-preload path and the generated, standalone production entry — never
@@ -17,6 +25,22 @@ const BOOT_PRECONDITION_EXIT_CODE = 78;
17
25
  */
18
26
  const DEFAULT_BIND_HOST = "localhost";
19
27
  /**
28
+ * Whether the HTTP-port preflight should run for a command's `connectors`
29
+ * preload.
30
+ *
31
+ * The bind-and-release probe is only meaningful when this boot will actually
32
+ * start the http connector: `true` (a full boot — http starts in its late
33
+ * phase) or an explicit list that names `"http"`. A scoped list WITHOUT http —
34
+ * e.g. `warlock seed`'s `["database", "cache", "logger"]`, or `migrate` — never
35
+ * binds the port, so probing it is pointless and actively harmful: the probe
36
+ * collides with a dev server already listening on that port (and inherits a
37
+ * stray `HTTP_PORT`), so a read-only data command fails on a port it was never
38
+ * going to use. Finding f9ace89e.
39
+ */
40
+ function shouldPreflightHttpPort(connectors) {
41
+ return connectors === true || connectors.includes("http");
42
+ }
43
+ /**
20
44
  * Probe the port declared in `src/config/http.ts`, if there is one.
21
45
  *
22
46
  * Reads config rather than taking arguments because the only honest answer to
@@ -82,6 +106,13 @@ async function preflightConfiguredHttpPort() {
82
106
  * grep for, the port and host by name, and the command that names the process
83
107
  * holding it — the supervisor cannot discover the owning PID for them, but it
84
108
  * can hand them the one line that will.
109
+ *
110
+ * The remedy is chosen by PROVENANCE, never appended as a menu. When the
111
+ * colliding port came from an ambient `HTTP_PORT` that beat `.env` (the exact
112
+ * two-rebuild trap of finding 8782b840), "edit src/config/http.ts and rebuild"
113
+ * is actively wrong — the env var wins, so the rebuild changes nothing. In that
114
+ * case name the variable and give the remedy that works (unset it); only when
115
+ * the port genuinely came from config do we point at the config file.
85
116
  */
86
117
  function reportPortInUse(error) {
87
118
  const ownerCommand = process.platform === "win32" ? `netstat -ano | findstr :${error.port}` : `lsof -i :${error.port}`;
@@ -90,12 +121,30 @@ function reportPortInUse(error) {
90
121
  ` ✖ EADDRINUSE: port ${error.port} is already in use on ${error.host}`,
91
122
  ` the application cannot start because something else is already listening there.`,
92
123
  ` find the owning process: ${ownerCommand}`,
93
- ` then stop it, or change http.port in src/config/http.ts and rebuild.`,
124
+ ...remedyLines(error.port),
94
125
  ""
95
126
  ];
96
127
  for (const line of lines) console.error(line);
97
128
  }
129
+ /**
130
+ * The remedy lines, chosen by where `http.port` actually came from.
131
+ *
132
+ * The provenance is the detector's own finding from env-load, carried here via
133
+ * {@link getRecordedEnvironmentOverride} rather than recomputed — so this asks
134
+ * "did an ambient HTTP_PORT beat .env, and is that the value now colliding?"
135
+ * and, only when the answer is yes, replaces the config-edit advice with the
136
+ * one that will actually free the port.
137
+ */
138
+ function remedyLines(collidingPort) {
139
+ const httpPortOverride = getRecordedEnvironmentOverride(HTTP_PORT_ENV_KEY);
140
+ if (httpPortOverride !== void 0 && Number(String(httpPortOverride.effectiveValue).trim()) === collidingPort) return [
141
+ ` this port came from the ${HTTP_PORT_ENV_KEY} environment variable (=${httpPortOverride.effectiveValue}), which overrode .env's ${HTTP_PORT_ENV_KEY}=${httpPortOverride.fileValue}.`,
142
+ ` editing src/config/http.ts and rebuilding will NOT change it — the environment variable wins.`,
143
+ ` then stop the process above, or unset ${HTTP_PORT_ENV_KEY} and run again.`
144
+ ];
145
+ return [` then stop it, or change http.port in src/config/http.ts and rebuild.`];
146
+ }
98
147
 
99
148
  //#endregion
100
- export { assertConfiguredHttpPortIsFree, preflightConfiguredHttpPort };
149
+ export { assertConfiguredHttpPortIsFree, preflightConfiguredHttpPort, remedyLines, shouldPreflightHttpPort };
101
150
  //# sourceMappingURL=boot-port-preflight.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"boot-port-preflight.mjs","names":[],"sources":["../../../../../../../core/src/http/boot-port-preflight.ts"],"sourcesContent":["import config from \"@mongez/config\";\nimport { assertPortIsAvailable, PortInUseError } from \"./port-preflight\";\nimport { resolveBindPort } from \"./resolve-bind-port\";\n\n/**\n * Mirrors `dev-server/supervisor.ts`'s `BOOT_PRECONDITION_EXIT_CODE`. Kept as\n * its own literal here (rather than imported) so this file — reached by both\n * the dev-preload path and the generated, standalone production entry — never\n * pulls dev-server's supervisor module (child_process, TTY handling, ...)\n * into a production bundle that has no supervisor at all. EX_CONFIG.\n */\nconst BOOT_PRECONDITION_EXIT_CODE = 78;\n\n/**\n * Host used when `http.host` is unset — the same default `HttpConnector`\n * binds with, so the probe tests the address the server will actually take.\n */\nconst DEFAULT_BIND_HOST = \"localhost\";\n\n/**\n * Probe the port declared in `src/config/http.ts`, if there is one.\n *\n * Reads config rather than taking arguments because the only honest answer to\n * \"which port will this boot bind\" lives in the loaded config, and the callers\n * that need this check (the generated production entry) run before anything\n * that could hand it over.\n *\n * A no-op for an app with no `http` config, or an `http.port` that is not a\n * usable port number — those apps never bind, so there is nothing to preflight\n * and nothing to fail on.\n *\n * @throws {PortInUseError} when the configured port is already taken.\n */\nexport async function assertConfiguredHttpPortIsFree(): Promise<void> {\n const httpConfig = config.get(\"http\");\n\n if (!httpConfig) {\n return;\n }\n\n // This runs BEFORE the connectors in the generated production entry, and\n // `preflightConfiguredHttpPort()` below only exits on `PortInUseError` —\n // any other throw is swallowed there. So a bad port must not throw HERE:\n // it stays a no-op, exactly like the previous hand-rolled coercion, and\n // `HttpConnector` (which now also calls `resolveBindPort`) is the one that\n // reports it, a moment later, with a channel a developer will actually see.\n let port: number;\n\n try {\n port = resolveBindPort(httpConfig.port);\n } catch {\n return;\n }\n\n // Port 0 asks the OS to pick a free one, so there is nothing to preflight:\n // probing 0 binds SOME unrelated ephemeral port and \"passes\" without proving\n // anything about the port this boot will end up on. The hand-rolled coercion\n // this replaced skipped 0 by rejecting `port <= 0`; `resolveBindPort` accepts\n // 0 as a legitimate TCP value, so the skip has to be stated here instead of\n // falling out of a range check.\n if (port === 0) {\n return;\n }\n\n await assertPortIsAvailable(port, httpConfig.host || DEFAULT_BIND_HOST);\n}\n\n/**\n * Fail the boot immediately, and by name, when the http port is already taken.\n *\n * Called from the generated production entry BEFORE the early-phase connectors,\n * because the port is the cheapest thing in the boot to check and the one most\n * likely to be wrong. `HttpConnector` is a LATE-phase connector, so its own\n * preflight is not reached until the database has connected, the cache has\n * connected and every module of app code has been imported — 7-13 seconds of\n * work, all of it discarded, before anyone learns the port was busy. This check\n * costs a bind-and-release on a socket that is never served.\n *\n * It does NOT replace `HttpConnector`'s preflight: a port can be taken in the\n * seconds between the two, and only the connector's check sits immediately\n * before the real `listen()`. This one exists to make the common case fast and\n * legible.\n *\n * Reports through `console.error` and NOT through `log.fatal`: the logger has\n * no channels yet at this point in the boot (the logger connector is part of\n * the early phase this runs ahead of), so a logged message here would reach\n * nobody. stderr always reaches the terminal, and `warlock start` forwards the\n * child's stderr verbatim.\n *\n * A probe that fails for any OTHER reason is deliberately swallowed: this is a\n * fast-fail convenience, and it must never be the thing that stops an app which\n * would otherwise have booted. `HttpConnector` still runs the real check.\n */\nexport async function preflightConfiguredHttpPort(): Promise<void> {\n try {\n await assertConfiguredHttpPortIsFree();\n } catch (error) {\n if (!(error instanceof PortInUseError)) {\n return;\n }\n\n reportPortInUse(error);\n\n process.exit(BOOT_PRECONDITION_EXIT_CODE);\n }\n}\n\n/**\n * Print the collision the way the developer needs to read it: the errno they\n * grep for, the port and host by name, and the command that names the process\n * holding it — the supervisor cannot discover the owning PID for them, but it\n * can hand them the one line that will.\n */\nfunction reportPortInUse(error: PortInUseError): void {\n const ownerCommand =\n process.platform === \"win32\"\n ? `netstat -ano | findstr :${error.port}`\n : `lsof -i :${error.port}`;\n\n const lines = [\n \"\",\n ` ✖ EADDRINUSE: port ${error.port} is already in use on ${error.host}`,\n ` the application cannot start because something else is already listening there.`,\n ` find the owning process: ${ownerCommand}`,\n ` then stop it, or change http.port in src/config/http.ts and rebuild.`,\n \"\",\n ];\n\n for (const line of lines) {\n console.error(line);\n }\n}\n"],"mappings":";;;;;;;;;;;;AAWA,MAAM,8BAA8B;;;;;AAMpC,MAAM,oBAAoB;;;;;;;;;;;;;;;AAgB1B,eAAsB,iCAAgD;CACpE,MAAM,aAAa,OAAO,IAAI,MAAM;CAEpC,IAAI,CAAC,YACH;CASF,IAAI;CAEJ,IAAI;EACF,OAAO,gBAAgB,WAAW,IAAI;CACxC,QAAQ;EACN;CACF;CAQA,IAAI,SAAS,GACX;CAGF,MAAM,sBAAsB,MAAM,WAAW,QAAQ,iBAAiB;AACxE;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,eAAsB,8BAA6C;CACjE,IAAI;EACF,MAAM,+BAA+B;CACvC,SAAS,OAAO;EACd,IAAI,EAAE,iBAAiB,iBACrB;EAGF,gBAAgB,KAAK;EAErB,QAAQ,KAAK,2BAA2B;CAC1C;AACF;;;;;;;AAQA,SAAS,gBAAgB,OAA6B;CACpD,MAAM,eACJ,QAAQ,aAAa,UACjB,2BAA2B,MAAM,SACjC,YAAY,MAAM;CAExB,MAAM,QAAQ;EACZ;EACA,wBAAwB,MAAM,KAAK,wBAAwB,MAAM;EACjE;EACA,8BAA8B;EAC9B;EACA;CACF;CAEA,KAAK,MAAM,QAAQ,OACjB,QAAQ,MAAM,IAAI;AAEtB"}
1
+ {"version":3,"file":"boot-port-preflight.mjs","names":[],"sources":["../../../../../../../core/src/http/boot-port-preflight.ts"],"sourcesContent":["import config from \"@mongez/config\";\nimport { getRecordedEnvironmentOverride } from \"../utils/recorded-environment-overrides\";\nimport { assertPortIsAvailable, PortInUseError } from \"./port-preflight\";\nimport { resolveBindPort } from \"./resolve-bind-port\";\n\n/**\n * The env var whose value `src/config/http.ts` reads for `http.port`\n * (`port: env(\"HTTP_PORT\", …)`). When an ambient value for it beats `.env`,\n * `http.port` resolves to the ambient value and editing the config literal\n * changes nothing — the whole point of the provenance branch below.\n */\nconst HTTP_PORT_ENV_KEY = \"HTTP_PORT\";\n\n/**\n * Mirrors `dev-server/supervisor.ts`'s `BOOT_PRECONDITION_EXIT_CODE`. Kept as\n * its own literal here (rather than imported) so this file — reached by both\n * the dev-preload path and the generated, standalone production entry — never\n * pulls dev-server's supervisor module (child_process, TTY handling, ...)\n * into a production bundle that has no supervisor at all. EX_CONFIG.\n */\nconst BOOT_PRECONDITION_EXIT_CODE = 78;\n\n/**\n * Host used when `http.host` is unset — the same default `HttpConnector`\n * binds with, so the probe tests the address the server will actually take.\n */\nconst DEFAULT_BIND_HOST = \"localhost\";\n\n/**\n * Whether the HTTP-port preflight should run for a command's `connectors`\n * preload.\n *\n * The bind-and-release probe is only meaningful when this boot will actually\n * start the http connector: `true` (a full boot — http starts in its late\n * phase) or an explicit list that names `\"http\"`. A scoped list WITHOUT http —\n * e.g. `warlock seed`'s `[\"database\", \"cache\", \"logger\"]`, or `migrate` — never\n * binds the port, so probing it is pointless and actively harmful: the probe\n * collides with a dev server already listening on that port (and inherits a\n * stray `HTTP_PORT`), so a read-only data command fails on a port it was never\n * going to use. Finding f9ace89e.\n */\nexport function shouldPreflightHttpPort(connectors: readonly string[] | true): boolean {\n return connectors === true || connectors.includes(\"http\");\n}\n\n/**\n * Probe the port declared in `src/config/http.ts`, if there is one.\n *\n * Reads config rather than taking arguments because the only honest answer to\n * \"which port will this boot bind\" lives in the loaded config, and the callers\n * that need this check (the generated production entry) run before anything\n * that could hand it over.\n *\n * A no-op for an app with no `http` config, or an `http.port` that is not a\n * usable port number — those apps never bind, so there is nothing to preflight\n * and nothing to fail on.\n *\n * @throws {PortInUseError} when the configured port is already taken.\n */\nexport async function assertConfiguredHttpPortIsFree(): Promise<void> {\n const httpConfig = config.get(\"http\");\n\n if (!httpConfig) {\n return;\n }\n\n // This runs BEFORE the connectors in the generated production entry, and\n // `preflightConfiguredHttpPort()` below only exits on `PortInUseError` —\n // any other throw is swallowed there. So a bad port must not throw HERE:\n // it stays a no-op, exactly like the previous hand-rolled coercion, and\n // `HttpConnector` (which now also calls `resolveBindPort`) is the one that\n // reports it, a moment later, with a channel a developer will actually see.\n let port: number;\n\n try {\n port = resolveBindPort(httpConfig.port);\n } catch {\n return;\n }\n\n // Port 0 asks the OS to pick a free one, so there is nothing to preflight:\n // probing 0 binds SOME unrelated ephemeral port and \"passes\" without proving\n // anything about the port this boot will end up on. The hand-rolled coercion\n // this replaced skipped 0 by rejecting `port <= 0`; `resolveBindPort` accepts\n // 0 as a legitimate TCP value, so the skip has to be stated here instead of\n // falling out of a range check.\n if (port === 0) {\n return;\n }\n\n await assertPortIsAvailable(port, httpConfig.host || DEFAULT_BIND_HOST);\n}\n\n/**\n * Fail the boot immediately, and by name, when the http port is already taken.\n *\n * Called from the generated production entry BEFORE the early-phase connectors,\n * because the port is the cheapest thing in the boot to check and the one most\n * likely to be wrong. `HttpConnector` is a LATE-phase connector, so its own\n * preflight is not reached until the database has connected, the cache has\n * connected and every module of app code has been imported — 7-13 seconds of\n * work, all of it discarded, before anyone learns the port was busy. This check\n * costs a bind-and-release on a socket that is never served.\n *\n * It does NOT replace `HttpConnector`'s preflight: a port can be taken in the\n * seconds between the two, and only the connector's check sits immediately\n * before the real `listen()`. This one exists to make the common case fast and\n * legible.\n *\n * Reports through `console.error` and NOT through `log.fatal`: the logger has\n * no channels yet at this point in the boot (the logger connector is part of\n * the early phase this runs ahead of), so a logged message here would reach\n * nobody. stderr always reaches the terminal, and `warlock start` forwards the\n * child's stderr verbatim.\n *\n * A probe that fails for any OTHER reason is deliberately swallowed: this is a\n * fast-fail convenience, and it must never be the thing that stops an app which\n * would otherwise have booted. `HttpConnector` still runs the real check.\n */\nexport async function preflightConfiguredHttpPort(): Promise<void> {\n try {\n await assertConfiguredHttpPortIsFree();\n } catch (error) {\n if (!(error instanceof PortInUseError)) {\n return;\n }\n\n reportPortInUse(error);\n\n process.exit(BOOT_PRECONDITION_EXIT_CODE);\n }\n}\n\n/**\n * Print the collision the way the developer needs to read it: the errno they\n * grep for, the port and host by name, and the command that names the process\n * holding it — the supervisor cannot discover the owning PID for them, but it\n * can hand them the one line that will.\n *\n * The remedy is chosen by PROVENANCE, never appended as a menu. When the\n * colliding port came from an ambient `HTTP_PORT` that beat `.env` (the exact\n * two-rebuild trap of finding 8782b840), \"edit src/config/http.ts and rebuild\"\n * is actively wrong — the env var wins, so the rebuild changes nothing. In that\n * case name the variable and give the remedy that works (unset it); only when\n * the port genuinely came from config do we point at the config file.\n */\nfunction reportPortInUse(error: PortInUseError): void {\n const ownerCommand =\n process.platform === \"win32\"\n ? `netstat -ano | findstr :${error.port}`\n : `lsof -i :${error.port}`;\n\n const lines = [\n \"\",\n ` ✖ EADDRINUSE: port ${error.port} is already in use on ${error.host}`,\n ` the application cannot start because something else is already listening there.`,\n ` find the owning process: ${ownerCommand}`,\n ...remedyLines(error.port),\n \"\",\n ];\n\n for (const line of lines) {\n console.error(line);\n }\n}\n\n/**\n * The remedy lines, chosen by where `http.port` actually came from.\n *\n * The provenance is the detector's own finding from env-load, carried here via\n * {@link getRecordedEnvironmentOverride} rather than recomputed — so this asks\n * \"did an ambient HTTP_PORT beat .env, and is that the value now colliding?\"\n * and, only when the answer is yes, replaces the config-edit advice with the\n * one that will actually free the port.\n */\nexport function remedyLines(collidingPort: number): string[] {\n const httpPortOverride = getRecordedEnvironmentOverride(HTTP_PORT_ENV_KEY);\n\n // Only claim the environment is the source when the ambient value is the one\n // that is actually colliding — an HTTP_PORT override of an UNRELATED port\n // must not misdirect the reader away from their real (config) port.\n const cameFromEnvironment =\n httpPortOverride !== undefined &&\n Number(String(httpPortOverride.effectiveValue).trim()) === collidingPort;\n\n if (cameFromEnvironment) {\n return [\n ` this port came from the ${HTTP_PORT_ENV_KEY} environment variable (=${httpPortOverride!.effectiveValue}), which overrode .env's ${HTTP_PORT_ENV_KEY}=${httpPortOverride!.fileValue}.`,\n ` editing src/config/http.ts and rebuilding will NOT change it — the environment variable wins.`,\n ` then stop the process above, or unset ${HTTP_PORT_ENV_KEY} and run again.`,\n ];\n }\n\n return [` then stop it, or change http.port in src/config/http.ts and rebuild.`];\n}\n"],"mappings":";;;;;;;;;;;;AAWA,MAAM,oBAAoB;;;;;;;;AAS1B,MAAM,8BAA8B;;;;;AAMpC,MAAM,oBAAoB;;;;;;;;;;;;;;AAe1B,SAAgB,wBAAwB,YAA+C;CACrF,OAAO,eAAe,QAAQ,WAAW,SAAS,MAAM;AAC1D;;;;;;;;;;;;;;;AAgBA,eAAsB,iCAAgD;CACpE,MAAM,aAAa,OAAO,IAAI,MAAM;CAEpC,IAAI,CAAC,YACH;CASF,IAAI;CAEJ,IAAI;EACF,OAAO,gBAAgB,WAAW,IAAI;CACxC,QAAQ;EACN;CACF;CAQA,IAAI,SAAS,GACX;CAGF,MAAM,sBAAsB,MAAM,WAAW,QAAQ,iBAAiB;AACxE;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,eAAsB,8BAA6C;CACjE,IAAI;EACF,MAAM,+BAA+B;CACvC,SAAS,OAAO;EACd,IAAI,EAAE,iBAAiB,iBACrB;EAGF,gBAAgB,KAAK;EAErB,QAAQ,KAAK,2BAA2B;CAC1C;AACF;;;;;;;;;;;;;;AAeA,SAAS,gBAAgB,OAA6B;CACpD,MAAM,eACJ,QAAQ,aAAa,UACjB,2BAA2B,MAAM,SACjC,YAAY,MAAM;CAExB,MAAM,QAAQ;EACZ;EACA,wBAAwB,MAAM,KAAK,wBAAwB,MAAM;EACjE;EACA,8BAA8B;EAC9B,GAAG,YAAY,MAAM,IAAI;EACzB;CACF;CAEA,KAAK,MAAM,QAAQ,OACjB,QAAQ,MAAM,IAAI;AAEtB;;;;;;;;;;AAWA,SAAgB,YAAY,eAAiC;CAC3D,MAAM,mBAAmB,+BAA+B,iBAAiB;CASzE,IAHE,qBAAqB,UACrB,OAAO,OAAO,iBAAiB,cAAc,CAAC,CAAC,KAAK,CAAC,MAAM,eAG3D,OAAO;EACL,6BAA6B,kBAAkB,0BAA0B,iBAAkB,eAAe,2BAA2B,kBAAkB,GAAG,iBAAkB,UAAU;EACtL;EACA,2CAA2C,kBAAkB;CAC/D;CAGF,OAAO,CAAC,wEAAwE;AAClF"}
@@ -25,7 +25,7 @@ import { buildIdempotencyCacheKey, hashBody, isValidIdempotencyKey } from "./mid
25
25
  import { parseSize } from "./middleware/utils/parse-size.mjs";
26
26
  import { ClosableServer, FastifyInstance, closeServerWithTimeout, getHttpServer, startHttpServer } from "./server.mjs";
27
27
  import { registerHttpPlugins } from "./plugins.mjs";
28
- import { assertConfiguredHttpPortIsFree, preflightConfiguredHttpPort } from "./boot-port-preflight.mjs";
28
+ import { assertConfiguredHttpPortIsFree, preflightConfiguredHttpPort, remedyLines, shouldPreflightHttpPort } from "./boot-port-preflight.mjs";
29
29
  import { PortInUseError, assertPortIsAvailable, isPortAvailable } from "./port-preflight.mjs";
30
30
  import { HttpReadyReport, getHttpReadyReport, resetHttpReadyReport, setHttpReadyReport } from "./ready-report.mjs";
31
31
  import { RequestContextStore, requestContext, useCurrentUser, useRequest, useRequestStore } from "./context/request-context.mjs";
@@ -20,7 +20,7 @@ import { parseSize } from "./middleware/utils/parse-size.mjs";
20
20
  import { middleware } from "./middleware/middleware-list.mjs";
21
21
  import "./middleware/index.mjs";
22
22
  import { PortInUseError, assertPortIsAvailable, isPortAvailable } from "./port-preflight.mjs";
23
- import { assertConfiguredHttpPortIsFree, preflightConfiguredHttpPort } from "./boot-port-preflight.mjs";
23
+ import { assertConfiguredHttpPortIsFree, preflightConfiguredHttpPort, remedyLines, shouldPreflightHttpPort } from "./boot-port-preflight.mjs";
24
24
  import { getHttpReadyReport, resetHttpReadyReport, setHttpReadyReport } from "./ready-report.mjs";
25
25
  import { requestMemo } from "./context/request-memo.mjs";
26
26
 
package/esm/index.d.mts CHANGED
@@ -43,7 +43,7 @@ import { buildIdempotencyCacheKey, hashBody, isValidIdempotencyKey } from "./htt
43
43
  import { parseSize } from "./http/middleware/utils/parse-size.mjs";
44
44
  import { ClosableServer, FastifyInstance, closeServerWithTimeout, getHttpServer, startHttpServer } from "./http/server.mjs";
45
45
  import { registerHttpPlugins } from "./http/plugins.mjs";
46
- import { assertConfiguredHttpPortIsFree, preflightConfiguredHttpPort } from "./http/boot-port-preflight.mjs";
46
+ import { assertConfiguredHttpPortIsFree, preflightConfiguredHttpPort, remedyLines, shouldPreflightHttpPort } from "./http/boot-port-preflight.mjs";
47
47
  import { PortInUseError, assertPortIsAvailable, isPortAvailable } from "./http/port-preflight.mjs";
48
48
  import { HttpReadyReport, getHttpReadyReport, resetHttpReadyReport, setHttpReadyReport } from "./http/ready-report.mjs";
49
49
  import { RequestContextStore, requestContext, useCurrentUser, useRequest, useRequestStore } from "./http/context/request-context.mjs";
@@ -161,7 +161,7 @@ import { WarlockConfigManager, isUnknownTsExtensionError, warlockConfigManager }
161
161
  import { env } from "@mongez/dotenv";
162
162
  import { colors } from "@mongez/copper";
163
163
  export * from "@mongez/localization";
164
- export { $registerUseCase, $unregisterUseCase, AccessConnector, AggregateExpressionInput, AggregateExpressions, AiConnector, AllRepositoryOptions, AppConfigurations, Application, BOOT_SIGNAL_ENV_KEY, BOOT_SIGNAL_VERSION, BadRequestError, BadSchemaUseCaseError, BaseConnector, type BenchmarkChannel, type BenchmarkConfigurations, type BenchmarkErrorResult, type BenchmarkOptions, BenchmarkProfiler, type BenchmarkProfilerOptions, type BenchmarkResult, BenchmarkSnapshots, type BenchmarkSnapshotsOptions, type BenchmarkStats, type BenchmarkSuccessResult, BootContext, BootListener, BootSignal, BootSignalType, BootValidator, CLICommand, type CLICommandAction, type CLICommandOption, type CLICommandOptions, type CLICommandPreload, type CLICommandSource, CacheConnector, type CacheMiddlewareOptions, CachedRepositoryOptions, type CapturedMail, CascadeAdapter, CascadeQueryBuilder, ChunkCallback, ClosableServer, CloudDriver, CloudStorageDriverContract, CloudStorageDriverOptions, CloudStorageFileData, type CommandActionData, type ConcurrencyLimitOptions, ConfigKey, ConfigKeyRegistry, ConfigName, ConfigRegistry, ConfigSpecialHandlers, ConflictError, Connector, ConnectorBuildContext, ConnectorBuildContribution, ConnectorBuildGenerateResult, ConnectorEsbuildPatch, ConnectorLifecyclePhase, ConnectorName, ConnectorPriority, ConnectorsManager, ConsoleChannel, ContainerTypes, CookieOptions, CursorPaginationOptions, CursorPaginationResult, DOSpacesDriver, DatabaseCacheDriver, type DatabaseCacheOptions, DatabaseConnector, DatabaseLog, DatabaseLogModel, DatabaseLogOptions, DecodedAccessToken, DefineResourceOptions, DeleteManyResult, EncryptionConfigurations, EncryptionPasswordConfigurations, Environment, EventSubscription, ExistsExceptCurrentIdRuleOptions, ExistsExceptCurrentUserRuleOptions, FastifyInstance, FileNamingStrategy, FileValidationOptions, FileValidator, FileVisibility, FilterFunction, FilterOperator, FilterOptions, FilterRule, FilterRules, ForbiddenError, GroupByFields, GroupedRoutesOptions, HealthCheck, HealthStatus, HeraldConnector, HttpConfigurations, HttpConnector, HttpContext, HttpError, HttpErrorCodes, HttpReadyReport, type IdempotencyOptions, Image, ImageFormat, ImageInput, ImageTransformCallback, ImageTransformConfig, ImageTransformOptions, type IpFilterOptions, ListOptions, LocalDriver, LocalStorageDriverOptions, LocalizedObject, LogConfigurations, LoggerConnector, MAIL_EVENTS, Mail, type MailAddress, type MailAttachment, type MailConfigurations, MailError, type MailErrorCode, type MailEvents, type MailMode, type MailOptions, type MailPriority, type MailResult, MailerConnector, type MailersConfig, type MaintenanceOptions, Middleware, MiddlewareResponse, MimeTypes, NoopChannel, type NormalizedMail, NotAcceptableError, NotAllowedError, NotificationsConnector, PaginationMode, PaginationResult, PartialMiddleware, PartialPick, Path, PipelineOptions, PortInUseError, PositionalHandlerSuspect, PrefixConfig, PrefixOptions, PresignedOptions, PresignedUploadOptions, PutDirectoryOptions, PutDirectoryResult, PutFromUrlOptions, PutOptions, QueryBuilderContract, Queue, R2Driver, R2StorageDriverOptions, type RateLimitOptions, RegisterConfiguredConnectorsOptions, RegisterResource, RegisteredUseCase, RepositoryAdapterContract, RepositoryConfigurations, RepositoryEvent, RepositoryManager, RepositoryOptions, RepositoryOptionsWithCursor, RepositoryOptionsWithPages, Request, RequestContextStore, RequestController, RequestControllerContract, RequestEvent, RequestHandler, RequestHandlerType, RequestHandlerValidation, RequestLocals, RequestLog, RequestMethod, RequestUser, type ResolvedCLICommandOption, Resource, ResourceArraySchema, ResourceCastType, ResourceConstructor, ResourceContract, ResourceFieldBuilder, ResourceFieldBuilderDateOutputOptions, ResourceFieldConfig, ResourceMethod, ResourceNotFoundError, ResourceOutputValueCastType, ResourceSchema, ResourceSelfReference, Response, ResponseBodyValue, ResponseEvent, ResponseSSEController, ResponseSchema, ResponseStatus, ResponseStreamController, Restful, RestfulMiddleware, ReturnedResponse, Route, RouteOptions, RouteRegistry, RouteResource, Router, RouterGroupCallback, RouterStacks, RuntimeStrategy, S3Driver, type SESConfigurations, type SMTPConfigurations, SafeFetchOptions, SafeFetchResult, SaveAsOptions, SaveMode, SaveOptions, ScopedStorage, ScopedStorageContract, SeedClock, SeedContext, SeedRecordRef, SeedResult, Seeder, SeederDependencyCycleError, SeederMetadata, SendBufferOptions, SendFileOptions, ServerError, ShutdownListener, SocketConnector, SocketOptions, Storage, StorageConfigurations, StorageConnector, StorageCopyEventPayload, StorageDriverConfig, StorageDriverContextStore, StorageDriverContract, StorageDriverName, StorageDriverRegistry, StorageDriverType, StorageError, StorageErrorOptions, StorageEventHandler, StorageEventPayload, StorageEventType, StorageFile, StorageFileData, StorageFileInfo, StorageManagerContract, StoragePutEventPayload, TemporaryTokenError, TemporaryTokenPayload, TemporaryTokenValidation, Track, TrackableModel, TypedAllRepositoryOptions, TypedRepositoryOptions, TypedRepositoryOptionsWithCursor, TypedRepositoryOptionsWithPages, UPLOADS_DEFAULTS, UnAuthorizedError, UniqueExceptCurrentIdRuleOptions, UniqueExceptCurrentUserRuleOptions, UnknownSeederDependencyError, UploadedFile, UploadedFileImageOptions, UploadedFileJson, UploadsConfigurations, UseCase, UseCaseAfterMiddleware, UseCaseBeforeMiddleware, UseCaseBroadcastChannel, UseCaseBroadcastEvent, UseCaseBroadcastOption, UseCaseConfigurations, UseCaseContext, UseCaseErrorResult, UseCaseEventsCallbacksMap, UseCaseGuard, UseCaseHandler, UseCaseOnExecutingContext, UseCaseResult, UseCaseRuntimeOptions, UseCaseWithSchema, ValidationConfiguration, WarlockConfig, WarlockConfigManager, WatermarkConfig, WhereOperator, addUseCaseHistory, anyMatch, app, appLog, appPath, assertConfiguredHttpPortIsFree, assertMailCount, assertMailSent, assertNoReservedConnectorNames, assertPortIsAvailable, assertUniqueConnectorNames, assetsUrl, bootstrap, broadcastUseCaseResult, buildIdempotencyCacheKey, cachePath, captureMail, clearPositionalHandlerSuspects, clearTestMailbox, closeAllMailers, closeMailer, closeServerWithTimeout, colors, command, config, configPath, configSpecialHandlers, connectorsManager, container, createHttpApplication, createRequestStore, decrypt, defaultHttpConfigurations, defineConfig, defineResource, describePositionalHandlerSuspect, displayEnvironmentMode, encrypt, ensureFatalIsVisible, env, environment, existsExceptCurrentIdRule, existsExceptCurrentUserRule, fetchLatestVersion, fileExtensionRule, fileRule, fileTypeRule, findMailsBySubject, findMailsTo, fireLifecycleEvent, forgetPositionalHandlerSuspects, generateMailId, getDefaultMailConfig, getHttpReadyReport, getHttpServer, getLastMail, getLocalized, getMailEventName, getMailMode, getMailboxSize, getMailer, getMailerConfig, getMimeType, getPoolStats, getSocketServer, getTestMailbox, getUseCase, getUseCaseHistory, getUseCases, globalEventsCallbacksMap, globalUseCasesEvents, hashBody, hashPassword, health, hmacHash, httpConfig, imageRule, increaseUseCaseFailedCalls, increaseUseCaseSuccessCalls, inspectHandlerSignature, ipMatches, isBootSignal, isDevelopmentMode, isNewerVersion, isPortAvailable, isPrivateOrReservedIp, isProductionMode, isTestMode, isUnknownTsExtensionError, isValidIdempotencyKey, listPositionalHandlerSuspects, loadS3, logResponse, logsPath, looksLikePositionalHandler, mailEvents, matchesDerivedRouteName, measure, middleware, normalizeRequestPath, normalizeRoutePath, parseSize, paths, preflightConfiguredHttpPort, promiseAllObject, publicPath, publicUrl, registerAppConfig, registerConfiguredConnectors, registerHttpPlugins, renderReact, renderReactMail, reportPositionalHandlerSuspects, requestContext, requestMemo, resetHttpReadyReport, resetMailConfig, resolveMailConfig, resolveWithinRoot, rootPath, routeNameMethodSuffix, router, runPipeline, safeFetchToBuffer, sanitizePath, seeder, sendBootSignal, sendMail, setBaseUrl, setConfig, setEnvironment, setHttpReadyReport, setLogConfigurations, setMailConfigurations, setMailMode, sleep, sluggable, srcPath, startHttpServer, stopHttpApplication, storage, storageConfig, storageConfigurations, storageDriverContext, storagePath, t, tempPath, toJson, uniqueExceptCurrentIdRule, uniqueExceptCurrentUserRule, uploadedFileMetadataSchema, uploadsConfig, uploadsPath, uploadsUrl, url, useCase, useComputedModel, useComputedSlug, useCurrentUser, useHashedPassword, useRequest, useRequestStore, verifyMailer, verifyPassword, warlockConfigManager, warlockPath, wasMailSentTo, wasMailSentWithSubject, wrapResponseInDataKey };
164
+ export { $registerUseCase, $unregisterUseCase, AccessConnector, AggregateExpressionInput, AggregateExpressions, AiConnector, AllRepositoryOptions, AppConfigurations, Application, BOOT_SIGNAL_ENV_KEY, BOOT_SIGNAL_VERSION, BadRequestError, BadSchemaUseCaseError, BaseConnector, type BenchmarkChannel, type BenchmarkConfigurations, type BenchmarkErrorResult, type BenchmarkOptions, BenchmarkProfiler, type BenchmarkProfilerOptions, type BenchmarkResult, BenchmarkSnapshots, type BenchmarkSnapshotsOptions, type BenchmarkStats, type BenchmarkSuccessResult, BootContext, BootListener, BootSignal, BootSignalType, BootValidator, CLICommand, type CLICommandAction, type CLICommandOption, type CLICommandOptions, type CLICommandPreload, type CLICommandSource, CacheConnector, type CacheMiddlewareOptions, CachedRepositoryOptions, type CapturedMail, CascadeAdapter, CascadeQueryBuilder, ChunkCallback, ClosableServer, CloudDriver, CloudStorageDriverContract, CloudStorageDriverOptions, CloudStorageFileData, type CommandActionData, type ConcurrencyLimitOptions, ConfigKey, ConfigKeyRegistry, ConfigName, ConfigRegistry, ConfigSpecialHandlers, ConflictError, Connector, ConnectorBuildContext, ConnectorBuildContribution, ConnectorBuildGenerateResult, ConnectorEsbuildPatch, ConnectorLifecyclePhase, ConnectorName, ConnectorPriority, ConnectorsManager, ConsoleChannel, ContainerTypes, CookieOptions, CursorPaginationOptions, CursorPaginationResult, DOSpacesDriver, DatabaseCacheDriver, type DatabaseCacheOptions, DatabaseConnector, DatabaseLog, DatabaseLogModel, DatabaseLogOptions, DecodedAccessToken, DefineResourceOptions, DeleteManyResult, EncryptionConfigurations, EncryptionPasswordConfigurations, Environment, EventSubscription, ExistsExceptCurrentIdRuleOptions, ExistsExceptCurrentUserRuleOptions, FastifyInstance, FileNamingStrategy, FileValidationOptions, FileValidator, FileVisibility, FilterFunction, FilterOperator, FilterOptions, FilterRule, FilterRules, ForbiddenError, GroupByFields, GroupedRoutesOptions, HealthCheck, HealthStatus, HeraldConnector, HttpConfigurations, HttpConnector, HttpContext, HttpError, HttpErrorCodes, HttpReadyReport, type IdempotencyOptions, Image, ImageFormat, ImageInput, ImageTransformCallback, ImageTransformConfig, ImageTransformOptions, type IpFilterOptions, ListOptions, LocalDriver, LocalStorageDriverOptions, LocalizedObject, LogConfigurations, LoggerConnector, MAIL_EVENTS, Mail, type MailAddress, type MailAttachment, type MailConfigurations, MailError, type MailErrorCode, type MailEvents, type MailMode, type MailOptions, type MailPriority, type MailResult, MailerConnector, type MailersConfig, type MaintenanceOptions, Middleware, MiddlewareResponse, MimeTypes, NoopChannel, type NormalizedMail, NotAcceptableError, NotAllowedError, NotificationsConnector, PaginationMode, PaginationResult, PartialMiddleware, PartialPick, Path, PipelineOptions, PortInUseError, PositionalHandlerSuspect, PrefixConfig, PrefixOptions, PresignedOptions, PresignedUploadOptions, PutDirectoryOptions, PutDirectoryResult, PutFromUrlOptions, PutOptions, QueryBuilderContract, Queue, R2Driver, R2StorageDriverOptions, type RateLimitOptions, RegisterConfiguredConnectorsOptions, RegisterResource, RegisteredUseCase, RepositoryAdapterContract, RepositoryConfigurations, RepositoryEvent, RepositoryManager, RepositoryOptions, RepositoryOptionsWithCursor, RepositoryOptionsWithPages, Request, RequestContextStore, RequestController, RequestControllerContract, RequestEvent, RequestHandler, RequestHandlerType, RequestHandlerValidation, RequestLocals, RequestLog, RequestMethod, RequestUser, type ResolvedCLICommandOption, Resource, ResourceArraySchema, ResourceCastType, ResourceConstructor, ResourceContract, ResourceFieldBuilder, ResourceFieldBuilderDateOutputOptions, ResourceFieldConfig, ResourceMethod, ResourceNotFoundError, ResourceOutputValueCastType, ResourceSchema, ResourceSelfReference, Response, ResponseBodyValue, ResponseEvent, ResponseSSEController, ResponseSchema, ResponseStatus, ResponseStreamController, Restful, RestfulMiddleware, ReturnedResponse, Route, RouteOptions, RouteRegistry, RouteResource, Router, RouterGroupCallback, RouterStacks, RuntimeStrategy, S3Driver, type SESConfigurations, type SMTPConfigurations, SafeFetchOptions, SafeFetchResult, SaveAsOptions, SaveMode, SaveOptions, ScopedStorage, ScopedStorageContract, SeedClock, SeedContext, SeedRecordRef, SeedResult, Seeder, SeederDependencyCycleError, SeederMetadata, SendBufferOptions, SendFileOptions, ServerError, ShutdownListener, SocketConnector, SocketOptions, Storage, StorageConfigurations, StorageConnector, StorageCopyEventPayload, StorageDriverConfig, StorageDriverContextStore, StorageDriverContract, StorageDriverName, StorageDriverRegistry, StorageDriverType, StorageError, StorageErrorOptions, StorageEventHandler, StorageEventPayload, StorageEventType, StorageFile, StorageFileData, StorageFileInfo, StorageManagerContract, StoragePutEventPayload, TemporaryTokenError, TemporaryTokenPayload, TemporaryTokenValidation, Track, TrackableModel, TypedAllRepositoryOptions, TypedRepositoryOptions, TypedRepositoryOptionsWithCursor, TypedRepositoryOptionsWithPages, UPLOADS_DEFAULTS, UnAuthorizedError, UniqueExceptCurrentIdRuleOptions, UniqueExceptCurrentUserRuleOptions, UnknownSeederDependencyError, UploadedFile, UploadedFileImageOptions, UploadedFileJson, UploadsConfigurations, UseCase, UseCaseAfterMiddleware, UseCaseBeforeMiddleware, UseCaseBroadcastChannel, UseCaseBroadcastEvent, UseCaseBroadcastOption, UseCaseConfigurations, UseCaseContext, UseCaseErrorResult, UseCaseEventsCallbacksMap, UseCaseGuard, UseCaseHandler, UseCaseOnExecutingContext, UseCaseResult, UseCaseRuntimeOptions, UseCaseWithSchema, ValidationConfiguration, WarlockConfig, WarlockConfigManager, WatermarkConfig, WhereOperator, addUseCaseHistory, anyMatch, app, appLog, appPath, assertConfiguredHttpPortIsFree, assertMailCount, assertMailSent, assertNoReservedConnectorNames, assertPortIsAvailable, assertUniqueConnectorNames, assetsUrl, bootstrap, broadcastUseCaseResult, buildIdempotencyCacheKey, cachePath, captureMail, clearPositionalHandlerSuspects, clearTestMailbox, closeAllMailers, closeMailer, closeServerWithTimeout, colors, command, config, configPath, configSpecialHandlers, connectorsManager, container, createHttpApplication, createRequestStore, decrypt, defaultHttpConfigurations, defineConfig, defineResource, describePositionalHandlerSuspect, displayEnvironmentMode, encrypt, ensureFatalIsVisible, env, environment, existsExceptCurrentIdRule, existsExceptCurrentUserRule, fetchLatestVersion, fileExtensionRule, fileRule, fileTypeRule, findMailsBySubject, findMailsTo, fireLifecycleEvent, forgetPositionalHandlerSuspects, generateMailId, getDefaultMailConfig, getHttpReadyReport, getHttpServer, getLastMail, getLocalized, getMailEventName, getMailMode, getMailboxSize, getMailer, getMailerConfig, getMimeType, getPoolStats, getSocketServer, getTestMailbox, getUseCase, getUseCaseHistory, getUseCases, globalEventsCallbacksMap, globalUseCasesEvents, hashBody, hashPassword, health, hmacHash, httpConfig, imageRule, increaseUseCaseFailedCalls, increaseUseCaseSuccessCalls, inspectHandlerSignature, ipMatches, isBootSignal, isDevelopmentMode, isNewerVersion, isPortAvailable, isPrivateOrReservedIp, isProductionMode, isTestMode, isUnknownTsExtensionError, isValidIdempotencyKey, listPositionalHandlerSuspects, loadS3, logResponse, logsPath, looksLikePositionalHandler, mailEvents, matchesDerivedRouteName, measure, middleware, normalizeRequestPath, normalizeRoutePath, parseSize, paths, preflightConfiguredHttpPort, promiseAllObject, publicPath, publicUrl, registerAppConfig, registerConfiguredConnectors, registerHttpPlugins, remedyLines, renderReact, renderReactMail, reportPositionalHandlerSuspects, requestContext, requestMemo, resetHttpReadyReport, resetMailConfig, resolveMailConfig, resolveWithinRoot, rootPath, routeNameMethodSuffix, router, runPipeline, safeFetchToBuffer, sanitizePath, seeder, sendBootSignal, sendMail, setBaseUrl, setConfig, setEnvironment, setHttpReadyReport, setLogConfigurations, setMailConfigurations, setMailMode, shouldPreflightHttpPort, sleep, sluggable, srcPath, startHttpServer, stopHttpApplication, storage, storageConfig, storageConfigurations, storageDriverContext, storagePath, t, tempPath, toJson, uniqueExceptCurrentIdRule, uniqueExceptCurrentUserRule, uploadedFileMetadataSchema, uploadsConfig, uploadsPath, uploadsUrl, url, useCase, useComputedModel, useComputedSlug, useCurrentUser, useHashedPassword, useRequest, useRequestStore, verifyMailer, verifyPassword, warlockConfigManager, warlockPath, wasMailSentTo, wasMailSentWithSubject, wrapResponseInDataKey };
165
165
  import "./config/types.mjs";
166
166
  import "./http/request.mjs";
167
167
  import "./http/types.mjs";
package/esm/index.mjs CHANGED
@@ -70,7 +70,7 @@ import { anyMatch, ipMatches } from "./http/middleware/utils/cidr-match.mjs";
70
70
  import { parseSize } from "./http/middleware/utils/parse-size.mjs";
71
71
  import { middleware } from "./http/middleware/middleware-list.mjs";
72
72
  import { PortInUseError, assertPortIsAvailable, isPortAvailable } from "./http/port-preflight.mjs";
73
- import { assertConfiguredHttpPortIsFree, preflightConfiguredHttpPort } from "./http/boot-port-preflight.mjs";
73
+ import { assertConfiguredHttpPortIsFree, preflightConfiguredHttpPort, remedyLines, shouldPreflightHttpPort } from "./http/boot-port-preflight.mjs";
74
74
  import { getHttpReadyReport, resetHttpReadyReport, setHttpReadyReport } from "./http/ready-report.mjs";
75
75
  import { requestMemo } from "./http/context/request-memo.mjs";
76
76
  import "./http/index.mjs";
@@ -159,4 +159,4 @@ import { colors } from "@mongez/copper";
159
159
 
160
160
  export * from "@mongez/localization"
161
161
 
162
- export { $registerUseCase, $unregisterUseCase, AccessConnector, AiConnector, Application, BOOT_SIGNAL_ENV_KEY, BOOT_SIGNAL_VERSION, BadRequestError, BadSchemaUseCaseError, BaseConnector, BenchmarkProfiler, BenchmarkSnapshots, CLICommand, CacheConnector, CascadeAdapter, CascadeQueryBuilder, CloudDriver, ConfigSpecialHandlers, ConflictError, ConnectorLifecyclePhase, ConnectorPriority, ConnectorsManager, ConsoleChannel, DOSpacesDriver, DatabaseCacheDriver, DatabaseConnector, DatabaseLog, DatabaseLogModel, FileValidator, ForbiddenError, HeraldConnector, HttpConnector, HttpError, HttpErrorCodes, Image, LocalDriver, LoggerConnector, MAIL_EVENTS, Mail, MailError, MailerConnector, MimeTypes, NoopChannel, NotAcceptableError, NotAllowedError, NotificationsConnector, Path, PortInUseError, Queue, R2Driver, RegisterResource, RepositoryManager, Request, RequestController, RequestLog, Resource, ResourceFieldBuilder, ResourceNotFoundError, Response, ResponseStatus, Restful, RouteRegistry, Router, S3Driver, ScopedStorage, SeederDependencyCycleError, ServerError, SocketConnector, Storage, StorageConnector, StorageError, StorageFile, UPLOADS_DEFAULTS, UnAuthorizedError, UnknownSeederDependencyError, UploadedFile, WarlockConfigManager, addUseCaseHistory, anyMatch, app, appLog, appPath, assertConfiguredHttpPortIsFree, assertMailCount, assertMailSent, assertNoReservedConnectorNames, assertPortIsAvailable, assertUniqueConnectorNames, assetsUrl, bootstrap, broadcastUseCaseResult, buildIdempotencyCacheKey, cachePath, captureMail, clearPositionalHandlerSuspects, clearTestMailbox, closeAllMailers, closeMailer, closeServerWithTimeout, colors, command, config, configPath, configSpecialHandlers, connectorsManager, container, createHttpApplication, createRequestStore, decrypt, defaultHttpConfigurations, defineConfig, defineResource, describePositionalHandlerSuspect, displayEnvironmentMode, encrypt, ensureFatalIsVisible, env, environment, existsExceptCurrentIdRule, existsExceptCurrentUserRule, fetchLatestVersion, fileExtensionRule, fileRule, fileTypeRule, findMailsBySubject, findMailsTo, fireLifecycleEvent, forgetPositionalHandlerSuspects, generateMailId, getDefaultMailConfig, getHttpReadyReport, getHttpServer, getLastMail, getLocalized, getMailEventName, getMailMode, getMailboxSize, getMailer, getMailerConfig, getMimeType, getPoolStats, getSocketServer, getTestMailbox, getUseCase, getUseCaseHistory, getUseCases, globalEventsCallbacksMap, globalUseCasesEvents, hashBody, hashPassword, health, hmacHash, httpConfig, imageRule, increaseUseCaseFailedCalls, increaseUseCaseSuccessCalls, inspectHandlerSignature, ipMatches, isBootSignal, isDevelopmentMode, isNewerVersion, isPortAvailable, isPrivateOrReservedIp, isProductionMode, isTestMode, isUnknownTsExtensionError, isValidIdempotencyKey, listPositionalHandlerSuspects, loadS3, logResponse, logsPath, looksLikePositionalHandler, mailEvents, matchesDerivedRouteName, measure, middleware, normalizeRequestPath, normalizeRoutePath, parseSize, paths, preflightConfiguredHttpPort, promiseAllObject, publicPath, publicUrl, registerAppConfig, registerConfiguredConnectors, registerHttpPlugins, renderReact, renderReactMail, reportPositionalHandlerSuspects, requestContext, requestMemo, resetHttpReadyReport, resetMailConfig, resolveMailConfig, resolveWithinRoot, rootPath, routeNameMethodSuffix, router, runPipeline, safeFetchToBuffer, sanitizePath, seeder, sendBootSignal, sendMail, setBaseUrl, setConfig, setEnvironment, setHttpReadyReport, setLogConfigurations, setMailConfigurations, setMailMode, sleep, sluggable, srcPath, startHttpServer, stopHttpApplication, storage, storageConfig, storageConfigurations, storageDriverContext, storagePath, t, tempPath, toJson, uniqueExceptCurrentIdRule, uniqueExceptCurrentUserRule, uploadedFileMetadataSchema, uploadsConfig, uploadsPath, uploadsUrl, url, useCase, useComputedModel, useComputedSlug, useCurrentUser, useHashedPassword, useRequest, useRequestStore, verifyMailer, verifyPassword, warlockConfigManager, warlockPath, wasMailSentTo, wasMailSentWithSubject, wrapResponseInDataKey };
162
+ export { $registerUseCase, $unregisterUseCase, AccessConnector, AiConnector, Application, BOOT_SIGNAL_ENV_KEY, BOOT_SIGNAL_VERSION, BadRequestError, BadSchemaUseCaseError, BaseConnector, BenchmarkProfiler, BenchmarkSnapshots, CLICommand, CacheConnector, CascadeAdapter, CascadeQueryBuilder, CloudDriver, ConfigSpecialHandlers, ConflictError, ConnectorLifecyclePhase, ConnectorPriority, ConnectorsManager, ConsoleChannel, DOSpacesDriver, DatabaseCacheDriver, DatabaseConnector, DatabaseLog, DatabaseLogModel, FileValidator, ForbiddenError, HeraldConnector, HttpConnector, HttpError, HttpErrorCodes, Image, LocalDriver, LoggerConnector, MAIL_EVENTS, Mail, MailError, MailerConnector, MimeTypes, NoopChannel, NotAcceptableError, NotAllowedError, NotificationsConnector, Path, PortInUseError, Queue, R2Driver, RegisterResource, RepositoryManager, Request, RequestController, RequestLog, Resource, ResourceFieldBuilder, ResourceNotFoundError, Response, ResponseStatus, Restful, RouteRegistry, Router, S3Driver, ScopedStorage, SeederDependencyCycleError, ServerError, SocketConnector, Storage, StorageConnector, StorageError, StorageFile, UPLOADS_DEFAULTS, UnAuthorizedError, UnknownSeederDependencyError, UploadedFile, WarlockConfigManager, addUseCaseHistory, anyMatch, app, appLog, appPath, assertConfiguredHttpPortIsFree, assertMailCount, assertMailSent, assertNoReservedConnectorNames, assertPortIsAvailable, assertUniqueConnectorNames, assetsUrl, bootstrap, broadcastUseCaseResult, buildIdempotencyCacheKey, cachePath, captureMail, clearPositionalHandlerSuspects, clearTestMailbox, closeAllMailers, closeMailer, closeServerWithTimeout, colors, command, config, configPath, configSpecialHandlers, connectorsManager, container, createHttpApplication, createRequestStore, decrypt, defaultHttpConfigurations, defineConfig, defineResource, describePositionalHandlerSuspect, displayEnvironmentMode, encrypt, ensureFatalIsVisible, env, environment, existsExceptCurrentIdRule, existsExceptCurrentUserRule, fetchLatestVersion, fileExtensionRule, fileRule, fileTypeRule, findMailsBySubject, findMailsTo, fireLifecycleEvent, forgetPositionalHandlerSuspects, generateMailId, getDefaultMailConfig, getHttpReadyReport, getHttpServer, getLastMail, getLocalized, getMailEventName, getMailMode, getMailboxSize, getMailer, getMailerConfig, getMimeType, getPoolStats, getSocketServer, getTestMailbox, getUseCase, getUseCaseHistory, getUseCases, globalEventsCallbacksMap, globalUseCasesEvents, hashBody, hashPassword, health, hmacHash, httpConfig, imageRule, increaseUseCaseFailedCalls, increaseUseCaseSuccessCalls, inspectHandlerSignature, ipMatches, isBootSignal, isDevelopmentMode, isNewerVersion, isPortAvailable, isPrivateOrReservedIp, isProductionMode, isTestMode, isUnknownTsExtensionError, isValidIdempotencyKey, listPositionalHandlerSuspects, loadS3, logResponse, logsPath, looksLikePositionalHandler, mailEvents, matchesDerivedRouteName, measure, middleware, normalizeRequestPath, normalizeRoutePath, parseSize, paths, preflightConfiguredHttpPort, promiseAllObject, publicPath, publicUrl, registerAppConfig, registerConfiguredConnectors, registerHttpPlugins, remedyLines, renderReact, renderReactMail, reportPositionalHandlerSuspects, requestContext, requestMemo, resetHttpReadyReport, resetMailConfig, resolveMailConfig, resolveWithinRoot, rootPath, routeNameMethodSuffix, router, runPipeline, safeFetchToBuffer, sanitizePath, seeder, sendBootSignal, sendMail, setBaseUrl, setConfig, setEnvironment, setHttpReadyReport, setLogConfigurations, setMailConfigurations, setMailMode, shouldPreflightHttpPort, sleep, sluggable, srcPath, startHttpServer, stopHttpApplication, storage, storageConfig, storageConfigurations, storageDriverContext, storagePath, t, tempPath, toJson, uniqueExceptCurrentIdRule, uniqueExceptCurrentUserRule, uploadedFileMetadataSchema, uploadsConfig, uploadsPath, uploadsUrl, url, useCase, useComputedModel, useComputedSlug, useCurrentUser, useHashedPassword, useRequest, useRequestStore, verifyMailer, verifyPassword, warlockConfigManager, warlockPath, wasMailSentTo, wasMailSentWithSubject, wrapResponseInDataKey };
@@ -1,3 +1,4 @@
1
+ import { recordEnvironmentOverrides } from "./recorded-environment-overrides.mjs";
1
2
  import { detectEnvironmentOverrides } from "./detect-environment-overrides.mjs";
2
3
  import { reportEnvironmentOverrides } from "./report-environment-overrides.mjs";
3
4
  import { loadEnv } from "@mongez/dotenv";
@@ -102,7 +103,9 @@ async function loadEnvironmentFiles(directory = process.cwd()) {
102
103
  if (!candidateEnvFiles(directory).some((file) => existsSync(file))) return;
103
104
  environmentLoaded = true;
104
105
  const processEnvironmentSnapshot = { ...process.env };
105
- reportEnvironmentOverrides(detectEnvironmentOverrides(resolveLoadedEnvFiles(directory), processEnvironmentSnapshot));
106
+ const overrides = detectEnvironmentOverrides(resolveLoadedEnvFiles(directory), processEnvironmentSnapshot);
107
+ recordEnvironmentOverrides(overrides);
108
+ reportEnvironmentOverrides(overrides);
106
109
  await loadEnv(void 0, {
107
110
  ...environmentLoaderOptions,
108
111
  dir: directory
@@ -1 +1 @@
1
- {"version":3,"file":"load-environment.mjs","names":[],"sources":["../../../../../../../core/src/utils/load-environment.ts"],"sourcesContent":["import { loadEnv, type EnvLoaderOptions } from \"@mongez/dotenv\";\nimport { existsSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { detectEnvironmentOverrides } from \"./detect-environment-overrides\";\nimport { reportEnvironmentOverrides } from \"./report-environment-overrides\";\n\n/**\n * Core's env precedence policy, in one place because two callers load env:\n * this module at boot, and the dev server when an `.env` file changes.\n *\n * `@mongez/dotenv` defaults to `precedence: \"file-wins\"`, which is backwards.\n * The `.env` file is a checked-in DEFAULT; a variable already exported into\n * `process.env` is the deliberate, situational override — a second instance on\n * another port, CI pointed at another database, a container's configuration.\n * Under the default, `PORT=6060 warlock dev` produced a server on the `.env`\n * file's 3000 and said nothing. A default that silently beats an explicit\n * instruction is the wrong way round, so core opts into `process-wins`:\n * the file supplies only the keys the environment does not already carry.\n *\n * EMPTY STRING — `FOO=` in the environment counts as SET, and wins, so the\n * file's value is discarded and `env(\"FOO\")` returns `\"\"`. Chosen over\n * \"empty means absent\" because the loader cannot tell a deliberate blanking\n * from an accident, and only one of the two readings is expressible: an\n * operator who wants the file's value can unset the variable, whereas under\n * \"empty means absent\" an operator who wants a blank value has no way to ask\n * for one. It also matches POSIX (an exported empty variable is set), dotenv,\n * dotenv-flow and Vite. Note the consequence: a blank export beats the second\n * argument too, so `env(\"FOO\", \"fallback\")` yields `\"\"`, not the fallback.\n *\n * Keys this loader itself wrote are tracked by the library and are NOT treated\n * as process-provided, so editing `.env` during a dev session still takes\n * effect on reload rather than being pinned by the previous load's own writes.\n */\nexport const environmentLoaderOptions: EnvLoaderOptions = {\n precedence: \"process-wins\",\n};\n\n/**\n * The files `loadEnv()` will look for, in the order it considers them.\n *\n * Mirrors `@mongez/dotenv`'s own resolution — `.env.shared` first, then\n * `.env.<NODE_ENV>` if it exists, else plain `.env` — because the presence\n * check has to ask the same question the loader is about to ask.\n */\nfunction candidateEnvFiles(directory: string): string[] {\n const files = [path.join(directory, \".env\"), path.join(directory, \".env.shared\")];\n\n if (process.env.NODE_ENV) {\n files.push(path.join(directory, `.env.${process.env.NODE_ENV}`));\n }\n\n return files;\n}\n\n/**\n * The files `loadEnv()` will actually read, in its own read order.\n *\n * Differs from {@link candidateEnvFiles}, which lists every candidate to\n * answer \"is there anything to load at all\". `loadEnv()` itself only ever\n * reads `.env.shared` (if present) plus ONE of `.env.<NODE_ENV>` /\n * `.env` — the NODE_ENV-specific file when it exists, `.env` otherwise —\n * never both. Override detection has to mirror that exactly, or a key\n * declared only in a file `loadEnv()` would not have touched could be\n * reported as having \"lost\" a comparison it was never actually part of.\n */\nfunction resolveLoadedEnvFiles(directory: string): string[] {\n const files: string[] = [];\n const sharedFile = path.join(directory, \".env.shared\");\n\n if (existsSync(sharedFile)) {\n files.push(sharedFile);\n }\n\n const nodeEnvFile = process.env.NODE_ENV\n ? path.join(directory, `.env.${process.env.NODE_ENV}`)\n : undefined;\n const derivedFile =\n nodeEnvFile && existsSync(nodeEnvFile) ? nodeEnvFile : path.join(directory, \".env\");\n\n if (existsSync(derivedFile)) {\n files.push(derivedFile);\n }\n\n return files;\n}\n\n/**\n * Whether this process has already loaded its env files.\n *\n * Two callers reach here on a bootstrapping command — the CLI preload phase and\n * `bootstrap()` — and `loadEnv()` defaults to `override: true`, so a second pass\n * re-parses the same files and re-writes `process.env`. Anything set in between\n * (a `warlock.config.ts` module body, a config file, an orchestrator) would be\n * silently clobbered by the later load. Same shape as the defect where a\n * caller's test-server port was overwritten by a re-read of `.env`.\n *\n * A deliberate reload is still possible and still used: the dev server calls\n * `loadEnv()` directly when an `.env` file changes on disk, which is a reload\n * the user asked for rather than an accidental second pass.\n */\nlet environmentLoaded = false;\n\n/**\n * Load `.env` files when there are any, and do nothing when there are not.\n *\n * Loads at most **once per process** — see {@link environmentLoaded}.\n *\n * `@mongez/dotenv`'s `loadEnvFile` **throws** when the file is absent. That was\n * survivable while only `dev` loaded env; now that every command loads it —\n * because `warlock.config.ts` calls `env()` in its module body and had been\n * evaluated against an empty store under every command — an unguarded call\n * would turn \"this project has no .env\" from a non-event into a hard failure\n * of `warlock build`. A project without a `.env` is legitimate: config files\n * carry defaults, and containers inject their own variables.\n *\n * The guard lives here rather than waiting on a fix in `@mongez/dotenv`: core\n * decides its own env policy and must not depend on another package's release\n * schedule to avoid crashing a working build.\n */\nexport async function loadEnvironmentFiles(directory: string = process.cwd()): Promise<void> {\n if (environmentLoaded) {\n return;\n }\n\n const hasEnvFile = candidateEnvFiles(directory).some((file) => existsSync(file));\n\n if (!hasEnvFile) {\n // Not latched: a project may create its `.env` between a command's preload\n // and its bootstrap, and there is nothing to protect from a second override\n // when the first pass loaded nothing.\n return;\n }\n\n environmentLoaded = true;\n\n // Snapshot BEFORE `loadEnv()` runs: the loader writes into `process.env` for\n // every key its files supply, so a snapshot taken afterwards could no\n // longer distinguish an ambient override from a value the loader itself\n // just wrote — see `detectEnvironmentOverrides`.\n const processEnvironmentSnapshot = { ...process.env };\n\n // The precedence itself is correct (see `environmentLoaderOptions` above);\n // the defect this guards against is the SILENCE — `.env` losing to an\n // ambient value with no diagnostic at all.\n reportEnvironmentOverrides(\n detectEnvironmentOverrides(resolveLoadedEnvFiles(directory), processEnvironmentSnapshot),\n );\n\n // `dir` has to be forwarded: `loadEnv()` defaults it to `process.cwd()`, so\n // without this the existence check above asks about `directory` while the\n // load itself reads somewhere else entirely. Identical in production, where\n // `directory` IS `process.cwd()`.\n await loadEnv(undefined, { ...environmentLoaderOptions, dir: directory });\n}\n\n/**\n * Forget that env has been loaded, so the next call loads again.\n *\n * @internal For tests, which run many boots inside one process.\n */\nexport function resetLoadedEnvironment(): void {\n environmentLoaded = false;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCA,MAAa,2BAA6C,EACxD,YAAY,eACd;;;;;;;;AASA,SAAS,kBAAkB,WAA6B;CACtD,MAAM,QAAQ,CAAC,KAAK,KAAK,WAAW,MAAM,GAAG,KAAK,KAAK,WAAW,aAAa,CAAC;CAEhF,IAAI,QAAQ,IAAI,UACd,MAAM,KAAK,KAAK,KAAK,WAAW,QAAQ,QAAQ,IAAI,UAAU,CAAC;CAGjE,OAAO;AACT;;;;;;;;;;;;AAaA,SAAS,sBAAsB,WAA6B;CAC1D,MAAM,QAAkB,CAAC;CACzB,MAAM,aAAa,KAAK,KAAK,WAAW,aAAa;CAErD,IAAI,WAAW,UAAU,GACvB,MAAM,KAAK,UAAU;CAGvB,MAAM,cAAc,QAAQ,IAAI,WAC5B,KAAK,KAAK,WAAW,QAAQ,QAAQ,IAAI,UAAU,IACnD;CACJ,MAAM,cACJ,eAAe,WAAW,WAAW,IAAI,cAAc,KAAK,KAAK,WAAW,MAAM;CAEpF,IAAI,WAAW,WAAW,GACxB,MAAM,KAAK,WAAW;CAGxB,OAAO;AACT;;;;;;;;;;;;;;;AAgBA,IAAI,oBAAoB;;;;;;;;;;;;;;;;;;AAmBxB,eAAsB,qBAAqB,YAAoB,QAAQ,IAAI,GAAkB;CAC3F,IAAI,mBACF;CAKF,IAAI,CAFe,kBAAkB,SAAS,CAAC,CAAC,MAAM,SAAS,WAAW,IAAI,CAEhE,GAIZ;CAGF,oBAAoB;CAMpB,MAAM,6BAA6B,EAAE,GAAG,QAAQ,IAAI;CAKpD,2BACE,2BAA2B,sBAAsB,SAAS,GAAG,0BAA0B,CACzF;CAMA,MAAM,QAAQ,QAAW;EAAE,GAAG;EAA0B,KAAK;CAAU,CAAC;AAC1E"}
1
+ {"version":3,"file":"load-environment.mjs","names":[],"sources":["../../../../../../../core/src/utils/load-environment.ts"],"sourcesContent":["import { loadEnv, type EnvLoaderOptions } from \"@mongez/dotenv\";\nimport { existsSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { detectEnvironmentOverrides } from \"./detect-environment-overrides\";\nimport { recordEnvironmentOverrides } from \"./recorded-environment-overrides\";\nimport { reportEnvironmentOverrides } from \"./report-environment-overrides\";\n\n/**\n * Core's env precedence policy, in one place because two callers load env:\n * this module at boot, and the dev server when an `.env` file changes.\n *\n * `@mongez/dotenv` defaults to `precedence: \"file-wins\"`, which is backwards.\n * The `.env` file is a checked-in DEFAULT; a variable already exported into\n * `process.env` is the deliberate, situational override — a second instance on\n * another port, CI pointed at another database, a container's configuration.\n * Under the default, `PORT=6060 warlock dev` produced a server on the `.env`\n * file's 3000 and said nothing. A default that silently beats an explicit\n * instruction is the wrong way round, so core opts into `process-wins`:\n * the file supplies only the keys the environment does not already carry.\n *\n * EMPTY STRING — `FOO=` in the environment counts as SET, and wins, so the\n * file's value is discarded and `env(\"FOO\")` returns `\"\"`. Chosen over\n * \"empty means absent\" because the loader cannot tell a deliberate blanking\n * from an accident, and only one of the two readings is expressible: an\n * operator who wants the file's value can unset the variable, whereas under\n * \"empty means absent\" an operator who wants a blank value has no way to ask\n * for one. It also matches POSIX (an exported empty variable is set), dotenv,\n * dotenv-flow and Vite. Note the consequence: a blank export beats the second\n * argument too, so `env(\"FOO\", \"fallback\")` yields `\"\"`, not the fallback.\n *\n * Keys this loader itself wrote are tracked by the library and are NOT treated\n * as process-provided, so editing `.env` during a dev session still takes\n * effect on reload rather than being pinned by the previous load's own writes.\n */\nexport const environmentLoaderOptions: EnvLoaderOptions = {\n precedence: \"process-wins\",\n};\n\n/**\n * The files `loadEnv()` will look for, in the order it considers them.\n *\n * Mirrors `@mongez/dotenv`'s own resolution — `.env.shared` first, then\n * `.env.<NODE_ENV>` if it exists, else plain `.env` — because the presence\n * check has to ask the same question the loader is about to ask.\n */\nfunction candidateEnvFiles(directory: string): string[] {\n const files = [path.join(directory, \".env\"), path.join(directory, \".env.shared\")];\n\n if (process.env.NODE_ENV) {\n files.push(path.join(directory, `.env.${process.env.NODE_ENV}`));\n }\n\n return files;\n}\n\n/**\n * The files `loadEnv()` will actually read, in its own read order.\n *\n * Differs from {@link candidateEnvFiles}, which lists every candidate to\n * answer \"is there anything to load at all\". `loadEnv()` itself only ever\n * reads `.env.shared` (if present) plus ONE of `.env.<NODE_ENV>` /\n * `.env` — the NODE_ENV-specific file when it exists, `.env` otherwise —\n * never both. Override detection has to mirror that exactly, or a key\n * declared only in a file `loadEnv()` would not have touched could be\n * reported as having \"lost\" a comparison it was never actually part of.\n */\nfunction resolveLoadedEnvFiles(directory: string): string[] {\n const files: string[] = [];\n const sharedFile = path.join(directory, \".env.shared\");\n\n if (existsSync(sharedFile)) {\n files.push(sharedFile);\n }\n\n const nodeEnvFile = process.env.NODE_ENV\n ? path.join(directory, `.env.${process.env.NODE_ENV}`)\n : undefined;\n const derivedFile =\n nodeEnvFile && existsSync(nodeEnvFile) ? nodeEnvFile : path.join(directory, \".env\");\n\n if (existsSync(derivedFile)) {\n files.push(derivedFile);\n }\n\n return files;\n}\n\n/**\n * Whether this process has already loaded its env files.\n *\n * Two callers reach here on a bootstrapping command — the CLI preload phase and\n * `bootstrap()` — and `loadEnv()` defaults to `override: true`, so a second pass\n * re-parses the same files and re-writes `process.env`. Anything set in between\n * (a `warlock.config.ts` module body, a config file, an orchestrator) would be\n * silently clobbered by the later load. Same shape as the defect where a\n * caller's test-server port was overwritten by a re-read of `.env`.\n *\n * A deliberate reload is still possible and still used: the dev server calls\n * `loadEnv()` directly when an `.env` file changes on disk, which is a reload\n * the user asked for rather than an accidental second pass.\n */\nlet environmentLoaded = false;\n\n/**\n * Load `.env` files when there are any, and do nothing when there are not.\n *\n * Loads at most **once per process** — see {@link environmentLoaded}.\n *\n * `@mongez/dotenv`'s `loadEnvFile` **throws** when the file is absent. That was\n * survivable while only `dev` loaded env; now that every command loads it —\n * because `warlock.config.ts` calls `env()` in its module body and had been\n * evaluated against an empty store under every command — an unguarded call\n * would turn \"this project has no .env\" from a non-event into a hard failure\n * of `warlock build`. A project without a `.env` is legitimate: config files\n * carry defaults, and containers inject their own variables.\n *\n * The guard lives here rather than waiting on a fix in `@mongez/dotenv`: core\n * decides its own env policy and must not depend on another package's release\n * schedule to avoid crashing a working build.\n */\nexport async function loadEnvironmentFiles(directory: string = process.cwd()): Promise<void> {\n if (environmentLoaded) {\n return;\n }\n\n const hasEnvFile = candidateEnvFiles(directory).some((file) => existsSync(file));\n\n if (!hasEnvFile) {\n // Not latched: a project may create its `.env` between a command's preload\n // and its bootstrap, and there is nothing to protect from a second override\n // when the first pass loaded nothing.\n return;\n }\n\n environmentLoaded = true;\n\n // Snapshot BEFORE `loadEnv()` runs: the loader writes into `process.env` for\n // every key its files supply, so a snapshot taken afterwards could no\n // longer distinguish an ambient override from a value the loader itself\n // just wrote — see `detectEnvironmentOverrides`.\n const processEnvironmentSnapshot = { ...process.env };\n\n // The precedence itself is correct (see `environmentLoaderOptions` above);\n // the defect this guards against is the SILENCE — `.env` losing to an\n // ambient value with no diagnostic at all.\n const overrides = detectEnvironmentOverrides(\n resolveLoadedEnvFiles(directory),\n processEnvironmentSnapshot,\n );\n\n // Print the overrides here AND retain them: a later point in the boot (the\n // port-collision reporter) needs to know an ambient `HTTP_PORT` beat `.env`\n // to avoid advising a rebuild that the env var would just override again —\n // see `recorded-environment-overrides.ts` (finding 8782b840).\n recordEnvironmentOverrides(overrides);\n reportEnvironmentOverrides(overrides);\n\n // `dir` has to be forwarded: `loadEnv()` defaults it to `process.cwd()`, so\n // without this the existence check above asks about `directory` while the\n // load itself reads somewhere else entirely. Identical in production, where\n // `directory` IS `process.cwd()`.\n await loadEnv(undefined, { ...environmentLoaderOptions, dir: directory });\n}\n\n/**\n * Forget that env has been loaded, so the next call loads again.\n *\n * @internal For tests, which run many boots inside one process.\n */\nexport function resetLoadedEnvironment(): void {\n environmentLoaded = false;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCA,MAAa,2BAA6C,EACxD,YAAY,eACd;;;;;;;;AASA,SAAS,kBAAkB,WAA6B;CACtD,MAAM,QAAQ,CAAC,KAAK,KAAK,WAAW,MAAM,GAAG,KAAK,KAAK,WAAW,aAAa,CAAC;CAEhF,IAAI,QAAQ,IAAI,UACd,MAAM,KAAK,KAAK,KAAK,WAAW,QAAQ,QAAQ,IAAI,UAAU,CAAC;CAGjE,OAAO;AACT;;;;;;;;;;;;AAaA,SAAS,sBAAsB,WAA6B;CAC1D,MAAM,QAAkB,CAAC;CACzB,MAAM,aAAa,KAAK,KAAK,WAAW,aAAa;CAErD,IAAI,WAAW,UAAU,GACvB,MAAM,KAAK,UAAU;CAGvB,MAAM,cAAc,QAAQ,IAAI,WAC5B,KAAK,KAAK,WAAW,QAAQ,QAAQ,IAAI,UAAU,IACnD;CACJ,MAAM,cACJ,eAAe,WAAW,WAAW,IAAI,cAAc,KAAK,KAAK,WAAW,MAAM;CAEpF,IAAI,WAAW,WAAW,GACxB,MAAM,KAAK,WAAW;CAGxB,OAAO;AACT;;;;;;;;;;;;;;;AAgBA,IAAI,oBAAoB;;;;;;;;;;;;;;;;;;AAmBxB,eAAsB,qBAAqB,YAAoB,QAAQ,IAAI,GAAkB;CAC3F,IAAI,mBACF;CAKF,IAAI,CAFe,kBAAkB,SAAS,CAAC,CAAC,MAAM,SAAS,WAAW,IAAI,CAEhE,GAIZ;CAGF,oBAAoB;CAMpB,MAAM,6BAA6B,EAAE,GAAG,QAAQ,IAAI;CAKpD,MAAM,YAAY,2BAChB,sBAAsB,SAAS,GAC/B,0BACF;CAMA,2BAA2B,SAAS;CACpC,2BAA2B,SAAS;CAMpC,MAAM,QAAQ,QAAW;EAAE,GAAG;EAA0B,KAAK;CAAU,CAAC;AAC1E"}
@@ -0,0 +1,41 @@
1
+ //#region ../core/src/utils/recorded-environment-overrides.ts
2
+ /**
3
+ * The environment overrides {@link detectEnvironmentOverrides} found on this
4
+ * boot, kept so a LATER point in the boot can ask which `.env` keys the ambient
5
+ * process environment beat — WITHOUT re-reading `.env` or `process.env` a second
6
+ * time, and without re-deriving the comparison.
7
+ *
8
+ * Why this exists: `reportPortInUse` (`src/http/boot-port-preflight.ts`) runs
9
+ * far downstream of `loadEnvironmentFiles()` and needs to know whether the
10
+ * colliding `http.port` came from an ambient `HTTP_PORT` (in which case
11
+ * "edit src/config/http.ts and rebuild" is ACTIVELY WRONG — the env var wins,
12
+ * so the rebuild changes nothing) or from config (where that advice is right).
13
+ * The detector already knows this at env-load time; the provenance has to
14
+ * travel to the point of use rather than be recomputed there (finding
15
+ * 8782b840, requirement 3).
16
+ *
17
+ * Keyed by env var name, last-write-wins — a single boot loads env at most once
18
+ * (`loadEnvironmentFiles` latches), and a deliberate dev-server reload re-records
19
+ * the current truth.
20
+ */
21
+ let recorded = /* @__PURE__ */ new Map();
22
+ /**
23
+ * Record the overrides detected on this boot. Replaces any prior record, so a
24
+ * re-load reflects the current environment rather than accumulating stale keys.
25
+ */
26
+ function recordEnvironmentOverrides(overrides) {
27
+ recorded = new Map(overrides.map((override) => [override.key, override]));
28
+ }
29
+ /**
30
+ * The recorded override for `key`, or `undefined` if the ambient environment
31
+ * did not beat a `.env` value for it on this boot. The returned object is the
32
+ * detector's own finding (effective value + the `.env` value it overrode), so a
33
+ * caller can name both without recomputing either.
34
+ */
35
+ function getRecordedEnvironmentOverride(key) {
36
+ return recorded.get(key);
37
+ }
38
+
39
+ //#endregion
40
+ export { getRecordedEnvironmentOverride, recordEnvironmentOverrides };
41
+ //# sourceMappingURL=recorded-environment-overrides.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recorded-environment-overrides.mjs","names":[],"sources":["../../../../../../../core/src/utils/recorded-environment-overrides.ts"],"sourcesContent":["import type { EnvironmentOverride } from \"./detect-environment-overrides\";\n\n/**\n * The environment overrides {@link detectEnvironmentOverrides} found on this\n * boot, kept so a LATER point in the boot can ask which `.env` keys the ambient\n * process environment beat — WITHOUT re-reading `.env` or `process.env` a second\n * time, and without re-deriving the comparison.\n *\n * Why this exists: `reportPortInUse` (`src/http/boot-port-preflight.ts`) runs\n * far downstream of `loadEnvironmentFiles()` and needs to know whether the\n * colliding `http.port` came from an ambient `HTTP_PORT` (in which case\n * \"edit src/config/http.ts and rebuild\" is ACTIVELY WRONG — the env var wins,\n * so the rebuild changes nothing) or from config (where that advice is right).\n * The detector already knows this at env-load time; the provenance has to\n * travel to the point of use rather than be recomputed there (finding\n * 8782b840, requirement 3).\n *\n * Keyed by env var name, last-write-wins — a single boot loads env at most once\n * (`loadEnvironmentFiles` latches), and a deliberate dev-server reload re-records\n * the current truth.\n */\nlet recorded = new Map<string, EnvironmentOverride>();\n\n/**\n * Record the overrides detected on this boot. Replaces any prior record, so a\n * re-load reflects the current environment rather than accumulating stale keys.\n */\nexport function recordEnvironmentOverrides(overrides: EnvironmentOverride[]): void {\n recorded = new Map(overrides.map((override) => [override.key, override]));\n}\n\n/**\n * The recorded override for `key`, or `undefined` if the ambient environment\n * did not beat a `.env` value for it on this boot. The returned object is the\n * detector's own finding (effective value + the `.env` value it overrode), so a\n * caller can name both without recomputing either.\n */\nexport function getRecordedEnvironmentOverride(key: string): EnvironmentOverride | undefined {\n return recorded.get(key);\n}\n\n/**\n * Forget every recorded override.\n *\n * @internal For tests, which run many boots inside one process.\n */\nexport function clearRecordedEnvironmentOverrides(): void {\n recorded = new Map();\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAqBA,IAAI,2BAAW,IAAI,IAAiC;;;;;AAMpD,SAAgB,2BAA2B,WAAwC;CACjF,WAAW,IAAI,IAAI,UAAU,KAAK,aAAa,CAAC,SAAS,KAAK,QAAQ,CAAC,CAAC;AAC1E;;;;;;;AAQA,SAAgB,+BAA+B,KAA8C;CAC3F,OAAO,SAAS,IAAI,GAAG;AACzB"}
package/package.json CHANGED
@@ -25,13 +25,13 @@
25
25
  "@mongez/slug": "^1.0.7",
26
26
  "@mongez/supportive-is": "^2.1.4",
27
27
  "@mongez/time-wizard": "^1.0.6",
28
- "@warlock.js/auth": "5.7.0",
29
- "@warlock.js/cache": "5.7.0",
30
- "@warlock.js/cascade": "5.7.0",
31
- "@warlock.js/context": "5.7.0",
32
- "@warlock.js/logger": "5.7.0",
33
- "@warlock.js/seal": "5.7.0",
34
- "@warlock.js/fs": "5.7.0",
28
+ "@warlock.js/auth": "5.8.0",
29
+ "@warlock.js/cache": "5.8.0",
30
+ "@warlock.js/cascade": "5.8.0",
31
+ "@warlock.js/context": "5.8.0",
32
+ "@warlock.js/logger": "5.8.0",
33
+ "@warlock.js/seal": "5.8.0",
34
+ "@warlock.js/fs": "5.8.0",
35
35
  "chokidar": "^5.0.0",
36
36
  "dayjs": "^1.11.19",
37
37
  "es-module-lexer": "^2.0.0",
@@ -57,10 +57,10 @@
57
57
  "react": "^19.2.3",
58
58
  "react-dom": "^19.2.3",
59
59
  "@react-email/render": "^2.0.5",
60
- "@warlock.js/herald": "5.7.0",
61
- "@warlock.js/ai": "5.7.0",
62
- "@warlock.js/access": "5.7.0",
63
- "@warlock.js/notifications": "5.7.0"
60
+ "@warlock.js/herald": "5.8.0",
61
+ "@warlock.js/ai": "5.8.0",
62
+ "@warlock.js/access": "5.8.0",
63
+ "@warlock.js/notifications": "5.8.0"
64
64
  },
65
65
  "peerDependenciesMeta": {
66
66
  "sharp": {
@@ -123,7 +123,7 @@
123
123
  ],
124
124
  "author": "hassanzohdy",
125
125
  "license": "MIT",
126
- "version": "5.7.0",
126
+ "version": "5.8.0",
127
127
  "type": "module",
128
128
  "main": "./esm/index.mjs",
129
129
  "module": "./esm/index.mjs",