Skip to content

Commit adaf63a

Browse files
authored
Merge pull request #30071 from storybookjs/shilman/build-index
2 parents 743bf90 + 7ddaf71 commit adaf63a

File tree

12 files changed

+224
-24
lines changed

12 files changed

+224
-24
lines changed

code/core/src/bin/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import versions from '../common/versions';
66

77
const args = process.argv.slice(2);
88

9-
if (['dev', 'build'].includes(args[0])) {
9+
if (['dev', 'build', 'index'].includes(args[0])) {
1010
require('storybook/internal/cli/bin');
1111
} else {
1212
let command;

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

+29
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import invariant from 'tiny-invariant';
1010

1111
import { version } from '../../../package.json';
1212
import { build } from '../build';
13+
import { buildIndex as index } from '../buildIndex';
1314
import { dev } from '../dev';
1415

1516
addToGlobalContext('cliVersion', versions.storybook);
@@ -133,6 +134,34 @@ command('build')
133134
}).catch(() => process.exit(1));
134135
});
135136

137+
command('index')
138+
.option('-o, --output-file <file-name>', 'JSON file to output index')
139+
.option('-c, --config-dir <dir-name>', 'Directory where to load Storybook configurations from')
140+
.option('--quiet', 'Suppress verbose build output')
141+
.option('--loglevel <level>', 'Control level of logging during build')
142+
.action(async (options) => {
143+
const { env } = process;
144+
env.NODE_ENV = env.NODE_ENV || 'production';
145+
146+
const pkg = await findPackage(__dirname);
147+
invariant(pkg, 'Failed to find the closest package.json file.');
148+
149+
logger.setLevel(options.loglevel);
150+
consoleLogger.log(picocolors.bold(`${pkg.name} v${pkg.version}\n`));
151+
152+
// The key is the field created in `options` variable for
153+
// each command line argument. Value is the env variable.
154+
getEnvConfig(options, {
155+
configDir: 'SBCONFIG_CONFIG_DIR',
156+
outputFile: 'SBCONFIG_OUTPUT_FILE',
157+
});
158+
159+
await index({
160+
...options,
161+
packageJson: pkg,
162+
}).catch(() => process.exit(1));
163+
});
164+
136165
program.on('command:*', ([invalidCmd]) => {
137166
consoleLogger.error(
138167
' Invalid command: %s.\n See --help for a list of available commands.',

code/core/src/cli/buildIndex.ts

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { cache } from 'storybook/internal/common';
2+
import { buildIndexStandalone, withTelemetry } from 'storybook/internal/core-server';
3+
import type { BuilderOptions, CLIBaseOptions } from 'storybook/internal/types';
4+
5+
export interface CLIIndexOptions extends CLIBaseOptions {
6+
configDir?: string;
7+
outputFile?: string;
8+
}
9+
10+
export const buildIndex = async (
11+
cliOptions: CLIIndexOptions & { packageJson?: Record<string, any> }
12+
) => {
13+
const options = {
14+
...cliOptions,
15+
configDir: cliOptions.configDir || '.storybook',
16+
outputFile: cliOptions.outputFile || 'index.json',
17+
ignorePreview: true,
18+
configType: 'PRODUCTION' as BuilderOptions['configType'],
19+
cache,
20+
packageJson: cliOptions.packageJson,
21+
};
22+
const presetOptions = {
23+
...options,
24+
corePresets: [],
25+
overridePresets: [],
26+
};
27+
await withTelemetry('index', { cliOptions, presetOptions }, () => buildIndexStandalone(options));
28+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { describe, expect, it } from 'vitest';
2+
3+
import { buildIndex } from './build-index';
4+
5+
describe('buildIndex', () => {
6+
it('should build index', async () => {
7+
const index = await buildIndex({
8+
configDir: `${__dirname}/utils/__mockdata__`,
9+
});
10+
expect(index).toMatchInlineSnapshot(`
11+
{
12+
"entries": {
13+
"my-component-a--story-one": {
14+
"componentPath": undefined,
15+
"id": "my-component-a--story-one",
16+
"importPath": "./core/src/core-server/utils/__mockdata__/docs-id-generation/A.stories.jsx",
17+
"name": "Story One",
18+
"tags": [
19+
"dev",
20+
"test",
21+
"autodocs",
22+
],
23+
"title": "A",
24+
"type": "story",
25+
},
26+
"my-component-b--docs": {
27+
"id": "my-component-b--docs",
28+
"importPath": "./core/src/core-server/utils/__mockdata__/docs-id-generation/B.docs.mdx",
29+
"name": "Docs",
30+
"storiesImports": [
31+
"./core/src/core-server/utils/__mockdata__/docs-id-generation/B.stories.jsx",
32+
],
33+
"tags": [
34+
"dev",
35+
"test",
36+
"attached-mdx",
37+
],
38+
"title": "B",
39+
"type": "docs",
40+
},
41+
"my-component-b--story-one": {
42+
"componentPath": undefined,
43+
"id": "my-component-b--story-one",
44+
"importPath": "./core/src/core-server/utils/__mockdata__/docs-id-generation/B.stories.jsx",
45+
"name": "Story One",
46+
"tags": [
47+
"dev",
48+
"test",
49+
],
50+
"title": "B",
51+
"type": "story",
52+
},
53+
},
54+
"v": 5,
55+
}
56+
`);
57+
});
58+
});
+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { writeFile } from 'node:fs/promises';
2+
3+
import { normalizeStories } from 'storybook/internal/common';
4+
import { logger } from 'storybook/internal/node-logger';
5+
import type { BuilderOptions, CLIOptions, LoadOptions } from 'storybook/internal/types';
6+
7+
import { loadStorybook } from './load';
8+
import { StoryIndexGenerator } from './utils/StoryIndexGenerator';
9+
10+
export type BuildIndexOptions = CLIOptions & LoadOptions & BuilderOptions;
11+
12+
export const buildIndex = async (options: BuildIndexOptions) => {
13+
const { presets } = await loadStorybook(options);
14+
const [indexers, stories, docsOptions] = await Promise.all([
15+
presets.apply('experimental_indexers', []),
16+
presets.apply('stories', []),
17+
presets.apply('docs', {}),
18+
]);
19+
20+
const { configDir } = options;
21+
const workingDir = process.cwd();
22+
const directories = {
23+
configDir,
24+
workingDir,
25+
};
26+
const normalizedStories = normalizeStories(stories, directories);
27+
const generator = new StoryIndexGenerator(normalizedStories, {
28+
...directories,
29+
indexers,
30+
docs: docsOptions,
31+
build: {},
32+
});
33+
34+
await generator.initialize();
35+
return generator.getIndex();
36+
};
37+
38+
export const buildIndexStandalone = async (options: BuildIndexOptions & { outputFile: string }) => {
39+
const index = await buildIndex(options);
40+
logger.info(`Writing index to ${options.outputFile}`);
41+
await writeFile(options.outputFile, JSON.stringify(index));
42+
};

code/core/src/core-server/build-static.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { existsSync } from 'node:fs';
2-
import { cp, mkdir, readdir } from 'node:fs/promises';
1+
import { cp, mkdir } from 'node:fs/promises';
32
import { rm } from 'node:fs/promises';
43
import { dirname, join, relative, resolve } from 'node:path';
54

code/core/src/core-server/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export { getPreviewHeadTemplate, getPreviewBodyTemplate } from 'storybook/intern
44

55
export * from './build-static';
66
export * from './build-dev';
7+
export * from './build-index';
78
export * from './withTelemetry';
89
export { default as build } from './standalone';
910
export { mapStaticDir } from './utils/server-statics';

code/core/src/core-server/standalone.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { dirname } from 'node:path';
22

33
import { buildDevStandalone } from './build-dev';
4+
import { buildIndexStandalone } from './build-index';
45
import { buildStaticStandalone } from './build-static';
56

67
async function build(options: any = {}, frameworkOptions: any = {}) {
@@ -26,7 +27,11 @@ async function build(options: any = {}, frameworkOptions: any = {}) {
2627
return buildStaticStandalone(commonOptions);
2728
}
2829

29-
throw new Error(`'mode' parameter should be either 'dev' or 'static'`);
30+
if (mode === 'index') {
31+
return buildIndexStandalone(commonOptions);
32+
}
33+
34+
throw new Error(`'mode' parameter should be either 'dev', 'static', or 'index'`);
3035
}
3136

3237
export default build;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export default {
2+
stories: ['docs-id-generation/*.(stories|docs).*'],
3+
framework: '@storybook/react-vite',
4+
};

code/core/src/telemetry/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export type EventType =
88
| 'boot'
99
| 'dev'
1010
| 'build'
11+
| 'index'
1112
| 'upgrade'
1213
| 'init'
1314
| 'scaffolded-empty'

code/core/src/types/modules/core-common.ts

+9-6
Original file line numberDiff line numberDiff line change
@@ -160,17 +160,22 @@ export interface LoadOptions {
160160
extendServer?: (server: HttpServer) => void;
161161
}
162162

163-
export interface CLIOptions {
163+
export interface CLIBaseOptions {
164+
disableTelemetry?: boolean;
165+
enableCrashReports?: boolean;
166+
configDir?: string;
167+
loglevel?: string;
168+
quiet?: boolean;
169+
}
170+
171+
export interface CLIOptions extends CLIBaseOptions {
164172
port?: number;
165173
ignorePreview?: boolean;
166174
previewUrl?: string;
167175
forceBuildPreview?: boolean;
168-
disableTelemetry?: boolean;
169-
enableCrashReports?: boolean;
170176
host?: string;
171177
initialPath?: string;
172178
exactPort?: boolean;
173-
configDir?: string;
174179
https?: boolean;
175180
sslCa?: string[];
176181
sslCert?: string;
@@ -179,8 +184,6 @@ export interface CLIOptions {
179184
managerCache?: boolean;
180185
open?: boolean;
181186
ci?: boolean;
182-
loglevel?: string;
183-
quiet?: boolean;
184187
versionUpdates?: boolean;
185188
docs?: boolean;
186189
test?: boolean;

0 commit comments

Comments
 (0)