Skip to content

Upgrade the console to Astro 7, Biome 2, TypeScript 6, and Node 24 - #979

Draft
epompeii wants to merge 10 commits into
develfrom
claude/console-dep-upgrade
Draft

Upgrade the console to Astro 7, Biome 2, TypeScript 6, and Node 24#979
epompeii wants to merge 10 commits into
develfrom
claude/console-dep-upgrade

Conversation

@epompeii

Copy link
Copy Markdown
Member

Upgrade the console to Astro 7, Biome 2, TypeScript 6, and Node 24, and take the
latest on everything else it depends on.

A deploy bug this uncovered

@astrojs/cloudflare 14 resolves wrangler.jsonc at build time and writes a
flattened dist/server/wrangler.json, which wrangler then reads through a config
redirect. That emitted config has no env key, so --env on wrangler deploy
selects nothing. It does not error. A dry run with --env dev and one without are
byte identical.

The dev deploy job built with no environment set and deployed with --env dev,
which would have published a worker named console, bound to bencher.dev, with
the production API URL. The dev deploy would have overwritten production.

The environment is now chosen at build time with CLOUDFLARE_ENV, and
deploy_target.js asserts the emitted worker name matches the intended target
before anything deploys, so a build that forgets the variable fails loudly
instead of shipping production config. It runs as a script rather than a
workflow step so that it also covers a hand run npx wrangler deploy, which is
the path with no other protection.

Markdown moves to Sätteri

Astro 7 renders markdown with Sätteri instead of remark/rehype, and Sätteri's
plugin API is deliberately not unified compatible, so rehype-slug and
rehype-autolink-headings cannot carry over. Heading ids now come from
satteriHeadingIdsPlugin, which slugs with github-slugger exactly as
rehype-slug did, and the heading autolink is reimplemented as a Sätteri hast
plugin in markdown.js.

All 7806 heading anchors across 1038 pages are unchanged, and the anchor markup is
byte identical to what the rehype pipeline emitted, attribute order included.
Anchors on published docs pages are load bearing, so this was verified by
building both branches and diffing every (file, tag, id) triple rather than by
sampling.

One subtlety worth recording: satteriHeadingIdsPlugin must be passed uncalled.
Passing an instance shares a single slugger across the whole site, so every
repeated heading picks up a -1, -2 suffix. Sätteri calls the factory once per
document. That form rewrote 2715 anchors before it was caught.

Changes a reader would notice

  • Pages in 8 locales reorder in the learn menu. The Rust custom harness page
    carried sortOrder: 4 in every translation, colliding with the Gungraun page;
    English already used 5. Astro 7 flipped the tie-break, which surfaced it. All
    nine locales now agree.
  • @sentry/astro 10 injects a sentry-route-name meta tag on every page.
  • Three console links to the docs gain rel="noreferrer", which drops referrer
    data bencher.dev previously received from the console. Biome 2 moved
    noBlankTarget from a11y to security and the existing suppressions no
    longer applied, which is the right outcome.
  • compressHTML takes Astro 7's new 'jsx' default, so whitespace between
    inline elements collapses by JSX rules. Sätteri's smart punctuation also pairs
    curly quotes correctly where the old pipeline emitted a mismatched pair.

Two real markup bugs the new compiler caught

Astro 7's Rust compiler rejects unclosed and mismatched tags that the old one
accepted silently. ApiPanel.astro closed with </DocsLayout> instead of
</ApiLayout>, and the perf embed opened <h2> and closed </h1>.

Versions

Every version was checked to be at least seven days old, because
services/console/.npmrc now sets min-release-age=7 and npm refuses anything
newer. A few pins are deliberately one patch behind the newest release for that
reason.

TypeScript stays on 6. @astrojs/check peers ^5 || ^6, so 7 is not installable
here.

The console now requires Node 24. Astro 7 needs at least 22.12 and the image ran
Node 20, so both build stages and the distroless runtime move up, digest pinned.

Configuration notes

  • Biome's config moves to biome.jsonc. A // comment in biome.json makes
    Biome discard the config and fall back to defaults with no error and no
    warning, which had quietly widened the check to all of dist.
  • prerenderEnvironment: "node" keeps the prerender behavior adapter 12 has
    today. The adapter's workerd default fails for two independent reasons. The
    bundled workerd tops out at compatibility date 2026-08-08 while the config asks
    for 2026-08-11, which also breaks local wrangler dev and leaves deployed
    Workers unaffected since Cloudflare builds its own. Past that, workerd
    disallows WASM code generation, so Shiki cannot load its highlighting engine
    and every page with a code block fails to prerender. Bumping workerd alone does
    not unblock workerd prerendering.
  • session: false, because nothing uses sessions and the adapter otherwise
    requires a KV namespace that does not exist.
  • @sentry/cloudflare is a hard requirement of @sentry/astro 10 under the
    Cloudflare adapter, which exits if it is missing.

