Skip to content
Merged
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
23 changes: 23 additions & 0 deletions e2e/react-start/import-protection-custom-config/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
node_modules
package-lock.json
yarn.lock
.DS_Store
.cache
.env
.vercel
.output
/build/
/api/
/server/build
/public/build
.env.sentry-build-plugin
/test-results/
/playwright-report/
/dist/
*.log
violations.*.json
error-build-result.json
error-dev-result.json
# The root .gitignore ignores `lib` globally (build output).
# Override here so src/lib/ test fixtures are tracked.
!src/lib/
31 changes: 31 additions & 0 deletions e2e/react-start/import-protection-custom-config/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "tanstack-react-start-e2e-import-protection-custom-config",
"private": true,
"sideEffects": false,
"type": "module",
"scripts": {
"dev": "vite dev --port 3000",
"dev:e2e": "vite dev",
"build": "vite build && tsc --noEmit",
"start": "pnpx srvx --prod -s ../client dist/server/server.js",
"test:e2e:mockMode": "rm -rf port*.txt; playwright test --project=chromium",
"test:e2e:errorMode": "rm -rf port*.txt; BEHAVIOR=error playwright test --project=chromium",
"test:e2e": "pnpm run test:e2e:mockMode && pnpm run test:e2e:errorMode"
},
"dependencies": {
"@tanstack/react-router": "workspace:^",
"@tanstack/react-start": "workspace:^",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"vite": "^7.3.1"
},
"devDependencies": {
"@playwright/test": "^1.50.1",
"@tanstack/router-e2e-utils": "workspace:^",
"@types/node": "^22.10.2",
"@types/react": "^19.0.8",
"@types/react-dom": "^19.0.3",
"srvx": "^0.11.7",
"typescript": "^5.7.2"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { defineConfig, devices } from '@playwright/test'
import { getTestServerPort } from '@tanstack/router-e2e-utils'
import { isErrorMode } from './tests/utils/isErrorMode'
import packageJson from './package.json' with { type: 'json' }

const PORT = await getTestServerPort(packageJson.name)
const baseURL = `http://localhost:${PORT}`

console.log('running in error mode:', isErrorMode.toString())

export default defineConfig({
testDir: './tests',
workers: 1,

globalSetup: isErrorMode
? './tests/error-mode.setup.ts'
: './tests/violations.setup.ts',

reporter: [['line']],

use: {
baseURL,
},

...(isErrorMode
? {}
: {
webServer: {
command: `rm -f webserver-build.log violations.build.json violations.dev.json && VITE_SERVER_PORT=${PORT} pnpm build > webserver-build.log 2>&1 && PORT=${PORT} VITE_SERVER_PORT=${PORT} pnpm start`,
url: baseURL,
reuseExistingServer: !process.env.CI,
stdout: 'pipe',
cwd: import.meta.dirname,
},
}),

projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
testMatch: isErrorMode ? 'error-mode.spec.ts' : 'custom-config.spec.ts',
},
],
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* This file contains browser-only APIs.
* It uses the custom `.frontend.ts` naming convention (NOT the default
* `.client.ts`) to mark it as client-only.
*
* The vite config denies `**\/*.frontend.*` in the server (SSR) environment.
*/
export function getBrowserInfo() {
return typeof window !== 'undefined' ? window.location.href : 'no-window'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* This file contains a secret that should only be available on the server.
* It uses the custom `.backend.ts` naming convention (NOT the default
* `.server.ts`) to mark it as server-only.
*
* The vite config denies `**\/*.backend.*` in the client environment.
*/
export const SECRET_KEY = 'custom-backend-secret-99999'

export function getBackendSecret() {
return SECRET_KEY
}
104 changes: 104 additions & 0 deletions e2e/react-start/import-protection-custom-config/src/routeTree.gen.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/* 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 FrontendLeakRouteImport } from './routes/frontend-leak'
import { Route as BackendLeakRouteImport } from './routes/backend-leak'
import { Route as IndexRouteImport } from './routes/index'

const FrontendLeakRoute = FrontendLeakRouteImport.update({
id: '/frontend-leak',
path: '/frontend-leak',
getParentRoute: () => rootRouteImport,
} as any)
const BackendLeakRoute = BackendLeakRouteImport.update({
id: '/backend-leak',
path: '/backend-leak',
getParentRoute: () => rootRouteImport,
} as any)
const IndexRoute = IndexRouteImport.update({
id: '/',
path: '/',
getParentRoute: () => rootRouteImport,
} as any)

export interface FileRoutesByFullPath {
'/': typeof IndexRoute
'/backend-leak': typeof BackendLeakRoute
'/frontend-leak': typeof FrontendLeakRoute
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
'/backend-leak': typeof BackendLeakRoute
'/frontend-leak': typeof FrontendLeakRoute
}
export interface FileRoutesById {
__root__: typeof rootRouteImport
'/': typeof IndexRoute
'/backend-leak': typeof BackendLeakRoute
'/frontend-leak': typeof FrontendLeakRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths: '/' | '/backend-leak' | '/frontend-leak'
fileRoutesByTo: FileRoutesByTo
to: '/' | '/backend-leak' | '/frontend-leak'
id: '__root__' | '/' | '/backend-leak' | '/frontend-leak'
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
BackendLeakRoute: typeof BackendLeakRoute
FrontendLeakRoute: typeof FrontendLeakRoute
}

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

const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
BackendLeakRoute: BackendLeakRoute,
FrontendLeakRoute: FrontendLeakRoute,
}
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>>
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { createRouter } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen'

export function getRouter() {
return createRouter({
routeTree,
scrollRestoration: true,
})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import {
createRootRoute,
HeadContent,
Link,
linkOptions,
Outlet,
Scripts,
} from '@tanstack/react-router'
Comment on lines +1 to +8
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix import member ordering to satisfy ESLint sort-imports.

Line 3 indicates member order is currently not lint-compliant.

Proposed diff
 import {
-  createRootRoute,
   HeadContent,
+  createRootRoute,
   Link,
   linkOptions,
   Outlet,
   Scripts,
 } from '@tanstack/react-router'
🧰 Tools
🪛 ESLint

[error] 3-3: Member 'HeadContent' of the import declaration should be sorted alphabetically.

(sort-imports)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/react-start/import-protection-custom-config/src/routes/__root.tsx` around
lines 1 - 8, The named imports from '@tanstack/react-router' in the import
statement (createRootRoute, HeadContent, Link, linkOptions, Outlet, Scripts) are
not in the order required by ESLint sort-imports; reorder the members to match
the project's alphabetical/sort-imports policy (e.g., alphabetically:
HeadContent, Link, Scripts, createRootRoute, linkOptions, Outlet or whatever
ordering your config enforces) so the import statement for these symbols is
lint-compliant, updating the import clause that references createRootRoute,
HeadContent, Link, linkOptions, Outlet, Scripts.


export const Route = createRootRoute({
head: () => ({
meta: [
{ charSet: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ title: 'Import Protection Custom Config E2E' },
],
}),
component: RootComponent,
})

const navLinks = linkOptions([
{ to: '/', label: 'Home' },
{ to: '/backend-leak', label: 'Backend Leak' },
{ to: '/frontend-leak', label: 'Frontend Leak' },
])

function RootComponent() {
return (
<html>
<head>
<HeadContent />
</head>
<body>
<nav>
{navLinks.map((link, index) => (
<span key={link.to}>
{index > 0 ? ' | ' : null}
<Link to={link.to}>{link.label}</Link>
</span>
))}
</nav>
<Outlet />
<Scripts />
</body>
</html>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { createFileRoute } from '@tanstack/react-router'
// This import triggers a file-based violation in the CLIENT env:
// backend-leak.tsx -> lib/credentials.backend.ts
// The custom deny pattern `**/*.backend.*` should catch this.
import { getBackendSecret } from '../lib/credentials.backend'

export const Route = createFileRoute('/backend-leak')({
component: BackendLeakRoute,
})

function BackendLeakRoute() {
return (
<div>
<h1 data-testid="backend-leak-heading">Backend Leak</h1>
<p data-testid="backend-leak-result">{String(getBackendSecret())}</p>
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { createFileRoute } from '@tanstack/react-router'
// This import triggers a file-based violation in the SERVER (SSR) env:
// frontend-leak.tsx -> lib/browser-api.frontend.ts
// The custom deny pattern `**/*.frontend.*` should catch this.
import { getBrowserInfo } from '../lib/browser-api.frontend'

export const Route = createFileRoute('/frontend-leak')({
component: FrontendLeakRoute,
})

function FrontendLeakRoute() {
return (
<div>
<h1 data-testid="frontend-leak-heading">Frontend Leak</h1>
<p data-testid="frontend-leak-result">{String(getBrowserInfo())}</p>
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { createFileRoute } from '@tanstack/react-router'

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

function Home() {
return (
<div>
<h1 data-testid="heading">Import Protection Custom Config E2E</h1>
<p data-testid="status">
App loaded successfully with custom file patterns
</p>
</div>
)
}
Loading
Loading