@warlock.js/core 5.32.0 → 5.34.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 +64 -46
- package/esm/application/application-config-types.d.mts +8 -0
- package/esm/application/application-config-types.d.mts.map +1 -1
- package/esm/application/boot-signal.d.mts +8 -8
- package/esm/application/boot-signal.d.mts.map +1 -1
- package/esm/application/boot-signal.mjs +15 -9
- package/esm/application/boot-signal.mjs.map +1 -1
- package/esm/cli/commands/ai/ai-command.action.mjs +140 -0
- package/esm/cli/commands/ai/ai-command.action.mjs.map +1 -0
- package/esm/cli/commands/ai.command.mjs +50 -0
- package/esm/cli/commands/ai.command.mjs.map +1 -0
- package/esm/cli/commands/check/check-command.action.mjs +4 -0
- package/esm/cli/commands/check/check-command.action.mjs.map +1 -1
- package/esm/cli/commands/check.command.mjs +6 -1
- package/esm/cli/commands/check.command.mjs.map +1 -1
- package/esm/cli/commands/e2e.command.mjs +63 -0
- package/esm/cli/commands/e2e.command.mjs.map +1 -0
- package/esm/cli/commands/load-e2e-spec.mjs +47 -0
- package/esm/cli/commands/load-e2e-spec.mjs.map +1 -0
- package/esm/cli/commands/pm2/pm2-command.action.mjs +91 -0
- package/esm/cli/commands/pm2/pm2-command.action.mjs.map +1 -0
- package/esm/cli/commands/pm2/pm2-config.mjs +115 -0
- package/esm/cli/commands/pm2/pm2-config.mjs.map +1 -0
- package/esm/cli/commands/pm2.command.mjs +53 -0
- package/esm/cli/commands/pm2.command.mjs.map +1 -0
- package/esm/cli/framework-cli-commands.mjs +6 -0
- package/esm/cli/framework-cli-commands.mjs.map +1 -1
- package/esm/connectors/connectors-manager.d.mts.map +1 -1
- package/esm/connectors/connectors-manager.mjs +1 -4
- package/esm/connectors/connectors-manager.mjs.map +1 -1
- package/esm/connectors/shutdown-timeout.mjs +7 -0
- package/esm/connectors/shutdown-timeout.mjs.map +1 -0
- package/esm/connectors/socket-connector.d.mts.map +1 -1
- package/esm/connectors/socket-connector.mjs +8 -1
- package/esm/connectors/socket-connector.mjs.map +1 -1
- package/esm/dev-server/health-checker/checkers/oxlint-health-checker.mjs +1 -1
- package/esm/dev-server/health-checker/default-health-checkers.mjs +2 -16
- package/esm/dev-server/health-checker/default-health-checkers.mjs.map +1 -1
- package/esm/dev-server/health-checker/file-health-checker.contract.d.mts +1 -2
- package/esm/dev-server/health-checker/file-health-checker.contract.d.mts.map +1 -1
- package/esm/dev-server/health-checker/file-health-result.d.mts +1 -1
- package/esm/dev-server/health-checker/file-health-result.mjs.map +1 -1
- package/esm/e2e/browser.d.mts +4 -0
- package/esm/e2e/browser.d.mts.map +1 -0
- package/esm/e2e/browser.mjs +88 -0
- package/esm/e2e/browser.mjs.map +1 -0
- package/esm/e2e/cdp.d.mts +152 -0
- package/esm/e2e/cdp.d.mts.map +1 -0
- package/esm/e2e/cdp.mjs +106 -0
- package/esm/e2e/cdp.mjs.map +1 -0
- package/esm/e2e/fixture.mjs +195 -0
- package/esm/e2e/fixture.mjs.map +1 -0
- package/esm/e2e/index.d.mts +5 -0
- package/esm/e2e/index.mjs +6 -0
- package/esm/e2e/owned-process.mjs +193 -0
- package/esm/e2e/owned-process.mjs.map +1 -0
- package/esm/e2e/page.d.mts +64 -0
- package/esm/e2e/page.d.mts.map +1 -0
- package/esm/e2e/page.mjs +376 -0
- package/esm/e2e/page.mjs.map +1 -0
- package/esm/e2e/run-e2e.d.mts +17 -0
- package/esm/e2e/run-e2e.d.mts.map +1 -0
- package/esm/e2e/run-e2e.mjs +143 -0
- package/esm/e2e/run-e2e.mjs.map +1 -0
- package/esm/e2e/types.d.mts +123 -0
- package/esm/e2e/types.d.mts.map +1 -0
- package/esm/e2e/types.mjs +24 -0
- package/esm/e2e/types.mjs.map +1 -0
- package/esm/generations/features/index.mjs +2 -0
- package/esm/generations/features/index.mjs.map +1 -1
- package/esm/generations/features/process.feature.mjs +65 -0
- package/esm/generations/features/process.feature.mjs.map +1 -0
- package/esm/http/port-preflight.d.mts.map +1 -1
- package/esm/http/port-preflight.mjs +2 -0
- package/esm/http/port-preflight.mjs.map +1 -1
- package/esm/index.d.mts +7 -2
- package/esm/index.mjs +7 -2
- package/esm/secrets/errors.d.mts +9 -0
- package/esm/secrets/errors.d.mts.map +1 -0
- package/esm/secrets/errors.mjs +24 -0
- package/esm/secrets/errors.mjs.map +1 -0
- package/esm/secrets/file-backend.mjs +167 -0
- package/esm/secrets/file-backend.mjs.map +1 -0
- package/esm/secrets/index.d.mts +6 -0
- package/esm/secrets/index.mjs +6 -0
- package/esm/secrets/keyring-backend.mjs +50 -0
- package/esm/secrets/keyring-backend.mjs.map +1 -0
- package/esm/secrets/memory-backend.d.mts +12 -0
- package/esm/secrets/memory-backend.d.mts.map +1 -0
- package/esm/secrets/memory-backend.mjs +28 -0
- package/esm/secrets/memory-backend.mjs.map +1 -0
- package/esm/secrets/resolve-driver.mjs +14 -0
- package/esm/secrets/resolve-driver.mjs.map +1 -0
- package/esm/secrets/secret-store.d.mts +25 -0
- package/esm/secrets/secret-store.d.mts.map +1 -0
- package/esm/secrets/secret-store.mjs +120 -0
- package/esm/secrets/secret-store.mjs.map +1 -0
- package/esm/secrets/secrets.d.mts +11 -0
- package/esm/secrets/secrets.d.mts.map +1 -0
- package/esm/secrets/secrets.mjs +72 -0
- package/esm/secrets/secrets.mjs.map +1 -0
- package/esm/secrets/types.d.mts +26 -0
- package/esm/secrets/types.d.mts.map +1 -0
- package/esm/socket/index.d.mts +2 -2
- package/esm/socket/index.mjs +2 -2
- package/esm/socket/utils.d.mts +7 -0
- package/esm/socket/utils.d.mts.map +1 -1
- package/esm/socket/utils.mjs +12 -1
- package/esm/socket/utils.mjs.map +1 -1
- package/esm/tests/fakes/index.d.mts +2 -1
- package/esm/tests/fakes/index.mjs +2 -1
- package/esm/tests/fakes/secrets-fake.d.mts +13 -0
- package/esm/tests/fakes/secrets-fake.d.mts.map +1 -0
- package/esm/tests/fakes/secrets-fake.mjs +50 -0
- package/esm/tests/fakes/secrets-fake.mjs.map +1 -0
- package/esm/tests/fakes/test-fakes.d.mts +2 -2
- package/esm/tests/fakes/test-fakes.d.mts.map +1 -1
- package/esm/tests/fakes/test-fakes.mjs +5 -2
- package/esm/tests/fakes/test-fakes.mjs.map +1 -1
- package/esm/tests/index.d.mts +2 -1
- package/esm/tests/index.mjs +2 -1
- package/esm/tests/vitest-setup.d.mts +1 -1
- package/esm/tests/vitest-setup.mjs.map +1 -1
- package/esm/tooling/check/lint-step.d.mts +2 -8
- package/esm/tooling/check/lint-step.d.mts.map +1 -1
- package/esm/tooling/check/lint-step.mjs +1 -42
- package/esm/tooling/check/lint-step.mjs.map +1 -1
- package/esm/tooling/check/run-check.d.mts +2 -0
- package/esm/tooling/check/run-check.d.mts.map +1 -1
- package/esm/tooling/check/run-check.mjs +3 -1
- package/esm/tooling/check/run-check.mjs.map +1 -1
- package/esm/tooling/problem.d.mts +1 -1
- package/esm/tooling/problem.d.mts.map +1 -1
- package/esm/tooling/problem.mjs.map +1 -1
- package/esm/updater/package-manager.mjs +5 -1
- package/esm/updater/package-manager.mjs.map +1 -1
- package/esm/upgrade/codemods/index.mjs +3 -0
- package/esm/upgrade/codemods/index.mjs.map +1 -1
- package/esm/upgrade/codemods/oxc-toolchain.mjs +191 -0
- package/esm/upgrade/codemods/oxc-toolchain.mjs.map +1 -0
- package/esm/upgrade/codemods/oxlint-rule-schemas.mjs +6 -0
- package/esm/upgrade/codemods/oxlint-rule-schemas.mjs.map +1 -0
- package/esm/upgrade/codemods/port-eslint-rules.mjs +343 -0
- package/esm/upgrade/codemods/port-eslint-rules.mjs.map +1 -0
- package/esm/upgrade/codemods/toolchain-templates.mjs +244 -0
- package/esm/upgrade/codemods/toolchain-templates.mjs.map +1 -0
- package/esm/upgrade/codemods/validate-oxlint-rule.mjs +60 -0
- package/esm/upgrade/codemods/validate-oxlint-rule.mjs.map +1 -0
- package/esm/upgrade/notes.mjs +14 -7
- package/esm/upgrade/notes.mjs.map +1 -1
- package/esm/upgrade/run-upgrade.mjs +25 -6
- package/esm/upgrade/run-upgrade.mjs.map +1 -1
- package/esm/warlock-config/types.d.mts +1 -1
- package/llms-full.txt +538 -29
- package/llms.txt +5 -3
- package/package.json +29 -20
- package/skills/README.md +2 -1
- package/skills/create-module/SKILL.md +2 -2
- package/skills/index.md +1 -1
- package/skills/run-app/SKILL.md +115 -6
- package/skills/store-secrets/SKILL.md +141 -0
- package/skills/test-browser/SKILL.md +119 -0
- package/skills/test-fakes/SKILL.md +13 -11
- package/skills/upgrade-from-5-29/SKILL.md +85 -7
- package/skills/warlock-check/SKILL.md +7 -3
- package/skills/warlock-doctor/SKILL.md +8 -0
- package/skills/wire-socket/SKILL.md +19 -0
- package/skills/write-cli-command/SKILL.md +21 -0
- package/esm/dev-server/health-checker/checkers/eslint-health-checker.mjs +0 -154
- package/esm/dev-server/health-checker/checkers/eslint-health-checker.mjs.map +0 -1
- package/esm/dev-server/health-checker/workers/eslint-health.worker.d.mts +0 -1
- package/esm/dev-server/health-checker/workers/eslint-health.worker.mjs +0 -192
- package/esm/dev-server/health-checker/workers/eslint-health.worker.mjs.map +0 -1
package/llms-full.txt
CHANGED
|
@@ -1879,8 +1879,8 @@ await warmupProductCache();
|
|
|
1879
1879
|
|
|
1880
1880
|
## Verify generated imports
|
|
1881
1881
|
|
|
1882
|
-
Generated web starter files match the scaffold’s
|
|
1883
|
-
|
|
1882
|
+
Generated web starter files match the scaffold’s Oxfmt configuration and its
|
|
1883
|
+
built-in import sorting.
|
|
1884
1884
|
After editing generated code, run `npm run format` and `npm run lint:check`.
|
|
1885
1885
|
Formatting retains unused imports; the linter reports them.
|
|
1886
1886
|
|
|
@@ -4430,7 +4430,7 @@ const result = await measure("publish-event", () =>
|
|
|
4430
4430
|
|
|
4431
4431
|
---
|
|
4432
4432
|
name: run-app
|
|
4433
|
-
description: '
|
|
4433
|
+
description: 'Operational commands — `warlock dev` (HMR + type-gen + health checks), `warlock build` (esbuild bundle), `warlock start` (spawn the production bundle), `warlock pm2 init` / `reload` (optional deployment tooling). All flags, all `warlock.config.ts` knobs that shape them. Triggers: `warlock pm2`, `ecosystem.config.cjs`, `wait_ready`, `kill_timeout`, `app.shutdownTimeout`, `warlock dev`, `warlock build`, `warlock start`, `devServer`, `--fresh`, `--skip-typings`, `--skip-health`, `outdir`, `outFile`, `sourcemap`, `PortInUseError`, `assertPortIsAvailable`, `EADDRINUSE`, `EsbuildBinaryMissingError`, `detectJavaScriptRuntime`, `assertNodeOnlyCommand`, Bun, Deno, `bun dist/app.js`, `deno run -A`; "start the dev server", "build for production", "run the bundle", "skip type generation", "tune watch globs", "dev server keyboard shortcuts", "press r to restart", "press q to quit", "restart the dev server", "port already in use", "run on Bun", "run on Deno", "supported runtimes"; typical config `warlock.config.ts > devServer / build`. Skip: writing a custom CLI — the `write-cli-command` topic; config shape — the `configure-app` topic; competing tooling `nodemon`, `tsx`, `ts-node-dev`, `esbuild` direct.'
|
|
4434
4434
|
---
|
|
4435
4435
|
|
|
4436
4436
|
# Warlock — run the app
|
|
@@ -4580,13 +4580,13 @@ export default defineConfig({
|
|
|
4580
4580
|
|
|
4581
4581
|
- **`watch.include` / `watch.exclude`** — globs piped into the file watcher. Override when you have non-`.ts(x)` files driving reloads (e.g. SQL fixtures), or to exclude a generated folder.
|
|
4582
4582
|
- **`generateTypings`** — turn off if you're committing generated typings and don't want them rewritten on every boot. The `--skip-typings` flag is the per-run version.
|
|
4583
|
-
- **`healthCheckers`** — built-in switches (`{ typescript?,
|
|
4583
|
+
- **`healthCheckers`** — built-in switches (`{ typescript?, oxlint? }`), custom checker arrays, or `false`. TypeScript and installed/configured Oxlint run by default. `--skip-health` disables checks for one run. ESLint support was removed in 5.34.0; use `npx warlock upgrade --from 5.33 --to 5.34.0` to remove stale options.
|
|
4584
4584
|
- **`transpileCacheDebug`** — diagnostic only. Names `.warlock/transpile/*.js` files `<slug>.<hash>.js` and appends `// @source <path>` markers so you can eyeball which cache entry came from which source. Leave off in normal use.
|
|
4585
4585
|
- **`checkForUpdates`** — on `warlock dev` start, check npm for a newer `@warlock.js/core` and print a one-line notice if one exists. Best-effort and non-blocking; auto-skipped in CI and non-TTY shells. In an interactive terminal the notice arms a **`u` shortcut** that updates every `@warlock.js/*` package, installs, and restarts the server; elsewhere it prints `npx warlock update` instead. The registry answer is cached for 24h in `.warlock/update-check.json`, so a day of restarts costs one lookup. See the `update-packages` topic.
|
|
4586
4586
|
- **`restartOnConfigChange`** — restart the dev server when `warlock.config.ts` or any `.env*` changes (default `true`). Set `false` to get a warning instead and restart by hand. Neither file can be hot-reloaded, so without a restart the running services keep the old values.
|
|
4587
4587
|
- **`timings`** — print a one-line, per-phase breakdown next to the `hmr update` line on every hot reload: `watcher`, `debounce`, `graph`, `reimport`, `connectors`. `watcher` is the raw-fs-notification-to-stabilised-event gap (chokidar's `awaitWriteFinish` window); `debounce` is the handler's own adaptive wait; the rest are self-explanatory. Opt-in, off by default — a disabled flag costs one boolean check per reload, since the watcher-settle bookkeeping only runs when this is on. Use it to see which phase a slow reload is actually spending time in.
|
|
4588
4588
|
|
|
4589
|
-
###
|
|
4589
|
+
### Default Oxlint file checks
|
|
4590
4590
|
|
|
4591
4591
|
Install `oxlint` in the app (`npm install --save-dev oxlint`) and add `.oxlintrc.json` at the app root:
|
|
4592
4592
|
|
|
@@ -4603,12 +4603,12 @@ import { defineConfig } from "@warlock.js/core";
|
|
|
4603
4603
|
|
|
4604
4604
|
export default defineConfig({
|
|
4605
4605
|
devServer: {
|
|
4606
|
-
healthCheckers: { typescript: true,
|
|
4606
|
+
healthCheckers: { typescript: true, oxlint: true },
|
|
4607
4607
|
},
|
|
4608
4608
|
});
|
|
4609
4609
|
```
|
|
4610
4610
|
|
|
4611
|
-
Omit
|
|
4611
|
+
Omit switches to run TypeScript and installed/configured Oxlint. Set `oxlint: false` to disable only Oxlint. A custom checker array replaces the built-in list. JSON/JSONC configs work on Node 22.15; current Oxlint TypeScript configs require Node 22.18 or newer.
|
|
4612
4612
|
|
|
4613
4613
|
## `warlock build` — production bundle
|
|
4614
4614
|
|
|
@@ -4842,11 +4842,120 @@ Configured http.port "03999" normalised to 3999.
|
|
|
4842
4842
|
Three consequences worth knowing:
|
|
4843
4843
|
|
|
4844
4844
|
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.
|
|
4845
|
-
2. **
|
|
4845
|
+
2. **PM2 receives its own readiness message (5.33.0+).** With an IPC channel and PM2's `pm_id` present, core sends `"ready"` after completed boot and leaves the channel open. Plain Node/Docker launches without IPC remain silent. `warlock start` takes priority when its handshake is present.
|
|
4846
4846
|
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.
|
|
4847
4847
|
|
|
4848
4848
|
If you need the same fact inside the app, use `Application.onceBooted()` / `Application.whenBooted()` — the signal and your listeners fire from the same latch.
|
|
4849
4849
|
|
|
4850
|
+
## `warlock pm2 init` / `reload` — optional production supervision
|
|
4851
|
+
|
|
4852
|
+
PM2 is operator tooling, never a core dependency. Install it separately and
|
|
4853
|
+
put `pm2` on `PATH`. Generate from the app root with the target environment:
|
|
4854
|
+
|
|
4855
|
+
```bash
|
|
4856
|
+
NODE_ENV=production npx warlock build
|
|
4857
|
+
NODE_ENV=production npx warlock pm2 init
|
|
4858
|
+
# package.json name=store, app.appName=api → store-api-production
|
|
4859
|
+
pm2 start ecosystem.config.cjs --only store-api-production
|
|
4860
|
+
|
|
4861
|
+
# Deploy a fresh successful build, then reload only this app:
|
|
4862
|
+
NODE_ENV=production npx warlock pm2 reload
|
|
4863
|
+
NODE_ENV=production npx warlock pm2 reload --id 7
|
|
4864
|
+
```
|
|
4865
|
+
|
|
4866
|
+
`init` reads app/http/socket config and resolved build paths without starting
|
|
4867
|
+
connectors, bootstrap or prestart. It creates `ecosystem.config.cjs` exclusively;
|
|
4868
|
+
an existing file is an error. It works without PM2 installed or a prior build.
|
|
4869
|
+
The file runs the built entry directly, with an absolute `cwd`, Node interpreter
|
|
4870
|
+
and source maps matching `build.sourcemap`.
|
|
4871
|
+
|
|
4872
|
+
The identity is `<project>-<app>-<NODE_ENV>`; project defaults to `package.json`
|
|
4873
|
+
name (`@team/store` → `team-store`), app to `app.appName` or `app`, environment
|
|
4874
|
+
to `NODE_ENV` or `development`. Use `--project store --app api` to override the
|
|
4875
|
+
components, and repeat those overrides on reload. Components accept letters,
|
|
4876
|
+
digits, `_` and `-`, starting with a letter/digit. Choose a combination unique
|
|
4877
|
+
within the daemon; no patterns or broad selectors are supported.
|
|
4878
|
+
|
|
4879
|
+
```bash
|
|
4880
|
+
NODE_ENV=production npx warlock pm2 init --project store --app api \
|
|
4881
|
+
--cluster --instances 2 --listen-timeout 60000
|
|
4882
|
+
NODE_ENV=production npx warlock pm2 reload --project store --app api
|
|
4883
|
+
```
|
|
4884
|
+
|
|
4885
|
+
Defaults: `exec_mode: "fork"`, `instances: 1`, `wait_ready: true`,
|
|
4886
|
+
`listen_timeout: 30000`. `--cluster` opts into two workers by default;
|
|
4887
|
+
`--instances` accepts a positive integer or `max`, only with `--cluster`.
|
|
4888
|
+
Fork reload falls back to restart and may interrupt traffic. PM2 cluster reload
|
|
4889
|
+
can replace workers while others serve, but a boot/readiness timeout can still
|
|
4890
|
+
cause downtime. Keep both the PM2 daemon and app on Node 22.15+; cluster uses
|
|
4891
|
+
the daemon's runtime.
|
|
4892
|
+
|
|
4893
|
+
`kill_timeout` equals the existing overall connector budget,
|
|
4894
|
+
`app.shutdownTimeout` (default 10000ms), now typed on `AppConfigurations`:
|
|
4895
|
+
|
|
4896
|
+
```ts
|
|
4897
|
+
import type { AppConfigurations } from "@warlock.js/core";
|
|
4898
|
+
|
|
4899
|
+
export default {
|
|
4900
|
+
appName: "api",
|
|
4901
|
+
shutdownTimeout: 20000,
|
|
4902
|
+
} satisfies AppConfigurations;
|
|
4903
|
+
```
|
|
4904
|
+
|
|
4905
|
+
Keep `http.gracefulShutdown.timeout` lower (e.g. 10000ms), with room for other
|
|
4906
|
+
connectors, hooks and log flushes. PM2's kill timer covers the whole exit;
|
|
4907
|
+
core's connector timer starts after app hooks and finishes before log flushing.
|
|
4908
|
+
The generated comment and CLI output show the configured readiness URL (or an
|
|
4909
|
+
unavailable notice for no HTTP, disabled health or ephemeral ports). PM2 itself
|
|
4910
|
+
does not poll it. Use an external deployment probe.
|
|
4911
|
+
|
|
4912
|
+
Core emits `process.send("ready")` once at completed boot when PM2's `pm_id`
|
|
4913
|
+
and IPC are present, after HTTP and the connectors have started. It never
|
|
4914
|
+
disconnects PM2's channel. Socket-only apps also await the listener and reject
|
|
4915
|
+
bind failures before completing boot. Rebuild with core 5.33+ before using `wait_ready`.
|
|
4916
|
+
`listen_timeout` limits waiting for readiness; `kill_timeout` limits shutdown
|
|
4917
|
+
before PM2's forced kill. Core handles PM2's default `SIGINT` gracefully.
|
|
4918
|
+
|
|
4919
|
+
With sockets enabled, cluster generation/reload refuse a missing
|
|
4920
|
+
`socket.adapter` factory and require `socket.options.transports: ["websocket"]`
|
|
4921
|
+
with WebSocket-only clients. The adapter handles broadcasts; polling still
|
|
4922
|
+
needs sticky routing to each worker, which this generator does not install.
|
|
4923
|
+
For polling, use individual fork listeners behind a sticky proxy or a custom
|
|
4924
|
+
cluster primary. See the `wire-socket` topic. Avoid duplicating worker jobs
|
|
4925
|
+
and in-memory sessions across HTTP workers.
|
|
4926
|
+
|
|
4927
|
+
`reload` requires a successful build marker, reads `pm2 jlist`, verifies **all**
|
|
4928
|
+
exact-name matches against the app's absolute cwd and entry, then calls
|
|
4929
|
+
`pm2 reload <numeric-id>` sequentially. `--id` narrows to one verified worker.
|
|
4930
|
+
Foreign cwd/script, invalid selectors, offline cluster workers or cluster
|
|
4931
|
+
workers without `wait_ready` are refused before reload. Reload uses settings
|
|
4932
|
+
already registered in PM2; regenerate/reapply the ecosystem when they change.
|
|
4933
|
+
|
|
4934
|
+
On shared servers, inspect the current PM2 id and path before stopping:
|
|
4935
|
+
|
|
4936
|
+
```bash
|
|
4937
|
+
pm2 list
|
|
4938
|
+
pm2 show 7
|
|
4939
|
+
pm2 stop 7
|
|
4940
|
+
```
|
|
4941
|
+
|
|
4942
|
+
Repeat for each intended worker id. Never stop by pattern or broad name, and
|
|
4943
|
+
avoid daemon-wide operations. PM2 ids are not OS PIDs and can change after an
|
|
4944
|
+
app is deleted/recreated.
|
|
4945
|
+
|
|
4946
|
+
To put logger entries in PM2's captured stdout, configure
|
|
4947
|
+
`src/config/log.ts` with `production: { channels: [new ConsoleLog()] }`
|
|
4948
|
+
(`ConsoleLog` comes from `@warlock.js/logger`). Keep `time: false` and omit
|
|
4949
|
+
`log_date_format` to preserve the logger's timestamp/module/action format.
|
|
4950
|
+
Generated `merge_logs: false` keeps worker logs separate. PM2 JSON output wraps
|
|
4951
|
+
captured text; it does not recover logger fields. Use `JSONFileLog` with a
|
|
4952
|
+
distinct per-worker storage path for structured persistent entries.
|
|
4953
|
+
|
|
4954
|
+
Official references: [PM2 readiness/shutdown](https://pm2.keymetrics.io/docs/usage/signals-clean-restart/),
|
|
4955
|
+
[cluster reload](https://pm2.keymetrics.io/docs/usage/cluster-mode/),
|
|
4956
|
+
[Socket.IO multiple nodes](https://socket.io/docs/v4/using-multiple-nodes/).
|
|
4957
|
+
Full docs: `docs/src/content/docs/v/latest/core/cli/pm2.mdx`.
|
|
4958
|
+
|
|
4850
4959
|
## Runtimes — Node, Bun, Deno
|
|
4851
4960
|
|
|
4852
4961
|
| Command | Node.js 22.15+ | Bun 1.4.3 | Deno 2.9.7 |
|
|
@@ -6378,25 +6487,294 @@ Client `PUT`s the bytes straight to R2 — your server never sees them.
|
|
|
6378
6487
|
- The `send-response` topic — `response.sendFile(absPath)` for serving local files.
|
|
6379
6488
|
|
|
6380
6489
|
|
|
6490
|
+
## store-secrets `@warlock.js/core/store-secrets/SKILL.md`
|
|
6491
|
+
|
|
6492
|
+
---
|
|
6493
|
+
name: store-secrets
|
|
6494
|
+
description: "Store app credentials with secrets.get/set/remove/has and environmentName; lazy optional OS keychain, read-only scoped environment variables, encrypted file fallback, typed SecretStoreError and memory-backed secretsFake. Use for provider keys and OAuth credentials the app must read back. Skip user passwords (hash-password) and encrypted database columns (encrypt-data)."
|
|
6495
|
+
---
|
|
6496
|
+
|
|
6497
|
+
# Application secret store
|
|
6498
|
+
|
|
6499
|
+
Use core's facade; there is no separate package or preparation step:
|
|
6500
|
+
|
|
6501
|
+
```ts
|
|
6502
|
+
import { secrets, SecretStoreError } from "@warlock.js/core";
|
|
6503
|
+
// The same API is available from "@warlock.js/core/secrets".
|
|
6504
|
+
|
|
6505
|
+
await secrets.set("vendor-token", "provider-credential");
|
|
6506
|
+
const token = await secrets.get("vendor-token"); // string | undefined
|
|
6507
|
+
const exists = await secrets.has("vendor-token"); // boolean; true for ""
|
|
6508
|
+
const removed = await secrets.remove("vendor-token"); // boolean
|
|
6509
|
+
const variable = secrets.environmentName("vendor-token"); // variable name, not value
|
|
6510
|
+
|
|
6511
|
+
try {
|
|
6512
|
+
await secrets.remove("vendor-token");
|
|
6513
|
+
} catch (error) {
|
|
6514
|
+
if (error instanceof SecretStoreError && error.code === "READ_ONLY") {
|
|
6515
|
+
// Change the deployment environment instead.
|
|
6516
|
+
} else {
|
|
6517
|
+
throw error;
|
|
6518
|
+
}
|
|
6519
|
+
}
|
|
6520
|
+
```
|
|
6521
|
+
|
|
6522
|
+
Names are case-sensitive, nonempty strings of at most 256 UTF-16 code units. Values are strings, including empty strings. Every operation is async, except `environmentName()`.
|
|
6523
|
+
|
|
6524
|
+
## Configuration and backend decisions
|
|
6525
|
+
|
|
6526
|
+
- Default namespace: app name plus the SHA-256 hash of the absolute project root. For changing release directories, configure a stable unique namespace including the deployment environment.
|
|
6527
|
+
- Reads: OS keychain → exact scoped environment variable → encrypted file.
|
|
6528
|
+
- Optional peer: `npm install @napi-rs/keyring`. Core lazily uses its 2.1.1 `AsyncEntry` API. Linux requires `secret-service` for persistence; it does not use the memory-only keyutils fallback. Sources: [official README](https://github.com/Brooooooklyn/keyring-node#readme), [published declarations](https://www.npmjs.com/package/@napi-rs/keyring?activeTab=code), verified 2026-10-19.
|
|
6529
|
+
- One initial read-only probe selects the keychain for that store lifetime. Missing peer/initially unavailable service falls back automatically. A later keychain failure throws `UNAVAILABLE`; do not silently change backends mid-operation. Keep backend availability consistent between deployments; independent backend writes are not reconciled automatically.
|
|
6530
|
+
- Environment names are `WARLOCK_SECRET_<namespace UTF-8 uppercase hex>_<name UTF-8 uppercase hex>`; use `environmentName()` to avoid guessing. No unscoped environment fallback. Both mutation methods throw `READ_ONLY` if that variable is present, including `""` and shadowed environment values.
|
|
6531
|
+
- File fallback derives a per-namespace HKDF-SHA256 key from `encryption.key` (64 hex characters, conventionally `APP_ENCRYPTION_KEY`) and uses AES-256-GCM with name/namespace authenticated data. It never stores an unencrypted value/key file. Core's global `encrypt()` helper cannot accept a derived key, so the backend uses Node crypto without changing global config. Consult `encrypt-data` for app key provisioning.
|
|
6532
|
+
- Default files: `storage/secrets/<namespace hash>/<name hash>.secret`. Directories `0700`, files `0600`, owner checks, no symlinks/hardlinks, synced temporary writes and rename. Parent directories must be trusted. Same-key concurrent writes use last completed rename.
|
|
6533
|
+
- File fallback fails closed on Windows because Node modes cannot enforce owner-only ACLs; use keychain/environment. See [Node file modes](https://nodejs.org/docs/latest-v22.x/api/fs.html#file-modes).
|
|
6534
|
+
- Keychain writes clear an old file copy; removal clears both writable layers. Retry errors after fixing the backend; a multi-layer operation may partially complete.
|
|
6535
|
+
|
|
6536
|
+
```ts title="src/config/secrets.ts"
|
|
6537
|
+
import type { SecretsConfigurations } from "@warlock.js/core";
|
|
6538
|
+
|
|
6539
|
+
export default {
|
|
6540
|
+
namespace: "sample-app-production",
|
|
6541
|
+
// directory: "/srv/sample-app/private-secrets",
|
|
6542
|
+
} satisfies SecretsConfigurations;
|
|
6543
|
+
```
|
|
6544
|
+
|
|
6545
|
+
Never change namespace or encryption key without a migration. Keep file storage outside publicly served paths, and secure the app key separately from encrypted backups.
|
|
6546
|
+
|
|
6547
|
+
## Isolated tests and explicit stores
|
|
6548
|
+
|
|
6549
|
+
`setupTest()` and the HTTP test harness install `secretsFake` by default. `NODE_ENV=test` alone does not install it. Opt out with `tests.fakes: { secrets: false }` only for real-backend tests. Memory is per process; HTTP server and test worker do not share it.
|
|
6550
|
+
|
|
6551
|
+
```ts
|
|
6552
|
+
import { secrets } from "@warlock.js/core";
|
|
6553
|
+
import { secretsFake } from "@warlock.js/core/tests";
|
|
6554
|
+
|
|
6555
|
+
secretsFake.install(); // only needed without the standard test harness
|
|
6556
|
+
try {
|
|
6557
|
+
secretsFake.clear();
|
|
6558
|
+
await secrets.set("vendor-token", "test-credential");
|
|
6559
|
+
await secretsFake.assertHas("vendor-token");
|
|
6560
|
+
await secretsFake.assertStored("vendor-token", "test-credential");
|
|
6561
|
+
await secrets.remove("vendor-token");
|
|
6562
|
+
await secretsFake.assertMissing("vendor-token");
|
|
6563
|
+
const installed = secretsFake.isInstalled();
|
|
6564
|
+
} finally {
|
|
6565
|
+
secretsFake.restore();
|
|
6566
|
+
}
|
|
6567
|
+
```
|
|
6568
|
+
|
|
6569
|
+
For standalone unit tests or distinct app instances:
|
|
6570
|
+
|
|
6571
|
+
```ts
|
|
6572
|
+
import { MemorySecretBackend, SecretStore } from "@warlock.js/core/secrets";
|
|
6573
|
+
import type { SecretBackend, SecretStoreOptions } from "@warlock.js/core/secrets";
|
|
6574
|
+
|
|
6575
|
+
const backend: SecretBackend = new MemorySecretBackend();
|
|
6576
|
+
const options: SecretStoreOptions = { namespace: "test-app", backend };
|
|
6577
|
+
const store = new SecretStore(options);
|
|
6578
|
+
await store.set("vendor-token", "test-credential");
|
|
6579
|
+
await store.get("vendor-token");
|
|
6580
|
+
await store.has("vendor-token");
|
|
6581
|
+
await store.remove("vendor-token");
|
|
6582
|
+
|
|
6583
|
+
const memory = new MemorySecretBackend();
|
|
6584
|
+
memory.clear();
|
|
6585
|
+
```
|
|
6586
|
+
|
|
6587
|
+
Options also allow a `directory` and `encryptionKey` (string or lazy callback); the default key reads `encryption.key` lazily. A custom `SecretBackend` implements async namespace-aware `get`, `set`, and `remove`. Explicit backends bypass keychain, environment and files; the test fake only swaps the shared facade.
|
|
6588
|
+
|
|
6589
|
+
## Error and logging contract
|
|
6590
|
+
|
|
6591
|
+
`SecretStoreError.code` is `INVALID_DRIVER`, `MEMORY_IN_PRODUCTION`, `INVALID_NAME`, `INVALID_VALUE`, `INVALID_NAMESPACE`, `READ_ONLY`, `KEY_REQUIRED`, `UNAVAILABLE`, `UNSAFE_FILE` or `CORRUPT_FILE`. Errors never include names, values, paths or vendor causes. `KEY_REQUIRED` means provision/fix the app encryption key; `CORRUPT_FILE` means wrong key, invalid format or failed authentication. Never replace an unreadable store with an empty one.
|
|
6592
|
+
|
|
6593
|
+
The store never logs credentials. It registers encountered names and literal values with `@warlock.js/logger` for process-lifetime masking, including old/removed/failed-write values, interpolated messages, Error text/causes, and channel error reports. Keys and exact values stay protected even with permissive custom censors. Registration retains values in memory. It cannot mask direct console/third-party logging, earlier logs, transformed/encoded values, or credentials never encountered by the store. Keep secrets out of logs even with masking enabled. Fake assertion errors do not print values.
|
|
6594
|
+
|
|
6595
|
+
## Built-app tests with the memory driver
|
|
6596
|
+
|
|
6597
|
+
Run the built server with `SECRETS_DRIVER=memory`, or select the driver in
|
|
6598
|
+
`src/config/secrets.ts`. Explicit config takes precedence over the environment;
|
|
6599
|
+
`"auto"` (the default) keeps keychain/environment/file behavior. Unknown driver
|
|
6600
|
+
names fail with `SecretStoreError.code === "INVALID_DRIVER"`.
|
|
6601
|
+
|
|
6602
|
+
```ts title="src/config/secrets.ts"
|
|
6603
|
+
import type { SecretsConfigurations } from "@warlock.js/core";
|
|
6604
|
+
|
|
6605
|
+
export default {
|
|
6606
|
+
namespace: "browser-test-server",
|
|
6607
|
+
driver: "memory",
|
|
6608
|
+
allowMemoryInProduction: true,
|
|
6609
|
+
} satisfies SecretsConfigurations;
|
|
6610
|
+
```
|
|
6611
|
+
|
|
6612
|
+
`allowMemoryInProduction: true` is required only when the test server runs with
|
|
6613
|
+
`NODE_ENV=production`. Without it, memory selection fails closed with
|
|
6614
|
+
`MEMORY_IN_PRODUCTION`; a built bundle does not bypass this check. Set this
|
|
6615
|
+
option explicitly in the test deployment configuration. `SecretStore` accepts
|
|
6616
|
+
the same `driver` and `allowMemoryInProduction` options for standalone stores.
|
|
6617
|
+
|
|
6618
|
+
The selected memory driver bypasses keychain, scoped environment secrets and
|
|
6619
|
+
files, and still registers values for log masking. Values are isolated by app
|
|
6620
|
+
namespace, lost on process exit, and never shared across workers or with a
|
|
6621
|
+
separate browser-test process. Seed fixtures through application startup or a
|
|
6622
|
+
test-only endpoint in the server process using the normal `secrets.set()` API:
|
|
6623
|
+
|
|
6624
|
+
```ts
|
|
6625
|
+
import { secrets } from "@warlock.js/core";
|
|
6626
|
+
|
|
6627
|
+
await secrets.set("vendor-token", "browser-test-credential");
|
|
6628
|
+
const token = await secrets.get("vendor-token");
|
|
6629
|
+
```
|
|
6630
|
+
|
|
6631
|
+
The normal test harness still installs `secretsFake`; explicit `backend`
|
|
6632
|
+
instances and that fake continue to bypass configured backend selection.
|
|
6633
|
+
|
|
6634
|
+
|
|
6635
|
+
## test-browser `@warlock.js/core/test-browser/SKILL.md`
|
|
6636
|
+
|
|
6637
|
+
---
|
|
6638
|
+
name: test-browser
|
|
6639
|
+
description: Write or repair browser e2e specs using warlock e2e and @warlock.js/core/e2e, with owned app fixtures, direct CDP interaction, responsive screenshots and run evidence. Use for real browser flows, click races or viewport overflow; use test-http for API-only tests.
|
|
6640
|
+
---
|
|
6641
|
+
|
|
6642
|
+
# Browser e2e
|
|
6643
|
+
|
|
6644
|
+
Use `@warlock.js/core/e2e`, a Node-only subpath, and default-export `defineE2E` from
|
|
6645
|
+
`e2e/*.e2e.ts`. Run `npx warlock e2e`. The CLI loads TypeScript itself; no Vitest
|
|
6646
|
+
bootstrap, Playwright, Puppeteer or external browser server is needed.
|
|
6647
|
+
|
|
6648
|
+
Every run copies the app into a temporary directory, excludes dotenv files and
|
|
6649
|
+
symlinks, links installed dependencies for reads, seeds its own environment and
|
|
6650
|
+
owns the app/browser process groups. Keep foreground commands in the fixture;
|
|
6651
|
+
never use a shared dev server or daemonize. Map the disposable `dataRoot` into the
|
|
6652
|
+
app's actual storage/database config. The harness cannot infer that mapping.
|
|
6653
|
+
`prepare` is where seeds and fake executables belong; `app.path` is relative to the
|
|
6654
|
+
copied app. Do not install dependencies through the shared `node_modules` link.
|
|
6655
|
+
|
|
6656
|
+
## Complete standalone spec
|
|
6657
|
+
|
|
6658
|
+
This spec creates its own small app; it can be pasted into an existing project
|
|
6659
|
+
with core installed and run with `npx warlock e2e`.
|
|
6660
|
+
|
|
6661
|
+
```ts
|
|
6662
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
6663
|
+
import { join } from "node:path";
|
|
6664
|
+
import { defineE2E } from "@warlock.js/core/e2e";
|
|
6665
|
+
|
|
6666
|
+
export default defineE2E({
|
|
6667
|
+
app: {
|
|
6668
|
+
start: [process.execPath, "form-fixture.mjs"],
|
|
6669
|
+
portFromFile: true,
|
|
6670
|
+
path: ["fake-bin"],
|
|
6671
|
+
env: ({ dataRoot }) => ({ FORM_DATA: dataRoot }),
|
|
6672
|
+
prepare: async ({ directory, dataRoot }) => {
|
|
6673
|
+
await mkdir(join(directory, "fake-bin"));
|
|
6674
|
+
await writeFile(join(dataRoot, "prefix.txt"), "Saved");
|
|
6675
|
+
await writeFile(
|
|
6676
|
+
join(directory, "form-fixture.mjs"),
|
|
6677
|
+
`
|
|
6678
|
+
import { createServer } from 'node:http';
|
|
6679
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
6680
|
+
import { join } from 'node:path';
|
|
6681
|
+
const prefix = readFileSync(join(process.env.FORM_DATA, 'prefix.txt'), 'utf8');
|
|
6682
|
+
const server = createServer((request, response) => {
|
|
6683
|
+
if (request.url === '/favicon.ico') { response.writeHead(204); response.end(); return; }
|
|
6684
|
+
response.setHeader('Content-Type', 'text/html');
|
|
6685
|
+
response.end('<!doctype html><meta name="viewport" content="width=device-width,initial-scale=1">'
|
|
6686
|
+
+ '<input id="name"><button id="save"><span>Save</span></button><h1 id="result">Ready</h1>'
|
|
6687
|
+
+ '<script>document.querySelector("#save").onclick=()=>{history.pushState({},"","/saved");document.querySelector("#result").textContent='
|
|
6688
|
+
+ JSON.stringify(prefix) + '+" "+document.querySelector("#name").value}</script>');
|
|
6689
|
+
});
|
|
6690
|
+
server.listen(0, '127.0.0.1', () => writeFileSync(process.env.WARLOCK_E2E_PORT_FILE, String(server.address().port)));
|
|
6691
|
+
`,
|
|
6692
|
+
);
|
|
6693
|
+
},
|
|
6694
|
+
},
|
|
6695
|
+
run: async ({ page, step }) => {
|
|
6696
|
+
await page.goto("/");
|
|
6697
|
+
await step("save the form", async () => {
|
|
6698
|
+
await page.fill("#name", "Ada");
|
|
6699
|
+
await page.type("#name", " Lovelace");
|
|
6700
|
+
await page.waitForNavigation(() => page.click("#save"), { url: "/saved" });
|
|
6701
|
+
await page.waitFor({ selector: "#result", text: "Saved Ada Lovelace" });
|
|
6702
|
+
});
|
|
6703
|
+
await page.captureViewports("saved");
|
|
6704
|
+
},
|
|
6705
|
+
});
|
|
6706
|
+
```
|
|
6707
|
+
|
|
6708
|
+
For an existing app, replace `prepare` and `start`; default start is
|
|
6709
|
+
`[process.execPath, "dist/app.js"]`. Optional
|
|
6710
|
+
`build: [process.execPath, "node_modules/@warlock.js/core/bin/warlock.js", "build"]`
|
|
6711
|
+
builds inside the copy. Supply app-specific test service variables via `env` and
|
|
6712
|
+
exclude production data directories with `exclude: ["storage", "data"]`.
|
|
6713
|
+
By default the fixture supplies an OS-assigned `PORT`/`HTTP_PORT`. Prefer the
|
|
6714
|
+
port-file handshake above when the app supports binding `0` itself.
|
|
6715
|
+
|
|
6716
|
+
## Interaction and evidence
|
|
6717
|
+
|
|
6718
|
+
- Use `page.click` for scrolling, settled boxes and hit testing, including a fresh
|
|
6719
|
+
check after hover. Do not replace it with `element.click()` or fixed sleeps.
|
|
6720
|
+
- `fill` clears editable content, then sends CDP text input; `type` appends Unicode
|
|
6721
|
+
text. Values do not appear in step names. `type` does not simulate shortcuts.
|
|
6722
|
+
- `waitFor({ selector, text, url, timeoutMs })` combines conditions. Arm navigation
|
|
6723
|
+
**before** clicking with `waitForNavigation(() => page.click(...))`.
|
|
6724
|
+
- `setViewport({ name, width, height, mobile })`, `assertViewport()` and
|
|
6725
|
+
`screenshot(name)` are available independently. `captureViewports(name)` checks
|
|
6726
|
+
and captures 1280×800 desktop and 390×844 phone by default, restoring the
|
|
6727
|
+
original viewport. `viewports` overrides the list. Final captures are automatic.
|
|
6728
|
+
- Overflow compares document/body scrollWidth **and innerWidth** against the
|
|
6729
|
+
requested width, so a phone that expands to 448 px fails. Keep the viewport meta
|
|
6730
|
+
tag; inspect the reported widest element IDs, classes and dimensions.
|
|
6731
|
+
- Health requires zero console errors, exceptions, HTTP statuses >=400 and failed
|
|
6732
|
+
loads (including cancellation). Narrow exceptions use
|
|
6733
|
+
`allowConsoleErrors: [/^Expected warning$/]` and
|
|
6734
|
+
`allowNetworkFailures: [{ url: "/missing", status: 404 }]`. Canceled-request rules
|
|
6735
|
+
can match `errorText`. Strings match substrings; regexps are also accepted.
|
|
6736
|
+
- `allowPendingRequests: ["/events"]` exempts an intentional stream from network
|
|
6737
|
+
idle, without allowing its errors. `waitForNetworkIdle()` remains available.
|
|
6738
|
+
- `page.evaluate<T>(expression)` is an escape hatch for assertions in the main
|
|
6739
|
+
document. Selector helpers do not enter frames or closed shadow roots.
|
|
6740
|
+
|
|
6741
|
+
Use `--browser /path/to/chrome` or `WARLOCK_E2E_BROWSER` if discovery fails.
|
|
6742
|
+
`browser: { args: ["--no-sandbox"] }` is an explicit option for controlled root
|
|
6743
|
+
containers; it is not a default. On Windows, command arrays should invoke JS tools
|
|
6744
|
+
through `process.execPath`, avoiding `.cmd` executables that require a shell.
|
|
6745
|
+
|
|
6746
|
+
Read `.warlock/e2e/run-*/evidence.json` and the PNGs/logs on failure. Evidence keeps
|
|
6747
|
+
allowed errors, step timings, viewport dimensions and recorded process identities.
|
|
6748
|
+
`--out` changes the evidence parent; `--path ./fake-bin,./engine-bin` prepends fake
|
|
6749
|
+
binary paths. `runE2E(spec, { outputDirectory, browser, path, signal })` is the
|
|
6750
|
+
programmatic runner; `E2ERunError.result` exposes evidence on failure.
|
|
6751
|
+
|
|
6752
|
+
Cleanup verifies the unique supervisor command line before stopping only its
|
|
6753
|
+
recorded process group (Windows: verified PID tree). It retains disposable files
|
|
6754
|
+
if ownership verification fails and reports the failure in evidence. Do not add
|
|
6755
|
+
process-name or port-based kills to a spec.
|
|
6756
|
+
|
|
6757
|
+
|
|
6381
6758
|
## test-fakes `@warlock.js/core/test-fakes/SKILL.md`
|
|
6382
6759
|
|
|
6383
6760
|
---
|
|
6384
6761
|
name: test-fakes
|
|
6385
|
-
description: 'The test fakes Warlock installs by default in the test environment — `mailFake`, `queueFake`, `cacheFake`, `storageFake`, `clockFake`, `idsFake` — and their assertions (`assertSent`, `assertPushed`, `assertHas`, `assertExists`, `travel`), plus how to opt out (`tests.fakes`, `setupTest({ fakes })`). Triggers: `mailFake`, `queueFake`, `cacheFake`, `storageFake`, `clockFake`, `idsFake`, `installTestFakes`, `restoreTestFakes`, `tests.fakes`, `FakeAssertionError`, `clock.now`, `ids.uuid`, `setDispatchInterceptor`; "assert an email was sent", "assert a job was dispatched", "freeze time in a test", "deterministic ids", "tests without Redis", "keep the real cache driver in tests". Typical import `import { mailFake, queueFake } from "@warlock.js/core/tests"`. Skip: HTTP request helpers — the `test-http` topic; fixtures — the `model-factories` topic.'
|
|
6762
|
+
description: 'The test fakes Warlock installs by default in the test environment — `mailFake`, `queueFake`, `cacheFake`, `storageFake`, `clockFake`, `idsFake`, `secretsFake` — and their assertions (`assertSent`, `assertPushed`, `assertHas`, `assertExists`, `travel`), plus how to opt out (`tests.fakes`, `setupTest({ fakes })`). Triggers: `mailFake`, `queueFake`, `cacheFake`, `storageFake`, `clockFake`, `idsFake`, `secretsFake`, `installTestFakes`, `restoreTestFakes`, `tests.fakes`, `FakeAssertionError`, `clock.now`, `ids.uuid`, `setDispatchInterceptor`; "assert an email was sent", "assert a job was dispatched", "freeze time in a test", "deterministic ids", "tests without Redis", "keep the real cache driver in tests". Typical import `import { mailFake, queueFake } from "@warlock.js/core/tests"`. Skip: HTTP request helpers — the `test-http` topic; fixtures — the `model-factories` topic.'
|
|
6386
6763
|
---
|
|
6387
6764
|
|
|
6388
6765
|
# Warlock — test fakes
|
|
6389
6766
|
|
|
6390
|
-
In the test environment
|
|
6767
|
+
In the test environment seven fakes are installed for you: `setupTest()` installs them in every test file once its connectors are up, `startHttpTestServer()` installs them in the shared server, and teardown restores them. Nothing is delivered, pushed to Redis, uploaded to a bucket, and time and ids are deterministic.
|
|
6391
6768
|
|
|
6392
|
-
| Fake | Replaces
|
|
6393
|
-
| ------------- |
|
|
6394
|
-
| `mailFake` | Mail delivery: `sendMail()` / `Mail` sends are captured
|
|
6395
|
-
| `queueFake` | `@warlock.js/queue` dispatch: jobs are recorded, not pushed
|
|
6396
|
-
| `cacheFake` | The cache's current driver: a fresh in-memory driver
|
|
6397
|
-
| `storageFake` | Every storage driver (local, S3, R2, Spaces): a temp directory
|
|
6398
|
-
| `clockFake` | `clock` from `@warlock.js/core`: frozen at 2026-01-01T00:00:00Z
|
|
6399
|
-
| `idsFake` | `ids` from `@warlock.js/core`: numbered UUIDs and tokens
|
|
6769
|
+
| Fake | Replaces |
|
|
6770
|
+
| ------------- | --------------------------------------------------------------------- |
|
|
6771
|
+
| `mailFake` | Mail delivery: `sendMail()` / `Mail` sends are captured |
|
|
6772
|
+
| `queueFake` | `@warlock.js/queue` dispatch: jobs are recorded, not pushed |
|
|
6773
|
+
| `cacheFake` | The cache's current driver: a fresh in-memory driver |
|
|
6774
|
+
| `storageFake` | Every storage driver (local, S3, R2, Spaces): a temp directory |
|
|
6775
|
+
| `clockFake` | `clock` from `@warlock.js/core`: frozen at 2026-01-01T00:00:00Z |
|
|
6776
|
+
| `idsFake` | `ids` from `@warlock.js/core`: numbered UUIDs and tokens |
|
|
6777
|
+
| `secretsFake` | The `secrets` facade: namespace-aware memory, no OS/environment/files |
|
|
6400
6778
|
|
|
6401
6779
|
## Assert what your code did
|
|
6402
6780
|
|
|
@@ -6424,7 +6802,8 @@ it("queues the report, mails the requester and caches the request", async () =>
|
|
|
6424
6802
|
- **queue** — `assertPushed(jobOrName, payloadSubset?)`, `assertNotPushed`, `assertPushedCount(n, job?)`, `assertNothingPushed()`, `pushed(job?, matcher?)`. Each record has `id`, `name`, `queue`, `payload`, `options` (`delay`, `priority`, `jobId`) and `pushedAt`. An explicit `jobId` stays idempotent and `job.find(id)` answers from the record. Jobs are not run — call the handler to test it.
|
|
6425
6803
|
- **cache** — `assertHas(key)`, `assertMissing(key)`, `assertStored(key, value)` (deep equality), `driver()`, `clear()`. TTLs, tags, namespaces and locks are the real memory driver's.
|
|
6426
6804
|
- **storage** — `assertExists(path, driver?)`, `assertMissing(path, driver?)`, `files(driver?)`, `root()`. Each configured driver name gets a folder under one temp root, shared with the HTTP test server through `WARLOCK_TEST_STORAGE_ROOT`; the process that created it deletes it.
|
|
6427
|
-
-
|
|
6805
|
+
- **secrets** — async `assertHas(name)`, `assertMissing(name)`, `assertStored(name, value)`, plus `clear()` and `isInstalled()`. Assertion errors never reveal values. See `store-secrets` for examples and explicit memory stores.
|
|
6806
|
+
- A failed assertion throws `FakeAssertionError`; secret assertions omit values. Other fakes list what was recorded.
|
|
6428
6807
|
|
|
6429
6808
|
## Time and ids
|
|
6430
6809
|
|
|
@@ -8012,17 +8391,95 @@ The CLI preserves incremental state and changed-file filtering. Development
|
|
|
8012
8391
|
health checks keep one native compiler session and update its snapshots. Syntax
|
|
8013
8392
|
analysis uses OXC. `warlock upgrade` still parses/transforms with ts-morph's own
|
|
8014
8393
|
bundled TypeScript, without using it as the app's compiler. New scaffolds default
|
|
8015
|
-
to Oxlint/Oxfmt (`--lint=oxc`)
|
|
8016
|
-
|
|
8017
|
-
|
|
8394
|
+
to Oxlint/Oxfmt (`--lint=oxc`) with TypeScript 7.0.2. ESLint support was
|
|
8395
|
+
removed in 5.34.0; migrate legacy apps using the steps below.
|
|
8396
|
+
|
|
8397
|
+
## 5.32 to 5.33
|
|
8398
|
+
|
|
8399
|
+
The original `oxc-toolchain` codemod remains available:
|
|
8018
8400
|
|
|
8019
8401
|
```bash
|
|
8020
|
-
|
|
8021
|
-
npx
|
|
8402
|
+
npx warlock upgrade --from 5.32 --to 5.33.0 --dry-run
|
|
8403
|
+
npx warlock upgrade --from 5.32 --to 5.33.0
|
|
8022
8404
|
```
|
|
8023
8405
|
|
|
8024
|
-
|
|
8025
|
-
|
|
8406
|
+
## 5.33 to 5.34 toolchain removal
|
|
8407
|
+
|
|
8408
|
+
**BREAKING:** ESLint and Prettier support was removed in **5.34.0**. Oxlint/Oxfmt is the only
|
|
8409
|
+
supported toolchain. `--lint eslint` and `healthCheckers.eslint` are removed.
|
|
8410
|
+
Install the new core release and `ts-morph`, then migrate an existing app:
|
|
8411
|
+
|
|
8412
|
+
```bash
|
|
8413
|
+
npm install --save-dev ts-morph
|
|
8414
|
+
npx warlock upgrade --from 5.33 --to 5.34.0 --dry-run
|
|
8415
|
+
npx warlock upgrade --from 5.33 --to 5.34.0
|
|
8416
|
+
npm install
|
|
8417
|
+
npm run format
|
|
8418
|
+
npm run lint:check
|
|
8419
|
+
npx warlock check
|
|
8420
|
+
```
|
|
8421
|
+
|
|
8422
|
+
The `oxc-toolchain-removal` step reapplies the preserved 5.33 `oxc-toolchain`
|
|
8423
|
+
migration when legacy configs, scripts or dependencies remain. Apps upgrading
|
|
8424
|
+
from 5.32 can still use `npx warlock upgrade --from 5.32 --to 5.33.0` first.
|
|
8425
|
+
It replaces standard scripts/dependencies, pins TypeScript 7.0.2, updates generated
|
|
8426
|
+
editor settings, removes `healthCheckers.eslint` and selects Oxlint. A generated
|
|
8427
|
+
`.prettierignore` becomes `.oxfmtignore`; Oxfmt scripts read it explicitly.
|
|
8428
|
+
|
|
8429
|
+
The `oxc-toolchain` codemod also updates generated VS Code settings and literal
|
|
8430
|
+
ESLint/Oxlint health-check switches. Custom editor settings, checker arrays,
|
|
8431
|
+
computed options and nonstandard scripts require review. Oxc diagnostics keep
|
|
8432
|
+
framework conventions in `warlock check`. Re-run the migration after addressing
|
|
8433
|
+
manual findings if needed; an app with only Oxc tooling is a no-op for this codemod.
|
|
8434
|
+
|
|
8435
|
+
## 5.33 to 5.34 plain object validation
|
|
8436
|
+
|
|
8437
|
+
**BREAKING:** Seal `v.object()` rejects arrays, dates, maps, sets, class instances
|
|
8438
|
+
and objects with custom prototypes, including under `.allowUnknown()` and
|
|
8439
|
+
`.stripUnknown()`. Plain and null-prototype dictionaries remain accepted. There
|
|
8440
|
+
is no permissive opt-in: choose an array/instance validator or construct a plain
|
|
8441
|
+
DTO explicitly before validation.
|
|
8442
|
+
|
|
8443
|
+
```ts
|
|
8444
|
+
import { v } from "@warlock.js/seal";
|
|
8445
|
+
|
|
8446
|
+
class User {
|
|
8447
|
+
name = "Ada";
|
|
8448
|
+
}
|
|
8449
|
+
const instance = new User();
|
|
8450
|
+
const user = v.object({ name: v.string() });
|
|
8451
|
+
await v.validate(user, { name: instance.name });
|
|
8452
|
+
const names = v.array(v.string());
|
|
8453
|
+
await v.validate(names, ["Ada"]);
|
|
8454
|
+
```
|
|
8455
|
+
|
|
8456
|
+
`v.record(valueValidator)` remains supported. To validate dictionary keys, use
|
|
8457
|
+
`v.record(keyValidator, valueValidator)`; malformed arguments now fail while
|
|
8458
|
+
defining the schema. Literal-key records infer partial dictionaries because
|
|
8459
|
+
only present keys are checked.
|
|
8460
|
+
Literal custom rules are translated without executing the config. Per-folder
|
|
8461
|
+
`no-restricted-imports` and `no-restricted-exports` become Oxlint overrides with
|
|
8462
|
+
file/exclusion patterns. Supported `@typescript-eslint/*` rules become native
|
|
8463
|
+
`typescript/*` rules (or native base rules). Options are checked against the
|
|
8464
|
+
pinned Oxlint 1.88.0 schema. Type-aware equivalents enable `options.typeAware`
|
|
8465
|
+
and add `oxlint-tsgolint@7.0.2004` to the app's dev dependencies.
|
|
8466
|
+
`unused-imports/no-unused-imports` uses `no-unused-vars` with safe import fixes;
|
|
8467
|
+
Oxlint shares the severity for imports and variables, so differing active
|
|
8468
|
+
severities are reported for review.
|
|
8469
|
+
|
|
8470
|
+
Only unsupported rules/options, computed or imported settings, selectors such
|
|
8471
|
+
as `no-restricted-syntax`, and conflicting existing Oxc rules need manual review.
|
|
8472
|
+
Each finding names its source `file:line`, rule and affected file patterns.
|
|
8473
|
+
Fully ported lint configs are removed; configs with findings remain for reference
|
|
8474
|
+
until those findings are addressed. Existing Oxc rules are kept on conflict.
|
|
8475
|
+
`--dry-run --json` reports the same plan without writes. Customized formatting,
|
|
8476
|
+
editor settings and nonstandard scripts can also require review.
|
|
8477
|
+
|
|
8478
|
+
**Upgrading:** new and migrated template configs enable
|
|
8479
|
+
`"categories": { "correctness": "error" }`. Address the additional diagnostics;
|
|
8480
|
+
existing Oxc configs retain their categories. A real lockfile beside a workspace
|
|
8481
|
+
marker determines the installer: `package-lock.json` plus `pnpm-workspace.yaml`
|
|
8482
|
+
without `pnpm-lock.yaml` now produces `npm install`.
|
|
8026
8483
|
|
|
8027
8484
|
|
|
8028
8485
|
## upload-file `@warlock.js/core/upload-file/SKILL.md`
|
|
@@ -10716,7 +11173,7 @@ const { messages } = await watchBoard({ agent: "implementer", timeoutMs: 30 * 60
|
|
|
10716
11173
|
|
|
10717
11174
|
---
|
|
10718
11175
|
name: warlock-check
|
|
10719
|
-
description: 'Verify a change with one command: `warlock check [--changed] [--json]` runs an incremental typecheck, the app''s
|
|
11176
|
+
description: 'Verify a change with one command: `warlock check [--changed] [--json]` runs an incremental typecheck, the app''s Oxlint, the tests affected by the change and framework convention checks, and reports one JSON object per problem `{ code, category, severity, source, file, line, column, message, hint, skill, docs, rule }` with exit 1 for problems in your code and 2 for problems with this machine (missing Redis/DB, busy port, Node version, missing tool). Also `warlock typecheck [--changed]`, `warlock explain <CODE>` and the stable `WL` error codes (`errorCodeOf`, `WarlockError`). Triggers: `warlock check`, `warlock typecheck`, `warlock explain`, `--changed`, `--base`, `runCheck`, `runTypecheck`, `CHECK_EXIT_CODES`, `ToolingProblem`, `WL7101`, `WL8003`, `errorCodeOf`, `explainErrorCode`, `WarlockError`, "verify my change", "run the checks", "is this failure my code or the machine", "what does WL3001 mean", "typecheck only changed files". Skip: app diagnostics at boot — the `warlock-doctor` topic; reading the app''s routes/models — the `warlock-inspect` topic; writing tests — the `test-http` / `test-service` topics.'
|
|
10720
11177
|
---
|
|
10721
11178
|
|
|
10722
11179
|
# Warlock — `warlock check`, `typecheck`, `explain`
|
|
@@ -10836,10 +11293,10 @@ const types = runTypecheck({ files: ["src/app/posts/services/publish.service.ts"
|
|
|
10836
11293
|
## Gotchas
|
|
10837
11294
|
|
|
10838
11295
|
- `check` boots nothing — no config, no connection, no port — so it runs the same with or without services; only tests that need them fail, and those report `WL8003`/`WL8004`.
|
|
10839
|
-
- Lint
|
|
11296
|
+
- Lint defaults to Oxlint (an app-root config + the app-installed package). No Oxlint config skips lint. A missing package reports `WL8002`. ESLint support was removed in 5.34.0; see `upgrade-from-5-29`.
|
|
10840
11297
|
- Tests use the app's own vitest (`vitest run`, or `vitest related <files> --run` with `--changed`). Test files without vitest installed → `WL8002`.
|
|
10841
11298
|
- The typecheck runs in a child `warlock typecheck` process; running out of memory is reported as `WL8007` instead of ending the run.
|
|
10842
|
-
- The convention checks are the framework's own and run the same with
|
|
11299
|
+
- The convention checks are the framework's own and run the same with Oxlint or no linter. A deliberate exception gets `// warlock-check-ignore WL9006 -- why` on the problem's line or the line above (no code = every code on that line).
|
|
10843
11300
|
|
|
10844
11301
|
## See also
|
|
10845
11302
|
|
|
@@ -10848,6 +11305,10 @@ const types = runTypecheck({ files: ["src/app/posts/services/publish.service.ts"
|
|
|
10848
11305
|
- The `warlock-inspect` topic — the app's routes, models and config as JSON.
|
|
10849
11306
|
- The `warlock-conventions` topic — the rules behind `WL9001`–`WL9012`, with the fix for each.
|
|
10850
11307
|
|
|
11308
|
+
Migrate lint and format tooling with
|
|
11309
|
+
`npx warlock upgrade --from 5.32 --to 5.33.0 --dry-run`, then remove `--dry-run`
|
|
11310
|
+
and work through the manual-review list. Use `--lint=oxc` for new apps.
|
|
11311
|
+
|
|
10851
11312
|
|
|
10852
11313
|
## warlock-conventions `@warlock.js/core/warlock-conventions/SKILL.md`
|
|
10853
11314
|
|
|
@@ -11279,6 +11740,14 @@ A `⚠ routes: 0 routes registered` line is the tell that a route module threw o
|
|
|
11279
11740
|
- The `warlock-check` topic — `warlock check` / `typecheck` / `explain` and the error-code registry the findings use.
|
|
11280
11741
|
- The `warlock-inspect` topic — `warlock inspect`, the same boot pass printed as a versioned JSON report of the app.
|
|
11281
11742
|
|
|
11743
|
+
## Lint and format checks
|
|
11744
|
+
|
|
11745
|
+
`warlock doctor` checks application wiring and HTTP health probes. Use
|
|
11746
|
+
`npx warlock check` for source lint diagnostics and `npm run format:check` for
|
|
11747
|
+
formatting. Oxlint/Oxfmt are the only supported tools in 5.34.0. Use the
|
|
11748
|
+
`oxc-toolchain-removal` upgrade step to migrate remaining legacy configs.
|
|
11749
|
+
Migrate with `npx warlock upgrade --from 5.32 --to 5.33.0` (preview with `--dry-run`).
|
|
11750
|
+
|
|
11282
11751
|
|
|
11283
11752
|
## warlock-inspect `@warlock.js/core/warlock-inspect/SKILL.md`
|
|
11284
11753
|
|
|
@@ -11884,6 +12353,25 @@ Reading `app.socket` (or `getSocketServer()`) at the top level of `main.ts` retu
|
|
|
11884
12353
|
|
|
11885
12354
|
## Emitting from a controller or service
|
|
11886
12355
|
|
|
12356
|
+
Use `broadcastSocketEvent` when a framework integration should tolerate an
|
|
12357
|
+
absent connector and broadcast through the configured adapter:
|
|
12358
|
+
|
|
12359
|
+
```ts
|
|
12360
|
+
import { broadcastSocketEvent } from "@warlock.js/core";
|
|
12361
|
+
|
|
12362
|
+
const dispatched = broadcastSocketEvent("inventory:changed", { tags: ["products"] });
|
|
12363
|
+
// false without a booted socket server; true means dispatched, not delivered.
|
|
12364
|
+
```
|
|
12365
|
+
|
|
12366
|
+
This broadcasts to all default-namespace clients, so payloads must be public.
|
|
12367
|
+
It uses `io.emit` (not `io.local.emit`), allowing `socket.adapter` to forward to
|
|
12368
|
+
other app processes. Each publisher, including a queue/CLI process, must boot
|
|
12369
|
+
the socket connector with the same adapter. See Socket.IO's official
|
|
12370
|
+
[Redis adapter](https://socket.io/docs/v4/redis-adapter/) and
|
|
12371
|
+
[multiple-node guidance](https://socket.io/docs/v4/using-multiple-nodes/).
|
|
12372
|
+
Web's `invalidatePageCache(tags)` uses this path for opt-in `config.live` pages;
|
|
12373
|
+
shared cache storage is also required when evicting across processes.
|
|
12374
|
+
|
|
11887
12375
|
Once the server is up, anything with access to `app.socket` can emit:
|
|
11888
12376
|
|
|
11889
12377
|
```ts title="src/app/notifications/services/notify-user.service.ts"
|
|
@@ -12284,6 +12772,7 @@ warlock generate (alias: g) — interactive picker
|
|
|
12284
12772
|
| `postman` | `@warlock.js/postman` |
|
|
12285
12773
|
| `herald` | `@warlock.js/herald` + `amqplib` + types; ejects `src/config/communicator.ts` |
|
|
12286
12774
|
| `test` | `vitest` + `@mongez/vite` + coverage; drops `test-global-setup.ts` / `test-setup.ts` / `vite.config.ts` |
|
|
12775
|
+
| `process` | `@warlock.js/process` pinned to installed Core; typed config, supervisor export, recovery/shutdown lifecycle and optional devtools process panel |
|
|
12287
12776
|
| `devtools` | `@warlock.js/devtools` as a dev dependency; `warlock dev` auto-loads its development dashboard at `/__warlock` |
|
|
12288
12777
|
|
|
12289
12778
|
Run `warlock add --list` to see what's currently registered. Pass `--packageManager <yarn\|pnpm\|npm>` to override auto-detection (defaults to whichever lockfile is present).
|
|
@@ -12294,6 +12783,10 @@ production install with dev dependencies excluded.
|
|
|
12294
12783
|
|
|
12295
12784
|
The Web starter keeps the page's universal non-component setup in `src/web/index.register.ts`, while `src/web/index.page.tsx` re-exports `register`. Warlock still discovers and runs the hook from the page namespace in both SSR and the browser, but editing the page component remains inside React's Fast Refresh boundary. Do not move the `register()` declaration inline beside the component: React Refresh treats that non-component export as incompatible and reloads the page, losing component state.
|
|
12296
12785
|
|
|
12786
|
+
### AI input measurement
|
|
12787
|
+
|
|
12788
|
+
`warlock ai measure --model <id> [--tools <module>] [--system <file>] [--skills <dir>] [--json]` loads `@warlock.js/ai` lazily without app bootstrap. Model IDs default to OpenAI Responses or use `openai:`, `anthropic:`, `google:` prefixes and the corresponding installed adapter/environment API key. `--model-module <module>` accepts `model`, `default`, or `resolveModel(id)` for custom providers/prices. Tools modules export a `tools` or default array; paths resolve from cwd. The report breaks down messages, system, skills catalogue, and tools, labels native counts versus estimates, and includes uncached input USD only when priced. CLI reports include an explicit `.` user probe and do not preload skill bodies or add skill tools. JSON stdout contains one report. No CLI config registration is required.
|
|
12789
|
+
|
|
12297
12790
|
### Misc
|
|
12298
12791
|
|
|
12299
12792
|
| Command | Purpose |
|
|
@@ -12378,6 +12871,22 @@ export default command({
|
|
|
12378
12871
|
- The `configure-app` topic — wiring `defineConfig` and `warlock.config.ts`.
|
|
12379
12872
|
- The `warlock-conventions` topic — module layout, canonical imports.
|
|
12380
12873
|
|
|
12874
|
+
## App-owned processes
|
|
12875
|
+
|
|
12876
|
+
`npx warlock add process` creates typed `src/config/process.ts` (owner, registry,
|
|
12877
|
+
default limits), `src/app/processes/index.ts` (the `processes` supervisor export),
|
|
12878
|
+
lifecycle hooks, and a read-only devtools list source. It imports lifecycle from
|
|
12879
|
+
`src/app/main.ts`. Startup awaits `recover()` using `Application.onValidateBoot`;
|
|
12880
|
+
graceful shutdown awaits `stopAll()` using `Application.onShutdown`. Recovery
|
|
12881
|
+
failure aborts boot. Repeated installation preserves existing files.
|
|
12882
|
+
|
|
12883
|
+
Set a unique owner and registry per app instance, then import `processes` from
|
|
12884
|
+
the generated module and await `processes.spawn({ kind: "worker", command,
|
|
12885
|
+
args, labels })`. Consume output and await `handle.exited`. Use handle stops or
|
|
12886
|
+
label filters; never stop processes by command or name. `spawnPty()` lazily
|
|
12887
|
+
loads separately installed optional peer `node-pty`. The `supervise-processes`
|
|
12888
|
+
skill in `@warlock.js/process` contains complete working examples.
|
|
12889
|
+
|
|
12381
12890
|
|
|
12382
12891
|
## write-middleware `@warlock.js/core/write-middleware/SKILL.md`
|
|
12383
12892
|
|