@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.
Files changed (173) hide show
  1. package/CHANGELOG.md +64 -46
  2. package/esm/application/application-config-types.d.mts +8 -0
  3. package/esm/application/application-config-types.d.mts.map +1 -1
  4. package/esm/application/boot-signal.d.mts +8 -8
  5. package/esm/application/boot-signal.d.mts.map +1 -1
  6. package/esm/application/boot-signal.mjs +15 -9
  7. package/esm/application/boot-signal.mjs.map +1 -1
  8. package/esm/cli/commands/ai/ai-command.action.mjs +140 -0
  9. package/esm/cli/commands/ai/ai-command.action.mjs.map +1 -0
  10. package/esm/cli/commands/ai.command.mjs +50 -0
  11. package/esm/cli/commands/ai.command.mjs.map +1 -0
  12. package/esm/cli/commands/check/check-command.action.mjs +4 -0
  13. package/esm/cli/commands/check/check-command.action.mjs.map +1 -1
  14. package/esm/cli/commands/check.command.mjs +6 -1
  15. package/esm/cli/commands/check.command.mjs.map +1 -1
  16. package/esm/cli/commands/e2e.command.mjs +63 -0
  17. package/esm/cli/commands/e2e.command.mjs.map +1 -0
  18. package/esm/cli/commands/load-e2e-spec.mjs +47 -0
  19. package/esm/cli/commands/load-e2e-spec.mjs.map +1 -0
  20. package/esm/cli/commands/pm2/pm2-command.action.mjs +91 -0
  21. package/esm/cli/commands/pm2/pm2-command.action.mjs.map +1 -0
  22. package/esm/cli/commands/pm2/pm2-config.mjs +115 -0
  23. package/esm/cli/commands/pm2/pm2-config.mjs.map +1 -0
  24. package/esm/cli/commands/pm2.command.mjs +53 -0
  25. package/esm/cli/commands/pm2.command.mjs.map +1 -0
  26. package/esm/cli/framework-cli-commands.mjs +6 -0
  27. package/esm/cli/framework-cli-commands.mjs.map +1 -1
  28. package/esm/connectors/connectors-manager.d.mts.map +1 -1
  29. package/esm/connectors/connectors-manager.mjs +1 -4
  30. package/esm/connectors/connectors-manager.mjs.map +1 -1
  31. package/esm/connectors/shutdown-timeout.mjs +7 -0
  32. package/esm/connectors/shutdown-timeout.mjs.map +1 -0
  33. package/esm/connectors/socket-connector.d.mts.map +1 -1
  34. package/esm/connectors/socket-connector.mjs +8 -1
  35. package/esm/connectors/socket-connector.mjs.map +1 -1
  36. package/esm/dev-server/health-checker/checkers/oxlint-health-checker.mjs +1 -1
  37. package/esm/dev-server/health-checker/default-health-checkers.mjs +2 -16
  38. package/esm/dev-server/health-checker/default-health-checkers.mjs.map +1 -1
  39. package/esm/dev-server/health-checker/file-health-checker.contract.d.mts +1 -2
  40. package/esm/dev-server/health-checker/file-health-checker.contract.d.mts.map +1 -1
  41. package/esm/dev-server/health-checker/file-health-result.d.mts +1 -1
  42. package/esm/dev-server/health-checker/file-health-result.mjs.map +1 -1
  43. package/esm/e2e/browser.d.mts +4 -0
  44. package/esm/e2e/browser.d.mts.map +1 -0
  45. package/esm/e2e/browser.mjs +88 -0
  46. package/esm/e2e/browser.mjs.map +1 -0
  47. package/esm/e2e/cdp.d.mts +152 -0
  48. package/esm/e2e/cdp.d.mts.map +1 -0
  49. package/esm/e2e/cdp.mjs +106 -0
  50. package/esm/e2e/cdp.mjs.map +1 -0
  51. package/esm/e2e/fixture.mjs +195 -0
  52. package/esm/e2e/fixture.mjs.map +1 -0
  53. package/esm/e2e/index.d.mts +5 -0
  54. package/esm/e2e/index.mjs +6 -0
  55. package/esm/e2e/owned-process.mjs +193 -0
  56. package/esm/e2e/owned-process.mjs.map +1 -0
  57. package/esm/e2e/page.d.mts +64 -0
  58. package/esm/e2e/page.d.mts.map +1 -0
  59. package/esm/e2e/page.mjs +376 -0
  60. package/esm/e2e/page.mjs.map +1 -0
  61. package/esm/e2e/run-e2e.d.mts +17 -0
  62. package/esm/e2e/run-e2e.d.mts.map +1 -0
  63. package/esm/e2e/run-e2e.mjs +143 -0
  64. package/esm/e2e/run-e2e.mjs.map +1 -0
  65. package/esm/e2e/types.d.mts +123 -0
  66. package/esm/e2e/types.d.mts.map +1 -0
  67. package/esm/e2e/types.mjs +24 -0
  68. package/esm/e2e/types.mjs.map +1 -0
  69. package/esm/generations/features/index.mjs +2 -0
  70. package/esm/generations/features/index.mjs.map +1 -1
  71. package/esm/generations/features/process.feature.mjs +65 -0
  72. package/esm/generations/features/process.feature.mjs.map +1 -0
  73. package/esm/http/port-preflight.d.mts.map +1 -1
  74. package/esm/http/port-preflight.mjs +2 -0
  75. package/esm/http/port-preflight.mjs.map +1 -1
  76. package/esm/index.d.mts +7 -2
  77. package/esm/index.mjs +7 -2
  78. package/esm/secrets/errors.d.mts +9 -0
  79. package/esm/secrets/errors.d.mts.map +1 -0
  80. package/esm/secrets/errors.mjs +24 -0
  81. package/esm/secrets/errors.mjs.map +1 -0
  82. package/esm/secrets/file-backend.mjs +167 -0
  83. package/esm/secrets/file-backend.mjs.map +1 -0
  84. package/esm/secrets/index.d.mts +6 -0
  85. package/esm/secrets/index.mjs +6 -0
  86. package/esm/secrets/keyring-backend.mjs +50 -0
  87. package/esm/secrets/keyring-backend.mjs.map +1 -0
  88. package/esm/secrets/memory-backend.d.mts +12 -0
  89. package/esm/secrets/memory-backend.d.mts.map +1 -0
  90. package/esm/secrets/memory-backend.mjs +28 -0
  91. package/esm/secrets/memory-backend.mjs.map +1 -0
  92. package/esm/secrets/resolve-driver.mjs +14 -0
  93. package/esm/secrets/resolve-driver.mjs.map +1 -0
  94. package/esm/secrets/secret-store.d.mts +25 -0
  95. package/esm/secrets/secret-store.d.mts.map +1 -0
  96. package/esm/secrets/secret-store.mjs +120 -0
  97. package/esm/secrets/secret-store.mjs.map +1 -0
  98. package/esm/secrets/secrets.d.mts +11 -0
  99. package/esm/secrets/secrets.d.mts.map +1 -0
  100. package/esm/secrets/secrets.mjs +72 -0
  101. package/esm/secrets/secrets.mjs.map +1 -0
  102. package/esm/secrets/types.d.mts +26 -0
  103. package/esm/secrets/types.d.mts.map +1 -0
  104. package/esm/socket/index.d.mts +2 -2
  105. package/esm/socket/index.mjs +2 -2
  106. package/esm/socket/utils.d.mts +7 -0
  107. package/esm/socket/utils.d.mts.map +1 -1
  108. package/esm/socket/utils.mjs +12 -1
  109. package/esm/socket/utils.mjs.map +1 -1
  110. package/esm/tests/fakes/index.d.mts +2 -1
  111. package/esm/tests/fakes/index.mjs +2 -1
  112. package/esm/tests/fakes/secrets-fake.d.mts +13 -0
  113. package/esm/tests/fakes/secrets-fake.d.mts.map +1 -0
  114. package/esm/tests/fakes/secrets-fake.mjs +50 -0
  115. package/esm/tests/fakes/secrets-fake.mjs.map +1 -0
  116. package/esm/tests/fakes/test-fakes.d.mts +2 -2
  117. package/esm/tests/fakes/test-fakes.d.mts.map +1 -1
  118. package/esm/tests/fakes/test-fakes.mjs +5 -2
  119. package/esm/tests/fakes/test-fakes.mjs.map +1 -1
  120. package/esm/tests/index.d.mts +2 -1
  121. package/esm/tests/index.mjs +2 -1
  122. package/esm/tests/vitest-setup.d.mts +1 -1
  123. package/esm/tests/vitest-setup.mjs.map +1 -1
  124. package/esm/tooling/check/lint-step.d.mts +2 -8
  125. package/esm/tooling/check/lint-step.d.mts.map +1 -1
  126. package/esm/tooling/check/lint-step.mjs +1 -42
  127. package/esm/tooling/check/lint-step.mjs.map +1 -1
  128. package/esm/tooling/check/run-check.d.mts +2 -0
  129. package/esm/tooling/check/run-check.d.mts.map +1 -1
  130. package/esm/tooling/check/run-check.mjs +3 -1
  131. package/esm/tooling/check/run-check.mjs.map +1 -1
  132. package/esm/tooling/problem.d.mts +1 -1
  133. package/esm/tooling/problem.d.mts.map +1 -1
  134. package/esm/tooling/problem.mjs.map +1 -1
  135. package/esm/updater/package-manager.mjs +5 -1
  136. package/esm/updater/package-manager.mjs.map +1 -1
  137. package/esm/upgrade/codemods/index.mjs +3 -0
  138. package/esm/upgrade/codemods/index.mjs.map +1 -1
  139. package/esm/upgrade/codemods/oxc-toolchain.mjs +191 -0
  140. package/esm/upgrade/codemods/oxc-toolchain.mjs.map +1 -0
  141. package/esm/upgrade/codemods/oxlint-rule-schemas.mjs +6 -0
  142. package/esm/upgrade/codemods/oxlint-rule-schemas.mjs.map +1 -0
  143. package/esm/upgrade/codemods/port-eslint-rules.mjs +343 -0
  144. package/esm/upgrade/codemods/port-eslint-rules.mjs.map +1 -0
  145. package/esm/upgrade/codemods/toolchain-templates.mjs +244 -0
  146. package/esm/upgrade/codemods/toolchain-templates.mjs.map +1 -0
  147. package/esm/upgrade/codemods/validate-oxlint-rule.mjs +60 -0
  148. package/esm/upgrade/codemods/validate-oxlint-rule.mjs.map +1 -0
  149. package/esm/upgrade/notes.mjs +14 -7
  150. package/esm/upgrade/notes.mjs.map +1 -1
  151. package/esm/upgrade/run-upgrade.mjs +25 -6
  152. package/esm/upgrade/run-upgrade.mjs.map +1 -1
  153. package/esm/warlock-config/types.d.mts +1 -1
  154. package/llms-full.txt +538 -29
  155. package/llms.txt +5 -3
  156. package/package.json +29 -20
  157. package/skills/README.md +2 -1
  158. package/skills/create-module/SKILL.md +2 -2
  159. package/skills/index.md +1 -1
  160. package/skills/run-app/SKILL.md +115 -6
  161. package/skills/store-secrets/SKILL.md +141 -0
  162. package/skills/test-browser/SKILL.md +119 -0
  163. package/skills/test-fakes/SKILL.md +13 -11
  164. package/skills/upgrade-from-5-29/SKILL.md +85 -7
  165. package/skills/warlock-check/SKILL.md +7 -3
  166. package/skills/warlock-doctor/SKILL.md +8 -0
  167. package/skills/wire-socket/SKILL.md +19 -0
  168. package/skills/write-cli-command/SKILL.md +21 -0
  169. package/esm/dev-server/health-checker/checkers/eslint-health-checker.mjs +0 -154
  170. package/esm/dev-server/health-checker/checkers/eslint-health-checker.mjs.map +0 -1
  171. package/esm/dev-server/health-checker/workers/eslint-health.worker.d.mts +0 -1
  172. package/esm/dev-server/health-checker/workers/eslint-health.worker.mjs +0 -192
  173. package/esm/dev-server/health-checker/workers/eslint-health.worker.mjs.map +0 -1
