Skip to content

docs: runtime migration plans (move quarto-cli off Deno) - #15133

Merged
cderv merged 86 commits into
mainfrom
runtime-migration-plans
Oct 20, 2026
Merged

cderv merged 86 commits into
mainfrom
runtime-migration-plans

Conversation

@cderv

@cderv cderv commented Oct 20, 2026

Copy link
Copy Markdown
Member

Description

Planning record for moving quarto-cli off Deno onto Node, as documentation only. No application code, no build change. It adds dev-docs/runtime-migration/: the decision record, the validating spike, two reviews with their triage, and one self-contained implementation plan per PR across four phases.

The work is organized as 45 PRs plus two time-boxed spikes and one upstream milestone (the Julia engine, ported by PumasAI), executed one at a time against main:

  • Phase 0, de-risk dependencies: a CI ratchet counting Deno.* call sites and direct std imports (PR 02), the Deno 2.9 bump and a committed --frozen lockfile, and swapping deno.land/x/skypack/cliffy/deno_dom dependencies for npm/jsr equivalents.
  • Phase 1, close the runtime boundary: route every Deno.* and std import through a new src/deno_ral/ layer, via mechanical codemods, while still shipping on Deno.
  • Phase 2, Node implementations, still on Deno: build the Node versions of the layer's modules behind a real-Node CI job with a shrink-only known-failures list. Two spikes settle Deno/Node compatibility and the test runner.
  • Phase 3, flip then remove Deno: switch the CLI launchers to Node (with a one-cycle dual-runtime window if the spike passes), move user scripts and build-ts-extension, then stop downloading Deno.

README.md is the execution index (the ordered PR table, dependency graph, ground rules, roles, release calendar). HANDOFF.md is the start-here for a fresh session. Every plan carries its own spec, files, checkbox tasks, verification commands, risks, and a suggested executor.

Merging this first puts the plans on main so each implementation PR can branch from main and reference them. The first plan to execute is PR 02 (the runtime-boundary ratchet). Its baseline numbers are re-measured against the merge-base at execution time, since main has moved since the plans were written.

No user-facing change, so no changelog line and no website PR.

Checklist

I have (if applicable):

  • referenced the GitHub issue this PR closes
  • updated the appropriate changelog in the PR
  • ensured the present test suite passes
  • added new tests
  • created a separate documentation PR in Quarto's website repo and linked it to this PR
AI-assisted PR
  • AI tool used: Claude (planning sessions), recorded in REVIEW-*.md and TRIAGE-*.md
  • Codebase grounding: local clone. Plans measured against main at b86f98d
  • Human review: I have reviewed, tested, and verified the AI-generated content before submitting.

Note: autonomous AI agents submitting PRs without human oversight are not permitted — see the Code of Conduct.

cderv and others added 30 commits October 20, 2026 15:23
Decision record for moving quarto-cli off Deno after Cloudflare's
acquisition, the Node 22 spike that validated the render path, a plan
template with the shared deno_ral vocabulary, and the ordered index of
the per-PR plans (01-39).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
net, http, watch, kv, platform/main, deno_dom FFI removal, type sweep,
std import seams, tests and package/src codemods, boundary-zero.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…demods, env)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ndencies)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…texmk)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Align the ratchet exclusion list (PR 02/25), extend the shared vocabulary
with the names later plans rely on, make isMainModule runtime-neutral in
PR 19, align the test harness call shape (PR 23/31), correct the enum and
parameter-property counts, and record the conventions the plans decided.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Consolidates six independent reviews of plans 01-39 against the code:
structural findings that change the plan's shape (hidden Deno uses in
bundled JS, type-only imports under Node strip-types, unported layer
modules, ordering defects at the flip, env and proxy contract, shared
cache files, codemod mechanics, backports), per-plan defects with
severity, the recommended landing strategy, the rework list and the
decisions the team needs to make before execution.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… errata

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…e rework

