Repository navigation
fix(install): support current TanStack Start setup and callback registration #253
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -105,12 +105,14 @@ export async function validatePackages(rules: ValidationRules, projectDir: strin | |
| const location = rule.location || 'any'; | ||
| const searchIn = location === 'any' ? allDeps : location === 'dependencies' ? deps : devDeps; | ||
|
|
||
| if (!searchIn[rule.name]) { | ||
| if (![rule.name, ...(rule.alternates || [])].some((name) => searchIn[name])) { | ||
| issues.push({ | ||
| type: 'package', | ||
| severity: 'error', | ||
| message: `Missing package: ${rule.name}`, | ||
| hint: `Run: npm install ${rule.name}`, | ||
| hint: `Run: npm install ${location === 'devDependencies' ? '--save-dev ' : ''}${rule.name}${ | ||
| rule.alternates?.length ? ` (or one of: ${rule.alternates.join(', ')})` : '' | ||
| }`, | ||
| }); | ||
| } | ||
| } | ||
|
|
@@ -187,43 +189,46 @@ export async function validateFiles(rules: ValidationRules, projectDir: string): | |
| continue; | ||
| } | ||
|
|
||
| // Check content patterns | ||
| // Any matching file may satisfy the rule, but its patterns must occur together. | ||
| if (rule.mustContain || rule.mustContainAny) { | ||
| const filePath = join(projectDir, matches[0]); | ||
| let content: string; | ||
| try { | ||
| content = await readFile(filePath, 'utf-8'); | ||
| } catch { | ||
| // File read error - skip content checks | ||
| continue; | ||
| } | ||
| let patternIssues: ValidationIssue[] | undefined; | ||
| for (const file of matches) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Prompt To Fix With AIThis is a comment left during a code review.
Path: src/lib/validation/validator.ts
Line: 195
Comment:
**Broad globs cause many reads**
When no file contains the expected pattern, this loop reads every matching file. The TanStack source rule matches all TS, TSX, JS, and JSX files under `app` and `src`, so validation of a large app can spend substantial time reading files just to produce a missing-pattern warning. A bounded or more targeted check would avoid that cost while still checking beyond the first match.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time! |
||
| let content: string; | ||
| try { | ||
| content = await readFile(join(projectDir, file), 'utf-8'); | ||
| } catch { | ||
| // A failed read must not hide another matching file. | ||
| continue; | ||
| } | ||
|
|
||
| // All must be present | ||
| if (rule.mustContain) { | ||
| for (const pattern of rule.mustContain) { | ||
| const fileIssues: ValidationIssue[] = []; | ||
| for (const pattern of rule.mustContain ?? []) { | ||
| if (!content.includes(pattern)) { | ||
| issues.push({ | ||
| fileIssues.push({ | ||
| type: 'pattern', | ||
| severity: rule.severity ?? 'warning', | ||
| message: `File ${matches[0]} missing expected pattern: "${pattern}"`, | ||
| hint: `Ensure ${matches[0]} contains: ${pattern}`, | ||
| message: `File ${file} missing expected pattern: "${pattern}"`, | ||
| hint: `Ensure ${file} contains: ${pattern}`, | ||
| }); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // At least one must be present | ||
| if (rule.mustContainAny) { | ||
| const hasAny = rule.mustContainAny.some((p) => content.includes(p)); | ||
| if (!hasAny) { | ||
| issues.push({ | ||
| if (rule.mustContainAny && !rule.mustContainAny.some((pattern) => content.includes(pattern))) { | ||
| fileIssues.push({ | ||
| type: 'pattern', | ||
| severity: rule.severity ?? 'warning', | ||
| message: `File ${matches[0]} missing one of: ${rule.mustContainAny.join(', ')}`, | ||
| hint: `Ensure ${matches[0]} contains one of these patterns`, | ||
| message: `File ${file} missing one of: ${rule.mustContainAny.join(', ')}`, | ||
| hint: `Ensure ${file} contains one of these patterns`, | ||
| }); | ||
| } | ||
|
|
||
| if (fileIssues.length === 0) { | ||
| patternIssues = []; | ||
| break; | ||
| } | ||
| // Retain one file's actionable diagnostics only if no match satisfies the rule. | ||
| patternIssues ??= fileIssues; | ||
| } | ||
| issues.push(...(patternIssues ?? [])); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -595,7 +600,8 @@ async function validateReactRouterRedirectUri(projectDir: string, issues: Valida | |
| * Validates that the TanStack Start redirect URI matches an existing callback route. | ||
| * | ||
| * TanStack Start uses file-based routing: | ||
| * - /auth/callback → app/routes/auth/callback.tsx | ||
| * Checks conventional nested, flat, index, and route files under app/routes or src/routes. | ||
| * Custom router configuration and runtime handler behavior are not inferred from source. | ||
| */ | ||
| async function validateTanstackStartRedirectUri(projectDir: string, issues: ValidationIssue[]): Promise<void> { | ||
| const envPath = join(projectDir, '.env.local'); | ||
|
|
@@ -631,38 +637,40 @@ async function validateTanstackStartRedirectUri(projectDir: string, issues: Vali | |
|
|
||
| const routePath = callbackPath.replace(/^\//, ''); | ||
|
|
||
| // TanStack Start route patterns | ||
| const routePatterns = [ | ||
| `app/routes/${routePath}.tsx`, | ||
| `app/routes/${routePath}.ts`, | ||
| `app/routes/${routePath}.jsx`, | ||
| `app/routes/${routePath}.js`, | ||
| `app/routes/${routePath}/index.tsx`, | ||
| `app/routes/${routePath}/index.ts`, | ||
| `app/routes/${routePath}/index.jsx`, | ||
| `app/routes/${routePath}/index.js`, | ||
| ]; | ||
| const dotPath = routePath.replace(/\//g, '.'); | ||
| const routePatterns = ['app', 'src'].flatMap((root) => | ||
| [routePath, dotPath].flatMap((path) => | ||
| ['', '/index', '/route', '.index', '.route'].flatMap((suffix) => | ||
| ['ts', 'tsx', 'js', 'jsx'].map((ext) => `${root}/routes/${path}${suffix}.${ext}`), | ||
| ), | ||
| ), | ||
| ); | ||
|
|
||
| const routeExists = routePatterns.some((pattern) => existsSync(join(projectDir, pattern))); | ||
|
|
||
| if (!routeExists) { | ||
| const existingRoutes = await fg(['app/routes/**/*callback*.{ts,tsx,js,jsx}'], { | ||
| cwd: projectDir, | ||
| }); | ||
| const existingRoutes = await fg( | ||
| [ | ||
| '{app,src}/routes/**/*callback*.{ts,tsx,js,jsx}', | ||
| '{app,src}/routes/**/*callback*/{index,route}.{ts,tsx,js,jsx}', | ||
| ], | ||
| { cwd: projectDir }, | ||
| ); | ||
|
|
||
| let hint = `Create a route at app/routes/${routePath}.tsx`; | ||
| let hint = `Create a route at app/routes/${routePath}.tsx or src/routes/${routePath}.tsx`; | ||
| if (existingRoutes.length > 0) { | ||
| const actualFile = existingRoutes[0]; | ||
| const actualPath = | ||
| '/' + | ||
| actualFile | ||
| .replace(/^app\/routes\//, '') | ||
| .replace(/^(app|src)\/routes\//, '') | ||
| .replace(/\.(tsx?|jsx?)$/, '') | ||
| .replace(/\/index$/, ''); | ||
| .replace(/\./g, '/') | ||
| .replace(/\/(index|route)$/, ''); | ||
| hint = | ||
| `Found callback route at ${actualFile} but redirect URI points to ${callbackPath}. Either:\n` + | ||
| ` 1. Change WORKOS_REDIRECT_URI to ${new URL(redirectUri).origin}${actualPath}\n` + | ||
| ` 2. Move the route to app/routes/${routePath}.tsx`; | ||
| ` 2. Move the route to app/routes/${routePath}.tsx or src/routes/${routePath}.tsx`; | ||
| } | ||
|
|
||
| issues.push({ | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.