diff --git a/code/lib/create-storybook/src/services/ProjectTypeService.test.ts b/code/lib/create-storybook/src/services/ProjectTypeService.test.ts index b9d3e1aa0773..45997fb472ae 100644 --- a/code/lib/create-storybook/src/services/ProjectTypeService.test.ts +++ b/code/lib/create-storybook/src/services/ProjectTypeService.test.ts @@ -189,6 +189,28 @@ describe('ProjectTypeService', () => { expect(result).toBe(ProjectType.REACT_NATIVE); }); + it('detects REACT_NATIVE via peerDependencies', async () => { + (pm as any).primaryPackageJson.packageJson = { + peerDependencies: { react: '^18.0.0', 'react-native': '^0.76.0' }, + }; + const service = new ProjectTypeService(pm); + // @ts-expect-error private method spy + vi.spyOn(service, 'isNxProject').mockReturnValue(false); + const result = await service.autoDetectProjectType({ html: false } as CommandOptions); + expect(result).toBe(ProjectType.REACT_NATIVE); + }); + + it('detects REACT_NATIVE via peerDependencies react-native-scripts', async () => { + (pm as any).primaryPackageJson.packageJson = { + peerDependencies: { react: '^18.0.0', 'react-native-scripts': '^5.0.0' }, + }; + const service = new ProjectTypeService(pm); + // @ts-expect-error private method spy + vi.spyOn(service, 'isNxProject').mockReturnValue(false); + const result = await service.autoDetectProjectType({ html: false } as CommandOptions); + expect(result).toBe(ProjectType.REACT_NATIVE); + }); + it('detects NUXT via nuxt', async () => { (pm as any).primaryPackageJson.packageJson = { dependencies: { nuxt: '^3.0.0' }, @@ -199,6 +221,53 @@ describe('ProjectTypeService', () => { const result = await service.autoDetectProjectType({ html: false } as CommandOptions); expect(result).toBe(ProjectType.NUXT); }); + + it('detects REACT via peerDependencies', async () => { + (pm as any).primaryPackageJson.packageJson = { + peerDependencies: { react: '^18.0.0' }, + }; + const service = new ProjectTypeService(pm); + // @ts-expect-error private method spy + vi.spyOn(service, 'isNxProject').mockReturnValue(false); + const result = await service.autoDetectProjectType({ html: false } as CommandOptions); + expect(result).toBe(ProjectType.REACT); + }); + + it('detects REACT via devDependencies', async () => { + (pm as any).primaryPackageJson.packageJson = { + devDependencies: { react: '^18.0.0' }, + }; + const service = new ProjectTypeService(pm); + // @ts-expect-error private method spy + vi.spyOn(service, 'isNxProject').mockReturnValue(false); + const result = await service.autoDetectProjectType({ html: false } as CommandOptions); + expect(result).toBe(ProjectType.REACT); + }); + + it('prefers REACT_NATIVE over REACT when markers are split across dependency sections', async () => { + (pm as any).primaryPackageJson.packageJson = { + dependencies: { 'react-native': '^0.76.0' }, + peerDependencies: { react: '^18.0.0' }, + }; + const service = new ProjectTypeService(pm); + // @ts-expect-error private method spy + vi.spyOn(service, 'isNxProject').mockReturnValue(false); + const result = await service.autoDetectProjectType({ html: false } as CommandOptions); + expect(result).toBe(ProjectType.REACT_NATIVE); + }); + + it('does not detect REACT from react-dom alone', async () => { + (pm as any).primaryPackageJson.packageJson = { + peerDependencies: { 'react-dom': '^18.0.0' }, + }; + const service = new ProjectTypeService(pm); + // @ts-expect-error private method spy + vi.spyOn(service, 'isNxProject').mockReturnValue(false); + + await expect( + service.autoDetectProjectType({ html: false } as CommandOptions) + ).rejects.toThrowError('Storybook failed to detect your project type'); + }); }); describe('validateProvidedType', () => { diff --git a/code/lib/create-storybook/src/services/ProjectTypeService.ts b/code/lib/create-storybook/src/services/ProjectTypeService.ts index 39e2699bcb6d..80a9a2beb40a 100644 --- a/code/lib/create-storybook/src/services/ProjectTypeService.ts +++ b/code/lib/create-storybook/src/services/ProjectTypeService.ts @@ -77,8 +77,9 @@ export class ProjectTypeService { { preset: ProjectType.REACT_NATIVE, dependencies: ['react-native', 'react-native-scripts'], - matcherFunction: ({ dependencies }) => { - return dependencies?.some(Boolean) ?? false; + peerDependencies: ['react-native', 'react-native-scripts'], + matcherFunction: ({ dependencies, peerDependencies }) => { + return (dependencies?.some(Boolean) || peerDependencies?.some(Boolean)) ?? false; }, }, { @@ -139,8 +140,9 @@ export class ProjectTypeService { { preset: ProjectType.REACT, dependencies: ['react'], - matcherFunction: ({ dependencies }) => { - return dependencies?.every(Boolean) ?? true; + peerDependencies: ['react'], + matcherFunction: ({ dependencies, peerDependencies }) => { + return (dependencies?.some(Boolean) || peerDependencies?.some(Boolean)) ?? false; }, }, ];