package/llms.txt CHANGED
@@ -27,11 +27,13 @@
27
27
  - [request-tracing](@warlock.js/core/request-tracing/SKILL.md): Vendor-neutral request tracing hooks (`http.tracing`) — observe request start/end and named phase spans (`route.match`, `middleware`, `validation`, `handler`, `response.write`) without adopting an OTel/vendor dependency in `core`. Covers the `TracingHooks` shape, trace id derivation from an inbound W3C `traceparent` vs `request.id`, that a throwing hook is caught and reported once (never breaks the request), and zero overhead when disabled. Triggers: `http.tracing`, `TracingHooks`, `onRequestStart`, `onRequestEnd`, `onPhase`, `traceparent`, `traceId`, `dispatchPhase`, "instrument requests", "trace a request", "slow phase logging", "APM / OTel bridge for Warlock". Skip: request-id header echo/inheritance (`X-Request-Id`) — the `use-middleware` topic; app-level structured logging — the `logger-basics` topic of the `warlock-js-logger` skill; competing libs `@opentelemetry/api` direct instrumentation, `express-request-id`, hand-rolled `X-Trace-Id` middleware.
28
28
  - [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 — the `build-url` topic; app metadata — the `use-app-context` topic; storage abstraction — the `store-file` topic; competing patterns: `path.join(process.cwd(), ...)`, hand-rolled directory constants.
29
29
  - [retry-operation](@warlock.js/core/retry-operation/SKILL.md): Wrap a flaky operation with `retry(fn, options)` — now provided by `@mongez/reinforcements` (not `@warlock.js/core`). `attempts` total tries, `delay` + `backoff` (linear/exponential/fn), `maxDelay`, `jitter`, `shouldRetry` to bail on permanent errors, `signal` to cancel, plus `retryable()` to pre-bind options. Triggers: `retry`, `retryable`, `RetryOptions`, `attempts`, `backoff`, `jitter`, `maxDelay`, `shouldRetry`, `signal`; "retry a flaky API call", "handle transient errors", "exponential backoff with jitter", "wrap an external request"; typical import `import { retry } from "@mongez/reinforcements"`. Skip: timing the retried op — the `benchmark-code` topic; use-case-level `retry` option — the `write-use-case` topic; competing libs `p-retry`, `async-retry`, `cockatiel`.
30
- - [run-app](@warlock.js/core/run-app/SKILL.md): Three operational commands — `warlock dev` (HMR + type-gen + health checks), `warlock build` (esbuild bundle), `warlock start` (spawn the production bundle). All flags, all `warlock.config.ts` knobs that shape them. Triggers: `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.
30
+ - [run-app](@warlock.js/core/run-app/SKILL.md): 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.
31
31
  - [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 — the `configure-app` topic; layered service patterns — the `warlock-conventions` topic; competing libs `nodemailer` direct, `@sendgrid/mail`, `resend`, `mailgun.js`.
32
32
  - [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`, `retainEvents`, `retainChunks`, `backpressure`, `@warlock.js/core/errors`, `ResourceNotFoundError`, `ForbiddenError`, `request.cookie`, `request.hasCookie`, `CookieJarUnavailableError`; "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 — the `create-controller` topic; route registration — the `register-route` topic; competing patterns: hand-rolled status codes via `reply.code(404).send(...)`, raw Fastify reply.
33
33
  - [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 — the `upload-file` topic; image transforms — the `process-image` topic; storage config shape — the `configure-app` topic; competing libs `@aws-sdk/client-s3`, `multer`, `formidable`.
34
- - [test-fakes](@warlock.js/core/test-fakes/SKILL.md): 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.
34
+ - [store-secrets](@warlock.js/core/store-secrets/SKILL.md): 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).
35
+ - [test-browser](@warlock.js/core/test-browser/SKILL.md): 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.
36
+ - [test-fakes](@warlock.js/core/test-fakes/SKILL.md): 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.
35
37
  - [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 })`, `startHttpTestServer({ port: 0 })`, `stopHttpTestServer`, `createTestHttpServer`, `TestHttpServer`, `createTestRequestClient`, `snapshotResponse`, `actingAs`, `AuthenticatedTestClient`, `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", "ephemeral test port", "test with the real Fastify plugins", "assert mail sent by a request", "response snapshot"; typical import `import { testGet, testPost, expectJson } from "@warlock.js/core/tests"`. Skip: pure unit tests — the `test-service` topic; controller shape — the `create-controller` topic; competing libs `supertest`, `light-my-request`, `nock`.
