@onlook/storybook-plugin 0.4.0-beta.2 → 0.4.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +671 -177
- package/package.json +6 -3
package/dist/index.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import fs, { existsSync } from 'fs';
|
|
2
|
+
import path3, { dirname, join, relative } from 'path';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
|
+
import { minimatch } from 'minimatch';
|
|
5
|
+
import * as prettier from 'prettier';
|
|
6
|
+
import { Project, SyntaxKind, ts, TypeFlags } from 'ts-morph';
|
|
7
|
+
import { glob } from 'glob';
|
|
4
8
|
import { withDefaultConfig } from 'react-docgen-typescript';
|
|
5
9
|
import generateModule from '@babel/generator';
|
|
6
10
|
import { parse } from '@babel/parser';
|
|
@@ -9,12 +13,612 @@ import * as t from '@babel/types';
|
|
|
9
13
|
import crypto from 'crypto';
|
|
10
14
|
import { chromium } from 'playwright';
|
|
11
15
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
// src/storybook-onlook-plugin.ts
|
|
17
|
+
function getComponentInfo(componentDir, projectRootDir) {
|
|
18
|
+
const fileParseInfo = path3.parse(componentDir);
|
|
19
|
+
const prefixExtRegex = new RegExp(`(\\.\\w+)+(?=\\${fileParseInfo.ext}$)`);
|
|
20
|
+
const prefixExtRegexMatch = fileParseInfo.base.match(prefixExtRegex);
|
|
21
|
+
const prefixExt = prefixExtRegexMatch ? prefixExtRegexMatch[0] : void 0;
|
|
22
|
+
const fileName = fileParseInfo.name.replace(prefixExt || "", "");
|
|
23
|
+
const componentName = fileName === "index" ? fileParseInfo.dir.split("/").pop() : fileName;
|
|
24
|
+
let relativeSourceFilePath = componentDir.replace(projectRootDir, "");
|
|
25
|
+
if (relativeSourceFilePath.startsWith("/") || relativeSourceFilePath.startsWith("\\")) {
|
|
26
|
+
relativeSourceFilePath = componentDir.replace(projectRootDir, "").slice(1);
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
fileBase: fileParseInfo.base,
|
|
30
|
+
fileExt: fileParseInfo.ext,
|
|
31
|
+
fileName,
|
|
32
|
+
filePrefixExt: prefixExt,
|
|
33
|
+
componentName,
|
|
34
|
+
relativeSourceFilePath
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function pascalCase(str) {
|
|
38
|
+
return str.replace(/[_\-.\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : "").replace(/^(.)/, (_, c) => c.toUpperCase());
|
|
39
|
+
}
|
|
40
|
+
function removeQuotesAndWrapWithDoubleQuotes(str) {
|
|
41
|
+
const quoteRemovedStr = str.replace(/"/g, "").replace(/'/g, "");
|
|
42
|
+
return quoteRemovedStr;
|
|
43
|
+
}
|
|
44
|
+
function getTypeFlagsName(flags) {
|
|
45
|
+
const keys = Object.keys(TypeFlags);
|
|
46
|
+
const setFlags = keys.find((key) => flags === TypeFlags[key]);
|
|
47
|
+
return setFlags || "err";
|
|
48
|
+
}
|
|
49
|
+
function getReactPropTypes({
|
|
50
|
+
sourceFile,
|
|
51
|
+
componentName
|
|
52
|
+
}) {
|
|
53
|
+
if (!componentName) {
|
|
54
|
+
return {
|
|
55
|
+
propTypes: void 0
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
let propsPattern = "component-props";
|
|
59
|
+
const pascalComponentName = pascalCase(componentName);
|
|
60
|
+
const propsType = sourceFile.getTypeAlias(`${pascalComponentName}Props`);
|
|
61
|
+
const propsInterface = sourceFile.getInterface(`${pascalComponentName}Props`);
|
|
62
|
+
const propsOnlyType = sourceFile.getTypeAlias("Props");
|
|
63
|
+
const propsOnlyInterface = sourceFile.getInterface("Props");
|
|
64
|
+
const propsInline = sourceFile.getVariableDeclaration(pascalComponentName)?.getInitializerIfKindOrThrow(ts.SyntaxKind.ArrowFunction);
|
|
65
|
+
const props = propsType?.getType() || propsInterface?.getType() || propsOnlyType?.getType() || propsOnlyInterface?.getType() || propsInline?.getParameters()[0]?.getType();
|
|
66
|
+
if (!props) {
|
|
67
|
+
return {
|
|
68
|
+
propTypes: []
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
let propsProperties = [];
|
|
72
|
+
const isPropsIntersection = props.isIntersection();
|
|
73
|
+
if (isPropsIntersection) {
|
|
74
|
+
propsProperties = [];
|
|
75
|
+
const intersectionTypes = props.getIntersectionTypes();
|
|
76
|
+
intersectionTypes.forEach((intersectionType) => {
|
|
77
|
+
const intersectionTypeText = intersectionType.getText();
|
|
78
|
+
if (intersectionTypeText.includes("HTMLAttributes")) return;
|
|
79
|
+
return propsProperties.push(...intersectionType.getProperties());
|
|
80
|
+
});
|
|
81
|
+
} else {
|
|
82
|
+
propsProperties = props.getProperties();
|
|
83
|
+
}
|
|
84
|
+
if (propsOnlyType || propsOnlyInterface) propsPattern = "props";
|
|
85
|
+
if (propsInline) propsPattern = "inline";
|
|
86
|
+
const propTypes = propsProperties.map((prop) => {
|
|
87
|
+
const propName = prop.getName();
|
|
88
|
+
const propType = prop.getValueDeclaration()?.getType();
|
|
89
|
+
if (!propType) {
|
|
90
|
+
return {
|
|
91
|
+
name: propName,
|
|
92
|
+
type: ["err"],
|
|
93
|
+
isOptional: prop.isOptional(),
|
|
94
|
+
value: []
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
if (propType.isUnion() && !propType.isBoolean()) {
|
|
98
|
+
const unionTypes = propType.getUnionTypes();
|
|
99
|
+
const type = Array.from(
|
|
100
|
+
new Set(unionTypes.map((union) => getTypeFlagsName(union.getFlags().valueOf())))
|
|
101
|
+
);
|
|
102
|
+
return {
|
|
103
|
+
name: propName,
|
|
104
|
+
type,
|
|
105
|
+
isOptional: prop.isOptional(),
|
|
106
|
+
value: unionTypes.map(
|
|
107
|
+
(union) => removeQuotesAndWrapWithDoubleQuotes(union.getText())
|
|
108
|
+
)
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
name: propName,
|
|
113
|
+
type: [prop.getValueDeclaration().getType().getText()],
|
|
114
|
+
isOptional: prop.isOptional(),
|
|
115
|
+
value: []
|
|
116
|
+
};
|
|
117
|
+
});
|
|
118
|
+
return {
|
|
119
|
+
propsPattern,
|
|
120
|
+
propTypes
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
var errorDefinition = {
|
|
124
|
+
// Common
|
|
125
|
+
EC00: {
|
|
126
|
+
title: "Unknown error",
|
|
127
|
+
isCustomDetail: true
|
|
128
|
+
},
|
|
129
|
+
EC01: {
|
|
130
|
+
title: "Not yet supported",
|
|
131
|
+
detail: "This preset name is included in the preset bust not yet supported. Please wait for support.",
|
|
132
|
+
isCustomDetail: false
|
|
133
|
+
},
|
|
134
|
+
EC02: {
|
|
135
|
+
title: "Preset is not supported",
|
|
136
|
+
isCustomDetail: true
|
|
137
|
+
},
|
|
138
|
+
EC03: {
|
|
139
|
+
title: "Unable to get component name or file path correctly.",
|
|
140
|
+
detail: "Please check the file path and try again.",
|
|
141
|
+
isCustomDetail: false
|
|
142
|
+
},
|
|
143
|
+
EC04: {
|
|
144
|
+
title: "Could not find argTypes",
|
|
145
|
+
detail: "Error in genReactStoryFile.",
|
|
146
|
+
isCustomDetail: false
|
|
147
|
+
},
|
|
148
|
+
EC05: {
|
|
149
|
+
title: "Could not find meta",
|
|
150
|
+
isCustomDetail: true
|
|
151
|
+
},
|
|
152
|
+
EC06: {
|
|
153
|
+
title: "Could not find initializer",
|
|
154
|
+
detail: "Could not get initializer of ObjectLiteralExpression.",
|
|
155
|
+
isCustomDetail: false
|
|
156
|
+
},
|
|
157
|
+
EC07: {
|
|
158
|
+
title: "Could not find component",
|
|
159
|
+
isCustomDetail: true
|
|
160
|
+
},
|
|
161
|
+
EC08: {
|
|
162
|
+
title: "Could not scan directory",
|
|
163
|
+
isCustomDetail: true
|
|
164
|
+
},
|
|
165
|
+
EC09: {
|
|
166
|
+
title: "Reading directory failed",
|
|
167
|
+
isCustomDetail: true
|
|
168
|
+
},
|
|
169
|
+
EC10: {
|
|
170
|
+
title: "Could not get property from stories",
|
|
171
|
+
isCustomDetail: true
|
|
172
|
+
},
|
|
173
|
+
EC11: {
|
|
174
|
+
title: "File is defective.",
|
|
175
|
+
detail: "An error occurred during abstract syntax tree parsing.\nPlease check your file for problems.",
|
|
176
|
+
isCustomDetail: false
|
|
177
|
+
},
|
|
178
|
+
// Lit
|
|
179
|
+
EL01: {
|
|
180
|
+
title: "Failed to save file",
|
|
181
|
+
isCustomDetail: true
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
var throwErr = (params) => {
|
|
185
|
+
const { errorCode, detail } = params;
|
|
186
|
+
const detailText = errorDefinition[errorCode].isCustomDetail ? detail : errorDefinition[errorCode].detail;
|
|
187
|
+
console.error(`[ASG:${errorCode}] ${errorDefinition[errorCode].title}
|
|
188
|
+
${detailText}`);
|
|
189
|
+
};
|
|
190
|
+
async function genReactStoryFile({
|
|
191
|
+
componentName,
|
|
192
|
+
fileBase,
|
|
193
|
+
fileName,
|
|
194
|
+
filePrefixExt,
|
|
195
|
+
path: path42,
|
|
196
|
+
fileExt,
|
|
197
|
+
relativeSourceFilePath,
|
|
198
|
+
sourceFile,
|
|
199
|
+
prettierConfigPath,
|
|
200
|
+
storiesFolder
|
|
201
|
+
}) {
|
|
202
|
+
if (!componentName || !fileBase) {
|
|
203
|
+
throwErr({
|
|
204
|
+
errorCode: "EC03"
|
|
205
|
+
});
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
const { propTypes } = getReactPropTypes({
|
|
209
|
+
sourceFile,
|
|
210
|
+
componentName
|
|
211
|
+
});
|
|
212
|
+
const pascalComponentName = pascalCase(componentName);
|
|
213
|
+
if (!propTypes) {
|
|
214
|
+
throwErr({
|
|
215
|
+
errorCode: "EC04"
|
|
216
|
+
});
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
const defaultExportDeclaration = sourceFile.getExportedDeclarations();
|
|
220
|
+
let isDefaultExportComponent = false;
|
|
221
|
+
defaultExportDeclaration.forEach((declaration, exportName) => {
|
|
222
|
+
if (exportName === "default") {
|
|
223
|
+
const defaultExportName = declaration[0]?.getSymbol()?.getName();
|
|
224
|
+
isDefaultExportComponent = defaultExportName === pascalComponentName;
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
const pathToComponent = storiesFolder ? "../" : "./";
|
|
228
|
+
const initialCode = `
|
|
229
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
230
|
+
|
|
231
|
+
${isDefaultExportComponent ? `import ${pascalComponentName} from "${pathToComponent}${fileName}${filePrefixExt || ""}";` : `import { ${pascalComponentName} } from "${pathToComponent}${fileName}${filePrefixExt || ""}";`}
|
|
232
|
+
|
|
233
|
+
const meta: Meta<typeof ${pascalComponentName}> = {
|
|
234
|
+
title: "components/${pascalComponentName}",
|
|
235
|
+
component: (args) => <${componentName} {...args} />,
|
|
236
|
+
tags: ["autodocs"],
|
|
237
|
+
args: {},
|
|
238
|
+
argTypes: {},
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
export default meta;
|
|
242
|
+
type Story = StoryObj<typeof meta>;
|
|
243
|
+
|
|
244
|
+
export const Primary: Story = {};
|
|
245
|
+
`;
|
|
246
|
+
const componentCode = `${pascalComponentName}`;
|
|
247
|
+
const args = {};
|
|
248
|
+
propTypes.forEach((prop) => {
|
|
249
|
+
if (prop.isOptional) return args[prop.name] = "undefined";
|
|
250
|
+
let value = prop.value.length > 0 ? `"${prop.value[0]}"` : "undefined";
|
|
251
|
+
if (prop.type.includes("boolean")) value = true;
|
|
252
|
+
args[prop.name] = value;
|
|
253
|
+
});
|
|
254
|
+
const argTypes = {};
|
|
255
|
+
propTypes.forEach((prop) => {
|
|
256
|
+
if (prop.type[0] === "boolean") {
|
|
257
|
+
return argTypes[prop.name] = {
|
|
258
|
+
control: "boolean"
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
if (prop.type[0] === "object") {
|
|
262
|
+
return argTypes[prop.name] = {
|
|
263
|
+
control: "object"
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
if (prop.value.length > 1) {
|
|
267
|
+
return argTypes[prop.name] = {
|
|
268
|
+
control: "select",
|
|
269
|
+
options: prop.value
|
|
270
|
+
};
|
|
271
|
+
} else {
|
|
272
|
+
if (prop.type[0] === "string") {
|
|
273
|
+
return argTypes[prop.name] = {
|
|
274
|
+
control: "text"
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
if (prop.type[0] === "number") {
|
|
278
|
+
return argTypes[prop.name] = {
|
|
279
|
+
control: "number"
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
return {
|
|
285
|
+
fileOptions: {
|
|
286
|
+
componentName,
|
|
287
|
+
fileBase,
|
|
288
|
+
fileName,
|
|
289
|
+
filePrefixExt,
|
|
290
|
+
path: path42,
|
|
291
|
+
fileExt,
|
|
292
|
+
relativeSourceFilePath,
|
|
293
|
+
sourceFile,
|
|
294
|
+
prettierConfigPath
|
|
295
|
+
},
|
|
296
|
+
generateOptions: {
|
|
297
|
+
fileExt: ".stories.tsx",
|
|
298
|
+
initialCode,
|
|
299
|
+
meta: {
|
|
300
|
+
component: componentCode,
|
|
301
|
+
args,
|
|
302
|
+
argTypes
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
function createLightProject() {
|
|
308
|
+
return new Project({
|
|
309
|
+
compilerOptions: { allowJs: true },
|
|
310
|
+
useInMemoryFileSystem: true
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
var resolvedPrettierConfig = null;
|
|
314
|
+
async function getPrettierConfig(prettierConfigPath) {
|
|
315
|
+
if (resolvedPrettierConfig) return resolvedPrettierConfig;
|
|
316
|
+
resolvedPrettierConfig = prettierConfigPath ? await prettier.resolveConfig(prettierConfigPath) : {
|
|
317
|
+
semi: true,
|
|
318
|
+
trailingComma: "all",
|
|
319
|
+
singleQuote: false,
|
|
320
|
+
printWidth: 80,
|
|
321
|
+
tabWidth: 2,
|
|
322
|
+
endOfLine: "lf"
|
|
323
|
+
};
|
|
324
|
+
return resolvedPrettierConfig ?? {};
|
|
325
|
+
}
|
|
326
|
+
async function genStoryFile({
|
|
327
|
+
options,
|
|
328
|
+
id,
|
|
329
|
+
projectRootDir
|
|
330
|
+
}) {
|
|
331
|
+
if (id.includes(".stories")) return;
|
|
332
|
+
const {
|
|
333
|
+
fileBase,
|
|
334
|
+
fileName,
|
|
335
|
+
fileExt,
|
|
336
|
+
filePrefixExt,
|
|
337
|
+
componentName,
|
|
338
|
+
relativeSourceFilePath
|
|
339
|
+
} = getComponentInfo(id, projectRootDir);
|
|
340
|
+
try {
|
|
341
|
+
const sourceCode = fs.readFileSync(id, "utf-8");
|
|
342
|
+
const sourceProject = createLightProject();
|
|
343
|
+
const sourceFile = sourceProject.createSourceFile(fileBase || "temp.tsx", sourceCode);
|
|
344
|
+
let genStoryFileOptions;
|
|
345
|
+
if (options.preset === "react") {
|
|
346
|
+
genStoryFileOptions = await genReactStoryFile({
|
|
347
|
+
componentName,
|
|
348
|
+
fileBase,
|
|
349
|
+
fileName,
|
|
350
|
+
path: id,
|
|
351
|
+
fileExt,
|
|
352
|
+
filePrefixExt,
|
|
353
|
+
relativeSourceFilePath,
|
|
354
|
+
sourceFile,
|
|
355
|
+
prettierConfigPath: options.prettierConfigPath,
|
|
356
|
+
storiesFolder: options.storiesFolder
|
|
357
|
+
});
|
|
358
|
+
} else {
|
|
359
|
+
throwErr({
|
|
360
|
+
errorCode: "EC02",
|
|
361
|
+
detail: `Preset ${options.preset} is not supported in this fork. Only "react" is supported.`
|
|
362
|
+
});
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
if (!genStoryFileOptions) {
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
const storiesFilePath = genStoryFileOptions.fileOptions.path.replace(
|
|
369
|
+
genStoryFileOptions.fileOptions.filePrefixExt ? genStoryFileOptions.fileOptions.filePrefixExt : `${genStoryFileOptions.fileOptions.fileExt}`,
|
|
370
|
+
genStoryFileOptions.generateOptions.fileExt
|
|
371
|
+
);
|
|
372
|
+
let storiesFolderPath = "";
|
|
373
|
+
let storiesFilePathWithStoriesFolder = "";
|
|
374
|
+
if (options.storiesFolder) {
|
|
375
|
+
const splitStoriesFilePath = storiesFilePath.split("/");
|
|
376
|
+
const fileNameWithStoriesFolder = `${options.storiesFolder}/${splitStoriesFilePath[splitStoriesFilePath.length - 1]}`;
|
|
377
|
+
storiesFilePathWithStoriesFolder = storiesFilePath.replace(
|
|
378
|
+
`${genStoryFileOptions.fileOptions.fileName}${genStoryFileOptions.generateOptions.fileExt}`,
|
|
379
|
+
fileNameWithStoriesFolder
|
|
380
|
+
);
|
|
381
|
+
storiesFolderPath = storiesFilePath.replace(
|
|
382
|
+
`${genStoryFileOptions.fileOptions.fileName}${genStoryFileOptions.generateOptions.fileExt}`,
|
|
383
|
+
options.storiesFolder || ""
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
const storiesFilePathFinal = options.storiesFolder ? storiesFilePathWithStoriesFolder : storiesFilePath;
|
|
387
|
+
const storyExists = fs.existsSync(storiesFilePathFinal);
|
|
388
|
+
if (!storyExists) {
|
|
389
|
+
if (options.storiesFolder) {
|
|
390
|
+
fs.mkdirSync(storiesFolderPath, { recursive: true });
|
|
391
|
+
}
|
|
392
|
+
fs.writeFileSync(
|
|
393
|
+
storiesFilePathFinal,
|
|
394
|
+
genStoryFileOptions.generateOptions.initialCode
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
const storiesProject = new Project();
|
|
398
|
+
const storiesSourceFile = storiesProject.addSourceFileAtPath(storiesFilePathFinal);
|
|
399
|
+
const meta = storiesSourceFile.getVariableDeclaration("meta");
|
|
400
|
+
if (!meta || !meta.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression)) {
|
|
401
|
+
throwErr({
|
|
402
|
+
errorCode: "EC05",
|
|
403
|
+
detail: `Could not find meta in file ${storiesSourceFile.getFilePath()}`
|
|
404
|
+
});
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
const initializer = meta.getInitializerIfKindOrThrow(
|
|
408
|
+
SyntaxKind.ObjectLiteralExpression
|
|
409
|
+
);
|
|
410
|
+
if (!initializer) {
|
|
411
|
+
throwErr({ errorCode: "EC06" });
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
if (genStoryFileOptions.generateOptions.meta.render) {
|
|
415
|
+
let renderProperty = initializer.getProperty("render");
|
|
416
|
+
while (!renderProperty) {
|
|
417
|
+
initializer.addPropertyAssignment({
|
|
418
|
+
name: "render",
|
|
419
|
+
initializer: "() => {}"
|
|
420
|
+
});
|
|
421
|
+
renderProperty = initializer.getProperty("render");
|
|
422
|
+
}
|
|
423
|
+
renderProperty.set({
|
|
424
|
+
initializer: genStoryFileOptions.generateOptions.meta.render
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
if (genStoryFileOptions.generateOptions.meta.component) {
|
|
428
|
+
let componentProperty = initializer.getProperty("component");
|
|
429
|
+
while (!componentProperty) {
|
|
430
|
+
initializer.addPropertyAssignment({
|
|
431
|
+
name: "component",
|
|
432
|
+
initializer: "null"
|
|
433
|
+
});
|
|
434
|
+
componentProperty = initializer.getProperty("component");
|
|
435
|
+
}
|
|
436
|
+
componentProperty.set({
|
|
437
|
+
initializer: genStoryFileOptions.generateOptions.meta.component
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
if (genStoryFileOptions.generateOptions.meta.args) {
|
|
441
|
+
let argsProperty = initializer.getProperty("args");
|
|
442
|
+
while (!argsProperty) {
|
|
443
|
+
initializer.addPropertyAssignment({
|
|
444
|
+
name: "args",
|
|
445
|
+
initializer: "{}"
|
|
446
|
+
});
|
|
447
|
+
argsProperty = initializer.getProperty("args");
|
|
448
|
+
}
|
|
449
|
+
const argText = Object.entries(genStoryFileOptions.generateOptions.meta.args).map((x) => x.join(":")).join(", ");
|
|
450
|
+
argsProperty.set({ initializer: `{ ${argText} }` });
|
|
451
|
+
}
|
|
452
|
+
if (genStoryFileOptions.generateOptions.meta.argTypes) {
|
|
453
|
+
let argTypesProperty = initializer.getProperty("argTypes");
|
|
454
|
+
while (!argTypesProperty) {
|
|
455
|
+
initializer.addPropertyAssignment({
|
|
456
|
+
name: "argTypes",
|
|
457
|
+
initializer: "{}"
|
|
458
|
+
});
|
|
459
|
+
argTypesProperty = initializer.getProperty("argTypes");
|
|
460
|
+
}
|
|
461
|
+
const argTypesText = JSON.stringify(
|
|
462
|
+
genStoryFileOptions.generateOptions.meta.argTypes,
|
|
463
|
+
null,
|
|
464
|
+
""
|
|
465
|
+
);
|
|
466
|
+
argTypesProperty.set({ initializer: `${argTypesText}` });
|
|
467
|
+
}
|
|
468
|
+
await storiesSourceFile.save();
|
|
469
|
+
const fileContent = fs.readFileSync(storiesFilePathFinal, "utf-8");
|
|
470
|
+
const config = await getPrettierConfig(
|
|
471
|
+
genStoryFileOptions.fileOptions.prettierConfigPath
|
|
472
|
+
);
|
|
473
|
+
const formattedContent = await prettier.format(fileContent, {
|
|
474
|
+
...config,
|
|
475
|
+
parser: "typescript"
|
|
476
|
+
});
|
|
477
|
+
fs.writeFileSync(storiesFilePathFinal, formattedContent);
|
|
478
|
+
} catch (err) {
|
|
479
|
+
console.warn(`[ASG] Failed to generate story for ${id}:`, err);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
async function getAllFilePaths({
|
|
483
|
+
patterns,
|
|
484
|
+
ignorePatterns,
|
|
485
|
+
projectRootDir
|
|
486
|
+
}) {
|
|
487
|
+
const fullPatterns = patterns.map(
|
|
488
|
+
(p) => path3.join(projectRootDir, p).replace(/\\/g, "/")
|
|
489
|
+
);
|
|
490
|
+
const ignoreFullPatterns = ignorePatterns?.map(
|
|
491
|
+
(p) => path3.join(projectRootDir, p).replace(/\\/g, "/")
|
|
492
|
+
);
|
|
493
|
+
const filePaths = await glob(fullPatterns, {
|
|
494
|
+
ignore: ignoreFullPatterns,
|
|
495
|
+
nodir: true
|
|
496
|
+
});
|
|
497
|
+
return filePaths.map((p) => p.replace(/\\/g, "/"));
|
|
498
|
+
}
|
|
499
|
+
var PLUGIN_NAME = "auto-story-generator";
|
|
500
|
+
var DEFAULT_BATCH_SIZE = 20;
|
|
501
|
+
var DEFAULT_CONCURRENCY = 4;
|
|
502
|
+
var mtimeCache = /* @__PURE__ */ new Map();
|
|
503
|
+
function hasFileChanged(filePath) {
|
|
504
|
+
try {
|
|
505
|
+
const stat = fs.statSync(filePath);
|
|
506
|
+
const mtime = stat.mtimeMs;
|
|
507
|
+
const cached = mtimeCache.get(filePath);
|
|
508
|
+
if (cached === mtime) return false;
|
|
509
|
+
mtimeCache.set(filePath, mtime);
|
|
510
|
+
return true;
|
|
511
|
+
} catch {
|
|
512
|
+
mtimeCache.delete(filePath);
|
|
513
|
+
return true;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
function getStoryFilePath(filePath, storiesFolder) {
|
|
517
|
+
const parsed = path3.parse(filePath);
|
|
518
|
+
const storyName = `${parsed.name}.stories.tsx`;
|
|
519
|
+
if (storiesFolder) {
|
|
520
|
+
return path3.join(parsed.dir, storiesFolder, storyName);
|
|
521
|
+
}
|
|
522
|
+
return path3.join(parsed.dir, storyName);
|
|
523
|
+
}
|
|
524
|
+
async function processBatch(files, options, projectRootDir, concurrency) {
|
|
525
|
+
let processed = 0;
|
|
526
|
+
for (let i = 0; i < files.length; i += concurrency) {
|
|
527
|
+
const chunk = files.slice(i, i + concurrency);
|
|
528
|
+
await Promise.all(
|
|
529
|
+
chunk.map(async (filePath) => {
|
|
530
|
+
await genStoryFile({
|
|
531
|
+
options,
|
|
532
|
+
id: filePath,
|
|
533
|
+
projectRootDir
|
|
534
|
+
});
|
|
535
|
+
processed++;
|
|
536
|
+
})
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
return processed;
|
|
540
|
+
}
|
|
541
|
+
function createAutoStoryPlugin(options) {
|
|
542
|
+
const projectRootDir = (options.projectRoot ?? process.cwd()).replace(/\\/g, "/");
|
|
543
|
+
const batchSize = options.batchSize ?? DEFAULT_BATCH_SIZE;
|
|
544
|
+
const concurrency = options.concurrency ?? DEFAULT_CONCURRENCY;
|
|
545
|
+
console.log("[ASG] Plugin created", {
|
|
546
|
+
preset: options.preset,
|
|
547
|
+
imports: options.imports,
|
|
548
|
+
isGenerateStoriesFileAtBuild: options.isGenerateStoriesFileAtBuild,
|
|
549
|
+
storiesFolder: options.storiesFolder,
|
|
550
|
+
projectRoot: projectRootDir,
|
|
551
|
+
cwd: process.cwd()
|
|
552
|
+
});
|
|
553
|
+
return {
|
|
554
|
+
name: PLUGIN_NAME,
|
|
555
|
+
async buildStart() {
|
|
556
|
+
console.log("[ASG] buildStart hook fired");
|
|
557
|
+
if (!options.isGenerateStoriesFileAtBuild) {
|
|
558
|
+
console.log("[ASG] Skipping \u2014 isGenerateStoriesFileAtBuild is false");
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
const patterns = options.imports ?? ["src/**/*.tsx"];
|
|
562
|
+
const ignorePatterns = options.ignores ?? [];
|
|
563
|
+
console.log(`[ASG] Scanning for components: ${patterns.join(", ")}`);
|
|
564
|
+
const allFiles = await getAllFilePaths({
|
|
565
|
+
patterns,
|
|
566
|
+
ignorePatterns,
|
|
567
|
+
projectRootDir
|
|
568
|
+
});
|
|
569
|
+
const filesToProcess = allFiles.filter((filePath) => {
|
|
570
|
+
if (filePath.includes(".stories")) return false;
|
|
571
|
+
if (options.cacheEnabled !== false) {
|
|
572
|
+
const storyPath = getStoryFilePath(filePath, options.storiesFolder);
|
|
573
|
+
const storyExists = fs.existsSync(storyPath);
|
|
574
|
+
if (!hasFileChanged(filePath) && storyExists) return false;
|
|
575
|
+
}
|
|
576
|
+
return true;
|
|
577
|
+
});
|
|
578
|
+
console.log(
|
|
579
|
+
`[ASG] Found ${allFiles.length} files, ${filesToProcess.length} need processing`
|
|
580
|
+
);
|
|
581
|
+
let totalProcessed = 0;
|
|
582
|
+
for (let i = 0; i < filesToProcess.length; i += batchSize) {
|
|
583
|
+
const batch = filesToProcess.slice(i, i + batchSize);
|
|
584
|
+
const count = await processBatch(batch, options, projectRootDir, concurrency);
|
|
585
|
+
totalProcessed += count;
|
|
586
|
+
if (options.onProgress) {
|
|
587
|
+
options.onProgress(totalProcessed, filesToProcess.length);
|
|
588
|
+
}
|
|
589
|
+
if (i + batchSize < filesToProcess.length) {
|
|
590
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
console.log(`[ASG] Generated stories for ${totalProcessed} components`);
|
|
594
|
+
},
|
|
595
|
+
async watchChange(id, change) {
|
|
596
|
+
if (change.event === "delete") return;
|
|
597
|
+
const normalizedId = id.replace(/\\/g, "/");
|
|
598
|
+
if (options.imports) {
|
|
599
|
+
const relativePath = normalizedId.replace(projectRootDir, "").replace(/^\//, "");
|
|
600
|
+
const matches = options.imports.some(
|
|
601
|
+
(pattern) => minimatch(relativePath, pattern)
|
|
602
|
+
);
|
|
603
|
+
if (!matches) return;
|
|
604
|
+
}
|
|
605
|
+
if (options.ignores) {
|
|
606
|
+
const relativePath = normalizedId.replace(projectRootDir, "").replace(/^\//, "");
|
|
607
|
+
const ignored = options.ignores.some(
|
|
608
|
+
(pattern) => minimatch(relativePath, pattern)
|
|
609
|
+
);
|
|
610
|
+
if (ignored) return;
|
|
611
|
+
}
|
|
612
|
+
mtimeCache.delete(normalizedId);
|
|
613
|
+
await genStoryFile({
|
|
614
|
+
options,
|
|
615
|
+
id: normalizedId,
|
|
616
|
+
projectRootDir
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
var index_default = { vite: createAutoStoryPlugin };
|
|
18
622
|
var FIXED_MARKER = "// @onlook-fixed";
|
|
19
623
|
var parser = withDefaultConfig({
|
|
20
624
|
shouldExtractLiteralValuesFromEnum: true,
|
|
@@ -25,12 +629,12 @@ var parser = withDefaultConfig({
|
|
|
25
629
|
}
|
|
26
630
|
});
|
|
27
631
|
function resolveComponentPath(storyFilePath) {
|
|
28
|
-
const dir =
|
|
29
|
-
const parentDir =
|
|
30
|
-
const storyName =
|
|
632
|
+
const dir = path3.dirname(storyFilePath);
|
|
633
|
+
const parentDir = path3.dirname(dir);
|
|
634
|
+
const storyName = path3.basename(storyFilePath);
|
|
31
635
|
const componentName = storyName.replace(".stories.tsx", ".tsx");
|
|
32
|
-
const componentPath =
|
|
33
|
-
return
|
|
636
|
+
const componentPath = path3.join(parentDir, componentName);
|
|
637
|
+
return fs.existsSync(componentPath) ? componentPath : null;
|
|
34
638
|
}
|
|
35
639
|
function generateArgTypes(componentPath) {
|
|
36
640
|
try {
|
|
@@ -71,7 +675,7 @@ function generateArgTypes(componentPath) {
|
|
|
71
675
|
}
|
|
72
676
|
function enrichStoryFile(storyFilePath) {
|
|
73
677
|
try {
|
|
74
|
-
const content =
|
|
678
|
+
const content = fs.readFileSync(storyFilePath, "utf-8");
|
|
75
679
|
if (content.includes(FIXED_MARKER)) {
|
|
76
680
|
return;
|
|
77
681
|
}
|
|
@@ -90,8 +694,8 @@ function enrichStoryFile(storyFilePath) {
|
|
|
90
694
|
export default meta;`
|
|
91
695
|
);
|
|
92
696
|
if (enriched !== content) {
|
|
93
|
-
|
|
94
|
-
console.log(`[AutoStories] Enriched ${
|
|
697
|
+
fs.writeFileSync(storyFilePath, enriched);
|
|
698
|
+
console.log(`[AutoStories] Enriched ${path3.basename(storyFilePath)} with argTypes`);
|
|
95
699
|
}
|
|
96
700
|
} catch (err) {
|
|
97
701
|
console.error(`[AutoStories] Failed to enrich story: ${storyFilePath}`, err);
|
|
@@ -119,7 +723,7 @@ function componentLocPlugin(options = {}) {
|
|
|
119
723
|
sourceFilename: filepath
|
|
120
724
|
});
|
|
121
725
|
let mutated = false;
|
|
122
|
-
const relativePath =
|
|
726
|
+
const relativePath = path3.relative(root, filepath);
|
|
123
727
|
traverse(ast, {
|
|
124
728
|
JSXElement(nodePath) {
|
|
125
729
|
const opening = nodePath.node.openingElement;
|
|
@@ -156,9 +760,9 @@ function componentLocPlugin(options = {}) {
|
|
|
156
760
|
}
|
|
157
761
|
};
|
|
158
762
|
}
|
|
159
|
-
var CACHE_DIR =
|
|
160
|
-
var SCREENSHOTS_DIR =
|
|
161
|
-
var MANIFEST_PATH =
|
|
763
|
+
var CACHE_DIR = path3.join(process.cwd(), ".storybook-cache");
|
|
764
|
+
var SCREENSHOTS_DIR = path3.join(CACHE_DIR, "screenshots");
|
|
765
|
+
var MANIFEST_PATH = path3.join(CACHE_DIR, "manifest.json");
|
|
162
766
|
var VIEWPORT_WIDTH = 1920;
|
|
163
767
|
var VIEWPORT_HEIGHT = 1080;
|
|
164
768
|
var MIN_COMPONENT_WIDTH = 420;
|
|
@@ -166,30 +770,30 @@ var MIN_COMPONENT_HEIGHT = 280;
|
|
|
166
770
|
|
|
167
771
|
// src/utils/fileSystem/fileSystem.ts
|
|
168
772
|
function ensureCacheDirectories() {
|
|
169
|
-
if (!
|
|
170
|
-
|
|
773
|
+
if (!fs.existsSync(CACHE_DIR)) {
|
|
774
|
+
fs.mkdirSync(CACHE_DIR, { recursive: true });
|
|
171
775
|
}
|
|
172
|
-
if (!
|
|
173
|
-
|
|
776
|
+
if (!fs.existsSync(SCREENSHOTS_DIR)) {
|
|
777
|
+
fs.mkdirSync(SCREENSHOTS_DIR, { recursive: true });
|
|
174
778
|
}
|
|
175
779
|
}
|
|
176
780
|
function computeFileHash(filePath) {
|
|
177
|
-
if (!
|
|
781
|
+
if (!fs.existsSync(filePath)) {
|
|
178
782
|
return "";
|
|
179
783
|
}
|
|
180
|
-
const content =
|
|
784
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
181
785
|
return crypto.createHash("sha256").update(content).digest("hex");
|
|
182
786
|
}
|
|
183
787
|
function loadManifest() {
|
|
184
|
-
if (
|
|
185
|
-
const content =
|
|
788
|
+
if (fs.existsSync(MANIFEST_PATH)) {
|
|
789
|
+
const content = fs.readFileSync(MANIFEST_PATH, "utf-8");
|
|
186
790
|
return JSON.parse(content);
|
|
187
791
|
}
|
|
188
792
|
return { stories: {} };
|
|
189
793
|
}
|
|
190
794
|
function saveManifest(manifest) {
|
|
191
795
|
ensureCacheDirectories();
|
|
192
|
-
|
|
796
|
+
fs.writeFileSync(MANIFEST_PATH, JSON.stringify(manifest, null, 2));
|
|
193
797
|
}
|
|
194
798
|
function updateManifest(storyId, sourcePath, fileHash, boundingBox) {
|
|
195
799
|
const manifest = loadManifest();
|
|
@@ -215,8 +819,8 @@ async function getBrowser() {
|
|
|
215
819
|
return browser;
|
|
216
820
|
}
|
|
217
821
|
function getScreenshotPath(storyId, theme) {
|
|
218
|
-
const storyDir =
|
|
219
|
-
return
|
|
822
|
+
const storyDir = path3.join(SCREENSHOTS_DIR, storyId);
|
|
823
|
+
return path3.join(storyDir, `${theme}.png`);
|
|
220
824
|
}
|
|
221
825
|
async function captureScreenshotBuffer(storyId, theme, width = VIEWPORT_WIDTH, height = VIEWPORT_HEIGHT, storybookUrl = "http://localhost:6006", timeoutMs = 3e4) {
|
|
222
826
|
const browser2 = await getBrowser();
|
|
@@ -303,9 +907,9 @@ async function captureScreenshotBuffer(storyId, theme, width = VIEWPORT_WIDTH, h
|
|
|
303
907
|
async function generateScreenshot(storyId, theme, storybookUrl = "http://localhost:6006", timeoutMs = 3e4) {
|
|
304
908
|
try {
|
|
305
909
|
ensureCacheDirectories();
|
|
306
|
-
const storyDir =
|
|
307
|
-
if (!
|
|
308
|
-
|
|
910
|
+
const storyDir = path3.join(SCREENSHOTS_DIR, storyId);
|
|
911
|
+
if (!fs.existsSync(storyDir)) {
|
|
912
|
+
fs.mkdirSync(storyDir, { recursive: true });
|
|
309
913
|
}
|
|
310
914
|
const screenshotPath = getScreenshotPath(storyId, theme);
|
|
311
915
|
const { buffer, boundingBox } = await captureScreenshotBuffer(
|
|
@@ -316,7 +920,7 @@ async function generateScreenshot(storyId, theme, storybookUrl = "http://localho
|
|
|
316
920
|
storybookUrl,
|
|
317
921
|
timeoutMs
|
|
318
922
|
);
|
|
319
|
-
|
|
923
|
+
fs.writeFileSync(screenshotPath, buffer);
|
|
320
924
|
return { path: screenshotPath, boundingBox };
|
|
321
925
|
} catch (error) {
|
|
322
926
|
console.error(`Error generating screenshot for ${storyId} (${theme}):`, error);
|
|
@@ -343,7 +947,7 @@ async function fetchStorybookIndex() {
|
|
|
343
947
|
}
|
|
344
948
|
function getStoriesForFile(filePath) {
|
|
345
949
|
if (!cachedIndex) return [];
|
|
346
|
-
const fileName =
|
|
950
|
+
const fileName = path3.basename(filePath);
|
|
347
951
|
return Object.values(cachedIndex.entries).filter((entry) => entry.type === "story" && entry.importPath.endsWith(fileName)).map((entry) => entry.id);
|
|
348
952
|
}
|
|
349
953
|
async function regenerateScreenshotsForFiles(files) {
|
|
@@ -432,18 +1036,6 @@ var DEFAULT_ALLOWED_ORIGINS = [
|
|
|
432
1036
|
];
|
|
433
1037
|
var AUTO_STORIES_FOLDER = ".onlook-stories";
|
|
434
1038
|
var storyRuntimeErrors = /* @__PURE__ */ new Map();
|
|
435
|
-
var discoveryConfig = {
|
|
436
|
-
imports: ["src/**/*.tsx"],
|
|
437
|
-
ignores: []
|
|
438
|
-
};
|
|
439
|
-
function deriveTitleFromPath(filePath) {
|
|
440
|
-
const parts = filePath.replace(/^src\//, "").split("/");
|
|
441
|
-
parts.pop();
|
|
442
|
-
return parts.filter((p) => !p.startsWith("(") && !p.startsWith("[")).join("/");
|
|
443
|
-
}
|
|
444
|
-
function toStoryId(title, name) {
|
|
445
|
-
return `${title.replace(/\//g, "-").toLowerCase()}--${name.toLowerCase()}`;
|
|
446
|
-
}
|
|
447
1039
|
var serveMetadataAndScreenshots = (req, res, next) => {
|
|
448
1040
|
if (req.url === "/onbook-health.json") {
|
|
449
1041
|
const cacheBuster = Date.now();
|
|
@@ -515,124 +1107,9 @@ var serveMetadataAndScreenshots = (req, res, next) => {
|
|
|
515
1107
|
});
|
|
516
1108
|
return;
|
|
517
1109
|
}
|
|
518
|
-
if (req.url === "/onbook-components.json") {
|
|
519
|
-
try {
|
|
520
|
-
const { globSync } = __require("glob");
|
|
521
|
-
const files = discoveryConfig.imports.flatMap(
|
|
522
|
-
(pattern) => globSync(pattern, {
|
|
523
|
-
ignore: discoveryConfig.ignores,
|
|
524
|
-
cwd: process.cwd()
|
|
525
|
-
})
|
|
526
|
-
);
|
|
527
|
-
const components = files.map((file) => ({
|
|
528
|
-
filePath: file,
|
|
529
|
-
componentName: path6.basename(file, path6.extname(file)),
|
|
530
|
-
title: deriveTitleFromPath(file)
|
|
531
|
-
}));
|
|
532
|
-
res.setHeader("Content-Type", "application/json");
|
|
533
|
-
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
534
|
-
res.end(JSON.stringify({ components }));
|
|
535
|
-
} catch (error) {
|
|
536
|
-
res.statusCode = 500;
|
|
537
|
-
res.setHeader("Content-Type", "application/json");
|
|
538
|
-
res.end(
|
|
539
|
-
JSON.stringify({
|
|
540
|
-
error: "Failed to discover components",
|
|
541
|
-
details: String(error)
|
|
542
|
-
})
|
|
543
|
-
);
|
|
544
|
-
}
|
|
545
|
-
return;
|
|
546
|
-
}
|
|
547
|
-
if (req.url === "/onbook-generate-story" && req.method === "POST") {
|
|
548
|
-
let body = "";
|
|
549
|
-
req.on("data", (chunk) => {
|
|
550
|
-
body += chunk.toString();
|
|
551
|
-
});
|
|
552
|
-
req.on("end", () => {
|
|
553
|
-
try {
|
|
554
|
-
const { filePath } = JSON.parse(body);
|
|
555
|
-
if (!filePath) {
|
|
556
|
-
res.statusCode = 400;
|
|
557
|
-
res.setHeader("Content-Type", "application/json");
|
|
558
|
-
res.end(JSON.stringify({ error: "filePath is required" }));
|
|
559
|
-
return;
|
|
560
|
-
}
|
|
561
|
-
const absPath = path6.resolve(filePath);
|
|
562
|
-
const dir = path6.dirname(absPath);
|
|
563
|
-
const ext = path6.extname(absPath);
|
|
564
|
-
const baseName = path6.basename(absPath, ext);
|
|
565
|
-
const storyDir = path6.join(dir, AUTO_STORIES_FOLDER);
|
|
566
|
-
const storyPath = path6.join(storyDir, `${baseName}.stories.tsx`);
|
|
567
|
-
const title = deriveTitleFromPath(filePath);
|
|
568
|
-
const storyId = toStoryId(title, "default");
|
|
569
|
-
if (fs5.existsSync(storyPath)) {
|
|
570
|
-
res.setHeader("Content-Type", "application/json");
|
|
571
|
-
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
572
|
-
res.end(
|
|
573
|
-
JSON.stringify({
|
|
574
|
-
storyId,
|
|
575
|
-
storyPath,
|
|
576
|
-
importPath: `./${filePath.replace(ext, `/${AUTO_STORIES_FOLDER}/${baseName}.stories.tsx`)}`,
|
|
577
|
-
existed: true
|
|
578
|
-
})
|
|
579
|
-
);
|
|
580
|
-
return;
|
|
581
|
-
}
|
|
582
|
-
const storyContent = [
|
|
583
|
-
"import type { Meta, StoryObj } from '@storybook/react';",
|
|
584
|
-
`import ${baseName} from '../${baseName}';`,
|
|
585
|
-
"",
|
|
586
|
-
`const meta: Meta<typeof ${baseName}> = {`,
|
|
587
|
-
` title: '${title}',`,
|
|
588
|
-
` component: ${baseName},`,
|
|
589
|
-
"};",
|
|
590
|
-
"export default meta;",
|
|
591
|
-
"",
|
|
592
|
-
`type Story = StoryObj<typeof ${baseName}>;`,
|
|
593
|
-
"",
|
|
594
|
-
"export const Default: Story = {};",
|
|
595
|
-
""
|
|
596
|
-
].join("\n");
|
|
597
|
-
if (!fs5.existsSync(storyDir)) {
|
|
598
|
-
fs5.mkdirSync(storyDir, { recursive: true });
|
|
599
|
-
}
|
|
600
|
-
fs5.writeFileSync(storyPath, storyContent);
|
|
601
|
-
console.log(`[STORYBOOK_PLUGIN] Generated story: ${storyPath}`);
|
|
602
|
-
res.setHeader("Content-Type", "application/json");
|
|
603
|
-
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
604
|
-
res.end(
|
|
605
|
-
JSON.stringify({
|
|
606
|
-
storyId,
|
|
607
|
-
storyPath,
|
|
608
|
-
importPath: `./${path6.relative(process.cwd(), storyPath)}`,
|
|
609
|
-
existed: false
|
|
610
|
-
})
|
|
611
|
-
);
|
|
612
|
-
} catch (error) {
|
|
613
|
-
res.statusCode = 500;
|
|
614
|
-
res.setHeader("Content-Type", "application/json");
|
|
615
|
-
res.end(
|
|
616
|
-
JSON.stringify({
|
|
617
|
-
error: "Failed to generate story",
|
|
618
|
-
details: String(error)
|
|
619
|
-
})
|
|
620
|
-
);
|
|
621
|
-
}
|
|
622
|
-
});
|
|
623
|
-
return;
|
|
624
|
-
}
|
|
625
|
-
if (req.method === "OPTIONS" && req.url?.startsWith("/onbook-")) {
|
|
626
|
-
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
627
|
-
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
628
|
-
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
629
|
-
res.statusCode = 204;
|
|
630
|
-
res.end();
|
|
631
|
-
return;
|
|
632
|
-
}
|
|
633
1110
|
if (req.url === "/onbook-index.json") {
|
|
634
1111
|
console.log("[STORYBOOK_PLUGIN] Serving /onbook-index.json endpoint");
|
|
635
|
-
const manifestPath =
|
|
1112
|
+
const manifestPath = path3.join(process.cwd(), ".storybook-cache", "manifest.json");
|
|
636
1113
|
const cacheBuster = Date.now();
|
|
637
1114
|
console.log("[STORYBOOK_PLUGIN] Fetching http://localhost:6006/index.json");
|
|
638
1115
|
fetch(`http://localhost:6006/index.json?_t=${cacheBuster}`, {
|
|
@@ -649,7 +1126,7 @@ var serveMetadataAndScreenshots = (req, res, next) => {
|
|
|
649
1126
|
});
|
|
650
1127
|
return response.json();
|
|
651
1128
|
}).then((indexData) => {
|
|
652
|
-
const manifest =
|
|
1129
|
+
const manifest = fs.existsSync(manifestPath) ? JSON.parse(fs.readFileSync(manifestPath, "utf-8")) : { stories: {} };
|
|
653
1130
|
const defaultBoundingBox = { width: 1920, height: 1080 };
|
|
654
1131
|
for (const [storyId, entry] of Object.entries(indexData.entries || {})) {
|
|
655
1132
|
const manifestEntry = manifest.stories?.[storyId];
|
|
@@ -717,7 +1194,7 @@ var serveMetadataAndScreenshots = (req, res, next) => {
|
|
|
717
1194
|
return;
|
|
718
1195
|
}
|
|
719
1196
|
if (req.url?.startsWith("/screenshots/")) {
|
|
720
|
-
const screenshotPath =
|
|
1197
|
+
const screenshotPath = path3.join(
|
|
721
1198
|
process.cwd(),
|
|
722
1199
|
".storybook-cache",
|
|
723
1200
|
req.url.replace("/screenshots/", "screenshots/")
|
|
@@ -726,11 +1203,11 @@ var serveMetadataAndScreenshots = (req, res, next) => {
|
|
|
726
1203
|
const storyId = urlParts[0];
|
|
727
1204
|
const themeFile = urlParts[1];
|
|
728
1205
|
const theme = themeFile?.replace(".png", "");
|
|
729
|
-
if (
|
|
1206
|
+
if (fs.existsSync(screenshotPath)) {
|
|
730
1207
|
res.setHeader("Content-Type", "image/png");
|
|
731
1208
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
732
1209
|
res.setHeader("Cache-Control", "public, max-age=3600");
|
|
733
|
-
|
|
1210
|
+
fs.createReadStream(screenshotPath).pipe(res);
|
|
734
1211
|
return;
|
|
735
1212
|
}
|
|
736
1213
|
if (storyId && theme && (theme === "light" || theme === "dark")) {
|
|
@@ -738,16 +1215,16 @@ var serveMetadataAndScreenshots = (req, res, next) => {
|
|
|
738
1215
|
`[STORYBOOK_PLUGIN] Generating screenshot on-demand: ${storyId}/${theme}`
|
|
739
1216
|
);
|
|
740
1217
|
captureScreenshotBuffer(storyId, theme).then(({ buffer }) => {
|
|
741
|
-
const storyDir =
|
|
1218
|
+
const storyDir = path3.join(
|
|
742
1219
|
process.cwd(),
|
|
743
1220
|
".storybook-cache",
|
|
744
1221
|
"screenshots",
|
|
745
1222
|
storyId
|
|
746
1223
|
);
|
|
747
|
-
if (!
|
|
748
|
-
|
|
1224
|
+
if (!fs.existsSync(storyDir)) {
|
|
1225
|
+
fs.mkdirSync(storyDir, { recursive: true });
|
|
749
1226
|
}
|
|
750
|
-
|
|
1227
|
+
fs.writeFileSync(screenshotPath, buffer);
|
|
751
1228
|
res.setHeader("Content-Type", "image/png");
|
|
752
1229
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
753
1230
|
res.setHeader("Cache-Control", "public, max-age=3600");
|
|
@@ -830,8 +1307,8 @@ function storybookOnlookPlugin(options = {}) {
|
|
|
830
1307
|
};
|
|
831
1308
|
const plugins = [componentLocPlugin(), mainPlugin];
|
|
832
1309
|
if (options.autoStories !== false) {
|
|
833
|
-
|
|
834
|
-
|
|
1310
|
+
const imports = options.autoStories ?? ["src/**/*.tsx"];
|
|
1311
|
+
const ignores = options.autoStoriesIgnore ?? [
|
|
835
1312
|
"src/**/*.stories.tsx",
|
|
836
1313
|
"src/**/*.stories.ts",
|
|
837
1314
|
"src/**/*.test.tsx",
|
|
@@ -841,10 +1318,27 @@ function storybookOnlookPlugin(options = {}) {
|
|
|
841
1318
|
"node_modules/**",
|
|
842
1319
|
"**/.onlook-stories/**"
|
|
843
1320
|
];
|
|
844
|
-
console.log("[STORYBOOK_PLUGIN]
|
|
845
|
-
imports
|
|
846
|
-
ignores
|
|
1321
|
+
console.log("[STORYBOOK_PLUGIN] Auto-story generation enabled", {
|
|
1322
|
+
imports,
|
|
1323
|
+
ignores,
|
|
1324
|
+
storiesFolder: AUTO_STORIES_FOLDER
|
|
847
1325
|
});
|
|
1326
|
+
try {
|
|
1327
|
+
plugins.push(
|
|
1328
|
+
index_default.vite({
|
|
1329
|
+
preset: "react",
|
|
1330
|
+
imports,
|
|
1331
|
+
ignores,
|
|
1332
|
+
storiesFolder: AUTO_STORIES_FOLDER,
|
|
1333
|
+
isGenerateStoriesFileAtBuild: true
|
|
1334
|
+
})
|
|
1335
|
+
);
|
|
1336
|
+
} catch (err) {
|
|
1337
|
+
console.error(
|
|
1338
|
+
"[STORYBOOK_PLUGIN] ASG plugin failed to initialize, continuing without auto-stories",
|
|
1339
|
+
err
|
|
1340
|
+
);
|
|
1341
|
+
}
|
|
848
1342
|
}
|
|
849
1343
|
return plugins;
|
|
850
1344
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onlook/storybook-plugin",
|
|
3
|
-
"version": "0.4.0-beta.
|
|
3
|
+
"version": "0.4.0-beta.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"onlook-storybook": "./dist/cli/index.js"
|
|
@@ -37,9 +37,12 @@
|
|
|
37
37
|
"@babel/traverse": "^7.26.9",
|
|
38
38
|
"@babel/types": "^7.26.9",
|
|
39
39
|
"@drizzle-team/brocli": "^0.11.0",
|
|
40
|
-
"glob": "^
|
|
40
|
+
"glob": "^10.3.10",
|
|
41
|
+
"minimatch": "^9.0.3",
|
|
41
42
|
"playwright": "^1.52.0",
|
|
42
|
-
"
|
|
43
|
+
"prettier": "^3.2.5",
|
|
44
|
+
"react-docgen-typescript": "^2.4.0",
|
|
45
|
+
"ts-morph": "^21.0.1"
|
|
43
46
|
},
|
|
44
47
|
"devDependencies": {
|
|
45
48
|
"@onbook/tsconfig": "workspace:*",
|