Skip to content

refactor: migrate react tests to typescript#16318

Merged
ematipico merged 8 commits into
withastro:mainfrom
ocavue-forks:ocavue/ts-test-3
Apr 16, 2026
Merged

refactor: migrate react tests to typescript#16318
ematipico merged 8 commits into
withastro:mainfrom
ocavue-forks:ocavue/ts-test-3

Conversation

@ocavue
Copy link
Copy Markdown
Contributor

@ocavue ocavue commented Apr 14, 2026

Changes

Part of #16241

Since everyone is working on the unit tests under packages/astro/, let me do something else and migrate the integration tests under packages/integrations/react/.


The React test migration itself is easy and boring: only 3 files to change. However, I want to use it as an example to show that we can migrate integration tests in smaller PRs, contrary to what the original issue suggests:

Once unit tests are ported, we move to integration tests. This might need to require a one-shot PR because our loadFixture utility is used in various packages.

I achieve this by using TypeScript's Project References feature. Basically, it involves splitting the whole monorepo into smaller TypeScript "projects". Each tsconfig.json file with composite: true represents one TypeScript "project".

Note

A "TypeScript project" and a "PNPM monorepo workspace package" are different concepts.

  • In TypeScript, a tsconfig.json represents a TypeScript project, and different projects are connected via the references field in tsconfig.json.
  • In PNPM, a package.json represents a workspace package, and different packages are connected via dependencies/devDependencies in package.json.
  • Usually, people put one or more TypeScript projects in a package. For example, Vite's official React TypeScript template is one package (package.json) with two TypeScript projects (tsconfig.app.json and tsconfig.node.json).

In this PR, I've added/modified the following TypeScript projects:

New projects:

  • .github/scripts/tsconfig.json — for .github/scripts/
  • scripts/tsconfig.json — for repo-level scripts/ (replaces the old scripts/jsconfig.json)
  • packages/integrations/react/tsconfig.test.json — for React integration tests, and it imports and uses the loadFixture utility from another project.

Modified projects (now composite: true with references):

  • packages/astro/tsconfig.json
  • packages/astro/tsconfig.test.json (where loadFixture utility is defined)
  • packages/astro/test/types/tsconfig.json

The references graph between these projects looks like this:

flowchart BT
    subgraph astro["packages/astro"]
        astroSrc["tsconfig.json<br/><i>(src)</i>"]
        astroTest["tsconfig.test.json<br/><i>(unit tests)</i>"]
        astroTypes["test/types/tsconfig.json<br/><i>(type tests)</i>"]
    end

    subgraph react["packages/integrations/react"]
        reactTest["tsconfig.test.json<br/><i>(integration tests)</i>"]
    end

    subgraph scripts["scripts/"]
        scriptsCfg["tsconfig.json"]
    end

    subgraph ghScripts[".github/scripts/"]
        ghScriptsCfg["tsconfig.json"]
    end

    astroTest -- references --> astroSrc
    astroTest -- references --> scriptsCfg
    reactTest -- references --> astroTest
    scriptsCfg -- references --> ghScriptsCfg
Loading

tsc --build instead of tsc --project

I've changed some scripts from tsc --project to tsc --build. This is required by the Project References feature: tsc --project only type-checks the single tsconfig.json you point it at and ignores any references it declares, whereas tsc --build walks the references graph and type-checks each referenced project in dependency order, reusing .tsbuildinfo caches for incremental rebuilds.

I've also added a repo-level typecheck:tests script (pnpm -r typecheck:tests) so that CI runs the type check across all workspace packages that define one, rather than only packages/astro/. For now, only two packages has typecheck:tests.

Testing

CI all green

Docs

No docs needed.

@ocavue ocavue marked this pull request as draft April 14, 2026 15:58
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 14, 2026

⚠️ No Changeset found

Latest commit: 658be10

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@github-actions github-actions Bot added 🚨 action Modifies GitHub Actions pkg: react Related to React (scope) pkg: integration Related to any renderer integration (scope) pkg: astro Related to the core `astro` package (scope) labels Apr 14, 2026
@ocavue ocavue marked this pull request as ready for review April 14, 2026 16:23
Comment thread packages/integrations/react/test/react-component.test.ts
Copy link
Copy Markdown
Contributor

@matthewp matthewp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not familiar with project references but the idea seems solid to me.

"declarationDir": "./dist",
"outDir": "./dist",
"jsx": "preserve",
"tsBuildInfoFile": "${configDir}/dist/._cache/tsconfig/tsbuildinfo.json",
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Notice that ._cache won't be published to NPM.

https://docs.npmjs.com/cli/v10/configuring-npm/package-json#files

I put the tsbuildinfo.json under dist/ so that developers can more easily remove the dist/ to clean both the built .d.ts files and the cached index file tsbuildinfo.json.

line: event.data.line,
column: event.data.column,
isSuite: event.data.details.type !== 'test',
isSuite: event.data.details.type === 'suite',
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I edited this line because somehow astro is still using @types/node v18, which doesn't contain 'test' for this field.

cat node_modules/@types/node/package.json | jq '.version'
"18.19.130"