Known and deferred

  • astro check goes from 758 errors to 806. Every added error is solid-js
    1.9.14 widening createResource with a third type parameter that leaks into
    the return as T | I. @astrojs/solid-js 7 peers solid-js ^1.9.13, so the
    version is not avoidable. The call sites use optional chaining, so this is type
    noise rather than a runtime defect. astro check does not run in CI. Fixing
    the 21 files is its own change.
  • noStaticElementInteractions is off. It flags 20 pre-existing sites, all Bulma
    modal backdrops and panel blocks that need real roles and key handlers.
  • Unused-symbol rules are off for .astro files. Biome 2 parses only the
    frontmatter, so it reports 1206 false positives on symbols used in templates.
    The cost is that a genuinely unused import in an .astro file is now invisible.
  • knip reports fewer findings than before, from 101 unused exports to 40, and
    the removed rehype packages are gone from the report. It still exits non-zero,
    as it does on devel.
  • The Docker image was not built locally, so the Node 24 build stages and the
    distroless runtime rest on CI's docker job. markdown.js is added to the
    COPY list, without which the image would build and then fail at runtime.
  • The deploy finding was established from the emitted config and
    wrangler deploy --dry-run, not from a live deploy.

Everett Pompeii added 10 commits August 16, 2026 04:07
Astro 7 requires Node 22.12 or newer, so the console image and every CI job
that runs npm move to Node 24. The self-hosted runtime image is
`gcr.io/distroless/nodejs*`, which has no Node 26 variant, so 24 is the
ceiling the shipped image can meet.

Add an `.npmrc` with `min-release-age=7` so a freshly published version is
never pulled in before it has had a week to be caught and unpublished.
Astro 5 to 7 brings a Rust template compiler, Vite 8, Zod 4, and Sätteri in
place of remark/rehype. TypeScript moves to 6 and Biome to 2 in the same pass.

Astro 7 renders Markdown with Sätteri, whose plugin API is deliberately not
unified-compatible, so `rehype-slug` and `rehype-autolink-headings` are
replaced by `satteriHeadingIdsPlugin()` and a `headingAutolink()` hast plugin
that reproduces the same anchor markup.

The Rust compiler rejects unclosed non-void tags that the old one silently
accepted, which surfaced two real bugs: `ApiPanel.astro` closed with
`</DocsLayout>` instead of `</ApiLayout>`, and the not-found heading in the
perf embed opened `<h2>` and closed `</h1>`.

The Cloudflare adapter now resolves `wrangler.jsonc` at build time and writes
the deployable config to `dist/server/wrangler.json`, so `main` becomes the
adapter entrypoint and the Wrangler environment is chosen by `CLOUDFLARE_ENV`
during the build. Passing `--env` to `wrangler deploy` no longer selects it,
so the dev deploy sets `CLOUDFLARE_ENV` on its build step; without that it
would have deployed the production config. Wrangler itself moves into
`devDependencies` so the lockfile is the single source of its version.

Static pages keep prerendering in Node. The adapter's new `workerd` prerender
default fails on this site, and Node prerendering is what the console does
today.

Sessions are turned off. Nothing reads or writes one, and adapters otherwise
wire up a default driver, which for Cloudflare means requiring a KV binding.

`@sentry/cloudflare` is now a hard requirement of `@sentry/astro` whenever the
Cloudflare adapter is configured, even with the server SDK disabled.

The `d3` to `d3-scale-chromatic` override is gone because the pinned version is
what `d3` already resolves, and the `solid-devtools` override is replaced by
one that lets it accept Vite 8.
The config schema changed: `files.ignore` became `files.includes` with `!`
negation, per-tool `includes` replaced the single ignore list, and
`--organize-imports-enabled` is gone because import organization is now an
assist. The console does not enforce it, so assists are turned off in the
config and the CI flag drops away. `services/action` is still on Biome 1 and
keeps the old flag.

`noExplicitAny` is now an error. `src/types/bencher.ts` is generated, so it is
exempt from the formatter but still linted.

