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
31 changes: 25 additions & 6 deletions src/frameworks/react-router/react-router-wizard-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@ import type { FrameworkConfig } from '@lib/framework-config';
import { detectNodePackageManagers } from '@lib/detection/package-manager';
import { Integration } from '@lib/constants';
import {
getDeclaredVersion,
findDeclaredPackage,
getInstalledPackageVersion,
hasDeclaredDependency,
type PackageJson,
} from '@utils/package-json';
import { tryGetPackageJson } from '@utils/setup-utils';
Expand All @@ -22,6 +21,22 @@ type ReactRouterContext = {
routerMode?: ReactRouterMode;
};

const REACT_ROUTER_PACKAGE = 'react-router';

/**
* v6 ships the router as `react-router-dom`; v7 consolidated onto
* `react-router`. Both names have to count, or v6 projects — which declare
* only `react-router-dom` — never match and fall through to the generic
* JavaScript fallbacks.
*/
const REACT_ROUTER_ALTERNATE_PACKAGES = ['react-router-dom'];

/** Lookup order matches `getReactRouterMode` in ./utils. */
const REACT_ROUTER_PACKAGES = [
...REACT_ROUTER_ALTERNATE_PACKAGES,
REACT_ROUTER_PACKAGE,
];

export const REACT_ROUTER_AGENT_CONFIG: FrameworkConfig<ReactRouterContext> = {
metadata: {
name: 'React Router',
Expand All @@ -41,20 +56,24 @@ export const REACT_ROUTER_AGENT_CONFIG: FrameworkConfig<ReactRouterContext> = {
},

detection: {
packageName: 'react-router',
packageName: REACT_ROUTER_PACKAGE,
alternatePackageNames: REACT_ROUTER_ALTERNATE_PACKAGES,
packageDisplayName: 'React Router',
getVersion: (packageJson: unknown) =>
getDeclaredVersion('react-router', packageJson as PackageJson),
findDeclaredPackage(REACT_ROUTER_PACKAGES, packageJson as PackageJson)
?.version,
getVersionBucket: getReactRouterVersionBucket,
minimumVersion: '6.0.0',
getInstalledVersion: (options: WizardRunOptions) =>
Promise.resolve(
getInstalledPackageVersion('react-router', options.installDir),
REACT_ROUTER_PACKAGES.map((name) =>
getInstalledPackageVersion(name, options.installDir),
).find((version) => version !== undefined),
),
detect: async (options) => {
const packageJson = await tryGetPackageJson(options);
return packageJson
? hasDeclaredDependency('react-router', packageJson)
? findDeclaredPackage(REACT_ROUTER_PACKAGES, packageJson) !== undefined
: false;
},
detectPackageManager: detectNodePackageManagers,
Expand Down
31 changes: 31 additions & 0 deletions src/lib/detection/__tests__/framework.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,37 @@ describe('detectFramework (end-to-end over real project dirs)', () => {
);
});

test('a React Router v6 app resolves to react-router, not a JS fallback', async () => {
// v6 declares only `react-router-dom`; v7 consolidated onto `react-router`.
// Matching just the v7 name sent every v6 project to javascript_web.
const opts = project({
'package.json': JSON.stringify({
dependencies: {
react: '^18',
'react-dom': '^18',
'react-router-dom': '^6.30.2',
},
}),
'package-lock.json': '{}',
'index.html': '<html></html>',
});
await expect(detectFramework(opts.installDir)).resolves.toBe(
Integration.reactRouter,
);
});

test('a React Router v7 app resolves to react-router', async () => {
const opts = project({
'package.json': JSON.stringify({
dependencies: { react: '^19', 'react-router': '7.10.1' },
}),
'package-lock.json': '{}',
});
await expect(detectFramework(opts.installDir)).resolves.toBe(
Integration.reactRouter,
);
});

test('a Vite React app resolves to javascript_web, not javascript_node', async () => {
const opts = project({
'package.json': JSON.stringify({
Expand Down
8 changes: 8 additions & 0 deletions src/lib/framework-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,14 @@ export interface FrameworkDetection {
/** Package name to check in package.json (e.g., "next", "react") */
packageName: string;

/**
* Other package names that count as this framework being installed, for
* frameworks that changed package name across major versions (React Router
* v6 ships as `react-router-dom`, v7 consolidated onto `react-router`).
* Checked alongside `packageName`.
*/
alternatePackageNames?: string[];

/** Human-readable name for error messages (e.g., "Next.js") */
packageDisplayName: string;

Expand Down
8 changes: 6 additions & 2 deletions src/lib/programs/posthog-integration/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,12 @@ export const posthogIntegrationConfig: ProgramConfig = {
installDir: session.installDir,
});
if (packageJson) {
const { hasDeclaredDependency } = await import('@utils/package-json');
if (!hasDeclaredDependency(config.detection.packageName, packageJson)) {
const { findDeclaredPackage } = await import('@utils/package-json');
const accepted = [
config.detection.packageName,
...(config.detection.alternatePackageNames ?? []),
];
if (!findDeclaredPackage(accepted, packageJson)) {
getUI().log.warn(
`${config.detection.packageDisplayName} does not seem to be installed. Continuing anyway — the agent will handle it.`,
);
Expand Down