We should update @types/node, but that's out of the scope of this PR.

@florian-lefebvre florian-lefebvre self-requested a review April 14, 2026 18:21
Copy link
Copy Markdown
Member

@florian-lefebvre florian-lefebvre left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I'm misunderstanding but it looks a bit odd that the test tsconfig for the react integration needs a reference to the astro test tsconfig.

In my original WIP PR (as POC), I had a root test tsconfig that all tests tsconfigs would extend. Do you have any opinions on whether project references are better than this?

To be clear I'm not against! Project references are useful, I'm just trying to see if it's the best solution

import { type FontProviderSchema, FontFamilySchema } from '../../src/assets/fonts/config.js';
import type { FontProvider, FontFamily } from '../../src/assets/fonts/types.js';
import type { CacheSchema, RouteRulesSchema } from '../../src/core/cache/config.js';
import { type FontProviderSchema, FontFamilySchema } from '../../dist/assets/fonts/config.js';
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, we were using src because zod could cause some issues. If that's not the case anymore, I'm glad!

Comment thread packages/astro/tsconfig.json Outdated
{
"extends": "../../tsconfig.base.json",
"include": ["src", "dev-only.d.ts"],
"include": ["src", "dev-only.d.ts", "package.json"],
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this needed?

Copy link
Copy Markdown
Contributor Author

@ocavue ocavue Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're importing package.json in packages/astro/test/units/cli/index.test.ts:8, thus we need to import package.json in one of the tsconfig.json file.

Since we only use package.json in tests, I will move this "include": ["package.json"] from packages/astro/tsconfig.json to packages/astro/tsconfig.test.json.

update: done in be3f770

@ematipico
Copy link
Copy Markdown
Member

.github/scripts/tsconfig.json — for .github/scripts/

I don't think we want that though, otherwise we would start getting type check issues for JS files. Maybe what we want is to port them to TS in a later stage.

Can we revert it?

@ocavue
Copy link
Copy Markdown
Contributor Author

ocavue commented Apr 15, 2026

.github/scripts/tsconfig.json — for .github/scripts/

I don't think we want that though, otherwise we would start getting type check issues for JS files. Maybe what we want is to port them to TS in a later stage.

@ematipico

First, let me show the import graph between these files:

Step 1: packages/integrations/react/tsconfig.test.jsonpackages/astro/tsconfig.test.json

packages/integrations/react/test/react-19-preloads.test.ts:3

import { loadFixture } from '../../../astro/test/test-utils.js';

Step 2: packages/astro/tsconfig.test.jsonscripts/tsconfig.json

packages/astro/test/test-utils.js:8

import { CILogger } from '../../../scripts/testing/github-utils.js';

Step 3: scripts/tsconfig.json.github/scripts/tsconfig.json

scripts/testing/github-utils.js:5

import { setSummary } from '../../.github/scripts/utils.mjs';

Summary

packages/integrations/react/test/react-component.test.ts (owned by `packages/integrations/react/tsconfig.test.json`)
  └─▶ packages/astro/test/test-utils.js (owned by `packages/astro/tsconfig.test.json`)
        └─▶ scripts/testing/github-utils.js (owned by `scripts/tsconfig.json`)
              └─▶ .github/scripts/utils.mjs (owned by `.github/scripts/tsconfig.json`)

Since .../react-component.test.ts imports .github/scripts/utils.mjs, and we want to type check the first file, we need to know the types of all files in this chain, including those .mjs files under .github/scripts. That's why I added a tsconfig.json under .github/scripts.

we would start getting type check issues for JS files

Yes. In this PR, I'm already type checking these JS files and fixed one issue I found, as shown in this comment.

Maybe what we want is to port them to TS in a later stage.

Porting these scripts from JS to TS won't eliminate the need for adding a tsconfig.json, as long as the import graph shown above stays the same.

Can we revert it?

If I simply delete .github/scripts/tsconfig.json, the CI type checking will fail.


I admit it's weird to put a tsconfig.json file under .github/scripts; it's just the easiest path I could take to make this PR green.

Taking a step back, I think the better approach is to ask two questions:

  • (A): In Step 2, why do we want to import an API from a script in a unit/integration test? Can we cut this dependency by, for example, copy-pasting the CILogger implementation or moving it into a package?

    If we don't need Step 2, then we don't need scripts/tsconfig.json, and thus we don't need .github/scripts/tsconfig.json. These scripts are still being just scripts are they are not part of the TypeScript type checking graph.

  • (B): In Step 3, why do we put scripts in two different locations (scripts/ and .github/scripts/)? Can we just put all of them under scripts/?

    If we merge .github/scripts/ and scripts/, then we don't need both scripts/tsconfig.json and .github/scripts/tsconfig.json. We can just keep one scripts/tsconfig.json file. We're still type checking these scripts, we just reduce a tsconfig.json file.

What do you think? (A) seems a bit complex, and (B) seems easier.

@ematipico
Copy link
Copy Markdown
Member

ematipico commented Apr 15, 2026

@ocavue are you up for some cleanup once the tests are ported to TypeScript? As far as I can tell, you created this so that the porting doesn't have to be massive, which I really appreciate. However, I suppose once we're done, we can go back to a less complex setup, correct?

@ocavue
Copy link
Copy Markdown
Contributor Author

ocavue commented Apr 15, 2026

@florian-lefebvre

it looks a bit odd that the test tsconfig for the react integration needs a reference to the astro test tsconfig.

In the current PR, packages/astro/test/test-utils.js is owned by packages/astro/tsconfig.test.json. Since the React integration tests need to use .../test-utils.js, they need a reference to packages/astro/tsconfig.test.json.

I guess this feels odd because packages/astro/tsconfig.test.json also owns packages/astro/test/units/*.test.ts, and those .../units/*.test.ts files are unrelated to the React integration.

I can add another tsconfig.json file to make the reference graph clearer:

  • packages/astro/tsconfig.test-utils.json (new) includes files like .../test-utils.js
  • packages/astro/tsconfig.test.json (already present in the current PR) includes all *.test.ts files under packages/astro/. It would need a reference to .../tsconfig.test-utils.json
  • packages/integrations/react/tsconfig.test.json (already present in the current PR) includes all *.test.ts files under packages/integrations/react/. It would also need a reference to .../tsconfig.test-utils.json

Would you prefer this approach? It is a trade-off between (A) having fewer tsconfig.json files, and (B) having a more accurate reference graph. I'm okey with both directions.


I had a root test tsconfig that all tests tsconfigs would extend. Do you have any opinions on whether project references are better than this?

In general, putting code from multiple PNPM packages (package.json) into the same TypeScript project (tsconfig.json) is not a good idea, for two reasons:

  1. Performance. With project references, TypeScript can catch changes at the right boundary. When you change an upstream TypeScript file and run tsc --build, it does not need to rebuild the entire monorepo. It only needs to rebuild the affected layer and its dependents.
  2. Preventing global type conflicts. For example, the browser and Node.js versions of setTimeout have different type definitions: the browser version returns a number, while the Node.js version returns an object. Vue also used to register global JSX types until fairly recently, which could cause type conflicts if you used Vue and React in the same TypeScript project. If we split them into separate projects with project references, we avoid this kind of problem.

@ocavue
Copy link
Copy Markdown
Contributor Author

ocavue commented Apr 15, 2026

are you up for some cleanup once the tests are ported to TypeScript

@ematipico Yes, I can do the cleanup afterward. The astro repo is slow when I open it locally in VSCode for features like jump definition, and I believe I can improve this as well with a better and less complex TypeScript setup.

Copy link
Copy Markdown
Member

@ematipico ematipico left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Waiting for @florian-lefebvre approval before merge

@ematipico ematipico merged commit 5ed467f into withastro:main Apr 16, 2026
25 checks passed
ematipico added a commit that referenced this pull request Apr 21, 2026
* fix(cloudflare): exclude starlight from SSR dep optimization (#16151)

* [ci] release (#16152)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* refactor(mdx): more unit tests, less integrations (#16158)

* fix: stop CSS traversal at page boundaries (#16116)

* [ci] format

* fix(content): clear stale asset imports on content collection entry update (#16124)

* fix(content): clear stale asset imports on content collection entry update

* fix lint errors

* fix(core): append assetQueryParams to inter-chunk JS imports (#15964) (#16110)

* fix(core): append assetQueryParams to inter-chunk JS imports (#15964)

* Add changeset for inter-chunk skew protection fix

* [ci] format

* chore: move unit tests to ts (#16157)

* [ci] format

* fix(vercel): edge middleware next() drops HTTP method and body (#16170)

* fix(vercel): edge middleware next() drops HTTP method and body

* fix: conditional and format-sensitive

* test: don't use tmp fixtures (#16177)

* fix(preact): pre-optimize @preact/signals to prevent dev reload flakiness (#16180)

* Preserve head metadata in Cloudflare dev rendering (#16161)

* Update dev head metadata for non-runnable pipeline

* Refine non-runnable component metadata loading

* Load component metadata in non-runnable dev

* Remove unused export for virtual component metadata constant

* Add docs for virtual component metadata module

* fix(cloudflare): ensure HMR works when `prerenderEnvironment` is set to 'node' (#16162)

Co-authored-by: Matthew Phillips <matthewphillips@cloudflare.com>

* Include injected routes when determining whether renderers are needed in SSR builds (#16178)

* fix(astro): Fix `isHTMLString` check failing in multi-realm environments (#16142)

* fix(container): don't escape slot HTML in renderToString during build

* performance issue

* oops

* apply Erika's suggestion

* use `isHTMLString` within `markHTMLString`

* simplify test fixtures

* format

* remove the no longer used `Symbol.toStringTag` from `HTMLString`

* rename to `htmlStringSymbol`

* update changeset

* Apply suggestion from @ematipico

---------

Co-authored-by: Emanuele Stoppa <my.burning@gmail.com>

* fix(underscore-redirects): respect trailingSlash config in redirects (#16034)

Co-authored-by: astrobot-houston <fred+astrobot@astro.build>

* [ci] format

* [ci] release (#16159)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Fix periods in URLs with trailing slashes causing 404s in dev server (#16154)

* Fix periods in URLs with trailing slashes causing 404s in dev server (#16140)

Pages with dots in their filenames (e.g. `hello.world.astro`) were incorrectly
treated as file-extension paths, forcing `trailingSlash: 'never'` regardless of
user config. Only endpoints with file extensions should force this behavior.

* ci: retry flaky e2e tests

* ci: retry flaky e2e tests

---------

Co-authored-by: Matthew Phillips <matthewphillips@cloudflare.com>

* docs(language-tools): mention js/ts settings namespace in vscode (#16167)

* docs(language-tools): mention js/ts settings namespace in vscode

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Matthew Phillips <matthew@matthewphillips.info>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* fix(e2e): remove bogus Node.js import breaking actions-blog tests (#16183)

A spurious import of createLoggerFromFlags from cli/flags.ts was added
to the client-side PostComment.tsx component via a sync commit, breaking
hydration and causing Comment and Logout tests to fail.

* test: increase testing coverage (#16189)

* [ci] format

* Preserve Cloudflare miniflare instance across dev server config restarts (#16059)

* fix: preserve viteServer.restart wrapper chain for Cloudflare adapter

* add changeset

* fix: use Vite in-place restart for config changes to preserve Cloudflare miniflare instance

* use vite.resolveConfig to get a proper ResolvedConfig instead of patching inlineConfig

* fix watcher listener accumulation, null-check hot.send, move restartInFlight to finally, add tests

* fix port drift on restart by passing current httpServer port to createVite

* remove non-actionable CSP dev warning

* merge main, fix restart tests to use static fixture dir

* fix(astro): remove unused re-exports causing Vite build warning (#16197)

* fix(astro): remove unused re-exports causing Vite build warning (#16188)

* chore: add changeset

---------

Co-authored-by: astrobot-houston <fred+astrobot@astro.build>

* Fix trailingSlash for extensionless endpoints in static builds (#16193)

* Unblock smoke tests: exclude astro-og-canvas@0.11.0 from minimumReleaseAge (#16211)

* Exclude astro-og-canvas@0.11.0 from minimumReleaseAge

* Exclude @types/node@24.12.2 from minimumReleaseAge

* feat: erasableSyntaxOnly (#15719)

* fix: react 19 ssr aito injection preload link (#16224)

* feat: integration test of react 19 auto injection preload link

* fix: when created preload in link, remove it and fix it for tag can be read

* feat: changeset file

* fix: pnpm-lock file

* fix: regexp for passing eslint

* [ci] format

* [ci] release (#16182)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* fix(cloudflare): exclude queue consumers from prerender worker (#16225)

* fix(cloudflare): exclude queue consumers from prerender worker config

The prerender worker's config callback was spreading the entire
entryWorkerConfig, including queues.consumers. When Miniflare sees
two workers both registered as consumers of the same queue, it rejects
with ERR_MULTIPLE_CONSUMERS. The prerender worker only renders static
HTML and has no need for queue consumer registrations.

This fix destructures queues from the entry worker config and only
preserves queue producers (bindings) in the prerender worker config.

Closes #16199

Co-Authored-By: Tadao <tadao@travisfixes.com>

* chore: update pnpm lockfile

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Tadao <tadao@travisfixes.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor: port tests to ts (#16243)

* fix: stream Fragment sync siblings before async children resolve (#16239)

* [ci] format

* Port 8 unit test files from JavaScript to TypeScript (#16249)

Part of the test-to-TypeScript migration (#16241).

Ported files:
- app/dev-url-construction.test
- app/headers.test
- app/url-attribute-xss.test
- assets/image-layout.test
- render/escape.test
- render/hydration.test
- routing/origin-pathname.test
- routing/routing-helpers.test

Removed @ts-check directives (redundant in .ts files), converted
JSDoc type annotations to native TypeScript where needed, and added
minimal type assertions for test mocks. typecheck:tests and both
test:unit:ts / test:unit:js pass.

* chore: adapt code to upstream deprecation (#16192)

* chore: adapt code to upstream deprecation

Signed-off-by: Alexander Niebuhr <45965090+alexanderniebuhr@users.noreply.github.com>

* fix remove ununsed import

Signed-off-by: Alexander Niebuhr <45965090+alexanderniebuhr@users.noreply.github.com>

* Apply suggestion from @alexanderniebuhr

* Apply suggestion from @alexanderniebuhr

* Apply suggestion from @alexanderniebuhr

* Apply suggestion from @alexanderniebuhr

---------

Signed-off-by: Alexander Niebuhr <45965090+alexanderniebuhr@users.noreply.github.com>

* refactor: migrate blog template to use new astro font loading api (#16128)

Co-authored-by: Florian Lefebvre <contact@florian-lefebvre.dev>

* [ci] format

* fix(assets): resolve Picture TDZ error when combined with content render() (#16171)

* fix(assets): resolve Picture TDZ error when combined with content render (#16036)

Introduce an internal virtual module (virtual:astro-get-image) that exports
only getImage and imageConfig without any Astro component references. The
content runtime now imports from this narrower module instead of astro:assets,
breaking the circular initialization dependency that caused a TDZ
ReferenceError when prerendered pages using <Picture> were bundled in the
same chunk as content collection render() calls.

* chore: add changeset for Picture TDZ fix

* fix: rename virtual module to virtual:astro:get-image

Use colon separator (virtual:astro:*) instead of hyphen so the module
matches existing optimizeDeps.exclude patterns in both Astro core and
the Cloudflare adapter. The hyphenated name was not excluded from
Vite's dependency optimizer, causing esbuild to fail resolving it.

* fix: add virtual:astro:get-image to dev-only.d.ts and remove ts-expect-error

Add type declaration for the virtual:astro:get-image module so TypeScript
recognizes the import without needing a @ts-expect-error directive.

* Apply suggestion from @alexanderniebuhr

---------

Co-authored-by: uni <uni@vmi3197945.contaboserver.net>

* Revives UnoCSS in dev mode when used with the client router (#16242)

* [ci] format

* chore: inline dlv dependency (#16259)

* chore: inline dlv dependency

* format

* Create three-kids-camp.md

* fix: use `for...of`

* [ci] format

* chore: upgrade biome (#16246)

* [ci] format

* [ci] release (#16244)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* test: port 16 routing unit tests to TypeScript (#16266)

* [ci] format

* refactor(core): rename logger internal types (#16271)

Co-authored-by: Princesseuh <3019731+Princesseuh@users.noreply.github.com>

* Bump @qwik.dev/partytown to v0.13.2 (#16265)

Co-authored-by: Emanuele Stoppa <my.burning@gmail.com>

* Consolidate inline script escaping into shared utility (#16303)

* Consolidate inline script escaping into shared stringifyForScript utility

Unify the two separate script-embedding escape approaches (defineScriptVars
and safeJsonStringify) into a single stringifyForScript function in escape.ts.
Uses a comprehensive < escape strategy rather than pattern-matching specific
tag sequences, covering all ETAGO variants in one pass.

* Add changeset

* Update changeset description

* Fix stringifyForScript to handle undefined values

* Update astro-directives test assertion for new escape sequence

* Skip actions server-output validation when an adapter is configured (#16202)

* Fix .svelte files in node_modules with Cloudflare prerenderEnvironment: node (#16210)

* fix(cloudflare,svelte): resolve .svelte files in node_modules with prerenderEnvironment: 'node'

Packages that ship .svelte files (e.g. bits-ui) failed with 'Unknown file
extension .svelte' when using the Cloudflare adapter with
prerenderEnvironment: 'node'. Two issues caused this:

1. The cf-externals plugin set top-level ssr.noExternal = true, which
   caused vitefu's crawlFrameworkPkgs to return an empty noExternal list
   (it assumed everything was already noExternal). This only applied to
   the ssr environment in Vite 6, leaving the prerender environment
   without the needed noExternal entries.

2. The createNodePrerenderPlugin disabled dep optimization entirely for
   the prerender environment (noDiscovery: true, include: []).

The fix removes ssr.noExternal = true from cf-externals, removes the dep
optimization override, and has @astrojs/svelte use crawlFrameworkPkgs to
discover svelte packages and add them to resolve.noExternal for all
server environments via configEnvironment.

* add changeset

* fix: use fileURLToPath for cross-platform root path

* test: add response body to assertion error for CI debugging

* fix: rename fake-svelte-pkg/dist to src to avoid .gitignore exclusion

* [ci] format

* fix: avoid full-reload in scss modules (#14924)

* fix: avoid full-reload in scss modules

* fix: improve regex

* fix: ci

* test: add test for modules

* fix(hmr): prevent full-reload for SCSS/CSS module changes

SCSS and CSS module file changes were triggering unnecessary full page
reloads during development instead of applying HMR updates. This broke
the developer experience by losing component state and scroll position
on every style edit.

Root cause: In the `astro:hmr-reload` Vite plugin, style files (CSS,
SCSS, SASS, LESS, etc.) in the SSR module graph were correctly skipped
via a regex check, but the handler returned `undefined` instead of an
empty array. In Vite 6, returning `undefined` from a `hotUpdate` hook
means "I didn't handle this", causing Vite to propagate through the SSR
module graph to `.astro` importers. Since `.astro` pages have no HMR
boundary, this triggered a full page reload.

Changes to `vite-plugin-hmr-reload`:

- Extract `isStyleModule()` helper that checks both `mod.file` and
  `mod.id` (stripping query params like `?inline`, `?used`) against
  the style extension regex. This correctly identifies all style-related
  modules including CSS module variants.

- Return `[]` (empty array) when only style modules were encountered
  in the SSR environment. This tells Vite "handled, nothing to update
  in SSR", preventing the propagation chain that caused full reloads.
  The client environment handles CSS HMR natively through
  framework-specific HMR boundaries (Preact, React, Vue, etc.).

Changes to e2e test fixtures:

- Update the SCSS module HMR test to use a Preact component with
  `client:load` instead of a pure server-rendered Astro page. This
  matches the real-world scenario from the original bug report (issue
  #14869) where Preact + SCSS modules triggered full reloads. CSS
  module HMR requires a client-side framework to re-render components
  with updated class name hashes — pure SSR pages cannot hot-update
  CSS module class names without a full reload.

Closes #14869

* fix(hmr): clarify comment about CSS HMR working for all pages

The previous comment suggested CSS HMR only worked through
framework-specific boundaries. Vite's built-in style update
mechanism handles it for all pages, with or without framework
components (covered by the scss-external test).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* update pnpm-lock.yaml

* chore: add changeset for SCSS/CSS module HMR fix

---------

Co-authored-by: Erika <3019731+Princesseuh@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Matthew Phillips <matthew@matthewphillips.info>
Co-authored-by: Matthew Phillips <matthewphillips@cloudflare.com>

* [ci] release (#16281)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* fix(dev): preserve --port flag after Vite-triggered server restart (#16311)

* fix(dev): preserve custom port across Vite-triggered server restarts

* chore: add changeset for --port fix

---------

Co-authored-by: astrobot-houston <fred+astrobot@astro.build>

* fix: add checkout step back to mergeability check (#15783)

* refactor: unit tests that are integration tests (#16280)

* refactor: unit tests that are integration tests

* refactor(test): move teardown.js to TypeScript

* refactor(test): move assets/fonts/utils.js to TypeScript

* refactor(test): move i18n/test-helpers.js to TypeScript

* refactor(test): move app/test-helpers.js to TypeScript

* refactor(test): move routing/test-helpers.js to TypeScript

* refactor(test): add TypeScript version of test-utils (JS kept for unconverted consumers)

* refactor(test): add TypeScript versions of mocks and build/test-helpers (JS kept for unconverted consumers)

* refactor(test): convert Group A batch 1 tests to TypeScript

* refactor(test): move action-status.test.js to TypeScript

* refactor(test): move actions-app.test.js to TypeScript

* refactor(test): move app/csrf.test.js to TypeScript

* refactor(test): move app/astro-response.test.js to TypeScript

* refactor(test): move app/astro-attrs.test.js to TypeScript

* refactor(test): move app/encoded-backslash-bypass.test.js to TypeScript

* refactor(test): move app/double-slash-bypass.test.js to TypeScript

* refactor(test): move app/error-pages.test.js to TypeScript

* refactor(test): move app/locals.test.js to TypeScript

* refactor(test): move app/response.test.js to TypeScript

* refactor(test): move app/trailing-slash.test.js to TypeScript

* refactor(test): move assets/getImage.test.js to TypeScript

* refactor(test): move assets/fonts/infra.test.js to TypeScript

* refactor(test): move assets/fonts/core.test.js to TypeScript

* refactor(test): move noop.test.js to TypeScript

* refactor(test): move call-middleware.test.js to TypeScript

* refactor(test): move preserve-build-client-dir.test.js to TypeScript

* refactor(test): move sequence.test.js to TypeScript

* refactor(test): move rendering.test.js to TypeScript

* refactor(test): move server-islands.test.js to TypeScript

* refactor(test): move sec-fetch.test.js to TypeScript

* refactor(test): move middleware-app.test.js to TypeScript

* refactor(test): move generate.test.js to TypeScript

* refactor(test): move router.test.js to TypeScript

* refactor(test): move i18n-middleware.test.js to TypeScript

* refactor(test): move i18n-app.test.js to TypeScript

* refactor(test): move head-injection-app.test.js to TypeScript

* refactor(test): move fallback.test.js to TypeScript

* refactor(test): move render-context.test.js to TypeScript

* refactor(test): move i18n-routing-static.test.js to TypeScript

* refactor(test): move i18n-static-build.test.js to TypeScript

* refactor(test): move paginate.test.js to TypeScript

* refactor(test): move context-helpers.test.js to TypeScript

* refactor(test): move manual-routing.test.js to TypeScript

* refactor(test): move html-primitives.test.js to TypeScript

* refactor(test): move manual-middleware.test.js to TypeScript

* refactor(test): move class-list-and-style.test.js to TypeScript

* refactor(test): move create-manifest.test.js to TypeScript

* refactor(test): move api.test.js to TypeScript

* refactor(test): move manifest.test.js to TypeScript

* refactor(test): move hooks.test.js to TypeScript

* refactor(test): move route-matching.test.js to TypeScript

* refactor(test): move get-params.test.js to TypeScript

* refactor(test): move render.test.js to TypeScript

* refactor(test): move static-build.test.js to TypeScript

* refactor(test): move rewrite-app.test.js to TypeScript

* refactor(test): move rewrite-validation.test.js to TypeScript

* refactor(test): move params-encoding.test.js to TypeScript

* refactor(test): delete old JS helpers and fix remaining .js import references to .ts

* refactor(test): update test:unit script for all-TypeScript unit tests

* refactor(test): remove dev-hydration test, extract integration-test-helpers for createRequestAndResponse

* fix(test): update unit tests for Logger→AstroLogger rename and API changes from main

* refactor: address linting

* refactor(test): replace fixable any types with proper types across unit tests

* fix tests

* Surface console output from workerd during Cloudflare prerendering (#16307)

* Surface console output from workerd during prerendering

* Filter out HTTP request logs from prerender server output

* Narrow log filter to only match internal __astro_ request paths

* Fix lint: disable no-control-regex for ANSI pattern, use non-capturing group

* fix(core): svg via f=svg (#16316)

* Fix static asset error responses including immutable cache headers (#16319)

* fix(node): prevent cache poisoning from conditional request errors

Move immutable cache header from send's 'headers' event to 'stream'
event so it is only set on successful responses. The 'headers' event
fires before precondition checks (If-Match, If-Unmodified-Since),
which meant error responses (412) were sent with
Cache-Control: public, max-age=31536000, immutable — allowing an
attacker to poison CDN caches with a single malformed If-Match request.

Also propagate the real HTTP status from send's error object instead
of always returning 500.

* add changeset

* update changeset wording

* Use redirect: manual in Cloudflare image binding transform (#16320)

* Use redirect: 'manual' in cloudflare image-binding-transform fetch

* Fix unused parameter lint error

* test: add unit test for dlv util (#16262)

* fix: netrify image validation (#16027)

* fix(netlify): enforce validation for remote image dimensions

* test(netlify): add regression test for missing image dimensions

* fix: astro.config.mjs file

* feat: changeset

* delete: unnecessary file

* fix: refactor to use loadFixture and project test infrastructure

* fix: update changeset

* fix: changeset explanation and refactor test code

* refactor(assets): move verifyOptions export to internal to avoid hydration regressions

* [ci] format

* Add nightly Semgrep security scanning workflow (#16205)

Co-authored-by: Felix Schneider <99918022+trueberryless@users.noreply.github.com>
Co-authored-by: Emanuele Stoppa <estoppa@cloudflare.com>

* ci: cron job semgrep (#16336)

* Update github-actions (#16146)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* [ci] release (#16314)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* chore: add rule for detecting incorrect imports (#16343)

* refactor: migrate react tests to typescript (#16318)

* refactor: migrate custom 404 tests to typescript (#16350)

* refactor: migrate vue tests to typescript (#16349)

* refactor: migrate vue tests to typescript

* fix import path

* chore: trigger ci

* fix(deps): update language tools (#16230)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>

* chore: fix lock file (#16352)

* refactor: migrate sitemap tests to typescript (#16353)

* fix: stabilize client build output filenames across runs (#16348)

Co-authored-by: Emanuele Stoppa <my.burning@gmail.com>

* changeset: add changeset for telemetry changes (#16257)

* refactor: migrate alpinejs tests to typescript (#16354)

* fix(core): fix append of assetQueryParams to inter-chunk JS *dynamic* imports (#16258) (#16282)

Co-authored-by: Leif Marcus <2342671+leifmarcus@users.noreply.github.com>

* refactor: migrate markdoc tests to typescript (#16355)

* refactor: migrate astro-rss tests to typescript (#16357)

* fix(dev): Passing on allowedDomains configuration. (#16317)

Co-authored-by: Emanuele Stoppa <my.burning@gmail.com>

* refactor: migrate svelte tests to typescript (#16351)

* chore: remove lone fixtures (#16363)

* fix(core): clean chunk name (#16367)

* fix(core): clean chunk name

* linting and tests

* refactor: migrate telemetry tests to typescript (#16358)

* refactor: migrate telemetry tests to typescript

* format

* remove types

* format

* just use any

* format

* fix typecheck

* chore: trigger ci

* refactor(astro): migrate error tests to typescript (#16377)

* refactor(astro): migrate error tests to typescript

* chore: trigger ci

* chore: trigger ci

* refactor(upgrade): migrate tests to typescript (#16372)

* refactor(upgrade): migrate tests to typescript

* add ShellFunction type

* fix(netlify): correct test describe signature (#16371)

* chore: reduce fixtures by merging them (#16364)

* feat: tweak issue auto-triage (#16284)

* [ci] format

* chore: absorb tests into others (#16365)

* perf(core): cache crawl result (#16381)

* refactor(astro): correct Fixture type signatures in test-utils (#16380)

* refactor(astro): correct Fixture type signatures in test-utils

* chore: trigger ci

* chore: trigger ci

* Improves Vue scoped style handling in DEV mode during client router navigation. (#16379)

* Improves Vue scoped style handling in DEV mode during client router navigation.

* invert test

* streamline Vue scoped style detection in DEV mode

* [ci] format

* refactor(mdx): migrate tests to typescript (#16359)

* refactor(vercel): migrate tests to typescript (#16360)

* chore(deps): update `@types/node` (#16362)

* [ci] release (#16356)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* refactor(node): migrate unit tests to typescript (#16388)

* refactor(remark): migrate tests to typescript (#16392)

* fix(i18n): respect trailingSlash config in domain routing (#16022)

Co-authored-by: ematipico <602478+ematipico@users.noreply.github.com>

* refactor(mdx): add SpyIntegrationLogger for test (#16384)

* refactor: remove PRERENDER env variable in tests (#16391)

* refactor(underscore-redirects): migrate tests to typescript (#16374)

* refactor(cloudflare): migrate tests to typescript (#16378)

* refactor(astro): migrate css tests to typescript (#16393)

* refactor(internal-helpers): migrate tests to typescript (#16401)

* refactor(netlify): migrate tests to typescript (#16395)

* refactor(astro): migrate ssr tests to typescript (#16394)

* refactor(node): migrate tests to typescript (#16376)

* refactor(create-astro): migrate tests to typescript (#16400)

* refactor(db): migrate tests to typescript (#16403)

* refactor(astro): migrate e2e tests to typescript (#16402)

* refactor(astro): migrate 39 tests to typescript (#16405)

* refactor(astro): migrate 9 tests to typescript (#16410)

* [ci] format

* refactor(astro): migrate dev tests to typescript (#16408)

* refactor(astro): migrate content tests to typescript (#16409)

* refactor(astro): migrate 10 tests to typescript (#16411)

* refactor(astro): migrate 19 tests to typescript (#16414)

* refactor(vercel): remove duplicated test files (#16416)

* refactor(astro): migrate core-image tests to typescript (#16413)

* refactor(astro): migrate 4 tests to typescript (#16427)

---------

Signed-off-by: Alexander Niebuhr <45965090+alexanderniebuhr@users.noreply.github.com>
Co-authored-by: Matthew Phillips <matthewphillips@cloudflare.com>
Co-authored-by: Houston (Bot) <108291165+astrobot-houston@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Martin DONADIEU <martin.donadieu@hey.com>
Co-authored-by: Martin DONADIEU <ematipico@users.noreply.github.com>
Co-authored-by: dataCenter430 <161712630+dataCenter430@users.noreply.github.com>
Co-authored-by: tmimmanuel <14046872+tmimmanuel@users.noreply.github.com>
Co-authored-by: tmimmanuel <matthewp@users.noreply.github.com>
Co-authored-by: BitToby <218712309+bittoby@users.noreply.github.com>
Co-authored-by: Rafael Yasuhide Sudo <rururux@gmail.com>
Co-authored-by: Alexander Niebuhr <45965090+alexanderniebuhr@users.noreply.github.com>
Co-authored-by: astrobot-houston <fred+astrobot@astro.build>
Co-authored-by: Alexander Niebuhr <alexanderniebuhr@users.noreply.github.com>
Co-authored-by: Desel72 <pedroluiscolmenares722@gmail.com>
Co-authored-by: Misrilal <106655807+Misrilal-Sah@users.noreply.github.com>
Co-authored-by: Matthew Phillips <matthew@matthewphillips.info>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Schahin <schahin@rouhanizadeh.de>
Co-authored-by: Florian Lefebvre <contact@florian-lefebvre.dev>
Co-authored-by: fkatsuhiro <113022468+fkatsuhiro@users.noreply.github.com>
Co-authored-by: travisBREAKS <148665997+travisbreaks@users.noreply.github.com>
Co-authored-by: Tadao <tadao@travisfixes.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Amar Reddy <20904126+AmarReddy4@users.noreply.github.com>
Co-authored-by: Alex Dombroski <alexdombroski1@gmail.com>
Co-authored-by: Alex Dombroski <florian-lefebvre@users.noreply.github.com>
Co-authored-by: uni <uni@vmi3197945.contaboserver.net>
Co-authored-by: Martin Trapp <94928215+martrapp@users.noreply.github.com>
Co-authored-by: Martin Trapp <martrapp@users.noreply.github.com>
Co-authored-by: Roman <dev@rman.dev>
Co-authored-by: barry <100205797+barry3406@users.noreply.github.com>
Co-authored-by: Princesseuh <3019731+Princesseuh@users.noreply.github.com>
Co-authored-by: ChrisLaRocque <larocque.christopher@gmail.com>
Co-authored-by: Aral Roca Gomez <aral-rg@hotmail.com>
Co-authored-by: Alejandro Romano <alejandro9r@gmail.com>
Co-authored-by: Ciaran Moran <iCiaran@users.noreply.github.com>
Co-authored-by: Fred K. Schott <622227+FredKSchott@users.noreply.github.com>
Co-authored-by: Felix Schneider <99918022+trueberryless@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: ocavue <ocavue@users.noreply.github.com>
Co-authored-by: James Murty <james@murty.co>
Co-authored-by: Leif Marcus <2342671+leifmarcus@users.noreply.github.com>
Co-authored-by: Peter Philipp <info@das-peter.ch>
Co-authored-by: Erika <Princesseuh@users.noreply.github.com>
Co-authored-by: Maxim Slobodchikov <93232189+maximslo@users.noreply.github.com>
Co-authored-by: Mathieu Mafille <81818115+mathieumaf@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🚨 action Modifies GitHub Actions pkg: astro Related to the core `astro` package (scope) pkg: integration Related to any renderer integration (scope) pkg: react Related to React (scope)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants