Upgrade the console to Astro 7, Biome 2, TypeScript 6, and Node 24 - #979
Upgrade the console to Astro 7, Biome 2, TypeScript 6, and Node 24#979epompeii wants to merge 10 commits into
Conversation
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.
|
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.
|
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/cloudflare14 resolveswrangler.jsoncat build time and writes aflattened
dist/server/wrangler.json, which wrangler then reads through a configredirect. That emitted config has no
envkey, so--envonwrangler deployselects nothing. It does not error. A dry run with
--env devand one without arebyte identical.
The dev deploy job built with no environment set and deployed with
--env dev,which would have published a worker named
console, bound tobencher.dev, withthe production API URL. The dev deploy would have overwritten production.
The environment is now chosen at build time with
CLOUDFLARE_ENV, anddeploy_target.jsasserts the emitted worker name matches the intended targetbefore 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 isthe 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-slugandrehype-autolink-headingscannot carry over. Heading ids now come fromsatteriHeadingIdsPlugin, which slugs withgithub-sluggerexactly asrehype-slugdid, and the heading autolink is reimplemented as a Sätteri hastplugin 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 bysampling.
One subtlety worth recording:
satteriHeadingIdsPluginmust be passed uncalled.Passing an instance shares a single slugger across the whole site, so every
repeated heading picks up a
-1,-2suffix. Sätteri calls the factory once perdocument. That form rewrote 2715 anchors before it was caught.
Changes a reader would notice
carried
sortOrder: 4in 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/astro10 injects asentry-route-namemeta tag on every page.rel="noreferrer", which drops referrerdata bencher.dev previously received from the console. Biome 2 moved
noBlankTargetfroma11ytosecurityand the existing suppressions nolonger applied, which is the right outcome.
compressHTMLtakes Astro 7's new'jsx'default, so whitespace betweeninline 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.astroclosed 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/.npmrcnow setsmin-release-age=7and npm refuses anythingnewer. A few pins are deliberately one patch behind the newest release for that
reason.
TypeScript stays on 6.
@astrojs/checkpeers^5 || ^6, so 7 is not installablehere.
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.jsonc. A//comment inbiome.jsonmakesBiome 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 hastoday. The adapter's
workerddefault fails for two independent reasons. Thebundled workerd tops out at compatibility date 2026-08-08 while the config asks
for 2026-08-11, which also breaks local
wrangler devand leaves deployedWorkers 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 otherwiserequires a KV namespace that does not exist.
@sentry/cloudflareis a hard requirement of@sentry/astro10 under theCloudflare adapter, which exits if it is missing.
Known and deferred
astro checkgoes from 758 errors to 806. Every added error issolid-js1.9.14 widening
createResourcewith a third type parameter that leaks intothe return as
T | I.@astrojs/solid-js7 peerssolid-js ^1.9.13, so theversion is not avoidable. The call sites use optional chaining, so this is type
noise rather than a runtime defect.
astro checkdoes not run in CI. Fixingthe 21 files is its own change.
noStaticElementInteractionsis off. It flags 20 pre-existing sites, all Bulmamodal backdrops and panel blocks that need real roles and key handlers.
.astrofiles. Biome 2 parses only thefrontmatter, so it reports 1206 false positives on symbols used in templates.
The cost is that a genuinely unused import in an
.astrofile is now invisible.knipreports fewer findings than before, from 101 unused exports to 40, andthe removed rehype packages are gone from the report. It still exits non-zero,
as it does on
devel.distroless runtime rest on CI's docker job.
markdown.jsis added to theCOPYlist, without which the image would build and then fail at runtime.wrangler deploy --dry-run, not from a live deploy.