@warlock.js/core 5.36.0 → 5.37.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 +28 -0
- package/esm/cli/commands/migrate.command.mjs +10 -0
- package/esm/cli/commands/migrate.command.mjs.map +1 -1
- package/esm/cli/commands/release/release-command.action.mjs +245 -0
- package/esm/cli/commands/release/release-command.action.mjs.map +1 -0
- package/esm/cli/commands/release.command.mjs +66 -0
- package/esm/cli/commands/release.command.mjs.map +1 -0
- package/esm/cli/framework-cli-commands.mjs +2 -0
- package/esm/cli/framework-cli-commands.mjs.map +1 -1
- package/esm/database/migrate-action.mjs +24 -2
- package/esm/database/migrate-action.mjs.map +1 -1
- package/esm/dev-server/loader/load-hook.d.mts.map +1 -1
- package/esm/dev-server/loader/load-hook.mjs +6 -0
- package/esm/dev-server/loader/load-hook.mjs.map +1 -1
- package/esm/index.d.mts +3 -2
- package/esm/index.mjs +2 -2
- package/esm/production/route-registration-snapshot.mjs +10 -3
- package/esm/production/route-registration-snapshot.mjs.map +1 -1
- package/esm/release/activate.mjs +261 -0
- package/esm/release/activate.mjs.map +1 -0
- package/esm/release/candidate.mjs +107 -0
- package/esm/release/candidate.mjs.map +1 -0
- package/esm/release/cascade-migration-provider.mjs +32 -0
- package/esm/release/cascade-migration-provider.mjs.map +1 -0
- package/esm/release/config.mjs +60 -0
- package/esm/release/config.mjs.map +1 -0
- package/esm/release/gate.mjs +67 -0
- package/esm/release/gate.mjs.map +1 -0
- package/esm/release/migrations-proof.mjs +100 -0
- package/esm/release/migrations-proof.mjs.map +1 -0
- package/esm/release/prune.mjs +42 -0
- package/esm/release/prune.mjs.map +1 -0
- package/esm/release/report.mjs +25 -0
- package/esm/release/report.mjs.map +1 -0
- package/esm/release/runner.mjs +55 -0
- package/esm/release/runner.mjs.map +1 -0
- package/esm/release/semver.mjs +53 -0
- package/esm/release/semver.mjs.map +1 -0
- package/esm/release/stage.mjs +122 -0
- package/esm/release/stage.mjs.map +1 -0
- package/esm/release/store.mjs +131 -0
- package/esm/release/store.mjs.map +1 -0
- package/esm/release/types.d.mts +102 -0
- package/esm/release/types.d.mts.map +1 -0
- package/esm/release/verify.mjs +94 -0
- package/esm/release/verify.mjs.map +1 -0
- package/esm/router/index.d.mts +2 -2
- package/esm/router/index.mjs +2 -2
- package/esm/router/router.d.mts +4 -2
- package/esm/router/router.d.mts.map +1 -1
- package/esm/router/router.mjs +13 -5
- package/esm/router/router.mjs.map +1 -1
- package/esm/router/types.d.mts +26 -0
- package/esm/router/types.d.mts.map +1 -1
- package/esm/router/unguarded-route-diagnostics.d.mts +6 -0
- package/esm/router/unguarded-route-diagnostics.d.mts.map +1 -1
- package/esm/router/unguarded-route-diagnostics.mjs +10 -2
- package/esm/router/unguarded-route-diagnostics.mjs.map +1 -1
- package/esm/tooling/check/security-conventions.mjs +19 -1
- package/esm/tooling/check/security-conventions.mjs.map +1 -1
- package/esm/tooling/inspect/collect-routes.mjs +3 -2
- package/esm/tooling/inspect/collect-routes.mjs.map +1 -1
- package/esm/validation/init.mjs +11 -4
- package/esm/validation/init.mjs.map +1 -1
- package/esm/validation/translate-rule.mjs +63 -0
- package/esm/validation/translate-rule.mjs.map +1 -0
- package/esm/warlock-config/index.d.mts +2 -1
- package/esm/warlock-config/types.d.mts +8 -0
- package/esm/warlock-config/types.d.mts.map +1 -1
- package/llms-full.txt +94 -2
- package/llms.txt +1 -0
- package/package.json +13 -13
- package/skills/deploy-release/SKILL.md +76 -0
- package/skills/index.md +1 -1
- package/skills/register-route/SKILL.md +12 -0
- package/skills/warlock-conventions/SKILL.md +1 -1
package/llms.txt
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
- [create-controller](@warlock.js/core/skills/create-controller/SKILL.md): Author HTTP controllers in @warlock.js/core — RequestHandler signature, validated input via seal schemas, response helpers, attaching metadata. Controllers are thin functions; business logic moves to services or use-cases. Triggers: `RequestHandler`, `Request<TSchema>`, `GuardedRequestHandler`, `request.validated`, `request.signal`, `request.input`, `controller.validation`, `controller.responseSchema`, `response.success`, `response.successCreate`; "write a controller", "attach a schema to a handler", "thin controller pattern", "guarded request type", "type the API response"; typical import `import { type RequestHandler } from "@warlock.js/core"`. Skip: response helper menu — the `send-response` topic; schema authoring — the `validate-input` topic; URL wiring — the `register-route` topic; competing patterns: `express` middleware functions, `@nestjs/common` `@Controller`/`@Get` decorators.
|
|
17
17
|
- [create-module](@warlock.js/core/skills/create-module/SKILL.md): Scaffold a new feature module under `src/app/<name>/` via `warlock generate.module` and the follow-up generators for controllers, models, repositories, resources, and validation schemas. Triggers: `warlock generate.module`, `generate.controller`, `generate.service`, `generate.model`, `generate.repository`, `generate.resource`, `generate.migration`, `--minimal`, `gen.m`; "scaffold a new module", "create CRUD bootstrap", "add a controller to a module", "generate a model"; typical CLI `npx warlock generate.module <name>`. Skip: framework-wide layout rules — the `warlock-conventions` topic; routes file shape — the `register-route` topic; controller shape — the `create-controller` topic; competing tooling: `@nestjs/cli`, `hygen`, hand-rolled folder layouts.
|
|
18
18
|
- [define-resource](@warlock.js/core/skills/define-resource/SKILL.md): Map model fields to wire-shape via `defineResource()` or `Resource` subclasses. Output-only — never put business logic, hydration, or reconciliation in a resource. Also covers the typed output: `ResourceOutput<typeof R>`, `defineResource<Out>()`, `Serialized<T, W>` and `ModelResourceRegistry`. Triggers: `defineResource`, `Resource`, `RegisterResource`, `toJSON`, `ResourceOutput`, `Serialized`, `ModelResourceRegistry`, `"self"`, `"localized"`, `"uploadsUrl"`; "shape an API response", "type a resource's output", "what does the client receive", "nest related resources", "rename a field on output", "self-referential tree resource"; typical import `import { defineResource } from "@warlock.js/core"`. Skip: localized columns — the `use-localization` topic; URL casting — the `build-url` topic; controller side — the `create-controller` topic; competing libs `@nestjs/swagger` `@ApiProperty`, `class-transformer`, hand-rolled DTO mappers.
|
|
19
|
+
- [deploy-release](@warlock.js/core/skills/deploy-release/SKILL.md): Deploy a Warlock application with `warlock release` — freeze a clean commit as a candidate, run the gate, prove migrations, stage the build, switch `current` by atomic symlink, verify the health URL and roll back, configured by the `release` section of `warlock.config.ts`. Triggers: `warlock release`, `release candidate`, `release gate`, `release migrations --prove`, `release stage`, `release activate`, `release verify`, `release rollback`, `release prune`, `ReleaseConfig`, `release.restart.command`, `release.verify`, `.warlock/releases`; "deploy my app", "zero-downtime deploy", "roll back a deploy", "atomic release", "release gate", "prove migrations before deploy". Skip: the `/health` and `/ready` endpoints themselves — the `health-checks` topic; generating a PM2 ecosystem file — `warlock pm2` (the `run-app` topic); publishing the framework packages — `releasing-warlock-monorepo`; competing tools Capistrano, Deployer, hand-rolled symlink scripts.
|
|
19
20
|
- [encrypt-data](@warlock.js/core/skills/encrypt-data/SKILL.md): Reversible AES-256-GCM `encrypt` / `decrypt` for secrets you need to read back; one-way HMAC-SHA256 `hmacHash` for deterministic fingerprints (lookup/dedup of encrypted columns). Keys come from `src/config/encryption.ts`. Triggers: `encrypt`, `decrypt`, `hmacHash`, `EncryptionConfigurations`, `APP_ENCRYPTION_KEY`, `APP_HMAC_KEY`; "store an API key reversibly", "fingerprint an encrypted column for lookup", "AES-256-GCM secret", "HMAC-SHA256 dedup key"; typical import `import { encrypt, decrypt, hmacHash } from "@warlock.js/core"`. Skip: password hashing — the `hash-password` topic; config wiring — the `configure-app` topic; competing libs Node `crypto` direct, `crypto-js`, `libsodium-wrappers`.
|
|
20
21
|
- [generate-openapi](@warlock.js/core/skills/generate-openapi/SKILL.md): Generate an OpenAPI 3.1 document from the registered routes with `warlock generate.openapi` (default `storage/openapi/openapi.json`), write a Postman collection from it with `warlock generate.postman` (default `storage/postman/collection.json`), and read it live in the dev-only API docs page at `/__warlock/docs`. Documents paths, path/query parameters, request bodies from Seal validation, responses from `handler.responseSchema`, 401/422 and bearer/cookie security from `authMiddleware()`. Triggers: `generate.openapi`, `openApiGeneratorCommand`, `buildOpenApiDocument`, `getDevelopmentOpenApiDocument`, `generate.postman`, `openApiToPostmanCollection`, `getDevelopmentPostmanCollection`, `responseSchema`, `--include-pages`, `/__warlock/docs`; "OpenAPI", "Swagger", "API docs", "import my API into Postman or Insomnia", "document my routes". Skip: declaring response types on a handler — the `create-controller` topic; the devtools dashboard — the `warlock-js-devtools` skill; listing routes — the `warlock-routes` topic.
|
|
21
22
|
- [generate-web-ui](@warlock.js/core/skills/generate-web-ui/SKILL.md): Generate web pages, schema-backed forms and CRUD UI with warlock generate page, form and crud-ui. Includes setup companions, design-system controls, useSubmitForm, optional guarded endpoints and module CRUD prerequisites.
|
package/package.json
CHANGED
|
@@ -25,12 +25,12 @@
|
|
|
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.
|
|
29
|
-
"@warlock.js/cascade": "5.
|
|
30
|
-
"@warlock.js/context": "5.
|
|
31
|
-
"@warlock.js/fs": "5.
|
|
32
|
-
"@warlock.js/logger": "5.
|
|
33
|
-
"@warlock.js/seal": "5.
|
|
28
|
+
"@warlock.js/cache": "5.37.0",
|
|
29
|
+
"@warlock.js/cascade": "5.37.0",
|
|
30
|
+
"@warlock.js/context": "5.37.0",
|
|
31
|
+
"@warlock.js/fs": "5.37.0",
|
|
32
|
+
"@warlock.js/logger": "5.37.0",
|
|
33
|
+
"@warlock.js/seal": "5.37.0",
|
|
34
34
|
"bcryptjs": "^3.0.3",
|
|
35
35
|
"chokidar": "^5.0.0",
|
|
36
36
|
"dayjs": "^1.11.19",
|
|
@@ -56,12 +56,12 @@
|
|
|
56
56
|
"@aws-sdk/lib-storage": "^3.955.0",
|
|
57
57
|
"@aws-sdk/s3-request-presigner": "^3.955.0",
|
|
58
58
|
"@react-email/render": "^2.0.5",
|
|
59
|
-
"@warlock.js/access": "5.
|
|
60
|
-
"@warlock.js/ai": "5.
|
|
61
|
-
"@warlock.js/ai-tools": "5.
|
|
62
|
-
"@warlock.js/devtools": "5.
|
|
63
|
-
"@warlock.js/herald": "5.
|
|
64
|
-
"@warlock.js/notifications": "5.
|
|
59
|
+
"@warlock.js/access": "5.37.0",
|
|
60
|
+
"@warlock.js/ai": "5.37.0",
|
|
61
|
+
"@warlock.js/ai-tools": "5.37.0",
|
|
62
|
+
"@warlock.js/devtools": "5.37.0",
|
|
63
|
+
"@warlock.js/herald": "5.37.0",
|
|
64
|
+
"@warlock.js/notifications": "5.37.0",
|
|
65
65
|
"nodemailer": "^8.0.5",
|
|
66
66
|
"react": "^19.2.3",
|
|
67
67
|
"react-dom": "^19.2.3",
|
|
@@ -146,7 +146,7 @@
|
|
|
146
146
|
"engines": {
|
|
147
147
|
"node": ">=22.15"
|
|
148
148
|
},
|
|
149
|
-
"version": "5.
|
|
149
|
+
"version": "5.37.0",
|
|
150
150
|
"type": "module",
|
|
151
151
|
"main": "./esm/index.mjs",
|
|
152
152
|
"module": "./esm/index.mjs",
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: deploy-release
|
|
3
|
+
description: 'Deploy a Warlock application with `warlock release` — freeze a clean commit as a candidate, run the gate, prove migrations, stage the build, switch `current` by atomic symlink, verify the health URL and roll back, configured by the `release` section of `warlock.config.ts`. Triggers: `warlock release`, `release candidate`, `release gate`, `release migrations --prove`, `release stage`, `release activate`, `release verify`, `release rollback`, `release prune`, `ReleaseConfig`, `release.restart.command`, `release.verify`, `.warlock/releases`; "deploy my app", "zero-downtime deploy", "roll back a deploy", "atomic release", "release gate", "prove migrations before deploy". Skip: the `/health` and `/ready` endpoints themselves — the `health-checks` topic; generating a PM2 ecosystem file — `warlock pm2` (the `run-app` topic); publishing the framework packages — `releasing-warlock-monorepo`; competing tools Capistrano, Deployer, hand-rolled symlink scripts.'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Warlock — deploy with `warlock release`
|
|
7
|
+
|
|
8
|
+
A recorded, step-by-step deploy for one host. Each step is a command; each writes into a per-release `manifest.json` and a shared `release.log`.
|
|
9
|
+
|
|
10
|
+
```text
|
|
11
|
+
candidate -> gate -> migrations --prove -> stage -> activate -> verify
|
|
12
|
+
\-> rollback
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Warlock never calls PM2, systemd or any process manager. The only restart is the command you set in `release.restart.command`.
|
|
16
|
+
|
|
17
|
+
## Configure
|
|
18
|
+
|
|
19
|
+
```ts title="warlock.config.ts"
|
|
20
|
+
import { defineConfig } from "@warlock.js/core";
|
|
21
|
+
|
|
22
|
+
export default defineConfig({
|
|
23
|
+
release: {
|
|
24
|
+
gate: {
|
|
25
|
+
checks: [
|
|
26
|
+
{ name: "typecheck", command: "npx warlock typecheck" },
|
|
27
|
+
{ name: "test", command: "npx warlock test" },
|
|
28
|
+
{ name: "build", command: "NODE_ENV=production npx warlock build" },
|
|
29
|
+
],
|
|
30
|
+
},
|
|
31
|
+
restart: { command: "pm2 reload my-app" },
|
|
32
|
+
verify: { url: "http://127.0.0.1:2030/health" },
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Defaults: `directory` `.warlock`, `distDir` `dist`, gate = typecheck, check, test, build. Add `.warlock` to `.gitignore`.
|
|
38
|
+
|
|
39
|
+
## Run the steps
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npx warlock release candidate [--commit <sha>] [--allow-dirty]
|
|
43
|
+
npx warlock release gate
|
|
44
|
+
npx warlock release migrations --prove --target staging
|
|
45
|
+
npx warlock release stage
|
|
46
|
+
npx warlock release activate --preflight
|
|
47
|
+
npx warlock release activate [--dry-run] [--allow-same]
|
|
48
|
+
npx warlock release verify || npx warlock release rollback
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Every command takes an optional release id (`release gate 1.4.0-a1b2c3d`); without one it uses the newest candidate, gated or staged release. `--json` prints the result as JSON; a failed step exits 1.
|
|
52
|
+
|
|
53
|
+
- **candidate** freezes the commit with `git archive` into `.warlock/releases/<version>-<shortsha>/source`. Refuses a dirty tree without `--allow-dirty` and never overwrites a release.
|
|
54
|
+
- **gate** runs the checks in order in the frozen source and stops at the first failure; results go into the manifest.
|
|
55
|
+
- **migrations** without `--prove` only lists the pending set. With `--prove` it snapshots the ledger, tables and indexes, runs the pending migrations, snapshots again and compares with the expected set (`release.migrations.expected`, default the pending set; `expectedTables`). The migrations really run: use `--target` (a `release.migrations.targets` entry whose `env` is applied before the database connects) on a database that is safe to migrate. A failed proof blocks activation.
|
|
56
|
+
- **stage** (needs a passing gate) copies `dist`, `package.json`, the lockfile, `release.include` entries and `release.json` into `staged/`, atomically.
|
|
57
|
+
- **activate** needs staged artefacts, a passed gate (and proof), and a version strictly greater than the active one (semver; `--allow-same` accepts an equal version, a lower one is refused). It switches `current -> releases/<id>` with an atomic rename, keeps the old target as `previous`, then runs the restart command with `WARLOCK_RELEASE_ID`, `WARLOCK_RELEASE_DIR` and `WARLOCK_RELEASE_CURRENT` set. If that command fails, the pointer stays switched and the exit code is 1.
|
|
58
|
+
- **verify** GETs `release.verify.url` with retries and a timeout and checks the status plus the `version` and `commit` fields of the JSON body against the active release (`verify.versionPath`, `commitPath` and `match` adjust that). Serve the `release.json` values from your health endpoint.
|
|
59
|
+
- **rollback** switches `current` back to `previous` (and `previous` to the release just left), reruns the restart command. It does not roll migrations back: a migration may have destroyed data no reverse step restores; use `warlock migrate --rollback` deliberately if needed.
|
|
60
|
+
- **list** and **status** show releases and the active one; **prune --keep <n>** is the only command that deletes releases and always keeps `current` and `previous`.
|
|
61
|
+
|
|
62
|
+
## Layout
|
|
63
|
+
|
|
64
|
+
```text
|
|
65
|
+
.warlock/current -> releases/<id> .warlock/previous -> releases/<id>
|
|
66
|
+
.warlock/release.log
|
|
67
|
+
.warlock/releases/<id>/{manifest.json, source/, staged/}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Run the app from `.warlock/current/staged` so a restart follows the pointer.
|
|
71
|
+
|
|
72
|
+
## Pitfalls
|
|
73
|
+
|
|
74
|
+
- Run `activate --dry-run` or `--preflight` first in CI; both change nothing.
|
|
75
|
+
- Without `restart.command` activation only moves the symlink; the running process keeps serving the old code.
|
|
76
|
+
- `verify` fails when the response does not report a version; set `verify.match` to `[]` to check only the status.
|
package/skills/index.md
CHANGED
|
@@ -22,7 +22,7 @@ When the framework is wrong or missing a capability, follow the `report-framewor
|
|
|
22
22
|
- **HTTP & routing:** register-route, warlock-routes, create-controller, use-middleware, write-middleware, validate-input, use-request-locals, build-restful, wire-socket
|
|
23
23
|
- **Responses & resources:** send-response, define-resource, use-model-transformers, use-localization
|
|
24
24
|
- **Domain layers:** write-use-case, use-repository, write-seeder
|
|
25
|
-
- **CLI & running the app:** generate-web-ui, run-app, write-cli-command, add-connector, health-checks, warlock-doctor, warlock-inspect, warlock-check, generate-openapi
|
|
25
|
+
- **CLI & running the app:** generate-web-ui, run-app, deploy-release, 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
28
|
- **Security & utilities:** hash-password, encrypt-data, store-secrets, retry-operation, benchmark-code, request-memo, request-tracing
|
|
@@ -292,3 +292,15 @@ router.post("/internal/reindex", reindexController, {
|
|
|
292
292
|
```
|
|
293
293
|
|
|
294
294
|
It is then never embedded in a page, and `warlock check` reports a browser reference to its name as `WL9020` (an error). The server still authorizes the call as for any route.
|
|
295
|
+
|
|
296
|
+
### Offer a route to some sites only — `sites`
|
|
297
|
+
|
|
298
|
+
In a multi-site app a route answers on every host, and a page of any site may embed it in its browser table. Declare the sites that may embed it on the route or on a group; a route's own `sites` replaces its group's, and a route with none is shared:
|
|
299
|
+
|
|
300
|
+
```ts
|
|
301
|
+
router.group({ prefix: "/api/admin", sites: ["admin"] }, () => {
|
|
302
|
+
router.post("/users", createUser, { name: "admin.users.create" });
|
|
303
|
+
});
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
This limits the browser table only; keep the route's guard. A guard in the route's middleware chain (group middleware included) also keeps it out of a guest's table, and a custom guard must say so with `markAuthGuard(fn)` unless its name contains `auth` or `guard`. `warlock inspect`, `warlock agents-md` and `WL9006` read the same chain, so a guard declared once on a group guards every route inside it.
|
|
@@ -186,7 +186,7 @@ router.post("/posts", createPostController, { middleware: [authMiddleware("user"
|
|
|
186
186
|
router.post("/contact", contactController, { public: true });
|
|
187
187
|
```
|
|
188
188
|
|
|
189
|
-
A route inside `guarded(() => …)` or `router.group({ middleware: [authMiddleware()] }, …)` is guarded. A middleware counts as a guard when its name contains `auth` or `guard`, or when it is a constant initialised with `markAuthGuard()` in the same file. `warlock dev` warns about the same routes at runtime.
|
|
189
|
+
A route inside `guarded(() => …)` or `router.group({ middleware: [authMiddleware()] }, …)` is guarded. A middleware counts as a guard when its name contains `auth` or `guard`, or when it is a constant initialised with `markAuthGuard()` in the same file, or a local factory that returns `markAuthGuard(fn)` and is called in the `middleware` list. `warlock dev` warns about the same routes at runtime.
|
|
190
190
|
|
|
191
191
|
### `WL9007` — safe redirects
|
|
192
192
|
|