Skip to content

Warlock.js 5.39.0

Choose a tag to compare

@hassanzohdy hassanzohdy released this 26 Oct 17:10
· 87 commits to main since this release

Released 2026-10-26.

Keep every family package (@warlock.js/* and create-warlock) on the same version.
There are no breaking changes in this release. Two changes alter behaviour without a
type error; review them before upgrading (section 1).


1. Breaking changes

  • None. 5.39.0 has no breaking changes.

Behaviour changes to review

  • --version and -v print the Warlock version only when no command is given. A
    command can take its own --version option (core).
  • Core-level refusals on page hosts now render the site's page for HTML requests
    (core, web). A GET or HEAD request that names text/html in Accept gets the
    page for a refused path such as /public/../x (403) or a malformed URL (400). API clients
    keep the JSON answer.

2. Standalone builds and fewer files (core, web)

  • Single bundle for web pages. build.singleBundle now works with web pages. The server
    pages and routes are inlined into one dist/app.js, and node dist/app.js starts from a
    copy of dist/ with no node_modules above it. A copy of dist/ also works from any
    working directory, because its client folder is found next to the running bundle.

  • Native addons are staged automatically. A package with a .node binary stays external
    and is copied into dist/node_modules/<package> with only what it needs to load. The build
    report names each one.

  • Optional integrations are detected. A lazy import() or require() of an optional peer
    or optional dependency that a framework package lists, and the app does not declare, stays a
    lazy runtime import that never runs. The build report names it, so no hand-written
    external list is needed. An import the app makes, or a package the app declares, is still
    bundled or fails the build.

  • Build-time tools are left out. Vite, the development server, the page scanner and the
    Babel parser are not in the production bundle, and the server never loads Vite. The native
    parser oxc-parser is replaced by a module that throws if it is ever called.

  • Production React. A singleBundle build sets process.env.NODE_ENV to "production"
    inside react, react-dom and scheduler only, so React's development build is dropped.
    A process.env.NODE_ENV entry in build.define turns this off.

  • Client build options. webConnector({ build: { chunks, precompress } }) controls the
    client output:

    • chunks: "split" (the default) writes a React vendor chunk, the entry, a lazy chunk per
      page and shared chunks.
    • chunks: "merged" keeps the same lazy page loading and packs small shared chunks together
      by size, which gives far fewer files.
    • chunks: "single" writes one JavaScript entry and one stylesheet, with dynamic imports
      inlined. It is for single-site apps only.
    • precompress: false skips the .br and .gz copies of each client asset (default true).

    Final-bytes chunk names, dedupe, the integrity check, preload lists and payload budgets work
    in every mode.

  • Desktop and offline recipe. The desktop and offline app recipe sets both chunks and
    precompress. build.singleBundle alone does not change them.

  • Dist file summary. warlock build prints the number of files and the total size of
    dist/ when it finishes.


3. Release notes in every install (core)

  • warlock release-notes prints the family release summaries that ship inside the package
    (releases/<version>-summary.md, from 5.30.0 onward). It reads files only, so it needs no
    network and no booted app.
    • With no arguments it prints the installed version's summary.
    • --from <version> (optionally with --to <version>) prints every later release, oldest
      first, led by a combined list of breaking changes.
    • --version <v> prints one release, --list lists the versions with dates, and --json
      prints the same data as JSON.
    • A version the package does not ship is reported with its GitHub release URL.
  • Pointers from upgrade and update. warlock upgrade, warlock upgrade --check and
    warlock update end with What's new: npx warlock release-notes --from <old>. The AGENTS.md
    section that warlock agents-md generates names the installed version and the command.
  • Agent skills. The update-packages, deploy-release and upgrade-from-5-29 skills tell
    agents to run npx warlock release-notes --from <current> before upgrading.

4. Tests and tooling (core, web)

  • Config files no longer walk the app graph. Loading config registered each config file
    together with its whole import graph, so a config that imported an application service made
    every test worker spend minutes in setup (about 480 seconds for a 3,000-module graph, measured).
    Config files now register with their direct import edges only. The dev server still builds the
    full graph from its own scan.
  • onPageError(hook) (web, from @warlock.js/web/connector) registers a server
    error-report hook at runtime, from a module's main.ts. It receives the same
    (error, context) as web.errors.report, runs through the same bounded queue, and returns a
    function that removes it. Use it instead of importing application code into src/config/web.ts.
    The types PageErrorHook, PageErrorInfo and PageErrorKind are exported.
  • warlock doctor check config-app-imports warns when a file under src/config imports,
    statically or with import(), anything under src/app. The message names the file and the
    import. Type-only imports are not reported.
  • warlock upgrade --check npm fix. npm 10.9 crashed on the family's lockfile with
    Cannot read properties of null (reading 'edgesOut'), which skipped every later step. When the
    npm on PATH is older than 11.6.2, the scratch install now runs npx -y npm@11.6.2, and --from
    is inferred from the installed @warlock.js/core.
  • WL9024 text. WL9024 applies to any integration spec: a *.spec.ts or *.test.ts file
    that is not *.unit.*, or any file under tests/. Its fix text now says to rename the file to
    <name>.unit.spec.ts outside tests/. The 5.38.0 note said *.test.ts to *.spec.ts, which
    was wrong.

5. Models (cascade)

  • System columns in typed get() and set(). On a model declared with a schema type, these
    columns are accepted without being declared: id (number), _id, created_at and
    updated_at (Date), deleted_at (Date | null), and their camelCase spellings. A column the
    schema declares keeps its own type, so a renamed timestamp or a string id belongs in the schema.
  • Clearer set() errors. A wrong value type is reported as a value mismatch, not as a
    misleading error about dot paths.
  • Typed runtime key. A column named at runtime is set with a key typed
    K extends ModelKey<Schema>. The new ModelSystemColumns and ModelColumns types are exported.

6. Web UX (web)

  • Refusal pages. For a GET or HEAD request that names text/html in Accept, a 401 or
    403 renders the site's forbidden.page.tsx. Any other refusal renders error.page.tsx, and
    then the framework fallback page, with the refused status. In multi-site mode the site is
    chosen by host, as for any page. API clients keep the JSON answer.

7. New apps (create-warlock)

  • Installs on npm 10.9. A fresh scaffold no longer crashes on npm 10.9 (the npm bundled with
    Node 22) with Cannot read properties of null (reading 'edgesOut'). The template pins Vitest
    5.0.5, because the Vitest releases before it trigger that npm bug on their own. The edgesOut
    hint stays as a last resort.

8. Upgrade notes

  1. Rehearse first. Run npx warlock upgrade --check. It runs the upgrade on a scratch copy and
    never writes the real project. It reports the breaking changes that affect the app and the
    outcome of each step.
  2. Update the family. Run npx warlock update. It ends with a pointer to the release notes.
  3. Read what changed. Run npx warlock release-notes --from 5.38.0 to print the summaries of
    every release after 5.38.0, with the combined breaking-changes list first.
  4. Review the two behaviour changes in section 1: --version and -v with a command, and
    browser requests refused by core on a page host.
  5. Opt in to the standalone build (optional). Set build: { singleBundle: true } in the app's
    warlock.config.ts, then run npx warlock build. Read the build report for the native addons,
    optional integrations and React notes, and check the file count and size that the build prints.
    Test the output by running node dist/app.js from a copy of dist/.
  6. Choose the client chunk mode (optional). Set webConnector({ build: { chunks, precompress } })
    in the web connector to pick "split", "merged" or "single" chunks, and whether to write
    compressed copies. For a desktop or offline app, follow the desktop and offline recipe, which sets
    both options.
  7. Use onPageError for error reporting (optional). If src/config/web.ts imports application
    code only to report errors, register the hook with onPageError(hook) from main.ts instead and
    remove the import. Run warlock doctor to check for config-app-imports warnings.
  8. Rename integration specs (if flagged). A WL9024 warning names an integration spec that
    imports nothing needing the app. Rename it to <name>.unit.spec.ts outside tests/.
  9. Update typed model code (if needed). Columns that the schema does not declare are now
    accepted only when they are system columns (section 5). Other columns still need a schema entry or a dot path.