@storybook/cli 10.4.0-alpha.14 → 10.4.0-alpha.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_node-chunks/{block-dependencies-versions-AM5QANNI.js → block-dependencies-versions-S3TJGYC2.js} +11 -11
- package/dist/_node-chunks/{block-experimental-addon-test-SKHKW34G.js → block-experimental-addon-test-WWZCXLB2.js} +9 -9
- package/dist/_node-chunks/{block-major-version-UAH3KKTU.js → block-major-version-JNM4ZAKB.js} +9 -9
- package/dist/_node-chunks/{block-node-version-HKDPGBID.js → block-node-version-HDPTJUPH.js} +9 -9
- package/dist/_node-chunks/{block-webpack5-frameworks-2TXPVSTJ.js → block-webpack5-frameworks-TETWAJLZ.js} +11 -11
- package/dist/_node-chunks/chunk-7BQI2DK6.js +20 -0
- package/dist/_node-chunks/chunk-7H4XHFS5.js +527 -0
- package/dist/_node-chunks/chunk-JYLYIY6L.js +810 -0
- package/dist/_node-chunks/{chunk-JTNWFOHX.js → chunk-NDN75UAD.js} +7 -7
- package/dist/_node-chunks/chunk-QBNIQBHH.js +23 -0
- package/dist/_node-chunks/{chunk-6JZE6CV7.js → chunk-QQ7E4GEX.js} +15 -15
- package/dist/_node-chunks/chunk-TSPB5MCI.js +11 -0
- package/dist/_node-chunks/{chunk-YIHJSEST.js → chunk-TZ336D6R.js} +6 -6
- package/dist/_node-chunks/{chunk-3OJ2R5RR.js → chunk-UKVJ7RQN.js} +7 -7
- package/dist/_node-chunks/{globby-WUDQ574N.js → globby-5IGUGWE7.js} +8 -8
- package/dist/_node-chunks/monorepo-4B4ULRGC.js +110 -0
- package/dist/_node-chunks/optimized-tests-MJI5NXQA.js +106 -0
- package/dist/_node-chunks/{p-limit-RP5MZM3R.js → p-limit-VO6RWWBJ.js} +7 -7
- package/dist/_node-chunks/pattern-copy-play-JNOATHUQ.js +20 -0
- package/dist/_node-chunks/relaxed-limits-MKAIPGR2.js +106 -0
- package/dist/_node-chunks/{run-Q5F4R4Z7.js → run-EJWCFREQ.js} +303 -1040
- package/dist/_node-chunks/{setup-VLUMEC3Q.js → setup-PUNMMEGI.js} +10 -10
- package/dist/bin/index.js +7 -7
- package/package.json +4 -4
- package/dist/_node-chunks/chunk-TLXEGV2N.js +0 -11
|
@@ -0,0 +1,527 @@
|
|
|
1
|
+
import CJS_COMPAT_NODE_URL_sgh3w93ap7c from 'node:url';
|
|
2
|
+
import CJS_COMPAT_NODE_PATH_sgh3w93ap7c from 'node:path';
|
|
3
|
+
import CJS_COMPAT_NODE_MODULE_sgh3w93ap7c from "node:module";
|
|
4
|
+
|
|
5
|
+
var __filename = CJS_COMPAT_NODE_URL_sgh3w93ap7c.fileURLToPath(import.meta.url);
|
|
6
|
+
var __dirname = CJS_COMPAT_NODE_PATH_sgh3w93ap7c.dirname(__filename);
|
|
7
|
+
var require = CJS_COMPAT_NODE_MODULE_sgh3w93ap7c.createRequire(import.meta.url);
|
|
8
|
+
|
|
9
|
+
// ------------------------------------------------------------
|
|
10
|
+
// end of CJS compatibility banner, injected by Storybook's esbuild configuration
|
|
11
|
+
// ------------------------------------------------------------
|
|
12
|
+
|
|
13
|
+
// src/ai/utils/ext.ts
|
|
14
|
+
function ext(language, jsx) {
|
|
15
|
+
return language === "ts" ? jsx ? "tsx" : "ts" : jsx ? "jsx" : "js";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// src/ai/utils/markdown.ts
|
|
19
|
+
import { dedent } from "ts-dedent";
|
|
20
|
+
function listRules(rules) {
|
|
21
|
+
return dedent`
|
|
22
|
+
${rules.filter(Boolean).map((s, i) => `${i + 1}. ${s}`).join(`
|
|
23
|
+
`)}
|
|
24
|
+
`;
|
|
25
|
+
}
|
|
26
|
+
function listSteps(steps, options) {
|
|
27
|
+
let level = options?.level ?? 2, prefix = "#".repeat(level);
|
|
28
|
+
return dedent`
|
|
29
|
+
${steps.filter(Boolean).map((step, i) => `${prefix} Step ${i + 1} \u2014 ${step.title}
|
|
30
|
+
|
|
31
|
+
${step.body}`).join(`
|
|
32
|
+
|
|
33
|
+
`)}
|
|
34
|
+
`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// src/ai/setup-prompts/partials/steps.ts
|
|
38
|
+
import { dedent as dedent3 } from "ts-dedent";
|
|
39
|
+
|
|
40
|
+
// src/ai/setup-prompts/partials/examples.ts
|
|
41
|
+
import { dedent as dedent2 } from "ts-dedent";
|
|
42
|
+
function getPreviewExample(projectInfo) {
|
|
43
|
+
let { configDir, language, framework, rendererPackage } = projectInfo, tsx = ext(language, !0), typeImport = framework || rendererPackage || "@storybook/react-vite";
|
|
44
|
+
return language === "js" ? dedent2`
|
|
45
|
+
\`\`\`${tsx}
|
|
46
|
+
// ${configDir}/preview.${tsx}
|
|
47
|
+
import '../src/index.css';
|
|
48
|
+
import MockDate from 'mockdate';
|
|
49
|
+
import { initialize, mswLoader } from 'msw-storybook-addon';
|
|
50
|
+
import { SessionProvider } from '../src/contexts/SessionContext';
|
|
51
|
+
import { mswHandlers } from './msw-handlers';
|
|
52
|
+
|
|
53
|
+
initialize({ onUnhandledRequest: 'bypass' });
|
|
54
|
+
|
|
55
|
+
const preview = {
|
|
56
|
+
decorators: [
|
|
57
|
+
(Story) => (
|
|
58
|
+
<SessionProvider>
|
|
59
|
+
<Story />
|
|
60
|
+
</SessionProvider>
|
|
61
|
+
),
|
|
62
|
+
],
|
|
63
|
+
loaders: [mswLoader],
|
|
64
|
+
parameters: { msw: { handlers: mswHandlers } },
|
|
65
|
+
async beforeEach() {
|
|
66
|
+
localStorage.setItem('theme', 'dark');
|
|
67
|
+
MockDate.set('2024-04-01T12:00:00Z');
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export default preview;
|
|
72
|
+
\`\`\`
|
|
73
|
+
` : dedent2`
|
|
74
|
+
\`\`\`${tsx}
|
|
75
|
+
// ${configDir}/preview.${tsx}
|
|
76
|
+
import type { Preview } from '${typeImport}';
|
|
77
|
+
import '../src/index.css';
|
|
78
|
+
import MockDate from 'mockdate';
|
|
79
|
+
import { initialize, mswLoader } from 'msw-storybook-addon';
|
|
80
|
+
import { SessionProvider } from '../src/contexts/SessionContext';
|
|
81
|
+
import { mswHandlers } from './msw-handlers';
|
|
82
|
+
|
|
83
|
+
initialize({ onUnhandledRequest: 'bypass' });
|
|
84
|
+
|
|
85
|
+
const preview: Preview = {
|
|
86
|
+
decorators: [
|
|
87
|
+
(Story) => (
|
|
88
|
+
<SessionProvider>
|
|
89
|
+
<Story />
|
|
90
|
+
</SessionProvider>
|
|
91
|
+
),
|
|
92
|
+
],
|
|
93
|
+
loaders: [mswLoader],
|
|
94
|
+
parameters: { msw: { handlers: mswHandlers } },
|
|
95
|
+
async beforeEach() {
|
|
96
|
+
localStorage.setItem('theme', 'dark');
|
|
97
|
+
MockDate.set('2024-04-01T12:00:00Z');
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export default preview;
|
|
102
|
+
\`\`\`
|
|
103
|
+
`;
|
|
104
|
+
}
|
|
105
|
+
function getPortalDecoratorExample(projectInfo) {
|
|
106
|
+
let { language } = projectInfo, tsx = ext(language, !0);
|
|
107
|
+
return dedent2`
|
|
108
|
+
\`\`\`${tsx}
|
|
109
|
+
// Add this entry to the \`decorators\` array of your preview config:
|
|
110
|
+
(Story) => {
|
|
111
|
+
for (const id of ['modal-root', 'drawer-root', 'toast-root']) {
|
|
112
|
+
if (!document.getElementById(id)) {
|
|
113
|
+
const el = document.createElement('div');
|
|
114
|
+
el.id = id;
|
|
115
|
+
document.body.appendChild(el);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return <Story />;
|
|
119
|
+
}
|
|
120
|
+
\`\`\`
|
|
121
|
+
`;
|
|
122
|
+
}
|
|
123
|
+
function getMainConfigExample(projectInfo) {
|
|
124
|
+
let { configDir, framework, rendererPackage, language } = projectInfo, ts = ext(language, !1), typeImport = framework || rendererPackage || "@storybook/react";
|
|
125
|
+
return language === "js" ? dedent2`
|
|
126
|
+
\`\`\`js
|
|
127
|
+
// ${configDir}/main.js
|
|
128
|
+
const config = { staticDirs: ['../public'] };
|
|
129
|
+
export default config;
|
|
130
|
+
\`\`\`
|
|
131
|
+
` : dedent2`
|
|
132
|
+
\`\`\`${ts}
|
|
133
|
+
// ${configDir}/main.${ts}
|
|
134
|
+
import type { StorybookConfig } from '${typeImport}';
|
|
135
|
+
|
|
136
|
+
const config: StorybookConfig = { staticDirs: ['../public'] };
|
|
137
|
+
export default config;
|
|
138
|
+
\`\`\`
|
|
139
|
+
`;
|
|
140
|
+
}
|
|
141
|
+
function getStoryExample(projectInfo) {
|
|
142
|
+
let { language, framework, rendererPackage } = projectInfo, tsx = ext(language, !0), typeImport = framework || rendererPackage || "@storybook/react-vite";
|
|
143
|
+
return language === "js" ? dedent2`
|
|
144
|
+
\`\`\`${tsx}
|
|
145
|
+
import { expect } from 'storybook/test';
|
|
146
|
+
import { Button } from './Button';
|
|
147
|
+
|
|
148
|
+
const meta = {
|
|
149
|
+
component: Button,
|
|
150
|
+
tags: ['ai-generated', 'needs-work'], // strip 'needs-work' once vitest passes
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
export default meta;
|
|
154
|
+
|
|
155
|
+
// Smoke check — one is enough per file
|
|
156
|
+
export const Primary = {
|
|
157
|
+
args: { children: 'Order now' },
|
|
158
|
+
play: async ({ canvas }) => {
|
|
159
|
+
await expect(canvas.getByRole('button', { name: /order now/i })).toBeVisible();
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// Variant-only stories: no play needed
|
|
164
|
+
export const Clear = { args: { children: 'Cancel', clear: true } };
|
|
165
|
+
export const Large = { args: { children: 'Checkout', large: true } };
|
|
166
|
+
export const WithIcon = { args: { icon: 'cart', 'aria-label': 'food cart' } };
|
|
167
|
+
\`\`\`
|
|
168
|
+
` : dedent2`
|
|
169
|
+
\`\`\`${tsx}
|
|
170
|
+
import type { Meta, StoryObj } from '${typeImport}';
|
|
171
|
+
import { expect } from 'storybook/test';
|
|
172
|
+
import { Button } from './Button';
|
|
173
|
+
|
|
174
|
+
const meta = {
|
|
175
|
+
component: Button,
|
|
176
|
+
tags: ['ai-generated', 'needs-work'], // strip 'needs-work' once vitest passes
|
|
177
|
+
} satisfies Meta<typeof Button>;
|
|
178
|
+
|
|
179
|
+
export default meta;
|
|
180
|
+
type Story = StoryObj<typeof meta>;
|
|
181
|
+
|
|
182
|
+
// Smoke check — one is enough per file
|
|
183
|
+
export const Primary: Story = {
|
|
184
|
+
args: { children: 'Order now' },
|
|
185
|
+
play: async ({ canvas }) => {
|
|
186
|
+
await expect(canvas.getByRole('button', { name: /order now/i })).toBeVisible();
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
// Variant-only stories: no play needed
|
|
191
|
+
export const Clear: Story = { args: { children: 'Cancel', clear: true } };
|
|
192
|
+
export const Large: Story = { args: { children: 'Checkout', large: true } };
|
|
193
|
+
export const WithIcon: Story = { args: { icon: 'cart', 'aria-label': 'food cart' } };
|
|
194
|
+
\`\`\`
|
|
195
|
+
`;
|
|
196
|
+
}
|
|
197
|
+
function getInteractionPlayExample(projectInfo) {
|
|
198
|
+
let { language } = projectInfo, tsx = ext(language, !0);
|
|
199
|
+
return dedent2`
|
|
200
|
+
\`\`\`${tsx}
|
|
201
|
+
export const FilledForm${language === "ts" ? ": Story" : ""} = {
|
|
202
|
+
play: async ({ canvas, userEvent }) => {
|
|
203
|
+
await userEvent.type(canvas.getByLabelText('email'), 'a@b.com', { delay: 50 });
|
|
204
|
+
await userEvent.click(canvas.getByRole('button', { name: /submit/i }));
|
|
205
|
+
await expect(await canvas.findByText(/welcome/i)).toBeVisible();
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
\`\`\`
|
|
209
|
+
`;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// src/ai/setup-prompts/partials/steps.ts
|
|
213
|
+
function discoveryStepStrict(projectInfo, { tsx }) {
|
|
214
|
+
return {
|
|
215
|
+
title: "Discover the runtime (\u226412 reads)",
|
|
216
|
+
body: dedent3`
|
|
217
|
+
Identify, in this order, using Glob/Grep first then targeted Reads:
|
|
218
|
+
|
|
219
|
+
- \`index.html\` — \`<link rel="stylesheet">\` tags, inline \`<style>\` blocks, fonts, and any \`<div id="...">\` mount or portal roots that aren't created by JS
|
|
220
|
+
- entry file (\`main.${tsx}\` / \`index.${tsx}\`) — providers wrapping \`<App />\`, root CSS imports
|
|
221
|
+
- \`App.${tsx}\` — top-level layout, router usage, providers it consumes
|
|
222
|
+
- providers / context files — what they expose
|
|
223
|
+
- root CSS — global styles, CSS variables, theme tokens (both JS-imported CSS **and** anything linked from \`index.html\`)
|
|
224
|
+
- data hooks — \`fetch(...)\`, \`useQuery\`, \`axios\`, etc. (capture base URL + endpoints actually called during render)
|
|
225
|
+
- browser state actually read at render — \`localStorage\`/\`sessionStorage\`/cookie keys
|
|
226
|
+
- portal targets — \`createPortal(...)\` and the DOM ids it mounts to (e.g. \`#modal-root\`)
|
|
227
|
+
- 1–2 real page or feature components (your story source-of-truth for JSX patterns)
|
|
228
|
+
|
|
229
|
+
Stop reading once you can answer: *"What providers, CSS, browser state, and network calls must the preview supply for a typical page to render?"*
|
|
230
|
+
`
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
function discoveryStepRelaxed(projectInfo, { tsx }) {
|
|
234
|
+
return {
|
|
235
|
+
title: "Discover the runtime (\u226440 reads)",
|
|
236
|
+
body: dedent3`
|
|
237
|
+
Identify, in this order, using Glob/Grep first then targeted Reads:
|
|
238
|
+
|
|
239
|
+
- \`index.html\` — \`<link rel="stylesheet">\` tags, inline \`<style>\` blocks, fonts, and any \`<div id="...">\` mount or portal roots that aren't created by JS
|
|
240
|
+
- entry file (\`main.${tsx}\` / \`index.${tsx}\`) — providers wrapping \`<App />\`, root CSS imports
|
|
241
|
+
- \`App.${tsx}\` — top-level layout, router usage, providers it consumes
|
|
242
|
+
- providers / context files — what they expose
|
|
243
|
+
- root CSS — global styles, CSS variables, theme tokens (both JS-imported CSS **and** anything linked from \`index.html\`)
|
|
244
|
+
- data hooks — \`fetch(...)\`, \`useQuery\`, \`axios\`, etc. (capture base URL + endpoints actually called during render)
|
|
245
|
+
- browser state actually read at render — \`localStorage\`/\`sessionStorage\`/cookie keys
|
|
246
|
+
- portal targets — \`createPortal(...)\` and the DOM ids it mounts to (e.g. \`#modal-root\`)
|
|
247
|
+
- 1–20 real page or feature components (your story source-of-truth for JSX patterns)
|
|
248
|
+
|
|
249
|
+
Stop reading once you can answer: *"What providers, CSS, browser state, and network calls must the preview supply for a typical page to render? What surrounding context do components need to render?"*
|
|
250
|
+
`
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
function verifyStep(projectInfo, { packageManager, tsx }) {
|
|
254
|
+
return {
|
|
255
|
+
title: "Verify in one batch, then iterate only on failures",
|
|
256
|
+
body: dedent3`**Read this rule once before running anything:** the first vitest invocation must run **all** the new stories together. No single-file runs before the batch.
|
|
257
|
+
|
|
258
|
+
\`\`\`bash
|
|
259
|
+
npx vitest --project storybook run
|
|
260
|
+
\`\`\`
|
|
261
|
+
|
|
262
|
+
Then run the project's TypeScript check (use the script from \`package.json\` — typically \`tsc --noEmit\` or \`${packageManager.getRunCommand("typecheck")}\`). Read the raw output once; don't pipe it through repeated \`grep\`/\`head\` invocations to slice it.
|
|
263
|
+
|
|
264
|
+
For each failure:
|
|
265
|
+
|
|
266
|
+
1. Read the error.
|
|
267
|
+
2. If multiple stories share the failure, fix the shared preview setup, not the stories.
|
|
268
|
+
3. Re-run vitest **only for the affected file(s)**: \`npx vitest --project storybook run path/to/Foo.stories.${tsx}\`.
|
|
269
|
+
4. Repeat until the file passes, then move on. Cap retries at ~5 per file — if it still fails, leave \`'needs-work'\` tag to inform the user.
|
|
270
|
+
5. When you keep failing on a story, play function, etc., do not substitute it for easier content that contributes less to codebase understanding.
|
|
271
|
+
|
|
272
|
+
**After a file passes**, edit its meta and remove \`'needs-work'\` so its tags become \`['ai-generated']\`. Files you couldn't fix keep \`['ai-generated', 'needs-work']\` — move on, don't loop forever.`
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
function cleanupStep(projectInfo, ctx) {
|
|
276
|
+
return {
|
|
277
|
+
title: "Clean up",
|
|
278
|
+
body: "Before finishing, remove debug code, broad mocks added during diagnosis, unused deps, and eval artifacts."
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
function monorepoStep(projectInfo, ctx) {
|
|
282
|
+
return {
|
|
283
|
+
title: "Monorepo preparation",
|
|
284
|
+
body: dedent3`Build any local monorepo dependencies identified during discovery, and keep track of existing errors in the codebase unrelated to the package changes you'll make.`
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
function buildSharedPreviewStep(projectInfo, { configDir, tsx }) {
|
|
288
|
+
return {
|
|
289
|
+
title: "Build the shared preview",
|
|
290
|
+
body: dedent3` Set up Storybook **once** so most stories work without per-story setup. **Edit the existing \`${configDir}/preview.${tsx}\`** (created by \`storybook init\`) — add to its existing config object, don't replace it.
|
|
291
|
+
|
|
292
|
+
The complete shape should look like this (merge the new pieces into what's already there):
|
|
293
|
+
|
|
294
|
+
${getPreviewExample(projectInfo)}
|
|
295
|
+
|
|
296
|
+
Rules for the preview:
|
|
297
|
+
|
|
298
|
+
- Use the **real** provider tree and the **real** root CSS import. Don't invent providers.
|
|
299
|
+
- If the app's CSS is loaded via \`<link>\` in \`index.html\` (rather than imported in JS), import the same file from preview so stories render with the same styles.
|
|
300
|
+
- Seed only the specific browser-state keys the app actually reads. Do **not** clear all of \`localStorage\`/\`sessionStorage\`/cookies, and do not reset Storybook's own state.
|
|
301
|
+
- Use \`mockdate\` only when render output depends on the date.
|
|
302
|
+
- Do not mock \`window\`, \`document\`, \`navigator\`, observers, or \`fetch\` directly.
|
|
303
|
+
`
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
function buildPortalStep(projectInfo, { configDir, tsx }) {
|
|
307
|
+
return {
|
|
308
|
+
title: "Portals (in a decorator, not `preview-body.html`)",
|
|
309
|
+
body: dedent3`If you found \`createPortal(..., document.getElementById('foo'))\` in discovery, **add a decorator in \`${configDir}/preview.${tsx}\` that creates the portal root** before the story renders. Do not use \`preview-body.html\`.
|
|
310
|
+
|
|
311
|
+
${getPortalDecoratorExample(projectInfo)}
|
|
312
|
+
|
|
313
|
+
Add this decorator to the \`decorators\` array of your preview config. Skip this step entirely if portals only target \`document.body\`.`
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
function mswStep(projectInfo, { configDir, mswInstall, ts }) {
|
|
317
|
+
return {
|
|
318
|
+
title: "MSW handlers (only what stories will hit)",
|
|
319
|
+
body: `Use \`msw-storybook-addon\`. Install with:
|
|
320
|
+
|
|
321
|
+
\`\`\`bash
|
|
322
|
+
${mswInstall}
|
|
323
|
+
npx msw init ./public --save
|
|
324
|
+
\`\`\`
|
|
325
|
+
|
|
326
|
+
Make sure \`${configDir}/main.${ts}\` serves \`./public\`:
|
|
327
|
+
|
|
328
|
+
${getMainConfigExample(projectInfo)}
|
|
329
|
+
|
|
330
|
+
Put handlers in \`${configDir}/msw-handlers.${ts}\`. Cover only the endpoints your stories will exercise \u2014 no catch-alls.
|
|
331
|
+
|
|
332
|
+
\`\`\`${ts}
|
|
333
|
+
// ${configDir}/msw-handlers.${ts}
|
|
334
|
+
import { http, HttpResponse } from 'msw';
|
|
335
|
+
|
|
336
|
+
export const mswHandlers = {
|
|
337
|
+
products: [
|
|
338
|
+
http.get('https://api.example.com/products', () =>
|
|
339
|
+
HttpResponse.json({ items: [{ id: 'p1', name: 'Example', price: 42 }] })
|
|
340
|
+
),
|
|
341
|
+
],
|
|
342
|
+
};
|
|
343
|
+
\`\`\`
|
|
344
|
+
`
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
function writeStoriesStep(projectInfo, { tsx }) {
|
|
348
|
+
return {
|
|
349
|
+
title: "Write up to 10 story files (in one batch)",
|
|
350
|
+
body: dedent3`
|
|
351
|
+
|
|
352
|
+
This step has **two required deliverables**:
|
|
353
|
+
|
|
354
|
+
a. Up to 10 colocated \`*.stories.${tsx}\` files for meaningful targets in the codebase.
|
|
355
|
+
b. **Exactly one \`CssCheck\` story** added to one of those files (spec below). This step is not complete without it.
|
|
356
|
+
|
|
357
|
+
**Substep a — pick targets and write the files.** Pick ~10 meaningful targets from the real codebase (low-level reusable → page components). Skip subcomponents, hooks, contexts, helpers, and \`App\` itself when real page components exist.
|
|
358
|
+
|
|
359
|
+
Each story file: ~3 exports for typical components, up to ~10 when warranted by real usage. Copy JSX patterns from real pages/routes/tests.
|
|
360
|
+
|
|
361
|
+
**Tag every new story file with \`['ai-generated', 'needs-work']\` from the start.** You will remove \`'needs-work'\` only after vitest confirms the file passes. This way, anything not yet verified — including stories you ran out of time to fix — stays correctly marked.
|
|
362
|
+
|
|
363
|
+
${getStoryExample(projectInfo)}
|
|
364
|
+
|
|
365
|
+
Story rules:
|
|
366
|
+
|
|
367
|
+
- Start every meta with \`tags: ['ai-generated', 'needs-work']\`.
|
|
368
|
+
- Show all imports explicitly.
|
|
369
|
+
- Don't add a custom \`title\`.
|
|
370
|
+
- Don't build large story-specific harnesses — fix preview instead.
|
|
371
|
+
- Don't create new app components.
|
|
372
|
+
|
|
373
|
+
**Substep b — add the single \`CssCheck\` story.** Before you finish this step, pick **one** visually distinctive component from the files you just wrote and add a \`CssCheck\` export to that file. Exactly **one** \`CssCheck\` across the whole project — not one per file. This step is not complete until the story exists.
|
|
374
|
+
|
|
375
|
+
Why it's mandatory: \`toBeVisible\` passes on an unstyled component. A concrete \`getComputedStyle\` value is the only proof that the shared preview actually loaded the app's CSS — without it, you have no idea whether your stories are rendering correctly.
|
|
376
|
+
|
|
377
|
+
How: read a real styling value from the component's source (e.g. a hex color in styled-components, a Tailwind class like \`bg-blue-600\`, a CSS variable from the theme), and assert the resolved \`getComputedStyle\` value:
|
|
378
|
+
|
|
379
|
+
\`\`\`${tsx}
|
|
380
|
+
export const CssCheck: Story = {
|
|
381
|
+
args: { children: 'Submit' },
|
|
382
|
+
play: async ({ canvas }) => {
|
|
383
|
+
const button = canvas.getByRole('button', { name: /submit/i });
|
|
384
|
+
// PrimaryButton uses bg-blue-600 — fails if Tailwind / global CSS did not load.
|
|
385
|
+
await expect(getComputedStyle(button).backgroundColor).toBe('rgb(37, 99, 235)');
|
|
386
|
+
},
|
|
387
|
+
};
|
|
388
|
+
\`\`\`
|
|
389
|
+
`
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
function interactionPlayStep(projectInfo, { tsx }) {
|
|
393
|
+
return {
|
|
394
|
+
title: "Add `play` functions only where they prove something non-trivial",
|
|
395
|
+
body: dedent3`
|
|
396
|
+
**Do not put a \`play\` on every story.** A \`play\` is worth writing only when it asserts something the rendered output alone doesn't already prove. Prefer one good \`play\` per file over five redundant ones.
|
|
397
|
+
|
|
398
|
+
Write a \`play\` when it can verify:
|
|
399
|
+
|
|
400
|
+
- an **interaction** (form fill + submit, click → menu opens, tab change reveals panel)
|
|
401
|
+
- **async data** actually arrived from MSW (waiting for mocked content to replace a spinner)
|
|
402
|
+
- a **portal** rendered into the right root (query via \`canvasElement.ownerDocument\`)
|
|
403
|
+
- a **CSS-driven state** that matters semantically (e.g. theme color, disabled styling, layout that confirms the global stylesheet loaded)
|
|
404
|
+
- **accessibility** that the component is responsible for (correct role/label exposure)
|
|
405
|
+
|
|
406
|
+
**Skip \`play\` entirely** when a story is just a static variant of the same component (different \`args\`, no new behavior). Repeating \`getByRole(...).toBeVisible()\` across \`Clear\`, \`Large\`, \`WithIcon\` etc. is redundant — the render itself already fails the test if the component throws or doesn't mount.
|
|
407
|
+
|
|
408
|
+
**Smoke plays must prove something the render alone doesn't.** A play that does only \`await expect(canvas.getByRole('button')).toBeVisible()\` adds nothing — the render already failed if the button didn't mount. Acceptable smoke plays assert one of:
|
|
409
|
+
|
|
410
|
+
- an **aria attribute reflecting state** (\`aria-expanded\`, \`aria-disabled\`, \`aria-checked\`, \`aria-current\`)
|
|
411
|
+
- a **prop value rendered as text or attribute** (e.g. \`args.label\` appears in the DOM, \`href\` matches \`args.to\`)
|
|
412
|
+
- **async content arriving** (\`findBy*\`, \`waitFor\` — proves the loader/MSW handler actually resolved)
|
|
413
|
+
- a **portal mounting in the right root** (queried via \`canvasElement.ownerDocument.body\`)
|
|
414
|
+
|
|
415
|
+
If none of those apply, skip the \`play\` and rely on the render itself.
|
|
416
|
+
|
|
417
|
+
Concretely, in a \`Button.stories.${tsx}\` with \`Primary\`, \`Clear\`, \`Large\`, \`WithIcon\`:
|
|
418
|
+
|
|
419
|
+
- \`Primary\` — keep one smoke \`play\` (one is enough for the file).
|
|
420
|
+
- \`Clear\`, \`Large\`, \`WithIcon\` — **no \`play\`**. They're variant-only stories.
|
|
421
|
+
|
|
422
|
+
(The single \`CssCheck\` story for the whole project was added in Step 5 — don't add another one here.)
|
|
423
|
+
|
|
424
|
+
Imports & play context — get this right or vitest will fail in subtle ways:
|
|
425
|
+
|
|
426
|
+
- \`expect\` and \`waitFor\` come from \`'storybook/test'\` — import those.
|
|
427
|
+
- \`canvas\`, \`userEvent\`, and \`canvasElement\` come from the **play arguments**: \`async ({ canvas, userEvent, canvasElement }) => { ... }\`. **Do not** \`import { userEvent } from 'storybook/test'\` and **do not** write \`const canvas = within(canvasElement)\` — both are already provided.
|
|
428
|
+
- For **portal queries only**, query via \`canvasElement.ownerDocument.body\`. You may import \`within\` from \`'storybook/test'\` for that case (e.g. \`within(canvasElement.ownerDocument.body).findByTestId(...)\`). Don't use \`within\` for anything else.
|
|
429
|
+
|
|
430
|
+
${getInteractionPlayExample(projectInfo)}`
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// src/ai/setup-prompts/partials/rules.ts
|
|
435
|
+
import { dedent as dedent4 } from "ts-dedent";
|
|
436
|
+
|
|
437
|
+
// ../../core/src/shared/utils/get-monorepo-type.ts
|
|
438
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
439
|
+
import { join } from "node:path";
|
|
440
|
+
import { getProjectRoot } from "storybook/internal/common";
|
|
441
|
+
var monorepoConfigs = {
|
|
442
|
+
Nx: "nx.json",
|
|
443
|
+
Turborepo: "turbo.json",
|
|
444
|
+
Lerna: "lerna.json",
|
|
445
|
+
Rush: "rush.json",
|
|
446
|
+
Lage: "lage.config.json"
|
|
447
|
+
}, getMonorepoType = () => {
|
|
448
|
+
let monorepoType = Object.keys(monorepoConfigs).find((monorepo) => {
|
|
449
|
+
let configFile = join(getProjectRoot(), monorepoConfigs[monorepo]);
|
|
450
|
+
return existsSync(configFile);
|
|
451
|
+
});
|
|
452
|
+
if (monorepoType)
|
|
453
|
+
return monorepoType;
|
|
454
|
+
if (!existsSync(join(getProjectRoot(), "package.json")))
|
|
455
|
+
return;
|
|
456
|
+
if (JSON.parse(
|
|
457
|
+
readFileSync(join(getProjectRoot(), "package.json"), { encoding: "utf8" })
|
|
458
|
+
)?.workspaces)
|
|
459
|
+
return "Workspaces";
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
// src/ai/setup-prompts/partials/rules.ts
|
|
463
|
+
function toolsVsShellRule(ctx) {
|
|
464
|
+
return dedent4`**Discover with Glob/Grep/Read, not shell.** Never use \`ls\`, \`find\`, \`cat\`, \`head\`, \`tail\`, shell \`grep\`, \`sed\`, or \`node -e\` for discovery or for editing files in bulk — these are slower per call and violate caching. Substitute bash commands for the specific tool names listed below, or available tools with the closest semantics:
|
|
465
|
+
- List a directory → \`Glob('src/components/*')\` (alt names: \`search_files\`, \`file_search\`), not \`ls src/components\`.
|
|
466
|
+
- Search a string → \`Grep('pattern', { path: 'src' })\` (alt names: \`grep_search\`, \`search_files\`), not \`grep -rn ...\` or \`find ... | xargs grep\`.
|
|
467
|
+
- Read a file → \`Read('path/to/file')\` (alt names: \`read_file\`), not \`cat\`/\`head\`/\`tail\`.
|
|
468
|
+
- Bulk-edit many files → multiple \`Edit\` calls (alt names: \`apply_patch\`, \`replace_in_file\`, \`replace\`), or one \`Edit\` with \`replace_all\` (alt names: \`replace\` with \`allow_multiple\`), not \`sed -i\`.`;
|
|
469
|
+
}
|
|
470
|
+
function nodeModuleReadsRule(ctx) {
|
|
471
|
+
return dedent4`**Never read or grep inside \`node_modules\`.** The imports shown in this prompt are correct — don't verify them by introspecting installed packages. If something seems off, re-read this prompt, not \`node_modules\`.`;
|
|
472
|
+
}
|
|
473
|
+
function monorepoRule(ctx) {
|
|
474
|
+
let monorepoType = getMonorepoType();
|
|
475
|
+
if (monorepoType)
|
|
476
|
+
return `**${monorepoType} monorepo.** Don't initially look for config or existing Storybook content in other packages. Start exploring from config and tooling local to the package where you are asked to set up Storybook. If it uses local monorepo dependencies, build all dependencies found during discovery before writing stories or running tests.`;
|
|
477
|
+
}
|
|
478
|
+
function packageManagerRule({ packageManagerName }) {
|
|
479
|
+
return packageManagerName ? dedent4`**Use \`${packageManagerName}\` for every install** (detected from this project's lockfile).` : "**Detect the package manager once** from the lockfile (`pnpm-lock.yaml` \u2192 pnpm, `yarn.lock` \u2192 yarn, `bun.lockb` \u2192 bun, otherwise npm) and use it for every install in this trial.";
|
|
480
|
+
}
|
|
481
|
+
function editOverWriteRule({ configDir, tsx }) {
|
|
482
|
+
return dedent4`**Edit > Write.** For any file you've Read, use \`Edit\`. Use \`Write\` only for new files. The project already has a \`${configDir}/preview.${tsx}\` from \`storybook init\` — **Edit** it, do not overwrite.`;
|
|
483
|
+
}
|
|
484
|
+
function batchTestsRule(ctx) {
|
|
485
|
+
return dedent4`**Batch the test loop.** Write **all** stories first, then run vitest **once** across everything. No per-file vitest runs until after that first batch run reveals failures.`;
|
|
486
|
+
}
|
|
487
|
+
function readBudgetRule(ctx) {
|
|
488
|
+
return dedent4`**Read budget: ~12 files for discovery.** Before writing any code you may Read at most ~12 files (\`index.html\`, entry, App, providers, routing, root CSS, 2–3 representative pages/components, 1–2 hooks, 1 test). If you need more, summarize and move on.`;
|
|
489
|
+
}
|
|
490
|
+
function readBudgetRuleRelaxed(ctx) {
|
|
491
|
+
return dedent4`**Read budget: ~40 files for discovery.** Before writing any code you may Read at most ~40 files: \`index.html\`, entry, App, providers, routing, root CSS, 1–2 hooks, 1 test, and spend the rest on components. You may read direct component dependencies essential to their understanding only after having read 20 components (or all components if fewer in the codebase).`;
|
|
492
|
+
}
|
|
493
|
+
function preferSharedFixesRule({
|
|
494
|
+
configDir,
|
|
495
|
+
tsx
|
|
496
|
+
}) {
|
|
497
|
+
return dedent4`**Prefer fixing the shared \`${configDir}/preview.${tsx}\`** over story-local workarounds when multiple stories fail the same way.`;
|
|
498
|
+
}
|
|
499
|
+
function noPolishRule(ctx) {
|
|
500
|
+
return dedent4`**Stop when the success criteria are met** — don't keep polishing.`;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
export {
|
|
504
|
+
ext,
|
|
505
|
+
listRules,
|
|
506
|
+
listSteps,
|
|
507
|
+
discoveryStepStrict,
|
|
508
|
+
discoveryStepRelaxed,
|
|
509
|
+
verifyStep,
|
|
510
|
+
cleanupStep,
|
|
511
|
+
monorepoStep,
|
|
512
|
+
buildSharedPreviewStep,
|
|
513
|
+
buildPortalStep,
|
|
514
|
+
mswStep,
|
|
515
|
+
writeStoriesStep,
|
|
516
|
+
interactionPlayStep,
|
|
517
|
+
toolsVsShellRule,
|
|
518
|
+
nodeModuleReadsRule,
|
|
519
|
+
monorepoRule,
|
|
520
|
+
packageManagerRule,
|
|
521
|
+
editOverWriteRule,
|
|
522
|
+
batchTestsRule,
|
|
523
|
+
readBudgetRule,
|
|
524
|
+
readBudgetRuleRelaxed,
|
|
525
|
+
preferSharedFixesRule,
|
|
526
|
+
noPolishRule
|
|
527
|
+
};
|