-
-
Notifications
You must be signed in to change notification settings - Fork 9.1k
/
example.ts
235 lines (212 loc) · 6.59 KB
/
example.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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
import path from 'path';
import { remove, pathExists, readJSON, writeJSON } from 'fs-extra';
import prompts from 'prompts';
import { getOptionsOrPrompt } from './utils/options';
import { executeCLIStep } from './utils/cli-step';
import { exec } from '../code/lib/cli/src/repro-generators/scripts';
import type { Parameters } from '../code/lib/cli/src/repro-generators/configs';
import { getInterpretedFile } from '../code/lib/core-common';
import { readConfig, writeConfig } from '../code/lib/csf-tools';
import { babelParse } from '../code/lib/csf-tools/src/babelParse';
const frameworks = ['react', 'angular'];
const addons = ['a11y', 'storysource'];
const examplesDir = path.resolve(__dirname, '../examples');
const codeDir = path.resolve(__dirname, '../code');
async function getOptions() {
return getOptionsOrPrompt('yarn example', {
framework: {
description: 'Which framework would you like to use?',
values: frameworks,
required: true as const,
},
addon: {
description: 'Which extra addons (beyond the CLI defaults) would you like installed?',
values: addons,
multiple: true as const,
},
includeStories: {
description: "Include Storybook's own stories?",
promptType: (_, { framework }) => framework === 'react',
},
create: {
description: 'Create the example from scratch (rather than degitting it)?',
},
forceDelete: {
description: 'Always delete an existing example, even if it has the same configuration?',
promptType: false,
},
forceReuse: {
description: 'Always reuse an existing example, even if it has a different configuration?',
promptType: false,
},
link: {
description: 'Link the storybook to the local code?',
inverse: true,
},
start: {
description: 'Start the example Storybook?',
inverse: true,
},
build: {
description: 'Build the example Storybook?',
},
watch: {
description: 'Start building used packages in watch mode as well as the example Storybook?',
},
dryRun: {
description: "Don't execute commands, just list them (dry run)?",
},
});
}
const steps = {
repro: {
command: 'repro',
description: 'Bootstrapping example',
icon: '👷',
hasArgument: true,
options: {
template: { values: frameworks },
e2e: {},
},
},
add: {
command: 'add',
description: 'Adding addon',
icon: '+',
hasArgument: true,
options: {},
},
link: {
command: 'link',
description: 'Linking packages',
icon: '🔗',
hasArgument: true,
options: { local: {}, start: { inverse: true } },
},
build: {
command: 'build',
description: 'Building example',
icon: '🔨',
options: {},
},
dev: {
command: 'dev',
description: 'Starting example',
icon: '🖥 ',
options: {},
},
};
const logger = console;
export const overrideMainConfig = async ({
cwd,
mainOverrides,
}: {
cwd: string;
mainOverrides: Parameters['mainOverrides'];
}) => {
logger.info(`📝 Overwriting main.js with the following configuration:`);
const configDir = path.join(cwd, '.storybook');
const mainConfigPath = getInterpretedFile(path.resolve(configDir, 'main'));
logger.debug(mainOverrides);
const mainConfig = await readConfig(mainConfigPath);
Object.keys(mainOverrides).forEach((field) => {
// NOTE: using setFieldNode and passing the output of babelParse()
mainConfig.setFieldNode([field], mainOverrides[field]);
});
await writeConfig(mainConfig);
};
const addPackageScripts = async ({
cwd,
scripts,
}: {
cwd: string;
scripts: Record<string, string>;
}) => {
logger.info(`🔢 Adding package resolutions:`);
const packageJsonPath = path.join(cwd, 'package.json');
const packageJson = await readJSON(packageJsonPath);
packageJson.scripts = {
...packageJson.scripts,
...scripts,
};
await writeJSON(packageJsonPath, packageJson, { spaces: 2 });
};
async function main() {
const optionValues = await getOptions();
const { framework, forceDelete, forceReuse, link, dryRun } = optionValues;
const cwd = path.join(examplesDir, framework as string);
const exists = await pathExists(cwd);
let shouldDelete = exists && !forceReuse;
if (exists && !forceDelete && !forceReuse) {
const relativePath = path.relative(process.cwd(), cwd);
({ shouldDelete } = await prompts({
type: 'toggle',
message: `${relativePath} already exists, should delete it and create a new one?`,
name: 'shouldDelete',
initial: false,
active: 'yes',
inactive: 'no',
}));
}
if (exists && shouldDelete && !dryRun) await remove(cwd);
if (!exists || shouldDelete) {
await executeCLIStep(steps.repro, {
argument: cwd,
optionValues: { template: framework },
cwd: examplesDir,
dryRun,
});
// TODO -- sb add <addon> doesn't actually work properly:
// - installs in `deps` not `devDeps`
// - does a `workspace:^` install (what does that mean?)
// - doesn't add to `main.js`
// eslint-disable-next-line no-restricted-syntax
for (const addon of optionValues.addon as string[]) {
const addonName = `@storybook/addon-${addon}`;
// eslint-disable-next-line no-await-in-loop
await executeCLIStep(steps.add, { argument: addonName, cwd, dryRun });
}
// TODO copy stories
if (link) {
await executeCLIStep(steps.link, {
argument: cwd,
cwd: codeDir,
dryRun,
optionValues: { local: true, start: false },
});
// TODO -- work out exactly where this should happen
const code = '(c) => ({ ...c, resolve: { ...c.resolve, symlinks: false } })';
const mainOverrides = {
// @ts-ignore (not sure why TS complains here, it does exist)
webpackFinal: babelParse(code).program.body[0].expression,
};
await overrideMainConfig({ cwd, mainOverrides } as any);
await addPackageScripts({
cwd,
scripts: {
storybook:
'NODE_OPTIONS="--preserve-symlinks --preserve-symlinks-main" storybook dev -p 6006',
'build-storybook':
'NODE_OPTIONS="--preserve-symlinks --preserve-symlinks-main" storybook build',
},
});
}
}
const { start } = optionValues;
if (start) {
await exec(
'yarn storybook',
{ cwd },
{
dryRun,
startMessage: `⬆️ Starting Storybook`,
errorMessage: `🚨 Starting Storybook failed`,
}
);
} else {
await executeCLIStep(steps.build, { cwd, dryRun });
// TODO serve
}
// TODO start dev
}
main().catch((err) => console.error(err));