@warlock.js/core 4.10.0 → 4.11.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.
- package/CHANGELOG.md +45 -0
- package/esm/application/application.d.mts +19 -0
- package/esm/application/application.d.mts.map +1 -1
- package/esm/application/application.mjs +25 -0
- package/esm/application/application.mjs.map +1 -1
- package/esm/application/boot-signal.d.mts +77 -0
- package/esm/application/boot-signal.d.mts.map +1 -0
- package/esm/application/boot-signal.mjs +64 -0
- package/esm/application/boot-signal.mjs.map +1 -0
- package/esm/application/index.d.mts +1 -0
- package/esm/application/index.mjs +1 -0
- package/esm/bootstrap.mjs +2 -2
- package/esm/bootstrap.mjs.map +1 -1
- package/esm/cli/cli-commands.manager.mjs +3 -3
- package/esm/cli/cli-commands.manager.mjs.map +1 -1
- package/esm/cli/cli-commands.utils.mjs +57 -1
- package/esm/cli/cli-commands.utils.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/module.generator.mjs +1 -1
- package/esm/cli/commands/generate/generators/module.generator.mjs.map +1 -1
- package/esm/cli/commands/start-production.command.mjs +4 -25
- package/esm/cli/commands/start-production.command.mjs.map +1 -1
- package/esm/cli/types.d.mts +6 -1
- package/esm/cli/types.d.mts.map +1 -1
- package/esm/config/config-getter.mjs +5 -5
- package/esm/config/config-getter.mjs.map +1 -1
- package/esm/config/config-loader.mjs +2 -2
- package/esm/config/config-loader.mjs.map +1 -1
- package/esm/config/config-setter.d.mts +27 -0
- package/esm/config/config-setter.d.mts.map +1 -0
- package/esm/config/config-setter.mjs +10 -0
- package/esm/config/config-setter.mjs.map +1 -0
- package/esm/config/index.mjs +1 -0
- package/esm/connectors/access-connector.mjs +2 -2
- package/esm/connectors/access-connector.mjs.map +1 -1
- package/esm/connectors/ai-connector.mjs +2 -2
- package/esm/connectors/ai-connector.mjs.map +1 -1
- package/esm/connectors/cache-connector.mjs +2 -2
- package/esm/connectors/cache-connector.mjs.map +1 -1
- package/esm/connectors/database-connector.mjs +2 -2
- package/esm/connectors/database-connector.mjs.map +1 -1
- package/esm/connectors/herald-connector.mjs +2 -2
- package/esm/connectors/herald-connector.mjs.map +1 -1
- package/esm/connectors/http-connector.d.mts.map +1 -1
- package/esm/connectors/http-connector.mjs +8 -7
- package/esm/connectors/http-connector.mjs.map +1 -1
- package/esm/connectors/logger-connector.mjs +2 -2
- package/esm/connectors/logger-connector.mjs.map +1 -1
- package/esm/connectors/mail-connector.mjs +2 -2
- package/esm/connectors/mail-connector.mjs.map +1 -1
- package/esm/connectors/notifications-connector.mjs +2 -2
- package/esm/connectors/notifications-connector.mjs.map +1 -1
- package/esm/connectors/socket-connector.mjs +3 -3
- package/esm/connectors/socket-connector.mjs.map +1 -1
- package/esm/connectors/storage.connector.d.mts +11 -5
- package/esm/connectors/storage.connector.d.mts.map +1 -1
- package/esm/connectors/storage.connector.mjs +11 -5
- package/esm/connectors/storage.connector.mjs.map +1 -1
- package/esm/dev-server/loader/load-hook.mjs +1 -1
- package/esm/generations/stubs.mjs +1 -1
- package/esm/generations/stubs.mjs.map +1 -1
- package/esm/http/config.mjs +2 -2
- package/esm/http/config.mjs.map +1 -1
- package/esm/http/createHttpApplication.mjs +2 -2
- package/esm/http/createHttpApplication.mjs.map +1 -1
- package/esm/http/index.d.mts +1 -0
- package/esm/http/index.mjs +1 -0
- package/esm/http/middleware/idempotency.middleware.mjs +5 -5
- package/esm/http/middleware/idempotency.middleware.mjs.map +1 -1
- package/esm/http/middleware/inject-request-context.mjs +2 -2
- package/esm/http/middleware/inject-request-context.mjs.map +1 -1
- package/esm/http/middleware/maintenance.middleware.mjs +4 -4
- package/esm/http/middleware/maintenance.middleware.mjs.map +1 -1
- package/esm/http/plugins.mjs +9 -9
- package/esm/http/plugins.mjs.map +1 -1
- package/esm/http/port-preflight.d.mts +34 -0
- package/esm/http/port-preflight.d.mts.map +1 -0
- package/esm/http/port-preflight.mjs +66 -0
- package/esm/http/port-preflight.mjs.map +1 -0
- package/esm/http/response.mjs +5 -5
- package/esm/http/response.mjs.map +1 -1
- package/esm/http/server.mjs +4 -4
- package/esm/http/server.mjs.map +1 -1
- package/esm/index.d.mts +6 -2
- package/esm/index.mjs +5 -1
- package/esm/production/assert-generated-imports.mjs +94 -0
- package/esm/production/assert-generated-imports.mjs.map +1 -0
- package/esm/production/production-builder.mjs +29 -4
- package/esm/production/production-builder.mjs.map +1 -1
- package/esm/production/production-supervisor.mjs +108 -0
- package/esm/production/production-supervisor.mjs.map +1 -0
- package/esm/storage/storage.d.mts +16 -0
- package/esm/storage/storage.d.mts.map +1 -1
- package/esm/storage/storage.mjs +24 -1
- package/esm/storage/storage.mjs.map +1 -1
- package/esm/tests/index.d.mts +2 -1
- package/esm/tests/index.mjs +1 -0
- package/esm/tests/start-http-development-server.d.mts +15 -2
- package/esm/tests/start-http-development-server.d.mts.map +1 -1
- package/esm/tests/start-http-development-server.mjs +43 -16
- package/esm/tests/start-http-development-server.mjs.map +1 -1
- package/esm/tests/test-helpers.d.mts.map +1 -1
- package/esm/tests/test-helpers.mjs +2 -1
- package/esm/tests/test-helpers.mjs.map +1 -1
- package/esm/tests/test-server-port-channel.d.mts +27 -0
- package/esm/tests/test-server-port-channel.d.mts.map +1 -0
- package/esm/tests/test-server-port-channel.mjs +32 -0
- package/esm/tests/test-server-port-channel.mjs.map +1 -0
- package/esm/utils/load-environment.mjs +59 -0
- package/esm/utils/load-environment.mjs.map +1 -0
- package/esm/utils/paths.mjs +2 -2
- package/esm/utils/paths.mjs.map +1 -1
- package/esm/validation/validateAll.mjs +2 -2
- package/esm/validation/validateAll.mjs.map +1 -1
- package/llms-full.txt +90 -3
- package/llms.txt +1 -1
- package/package.json +13 -13
- package/skills/run-app/SKILL.md +63 -0
- package/skills/test-http/SKILL.md +27 -3
package/package.json
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"@mongez/concat-route": "^1.1.4",
|
|
29
29
|
"@mongez/config": "^1.1.4",
|
|
30
30
|
"@mongez/copper": "^2.1.2",
|
|
31
|
-
"@mongez/dotenv": "^1.
|
|
31
|
+
"@mongez/dotenv": "^1.3.1",
|
|
32
32
|
"@mongez/events": "^2.2.6",
|
|
33
33
|
"@mongez/http": "^3.3.8",
|
|
34
34
|
"@mongez/localization": "^3.4.6",
|
|
@@ -36,13 +36,13 @@
|
|
|
36
36
|
"@mongez/slug": "^1.0.7",
|
|
37
37
|
"@mongez/supportive-is": "^2.1.3",
|
|
38
38
|
"@mongez/time-wizard": "^1.0.6",
|
|
39
|
-
"@warlock.js/auth": "4.
|
|
40
|
-
"@warlock.js/cache": "4.
|
|
41
|
-
"@warlock.js/cascade": "4.
|
|
42
|
-
"@warlock.js/context": "4.
|
|
43
|
-
"@warlock.js/logger": "4.
|
|
44
|
-
"@warlock.js/seal": "4.
|
|
45
|
-
"@warlock.js/fs": "4.
|
|
39
|
+
"@warlock.js/auth": "4.11.0",
|
|
40
|
+
"@warlock.js/cache": "4.11.0",
|
|
41
|
+
"@warlock.js/cascade": "4.11.0",
|
|
42
|
+
"@warlock.js/context": "4.11.0",
|
|
43
|
+
"@warlock.js/logger": "4.11.0",
|
|
44
|
+
"@warlock.js/seal": "4.11.0",
|
|
45
|
+
"@warlock.js/fs": "4.11.0",
|
|
46
46
|
"chokidar": "^5.0.0",
|
|
47
47
|
"dayjs": "^1.11.19",
|
|
48
48
|
"es-module-lexer": "^2.0.0",
|
|
@@ -68,15 +68,15 @@
|
|
|
68
68
|
"react": "^19.2.3",
|
|
69
69
|
"react-dom": "^19.2.3",
|
|
70
70
|
"@react-email/render": "^2.0.5",
|
|
71
|
-
"@warlock.js/herald": "4.
|
|
72
|
-
"@warlock.js/ai": "4.
|
|
73
|
-
"@warlock.js/access": "4.
|
|
74
|
-
"@warlock.js/notifications": "4.
|
|
71
|
+
"@warlock.js/herald": "4.11.0",
|
|
72
|
+
"@warlock.js/ai": "4.11.0",
|
|
73
|
+
"@warlock.js/access": "4.11.0",
|
|
74
|
+
"@warlock.js/notifications": "4.11.0"
|
|
75
75
|
},
|
|
76
76
|
"bin": {
|
|
77
77
|
"warlock": "bin/warlock.js"
|
|
78
78
|
},
|
|
79
|
-
"version": "4.
|
|
79
|
+
"version": "4.11.0",
|
|
80
80
|
"type": "module",
|
|
81
81
|
"main": "./esm/index.mjs",
|
|
82
82
|
"module": "./esm/index.mjs",
|
package/skills/run-app/SKILL.md
CHANGED
|
@@ -219,6 +219,57 @@ Just `warlockConfig: true` — same as `build`. The actual app bootstrap happens
|
|
|
219
219
|
|
|
220
220
|
Means `docker stop` / `kubectl delete pod` works as expected: SIGTERM reaches the bundle, your graceful-shutdown hooks fire, then the parent exits.
|
|
221
221
|
|
|
222
|
+
### pnpm needs esbuild's install script allowed
|
|
223
|
+
|
|
224
|
+
pnpm 10+ will not run a dependency's install script unless the app names it. esbuild's script links its platform-native binary, and `warlock build` shells out to that binary — so the app installs cleanly and then cannot build:
|
|
225
|
+
|
|
226
|
+
```
|
|
227
|
+
[ERR_PNPM_IGNORED_BUILDS] Ignored build scripts: esbuild@0.27.7
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Fix it once in the app's `pnpm-workspace.yaml`:
|
|
231
|
+
|
|
232
|
+
```yaml
|
|
233
|
+
allowBuilds:
|
|
234
|
+
esbuild: true
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Note pnpm reads this from `pnpm-workspace.yaml`, **not** from `package.json`'s `pnpm` field — pnpm 11 warns that the field is ignored and then carries on, so settings left there fail silently.
|
|
238
|
+
|
|
239
|
+
Nothing else is needed for pnpm. Warlock never requires an app to declare a package it does not import: generated code is checked at build time against the app's own `dependencies`, so `warlock build` failing over an unfamiliar package name is a framework bug, not a missing dependency.
|
|
240
|
+
|
|
241
|
+
### Output streams — what a supervisor may trust
|
|
242
|
+
|
|
243
|
+
**A success line on stdout means the app is serving requests.** That is a contract, not a convention, and you can build a CI gate or a health probe on it.
|
|
244
|
+
|
|
245
|
+
| Stream | Carries |
|
|
246
|
+
| ---------- | -------------------------------------------------------------- |
|
|
247
|
+
| **stdout** | the started banner, and start failures. Nothing else. |
|
|
248
|
+
| **stderr** | progress (`🚀 Starting production server...`), diagnostics, the application's own logs |
|
|
249
|
+
|
|
250
|
+
The started banner prints **only** when the running application reports a completed boot — not when the command starts, not when the child is spawned. A child that dies before reporting is a failed start: the failure is written to **both** streams (stderr for humans and log collectors, stdout so a supervisor greping for the banner finds a failure rather than silence), and `warlock start` exits non-zero **even when the child itself exited `0`**.
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
# a CI gate can be this blunt, and it is now correct
|
|
254
|
+
yarn warlock start | grep -q "production server started"
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### How readiness is reported
|
|
258
|
+
|
|
259
|
+
`warlock start` spawns the bundle with an IPC channel and sets `WARLOCK_BOOT_SIGNAL=1` on it. `Application.markBooted()` — which the production entry calls after the late-phase connectors (http, socket) are up — sends one versioned message and closes the channel:
|
|
260
|
+
|
|
261
|
+
```ts
|
|
262
|
+
{ type: "warlock:ready", version: 1, pid, at, environment, runtimeStrategy, bootDurationMs?, port? }
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Three consequences worth knowing:
|
|
266
|
+
|
|
267
|
+
1. **A queue worker with no http connector still reports.** Readiness hangs on a completed boot, not on a bound port. `port` is simply absent.
|
|
268
|
+
2. **Running the bundle any other way changes nothing.** `node dist/app.js`, a Docker `CMD`, or pm2 — the signal is a strict no-op without both the IPC channel and the handshake flag, so Warlock never writes into a channel that belongs to another supervisor.
|
|
269
|
+
3. **A bundle built before 4.11.0 has no signal.** It starts and runs normally, and after ten seconds prints a note on **stderr only** telling you to re-run `warlock build`. An absent signal is never an error, never fails the run, and never kills a slow boot.
|
|
270
|
+
|
|
271
|
+
If you need the same fact inside the app, use `Application.onceBooted()` / `Application.whenBooted()` — the signal and your listeners fire from the same latch.
|
|
272
|
+
|
|
222
273
|
## Picking which mode you're in
|
|
223
274
|
|
|
224
275
|
`Application.environment` and `Application.runtimeStrategy` are separate axes:
|
|
@@ -286,6 +337,18 @@ export default defineConfig({
|
|
|
286
337
|
|
|
287
338
|
CI sets `BUILD_OUT=build/<sha>` per pipeline. `warlock start` reads the same config and finds the bundle without any hardcoded paths.
|
|
288
339
|
|
|
340
|
+
**This recipe only started working in 4.11.0.** Before that, `warlock.config.ts` was evaluated *before* any `.env` file was read, so every `env()` call in it returned its default — silently, under every command. If you copied this recipe earlier and concluded that `BUILD_OUT` was ignored, it was.
|
|
341
|
+
|
|
342
|
+
**Which file the value comes from is decided by `NODE_ENV`, and no Warlock command sets it.** `env()` reads `.env.<NODE_ENV>` when that file exists and falls back to plain `.env`. So:
|
|
343
|
+
|
|
344
|
+
```bash
|
|
345
|
+
warlock build # NODE_ENV unset → reads .env
|
|
346
|
+
NODE_ENV=production warlock build # → reads .env.production
|
|
347
|
+
NODE_ENV=staging warlock build # → reads .env.staging
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
That is deliberate: `build` and `start` do **not** force `production`. Forcing it would silently change which file an existing `NODE_ENV=staging` pipeline reads, and would have the framework overriding an operator's explicit choice at the moment of deployment. Set `NODE_ENV` in your Dockerfile, CI job, or process manager — the same place you already set it for `Application.environment`.
|
|
351
|
+
|
|
289
352
|
### Skip type-gen on machines without write access
|
|
290
353
|
|
|
291
354
|
```bash
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: test-http
|
|
3
|
-
description: 'Integration tests against a real HTTP server — `startHttpTestServer()` boots one shared server in globalSetup, then `testGet` / `testPost` / `expectJson` make typed requests against it. Triggers: `startHttpTestServer`, `stopHttpTestServer`, `testGet`, `testPost`, `testPut`, `testPatch`, `testDelete`, `expectJson`, `getTestServerUrl`, `testRequest`; "integration-test a controller", "end-to-end HTTP test", "globalSetup HTTP server", "assert status and body shape"; typical import `import { testGet, testPost, expectJson } from "@warlock.js/core"`. Skip: pure unit tests — `@warlock.js/core/test-service/SKILL.md`; controller shape — `@warlock.js/core/create-controller/SKILL.md`; competing libs `supertest`, `light-my-request`, `nock`.'
|
|
3
|
+
description: 'Integration tests against a real HTTP server — `startHttpTestServer()` boots one shared server in globalSetup, then `testGet` / `testPost` / `expectJson` make typed requests against it. Triggers: `startHttpTestServer`, `startHttpTestServer({ port })`, `stopHttpTestServer`, `testGet`, `testPost`, `testPut`, `testPatch`, `testDelete`, `expectJson`, `getTestServerUrl`, `testRequest`, `PortInUseError`, `assertPortIsAvailable`, `isPortAvailable`; "integration-test a controller", "end-to-end HTTP test", "globalSetup HTTP server", "assert status and body shape", "test server port already in use", "EADDRINUSE while running tests", "run tests while the dev server is up"; typical import `import { testGet, testPost, expectJson } from "@warlock.js/core"`. Skip: pure unit tests — `@warlock.js/core/test-service/SKILL.md`; controller shape — `@warlock.js/core/create-controller/SKILL.md`; competing libs `supertest`, `light-my-request`, `nock`.'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Warlock — HTTP integration tests
|
|
@@ -56,6 +56,30 @@ Unlike the dev server, it doesn't watch files, doesn't do HMR, doesn't run healt
|
|
|
56
56
|
|
|
57
57
|
Both are idempotent. A second `start` returns early; `stop` on a non-running server logs and returns.
|
|
58
58
|
|
|
59
|
+
### Choosing the port — `startHttpTestServer({ port })`
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
await startHttpTestServer({ port: 3999 });
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The explicit port wins over `http.port` — including over `HTTP_PORT` in `.env`. Use it to run a suite while the dev server holds the configured port, or to run two suites side by side.
|
|
66
|
+
|
|
67
|
+
**Nothing else can move the server.** `startHttpTestServer()` bootstraps the app itself, and that bootstrap re-reads `.env` (dotenv overrides by default), so any value you set before calling it is gone before the port is read. Assigning `process.env.HTTP_PORT` does not work either: `env()` reads dotenv's own store and never falls back to `process.env`. The option is applied after the bootstrap, immediately before the connector binds — that is why it is the only channel that survives.
|
|
68
|
+
|
|
69
|
+
The request helpers follow with no extra wiring: the bound port is published as `WARLOCK_TEST_SERVER_PORT`, which the workers inherit, and `getTestServerUrl()` prefers it over their own `.env`-resolved `http.port`. `stopHttpTestServer()` withdraws it.
|
|
70
|
+
|
|
71
|
+
### The port is preflighted
|
|
72
|
+
|
|
73
|
+
Before binding, the test server checks the resolved port is free and fails with an instruction if it isn't:
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
Port 2031 is already in use on localhost. Stop the dev server (or whatever else is
|
|
77
|
+
listening on port 2031) and run again, or start on a free port — e.g.
|
|
78
|
+
startHttpTestServer({ port: 2032 }).
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
This runs whether or not you passed a port, so a collision never reaches you as a bare `EADDRINUSE` from inside Fastify. The failure is a `PortInUseError` carrying `port` and `host`. The same check is available on its own — `assertPortIsAvailable(port, host)` throws it, `isPortAvailable(port, host)` returns a boolean.
|
|
82
|
+
|
|
59
83
|
## Project wiring — `src/test-global-setup.ts` + `vite.config.ts`
|
|
60
84
|
|
|
61
85
|
```ts title="src/test-global-setup.ts"
|
|
@@ -105,7 +129,7 @@ import { getTestServerUrl } from "@warlock.js/core";
|
|
|
105
129
|
const url = getTestServerUrl(); // → "http://localhost:2031" (defaults)
|
|
106
130
|
```
|
|
107
131
|
|
|
108
|
-
Reads `http.host` (default `"localhost"`) and `http.port` (default `2031`) from config
|
|
132
|
+
Reads `http.host` (default `"localhost"`) and `http.port` (default `2031`) from config — unless `startHttpTestServer` published a port, which wins, since a worker's own config never sees the `{ port }` option passed in `globalSetup`. If you change the HTTP config, helpers follow automatically.
|
|
109
133
|
|
|
110
134
|
### Verb helpers
|
|
111
135
|
|
|
@@ -274,7 +298,7 @@ This is fine for normal test flow. It bites when you're inside a transaction the
|
|
|
274
298
|
## Gotchas
|
|
275
299
|
|
|
276
300
|
- **`globalSetup` must export `setup` and `teardown`.** Vitest reads them by name. A typo in the export gets you a confusing "server not running" error on the first `testGet` call.
|
|
277
|
-
- **Port conflicts.** If `http.port` matches your running dev server, `
|
|
301
|
+
- **Port conflicts.** If `http.port` matches your running dev server, the preflight fails with a `PortInUseError` naming the port. Either stop the dev server or pass a free one: `startHttpTestServer({ port: 3999 })`.
|
|
278
302
|
- **Auth tokens need a real user.** Generating a JWT with a non-existent `user_id` works — but the auth middleware's user-loading step will reject the request with 401 because it can't find the user in the DB.
|
|
279
303
|
- **`expectJson` parses the body once.** If you call it twice on the same response, the second call gets an already-consumed stream error. Capture the result.
|
|
280
304
|
- **The HTTP server's connection is NOT torn down between test files.** Data persists across files within a single `vitest` run. Either truncate in `afterEach` / `afterAll`, or design your tests to be order-independent.
|