36
38
  - [test-service](@warlock.js/core/test-service/SKILL.md): Pure unit tests against services, repositories, models, and use-cases — `setupTest({ connectors })` bootstraps the framework with its own DB/cache connections so you can call your code directly, and `teardownTest()` closes it. Triggers: `setupTest`, `teardownTest`, `setupTest({ fakes })`, `tests.fakes`, `src/test-setup.ts`, `tests.connectors`, `tests.setupTimeout`, `Application.setEnvironment`, `clock`, `ids`, `warlock test --changed`; "unit-test a service", "test a repository query", "vitest setupFiles", "skip connectors for pure-logic tests", "run only the tests my change affects", "deterministic time in a test"; typical import `import { setupTest, teardownTest } from "@warlock.js/core/tests"`. Skip: HTTP integration — the `test-http` topic; warlock add test scaffold — the `write-cli-command` topic; competing tooling: jest direct, `supertest`, `nock`.
37
39
  - [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 — the `run-app` topic; writing a custom command — the `write-cli-command` topic; installing a NEW feature package (auth, mail, storage) — that is `warlock add`; releasing/publishing the framework — workspace release tooling, not this command.
@@ -46,7 +48,7 @@
46
48
  - [validate-input](@warlock.js/core/validate-input/SKILL.md): Author seal schemas, attach them to controllers via `controller.validation = { schema }`, infer types via `Infer<typeof schema>`, and layer DB-aware (`unique`/`exists`) and file validators on top. Triggers: `v.object`, `v.string`, `v.email`, `Infer`, `controller.validation`, `.unique`, `.exists`, `uniqueExceptCurrentId`, `request.validated`; "validate a request body", "attach a schema to a controller", "DB-aware unique rule", "infer schema types"; typical import `import { v, type Infer } from "@warlock.js/seal"`. Skip: schema authoring foundations — the `seal-basics` topic of the `warlock-js-seal` skill; controller wiring — the `create-controller` topic; file rules deep-dive — the `upload-file` topic; competing libs `zod`, `joi`, `yup`, `class-validator`.
47
49
  - [warlock-agents-md](@warlock.js/core/warlock-agents-md/SKILL.md): Generate the app-specific section of AGENTS.md from the booted app with `warlock agents-md [--write] [--json]` — modules, a routes summary with unguarded writes flagged, models, the conventions `warlock check` enforces, the installed skills and the verify commands — between `<!-- warlock:generated:start -->` / `<!-- warlock:generated:end -->` markers so hand-written content is never touched; `warlock dev` refreshes it (opt out with `devServer.agentsMd: false`). Triggers: `warlock agents-md`, `AGENTS.md`, `warlock:generated:start`, `renderAgentsMdSection`, `writeAgentsMd`, `upsertGeneratedSection`, `listInstalledSkills`, `createAgentsMdRefresher`, `devServer.agentsMd`, `WL7501`; "give agents the app facts", "keep AGENTS.md up to date", "which skills apply to this app". Skip: the full machine-readable report — the `warlock-inspect` topic; verifying a change — the `warlock-check` topic.
48
50
  - [warlock-board](@warlock.js/core/warlock-board/SKILL.md): Coordinate two coding agents (an implementer and a reviewer) through `warlock board`: work items with a review state machine (todo, in-progress, ready-for-review, changes-requested, approved, blocked), append-only numbered messages per recipient in the git-ignored `.warlock/board/`, `VERDICT: APPROVED` / `VERDICT: CHANGES REQUESTED` lines that move the item, and a blocking `watch` with a timeout; `--json` on every action. Triggers: `warlock board`, `board init`, `board add`, `board status`, `board send`, `board watch`, `--verdict`, `WARLOCK_AGENT`, `.warlock/board`, `to-reviewer/`, `initBoard`, `addBoardItem`, `moveBoardItem`, `sendBoardMessage`, `watchBoard`, `readBoardStatus`, `WL7401`, `WL7402`, `WL7403`, `WL7404`; "hand this to the reviewer agent", "wait for the review", "two agents working together", "request changes", "approve the item". Skip: verifying the code itself — the `warlock-check` topic; app facts — the `warlock-inspect` topic.
49
- - [warlock-check](@warlock.js/core/warlock-check/SKILL.md): Verify a change with one command: `warlock check [--changed] [--json]` runs an incremental typecheck, the app's ESLint/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.
51
+ - [warlock-check](@warlock.js/core/warlock-check/SKILL.md): 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.
50
52
  - [warlock-conventions](@warlock.js/core/warlock-conventions/SKILL.md): Framework-wide invariants for projects built on @warlock.js/core — module layout, canonical imports, layered flow, file naming, and the non-negotiable rules every other warlock skill assumes. Triggers: `src/app/<module>`, `routes.ts`, `main.ts`, `Request<TSchema>`, `RequestHandler`, `GuardedRequestHandler`, `app/<module>/...`; "where do files go in this project", "canonical Warlock imports", "module layout rules", "controller-service-repository layering"; typical import `import { router, type RequestHandler } from "@warlock.js/core"`. Skip: scaffold a new module — the `create-module` topic; route shape — the `register-route` topic; controller shape — the `create-controller` topic; competing patterns: `express` ad-hoc layouts, `@nestjs/common` decorator-driven structure.
51
53
  - [warlock-doctor](@warlock.js/core/warlock-doctor/SKILL.md): Run `warlock doctor` — a read-only diagnostics command that checks routes / config / connectors / optional-peers / health endpoints / release hygiene and prints a pass/warn/fail report, exiting non-zero on any failure. Add your own probe with the `DoctorCheck` contract and `runChecks` / `formatReportLines`. Triggers: `warlock doctor`, `warlock doctor --json`, `toDoctorJson`, `WL1001`, `doctorCommand`, `DoctorCheck`, `CheckResult`, `CheckStatus`, `DoctorReport`, `runChecks`, `formatReportLines`, `printReport`, `defaultDoctorChecks`; "diagnose my app", "preflight / preflight check", "is the app healthy", "why are there 0 routes", "pre-release sanity check", "CI smoke check"; run as `npx warlock doctor`. Skip: the live `/health` + `/ready` HTTP probes — the `health-checks` topic; authoring a general CLI command — the `write-cli-command` topic; releasing the package — `releasing-warlock-monorepo`; competing tools `npm doctor`, `nest info`, hand-rolled preflight scripts.
52
54
  - [warlock-inspect](@warlock.js/core/warlock-inspect/SKILL.md): Ask the app instead of grepping it: `warlock inspect --json` prints the facts of the booted app — routes (middleware chain, auth guard, CSRF, rate limit, logging, request/response JSON Schema), models (table, columns, relations, resource), resources, config keys and types, event listeners, pages (layouts, loaders, actions), queue jobs, scheduled tasks and installed @warlock.js versions — as a versioned report (`schemaVersion: 1`) with a published JSON Schema; `inspectApp()` returns the same object. Triggers: `warlock inspect`, `--section`, `inspect_routes`, `inspectApp`, `bootForInspect`, `InspectReport`, `INSPECT_REPORT_JSON_SCHEMA`, `inspect-report.schema.json`, `@warlock.js/core/tooling`; "which routes are unguarded", "what does this app expose", "what models and columns exist", "which config keys are set", "list event listeners", "app facts as JSON for an agent". Skip: a route table for humans — the `warlock-routes` topic; health diagnostics — the `warlock-doctor` topic; verifying a change — the `warlock-check` topic; an OpenAPI file — the `generate-openapi` topic.
package/package.json CHANGED
@@ -25,18 +25,17 @@
25
25
  "@mongez/slug": "^1.0.7",
26
26
  "@mongez/supportive-is": "^3.0.0",
27
27
  "@mongez/time-wizard": "^1.0.6",
28
- "@warlock.js/cache": "5.32.0",
29
- "@warlock.js/cascade": "5.32.0",
30
- "@warlock.js/context": "5.32.0",
31
- "@warlock.js/fs": "5.32.0",
32
- "@warlock.js/logger": "5.32.0",
33
- "@warlock.js/seal": "5.32.0",
28
+ "@warlock.js/cache": "5.34.0",
29
+ "@warlock.js/cascade": "5.34.0",
30
+ "@warlock.js/context": "5.34.0",
31
+ "@warlock.js/fs": "5.34.0",
32
+ "@warlock.js/logger": "5.34.0",
33
+ "@warlock.js/seal": "5.34.0",
34
34
  "bcryptjs": "^3.0.3",
35
35
  "chokidar": "^5.0.0",
36
36
  "dayjs": "^1.11.19",
37
37
  "es-module-lexer": "^2.0.0",
38
38
  "esbuild": "^0.28.2",
39
- "eslint": "^9.18.0",
40
39
  "fast-glob": "^3.3.3",
41
40
  "fastify": "^5.12.5",
42
41
  "find-my-way": "^9.9.0",
@@ -50,17 +49,18 @@
50
49
  "typescript": "7.0.2"
51
50
  },
