mirror of
https://github.com/storybookjs/storybook.git
synced 2025-03-15 05:02:24 +08:00
Merge branch 'next' into docs_updates_contributions
This commit is contained in:
commit
2f4cbb7be7
@ -62,7 +62,7 @@
|
||||
"@types/cross-spawn": "^6.0.2",
|
||||
"cross-spawn": "^7.0.3",
|
||||
"globby": "^11.0.2",
|
||||
"jscodeshift": "^0.14.0",
|
||||
"jscodeshift": "^0.15.1",
|
||||
"lodash": "^4.17.21",
|
||||
"prettier": "^2.8.0",
|
||||
"recast": "^0.23.1"
|
||||
|
@ -34,7 +34,7 @@ export const getDirectoryFromWorkingDir = ({
|
||||
|
||||
export const normalizeStoriesEntry = (
|
||||
entry: StoriesEntry,
|
||||
{ configDir, workingDir, default_files_pattern = DEFAULT_FILES_PATTERN }: NormalizeOptions
|
||||
{ configDir, workingDir, defaultFilesPattern = DEFAULT_FILES_PATTERN }: NormalizeOptions
|
||||
): NormalizedStoriesSpecifier => {
|
||||
let specifierWithoutMatcher: Omit<NormalizedStoriesSpecifier, 'importPathMatcher'>;
|
||||
|
||||
@ -53,7 +53,7 @@ export const normalizeStoriesEntry = (
|
||||
specifierWithoutMatcher = {
|
||||
titlePrefix: DEFAULT_TITLE_PREFIX,
|
||||
directory: entry,
|
||||
files: default_files_pattern,
|
||||
files: defaultFilesPattern,
|
||||
};
|
||||
} else {
|
||||
specifierWithoutMatcher = {
|
||||
@ -65,7 +65,7 @@ export const normalizeStoriesEntry = (
|
||||
} else {
|
||||
specifierWithoutMatcher = {
|
||||
titlePrefix: DEFAULT_TITLE_PREFIX,
|
||||
files: default_files_pattern,
|
||||
files: defaultFilesPattern,
|
||||
...entry,
|
||||
};
|
||||
}
|
||||
@ -99,7 +99,7 @@ export const normalizeStoriesEntry = (
|
||||
interface NormalizeOptions {
|
||||
configDir: string;
|
||||
workingDir: string;
|
||||
default_files_pattern?: string;
|
||||
defaultFilesPattern?: string;
|
||||
}
|
||||
|
||||
export const normalizeStories = (entries: StoriesEntry[], options: NormalizeOptions) => {
|
||||
|
@ -1,14 +1,5 @@
|
||||
import type {
|
||||
Options,
|
||||
PresetProperty,
|
||||
StoriesEntry,
|
||||
StorybookConfig,
|
||||
TestBuildFlags,
|
||||
} from '@storybook/types';
|
||||
import { normalizeStories, commonGlobOptions } from '@storybook/core-common';
|
||||
import { isAbsolute, join, relative } from 'path';
|
||||
import slash from 'slash';
|
||||
import { glob } from 'glob';
|
||||
import type { Options, PresetProperty, StorybookConfig, TestBuildFlags } from '@storybook/types';
|
||||
import { removeMDXEntries } from '../utils/remove-mdx-entries';
|
||||
|
||||
export const framework: PresetProperty<'framework', StorybookConfig> = async (config) => {
|
||||
// This will get called with the values from the user's main config, but before
|
||||
@ -25,49 +16,7 @@ export const framework: PresetProperty<'framework', StorybookConfig> = async (co
|
||||
|
||||
export const stories: PresetProperty<'stories', StorybookConfig> = async (entries, options) => {
|
||||
if (options?.build?.test?.disableMDXEntries) {
|
||||
const list = normalizeStories(entries, {
|
||||
configDir: options.configDir,
|
||||
workingDir: options.configDir,
|
||||
default_files_pattern: '**/*.@(stories.@(js|jsx|mjs|ts|tsx))',
|
||||
});
|
||||
const result = (
|
||||
await Promise.all(
|
||||
list.map(async ({ directory, files, titlePrefix }) => {
|
||||
const pattern = join(directory, files);
|
||||
const absolutePattern = isAbsolute(pattern) ? pattern : join(options.configDir, pattern);
|
||||
const absoluteDirectory = isAbsolute(directory)
|
||||
? directory
|
||||
: join(options.configDir, directory);
|
||||
|
||||
return {
|
||||
files: (
|
||||
await glob(slash(absolutePattern), {
|
||||
...commonGlobOptions(absolutePattern),
|
||||
follow: true,
|
||||
})
|
||||
).map((f) => relative(absoluteDirectory, f)),
|
||||
directory,
|
||||
titlePrefix,
|
||||
};
|
||||
})
|
||||
)
|
||||
).flatMap<StoriesEntry>((expanded, i) => {
|
||||
const filteredEntries = expanded.files.filter((s) => !s.endsWith('.mdx'));
|
||||
// only return the filtered entries when there is something to filter
|
||||
// as webpack is faster with unexpanded globs
|
||||
let items = [];
|
||||
if (filteredEntries.length < expanded.files.length) {
|
||||
items = filteredEntries.map((k) => ({
|
||||
...expanded,
|
||||
files: `**/${k}`,
|
||||
}));
|
||||
} else {
|
||||
items = [list[i]];
|
||||
}
|
||||
|
||||
return items;
|
||||
});
|
||||
return result;
|
||||
return removeMDXEntries(entries, options);
|
||||
}
|
||||
return entries;
|
||||
};
|
||||
|
@ -0,0 +1,248 @@
|
||||
import { glob as globlOriginal } from 'glob';
|
||||
import { type StoriesEntry } from '@storybook/types';
|
||||
import { normalizeStoriesEntry } from '@storybook/core-common';
|
||||
import { join } from 'path';
|
||||
import slash from 'slash';
|
||||
import { removeMDXEntries } from '../remove-mdx-entries';
|
||||
|
||||
const glob = globlOriginal as jest.MockedFunction<typeof globlOriginal>;
|
||||
|
||||
const configDir = '/configDir/';
|
||||
const workingDir = '/';
|
||||
|
||||
jest.mock('glob', () => ({ glob: jest.fn() }));
|
||||
|
||||
const createList = (list: { entry: StoriesEntry; result: string[] }[]) => {
|
||||
return list.reduce<Record<string, { result: string[]; entry: StoriesEntry }>>(
|
||||
(acc, { entry, result }) => {
|
||||
const { directory, files } = normalizeStoriesEntry(entry, {
|
||||
configDir,
|
||||
workingDir,
|
||||
});
|
||||
acc[slash(join('/', directory, files))] = { result, entry };
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
);
|
||||
};
|
||||
|
||||
const createGlobMock = (input: ReturnType<typeof createList>) => {
|
||||
return async (k: string | string[]) => {
|
||||
if (Array.isArray(k)) {
|
||||
throw new Error('do not pass an array to glob during tests');
|
||||
}
|
||||
if (input[slash(k)]) {
|
||||
return input[slash(k)]?.result;
|
||||
}
|
||||
|
||||
throw new Error('can not find key in input');
|
||||
};
|
||||
};
|
||||
|
||||
test('empty', async () => {
|
||||
const list = createList([]);
|
||||
glob.mockImplementation(createGlobMock(list));
|
||||
|
||||
await expect(() => removeMDXEntries(Object.keys(list), { configDir })).rejects
|
||||
.toThrowErrorMatchingInlineSnapshot(`
|
||||
"Storybook could not index your stories.
|
||||
Your main configuration somehow does not contain a 'stories' field, or it resolved to an empty array.
|
||||
|
||||
Please check your main configuration file and make sure it exports a 'stories' field that is not an empty array.
|
||||
|
||||
More info: https://storybook.js.org/docs/react/faq#can-i-have-a-storybook-with-no-local-stories
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
||||
test('minimal', async () => {
|
||||
const list = createList([{ entry: '*.js', result: [] }]);
|
||||
glob.mockImplementation(createGlobMock(list));
|
||||
|
||||
const result = await removeMDXEntries(
|
||||
Object.values(list).map((e) => e.entry),
|
||||
{ configDir }
|
||||
);
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"directory": ".",
|
||||
"files": "*.js",
|
||||
"titlePrefix": "",
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
test('multiple', async () => {
|
||||
const list = createList([
|
||||
{ entry: '*.ts', result: [] },
|
||||
{ entry: '*.js', result: [] },
|
||||
]);
|
||||
glob.mockImplementation(createGlobMock(list));
|
||||
|
||||
const result = await removeMDXEntries(
|
||||
Object.values(list).map((e) => e.entry),
|
||||
{ configDir }
|
||||
);
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"directory": ".",
|
||||
"files": "*.ts",
|
||||
"titlePrefix": "",
|
||||
},
|
||||
Object {
|
||||
"directory": ".",
|
||||
"files": "*.js",
|
||||
"titlePrefix": "",
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
test('mdx but not matching any files', async () => {
|
||||
const list = createList([
|
||||
{ entry: '*.mdx', result: [] },
|
||||
{ entry: '*.js', result: [] },
|
||||
]);
|
||||
glob.mockImplementation(createGlobMock(list));
|
||||
|
||||
const result = await removeMDXEntries(
|
||||
Object.values(list).map((e) => e.entry),
|
||||
{ configDir }
|
||||
);
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"directory": ".",
|
||||
"files": "*.mdx",
|
||||
"titlePrefix": "",
|
||||
},
|
||||
Object {
|
||||
"directory": ".",
|
||||
"files": "*.js",
|
||||
"titlePrefix": "",
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
test('removes entries that only yield mdx files', async () => {
|
||||
const list = createList([
|
||||
{ entry: '*.mdx', result: ['/configDir/my-file.mdx'] },
|
||||
{ entry: '*.js', result: [] },
|
||||
]);
|
||||
glob.mockImplementation(createGlobMock(list));
|
||||
|
||||
const result = await removeMDXEntries(
|
||||
Object.values(list).map((e) => e.entry),
|
||||
{ configDir }
|
||||
);
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"directory": ".",
|
||||
"files": "*.js",
|
||||
"titlePrefix": "",
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
test('expands entries that only yield mixed files', async () => {
|
||||
const list = createList([
|
||||
{ entry: '*.@(mdx|ts)', result: ['/configDir/my-file.mdx', '/configDir/my-file.ts'] },
|
||||
{ entry: '*.js', result: [] },
|
||||
]);
|
||||
glob.mockImplementation(createGlobMock(list));
|
||||
|
||||
const result = await removeMDXEntries(
|
||||
Object.values(list).map((e) => e.entry),
|
||||
{ configDir }
|
||||
);
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"directory": ".",
|
||||
"files": "**/my-file.ts",
|
||||
"titlePrefix": "",
|
||||
},
|
||||
Object {
|
||||
"directory": ".",
|
||||
"files": "*.js",
|
||||
"titlePrefix": "",
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
test('passes titlePrefix', async () => {
|
||||
const list = createList([
|
||||
{
|
||||
entry: { files: '*.@(mdx|ts)', directory: '.', titlePrefix: 'foo' },
|
||||
result: ['/configDir/my-file.mdx', '/configDir/my-file.ts'],
|
||||
},
|
||||
]);
|
||||
glob.mockImplementation(createGlobMock(list));
|
||||
|
||||
const result = await removeMDXEntries(
|
||||
Object.values(list).map((e) => e.entry),
|
||||
{ configDir }
|
||||
);
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"directory": ".",
|
||||
"files": "**/my-file.ts",
|
||||
"titlePrefix": "foo",
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
test('expands to multiple entries', async () => {
|
||||
const list = createList([
|
||||
{
|
||||
entry: { files: '*.@(mdx|ts)', directory: '.', titlePrefix: 'foo' },
|
||||
result: [
|
||||
'/configDir/my-file.mdx',
|
||||
'/configDir/my-file1.ts',
|
||||
'/configDir/my-file2.ts',
|
||||
'/configDir/my-file3.ts',
|
||||
],
|
||||
},
|
||||
]);
|
||||
glob.mockImplementation(createGlobMock(list));
|
||||
|
||||
const result = await removeMDXEntries(
|
||||
Object.values(list).map((e) => e.entry),
|
||||
{ configDir }
|
||||
);
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"directory": ".",
|
||||
"files": "**/my-file1.ts",
|
||||
"titlePrefix": "foo",
|
||||
},
|
||||
Object {
|
||||
"directory": ".",
|
||||
"files": "**/my-file2.ts",
|
||||
"titlePrefix": "foo",
|
||||
},
|
||||
Object {
|
||||
"directory": ".",
|
||||
"files": "**/my-file3.ts",
|
||||
"titlePrefix": "foo",
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
57
code/lib/core-server/src/utils/remove-mdx-entries.ts
Normal file
57
code/lib/core-server/src/utils/remove-mdx-entries.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import type { Options, StoriesEntry } from '@storybook/types';
|
||||
import { normalizeStories, commonGlobOptions } from '@storybook/core-common';
|
||||
import { isAbsolute, join, relative } from 'path';
|
||||
import slash from 'slash';
|
||||
import { glob } from 'glob';
|
||||
|
||||
export async function removeMDXEntries(
|
||||
entries: StoriesEntry[],
|
||||
options: Pick<Options, 'configDir'>
|
||||
): Promise<StoriesEntry[]> {
|
||||
const list = normalizeStories(entries, {
|
||||
configDir: options.configDir,
|
||||
workingDir: options.configDir,
|
||||
defaultFilesPattern: '**/*.@(stories.@(js|jsx|mjs|ts|tsx))',
|
||||
});
|
||||
const result = (
|
||||
await Promise.all(
|
||||
list.map(async ({ directory, files, titlePrefix }) => {
|
||||
const pattern = join(directory, files);
|
||||
const absolutePattern = isAbsolute(pattern) ? pattern : join(options.configDir, pattern);
|
||||
const absoluteDirectory = isAbsolute(directory)
|
||||
? directory
|
||||
: join(options.configDir, directory);
|
||||
|
||||
return {
|
||||
files: (
|
||||
await glob(slash(absolutePattern), {
|
||||
...commonGlobOptions(absolutePattern),
|
||||
follow: true,
|
||||
})
|
||||
).map((f) => relative(absoluteDirectory, f)),
|
||||
directory,
|
||||
titlePrefix,
|
||||
};
|
||||
})
|
||||
)
|
||||
).flatMap<StoriesEntry>(({ directory, files, titlePrefix }, i) => {
|
||||
const filteredEntries = files.filter((s) => !s.endsWith('.mdx'));
|
||||
// only return the filtered entries when there is something to filter
|
||||
// as webpack is faster with unexpanded globs
|
||||
let items = [];
|
||||
if (filteredEntries.length < files.length) {
|
||||
items = filteredEntries.map((k) => ({
|
||||
directory,
|
||||
titlePrefix,
|
||||
files: `**/${k}`,
|
||||
}));
|
||||
} else {
|
||||
items = [
|
||||
{ directory: list[i].directory, titlePrefix: list[i].titlePrefix, files: list[i].files },
|
||||
];
|
||||
}
|
||||
|
||||
return items;
|
||||
});
|
||||
return result;
|
||||
}
|
@ -47,7 +47,7 @@
|
||||
"devDependencies": {
|
||||
"jest": "^29.7.0",
|
||||
"jest-specific-snapshot": "^8.0.0",
|
||||
"jscodeshift": "^0.14.0",
|
||||
"jscodeshift": "^0.15.1",
|
||||
"typescript": "~4.9.3"
|
||||
},
|
||||
"publishConfig": {
|
||||
|
@ -6317,7 +6317,7 @@ __metadata:
|
||||
globby: "npm:^11.0.2"
|
||||
jest: "npm:^29.7.0"
|
||||
jest-specific-snapshot: "npm:^8.0.0"
|
||||
jscodeshift: "npm:^0.14.0"
|
||||
jscodeshift: "npm:^0.15.1"
|
||||
lodash: "npm:^4.17.21"
|
||||
mdast-util-mdx-jsx: "npm:^2.1.2"
|
||||
mdast-util-mdxjs-esm: "npm:^1.3.1"
|
||||
@ -6922,7 +6922,7 @@ __metadata:
|
||||
dependencies:
|
||||
jest: "npm:^29.7.0"
|
||||
jest-specific-snapshot: "npm:^8.0.0"
|
||||
jscodeshift: "npm:^0.14.0"
|
||||
jscodeshift: "npm:^0.15.1"
|
||||
typescript: "npm:~4.9.3"
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
@ -11154,15 +11154,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ast-types@npm:0.15.2":
|
||||
version: 0.15.2
|
||||
resolution: "ast-types@npm:0.15.2"
|
||||
dependencies:
|
||||
tslib: "npm:^2.0.1"
|
||||
checksum: 5b26e3656e9e8d1db8c8d14971d0cb88ca0138aacce72171cb4cd4555fc8dc53c07e821c568e57fe147366931708fefd25cb9d7e880d42ce9cb569947844c962
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ast-types@npm:^0.16.1":
|
||||
version: 0.16.1
|
||||
resolution: "ast-types@npm:0.16.1"
|
||||
@ -20127,37 +20118,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"jscodeshift@npm:^0.14.0":
|
||||
version: 0.14.0
|
||||
resolution: "jscodeshift@npm:0.14.0"
|
||||
dependencies:
|
||||
"@babel/core": "npm:^7.13.16"
|
||||
"@babel/parser": "npm:^7.13.16"
|
||||
"@babel/plugin-proposal-class-properties": "npm:^7.13.0"
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator": "npm:^7.13.8"
|
||||
"@babel/plugin-proposal-optional-chaining": "npm:^7.13.12"
|
||||
"@babel/plugin-transform-modules-commonjs": "npm:^7.13.8"
|
||||
"@babel/preset-flow": "npm:^7.13.13"
|
||||
"@babel/preset-typescript": "npm:^7.13.0"
|
||||
"@babel/register": "npm:^7.13.16"
|
||||
babel-core: "npm:^7.0.0-bridge.0"
|
||||
chalk: "npm:^4.1.2"
|
||||
flow-parser: "npm:0.*"
|
||||
graceful-fs: "npm:^4.2.4"
|
||||
micromatch: "npm:^4.0.4"
|
||||
neo-async: "npm:^2.5.0"
|
||||
node-dir: "npm:^0.1.17"
|
||||
recast: "npm:^0.21.0"
|
||||
temp: "npm:^0.8.4"
|
||||
write-file-atomic: "npm:^2.3.0"
|
||||
peerDependencies:
|
||||
"@babel/preset-env": ^7.1.6
|
||||
bin:
|
||||
jscodeshift: bin/jscodeshift.js
|
||||
checksum: dab63bdb4b7e67d79634fcd3f5dc8b227146e9f68aa88700bc49c5a45b6339d05bd934a98aa53d29abd04f81237d010e7e037799471b2aab66ec7b9a7d752786
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"jscodeshift@npm:^0.15.1":
|
||||
version: 0.15.1
|
||||
resolution: "jscodeshift@npm:0.15.1"
|
||||
@ -25954,18 +25914,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"recast@npm:^0.21.0":
|
||||
version: 0.21.5
|
||||
resolution: "recast@npm:0.21.5"
|
||||
dependencies:
|
||||
ast-types: "npm:0.15.2"
|
||||
esprima: "npm:~4.0.0"
|
||||
source-map: "npm:~0.6.1"
|
||||
tslib: "npm:^2.0.1"
|
||||
checksum: a45168c82195f24fa2c70293a624fece0069a2e8e8adb637f9963777735f81cb3bb62e55172db677ec3573b08b2daaf1eddd85b74da6fe0bd37c9b15eeaf94b4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"recast@npm:^0.23.1, recast@npm:^0.23.3":
|
||||
version: 0.23.4
|
||||
resolution: "recast@npm:0.23.4"
|
||||
|
Loading…
x
Reference in New Issue
Block a user