Skip to content

Commit

Permalink
Merge pull request #28519 from storybookjs/kasper/cli-split
Browse files Browse the repository at this point in the history
Core: Split Storybook CLI
  • Loading branch information
kasperpeulen authored Aug 5, 2024
2 parents b94ead3 + 4d7a006 commit 0eac7a5
Show file tree
Hide file tree
Showing 213 changed files with 1,013 additions and 543 deletions.
29 changes: 25 additions & 4 deletions code/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,16 @@
"import": "./dist/preview/globals.js",
"require": "./dist/preview/globals.cjs"
},
"./cli": {
"types": "./dist/cli/index.d.ts",
"import": "./dist/cli/index.js",
"require": "./dist/cli/index.cjs"
},
"./cli/bin": {
"types": "./dist/cli/bin/index.d.ts",
"import": "./dist/cli/bin/index.js",
"require": "./dist/cli/bin/index.cjs"
},
"./package.json": "./package.json"
},
"main": "dist/index.cjs",
Expand Down Expand Up @@ -239,6 +249,12 @@
],
"preview/globals": [
"./dist/preview/globals.d.ts"
],
"cli": [
"./dist/cli/index.d.ts"
],
"cli/bin": [
"./dist/cli/bin/index.d.ts"
]
}
},
Expand All @@ -262,7 +278,7 @@
"express": "^4.19.2",
"process": "^0.11.10",
"recast": "^0.23.5",
"util": "^0.12.4",
"semver": "^7.6.2",
"ws": "^8.2.3"
},
"devDependencies": {
Expand All @@ -280,7 +296,7 @@
"@emotion/styled": "^11.11.0",
"@emotion/use-insertion-effect-with-fallbacks": "^1.0.1",
"@fal-works/esbuild-plugin-global-externals": "^2.1.2",
"@ndelangen/fs-extra-unified": "^1.0.3",
"@ndelangen/get-tarball": "^3.0.7",
"@popperjs/core": "^2.6.0",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-scroll-area": "^1.0.5",
Expand All @@ -304,10 +320,11 @@
"@types/picomatch": "^2.3.0",
"@types/prettier": "^3.0.0",
"@types/pretty-hrtime": "^1.0.0",
"@types/prompts": "^2.0.9",
"@types/qs": "^6",
"@types/react-syntax-highlighter": "11.0.5",
"@types/react-transition-group": "^4",
"@types/semver": "^7.3.4",
"@types/semver": "^7.5.8",
"@types/ws": "^8",
"@vitest/utils": "^1.3.1",
"@yarnpkg/esbuild-plugin-pnp": "^3.0.0-rc.10",
Expand All @@ -323,13 +340,15 @@
"chai": "^4.4.1",
"chalk": "^5.3.0",
"cli-table3": "^0.6.1",
"commander": "^6.2.1",
"comment-parser": "^1.4.1",
"compression": "^1.7.4",
"copy-to-clipboard": "^3.3.1",
"cross-spawn": "^7.0.3",
"css": "^3.0.0",
"deep-object-diff": "^1.1.0",
"dequal": "^2.0.2",
"detect-indent": "^7.0.1",
"detect-package-manager": "^3.0.2",
"detect-port": "^1.3.0",
"diff": "^5.2.0",
Expand All @@ -347,12 +366,14 @@
"flush-promises": "^1.0.2",
"fs-extra": "^11.1.0",
"fuse.js": "^3.6.1",
"get-npm-tarball-url": "^2.0.3",
"glob": "^10.0.0",
"globby": "^14.0.1",
"handlebars": "^4.7.7",
"js-yaml": "^4.1.0",
"jsdoc-type-pratt-parser": "^4.0.0",
"lazy-universal-dotenv": "^4.0.0",
"leven": "^4.0.0",
"lodash": "^4.17.21",
"markdown-to-jsx": "^7.4.5",
"memoizerific": "^1.11.3",
Expand All @@ -378,9 +399,9 @@
"react-transition-group": "^4.4.5",
"require-from-string": "^2.0.2",
"resolve-from": "^5.0.0",
"semver": "^7.3.7",
"slash": "^5.0.0",
"store2": "^2.14.2",
"strip-json-comments": "^5.0.1",
"telejson": "^7.2.0",
"tiny-invariant": "^1.3.1",
"tinyspy": "^2.2.0",
Expand Down
2 changes: 2 additions & 0 deletions code/core/scripts/entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ export const getEntries = (cwd: string) => {
define('src/manager/globals-module-info.ts', ['node'], true),
define('src/manager/globals.ts', ['node'], true),
define('src/preview/globals.ts', ['node'], true),
define('src/cli/index.ts', ['node'], true),
define('src/cli/bin/index.ts', ['node'], true),
];
};

Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import fs from 'fs';
import { join } from 'path';
import prompts from 'prompts';
import { dedent } from 'ts-dedent';
import { MissingAngularJsonError } from 'storybook/internal/server-errors';
import { MissingAngularJsonError } from '@storybook/core/server-errors';
import boxen from 'boxen';
import { logger } from '@storybook/core/node-logger';

