mirror of
https://github.com/storybookjs/storybook.git
synced 2025-04-05 06:41:17 +08:00
Merge pull request #20719 from storybookjs/feat/react-native-v6-cli-init
CLI: Update init script for react-native v6.5
This commit is contained in:
commit
b814979dd8
@ -14,6 +14,7 @@ module.exports = {
|
||||
'import/no-extraneous-dependencies': ignore,
|
||||
'global-require': ignore,
|
||||
'no-redeclare': ignore,
|
||||
'react/prop-types': ignore,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -1,54 +1,34 @@
|
||||
import { join } from 'path';
|
||||
import chalk from 'chalk';
|
||||
import shell from 'shelljs';
|
||||
import { getBabelDependencies, paddedLog, copyTemplate } from '../../helpers';
|
||||
import { getCliDir } from '../../dirs';
|
||||
import { copyTemplateFiles, getBabelDependencies } from '../../helpers';
|
||||
import type { JsPackageManager } from '../../js-package-manager';
|
||||
import type { NpmOptions } from '../../NpmOptions';
|
||||
import { SupportedLanguage } from '../../project_types';
|
||||
|
||||
const generator = async (
|
||||
packageManager: JsPackageManager,
|
||||
npmOptions: NpmOptions,
|
||||
installServer: boolean
|
||||
npmOptions: NpmOptions
|
||||
): Promise<void> => {
|
||||
// set correct project name on entry files if possible
|
||||
const dirname = shell.ls('-d', 'ios/*.xcodeproj').stdout;
|
||||
|
||||
// Only notify about app name if running in React Native vanilla (Expo projects do not have ios directory)
|
||||
if (dirname) {
|
||||
const projectName = dirname.slice('ios/'.length, dirname.length - '.xcodeproj'.length - 1);
|
||||
|
||||
if (projectName) {
|
||||
shell.sed('-i', '%APP_NAME%', projectName, 'storybook/index.js');
|
||||
} else {
|
||||
paddedLog(
|
||||
chalk.red(
|
||||
'ERR: Could not determine project name, to fix: https://github.com/storybookjs/storybook/issues/1277'
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const packageJson = packageManager.retrievePackageJson();
|
||||
|
||||
const missingReactDom =
|
||||
!packageJson.dependencies['react-dom'] && !packageJson.devDependencies['react-dom'];
|
||||
const reactVersion = packageJson.dependencies.react;
|
||||
|
||||
// should resolve to latest 5.3 version, this is required until react-native storybook supports v6
|
||||
const webAddonsV5 = [
|
||||
'@storybook/addon-links@^5.3',
|
||||
'@storybook/addon-knobs@^5.3',
|
||||
'@storybook/addon-actions@^5.3',
|
||||
const packagesToResolve = [
|
||||
// addon-ondevice-controls peer deps
|
||||
'react-native-safe-area-context',
|
||||
'@react-native-async-storage/async-storage',
|
||||
'@react-native-community/datetimepicker',
|
||||
'@react-native-community/slider',
|
||||
];
|
||||
|
||||
const nativeAddons = ['@storybook/addon-ondevice-knobs', '@storybook/addon-ondevice-actions'];
|
||||
|
||||
const packagesToResolve = [
|
||||
...nativeAddons,
|
||||
'@storybook/react-native',
|
||||
installServer && '@storybook/react-native-server',
|
||||
].filter(Boolean);
|
||||
// change these to latest version once v6 stable is released
|
||||
const packagesWithFixedVersion = [
|
||||
'@storybook/addon-actions@^6.5.14',
|
||||
'@storybook/addon-controls@^6.5.14',
|
||||
'@storybook/addon-ondevice-controls@6.5.0-rc.0',
|
||||
'@storybook/addon-ondevice-actions@6.5.0-rc.0',
|
||||
'@storybook/react-native@6.5.0-rc.0',
|
||||
];
|
||||
|
||||
const resolvedPackages = await packageManager.getVersionedPackages(packagesToResolve);
|
||||
|
||||
@ -56,21 +36,25 @@ const generator = async (
|
||||
|
||||
const packages = [
|
||||
...babelDependencies,
|
||||
...packagesWithFixedVersion,
|
||||
...resolvedPackages,
|
||||
...webAddonsV5,
|
||||
missingReactDom && reactVersion && `react-dom@${reactVersion}`,
|
||||
].filter(Boolean);
|
||||
|
||||
packageManager.addDependencies({ ...npmOptions, packageJson }, packages);
|
||||
packageManager.addScripts({
|
||||
'storybook-generate': 'sb-rn-get-stories',
|
||||
'storybook-watch': 'sb-rn-watcher',
|
||||
});
|
||||
|
||||
if (installServer) {
|
||||
packageManager.addStorybookCommandInScripts({
|
||||
port: 7007,
|
||||
});
|
||||
}
|
||||
const storybookConfigFolder = '.storybook';
|
||||
|
||||
const templateDir = join(getCliDir(), 'templates', 'react-native');
|
||||
copyTemplate(templateDir);
|
||||
await copyTemplateFiles({
|
||||
renderer: 'react-native',
|
||||
language: SupportedLanguage.JAVASCRIPT,
|
||||
destination: storybookConfigFolder,
|
||||
includeCommonAssets: false,
|
||||
});
|
||||
};
|
||||
|
||||
export default generator;
|
||||
|
@ -4,7 +4,7 @@ import { dedent } from 'ts-dedent';
|
||||
import type { NpmOptions } from '../NpmOptions';
|
||||
import type { SupportedRenderers, SupportedFrameworks, Builder } from '../project_types';
|
||||
import { externalFrameworks, CoreBuilder } from '../project_types';
|
||||
import { getBabelDependencies, copyComponents } from '../helpers';
|
||||
import { getBabelDependencies, copyTemplateFiles } from '../helpers';
|
||||
import { configureMain, configurePreview } from './configure';
|
||||
import type { JsPackageManager } from '../js-package-manager';
|
||||
import { getPackageDetails } from '../js-package-manager';
|
||||
@ -309,6 +309,10 @@ export async function baseGenerator(
|
||||
|
||||
if (addComponents) {
|
||||
const templateLocation = hasFrameworkTemplates(framework) ? framework : rendererId;
|
||||
await copyComponents(templateLocation, language, componentsDestinationPath);
|
||||
await copyTemplateFiles({
|
||||
renderer: templateLocation,
|
||||
language,
|
||||
destination: componentsDestinationPath,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -76,7 +76,7 @@ describe('Helpers', () => {
|
||||
(filePath) =>
|
||||
componentsDirectory.includes(filePath) || filePath === '@storybook/react/template/cli'
|
||||
);
|
||||
await helpers.copyComponents('react', language);
|
||||
await helpers.copyTemplateFiles({ renderer: 'react', language });
|
||||
|
||||
const copySpy = jest.spyOn(fse, 'copy');
|
||||
expect(copySpy).toHaveBeenNthCalledWith(
|
||||
@ -95,7 +95,7 @@ describe('Helpers', () => {
|
||||
(fse.pathExists as jest.Mock).mockImplementation((filePath) => {
|
||||
return filePath === '@storybook/react/template/cli' || filePath === './src';
|
||||
});
|
||||
await helpers.copyComponents('react', SupportedLanguage.JAVASCRIPT);
|
||||
await helpers.copyTemplateFiles({ renderer: 'react', language: SupportedLanguage.JAVASCRIPT });
|
||||
expect(fse.copy).toHaveBeenCalledWith(expect.anything(), './src/stories', expect.anything());
|
||||
});
|
||||
|
||||
@ -103,7 +103,7 @@ describe('Helpers', () => {
|
||||
(fse.pathExists as jest.Mock).mockImplementation((filePath) => {
|
||||
return filePath === '@storybook/react/template/cli';
|
||||
});
|
||||
await helpers.copyComponents('react', SupportedLanguage.JAVASCRIPT);
|
||||
await helpers.copyTemplateFiles({ renderer: 'react', language: SupportedLanguage.JAVASCRIPT });
|
||||
expect(fse.copy).toHaveBeenCalledWith(expect.anything(), './stories', expect.anything());
|
||||
});
|
||||
|
||||
@ -111,7 +111,7 @@ describe('Helpers', () => {
|
||||
const renderer = 'unknown renderer' as SupportedRenderers;
|
||||
const expectedMessage = `Unsupported renderer: ${renderer}`;
|
||||
await expect(
|
||||
helpers.copyComponents(renderer, SupportedLanguage.JAVASCRIPT)
|
||||
helpers.copyTemplateFiles({ renderer, language: SupportedLanguage.JAVASCRIPT })
|
||||
).rejects.toThrowError(expectedMessage);
|
||||
});
|
||||
|
||||
|
@ -186,17 +186,25 @@ export function copyTemplate(templateRoot: string, destination = '.') {
|
||||
fse.copySync(templateDir, destination, { overwrite: true });
|
||||
}
|
||||
|
||||
export async function copyComponents(
|
||||
renderer: SupportedFrameworks | SupportedRenderers,
|
||||
language: SupportedLanguage,
|
||||
destination?: string
|
||||
) {
|
||||
type CopyTemplateFilesOptions = {
|
||||
renderer: SupportedFrameworks | SupportedRenderers;
|
||||
language: SupportedLanguage;
|
||||
includeCommonAssets?: boolean;
|
||||
destination?: string;
|
||||
};
|
||||
|
||||
export async function copyTemplateFiles({
|
||||
renderer,
|
||||
language,
|
||||
destination,
|
||||
includeCommonAssets = true,
|
||||
}: CopyTemplateFilesOptions) {
|
||||
const languageFolderMapping: Record<SupportedLanguage, string> = {
|
||||
[SupportedLanguage.JAVASCRIPT]: 'js',
|
||||
[SupportedLanguage.TYPESCRIPT]: 'ts',
|
||||
[SupportedLanguage.TYPESCRIPT_LEGACY]: 'ts-legacy',
|
||||
};
|
||||
const componentsPath = async () => {
|
||||
const templatePath = async () => {
|
||||
const baseDir = getRendererDir(renderer);
|
||||
const assetsDir = join(baseDir, 'template/cli');
|
||||
|
||||
@ -234,10 +242,12 @@ export async function copyComponents(
|
||||
};
|
||||
|
||||
const destinationPath = destination ?? (await targetPath());
|
||||
await fse.copy(join(getCliDir(), 'rendererAssets/common'), destinationPath, {
|
||||
overwrite: true,
|
||||
});
|
||||
await fse.copy(await componentsPath(), destinationPath, { overwrite: true });
|
||||
if (includeCommonAssets) {
|
||||
await fse.copy(join(getCliDir(), 'rendererAssets/common'), destinationPath, {
|
||||
overwrite: true,
|
||||
});
|
||||
}
|
||||
await fse.copy(await templatePath(), destinationPath, { overwrite: true });
|
||||
}
|
||||
|
||||
// Given a package.json, finds any official storybook package within it
|
||||
|
@ -78,21 +78,9 @@ const installStorybook = <Project extends ProjectType>(
|
||||
);
|
||||
|
||||
case ProjectType.REACT_NATIVE: {
|
||||
return (
|
||||
options.yes
|
||||
? Promise.resolve({ server: true })
|
||||
: (prompts([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'server',
|
||||
message:
|
||||
'Do you want to install dependencies necessary to run Storybook server? You can manually do it later by install @storybook/react-native-server',
|
||||
initial: false,
|
||||
},
|
||||
]) as Promise<{ server: boolean }>)
|
||||
)
|
||||
.then(({ server }) => reactNativeGenerator(packageManager, npmOptions, server))
|
||||
.then(commandLog('Adding Storybook support to your "React Native" app\n'));
|
||||
return reactNativeGenerator(packageManager, npmOptions).then(
|
||||
commandLog('Adding Storybook support to your "React Native" app\n')
|
||||
);
|
||||
}
|
||||
|
||||
case ProjectType.QWIK: {
|
||||
@ -349,28 +337,28 @@ async function doInitiate(options: CommandOptions, pkg: PackageJson): Promise<vo
|
||||
telemetry('init', { projectType });
|
||||
}
|
||||
|
||||
await automigrate({ yes: options.yes || process.env.CI === 'true', packageManager: pkgMgr });
|
||||
|
||||
logger.log('\nTo run your Storybook, type:\n');
|
||||
|
||||
if (projectType === ProjectType.ANGULAR) {
|
||||
codeLog([`ng run ${installResult.projectName}:storybook`]);
|
||||
} else {
|
||||
codeLog([packageManager.getRunStorybookCommand()]);
|
||||
if (projectType !== ProjectType.REACT_NATIVE) {
|
||||
await automigrate({ yes: options.yes || process.env.CI === 'true', packageManager: pkgMgr });
|
||||
}
|
||||
|
||||
logger.log('\nFor more information visit:', chalk.cyan('https://storybook.js.org'));
|
||||
|
||||
if (projectType === ProjectType.REACT_NATIVE) {
|
||||
const REACT_NATIVE_REPO = 'https://github.com/storybookjs/react-native';
|
||||
|
||||
if (projectType === ProjectType.ANGULAR) {
|
||||
logger.log('\nTo run your Storybook, type:\n');
|
||||
codeLog([`ng run ${installResult.projectName}:storybook`]);
|
||||
} else if (projectType === ProjectType.REACT_NATIVE) {
|
||||
logger.log();
|
||||
logger.log(chalk.red('NOTE: installation is not 100% automated.'));
|
||||
logger.log(chalk.yellow('NOTE: installation is not 100% automated.\n'));
|
||||
logger.log(`To quickly run Storybook, replace contents of your app entry with:\n`);
|
||||
codeLog(["export {default} from './storybook';"]);
|
||||
codeLog(["export {default} from './.storybook';"]);
|
||||
logger.log('\n Then to run your Storybook, type:\n');
|
||||
codeLog([packageManager.getRunCommand('start')]);
|
||||
logger.log('\n For more in information, see the github readme:\n');
|
||||
logger.log(chalk.cyan(REACT_NATIVE_REPO));
|
||||
logger.log(chalk.cyan('https://github.com/storybookjs/react-native'));
|
||||
logger.log();
|
||||
} else {
|
||||
logger.log('\nTo run your Storybook, type:\n');
|
||||
codeLog([packageManager.getRunStorybookCommand()]);
|
||||
}
|
||||
|
||||
// Add a new line for the clear visibility.
|
||||
|
@ -1,3 +0,0 @@
|
||||
import '@storybook/addon-actions/register';
|
||||
import '@storybook/addon-links/register';
|
||||
import '@storybook/addon-knobs/register';
|
@ -1,29 +0,0 @@
|
||||
// if you use expo remove this line
|
||||
import { AppRegistry } from 'react-native';
|
||||
|
||||
import { getStorybookUI, configure, addDecorator } from '@storybook/react-native';
|
||||
import { withKnobs } from '@storybook/addon-knobs';
|
||||
|
||||
import './rn-addons';
|
||||
|
||||
// enables knobs for all stories
|
||||
addDecorator(withKnobs);
|
||||
|
||||
// import stories
|
||||
configure(
|
||||
() => {
|
||||
require('./stories');
|
||||
},
|
||||
module,
|
||||
false
|
||||
);
|
||||
|
||||
// Refer to https://github.com/storybookjs/react-native/tree/master/app/react-native#getstorybookui-options
|
||||
// To find allowed options for getStorybookUI
|
||||
const StorybookUIRoot = getStorybookUI({});
|
||||
|
||||
// If you are using React Native vanilla and after installation you don't see your app name here, write it manually.
|
||||
// If you use Expo you should remove this line.
|
||||
AppRegistry.registerComponent('%APP_NAME%', () => StorybookUIRoot);
|
||||
|
||||
export default StorybookUIRoot;
|
@ -1,2 +0,0 @@
|
||||
import '@storybook/addon-ondevice-actions/register';
|
||||
import '@storybook/addon-ondevice-knobs/register';
|
@ -1,20 +0,0 @@
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import { text } from '@storybook/addon-knobs';
|
||||
import { storiesOf } from '@storybook/react-native';
|
||||
import React from 'react';
|
||||
import { Text } from 'react-native';
|
||||
import Button from '.';
|
||||
import CenterView from '../CenterView';
|
||||
|
||||
storiesOf('Button', module)
|
||||
.addDecorator((getStory) => <CenterView>{getStory()}</CenterView>)
|
||||
.add('with text', () => (
|
||||
<Button onPress={action('clicked-text')}>
|
||||
<Text>{text('Button text', 'Hello Button')}</Text>
|
||||
</Button>
|
||||
))
|
||||
.add('with some emoji', () => (
|
||||
<Button onPress={action('clicked-emoji')}>
|
||||
<Text>😀 😎 👍 💯</Text>
|
||||
</Button>
|
||||
));
|
@ -1,17 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { TouchableHighlight } from 'react-native';
|
||||
|
||||
export default function Button({ onPress, label }) {
|
||||
return <TouchableHighlight onPress={onPress}>{label}</TouchableHighlight>;
|
||||
}
|
||||
|
||||
Button.defaultProps = {
|
||||
label: null,
|
||||
onPress: () => {},
|
||||
};
|
||||
|
||||
Button.propTypes = {
|
||||
label: PropTypes.node,
|
||||
onPress: PropTypes.func,
|
||||
};
|
@ -1,16 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { View } from 'react-native';
|
||||
import style from './style';
|
||||
|
||||
export default function CenterView({ label }) {
|
||||
return <View style={style.main}>{label}</View>;
|
||||
}
|
||||
|
||||
CenterView.defaultProps = {
|
||||
label: null,
|
||||
};
|
||||
|
||||
CenterView.propTypes = {
|
||||
label: PropTypes.node,
|
||||
};
|
@ -1,8 +0,0 @@
|
||||
export default {
|
||||
main: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#F5FCFF',
|
||||
},
|
||||
};
|
@ -1,6 +0,0 @@
|
||||
import React from 'react';
|
||||
import { linkTo } from '@storybook/addon-links';
|
||||
import { storiesOf } from '@storybook/react-native';
|
||||
import Welcome from '.';
|
||||
|
||||
storiesOf('Welcome', module).add('to Storybook', () => <Welcome showApp={linkTo('Button')} />);
|
@ -1,57 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { View, Text } from 'react-native';
|
||||
|
||||
export default class Welcome extends React.Component {
|
||||
styles = {
|
||||
wrapper: {
|
||||
flex: 1,
|
||||
padding: 24,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
header: {
|
||||
fontSize: 18,
|
||||
marginBottom: 18,
|
||||
},
|
||||
content: {
|
||||
fontSize: 12,
|
||||
marginBottom: 10,
|
||||
lineHeight: 18,
|
||||
},
|
||||
};
|
||||
|
||||
showApp = (event) => {
|
||||
const { showApp } = this.props;
|
||||
event.preventDefault();
|
||||
|
||||
if (showApp) {
|
||||
showApp();
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View style={this.styles.wrapper}>
|
||||
<Text style={this.styles.header}>Welcome to React Native Storybook</Text>
|
||||
<Text style={this.styles.content}>
|
||||
This is a UI Component development environment for your React Native app. Here you can
|
||||
display and interact with your UI components as stories. A story is a single state of one
|
||||
or more UI components. You can have as many stories as you want. In other words a story is
|
||||
like a visual test case.
|
||||
</Text>
|
||||
<Text style={this.styles.content}>
|
||||
We have added some stories inside the "storybook/stories" directory for examples. Try
|
||||
editing the "storybook/stories/Welcome.js" file to edit this message.
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Welcome.defaultProps = {
|
||||
showApp: null,
|
||||
};
|
||||
|
||||
Welcome.propTypes = {
|
||||
showApp: PropTypes.func,
|
||||
};
|
@ -1,2 +0,0 @@
|
||||
import './Button/Button.stories';
|
||||
import './Welcome/Welcome.stories';
|
@ -1,5 +0,0 @@
|
||||
export const parameters = {
|
||||
server: {
|
||||
url: 'http://storybook-server-demo.netlify.app/api',
|
||||
},
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user