Skip to content

Commit

Permalink
parse pnp paths in storybook metadata
Browse files Browse the repository at this point in the history
  • Loading branch information
yannbf committed Jun 23, 2023
1 parent 6d0596f commit 8fab502
Show file tree
Hide file tree
Showing 3 changed files with 171 additions and 14 deletions.
34 changes: 28 additions & 6 deletions code/lib/telemetry/src/get-framework-info.ts
@@ -1,4 +1,7 @@
import type { PackageJson, StorybookConfig } from '@storybook/types';
import path from 'path';
import { frameworkPackages } from '@storybook/core-common';
import { cleanPaths } from './sanitize';
import { getActualPackageJson } from './package-json';

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

export async function getFrameworkInfo(mainConfig: StorybookConfig) {
const { framework: frameworkInput } = mainConfig;
export const getFrameworkPackageName = (mainConfig?: StorybookConfig) => {
const packageNameOrPath =
typeof mainConfig?.framework === 'string' ? mainConfig.framework : mainConfig?.framework?.name;

if (!packageNameOrPath) {
return null;
}

const normalizedPath = path.normalize(packageNameOrPath).replace(new RegExp(/\\/, 'g'), '/');

if (!frameworkInput) return {};
const knownFramework = Object.keys(frameworkPackages).find((pkg) => normalizedPath.endsWith(pkg));

return knownFramework || cleanPaths(packageNameOrPath).replace(/.*node_modules[\\/]/, '');
};

export async function getFrameworkInfo(mainConfig: StorybookConfig) {
if (!mainConfig.framework) return {};

const framework = typeof frameworkInput === 'string' ? { name: frameworkInput } : frameworkInput;
const frameworkName = getFrameworkPackageName(mainConfig);
if (!frameworkName) return {};
const frameworkOptions =
typeof mainConfig.framework === 'object' ? mainConfig.framework.options : {};

const frameworkPackageJson = await getActualPackageJson(framework.name);
const frameworkPackageJson = await getActualPackageJson(frameworkName);

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

return {
framework,
framework: {
name: frameworkName,
options: frameworkOptions,
},
builder,
renderer,
};
Expand Down
8 changes: 4 additions & 4 deletions code/lib/telemetry/src/sanitize.test.ts
Expand Up @@ -73,10 +73,10 @@ describe(`Errors Helpers`, () => {

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

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

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

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

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

expect(cleanPaths(errorMessage, `\\`)).toBe(
`This path $SNIP\\${filePath} is a test $SNIP\\${filePath}`
`This path should be sanitized $SNIP\\${filePath}`
);
mockCwd.mockRestore();
}
Expand Down
143 changes: 139 additions & 4 deletions code/lib/telemetry/src/storybook-metadata.test.ts
Expand Up @@ -47,13 +47,25 @@ jest.mock('detect-package-manager', () => ({
getNpmVersion: () => '3.1.1',
}));

const originalSep = path.sep;

describe('sanitizeAddonName', () => {
const originalSep = path.sep;
let cwdSpy: jest.SpyInstance;

beforeEach(() => {
// @ts-expect-error the property is read only but we can change it for testing purposes
path.sep = originalSep;
});

afterAll(() => {
// @ts-expect-error the property is read only but we can change it for testing purposes
path.sep = originalSep;
});

afterEach(() => {
cwdSpy?.mockRestore();
});

test('special addon names', () => {
const addonNames = [
'@storybook/preset-create-react-app',
Expand Down Expand Up @@ -82,7 +94,7 @@ describe('sanitizeAddonName', () => {
// @ts-expect-error the property is read only but we can change it for testing purposes
path.sep = '\\';
const cwdMockPath = `C:\\Users\\username\\storybook-app`;
jest.spyOn(process, `cwd`).mockImplementationOnce(() => cwdMockPath);
cwdSpy = jest.spyOn(process, `cwd`).mockReturnValueOnce(cwdMockPath);

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

expect(sanitizeAddonName(`${cwdMockPath}/local-addon/themes.js`)).toEqual(
'$SNIP/local-addon/themes'
);
});
});

describe('await computeStorybookMetadata', () => {
describe('computeStorybookMetadata', () => {
beforeEach(() => {
// @ts-expect-error the property is read only but we can change it for testing purposes
path.sep = originalSep;
});

afterAll(() => {
// @ts-expect-error the property is read only but we can change it for testing purposes
path.sep = originalSep;
});

describe('pnp paths', () => {
let cwdSpy: jest.SpyInstance;

afterEach(() => {
cwdSpy?.mockRestore();
});

test('should parse pnp paths for known frameworks', async () => {
const unixResult = await computeStorybookMetadata({
packageJson: packageJsonMock,
mainConfig: {
...mainJsMock,
framework: {
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',
options: {
fastRefresh: false,
},
},
},
});

expect(unixResult.framework).toEqual({
name: '@storybook/react-vite',
options: { fastRefresh: false },
});

const windowsResult = await computeStorybookMetadata({
packageJson: packageJsonMock,
mainConfig: {
...mainJsMock,
framework: {
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',
options: {
fastRefresh: false,
},
},
},
});

expect(windowsResult.framework).toEqual({
name: '@storybook/react-vite',
options: { fastRefresh: false },
});
});

test('should parse pnp paths for unknown frameworks', async () => {
const unixResult = await computeStorybookMetadata({
packageJson: packageJsonMock,
mainConfig: {
...mainJsMock,
framework: {
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',
},
},
});

expect(unixResult.framework).toEqual({
name: 'storybook-react-rust',
});

const windowsResult = await computeStorybookMetadata({
packageJson: packageJsonMock,
mainConfig: {
...mainJsMock,
framework: {
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',
},
},
});

expect(windowsResult.framework).toEqual({
name: 'storybook-react-rust',
});
});

test('should sanitize pnp paths for local frameworks', async () => {
// @ts-expect-error the property is read only but we can change it for testing purposes
path.sep = '/';
cwdSpy = jest.spyOn(process, 'cwd').mockReturnValue('/Users/foo/my-project');

const unixResult = await computeStorybookMetadata({
packageJson: packageJsonMock,
mainConfig: {
...mainJsMock,
framework: {
name: '/Users/foo/my-project/.storybook/some-local-framework',
},
},
});

expect(unixResult.framework).toEqual({
name: '$SNIP/.storybook/some-local-framework',
});

// @ts-expect-error the property is read only but we can change it for testing purposes
path.sep = '\\';
cwdSpy = jest.spyOn(process, 'cwd').mockReturnValue('C:\\Users\\foo\\my-project');
const windowsResult = await computeStorybookMetadata({
packageJson: packageJsonMock,
mainConfig: {
...mainJsMock,
framework: {
name: 'C:\\Users\\foo\\my-project\\.storybook\\some-local-framework',
},
},
});

expect(windowsResult.framework).toEqual({
name: '$SNIP\\.storybook\\some-local-framework',
});
});
});

test('should return frameworkOptions from mainjs', async () => {
const reactResult = await computeStorybookMetadata({
packageJson: packageJsonMock,
Expand Down

0 comments on commit 8fab502

Please sign in to comment.