Biome 2 reads many more file types than Biome 1 did, so generated output,
static assets, and standalone SVGs are excluded rather than linted as source.
Unused-symbol rules are off for `.astro` files: Biome only parses the
frontmatter, so anything used in the template below reads as unused, which was
1206 false positives.

`noStaticElementInteractions` is new to `recommended` and flags 20 Bulma modal
backdrops and tab headers. Giving those real roles and key handlers is its own
change, so the rule is off for now.

`noBlankTarget` moved from `a11y` to `security`, which left three suppression
comments pointing at a category that no longer parses. The links get the
`rel="noreferrer"` they should have had instead. Two `map()` callbacks in the
API body parameter table now return explicitly on the path that always fell
through to `undefined`.

The config moves to `biome.jsonc`: comments in a `biome.json` make Biome fall
back to its defaults without reporting an error, which silently widened the
check to every file in `dist`.
Mechanical only: `npx biome format --write .`. Biome 2 collapses these
ternaries and calls onto fewer lines than Biome 1 did.
Knip 6 compiles `.astro` and `.mdx` itself, but only for extensions the
`project` globs admit, so the globs now cover them along with Sass. That lets
it follow imports through the templates and stylesheets, which resolves
`astro-seo`, `slugify`, and `sass`, and drops the unused export count from 101
to 40. The root `entry` pattern matched nothing and is gone.

`@sentry/cloudflare` and `wrangler` are never imported: the first is required
by `@sentry/astro` at build time and the second is a CLI. Both are ignored
rather than removed.

`markdown.js` imports `defineHastPlugin` from `satteri`, which
`@astrojs/markdown-satteri` does not re-export, so `satteri` becomes a direct
dependency instead of a transitive one.

Vitest 4 moved its Vite config types to `vitest/config`, so the triple-slash
reference has to follow.
Sätteri accepts a hast plugin as either an instance or a factory, and calls
the factory once per document. Passing `satteriHeadingIdsPlugin()` as an
instance shares one `github-slugger` across every page in the build, so the
second page to use a heading text gets a `-1` suffix, the third `-2`, and so
on. That rewrote 2715 of the 7806 heading anchors on the site.

Passing the factories instead reproduces the previous anchors exactly: the
heading ids and the autolink markup are byte-identical to the remark/rehype
output across every page.
Astro 7 strips whitespace with JSX rules, which drops the newline between the
published date and the "Last Updated" fragment in the docs footer. An explicit
space keeps them apart.

Every translation of the Rust custom harness page carried `sortOrder: 4`, the
same value as the Gungraun page, while English correctly used 5. The learn menu
sorts on that field, so the two pages traded places whenever the collection
iteration order changed, which is what Astro 7 did. All nine locales now agree
on 5.
The 1.0 release adds interpolated tag names, better error messages, and type
declarations. The `html` tagged template the perf plot tooltip uses is
unchanged, and the new types cost nothing: `astro check` reports one error
fewer than on 0.3.1.
The Biome guidance described `organizeImports` behavior that Biome 2 replaced,
and nothing recorded that the Wrangler environment is now chosen by the build.
The Cloudflare environment is baked in at build time and the emitted
`dist/server/wrangler.json` carries no `env` key, so `wrangler deploy` and
`wrangler deploy --env dev` produce byte-identical output. A build that forgets
`CLOUDFLARE_ENV` therefore deploys the production Worker with no warning, and
the default is production.

`deploy_target.js` reads the built config and refuses to continue unless the
Worker name matches the intended target, naming both. It sits beside
`adapter.js` rather than living inline in the workflow because the unguarded
path that matters most is a hand-run `npx wrangler deploy`, which no workflow
step can protect, and because one script keeps the two deploy jobs from
drifting apart.