Consistency pass across all plans: run-tests.sh is given files, never
directories; vocabulary additions in the template; assessment KV
signature; PR 02 allow-list wording. README carries the new sequence
(46 PRs, two spikes, milestone J), dependency graph, decisions and the
measured ratchet baseline.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Verify findings R01-R21 against the plan text and split the follow-up into
maintainer decisions, a cross-plan reconciliation pass and per-plan local
corrections. Link the triage from the handoff and the README.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
PR 02 owns the rule: direct std imports of the import-map seams PR 22's
inventory marks "import map" (async, crypto, datetime, streams, io/*,
log minus log/file-handler, fmt/printf, dotenv/stringify) are allow-listed
by the scanner (kStdAllowListed, kStdCountedExceptions); every other std
import, URL or jsr specifier counts. The baseline numbers are re-measured
under the rule with a port of the scanner (src 49, package/src 3; the
unfiltered run reproduces the earlier 62 and 4), the fixture gains a
permitted-only file and the log/file-handler exception, and PR 22, PR 25,
the README ground rule, the progress metric and the template say the same
thing by reference. PR 25's zero is now zero counted std imports and its
inventory grep no longer rejects plain `log` importers.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…arrowed

PR 25 now owns one runtime residue table (item, runtime it runs on, owner
task, removal PR, gate) covering the resource scripts, ast-diagram, the
engine create template, the tinitex deno-compile files, the Deno CLI dev
utilities, deno fmt/check in src, the quarto run and build-ts-extension
paths, the built editor artifacts, the test fixtures, tools/*.ts and the
Julia subtree. PR 39 becomes a Phase 1 prerequisite of PR 25 and the
conditional quarto-latexmk allow-list entry is gone. PR 33's shim-removal
gate is src and src-js only, with resources at the residue value, and a
new Task 16 ports or retires the deno info / deno run / deno fmt / deno
check callers (decisions 4 and 5 recorded). PR 35 gains Task 6 (juice.ts
and ast-diagram on Node APIs, check-usage retired); the engine template
line moves to PR 36; PR 24, 37 and the README table and graph refer to
the table.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The template now states the env key contract PRs 14 and 27b implement
(TypeError on an empty key or one containing = or NUL, and on a NUL value),
the exit contract PR 28 decided (stdio made blocking at load, exitCode set,
immediate exit, broken pipe ends quietly with the current code) and the KV
file rule (one JSON backend on both runtimes from PR 30). PR 08's exit
comments, PR 28's broken-pipe acceptance line (status 0, not 7), PR 34's
contract row 2 and PR 14b's inspect-pipe header follow it. PR 33 drops the
--unstable-kv flag and the "PR 30 keeps the Deno KV backend until PR 37"
sentence from the QUARTO_RUNTIME=deno launchers; PR 37 no longer plans to
remove a kv.ts Deno backend that does not exist.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…both

PR 26's Interfaces now state bundle()'s inputs once (entry, outfile,
banner, define, shim until PR 33, sourcemap, writeMeta), the interop
prelude every bundle starts with (require, __filename, __dirname, PR 20b's
tree-sitter requirement) and the two modes, CLI and fixture, with their
callers and lifecycle through PRs 31, 32 and 33. PR 33's rewrite keeps
banner, define and writeMeta and the prelude, so build-test.mjs is
unchanged by the flip; --write-meta writes <outfile>.meta.json, which
PR 33's module-graph task reads. PR 20b, PR 32 and the template refer to
the contract instead of restating it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
PR 37 now carries the event-to-job graph it leaves behind (events, OSes,
bucket generation, the reusable-workflow callers and the required check
names) and its Task 3 keeps the schedule trigger, node-macos, render-parity
and node-binary while deleting only the Deno jobs and the parity compare;
test-smokes-parallel.yml goes. render-parity gets a Node-only meaning
(dev bundle versus distribution bundle, decision 5). The acceptance checks
the graph, not just a grep for "deno", and deno-sanity leaves the required
list only after the first green main run. PR 32 points at the graph.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
SPIKES.md moves Spike V after PR 30 (its ten files spawn processes, serve
HTTP, open the KV store and render, so the ports 27-30 are inputs), adds a
failure-classification step that sets runtime gaps aside against their
owning PR, and replaces "choose node:test if neither is eligible" with an
inconclusive outcome with node:test as a provisional choice that blocks
PR 32's required gate until a re-run. PR 31's dependencies and checklist
and the README table and graph follow.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ckaging and version decisions

PR 35 owns the matrix (rows: internal API, prebuilt engines, rebuilt
engines, plain scripts, stdlib/* scripts, Deno-native scripts), the
"not available on Node" table, the version rule and the governing-lockfile
table; its stdlib bundles get a module-local Deno binding and the member
table covers what @std/fs 1.0.16 reaches. PR 36 refers to rows 2-3,
serves log/ and fs/ of the default engine import map as @quarto/std-*
packages built by PR 35's builder (Decision 3), resolves them at the root
lockfile versions with the version rule as a warning (Decision 4), and
gains the fs/log engine fixtures. PRs 00c, 34 and 37 refer to the matrix;
README graph: 35 -> 36.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… 28, 35, 36, 37

One table in PR 33 (after Task 13) of the CLI runtime (QUARTO_RUNTIME) x
script runtime (QUARTO_RUN_RUNTIME) combinations supported until PR 37, with
the executable each uses and the test that validates it, plus the rule:
src/ spawns Node on architectureToolsPath("node") and Deno on
requireDenoToolBinary(), never on execPath(), which is only the executable
running the current process.

Audit of the post-PR-33 Node subprocess callers: PR 35 runOnNode() and PR 36
typeCheck() used execPath() with Node flags and broke under
QUARTO_RUNTIME=deno (review R08); both now use architectureToolsPath("node").
Doc comment on execPath() in PR 28 and _TEMPLATE.md; references and
acceptance greps in 33, 35, 36 and 37.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
cderv and others added 26 commits October 20, 2026 15:25
…R 14 (organisation review 2026-10-17)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…R 14 (organisation review 2026-10-17)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…R 14 (organisation review 2026-10-17)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…R 14 (organisation review 2026-10-17)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…on PR 08; Unblocks PR 32 (organisation review 2026-10-17)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…26-10-17)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…26-10-17)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…10-17)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…Size L by file count (organisation review 2026-10-17)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… first; Unblocks PR 26 (organisation review 2026-10-17)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… first (organisation review 2026-10-17)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…organisation review 2026-10-17)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…10-17)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…e); Unblocks 28, 29, 30 (organisation review 2026-10-17)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…anisation review 2026-10-17)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ipt bodies; Size M by file count (organisation review 2026-10-17)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…parity OK, maintainer's branch-protection switch (organisation review 2026-10-17)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…pients; Decision 0 on splitting the PR (organisation review 2026-10-17)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…2026-10-17)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…-2 have a decider; Decision 0 on splitting; Size M by file count (organisation review 2026-10-17)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…f PR 34 has not landed (organisation review 2026-10-17)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Replace the placeholder 'to fill in' Roles table with the actual
assignment: Chris holds every maintainer/reviewer/release-manager/
spike-decider role; the plan coordinator is a fresh LLM session per
plan; the plan executor is a sub-agent-driven LLM (Chris runs the
reserved gh pr create / push / workflow_dispatch steps).

Update HANDOFF to note the Roles table is filled in; the cut dates
remain Chris's to confirm. Rename 'worker' to 'executor' in README's
'How to execute a plan' to match the new table.
@posit-snyk-bot

posit-snyk-bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Collaborator

✅ Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
✅ Open Source Security 0 0 0 0 0 issues
✅ Licenses 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@cderv
cderv merged commit 7063e04 into main Oct 20, 2026
7 of 9 checks passed
@cderv
cderv deleted the runtime-migration-plans branch October 20, 2026 18:45
@gordonwoodhull

gordonwoodhull commented Oct 20, 2026 •

Copy link
Copy Markdown
Member

@cderv, note that you’ll need to use my fork and branch of the Julia engine, for now, to be Node compatible.

https://github.com/gordonwoodhull/quarto-julia-engine/tree/q2/node-port

I’m waiting until

is merged to a stable Quarto 1 release, before submitting a PR upstream.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants