Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Codemod: Add option to have relative path in csf factories codemod #30463

Merged
merged 8 commits into from
Feb 5, 2025
Merged
Show file tree
Hide file tree
Changes from 4 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
89 changes: 72 additions & 17 deletions code/lib/cli-storybook/src/codemod/csf-factories.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { type JsPackageManager, syncStorybookAddons } from 'storybook/internal/common';

import prompts from 'prompts';
import { dedent } from 'ts-dedent';

import { runCodemod } from '../automigrate/codemod';
import { getFrameworkPackageName } from '../automigrate/helpers/mainConfigFile';
Expand All @@ -13,19 +14,26 @@ export const logger = console;
async function runStoriesCodemod(options: {
dryRun: boolean | undefined;
packageManager: JsPackageManager;
useSubPathImports: boolean;
previewConfigPath: string;
}) {
const { dryRun, packageManager } = options;
const { dryRun, packageManager, ...codemodOptions } = options;
try {
let globString = 'src/stories/*.stories.*';
let globString = 'src/**/*.stories.*';
if (!process.env.IN_STORYBOOK_SANDBOX) {
logger.log('Please enter the glob for your stories to migrate');
globString = (
await prompts({
type: 'text',
name: 'glob',
message: 'glob',
initial: globString,
})
await prompts(
{
type: 'text',
name: 'glob',
message: 'glob',
initial: globString,
},
{
onCancel: () => process.exit(0),
}
)
).glob;
}

Expand All @@ -39,7 +47,9 @@ async function runStoriesCodemod(options: {
ignoreError: true,
});

await runCodemod(globString, storyToCsfFactory, { dryRun });
await runCodemod(globString, (info) => storyToCsfFactory(info, codemodOptions), {
dryRun,
});
} catch (err: any) {
console.log('err message', err.message);
if (err.message === 'No files matched') {
Expand All @@ -62,15 +72,60 @@ export const csfFactories: CommandFix = {
packageJson,
packageManager,
}) {
logger.log(`Adding imports map in ${packageManager.packageJsonPath()}`);
packageJson.imports = {
...packageJson.imports,
// @ts-expect-error we need to upgrade type-fest
'#*': ['./*', './*.ts', './*.tsx', './*.js', './*.jsx'],
};
await packageManager.writePackageJson(packageJson);
let useSubPathImports = true;
if (!process.env.IN_STORYBOOK_SANDBOX) {
// prompt whether the user wants to use imports map
logger.log(
dedent`
The CSF factories format relies on subpath imports (the imports map in your \`package.json\`), which makes it more convenient to import the preview config in your stories.

We recommend using the **imports map** option, as it's the TypeScript standard for module resolution.
However, please note that this might not work if you have an outdated tsconfig, use custom paths or type alias plugins configured in your project.

More info: https://storybook.js.org/docs/writing-stories/mocking-data-and-modules/mocking-modules#subpath-imports

As we modify your story files, we can provide two options of imports:

- **Subpath imports (recommended):** \`import preview from '#.storybook/preview'\`
- **Relative imports (fallback):** \`import preview from '../../.storybook/preview'\`
yannbf marked this conversation as resolved.
Show resolved Hide resolved
`
);
useSubPathImports = (
await prompts(
{
type: 'select',
name: 'useSubPathImports',
message: 'Which would you like to use?',
choices: [
{ title: 'Subpath imports', value: true },
{ title: 'Relative imports', value: false },
],
initial: 0,
},
{
onCancel: () => process.exit(0),
}
)
).useSubPathImports;
logger.log();
}

await runStoriesCodemod({ dryRun, packageManager });
if (useSubPathImports) {
logger.log(`Adding imports map in ${packageManager.packageJsonPath()}`);
packageJson.imports = {
...packageJson.imports,
// @ts-expect-error we need to upgrade type-fest
'#*': ['./*', './*.ts', './*.tsx', './*.js', './*.jsx'],
};
await packageManager.writePackageJson(packageJson);
}

await runStoriesCodemod({
dryRun,
packageManager,
useSubPathImports,
previewConfigPath: previewConfigPath!,
yannbf marked this conversation as resolved.
Show resolved Hide resolved
});

logger.log('Applying codemod on your main config...');
const frameworkPackage =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,16 @@ describe('main/preview codemod: general parsing functionality', () => {
).toHaveLength(1);
});

it('should leave already transformed code as is', async () => {
const original = dedent`
import { defineMain } from '@storybook/react-vite/node';

export default defineMain({});
`;
const transformed = await transform(original);
expect(transformed).toEqual(original);
});

it('should remove legacy main config type imports', async () => {
await expect(
transform(dedent`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,9 @@ export async function configToCsfFactory(
const existingImport = programNode.body.find(
(node) =>
t.isImportDeclaration(node) &&
node.source.value === configImport.source.value &&
!node.importKind
!t.isImportSpecifier(node) &&
node.importKind !== 'type' &&
node.source.value === configImport.source.value
yannbf marked this conversation as resolved.
Show resolved Hide resolved
);

if (existingImport && t.isImportDeclaration(existingImport)) {
Expand Down
25 changes: 23 additions & 2 deletions code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import { types as t, traverse } from 'storybook/internal/babel';
import { isValidPreviewPath, loadCsf, printCsf } from 'storybook/internal/csf-tools';

import { dirname, relative } from 'path';

import type { FileInfo } from '../../automigrate/codemod';
import { logger } from '../csf-factories';
import { cleanupTypeImports } from './csf-factories-utils';
Expand All @@ -20,7 +22,12 @@
'ComponentMeta',
];

export async function storyToCsfFactory(info: FileInfo) {
type Options = { previewConfigPath: string; useSubPathImports: boolean };

export async function storyToCsfFactory(
info: FileInfo,
{ previewConfigPath, useSubPathImports }: Options

Check failure on line 29 in code/lib/cli-storybook/src/codemod/helpers/story-to-csf-factory.ts

View workflow job for this annotation

GitHub Actions / Core Unit Tests, windows-latest

src/codemod/helpers/story-to-csf-factory.test.ts > stories codemod > javascript > should support non-conventional formats (INCOMPLETE)

TypeError: Cannot destructure property 'previewConfigPath' of 'undefined' as it is undefined. ❯ storyToCsfFactory src/codemod/helpers/story-to-csf-factory.ts:29:5 ❯ transform src/codemod/helpers/story-to-csf-factory.test.ts:18:13 ❯ src/codemod/helpers/story-to-csf-factory.test.ts:234:33
) {
const csf = loadCsf(info.source, { makeTitle: () => 'FIXME' });
try {
csf.parse();
Expand All @@ -46,6 +53,20 @@
n.declarations.some((declaration) => t.isIdentifier(declaration.id, { name: 'preview' }))
);

let previewPath = '#.storybook/preview';
if (!useSubPathImports) {
// calculate relative path from info.path to previewConfigPath
const relativePath = relative(dirname(info.path), previewConfigPath)
// Convert Windows backslashes to forward slashes if present
.replace(/\\/g, '/')
// Remove .ts or .js extension
.replace(/\.(ts|js)x?$/, '');
yannbf marked this conversation as resolved.
Show resolved Hide resolved

// If the path doesn't start with . or .., add ./
const normalizedPath = relativePath.startsWith('.') ? relativePath : `./${relativePath}`;
previewPath = normalizedPath;
}

let sbConfigImportName = hasRootLevelConfig ? 'storybookPreview' : 'preview';

const sbConfigImportSpecifier = t.importDefaultSpecifier(t.identifier(sbConfigImportName));
Expand Down Expand Up @@ -228,7 +249,7 @@
if (hasMeta && !foundConfigImport) {
const configImport = t.importDeclaration(
[t.importDefaultSpecifier(t.identifier(sbConfigImportName))],
t.stringLiteral('#.storybook/preview')
t.stringLiteral(previewPath)
);
programNode.body.unshift(configImport);
}
Expand Down
Loading