Expand Down
143 changes: 143 additions & 0 deletions code/core/src/cli/bin/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import program from 'commander';
import chalk from 'chalk';
import leven from 'leven';
import { findPackageSync } from 'fd-package-json';
import invariant from 'tiny-invariant';

import { logger } from '@storybook/core/node-logger';
import { addToGlobalContext } from '@storybook/core/telemetry';
import { parseList, getEnvConfig, versions } from '@storybook/core/common';

import { dev } from '../dev';
import { build } from '../build';

addToGlobalContext('cliVersion', versions.storybook);

const pkg = findPackageSync(__dirname);
invariant(pkg, 'Failed to find the closest package.json file.');
const consoleLogger = console;

const command = (name: string) =>
program
.command(name)
.option(
'--disable-telemetry',
'Disable sending telemetry data',
// default value is false, but if the user sets STORYBOOK_DISABLE_TELEMETRY, it can be true
process.env.STORYBOOK_DISABLE_TELEMETRY && process.env.STORYBOOK_DISABLE_TELEMETRY !== 'false'
)
.option('--debug', 'Get more logs in debug mode', false)
.option('--enable-crash-reports', 'Enable sending crash reports to telemetry data');

command('dev')
.option('-p, --port <number>', 'Port to run Storybook', (str) => parseInt(str, 10))
.option('-h, --host <string>', 'Host to run Storybook')
.option('-c, --config-dir <dir-name>', 'Directory where to load Storybook configurations from')
.option(
'--https',
'Serve Storybook over HTTPS. Note: You must provide your own certificate information.'
)
.option(
'--ssl-ca <ca>',
'Provide an SSL certificate authority. (Optional with --https, required if using a self-signed certificate)',
parseList
)
.option('--ssl-cert <cert>', 'Provide an SSL certificate. (Required with --https)')
.option('--ssl-key <key>', 'Provide an SSL key. (Required with --https)')
.option('--smoke-test', 'Exit after successful start')
.option('--ci', "CI mode (skip interactive prompts, don't open browser)")
.option('--no-open', 'Do not open Storybook automatically in the browser')
.option('--loglevel <level>', 'Control level of logging during build')
.option('--quiet', 'Suppress verbose build output')
.option('--no-version-updates', 'Suppress update check', true)
.option('--debug-webpack', 'Display final webpack configurations for debugging purposes')
.option(
'--webpack-stats-json [directory]',
'Write Webpack stats JSON to disk (synonym for `--stats-json`)'
)
.option('--stats-json [directory]', 'Write stats JSON to disk')
.option(
'--preview-url <string>',
'Disables the default storybook preview and lets your use your own'
)
.option('--force-build-preview', 'Build the preview iframe even if you are using --preview-url')
.option('--docs', 'Build a documentation-only site using addon-docs')
.option('--exact-port', 'Exit early if the desired port is not available')
.option(
'--initial-path [path]',
'URL path to be appended when visiting Storybook for the first time'
)
.action(async (options) => {
logger.setLevel(program.loglevel);
consoleLogger.log(chalk.bold(`${pkg.name} v${pkg.version}`) + chalk.reset('\n'));

// The key is the field created in `options` variable for
// each command line argument. Value is the env variable.
getEnvConfig(options, {
port: 'SBCONFIG_PORT',
host: 'SBCONFIG_HOSTNAME',
staticDir: 'SBCONFIG_STATIC_DIR',
configDir: 'SBCONFIG_CONFIG_DIR',
ci: 'CI',
});

if (parseInt(`${options.port}`, 10)) {
options.port = parseInt(`${options.port}`, 10);
}

await dev({ ...options, packageJson: pkg }).catch(() => process.exit(1));
});

command('build')
.option('-o, --output-dir <dir-name>', 'Directory where to store built files')
.option('-c, --config-dir <dir-name>', 'Directory where to load Storybook configurations from')
.option('--quiet', 'Suppress verbose build output')
.option('--loglevel <level>', 'Control level of logging during build')
.option('--debug-webpack', 'Display final webpack configurations for debugging purposes')
.option(
'--webpack-stats-json [directory]',
'Write Webpack stats JSON to disk (synonym for `--stats-json`)'
)
.option('--stats-json [directory]', 'Write stats JSON to disk')
.option(
'--preview-url <string>',
'Disables the default storybook preview and lets your use your own'
)
.option('--force-build-preview', 'Build the preview iframe even if you are using --preview-url')
.option('--docs', 'Build a documentation-only site using addon-docs')
.option('--test', 'Build stories optimized for testing purposes.')
.action(async (options) => {
process.env.NODE_ENV = process.env.NODE_ENV || 'production';
logger.setLevel(program.loglevel);
consoleLogger.log(chalk.bold(`${pkg.name} v${pkg.version}\n`));

// The key is the field created in `options` variable for
// each command line argument. Value is the env variable.
getEnvConfig(options, {
staticDir: 'SBCONFIG_STATIC_DIR',
outputDir: 'SBCONFIG_OUTPUT_DIR',
configDir: 'SBCONFIG_CONFIG_DIR',
});

await build({
...options,
packageJson: pkg,
test: !!options.test || process.env.SB_TESTBUILD === 'true',
}).catch(() => process.exit(1));
});

