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
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
node_modules
.DS_Store
.env
.output
.nitro
.tanstack
26 changes: 26 additions & 0 deletions e2e-tests/test-applications/tanstack-start-test-app/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "tanstack-start-test-app",
"private": true,
"type": "module",
"scripts": {
"dev": "vite dev",
"build": "vite build",
"start": "node .output/server/index.mjs"
},
"dependencies": {
"@tanstack/react-router": "^1.132.0",
"@tanstack/react-start": "^1.132.0",
"@tanstack/router-plugin": "^1.132.0",
"nitro": "latest",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"vite-tsconfig-paths": "^6.0.2"
},
"devDependencies": {
"@types/react": "^19.2.0",
"@types/react-dom": "^19.2.0",
"@vitejs/plugin-react": "^5.0.4",
"typescript": "^5.7.2",
"vite": "^7.1.7"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/* eslint-disable */

// @ts-nocheck

// noinspection JSUnusedGlobalSymbols

// This file was automatically generated by TanStack Router.
// You should NOT make any changes in this file as it will be overwritten.
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.

import { Route as rootRouteImport } from './routes/__root'
import { Route as IndexRouteImport } from './routes/index'

const IndexRoute = IndexRouteImport.update({
id: '/',
path: '/',
getParentRoute: () => rootRouteImport,
} as any)

export interface FileRoutesByFullPath {
'/': typeof IndexRoute
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
}
export interface FileRoutesById {
__root__: typeof rootRouteImport
'/': typeof IndexRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths: '/'
fileRoutesByTo: FileRoutesByTo
to: '/'
id: '__root__' | '/'
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
}

declare module '@tanstack/react-router' {
interface FileRoutesByPath {
'/': {
id: '/'
path: '/'
fullPath: '/'
preLoaderRoute: typeof IndexRouteImport
parentRoute: typeof rootRouteImport
}
}
}

const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
}
export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren)
._addFileTypes<FileRouteTypes>()

import type { getRouter } from './router.tsx'
import type { createStart } from '@tanstack/react-start'
declare module '@tanstack/react-start' {
interface Register {
ssr: true
router: Awaited<ReturnType<typeof getRouter>>
}
}
16 changes: 16 additions & 0 deletions e2e-tests/test-applications/tanstack-start-test-app/src/router.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { createRouter } from '@tanstack/react-router';
import { routeTree } from '@/routeTree.gen';

export function getRouter() {
return createRouter({
routeTree,
defaultPreload: 'intent',
scrollRestoration: true,
});
}

