@onlook/storybook-plugin 0.4.0-beta.6 → 0.4.0-beta.9
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/README.md +1 -1
- package/dist/cli/index.js +195 -52
- package/dist/index.d.ts +94 -21
- package/dist/index.js +817 -814
- package/dist/preset/index.d.ts +10 -0
- package/dist/preset/index.js +1341 -0
- package/dist/preview/index.d.ts +156 -0
- package/dist/preview/index.js +302 -0
- package/dist/screenshot-service/index.d.ts +0 -6
- package/dist/screenshot-service/index.js +192 -49
- package/dist/screenshot-service/utils/browser/index.d.ts +0 -6
- package/dist/screenshot-service/utils/browser/index.js +78 -14
- package/dist/storybook-onlook-plugin-CigILdDb.d.ts +61 -0
- package/package.json +18 -10
package/dist/index.js
CHANGED
|
@@ -1,671 +1,310 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
import path6, { dirname, join, relative } from 'path';
|
|
3
|
+
import crypto, { createHash } from 'crypto';
|
|
4
|
+
import fs4, { existsSync } from 'fs';
|
|
3
5
|
import { fileURLToPath } from 'url';
|
|
4
|
-
import { minimatch } from 'minimatch';
|
|
5
|
-
import { Project, ts, TypeFlags } from 'ts-morph';
|
|
6
|
-
import { glob } from 'glob';
|
|
7
|
-
import { withDefaultConfig } from 'react-docgen-typescript';
|
|
8
6
|
import generateModule from '@babel/generator';
|
|
9
7
|
import { parse } from '@babel/parser';
|
|
10
8
|
import traverseModule from '@babel/traverse';
|
|
11
9
|
import * as t from '@babel/types';
|
|
12
|
-
import
|
|
10
|
+
import { execFile } from 'child_process';
|
|
11
|
+
import { createRequire as createRequire$1 } from 'module';
|
|
12
|
+
import { promisify } from 'util';
|
|
13
13
|
import { chromium } from 'playwright';
|
|
14
|
+
import { readCsf } from 'storybook/internal/csf-tools';
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
function pascalCase(str) {
|
|
37
|
-
return str.replace(/[_\-.\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : "").replace(/^(.)/, (_, c) => c.toUpperCase());
|
|
38
|
-
}
|
|
39
|
-
function removeQuotesAndWrapWithDoubleQuotes(str) {
|
|
40
|
-
const quoteRemovedStr = str.replace(/"/g, "").replace(/'/g, "");
|
|
41
|
-
return quoteRemovedStr;
|
|
16
|
+
globalThis.require = createRequire(import.meta.url);
|
|
17
|
+
|
|
18
|
+
// src/containment-contract/containment-contract.ts
|
|
19
|
+
var ONLOOK_CONTAINMENT_CONTRACT_VERSION = 1;
|
|
20
|
+
var ONLOOK_CONTAINED_MODULES_GLOBAL = "__ONLOOK_CONTAINED_MODULES__";
|
|
21
|
+
var ONLOOK_CONTAINED_PROP = "__onlookContained";
|
|
22
|
+
var ONLOOK_CONTAINMENT_CONTRACT_GLOBAL = "__ONLOOK_CONTAINMENT_CONTRACT__";
|
|
23
|
+
var ONLOOK_CONTAINED_EVENT = "onlook/contained";
|
|
24
|
+
var ONLOOK_RENDERED_EVENT = "onlook/rendered";
|
|
25
|
+
var ONLOOK_UNCONTAINED_ERROR_EVENT = "onlook/uncontained-error";
|
|
26
|
+
var ONLOOK_RENDER_STATE_ATTR = "data-onlook-render-state";
|
|
27
|
+
var ONLOOK_RENDER_STORY_ATTR = "data-onlook-story-id";
|
|
28
|
+
var ONLOOK_CONTAINED_CARD_ATTR = "data-onlook-contained-card";
|
|
29
|
+
var ENV_CONTAINMENT_PLUGIN_NAME = "onlook-env-containment";
|
|
30
|
+
var ENV_CONTAINMENT_VIRTUAL_PREFIX = "\0onlook-env-containment:";
|
|
31
|
+
var VALID_IDENT_RE = /^[A-Za-z_$][\w$]*$/;
|
|
32
|
+
var MATCH_SUFFIXES = ["", ".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
|
|
33
|
+
function normalizeSlashes(p) {
|
|
34
|
+
return p.replace(/\\/g, "/");
|
|
42
35
|
}
|
|
43
|
-
function
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
return setFlags || "err";
|
|
36
|
+
function stripQuery(id) {
|
|
37
|
+
const q = id.indexOf("?");
|
|
38
|
+
return q === -1 ? id : id.slice(0, q);
|
|
47
39
|
}
|
|
48
|
-
function
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
propTypes: void 0
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
let propsPattern = "component-props";
|
|
58
|
-
const pascalComponentName = pascalCase(componentName);
|
|
59
|
-
const propsType = sourceFile.getTypeAlias(`${pascalComponentName}Props`);
|
|
60
|
-
const propsInterface = sourceFile.getInterface(`${pascalComponentName}Props`);
|
|
61
|
-
const propsOnlyType = sourceFile.getTypeAlias("Props");
|
|
62
|
-
const propsOnlyInterface = sourceFile.getInterface("Props");
|
|
63
|
-
const propsInline = sourceFile.getVariableDeclaration(pascalComponentName)?.getInitializerIfKindOrThrow(ts.SyntaxKind.ArrowFunction);
|
|
64
|
-
const props = propsType?.getType() || propsInterface?.getType() || propsOnlyType?.getType() || propsOnlyInterface?.getType() || propsInline?.getParameters()[0]?.getType();
|
|
65
|
-
if (!props) {
|
|
66
|
-
return {
|
|
67
|
-
propTypes: []
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
let propsProperties = [];
|
|
71
|
-
const isPropsIntersection = props.isIntersection();
|
|
72
|
-
if (isPropsIntersection) {
|
|
73
|
-
propsProperties = [];
|
|
74
|
-
const intersectionTypes = props.getIntersectionTypes();
|
|
75
|
-
intersectionTypes.forEach((intersectionType) => {
|
|
76
|
-
const intersectionTypeText = intersectionType.getText();
|
|
77
|
-
if (intersectionTypeText.includes("HTMLAttributes")) return;
|
|
78
|
-
return propsProperties.push(...intersectionType.getProperties());
|
|
79
|
-
});
|
|
80
|
-
} else {
|
|
81
|
-
propsProperties = props.getProperties();
|
|
82
|
-
}
|
|
83
|
-
if (propsOnlyType || propsOnlyInterface) propsPattern = "props";
|
|
84
|
-
if (propsInline) propsPattern = "inline";
|
|
85
|
-
const propTypes = propsProperties.map((prop) => {
|
|
86
|
-
const propName = prop.getName();
|
|
87
|
-
const propType = prop.getValueDeclaration()?.getType();
|
|
88
|
-
if (!propType) {
|
|
89
|
-
return {
|
|
90
|
-
name: propName,
|
|
91
|
-
type: ["err"],
|
|
92
|
-
isOptional: prop.isOptional(),
|
|
93
|
-
value: []
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
if (propType.isUnion() && !propType.isBoolean()) {
|
|
97
|
-
const unionTypes = propType.getUnionTypes();
|
|
98
|
-
const type = Array.from(
|
|
99
|
-
new Set(unionTypes.map((union) => getTypeFlagsName(union.getFlags().valueOf())))
|
|
100
|
-
);
|
|
101
|
-
return {
|
|
102
|
-
name: propName,
|
|
103
|
-
type,
|
|
104
|
-
isOptional: prop.isOptional(),
|
|
105
|
-
value: unionTypes.map(
|
|
106
|
-
(union) => removeQuotesAndWrapWithDoubleQuotes(union.getText())
|
|
107
|
-
)
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
return {
|
|
111
|
-
name: propName,
|
|
112
|
-
type: [prop.getValueDeclaration().getType().getText()],
|
|
113
|
-
isOptional: prop.isOptional(),
|
|
114
|
-
value: []
|
|
115
|
-
};
|
|
116
|
-
});
|
|
117
|
-
return {
|
|
118
|
-
propsPattern,
|
|
119
|
-
propTypes
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
var errorDefinition = {
|
|
123
|
-
// Common
|
|
124
|
-
EC00: {
|
|
125
|
-
title: "Unknown error",
|
|
126
|
-
isCustomDetail: true
|
|
127
|
-
},
|
|
128
|
-
EC01: {
|
|
129
|
-
title: "Not yet supported",
|
|
130
|
-
detail: "This preset name is included in the preset bust not yet supported. Please wait for support.",
|
|
131
|
-
isCustomDetail: false
|
|
132
|
-
},
|
|
133
|
-
EC02: {
|
|
134
|
-
title: "Preset is not supported",
|
|
135
|
-
isCustomDetail: true
|
|
136
|
-
},
|
|
137
|
-
EC03: {
|
|
138
|
-
title: "Unable to get component name or file path correctly.",
|
|
139
|
-
detail: "Please check the file path and try again.",
|
|
140
|
-
isCustomDetail: false
|
|
141
|
-
},
|
|
142
|
-
EC04: {
|
|
143
|
-
title: "Could not find argTypes",
|
|
144
|
-
detail: "Error in genReactStoryFile.",
|
|
145
|
-
isCustomDetail: false
|
|
146
|
-
},
|
|
147
|
-
EC05: {
|
|
148
|
-
title: "Could not find meta",
|
|
149
|
-
isCustomDetail: true
|
|
150
|
-
},
|
|
151
|
-
EC06: {
|
|
152
|
-
title: "Could not find initializer",
|
|
153
|
-
detail: "Could not get initializer of ObjectLiteralExpression.",
|
|
154
|
-
isCustomDetail: false
|
|
155
|
-
},
|
|
156
|
-
EC07: {
|
|
157
|
-
title: "Could not find component",
|
|
158
|
-
isCustomDetail: true
|
|
159
|
-
},
|
|
160
|
-
EC08: {
|
|
161
|
-
title: "Could not scan directory",
|
|
162
|
-
isCustomDetail: true
|
|
163
|
-
},
|
|
164
|
-
EC09: {
|
|
165
|
-
title: "Reading directory failed",
|
|
166
|
-
isCustomDetail: true
|
|
167
|
-
},
|
|
168
|
-
EC10: {
|
|
169
|
-
title: "Could not get property from stories",
|
|
170
|
-
isCustomDetail: true
|
|
171
|
-
},
|
|
172
|
-
EC11: {
|
|
173
|
-
title: "File is defective.",
|
|
174
|
-
detail: "An error occurred during abstract syntax tree parsing.\nPlease check your file for problems.",
|
|
175
|
-
isCustomDetail: false
|
|
176
|
-
},
|
|
177
|
-
// Lit
|
|
178
|
-
EL01: {
|
|
179
|
-
title: "Failed to save file",
|
|
180
|
-
isCustomDetail: true
|
|
181
|
-
}
|
|
182
|
-
};
|
|
183
|
-
var throwErr = (params) => {
|
|
184
|
-
const { errorCode, detail } = params;
|
|
185
|
-
const detailText = errorDefinition[errorCode].isCustomDetail ? detail : errorDefinition[errorCode].detail;
|
|
186
|
-
console.error(`[ASG:${errorCode}] ${errorDefinition[errorCode].title}
|
|
187
|
-
${detailText}`);
|
|
188
|
-
};
|
|
189
|
-
function genReactStoryFile(input) {
|
|
190
|
-
const {
|
|
191
|
-
componentName,
|
|
192
|
-
fileBase,
|
|
193
|
-
fileName,
|
|
194
|
-
filePrefixExt,
|
|
195
|
-
relativeSourceFilePath,
|
|
196
|
-
sourceFile,
|
|
197
|
-
storiesFolder
|
|
198
|
-
} = input;
|
|
199
|
-
if (!componentName || !fileBase) {
|
|
200
|
-
throwErr({ errorCode: "EC03" });
|
|
40
|
+
function envContainmentPlugin(options) {
|
|
41
|
+
const substitutions = (options?.substitute ?? []).filter(
|
|
42
|
+
(s) => typeof s.module === "string" && s.module.length > 0
|
|
43
|
+
);
|
|
44
|
+
const defineEntries = options?.define ?? {};
|
|
45
|
+
if (substitutions.length === 0 && Object.keys(defineEntries).length === 0) {
|
|
201
46
|
return null;
|
|
202
47
|
}
|
|
203
|
-
const
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
throwErr({ errorCode: "EC04" });
|
|
207
|
-
return null;
|
|
48
|
+
const byModule = /* @__PURE__ */ new Map();
|
|
49
|
+
for (const sub of substitutions) {
|
|
50
|
+
byModule.set(normalizeSlashes(sub.module).replace(/^\.\//, ""), sub);
|
|
208
51
|
}
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
const defaultExportName = declaration[0]?.getSymbol()?.getName();
|
|
214
|
-
isDefaultExportComponent = defaultExportName === pascalComponentName;
|
|
215
|
-
}
|
|
216
|
-
});
|
|
217
|
-
const pathToComponent = storiesFolder ? "../" : "./";
|
|
218
|
-
const importSuffix = filePrefixExt || "";
|
|
219
|
-
const importStatement = isDefaultExportComponent ? `import ${pascalComponentName} from "${pathToComponent}${fileName}${importSuffix}";` : `import { ${pascalComponentName} } from "${pathToComponent}${fileName}${importSuffix}";`;
|
|
220
|
-
const args = {};
|
|
221
|
-
for (const prop of propTypes) {
|
|
222
|
-
if (prop.isOptional) {
|
|
223
|
-
args[prop.name] = "undefined";
|
|
224
|
-
continue;
|
|
225
|
-
}
|
|
226
|
-
if (prop.type.includes("boolean")) {
|
|
227
|
-
args[prop.name] = "false";
|
|
228
|
-
} else if (prop.value.length > 0) {
|
|
229
|
-
args[prop.name] = `"${prop.value[0]}"`;
|
|
230
|
-
} else if (prop.type.includes("string") || prop.type.includes("String")) {
|
|
231
|
-
args[prop.name] = `"${prop.name}"`;
|
|
232
|
-
} else if (prop.type.includes("number") || prop.type.includes("Number")) {
|
|
233
|
-
args[prop.name] = "0";
|
|
234
|
-
} else {
|
|
235
|
-
args[prop.name] = "undefined";
|
|
236
|
-
}
|
|
52
|
+
const targetBasenames = /* @__PURE__ */ new Set();
|
|
53
|
+
for (const rel of byModule.keys()) {
|
|
54
|
+
const base = rel.slice(rel.lastIndexOf("/") + 1);
|
|
55
|
+
targetBasenames.add(base.replace(/\.[^.]+$/, ""));
|
|
237
56
|
}
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
57
|
+
const specifierMightMatch = (cleaned) => {
|
|
58
|
+
const segment = cleaned.slice(cleaned.lastIndexOf("/") + 1);
|
|
59
|
+
return targetBasenames.has(segment.replace(/\.[^.]+$/, ""));
|
|
60
|
+
};
|
|
61
|
+
const resolveOutcomes = /* @__PURE__ */ new Map();
|
|
62
|
+
const roots = /* @__PURE__ */ new Set([normalizeSlashes(process.cwd())]);
|
|
63
|
+
const matchTarget = (absPath) => {
|
|
64
|
+
const norm = normalizeSlashes(path6.normalize(absPath));
|
|
65
|
+
for (const rel of byModule.keys()) {
|
|
66
|
+
for (const root of roots) {
|
|
67
|
+
const target = `${root}/${rel}`;
|
|
68
|
+
for (const suffix of MATCH_SUFFIXES) {
|
|
69
|
+
if (`${norm}${suffix}` === target) return rel;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
251
72
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
)
|
|
73
|
+
return null;
|
|
74
|
+
};
|
|
75
|
+
return {
|
|
76
|
+
name: ENV_CONTAINMENT_PLUGIN_NAME,
|
|
77
|
+
// `pre` so we see specifiers before Vite's alias/resolve pipeline and
|
|
78
|
+
// before other resolveId hooks (e.g. tsconfig-paths) claim them.
|
|
79
|
+
enforce: "pre",
|
|
80
|
+
config() {
|
|
81
|
+
const keys = Object.entries(defineEntries);
|
|
82
|
+
if (keys.length === 0) return {};
|
|
83
|
+
const define = {};
|
|
84
|
+
for (const [key, value] of keys) {
|
|
85
|
+
const serialized = JSON.stringify(value);
|
|
86
|
+
define[`process.env.${key}`] = serialized;
|
|
87
|
+
define[`import.meta.env.${key}`] = serialized;
|
|
88
|
+
}
|
|
89
|
+
return { define };
|
|
90
|
+
},
|
|
91
|
+
configResolved(config) {
|
|
92
|
+
if (config.root) roots.add(normalizeSlashes(config.root));
|
|
93
|
+
},
|
|
94
|
+
async resolveId(source, importer) {
|
|
95
|
+
if (byModule.size === 0) return null;
|
|
96
|
+
if (source.startsWith("\0")) return null;
|
|
97
|
+
const cleaned = stripQuery(source);
|
|
98
|
+
let direct = null;
|
|
99
|
+
if (path6.isAbsolute(cleaned)) {
|
|
100
|
+
direct = cleaned;
|
|
101
|
+
} else if (importer && (cleaned.startsWith("./") || cleaned.startsWith("../"))) {
|
|
102
|
+
direct = path6.resolve(path6.dirname(stripQuery(importer)), cleaned);
|
|
103
|
+
}
|
|
104
|
+
if (direct) {
|
|
105
|
+
const rel = matchTarget(direct);
|
|
106
|
+
if (rel) return ENV_CONTAINMENT_VIRTUAL_PREFIX + rel;
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
if (!specifierMightMatch(cleaned)) return null;
|
|
110
|
+
if (typeof this.resolve !== "function") return null;
|
|
111
|
+
const importerDir = importer ? path6.dirname(stripQuery(importer)) : "";
|
|
112
|
+
const outcomeKey = `${importerDir}|${source}`;
|
|
113
|
+
const memoized = resolveOutcomes.get(outcomeKey);
|
|
114
|
+
if (memoized !== void 0) return memoized;
|
|
115
|
+
const resolved = await this.resolve(source, importer, { skipSelf: true });
|
|
116
|
+
let outcome = null;
|
|
117
|
+
if (resolved?.id && !resolved.external) {
|
|
118
|
+
const rel = matchTarget(stripQuery(resolved.id));
|
|
119
|
+
if (rel) outcome = ENV_CONTAINMENT_VIRTUAL_PREFIX + rel;
|
|
120
|
+
}
|
|
121
|
+
resolveOutcomes.set(outcomeKey, outcome);
|
|
122
|
+
return outcome;
|
|
123
|
+
},
|
|
124
|
+
load(id) {
|
|
125
|
+
if (!id.startsWith(ENV_CONTAINMENT_VIRTUAL_PREFIX)) return null;
|
|
126
|
+
const rel = id.slice(ENV_CONTAINMENT_VIRTUAL_PREFIX.length);
|
|
127
|
+
const sub = byModule.get(rel);
|
|
128
|
+
if (!sub) return null;
|
|
129
|
+
return buildStandInSource(sub);
|
|
279
130
|
}
|
|
280
|
-
|
|
281
|
-
}
|
|
131
|
+
};
|
|
282
132
|
}
|
|
283
|
-
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
133
|
+
function buildStandInSource(sub) {
|
|
134
|
+
const marker = {
|
|
135
|
+
modulePath: sub.module,
|
|
136
|
+
reason: "env-validation",
|
|
137
|
+
envKeys: sub.envKeys ?? [],
|
|
138
|
+
...sub.schemaKind ? { schemaKind: sub.schemaKind } : {}
|
|
139
|
+
};
|
|
140
|
+
const named = Array.from(
|
|
141
|
+
new Set(
|
|
142
|
+
(sub.exports ?? []).filter(
|
|
143
|
+
(name) => VALID_IDENT_RE.test(name) && name !== "default"
|
|
144
|
+
)
|
|
145
|
+
)
|
|
146
|
+
);
|
|
147
|
+
return [
|
|
148
|
+
"// Generated by @onlook/storybook-plugin (env containment).",
|
|
149
|
+
`// The real module (${sub.module}) validates environment variables at`,
|
|
150
|
+
"// module scope and throws on import when keys are missing. This stand-in",
|
|
151
|
+
"// defers the throw to property access so Storybook can boot and the",
|
|
152
|
+
"// Onlook error boundary can attribute the failure to the module.",
|
|
153
|
+
`const __onlookMarker = ${JSON.stringify(marker)};`,
|
|
154
|
+
"const __onlookGlobal = globalThis;",
|
|
155
|
+
`__onlookGlobal.${ONLOOK_CONTAINED_MODULES_GLOBAL} =`,
|
|
156
|
+
` __onlookGlobal.${ONLOOK_CONTAINED_MODULES_GLOBAL} || {};`,
|
|
157
|
+
`__onlookGlobal.${ONLOOK_CONTAINED_MODULES_GLOBAL}[__onlookMarker.modulePath] = __onlookMarker;`,
|
|
158
|
+
"function __onlookContainedError(access) {",
|
|
159
|
+
" const keys = __onlookMarker.envKeys.length",
|
|
160
|
+
" ? ' Missing env keys: ' + __onlookMarker.envKeys.join(', ') + '.'",
|
|
161
|
+
" : '';",
|
|
162
|
+
" const err = new Error(",
|
|
163
|
+
" '[onlook] ' + __onlookMarker.modulePath +",
|
|
164
|
+
" ' was contained: it validates environment variables at module scope' +",
|
|
165
|
+
" ' and the required keys are not set. Accessed: ' + access + '.' + keys,",
|
|
166
|
+
" );",
|
|
167
|
+
` err.${ONLOOK_CONTAINED_PROP} = __onlookMarker;`,
|
|
168
|
+
" return err;",
|
|
169
|
+
"}",
|
|
170
|
+
"function __onlookStandIn(exportName) {",
|
|
171
|
+
" return new Proxy(function () {}, {",
|
|
172
|
+
" get(_target, prop) {",
|
|
173
|
+
` if (prop === '${ONLOOK_CONTAINED_PROP}') return __onlookMarker;`,
|
|
174
|
+
" if (typeof prop === 'symbol' || prop === 'then') return undefined;",
|
|
175
|
+
" throw __onlookContainedError(exportName + '.' + String(prop));",
|
|
176
|
+
" },",
|
|
177
|
+
" apply() {",
|
|
178
|
+
" throw __onlookContainedError(exportName + '()');",
|
|
179
|
+
" },",
|
|
180
|
+
" construct() {",
|
|
181
|
+
" throw __onlookContainedError('new ' + exportName + '()');",
|
|
182
|
+
" },",
|
|
183
|
+
" });",
|
|
184
|
+
"}",
|
|
185
|
+
"export default __onlookStandIn('default');",
|
|
186
|
+
...named.map(
|
|
187
|
+
(name) => `export const ${name} = __onlookStandIn(${JSON.stringify(name)});`
|
|
297
188
|
),
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
export default meta;
|
|
302
|
-
type Story = StoryObj<typeof meta>;
|
|
303
|
-
|
|
304
|
-
export const Primary: Story = {};
|
|
305
|
-
`;
|
|
189
|
+
""
|
|
190
|
+
].join("\n");
|
|
306
191
|
}
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
192
|
+
|
|
193
|
+
// src/module-load-catch-all/module-load-catch-all.ts
|
|
194
|
+
var STORYBOOK_VIRTUAL_STORIES_ID = "virtual:/@storybook/builder-vite/storybook-stories.js";
|
|
195
|
+
var RESOLVED_STORYBOOK_VIRTUAL_STORIES_ID = `\0${STORYBOOK_VIRTUAL_STORIES_ID}`;
|
|
196
|
+
var MODULE_LOAD_CATCH_ALL_PLUGIN_NAME = "onlook-module-load-catch-all";
|
|
197
|
+
var IMPORT_FN_CALL = "return await importers[path]();";
|
|
198
|
+
var INJECTED_BINDING = "__onlookSyntheticCsfModule";
|
|
199
|
+
async function onlookSyntheticCsfModule(importPath, loadError, containedProp) {
|
|
200
|
+
const original = loadError instanceof Error ? loadError : new Error(String(loadError));
|
|
201
|
+
const synthetic = new Error(
|
|
202
|
+
`Story module failed to load: ${importPath}
|
|
203
|
+
${original.message}`
|
|
204
|
+
);
|
|
205
|
+
const marker = original[containedProp];
|
|
206
|
+
if (marker !== void 0) {
|
|
207
|
+
synthetic[containedProp] = marker;
|
|
314
208
|
}
|
|
315
|
-
|
|
316
|
-
p = p.replace(/\/+/g, "/").replace(/^\/|\/$/g, "");
|
|
317
|
-
return p ? `${p}/${componentName}` : componentName;
|
|
318
|
-
}
|
|
319
|
-
function formatObjectEntries(obj, indent) {
|
|
320
|
-
const entries = Object.entries(obj);
|
|
321
|
-
if (entries.length === 0) return "";
|
|
322
|
-
const lines = entries.map(([key, value]) => `${indent}${key}: ${value},`);
|
|
323
|
-
return `
|
|
324
|
-
${lines.join("\n")}
|
|
325
|
-
`;
|
|
326
|
-
}
|
|
327
|
-
function createLightProject() {
|
|
328
|
-
return new Project({
|
|
329
|
-
compilerOptions: { allowJs: true },
|
|
330
|
-
useInMemoryFileSystem: true
|
|
331
|
-
});
|
|
332
|
-
}
|
|
333
|
-
async function genStoryFile({
|
|
334
|
-
options,
|
|
335
|
-
id,
|
|
336
|
-
projectRootDir
|
|
337
|
-
}) {
|
|
338
|
-
if (id.includes(".stories")) return null;
|
|
339
|
-
const {
|
|
340
|
-
fileBase,
|
|
341
|
-
fileName,
|
|
342
|
-
fileExt,
|
|
343
|
-
filePrefixExt,
|
|
344
|
-
componentName,
|
|
345
|
-
relativeSourceFilePath
|
|
346
|
-
} = getComponentInfo(id, projectRootDir);
|
|
209
|
+
synthetic.cause = original;
|
|
347
210
|
try {
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
errorCode: "EC02",
|
|
354
|
-
detail: `Preset ${options.preset} is not supported in this fork. Only "react" is supported.`
|
|
355
|
-
});
|
|
356
|
-
return;
|
|
357
|
-
}
|
|
358
|
-
const storyCode = genReactStoryFile({
|
|
359
|
-
componentName,
|
|
360
|
-
fileBase,
|
|
361
|
-
fileName,
|
|
362
|
-
path: id,
|
|
363
|
-
fileExt,
|
|
364
|
-
filePrefixExt,
|
|
365
|
-
relativeSourceFilePath,
|
|
366
|
-
sourceFile,
|
|
367
|
-
storiesFolder: options.storiesFolder
|
|
368
|
-
});
|
|
369
|
-
if (!storyCode) return null;
|
|
370
|
-
const parsed = path4.parse(id);
|
|
371
|
-
const storyFileName = `${fileName}.stories.tsx`;
|
|
372
|
-
let storiesFilePath;
|
|
373
|
-
if (options.storiesFolder) {
|
|
374
|
-
const storiesFolderPath = path4.join(parsed.dir, options.storiesFolder);
|
|
375
|
-
fs2.mkdirSync(storiesFolderPath, { recursive: true });
|
|
376
|
-
storiesFilePath = path4.join(storiesFolderPath, storyFileName);
|
|
377
|
-
} else {
|
|
378
|
-
storiesFilePath = path4.join(parsed.dir, storyFileName);
|
|
379
|
-
}
|
|
380
|
-
if (!fs2.existsSync(storiesFilePath)) {
|
|
381
|
-
fs2.writeFileSync(storiesFilePath, storyCode);
|
|
382
|
-
}
|
|
383
|
-
return storiesFilePath;
|
|
384
|
-
} catch (err) {
|
|
385
|
-
console.warn(`[ASG] Failed to generate story for ${id}:`, err);
|
|
386
|
-
return null;
|
|
211
|
+
console.error(
|
|
212
|
+
"[ONLOOK] Story module failed to load \u2014 serving contained synthetic stories.",
|
|
213
|
+
{ importPath, error: original }
|
|
214
|
+
);
|
|
215
|
+
} catch {
|
|
387
216
|
}
|
|
388
|
-
|
|
389
|
-
async function getAllFilePaths({
|
|
390
|
-
patterns,
|
|
391
|
-
ignorePatterns,
|
|
392
|
-
projectRootDir
|
|
393
|
-
}) {
|
|
394
|
-
const fullPatterns = patterns.map(
|
|
395
|
-
(p) => path4.join(projectRootDir, p).replace(/\\/g, "/")
|
|
396
|
-
);
|
|
397
|
-
const ignoreFullPatterns = ignorePatterns?.map(
|
|
398
|
-
(p) => path4.join(projectRootDir, p).replace(/\\/g, "/")
|
|
399
|
-
);
|
|
400
|
-
const filePaths = await glob(fullPatterns, {
|
|
401
|
-
ignore: ignoreFullPatterns,
|
|
402
|
-
nodir: true
|
|
403
|
-
});
|
|
404
|
-
return filePaths.map((p) => p.replace(/\\/g, "/"));
|
|
405
|
-
}
|
|
406
|
-
var PLUGIN_NAME = "auto-story-generator";
|
|
407
|
-
var DEFAULT_BATCH_SIZE = 50;
|
|
408
|
-
var DEFAULT_CONCURRENCY = 8;
|
|
409
|
-
var CACHE_FILE_NAME = ".asg-cache.json";
|
|
410
|
-
var mtimeCache = /* @__PURE__ */ new Map();
|
|
411
|
-
function loadDiskCache(projectRootDir) {
|
|
217
|
+
const stories = [];
|
|
412
218
|
try {
|
|
413
|
-
const
|
|
414
|
-
if (
|
|
415
|
-
const
|
|
416
|
-
for (const
|
|
417
|
-
|
|
219
|
+
const response = await fetch("./index.json");
|
|
220
|
+
if (response?.ok) {
|
|
221
|
+
const index = await response.json();
|
|
222
|
+
for (const entry of Object.values(index.entries ?? {})) {
|
|
223
|
+
if (!entry || entry.type !== "story") continue;
|
|
224
|
+
if (entry.importPath !== importPath) continue;
|
|
225
|
+
if (typeof entry.id !== "string") continue;
|
|
226
|
+
stories.push({
|
|
227
|
+
id: entry.id,
|
|
228
|
+
name: typeof entry.name === "string" ? entry.name : entry.id
|
|
229
|
+
});
|
|
418
230
|
}
|
|
419
|
-
console.log(`[ASG] Loaded ${mtimeCache.size} entries from disk cache`);
|
|
420
231
|
}
|
|
421
232
|
} catch {
|
|
422
233
|
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
data[key] = value;
|
|
234
|
+
if (stories.length === 0) {
|
|
235
|
+
try {
|
|
236
|
+
const search = typeof window !== "undefined" && window.location ? window.location.search : "";
|
|
237
|
+
const urlId = new URLSearchParams(search).get("id");
|
|
238
|
+
if (urlId) stories.push({ id: urlId, name: "Needs setup" });
|
|
239
|
+
} catch {
|
|
430
240
|
}
|
|
431
|
-
fs2.writeFileSync(cachePath, JSON.stringify(data));
|
|
432
|
-
} catch {
|
|
433
241
|
}
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
mtimeCache.set(filePath, mtime);
|
|
442
|
-
return true;
|
|
443
|
-
} catch {
|
|
444
|
-
mtimeCache.delete(filePath);
|
|
445
|
-
return true;
|
|
242
|
+
const moduleExports = { default: {} };
|
|
243
|
+
const render = () => {
|
|
244
|
+
throw synthetic;
|
|
245
|
+
};
|
|
246
|
+
if (stories.length === 0) {
|
|
247
|
+
moduleExports.OnlookContained = { name: "Needs setup", render };
|
|
248
|
+
return moduleExports;
|
|
446
249
|
}
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
250
|
+
let i = 0;
|
|
251
|
+
for (const story of stories) {
|
|
252
|
+
moduleExports[`OnlookContained${i}`] = {
|
|
253
|
+
name: story.name,
|
|
254
|
+
// Forces the exact index story id — export names can't be recovered
|
|
255
|
+
// from a module that never evaluated.
|
|
256
|
+
parameters: { __id: story.id },
|
|
257
|
+
render
|
|
258
|
+
};
|
|
259
|
+
i += 1;
|
|
453
260
|
}
|
|
454
|
-
return
|
|
261
|
+
return moduleExports;
|
|
455
262
|
}
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
)
|
|
471
|
-
|
|
472
|
-
|
|
263
|
+
function wrapImportFn(code) {
|
|
264
|
+
if (code.includes(INJECTED_BINDING)) return null;
|
|
265
|
+
if (!code.includes(IMPORT_FN_CALL)) return null;
|
|
266
|
+
const wrappedCall = [
|
|
267
|
+
"try {",
|
|
268
|
+
" return await importers[path]();",
|
|
269
|
+
" } catch (__onlookLoadError) {",
|
|
270
|
+
` return await ${INJECTED_BINDING}(path, __onlookLoadError, ${JSON.stringify(ONLOOK_CONTAINED_PROP)});`,
|
|
271
|
+
" }"
|
|
272
|
+
].join("\n");
|
|
273
|
+
const body = code.replace(IMPORT_FN_CALL, wrappedCall);
|
|
274
|
+
return [
|
|
275
|
+
body,
|
|
276
|
+
"",
|
|
277
|
+
"// Injected by @onlook/storybook-plugin (module-load catch-all, U7).",
|
|
278
|
+
`const ${INJECTED_BINDING} = ${onlookSyntheticCsfModule.toString()};`,
|
|
279
|
+
""
|
|
280
|
+
].join("\n");
|
|
473
281
|
}
|
|
474
|
-
function
|
|
475
|
-
|
|
476
|
-
const batchSize = options.batchSize ?? DEFAULT_BATCH_SIZE;
|
|
477
|
-
const concurrency = options.concurrency ?? DEFAULT_CONCURRENCY;
|
|
478
|
-
let hasRun = false;
|
|
479
|
-
const allGeneratedFiles = [];
|
|
480
|
-
console.log("[ASG] Plugin created", {
|
|
481
|
-
preset: options.preset,
|
|
482
|
-
imports: options.imports,
|
|
483
|
-
isGenerateStoriesFileAtBuild: options.isGenerateStoriesFileAtBuild,
|
|
484
|
-
storiesFolder: options.storiesFolder,
|
|
485
|
-
projectRoot: projectRootDir,
|
|
486
|
-
cwd: process.cwd()
|
|
487
|
-
});
|
|
488
|
-
async function generateAllStories() {
|
|
489
|
-
if (hasRun) return;
|
|
490
|
-
hasRun = true;
|
|
491
|
-
if (!options.isGenerateStoriesFileAtBuild) {
|
|
492
|
-
console.log("[ASG] Skipping \u2014 isGenerateStoriesFileAtBuild is false");
|
|
493
|
-
return;
|
|
494
|
-
}
|
|
495
|
-
loadDiskCache(projectRootDir);
|
|
496
|
-
const patterns = options.imports ?? ["src/**/*.tsx"];
|
|
497
|
-
const ignorePatterns = options.ignores ?? [];
|
|
498
|
-
console.log(`[ASG] Scanning for components: ${patterns.join(", ")}`);
|
|
499
|
-
const allFiles = await getAllFilePaths({
|
|
500
|
-
patterns,
|
|
501
|
-
ignorePatterns,
|
|
502
|
-
projectRootDir
|
|
503
|
-
});
|
|
504
|
-
const filesToProcess = allFiles.filter((filePath) => {
|
|
505
|
-
if (filePath.includes(".stories")) return false;
|
|
506
|
-
if (options.cacheEnabled !== false) {
|
|
507
|
-
const storyPath = getStoryFilePath(filePath, options.storiesFolder);
|
|
508
|
-
const storyExists = fs2.existsSync(storyPath);
|
|
509
|
-
if (!hasFileChanged(filePath) && storyExists) return false;
|
|
510
|
-
}
|
|
511
|
-
return true;
|
|
512
|
-
});
|
|
513
|
-
console.log(
|
|
514
|
-
`[ASG] Found ${allFiles.length} files, ${filesToProcess.length} need processing`
|
|
515
|
-
);
|
|
516
|
-
let totalProcessed = 0;
|
|
517
|
-
for (let i = 0; i < filesToProcess.length; i += batchSize) {
|
|
518
|
-
const batch = filesToProcess.slice(i, i + batchSize);
|
|
519
|
-
const count = await processBatch(
|
|
520
|
-
batch,
|
|
521
|
-
options,
|
|
522
|
-
projectRootDir,
|
|
523
|
-
concurrency,
|
|
524
|
-
allGeneratedFiles
|
|
525
|
-
);
|
|
526
|
-
totalProcessed += count;
|
|
527
|
-
if (options.onProgress) {
|
|
528
|
-
options.onProgress(totalProcessed, filesToProcess.length);
|
|
529
|
-
}
|
|
530
|
-
if (i + batchSize < filesToProcess.length) {
|
|
531
|
-
await new Promise((r) => setTimeout(r, 0));
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
saveDiskCache(projectRootDir);
|
|
535
|
-
console.log(`[ASG] Generated stories for ${totalProcessed} components`);
|
|
536
|
-
}
|
|
282
|
+
function moduleLoadCatchAllPlugin() {
|
|
283
|
+
let warnedDrift = false;
|
|
537
284
|
return {
|
|
538
|
-
name:
|
|
539
|
-
//
|
|
540
|
-
//
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
285
|
+
name: MODULE_LOAD_CATCH_ALL_PLUGIN_NAME,
|
|
286
|
+
// 'pre' so the wrap sees builder-vite's raw codegen (served from its
|
|
287
|
+
// `load` hook) before normal-phase transforms (e.g. plugin-react, whose
|
|
288
|
+
// default filter matches the virtual id's `.js` suffix) reshape it.
|
|
289
|
+
enforce: "pre",
|
|
290
|
+
transform(code, id) {
|
|
291
|
+
if (id !== RESOLVED_STORYBOOK_VIRTUAL_STORIES_ID && id !== STORYBOOK_VIRTUAL_STORIES_ID) {
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
const wrapped = wrapImportFn(code);
|
|
295
|
+
if (wrapped === null) {
|
|
296
|
+
if (!warnedDrift && !code.includes(INJECTED_BINDING)) {
|
|
297
|
+
warnedDrift = true;
|
|
298
|
+
console.warn(
|
|
299
|
+
"[STORYBOOK_PLUGIN] module-load-catch-all: unrecognized importFn codegen \u2014 passing through (module-load failures will surface as raw Storybook overlays)."
|
|
550
300
|
);
|
|
551
|
-
server.watcher.emit("change", firstFile);
|
|
552
301
|
}
|
|
302
|
+
return null;
|
|
553
303
|
}
|
|
554
|
-
|
|
555
|
-
// buildStart fires for builds (fallback)
|
|
556
|
-
async buildStart() {
|
|
557
|
-
console.log("[ASG] buildStart hook fired");
|
|
558
|
-
await generateAllStories();
|
|
559
|
-
},
|
|
560
|
-
async watchChange(id, change) {
|
|
561
|
-
if (change.event === "delete") return;
|
|
562
|
-
const normalizedId = id.replace(/\\/g, "/");
|
|
563
|
-
if (options.imports) {
|
|
564
|
-
const relativePath = normalizedId.replace(projectRootDir, "").replace(/^\//, "");
|
|
565
|
-
const matches = options.imports.some(
|
|
566
|
-
(pattern) => minimatch(relativePath, pattern)
|
|
567
|
-
);
|
|
568
|
-
if (!matches) return;
|
|
569
|
-
}
|
|
570
|
-
if (options.ignores) {
|
|
571
|
-
const relativePath = normalizedId.replace(projectRootDir, "").replace(/^\//, "");
|
|
572
|
-
const ignored = options.ignores.some(
|
|
573
|
-
(pattern) => minimatch(relativePath, pattern)
|
|
574
|
-
);
|
|
575
|
-
if (ignored) return;
|
|
576
|
-
}
|
|
577
|
-
mtimeCache.delete(normalizedId);
|
|
578
|
-
await genStoryFile({
|
|
579
|
-
options,
|
|
580
|
-
id: normalizedId,
|
|
581
|
-
projectRootDir
|
|
582
|
-
});
|
|
304
|
+
return { code: wrapped, map: null };
|
|
583
305
|
}
|
|
584
306
|
};
|
|
585
307
|
}
|
|
586
|
-
var index_default = { vite: createAutoStoryPlugin };
|
|
587
|
-
var FIXED_MARKER = "// @onlook-fixed";
|
|
588
|
-
var parser = withDefaultConfig({
|
|
589
|
-
shouldExtractLiteralValuesFromEnum: true,
|
|
590
|
-
shouldRemoveUndefinedFromOptional: true,
|
|
591
|
-
propFilter: (prop) => {
|
|
592
|
-
if (prop.parent?.fileName.includes("node_modules")) return false;
|
|
593
|
-
return true;
|
|
594
|
-
}
|
|
595
|
-
});
|
|
596
|
-
function resolveComponentPath(storyFilePath) {
|
|
597
|
-
const dir = path4.dirname(storyFilePath);
|
|
598
|
-
const parentDir = path4.dirname(dir);
|
|
599
|
-
const storyName = path4.basename(storyFilePath);
|
|
600
|
-
const componentName = storyName.replace(".stories.tsx", ".tsx");
|
|
601
|
-
const componentPath = path4.join(parentDir, componentName);
|
|
602
|
-
return fs2.existsSync(componentPath) ? componentPath : null;
|
|
603
|
-
}
|
|
604
|
-
function generateArgTypes(componentPath) {
|
|
605
|
-
try {
|
|
606
|
-
const docs = parser.parse(componentPath);
|
|
607
|
-
if (docs.length === 0) return null;
|
|
608
|
-
const doc = docs[0];
|
|
609
|
-
if (!doc) return null;
|
|
610
|
-
const argTypes = {};
|
|
611
|
-
for (const [name, prop] of Object.entries(doc.props)) {
|
|
612
|
-
if (prop.type.name.includes("=>") || prop.type.name === "Function") {
|
|
613
|
-
continue;
|
|
614
|
-
}
|
|
615
|
-
const argType = {};
|
|
616
|
-
if (prop.description) {
|
|
617
|
-
argType.description = prop.description;
|
|
618
|
-
}
|
|
619
|
-
if (prop.type.name === "enum" && prop.type.value) {
|
|
620
|
-
argType.control = { type: "select" };
|
|
621
|
-
argType.options = prop.type.value.map(
|
|
622
|
-
(v) => v.value
|
|
623
|
-
);
|
|
624
|
-
}
|
|
625
|
-
if (prop.type.name === "boolean") {
|
|
626
|
-
argType.control = { type: "boolean" };
|
|
627
|
-
}
|
|
628
|
-
if (prop.type.name === "number") {
|
|
629
|
-
argType.control = { type: "number" };
|
|
630
|
-
}
|
|
631
|
-
if (Object.keys(argType).length > 0) {
|
|
632
|
-
argTypes[name] = argType;
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
return Object.keys(argTypes).length > 0 ? argTypes : null;
|
|
636
|
-
} catch (err) {
|
|
637
|
-
console.error(`[AutoStories] Failed to parse component: ${componentPath}`, err);
|
|
638
|
-
return null;
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
function enrichStoryFile(storyFilePath) {
|
|
642
|
-
try {
|
|
643
|
-
const content = fs2.readFileSync(storyFilePath, "utf-8");
|
|
644
|
-
if (content.includes(FIXED_MARKER)) {
|
|
645
|
-
return;
|
|
646
|
-
}
|
|
647
|
-
if (content.includes("argTypes:")) {
|
|
648
|
-
return;
|
|
649
|
-
}
|
|
650
|
-
const componentPath = resolveComponentPath(storyFilePath);
|
|
651
|
-
if (!componentPath) return;
|
|
652
|
-
const argTypes = generateArgTypes(componentPath);
|
|
653
|
-
if (!argTypes) return;
|
|
654
|
-
const argTypesStr = JSON.stringify(argTypes, null, 2).split("\n").map((line, i) => i === 0 ? line : ` ${line}`).join("\n");
|
|
655
|
-
const enriched = content.replace(
|
|
656
|
-
/};\s*\nexport default meta;/,
|
|
657
|
-
` argTypes: ${argTypesStr},
|
|
658
|
-
};
|
|
659
|
-
export default meta;`
|
|
660
|
-
);
|
|
661
|
-
if (enriched !== content) {
|
|
662
|
-
fs2.writeFileSync(storyFilePath, enriched);
|
|
663
|
-
console.log(`[AutoStories] Enriched ${path4.basename(storyFilePath)} with argTypes`);
|
|
664
|
-
}
|
|
665
|
-
} catch (err) {
|
|
666
|
-
console.error(`[AutoStories] Failed to enrich story: ${storyFilePath}`, err);
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
308
|
function componentLocPlugin(options = {}) {
|
|
670
309
|
const include = options.include ?? /\.(jsx|tsx)$/;
|
|
671
310
|
const traverse = traverseModule.default ?? traverseModule;
|
|
@@ -682,13 +321,14 @@ function componentLocPlugin(options = {}) {
|
|
|
682
321
|
const filepath = id.split("?", 1)[0];
|
|
683
322
|
if (!filepath || filepath.includes("node_modules")) return null;
|
|
684
323
|
if (!include.test(filepath)) return null;
|
|
324
|
+
if (/\.stories\.(jsx?|tsx?)$/.test(filepath)) return null;
|
|
685
325
|
const ast = parse(code, {
|
|
686
326
|
sourceType: "module",
|
|
687
327
|
plugins: ["jsx", "typescript"],
|
|
688
328
|
sourceFilename: filepath
|
|
689
329
|
});
|
|
690
330
|
let mutated = false;
|
|
691
|
-
const relativePath =
|
|
331
|
+
const relativePath = path6.relative(root, filepath);
|
|
692
332
|
traverse(ast, {
|
|
693
333
|
JSXElement(nodePath) {
|
|
694
334
|
const opening = nodePath.node.openingElement;
|
|
@@ -725,9 +365,31 @@ function componentLocPlugin(options = {}) {
|
|
|
725
365
|
}
|
|
726
366
|
};
|
|
727
367
|
}
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
368
|
+
|
|
369
|
+
// src/fast-refresh-tolerant-exports/fast-refresh-tolerant-exports.ts
|
|
370
|
+
function fastRefreshTolerantExportsPlugin() {
|
|
371
|
+
const REGISTER_CALL_RE = /(\w+)\.registerExportsForReactRefresh\((["'])([^"'\n]+)\2,\s*([A-Za-z_$][\w$]*)\)/;
|
|
372
|
+
return {
|
|
373
|
+
name: "onbook-fast-refresh-tolerant-exports",
|
|
374
|
+
enforce: "post",
|
|
375
|
+
apply: "serve",
|
|
376
|
+
transform(code, id) {
|
|
377
|
+
if (!/\.(tsx|jsx)$/.test(id)) return null;
|
|
378
|
+
if (id.includes("node_modules")) return null;
|
|
379
|
+
const m = code.match(REGISTER_CALL_RE);
|
|
380
|
+
if (!m) return null;
|
|
381
|
+
const [fullMatch, , , idValue, exportsVar] = m;
|
|
382
|
+
if (!idValue || !exportsVar) return null;
|
|
383
|
+
const idLiteral = JSON.stringify(idValue);
|
|
384
|
+
const postRegister = `;(()=>{if(typeof window==='undefined')return;const M=(window.__onl1176_nonComponentExports||=Object.create(null));const isLikelyComponentType=(t)=>{switch(typeof t){case 'function':{if(t.prototype!=null){if(t.prototype.isReactComponent)return true;const o=Object.getOwnPropertyNames(t.prototype);if(o.length>1||o[0]!=='constructor')return false;if(Object.getPrototypeOf(t.prototype)!==Object.prototype)return false;}const n=t.name||t.displayName;return typeof n==='string'&&/^[A-Z]/.test(n);}case 'object':{if(t==null)return false;const s=t.$$typeof;return s===Symbol.for('react.forward_ref')||s===Symbol.for('react.memo');}default:return false;}};const isPlainObject=(o)=>Object.prototype.toString.call(o)==='[object Object]'&&(o.constructor===Object||o.constructor===undefined);const isCompoundComponent=(t)=>{if(!isPlainObject(t))return false;for(const k in t)if(!isLikelyComponentType(t[k]))return false;return true;};const isComp=(v)=>isLikelyComponentType(v)||isCompoundComponent(v);const ig=[];for(const k in ${exportsVar}){if(k==='__esModule')continue;if(!isComp(${exportsVar}[k]))ig.push(k);}M[` + idLiteral + "]=ig;if(!window.__getReactRefreshIgnoredExports){window.__getReactRefreshIgnoredExports=({id})=>window.__onl1176_nonComponentExports?.[id]||[];}})();";
|
|
385
|
+
const out = code.replace(fullMatch, fullMatch + postRegister);
|
|
386
|
+
return out === code ? null : { code: out, map: null };
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
var CACHE_DIR = path6.join(process.cwd(), ".storybook-cache");
|
|
391
|
+
var SCREENSHOTS_DIR = path6.join(CACHE_DIR, "screenshots");
|
|
392
|
+
var MANIFEST_PATH = path6.join(CACHE_DIR, "manifest.json");
|
|
731
393
|
var VIEWPORT_WIDTH = 1920;
|
|
732
394
|
var VIEWPORT_HEIGHT = 1080;
|
|
733
395
|
var MIN_COMPONENT_WIDTH = 420;
|
|
@@ -735,30 +397,30 @@ var MIN_COMPONENT_HEIGHT = 280;
|
|
|
735
397
|
|
|
736
398
|
// src/utils/fileSystem/fileSystem.ts
|
|
737
399
|
function ensureCacheDirectories() {
|
|
738
|
-
if (!
|
|
739
|
-
|
|
400
|
+
if (!fs4.existsSync(CACHE_DIR)) {
|
|
401
|
+
fs4.mkdirSync(CACHE_DIR, { recursive: true });
|
|
740
402
|
}
|
|
741
|
-
if (!
|
|
742
|
-
|
|
403
|
+
if (!fs4.existsSync(SCREENSHOTS_DIR)) {
|
|
404
|
+
fs4.mkdirSync(SCREENSHOTS_DIR, { recursive: true });
|
|
743
405
|
}
|
|
744
406
|
}
|
|
745
407
|
function computeFileHash(filePath) {
|
|
746
|
-
if (!
|
|
408
|
+
if (!fs4.existsSync(filePath)) {
|
|
747
409
|
return "";
|
|
748
410
|
}
|
|
749
|
-
const content =
|
|
411
|
+
const content = fs4.readFileSync(filePath, "utf-8");
|
|
750
412
|
return crypto.createHash("sha256").update(content).digest("hex");
|
|
751
413
|
}
|
|
752
414
|
function loadManifest() {
|
|
753
|
-
if (
|
|
754
|
-
const content =
|
|
415
|
+
if (fs4.existsSync(MANIFEST_PATH)) {
|
|
416
|
+
const content = fs4.readFileSync(MANIFEST_PATH, "utf-8");
|
|
755
417
|
return JSON.parse(content);
|
|
756
418
|
}
|
|
757
419
|
return { stories: {} };
|
|
758
420
|
}
|
|
759
421
|
function saveManifest(manifest) {
|
|
760
422
|
ensureCacheDirectories();
|
|
761
|
-
|
|
423
|
+
fs4.writeFileSync(MANIFEST_PATH, JSON.stringify(manifest, null, 2));
|
|
762
424
|
}
|
|
763
425
|
function updateManifest(storyId, sourcePath, fileHash, boundingBox) {
|
|
764
426
|
const manifest = loadManifest();
|
|
@@ -774,22 +436,82 @@ function updateManifest(storyId, sourcePath, fileHash, boundingBox) {
|
|
|
774
436
|
};
|
|
775
437
|
saveManifest(manifest);
|
|
776
438
|
}
|
|
777
|
-
var
|
|
439
|
+
var execFileAsync = promisify(execFile);
|
|
440
|
+
var require2 = createRequire$1(import.meta.url);
|
|
441
|
+
var browserPromise = null;
|
|
442
|
+
var installPromise = null;
|
|
443
|
+
var isMissingExecutableError = (err) => {
|
|
444
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
445
|
+
return msg.includes("Executable doesn't exist") || msg.includes("Please run the following command to download new browsers") || // Older playwright revisions phrase the version-mismatch case differently.
|
|
446
|
+
/chromium-\d+\/chrome-linux\/chrome/i.test(msg);
|
|
447
|
+
};
|
|
448
|
+
async function installChromium() {
|
|
449
|
+
if (!installPromise) {
|
|
450
|
+
installPromise = (async () => {
|
|
451
|
+
const cliPath = require2.resolve("playwright-core/cli.js");
|
|
452
|
+
console.log(
|
|
453
|
+
`[STORYBOOK_PLUGIN] chromium binary missing \u2014 installing via ${cliPath}`
|
|
454
|
+
);
|
|
455
|
+
try {
|
|
456
|
+
const { stdout, stderr } = await execFileAsync(
|
|
457
|
+
process.execPath,
|
|
458
|
+
[cliPath, "install", "--force", "chromium"],
|
|
459
|
+
{ timeout: 5 * 60 * 1e3, maxBuffer: 32 * 1024 * 1024 }
|
|
460
|
+
);
|
|
461
|
+
if (stdout)
|
|
462
|
+
console.log(`[STORYBOOK_PLUGIN] playwright install: ${stdout.slice(-1e3)}`);
|
|
463
|
+
if (stderr)
|
|
464
|
+
console.log(
|
|
465
|
+
`[STORYBOOK_PLUGIN] playwright install stderr: ${stderr.slice(-1e3)}`
|
|
466
|
+
);
|
|
467
|
+
console.log("[STORYBOOK_PLUGIN] chromium installed");
|
|
468
|
+
} catch (err) {
|
|
469
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
470
|
+
console.error(`[STORYBOOK_PLUGIN] chromium install FAILED: ${detail}`);
|
|
471
|
+
throw new Error(`Playwright chromium install failed: ${detail}`);
|
|
472
|
+
} finally {
|
|
473
|
+
installPromise = null;
|
|
474
|
+
}
|
|
475
|
+
})();
|
|
476
|
+
}
|
|
477
|
+
return installPromise;
|
|
478
|
+
}
|
|
479
|
+
async function launchWithSelfHeal() {
|
|
480
|
+
try {
|
|
481
|
+
return await chromium.launch({ headless: true });
|
|
482
|
+
} catch (err) {
|
|
483
|
+
if (!isMissingExecutableError(err)) throw err;
|
|
484
|
+
console.log("[STORYBOOK_PLUGIN] launch failed, attempting self-heal install");
|
|
485
|
+
await installChromium();
|
|
486
|
+
try {
|
|
487
|
+
return await chromium.launch({ headless: true });
|
|
488
|
+
} catch (postInstallErr) {
|
|
489
|
+
const detail = postInstallErr instanceof Error ? postInstallErr.message : String(postInstallErr);
|
|
490
|
+
console.error(
|
|
491
|
+
`[STORYBOOK_PLUGIN] launch still failing after install \u2014 likely path or libs issue: ${detail}`
|
|
492
|
+
);
|
|
493
|
+
throw new Error(
|
|
494
|
+
`Playwright chromium is unavailable in this sandbox (install ran but launch still failed): ${detail}`
|
|
495
|
+
);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
778
499
|
async function getBrowser() {
|
|
779
|
-
if (!
|
|
780
|
-
|
|
781
|
-
|
|
500
|
+
if (!browserPromise) {
|
|
501
|
+
browserPromise = launchWithSelfHeal().catch((err) => {
|
|
502
|
+
browserPromise = null;
|
|
503
|
+
throw err;
|
|
782
504
|
});
|
|
783
505
|
}
|
|
784
|
-
return
|
|
506
|
+
return browserPromise;
|
|
785
507
|
}
|
|
786
508
|
function getScreenshotPath(storyId, theme) {
|
|
787
|
-
const storyDir =
|
|
788
|
-
return
|
|
509
|
+
const storyDir = path6.join(SCREENSHOTS_DIR, storyId);
|
|
510
|
+
return path6.join(storyDir, `${theme}.png`);
|
|
789
511
|
}
|
|
790
512
|
async function captureScreenshotBuffer(storyId, theme, width = VIEWPORT_WIDTH, height = VIEWPORT_HEIGHT, storybookUrl = "http://localhost:6006", timeoutMs = 3e4) {
|
|
791
|
-
const
|
|
792
|
-
const context = await
|
|
513
|
+
const browser = await getBrowser();
|
|
514
|
+
const context = await browser.newContext({
|
|
793
515
|
viewport: { width, height },
|
|
794
516
|
deviceScaleFactor: 2
|
|
795
517
|
});
|
|
@@ -872,9 +594,9 @@ async function captureScreenshotBuffer(storyId, theme, width = VIEWPORT_WIDTH, h
|
|
|
872
594
|
async function generateScreenshot(storyId, theme, storybookUrl = "http://localhost:6006", timeoutMs = 3e4) {
|
|
873
595
|
try {
|
|
874
596
|
ensureCacheDirectories();
|
|
875
|
-
const storyDir =
|
|
876
|
-
if (!
|
|
877
|
-
|
|
597
|
+
const storyDir = path6.join(SCREENSHOTS_DIR, storyId);
|
|
598
|
+
if (!fs4.existsSync(storyDir)) {
|
|
599
|
+
fs4.mkdirSync(storyDir, { recursive: true });
|
|
878
600
|
}
|
|
879
601
|
const screenshotPath = getScreenshotPath(storyId, theme);
|
|
880
602
|
const { buffer, boundingBox } = await captureScreenshotBuffer(
|
|
@@ -885,7 +607,7 @@ async function generateScreenshot(storyId, theme, storybookUrl = "http://localho
|
|
|
885
607
|
storybookUrl,
|
|
886
608
|
timeoutMs
|
|
887
609
|
);
|
|
888
|
-
|
|
610
|
+
fs4.writeFileSync(screenshotPath, buffer);
|
|
889
611
|
return { path: screenshotPath, boundingBox };
|
|
890
612
|
} catch (error) {
|
|
891
613
|
console.error(`Error generating screenshot for ${storyId} (${theme}):`, error);
|
|
@@ -893,12 +615,93 @@ async function generateScreenshot(storyId, theme, storybookUrl = "http://localho
|
|
|
893
615
|
}
|
|
894
616
|
}
|
|
895
617
|
|
|
618
|
+
// src/screenshot-service/screenshot-service.ts
|
|
619
|
+
process.env.ONBOOK_PROXY_TOKEN ?? null;
|
|
620
|
+
process.env.ONBOOK_BROADCAST_URL ?? null;
|
|
621
|
+
|
|
622
|
+
// src/utils/notifyOnbookIndexChanged/notifyOnbookIndexChanged.ts
|
|
623
|
+
var DEBOUNCE_MS = 300;
|
|
624
|
+
var REQUEST_TIMEOUT_MS = 5e3;
|
|
625
|
+
var pending = null;
|
|
626
|
+
var inFlight = false;
|
|
627
|
+
var pendingTrailingCall = false;
|
|
628
|
+
function notifyOnbookIndexChanged() {
|
|
629
|
+
const proxyToken = process.env.ONBOOK_PROXY_TOKEN;
|
|
630
|
+
const broadcastUrl = process.env.ONBOOK_BROADCAST_URL;
|
|
631
|
+
if (!proxyToken || !broadcastUrl) return;
|
|
632
|
+
if (pending) clearTimeout(pending);
|
|
633
|
+
pending = setTimeout(() => {
|
|
634
|
+
pending = null;
|
|
635
|
+
if (inFlight) {
|
|
636
|
+
pendingTrailingCall = true;
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
void fire(proxyToken, broadcastUrl);
|
|
640
|
+
}, DEBOUNCE_MS);
|
|
641
|
+
}
|
|
642
|
+
async function fire(proxyToken, broadcastUrl) {
|
|
643
|
+
inFlight = true;
|
|
644
|
+
try {
|
|
645
|
+
const res = await fetch(broadcastUrl, {
|
|
646
|
+
method: "POST",
|
|
647
|
+
headers: {
|
|
648
|
+
Authorization: `Bearer ${proxyToken}`,
|
|
649
|
+
"Content-Type": "application/json"
|
|
650
|
+
},
|
|
651
|
+
body: JSON.stringify({ event: { type: "STORYBOOK_INDEX_CHANGED" } }),
|
|
652
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
653
|
+
});
|
|
654
|
+
if (!res.ok) {
|
|
655
|
+
console.warn("[STORYBOOK_PLUGIN] broadcast non-2xx", {
|
|
656
|
+
status: res.status,
|
|
657
|
+
statusText: res.statusText
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
} catch (err) {
|
|
661
|
+
console.warn(
|
|
662
|
+
"[STORYBOOK_PLUGIN] index-changed broadcast failed",
|
|
663
|
+
err instanceof Error ? err.message : String(err)
|
|
664
|
+
);
|
|
665
|
+
} finally {
|
|
666
|
+
inFlight = false;
|
|
667
|
+
if (pendingTrailingCall) {
|
|
668
|
+
pendingTrailingCall = false;
|
|
669
|
+
notifyOnbookIndexChanged();
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// src/utils/notifyOnbookIndexChanged/waitForIndexAndBroadcast.ts
|
|
675
|
+
var PROBE_INTERVAL_MS = 500;
|
|
676
|
+
var PROBE_CEILING_MS = 5 * 60 * 1e3;
|
|
677
|
+
async function waitForIndexAndBroadcast(port = 6006) {
|
|
678
|
+
const url = `http://localhost:${port}/onbook-index.json`;
|
|
679
|
+
const deadline = Date.now() + PROBE_CEILING_MS;
|
|
680
|
+
while (Date.now() < deadline) {
|
|
681
|
+
try {
|
|
682
|
+
const res = await fetch(url, {
|
|
683
|
+
signal: AbortSignal.timeout(2e3)
|
|
684
|
+
});
|
|
685
|
+
if (res.ok) {
|
|
686
|
+
notifyOnbookIndexChanged();
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
} catch {
|
|
690
|
+
}
|
|
691
|
+
await new Promise((r) => setTimeout(r, PROBE_INTERVAL_MS));
|
|
692
|
+
}
|
|
693
|
+
console.warn("[STORYBOOK_PLUGIN] waitForIndexAndBroadcast hit ceiling without a 2xx", {
|
|
694
|
+
url,
|
|
695
|
+
ceilingMs: PROBE_CEILING_MS
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
|
|
896
699
|
// src/handlers/handleStoryFileChange/handleStoryFileChange.ts
|
|
897
700
|
var cachedIndex = null;
|
|
898
701
|
var indexFetchPromise = null;
|
|
899
702
|
var pendingFiles = /* @__PURE__ */ new Set();
|
|
900
703
|
var debounceTimer = null;
|
|
901
|
-
var
|
|
704
|
+
var DEBOUNCE_MS2 = 500;
|
|
902
705
|
async function fetchStorybookIndex() {
|
|
903
706
|
try {
|
|
904
707
|
const response = await fetch("http://localhost:6006/index.json");
|
|
@@ -912,7 +715,7 @@ async function fetchStorybookIndex() {
|
|
|
912
715
|
}
|
|
913
716
|
function getStoriesForFile(filePath) {
|
|
914
717
|
if (!cachedIndex) return [];
|
|
915
|
-
const fileName =
|
|
718
|
+
const fileName = path6.basename(filePath);
|
|
916
719
|
return Object.values(cachedIndex.entries).filter((entry) => entry.type === "story" && entry.importPath.endsWith(fileName)).map((entry) => entry.id);
|
|
917
720
|
}
|
|
918
721
|
async function regenerateScreenshotsForFiles(files) {
|
|
@@ -957,6 +760,7 @@ async function regenerateScreenshotsForFiles(files) {
|
|
|
957
760
|
function handleStoryFileChange({ file, modules }) {
|
|
958
761
|
if (file.endsWith(".stories.tsx") || file.endsWith(".stories.ts")) {
|
|
959
762
|
console.log(`[Screenshots] Story file changed: ${file}`);
|
|
763
|
+
notifyOnbookIndexChanged();
|
|
960
764
|
if (!cachedIndex && !indexFetchPromise) {
|
|
961
765
|
indexFetchPromise = fetchStorybookIndex();
|
|
962
766
|
}
|
|
@@ -973,10 +777,123 @@ function handleStoryFileChange({ file, modules }) {
|
|
|
973
777
|
} catch (error) {
|
|
974
778
|
console.error("[Screenshots] Error regenerating screenshots:", error);
|
|
975
779
|
}
|
|
976
|
-
},
|
|
780
|
+
}, DEBOUNCE_MS2);
|
|
977
781
|
return modules;
|
|
978
782
|
}
|
|
979
783
|
}
|
|
784
|
+
|
|
785
|
+
// src/screenshot-service/utils/console-logs/console-logs.ts
|
|
786
|
+
async function captureConsoleLogs(storyId, theme = "light", storybookUrl = "http://localhost:6006", timeoutMs = 3e4) {
|
|
787
|
+
const browser = await getBrowser();
|
|
788
|
+
const context = await browser.newContext({
|
|
789
|
+
viewport: { width: VIEWPORT_WIDTH, height: VIEWPORT_HEIGHT }
|
|
790
|
+
});
|
|
791
|
+
const page = await context.newPage();
|
|
792
|
+
const logs = [];
|
|
793
|
+
const pageErrors = [];
|
|
794
|
+
page.on("console", (msg) => {
|
|
795
|
+
const level = msg.type();
|
|
796
|
+
logs.push({
|
|
797
|
+
level: level === "warning" ? "warn" : level,
|
|
798
|
+
text: msg.text(),
|
|
799
|
+
timestamp: Date.now()
|
|
800
|
+
});
|
|
801
|
+
});
|
|
802
|
+
page.on("pageerror", (err) => {
|
|
803
|
+
pageErrors.push(err.message);
|
|
804
|
+
});
|
|
805
|
+
const url = `${storybookUrl}/iframe.html?id=${storyId}&viewMode=story&globals=theme:${theme}`;
|
|
806
|
+
try {
|
|
807
|
+
await page.goto(url, { timeout: timeoutMs });
|
|
808
|
+
await page.waitForLoadState("domcontentloaded", { timeout: timeoutMs });
|
|
809
|
+
await page.waitForLoadState("load", { timeout: timeoutMs });
|
|
810
|
+
try {
|
|
811
|
+
await page.waitForLoadState("networkidle", { timeout: 5e3 });
|
|
812
|
+
} catch {
|
|
813
|
+
}
|
|
814
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
815
|
+
return { logs, pageErrors, url };
|
|
816
|
+
} finally {
|
|
817
|
+
await context.close();
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
// src/soft-story-rerender/soft-story-rerender.ts
|
|
822
|
+
function softStoryRerenderPlugin() {
|
|
823
|
+
let warnedOnce = false;
|
|
824
|
+
return {
|
|
825
|
+
name: "onbook-soft-story-rerender",
|
|
826
|
+
enforce: "post",
|
|
827
|
+
apply: "serve",
|
|
828
|
+
transform(code, _id) {
|
|
829
|
+
if (!code.includes("__STORYBOOK_PREVIEW__.onStoriesChanged")) return null;
|
|
830
|
+
const pattern = /import\.meta\.hot\.accept\((['"`])([^'"`\n]+)\1,\s*(?:async\s+)?\(?\s*newModule\s*\)?\s*=>\s*\{[\s\S]*?window\.__STORYBOOK_PREVIEW__\.onStoriesChanged\(\{\s*importFn:\s*newModule\.importFn\s*\}\);?\s*\}\);/;
|
|
831
|
+
if (!pattern.test(code)) {
|
|
832
|
+
if (!warnedOnce) {
|
|
833
|
+
warnedOnce = true;
|
|
834
|
+
console.warn(
|
|
835
|
+
"[onbook soft-story-rerender] preview bootstrap matched the file but the stories-accept-handler pattern did not \u2014 soft rerender disabled for this build of @storybook/builder-vite. Story edits will still work, just with the default Storybook flash."
|
|
836
|
+
);
|
|
837
|
+
}
|
|
838
|
+
return null;
|
|
839
|
+
}
|
|
840
|
+
const replacement = `import.meta.hot.accept($QUOTE$$PATH$$QUOTE$, async (newModule) => {
|
|
841
|
+
// ONL-1176 soft rerender \u2014 call rerender() on each active StoryRender
|
|
842
|
+
// instead of preview.onStoriesChanged() (which tears down + remounts).
|
|
843
|
+
// This accept handler fires in EVERY canvas iframe whenever ANY story
|
|
844
|
+
// file changes (every iframe's preview bundle imports the same virtual
|
|
845
|
+
// stories index). We identity-check each render's unboundStoryFn so
|
|
846
|
+
// iframes whose underlying story file didn't change stay perfectly
|
|
847
|
+
// still \u2014 no 1-frame no-op tick.
|
|
848
|
+
const preview = window.__STORYBOOK_PREVIEW__;
|
|
849
|
+
if (!preview || !preview.storyStoreValue) {
|
|
850
|
+
window.__STORYBOOK_PREVIEW__?.onStoriesChanged({ importFn: newModule.importFn });
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
try {
|
|
854
|
+
await preview.storyStoreValue.onStoriesChanged({ importFn: newModule.importFn });
|
|
855
|
+
const renders = preview.storyRenders || [];
|
|
856
|
+
if (renders.length === 0) {
|
|
857
|
+
preview.onStoriesChanged({ importFn: newModule.importFn });
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
await Promise.all(
|
|
861
|
+
renders.map(async (r) => {
|
|
862
|
+
try {
|
|
863
|
+
const nextStory = await preview.storyStoreValue.loadStory({ storyId: r.id });
|
|
864
|
+
if (!nextStory) return;
|
|
865
|
+
const prev = r.story;
|
|
866
|
+
const unchanged =
|
|
867
|
+
prev &&
|
|
868
|
+
prev.unboundStoryFn === nextStory.unboundStoryFn &&
|
|
869
|
+
prev.storyFn === nextStory.storyFn &&
|
|
870
|
+
prev.moduleExport === nextStory.moduleExport &&
|
|
871
|
+
prev.playFunction === nextStory.playFunction;
|
|
872
|
+
if (unchanged) return;
|
|
873
|
+
r.story = nextStory;
|
|
874
|
+
// Stories using the \`mount\` API can't be rerendered in place
|
|
875
|
+
// (their play function owns mounting), so fall back to remount.
|
|
876
|
+
if (nextStory.usesMount && r.remount) {
|
|
877
|
+
return r.remount();
|
|
878
|
+
}
|
|
879
|
+
return r.rerender();
|
|
880
|
+
} catch (innerErr) {
|
|
881
|
+
console.warn('[onbook] rerender failed for story', r.id, innerErr);
|
|
882
|
+
}
|
|
883
|
+
}),
|
|
884
|
+
);
|
|
885
|
+
} catch (err) {
|
|
886
|
+
console.warn('[onbook] soft rerender failed, falling back', err);
|
|
887
|
+
preview.onStoriesChanged({ importFn: newModule.importFn });
|
|
888
|
+
}
|
|
889
|
+
});`;
|
|
890
|
+
const out = code.replace(pattern, (_match, quote, path7) => {
|
|
891
|
+
return replacement.replace("$QUOTE$$PATH$$QUOTE$", `${quote}${path7}${quote}`);
|
|
892
|
+
});
|
|
893
|
+
return out === code ? null : { code: out, map: null };
|
|
894
|
+
}
|
|
895
|
+
};
|
|
896
|
+
}
|
|
980
897
|
function findGitRoot(startPath) {
|
|
981
898
|
let currentPath = startPath;
|
|
982
899
|
while (currentPath !== dirname(currentPath)) {
|
|
@@ -995,86 +912,71 @@ var gitRoot = findGitRoot(storybookDir);
|
|
|
995
912
|
var storybookLocation = gitRoot ? relative(gitRoot, storybookDir) : "";
|
|
996
913
|
var repoRoot = gitRoot || process.cwd();
|
|
997
914
|
var DEFAULT_ALLOWED_ORIGINS = [
|
|
998
|
-
"https://app.onlook.
|
|
915
|
+
"https://app.onlook.com",
|
|
999
916
|
"http://localhost:3000",
|
|
1000
917
|
"http://localhost:6006"
|
|
1001
918
|
];
|
|
1002
|
-
var
|
|
1003
|
-
var
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
if (importPath.includes(AUTO_STORIES_FOLDER)) {
|
|
1016
|
-
autoStories.push(storyId);
|
|
1017
|
-
} else {
|
|
1018
|
-
userStories.push(storyId);
|
|
1019
|
-
}
|
|
1020
|
-
}
|
|
1021
|
-
const healthy = autoStories.filter((id) => !storyRuntimeErrors.has(id));
|
|
1022
|
-
const broken = autoStories.filter((id) => storyRuntimeErrors.has(id)).map((id) => ({
|
|
1023
|
-
storyId: id,
|
|
1024
|
-
// biome-ignore lint/style/noNonNullAssertion: filtered above
|
|
1025
|
-
error: storyRuntimeErrors.get(id)
|
|
1026
|
-
}));
|
|
1027
|
-
res.setHeader("Content-Type", "application/json");
|
|
1028
|
-
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
1029
|
-
res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
|
|
1030
|
-
res.end(
|
|
1031
|
-
JSON.stringify({
|
|
1032
|
-
autoStoryCount: autoStories.length,
|
|
1033
|
-
userStoryCount: userStories.length,
|
|
1034
|
-
healthy,
|
|
1035
|
-
broken
|
|
1036
|
-
})
|
|
1037
|
-
);
|
|
1038
|
-
}).catch((error) => {
|
|
1039
|
-
res.statusCode = 500;
|
|
1040
|
-
res.setHeader("Content-Type", "application/json");
|
|
1041
|
-
res.end(
|
|
1042
|
-
JSON.stringify({
|
|
1043
|
-
error: "Failed to fetch story index",
|
|
1044
|
-
details: String(error)
|
|
1045
|
-
})
|
|
1046
|
-
);
|
|
919
|
+
var manifestCache = null;
|
|
920
|
+
var manifestHash = null;
|
|
921
|
+
function readManifestFromDisk(filePath) {
|
|
922
|
+
try {
|
|
923
|
+
if (!fs4.existsSync(filePath)) return null;
|
|
924
|
+
const raw = fs4.readFileSync(filePath, "utf-8");
|
|
925
|
+
const hash = createHash("sha1").update(raw).digest("hex");
|
|
926
|
+
const manifest = JSON.parse(raw);
|
|
927
|
+
return { manifest, hash };
|
|
928
|
+
} catch (err) {
|
|
929
|
+
console.warn("[STORYBOOK_PLUGIN] Failed to read manifest", {
|
|
930
|
+
filePath,
|
|
931
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1047
932
|
});
|
|
1048
|
-
return;
|
|
933
|
+
return null;
|
|
1049
934
|
}
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
req.on("end", () => {
|
|
1056
|
-
try {
|
|
1057
|
-
const { storyId, error } = JSON.parse(body);
|
|
1058
|
-
if (storyId && error) {
|
|
1059
|
-
storyRuntimeErrors.set(storyId, error);
|
|
1060
|
-
console.log(
|
|
1061
|
-
`[STORYBOOK_PLUGIN] Story runtime error reported: ${storyId}`,
|
|
1062
|
-
error
|
|
1063
|
-
);
|
|
1064
|
-
}
|
|
1065
|
-
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
1066
|
-
res.statusCode = 200;
|
|
1067
|
-
res.end("ok");
|
|
1068
|
-
} catch {
|
|
1069
|
-
res.statusCode = 400;
|
|
1070
|
-
res.end("Invalid JSON");
|
|
1071
|
-
}
|
|
1072
|
-
});
|
|
1073
|
-
return;
|
|
935
|
+
}
|
|
936
|
+
function refreshManifest(filePath) {
|
|
937
|
+
const result = readManifestFromDisk(filePath);
|
|
938
|
+
if (!result) {
|
|
939
|
+
return false;
|
|
1074
940
|
}
|
|
941
|
+
if (result.hash === manifestHash) return false;
|
|
942
|
+
manifestCache = result.manifest;
|
|
943
|
+
manifestHash = result.hash;
|
|
944
|
+
console.log("[STORYBOOK_PLUGIN] Manifest cache refreshed", {
|
|
945
|
+
filePath,
|
|
946
|
+
hash: result.hash.slice(0, 8),
|
|
947
|
+
storyCount: Object.keys(result.manifest.stories ?? {}).length
|
|
948
|
+
});
|
|
949
|
+
return true;
|
|
950
|
+
}
|
|
951
|
+
var TAILWIND_ENTRY_CANDIDATES = [
|
|
952
|
+
"src/app/globals.css",
|
|
953
|
+
"app/globals.css",
|
|
954
|
+
"src/index.css",
|
|
955
|
+
"src/main.css",
|
|
956
|
+
"src/styles.css",
|
|
957
|
+
"src/styles/globals.css",
|
|
958
|
+
"src/styles/index.css",
|
|
959
|
+
"styles/globals.css",
|
|
960
|
+
"styles/index.css",
|
|
961
|
+
"app.css",
|
|
962
|
+
"index.css"
|
|
963
|
+
];
|
|
964
|
+
var TAILWIND_DIRECTIVE_PATTERN = /@import\s+['"](?:tailwindcss|tailwindcss\/.+)['"]|@tailwind\s+(?:base|components|utilities)/;
|
|
965
|
+
function detectTailwindEntryCss(cwd) {
|
|
966
|
+
for (const rel of TAILWIND_ENTRY_CANDIDATES) {
|
|
967
|
+
const abs = path6.join(cwd, rel);
|
|
968
|
+
try {
|
|
969
|
+
if (!fs4.existsSync(abs)) continue;
|
|
970
|
+
const head = fs4.readFileSync(abs, "utf-8").slice(0, 2048);
|
|
971
|
+
if (TAILWIND_DIRECTIVE_PATTERN.test(head)) return abs;
|
|
972
|
+
} catch {
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
return null;
|
|
976
|
+
}
|
|
977
|
+
var serveMetadataAndScreenshots = (req, res, next) => {
|
|
1075
978
|
if (req.url === "/onbook-index.json") {
|
|
1076
979
|
console.log("[STORYBOOK_PLUGIN] Serving /onbook-index.json endpoint");
|
|
1077
|
-
const manifestPath = path4.join(process.cwd(), ".storybook-cache", "manifest.json");
|
|
1078
980
|
const cacheBuster = Date.now();
|
|
1079
981
|
console.log("[STORYBOOK_PLUGIN] Fetching http://localhost:6006/index.json");
|
|
1080
982
|
fetch(`http://localhost:6006/index.json?_t=${cacheBuster}`, {
|
|
@@ -1089,20 +991,9 @@ var serveMetadataAndScreenshots = (req, res, next) => {
|
|
|
1089
991
|
ok: response.ok,
|
|
1090
992
|
statusText: response.statusText
|
|
1091
993
|
});
|
|
1092
|
-
if (!response.ok) {
|
|
1093
|
-
console.log("[STORYBOOK_PLUGIN] Index not ready yet", {
|
|
1094
|
-
status: response.status
|
|
1095
|
-
});
|
|
1096
|
-
res.statusCode = 503;
|
|
1097
|
-
res.setHeader("Content-Type", "application/json");
|
|
1098
|
-
res.setHeader("Retry-After", "10");
|
|
1099
|
-
res.end(JSON.stringify({ code: "INDEX_NOT_READY" }));
|
|
1100
|
-
return null;
|
|
1101
|
-
}
|
|
1102
994
|
return response.json();
|
|
1103
995
|
}).then((indexData) => {
|
|
1104
|
-
|
|
1105
|
-
const manifest = fs2.existsSync(manifestPath) ? JSON.parse(fs2.readFileSync(manifestPath, "utf-8")) : { stories: {} };
|
|
996
|
+
const manifest = manifestCache ?? { stories: {} };
|
|
1106
997
|
const defaultBoundingBox = { width: 1920, height: 1080 };
|
|
1107
998
|
for (const [storyId, entry] of Object.entries(indexData.entries || {})) {
|
|
1108
999
|
const manifestEntry = manifest.stories?.[storyId];
|
|
@@ -1120,17 +1011,15 @@ var serveMetadataAndScreenshots = (req, res, next) => {
|
|
|
1120
1011
|
res.setHeader("Expires", "0");
|
|
1121
1012
|
res.end(JSON.stringify(indexData));
|
|
1122
1013
|
}).catch((error) => {
|
|
1123
|
-
console.
|
|
1124
|
-
error: error instanceof Error ? error.message : String(error)
|
|
1014
|
+
console.error("[STORYBOOK_PLUGIN] Failed to fetch/extend index.json", {
|
|
1015
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1016
|
+
errorType: error instanceof Error ? error.constructor.name : typeof error,
|
|
1017
|
+
stack: error instanceof Error ? error.stack : void 0
|
|
1125
1018
|
});
|
|
1126
|
-
res.statusCode =
|
|
1019
|
+
res.statusCode = 500;
|
|
1127
1020
|
res.setHeader("Content-Type", "application/json");
|
|
1128
|
-
res.setHeader("Retry-After", "10");
|
|
1129
1021
|
res.end(
|
|
1130
|
-
JSON.stringify({
|
|
1131
|
-
code: "INDEX_NOT_READY",
|
|
1132
|
-
details: String(error)
|
|
1133
|
-
})
|
|
1022
|
+
JSON.stringify({ error: "Failed to fetch index", details: String(error) })
|
|
1134
1023
|
);
|
|
1135
1024
|
});
|
|
1136
1025
|
return;
|
|
@@ -1171,8 +1060,42 @@ var serveMetadataAndScreenshots = (req, res, next) => {
|
|
|
1171
1060
|
});
|
|
1172
1061
|
return;
|
|
1173
1062
|
}
|
|
1063
|
+
if (req.url?.startsWith("/api/console-logs")) {
|
|
1064
|
+
const url = new URL(req.url, "http://localhost");
|
|
1065
|
+
const storyId = url.searchParams.get("storyId");
|
|
1066
|
+
const theme = url.searchParams.get("theme") || "light";
|
|
1067
|
+
if (!storyId) {
|
|
1068
|
+
res.statusCode = 400;
|
|
1069
|
+
res.setHeader("Content-Type", "application/json");
|
|
1070
|
+
res.end(JSON.stringify({ error: "storyId is required" }));
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1073
|
+
if (theme !== "light" && theme !== "dark") {
|
|
1074
|
+
res.statusCode = 400;
|
|
1075
|
+
res.setHeader("Content-Type", "application/json");
|
|
1076
|
+
res.end(JSON.stringify({ error: 'theme must be "light" or "dark"' }));
|
|
1077
|
+
return;
|
|
1078
|
+
}
|
|
1079
|
+
captureConsoleLogs(storyId, theme).then((result) => {
|
|
1080
|
+
res.setHeader("Content-Type", "application/json");
|
|
1081
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
1082
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
1083
|
+
res.end(JSON.stringify(result));
|
|
1084
|
+
}).catch((error) => {
|
|
1085
|
+
console.error("Console log capture error:", error);
|
|
1086
|
+
res.statusCode = 500;
|
|
1087
|
+
res.setHeader("Content-Type", "application/json");
|
|
1088
|
+
res.end(
|
|
1089
|
+
JSON.stringify({
|
|
1090
|
+
error: "Failed to capture console logs",
|
|
1091
|
+
details: String(error)
|
|
1092
|
+
})
|
|
1093
|
+
);
|
|
1094
|
+
});
|
|
1095
|
+
return;
|
|
1096
|
+
}
|
|
1174
1097
|
if (req.url?.startsWith("/screenshots/")) {
|
|
1175
|
-
const screenshotPath =
|
|
1098
|
+
const screenshotPath = path6.join(
|
|
1176
1099
|
process.cwd(),
|
|
1177
1100
|
".storybook-cache",
|
|
1178
1101
|
req.url.replace("/screenshots/", "screenshots/")
|
|
@@ -1181,11 +1104,11 @@ var serveMetadataAndScreenshots = (req, res, next) => {
|
|
|
1181
1104
|
const storyId = urlParts[0];
|
|
1182
1105
|
const themeFile = urlParts[1];
|
|
1183
1106
|
const theme = themeFile?.replace(".png", "");
|
|
1184
|
-
if (
|
|
1107
|
+
if (fs4.existsSync(screenshotPath)) {
|
|
1185
1108
|
res.setHeader("Content-Type", "image/png");
|
|
1186
1109
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
1187
1110
|
res.setHeader("Cache-Control", "public, max-age=3600");
|
|
1188
|
-
|
|
1111
|
+
fs4.createReadStream(screenshotPath).pipe(res);
|
|
1189
1112
|
return;
|
|
1190
1113
|
}
|
|
1191
1114
|
if (storyId && theme && (theme === "light" || theme === "dark")) {
|
|
@@ -1193,16 +1116,16 @@ var serveMetadataAndScreenshots = (req, res, next) => {
|
|
|
1193
1116
|
`[STORYBOOK_PLUGIN] Generating screenshot on-demand: ${storyId}/${theme}`
|
|
1194
1117
|
);
|
|
1195
1118
|
captureScreenshotBuffer(storyId, theme).then(({ buffer }) => {
|
|
1196
|
-
const storyDir =
|
|
1119
|
+
const storyDir = path6.join(
|
|
1197
1120
|
process.cwd(),
|
|
1198
1121
|
".storybook-cache",
|
|
1199
1122
|
"screenshots",
|
|
1200
1123
|
storyId
|
|
1201
1124
|
);
|
|
1202
|
-
if (!
|
|
1203
|
-
|
|
1125
|
+
if (!fs4.existsSync(storyDir)) {
|
|
1126
|
+
fs4.mkdirSync(storyDir, { recursive: true });
|
|
1204
1127
|
}
|
|
1205
|
-
|
|
1128
|
+
fs4.writeFileSync(screenshotPath, buffer);
|
|
1206
1129
|
res.setHeader("Content-Type", "image/png");
|
|
1207
1130
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
1208
1131
|
res.setHeader("Cache-Control", "public, max-age=3600");
|
|
@@ -1214,6 +1137,8 @@ var serveMetadataAndScreenshots = (req, res, next) => {
|
|
|
1214
1137
|
);
|
|
1215
1138
|
res.statusCode = 500;
|
|
1216
1139
|
res.setHeader("Content-Type", "application/json");
|
|
1140
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
1141
|
+
res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
|
|
1217
1142
|
res.end(
|
|
1218
1143
|
JSON.stringify({
|
|
1219
1144
|
error: "Failed to generate screenshot",
|
|
@@ -1224,11 +1149,41 @@ var serveMetadataAndScreenshots = (req, res, next) => {
|
|
|
1224
1149
|
return;
|
|
1225
1150
|
}
|
|
1226
1151
|
res.statusCode = 404;
|
|
1152
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
1153
|
+
res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
|
|
1227
1154
|
res.end("Screenshot not found");
|
|
1228
1155
|
return;
|
|
1229
1156
|
}
|
|
1230
1157
|
next();
|
|
1231
1158
|
};
|
|
1159
|
+
var ONLOOK_PLUGIN_CLAIMS_GLOBAL = "__ONLOOK_STORYBOOK_PLUGIN_CLAIMS__";
|
|
1160
|
+
function claimRegistry() {
|
|
1161
|
+
const g = globalThis;
|
|
1162
|
+
let registry = g[ONLOOK_PLUGIN_CLAIMS_GLOBAL];
|
|
1163
|
+
if (!registry) {
|
|
1164
|
+
registry = /* @__PURE__ */ new WeakMap();
|
|
1165
|
+
g[ONLOOK_PLUGIN_CLAIMS_GLOBAL] = registry;
|
|
1166
|
+
}
|
|
1167
|
+
return registry;
|
|
1168
|
+
}
|
|
1169
|
+
function withDedupeApply(plugins, instanceId) {
|
|
1170
|
+
for (const plugin of plugins) {
|
|
1171
|
+
const prior = plugin.apply;
|
|
1172
|
+
plugin.apply = (config, env) => {
|
|
1173
|
+
const priorOk = prior === void 0 ? true : typeof prior === "string" ? env.command === prior : prior(config, env);
|
|
1174
|
+
if (!priorOk) return false;
|
|
1175
|
+
if (env === null || typeof env !== "object") return true;
|
|
1176
|
+
const registry = claimRegistry();
|
|
1177
|
+
const claimed = registry.get(env);
|
|
1178
|
+
if (claimed === void 0) {
|
|
1179
|
+
registry.set(env, instanceId);
|
|
1180
|
+
return true;
|
|
1181
|
+
}
|
|
1182
|
+
return claimed === instanceId;
|
|
1183
|
+
};
|
|
1184
|
+
}
|
|
1185
|
+
return plugins;
|
|
1186
|
+
}
|
|
1232
1187
|
function storybookOnlookPlugin(options = {}) {
|
|
1233
1188
|
if (process.env.CHROMATIC || process.env.CI) {
|
|
1234
1189
|
console.log("[STORYBOOK_PLUGIN] Disabled in CI/Chromatic environment");
|
|
@@ -1236,25 +1191,28 @@ function storybookOnlookPlugin(options = {}) {
|
|
|
1236
1191
|
}
|
|
1237
1192
|
const port = options.port ?? 6006;
|
|
1238
1193
|
const allowedOrigins = [...DEFAULT_ALLOWED_ORIGINS, ...options.allowedOrigins ?? []];
|
|
1194
|
+
const hmrOverride = options.hmr;
|
|
1239
1195
|
console.log("[STORYBOOK_PLUGIN] Plugin initialized", {
|
|
1240
1196
|
port,
|
|
1241
1197
|
allowedOrigins,
|
|
1242
1198
|
storybookLocation,
|
|
1243
|
-
repoRoot
|
|
1199
|
+
repoRoot,
|
|
1200
|
+
hmr: hmrOverride ?? null
|
|
1244
1201
|
});
|
|
1245
1202
|
const mainPlugin = {
|
|
1246
1203
|
name: "storybook-onlook-plugin",
|
|
1247
1204
|
config() {
|
|
1248
1205
|
return {
|
|
1249
1206
|
server: {
|
|
1250
|
-
//
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1207
|
+
// HMR override applies only when the caller explicitly opts in
|
|
1208
|
+
// (typically: Storybook fronted over HTTPS by a reverse proxy).
|
|
1209
|
+
// Locally, omitting this lets Vite use defaults
|
|
1210
|
+
// (ws://localhost:<port>) and HMR works as expected.
|
|
1211
|
+
...hmrOverride && {
|
|
1212
|
+
hmr: {
|
|
1213
|
+
...hmrOverride,
|
|
1214
|
+
port
|
|
1215
|
+
}
|
|
1258
1216
|
},
|
|
1259
1217
|
cors: {
|
|
1260
1218
|
origin: allowedOrigins
|
|
@@ -1265,67 +1223,112 @@ function storybookOnlookPlugin(options = {}) {
|
|
|
1265
1223
|
configureServer(server) {
|
|
1266
1224
|
console.log("[STORYBOOK_PLUGIN] Configuring server middleware");
|
|
1267
1225
|
server.middlewares.use(serveMetadataAndScreenshots);
|
|
1226
|
+
const cachedManifestPath = path6.join(
|
|
1227
|
+
process.cwd(),
|
|
1228
|
+
".storybook-cache",
|
|
1229
|
+
"manifest.json"
|
|
1230
|
+
);
|
|
1231
|
+
refreshManifest(cachedManifestPath);
|
|
1232
|
+
let pending2;
|
|
1233
|
+
const scheduleRefresh = (file) => {
|
|
1234
|
+
if (file !== cachedManifestPath) return;
|
|
1235
|
+
if (pending2) clearTimeout(pending2);
|
|
1236
|
+
pending2 = setTimeout(() => {
|
|
1237
|
+
pending2 = void 0;
|
|
1238
|
+
refreshManifest(cachedManifestPath);
|
|
1239
|
+
}, 100);
|
|
1240
|
+
};
|
|
1241
|
+
server.watcher.on("add", scheduleRefresh);
|
|
1242
|
+
server.watcher.on("change", scheduleRefresh);
|
|
1243
|
+
const storyGlobs = options.storyGlobs ?? ["**/*.stories.@(js|jsx|mjs|ts|tsx|mdx)"];
|
|
1244
|
+
const resolvedStoryGlobs = storyGlobs.map(
|
|
1245
|
+
(glob) => path6.isAbsolute(glob) ? glob : path6.join(process.cwd(), glob)
|
|
1246
|
+
);
|
|
1247
|
+
if (resolvedStoryGlobs.length > 0) {
|
|
1248
|
+
server.watcher.add(resolvedStoryGlobs);
|
|
1249
|
+
console.log("[STORYBOOK_PLUGIN] Extended watcher coverage", {
|
|
1250
|
+
storyGlobs: resolvedStoryGlobs
|
|
1251
|
+
});
|
|
1252
|
+
}
|
|
1253
|
+
const tailwindEntryPath = (() => {
|
|
1254
|
+
if (options.tailwindEntryCss === false) return null;
|
|
1255
|
+
if (typeof options.tailwindEntryCss === "string") {
|
|
1256
|
+
return path6.isAbsolute(options.tailwindEntryCss) ? options.tailwindEntryCss : path6.join(process.cwd(), options.tailwindEntryCss);
|
|
1257
|
+
}
|
|
1258
|
+
return detectTailwindEntryCss(process.cwd());
|
|
1259
|
+
})();
|
|
1260
|
+
if (tailwindEntryPath) {
|
|
1261
|
+
console.log("[STORYBOOK_PLUGIN] Tailwind rescan watcher enabled", {
|
|
1262
|
+
entry: tailwindEntryPath,
|
|
1263
|
+
autoDetected: options.tailwindEntryCss === void 0
|
|
1264
|
+
});
|
|
1265
|
+
const seenDirs = /* @__PURE__ */ new Set();
|
|
1266
|
+
let watcherReady = false;
|
|
1267
|
+
server.watcher.once("ready", () => {
|
|
1268
|
+
watcherReady = true;
|
|
1269
|
+
});
|
|
1270
|
+
server.watcher.on("add", (file) => {
|
|
1271
|
+
if (!/\.tsx?$/.test(file)) return;
|
|
1272
|
+
const dir = path6.dirname(file);
|
|
1273
|
+
const isNewDir = !seenDirs.has(dir);
|
|
1274
|
+
seenDirs.add(dir);
|
|
1275
|
+
if (!watcherReady || !isNewDir) return;
|
|
1276
|
+
try {
|
|
1277
|
+
const now = /* @__PURE__ */ new Date();
|
|
1278
|
+
fs4.utimesSync(tailwindEntryPath, now, now);
|
|
1279
|
+
console.log("[STORYBOOK_PLUGIN] Tailwind rescan for new dir", {
|
|
1280
|
+
dir,
|
|
1281
|
+
entry: tailwindEntryPath
|
|
1282
|
+
});
|
|
1283
|
+
} catch (err) {
|
|
1284
|
+
console.warn("[STORYBOOK_PLUGIN] Tailwind rescan: utimes failed", {
|
|
1285
|
+
entry: tailwindEntryPath,
|
|
1286
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1287
|
+
});
|
|
1288
|
+
}
|
|
1289
|
+
});
|
|
1290
|
+
}
|
|
1268
1291
|
server.httpServer?.once("listening", () => {
|
|
1292
|
+
const probePort = server.config.server.port ?? port;
|
|
1269
1293
|
console.log("[STORYBOOK_PLUGIN] Server is listening", {
|
|
1270
|
-
port:
|
|
1294
|
+
port: probePort,
|
|
1271
1295
|
host: server.config.server.host
|
|
1272
1296
|
});
|
|
1297
|
+
void waitForIndexAndBroadcast(probePort);
|
|
1273
1298
|
});
|
|
1274
1299
|
},
|
|
1275
1300
|
configurePreviewServer(server) {
|
|
1276
1301
|
console.log("[STORYBOOK_PLUGIN] Configuring preview server middleware");
|
|
1277
1302
|
server.middlewares.use(serveMetadataAndScreenshots);
|
|
1303
|
+
refreshManifest(path6.join(process.cwd(), ".storybook-cache", "manifest.json"));
|
|
1278
1304
|
},
|
|
1279
|
-
handleHotUpdate
|
|
1280
|
-
if (ctx.file.includes(AUTO_STORIES_FOLDER) && ctx.file.endsWith(".stories.tsx")) {
|
|
1281
|
-
enrichStoryFile(ctx.file);
|
|
1282
|
-
}
|
|
1283
|
-
return handleStoryFileChange(ctx);
|
|
1284
|
-
}
|
|
1305
|
+
handleHotUpdate: handleStoryFileChange
|
|
1285
1306
|
};
|
|
1286
|
-
const
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
"src/**/not-found.tsx",
|
|
1304
|
-
"src/**/template.tsx"
|
|
1305
|
-
];
|
|
1306
|
-
console.log("[STORYBOOK_PLUGIN] Auto-story generation enabled", {
|
|
1307
|
-
imports,
|
|
1308
|
-
ignores,
|
|
1309
|
-
storiesFolder: AUTO_STORIES_FOLDER
|
|
1310
|
-
});
|
|
1307
|
+
const envContainment = envContainmentPlugin(options.envContainment);
|
|
1308
|
+
const moduleLoadCatchAll = options.moduleLoadCatchAll === false ? null : moduleLoadCatchAllPlugin();
|
|
1309
|
+
return withDedupeApply(
|
|
1310
|
+
[
|
|
1311
|
+
...envContainment ? [envContainment] : [],
|
|
1312
|
+
...moduleLoadCatchAll ? [moduleLoadCatchAll] : [],
|
|
1313
|
+
componentLocPlugin(),
|
|
1314
|
+
mainPlugin,
|
|
1315
|
+
fastRefreshTolerantExportsPlugin(),
|
|
1316
|
+
softStoryRerenderPlugin()
|
|
1317
|
+
],
|
|
1318
|
+
/* @__PURE__ */ Symbol("onlook-storybook-plugin-instance")
|
|
1319
|
+
);
|
|
1320
|
+
}
|
|
1321
|
+
var tolerantCsfIndexer = {
|
|
1322
|
+
test: /(stories|story)\.(m?js|ts)x?$/,
|
|
1323
|
+
createIndex: async (fileName, options) => {
|
|
1311
1324
|
try {
|
|
1312
|
-
|
|
1313
|
-
index_default.vite({
|
|
1314
|
-
preset: "react",
|
|
1315
|
-
imports,
|
|
1316
|
-
ignores,
|
|
1317
|
-
storiesFolder: AUTO_STORIES_FOLDER,
|
|
1318
|
-
isGenerateStoriesFileAtBuild: true
|
|
1319
|
-
})
|
|
1320
|
-
);
|
|
1325
|
+
return (await readCsf(fileName, options)).parse().indexInputs;
|
|
1321
1326
|
} catch (err) {
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
);
|
|
1327
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1328
|
+
console.warn("[onbook] Skipping broken story file:", fileName, msg);
|
|
1329
|
+
return [];
|
|
1326
1330
|
}
|
|
1327
1331
|
}
|
|
1328
|
-
|
|
1329
|
-
}
|
|
1332
|
+
};
|
|
1330
1333
|
|
|
1331
|
-
export {
|
|
1334
|
+
export { MODULE_LOAD_CATCH_ALL_PLUGIN_NAME, ONLOOK_CONTAINED_CARD_ATTR, ONLOOK_CONTAINED_EVENT, ONLOOK_CONTAINED_MODULES_GLOBAL, ONLOOK_CONTAINED_PROP, ONLOOK_CONTAINMENT_CONTRACT_GLOBAL, ONLOOK_CONTAINMENT_CONTRACT_VERSION, ONLOOK_RENDERED_EVENT, ONLOOK_RENDER_STATE_ATTR, ONLOOK_RENDER_STORY_ATTR, ONLOOK_UNCONTAINED_ERROR_EVENT, RESOLVED_STORYBOOK_VIRTUAL_STORIES_ID, STORYBOOK_VIRTUAL_STORIES_ID, envContainmentPlugin, moduleLoadCatchAllPlugin, storybookOnlookPlugin, tolerantCsfIndexer };
|