Skip to content

Warlock.js 5.37.0

Choose a tag to compare

@hassanzohdy hassanzohdy released this 24 Oct 11:25
· 187 commits to main since this release

1. Breaking changes

  • Document-store rollbacks no longer erase fields by default (cascade). On MongoDB,
    a plain dropColumn() or dropColumns() inside down() is skipped with a warning
    instead of running $unset on every document. Forward drops (up(), drop: [...])
    are unchanged. To erase the data on purpose, pass { destructive: true }.
  • Add-only alters derive their rollback (cascade). A Migration.alter that only adds
    columns or fields no longer needs a hand-written down(). Delete that down() so the
    rollback is derived; on SQL it drops the added columns, on document stores it only
    updates the ledger.
  • Rollback and fresh migrations ask first (core, cascade). warlock migrate --rollback,
    warlock migrate --fresh, cascade migrate:rollback and cascade migrate --fresh print a
    destructive-operation summary. Outside development (Warlock's environment for warlock migrate;
    NODE_ENV other than development for the cascade CLI), a rollback that removes data asks for confirmation and
    refuses without a terminal. Scripts and CI jobs must pass --yes (or --force). A rollback
    that removes no data does not ask. Programmatic rollbackMigrations() and freshMigrate()
    do not prompt; call confirmRollback() first to get the same guard.

No codemod is needed for 5.37.0. The changes above are manual: review the add-only
down() methods, pass { destructive: true } where erasing data is intended, and add
--yes to deploy scripts.


2. Named API route table controls and guard detection

  • web.namedApiRoutes controls what each page embeds. false embeds no named routes, so
    a named-route call throws (in development the error names the option; explicit path and
    method still work). An allowlist of route names with prefix.* patterns, or a filter
    function (route, { site, guest }), narrows the table. { include, privatePrefixes }
    combines both. Every form can only remove routes from what the page's client code reaches.
  • privatePrefixes keeps every route under the listed path prefixes (for example
    /api/admin/ or /api/webhooks/) out of every table, for any viewer and any site.
    Prefixes match whole path segments.
  • Per-site routes. The sites option on a route, or on router.group({ sites }), names
    the multi-site keys that may offer it in the browser. A route with no sites is shared by
    every site, and a route's own value replaces its group's.
  • Guarded routes for guests. A route with an auth guard in its middleware chain (group
    middleware included) is left out of the table for a guest. A viewer counts as a guest only
    when web.session is configured and resolves no user. Without web.session the viewer is
    unknown, and guarded routes stay in the table.
  • Guard detection in route inspection. warlock inspect and the generated AGENTS.md
    list of mutating routes without an auth guard now count middleware that declares itself a
    guard with markAuthGuard(), including one registered on a router.group. warlock check
    (WL9006) handles inline markAuthGuard(fn, false) and local guard factories used as
    middleware: [requireAdmin()].
  • Types and seams. The new option types are exported from web. The server seam
    resolveNamedApiRoutes(names, options) takes { config, site, guest }.

3. Safer migrations on document stores

  • Rollback plan. migrationRunner.planRollback(batches) lists, per migration, the
    collections or tables dropped or truncated and the fields or columns dropped or unset,
    with document and row counts. The counts come from new optional countRows and
    countWithColumns driver methods (MongoDB; countRows on Postgres).
  • Confirmation. confirmRollback({ all, batches, yes, development }) prints that plan and
    asks outside development. The CLI rollback and fresh commands accept --yes / -y and --force.
  • Guards in hand-written down(). Migration#isDocumentStore lets a migration skip a
    SQL-only drop on a document store.

4. warlock release for application deploys

warlock release is a deploy pipeline for applications, configured by a new release
section in warlock.config.ts:

  • candidate freezes a clean commit into .warlock/releases/<version>-<shortsha>/ with
    a manifest (version, commit, time, Node version, lockfile hash).
  • gate runs the named checks in order (default: typecheck, check, test, build) and
    stops at the first failure.
  • migrations --prove runs the pending migrations for real and checks the ledger, tables
    and indexes against the expected set.
  • stage, then activate checks the staged files, the gate and that the version is
    newer than the active one, switches current with an atomic symlink, keeps previous and
    runs the restart command you configure. Warlock never calls a process manager itself.
  • verify checks the version and commit reported at release.verify.url. rollback
    switches back and reruns the restart command; it does not roll migrations back.
  • list, status and prune --keep <n> (the only command that deletes releases).

Every action accepts --json, and each release keeps a manifest plus a shared release.log.


5. Eval repeats and variance-aware gating (ai)

  • agent.eval({ repeat }) runs every case several times (default 1). Results carry pass
    counts, per-run results and score and pass-rate intervals. Reports add overall and
    per-category statistics (EvalCase.category), plus stableFailures and flakyCases.
    toJUnit shows passed 2/3 runs.
  • gate: "interval" flags a regression only when the baseline interval lies above the
    current one by more than tolerance, so run-to-run noise no longer reads as a regression.
    The default gate: "point" is unchanged. failOnStableFailures fails the verdict when a
    case failed every run.
  • diff takes { tolerance, gate, confidence, failOnStableFailures } as its third
    argument; a plain tolerance number still works. EvalRegression adds regressedMetrics,
    stableFailures and flaky.
  • Baselines store the repeat count and intervals. Baselines saved by earlier versions
    still load, and each of their cases counts as one sample.

6. Fixes and docs

  • Validation messages (core). The default validation translation no longer
    returns a message with an unfilled :placeholder. The aliases :length and
    :matchingInput are mapped, and a translation that still cannot be filled falls back to
    seal's message. In development, the first unfillable validation.* key logs a warning.
    App overrides keep working.
  • Test processes exit (logger). FileLog's background flush timer no longer keeps the
    process alive, so finished Vitest runs and CLI commands stop waiting on it. flush() and
    flushSync() still write buffered entries.
  • Stylesheet imports in page discovery (core). The dev loader answers stylesheet imports
    (.css, .scss, .sass, .less, .styl) with an empty module, so sitemap discovery no
    longer fails with ERR_UNKNOWN_FILE_EXTENSION on a page that imports one.
  • Hydration mismatch hint (web). In development, a React hydration mismatch logs the
    first mismatching node: its element path, the server text and the client text. Production
    builds are unchanged.
  • Intl guidance (docs). The create-a-page and localize-pages skills and the
    localization and client/server boundary docs warn that Intl.DisplayNames,
    localeCompare sorting and similar Intl output can differ between Node and browsers and
    cause hydration mismatches. They recommend a shared pre-computed table or rendering after
    hydration.

7. Verification

Checks ran on Node 22.15, each through a memory- and CPU-capped runner:

  • Typecheck and the strictness gate: clean.
  • Full test suite: core 4,552 passed, web 3,411 passed, the other packages 5,682
    passed, no failures. A new spec spawns Vitest on a fixture app in both test
    lifetimes and asserts the process exits on its own.
  • create-warlock standalone, release-tool and workspace script tests: passed.
  • Skill snippet, docs build, format, lint, public text, spec coverage, MDX and skill
    reference checks: clean.

The publish run repeats the full family gate (typecheck, tests, build, pack,
local-registry install and the blog app) before anything reaches npm.