declare module '@tanstack/react-router' {
interface Register {
router: ReturnType<typeof getRouter>;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import {
HeadContent,
Outlet,
Scripts,
createRootRoute,
} from '@tanstack/react-router';

export const Route = createRootRoute({
head: () => ({
meta: [
{ charSet: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
],
}),
component: RootLayout,
});

function RootLayout() {
return (
<html>
<head>
<HeadContent />
</head>
<body>
<Outlet />
<Scripts />
</body>
</html>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { createFileRoute } from '@tanstack/react-router';

export const Route = createFileRoute('/')({
component: Home,
});

function Home() {
return <h1>Home</h1>;
}
20 changes: 20 additions & 0 deletions e2e-tests/test-applications/tanstack-start-test-app/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"include": ["**/*.ts", "**/*.tsx"],
"compilerOptions": {
"target": "ES2022",
"jsx": "react-jsx",
"module": "ESNext",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"types": ["vite/client"],
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": false,
"noEmit": true,
"skipLibCheck": true,
"strict": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}
14 changes: 14 additions & 0 deletions e2e-tests/test-applications/tanstack-start-test-app/vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { tanstackStart } from '@tanstack/react-start/plugin/vite';
import { defineConfig } from 'vite';
import tsConfigPaths from 'vite-tsconfig-paths';
import viteReact from '@vitejs/plugin-react';
import { nitro } from 'nitro/vite';

export default defineConfig({
plugins: [
tsConfigPaths({ projects: ['./tsconfig.json'] }),
tanstackStart({ srcDirectory: 'src' }),
viteReact(),
nitro(),
],
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Vite plugin order differs from documented recommendation

Medium Severity

The TanStack Start documentation specifies the Vite plugin order as tanstackStart(), nitro(), viteReact(), but here nitro() is placed after viteReact(). Vite plugin hook execution follows array order, so this incorrect ordering could cause build or runtime failures — potentially explaining the Linux CI failures mentioned in the PR discussion.

Fix in Cursor Fix in Web

});
34 changes: 34 additions & 0 deletions e2e-tests/tests/tanstack-start.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { execSync } from 'node:child_process';
import {
checkIfBuilds,
checkIfRunsOnDevMode,
checkIfRunsOnProdMode,
createIsolatedTestEnv,
} from '../utils';
import { afterAll, beforeAll, describe, test } from 'vitest';

describe('TanStack Start', () => {
const { projectDir, cleanup } = createIsolatedTestEnv(
'tanstack-start-test-app',
);

beforeAll(() => {
execSync('npm install', { cwd: projectDir, stdio: 'pipe' });
});

afterAll(() => {
cleanup();
});

test('builds successfully', async () => {
await checkIfBuilds(projectDir);
});

test('runs on dev mode correctly', async () => {
await checkIfRunsOnDevMode(projectDir, 'ready in');
});

test('runs on prod mode correctly', async () => {
await checkIfRunsOnProdMode(projectDir, 'Listening on');
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Dev test before prod test corrupts build output

High Severity

The runs on dev mode correctly test is ordered before runs on prod mode correctly, unlike all other e2e tests in this repo (e.g. angular-17, angular-19) which consistently run prod before dev. The prod test depends on the .output/server/index.mjs file created by the build step, but running vite dev (TanStack Start / Nitro dev server) in between likely cleans or overwrites the .output directory, causing the subsequent npm run start to fail. Swapping the order of the dev and prod tests to match the existing pattern would fix this.

Fix in Cursor Fix in Web

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

this is true for angular but not for other tests

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.

For me it was reproducible locally with yarn test:e2e:bin "tanstack-start"

It seems that tanstack doesn't like the TEST env variable. When this is added then it doesn't add the Listening on output as shown in the following image:

Screenshot 2026-02-20 at 16 40 35

Locally I fixed it by changing the following in the ProcessRunner (not sure though if this is a valid fix):

-    this.taskHandle = spawn(cmd, args, { cwd: opts?.cwd, stdio: 'pipe' });
+    const { TEST: _test, ...envWithoutTest } = process.env;
+    this.taskHandle = spawn(cmd, args, {
+      cwd: opts?.cwd,
+      stdio: 'pipe',
+      env: envWithoutTest,
+    });

Most possible culprit are these lines: https://github.com/h3js/srvx/blob/6ca26768ef4214513d34997f3346e6fcd57358b5/src/_utils.ts#L34-L36

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Nice thank you!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Now it works on macOS but still fails on linux lol

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.

I see a good step forward. I'll check out what is happening on linux as well

});
14 changes: 13 additions & 1 deletion e2e-tests/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,19 @@
opts?: {
cwd?: string;
debug?: boolean;
stripTestEnvVars?: boolean;
},
) {
this.taskHandle = spawn(cmd, args, { cwd: opts?.cwd, stdio: 'pipe' });
// undefined means spawn inherits process.env by default
const env = opts?.stripTestEnvVars
? (() => {
const e = { ...process.env };
delete e.TEST;
return e;
})()
: undefined;

this.taskHandle = spawn(cmd, args, { cwd: opts?.cwd, stdio: 'pipe', env });

if (opts?.debug) {
this.taskHandle.stdout?.pipe(process.stdout);
Expand Down Expand Up @@ -207,7 +217,7 @@
resolve(false);
} else {
reject(
new Error(

Check failure on line 220 in e2e-tests/utils/index.ts

View workflow job for this annotation

GitHub Actions / tanstack-start E2E Tests (ubuntu-latest)

tests/tanstack-start.test.ts > TanStack Start > runs on prod mode correctly

Error: Timeout waiting for output: Listening on. Got the following instead: > start > node .output/server/index.mjs ❯ Timeout._onTimeout utils/index.ts:220:13
`Timeout waiting for output: ${output}. Got the following instead: ${outputBuffer}`,
),
);
Expand Down Expand Up @@ -417,6 +427,7 @@
export async function checkIfBuilds(projectDir: string) {
const npmRunner = new ProcessRunner('npm', ['run', 'build'], {
cwd: projectDir,
stripTestEnvVars: true,
});

const builtSuccessfully = await npmRunner.waitForStatusCode(0, {
Expand Down Expand Up @@ -580,6 +591,7 @@
) {
const npmRunner = new ProcessRunner('npm', ['run', startCommand], {
cwd: projectDir,
stripTestEnvVars: true,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Dev mode missing stripTestEnvVars for TanStack Start

Medium Severity

The PR discussion identifies that TanStack Start doesn't like the TEST env variable. stripTestEnvVars: true was added to checkIfBuilds and checkIfRunsOnProdMode, but checkIfRunsOnDevMode was not updated — it still passes the TEST env var through to the spawned process. Since the TanStack Start dev server also uses the nitro() Vite plugin (see vite.config.ts), the dev mode test is likely affected by the same TEST env var issue.

Additional Locations (1)

Fix in Cursor Fix in Web

});

expect(
Expand Down
Loading