@warlock.js/core 4.12.0 → 4.14.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 +214 -0
- package/esm/cli/cli-commands.manager.mjs.map +1 -1
- package/esm/cli/commands/add.command.mjs +1 -1
- package/esm/cli/commands/add.command.mjs.map +1 -1
- package/esm/cli/commands/build.command.mjs +1 -1
- package/esm/cli/commands/build.command.mjs.map +1 -1
- package/esm/cli/commands/create-database.command.mjs +1 -1
- package/esm/cli/commands/create-database.command.mjs.map +1 -1
- package/esm/cli/commands/dev-server.command.mjs +1 -1
- package/esm/cli/commands/dev-server.command.mjs.map +1 -1
- package/esm/cli/commands/doctor/doctor-command.action.mjs.map +1 -1
- package/esm/cli/commands/doctor.command.mjs +1 -1
- package/esm/cli/commands/doctor.command.mjs.map +1 -1
- package/esm/cli/commands/drop-tables.command.mjs +1 -1
- package/esm/cli/commands/drop-tables.command.mjs.map +1 -1
- package/esm/cli/commands/generate/generate.command.mjs +1 -1
- package/esm/cli/commands/generate/generate.command.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/controller.generator.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/migration.generator.mjs +1 -1
- package/esm/cli/commands/generate/generators/migration.generator.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/model.generator.mjs +1 -1
- package/esm/cli/commands/generate/generators/model.generator.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/generate/generators/repository.generator.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/resource.generator.mjs.map +1 -1
- package/esm/cli/commands/generate/generators/service.generator.mjs.map +1 -1
- package/esm/cli/commands/generate/utils/path-resolver.mjs +1 -1
- package/esm/cli/commands/generate/utils/writer.mjs +1 -1
- package/esm/cli/commands/migrate.command.mjs +1 -1
- package/esm/cli/commands/migrate.command.mjs.map +1 -1
- package/esm/cli/commands/routes/routes-command.action.mjs.map +1 -1
- package/esm/cli/commands/routes.command.mjs +1 -1
- package/esm/cli/commands/routes.command.mjs.map +1 -1
- package/esm/cli/commands/seed.command.mjs +1 -1
- package/esm/cli/commands/seed.command.mjs.map +1 -1
- package/esm/cli/commands/start-production.command.mjs +1 -1
- package/esm/cli/commands/start-production.command.mjs.map +1 -1
- package/esm/cli/commands/storage-put.action.mjs.map +1 -1
- package/esm/cli/commands/storage-put.command.mjs +1 -1
- package/esm/cli/commands/storage-put.command.mjs.map +1 -1
- package/esm/cli/commands/typings-generator.command.mjs +2 -2
- package/esm/cli/commands/typings-generator.command.mjs.map +1 -1
- package/esm/cli/commands/update.command.mjs +1 -1
- package/esm/cli/commands/update.command.mjs.map +1 -1
- package/esm/cli/commands-loader.mjs +1 -1
- package/esm/cli/commands-loader.mjs.map +1 -1
- package/esm/{cli → commands}/cli-command.d.mts +1 -1
- package/esm/commands/cli-command.d.mts.map +1 -0
- package/esm/{cli → commands}/cli-command.mjs +1 -1
- package/esm/commands/cli-command.mjs.map +1 -0
- package/esm/{cli → commands}/types.d.mts +1 -1
- package/esm/commands/types.d.mts.map +1 -0
- package/esm/config/load-config-files.mjs +1 -1
- package/esm/config/load-config-files.mjs.map +1 -1
- package/esm/connectors/base-connector.mjs +1 -1
- package/esm/connectors/base-connector.mjs.map +1 -1
- package/esm/connectors/connectors-manager.d.mts.map +1 -1
- package/esm/connectors/connectors-manager.mjs +5 -3
- package/esm/connectors/connectors-manager.mjs.map +1 -1
- package/esm/connectors/http-connector.d.mts.map +1 -1
- package/esm/connectors/http-connector.mjs +0 -2
- package/esm/connectors/http-connector.mjs.map +1 -1
- package/esm/database/create-database-action.mjs.map +1 -1
- package/esm/database/drop-tables-action.mjs.map +1 -1
- package/esm/database/migrate-action.mjs +1 -1
- package/esm/database/migrate-action.mjs.map +1 -1
- package/esm/database/seed-command-action.mjs +1 -1
- package/esm/database/seed-command-action.mjs.map +1 -1
- package/esm/dev-server/dev-logger.mjs +1 -1
- package/esm/dev-server/dev-logger.mjs.map +1 -1
- package/esm/dev-server/development-server.mjs +2 -2
- package/esm/dev-server/file-event-handler.mjs +1 -1
- package/esm/dev-server/file-event-handler.mjs.map +1 -1
- package/esm/dev-server/file-manager.mjs +1 -1
- package/esm/dev-server/file-manager.mjs.map +1 -1
- package/esm/dev-server/file-operations.mjs +1 -1
- package/esm/dev-server/file-operations.mjs.map +1 -1
- package/esm/dev-server/files-orchestrator.mjs +3 -18
- package/esm/dev-server/files-orchestrator.mjs.map +1 -1
- package/esm/dev-server/files-watcher.mjs +1 -1
- package/esm/dev-server/files-watcher.mjs.map +1 -1
- package/esm/dev-server/health-checker/file-health-checker.contract.d.mts +1 -1
- package/esm/dev-server/layer-executor.mjs +1 -1
- package/esm/dev-server/loader/register-loader.mjs +1 -1
- package/esm/dev-server/package-json-manager.mjs +1 -1
- package/esm/dev-server/package-json-manager.mjs.map +1 -1
- package/esm/dev-server/parse-imports.mjs +2 -2
- package/esm/dev-server/parse-imports.mjs.map +1 -1
- package/esm/dev-server/tsconfig-manager.mjs +1 -1
- package/esm/dev-server/tsconfig-manager.mjs.map +1 -1
- package/esm/dev-server/type-generator.mjs +1 -1
- package/esm/dev-server/type-generator.mjs.map +1 -1
- package/esm/dev-server/utils.mjs +1 -1
- package/esm/dev-server/utils.mjs.map +1 -1
- package/esm/generations/add-command.action.mjs +20 -8
- package/esm/generations/add-command.action.mjs.map +1 -1
- package/esm/http/build-cors-options.mjs +32 -0
- package/esm/http/build-cors-options.mjs.map +1 -0
- package/esm/http/middleware/max-body-size.middleware.d.mts +13 -5
- package/esm/http/middleware/max-body-size.middleware.d.mts.map +1 -1
- package/esm/http/middleware/max-body-size.middleware.mjs +13 -5
- package/esm/http/middleware/max-body-size.middleware.mjs.map +1 -1
- package/esm/http/plugins.d.mts.map +1 -1
- package/esm/http/plugins.mjs +2 -9
- package/esm/http/plugins.mjs.map +1 -1
- package/esm/http/server.d.mts.map +1 -1
- package/esm/http/server.mjs +3 -8
- package/esm/http/server.mjs.map +1 -1
- package/esm/index.d.mts +9 -17
- package/esm/index.mjs +4 -15
- package/esm/production/production-builder.mjs +51 -3
- package/esm/production/production-builder.mjs.map +1 -1
- package/esm/production/resolve-build-config.mjs +1 -1
- package/esm/production/resolve-build-config.mjs.map +1 -1
- package/esm/router/router.d.mts +9 -0
- package/esm/router/router.d.mts.map +1 -1
- package/esm/router/router.mjs +58 -5
- package/esm/router/router.mjs.map +1 -1
- package/esm/router/types.d.mts +24 -1
- package/esm/router/types.d.mts.map +1 -1
- package/esm/tests/index.d.mts +3 -2
- package/esm/tests/index.mjs +2 -3
- package/esm/tests/start-http-development-server.d.mts.map +1 -1
- package/esm/tests/start-http-development-server.mjs +38 -15
- package/esm/tests/start-http-development-server.mjs.map +1 -1
- package/esm/tests/test-connectors-selection.d.mts +14 -0
- package/esm/tests/test-connectors-selection.d.mts.map +1 -0
- package/esm/tests/test-connectors-selection.mjs +72 -0
- package/esm/tests/test-connectors-selection.mjs.map +1 -0
- package/esm/tests/test-helpers.d.mts.map +1 -1
- package/esm/tests/test-helpers.mjs +34 -13
- package/esm/tests/test-helpers.mjs.map +1 -1
- package/esm/tests/test-lifecycle-state.mjs +27 -0
- package/esm/tests/test-lifecycle-state.mjs.map +1 -0
- package/esm/tests/test-setup-timeout.mjs +53 -0
- package/esm/tests/test-setup-timeout.mjs.map +1 -0
- package/esm/tests/vitest-setup.d.mts +64 -8
- package/esm/tests/vitest-setup.d.mts.map +1 -1
- package/esm/tests/vitest-setup.mjs +317 -11
- package/esm/tests/vitest-setup.mjs.map +1 -1
- package/esm/utils/framework-vesion.mjs +1 -1
- package/esm/utils/index.d.mts +1 -0
- package/esm/utils/index.mjs +1 -0
- package/esm/utils/normalized-path.d.mts +55 -0
- package/esm/utils/normalized-path.d.mts.map +1 -0
- package/esm/{dev-server/path.mjs → utils/normalized-path.mjs} +15 -2
- package/esm/utils/normalized-path.mjs.map +1 -0
- package/esm/vite/index.d.mts +2 -0
- package/esm/vite/index.mjs +1 -1
- package/esm/vite/lower-stage3-decorators.d.mts +1 -1
- package/esm/vite/lower-stage3-decorators.mjs +1 -1
- package/esm/vite/lower-stage3-decorators.mjs.map +1 -1
- package/esm/warlock-config/index.mjs +1 -1
- package/esm/warlock-config/types.d.mts +34 -1
- package/esm/warlock-config/types.d.mts.map +1 -1
- package/esm/warlock-config/warlock-config.manager.d.mts.map +1 -1
- package/esm/warlock-config/warlock-config.manager.mjs +1 -2
- package/esm/warlock-config/warlock-config.manager.mjs.map +1 -1
- package/llms-full.txt +78 -23
- package/llms.txt +3 -3
- package/package.json +24 -12
- package/skills/lower-stage3-decorators/SKILL.md +4 -2
- package/skills/run-app/SKILL.md +6 -0
- package/skills/test-http/SKILL.md +37 -12
- package/skills/test-service/SKILL.md +167 -24
- package/skills/use-middleware/SKILL.md +5 -1
- package/esm/cli/cli-command.d.mts.map +0 -1
- package/esm/cli/cli-command.mjs.map +0 -1
- package/esm/cli/types.d.mts.map +0 -1
- package/esm/dev-server/files-orchestrator.d.mts +0 -89
- package/esm/dev-server/files-orchestrator.d.mts.map +0 -1
- package/esm/dev-server/files-watcher.d.mts +0 -69
- package/esm/dev-server/files-watcher.d.mts.map +0 -1
- package/esm/dev-server/health-checker/checkers/base-health-checker.d.mts +0 -55
- package/esm/dev-server/health-checker/checkers/base-health-checker.d.mts.map +0 -1
- package/esm/dev-server/health-checker/checkers/eslint-health-checker.d.mts +0 -44
- package/esm/dev-server/health-checker/checkers/eslint-health-checker.d.mts.map +0 -1
- package/esm/dev-server/health-checker/checkers/typescript-health-checker.d.mts +0 -60
- package/esm/dev-server/health-checker/checkers/typescript-health-checker.d.mts.map +0 -1
- package/esm/dev-server/health-checker/files-healthcare.manager.d.mts +0 -119
- package/esm/dev-server/health-checker/files-healthcare.manager.d.mts.map +0 -1
- package/esm/dev-server/health-checker/index.mjs +0 -7
- package/esm/dev-server/module-loader.d.mts +0 -63
- package/esm/dev-server/module-loader.d.mts.map +0 -1
- package/esm/dev-server/path.mjs.map +0 -1
- package/esm/tests/test-server-port-channel.d.mts +0 -27
- package/esm/tests/test-server-port-channel.d.mts.map +0 -1
- /package/esm/{cli → commands}/index.d.mts +0 -0
package/llms-full.txt
CHANGED
|
@@ -2439,7 +2439,7 @@ For an even smoother handoff, give the load balancer time to observe the 503 bef
|
|
|
2439
2439
|
|
|
2440
2440
|
---
|
|
2441
2441
|
name: lower-stage3-decorators
|
|
2442
|
-
description: 'Vite/Vitest plugin `lowerStage3Decorators()` that lowers TC39 Stage-3 (native) decorators with esbuild before oxc / the SSR rewrite mangles them — so `@RegisterModel`-decorated Cascade models load under Vitest 4 / Vite 8. Triggers: `lowerStage3Decorators`, "SyntaxError: Invalid or unexpected token" on a decorated class, "@(0, __vite_ssr_import__.X)()", "decorator crashes vitest", "models won''t load in tests", "Vite 8 oxc decorators"; typical import `import { lowerStage3Decorators } from "@warlock.js/core"` in `vite.config.ts` / `vitest.config.ts`. Skip: writing the tests themselves — `@warlock.js/core/test-service/SKILL.md` / `@warlock.js/core/test-http/SKILL.md`; runtime migrations — `@warlock.js/cascade/write-migration/SKILL.md`.'
|
|
2442
|
+
description: 'Vite/Vitest plugin `lowerStage3Decorators()` that lowers TC39 Stage-3 (native) decorators with esbuild before oxc / the SSR rewrite mangles them — so `@RegisterModel`-decorated Cascade models load under Vitest 4 / Vite 8. Triggers: `lowerStage3Decorators`, "SyntaxError: Invalid or unexpected token" on a decorated class, "@(0, __vite_ssr_import__.X)()", "decorator crashes vitest", "models won''t load in tests", "Vite 8 oxc decorators"; typical import `import { lowerStage3Decorators } from "@warlock.js/core/vite"` in `vite.config.ts` / `vitest.config.ts`. Skip: writing the tests themselves — `@warlock.js/core/test-service/SKILL.md` / `@warlock.js/core/test-http/SKILL.md`; runtime migrations — `@warlock.js/cascade/write-migration/SKILL.md`.'
|
|
2443
2443
|
---
|
|
2444
2444
|
|
|
2445
2445
|
# Lower Stage-3 decorators for Vite/Vitest
|
|
@@ -2454,12 +2454,14 @@ The cause is two transforms that don't understand each other. Vite 8 transpiles
|
|
|
2454
2454
|
|
|
2455
2455
|
`lowerStage3Decorators()` fixes it by getting **esbuild** to lower the decorators *first*.
|
|
2456
2456
|
|
|
2457
|
+
⚠ **Changed in 4.13.0 — the import is a subpath now.** The plugin used to be re-exported from the package root; it is not any more, because that put the Vite integration into every application's production module graph. `import { lowerStage3Decorators } from "@warlock.js/core"` now fails with *"has no exported member"* — **add `/vite` to the specifier and nothing else changes.**
|
|
2458
|
+
|
|
2457
2459
|
## Use it
|
|
2458
2460
|
|
|
2459
2461
|
Drop it **first** in your `plugins` array:
|
|
2460
2462
|
|
|
2461
2463
|
```ts title="vite.config.ts"
|
|
2462
|
-
import { lowerStage3Decorators } from "@warlock.js/core";
|
|
2464
|
+
import { lowerStage3Decorators } from "@warlock.js/core/vite";
|
|
2463
2465
|
import mongezVite from "@mongez/vite";
|
|
2464
2466
|
import { defineConfig } from "vitest/config";
|
|
2465
2467
|
|
|
@@ -2519,7 +2521,9 @@ await new Image("./photo.jpg")
|
|
|
2519
2521
|
.save("./output.webp");
|
|
2520
2522
|
```
|
|
2521
2523
|
|
|
2522
|
-
That's the full contract. The chain doesn't
|
|
2524
|
+
That's the full contract. The chain doesn't run sharp until the output method fires; the
|
|
2525
|
+
constructor resolves the sharp module itself, so a missing sharp throws there rather than at the
|
|
2526
|
+
final await.
|
|
2523
2527
|
|
|
2524
2528
|
## Installation
|
|
2525
2529
|
|
|
@@ -3285,6 +3289,8 @@ export default defineConfig({
|
|
|
3285
3289
|
outFile: "app.js", // default — bundle filename
|
|
3286
3290
|
minify: true, // default — esbuild minify
|
|
3287
3291
|
sourcemap: true, // default — true | false | "inline" | "linked"
|
|
3292
|
+
singleBundle: false, // default — one runnable file, deps inlined
|
|
3293
|
+
esmShim: true, // default — require/__filename/__dirname for bundled CJS
|
|
3288
3294
|
},
|
|
3289
3295
|
});
|
|
3290
3296
|
```
|
|
@@ -3296,6 +3302,10 @@ Defaults are sensible for the typical "Node service" deployment. Knobs to actual
|
|
|
3296
3302
|
- **`minify: false`** — flip to debug a production-only bug. Larger bundle, readable stack traces.
|
|
3297
3303
|
- **`sourcemap: "inline"`** — embed the source map in the bundle. Useful when your error reporter only captures the bundle and can't fetch a `.map` sidecar.
|
|
3298
3304
|
- **`sourcemap: false`** — skip source maps entirely. Smaller artifact, but stack traces in production logs lose their file:line precision (and `warlock start` will not enable `--enable-source-maps` since there's nothing to map).
|
|
3305
|
+
- **`singleBundle: true`** — inline dependencies so `node dist/app.js` runs with no `node_modules` and no launcher. Sets `packages: "bundle"` + `splitting: false` as **defaults you can override**. ⚠ Native `.node` addons are still emitted alongside — "single bundle" is one JS file *plus* any native addons. Do NOT reach for it as the default: keeping deps external is right when you deploy the folder.
|
|
3306
|
+
- **`esmShim: false`** — only if you are certain nothing in the graph is CommonJS. Leave it on.
|
|
3307
|
+
|
|
3308
|
+
⚠ **The trap this replaced.** Setting `packages: "bundle"` by hand used to produce a clean build whose process died on start with `Error: Dynamic require of "node:assert" is not supported`. The output is ESM; bundled CJS deps call `require(...)` and read `__dirname`, and neither exists in an ES module, so the bundler substitutes a throwing stub. **A green `warlock build` was not evidence the bundle ran.** `esmShim` now injects `createRequire(import.meta.url)` and friends automatically for any ESM build, so both `singleBundle` and a hand-written `packages: "bundle"` work. An existing hand-written `banner` is preserved — the shim is prepended, not substituted.
|
|
3299
3309
|
|
|
3300
3310
|
### What it preloads
|
|
3301
3311
|
|
|
@@ -4556,20 +4566,22 @@ Client `PUT`s the bytes straight to R2 — your server never sees them.
|
|
|
4556
4566
|
|
|
4557
4567
|
---
|
|
4558
4568
|
name: test-http
|
|
4559
|
-
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`.'
|
|
4569
|
+
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/tests"`. 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`.'
|
|
4560
4570
|
---
|
|
4561
4571
|
|
|
4562
4572
|
# Warlock — HTTP integration tests
|
|
4563
4573
|
|
|
4564
4574
|
Some tests need the full stack: route matching, middleware chain, validation, controller, response serialization. For those, you boot the real HTTP server once per test run and make real `fetch` calls against it.
|
|
4565
4575
|
|
|
4566
|
-
`startHttpTestServer()` is the bootstrap. `testGet` / `testPost` / `expectJson` are the call helpers. Both ship in `@warlock.js/core`.
|
|
4576
|
+
`startHttpTestServer()` is the bootstrap. `testGet` / `testPost` / `expectJson` are the call helpers. Both ship in `@warlock.js/core/tests`.
|
|
4577
|
+
|
|
4578
|
+
⚠ **Changed in 4.13.0 — the import is a subpath now.** These helpers used to be re-exported from the package root; they are not any more, because that put the test helpers into every application's production module graph. `import { testGet } from "@warlock.js/core"` now fails with *"has no exported member"* — **add `/tests` to the specifier and nothing else changes.**
|
|
4567
4579
|
|
|
4568
4580
|
## The shape
|
|
4569
4581
|
|
|
4570
4582
|
```ts title="src/app/users/tests/users.controller.test.ts"
|
|
4571
4583
|
import { describe, expect, it } from "vitest";
|
|
4572
|
-
import { expectJson, testGet, testPost } from "@warlock.js/core";
|
|
4584
|
+
import { expectJson, testGet, testPost } from "@warlock.js/core/tests";
|
|
4573
4585
|
|
|
4574
4586
|
describe("Users API", () => {
|
|
4575
4587
|
it("GET /users returns the list", async () => {
|
|
@@ -4595,7 +4607,7 @@ No `beforeAll`, no manual server start — the project's `src/test-global-setup.
|
|
|
4595
4607
|
## The bootstrap — `startHttpTestServer` / `stopHttpTestServer`
|
|
4596
4608
|
|
|
4597
4609
|
```ts
|
|
4598
|
-
import { startHttpTestServer, stopHttpTestServer } from "@warlock.js/core";
|
|
4610
|
+
import { startHttpTestServer, stopHttpTestServer } from "@warlock.js/core/tests";
|
|
4599
4611
|
```
|
|
4600
4612
|
|
|
4601
4613
|
`startHttpTestServer()` boots a **minimal but real** HTTP server:
|
|
@@ -4636,6 +4648,23 @@ startHttpTestServer({ port: 2032 }).
|
|
|
4636
4648
|
|
|
4637
4649
|
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.
|
|
4638
4650
|
|
|
4651
|
+
### Port `0` is refused
|
|
4652
|
+
|
|
4653
|
+
`port: 0` — "let the OS pick one" — is not supported, and says so rather than half-working:
|
|
4654
|
+
|
|
4655
|
+
```
|
|
4656
|
+
startHttpTestServer() cannot run on port 0. Pass an explicit port — e.g.
|
|
4657
|
+
startHttpTestServer({ port: 3999 }) — or set `http.port` in your config. Test workers
|
|
4658
|
+
are separate processes that resolve the server's URL from the port published at
|
|
4659
|
+
startup, and an OS-assigned port is not knowable to them.
|
|
4660
|
+
```
|
|
4661
|
+
|
|
4662
|
+
The reason is the worker handoff described above: the port the OS assigns is never recorded anywhere the workers can read, so `getTestServerUrl()` would resolve `0` and send every request to `http://host:0`.
|
|
4663
|
+
|
|
4664
|
+
### If startup fails, nothing is left behind
|
|
4665
|
+
|
|
4666
|
+
`startHttpTestServer()` owns the connectors it starts. A failure part-way through — a bad config, a connector that won't boot — tears down whatever came up, withdraws the published port, and **rethrows the original error**; a failure during that cleanup is logged but never replaces the cause. `stopHttpTestServer()` withdraws the port and resets its state even when the shutdown itself throws, and still surfaces that failure.
|
|
4667
|
+
|
|
4639
4668
|
## Project wiring — `src/test-global-setup.ts` + `vite.config.ts`
|
|
4640
4669
|
|
|
4641
4670
|
```ts title="src/test-global-setup.ts"
|
|
@@ -4643,7 +4672,7 @@ This runs whether or not you passed a port, so a collision never reaches you as
|
|
|
4643
4672
|
* Global Test Setup
|
|
4644
4673
|
* Runs ONCE in the main process before all test workers start.
|
|
4645
4674
|
*/
|
|
4646
|
-
import { startHttpTestServer, stopHttpTestServer } from "@warlock.js/core";
|
|
4675
|
+
import { startHttpTestServer, stopHttpTestServer } from "@warlock.js/core/tests";
|
|
4647
4676
|
|
|
4648
4677
|
export async function setup() {
|
|
4649
4678
|
await startHttpTestServer();
|
|
@@ -4655,7 +4684,7 @@ export async function teardown() {
|
|
|
4655
4684
|
```
|
|
4656
4685
|
|
|
4657
4686
|
```ts title="vite.config.ts"
|
|
4658
|
-
import { lowerStage3Decorators } from "@warlock.js/core";
|
|
4687
|
+
import { lowerStage3Decorators } from "@warlock.js/core/vite";
|
|
4659
4688
|
import mongezVite from "@mongez/vite";
|
|
4660
4689
|
import { defineConfig } from "vitest/config";
|
|
4661
4690
|
|
|
@@ -4680,7 +4709,7 @@ Everything is built on native `fetch` — no extra dependency, no special wire f
|
|
|
4680
4709
|
### URL resolution
|
|
4681
4710
|
|
|
4682
4711
|
```ts
|
|
4683
|
-
import { getTestServerUrl } from "@warlock.js/core";
|
|
4712
|
+
import { getTestServerUrl } from "@warlock.js/core/tests";
|
|
4684
4713
|
|
|
4685
4714
|
const url = getTestServerUrl(); // → "http://localhost:2031" (defaults)
|
|
4686
4715
|
```
|
|
@@ -4697,7 +4726,7 @@ import {
|
|
|
4697
4726
|
testPut,
|
|
4698
4727
|
testPatch,
|
|
4699
4728
|
testDelete,
|
|
4700
|
-
} from "@warlock.js/core";
|
|
4729
|
+
} from "@warlock.js/core/tests";
|
|
4701
4730
|
|
|
4702
4731
|
await testGet("/products");
|
|
4703
4732
|
await testGet("/products?published=true", { headers: { "X-Tenant": "abc" } });
|
|
@@ -4714,7 +4743,7 @@ All accept a relative path (leading `/` optional) and a standard `RequestInit`.
|
|
|
4714
4743
|
### Parsing + asserting — `expectJson<T>`
|
|
4715
4744
|
|
|
4716
4745
|
```ts
|
|
4717
|
-
import { expectJson, parseJsonResponse } from "@warlock.js/core";
|
|
4746
|
+
import { expectJson, parseJsonResponse } from "@warlock.js/core/tests";
|
|
4718
4747
|
|
|
4719
4748
|
// Parse-only
|
|
4720
4749
|
const body = await parseJsonResponse<MyShape>(response);
|
|
@@ -4733,7 +4762,7 @@ const body = await expectJson<MyShape>(response, 404); // expects 404 (testi
|
|
|
4733
4762
|
|
|
4734
4763
|
```ts
|
|
4735
4764
|
import { describe, expect, it } from "vitest";
|
|
4736
|
-
import { expectJson, testGet, testPost } from "@warlock.js/core";
|
|
4765
|
+
import { expectJson, testGet, testPost } from "@warlock.js/core/tests";
|
|
4737
4766
|
|
|
4738
4767
|
describe("Products API — happy path", () => {
|
|
4739
4768
|
it("creates and reads back a product", async () => {
|
|
@@ -4872,7 +4901,7 @@ This is fine for normal test flow. It bites when you're inside a transaction the
|
|
|
4872
4901
|
|
|
4873
4902
|
---
|
|
4874
4903
|
name: test-service
|
|
4875
|
-
description: 'Pure unit tests against services, repositories, models, and use-cases — `setupTest({ connectors })` bootstraps each Vitest worker with its own DB/cache connections so you can call your code directly. Triggers: `setupTest`, `src/test-setup.ts`, `tests.connectors`, `Application.setEnvironment`; "unit-test a service", "test a repository query", "vitest setupFiles", "skip connectors for pure-logic tests"; typical import `import { setupTest } from "@warlock.js/core"`. Skip: HTTP integration — `@warlock.js/core/test-http/SKILL.md`; warlock add test scaffold — `@warlock.js/core/write-cli-command/SKILL.md`; competing tooling: jest direct, `supertest`, `nock`.'
|
|
4904
|
+
description: 'Pure unit tests against services, repositories, models, and use-cases — `setupTest({ connectors })` bootstraps each Vitest worker with its own DB/cache connections so you can call your code directly. Triggers: `setupTest`, `src/test-setup.ts`, `tests.connectors`, `Application.setEnvironment`; "unit-test a service", "test a repository query", "vitest setupFiles", "skip connectors for pure-logic tests"; typical import `import { setupTest } from "@warlock.js/core/tests"`. Skip: HTTP integration — `@warlock.js/core/test-http/SKILL.md`; warlock add test scaffold — `@warlock.js/core/write-cli-command/SKILL.md`; competing tooling: jest direct, `supertest`, `nock`.'
|
|
4876
4905
|
---
|
|
4877
4906
|
|
|
4878
4907
|
# Warlock — test a service
|
|
@@ -4881,6 +4910,8 @@ For unit tests, you import the thing under test and call it directly. No HTTP, n
|
|
|
4881
4910
|
|
|
4882
4911
|
`setupTest()` is the one-call bootstrap that gives each worker that environment.
|
|
4883
4912
|
|
|
4913
|
+
⚠ **Changed in 4.13.0 — the import is a subpath now.** `setupTest` used to be re-exported from the package root; it is not any more, because that put the test helpers into every application's production module graph. `import { setupTest } from "@warlock.js/core"` now fails with *"has no exported member"* — **add `/tests` to the specifier and nothing else changes.**
|
|
4914
|
+
|
|
4884
4915
|
## The shape
|
|
4885
4916
|
|
|
4886
4917
|
```ts title="src/app/users/tests/register-user.service.test.ts"
|
|
@@ -4909,7 +4940,7 @@ No `beforeAll(setupTest)` in this file — the project's `src/test-setup.ts` (re
|
|
|
4909
4940
|
## `setupTest({ connectors })` — the worker bootstrap
|
|
4910
4941
|
|
|
4911
4942
|
```ts
|
|
4912
|
-
import { setupTest } from "@warlock.js/core";
|
|
4943
|
+
import { setupTest } from "@warlock.js/core/tests";
|
|
4913
4944
|
|
|
4914
4945
|
await setupTest({ connectors: true });
|
|
4915
4946
|
```
|
|
@@ -4957,13 +4988,13 @@ The `warlock add test` feature creates both files. The standard wiring:
|
|
|
4957
4988
|
* Per-Worker Test Setup
|
|
4958
4989
|
* Runs in EACH Vitest worker thread before tests execute.
|
|
4959
4990
|
*/
|
|
4960
|
-
import { setupTest } from "@warlock.js/core";
|
|
4991
|
+
import { setupTest } from "@warlock.js/core/tests";
|
|
4961
4992
|
|
|
4962
4993
|
await setupTest({ connectors: true });
|
|
4963
4994
|
```
|
|
4964
4995
|
|
|
4965
4996
|
```ts title="vite.config.ts"
|
|
4966
|
-
import { lowerStage3Decorators } from "@warlock.js/core";
|
|
4997
|
+
import { lowerStage3Decorators } from "@warlock.js/core/vite";
|
|
4967
4998
|
import mongezVite from "@mongez/vite";
|
|
4968
4999
|
import { defineConfig } from "vitest/config";
|
|
4969
5000
|
|
|
@@ -5082,11 +5113,11 @@ Vitest runs tests in a single worker file sequentially, so an `afterEach` trunca
|
|
|
5082
5113
|
|
|
5083
5114
|
```ts title="src/app/utils/tests/slugify.test.ts"
|
|
5084
5115
|
import { beforeAll, describe, expect, it } from "vitest";
|
|
5085
|
-
import { setupTest } from "@warlock.js/core";
|
|
5116
|
+
import { setupTest } from "@warlock.js/core/tests";
|
|
5086
5117
|
import { slugify } from "../utils/slugify";
|
|
5087
5118
|
|
|
5088
5119
|
beforeAll(async () => {
|
|
5089
|
-
await setupTest({ connectors: false }); //
|
|
5120
|
+
await setupTest({ connectors: false }); // starts no connectors at all
|
|
5090
5121
|
});
|
|
5091
5122
|
|
|
5092
5123
|
describe("slugify", () => {
|
|
@@ -5096,7 +5127,9 @@ describe("slugify", () => {
|
|
|
5096
5127
|
});
|
|
5097
5128
|
```
|
|
5098
5129
|
|
|
5099
|
-
`setupTest` is idempotent per worker (`isSetupComplete` flag) — calling it again with different options after `src/test-setup.ts` already ran is a no-op. To genuinely skip connectors, either set `tests.connectors: false` in config (project-wide) or rely on the default in `src/test-setup.ts` being what you want most of the time.
|
|
5130
|
+
`setupTest` is idempotent per worker (`isSetupComplete` flag) — calling it again with different options after `src/test-setup.ts` already ran is a no-op. **That includes a `connectors: false` call: if `src/test-setup.ts` already ran `setupTest()` in this worker, the example above changes nothing.** To genuinely skip connectors, either set `tests.connectors: false` in config (project-wide) or rely on the default in `src/test-setup.ts` being what you want most of the time.
|
|
5131
|
+
|
|
5132
|
+
⚠ **Config beats the parameter.** If `tests.connectors` is set at all, `setupTest({ connectors })` cannot override it — the config value wins. That is the current contract, not an accident; a per-call override is under discussion for a later release.
|
|
5100
5133
|
|
|
5101
5134
|
## Gotchas
|
|
5102
5135
|
|
|
@@ -5104,7 +5137,7 @@ describe("slugify", () => {
|
|
|
5104
5137
|
- **Per-worker connections are separate from the HTTP server's connections.** A row inserted by a service-level test is on the worker's connection; the HTTP test server has its own. They don't see each other unless they're both pointing at the same physical DB and the inserting test has already committed.
|
|
5105
5138
|
- **`NODE_ENV` is set to `"test"`** by `setupTest`. Code that branches on `Application.isProduction` / `Application.isDevelopment` sees `false` for both. If your tests need production-like config (cookies, CORS), set those values in `src/config/*.ts` explicitly under the test branch — don't rely on the env flag.
|
|
5106
5139
|
- **No HTTP from this layer.** `setupTest({ connectors: true })` never starts the HTTP connector by design. Don't try to `request.app.http` your way to a fetch test — use the `test-http` skill instead.
|
|
5107
|
-
- **Don't import `vitest-setup` from `@warlock.js/core/src/...`.** The public surface is `import { setupTest } from "@warlock.js/core"`. Reaching into source paths breaks when the package layout shifts.
|
|
5140
|
+
- **Don't import `vitest-setup` from `@warlock.js/core/src/...`.** The public surface is `import { setupTest } from "@warlock.js/core/tests"`. Reaching into source paths breaks when the package layout shifts.
|
|
5108
5141
|
- **Test files need the `.test.ts` suffix.** `include: ["src/app/**/*.test.ts"]` is what vitest scans. A file named `service.tests.ts` (plural) silently doesn't run.
|
|
5109
5142
|
|
|
5110
5143
|
## See also
|
|
@@ -6241,6 +6274,8 @@ Server errors (5xx) are not cached — clients can retry past a 5xx. 4xx respons
|
|
|
6241
6274
|
|
|
6242
6275
|
`http.bodyLimit` in config is read by Fastify at server-start and applies to every body. `middleware.maxBodySize()` is a per-route middleware on top — it checks `Content-Length` after route match and rejects with 413 before body parsing runs. Use both: global as a safety net, per-route for tight caps on small-payload endpoints.
|
|
6243
6276
|
|
|
6277
|
+
⚠ **Since 4.13.0, configuring nothing gives you Fastify's own 1 MB limit.** Earlier versions defaulted to 200 GB, which **replaced** Fastify's protection rather than adding to it — so an app that set nothing had effectively no cap. **Set `bodyLimit` explicitly if you need more than 1 MB.**
|
|
6278
|
+
|
|
6244
6279
|
```ts
|
|
6245
6280
|
// src/config/http.ts
|
|
6246
6281
|
export default { bodyLimit: 10 * 1024 * 1024 }; // 10MB globally
|
|
@@ -6271,7 +6306,9 @@ export default {
|
|
|
6271
6306
|
|
|
6272
6307
|
## `ipFilter` — fail-closed
|
|
6273
6308
|
|
|
6274
|
-
`deny` wins over `allow`. If the IP can't be read (empty / unparseable), the request is rejected with 403. Reads via `request.detectIp()
|
|
6309
|
+
`deny` wins over `allow`. If the IP can't be read (empty / unparseable), the request is rejected with 403. Reads via `request.detectIp()`.
|
|
6310
|
+
|
|
6311
|
+
⚠ **Since 4.13.0 `http.trustProxy` defaults to `false`**, so `request.detectIp()` returns the socket address and **`X-Real-IP` / `X-Forwarded-For` are ignored unless you opt in.** Set `trustProxy: true` **only when you are genuinely behind a proxy that overwrites those headers** — before 4.13.0 the default was `true`, which meant any client could set its own forwarding header and be believed.
|
|
6275
6312
|
|
|
6276
6313
|
```ts
|
|
6277
6314
|
import { middleware } from "@warlock.js/core";
|
|
@@ -8151,12 +8188,30 @@ The framework ships a fixed set of commands you call but don't author. Knowing t
|
|
|
8151
8188
|
|
|
8152
8189
|
| Command | Flags / args | Preloads |
|
|
8153
8190
|
| -------- | ----------------------------------------------- | ------------------------------ |
|
|
8154
|
-
| `warlock migrate` | `--list` (
|
|
8191
|
+
| `warlock migrate` | `--list` / `-l` (executed **and** pending), `--pending` (pending only, sets an exit code), `--fresh` / `-f` (drop tables first) | database, logger |
|
|
8155
8192
|
| `warlock seed` | `--name <pattern>` (run seeds matching the pattern) | full bootstrap (env, configs, app modules) |
|
|
8156
8193
|
| `warlock create-database <name>` | bare positional `<name>` | database |
|
|
8157
8194
|
| `warlock drop.tables` | `--force, -f` (skip confirmation prompt) | database, logger |
|
|
8158
8195
|
| `warlock db.indexes` | builds DB indexes for every registered model | database |
|
|
8159
8196
|
|
|
8197
|
+
**Asking what will run next.** `warlock migrate --list` prints executed migrations and then the pending ones **in execution order**. Do not derive the pending set by differencing `--all` against `--list`: `--all` globs `src/app` only, while `--list` reads the migrations table, which also holds migrations that packages register through `database.migrations` (`@warlock.js/auth` contributes two). The difference under-counts pending, in the direction that says "safe to proceed".
|
|
8198
|
+
|
|
8199
|
+
`--list` is a report and always exits `0`. `--pending` is a gate, and its exit code is its whole API:
|
|
8200
|
+
|
|
8201
|
+
| Exit | Meaning |
|
|
8202
|
+
| ---- | ------- |
|
|
8203
|
+
| `0` | computed, nothing pending |
|
|
8204
|
+
| `1` | computed, N pending |
|
|
8205
|
+
| `2` | **could not be computed** |
|
|
8206
|
+
|
|
8207
|
+
```bash
|
|
8208
|
+
warlock migrate --pending && ./deploy.sh
|
|
8209
|
+
```
|
|
8210
|
+
|
|
8211
|
+
`2` is separate from `1` on purpose — a script must be able to tell a backlog from an unknown, because one means *run them* and the other means *stop*. When the migration files cannot be loaded, both commands print `Pending: unavailable — <reason>` beneath a complete executed listing rather than reporting `0`.
|
|
8212
|
+
|
|
8213
|
+
**If you are writing a command that reports on pending migrations:** register migrations first. `listPendingMigrations()` filters the runner's registry, so a caller that has not loaded anything gets `[]` — which reads as "nothing pending" and is not the same claim.
|
|
8214
|
+
|
|
8160
8215
|
### Scaffolding
|
|
8161
8216
|
|
|
8162
8217
|
The `generate.*` family covers every module piece:
|
package/llms.txt
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
- [encrypt-data](@warlock.js/core/encrypt-data/SKILL.md): Reversible AES-256-GCM `encrypt` / `decrypt` for secrets you need to read back; one-way HMAC-SHA256 `hmacHash` for deterministic fingerprints (lookup/dedup of encrypted columns). Keys come from `src/config/encryption.ts`. Triggers: `encrypt`, `decrypt`, `hmacHash`, `EncryptionConfigurations`, `APP_ENCRYPTION_KEY`, `APP_HMAC_KEY`; "store an API key reversibly", "fingerprint an encrypted column for lookup", "AES-256-GCM secret", "HMAC-SHA256 dedup key"; typical import `import { encrypt, decrypt, hmacHash } from "@warlock.js/core"`. Skip: password hashing — `@warlock.js/core/hash-password/SKILL.md`; config wiring — `@warlock.js/core/configure-app/SKILL.md`; competing libs Node `crypto` direct, `crypto-js`, `libsodium-wrappers`.
|
|
18
18
|
- [hash-password](@warlock.js/core/hash-password/SKILL.md): One-way bcrypt password hashing — `hashPassword` / `verifyPassword`, plus the declarative `useHashedPassword()` schema transformer that auto-hashes a model's password field on save. Salt rounds come from `src/config/encryption.ts`. Triggers: `hashPassword`, `verifyPassword`, `useHashedPassword`, `password.salt`, `bcryptjs`; "hash a user password", "verify login credentials", "auto-hash on save", "rotate a password"; typical import `import { hashPassword, verifyPassword } from "@warlock.js/core"`. Skip: reversible secrets — `@warlock.js/core/encrypt-data/SKILL.md`; the other transformers — `@warlock.js/core/use-model-transformers/SKILL.md`; config wiring — `@warlock.js/core/configure-app/SKILL.md`; competing libs `bcrypt` native, `argon2`, `scrypt`.
|
|
19
19
|
- [health-checks](@warlock.js/core/health-checks/SKILL.md): Built-in liveness (`/health`) and readiness (`/ready`) endpoints plus graceful HTTP request draining for zero-downtime deploys — the `health` registry (`health.addCheck`/`removeCheck`), the `http.health.*` and `http.gracefulShutdown.*` config, and how readiness ties into `Application.isShuttingDown`. Triggers: `health`, `health.addCheck`, `health.removeCheck`, `HealthCheck`, `/health`, `/ready`, `http.health`, `http.gracefulShutdown`, `forceCloseConnections`, "liveness probe", "readiness probe", "graceful shutdown", "drain in-flight requests", "zero-downtime deploy", "kubernetes health check", "503 until ready"; typical import `import { health } from "@warlock.js/core"`. Skip: the `Application.onShutdown` / `onceBooted` lifecycle hooks — `@warlock.js/core/use-app-context/SKILL.md`; maintenance-mode 503s — `@warlock.js/core/use-middleware/SKILL.md`; connector lifecycle — `@warlock.js/core/add-connector/SKILL.md`; competing libs `@fastify/under-pressure`, `terminus`, hand-rolled `/health` controllers.
|
|
20
|
-
- [lower-stage3-decorators](@warlock.js/core/lower-stage3-decorators/SKILL.md): Vite/Vitest plugin `lowerStage3Decorators()` that lowers TC39 Stage-3 (native) decorators with esbuild before oxc / the SSR rewrite mangles them — so `@RegisterModel`-decorated Cascade models load under Vitest 4 / Vite 8. Triggers: `lowerStage3Decorators`, "SyntaxError: Invalid or unexpected token" on a decorated class, "@(0, __vite_ssr_import__.X)()", "decorator crashes vitest", "models won't load in tests", "Vite 8 oxc decorators"; typical import `import { lowerStage3Decorators } from "@warlock.js/core"` in `vite.config.ts` / `vitest.config.ts`. Skip: writing the tests themselves — `@warlock.js/core/test-service/SKILL.md` / `@warlock.js/core/test-http/SKILL.md`; runtime migrations — `@warlock.js/cascade/write-migration/SKILL.md`.
|
|
20
|
+
- [lower-stage3-decorators](@warlock.js/core/lower-stage3-decorators/SKILL.md): Vite/Vitest plugin `lowerStage3Decorators()` that lowers TC39 Stage-3 (native) decorators with esbuild before oxc / the SSR rewrite mangles them — so `@RegisterModel`-decorated Cascade models load under Vitest 4 / Vite 8. Triggers: `lowerStage3Decorators`, "SyntaxError: Invalid or unexpected token" on a decorated class, "@(0, __vite_ssr_import__.X)()", "decorator crashes vitest", "models won't load in tests", "Vite 8 oxc decorators"; typical import `import { lowerStage3Decorators } from "@warlock.js/core/vite"` in `vite.config.ts` / `vitest.config.ts`. Skip: writing the tests themselves — `@warlock.js/core/test-service/SKILL.md` / `@warlock.js/core/test-http/SKILL.md`; runtime migrations — `@warlock.js/cascade/write-migration/SKILL.md`.
|
|
21
21
|
- [process-image](@warlock.js/core/process-image/SKILL.md): Transform images with the `Image` class — resize, crop, rotate, format, quality, watermark, blur, etc. — using a deferred pipeline that runs only at `save()` / `toBuffer()` / `toBase64()` / `toDataUrl()` time. Requires sharp via `warlock add image`. Triggers: `Image`, `Image.fromFile`, `Image.fromBuffer`, `Image.fromUrl`, `.resize`, `.crop`, `.watermark`, `.toBuffer`, `.toDataUrl`, `.apply`; "resize an image", "generate a thumbnail", "watermark a product photo", "build an image pipeline"; typical import `import { Image } from "@warlock.js/core"`. Skip: multipart upload entry — `@warlock.js/core/upload-file/SKILL.md`; storage persistence — `@warlock.js/core/store-file/SKILL.md`; competing libs `sharp` direct, `jimp`, `imagemagick`, `gm`.
|
|
22
22
|
- [register-route](@warlock.js/core/register-route/SKILL.md): Register HTTP routes via @warlock.js/core's router — single routes, prefix groups, middleware-guarded blocks, and RESTful resource chains. Routes always live in `src/app/<module>/routes.ts`. Triggers: `router.get`, `router.post`, `router.prefix`, `router.group`, `router.route`, `guarded`; "add a route", "wire a controller to a URL", "group routes by prefix", "register a RESTful resource"; typical import `import { router } from "@warlock.js/core"`. Skip: handler shape — `@warlock.js/core/create-controller/SKILL.md`; CRUD chain details — `@warlock.js/core/build-restful/SKILL.md`; middleware authoring — `@warlock.js/core/write-middleware/SKILL.md`; competing libs `express`, `fastify`, `koa`, `@nestjs/common`.
|
|
23
23
|
- [resolve-path](@warlock.js/core/resolve-path/SKILL.md): Path helpers anchored at `process.cwd()` — `rootPath`, `srcPath`, `appPath`, `configPath`, `publicPath`, `storagePath`, `uploadsPath`, `cachePath`, `logsPath`, `tempPath`, `warlockPath`, `sanitizePath`. Optional `uploads.root` config overrides the uploads anchor. Triggers: `appPath`, `configPath`, `uploadsPath`, `storagePath`, `publicPath`, `cachePath`, `logsPath`, `tempPath`, `sanitizePath`, `paths`; "resolve a path inside src/app", "absolute upload destination", "sanitize a user filename", "ship uploads to a mounted volume"; typical import `import { appPath, uploadsPath } from "@warlock.js/core"`. Skip: HTTP URL helpers — `@warlock.js/core/build-url/SKILL.md`; app metadata — `@warlock.js/core/use-app-context/SKILL.md`; storage abstraction — `@warlock.js/core/store-file/SKILL.md`; competing patterns: `path.join(process.cwd(), ...)`, hand-rolled directory constants.
|
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
- [send-mail](@warlock.js/core/send-mail/SKILL.md): Send transactional email — `Mail` fluent builder, `sendMail()` direct call, React Email components. Test mode auto-captures into an in-memory mailbox; dev mode logs. Triggers: `Mail.to`, `sendMail`, `setMailMode`, `mailEvents`, `assertMailSent`, `getTestMailbox`, `wasMailSentTo`, `closeAllMailers`; "send a transactional email", "build a React Email template", "configure SMTP or SES", "assert an email was sent in tests"; typical import `import { Mail, sendMail } from "@warlock.js/core"`. Skip: per-config wiring — `@warlock.js/core/configure-app/SKILL.md`; layered service patterns — `@warlock.js/core/warlock-conventions/SKILL.md`; competing libs `nodemailer` direct, `@sendgrid/mail`, `resend`, `mailgun.js`.
|
|
27
27
|
- [send-response](@warlock.js/core/send-response/SKILL.md): Send HTTP responses via @warlock.js/core's Response helpers — success/error variants, status helpers, redirects, files, streams, and SSE. Picking the right helper carries the HTTP semantic without manual status codes. Triggers: `response.success`, `response.successCreate`, `response.notFound`, `response.forbidden`, `response.badRequest`, `response.sendFile`, `response.stream`, `response.sse`, `response.replay`, `ResourceNotFoundError`, `ForbiddenError`; "return a 201 from a controller", "send a file", "stream Server-Sent Events", "throw HTTP-shaped errors from services"; typical import `import type { RequestHandler, Response } from "@warlock.js/core"`. Skip: controller shape — `@warlock.js/core/create-controller/SKILL.md`; route registration — `@warlock.js/core/register-route/SKILL.md`; competing patterns: hand-rolled status codes via `reply.code(404).send(...)`, raw Fastify reply.
|
|
28
28
|
- [store-file](@warlock.js/core/store-file/SKILL.md): Read/write/delete files via the `storage` singleton — disks, drivers (local/S3/R2/DO Spaces), `storage.use(name)`, `StorageFile` handles, presigned URLs. Triggers: `storage.put`, `storage.get`, `storage.use`, `StorageFile`, `storageConfigurations`, `getPresignedUrl`, `getPresignedUploadUrl`; "save an uploaded file", "switch between local and S3", "generate a presigned URL", "read file metadata"; typical import `import { storage } from "@warlock.js/core"`. Skip: multipart parsing + image chain — `@warlock.js/core/upload-file/SKILL.md`; image transforms — `@warlock.js/core/process-image/SKILL.md`; storage config shape — `@warlock.js/core/configure-app/SKILL.md`; competing libs `@aws-sdk/client-s3`, `multer`, `formidable`.
|
|
29
|
-
- [test-http](@warlock.js/core/test-http/SKILL.md): 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`.
|
|
30
|
-
- [test-service](@warlock.js/core/test-service/SKILL.md): Pure unit tests against services, repositories, models, and use-cases — `setupTest({ connectors })` bootstraps each Vitest worker with its own DB/cache connections so you can call your code directly. Triggers: `setupTest`, `src/test-setup.ts`, `tests.connectors`, `Application.setEnvironment`; "unit-test a service", "test a repository query", "vitest setupFiles", "skip connectors for pure-logic tests"; typical import `import { setupTest } from "@warlock.js/core"`. Skip: HTTP integration — `@warlock.js/core/test-http/SKILL.md`; warlock add test scaffold — `@warlock.js/core/write-cli-command/SKILL.md`; competing tooling: jest direct, `supertest`, `nock`.
|
|
29
|
+
- [test-http](@warlock.js/core/test-http/SKILL.md): 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/tests"`. 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`.
|
|
30
|
+
- [test-service](@warlock.js/core/test-service/SKILL.md): Pure unit tests against services, repositories, models, and use-cases — `setupTest({ connectors })` bootstraps each Vitest worker with its own DB/cache connections so you can call your code directly. Triggers: `setupTest`, `src/test-setup.ts`, `tests.connectors`, `Application.setEnvironment`; "unit-test a service", "test a repository query", "vitest setupFiles", "skip connectors for pure-logic tests"; typical import `import { setupTest } from "@warlock.js/core/tests"`. Skip: HTTP integration — `@warlock.js/core/test-http/SKILL.md`; warlock add test scaffold — `@warlock.js/core/write-cli-command/SKILL.md`; competing tooling: jest direct, `supertest`, `nock`.
|
|
31
31
|
- [update-packages](@warlock.js/core/update-packages/SKILL.md): Keep a project current with `warlock update` — bump every `@warlock.js/*` dependency in package.json to its latest published version (range operator preserved), then run the lockfile-detected package manager install. Also covers the `warlock dev` update notice, its `u` update-and-restart keyboard shortcut, and the `devServer.checkForUpdates` toggle. Triggers: `warlock update`, `--no-install`, `--dry-run`, `--check`, `checkForUpdates`, `fetchLatestVersion`, `isNewerVersion`; "update warlock packages", "upgrade the framework", "is there a new warlock version", "update notice in the dev server", "press u to update", "dev server keyboard shortcut", "update check offline", "bump @warlock.js/* to latest"; typical CLI `warlock update`. Skip: dev/build/start runtime — `@warlock.js/core/run-app/SKILL.md`; writing a custom command — `@warlock.js/core/write-cli-command/SKILL.md`; installing a NEW feature package (auth, mail, storage) — that is `warlock add`; releasing/publishing the framework — workspace release tooling, not this command.
|
|
32
32
|
- [upload-file](@warlock.js/core/upload-file/SKILL.md): Handle multipart file uploads — read via `request.file()` or `request.validated()`, validate with `v.file()`, save via `UploadedFile.save()` or the storage layer, transform images inline. Triggers: `UploadedFile`, `request.file`, `v.file`, `.save`, `.saveAs`, `.resize`, `.format`, `.quality`, `.image`, `.mimeType`, `.maxSize`; "accept a file upload", "validate file size and mime", "save to S3 or local disk", "resize an uploaded image on save"; typical import `import type { UploadedFile, RequestHandler } from "@warlock.js/core"`. Skip: storage drivers + presigned URLs — `@warlock.js/core/store-file/SKILL.md`; image-only transforms — `@warlock.js/core/process-image/SKILL.md`; schema rules — `@warlock.js/core/validate-input/SKILL.md`; competing libs `multer`, `formidable`, `busboy`.
|
|
33
33
|
- [use-app-context](@warlock.js/core/use-app-context/SKILL.md): Read app-wide context — the `Application` static class (env, version, uptime, runtime strategy, boot lifecycle) plus the `app` runtime accessor (live Fastify, socket.io, router, database via the DI container). Triggers: `Application.isProduction`, `Application.environment`, `Application.runtimeStrategy`, `Application.uptime`, `Application.version`, `Application.onceBooted`, `Application.whenBooted`, `Application.isBooted`, `Application.onShutdown`, `Application.isShuttingDown`, `app.http`, `app.socket`, `app.database`, `app.router`; "branch on environment", "reach the live Fastify instance", "framework version in health endpoint", "dev vs production runtime check", "run code once the app is fully booted", "after all connectors started", "app booted hook", "run cleanup before shutdown", "graceful shutdown hook"; typical import `import { Application, app } from "@warlock.js/core"`. Skip: path helpers — `@warlock.js/core/resolve-path/SKILL.md`; connector start order — `@warlock.js/core/add-connector/SKILL.md`; competing patterns: bare `process.env.NODE_ENV`, ad-hoc Fastify imports.
|
package/package.json
CHANGED
|
@@ -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.14.0",
|
|
40
|
+
"@warlock.js/cache": "4.14.0",
|
|
41
|
+
"@warlock.js/cascade": "4.14.0",
|
|
42
|
+
"@warlock.js/context": "4.14.0",
|
|
43
|
+
"@warlock.js/logger": "4.14.0",
|
|
44
|
+
"@warlock.js/seal": "4.14.0",
|
|
45
|
+
"@warlock.js/fs": "4.14.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.14.0",
|
|
72
|
+
"@warlock.js/ai": "4.14.0",
|
|
73
|
+
"@warlock.js/access": "4.14.0",
|
|
74
|
+
"@warlock.js/notifications": "4.14.0"
|
|
75
75
|
},
|
|
76
76
|
"bin": {
|
|
77
77
|
"warlock": "bin/warlock.js"
|
|
78
78
|
},
|
|
79
|
-
"version": "4.
|
|
79
|
+
"version": "4.14.0",
|
|
80
80
|
"type": "module",
|
|
81
81
|
"main": "./esm/index.mjs",
|
|
82
82
|
"module": "./esm/index.mjs",
|
|
@@ -94,6 +94,18 @@
|
|
|
94
94
|
"default": "./esm/cli/start.mjs"
|
|
95
95
|
}
|
|
96
96
|
},
|
|
97
|
+
"./tests": {
|
|
98
|
+
"import": {
|
|
99
|
+
"types": "./esm/tests/index.d.mts",
|
|
100
|
+
"default": "./esm/tests/index.mjs"
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
"./vite": {
|
|
104
|
+
"import": {
|
|
105
|
+
"types": "./esm/vite/index.d.mts",
|
|
106
|
+
"default": "./esm/vite/index.mjs"
|
|
107
|
+
}
|
|
108
|
+
},
|
|
97
109
|
"./dev-server/health-checker/workers/eslint-health.worker": {
|
|
98
110
|
"import": {
|
|
99
111
|
"types": "./esm/dev-server/health-checker/workers/eslint-health.worker.d.mts",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: lower-stage3-decorators
|
|
3
|
-
description: 'Vite/Vitest plugin `lowerStage3Decorators()` that lowers TC39 Stage-3 (native) decorators with esbuild before oxc / the SSR rewrite mangles them — so `@RegisterModel`-decorated Cascade models load under Vitest 4 / Vite 8. Triggers: `lowerStage3Decorators`, "SyntaxError: Invalid or unexpected token" on a decorated class, "@(0, __vite_ssr_import__.X)()", "decorator crashes vitest", "models won''t load in tests", "Vite 8 oxc decorators"; typical import `import { lowerStage3Decorators } from "@warlock.js/core"` in `vite.config.ts` / `vitest.config.ts`. Skip: writing the tests themselves — `@warlock.js/core/test-service/SKILL.md` / `@warlock.js/core/test-http/SKILL.md`; runtime migrations — `@warlock.js/cascade/write-migration/SKILL.md`.'
|
|
3
|
+
description: 'Vite/Vitest plugin `lowerStage3Decorators()` that lowers TC39 Stage-3 (native) decorators with esbuild before oxc / the SSR rewrite mangles them — so `@RegisterModel`-decorated Cascade models load under Vitest 4 / Vite 8. Triggers: `lowerStage3Decorators`, "SyntaxError: Invalid or unexpected token" on a decorated class, "@(0, __vite_ssr_import__.X)()", "decorator crashes vitest", "models won''t load in tests", "Vite 8 oxc decorators"; typical import `import { lowerStage3Decorators } from "@warlock.js/core/vite"` in `vite.config.ts` / `vitest.config.ts`. Skip: writing the tests themselves — `@warlock.js/core/test-service/SKILL.md` / `@warlock.js/core/test-http/SKILL.md`; runtime migrations — `@warlock.js/cascade/write-migration/SKILL.md`.'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Lower Stage-3 decorators for Vite/Vitest
|
|
@@ -15,12 +15,14 @@ The cause is two transforms that don't understand each other. Vite 8 transpiles
|
|
|
15
15
|
|
|
16
16
|
`lowerStage3Decorators()` fixes it by getting **esbuild** to lower the decorators *first*.
|
|
17
17
|
|
|
18
|
+
⚠ **Changed in 4.13.0 — the import is a subpath now.** The plugin used to be re-exported from the package root; it is not any more, because that put the Vite integration into every application's production module graph. `import { lowerStage3Decorators } from "@warlock.js/core"` now fails with *"has no exported member"* — **add `/vite` to the specifier and nothing else changes.**
|
|
19
|
+
|
|
18
20
|
## Use it
|
|
19
21
|
|
|
20
22
|
Drop it **first** in your `plugins` array:
|
|
21
23
|
|
|
22
24
|
```ts title="vite.config.ts"
|
|
23
|
-
import { lowerStage3Decorators } from "@warlock.js/core";
|
|
25
|
+
import { lowerStage3Decorators } from "@warlock.js/core/vite";
|
|
24
26
|
import mongezVite from "@mongez/vite";
|
|
25
27
|
import { defineConfig } from "vitest/config";
|
|
26
28
|
|
package/skills/run-app/SKILL.md
CHANGED
|
@@ -165,6 +165,8 @@ export default defineConfig({
|
|
|
165
165
|
outFile: "app.js", // default — bundle filename
|
|
166
166
|
minify: true, // default — esbuild minify
|
|
167
167
|
sourcemap: true, // default — true | false | "inline" | "linked"
|
|
168
|
+
singleBundle: false, // default — one runnable file, deps inlined
|
|
169
|
+
esmShim: true, // default — require/__filename/__dirname for bundled CJS
|
|
168
170
|
},
|
|
169
171
|
});
|
|
170
172
|
```
|
|
@@ -176,6 +178,10 @@ Defaults are sensible for the typical "Node service" deployment. Knobs to actual
|
|
|
176
178
|
- **`minify: false`** — flip to debug a production-only bug. Larger bundle, readable stack traces.
|
|
177
179
|
- **`sourcemap: "inline"`** — embed the source map in the bundle. Useful when your error reporter only captures the bundle and can't fetch a `.map` sidecar.
|
|
178
180
|
- **`sourcemap: false`** — skip source maps entirely. Smaller artifact, but stack traces in production logs lose their file:line precision (and `warlock start` will not enable `--enable-source-maps` since there's nothing to map).
|
|
181
|
+
- **`singleBundle: true`** — inline dependencies so `node dist/app.js` runs with no `node_modules` and no launcher. Sets `packages: "bundle"` + `splitting: false` as **defaults you can override**. ⚠ Native `.node` addons are still emitted alongside — "single bundle" is one JS file *plus* any native addons. Do NOT reach for it as the default: keeping deps external is right when you deploy the folder.
|
|
182
|
+
- **`esmShim: false`** — only if you are certain nothing in the graph is CommonJS. Leave it on.
|
|
183
|
+
|
|
184
|
+
⚠ **The trap this replaced.** Setting `packages: "bundle"` by hand used to produce a clean build whose process died on start with `Error: Dynamic require of "node:assert" is not supported`. The output is ESM; bundled CJS deps call `require(...)` and read `__dirname`, and neither exists in an ES module, so the bundler substitutes a throwing stub. **A green `warlock build` was not evidence the bundle ran.** `esmShim` now injects `createRequire(import.meta.url)` and friends automatically for any ESM build, so both `singleBundle` and a hand-written `packages: "bundle"` work. An existing hand-written `banner` is preserved — the shim is prepended, not substituted.
|
|
179
185
|
|
|
180
186
|
### What it preloads
|
|
181
187
|
|
|
@@ -1,19 +1,21 @@
|
|
|
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`, `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`.'
|
|
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/tests"`. 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
|
|
7
7
|
|
|
8
8
|
Some tests need the full stack: route matching, middleware chain, validation, controller, response serialization. For those, you boot the real HTTP server once per test run and make real `fetch` calls against it.
|
|
9
9
|
|
|
10
|
-
`startHttpTestServer()` is the bootstrap. `testGet` / `testPost` / `expectJson` are the call helpers. Both ship in `@warlock.js/core`.
|
|
10
|
+
`startHttpTestServer()` is the bootstrap. `testGet` / `testPost` / `expectJson` are the call helpers. Both ship in `@warlock.js/core/tests`.
|
|
11
|
+
|
|
12
|
+
⚠ **Changed in 4.13.0 — the import is a subpath now.** These helpers used to be re-exported from the package root; they are not any more, because that put the test helpers into every application's production module graph. `import { testGet } from "@warlock.js/core"` now fails with *"has no exported member"* — **add `/tests` to the specifier and nothing else changes.**
|
|
11
13
|
|
|
12
14
|
## The shape
|
|
13
15
|
|
|
14
16
|
```ts title="src/app/users/tests/users.controller.test.ts"
|
|
15
17
|
import { describe, expect, it } from "vitest";
|
|
16
|
-
import { expectJson, testGet, testPost } from "@warlock.js/core";
|
|
18
|
+
import { expectJson, testGet, testPost } from "@warlock.js/core/tests";
|
|
17
19
|
|
|
18
20
|
describe("Users API", () => {
|
|
19
21
|
it("GET /users returns the list", async () => {
|
|
@@ -39,7 +41,7 @@ No `beforeAll`, no manual server start — the project's `src/test-global-setup.
|
|
|
39
41
|
## The bootstrap — `startHttpTestServer` / `stopHttpTestServer`
|
|
40
42
|
|
|
41
43
|
```ts
|
|
42
|
-
import { startHttpTestServer, stopHttpTestServer } from "@warlock.js/core";
|
|
44
|
+
import { startHttpTestServer, stopHttpTestServer } from "@warlock.js/core/tests";
|
|
43
45
|
```
|
|
44
46
|
|
|
45
47
|
`startHttpTestServer()` boots a **minimal but real** HTTP server:
|
|
@@ -80,6 +82,23 @@ startHttpTestServer({ port: 2032 }).
|
|
|
80
82
|
|
|
81
83
|
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
84
|
|
|
85
|
+
### Port `0` is refused
|
|
86
|
+
|
|
87
|
+
`port: 0` — "let the OS pick one" — is not supported, and says so rather than half-working:
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
startHttpTestServer() cannot run on port 0. Pass an explicit port — e.g.
|
|
91
|
+
startHttpTestServer({ port: 3999 }) — or set `http.port` in your config. Test workers
|
|
92
|
+
are separate processes that resolve the server's URL from the port published at
|
|
93
|
+
startup, and an OS-assigned port is not knowable to them.
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The reason is the worker handoff described above: the port the OS assigns is never recorded anywhere the workers can read, so `getTestServerUrl()` would resolve `0` and send every request to `http://host:0`.
|
|
97
|
+
|
|
98
|
+
### If startup fails, nothing is left behind
|
|
99
|
+
|
|
100
|
+
`startHttpTestServer()` owns the connectors it starts. A failure part-way through — a bad config, a connector that won't boot — tears down whatever came up, withdraws the published port, and **rethrows the original error**; a failure during that cleanup is logged but never replaces the cause. `stopHttpTestServer()` withdraws the port and resets its state even when the shutdown itself throws, and still surfaces that failure.
|
|
101
|
+
|
|
83
102
|
## Project wiring — `src/test-global-setup.ts` + `vite.config.ts`
|
|
84
103
|
|
|
85
104
|
```ts title="src/test-global-setup.ts"
|
|
@@ -87,7 +106,7 @@ This runs whether or not you passed a port, so a collision never reaches you as
|
|
|
87
106
|
* Global Test Setup
|
|
88
107
|
* Runs ONCE in the main process before all test workers start.
|
|
89
108
|
*/
|
|
90
|
-
import { startHttpTestServer, stopHttpTestServer } from "@warlock.js/core";
|
|
109
|
+
import { startHttpTestServer, stopHttpTestServer } from "@warlock.js/core/tests";
|
|
91
110
|
|
|
92
111
|
export async function setup() {
|
|
93
112
|
await startHttpTestServer();
|
|
@@ -99,7 +118,7 @@ export async function teardown() {
|
|
|
99
118
|
```
|
|
100
119
|
|
|
101
120
|
```ts title="vite.config.ts"
|
|
102
|
-
import { lowerStage3Decorators } from "@warlock.js/core";
|
|
121
|
+
import { lowerStage3Decorators } from "@warlock.js/core/vite";
|
|
103
122
|
import mongezVite from "@mongez/vite";
|
|
104
123
|
import { defineConfig } from "vitest/config";
|
|
105
124
|
|
|
@@ -107,7 +126,7 @@ export default defineConfig({
|
|
|
107
126
|
plugins: [lowerStage3Decorators(), mongezVite()],
|
|
108
127
|
test: {
|
|
109
128
|
globalSetup: "./src/test-global-setup.ts", // ← starts the HTTP server
|
|
110
|
-
setupFiles: ["./src/test-setup.ts"], // ←
|
|
129
|
+
setupFiles: ["./src/test-setup.ts"], // ← setupTest + afterAll(teardownTest), per test file
|
|
111
130
|
environment: "node",
|
|
112
131
|
globals: false,
|
|
113
132
|
include: ["src/app/**/*.test.ts"],
|
|
@@ -115,7 +134,13 @@ export default defineConfig({
|
|
|
115
134
|
});
|
|
116
135
|
```
|
|
117
136
|
|
|
118
|
-
Both files (and this config, with `lowerStage3Decorators()` first so decorated models load) are created by `warlock add test`. The split is intentional: `globalSetup` runs ONCE in the main vitest process; `setupFiles` runs
|
|
137
|
+
Both files (and this config, with `lowerStage3Decorators()` first so decorated models load) are created by `warlock add test`. The split is intentional: `globalSetup` runs **ONCE** in the main vitest process; `setupFiles` runs **before every test file**.
|
|
138
|
+
|
|
139
|
+
⚠ **Corrected in 4.14.0.** This line previously said `setupFiles` runs "per worker thread". **It does not** — Vitest runs it before each test file, and the setup module's registry is rebuilt every time. Measured across all four `pool` × `isolate` combinations.
|
|
140
|
+
|
|
141
|
+
**So the service-layer framework is file-scoped: bootstrapped by `setupTest` and closed by the `afterAll(teardownTest)` the setup file registers, once per test file.** ⚠ **`setupTest` alone is not the whole wiring — the paired teardown is mandatory from 4.14.0**; see the `test-service` skill.
|
|
142
|
+
|
|
143
|
+
**HTTP is the exception and stays in `globalSetup`**, which genuinely does run once in the main vitest process and owns a real port. **That split is the point:** one server for the whole run, one framework per test file.
|
|
119
144
|
|
|
120
145
|
## HTTP request helpers
|
|
121
146
|
|
|
@@ -124,7 +149,7 @@ Everything is built on native `fetch` — no extra dependency, no special wire f
|
|
|
124
149
|
### URL resolution
|
|
125
150
|
|
|
126
151
|
```ts
|
|
127
|
-
import { getTestServerUrl } from "@warlock.js/core";
|
|
152
|
+
import { getTestServerUrl } from "@warlock.js/core/tests";
|
|
128
153
|
|
|
129
154
|
const url = getTestServerUrl(); // → "http://localhost:2031" (defaults)
|
|
130
155
|
```
|
|
@@ -141,7 +166,7 @@ import {
|
|
|
141
166
|
testPut,
|
|
142
167
|
testPatch,
|
|
143
168
|
testDelete,
|
|
144
|
-
} from "@warlock.js/core";
|
|
169
|
+
} from "@warlock.js/core/tests";
|
|
145
170
|
|
|
146
171
|
await testGet("/products");
|
|
147
172
|
await testGet("/products?published=true", { headers: { "X-Tenant": "abc" } });
|
|
@@ -158,7 +183,7 @@ All accept a relative path (leading `/` optional) and a standard `RequestInit`.
|
|
|
158
183
|
### Parsing + asserting — `expectJson<T>`
|
|
159
184
|
|
|
160
185
|
```ts
|
|
161
|
-
import { expectJson, parseJsonResponse } from "@warlock.js/core";
|
|
186
|
+
import { expectJson, parseJsonResponse } from "@warlock.js/core/tests";
|
|
162
187
|
|
|
163
188
|
// Parse-only
|
|
164
189
|
const body = await parseJsonResponse<MyShape>(response);
|
|
@@ -177,7 +202,7 @@ const body = await expectJson<MyShape>(response, 404); // expects 404 (testi
|
|
|
177
202
|
|
|
178
203
|
```ts
|
|
179
204
|
import { describe, expect, it } from "vitest";
|
|
180
|
-
import { expectJson, testGet, testPost } from "@warlock.js/core";
|
|
205
|
+
import { expectJson, testGet, testPost } from "@warlock.js/core/tests";
|
|
181
206
|
|
|
182
207
|
describe("Products API — happy path", () => {
|
|
183
208
|
it("creates and reads back a product", async () => {
|