/
normalize-stories.ts
132 lines (114 loc) · 3.92 KB
/
normalize-stories.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
import fs from 'fs';
import path from 'path';
import deprecate from 'util-deprecate';
import dedent from 'ts-dedent';
import { scan } from 'picomatch';
import slash from 'slash';
import type { StoriesEntry, NormalizedStoriesSpecifier } from '../types';
import { normalizeStoryPath } from './paths';
import { globToRegexp } from './glob-to-regexp';
const DEFAULT_TITLE_PREFIX = '';
const DEFAULT_FILES = '**/*.stories.@(mdx|tsx|ts|jsx|js)';
// LEGACY support for bad glob patterns we had in SB 5 - remove in SB7
const fixBadGlob = deprecate(
(match: RegExpMatchArray) => {
return match.input.replace(match[1], `@${match[1]}`);
},
dedent`
You have specified an invalid glob, we've attempted to fix it, please ensure that the glob you specify is valid. See: https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#correct-globs-in-mainjs
`
);
const detectBadGlob = (val: string) => {
const match = val.match(/\.(\([^)]+\))/);
if (match) {
return fixBadGlob(match);
}
return val;
};
const isDirectory = (configDir: string, entry: string) => {
try {
return fs.lstatSync(path.resolve(configDir, entry)).isDirectory();
} catch (err) {
return false;
}
};
export const getDirectoryFromWorkingDir = ({
configDir,
workingDir,
directory,
}: NormalizeOptions & { directory: string }) => {
const directoryFromConfig = path.resolve(configDir, directory);
const directoryFromWorking = path.relative(workingDir, directoryFromConfig);
// relative('/foo', '/foo/src') => 'src'
// but we want `./src` to match importPaths
return normalizeStoryPath(directoryFromWorking);
};
export const normalizeStoriesEntry = (
entry: StoriesEntry,
{ configDir, workingDir }: NormalizeOptions
): NormalizedStoriesSpecifier => {
let specifierWithoutMatcher: Omit<NormalizedStoriesSpecifier, 'importPathMatcher'>;
if (typeof entry === 'string') {
if (!entry.includes('*')) {
if (isDirectory(configDir, entry)) {
specifierWithoutMatcher = {
titlePrefix: DEFAULT_TITLE_PREFIX,
directory: entry,
files: DEFAULT_FILES,
};
} else {
specifierWithoutMatcher = {
titlePrefix: DEFAULT_TITLE_PREFIX,
directory: path.dirname(entry),
files: path.basename(entry),
};
}
} else {
const fixedEntry = detectBadGlob(entry);
const globResult = scan(fixedEntry);
const directory = globResult.isGlob
? globResult.prefix + globResult.base
: path.dirname(fixedEntry);
const filesFallback =
directory !== '.' ? fixedEntry.substr(directory.length + 1) : fixedEntry;
const files = globResult.isGlob ? globResult.glob : filesFallback;
specifierWithoutMatcher = {
titlePrefix: DEFAULT_TITLE_PREFIX,
directory,
files,
};
}
} else {
specifierWithoutMatcher = {
titlePrefix: DEFAULT_TITLE_PREFIX,
files: DEFAULT_FILES,
...entry,
};
}
// We are going to be doing everything with node importPaths which use
// URL format, i.e. `/` as a separator, so let's make sure we've normalized
const files = slash(specifierWithoutMatcher.files);
// At this stage `directory` is relative to `main.js` (the config dir)
// We want to work relative to the working dir, so we transform it here.
const { directory: directoryRelativeToConfig } = specifierWithoutMatcher;
const directory = slash(
getDirectoryFromWorkingDir({
configDir,
workingDir,
directory: directoryRelativeToConfig,
})
).replace(/\/$/, '');
// Now make the importFn matcher.
const importPathMatcher = globToRegexp(`${directory}/${files}`);
return {
...specifierWithoutMatcher,
directory,
importPathMatcher,
};
};
interface NormalizeOptions {
configDir: string;
workingDir: string;
}
export const normalizeStories = (entries: StoriesEntry[], options: NormalizeOptions) =>
entries.map((entry) => normalizeStoriesEntry(entry, options));