@variance-authority/storybook-collector 0.19.0 → 0.24.1
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/CHANGELOG.md +28 -0
- package/README.md +14 -0
- package/dist/execution.d.ts +6 -0
- package/dist/execution.js +1 -0
- package/dist/execution.js.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/package.json +9 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# @variance-authority/storybook-collector
|
|
2
2
|
|
|
3
|
+
## 0.24.1
|
|
4
|
+
|
|
5
|
+
Lockstep release — nothing in this package changed. Every `@variance-authority/*` package shares one version.
|
|
6
|
+
|
|
7
|
+
## 0.24.0
|
|
8
|
+
|
|
9
|
+
Lockstep release — nothing in this package changed. Every `@variance-authority/*` package shares one version.
|
|
10
|
+
|
|
11
|
+
## 0.23.0
|
|
12
|
+
|
|
13
|
+
Lockstep release — nothing in this package changed. Every `@variance-authority/*` package shares one version.
|
|
14
|
+
|
|
15
|
+
## 0.22.0
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- eaf5d1d: Under `VARIANCE_AUTHORITY_STORY=1`, a Storybook run with `tests` on writes each story's test story: the code the preview ran for it, in order, with what it printed. A story read twice, in the shared world and alone, is one story.
|
|
20
|
+
|
|
21
|
+
## 0.21.0
|
|
22
|
+
|
|
23
|
+
Lockstep release — nothing in this package changed. Every `@variance-authority/*` package shares one version.
|
|
24
|
+
|
|
25
|
+
## 0.20.0
|
|
26
|
+
|
|
27
|
+
### Patch Changes
|
|
28
|
+
|
|
29
|
+
- f0284c4: Include the configured loader chain in Rspack and Webpack source-map refusals, and explain how upstream transforms preserve their input maps.
|
|
30
|
+
|
|
3
31
|
## 0.19.0
|
|
4
32
|
|
|
5
33
|
Lockstep release — nothing in this package changed. Every `@variance-authority/*` package shares one version.
|
package/README.md
CHANGED
|
@@ -399,6 +399,15 @@ config `@storybook/addon-vitest` loads your presets for, inside Vitest, gets
|
|
|
399
399
|
nothing from the addon either: that run is a Vitest run, and the Vitest seam
|
|
400
400
|
records it where you configured one.
|
|
401
401
|
|
|
402
|
+
Each loader that rewrites a file must return a source map composed with its
|
|
403
|
+
input map. Enabling `devtool` gives the probes a map only where that chain is
|
|
404
|
+
intact. A refusal saying there is no map back to the file means that chain is
|
|
405
|
+
missing; one saying the map carries text different from the file on disk means
|
|
406
|
+
the map describes another transform's input. The refusal lists the loader chain
|
|
407
|
+
when the bundler provides it. Check map composition in those upstream loaders
|
|
408
|
+
while keeping the probes after the compilers. Removing a file through `include`
|
|
409
|
+
also removes it from recorded coverage.
|
|
410
|
+
|
|
402
411
|
Pass the probes' options as the addon's: `{ name:
|
|
403
412
|
'@variance-authority/storybook-collector', options: { mode: 'entries' } }`.
|
|
404
413
|
`include` decides which modules are your source, every file outside
|
|
@@ -429,6 +438,11 @@ from it, and every path the run records is relative to the repository that
|
|
|
429
438
|
contains it. Without a collector in the page, the run prints that on stderr and
|
|
430
439
|
records nothing.
|
|
431
440
|
|
|
441
|
+
Set `VARIANCE_AUTHORITY_STORY=1` for the run and `tests` also writes each
|
|
442
|
+
story's [test story](https://variance-authority.dev/docs/test-stories): the
|
|
443
|
+
code the preview ran for it, in order, with what it printed. `variance story`
|
|
444
|
+
reads it with `--file` set to the `.stories` file and `--name` to the story's title and name, `Cart/Empty`.
|
|
445
|
+
|
|
432
446
|
`mode` is the probe recipe, and it has to be the one the preview was built
|
|
433
447
|
under: a snapshot names the recipe its ordinals were cut by, and a merge
|
|
434
448
|
discards a layer cut by another one, so a preview probing under `entries` and a
|
package/dist/execution.d.ts
CHANGED
|
@@ -23,6 +23,12 @@ import { type StoryTiming } from '@variance-authority/storybook';
|
|
|
23
23
|
export type StoryExecutionOptions = Omit<SubjectRecording, 'heads'>;
|
|
24
24
|
/** One run's accumulation: a window per story, written once at the end. */
|
|
25
25
|
export interface StoryRecorder {
|
|
26
|
+
/**
|
|
27
|
+
* The script that makes the preview tape each story's story, where the run
|
|
28
|
+
* asked for stories under `VARIANCE_AUTHORITY_STORY`: put on the page ahead
|
|
29
|
+
* of the collector's bundle.
|
|
30
|
+
*/
|
|
31
|
+
readonly storyPage?: () => Promise<string>;
|
|
26
32
|
/**
|
|
27
33
|
* Close one story's window.
|
|
28
34
|
*
|
package/dist/execution.js
CHANGED
|
@@ -30,6 +30,7 @@ export async function createStoryRecorder(index, options) {
|
|
|
30
30
|
const root = recorder.root;
|
|
31
31
|
const storyFiles = await storyFilesFrom(index, ran, root);
|
|
32
32
|
return {
|
|
33
|
+
...(recorder.storyPage === undefined ? {} : { storyPage: recorder.storyPage }),
|
|
33
34
|
note: async (page, subjectId, complete, timing) => {
|
|
34
35
|
// A story read at several widths is one declaration and several cases:
|
|
35
36
|
// each width executed the story on its own, and a hook that reads the
|
package/dist/execution.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"execution.js","sourceRoot":"","sources":["../src/execution.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What each story executed, recorded for the next run's selection.\n *\n * The instrument is the one `@variance-authority/sense` already uses; only the\n * transport differs, which is the rule\n * [spec 0028](https://github.com/Variance-Authority/variance-authority/blob/main/docs/specs/0028-the-instrument.md) states. The preview is\n * built with the package's addon, which adds the probes whichever builder runs\n * it ([`preset.ts`](./preset.ts)), the page counts block crossings, and this\n * closes one window per story and joins them at the end of the run.\n *\n * A story is its own owner. Storybook is an execution surface this tool drives\n * one subject at a time, so — unlike a test runner, where the file is the\n * smallest thing a runner can be asked to execute — the crossings of one story\n * belong to that story and to nothing else.\n */\n\nimport { readFile } from 'node:fs/promises';\nimport { relative, resolve, sep } from 'node:path';\nimport {\n createSubjectRecorder,\n preconditionOf,\n type EvaluatingPage,\n type SubjectNote,\n type SubjectRecording,\n} from '@variance-authority/sense/journal';\nimport { parseStoryIndex, type StoryTiming } from '@variance-authority/storybook';\nimport { storyIdOf } from './parameters.js';\n\n/**\n * Where a story run's execution evidence is joined, read and kept: the shared\n * recording, with `root` also the directory a story's `importPath` is read\n * from, because it is relative to the directory Storybook ran in.\n */\nexport type StoryExecutionOptions = Omit<SubjectRecording, 'heads'>;\n\n/** One run's accumulation: a window per story, written once at the end. */\nexport interface StoryRecorder {\n /**\n * Close one story's window.\n *\n * `complete` is false for a story this run refused. A refused story still\n * executed code, so its crossings are kept — what it may never do is justify\n * excluding itself from a later run.\n *\n * `timing` is where the page spent the story: the preview's load is the\n * story's `prepare`, 0 for a story the preview switched to, its file's\n * import its `collect`, its render its `cases`.\n */\n readonly note: (\n page: EvaluatingPage,\n subjectId: string,\n complete: boolean,\n timing?: StoryTiming,\n ) => Promise<void>;\n /**\n * Merge the run into the index, or say on stderr why it could not.\n *\n * `costs` is the run's own time for each story, by subject id: first\n * collection to decision, the figure `variance ask costs` answers and the\n * next run shards on. It is recorded as the story's duration, on its row and\n * on its case, rather than timed a second time here. A story the run did not\n * time is recorded without one.\n */\n readonly close: (costs?: ReadonlyMap<string, number>) => Promise<void>;\n}\n\nexport async function createStoryRecorder(\n index: string,\n options: StoryExecutionOptions,\n): Promise<StoryRecorder> {\n const ran = resolve(options.root ?? process.cwd());\n const recorder = createSubjectRecorder(\n {\n subject: 'story',\n uninstrumented:\n '`tests` is on and the Storybook preview has no execution ' +\n \"collector in it — add `addons: ['@variance-authority/storybook-collector']` \" +\n 'to its `.storybook/main.js` and build it with `storybook build --test`, or the ' +\n 'next `--since` will run everything',\n },\n options,\n );\n const root = recorder.root;\n const storyFiles = await storyFilesFrom(index, ran, root);\n\n return {\n note: async (page, subjectId, complete, timing) => {\n // A story read at several widths is one declaration and several cases:\n // each width executed the story on its own, and a hook that reads the\n // width took a different branch at each.\n const widened = subjectId.replace(/^story:/, '');\n const storyId = storyIdOf(subjectId);\n const story = storyFiles.get(storyId);\n const precondition =\n story === undefined ? undefined : await preconditionOf(root, story.file);\n // The index names a story by where it is declared, which is the same\n // coordinate a person reads in the sidebar and the same one a diff\n // touches. A story the driving index does not know — a subject named on\n // the command line, a preview rebuilt since — has no file to be a case\n // in, and contributes to the file-level record only.\n //\n // FIXME: the row is owned by `story:<id>` and the case by `story.file`,\n // and the case index retires the cases of a file a run finished, read off\n // the rows. No row names a story file, so no case is ever retired by a\n // run: a story removed or renamed keeps its case until its file is\n // deleted.\n await recorder.note(page, subjectId, complete, {\n ...(precondition === undefined ? {} : { preconditions: [precondition] }),\n ...(story === undefined\n ? {}\n : {\n case: {\n file: story.file,\n name: `${story.title}/${story.name}${widened.slice(storyId.length)}`,\n id: widened,\n },\n }),\n ...phasesOf(timing),\n });\n },\n close: recorder.close,\n };\n}\n\n/** A story's timing under the names the record gives a test file's phases. */\nfunction phasesOf(timing: StoryTiming | undefined): Pick<SubjectNote, 'phases'> {\n if (timing === undefined) return {};\n const phases = {\n ...(timing.ready === undefined ? {} : { prepare: timing.ready }),\n ...(timing.collect === undefined ? {} : { collect: timing.collect }),\n ...(timing.cases === undefined ? {} : { cases: timing.cases }),\n };\n return Object.keys(phases).length === 0 ? {} : { phases };\n}\n\n/**\n * Story id to where it is declared, from the index the run is driving.\n *\n * The one route from a subject back to a file, and the reason a story's own\n * `.stories` file can select it: nothing *enters* a story file the way execution\n * enters a module, so without this a commit that edits one selects nothing. The\n * title and the name come along because the execution index names a case by its\n * declaration rather than by the id Storybook slugged from it.\n *\n * Storybook writes `importPath` from the directory it ran in, `./` and all. A\n * diff names files from the checkout, so the path is resolved from `ran` and\n * named from `root`; in a workspace the two are a package apart.\n */\nasync function storyFilesFrom(\n index: string,\n ran: string,\n root: string,\n): Promise<Map<string, StoryCoordinate>> {\n const parsed = parseStoryIndex(JSON.parse(await readFile(index, 'utf8')), index);\n return new Map(\n parsed.stories.map((story) => [\n story.id,\n {\n file: relative(root, resolve(ran, story.importPath)).split(sep).join('/'),\n title: story.title,\n name: story.name,\n },\n ]),\n );\n}\n\n/** Where a story is declared and what it is called there. */\ninterface StoryCoordinate {\n readonly file: string;\n readonly title: string;\n readonly name: string;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"execution.js","sourceRoot":"","sources":["../src/execution.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What each story executed, recorded for the next run's selection.\n *\n * The instrument is the one `@variance-authority/sense` already uses; only the\n * transport differs, which is the rule\n * [spec 0028](https://github.com/Variance-Authority/variance-authority/blob/main/docs/specs/0028-the-instrument.md) states. The preview is\n * built with the package's addon, which adds the probes whichever builder runs\n * it ([`preset.ts`](./preset.ts)), the page counts block crossings, and this\n * closes one window per story and joins them at the end of the run.\n *\n * A story is its own owner. Storybook is an execution surface this tool drives\n * one subject at a time, so — unlike a test runner, where the file is the\n * smallest thing a runner can be asked to execute — the crossings of one story\n * belong to that story and to nothing else.\n */\n\nimport { readFile } from 'node:fs/promises';\nimport { relative, resolve, sep } from 'node:path';\nimport {\n createSubjectRecorder,\n preconditionOf,\n type EvaluatingPage,\n type SubjectNote,\n type SubjectRecording,\n} from '@variance-authority/sense/journal';\nimport { parseStoryIndex, type StoryTiming } from '@variance-authority/storybook';\nimport { storyIdOf } from './parameters.js';\n\n/**\n * Where a story run's execution evidence is joined, read and kept: the shared\n * recording, with `root` also the directory a story's `importPath` is read\n * from, because it is relative to the directory Storybook ran in.\n */\nexport type StoryExecutionOptions = Omit<SubjectRecording, 'heads'>;\n\n/** One run's accumulation: a window per story, written once at the end. */\nexport interface StoryRecorder {\n /**\n * The script that makes the preview tape each story's story, where the run\n * asked for stories under `VARIANCE_AUTHORITY_STORY`: put on the page ahead\n * of the collector's bundle.\n */\n readonly storyPage?: () => Promise<string>;\n /**\n * Close one story's window.\n *\n * `complete` is false for a story this run refused. A refused story still\n * executed code, so its crossings are kept — what it may never do is justify\n * excluding itself from a later run.\n *\n * `timing` is where the page spent the story: the preview's load is the\n * story's `prepare`, 0 for a story the preview switched to, its file's\n * import its `collect`, its render its `cases`.\n */\n readonly note: (\n page: EvaluatingPage,\n subjectId: string,\n complete: boolean,\n timing?: StoryTiming,\n ) => Promise<void>;\n /**\n * Merge the run into the index, or say on stderr why it could not.\n *\n * `costs` is the run's own time for each story, by subject id: first\n * collection to decision, the figure `variance ask costs` answers and the\n * next run shards on. It is recorded as the story's duration, on its row and\n * on its case, rather than timed a second time here. A story the run did not\n * time is recorded without one.\n */\n readonly close: (costs?: ReadonlyMap<string, number>) => Promise<void>;\n}\n\nexport async function createStoryRecorder(\n index: string,\n options: StoryExecutionOptions,\n): Promise<StoryRecorder> {\n const ran = resolve(options.root ?? process.cwd());\n const recorder = createSubjectRecorder(\n {\n subject: 'story',\n uninstrumented:\n '`tests` is on and the Storybook preview has no execution ' +\n \"collector in it — add `addons: ['@variance-authority/storybook-collector']` \" +\n 'to its `.storybook/main.js` and build it with `storybook build --test`, or the ' +\n 'next `--since` will run everything',\n },\n options,\n );\n const root = recorder.root;\n const storyFiles = await storyFilesFrom(index, ran, root);\n\n return {\n ...(recorder.storyPage === undefined ? {} : { storyPage: recorder.storyPage }),\n note: async (page, subjectId, complete, timing) => {\n // A story read at several widths is one declaration and several cases:\n // each width executed the story on its own, and a hook that reads the\n // width took a different branch at each.\n const widened = subjectId.replace(/^story:/, '');\n const storyId = storyIdOf(subjectId);\n const story = storyFiles.get(storyId);\n const precondition =\n story === undefined ? undefined : await preconditionOf(root, story.file);\n // The index names a story by where it is declared, which is the same\n // coordinate a person reads in the sidebar and the same one a diff\n // touches. A story the driving index does not know — a subject named on\n // the command line, a preview rebuilt since — has no file to be a case\n // in, and contributes to the file-level record only.\n //\n // FIXME: the row is owned by `story:<id>` and the case by `story.file`,\n // and the case index retires the cases of a file a run finished, read off\n // the rows. No row names a story file, so no case is ever retired by a\n // run: a story removed or renamed keeps its case until its file is\n // deleted.\n await recorder.note(page, subjectId, complete, {\n ...(precondition === undefined ? {} : { preconditions: [precondition] }),\n ...(story === undefined\n ? {}\n : {\n case: {\n file: story.file,\n name: `${story.title}/${story.name}${widened.slice(storyId.length)}`,\n id: widened,\n },\n }),\n ...phasesOf(timing),\n });\n },\n close: recorder.close,\n };\n}\n\n/** A story's timing under the names the record gives a test file's phases. */\nfunction phasesOf(timing: StoryTiming | undefined): Pick<SubjectNote, 'phases'> {\n if (timing === undefined) return {};\n const phases = {\n ...(timing.ready === undefined ? {} : { prepare: timing.ready }),\n ...(timing.collect === undefined ? {} : { collect: timing.collect }),\n ...(timing.cases === undefined ? {} : { cases: timing.cases }),\n };\n return Object.keys(phases).length === 0 ? {} : { phases };\n}\n\n/**\n * Story id to where it is declared, from the index the run is driving.\n *\n * The one route from a subject back to a file, and the reason a story's own\n * `.stories` file can select it: nothing *enters* a story file the way execution\n * enters a module, so without this a commit that edits one selects nothing. The\n * title and the name come along because the execution index names a case by its\n * declaration rather than by the id Storybook slugged from it.\n *\n * Storybook writes `importPath` from the directory it ran in, `./` and all. A\n * diff names files from the checkout, so the path is resolved from `ran` and\n * named from `root`; in a workspace the two are a package apart.\n */\nasync function storyFilesFrom(\n index: string,\n ran: string,\n root: string,\n): Promise<Map<string, StoryCoordinate>> {\n const parsed = parseStoryIndex(JSON.parse(await readFile(index, 'utf8')), index);\n return new Map(\n parsed.stories.map((story) => [\n story.id,\n {\n file: relative(root, resolve(ran, story.importPath)).split(sep).join('/'),\n title: story.title,\n name: story.name,\n },\n ]),\n );\n}\n\n/** Where a story is declared and what it is called there. */\ninterface StoryCoordinate {\n readonly file: string;\n readonly title: string;\n readonly name: string;\n}\n"]}
|
package/dist/index.js
CHANGED
|
@@ -126,7 +126,9 @@ export function storybookCollector(options = {}) {
|
|
|
126
126
|
const recorder = options.tests === undefined || options.tests === false
|
|
127
127
|
? undefined
|
|
128
128
|
: await createStoryRecorder(index, options.tests === true ? {} : options.tests);
|
|
129
|
-
|
|
129
|
+
// Ahead of the agent, so the collector it evaluates hands its engine to the tape.
|
|
130
|
+
const agent = await pageAgentBundle();
|
|
131
|
+
const bundle = recorder?.storyPage === undefined ? agent : `${await recorder.storyPage()}\n${agent}`;
|
|
130
132
|
let served;
|
|
131
133
|
if (options.baseUrl === undefined)
|
|
132
134
|
served = await serveStatic(staticDir);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { dirname, join } from 'node:path';\nimport { existsSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport { fileURLToPath } from 'node:url';\nimport { createCallSiteResolver, overlaySourceIndex } from '@variance-authority/core/attribute';\nimport { recipeToDrive, type Recipe } from '@variance-authority/core/format';\nimport { createDeclarationReader, fetchModules } from '@variance-authority/playwright';\nimport { createStoryRecorder } from './execution.js';\nimport { harnessPage, readParameters } from '@variance-authority/storybook';\nimport { realize, storyIdOf } from './parameters.js';\nimport type { StorybookCollectorOptions } from './options.js';\nimport { readStory, type Reading } from './read.js';\nimport { openWorld, type World, type WorldRecipe } from './world.js';\nexport type {\n Collected,\n Collector,\n CollectorConfig,\n CollectorContext,\n Plan,\n PlannedSubject,\n Visit,\n VisitedSubject,\n} from './contract.js';\nimport type { Collected, Collector, CollectorContext, Plan, PlannedSubject, Visit } from './contract.js';\nimport { operatorError } from './operator.js';\n\n/** Said in two places, because a mismatched kind can be caught at either. */\nconst WRONG_KIND =\n 'this collector expects `subjects.kind: \"storybook\"`, which is what supplies the plan';\nimport { serveStatic, type StaticServer } from './serve.js';\nimport { scanSource } from './source.js';\n\n/**\n * The Storybook collector, shipped.\n *\n * ## Why this package exists, stated as the thing it retracts\n *\n * This repository's position was that mounting is the adopter's, once, per\n * project — and it measured the cost honestly at 341 lines rather than the\n * \"about thirty\" the source had guessed. That position is right about React\n * applications and wrong about Storybook, for a reason the measurement itself\n * shows: of those 341 lines, the parts that were genuinely *this project's* were\n * a map of three ready selectors, a directory to scan for components, and a\n * static server. Everything else was Storybook's own contract being re-typed.\n *\n * A story index is a documented artifact. A preview owns its own mount and\n * exposes a channel for switching stories. So for this one subject source the\n * mounting problem is already solved by somebody else, and asking every adopter\n * to re-solve it was charging them for our boundary rather than for their\n * project.\n *\n * **What stays theirs.** Readiness per story, because Storybook's `storyRendered`\n * fires when the story function returns — which for a component that defers work\n * is *before the component exists*. That is knowledge about a component, and a\n * project-wide default would be a guess about all of them.\n *\n * ## What it does not make generic\n *\n * Nothing outside Storybook. A Playwright suite, a route table or a bespoke\n * mount is still a collector somebody writes — see\n * [`@variance-authority/playwright-test`](https://variance-authority.dev/reference/packages/playwright-test) for the surface\n * where the adopter's own test body plays that part instead.\n */\n\nconst STORY_ROOTS = ['#storybook-root', '#root'];\n\n/**\n * Two candidate paths, because this module is imported from two places.\n *\n * A consumer imports `dist/index.js` and the bundle sits beside it. This\n * repository's own tests import `src/index.ts`, where it does not — and a\n * collector that only worked when nobody in the project tested it would be a\n * collector nobody in the project tested.\n */\nasync function pageAgentBundle(): Promise<string> {\n const candidates = [\n new URL('./page-agent.bundle.js', import.meta.url),\n new URL('../dist/page-agent.bundle.js', import.meta.url),\n ];\n\n for (const path of candidates) {\n try {\n return await readFile(path, 'utf8');\n } catch {\n // Next candidate. The throw below names both, so a genuinely missing\n // bundle still fails with somewhere to look rather than a bare ENOENT.\n }\n }\n\n throw new Error(\n `the storybook page agent bundle is missing (looked in ` +\n `${candidates.map((path) => fileURLToPath(path)).join(' and ')}); ` +\n 'it is built when the package is built: a checkout is missing its build, and an ' +\n 'installed copy is missing a published file',\n );\n}\n\n/**\n * Build the module a `subjects.collector` path should default-export.\n *\n * A factory rather than a plugin: the config still names a module, the module\n * still exports a function, and the extension mechanism is unchanged. What the\n * adopter writes shrinks to the two facts only they hold.\n *\n * ```js\n * import { storybookCollector } from '@variance-authority/storybook-collector';\n *\n * export default storybookCollector({\n * ready: { 'surface--deferred': '[data-testid=\"ready\"]' },\n * source: { dirs: ['src'] },\n * });\n * ```\n */\nexport function storybookCollector(\n options: StorybookCollectorOptions = {},\n): (context: CollectorContext) => Promise<Collector> {\n return async function createCollector(context: CollectorContext): Promise<Collector> {\n const { config, plan } = context;\n const roots = options.roots ?? STORY_ROOTS;\n\n // Resolved here as well as in the page, because the driver applies three of\n // its tricks and has to know which. An unknown id or an instant with no zone\n // is the operator's to fix, and is said before a browser opens.\n let stabilize: Recipe;\n try {\n stabilize = recipeToDrive(options);\n } catch (error) {\n throw operatorError((error as Error).message, { cause: error });\n }\n\n // Checked before anything reads it. `index` is what `subjects.kind:\n // \"storybook\"` supplies, so a config naming another kind arrives here with\n // nothing to serve — and the guard further down in `plan()` is too late,\n // because this line runs first.\n const index = config.subjects.index;\n if (index === undefined) throw operatorError(WRONG_KIND);\n\n // The build the config named, not a sibling this file assumed. Serving the\n // directory that holds the planned index is the only arrangement in which the\n // stories driven and the stories planned are guaranteed to be the same ones.\n const staticDir = dirname(index);\n\n if (options.baseUrl === undefined && !existsSync(join(staticDir, 'index.json'))) {\n throw operatorError(\n `${staticDir} has no index.json; build the Storybook first, or point ` +\n '`baseUrl` at one that is already served',\n );\n }\n\n const source = options.source === undefined ? undefined : scanSource(process.cwd(), options.source);\n\n // What each story executed, if anybody asked. The index it feeds is the one\n // the Vitest seam writes: same probe recipe, same block ordinals, one index\n // across every origin.\n const recorder =\n options.tests === undefined || options.tests === false\n ? undefined\n : await createStoryRecorder(index, options.tests === true ? {} : options.tests);\n const bundle = await pageAgentBundle();\n\n let served: StaticServer | undefined;\n if (options.baseUrl === undefined) served = await serveStatic(staticDir);\n const baseUrl = options.baseUrl ?? served!.baseUrl;\n\n // The recipe both worlds are built from, written down once.\n //\n // `collectAlone` opens its world from this same value, which is what makes\n // \"identical except for isolation\" a property of the code rather than a\n // discipline two call sites keep by hand: the viewport, the fonts, the\n // bundle, the preview address and the network options cannot differ between\n // the two worlds, because there is only one of each. See `world.ts`.\n //\n // Pointed at the preview, so `collectStory` finds itself already there and\n // does not navigate: one navigation for a whole run is the saving ADR-0009\n // rests on, and it is easiest to keep by never taking a second one. The\n // isolated world opens on the same address for a second reason — the preview\n // shell is not a subject, so landing on it is not something having run first.\n const recipe: WorldRecipe = {\n harness: {\n url: `${baseUrl}/iframe.html`,\n bundle,\n viewport: config.viewport,\n ...(config.fonts !== undefined ? { fonts: config.fonts } : {}),\n ...(config.timezoneId !== undefined ? { timezoneId: config.timezoneId } : {}),\n ...(config.locale !== undefined ? { locale: config.locale } : {}),\n ...(options.headless !== undefined ? { headless: options.headless } : {}),\n stabilize,\n // Every world of the run in one context, which is where the browser\n // keeps its HTTP cache: a worker's preview boots from what the first\n // world fetched. The pages share cookies and storage with it, as stories\n // in one preview already share a document. Across workers what a story\n // finds left behind depends on scheduling: which worker read which story\n // before it decides the cookies, storage and service workers, msw's\n // among them, that it meets.\n warm: true,\n },\n // Installed in `prepare`, which runs before the first navigation: an\n // observation that starts afterwards has already missed the assets the\n // preview loaded on its way up, and a URL nobody saw is a hole in the key.\n ...(options.network === false\n ? {}\n : {\n network: {\n ...(config.blank !== undefined ? { blank: config.blank } : {}),\n ...(options.hashAssets !== undefined ? { hashAssets: options.hashAssets } : {}),\n // Watching without turning the cache off. Safe because a Storybook\n // world never resets its observation: each story carries the\n // page's whole set, so nothing the cache answers silently is lost.\n cache: true,\n },\n }),\n };\n\n let world: World;\n try {\n world = await openWorld(recipe);\n } catch (error) {\n await served?.close();\n throw error;\n }\n\n // Every input to a `Collected` that is not the world, computed once and\n // handed to both readings — so the only thing that differs between a shared\n // collection and an isolated one is what else has run.\n const reading: Reading = {\n config,\n options,\n baseUrl,\n roots,\n ...(source !== undefined ? { source } : {}),\n };\n\n // One per run, not one per story. Every story in a Storybook is written in\n // the same handful of modules, so the second story onward resolves its call\n // sites out of this cache without a single fetch. Built over the run's own\n // page, because an isolated world is closed before anyone could ask it for a\n // frame — and it would resolve the same modules from the same server anyway.\n const callSites = createCallSiteResolver(fetchModules(world.page), { sourceRoot: process.cwd() });\n\n // Asked of the engine, laid over the scan. The scan answers names it found in\n // the configured directories; the engine answers the functions this page\n // rendered, and where the two name the same component the engine's file is\n // the one that ran. Read after each subject so the union grows with the run.\n //\n // The run's reader, not the only one. What the engine has met is a fact\n // about the page it met them in: a name registered by an earlier subject\n // stays in this registry for the rest of the run, so an isolated reading\n // laid over it would carry what else has run — the one thing it exists to\n // leave out. `collectAlone` reads the engine of the world it opened.\n const declared = createDeclarationReader(world.page);\n async function withDeclared(collected: Collected, reader = declared): Promise<Collected> {\n if (!collected.ok) return collected;\n const engine = await reader.read();\n if (Object.keys(engine).length === 0) return collected;\n return { ...collected, source: overlaySourceIndex(collected.source ?? {}, engine) };\n }\n\n /**\n * Reach one planned story on a standing world: read its parameters, then\n * read each subject they make of it.\n *\n * The parameters are read first, on the page the story is about to be shown\n * in, which imports the story's file — the import `showStory` would have\n * made, charged to this story's `collect`. A story that excludes itself is\n * not shown. Each width is the same story on the same page at another size,\n * so nothing is opened, loaded or booted for it.\n */\n async function visit(\n on: World,\n planned: PlannedSubject,\n reader: ReturnType<typeof createDeclarationReader>,\n ): Promise<Collected | Visit> {\n const read = await readParameters(harnessPage({ page: on.page }), storyIdOf(planned.subject.id));\n const realized = realize(planned, read, config.viewport);\n if ('excluded' in realized) return { ok: false, excluded: true, because: realized.excluded };\n if ('refused' in realized) return { ok: false, because: realized.refused };\n\n const [only] = realized.subjects;\n if (realized.subjects.length === 1 && only === planned && realized.warning === undefined) {\n return withDeclared(await readStory(on, reading, planned, recorder), reader);\n }\n\n const subjects: { planned: PlannedSubject; collected: Collected }[] = [];\n for (const subject of realized.subjects) {\n subjects.push({ planned: subject, collected: await withDeclared(await readStory(on, reading, subject, recorder), reader) });\n }\n return {\n subjects,\n ...(realized.warning === undefined ? {} : { warnings: [realized.warning] }),\n ...(realized.incomplete === true ? { incomplete: true as const } : {}),\n };\n }\n\n const collector: Collector = {\n async plan(): Promise<Plan> {\n if (plan === undefined) {\n throw operatorError(WRONG_KIND);\n }\n return plan;\n },\n\n async collect(planned: PlannedSubject): Promise<Collected | Visit> {\n return visit(world, planned, declared);\n },\n\n /**\n * The same reading, out of a world nothing else has touched.\n *\n * A fresh browser, a fresh context and a fresh page on the same preview,\n * showing this story and no other. Everything else is held identical by\n * construction: the world comes from the same {@link WorldRecipe} the run\n * was opened with, and the reading from the same {@link Reading} — same\n * viewport, same fonts, same ready selector, same ignore rules, same\n * wiring and holdings, same suspense budget, same roots, same source\n * index, and the same static server, so every asset URL and therefore the\n * environment key is the one the shared world produced.\n *\n * That last one is why the server is reused rather than restarted. A\n * second server answers on a second port, every asset URL moves, the key\n * moves with it, and every subject comes back changed with the port as its\n * cause — a clean collection that differs from the dirty one everywhere\n * settles nothing.\n *\n * The cost is a browser launch per subject, which is why `alone.limit`\n * exists and why the run only spends it on subjects it already called\n * `changed`.\n */\n async collectAlone(planned: PlannedSubject): Promise<Collected> {\n const alone = await openWorld({ ...recipe, harness: { ...recipe.harness, isolated: true } });\n // This world's engine. The run's reader has met every function every\n // earlier subject rendered, and would lay all of them over this reading.\n const declaredAlone = createDeclarationReader(alone.page);\n try {\n return await withDeclared(await readStory(alone, reading, planned), declaredAlone);\n } finally {\n // Always, including after a throw. A world left open is a browser\n // process and a port held for the rest of the run, and the run is\n // still going: this is called per changed subject, up to `alone.limit`\n // of them.\n await declaredAlone.close();\n await alone.close();\n }\n },\n\n /**\n * A second standing world for a worker: the same recipe, so the same\n * server, bundle and fonts, and therefore the key the run's own world\n * produces. Its engine reader is its own, because what an engine has met is\n * a fact about one page. The recorder is shared — it drains the page it is\n * handed, so a story's crossings are its own whichever world ran it — and\n * is closed once, by this collector.\n */\n async openWorker(): Promise<Collector> {\n const lane = await openWorld(recipe);\n const declaredHere = createDeclarationReader(lane.page);\n return {\n plan: async () => collector.plan(),\n collect: async (planned) => visit(lane, planned, declaredHere),\n callSites,\n async close(): Promise<void> {\n await declaredHere.close();\n await lane.close();\n },\n };\n },\n\n callSites,\n\n // The route collector closes in the same order, recorder first, so every\n // head's account is in before the world that drove it goes.\n // FIXME: one collector shell for both; until then they are kept in step by hand.\n async close(costs?: ReadonlyMap<string, number>): Promise<void> {\n await recorder?.close(costs);\n await declared.close();\n // Only the run's own world. An isolated one is opened and closed inside\n // `collectAlone`, and a worker's by the run before this is called.\n await world.close();\n await served?.close();\n },\n };\n return collector;\n };\n}\n\nexport type { StorybookCollectorOptions } from './options.js';\nexport type { StoryExecutionOptions } from './execution.js';\nexport { DEFAULT_STABILIZE } from '@variance-authority/core/format';\nexport type { SourceScan } from './source.js';\nexport type { AcquireRequest, Acquired } from './page-agent.js';\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { dirname, join } from 'node:path';\nimport { existsSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport { fileURLToPath } from 'node:url';\nimport { createCallSiteResolver, overlaySourceIndex } from '@variance-authority/core/attribute';\nimport { recipeToDrive, type Recipe } from '@variance-authority/core/format';\nimport { createDeclarationReader, fetchModules } from '@variance-authority/playwright';\nimport { createStoryRecorder } from './execution.js';\nimport { harnessPage, readParameters } from '@variance-authority/storybook';\nimport { realize, storyIdOf } from './parameters.js';\nimport type { StorybookCollectorOptions } from './options.js';\nimport { readStory, type Reading } from './read.js';\nimport { openWorld, type World, type WorldRecipe } from './world.js';\nexport type {\n Collected,\n Collector,\n CollectorConfig,\n CollectorContext,\n Plan,\n PlannedSubject,\n Visit,\n VisitedSubject,\n} from './contract.js';\nimport type { Collected, Collector, CollectorContext, Plan, PlannedSubject, Visit } from './contract.js';\nimport { operatorError } from './operator.js';\n\n/** Said in two places, because a mismatched kind can be caught at either. */\nconst WRONG_KIND =\n 'this collector expects `subjects.kind: \"storybook\"`, which is what supplies the plan';\nimport { serveStatic, type StaticServer } from './serve.js';\nimport { scanSource } from './source.js';\n\n/**\n * The Storybook collector, shipped.\n *\n * ## Why this package exists, stated as the thing it retracts\n *\n * This repository's position was that mounting is the adopter's, once, per\n * project — and it measured the cost honestly at 341 lines rather than the\n * \"about thirty\" the source had guessed. That position is right about React\n * applications and wrong about Storybook, for a reason the measurement itself\n * shows: of those 341 lines, the parts that were genuinely *this project's* were\n * a map of three ready selectors, a directory to scan for components, and a\n * static server. Everything else was Storybook's own contract being re-typed.\n *\n * A story index is a documented artifact. A preview owns its own mount and\n * exposes a channel for switching stories. So for this one subject source the\n * mounting problem is already solved by somebody else, and asking every adopter\n * to re-solve it was charging them for our boundary rather than for their\n * project.\n *\n * **What stays theirs.** Readiness per story, because Storybook's `storyRendered`\n * fires when the story function returns — which for a component that defers work\n * is *before the component exists*. That is knowledge about a component, and a\n * project-wide default would be a guess about all of them.\n *\n * ## What it does not make generic\n *\n * Nothing outside Storybook. A Playwright suite, a route table or a bespoke\n * mount is still a collector somebody writes — see\n * [`@variance-authority/playwright-test`](https://variance-authority.dev/reference/packages/playwright-test) for the surface\n * where the adopter's own test body plays that part instead.\n */\n\nconst STORY_ROOTS = ['#storybook-root', '#root'];\n\n/**\n * Two candidate paths, because this module is imported from two places.\n *\n * A consumer imports `dist/index.js` and the bundle sits beside it. This\n * repository's own tests import `src/index.ts`, where it does not — and a\n * collector that only worked when nobody in the project tested it would be a\n * collector nobody in the project tested.\n */\nasync function pageAgentBundle(): Promise<string> {\n const candidates = [\n new URL('./page-agent.bundle.js', import.meta.url),\n new URL('../dist/page-agent.bundle.js', import.meta.url),\n ];\n\n for (const path of candidates) {\n try {\n return await readFile(path, 'utf8');\n } catch {\n // Next candidate. The throw below names both, so a genuinely missing\n // bundle still fails with somewhere to look rather than a bare ENOENT.\n }\n }\n\n throw new Error(\n `the storybook page agent bundle is missing (looked in ` +\n `${candidates.map((path) => fileURLToPath(path)).join(' and ')}); ` +\n 'it is built when the package is built: a checkout is missing its build, and an ' +\n 'installed copy is missing a published file',\n );\n}\n\n/**\n * Build the module a `subjects.collector` path should default-export.\n *\n * A factory rather than a plugin: the config still names a module, the module\n * still exports a function, and the extension mechanism is unchanged. What the\n * adopter writes shrinks to the two facts only they hold.\n *\n * ```js\n * import { storybookCollector } from '@variance-authority/storybook-collector';\n *\n * export default storybookCollector({\n * ready: { 'surface--deferred': '[data-testid=\"ready\"]' },\n * source: { dirs: ['src'] },\n * });\n * ```\n */\nexport function storybookCollector(\n options: StorybookCollectorOptions = {},\n): (context: CollectorContext) => Promise<Collector> {\n return async function createCollector(context: CollectorContext): Promise<Collector> {\n const { config, plan } = context;\n const roots = options.roots ?? STORY_ROOTS;\n\n // Resolved here as well as in the page, because the driver applies three of\n // its tricks and has to know which. An unknown id or an instant with no zone\n // is the operator's to fix, and is said before a browser opens.\n let stabilize: Recipe;\n try {\n stabilize = recipeToDrive(options);\n } catch (error) {\n throw operatorError((error as Error).message, { cause: error });\n }\n\n // Checked before anything reads it. `index` is what `subjects.kind:\n // \"storybook\"` supplies, so a config naming another kind arrives here with\n // nothing to serve — and the guard further down in `plan()` is too late,\n // because this line runs first.\n const index = config.subjects.index;\n if (index === undefined) throw operatorError(WRONG_KIND);\n\n // The build the config named, not a sibling this file assumed. Serving the\n // directory that holds the planned index is the only arrangement in which the\n // stories driven and the stories planned are guaranteed to be the same ones.\n const staticDir = dirname(index);\n\n if (options.baseUrl === undefined && !existsSync(join(staticDir, 'index.json'))) {\n throw operatorError(\n `${staticDir} has no index.json; build the Storybook first, or point ` +\n '`baseUrl` at one that is already served',\n );\n }\n\n const source = options.source === undefined ? undefined : scanSource(process.cwd(), options.source);\n\n // What each story executed, if anybody asked. The index it feeds is the one\n // the Vitest seam writes: same probe recipe, same block ordinals, one index\n // across every origin.\n const recorder =\n options.tests === undefined || options.tests === false\n ? undefined\n : await createStoryRecorder(index, options.tests === true ? {} : options.tests);\n // Ahead of the agent, so the collector it evaluates hands its engine to the tape.\n const agent = await pageAgentBundle();\n const bundle = recorder?.storyPage === undefined ? agent : `${await recorder.storyPage()}\\n${agent}`;\n\n let served: StaticServer | undefined;\n if (options.baseUrl === undefined) served = await serveStatic(staticDir);\n const baseUrl = options.baseUrl ?? served!.baseUrl;\n\n // The recipe both worlds are built from, written down once.\n //\n // `collectAlone` opens its world from this same value, which is what makes\n // \"identical except for isolation\" a property of the code rather than a\n // discipline two call sites keep by hand: the viewport, the fonts, the\n // bundle, the preview address and the network options cannot differ between\n // the two worlds, because there is only one of each. See `world.ts`.\n //\n // Pointed at the preview, so `collectStory` finds itself already there and\n // does not navigate: one navigation for a whole run is the saving ADR-0009\n // rests on, and it is easiest to keep by never taking a second one. The\n // isolated world opens on the same address for a second reason — the preview\n // shell is not a subject, so landing on it is not something having run first.\n const recipe: WorldRecipe = {\n harness: {\n url: `${baseUrl}/iframe.html`,\n bundle,\n viewport: config.viewport,\n ...(config.fonts !== undefined ? { fonts: config.fonts } : {}),\n ...(config.timezoneId !== undefined ? { timezoneId: config.timezoneId } : {}),\n ...(config.locale !== undefined ? { locale: config.locale } : {}),\n ...(options.headless !== undefined ? { headless: options.headless } : {}),\n stabilize,\n // Every world of the run in one context, which is where the browser\n // keeps its HTTP cache: a worker's preview boots from what the first\n // world fetched. The pages share cookies and storage with it, as stories\n // in one preview already share a document. Across workers what a story\n // finds left behind depends on scheduling: which worker read which story\n // before it decides the cookies, storage and service workers, msw's\n // among them, that it meets.\n warm: true,\n },\n // Installed in `prepare`, which runs before the first navigation: an\n // observation that starts afterwards has already missed the assets the\n // preview loaded on its way up, and a URL nobody saw is a hole in the key.\n ...(options.network === false\n ? {}\n : {\n network: {\n ...(config.blank !== undefined ? { blank: config.blank } : {}),\n ...(options.hashAssets !== undefined ? { hashAssets: options.hashAssets } : {}),\n // Watching without turning the cache off. Safe because a Storybook\n // world never resets its observation: each story carries the\n // page's whole set, so nothing the cache answers silently is lost.\n cache: true,\n },\n }),\n };\n\n let world: World;\n try {\n world = await openWorld(recipe);\n } catch (error) {\n await served?.close();\n throw error;\n }\n\n // Every input to a `Collected` that is not the world, computed once and\n // handed to both readings — so the only thing that differs between a shared\n // collection and an isolated one is what else has run.\n const reading: Reading = {\n config,\n options,\n baseUrl,\n roots,\n ...(source !== undefined ? { source } : {}),\n };\n\n // One per run, not one per story. Every story in a Storybook is written in\n // the same handful of modules, so the second story onward resolves its call\n // sites out of this cache without a single fetch. Built over the run's own\n // page, because an isolated world is closed before anyone could ask it for a\n // frame — and it would resolve the same modules from the same server anyway.\n const callSites = createCallSiteResolver(fetchModules(world.page), { sourceRoot: process.cwd() });\n\n // Asked of the engine, laid over the scan. The scan answers names it found in\n // the configured directories; the engine answers the functions this page\n // rendered, and where the two name the same component the engine's file is\n // the one that ran. Read after each subject so the union grows with the run.\n //\n // The run's reader, not the only one. What the engine has met is a fact\n // about the page it met them in: a name registered by an earlier subject\n // stays in this registry for the rest of the run, so an isolated reading\n // laid over it would carry what else has run — the one thing it exists to\n // leave out. `collectAlone` reads the engine of the world it opened.\n const declared = createDeclarationReader(world.page);\n async function withDeclared(collected: Collected, reader = declared): Promise<Collected> {\n if (!collected.ok) return collected;\n const engine = await reader.read();\n if (Object.keys(engine).length === 0) return collected;\n return { ...collected, source: overlaySourceIndex(collected.source ?? {}, engine) };\n }\n\n /**\n * Reach one planned story on a standing world: read its parameters, then\n * read each subject they make of it.\n *\n * The parameters are read first, on the page the story is about to be shown\n * in, which imports the story's file — the import `showStory` would have\n * made, charged to this story's `collect`. A story that excludes itself is\n * not shown. Each width is the same story on the same page at another size,\n * so nothing is opened, loaded or booted for it.\n */\n async function visit(\n on: World,\n planned: PlannedSubject,\n reader: ReturnType<typeof createDeclarationReader>,\n ): Promise<Collected | Visit> {\n const read = await readParameters(harnessPage({ page: on.page }), storyIdOf(planned.subject.id));\n const realized = realize(planned, read, config.viewport);\n if ('excluded' in realized) return { ok: false, excluded: true, because: realized.excluded };\n if ('refused' in realized) return { ok: false, because: realized.refused };\n\n const [only] = realized.subjects;\n if (realized.subjects.length === 1 && only === planned && realized.warning === undefined) {\n return withDeclared(await readStory(on, reading, planned, recorder), reader);\n }\n\n const subjects: { planned: PlannedSubject; collected: Collected }[] = [];\n for (const subject of realized.subjects) {\n subjects.push({ planned: subject, collected: await withDeclared(await readStory(on, reading, subject, recorder), reader) });\n }\n return {\n subjects,\n ...(realized.warning === undefined ? {} : { warnings: [realized.warning] }),\n ...(realized.incomplete === true ? { incomplete: true as const } : {}),\n };\n }\n\n const collector: Collector = {\n async plan(): Promise<Plan> {\n if (plan === undefined) {\n throw operatorError(WRONG_KIND);\n }\n return plan;\n },\n\n async collect(planned: PlannedSubject): Promise<Collected | Visit> {\n return visit(world, planned, declared);\n },\n\n /**\n * The same reading, out of a world nothing else has touched.\n *\n * A fresh browser, a fresh context and a fresh page on the same preview,\n * showing this story and no other. Everything else is held identical by\n * construction: the world comes from the same {@link WorldRecipe} the run\n * was opened with, and the reading from the same {@link Reading} — same\n * viewport, same fonts, same ready selector, same ignore rules, same\n * wiring and holdings, same suspense budget, same roots, same source\n * index, and the same static server, so every asset URL and therefore the\n * environment key is the one the shared world produced.\n *\n * That last one is why the server is reused rather than restarted. A\n * second server answers on a second port, every asset URL moves, the key\n * moves with it, and every subject comes back changed with the port as its\n * cause — a clean collection that differs from the dirty one everywhere\n * settles nothing.\n *\n * The cost is a browser launch per subject, which is why `alone.limit`\n * exists and why the run only spends it on subjects it already called\n * `changed`.\n */\n async collectAlone(planned: PlannedSubject): Promise<Collected> {\n const alone = await openWorld({ ...recipe, harness: { ...recipe.harness, isolated: true } });\n // This world's engine. The run's reader has met every function every\n // earlier subject rendered, and would lay all of them over this reading.\n const declaredAlone = createDeclarationReader(alone.page);\n try {\n return await withDeclared(await readStory(alone, reading, planned), declaredAlone);\n } finally {\n // Always, including after a throw. A world left open is a browser\n // process and a port held for the rest of the run, and the run is\n // still going: this is called per changed subject, up to `alone.limit`\n // of them.\n await declaredAlone.close();\n await alone.close();\n }\n },\n\n /**\n * A second standing world for a worker: the same recipe, so the same\n * server, bundle and fonts, and therefore the key the run's own world\n * produces. Its engine reader is its own, because what an engine has met is\n * a fact about one page. The recorder is shared — it drains the page it is\n * handed, so a story's crossings are its own whichever world ran it — and\n * is closed once, by this collector.\n */\n async openWorker(): Promise<Collector> {\n const lane = await openWorld(recipe);\n const declaredHere = createDeclarationReader(lane.page);\n return {\n plan: async () => collector.plan(),\n collect: async (planned) => visit(lane, planned, declaredHere),\n callSites,\n async close(): Promise<void> {\n await declaredHere.close();\n await lane.close();\n },\n };\n },\n\n callSites,\n\n // The route collector closes in the same order, recorder first, so every\n // head's account is in before the world that drove it goes.\n // FIXME: one collector shell for both; until then they are kept in step by hand.\n async close(costs?: ReadonlyMap<string, number>): Promise<void> {\n await recorder?.close(costs);\n await declared.close();\n // Only the run's own world. An isolated one is opened and closed inside\n // `collectAlone`, and a worker's by the run before this is called.\n await world.close();\n await served?.close();\n },\n };\n return collector;\n };\n}\n\nexport type { StorybookCollectorOptions } from './options.js';\nexport type { StoryExecutionOptions } from './execution.js';\nexport { DEFAULT_STABILIZE } from '@variance-authority/core/format';\nexport type { SourceScan } from './source.js';\nexport type { AcquireRequest, Acquired } from './page-agent.js';\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@variance-authority/storybook-collector",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.1",
|
|
4
4
|
"description": "Turn a built or already-served Storybook into Variance Authority subjects.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"variance-authority",
|
|
@@ -44,17 +44,18 @@
|
|
|
44
44
|
"build": "tsc --build"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@variance-authority/core": "^0.
|
|
48
|
-
"@variance-authority/dom": "^0.
|
|
49
|
-
"@variance-authority/playwright": "^0.
|
|
50
|
-
"@variance-authority/react": "^0.
|
|
51
|
-
"@variance-authority/sense": "^0.
|
|
52
|
-
"@variance-authority/storybook": "^0.
|
|
47
|
+
"@variance-authority/core": "^0.24.1",
|
|
48
|
+
"@variance-authority/dom": "^0.24.1",
|
|
49
|
+
"@variance-authority/playwright": "^0.24.1",
|
|
50
|
+
"@variance-authority/react": "^0.24.1",
|
|
51
|
+
"@variance-authority/sense": "^0.24.1",
|
|
52
|
+
"@variance-authority/storybook": "^0.24.1"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@rsbuild/core": "~2.2.7",
|
|
56
56
|
"@rspack/core": "^2.2.6",
|
|
57
|
+
"playwright": "^1.49.0",
|
|
57
58
|
"storybook": "^9.1.13",
|
|
58
|
-
"vite": "^6.
|
|
59
|
+
"vite": "^6.4.3"
|
|
59
60
|
}
|
|
60
61
|
}
|