Skip to content

Commit 8fab502

Browse files
committed
parse pnp paths in storybook metadata
1 parent 6d0596f commit 8fab502

File tree

3 files changed

+171
-14
lines changed

3 files changed

+171
-14
lines changed

code/lib/telemetry/src/get-framework-info.ts

+28-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
import type { PackageJson, StorybookConfig } from '@storybook/types';
2+
import path from 'path';
3+
import { frameworkPackages } from '@storybook/core-common';
4+
import { cleanPaths } from './sanitize';
25
import { getActualPackageJson } from './package-json';
36

47
const knownRenderers = [
@@ -30,20 +33,39 @@ function findMatchingPackage(packageJson: PackageJson, suffixes: string[]) {
3033
return suffixes.map((suffix) => `@storybook/${suffix}`).find((pkg) => allDependencies[pkg]);
3134
}
3235

33-
export async function getFrameworkInfo(mainConfig: StorybookConfig) {
34-
const { framework: frameworkInput } = mainConfig;
36+
export const getFrameworkPackageName = (mainConfig?: StorybookConfig) => {
37+
const packageNameOrPath =
38+
typeof mainConfig?.framework === 'string' ? mainConfig.framework : mainConfig?.framework?.name;
39+
40+
if (!packageNameOrPath) {
41+
return null;
42+
}
43+
44+
const normalizedPath = path.normalize(packageNameOrPath).replace(new RegExp(/\\/, 'g'), '/');
3545

36-
if (!frameworkInput) return {};
46+
const knownFramework = Object.keys(frameworkPackages).find((pkg) => normalizedPath.endsWith(pkg));
47+
48+
return knownFramework || cleanPaths(packageNameOrPath).replace(/.*node_modules[\\/]/, '');
49+
};
50+
51+
export async function getFrameworkInfo(mainConfig: StorybookConfig) {
52+
if (!mainConfig.framework) return {};
3753

38-
const framework = typeof frameworkInput === 'string' ? { name: frameworkInput } : frameworkInput;
54+
const frameworkName = getFrameworkPackageName(mainConfig);
55+
if (!frameworkName) return {};
56+
const frameworkOptions =
57+
typeof mainConfig.framework === 'object' ? mainConfig.framework.options : {};
3958

40-
const frameworkPackageJson = await getActualPackageJson(framework.name);
59+
const frameworkPackageJson = await getActualPackageJson(frameworkName);
4160

4261
const builder = findMatchingPackage(frameworkPackageJson, knownBuilders);
4362
const renderer = findMatchingPackage(frameworkPackageJson, knownRenderers);
4463

4564
return {
46-
framework,
65+
framework: {
66+
name: frameworkName,
67+
options: frameworkOptions,
68+
},
4769
builder,
4870
renderer,
4971
};

code/lib/telemetry/src/sanitize.test.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,10 @@ describe(`Errors Helpers`, () => {
7373

7474
const mockCwd = jest.spyOn(process, `cwd`).mockImplementation(() => cwdMockPath);
7575

76-
const errorMessage = `This path ${fullPath} is a test ${fullPath}`;
76+
const errorMessage = `This path should be sanitized ${fullPath}`;
7777

7878
expect(cleanPaths(errorMessage, `/`)).toBe(
79-
`This path $SNIP/${filePath} is a test $SNIP/${filePath}`
79+
`This path should be sanitized $SNIP/${filePath}`
8080
);
8181
mockCwd.mockRestore();
8282
}
@@ -90,10 +90,10 @@ describe(`Errors Helpers`, () => {
9090

9191
const mockCwd = jest.spyOn(process, `cwd`).mockImplementation(() => cwdMockPath);
9292

93-
const errorMessage = `This path ${fullPath} is a test ${fullPath}`;
93+
const errorMessage = `This path should be sanitized ${fullPath}`;
9494

9595
expect(cleanPaths(errorMessage, `\\`)).toBe(
96-
`This path $SNIP\\${filePath} is a test $SNIP\\${filePath}`
96+
`This path should be sanitized $SNIP\\${filePath}`
9797
);
9898
mockCwd.mockRestore();
9999
}

code/lib/telemetry/src/storybook-metadata.test.ts

+139-4
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,25 @@ jest.mock('detect-package-manager', () => ({
4747
getNpmVersion: () => '3.1.1',
4848
}));
4949

50+
const originalSep = path.sep;
51+
5052
describe('sanitizeAddonName', () => {
51-
const originalSep = path.sep;
53+
let cwdSpy: jest.SpyInstance;
54+
5255
beforeEach(() => {
5356
// @ts-expect-error the property is read only but we can change it for testing purposes
5457
path.sep = originalSep;
5558
});
5659

60+
afterAll(() => {
61+
// @ts-expect-error the property is read only but we can change it for testing purposes
62+
path.sep = originalSep;
63+
});
64+
65+
afterEach(() => {
66+
cwdSpy?.mockRestore();
67+
});
68+
5769
test('special addon names', () => {
5870
const addonNames = [
5971
'@storybook/preset-create-react-app',
@@ -82,7 +94,7 @@ describe('sanitizeAddonName', () => {
8294
// @ts-expect-error the property is read only but we can change it for testing purposes
8395
path.sep = '\\';
8496
const cwdMockPath = `C:\\Users\\username\\storybook-app`;
85-
jest.spyOn(process, `cwd`).mockImplementationOnce(() => cwdMockPath);
97+
cwdSpy = jest.spyOn(process, `cwd`).mockReturnValueOnce(cwdMockPath);
8698

8799
expect(sanitizeAddonName(`${cwdMockPath}\\local-addon\\themes.js`)).toEqual(
88100
'$SNIP\\local-addon\\themes'
@@ -93,15 +105,138 @@ describe('sanitizeAddonName', () => {
93105
// @ts-expect-error the property is read only but we can change it for testing purposes
94106
path.sep = '/';
95107
const cwdMockPath = `/Users/username/storybook-app`;
96-
jest.spyOn(process, `cwd`).mockImplementationOnce(() => cwdMockPath);
108+
cwdSpy = jest.spyOn(process, `cwd`).mockReturnValue(cwdMockPath);
97109

98110
expect(sanitizeAddonName(`${cwdMockPath}/local-addon/themes.js`)).toEqual(
99111
'$SNIP/local-addon/themes'
100112
);
101113
});
102114
});
103115

104-
describe('await computeStorybookMetadata', () => {
116+
describe('computeStorybookMetadata', () => {
117+
beforeEach(() => {
118+
// @ts-expect-error the property is read only but we can change it for testing purposes
119+
path.sep = originalSep;
120+
});
121+
122+
afterAll(() => {
123+
// @ts-expect-error the property is read only but we can change it for testing purposes
124+
path.sep = originalSep;
125+
});
126+
127+
describe('pnp paths', () => {
128+
let cwdSpy: jest.SpyInstance;
129+
130+
afterEach(() => {
131+
cwdSpy?.mockRestore();
132+
});
133+
134+
test('should parse pnp paths for known frameworks', async () => {
135+
const unixResult = await computeStorybookMetadata({
136+
packageJson: packageJsonMock,
137+
mainConfig: {
138+
...mainJsMock,
139+
framework: {
140+
name: '/Users/foo/storybook/.yarn/__virtual__/@storybook-react-vite-virtual-769c990b9/0/cache/@storybook-react-vite-npm-7.1.0-alpha.38-512b-a23.zip/node_modules/@storybook/react-vite',
141+
options: {
142+
fastRefresh: false,
143+
},
144+
},
145+
},
146+
});
147+
148+
expect(unixResult.framework).toEqual({
149+
name: '@storybook/react-vite',
150+
options: { fastRefresh: false },
151+
});
152+
153+
const windowsResult = await computeStorybookMetadata({
154+
packageJson: packageJsonMock,
155+
mainConfig: {
156+
...mainJsMock,
157+
framework: {
158+
name: 'C:\\Users\\foo\\storybook\\.yarn\\__virtual__\\@storybook-react-vite-virtual-769c990b9\\0\\cache\\@storybook-react-vite-npm-7.1.0-alpha.38-512b-a23.zip\\node_modules\\@storybook\\react-vite',
159+
options: {
160+
fastRefresh: false,
161+
},
162+
},
163+
},
164+
});
165+
166+
expect(windowsResult.framework).toEqual({
167+
name: '@storybook/react-vite',
168+
options: { fastRefresh: false },
169+
});
170+
});
171+
172+
test('should parse pnp paths for unknown frameworks', async () => {
173+
const unixResult = await computeStorybookMetadata({
174+
packageJson: packageJsonMock,
175+
mainConfig: {
176+
...mainJsMock,
177+
framework: {
178+
name: '/Users/foo/my-project/.yarn/__virtual__/@storybook-react-vite-virtual-769c990b9/0/cache/@storybook-react-rust-npm-7.1.0-alpha.38-512b-a23.zip/node_modules/storybook-react-rust',
179+
},
180+
},
181+
});
182+
183+
expect(unixResult.framework).toEqual({
184+
name: 'storybook-react-rust',
185+
});
186+
187+
const windowsResult = await computeStorybookMetadata({
188+
packageJson: packageJsonMock,
189+
mainConfig: {
190+
...mainJsMock,
191+
framework: {
192+
name: 'C:\\Users\\foo\\my-project\\.yarn\\__virtual__\\@storybook-react-vite-virtual-769c990b9\\0\\cache\\@storybook-react-rust-npm-7.1.0-alpha.38-512b-a23.zip\\node_modules\\storybook-react-rust',
193+
},
194+
},
195+
});
196+
197+
expect(windowsResult.framework).toEqual({
198+
name: 'storybook-react-rust',
199+
});
200+
});
201+
202+
test('should sanitize pnp paths for local frameworks', async () => {
203+
// @ts-expect-error the property is read only but we can change it for testing purposes
204+
path.sep = '/';
205+
cwdSpy = jest.spyOn(process, 'cwd').mockReturnValue('/Users/foo/my-project');
206+
207+
const unixResult = await computeStorybookMetadata({
208+
packageJson: packageJsonMock,
209+
mainConfig: {
210+
...mainJsMock,
211+
framework: {
212+
name: '/Users/foo/my-project/.storybook/some-local-framework',
213+
},
214+
},
215+
});
216+
217+
expect(unixResult.framework).toEqual({
218+
name: '$SNIP/.storybook/some-local-framework',
219+
});
220+
221+
// @ts-expect-error the property is read only but we can change it for testing purposes
222+
path.sep = '\\';
223+
cwdSpy = jest.spyOn(process, 'cwd').mockReturnValue('C:\\Users\\foo\\my-project');
224+
const windowsResult = await computeStorybookMetadata({
225+
packageJson: packageJsonMock,
226+
mainConfig: {
227+
...mainJsMock,
228+
framework: {
229+
name: 'C:\\Users\\foo\\my-project\\.storybook\\some-local-framework',
230+
},
231+
},
232+
});
233+
234+
expect(windowsResult.framework).toEqual({
235+
name: '$SNIP\\.storybook\\some-local-framework',
236+
});
237+
});
238+
});
239+
105240
test('should return frameworkOptions from mainjs', async () => {
106241
const reactResult = await computeStorybookMetadata({
107242
packageJson: packageJsonMock,

0 commit comments

Comments
 (0)