mirror of
https://github.com/storybookjs/storybook.git
synced 2025-04-07 07:21:17 +08:00
Merge pull request #30882 from storybookjs/shilman/rnstorybook-automigration
CLI: Add React Native `.rnstorybook` CLI automigration
This commit is contained in:
commit
04fc157e04
32
MIGRATION.md
32
MIGRATION.md
@ -1,6 +1,7 @@
|
||||
<h1>Migration</h1>
|
||||
|
||||
- [From version 8.x to 9.0.0](#from-version-8x-to-900)
|
||||
- [React-Native config dir renamed](#react-native-config-dir-renamed)
|
||||
- [Addon viewport and addon backgrounds synchronized configuration and use globals](#addon-viewport-and-addon-backgrounds-synchronized-configuration-and-use-globals)
|
||||
- [Manager builder removed alias for `util`, `assert` and `process`](#manager-builder-removed-alias-for-util-assert-and-process)
|
||||
- [Actions addon moved to core](#actions-addon-moved-to-core)
|
||||
@ -125,17 +126,17 @@
|
||||
- [Tab addons cannot manually route, Tool addons can filter their visibility via tabId](#tab-addons-cannot-manually-route-tool-addons-can-filter-their-visibility-via-tabid)
|
||||
- [Removed `config` preset](#removed-config-preset-1)
|
||||
- [From version 7.5.0 to 7.6.0](#from-version-750-to-760)
|
||||
- [CommonJS with Vite is deprecated](#commonjs-with-vite-is-deprecated)
|
||||
- [Using implicit actions during rendering is deprecated](#using-implicit-actions-during-rendering-is-deprecated)
|
||||
- [typescript.skipBabel deprecated](#typescriptskipbabel-deprecated)
|
||||
- [Primary doc block accepts of prop](#primary-doc-block-accepts-of-prop)
|
||||
- [Addons no longer need a peer dependency on React](#addons-no-longer-need-a-peer-dependency-on-react)
|
||||
- [CommonJS with Vite is deprecated](#commonjs-with-vite-is-deprecated)
|
||||
- [Using implicit actions during rendering is deprecated](#using-implicit-actions-during-rendering-is-deprecated)
|
||||
- [typescript.skipBabel deprecated](#typescriptskipbabel-deprecated)
|
||||
- [Primary doc block accepts of prop](#primary-doc-block-accepts-of-prop)
|
||||
- [Addons no longer need a peer dependency on React](#addons-no-longer-need-a-peer-dependency-on-react)
|
||||
- [From version 7.4.0 to 7.5.0](#from-version-740-to-750)
|
||||
- [`storyStoreV6` and `storiesOf` is deprecated](#storystorev6-and-storiesof-is-deprecated)
|
||||
- [`storyIndexers` is replaced with `experimental_indexers`](#storyindexers-is-replaced-with-experimental_indexers)
|
||||
- [`storyStoreV6` and `storiesOf` is deprecated](#storystorev6-and-storiesof-is-deprecated)
|
||||
- [`storyIndexers` is replaced with `experimental_indexers`](#storyindexers-is-replaced-with-experimental_indexers)
|
||||
- [From version 7.0.0 to 7.2.0](#from-version-700-to-720)
|
||||
- [Addon API is more type-strict](#addon-api-is-more-type-strict)
|
||||
- [Addon-controls hideNoControlsWarning parameter is deprecated](#addon-controls-hidenocontrolswarning-parameter-is-deprecated)
|
||||
- [Addon API is more type-strict](#addon-api-is-more-type-strict)
|
||||
- [Addon-controls hideNoControlsWarning parameter is deprecated](#addon-controls-hidenocontrolswarning-parameter-is-deprecated)
|
||||
- [From version 6.5.x to 7.0.0](#from-version-65x-to-700)
|
||||
- [7.0 breaking changes](#70-breaking-changes)
|
||||
- [Dropped support for Node 15 and below](#dropped-support-for-node-15-and-below)
|
||||
@ -161,7 +162,7 @@
|
||||
- [Deploying build artifacts](#deploying-build-artifacts)
|
||||
- [Dropped support for file URLs](#dropped-support-for-file-urls)
|
||||
- [Serving with nginx](#serving-with-nginx)
|
||||
- [Ignore story files from node\_modules](#ignore-story-files-from-node_modules)
|
||||
- [Ignore story files from node_modules](#ignore-story-files-from-node_modules)
|
||||
- [7.0 Core changes](#70-core-changes)
|
||||
- [7.0 feature flags removed](#70-feature-flags-removed)
|
||||
- [Story context is prepared before for supporting fine grained updates](#story-context-is-prepared-before-for-supporting-fine-grained-updates)
|
||||
@ -175,7 +176,7 @@
|
||||
- [Addon-interactions: Interactions debugger is now default](#addon-interactions-interactions-debugger-is-now-default)
|
||||
- [7.0 Vite changes](#70-vite-changes)
|
||||
- [Vite builder uses Vite config automatically](#vite-builder-uses-vite-config-automatically)
|
||||
- [Vite cache moved to node\_modules/.cache/.vite-storybook](#vite-cache-moved-to-node_modulescachevite-storybook)
|
||||
- [Vite cache moved to node_modules/.cache/.vite-storybook](#vite-cache-moved-to-node_modulescachevite-storybook)
|
||||
- [7.0 Webpack changes](#70-webpack-changes)
|
||||
- [Webpack4 support discontinued](#webpack4-support-discontinued)
|
||||
- [Babel mode v7 exclusively](#babel-mode-v7-exclusively)
|
||||
@ -226,7 +227,7 @@
|
||||
- [Dropped addon-docs manual babel configuration](#dropped-addon-docs-manual-babel-configuration)
|
||||
- [Dropped addon-docs manual configuration](#dropped-addon-docs-manual-configuration)
|
||||
- [Autoplay in docs](#autoplay-in-docs)
|
||||
- [Removed STORYBOOK\_REACT\_CLASSES global](#removed-storybook_react_classes-global)
|
||||
- [Removed STORYBOOK_REACT_CLASSES global](#removed-storybook_react_classes-global)
|
||||
- [7.0 Deprecations and default changes](#70-deprecations-and-default-changes)
|
||||
- [storyStoreV7 enabled by default](#storystorev7-enabled-by-default)
|
||||
- [`Story` type deprecated](#story-type-deprecated)
|
||||
@ -441,6 +442,13 @@
|
||||
|
||||
## From version 8.x to 9.0.0
|
||||
|
||||
### React-Native config dir renamed
|
||||
|
||||
In Storybook 9, React Native (RN) projects use the `.rnstorybook` config directory instead of `.storybook`.
|
||||
That makes it easier for RN and React Native Web (RNW) storybooks to co-exist in the same project.
|
||||
|
||||
To upgrade, either rename your `.storybook` directory to `.rnstorybook` or if you wish to continue using `.storybook` (not recommended), you can use the [`configPath`](https://github.com/storybookjs/react-native#configpath) option to specify `.storybook` manually.
|
||||
|
||||
### Addon viewport and addon backgrounds synchronized configuration and use globals
|
||||
|
||||
The feature flags: `viewportStoryGlobals` and `backgroundsStoryGlobals` have been removed, please remove these from your `.storybook/main.ts` file.
|
||||
|
@ -49,6 +49,7 @@
|
||||
"create-storybook": "workspace:*",
|
||||
"cross-spawn": "^7.0.3",
|
||||
"envinfo": "^7.7.3",
|
||||
"execa": "^9.5.2",
|
||||
"fd-package-json": "^1.2.0",
|
||||
"find-up": "^5.0.0",
|
||||
"giget": "^1.0.0",
|
||||
|
@ -23,6 +23,7 @@ import { removeArgtypesRegex } from './remove-argtypes-regex';
|
||||
import { removedGlobalClientAPIs } from './remove-global-client-apis';
|
||||
import { removeLegacyMDX1 } from './remove-legacymdx1';
|
||||
import { rendererToFramework } from './renderer-to-framework';
|
||||
import { rnstorybookConfig } from './rnstorybook-config';
|
||||
import { sbBinary } from './sb-binary';
|
||||
import { sbScripts } from './sb-scripts';
|
||||
import { storyshotsMigration } from './storyshots-migration';
|
||||
@ -70,6 +71,7 @@ export const allFixes: Fix[] = [
|
||||
consolidatedImports,
|
||||
addonExperimentalTest,
|
||||
rendererToFramework,
|
||||
rnstorybookConfig,
|
||||
];
|
||||
|
||||
export const initFixes: Fix[] = [eslintPlugin];
|
||||
|
@ -0,0 +1,158 @@
|
||||
import { existsSync } from 'node:fs';
|
||||
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import type { StorybookConfigRaw } from 'storybook/internal/types';
|
||||
|
||||
// eslint-disable-next-line depend/ban-dependencies
|
||||
import { $ } from 'execa';
|
||||
// eslint-disable-next-line depend/ban-dependencies
|
||||
import { globby } from 'globby';
|
||||
|
||||
import { makePackageManager } from '../helpers/testing-helpers';
|
||||
import { rnstorybookConfig } from './rnstorybook-config';
|
||||
|
||||
const { check } = rnstorybookConfig;
|
||||
|
||||
const mockMainConfig: StorybookConfigRaw = {
|
||||
stories: ['../stories/**/*.stories.@(js|jsx|ts|tsx)'],
|
||||
};
|
||||
|
||||
vi.mock('node:fs');
|
||||
vi.mock('execa');
|
||||
vi.mock('globby');
|
||||
|
||||
describe('react-native-config fix', () => {
|
||||
beforeEach(() => {
|
||||
vi.mocked($).mockClear();
|
||||
// @ts-expect-error blah
|
||||
vi.mocked($).mockResolvedValue({ stdout: '' });
|
||||
vi.mocked(globby).mockResolvedValue(['storybook.requires.ts']);
|
||||
});
|
||||
|
||||
describe('no-ops', () => {
|
||||
it('when @storybook/react-native is not installed', async () => {
|
||||
const packageManager = makePackageManager({
|
||||
devDependencies: {},
|
||||
});
|
||||
|
||||
await expect(
|
||||
check({
|
||||
packageManager,
|
||||
mainConfigPath: '.storybook/main.js',
|
||||
mainConfig: mockMainConfig,
|
||||
storybookVersion: '8.0.0',
|
||||
})
|
||||
).resolves.toBeNull();
|
||||
});
|
||||
|
||||
it('when .storybook directory does not exist', async () => {
|
||||
const packageManager = makePackageManager({
|
||||
devDependencies: {
|
||||
'@storybook/react-native': '^8.0.0',
|
||||
},
|
||||
});
|
||||
|
||||
vi.mocked(existsSync).mockReturnValue(false);
|
||||
|
||||
await expect(
|
||||
check({
|
||||
packageManager,
|
||||
mainConfigPath: '.storybook/main.js',
|
||||
mainConfig: mockMainConfig,
|
||||
storybookVersion: '8.0.0',
|
||||
})
|
||||
).resolves.toBeNull();
|
||||
});
|
||||
|
||||
it('when .rnstorybook directory already exists', async () => {
|
||||
const packageManager = makePackageManager({
|
||||
devDependencies: {
|
||||
'@storybook/react-native': '^8.0.0',
|
||||
},
|
||||
});
|
||||
vi.mocked(existsSync).mockReturnValue(true);
|
||||
|
||||
await expect(
|
||||
check({
|
||||
packageManager,
|
||||
mainConfigPath: '.storybook/main.js',
|
||||
mainConfig: mockMainConfig,
|
||||
storybookVersion: '8.0.0',
|
||||
})
|
||||
).resolves.toBeNull();
|
||||
});
|
||||
|
||||
it('when @storybook/react-native is installed and .storybook exists but no requires file', async () => {
|
||||
const packageManager = makePackageManager({
|
||||
devDependencies: {
|
||||
'@storybook/react-native': '^8.0.0',
|
||||
},
|
||||
});
|
||||
|
||||
// Mock existsSync to return true for .storybook and false for .rnstorybook
|
||||
vi.mocked(existsSync).mockImplementation((path) => path.toString().includes('.storybook'));
|
||||
vi.mocked(globby).mockResolvedValue([]);
|
||||
const result = await check({
|
||||
packageManager,
|
||||
mainConfigPath: '.storybook/main.js',
|
||||
mainConfig: mockMainConfig,
|
||||
storybookVersion: '8.0.0',
|
||||
});
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('continue', () => {
|
||||
it('when @storybook/react-native is installed and .storybook exists', async () => {
|
||||
const packageManager = makePackageManager({
|
||||
devDependencies: {
|
||||
'@storybook/react-native': '^8.0.0',
|
||||
},
|
||||
});
|
||||
|
||||
// Mock existsSync to return true for .storybook and false for .rnstorybook
|
||||
vi.mocked(existsSync).mockImplementation((path) => path.toString().includes('.storybook'));
|
||||
|
||||
const result = await check({
|
||||
packageManager,
|
||||
mainConfigPath: '.storybook/main.js',
|
||||
mainConfig: mockMainConfig,
|
||||
storybookVersion: '8.0.0',
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
storybookDir: expect.stringContaining('.storybook'),
|
||||
rnStorybookDir: expect.stringContaining('.rnstorybook'),
|
||||
dotStorybookReferences: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('when there are references to .storybook in the project', async () => {
|
||||
// @ts-expect-error blah
|
||||
vi.mocked($).mockResolvedValue({ stdout: 'a\nb\nc' });
|
||||
const packageManager = makePackageManager({
|
||||
devDependencies: {
|
||||
'@storybook/react-native': '^8.0.0',
|
||||
},
|
||||
});
|
||||
|
||||
// Mock existsSync to return true for .storybook and false for .rnstorybook
|
||||
vi.mocked(existsSync).mockImplementation((path) => path.toString().includes('.storybook'));
|
||||
|
||||
const result = await check({
|
||||
packageManager,
|
||||
mainConfigPath: '.storybook/main.js',
|
||||
mainConfig: mockMainConfig,
|
||||
storybookVersion: '8.0.0',
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
storybookDir: expect.stringContaining('.storybook'),
|
||||
rnStorybookDir: expect.stringContaining('.rnstorybook'),
|
||||
dotStorybookReferences: ['a', 'b', 'c'],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,100 @@
|
||||
import { existsSync } from 'node:fs';
|
||||
import { readFile, rename, writeFile } from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
|
||||
import picocolors from 'picocolors';
|
||||
import { dedent } from 'ts-dedent';
|
||||
|
||||
import type { Fix } from '../types';
|
||||
|
||||
interface Options {
|
||||
dotStorybookReferences: string[];
|
||||
storybookDir: string;
|
||||
rnStorybookDir: string;
|
||||
}
|
||||
|
||||
/** Replaces all occurrences of a string in a file with another string */
|
||||
async function renameInFile(filePath: string, oldText: string, newText: string): Promise<void> {
|
||||
try {
|
||||
const content = await readFile(filePath, 'utf8');
|
||||
const updatedContent = content.replaceAll(oldText, newText);
|
||||
await writeFile(filePath, updatedContent, 'utf8');
|
||||
} catch (error) {
|
||||
console.error(`Error updating references in ${filePath}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
const getDotStorybookReferences = async () => {
|
||||
try {
|
||||
// eslint-disable-next-line depend/ban-dependencies
|
||||
const { $ } = await import('execa');
|
||||
const { stdout } = await $`git grep -l \\.storybook`;
|
||||
return stdout.split('\n').filter(Boolean);
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export const rnstorybookConfig: Fix<Options> = {
|
||||
id: 'rnstorybook-config',
|
||||
|
||||
versionRange: ['<9.0.0', '>=9.0.0'],
|
||||
|
||||
async check({ packageManager, mainConfigPath }) {
|
||||
const allDependencies = await packageManager.getAllDependencies();
|
||||
|
||||
if (!allDependencies['@storybook/react-native']) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check if .storybook directory exists
|
||||
const projectDir = mainConfigPath ? join(mainConfigPath, '..', '..') : process.cwd();
|
||||
const storybookDir = join(projectDir, '.storybook');
|
||||
const rnStorybookDir = join(projectDir, '.rnstorybook');
|
||||
// eslint-disable-next-line depend/ban-dependencies
|
||||
const { globby } = await import('globby');
|
||||
|
||||
const requiresFiles = await globby(join(storybookDir, 'storybook.requires.*'));
|
||||
|
||||
if (existsSync(storybookDir) && requiresFiles.length > 0 && !existsSync(rnStorybookDir)) {
|
||||
const dotStorybookReferences = await getDotStorybookReferences();
|
||||
return { storybookDir, rnStorybookDir, dotStorybookReferences };
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
prompt({ dotStorybookReferences }) {
|
||||
const references =
|
||||
dotStorybookReferences.length > 0
|
||||
? dedent`
|
||||
We will update the following files to reference ${picocolors.yellow('.rnstorybook')}:
|
||||
${dotStorybookReferences.map((ref: string) => picocolors.cyan('- ' + ref)).join('\n')}
|
||||
`.trim()
|
||||
: dedent`
|
||||
Oddly, we did not find any source files that reference the ${picocolors.yellow('.storybook')} directory.
|
||||
If they exist, please update them by hand to reference ${picocolors.yellow('.rnstorybook')} instead.
|
||||
`.trim();
|
||||
|
||||
return dedent`
|
||||
In Storybook 9, React Native projects use the ${picocolors.yellow('.rnstorybook')} directory for
|
||||
configuration instead of ${picocolors.yellow('.storybook')}.
|
||||
|
||||
${references}
|
||||
|
||||
More info: ${picocolors.cyan('https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#react-native-config-dir-renamed')}
|
||||
|
||||
Would you like to automatically move your config files to the new location?`;
|
||||
},
|
||||
|
||||
async run({ result: { storybookDir, rnStorybookDir, dotStorybookReferences }, dryRun }) {
|
||||
if (!dryRun) {
|
||||
await Promise.all(
|
||||
dotStorybookReferences.map(async (ref) => {
|
||||
await renameInFile(ref, '.storybook', '.rnstorybook');
|
||||
})
|
||||
);
|
||||
await rename(storybookDir, rnStorybookDir);
|
||||
}
|
||||
},
|
||||
};
|
@ -6598,6 +6598,7 @@ __metadata:
|
||||
create-storybook: "workspace:*"
|
||||
cross-spawn: "npm:^7.0.3"
|
||||
envinfo: "npm:^7.7.3"
|
||||
execa: "npm:^9.5.2"
|
||||
fd-package-json: "npm:^1.2.0"
|
||||
find-up: "npm:^5.0.0"
|
||||
giget: "npm:^1.0.0"
|
||||
|
Loading…
x
Reference in New Issue
Block a user