Skip to content

Commit 0eac7a5

Browse files
authored
Merge pull request #28519 from storybookjs/kasper/cli-split
Core: Split Storybook CLI
2 parents b94ead3 + 4d7a006 commit 0eac7a5

File tree

213 files changed

+1013
-543
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

213 files changed

+1013
-543
lines changed

code/core/package.json

+25-4
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,16 @@
152152
"import": "./dist/preview/globals.js",
153153
"require": "./dist/preview/globals.cjs"
154154
},
155+
"./cli": {
156+
"types": "./dist/cli/index.d.ts",
157+
"import": "./dist/cli/index.js",
158+
"require": "./dist/cli/index.cjs"
159+
},
160+
"./cli/bin": {
161+
"types": "./dist/cli/bin/index.d.ts",
162+
"import": "./dist/cli/bin/index.js",
163+
"require": "./dist/cli/bin/index.cjs"
164+
},
155165
"./package.json": "./package.json"
156166
},
157167
"main": "dist/index.cjs",
@@ -239,6 +249,12 @@
239249
],
240250
"preview/globals": [
241251
"./dist/preview/globals.d.ts"
252+
],
253+
"cli": [
254+
"./dist/cli/index.d.ts"
255+
],
256+
"cli/bin": [
257+
"./dist/cli/bin/index.d.ts"
242258
]
243259
}
244260
},
@@ -262,7 +278,7 @@
262278
"express": "^4.19.2",
263279
"process": "^0.11.10",
264280
"recast": "^0.23.5",
265-
"util": "^0.12.4",
281+
"semver": "^7.6.2",
266282
"ws": "^8.2.3"
267283
},
268284
"devDependencies": {
@@ -280,7 +296,7 @@
280296
"@emotion/styled": "^11.11.0",
281297
"@emotion/use-insertion-effect-with-fallbacks": "^1.0.1",
282298
"@fal-works/esbuild-plugin-global-externals": "^2.1.2",
283-
"@ndelangen/fs-extra-unified": "^1.0.3",
299+
"@ndelangen/get-tarball": "^3.0.7",
284300
"@popperjs/core": "^2.6.0",
285301
"@radix-ui/react-dialog": "^1.0.5",
286302
"@radix-ui/react-scroll-area": "^1.0.5",
@@ -304,10 +320,11 @@
304320
"@types/picomatch": "^2.3.0",
305321
"@types/prettier": "^3.0.0",
306322
"@types/pretty-hrtime": "^1.0.0",
323+
"@types/prompts": "^2.0.9",
307324
"@types/qs": "^6",
308325
"@types/react-syntax-highlighter": "11.0.5",
309326
"@types/react-transition-group": "^4",
310-
"@types/semver": "^7.3.4",
327+
"@types/semver": "^7.5.8",
311328
"@types/ws": "^8",
312329
"@vitest/utils": "^1.3.1",
313330
"@yarnpkg/esbuild-plugin-pnp": "^3.0.0-rc.10",
@@ -323,13 +340,15 @@
323340
"chai": "^4.4.1",
324341
"chalk": "^5.3.0",
325342
"cli-table3": "^0.6.1",
343+
"commander": "^6.2.1",
326344
"comment-parser": "^1.4.1",
327345
"compression": "^1.7.4",
328346
"copy-to-clipboard": "^3.3.1",
329347
"cross-spawn": "^7.0.3",
330348
"css": "^3.0.0",
331349
"deep-object-diff": "^1.1.0",
332350
"dequal": "^2.0.2",
351+
"detect-indent": "^7.0.1",
333352
"detect-package-manager": "^3.0.2",
334353
"detect-port": "^1.3.0",
335354
"diff": "^5.2.0",
@@ -347,12 +366,14 @@
347366
"flush-promises": "^1.0.2",
348367
"fs-extra": "^11.1.0",
349368
"fuse.js": "^3.6.1",
369+
"get-npm-tarball-url": "^2.0.3",
350370
"glob": "^10.0.0",
351371
"globby": "^14.0.1",
352372
"handlebars": "^4.7.7",
353373
"js-yaml": "^4.1.0",
354374
"jsdoc-type-pratt-parser": "^4.0.0",
355375
"lazy-universal-dotenv": "^4.0.0",
376+
"leven": "^4.0.0",
356377
"lodash": "^4.17.21",
357378
"markdown-to-jsx": "^7.4.5",
358379
"memoizerific": "^1.11.3",
@@ -378,9 +399,9 @@
378399
"react-transition-group": "^4.4.5",
379400
"require-from-string": "^2.0.2",
380401
"resolve-from": "^5.0.0",
381-
"semver": "^7.3.7",
382402
"slash": "^5.0.0",
383403
"store2": "^2.14.2",
404+
"strip-json-comments": "^5.0.1",
384405
"telejson": "^7.2.0",
385406
"tiny-invariant": "^1.3.1",
386407
"tinyspy": "^2.2.0",

code/core/scripts/entries.ts

+2
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ export const getEntries = (cwd: string) => {
3636
define('src/manager/globals-module-info.ts', ['node'], true),
3737
define('src/manager/globals.ts', ['node'], true),
3838
define('src/preview/globals.ts', ['node'], true),
39+
define('src/cli/index.ts', ['node'], true),
40+
define('src/cli/bin/index.ts', ['node'], true),
3941
];
4042
};
4143