52
51
  "peerDependencies": {
52
+ "@napi-rs/keyring": "^2.1.1",
53
53
  "@aws-sdk/client-s3": "^3.955.0",
54
54
  "@aws-sdk/client-sesv2": "^3.1025.0",
55
55
  "@aws-sdk/lib-storage": "^3.955.0",
56
56
  "@aws-sdk/s3-request-presigner": "^3.955.0",
57
57
  "@react-email/render": "^2.0.5",
58
- "@warlock.js/access": "5.32.0",
59
- "@warlock.js/ai": "5.32.0",
60
- "@warlock.js/ai-tools": "5.32.0",
61
- "@warlock.js/devtools": "5.32.0",
62
- "@warlock.js/herald": "5.32.0",
63
- "@warlock.js/notifications": "5.32.0",
58
+ "@warlock.js/access": "5.34.0",
59
+ "@warlock.js/ai": "5.34.0",
60
+ "@warlock.js/ai-tools": "5.34.0",
61
+ "@warlock.js/devtools": "5.34.0",
62
+ "@warlock.js/herald": "5.34.0",
63
+ "@warlock.js/notifications": "5.34.0",
64
64
  "nodemailer": "^8.0.5",
65
65
  "react": "^19.2.3",
66
66
  "react-dom": "^19.2.3",
@@ -69,6 +69,9 @@
69
69
  "ts-morph": ">=27.0.0"
70
70
  },
