Skip to content

Commit 01a532a

Browse files
committed
parse pnp paths in storybook metadata
1 parent 6d0596f commit 01a532a

File tree

3 files changed

+159
-14
lines changed

3 files changed

+159
-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

+127-4
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,19 @@ 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;
5254
beforeEach(() => {
5355
// @ts-expect-error the property is read only but we can change it for testing purposes
5456
path.sep = originalSep;
5557
});
5658

59+
afterEach(() => {
60+
cwdSpy?.mockRestore();
61+
});
62+
5763
test('special addon names', () => {
5864
const addonNames = [
5965
'@storybook/preset-create-react-app',
@@ -82,7 +88,7 @@ describe('sanitizeAddonName', () => {
8288
// @ts-expect-error the property is read only but we can change it for testing purposes
8389
path.sep = '\\';
8490
const cwdMockPath = `C:\\Users\\username\\storybook-app`;
85-
jest.spyOn(process, `cwd`).mockImplementationOnce(() => cwdMockPath);
91+
cwdSpy = jest.spyOn(process, `cwd`).mockReturnValueOnce(cwdMockPath);
8692

8793
expect(sanitizeAddonName(`${cwdMockPath}\\local-addon\\themes.js`)).toEqual(
8894
'$SNIP\\local-addon\\themes'
@@ -93,15 +99,132 @@ describe('sanitizeAddonName', () => {
9399
// @ts-expect-error the property is read only but we can change it for testing purposes
94100
path.sep = '/';
95101
const cwdMockPath = `/Users/username/storybook-app`;
96-
jest.spyOn(process, `cwd`).mockImplementationOnce(() => cwdMockPath);
102+
cwdSpy = jest.spyOn(process, `cwd`).mockReturnValue(cwdMockPath);
97103

98104
expect(sanitizeAddonName(`${cwdMockPath}/local-addon/themes.js`)).toEqual(
99105
'$SNIP/local-addon/themes'
100106
);
101107
});
102108
});
103109

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

0 commit comments

Comments
 (0)