Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion configuration
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
# IMPORTANT: When these are updated, you must also update the versions
# in src/command/check/check.ts
# Binary dependencies
export DENO=v2.7.14
export DENO=v2.9.8
# TODO figure out where 0.1.41 apple silicon libs are available
export DENO_DOM=v0.1.41-alpha-artifacts
export PANDOC=3.10
Expand Down
2 changes: 1 addition & 1 deletion dev-docs/runtime-migration/HANDOFF.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ These were throwaway and are recreated by the plans when needed:
`REVIEW-2026-10-15.md` is the principal review of the first draft (13 structural findings, per-plan defects, the landing strategy, the decisions taken in its section 6).
Every plan was then reworked against it: six new PRs (00a, 00b, 00c, 02b, 14b, 20b, 27b), PR 21 folded into 25, PR 07 and PR 39 rewritten and renamed, `SPIKES.md` added, `_TEMPLATE.md` vocabulary updated, `00-assessment.md` given an errata section.
`README.md` carries the new table, dependency graph, conventions and baseline.
The plans are now executable in the README order; PR 02 (#15135) and 02c (#15153) are merged, and the next to run are 00a, then 00b.
The plans are now executable in the README order; PR 02 (#15135) and 02c (#15153) are merged; 00a is in progress as the Deno 2.9.8 security bump (#15152), and 00b runs next.

## Reconciliation pass (bucket B of `TRIAGE-2026-10-16.md`): done on 2026-10-16

Expand Down
8 changes: 6 additions & 2 deletions dev-docs/runtime-migration/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,10 @@ The rows below are the ones that still need a named person or an external answer
| PR 18 Tasks 1–3 (from the #15119 fix, once `src/deno_ral/kv.ts` is on `main`) | `kv.ts` already exists (`openKv`, `getKv`, `kvOp`, `KvDamagedError`) and both caches go through it, so Task 1 extends the file instead of creating it and its "Module not found" RED step no longer holds. `openKeyValueStore` keeps the damage mapping: a damaged value reads as a miss with one warning, a damaged file fails with `KvDamagedError` naming the folder to delete, any other error is wrapped with the path. The `src` baseline drops by 5, not 7 (the two `Deno.openKv` calls are already in the layer). The `Deno.openKv` grep acceptance check covers `src` and `package` only, since three cache tests build damaged fixtures with `Deno.openKv` on purpose. The `kv.ts` row in `.claude/rules/typescript/deno-ral.md` exists: update it | coordinator | PR 18 step 0 |
| PR 19 Task 1, PR 27b | both replace the whole body of `platform.ts`: keep the `v8Branch` export (`_TEMPLATE.md`, "`platform.ts`"); PR 27b computes it from `process.versions.v8` | coordinator | step 0 of each |
| PR 30 | tagged `v8-<branch>/` directories are left behind like the untagged `.kv` files; remove `v8Branch` and its contract-test line together with its last consumer. What "damaged" means for the JSON index (unparsable or truncated file): keep the miss-with-warning versus fail-naming-the-folder split or not. The Deno KV damage tests (`tests/unit/kv-damage.test.ts` and the damaged-store cases in `sass-cache.test.ts` and `project-cache.test.ts`, from the #15119 fix) build real SQLite damage: they are replaced or deleted here, because PR 32 runs the full unit suite on Node, where neither `Deno.openKv` nor its damage exists, and "`sass-cache.test.ts` stays green" is restated accordingly | maintainer (damage semantics), coordinator (the rest) | PR 30 step 0 |
| PR 00b, vendor script steps | Deno 2.9 rejects permission flags on a non-global `deno install` (denoland/deno#36489), and the vendor scripts keep going after a failed entrypoint, so `--allow-all` vendors nothing. Every `deno install` line the plan adds uses `--allow-import`, as the current vendor scripts do since #15152; its `deno run` and `deno bundle` lines keep `--allow-all` | executor | step 0 of PR 00b |
| PR 01 | The `assert` → `with` half landed with #15152 (TypeScript 6 in Deno 2.9 rejects `assert`, TS2880), and it changed the IDE bundles: esbuild now wraps the JSON module as ESM (same data, same `.default`). Skip those edits and the "bundles unchanged" check; the PR keeps only the bslib ordering | executor | step 0 of PR 01 |
| PR 15 Task 2 | The replacement `port.ts` listing has `4160, // sieve` and a private `isPortSafe`. Since #15152 the list has `4190` (the Fetch standard's bad port, which Deno 2.9's `fetch` refuses) and `isPortSafe` is exported for `tests/unit/core/port.test.ts`. Keep both; the test must still pass | executor | step 0 of PR 15 |
| PRs 15, 16, 23 and 31, sanitizer checks | Deno 2.9 turns test sanitizers off by default and `tests/test.ts` passes none, and forcing them on fails every test with a harness leak (an open file and a timer, already present on Deno 2.7.14). The plans' "no sanitizer failures" checks and PR 31's "Leak guarantees" reference to Deno's sanitizers prove nothing until the harness leak is fixed and sanitizers are re-enabled | maintainer | before PR 15 runs |

## Shared rules and tables

Expand Down Expand Up @@ -200,10 +204,10 @@ Every PR that changes a count lands after 02c; preparing it earlier is fine.
|---|---|---|---|---|---|---|
| 02 | [02-runtime-boundary-check.md](02-runtime-boundary-check.md) | CI ratchet counting `Deno.*` and direct std imports in five scopes (`src`, `src-js`, `resources`, `package/src`, `tests`); `tests/asserts.ts`; lands first | none | Sonnet | S | merged |
| 02c | [02c-ratchet-pr-delta-and-guidance.md](02c-ratchet-pr-delta-and-guidance.md) | PR runs compare with the base branch; findings name a working replacement or ask the maintainer to accept; `--accept-increase` | 02 | Sonnet | S | merged |
| 00a | [00a-deno-bump.md](00a-deno-bump.md) | Deno 2.7.14 → newest 2.9.x everywhere the version appears | 02 | Sonnet | S | planned |
| 00a | [00a-deno-bump.md](00a-deno-bump.md) | Deno 2.7.14 → newest 2.9.x everywhere the version appears | 02 | Sonnet | S | in progress |
| 00b | [00b-lockfile-and-bundle-gate.md](00b-lockfile-and-bundle-gate.md) | Committed `deno.lock` with `--frozen` (six entrypoints); `tools/check-test-imports.ts` keeps the unlocked tests inside that graph; three-OS bundle gate (cold cache, external-specifier grep, five renders) | 02, 00a | Opus | M | planned |
| 00c | [00c-quarto-run-deprecation-notice.md](00c-quarto-run-deprecation-notice.md) | One-time notice when `quarto run` or a pre/post-render `.ts` script runs on Deno; `QUARTO_RUN_DENO_NOTICE=0` | 02 | Sonnet | S | planned |
| 01 | [01-quick-fixes-from-spike.md](01-quick-fixes-from-spike.md) | `assert`→`with` on two JSON imports; deterministic bslib Sass ordering | 02 | Sonnet | S | planned |
| 01 | [01-quick-fixes-from-spike.md](01-quick-fixes-from-spike.md) | Deterministic bslib Sass ordering (the `assert`→`with` half landed with 00a) | 02 | Sonnet | S | planned |
| 03 | [03-deps-skypack-to-npm.md](03-deps-skypack-to-npm.md) | Replace every `cdn.skypack.dev` import-map entry with `npm:`; CommonJS bundling proof | 02, 00b | Sonnet | M | planned |
| 04 | [04-deps-deno-land-x-small.md](04-deps-deno-land-x-small.md) | Replace semver, media_types, xmlp, xml, cache, events; cookie jar on tough-cookie | 02, 00b | Opus | M | planned |
| 05 | [05-deps-deno-dom-jsr.md](05-deps-deno-dom-jsr.md) | deno_dom from `deno.land/x` to `jsr:@b-fuze/deno-dom` 0.1.56; `parseHtml` timing baseline on the native plugin | 02, 00b | Opus | M | planned |
Expand Down
2 changes: 1 addition & 1 deletion dev-docs/upgrade-dependencies.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Change version numbers in `./configuration` to correspond to new versions.

Update hardcoded version strings in `src/command/check/check.ts` (`versionConstraints` array, ~line 249) so that they match the new versions in `configuration`. The `configuration` file warns about this in a comment.
Update hardcoded version strings in `src/command/check/check.ts` (the `versionConstraints` array in `checkVersions`) so that they match the new versions in `configuration`. The `configuration` file warns about this in a comment, and `tests/smoke/check/check-versions-no-mismatch.test.ts` fails until they match.

## Verify installer signing & notarization before merging (bundled binaries)

Expand Down
2 changes: 2 additions & 0 deletions news/changelog-1.11.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ All changes included in 1.11:
## Dependencies

- ([#15043](https://github.com/quarto-dev/quarto-cli/issues/15043)): Update the OJS runtime to acorn 8.17, Observable Inspector 5.0.1, and Observable Inputs 0.10.6. `FileAttachment(...).mimeType` now reports `text/javascript` instead of `application/javascript` for `.js` and `.mjs` files.
- ([#15152](https://github.com/quarto-dev/quarto-cli/issues/15152)): Update `deno` to v2.9.8. HTTPS requests made by Quarto (`quarto publish`, `quarto add`, `quarto use template`, `quarto install`) no longer trust certificates issued through the compromised eMudhra / emSign certificate authorities, which Deno 2.7.14 accepted.

## Accessibility

Expand Down Expand Up @@ -190,4 +191,5 @@ All changes included in 1.11:
- ([#15097](https://github.com/quarto-dev/quarto-cli/pull/15097)): Fix the `.content-hidden unless-profile` attribute completion inserting invalid syntax (its `=` was missing), and typos in three attribute completion descriptions. (author: @rkskmt)
- ([#15126](https://github.com/quarto-dev/quarto-cli/issues/15126)): Translate the `revealjs-menu-button-label` language key in French. (author: @D3ller)
- ([#15140](https://github.com/quarto-dev/quarto-cli/pull/15140)): Add support for definition lists written as a `definition-list` div holding a nested bullet list, the definition list syntax of Quarto 2.
- ([#15157](https://github.com/quarto-dev/quarto-cli/issues/15157)): Fix `quarto preview` and other commands sometimes starting a local server on port 4190, which browsers refuse to connect to.
- ([#15180](https://github.com/quarto-dev/quarto-cli/pull/15180)): Fix older Quarto versions failing to render with `RangeError: could not deserialize value` once a Quarto built on a newer Deno has used the same cache. Also fix a project cache that already has entries failing to clear with `Path must be a string`.
2 changes: 1 addition & 1 deletion package/scripts/vendoring/vendor.cmd
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ IF EXIST "!DENO_DIR!" (
PUSHD "!QUARTO_SRC_PATH!"

FOR %%E IN (quarto.ts vendor_deps.ts ..\tests\test-deps.ts ..\package\scripts\deno_std\deno_std.ts) DO (
CALL !DENO_BIN_PATH! install --allow-all --no-config --entrypoint %%E --importmap="!QUARTO_SRC_PATH!\import_map.json" || (
CALL !DENO_BIN_PATH! install --allow-import --no-config --entrypoint %%E --importmap="!QUARTO_SRC_PATH!\import_map.json" || (
ECHO Warning: Failed to vendor %%E, continuing...
)
)
Expand Down
2 changes: 1 addition & 1 deletion package/scripts/vendoring/vendor.sh
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ fi
pushd "${QUARTO_SRC_PATH}"
set +e
for entrypoint in quarto.ts vendor_deps.ts ../tests/test-deps.ts ../package/scripts/deno_std/deno_std.ts; do
$DENO_BIN_PATH install --allow-all --no-config --entrypoint $entrypoint "--importmap=$QUARTO_SRC_PATH/import_map.json"
$DENO_BIN_PATH install --allow-import --no-config --entrypoint $entrypoint "--importmap=$QUARTO_SRC_PATH/import_map.json"
done
set -e
popd
2 changes: 1 addition & 1 deletion src/command/check/check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ async function checkVersions(conf: CheckConfiguration) {
const versionConstraints: [string | undefined, string, string][] = [
[pandocVersion, "3.10.0", "Pandoc"],
[sassVersion, "1.101.0", "Dart Sass"],
[denoVersion, "2.7.14", "Deno"],
[denoVersion, "2.9.8", "Deno"],
[typstVersion, "0.15.1", "Typst"],
];
const checkData: [string | undefined, string, string][] = versionConstraints
Expand Down
2 changes: 1 addition & 1 deletion src/core/lib/yaml-intelligence/parsing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export async function getTreeSitter(): Promise<any> {

const treeSitterYamlJson = (await import(
"../../../resources/editor/tools/yaml/tree-sitter-yaml.json",
{ assert: { type: "json" } }
{ with: { type: "json" } }
)).default as { data: number[] };

const YAML = await Parser.Language.load(
Expand Down
2 changes: 1 addition & 1 deletion src/core/lib/yaml-intelligence/yaml-intelligence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1208,7 +1208,7 @@ async function init(
const ideInit = async () => {
const resourceModule = (await import(
"../../../resources/editor/tools/yaml/yaml-intelligence-resources.json",
{ assert: { type: "json" } }
{ with: { type: "json" } }
)).default as Record<string, unknown>;

await getTreeSitter();
Expand Down
11 changes: 11 additions & 0 deletions src/core/log.ts
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,17 @@ export function logError(e: unknown, defaultShowStack = true) {
// print error name if requested
let message = err.printName ? `${err.name}: ${err.message}` : err.message;

// runtime errors such as Deno's "fetch failed" carry the reason in `cause`
const seen = new Set<unknown>([err]);
for (
let cause = err.cause;
cause instanceof Error && !seen.has(cause);
cause = cause.cause
) {
seen.add(cause);
message = `${message}: ${cause.message}`;
}

if (e instanceof InternalError) {
// always print stack information of internal errors
message = message +
Expand Down
4 changes: 2 additions & 2 deletions src/core/port.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ import { kLocalhost } from "./port-consts.ts";
const kMinPort = 3000;
const kMaxPort = 8000;

function isPortSafe(port: number): boolean {
export function isPortSafe(port: number): boolean {
// cf https://superuser.com/a/188070
// and netwerk/base/nsIOService.cpp in Firefox's source
//
// excludes port numbers that Chrome or Firefox consider unsafe
return ![
3659, // apple-sasl / PasswordServer
4045, // lockd
4160, // sieve
4190, // sieve
5060, // sip
5061, // sips
6000, // X11
Expand Down
7 changes: 5 additions & 2 deletions src/publish/posit-connect-cloud/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,10 @@ export class PositConnectCloudClient {
);
}

public async uploadBundle(uploadUrl: string, bundleData: Uint8Array) {
public async uploadBundle(
uploadUrl: string,
bundleData: Uint8Array<ArrayBuffer>,
) {
publishDebug(
`Uploading bundle (${bundleData.length} bytes)`,
);
Expand Down Expand Up @@ -290,7 +293,7 @@ export class PositConnectCloudClient {
method: string,
url: string,
headers: Record<string, string>,
body?: string | Uint8Array,
body?: string | Uint8Array<ArrayBuffer>,
): Promise<Response> {
await this.ensureValidToken_();
const buildHeaders = () => ({
Expand Down
55 changes: 25 additions & 30 deletions src/resources/editor/tools/vs-code.mjs

Large diffs are not rendered by default.

55 changes: 25 additions & 30 deletions src/resources/editor/tools/yaml/web-worker.js

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions tests/docs/search/issue-9905/navbar/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
/.quarto/

**/*.quarto_ipynb
2 changes: 2 additions & 0 deletions tests/docs/search/issue-9905/sidebar/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
/.quarto/

**/*.quarto_ipynb
2 changes: 2 additions & 0 deletions tests/docs/site-navigation/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
/.quarto/

**/*.quarto_ipynb
2 changes: 2 additions & 0 deletions tests/docs/websites/drafts/drafts-default/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
/.quarto/

**/*.quarto_ipynb
2 changes: 2 additions & 0 deletions tests/docs/websites/drafts/drafts-env/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
/.quarto/

**/*.quarto_ipynb
2 changes: 2 additions & 0 deletions tests/docs/websites/drafts/drafts-gone/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
/.quarto/

**/*.quarto_ipynb
2 changes: 2 additions & 0 deletions tests/docs/websites/drafts/drafts-preprocessor/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
/.quarto/

**/*.quarto_ipynb
2 changes: 2 additions & 0 deletions tests/docs/websites/drafts/drafts-visible/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
/.quarto/

**/*.quarto_ipynb
2 changes: 2 additions & 0 deletions tests/docs/websites/drafts/no-drafts/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
/.quarto/

**/*.quarto_ipynb
2 changes: 2 additions & 0 deletions tests/docs/websites/issue-3686/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
/.quarto/

**/*.quarto_ipynb
2 changes: 2 additions & 0 deletions tests/docs/websites/issue-5756/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
/.quarto/

**/*.quarto_ipynb
2 changes: 1 addition & 1 deletion tests/smoke/axe/axe-transport-failclosed.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ function unanswerable(browser: ScanBrowser): Promise<unknown> {
* rather than passing it.
*/
async function settled(p: Promise<unknown>): Promise<string> {
let timer: number | undefined;
let timer: ReturnType<typeof setTimeout> | undefined;
try {
await Promise.race([
p,
Expand Down
81 changes: 81 additions & 0 deletions tests/unit/core/log-error.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* log-error.test.ts
*
* Copyright (C) 2026 Posit Software, PBC
*/

import { test } from "../../test.ts";
import { printsMessage } from "../../verify.ts";
import { logError } from "../../../src/core/log.ts";

// Deno's fetch rejects with TypeError("fetch failed") and keeps the reason
// (TLS, DNS, connection) only in `cause`.
test({
name: "logError prints the cause of a failed fetch",
type: "unit",
context: {},
// deno-lint-ignore require-await
execute: async () => {
logError(
new TypeError("fetch failed", {
cause: new Error(
"error sending request for url (https://example.com/): client error (Connect): invalid peer certificate: certificate expired",
),
}),
);
},
verify: [
printsMessage({
level: "ERROR",
regex: /fetch failed: error sending request .*certificate expired/,
}),
],
});

test({
name: "logError prints every cause in a chain",
type: "unit",
context: {},
// deno-lint-ignore require-await
execute: async () => {
logError(
new Error("outer", {
cause: new Error("middle", { cause: new Error("inner") }),
}),
);
},
verify: [
printsMessage({ level: "ERROR", regex: /outer: middle: inner/ }),
],
});

test({
name: "logError stops at a self-referencing cause",
type: "unit",
context: {},
// deno-lint-ignore require-await
execute: async () => {
const err = new Error("self");
err.cause = err;
logError(err);
},
verify: [
printsMessage({ level: "ERROR", regex: /^self$/m }),
],
});

test({
name: "logError stops at a cause cycle",
type: "unit",
context: {},
// deno-lint-ignore require-await
execute: async () => {
const first = new Error("first");
const second = new Error("second", { cause: first });
first.cause = second;
logError(first);
},
verify: [
printsMessage({ level: "ERROR", regex: /^first: second$/m }),
],
});
45 changes: 45 additions & 0 deletions tests/unit/core/port.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* port.test.ts
*
* Copyright (C) 2026 Posit Software, PBC
*/

import { unitTest } from "../../test.ts";
import { assert } from "../../asserts.ts";
import { isPortSafe } from "../../../src/core/port.ts";

// Quarto fetches from the servers it starts on ports from findOpenPort
// (e.g. waiting for headless Chrome in src/core/cri/launch.ts). Deno's fetch
// refuses the Fetch standard's "bad ports", so every port isPortSafe accepts
// must be one Deno's fetch will request.
unitTest(
"isPortSafe accepts no port that Deno's fetch blocks",
async () => {
const candidates: number[] = [];
for (let port = 3000; port < 8000; ++port) {
if (isPortSafe(port)) candidates.push(port);
}
const blocked: number[] = [];
const kBatch = 500;
for (let i = 0; i < candidates.length; i += kBatch) {
await Promise.all(
candidates.slice(i, i + kBatch).map(async (port) => {
try {
const response = await fetch(`http://127.0.0.1:${port}/`, {
signal: AbortSignal.timeout(250),
});
await response.body?.cancel();
} catch (e) {
if (String(e).includes(`port ${port} are blocked`)) {
blocked.push(port);
}
}
}),
);
}
assert(
blocked.length === 0,
`isPortSafe accepts ports Deno's fetch blocks: ${blocked.join(", ")}`,
);
},
);
2 changes: 1 addition & 1 deletion tests/unit/extension-dirs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ async function inputExtensionDirsInWorker(
new URL("./extension-dirs-worker.ts", import.meta.url).href,
{ type: "module" },
);
let timer: number | undefined;
let timer: ReturnType<typeof setTimeout> | undefined;
try {
await nextMessage(worker);
const reply = nextMessage(worker);
Expand Down
Loading