program.on('command:*', ([invalidCmd]) => {
consoleLogger.error(
' Invalid command: %s.\n See --help for a list of available commands.',
invalidCmd
);
// eslint-disable-next-line no-underscore-dangle
const availableCommands = program.commands.map((cmd) => cmd._name);
const suggestion = availableCommands.find((cmd) => leven(cmd, invalidCmd) < 3);
if (suggestion) {
consoleLogger.info(`\n Did you mean ${suggestion}?`);
}
process.exit(1);
});

program.usage('<command> [options]').version(String(pkg.version)).parse(process.argv);
File renamed without changes.
File renamed without changes.
8 changes: 4 additions & 4 deletions code/lib/cli/src/detect.ts → code/core/src/cli/detect.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as fs from 'fs';
import findUp from 'find-up';
import { findUpSync } from 'find-up';
import semver from 'semver';
import { logger } from '@storybook/core/node-logger';

Expand Down Expand Up @@ -110,8 +110,8 @@ export function detectFrameworkPreset(
* @returns CoreBuilder
*/
export async function detectBuilder(packageManager: JsPackageManager, projectType: ProjectType) {
const viteConfig = findUp.sync(viteConfigFiles);
const webpackConfig = findUp.sync(webpackConfigFiles);
const viteConfig = findUpSync(viteConfigFiles);
const webpackConfig = findUpSync(webpackConfigFiles);
const dependencies = await packageManager.getAllDependencies();

if (viteConfig || (dependencies.vite && dependencies.webpack === undefined)) {
Expand Down Expand Up @@ -161,7 +161,7 @@ export function isStorybookInstantiated(configDir = resolve(process.cwd(), '.sto
}

export async function detectPnp() {
return !!findUp.sync(['.pnp.js', '.pnp.cjs']);
return !!findUpSync(['.pnp.js', '.pnp.cjs']);
}

export async function detectLanguage(packageManager: JsPackageManager) {
Expand Down
File renamed without changes.
9 changes: 2 additions & 7 deletions code/lib/cli/src/dirs.ts → code/core/src/cli/dirs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,11 @@ import invariant from 'tiny-invariant';
import { externalFrameworks } from './project_types';
import type { SupportedRenderers } from './project_types';
import type { JsPackageManager } from '@storybook/core/common';
import { versions } from '@storybook/core/common';
import { temporaryDirectory, versions } from '@storybook/core/common';
import type { SupportedFrameworks } from '@storybook/core/types';

export function getCliDir() {
return dirname(require.resolve('storybook/package.json'));
}

const resolveUsingBranchInstall = async (packageManager: JsPackageManager, request: string) => {
const { temporaryDirectory } = await import('tempy');
const tempDirectory = temporaryDirectory();
const tempDirectory = await temporaryDirectory();
const name = request as keyof typeof versions;

// FIXME: this might not be the right version for community packages
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,23 @@ import chalk from 'chalk';
import { readConfig, writeConfig } from '@storybook/core/csf-tools';
import type { JsPackageManager } from '@storybook/core/common';
import { paddedLog } from '@storybook/core/common';
import fs from 'node:fs';

export const SUPPORTED_ESLINT_EXTENSIONS = ['js', 'cjs', 'json'];
const UNSUPPORTED_ESLINT_EXTENSIONS = ['yaml', 'yml'];

export const findEslintFile = () => {
const filePrefix = '.eslintrc';
const unsupportedExtension = UNSUPPORTED_ESLINT_EXTENSIONS.find((ext: string) =>
fse.existsSync(`${filePrefix}.${ext}`)
fs.existsSync(`${filePrefix}.${ext}`)
);

if (unsupportedExtension) {
throw new Error(unsupportedExtension);
}

const extension = SUPPORTED_ESLINT_EXTENSIONS.find((ext: string) =>
fse.existsSync(`${filePrefix}.${ext}`)
fs.existsSync(`${filePrefix}.${ext}`)
);
return extension ? `${filePrefix}.${extension}` : null;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ vi.mock('fs', async (importOriginal) => {
vi.mock('./dirs', () => ({
getRendererDir: (_: JsPackageManager, renderer: string) =>
normalizePath(`@storybook/${renderer}`),
getCliDir: () => normalizePath('storybook'),
}));

vi.mock('fs-extra', async (importOriginal) => {
Expand Down Expand Up @@ -123,11 +122,12 @@ describe('Helpers', () => {
renderer: 'react',
language,
packageManager: packageManagerMock,
commonAssetsDir: normalizePath('create-storybook/rendererAssets/common'),
});

expect(fse.copy).toHaveBeenNthCalledWith(
1,
normalizePath('storybook/rendererAssets/common'),
normalizePath('create-storybook/rendererAssets/common'),
'./stories',
expect.anything()
);
Expand Down
Loading

0 comments on commit 0eac7a5

Please sign in to comment.