@onlook/storybook-plugin 0.4.0-beta.1 → 0.4.0-beta.3

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.
Files changed (2) hide show
  1. package/dist/index.js +637 -41
  2. package/package.json +6 -3
package/dist/index.js CHANGED
@@ -1,7 +1,10 @@
1
- import fs5, { existsSync } from 'fs';
2
- import path, { dirname, join, relative } from 'path';
1
+ import fs, { existsSync } from 'fs';
2
+ import path3, { dirname, join, relative } from 'path';
3
3
  import { fileURLToPath } from 'url';
4
- import autoStoryGenerator from '@takuma-ru/auto-story-generator';
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';
5
8
  import { withDefaultConfig } from 'react-docgen-typescript';
6
9
  import generateModule from '@babel/generator';
7
10
  import { parse } from '@babel/parser';
@@ -11,6 +14,599 @@ import crypto from 'crypto';
11
14
  import { chromium } from 'playwright';
12
15
 
13
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
+ return {
546
+ name: PLUGIN_NAME,
547
+ async buildStart() {
548
+ if (!options.isGenerateStoriesFileAtBuild) return;
549
+ const patterns = options.imports ?? ["src/**/*.tsx"];
550
+ const ignorePatterns = options.ignores ?? [];
551
+ console.log(`[ASG] Scanning for components: ${patterns.join(", ")}`);
552
+ const allFiles = await getAllFilePaths({
553
+ patterns,
554
+ ignorePatterns,
555
+ projectRootDir
556
+ });
557
+ const filesToProcess = allFiles.filter((filePath) => {
558
+ if (filePath.includes(".stories")) return false;
559
+ if (options.cacheEnabled !== false) {
560
+ const storyPath = getStoryFilePath(filePath, options.storiesFolder);
561
+ const storyExists = fs.existsSync(storyPath);
562
+ if (!hasFileChanged(filePath) && storyExists) return false;
563
+ }
564
+ return true;
565
+ });
566
+ console.log(
567
+ `[ASG] Found ${allFiles.length} files, ${filesToProcess.length} need processing`
568
+ );
569
+ let totalProcessed = 0;
570
+ for (let i = 0; i < filesToProcess.length; i += batchSize) {
571
+ const batch = filesToProcess.slice(i, i + batchSize);
572
+ const count = await processBatch(batch, options, projectRootDir, concurrency);
573
+ totalProcessed += count;
574
+ if (options.onProgress) {
575
+ options.onProgress(totalProcessed, filesToProcess.length);
576
+ }
577
+ if (i + batchSize < filesToProcess.length) {
578
+ await new Promise((r) => setTimeout(r, 0));
579
+ }
580
+ }
581
+ console.log(`[ASG] Generated stories for ${totalProcessed} components`);
582
+ },
583
+ async watchChange(id, change) {
584
+ if (change.event === "delete") return;
585
+ const normalizedId = id.replace(/\\/g, "/");
586
+ if (options.imports) {
587
+ const relativePath = normalizedId.replace(projectRootDir, "").replace(/^\//, "");
588
+ const matches = options.imports.some(
589
+ (pattern) => minimatch(relativePath, pattern)
590
+ );
591
+ if (!matches) return;
592
+ }
593
+ if (options.ignores) {
594
+ const relativePath = normalizedId.replace(projectRootDir, "").replace(/^\//, "");
595
+ const ignored = options.ignores.some(
596
+ (pattern) => minimatch(relativePath, pattern)
597
+ );
598
+ if (ignored) return;
599
+ }
600
+ mtimeCache.delete(normalizedId);
601
+ await genStoryFile({
602
+ options,
603
+ id: normalizedId,
604
+ projectRootDir
605
+ });
606
+ }
607
+ };
608
+ }
609
+ var index_default = { vite: createAutoStoryPlugin };
14
610
  var FIXED_MARKER = "// @onlook-fixed";
15
611
  var parser = withDefaultConfig({
16
612
  shouldExtractLiteralValuesFromEnum: true,
@@ -21,12 +617,12 @@ var parser = withDefaultConfig({
21
617
  }
22
618
  });
23
619
  function resolveComponentPath(storyFilePath) {
24
- const dir = path.dirname(storyFilePath);
25
- const parentDir = path.dirname(dir);
26
- const storyName = path.basename(storyFilePath);
620
+ const dir = path3.dirname(storyFilePath);
621
+ const parentDir = path3.dirname(dir);
622
+ const storyName = path3.basename(storyFilePath);
27
623
  const componentName = storyName.replace(".stories.tsx", ".tsx");
28
- const componentPath = path.join(parentDir, componentName);
29
- return fs5.existsSync(componentPath) ? componentPath : null;
624
+ const componentPath = path3.join(parentDir, componentName);
625
+ return fs.existsSync(componentPath) ? componentPath : null;
30
626
  }
31
627
  function generateArgTypes(componentPath) {
32
628
  try {
@@ -67,7 +663,7 @@ function generateArgTypes(componentPath) {
67
663
  }
68
664
  function enrichStoryFile(storyFilePath) {
69
665
  try {
70
- const content = fs5.readFileSync(storyFilePath, "utf-8");
666
+ const content = fs.readFileSync(storyFilePath, "utf-8");
71
667
  if (content.includes(FIXED_MARKER)) {
72
668
  return;
73
669
  }
@@ -86,8 +682,8 @@ function enrichStoryFile(storyFilePath) {
86
682
  export default meta;`
87
683
  );
88
684
  if (enriched !== content) {
89
- fs5.writeFileSync(storyFilePath, enriched);
90
- console.log(`[AutoStories] Enriched ${path.basename(storyFilePath)} with argTypes`);
685
+ fs.writeFileSync(storyFilePath, enriched);
686
+ console.log(`[AutoStories] Enriched ${path3.basename(storyFilePath)} with argTypes`);
91
687
  }
92
688
  } catch (err) {
93
689
  console.error(`[AutoStories] Failed to enrich story: ${storyFilePath}`, err);
@@ -115,7 +711,7 @@ function componentLocPlugin(options = {}) {
115
711
  sourceFilename: filepath
116
712
  });
117
713
  let mutated = false;
118
- const relativePath = path.relative(root, filepath);
714
+ const relativePath = path3.relative(root, filepath);
119
715
  traverse(ast, {
120
716
  JSXElement(nodePath) {
121
717
  const opening = nodePath.node.openingElement;
@@ -152,9 +748,9 @@ function componentLocPlugin(options = {}) {
152
748
  }
153
749
  };
154
750
  }
155
- var CACHE_DIR = path.join(process.cwd(), ".storybook-cache");
156
- var SCREENSHOTS_DIR = path.join(CACHE_DIR, "screenshots");
157
- var MANIFEST_PATH = path.join(CACHE_DIR, "manifest.json");
751
+ var CACHE_DIR = path3.join(process.cwd(), ".storybook-cache");
752
+ var SCREENSHOTS_DIR = path3.join(CACHE_DIR, "screenshots");
753
+ var MANIFEST_PATH = path3.join(CACHE_DIR, "manifest.json");
158
754
  var VIEWPORT_WIDTH = 1920;
159
755
  var VIEWPORT_HEIGHT = 1080;
160
756
  var MIN_COMPONENT_WIDTH = 420;
@@ -162,30 +758,30 @@ var MIN_COMPONENT_HEIGHT = 280;
162
758
 
163
759
  // src/utils/fileSystem/fileSystem.ts
164
760
  function ensureCacheDirectories() {
165
- if (!fs5.existsSync(CACHE_DIR)) {
166
- fs5.mkdirSync(CACHE_DIR, { recursive: true });
761
+ if (!fs.existsSync(CACHE_DIR)) {
762
+ fs.mkdirSync(CACHE_DIR, { recursive: true });
167
763
  }
168
- if (!fs5.existsSync(SCREENSHOTS_DIR)) {
169
- fs5.mkdirSync(SCREENSHOTS_DIR, { recursive: true });
764
+ if (!fs.existsSync(SCREENSHOTS_DIR)) {
765
+ fs.mkdirSync(SCREENSHOTS_DIR, { recursive: true });
170
766
  }
171
767
  }
172
768
  function computeFileHash(filePath) {
173
- if (!fs5.existsSync(filePath)) {
769
+ if (!fs.existsSync(filePath)) {
174
770
  return "";
175
771
  }
176
- const content = fs5.readFileSync(filePath, "utf-8");
772
+ const content = fs.readFileSync(filePath, "utf-8");
177
773
  return crypto.createHash("sha256").update(content).digest("hex");
178
774
  }
179
775
  function loadManifest() {
180
- if (fs5.existsSync(MANIFEST_PATH)) {
181
- const content = fs5.readFileSync(MANIFEST_PATH, "utf-8");
776
+ if (fs.existsSync(MANIFEST_PATH)) {
777
+ const content = fs.readFileSync(MANIFEST_PATH, "utf-8");
182
778
  return JSON.parse(content);
183
779
  }
184
780
  return { stories: {} };
185
781
  }
186
782
  function saveManifest(manifest) {
187
783
  ensureCacheDirectories();
188
- fs5.writeFileSync(MANIFEST_PATH, JSON.stringify(manifest, null, 2));
784
+ fs.writeFileSync(MANIFEST_PATH, JSON.stringify(manifest, null, 2));
189
785
  }
190
786
  function updateManifest(storyId, sourcePath, fileHash, boundingBox) {
191
787
  const manifest = loadManifest();
@@ -211,8 +807,8 @@ async function getBrowser() {
211
807
  return browser;
212
808
  }
213
809
  function getScreenshotPath(storyId, theme) {
214
- const storyDir = path.join(SCREENSHOTS_DIR, storyId);
215
- return path.join(storyDir, `${theme}.png`);
810
+ const storyDir = path3.join(SCREENSHOTS_DIR, storyId);
811
+ return path3.join(storyDir, `${theme}.png`);
216
812
  }
217
813
  async function captureScreenshotBuffer(storyId, theme, width = VIEWPORT_WIDTH, height = VIEWPORT_HEIGHT, storybookUrl = "http://localhost:6006", timeoutMs = 3e4) {
218
814
  const browser2 = await getBrowser();
@@ -299,9 +895,9 @@ async function captureScreenshotBuffer(storyId, theme, width = VIEWPORT_WIDTH, h
299
895
  async function generateScreenshot(storyId, theme, storybookUrl = "http://localhost:6006", timeoutMs = 3e4) {
300
896
  try {
301
897
  ensureCacheDirectories();
302
- const storyDir = path.join(SCREENSHOTS_DIR, storyId);
303
- if (!fs5.existsSync(storyDir)) {
304
- fs5.mkdirSync(storyDir, { recursive: true });
898
+ const storyDir = path3.join(SCREENSHOTS_DIR, storyId);
899
+ if (!fs.existsSync(storyDir)) {
900
+ fs.mkdirSync(storyDir, { recursive: true });
305
901
  }
306
902
  const screenshotPath = getScreenshotPath(storyId, theme);
307
903
  const { buffer, boundingBox } = await captureScreenshotBuffer(
@@ -312,7 +908,7 @@ async function generateScreenshot(storyId, theme, storybookUrl = "http://localho
312
908
  storybookUrl,
313
909
  timeoutMs
314
910
  );
315
- fs5.writeFileSync(screenshotPath, buffer);
911
+ fs.writeFileSync(screenshotPath, buffer);
316
912
  return { path: screenshotPath, boundingBox };
317
913
  } catch (error) {
318
914
  console.error(`Error generating screenshot for ${storyId} (${theme}):`, error);
@@ -339,7 +935,7 @@ async function fetchStorybookIndex() {
339
935
  }
340
936
  function getStoriesForFile(filePath) {
341
937
  if (!cachedIndex) return [];
342
- const fileName = path.basename(filePath);
938
+ const fileName = path3.basename(filePath);
343
939
  return Object.values(cachedIndex.entries).filter((entry) => entry.type === "story" && entry.importPath.endsWith(fileName)).map((entry) => entry.id);
344
940
  }
345
941
  async function regenerateScreenshotsForFiles(files) {
@@ -501,7 +1097,7 @@ var serveMetadataAndScreenshots = (req, res, next) => {
501
1097
  }
502
1098
  if (req.url === "/onbook-index.json") {
503
1099
  console.log("[STORYBOOK_PLUGIN] Serving /onbook-index.json endpoint");
504
- const manifestPath = path.join(process.cwd(), ".storybook-cache", "manifest.json");
1100
+ const manifestPath = path3.join(process.cwd(), ".storybook-cache", "manifest.json");
505
1101
  const cacheBuster = Date.now();
506
1102
  console.log("[STORYBOOK_PLUGIN] Fetching http://localhost:6006/index.json");
507
1103
  fetch(`http://localhost:6006/index.json?_t=${cacheBuster}`, {
@@ -518,7 +1114,7 @@ var serveMetadataAndScreenshots = (req, res, next) => {
518
1114
  });
519
1115
  return response.json();
520
1116
  }).then((indexData) => {
521
- const manifest = fs5.existsSync(manifestPath) ? JSON.parse(fs5.readFileSync(manifestPath, "utf-8")) : { stories: {} };
1117
+ const manifest = fs.existsSync(manifestPath) ? JSON.parse(fs.readFileSync(manifestPath, "utf-8")) : { stories: {} };
522
1118
  const defaultBoundingBox = { width: 1920, height: 1080 };
523
1119
  for (const [storyId, entry] of Object.entries(indexData.entries || {})) {
524
1120
  const manifestEntry = manifest.stories?.[storyId];
@@ -586,7 +1182,7 @@ var serveMetadataAndScreenshots = (req, res, next) => {
586
1182
  return;
587
1183
  }
588
1184
  if (req.url?.startsWith("/screenshots/")) {
589
- const screenshotPath = path.join(
1185
+ const screenshotPath = path3.join(
590
1186
  process.cwd(),
591
1187
  ".storybook-cache",
592
1188
  req.url.replace("/screenshots/", "screenshots/")
@@ -595,11 +1191,11 @@ var serveMetadataAndScreenshots = (req, res, next) => {
595
1191
  const storyId = urlParts[0];
596
1192
  const themeFile = urlParts[1];
597
1193
  const theme = themeFile?.replace(".png", "");
598
- if (fs5.existsSync(screenshotPath)) {
1194
+ if (fs.existsSync(screenshotPath)) {
599
1195
  res.setHeader("Content-Type", "image/png");
600
1196
  res.setHeader("Access-Control-Allow-Origin", "*");
601
1197
  res.setHeader("Cache-Control", "public, max-age=3600");
602
- fs5.createReadStream(screenshotPath).pipe(res);
1198
+ fs.createReadStream(screenshotPath).pipe(res);
603
1199
  return;
604
1200
  }
605
1201
  if (storyId && theme && (theme === "light" || theme === "dark")) {
@@ -607,16 +1203,16 @@ var serveMetadataAndScreenshots = (req, res, next) => {
607
1203
  `[STORYBOOK_PLUGIN] Generating screenshot on-demand: ${storyId}/${theme}`
608
1204
  );
609
1205
  captureScreenshotBuffer(storyId, theme).then(({ buffer }) => {
610
- const storyDir = path.join(
1206
+ const storyDir = path3.join(
611
1207
  process.cwd(),
612
1208
  ".storybook-cache",
613
1209
  "screenshots",
614
1210
  storyId
615
1211
  );
616
- if (!fs5.existsSync(storyDir)) {
617
- fs5.mkdirSync(storyDir, { recursive: true });
1212
+ if (!fs.existsSync(storyDir)) {
1213
+ fs.mkdirSync(storyDir, { recursive: true });
618
1214
  }
619
- fs5.writeFileSync(screenshotPath, buffer);
1215
+ fs.writeFileSync(screenshotPath, buffer);
620
1216
  res.setHeader("Content-Type", "image/png");
621
1217
  res.setHeader("Access-Control-Allow-Origin", "*");
622
1218
  res.setHeader("Cache-Control", "public, max-age=3600");
@@ -717,7 +1313,7 @@ function storybookOnlookPlugin(options = {}) {
717
1313
  });
718
1314
  try {
719
1315
  plugins.push(
720
- autoStoryGenerator.vite({
1316
+ index_default.vite({
721
1317
  preset: "react",
722
1318
  imports,
723
1319
  ignores,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onlook/storybook-plugin",
3
- "version": "0.4.0-beta.1",
3
+ "version": "0.4.0-beta.3",
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
- "@takuma-ru/auto-story-generator": "^0.4.0",
40
+ "glob": "^10.3.10",
41
+ "minimatch": "^9.0.3",
41
42
  "playwright": "^1.52.0",
42
- "react-docgen-typescript": "^2.4.0"
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:*",