71
71
  "peerDependenciesMeta": {
72
+ "@napi-rs/keyring": {
73
+ "optional": true
74
+ },
72
75
  "sharp": {
73
76
  "optional": true
74
77
  },
@@ -142,7 +145,7 @@
142
145
  "engines": {
143
146
  "node": ">=22.15"
144
147
  },
145
- "version": "5.32.0",
148
+ "version": "5.34.0",
146
149
  "type": "module",
147
150
  "main": "./esm/index.mjs",
148
151
  "module": "./esm/index.mjs",
@@ -166,6 +169,12 @@
166
169
  "default": "./esm/tests/index.mjs"
167
170
  }
168
171
  },
172
+ "./e2e": {
173
+ "import": {
174
+ "types": "./esm/e2e/index.d.mts",
175
+ "default": "./esm/e2e/index.mjs"
176
+ }
177
+ },
169
178
  "./vite": {
170
179
  "import": {
171
180
  "types": "./esm/vite/index.d.mts",
@@ -184,6 +193,12 @@
184
193
  "default": "./esm/net/index.mjs"
185
194
  }
186
195
  },
196
+ "./secrets": {
197
+ "import": {
198
+ "types": "./esm/secrets/index.d.mts",
199
+ "default": "./esm/secrets/index.mjs"
200
+ }
201
+ },
187
202
  "./tooling": {
188
203
  "import": {
189
204
  "types": "./esm/tooling/index.d.mts",
@@ -196,12 +211,6 @@
196
211
  "default": "./esm/production/route-registration-child.mjs"
197
212
  }
198
213
  },