File renamed without changes.

code/lib/cli/src/generators/ANGULAR/helpers.ts renamed to code/core/src/cli/angular/helpers.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import fs from 'fs';
22
import { join } from 'path';
33
import prompts from 'prompts';
44
import { dedent } from 'ts-dedent';
5-
import { MissingAngularJsonError } from 'storybook/internal/server-errors';
5+
import { MissingAngularJsonError } from '@storybook/core/server-errors';
66
import boxen from 'boxen';
77
import { logger } from '@storybook/core/node-logger';
88

code/core/src/cli/bin/index.ts

+143
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import program from 'commander';
2+
import chalk from 'chalk';
3+
import leven from 'leven';
4+
import { findPackageSync } from 'fd-package-json';
5+
import invariant from 'tiny-invariant';
6+
7+
import { logger } from '@storybook/core/node-logger';
8+
import { addToGlobalContext } from '@storybook/core/telemetry';
9+
import { parseList, getEnvConfig, versions } from '@storybook/core/common';
10+
11+
import { dev } from '../dev';
12+
import { build } from '../build';
13+
14+
addToGlobalContext('cliVersion', versions.storybook);
15+
16+
const pkg = findPackageSync(__dirname);
17+
invariant(pkg, 'Failed to find the closest package.json file.');
18+
const consoleLogger = console;
19+
20+
const command = (name: string) =>
21+
program
22+
.command(name)
23+
.option(
24+
'--disable-telemetry',
25+
'Disable sending telemetry data',
26+
// default value is false, but if the user sets STORYBOOK_DISABLE_TELEMETRY, it can be true
27+
process.env.STORYBOOK_DISABLE_TELEMETRY && process.env.STORYBOOK_DISABLE_TELEMETRY !== 'false'
28+
)
29+
.option('--debug', 'Get more logs in debug mode', false)
30+
.option('--enable-crash-reports', 'Enable sending crash reports to telemetry data');
31+
32+
command('dev')
33+
.option('-p, --port <number>', 'Port to run Storybook', (str) => parseInt(str, 10))
34+
.option('-h, --host <string>', 'Host to run Storybook')
35+
.option('-c, --config-dir <dir-name>', 'Directory where to load Storybook configurations from')
36+
.option(
37+
'--https',
38+
'Serve Storybook over HTTPS. Note: You must provide your own certificate information.'
39+
)
40+
.option(
41+
'--ssl-ca <ca>',
42+
'Provide an SSL certificate authority. (Optional with --https, required if using a self-signed certificate)',
43+
parseList
44+
)
45+
.option('--ssl-cert <cert>', 'Provide an SSL certificate. (Required with --https)')
46+
.option('--ssl-key <key>', 'Provide an SSL key. (Required with --https)')
47+
.option('--smoke-test', 'Exit after successful start')
48+
.option('--ci', "CI mode (skip interactive prompts, don't open browser)")
49+
.option('--no-open', 'Do not open Storybook automatically in the browser')
50+
.option('--loglevel <level>', 'Control level of logging during build')
51+
.option('--quiet', 'Suppress verbose build output')
52+
.option('--no-version-updates', 'Suppress update check', true)
53+
.option('--debug-webpack', 'Display final webpack configurations for debugging purposes')
54+
.option(
55+
'--webpack-stats-json [directory]',
56+
'Write Webpack stats JSON to disk (synonym for `--stats-json`)'
57+
)
58+
.option('--stats-json [directory]', 'Write stats JSON to disk')
59+
.option(
60+
'--preview-url <string>',
61+
'Disables the default storybook preview and lets your use your own'
62+
)
63+
.option('--force-build-preview', 'Build the preview iframe even if you are using --preview-url')
64+
.option('--docs', 'Build a documentation-only site using addon-docs')
65+
.option('--exact-port', 'Exit early if the desired port is not available')
66+
.option(
67+
'--initial-path [path]',
68+
'URL path to be appended when visiting Storybook for the first time'
69+
)
70+
.action(async (options) => {
71+
logger.setLevel(program.loglevel);
72+
consoleLogger.log(chalk.bold(`${pkg.name} v${pkg.version}`) + chalk.reset('\n'));
73+
74+
// The key is the field created in `options` variable for
75+
// each command line argument. Value is the env variable.
76+
getEnvConfig(options, {
77+
port: 'SBCONFIG_PORT',
78+
host: 'SBCONFIG_HOSTNAME',
79+
staticDir: 'SBCONFIG_STATIC_DIR',
80+
configDir: 'SBCONFIG_CONFIG_DIR',
81+
ci: 'CI',
82+
});
83+
84+
if (parseInt(`${options.port}`, 10)) {
85+
options.port = parseInt(`${options.port}`, 10);
86+
}
87+
88+
await dev({ ...options, packageJson: pkg }).catch(() => process.exit(1));
89+
});
90+
91+
command('build')
92+
.option('-o, --output-dir <dir-name>', 'Directory where to store built files')
93+
.option('-c, --config-dir <dir-name>', 'Directory where to load Storybook configurations from')
94+
.option('--quiet', 'Suppress verbose build output')
95+
.option('--loglevel <level>', 'Control level of logging during build')
96+
.option('--debug-webpack', 'Display final webpack configurations for debugging purposes')
97+
.option(
98+
'--webpack-stats-json [directory]',
99+
'Write Webpack stats JSON to disk (synonym for `--stats-json`)'
100+
)
101+
.option('--stats-json [directory]', 'Write stats JSON to disk')
102+
.option(
103+
'--preview-url <string>',
104+
'Disables the default storybook preview and lets your use your own'
105+
)
106+
.option('--force-build-preview', 'Build the preview iframe even if you are using --preview-url')
107+
.option('--docs', 'Build a documentation-only site using addon-docs')
108+
.option('--test', 'Build stories optimized for testing purposes.')
109+
.action(async (options) => {
110+
process.env.NODE_ENV = process.env.NODE_ENV || 'production';
111+
logger.setLevel(program.loglevel);
112+
consoleLogger.log(chalk.bold(`${pkg.name} v${pkg.version}\n`));
113+
114+
// The key is the field created in `options` variable for
115+
// each command line argument. Value is the env variable.
116+
getEnvConfig(options, {
117+
staticDir: 'SBCONFIG_STATIC_DIR',
118+
outputDir: 'SBCONFIG_OUTPUT_DIR',
119+
configDir: 'SBCONFIG_CONFIG_DIR',
120+
});
121+
122+
await build({
123+
...options,
124+
packageJson: pkg,
125+
test: !!options.test || process.env.SB_TESTBUILD === 'true',
126+
}).catch(() => process.exit(1));
127+
});
128+
129+
program.on('command:*', ([invalidCmd]) => {
130+
consoleLogger.error(
131+
' Invalid command: %s.\n See --help for a list of available commands.',
132+
invalidCmd
133+
);
134+
// eslint-disable-next-line no-underscore-dangle
135+
const availableCommands = program.commands.map((cmd) => cmd._name);
136+
const suggestion = availableCommands.find((cmd) => leven(cmd, invalidCmd) < 3);
137+
if (suggestion) {
138+
consoleLogger.info(`\n Did you mean ${suggestion}?`);
139+
}
140+
process.exit(1);
141+
});
142+
143+
program.usage('<command> [options]').version(String(pkg.version)).parse(process.argv);
File renamed without changes.
File renamed without changes.

code/lib/cli/src/detect.ts renamed to code/core/src/cli/detect.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as fs from 'fs';
2-
import findUp from 'find-up';
2+
import { findUpSync } from 'find-up';
33
import semver from 'semver';
44
import { logger } from '@storybook/core/node-logger';
55

@@ -110,8 +110,8 @@ export function detectFrameworkPreset(
110110
* @returns CoreBuilder
111111
*/
112112
export async function detectBuilder(packageManager: JsPackageManager, projectType: ProjectType) {
113-
const viteConfig = findUp.sync(viteConfigFiles);
114-
const webpackConfig = findUp.sync(webpackConfigFiles);
113+
const viteConfig = findUpSync(viteConfigFiles);
114+
const webpackConfig = findUpSync(webpackConfigFiles);
115115
const dependencies = await packageManager.getAllDependencies();
116116

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

163163
export async function detectPnp() {
164-
return !!findUp.sync(['.pnp.js', '.pnp.cjs']);
164+
return !!findUpSync(['.pnp.js', '.pnp.cjs']);
165165
}
166166

167167
export async function detectLanguage(packageManager: JsPackageManager) {
File renamed without changes.

code/lib/cli/src/dirs.ts renamed to code/core/src/cli/dirs.ts

+2-7
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,11 @@ import invariant from 'tiny-invariant';
77
import { externalFrameworks } from './project_types';
88
import type { SupportedRenderers } from './project_types';
99
import type { JsPackageManager } from '@storybook/core/common';
10-
import { versions } from '@storybook/core/common';
10+
import { temporaryDirectory, versions } from '@storybook/core/common';
1111
import type { SupportedFrameworks } from '@storybook/core/types';
1212

13-
export function getCliDir() {
14-
return dirname(require.resolve('storybook/package.json'));
15-
}
16-
1713
const resolveUsingBranchInstall = async (packageManager: JsPackageManager, request: string) => {
18-
const { temporaryDirectory } = await import('tempy');
19-
const tempDirectory = temporaryDirectory();
14+
const tempDirectory = await temporaryDirectory();
2015
const name = request as keyof typeof versions;
2116

2217
// FIXME: this might not be the right version for community packages

code/lib/cli/src/automigrate/helpers/eslintPlugin.ts renamed to code/core/src/cli/eslintPlugin.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,23 @@ import chalk from 'chalk';
77
import { readConfig, writeConfig } from '@storybook/core/csf-tools';
88
import type { JsPackageManager } from '@storybook/core/common';
99
import { paddedLog } from '@storybook/core/common';
10+
import fs from 'node:fs';
1011

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

1415
export const findEslintFile = () => {
1516
const filePrefix = '.eslintrc';
1617
const unsupportedExtension = UNSUPPORTED_ESLINT_EXTENSIONS.find((ext: string) =>
17-
fse.existsSync(`${filePrefix}.${ext}`)
18+
fs.existsSync(`${filePrefix}.${ext}`)
1819
);
1920

2021
if (unsupportedExtension) {
2122
throw new Error(unsupportedExtension);
2223
}
2324

2425
const extension = SUPPORTED_ESLINT_EXTENSIONS.find((ext: string) =>
25-
fse.existsSync(`${filePrefix}.${ext}`)
26+
fs.existsSync(`${filePrefix}.${ext}`)
2627
);
2728
return extension ? `${filePrefix}.${extension}` : null;
2829
};

code/lib/cli/src/helpers.test.ts renamed to code/core/src/cli/helpers.test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ vi.mock('fs', async (importOriginal) => {
3838
vi.mock('./dirs', () => ({
3939
getRendererDir: (_: JsPackageManager, renderer: string) =>
4040
normalizePath(`@storybook/${renderer}`),
41-
getCliDir: () => normalizePath('storybook'),
4241
}));
4342

4443
vi.mock('fs-extra', async (importOriginal) => {
@@ -123,11 +122,12 @@ describe('Helpers', () => {
123122
renderer: 'react',
124123
language,
125124
packageManager: packageManagerMock,
125+
commonAssetsDir: normalizePath('create-storybook/rendererAssets/common'),
126126
});
127127

128128
expect(fse.copy).toHaveBeenNthCalledWith(
129129
1,
130-
normalizePath('storybook/rendererAssets/common'),
130+
normalizePath('create-storybook/rendererAssets/common'),
131131
'./stories',
132132
expect.anything()
133133
);

0 commit comments

Comments
 (0)