diff --git a/.changeset/clean-biome-starters.md b/.changeset/clean-biome-starters.md new file mode 100644 index 00000000..1b1fa9a8 --- /dev/null +++ b/.changeset/clean-biome-starters.md @@ -0,0 +1,5 @@ +--- +'@tanstack/create': patch +--- + +Generate React and Solid starters that pass their Biome lint checks. Use Router's nonce-aware theme script helper in React and set the default document language in Solid. diff --git a/packages/cli/tests-e2e/biome-smoke.spec.ts b/packages/cli/tests-e2e/biome-smoke.spec.ts new file mode 100644 index 00000000..46b9a824 --- /dev/null +++ b/packages/cli/tests-e2e/biome-smoke.spec.ts @@ -0,0 +1,31 @@ +import { execFile } from 'node:child_process' +import { promisify } from 'node:util' +import { test } from '@playwright/test' +import { createReactAppFixture } from './helpers' + +const execFileAsync = promisify(execFile) + +for (const framework of ['react', 'solid'] as const) { + for (const blank of [false, true]) { + test(`@blocking lints a ${blank ? 'blank' : 'standard'} ${framework} Biome app`, async () => { + const fixture = await createReactAppFixture({ + appName: `${framework}-biome-${blank ? 'blank' : 'standard'}`, + framework, + blank, + toolchain: 'biome', + packageManager: 'pnpm', + skipDevServer: true, + runQualityGatesChecks: true, + }) + try { + await execFileAsync('pnpm', ['run', 'lint'], { + cwd: fixture.appDir, + timeout: 60_000, + }) + } finally { + await fixture.stop() + await fixture.cleanup() + } + }) + } +} diff --git a/packages/cli/tests-e2e/helpers.ts b/packages/cli/tests-e2e/helpers.ts index b564f07d..de699d23 100644 --- a/packages/cli/tests-e2e/helpers.ts +++ b/packages/cli/tests-e2e/helpers.ts @@ -25,6 +25,7 @@ type CreateAppFixtureOptions = { bundler?: 'vite' | 'rsbuild' packageManager?: 'pnpm' | 'npm' | 'yarn' | 'bun' | 'deno' blank?: boolean + toolchain?: string deployment?: string routerOnly?: boolean template?: string @@ -139,7 +140,9 @@ function waitForDevServerURL(getStdout: () => string, timeoutMs = 90_000) { } if (Date.now() - started > timeoutMs) { - rejectPromise(new Error('Timed out waiting for dev server URL in output')) + rejectPromise( + new Error('Timed out waiting for dev server URL in output'), + ) return } @@ -353,8 +356,12 @@ export async function createAppFixture( } const totalMs = now() - start - const summary = timings.map(([label, ms]) => `${label}=${Math.round(ms)}ms`).join(' | ') - console.log(`[e2e:timing] ${options.appName} :: ${summary} | total=${Math.round(totalMs)}ms`) + const summary = timings + .map(([label, ms]) => `${label}=${Math.round(ms)}ms`) + .join(' | ') + console.log( + `[e2e:timing] ${options.appName} :: ${summary} | total=${Math.round(totalMs)}ms`, + ) } const rootDir = await mkdtemp(join(tmpdir(), 'tanstack-cli-e2e-')) @@ -362,6 +369,7 @@ export async function createAppFixture( appName, blank = false, deployment, + toolchain, template, addOns, afterCreate, @@ -399,6 +407,10 @@ export async function createAppFixture( createArgs.push('--blank', '--yes', '--no-intent') } + if (toolchain) { + createArgs.push('--toolchain', toolchain) + } + if (deployment) { createArgs.push('--deployment', deployment) } @@ -413,16 +425,12 @@ export async function createAppFixture( try { const createStartedAt = now() - await runCommand( - process.execPath, - createArgs, - { - cwd: rootDir, - env: { - CI: '1', - }, + await runCommand(process.execPath, createArgs, { + cwd: rootDir, + env: { + CI: '1', }, - ) + }) mark('create', createStartedAt) await afterCreate?.(appDir) @@ -431,12 +439,16 @@ export async function createAppFixture( if (postCreateAddOns?.length) { const postAddOnsStartedAt = now() - await runCommand(process.execPath, [cliDistPath, 'add', ...postCreateAddOns, '--forced'], { - cwd: appDir, - env: { - CI: '1', + await runCommand( + process.execPath, + [cliDistPath, 'add', ...postCreateAddOns, '--forced'], + { + cwd: appDir, + env: { + CI: '1', + }, }, - }) + ) mark('post-add-ons', postAddOnsStartedAt) @@ -565,7 +577,10 @@ export function attachRuntimeGuards(page: Page, appUrl: string): RuntimeGuards { } const errorText = request.failure()?.errorText || 'unknown error' - if (errorText.includes('ERR_ABORTED') || isBlockedByClientError(errorText)) { + if ( + errorText.includes('ERR_ABORTED') || + isBlockedByClientError(errorText) + ) { return } diff --git a/packages/create/src/frameworks/react/project/base/src/routes/__root.tsx.ejs b/packages/create/src/frameworks/react/project/base/src/routes/__root.tsx.ejs index 4b64f29d..824da9a6 100644 --- a/packages/create/src/frameworks/react/project/base/src/routes/__root.tsx.ejs +++ b/packages/create/src/frameworks/react/project/base/src/routes/__root.tsx.ejs @@ -33,7 +33,7 @@ function RootComponent() { <% } else if (!includeExamples || blank) { %> <% let hasContext = addOnEnabled["apollo-client"] || addOnEnabled["tanstack-query"]; %> import { - HeadContent, Scripts, <% if (hasContext) { %>createRootRouteWithContext<% } else { %>createRootRoute<% } %> } from '@tanstack/react-router' + HeadContent, Scripts, <% if (addOnEnabled.themer) { %>ScriptOnce, <% } %><% if (hasContext) { %>createRootRouteWithContext<% } else { %>createRootRoute<% } %> } from '@tanstack/react-router' <% if (showDevtools) { %>import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools'; import { TanStackDevtools } from '@tanstack/react-devtools' <% } %> @@ -103,7 +103,7 @@ function RootDocument({ children }: { children: React.ReactNode }) { return ( <% if (addOnEnabled.paraglide) { %> suppressHydrationWarning<% } %>><% } else { %> suppressHydrationWarning<% } %>><% } %> - <% if (addOnEnabled.themer) { %>