199
- "./dev-server/health-checker/workers/eslint-health.worker": {
200
- "import": {
201
- "types": "./esm/dev-server/health-checker/workers/eslint-health.worker.d.mts",
202
- "default": "./esm/dev-server/health-checker/workers/eslint-health.worker.mjs"
203
- }
204
- },
205
214
  "./dev-server/health-checker/workers/oxlint-health.worker": {
206
215
  "import": {
207
216
  "types": "./esm/dev-server/health-checker/workers/oxlint-health.worker.d.mts",
package/skills/README.md CHANGED
@@ -61,7 +61,8 @@ Each folder holds one `SKILL.md` (an agent-facing how-to for a single task). The
61
61
 
62
62
  - `test-http` — integration tests against a real HTTP server (`startHttpTestServer()`, `testGet` / `testPost`).
63
63
  - `test-service` — pure unit tests against services/repositories/models/use-cases via `setupTest({ connectors })`.
64
- - `test-fakes` — mail, queue, cache, storage, clock and ids fakes, on by default in tests, and their assertions; opting out.
64
+ - `store-secrets` — app-scoped credentials, lazy keychain/environment/encrypted file storage, memory backends and safe errors.
65
+ - `test-fakes` — mail, queue, cache, storage, clock, ids and secrets fakes, on by default in tests, and their assertions; opting out.
65
66
  - `model-factories` — `defineFactory(Model, (seed) => attrs)`: deterministic `make()` / `create(n)` / states.
66
67
 
67
68
  ## Utilities
@@ -191,7 +191,7 @@ await warmupProductCache();
191
191
 
192
192
  ## Verify generated imports
193
193
 
194
- Generated web starter files match the scaffold’s Prettier configuration, using
195
- `@ianvs/prettier-plugin-sort-imports` with Babel independently of TypeScript 7.
194
+ Generated web starter files match the scaffold’s Oxfmt configuration and its
195
+ built-in import sorting.
196
196
  After editing generated code, run `npm run format` and `npm run lint:check`.
197
197
  Formatting retains unused imports; the linter reports them.
package/skills/index.md CHANGED
@@ -25,7 +25,7 @@ When the framework is wrong or missing a capability, follow `report-framework-is
25
25
  - **CLI & running the app:** run-app, write-cli-command, add-connector, health-checks, warlock-doctor, warlock-inspect, warlock-check, generate-openapi
26
26
  - **Agent tooling:** warlock-mcp, warlock-trace, warlock-agents-md, warlock-board
27
27
  - **Storage, files & mail:** store-file, upload-file, process-image, send-mail
28
- - **Security & utilities:** hash-password, encrypt-data, retry-operation, benchmark-code, request-memo, request-tracing
28
+ - **Security & utilities:** hash-password, encrypt-data, store-secrets, retry-operation, benchmark-code, request-memo, request-tracing
29
29
  - **Testing:** test-http, test-service, test-fakes, model-factories
30
30
 
31
31
  ## Conventions and pitfalls
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: run-app
3
- description: 'Three operational commands — `warlock dev` (HMR + type-gen + health checks), `warlock build` (esbuild bundle), `warlock start` (spawn the production bundle). All flags, all `warlock.config.ts` knobs that shape them. Triggers: `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.'
3
+ 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.'
4
4
  ---
5
5
 
6
6
  # Warlock — run the app
@@ -150,13 +150,13 @@ export default defineConfig({
150
150
 
151
151
  - **`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.
152
152
  - **`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.
153
- - **`healthCheckers`** — built-in switches (`{ typescript?, eslint?, oxlint? }`), custom file health checker contracts, or `false` to disable. The `--skip-health` flag disables all checkers for that run. TypeScript runs by default. ESLint uses core's dependency and requires only an app-root flat config; Oxlint requires its package installed in the app plus a config. Both run when configured.
153
+ - **`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.
154
154
  - **`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.
155
155
  - **`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.
156
156
  - **`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.
157
157
  - **`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.
158
158
 
159
- ### Optional Oxlint file checks
159
+ ### Default Oxlint file checks
160
160
 
161
161
  Install `oxlint` in the app (`npm install --save-dev oxlint`) and add `.oxlintrc.json` at the app root:
162
162
 
@@ -173,12 +173,12 @@ import { defineConfig } from "@warlock.js/core";
173
173
 
174
174
  export default defineConfig({
175
175
  devServer: {
176
- healthCheckers: { typescript: true, eslint: false, oxlint: true },
176
+ healthCheckers: { typescript: true, oxlint: true },
177
177
  },
178
178
  });
179
179
  ```
180
180
 
181
- Omit the switches to run TypeScript and every installed/configured linter; `true` still requires the linter's config, and Oxlint also requires its app-installed package. Set `oxlint: false` to disable only Oxlint while leaving other defaults intact. A custom checker array replaces the built-in list. Config discovery also accepts `.oxlintrc.jsonc`, `oxlint.config.ts` and `oxlint.config.mts`; use JSON/JSONC on Node 22.15 because current Oxlint TypeScript configs need Node 22.18 or newer.
181
+ 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.
182
182
 
183
183
  ## `warlock build` — production bundle
184
184
 
@@ -412,11 +412,120 @@ Configured http.port "03999" normalised to 3999.
412
412
  Three consequences worth knowing:
413
413
 
414
414
  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.
415
- 2. **Running the bundle any other way changes nothing.** `node dist/app.js`, a Docker `CMD`, or pm2 — the signal is a strict no-op without both the IPC channel and the handshake flag, so Warlock never writes into a channel that belongs to another supervisor.
415
+ 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.
416
416
  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.
417
417
 
418
418
  If you need the same fact inside the app, use `Application.onceBooted()` / `Application.whenBooted()` — the signal and your listeners fire from the same latch.
419
419
 
420
+ ## `warlock pm2 init` / `reload` — optional production supervision
421
+
422
+ PM2 is operator tooling, never a core dependency. Install it separately and
423
+ put `pm2` on `PATH`. Generate from the app root with the target environment:
424
+
425
+ ```bash
426
+ NODE_ENV=production npx warlock build
427
+ NODE_ENV=production npx warlock pm2 init
428
+ # package.json name=store, app.appName=api → store-api-production
429
+ pm2 start ecosystem.config.cjs --only store-api-production
430
+
431
+ # Deploy a fresh successful build, then reload only this app:
432
+ NODE_ENV=production npx warlock pm2 reload
433
+ NODE_ENV=production npx warlock pm2 reload --id 7
434
+ ```
435
+
436
+ `init` reads app/http/socket config and resolved build paths without starting
437
+ connectors, bootstrap or prestart. It creates `ecosystem.config.cjs` exclusively;
438
+ an existing file is an error. It works without PM2 installed or a prior build.
439
+ The file runs the built entry directly, with an absolute `cwd`, Node interpreter
440
+ and source maps matching `build.sourcemap`.
441
+
442
+ The identity is `<project>-<app>-<NODE_ENV>`; project defaults to `package.json`
443
+ name (`@team/store` → `team-store`), app to `app.appName` or `app`, environment
444
+ to `NODE_ENV` or `development`. Use `--project store --app api` to override the
445
+ components, and repeat those overrides on reload. Components accept letters,
446
+ digits, `_` and `-`, starting with a letter/digit. Choose a combination unique
447
+ within the daemon; no patterns or broad selectors are supported.
448
+
449
+ ```bash
450
+ NODE_ENV=production npx warlock pm2 init --project store --app api \
451
+ --cluster --instances 2 --listen-timeout 60000
452
+ NODE_ENV=production npx warlock pm2 reload --project store --app api
453
+ ```
454
+
455
+ Defaults: `exec_mode: "fork"`, `instances: 1`, `wait_ready: true`,
456
+ `listen_timeout: 30000`. `--cluster` opts into two workers by default;
457
+ `--instances` accepts a positive integer or `max`, only with `--cluster`.
458
+ Fork reload falls back to restart and may interrupt traffic. PM2 cluster reload
459
+ can replace workers while others serve, but a boot/readiness timeout can still
460
+ cause downtime. Keep both the PM2 daemon and app on Node 22.15+; cluster uses
461
+ the daemon's runtime.
462
+
463
+ `kill_timeout` equals the existing overall connector budget,
464
+ `app.shutdownTimeout` (default 10000ms), now typed on `AppConfigurations`:
465
+
466
+ ```ts
467
+ import type { AppConfigurations } from "@warlock.js/core";
468
+
469
+ export default {
470
+ appName: "api",
471
+ shutdownTimeout: 20000,
472
+ } satisfies AppConfigurations;
473
+ ```
474
+
475
+ Keep `http.gracefulShutdown.timeout` lower (e.g. 10000ms), with room for other
476
+ connectors, hooks and log flushes. PM2's kill timer covers the whole exit;
477
+ core's connector timer starts after app hooks and finishes before log flushing.
478
+ The generated comment and CLI output show the configured readiness URL (or an
479
+ unavailable notice for no HTTP, disabled health or ephemeral ports). PM2 itself
480
+ does not poll it. Use an external deployment probe.
481
+
482
+ Core emits `process.send("ready")` once at completed boot when PM2's `pm_id`
483
+ and IPC are present, after HTTP and the connectors have started. It never
484
+ disconnects PM2's channel. Socket-only apps also await the listener and reject
485
+ bind failures before completing boot. Rebuild with core 5.33+ before using `wait_ready`.
486
+ `listen_timeout` limits waiting for readiness; `kill_timeout` limits shutdown
487
+ before PM2's forced kill. Core handles PM2's default `SIGINT` gracefully.
488
+
489
+ With sockets enabled, cluster generation/reload refuse a missing
490
+ `socket.adapter` factory and require `socket.options.transports: ["websocket"]`
491
+ with WebSocket-only clients. The adapter handles broadcasts; polling still
492
+ needs sticky routing to each worker, which this generator does not install.
493
+ For polling, use individual fork listeners behind a sticky proxy or a custom
494
+ cluster primary. See the `wire-socket` topic. Avoid duplicating worker jobs
495
+ and in-memory sessions across HTTP workers.
496
+
497
+ `reload` requires a successful build marker, reads `pm2 jlist`, verifies **all**
498
+ exact-name matches against the app's absolute cwd and entry, then calls
499
+ `pm2 reload <numeric-id>` sequentially. `--id` narrows to one verified worker.
500
+ Foreign cwd/script, invalid selectors, offline cluster workers or cluster
501
+ workers without `wait_ready` are refused before reload. Reload uses settings
502
+ already registered in PM2; regenerate/reapply the ecosystem when they change.
503
+
504
+ On shared servers, inspect the current PM2 id and path before stopping:
505
+
506
+ ```bash
507
+ pm2 list
508
+ pm2 show 7
509
+ pm2 stop 7
510
+ ```
511
+
512
+ Repeat for each intended worker id. Never stop by pattern or broad name, and
513
+ avoid daemon-wide operations. PM2 ids are not OS PIDs and can change after an
514
+ app is deleted/recreated.
515
+
516
+ To put logger entries in PM2's captured stdout, configure
517
+ `src/config/log.ts` with `production: { channels: [new ConsoleLog()] }`
518
+ (`ConsoleLog` comes from `@warlock.js/logger`). Keep `time: false` and omit
519
+ `log_date_format` to preserve the logger's timestamp/module/action format.
520
+ Generated `merge_logs: false` keeps worker logs separate. PM2 JSON output wraps
521
+ captured text; it does not recover logger fields. Use `JSONFileLog` with a
522
+ distinct per-worker storage path for structured persistent entries.
523
+
524
+ Official references: [PM2 readiness/shutdown](https://pm2.keymetrics.io/docs/usage/signals-clean-restart/),
525
+ [cluster reload](https://pm2.keymetrics.io/docs/usage/cluster-mode/),
526
+ [Socket.IO multiple nodes](https://socket.io/docs/v4/using-multiple-nodes/).
527
+ Full docs: `docs/src/content/docs/v/latest/core/cli/pm2.mdx`.
528
+
420
529
  ## Runtimes — Node, Bun, Deno
421
530
 
422
531
  | Command | Node.js 22.15+ | Bun 1.4.3 | Deno 2.9.7 |
@@ -0,0 +1,141 @@
1
+ ---
2
+ name: store-secrets
3
+ 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)."
4
+ ---
5
+
6
+ # Application secret store
7
+
8
+ Use core's facade; there is no separate package or preparation step:
9
+
10
+ ```ts
11
+ import { secrets, SecretStoreError } from "@warlock.js/core";
12
+ // The same API is available from "@warlock.js/core/secrets".
13
+
14
+ await secrets.set("vendor-token", "provider-credential");
15
+ const token = await secrets.get("vendor-token"); // string | undefined
16
+ const exists = await secrets.has("vendor-token"); // boolean; true for ""
17
+ const removed = await secrets.remove("vendor-token"); // boolean
18
+ const variable = secrets.environmentName("vendor-token"); // variable name, not value
19
+
20
+ try {
21
+ await secrets.remove("vendor-token");
22
+ } catch (error) {
23
+ if (error instanceof SecretStoreError && error.code === "READ_ONLY") {
24
+ // Change the deployment environment instead.
25
+ } else {
26
+ throw error;
27
+ }
28
+ }
29
+ ```
30
+
31
+ 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()`.
32
+
33
+ ## Configuration and backend decisions
34
+
35
+ - 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.
36
+ - Reads: OS keychain → exact scoped environment variable → encrypted file.
37
+ - 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.
38
+ - 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.
39
+ - 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.
40
+ - 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.
41
+ - 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.
42
+ - 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).
43
+ - 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.
44
+
45
+ ```ts title="src/config/secrets.ts"
46
+ import type { SecretsConfigurations } from "@warlock.js/core";
47
+
48
+ export default {
49
+ namespace: "sample-app-production",
50
+ // directory: "/srv/sample-app/private-secrets",
51
+ } satisfies SecretsConfigurations;
52
+ ```
53
+
54
+ 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.
55
+
56
+ ## Isolated tests and explicit stores
57
+
58
+ `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.
59
+
60
+ ```ts
61
+ import { secrets } from "@warlock.js/core";
62
+ import { secretsFake } from "@warlock.js/core/tests";
63
+
64
+ secretsFake.install(); // only needed without the standard test harness
65
+ try {
66
+ secretsFake.clear();
67
+ await secrets.set("vendor-token", "test-credential");
68
+ await secretsFake.assertHas("vendor-token");
69
+ await secretsFake.assertStored("vendor-token", "test-credential");
70
+ await secrets.remove("vendor-token");
71
+ await secretsFake.assertMissing("vendor-token");
72
+ const installed = secretsFake.isInstalled();
73
+ } finally {
74
+ secretsFake.restore();
75
+ }
76
+ ```
77
+
78
+ For standalone unit tests or distinct app instances:
79
+
80
+ ```ts
81
+ import { MemorySecretBackend, SecretStore } from "@warlock.js/core/secrets";
82
+ import type { SecretBackend, SecretStoreOptions } from "@warlock.js/core/secrets";
83
+
84
+ const backend: SecretBackend = new MemorySecretBackend();
85
+ const options: SecretStoreOptions = { namespace: "test-app", backend };
86
+ const store = new SecretStore(options);
87
+ await store.set("vendor-token", "test-credential");
88
+ await store.get("vendor-token");
89
+ await store.has("vendor-token");
90
+ await store.remove("vendor-token");
91
+
92
+ const memory = new MemorySecretBackend();
93
+ memory.clear();
94
+ ```
95
+
96
+ 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.
97
+
98
+ ## Error and logging contract
99
+
100
+ `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.
101
+
102
+ 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.
103
+
104
+ ## Built-app tests with the memory driver
105
+
106
+ Run the built server with `SECRETS_DRIVER=memory`, or select the driver in
107
+ `src/config/secrets.ts`. Explicit config takes precedence over the environment;
108
+ `"auto"` (the default) keeps keychain/environment/file behavior. Unknown driver
109
+ names fail with `SecretStoreError.code === "INVALID_DRIVER"`.
110
+
111
+ ```ts title="src/config/secrets.ts"
112
+ import type { SecretsConfigurations } from "@warlock.js/core";
113
+
114
+ export default {
115
+ namespace: "browser-test-server",
116
+ driver: "memory",
117
+ allowMemoryInProduction: true,
118
+ } satisfies SecretsConfigurations;
119
+ ```
120
+
121
+ `allowMemoryInProduction: true` is required only when the test server runs with
122
+ `NODE_ENV=production`. Without it, memory selection fails closed with
123
+ `MEMORY_IN_PRODUCTION`; a built bundle does not bypass this check. Set this
124
+ option explicitly in the test deployment configuration. `SecretStore` accepts
125
+ the same `driver` and `allowMemoryInProduction` options for standalone stores.
126
+
127
+ The selected memory driver bypasses keychain, scoped environment secrets and
128
+ files, and still registers values for log masking. Values are isolated by app
129
+ namespace, lost on process exit, and never shared across workers or with a
130
+ separate browser-test process. Seed fixtures through application startup or a
131
+ test-only endpoint in the server process using the normal `secrets.set()` API:
132
+
133
+ ```ts
134
+ import { secrets } from "@warlock.js/core";
135
+
136
+ await secrets.set("vendor-token", "browser-test-credential");
137
+ const token = await secrets.get("vendor-token");
138
+ ```
139
+
140
+ The normal test harness still installs `secretsFake`; explicit `backend`
141
+ instances and that fake continue to bypass configured backend selection.