Also correct the `prerenderEnvironment` rationale. Two separate things block
`workerd` prerendering, and neither is what the old comment claimed: the pinned
`workerd` rejects the configured compatibility date, and past that it disallows
the WebAssembly code generation Shiki needs for syntax highlighting.
@socket-security

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updatednpm/​happy-dom@​20.9.0 ⏵ 20.11.26610088 +196 +1100
Addednpm/​@​astrojs/​markdown-satteri@​0.3.51001006793100
Updatednpm/​astro-expressive-code@​0.40.2 ⏵ 0.44.110010074 +190 -2100
Addednpm/​@​sentry/​cloudflare@​10.69.07610010096100
Updatednpm/​vitest@​3.0.5 ⏵ 4.1.1098 +1100 +757999 +1100
Updatednpm/​@​astrojs/​check@​0.5.10 ⏵ 0.9.10100 +110080 +1591 +4100
Updatednpm/​bulma@​1.0.3 ⏵ 1.0.410010010080100
Updatednpm/​@​astrojs/​solid-js@​5.0.4 ⏵ 7.0.2991008094 +7100
Updatednpm/​@​types/​node@​17.0.45 ⏵ 26.2.01001008195 -1100
Updatednpm/​astro-seo@​0.8.4 ⏵ 1.1.010010097 +182100
Updatednpm/​@​astrojs/​sitemap@​3.2.1 ⏵ 3.7.3100 +110082 +187 -2100
Updatednpm/​solid-devtools@​0.33.0 ⏵ 0.34.597 +1100100 +182 -1100
Updatednpm/​@​astrojs/​node@​9.0.3 ⏵ 11.1.0100100 +2182 +197100
Updatednpm/​@​astrojs/​mdx@​4.0.8 ⏵ 7.0.59910083 +196 -1100
Updatednpm/​@​astrojs/​cloudflare@​12.6.4 ⏵ 14.2.0100 +1100 +1784 +397 +1100
Updatednpm/​@​observablehq/​plot@​0.6.16 ⏵ 0.6.1710010085 +384100
Updatednpm/​slugify@​1.6.6 ⏵ 1.6.9100100100 +184100
Updatednpm/​pagefind@​1.5.0 ⏵ 1.5.2991008686 -2100
Updatednpm/​htl@​0.3.1 ⏵ 1.0.09910010086 +4100
Updatednpm/​astro@​5.2.6 ⏵ 7.2.098 +1100 +4188 +198100
Addednpm/​satteri@​0.9.5901008992100
Updatednpm/​@​mermaid-js/​mermaid-cli@​11.12.0 ⏵ 11.16.09910010089100
Updatednpm/​typescript@​5.7.3 ⏵ 6.0.3100 +110090 +110090
Updatednpm/​@​solid-primitives/​scheduled@​1.5.0 ⏵ 1.5.310010010090 +6100
Updatednpm/​wrangler@​4.122.0 ⏵ 4.120.098 +11009296100
Updatednpm/​axios@​1.7.9 ⏵ 1.19.098 -1100 +7510092100
Updatednpm/​@​solid-primitives/​resize-observer@​2.1.0 ⏵ 2.2.0100 +110010094 +9100
Updatednpm/​knip@​5.44.1 ⏵ 6.32.099 +110095 +896 +1100
Updatednpm/​jsdom@​29.1.1 ⏵ 30.0.198100100 +195100
Updatednpm/​solid-js@​1.9.4 ⏵ 1.9.14100 +110010096 +2100
Updatednpm/​@​sentry/​astro@​9.0.1 ⏵ 10.69.09810010096 +1100
Updatednpm/​sass@​1.84.0 ⏵ 1.102.0100100100 +197100
See 1 more rows in the dashboard

View full report

@socket-security

Copy link
Copy Markdown

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

Action Severity Alert  (click "▶" to expand/collapse)
Warn High
Obfuscated code: npm @astrojs/compiler is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: services/console/package-lock.jsonnpm/@astrojs/check@0.9.10npm/@astrojs/compiler@2.13.1

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/@astrojs/compiler@2.13.1. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm @emnapi/runtime is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: services/console/package-lock.jsonnpm/satteri@0.9.5npm/@emnapi/runtime@1.11.1

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/@emnapi/runtime@1.11.1. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm @sentry/node-core is 60.0% likely obfuscated

Confidence: 0.60

Location: Package overview

From: services/console/package-lock.jsonnpm/@sentry/astro@10.69.0npm/@sentry/node-core@10.69.0

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/@sentry/node-core@10.69.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm formatly is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: services/console/package-lock.jsonnpm/knip@6.32.0npm/formatly@0.3.0

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/formatly@0.3.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm jotai is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: services/console/package-lock.jsonnpm/@mermaid-js/mermaid-cli@11.16.0npm/jotai@2.20.2

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/jotai@2.20.2. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm jsdom is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: services/console/package-lock.jsonnpm/jsdom@30.0.1

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/jsdom@30.0.1. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm robust-predicates is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: services/console/package-lock.jsonnpm/d3@7.9.0npm/robust-predicates@3.0.3

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/robust-predicates@3.0.3. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant