From 66591f441366ee5d0958e8546402191813e7a54e Mon Sep 17 00:00:00 2001 From: Michael Shilman Date: Mon, 16 Dec 2024 11:26:59 +0800 Subject: [PATCH] CLI: Add index command --- code/core/src/cli/bin/index.ts | 26 +++++++++ code/core/src/cli/buildIndex.ts | 23 ++++++++ code/core/src/core-server/build-index.ts | 71 +++++++++++++++++++++++ code/core/src/core-server/build-static.ts | 3 +- code/core/src/core-server/index.ts | 1 + code/core/src/telemetry/types.ts | 1 + code/lib/cli/src/proxy.ts | 2 +- 7 files changed, 124 insertions(+), 3 deletions(-) create mode 100644 code/core/src/cli/buildIndex.ts create mode 100644 code/core/src/core-server/build-index.ts diff --git a/code/core/src/cli/bin/index.ts b/code/core/src/cli/bin/index.ts index 060879635d6..97933f4a3dc 100644 --- a/code/core/src/cli/bin/index.ts +++ b/code/core/src/cli/bin/index.ts @@ -10,6 +10,7 @@ import picocolors from 'picocolors'; import invariant from 'tiny-invariant'; import { build } from '../build'; +import { buildIndex as index } from '../buildIndex'; import { dev } from '../dev'; addToGlobalContext('cliVersion', versions.storybook); @@ -127,6 +128,31 @@ command('build') }).catch(() => process.exit(1)); }); +command('index') + .option('-o, --output-file ', 'JSON file to output index') + .option('-c, --config-dir ', 'Directory where to load Storybook configurations from') + .option('--quiet', 'Suppress verbose build output') + .option('--loglevel ', 'Control level of logging during build') + .action(async (options) => { + process.env.NODE_ENV = process.env.NODE_ENV || 'production'; + logger.setLevel(options.loglevel); + consoleLogger.log(picocolors.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 index({ + ...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.', diff --git a/code/core/src/cli/buildIndex.ts b/code/core/src/cli/buildIndex.ts new file mode 100644 index 00000000000..eaef494b26f --- /dev/null +++ b/code/core/src/cli/buildIndex.ts @@ -0,0 +1,23 @@ +import { cache } from '@storybook/core/common'; + +import { buildIndexStandalone, withTelemetry } from '@storybook/core/core-server'; + +import { findPackage } from 'fd-package-json'; +import invariant from 'tiny-invariant'; + +export const buildIndex = async (cliOptions: any) => { + const packageJson = await findPackage(__dirname); + invariant(packageJson, 'Failed to find the closest package.json file.'); + const options = { + ...cliOptions, + configDir: cliOptions.configDir || './.storybook', + outputFile: cliOptions.outputFile || './index.json', + ignorePreview: true, + configType: 'PRODUCTION', + cache, + packageJson, + }; + await withTelemetry('index', { cliOptions, presetOptions: options }, () => + buildIndexStandalone(options) + ); +}; diff --git a/code/core/src/core-server/build-index.ts b/code/core/src/core-server/build-index.ts new file mode 100644 index 00000000000..17c13b466da --- /dev/null +++ b/code/core/src/core-server/build-index.ts @@ -0,0 +1,71 @@ +import { writeFile } from 'node:fs/promises'; +import { join } from 'node:path'; +import { resolve } from 'node:path'; + +import { loadAllPresets, loadMainConfig, normalizeStories } from '@storybook/core/common'; +import type { BuilderOptions, CLIOptions, LoadOptions } from '@storybook/core/types'; + +import { logger } from '@storybook/core/node-logger'; + +import { StoryIndexGenerator } from './utils/StoryIndexGenerator'; + +type BuildIndexOptions = CLIOptions & LoadOptions & BuilderOptions & { outputFile: string }; + +export const buildIndex = async (options: BuildIndexOptions) => { + const configDir = resolve(options.configDir ?? '.storybook'); + const config = await loadMainConfig({ + configDir, + noCache: true, + }); + + const { framework } = config; + const corePresets = []; + + const frameworkName = typeof framework === 'string' ? framework : framework?.name; + if (frameworkName) { + corePresets.push(join(frameworkName, 'preset')); + } else if (!options.ignorePreview) { + logger.warn(`you have not specified a framework in your ${options.configDir}/main.js`); + } + + const presets = await loadAllPresets({ + corePresets: [ + require.resolve('@storybook/core/core-server/presets/common-preset'), + ...corePresets, + ], + overridePresets: [ + require.resolve('@storybook/core/core-server/presets/common-override-preset'), + ], + isCritical: true, + ...options, + }); + const [indexers, stories, docsOptions] = await Promise.all([ + // presets.apply('features'), + presets.apply('experimental_indexers', []), + presets.apply('stories', []), + presets.apply('docs', {}), + ]); + + const workingDir = process.cwd(); + const directories = { + configDir, + workingDir, + }; + const normalizedStories = normalizeStories(stories, directories); + const generator = new StoryIndexGenerator(normalizedStories, { + ...directories, + indexers, + docs: docsOptions, + build: {}, + }); + + await generator.initialize(); + const index = await generator.getIndex(); + return index; +}; + +export const buildIndexStandalone = async (options: BuildIndexOptions) => { + const index = await buildIndex(options); + console.log(`Writing index to ${options.outputFile}`); + await writeFile(options.outputFile, JSON.stringify(index, null, 2)); +}; diff --git a/code/core/src/core-server/build-static.ts b/code/core/src/core-server/build-static.ts index ab0d979acaf..9d530ec6543 100644 --- a/code/core/src/core-server/build-static.ts +++ b/code/core/src/core-server/build-static.ts @@ -1,5 +1,4 @@ -import { existsSync } from 'node:fs'; -import { cp, mkdir, readdir } from 'node:fs/promises'; +import { cp, mkdir } from 'node:fs/promises'; import { rm } from 'node:fs/promises'; import { dirname, join, relative, resolve } from 'node:path'; diff --git a/code/core/src/core-server/index.ts b/code/core/src/core-server/index.ts index 5a74e0056c1..4c5264f34ba 100644 --- a/code/core/src/core-server/index.ts +++ b/code/core/src/core-server/index.ts @@ -4,6 +4,7 @@ export { getPreviewHeadTemplate, getPreviewBodyTemplate } from '@storybook/core/ export * from './build-static'; export * from './build-dev'; +export * from './build-index'; export * from './withTelemetry'; export { default as build } from './standalone'; export { mapStaticDir } from './utils/server-statics'; diff --git a/code/core/src/telemetry/types.ts b/code/core/src/telemetry/types.ts index 757f5afc197..9feeab18f45 100644 --- a/code/core/src/telemetry/types.ts +++ b/code/core/src/telemetry/types.ts @@ -8,6 +8,7 @@ export type EventType = | 'boot' | 'dev' | 'build' + | 'index' | 'upgrade' | 'init' | 'scaffolded-empty' diff --git a/code/lib/cli/src/proxy.ts b/code/lib/cli/src/proxy.ts index 12cac7558ae..fdfc39c0fac 100644 --- a/code/lib/cli/src/proxy.ts +++ b/code/lib/cli/src/proxy.ts @@ -4,7 +4,7 @@ import { spawn } from 'child_process'; const args = process.argv.slice(2); -if (['dev', 'build'].includes(args[0])) { +if (['dev', 'build', 'index'].includes(args[0])) { require('@storybook/core/cli/bin'); } else { const proxiedArgs =