@effinrich/forgekit-storybook-plugin 2.0.0
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 +221 -0
- package/bin/forgekit.js +2 -0
- package/dist/chunk-C2HX5UGS.mjs +704 -0
- package/dist/chunk-C2HX5UGS.mjs.map +1 -0
- package/dist/chunk-D2RQPIRR.mjs +413 -0
- package/dist/chunk-D2RQPIRR.mjs.map +1 -0
- package/dist/chunk-T4UFXGMC.js +704 -0
- package/dist/chunk-T4UFXGMC.js.map +1 -0
- package/dist/chunk-WUKJNZOF.js +413 -0
- package/dist/chunk-WUKJNZOF.js.map +1 -0
- package/dist/cli.d.mts +2 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +523 -0
- package/dist/cli.js.map +1 -0
- package/dist/cli.mjs +523 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/forge-test-EGP3AGFI.mjs +7 -0
- package/dist/forge-test-EGP3AGFI.mjs.map +1 -0
- package/dist/forge-test-FLCVDJFR.js +7 -0
- package/dist/forge-test-FLCVDJFR.js.map +1 -0
- package/dist/index.d.mts +206 -0
- package/dist/index.d.ts +206 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +29 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +81 -0
|
@@ -0,0 +1,704 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
var _chunkWUKJNZOFjs = require('./chunk-WUKJNZOF.js');
|
|
10
|
+
|
|
11
|
+
// src/core/scan-directory.ts
|
|
12
|
+
var _fastglob = require('fast-glob'); var _fastglob2 = _interopRequireDefault(_fastglob);
|
|
13
|
+
var _path = require('path'); var path = _interopRequireWildcard(_path); var path2 = _interopRequireWildcard(_path); var path3 = _interopRequireWildcard(_path); var path4 = _interopRequireWildcard(_path); var path5 = _interopRequireWildcard(_path);
|
|
14
|
+
var _fs = require('fs'); var fs = _interopRequireWildcard(_fs); var fs2 = _interopRequireWildcard(_fs); var fs3 = _interopRequireWildcard(_fs);
|
|
15
|
+
async function scanDirectory(dir) {
|
|
16
|
+
const absoluteDir = path.resolve(dir);
|
|
17
|
+
if (!fs.existsSync(absoluteDir)) {
|
|
18
|
+
throw new Error(`Directory not found: ${absoluteDir}`);
|
|
19
|
+
}
|
|
20
|
+
const extensions = _chunkWUKJNZOFjs.COMPONENT_EXTENSIONS.map((e) => e.replace(".", "")).join(",");
|
|
21
|
+
const ignorePatterns = [
|
|
22
|
+
..._chunkWUKJNZOFjs.IGNORED_DIRS.map((d) => `**/${d}/**`),
|
|
23
|
+
"**/*.spec.*",
|
|
24
|
+
"**/*.test.*",
|
|
25
|
+
"**/*.stories.*",
|
|
26
|
+
"**/*.styles.*",
|
|
27
|
+
"**/*.style.*",
|
|
28
|
+
"**/index.ts",
|
|
29
|
+
"**/index.tsx"
|
|
30
|
+
];
|
|
31
|
+
const files = await _fastglob2.default.call(void 0, `**/*.{${extensions}}`, {
|
|
32
|
+
cwd: absoluteDir,
|
|
33
|
+
ignore: ignorePatterns,
|
|
34
|
+
absolute: true
|
|
35
|
+
});
|
|
36
|
+
const components = [];
|
|
37
|
+
const withStories = [];
|
|
38
|
+
const withoutStories = [];
|
|
39
|
+
const notAnalyzable = [];
|
|
40
|
+
for (const filePath of files) {
|
|
41
|
+
const analysis = _chunkWUKJNZOFjs.analyzeComponent.call(void 0, filePath);
|
|
42
|
+
const storyPath = filePath.replace(/\.tsx?$/, _chunkWUKJNZOFjs.STORY_FILE_SUFFIX);
|
|
43
|
+
const hasStory = fs.existsSync(storyPath);
|
|
44
|
+
components.push({ filePath, analysis, hasStory });
|
|
45
|
+
if (!analysis) {
|
|
46
|
+
notAnalyzable.push(filePath);
|
|
47
|
+
} else if (hasStory) {
|
|
48
|
+
withStories.push(filePath);
|
|
49
|
+
} else {
|
|
50
|
+
withoutStories.push(filePath);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
components,
|
|
55
|
+
withStories,
|
|
56
|
+
withoutStories,
|
|
57
|
+
notAnalyzable,
|
|
58
|
+
total: files.length
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// src/core/generate-interaction-tests.ts
|
|
63
|
+
function generateInteractionTests(analysis) {
|
|
64
|
+
const lines = [];
|
|
65
|
+
const { props, name, hasChildren } = analysis;
|
|
66
|
+
const clickHandlers = props.filter(
|
|
67
|
+
(p) => p.isCallback && (p.name === "onClick" || p.name === "onPress" || p.name === "onSubmit" || p.name === "onClose")
|
|
68
|
+
);
|
|
69
|
+
const toggleProps = props.filter(
|
|
70
|
+
(p) => !p.isCallback && (p.type.toLowerCase() === "boolean" || p.type.toLowerCase() === "bool") && (p.name === "checked" || p.name === "isChecked" || p.name === "selected" || p.name === "open" || p.name === "isOpen")
|
|
71
|
+
);
|
|
72
|
+
if (clickHandlers.length > 0) {
|
|
73
|
+
lines.push(...buildClickTest(name, clickHandlers));
|
|
74
|
+
}
|
|
75
|
+
lines.push(...buildRenderTest(name, hasChildren));
|
|
76
|
+
if (clickHandlers.length > 0 || toggleProps.length > 0) {
|
|
77
|
+
lines.push("");
|
|
78
|
+
lines.push(...buildKeyboardTest(name, clickHandlers));
|
|
79
|
+
}
|
|
80
|
+
lines.push("");
|
|
81
|
+
lines.push(...buildA11yTest(name, hasChildren));
|
|
82
|
+
return lines;
|
|
83
|
+
}
|
|
84
|
+
function buildClickTest(componentName, clickHandlers) {
|
|
85
|
+
const lines = [];
|
|
86
|
+
const handler = clickHandlers[0];
|
|
87
|
+
lines.push(`export const ClickInteraction: Story = {`);
|
|
88
|
+
lines.push(` args: {`);
|
|
89
|
+
lines.push(` ${handler.name}: fn(),`);
|
|
90
|
+
lines.push(` },`);
|
|
91
|
+
lines.push(` play: async ({ canvasElement, args }) => {`);
|
|
92
|
+
lines.push(` const canvas = within(canvasElement);`);
|
|
93
|
+
lines.push("");
|
|
94
|
+
if (handler.name === "onSubmit") {
|
|
95
|
+
lines.push(
|
|
96
|
+
` const submitButton = canvas.getByRole('button', { name: /submit/i });`
|
|
97
|
+
);
|
|
98
|
+
lines.push(` await userEvent.click(submitButton);`);
|
|
99
|
+
lines.push("");
|
|
100
|
+
lines.push(` await expect(args.${handler.name}).toHaveBeenCalledTimes(1);`);
|
|
101
|
+
} else if (handler.name === "onClose") {
|
|
102
|
+
lines.push(
|
|
103
|
+
` const closeButton = canvas.getByRole('button', { name: /close/i });`
|
|
104
|
+
);
|
|
105
|
+
lines.push(` await userEvent.click(closeButton);`);
|
|
106
|
+
lines.push("");
|
|
107
|
+
lines.push(` await expect(args.${handler.name}).toHaveBeenCalledTimes(1);`);
|
|
108
|
+
} else {
|
|
109
|
+
lines.push(
|
|
110
|
+
` const element = canvas.getByRole('button');`
|
|
111
|
+
);
|
|
112
|
+
lines.push(` await userEvent.click(element);`);
|
|
113
|
+
lines.push("");
|
|
114
|
+
lines.push(` await expect(args.${handler.name}).toHaveBeenCalledTimes(1);`);
|
|
115
|
+
}
|
|
116
|
+
lines.push(` },`);
|
|
117
|
+
lines.push(`};`);
|
|
118
|
+
return lines;
|
|
119
|
+
}
|
|
120
|
+
function buildRenderTest(componentName, hasChildren) {
|
|
121
|
+
const lines = [];
|
|
122
|
+
lines.push("");
|
|
123
|
+
lines.push(`export const RendersCorrectly: Story = {`);
|
|
124
|
+
if (hasChildren) {
|
|
125
|
+
lines.push(` args: {`);
|
|
126
|
+
lines.push(` children: 'Test content',`);
|
|
127
|
+
lines.push(` },`);
|
|
128
|
+
}
|
|
129
|
+
lines.push(` play: async ({ canvasElement }) => {`);
|
|
130
|
+
lines.push(` const canvas = within(canvasElement);`);
|
|
131
|
+
lines.push("");
|
|
132
|
+
if (hasChildren) {
|
|
133
|
+
lines.push(` const element = canvas.getByText('Test content');`);
|
|
134
|
+
lines.push(` await expect(element).toBeInTheDocument();`);
|
|
135
|
+
} else {
|
|
136
|
+
lines.push(` // Verify the component renders without crashing`);
|
|
137
|
+
lines.push(` await expect(canvasElement.firstChild).toBeInTheDocument();`);
|
|
138
|
+
}
|
|
139
|
+
lines.push(` },`);
|
|
140
|
+
lines.push(`};`);
|
|
141
|
+
return lines;
|
|
142
|
+
}
|
|
143
|
+
function buildA11yTest(componentName, hasChildren) {
|
|
144
|
+
const lines = [];
|
|
145
|
+
lines.push(`export const AccessibilityAudit: Story = {`);
|
|
146
|
+
lines.push(` tags: ['a11y'],`);
|
|
147
|
+
if (hasChildren) {
|
|
148
|
+
lines.push(` args: {`);
|
|
149
|
+
lines.push(` children: '${componentName} content',`);
|
|
150
|
+
lines.push(` },`);
|
|
151
|
+
}
|
|
152
|
+
lines.push(` play: async ({ canvasElement }) => {`);
|
|
153
|
+
lines.push(` const canvas = within(canvasElement);`);
|
|
154
|
+
lines.push("");
|
|
155
|
+
lines.push(` // Verify component is rendered and visible`);
|
|
156
|
+
if (hasChildren) {
|
|
157
|
+
lines.push(
|
|
158
|
+
` const element = canvas.getByText('${componentName} content');`
|
|
159
|
+
);
|
|
160
|
+
lines.push(` await expect(element).toBeInTheDocument();`);
|
|
161
|
+
} else {
|
|
162
|
+
lines.push(
|
|
163
|
+
` await expect(canvasElement.firstChild).toBeInTheDocument();`
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
lines.push("");
|
|
167
|
+
lines.push(` // Verify no implicit ARIA role violations`);
|
|
168
|
+
lines.push(` // The @storybook/addon-a11y will run axe-core checks on this story`);
|
|
169
|
+
lines.push(` // Configure rules in .storybook/preview.tsx via the a11y addon parameter`);
|
|
170
|
+
lines.push(` },`);
|
|
171
|
+
lines.push(`};`);
|
|
172
|
+
return lines;
|
|
173
|
+
}
|
|
174
|
+
function buildKeyboardTest(_componentName, clickHandlers) {
|
|
175
|
+
const lines = [];
|
|
176
|
+
const handler = clickHandlers[0];
|
|
177
|
+
if (!handler) return lines;
|
|
178
|
+
lines.push(`export const KeyboardNavigation: Story = {`);
|
|
179
|
+
lines.push(` args: {`);
|
|
180
|
+
lines.push(` ${handler.name}: fn(),`);
|
|
181
|
+
lines.push(` },`);
|
|
182
|
+
lines.push(` play: async ({ canvasElement, args }) => {`);
|
|
183
|
+
lines.push(` const canvas = within(canvasElement);`);
|
|
184
|
+
lines.push("");
|
|
185
|
+
lines.push(` const element = canvas.getByRole('button');`);
|
|
186
|
+
lines.push(` await userEvent.tab();`);
|
|
187
|
+
lines.push(` await expect(element).toHaveFocus();`);
|
|
188
|
+
lines.push("");
|
|
189
|
+
lines.push(` await userEvent.keyboard('{Enter}');`);
|
|
190
|
+
lines.push(` await expect(args.${handler.name}).toHaveBeenCalled();`);
|
|
191
|
+
lines.push(` },`);
|
|
192
|
+
lines.push(`};`);
|
|
193
|
+
return lines;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// src/core/generate-story-content.ts
|
|
197
|
+
function generateStoryContent(options) {
|
|
198
|
+
const { analysis, storyTitle, importPath, skipInteractionTests } = options;
|
|
199
|
+
const { name } = analysis;
|
|
200
|
+
const lines = [];
|
|
201
|
+
lines.push(...buildImports(analysis, importPath, skipInteractionTests));
|
|
202
|
+
lines.push("");
|
|
203
|
+
lines.push(...buildMeta(analysis, storyTitle));
|
|
204
|
+
lines.push("");
|
|
205
|
+
lines.push(`type Story = StoryObj<typeof ${name}>;`);
|
|
206
|
+
lines.push("");
|
|
207
|
+
lines.push(...buildDefaultStory(analysis));
|
|
208
|
+
const variantStories = buildVariantStories(analysis);
|
|
209
|
+
if (variantStories.length > 0) {
|
|
210
|
+
lines.push("");
|
|
211
|
+
lines.push(...variantStories);
|
|
212
|
+
}
|
|
213
|
+
if (!skipInteractionTests) {
|
|
214
|
+
const interactionStories = generateInteractionTests(analysis);
|
|
215
|
+
if (interactionStories.length > 0) {
|
|
216
|
+
lines.push("");
|
|
217
|
+
lines.push(...interactionStories);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
lines.push("");
|
|
221
|
+
return lines.join("\n");
|
|
222
|
+
}
|
|
223
|
+
function buildImports(analysis, importPath, skipInteractionTests) {
|
|
224
|
+
const lines = [];
|
|
225
|
+
const metaImports = ["Meta", "StoryObj"];
|
|
226
|
+
lines.push(
|
|
227
|
+
`import type { ${metaImports.join(", ")} } from '${_chunkWUKJNZOFjs.STORYBOOK_META_IMPORT}';`
|
|
228
|
+
);
|
|
229
|
+
if (!skipInteractionTests) {
|
|
230
|
+
const testImports = buildTestImports(analysis);
|
|
231
|
+
if (testImports.length > 0) {
|
|
232
|
+
lines.push(
|
|
233
|
+
`import { ${testImports.join(", ")} } from '${_chunkWUKJNZOFjs.STORYBOOK_TEST_IMPORT}';`
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if (analysis.usesRouter) {
|
|
238
|
+
lines.push(
|
|
239
|
+
`import { withRouter } from 'storybook-addon-react-router-v6';`
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
lines.push(`import React from 'react';`);
|
|
243
|
+
if (analysis.exportType === "default") {
|
|
244
|
+
lines.push(`import ${analysis.name} from '${importPath}';`);
|
|
245
|
+
} else {
|
|
246
|
+
lines.push(`import { ${analysis.name} } from '${importPath}';`);
|
|
247
|
+
}
|
|
248
|
+
return lines;
|
|
249
|
+
}
|
|
250
|
+
function buildTestImports(analysis) {
|
|
251
|
+
const imports = /* @__PURE__ */ new Set();
|
|
252
|
+
const { props } = analysis;
|
|
253
|
+
const hasCallbacks = props.some((p) => p.isCallback);
|
|
254
|
+
const hasInteractiveProps = props.some(
|
|
255
|
+
(p) => ["string", "number"].includes(_nullishCoalesce(inferControlType(p), () => ( "")))
|
|
256
|
+
);
|
|
257
|
+
const hasClickable = props.some(
|
|
258
|
+
(p) => p.name === "onClick" || p.name === "onPress"
|
|
259
|
+
);
|
|
260
|
+
if (hasCallbacks) imports.add("fn");
|
|
261
|
+
if (hasClickable || hasInteractiveProps) {
|
|
262
|
+
imports.add("expect");
|
|
263
|
+
imports.add("within");
|
|
264
|
+
}
|
|
265
|
+
if (hasClickable) imports.add("userEvent");
|
|
266
|
+
if (hasInteractiveProps) imports.add("userEvent");
|
|
267
|
+
return Array.from(imports);
|
|
268
|
+
}
|
|
269
|
+
function buildMeta(analysis, storyTitle) {
|
|
270
|
+
const lines = [];
|
|
271
|
+
const { name, props, usesRouter } = analysis;
|
|
272
|
+
lines.push(`const meta: Meta<typeof ${name}> = {`);
|
|
273
|
+
lines.push(` component: ${name},`);
|
|
274
|
+
lines.push(` title: '${storyTitle}',`);
|
|
275
|
+
lines.push(` tags: ['autodocs'],`);
|
|
276
|
+
if (usesRouter) {
|
|
277
|
+
lines.push(` decorators: [withRouter],`);
|
|
278
|
+
}
|
|
279
|
+
const argTypes = buildArgTypes(props);
|
|
280
|
+
if (argTypes.length > 0) {
|
|
281
|
+
lines.push(` argTypes: {`);
|
|
282
|
+
lines.push(...argTypes);
|
|
283
|
+
lines.push(` },`);
|
|
284
|
+
}
|
|
285
|
+
const defaultArgs = buildDefaultArgs(props);
|
|
286
|
+
if (defaultArgs.length > 0) {
|
|
287
|
+
lines.push(` args: {`);
|
|
288
|
+
lines.push(...defaultArgs);
|
|
289
|
+
lines.push(` },`);
|
|
290
|
+
}
|
|
291
|
+
lines.push(`};`);
|
|
292
|
+
lines.push("");
|
|
293
|
+
lines.push(`export default meta;`);
|
|
294
|
+
return lines;
|
|
295
|
+
}
|
|
296
|
+
function buildArgTypes(props) {
|
|
297
|
+
const lines = [];
|
|
298
|
+
for (const prop of props) {
|
|
299
|
+
if (prop.name === "children") continue;
|
|
300
|
+
if (prop.isCallback) {
|
|
301
|
+
lines.push(` ${prop.name}: { action: '${prop.name}' },`);
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
if (prop.unionValues && prop.unionValues.length > 0) {
|
|
305
|
+
const options = prop.unionValues.map((v) => `'${v}'`).join(", ");
|
|
306
|
+
lines.push(` ${prop.name}: {`);
|
|
307
|
+
lines.push(` options: [${options}],`);
|
|
308
|
+
lines.push(` control: { type: 'select' },`);
|
|
309
|
+
lines.push(` },`);
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
const controlType = inferControlType(prop);
|
|
313
|
+
if (controlType) {
|
|
314
|
+
lines.push(` ${prop.name}: { control: { type: '${controlType}' } },`);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return lines;
|
|
318
|
+
}
|
|
319
|
+
function buildDefaultArgs(props) {
|
|
320
|
+
const lines = [];
|
|
321
|
+
for (const prop of props) {
|
|
322
|
+
if (!prop.required) continue;
|
|
323
|
+
if (prop.isCallback) {
|
|
324
|
+
lines.push(` ${prop.name}: fn(),`);
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
const defaultValue = inferDefaultValue(prop);
|
|
328
|
+
if (defaultValue !== void 0) {
|
|
329
|
+
lines.push(` ${prop.name}: ${defaultValue},`);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return lines;
|
|
333
|
+
}
|
|
334
|
+
function buildDefaultStory(analysis) {
|
|
335
|
+
const lines = [];
|
|
336
|
+
const { hasChildren } = analysis;
|
|
337
|
+
lines.push(`export const Default: Story = {`);
|
|
338
|
+
if (hasChildren) {
|
|
339
|
+
lines.push(` args: {`);
|
|
340
|
+
lines.push(` children: '${analysis.name} content',`);
|
|
341
|
+
lines.push(` },`);
|
|
342
|
+
}
|
|
343
|
+
lines.push(`};`);
|
|
344
|
+
return lines;
|
|
345
|
+
}
|
|
346
|
+
function buildVariantStories(analysis) {
|
|
347
|
+
const lines = [];
|
|
348
|
+
const { props, name } = analysis;
|
|
349
|
+
const sizeProp = props.find((p) => p.name === "size");
|
|
350
|
+
if (_optionalChain([sizeProp, 'optionalAccess', _ => _.unionValues]) && sizeProp.unionValues.length > 0) {
|
|
351
|
+
lines.push(`export const Sizes: Story = {`);
|
|
352
|
+
lines.push(` render: (args) => (`);
|
|
353
|
+
lines.push(` <>`);
|
|
354
|
+
for (const size of sizeProp.unionValues) {
|
|
355
|
+
lines.push(
|
|
356
|
+
` <${name} {...args} size="${size}">${analysis.hasChildren ? `Size ${size}` : ""}</${name}>`
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
lines.push(` </>`);
|
|
360
|
+
lines.push(` ),`);
|
|
361
|
+
lines.push(`};`);
|
|
362
|
+
lines.push("");
|
|
363
|
+
}
|
|
364
|
+
const variantProp = props.find((p) => p.name === "variant");
|
|
365
|
+
if (_optionalChain([variantProp, 'optionalAccess', _2 => _2.unionValues]) && variantProp.unionValues.length > 0) {
|
|
366
|
+
lines.push(`export const Variants: Story = {`);
|
|
367
|
+
lines.push(` render: (args) => (`);
|
|
368
|
+
lines.push(` <>`);
|
|
369
|
+
for (const variant of variantProp.unionValues) {
|
|
370
|
+
lines.push(
|
|
371
|
+
` <${name} {...args} variant="${variant}">${analysis.hasChildren ? `Variant ${variant}` : ""}</${name}>`
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
lines.push(` </>`);
|
|
375
|
+
lines.push(` ),`);
|
|
376
|
+
lines.push(`};`);
|
|
377
|
+
lines.push("");
|
|
378
|
+
}
|
|
379
|
+
const colorPaletteProp = props.find((p) => p.name === "colorPalette");
|
|
380
|
+
if (_optionalChain([colorPaletteProp, 'optionalAccess', _3 => _3.unionValues]) && colorPaletteProp.unionValues.length > 0) {
|
|
381
|
+
lines.push(`export const ColorPalettes: Story = {`);
|
|
382
|
+
lines.push(` render: (args) => (`);
|
|
383
|
+
lines.push(` <>`);
|
|
384
|
+
for (const palette of colorPaletteProp.unionValues) {
|
|
385
|
+
lines.push(
|
|
386
|
+
` <${name} {...args} colorPalette="${palette}">${analysis.hasChildren ? palette : ""}</${name}>`
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
lines.push(` </>`);
|
|
390
|
+
lines.push(` ),`);
|
|
391
|
+
lines.push(`};`);
|
|
392
|
+
lines.push("");
|
|
393
|
+
}
|
|
394
|
+
const disabledProp = props.find(
|
|
395
|
+
(p) => p.name === "disabled" || p.name === "isDisabled"
|
|
396
|
+
);
|
|
397
|
+
if (disabledProp) {
|
|
398
|
+
lines.push(`export const Disabled: Story = {`);
|
|
399
|
+
lines.push(` args: {`);
|
|
400
|
+
lines.push(` ${disabledProp.name}: true,`);
|
|
401
|
+
lines.push(` },`);
|
|
402
|
+
lines.push(`};`);
|
|
403
|
+
}
|
|
404
|
+
return lines;
|
|
405
|
+
}
|
|
406
|
+
function inferControlType(prop) {
|
|
407
|
+
const type = prop.type.toLowerCase();
|
|
408
|
+
if (type === "boolean" || type === "bool") return "boolean";
|
|
409
|
+
if (type === "string") return "text";
|
|
410
|
+
if (type === "number") return "number";
|
|
411
|
+
if (type.includes("react.reactnode") || type.includes("reactnode"))
|
|
412
|
+
return null;
|
|
413
|
+
if (prop.isCallback) return null;
|
|
414
|
+
return null;
|
|
415
|
+
}
|
|
416
|
+
function inferDefaultValue(prop) {
|
|
417
|
+
if (prop.defaultValue) return prop.defaultValue;
|
|
418
|
+
const type = prop.type.toLowerCase();
|
|
419
|
+
if (type === "string") return `'Example ${prop.name}'`;
|
|
420
|
+
if (type === "number") return "0";
|
|
421
|
+
if (type === "boolean" || type === "bool") return "false";
|
|
422
|
+
if (prop.unionValues && prop.unionValues.length > 0) {
|
|
423
|
+
return `'${prop.unionValues[0]}'`;
|
|
424
|
+
}
|
|
425
|
+
if (prop.isCallback) return "fn()";
|
|
426
|
+
return void 0;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// src/core/score-coverage.ts
|
|
430
|
+
function scoreCoverage(covered, total) {
|
|
431
|
+
if (total === 0) {
|
|
432
|
+
return { covered: 0, total: 0, percentage: 0, grade: "F" };
|
|
433
|
+
}
|
|
434
|
+
const percentage = Math.round(covered / total * 100);
|
|
435
|
+
let grade;
|
|
436
|
+
if (percentage >= 90) grade = "A";
|
|
437
|
+
else if (percentage >= 75) grade = "B";
|
|
438
|
+
else if (percentage >= 50) grade = "C";
|
|
439
|
+
else if (percentage >= 25) grade = "D";
|
|
440
|
+
else grade = "F";
|
|
441
|
+
return { covered, total, percentage, grade };
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// src/core/infer-title.ts
|
|
445
|
+
|
|
446
|
+
function inferStoryTitle(filePath, baseDir) {
|
|
447
|
+
const resolvedBase = baseDir ? path2.resolve(baseDir) : path2.dirname(filePath);
|
|
448
|
+
const resolvedFile = path2.resolve(filePath);
|
|
449
|
+
let relative2 = path2.relative(resolvedBase, resolvedFile);
|
|
450
|
+
const fileName = path2.basename(relative2, path2.extname(relative2));
|
|
451
|
+
const dirPart = path2.dirname(relative2);
|
|
452
|
+
if (dirPart === ".") {
|
|
453
|
+
return `Components / ${toPascalCase(fileName)}`;
|
|
454
|
+
}
|
|
455
|
+
let segments = dirPart.split(path2.sep);
|
|
456
|
+
const stripPrefixes = ["src", "lib", "source"];
|
|
457
|
+
while (segments.length > 0 && stripPrefixes.includes(segments[0].toLowerCase())) {
|
|
458
|
+
segments.shift();
|
|
459
|
+
}
|
|
460
|
+
const parts = segments.map((seg) => toPascalCase(seg));
|
|
461
|
+
const componentName = toPascalCase(fileName);
|
|
462
|
+
parts.push(componentName);
|
|
463
|
+
const deduped = [];
|
|
464
|
+
for (const part of parts) {
|
|
465
|
+
if (part !== deduped[deduped.length - 1]) {
|
|
466
|
+
deduped.push(part);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
if (deduped.length === 0) {
|
|
470
|
+
return `Components / ${componentName}`;
|
|
471
|
+
}
|
|
472
|
+
return deduped.join(" / ");
|
|
473
|
+
}
|
|
474
|
+
function toPascalCase(str) {
|
|
475
|
+
return str.split(/[-_]/).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// src/api/forge-story.ts
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
async function forgeStory(options) {
|
|
482
|
+
const { componentPath, storyTitle, skipInteractionTests = false, overwrite = false, dryRun = false } = options;
|
|
483
|
+
const resolvedPath = resolveComponentPath(componentPath);
|
|
484
|
+
if (!resolvedPath) {
|
|
485
|
+
throw new Error(`Component file not found: ${componentPath}`);
|
|
486
|
+
}
|
|
487
|
+
const storyPath = resolvedPath.replace(/\.tsx?$/, _chunkWUKJNZOFjs.STORY_FILE_SUFFIX);
|
|
488
|
+
if (fs2.existsSync(storyPath) && !overwrite) {
|
|
489
|
+
throw new Error(`Story already exists: ${storyPath}. Use --overwrite to replace.`);
|
|
490
|
+
}
|
|
491
|
+
const analysis = _chunkWUKJNZOFjs.analyzeComponent.call(void 0, resolvedPath);
|
|
492
|
+
if (!analysis) {
|
|
493
|
+
throw new Error(`Could not analyze component at ${resolvedPath}. Ensure it exports a React component.`);
|
|
494
|
+
}
|
|
495
|
+
const title = _nullishCoalesce(storyTitle, () => ( inferStoryTitle(resolvedPath)));
|
|
496
|
+
const importPath = `./${analysis.fileName}`;
|
|
497
|
+
const content = generateStoryContent({
|
|
498
|
+
analysis,
|
|
499
|
+
storyTitle: title,
|
|
500
|
+
importPath,
|
|
501
|
+
skipInteractionTests
|
|
502
|
+
});
|
|
503
|
+
const storiesGenerated = collectStoriesGenerated(analysis, skipInteractionTests);
|
|
504
|
+
let written = false;
|
|
505
|
+
if (!dryRun) {
|
|
506
|
+
const dir = path3.dirname(storyPath);
|
|
507
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
508
|
+
fs2.writeFileSync(storyPath, content, "utf-8");
|
|
509
|
+
written = true;
|
|
510
|
+
}
|
|
511
|
+
return { storyPath, content, analysis, storiesGenerated, written };
|
|
512
|
+
}
|
|
513
|
+
function resolveComponentPath(componentPath) {
|
|
514
|
+
const resolved = path3.resolve(componentPath);
|
|
515
|
+
if (fs2.existsSync(resolved)) return resolved;
|
|
516
|
+
for (const ext of _chunkWUKJNZOFjs.COMPONENT_EXTENSIONS) {
|
|
517
|
+
const withExt = resolved + ext;
|
|
518
|
+
if (fs2.existsSync(withExt)) return withExt;
|
|
519
|
+
}
|
|
520
|
+
return null;
|
|
521
|
+
}
|
|
522
|
+
function collectStoriesGenerated(analysis, skipInteractionTests) {
|
|
523
|
+
const stories = ["Default"];
|
|
524
|
+
if (analysis.props.some((p) => p.name === "size" && _optionalChain([p, 'access', _4 => _4.unionValues, 'optionalAccess', _5 => _5.length]))) {
|
|
525
|
+
stories.push("Sizes");
|
|
526
|
+
}
|
|
527
|
+
if (analysis.props.some((p) => p.name === "variant" && _optionalChain([p, 'access', _6 => _6.unionValues, 'optionalAccess', _7 => _7.length]))) {
|
|
528
|
+
stories.push("Variants");
|
|
529
|
+
}
|
|
530
|
+
if (analysis.props.some((p) => p.name === "colorPalette" && _optionalChain([p, 'access', _8 => _8.unionValues, 'optionalAccess', _9 => _9.length]))) {
|
|
531
|
+
stories.push("ColorPalettes");
|
|
532
|
+
}
|
|
533
|
+
if (analysis.props.some((p) => p.name === "disabled" || p.name === "isDisabled")) {
|
|
534
|
+
stories.push("Disabled");
|
|
535
|
+
}
|
|
536
|
+
if (!skipInteractionTests) {
|
|
537
|
+
stories.push("RendersCorrectly", "AccessibilityAudit");
|
|
538
|
+
if (analysis.props.some(
|
|
539
|
+
(p) => p.isCallback && ["onClick", "onPress", "onSubmit", "onClose"].includes(p.name)
|
|
540
|
+
)) {
|
|
541
|
+
stories.push("ClickInteraction", "KeyboardNavigation");
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
return stories;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// src/core/watch.ts
|
|
548
|
+
var _chokidar = require('chokidar');
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
function watchDirectory(options, callback) {
|
|
552
|
+
const {
|
|
553
|
+
dir,
|
|
554
|
+
ignore = [],
|
|
555
|
+
debounceMs = _chunkWUKJNZOFjs.DEFAULT_DEBOUNCE_MS,
|
|
556
|
+
skipInteractionTests = false
|
|
557
|
+
} = options;
|
|
558
|
+
const absoluteDir = path4.resolve(dir);
|
|
559
|
+
if (!fs3.existsSync(absoluteDir)) {
|
|
560
|
+
throw new Error(`Watch directory not found: ${absoluteDir}`);
|
|
561
|
+
}
|
|
562
|
+
const extensions = _chunkWUKJNZOFjs.COMPONENT_EXTENSIONS.map((e) => e.replace(".", ""));
|
|
563
|
+
const globPattern = `**/*.{${extensions.join(",")}}`;
|
|
564
|
+
const ignored = [
|
|
565
|
+
..._chunkWUKJNZOFjs.IGNORED_DIRS.map((d) => `**/${d}/**`),
|
|
566
|
+
"**/*.spec.*",
|
|
567
|
+
"**/*.test.*",
|
|
568
|
+
"**/*.stories.*",
|
|
569
|
+
"**/*.styles.*",
|
|
570
|
+
"**/*.style.*",
|
|
571
|
+
"**/index.{ts,tsx}",
|
|
572
|
+
...ignore
|
|
573
|
+
];
|
|
574
|
+
const pending = /* @__PURE__ */ new Map();
|
|
575
|
+
const watcher = _chokidar.watch.call(void 0, globPattern, {
|
|
576
|
+
cwd: absoluteDir,
|
|
577
|
+
ignored,
|
|
578
|
+
ignoreInitial: true,
|
|
579
|
+
awaitWriteFinish: {
|
|
580
|
+
stabilityThreshold: 100,
|
|
581
|
+
pollInterval: 50
|
|
582
|
+
}
|
|
583
|
+
});
|
|
584
|
+
const processChange = async (relativePath) => {
|
|
585
|
+
const filePath = path4.join(absoluteDir, relativePath);
|
|
586
|
+
const storyPath = filePath.replace(/\.tsx?$/, _chunkWUKJNZOFjs.STORY_FILE_SUFFIX);
|
|
587
|
+
const isUpdate = fs3.existsSync(storyPath);
|
|
588
|
+
try {
|
|
589
|
+
await forgeStory({
|
|
590
|
+
componentPath: filePath,
|
|
591
|
+
skipInteractionTests,
|
|
592
|
+
overwrite: true,
|
|
593
|
+
quiet: true
|
|
594
|
+
});
|
|
595
|
+
_optionalChain([callback, 'optionalCall', _10 => _10({
|
|
596
|
+
type: isUpdate ? "update" : "generate",
|
|
597
|
+
file: filePath,
|
|
598
|
+
storyPath
|
|
599
|
+
})]);
|
|
600
|
+
} catch (err) {
|
|
601
|
+
_optionalChain([callback, 'optionalCall', _11 => _11({
|
|
602
|
+
type: "error",
|
|
603
|
+
file: filePath,
|
|
604
|
+
error: err
|
|
605
|
+
})]);
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
const debouncedProcess = (relativePath) => {
|
|
609
|
+
if (pending.has(relativePath)) {
|
|
610
|
+
clearTimeout(pending.get(relativePath));
|
|
611
|
+
}
|
|
612
|
+
pending.set(
|
|
613
|
+
relativePath,
|
|
614
|
+
setTimeout(() => {
|
|
615
|
+
pending.delete(relativePath);
|
|
616
|
+
processChange(relativePath);
|
|
617
|
+
}, debounceMs)
|
|
618
|
+
);
|
|
619
|
+
};
|
|
620
|
+
watcher.on("change", debouncedProcess);
|
|
621
|
+
watcher.on("add", debouncedProcess);
|
|
622
|
+
watcher.on("ready", () => _optionalChain([callback, 'optionalCall', _12 => _12({ type: "ready" })]));
|
|
623
|
+
return {
|
|
624
|
+
async close() {
|
|
625
|
+
for (const timeout of pending.values()) {
|
|
626
|
+
clearTimeout(timeout);
|
|
627
|
+
}
|
|
628
|
+
pending.clear();
|
|
629
|
+
await watcher.close();
|
|
630
|
+
}
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// src/api/forge-stories.ts
|
|
635
|
+
|
|
636
|
+
async function forgeStories(options) {
|
|
637
|
+
const {
|
|
638
|
+
dir,
|
|
639
|
+
skipInteractionTests = false,
|
|
640
|
+
overwrite = false,
|
|
641
|
+
dryRun = false,
|
|
642
|
+
includeComponentTests = false
|
|
643
|
+
} = options;
|
|
644
|
+
const { forgeTest } = includeComponentTests ? await Promise.resolve().then(() => _interopRequireWildcard(require("./forge-test-FLCVDJFR.js"))) : { forgeTest: null };
|
|
645
|
+
const absoluteDir = path5.resolve(dir);
|
|
646
|
+
const scan = await scanDirectory(absoluteDir);
|
|
647
|
+
let generated = 0;
|
|
648
|
+
let failed = 0;
|
|
649
|
+
const errors = [];
|
|
650
|
+
const filesToProcess = overwrite ? [...scan.withoutStories, ...scan.withStories] : scan.withoutStories;
|
|
651
|
+
for (const filePath of filesToProcess) {
|
|
652
|
+
try {
|
|
653
|
+
await forgeStory({
|
|
654
|
+
componentPath: filePath,
|
|
655
|
+
skipInteractionTests,
|
|
656
|
+
overwrite: true,
|
|
657
|
+
dryRun,
|
|
658
|
+
quiet: true
|
|
659
|
+
});
|
|
660
|
+
generated++;
|
|
661
|
+
if (forgeTest) {
|
|
662
|
+
try {
|
|
663
|
+
await forgeTest({
|
|
664
|
+
componentPath: filePath,
|
|
665
|
+
overwrite: true,
|
|
666
|
+
dryRun,
|
|
667
|
+
quiet: true
|
|
668
|
+
});
|
|
669
|
+
} catch (e2) {
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
} catch (err) {
|
|
673
|
+
failed++;
|
|
674
|
+
errors.push({
|
|
675
|
+
file: filePath,
|
|
676
|
+
error: err.message
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
const totalAnalyzable = scan.total - scan.notAnalyzable.length;
|
|
681
|
+
const totalCovered = overwrite ? generated : scan.withStories.length + generated;
|
|
682
|
+
const coverage = scoreCoverage(totalCovered, totalAnalyzable);
|
|
683
|
+
return {
|
|
684
|
+
generated,
|
|
685
|
+
failed,
|
|
686
|
+
alreadyCovered: scan.withStories.length,
|
|
687
|
+
notAnalyzable: scan.notAnalyzable.length,
|
|
688
|
+
total: totalAnalyzable,
|
|
689
|
+
coverage,
|
|
690
|
+
errors
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
|
|
695
|
+
|
|
696
|
+
|
|
697
|
+
|
|
698
|
+
|
|
699
|
+
|
|
700
|
+
|
|
701
|
+
|
|
702
|
+
|
|
703
|
+
exports.scanDirectory = scanDirectory; exports.generateInteractionTests = generateInteractionTests; exports.generateStoryContent = generateStoryContent; exports.scoreCoverage = scoreCoverage; exports.inferStoryTitle = inferStoryTitle; exports.forgeStory = forgeStory; exports.watchDirectory = watchDirectory; exports.forgeStories = forgeStories;
|
|
704
|
+
//# sourceMappingURL=chunk-T4UFXGMC.js.map
|