@variance-authority/storybook-collector 0.9.0 → 0.10.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/CHANGELOG.md +11 -0
- package/README.md +3 -1
- package/dist/contract.d.ts +17 -3
- package/dist/contract.js.map +1 -1
- package/dist/execution.d.ts +12 -2
- package/dist/execution.js +44 -5
- package/dist/execution.js.map +1 -1
- package/dist/index.js +29 -5
- package/dist/index.js.map +1 -1
- package/dist/page-agent.bundle.js +36 -5
- package/dist/parameters.d.ts +21 -0
- package/dist/parameters.js +58 -0
- package/dist/parameters.js.map +1 -0
- package/dist/read.js +2 -1
- package/dist/read.js.map +1 -1
- package/package.json +7 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# @variance-authority/storybook-collector
|
|
2
2
|
|
|
3
|
+
## 0.10.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 486caa3: Declare the test suites your repository runs, each with its kind, under `suites` in the root `variance.config.json`: `{ "suites": { "unit": { "kind": "unit" }, "stories": { "kind": "visual" } } }`. The kind is one of `unit`, `integration`, `e2e` and `visual`. Each declared suite records on its own, under `suites/<name>/` in the cache, with its case index and runs log beside it, so a run of one suite never replaces what another recorded. A worktree seeds each suite from the same suite in the primary checkout.
|
|
8
|
+
|
|
9
|
+
Every recording integration takes a `suite` option: the Vitest, Jest and Rstest seams, `startRecording`, `recordExecution`, the Playwright reporter and `varianceExecution`, and the Storybook collector's `tests`. Once `suites` is declared, a run that names no suite, names one the file does not declare, or names both a suite and a `coverageFile` stops before it starts. A repository that declares no suites keeps its one record. `testCoverageFile` and `readableTestCoverage` take `{ suite, cacheRoot }` as their second argument, where they took a bare `cacheRoot` string, and `declaredSuites`, `parseSuites`, `recordFileFor`, `SUITE_KINDS` and `SuitesError` are exported from `@variance-authority/sense/test-selection`. The CLI refuses `suites` in a config file below the repository root.
|
|
10
|
+
- 3d15479: A Storybook run's recording times each story with the time the run spent on it
|
|
11
|
+
|
|
12
|
+
The run hands its collector the time it spent on each subject, from the first collection to the decision, as the collector closes — `close(costs)`, where `costs` maps a subject id to whole milliseconds. The Storybook collector records that figure as each story's duration, on the story's row and on its case, so `variance ask slowest-tests` ranks stories by the same time `variance ask costs` reports and the next run shards on. A story read more than once is timed once, and a story the run did not time has no duration.
|
|
13
|
+
|
|
3
14
|
## 0.9.0
|
|
4
15
|
|
|
5
16
|
Lockstep release — nothing in this package changed. Every `@variance-authority/*` package shares one version.
|
package/README.md
CHANGED
|
@@ -374,7 +374,9 @@ export default storybookCollector({
|
|
|
374
374
|
`tests` accepts `true` or an options object: `label` must match the one the
|
|
375
375
|
preview's `testSelectionProbes()` was given, and `cacheRoot` and `coverageFile`
|
|
376
376
|
override the repository-keyed cache paths for the block records and the coverage
|
|
377
|
-
index. `
|
|
377
|
+
index. `suite` names the suite this run is, when the root `variance.config.json`
|
|
378
|
+
declares its suites under `suites`; it is required once any suite is declared,
|
|
379
|
+
and cannot be combined with `coverageFile`. `root` is the directory Storybook ran in, the cwd by default: story paths
|
|
378
380
|
and a relative `coverageFile` or `executionFile` are read from it, and every
|
|
379
381
|
path the run records is relative to the repository that contains it.
|
|
380
382
|
Without a collector in the page, the run says so on stderr and records nothing.
|
package/dist/contract.d.ts
CHANGED
|
@@ -75,9 +75,12 @@ export interface PlannedSubject {
|
|
|
75
75
|
* What the subject declares itself to be, from the artifact that produced it.
|
|
76
76
|
*
|
|
77
77
|
* Storybook's built index carries `tags` and does not carry a story's
|
|
78
|
-
* `parameters`, so a tag is the
|
|
79
|
-
*
|
|
78
|
+
* `parameters`, so a tag is the one per-story declaration the index can plan
|
|
79
|
+
* from — and it is the right one anyway: what a subject *is* belongs in its
|
|
80
80
|
* own name, next to it, rather than in a central file repeating every id.
|
|
81
|
+
* How a story is *read* — excluded, resized, read at several widths — comes
|
|
82
|
+
* from its parameters, which `withParameters` lays over this plan from the
|
|
83
|
+
* running preview.
|
|
81
84
|
*
|
|
82
85
|
* Selection lives here; definition lives in the config. A tag is a word a
|
|
83
86
|
* story wears, and what that word *means* is the operator's to write down
|
|
@@ -163,6 +166,17 @@ export interface Collector {
|
|
|
163
166
|
* `changed`, up to `alone.limit`.
|
|
164
167
|
*/
|
|
165
168
|
collectAlone?(subject: PlannedSubject): Promise<Collected>;
|
|
166
|
-
|
|
169
|
+
/**
|
|
170
|
+
* Another standing world from the same recipe and the same server, for a
|
|
171
|
+
* second worker. Optional in the contract the CLI declares; absent, a run
|
|
172
|
+
* configured with `workers` collects in one world and says so.
|
|
173
|
+
*/
|
|
174
|
+
openWorker?(): Promise<Collector>;
|
|
175
|
+
/**
|
|
176
|
+
* `costs` is what the run timed for each story, first collection to
|
|
177
|
+
* decision, by subject id; the execution record carries it as each story's
|
|
178
|
+
* duration.
|
|
179
|
+
*/
|
|
180
|
+
close(costs?: ReadonlyMap<string, number>): Promise<void>;
|
|
167
181
|
}
|
|
168
182
|
//# sourceMappingURL=contract.d.ts.map
|
package/dist/contract.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"","sourcesContent":["import type { CallSiteResolver, SourceIndex } from '@variance-authority/core/attribute';\nimport type {\n Diagnostic,\n RenderDocument,\n SemanticSnapshot,\n SubjectRef,\n Viewport,\n} from '@variance-authority/core/format';\n\n/**\n * The contract a collector satisfies, restated here rather than imported.\n *\n * The CLI declares the same shapes and this package does not depend on it — a\n * surface package pulling in the binary would put a config parser, a docket and\n * six commands into an adopter's dependency tree to describe four values\n * (ADR-0024). What keeps the two copies honest is that the CLI imports *this*\n * collector in its own tests, so a drift between them fails a build rather than\n * a run.\n */\n\n/** The subset of a run's configuration this collector reads. */\nexport interface CollectorConfig {\n readonly viewport: Viewport;\n readonly fonts?: readonly string[];\n /**\n * Subtrees this project excludes, from `config.ignore` (spec 0024).\n *\n * Only the two fields a page needs: a rule id to record the mark under, and a\n * selector to find it with. Everything else about a rule — its reason, its\n * expiry, the bands it narrows to — is decided after collection, where a clock\n * and the whole run's diffs are available and a browser is not.\n */\n readonly ignore?: readonly {\n readonly id: string;\n readonly select?: string;\n /**\n * Subjects the rule names. Read here to decide what to send, and never sent.\n *\n * A page can resolve a selector and cannot know which subject it is one of,\n * so scoping has to happen on this side of the boundary. Without it a rule\n * written for one route marks its element in every subject that renders the\n * same shared layout, and a real regression inside that element is absorbed\n * everywhere — an ignore silencing more than it says, which is the one thing\n * the mechanism must not do.\n */\n readonly subjects?: readonly string[];\n\n /** Tags the subject must carry. Read here to decide what to send, never sent. */\n readonly tags?: readonly string[];\n }[];\n\n /**\n * Images to serve as nothing, from `config.blank`.\n *\n * Handed to the driver rather than to the page, because it is enforced on the\n * wire — before a byte is decoded, and therefore before the layout it would\n * have participated in exists. Not scoped by subject, and it cannot be: a\n * request carries no idea which subject will end up using it, which is the\n * same reason the asset map is per page rather than per subject.\n */\n readonly blank?: readonly {\n readonly id: string;\n readonly url?: string;\n readonly minPixels?: number;\n readonly maxPixels?: number;\n }[];\n\n /**\n * The subjects section of the config, as the operator wrote it.\n *\n * `index` is optional because it belongs to `subjects.kind: \"storybook\"`, and\n * a config naming any other kind still reaches this collector — the kind is\n * chosen in the config and the collector is chosen in the config, and nothing\n * makes the operator pick a matching pair. Typed as always present, the first\n * thing to read it hands `undefined` to `node:path` and the adopter gets a\n * TypeError about an argument instead of a sentence naming the wrong word.\n */\n readonly subjects: { readonly index?: string };\n}\n\nexport interface PlannedSubject {\n readonly subject: SubjectRef;\n readonly viewport?: Viewport;\n\n /**\n * What the subject declares itself to be, from the artifact that produced it.\n *\n * Storybook's built index carries `tags` and does not carry a story's\n * `parameters`, so a tag is the
|
|
1
|
+
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"","sourcesContent":["import type { CallSiteResolver, SourceIndex } from '@variance-authority/core/attribute';\nimport type {\n Diagnostic,\n RenderDocument,\n SemanticSnapshot,\n SubjectRef,\n Viewport,\n} from '@variance-authority/core/format';\n\n/**\n * The contract a collector satisfies, restated here rather than imported.\n *\n * The CLI declares the same shapes and this package does not depend on it — a\n * surface package pulling in the binary would put a config parser, a docket and\n * six commands into an adopter's dependency tree to describe four values\n * (ADR-0024). What keeps the two copies honest is that the CLI imports *this*\n * collector in its own tests, so a drift between them fails a build rather than\n * a run.\n */\n\n/** The subset of a run's configuration this collector reads. */\nexport interface CollectorConfig {\n readonly viewport: Viewport;\n readonly fonts?: readonly string[];\n /**\n * Subtrees this project excludes, from `config.ignore` (spec 0024).\n *\n * Only the two fields a page needs: a rule id to record the mark under, and a\n * selector to find it with. Everything else about a rule — its reason, its\n * expiry, the bands it narrows to — is decided after collection, where a clock\n * and the whole run's diffs are available and a browser is not.\n */\n readonly ignore?: readonly {\n readonly id: string;\n readonly select?: string;\n /**\n * Subjects the rule names. Read here to decide what to send, and never sent.\n *\n * A page can resolve a selector and cannot know which subject it is one of,\n * so scoping has to happen on this side of the boundary. Without it a rule\n * written for one route marks its element in every subject that renders the\n * same shared layout, and a real regression inside that element is absorbed\n * everywhere — an ignore silencing more than it says, which is the one thing\n * the mechanism must not do.\n */\n readonly subjects?: readonly string[];\n\n /** Tags the subject must carry. Read here to decide what to send, never sent. */\n readonly tags?: readonly string[];\n }[];\n\n /**\n * Images to serve as nothing, from `config.blank`.\n *\n * Handed to the driver rather than to the page, because it is enforced on the\n * wire — before a byte is decoded, and therefore before the layout it would\n * have participated in exists. Not scoped by subject, and it cannot be: a\n * request carries no idea which subject will end up using it, which is the\n * same reason the asset map is per page rather than per subject.\n */\n readonly blank?: readonly {\n readonly id: string;\n readonly url?: string;\n readonly minPixels?: number;\n readonly maxPixels?: number;\n }[];\n\n /**\n * The subjects section of the config, as the operator wrote it.\n *\n * `index` is optional because it belongs to `subjects.kind: \"storybook\"`, and\n * a config naming any other kind still reaches this collector — the kind is\n * chosen in the config and the collector is chosen in the config, and nothing\n * makes the operator pick a matching pair. Typed as always present, the first\n * thing to read it hands `undefined` to `node:path` and the adopter gets a\n * TypeError about an argument instead of a sentence naming the wrong word.\n */\n readonly subjects: { readonly index?: string };\n}\n\nexport interface PlannedSubject {\n readonly subject: SubjectRef;\n readonly viewport?: Viewport;\n\n /**\n * What the subject declares itself to be, from the artifact that produced it.\n *\n * Storybook's built index carries `tags` and does not carry a story's\n * `parameters`, so a tag is the one per-story declaration the index can plan\n * from — and it is the right one anyway: what a subject *is* belongs in its\n * own name, next to it, rather than in a central file repeating every id.\n * How a story is *read* — excluded, resized, read at several widths — comes\n * from its parameters, which `withParameters` lays over this plan from the\n * running preview.\n *\n * Selection lives here; definition lives in the config. A tag is a word a\n * story wears, and what that word *means* is the operator's to write down\n * once, where a typo can be refused by name.\n */\n readonly tags?: readonly string[];\n}\n\nexport interface Plan {\n readonly subjects: readonly PlannedSubject[];\n readonly notObserved: readonly unknown[];\n readonly warnings: readonly string[];\n}\n\nexport type Collected =\n | {\n readonly ok: true;\n readonly document: RenderDocument;\n readonly snapshot?: SemanticSnapshot;\n readonly source?: SourceIndex;\n\n /**\n * Stabilization tricks applied to the page before this subject was read.\n *\n * Reported so a run can say what it did to somebody else's page. The\n * digest of the same list is in the environment key, which is what makes a\n * differently-stabilized baseline `incomparable`; this is the half a\n * person reads.\n */\n readonly stabilization?: readonly string[];\n\n /**\n * What the adapter noticed about the *way* this subject was produced.\n *\n * Not about the picture — the document and the snapshot carry that — but\n * about the reading: a story Storybook finished by reporting it failed, a\n * preview that had to be reloaded to show it, a render that never\n * finished. Each of those leaves a real image that a run can compare, and\n * each of them changes what a person should conclude from a green verdict,\n * so they travel with the subject rather than being dropped at the seam.\n */\n readonly diagnostics?: readonly Diagnostic[];\n }\n | { readonly ok: false; readonly because: string };\n\nexport interface CollectorContext {\n readonly config: CollectorConfig;\n /** Computed by the run from the story index. Returned unchanged. */\n readonly plan?: Plan;\n}\n\nexport interface Collector {\n plan(): Promise<Plan>;\n collect(subject: PlannedSubject): Promise<Collected>;\n\n /**\n * The frames a snapshot carries, spent on demand.\n *\n * Present when the collector drives a browser, because resolving a frame means\n * fetching the module it names and the page is the only place that request is\n * already correct. Absent otherwise, and absence is not a degradation: it means\n * either that nothing captured frames, or that the run has no live page to\n * fetch through — and both are a report without call sites rather than a\n * report that is wrong.\n *\n * Handed to `locateSites` with the few nodes a region or a finding names. The\n * resolver is per collector rather than per subject: the cache is what makes\n * this bounded, and a suite's subjects share their modules.\n */\n readonly callSites?: CallSiteResolver;\n\n /**\n * Collect this subject again, in a world nothing else has touched.\n *\n * The saving this whole tool is built on is that the world is *not* rebuilt\n * between subjects (ADR-0009): one browser, one page, one Storybook, for the\n * length of a run. What it buys is the possibility that story B renders\n * differently because story A ran first — and a comparison cannot tell that\n * apart from a regression, because both arrive as \"the pixels moved\".\n *\n * One clean collection settles it. If the difference is gone with nothing else\n * in the world, the baseline was right and the session moved this subject, and\n * the run reports `order-dependent` rather than asking anybody to accept it.\n *\n * Optional in the contract the CLI declares, because a collector holding a\n * single page open has no clean world to offer and the run says so rather than\n * reading silence as \"nothing leaked\". This collector implements it: a\n * Storybook preview can be opened twice, and the second one costs a browser\n * launch — which is why the run only spends it on subjects it already called\n * `changed`, up to `alone.limit`.\n */\n collectAlone?(subject: PlannedSubject): Promise<Collected>;\n\n /**\n * Another standing world from the same recipe and the same server, for a\n * second worker. Optional in the contract the CLI declares; absent, a run\n * configured with `workers` collects in one world and says so.\n */\n openWorker?(): Promise<Collector>;\n\n /**\n * `costs` is what the run timed for each story, first collection to\n * decision, by subject id; the execution record carries it as each story's\n * duration.\n */\n close(costs?: ReadonlyMap<string, number>): Promise<void>;\n}\n"]}
|
package/dist/execution.d.ts
CHANGED
|
@@ -27,6 +27,8 @@ export interface StoryExecutionOptions {
|
|
|
27
27
|
readonly cacheRoot?: string;
|
|
28
28
|
/** Coverage index. Defaults to the repository-keyed cache the runner seams share. */
|
|
29
29
|
readonly coverageFile?: string;
|
|
30
|
+
/** The suite this run is, as the root `variance.config.json` declares it under `suites`. */
|
|
31
|
+
readonly suite?: string;
|
|
30
32
|
/**
|
|
31
33
|
* Where the execution index — which individual story entered which region —
|
|
32
34
|
* goes. Defaults beside the snapshot, as the Vitest seam's does.
|
|
@@ -77,8 +79,16 @@ export interface StoryRecorder {
|
|
|
77
79
|
* excluding itself from a later run.
|
|
78
80
|
*/
|
|
79
81
|
readonly note: (page: EvaluatingPage, subjectId: string, complete: boolean) => Promise<void>;
|
|
80
|
-
/**
|
|
81
|
-
|
|
82
|
+
/**
|
|
83
|
+
* Merge the run into the index, or say on stderr why it could not.
|
|
84
|
+
*
|
|
85
|
+
* `costs` is the run's own time for each story, by subject id: first
|
|
86
|
+
* collection to decision, the figure `variance ask costs` answers and the
|
|
87
|
+
* next run shards on. It is recorded as the story's duration, on its row and
|
|
88
|
+
* on its case, rather than timed a second time here. A story the run did not
|
|
89
|
+
* time is recorded without one.
|
|
90
|
+
*/
|
|
91
|
+
readonly close: (costs?: ReadonlyMap<string, number>) => Promise<void>;
|
|
82
92
|
}
|
|
83
93
|
export declare function createStoryRecorder(index: string, options: StoryExecutionOptions): Promise<StoryRecorder>;
|
|
84
94
|
//# sourceMappingURL=execution.d.ts.map
|
package/dist/execution.js
CHANGED
|
@@ -17,11 +17,13 @@ import { relative, resolve, sep } from 'node:path';
|
|
|
17
17
|
import { drainExecution, preconditionOf, recordExecution, } from '@variance-authority/sense/journal';
|
|
18
18
|
import { parseStoryIndex } from '@variance-authority/storybook';
|
|
19
19
|
import { repositoryRoot } from '@variance-authority/sense/test-selection';
|
|
20
|
+
import { storyIdOf } from './parameters.js';
|
|
20
21
|
export async function createStoryRecorder(index, options) {
|
|
21
22
|
const ran = resolve(options.root ?? process.cwd());
|
|
22
23
|
const root = repositoryRoot(ran);
|
|
23
24
|
const storyFiles = await storyFilesFrom(index, ran, root);
|
|
24
25
|
const observed = [];
|
|
26
|
+
// Each case with the subject it was noted for, so the run's time finds it.
|
|
25
27
|
const cases = [];
|
|
26
28
|
let seen = false;
|
|
27
29
|
return {
|
|
@@ -30,7 +32,11 @@ export async function createStoryRecorder(index, options) {
|
|
|
30
32
|
if (journal === undefined)
|
|
31
33
|
return;
|
|
32
34
|
seen = true;
|
|
33
|
-
|
|
35
|
+
// A story read at several widths is one declaration and several cases:
|
|
36
|
+
// each width executed the story on its own, and a hook that reads the
|
|
37
|
+
// width took a different branch at each.
|
|
38
|
+
const widened = subjectId.replace(/^story:/, '');
|
|
39
|
+
const storyId = storyIdOf(subjectId);
|
|
34
40
|
const story = storyFiles.get(storyId);
|
|
35
41
|
const precondition = story === undefined ? undefined : await preconditionOf(root, story.file);
|
|
36
42
|
observed.push({
|
|
@@ -45,10 +51,19 @@ export async function createStoryRecorder(index, options) {
|
|
|
45
51
|
// the command line, a preview rebuilt since — has no file to be a case
|
|
46
52
|
// in, and contributes to the file-level record only.
|
|
47
53
|
if (story !== undefined) {
|
|
48
|
-
cases.push({
|
|
54
|
+
cases.push({
|
|
55
|
+
subject: subjectId,
|
|
56
|
+
observed: {
|
|
57
|
+
file: story.file,
|
|
58
|
+
name: `${story.title}/${story.name}${widened.slice(storyId.length)}`,
|
|
59
|
+
id: widened,
|
|
60
|
+
stopped: !complete,
|
|
61
|
+
journal,
|
|
62
|
+
},
|
|
63
|
+
});
|
|
49
64
|
}
|
|
50
65
|
},
|
|
51
|
-
close: async () => {
|
|
66
|
+
close: async (costs) => {
|
|
52
67
|
if (!seen) {
|
|
53
68
|
process.stderr.write('variance-authority: `tests` is on and the Storybook preview has no execution ' +
|
|
54
69
|
'collector in it — build it with `testSelectionProbes()` from ' +
|
|
@@ -72,13 +87,14 @@ export async function createStoryRecorder(index, options) {
|
|
|
72
87
|
try {
|
|
73
88
|
record = await recordExecution({
|
|
74
89
|
root,
|
|
75
|
-
subjects: observed,
|
|
90
|
+
subjects: zip(observed, timedOnce(observed.map((one) => one.owner), costs)),
|
|
76
91
|
...(options.label === undefined ? {} : { label: options.label }),
|
|
77
92
|
...(options.cacheRoot === undefined ? {} : { cacheRoot: options.cacheRoot }),
|
|
78
93
|
// Against `root`, as the runner seams resolve them, never the checkout.
|
|
79
94
|
...(options.coverageFile === undefined
|
|
80
95
|
? {}
|
|
81
96
|
: { coverageFile: resolve(ran, options.coverageFile) }),
|
|
97
|
+
...(options.suite === undefined ? {} : { suite: options.suite }),
|
|
82
98
|
...(options.mode === undefined ? {} : { mode: options.mode }),
|
|
83
99
|
...(options.preconditions === undefined
|
|
84
100
|
? {}
|
|
@@ -87,7 +103,9 @@ export async function createStoryRecorder(index, options) {
|
|
|
87
103
|
? {}
|
|
88
104
|
: { heads: options.heads }),
|
|
89
105
|
...(options.commit === undefined ? {} : { commit: options.commit }),
|
|
90
|
-
...(cases.length === 0
|
|
106
|
+
...(cases.length === 0
|
|
107
|
+
? {}
|
|
108
|
+
: { cases: zip(cases.map((one) => one.observed), timedOnce(cases.map((one) => one.subject), costs)) }),
|
|
91
109
|
...(options.executionFile === undefined
|
|
92
110
|
? {}
|
|
93
111
|
: { executionFile: resolve(ran, options.executionFile) }),
|
|
@@ -105,6 +123,27 @@ export async function createStoryRecorder(index, options) {
|
|
|
105
123
|
},
|
|
106
124
|
};
|
|
107
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* The run's time for each noted subject, given to the first note of that
|
|
128
|
+
* subject only.
|
|
129
|
+
*
|
|
130
|
+
* A subject read again — once more in the same world, once alone — is noted
|
|
131
|
+
* each time, and the record sums what its entries carry; the run's time
|
|
132
|
+
* already covers every reading, from the first collection to the decision.
|
|
133
|
+
*/
|
|
134
|
+
function timedOnce(subjects, costs) {
|
|
135
|
+
const given = new Set();
|
|
136
|
+
return subjects.map((subject) => {
|
|
137
|
+
const duration = costs?.get(subject);
|
|
138
|
+
if (duration === undefined || given.has(subject))
|
|
139
|
+
return {};
|
|
140
|
+
given.add(subject);
|
|
141
|
+
return { duration };
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
function zip(entries, timed) {
|
|
145
|
+
return entries.map((entry, index) => ({ ...entry, ...timed[index] }));
|
|
146
|
+
}
|
|
108
147
|
/**
|
|
109
148
|
* Story id to where it is declared, from the index the run is driving.
|
|
110
149
|
*
|
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](../../../docs/specs/0028-the-instrument.md) states. The preview is\n * built with `testSelectionProbes()`, 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 drainExecution,\n preconditionOf,\n recordExecution,\n type EvaluatingPage,\n type InstrumentMode,\n type ObservedCase,\n type ObservedSubject,\n} from '@variance-authority/sense/journal';\nimport { parseStoryIndex } from '@variance-authority/storybook';\nimport { repositoryRoot } from '@variance-authority/sense/test-selection';\n\n/** Where a story run's execution evidence is joined, read and kept. */\nexport interface StoryExecutionOptions {\n /**\n * A directory inside the repository; defaults to the cwd. Recorded paths are\n * relative to the checkout it sits in, never to it. A story's `importPath` is\n * read from here, because it is relative to the directory Storybook ran in.\n */\n readonly root?: string;\n /** Matches the `label` the build's `testSelectionProbes()` used. Defaults to `build`. */\n readonly label?: string;\n /** Where the build wrote its block records. Defaults to the repository's cache. */\n readonly cacheRoot?: string;\n /** Coverage index. Defaults to the repository-keyed cache the runner seams share. */\n readonly coverageFile?: string;\n /**\n * Where the execution index — which individual story entered which region —\n * goes. Defaults beside the snapshot, as the Vitest seam's does.\n *\n * A story is the case this costs nothing to name: the driver shows one at a\n * time, so the per-case grain a unit runner needs a custom runner for is\n * already here.\n */\n readonly executionFile?: string;\n /**\n * The probe recipe the preview was built with, matching\n * `testSelectionProbes()`'s `mode`. `presence` when absent, as it is there.\n *\n * Both sides answer the same or neither works: a journal cut by one recipe\n * and folded as another is refused, and a snapshot a runner seam also writes\n * under a different recipe has each run retire the other's evidence.\n */\n readonly mode?: InstrumentMode;\n /**\n * Files whose contents are preconditions of every story this run recorded.\n *\n * A `.storybook/preview` file, a theme module every decorator reads — a\n * story *file* is already a precondition of its own stories, and this is for\n * the ones no story declares and nothing enters.\n */\n readonly preconditions?: readonly string[];\n /**\n * Other builds this same run drove, by the label each instrumented under.\n *\n * A preview and the application behind it are two builds of overlapping\n * source, and a story that reaches both is one observation. Their stores\n * join this recording rather than getting one of their own.\n */\n readonly heads?: readonly string[];\n /**\n * Where this recording stands. Defaults to the checkout's `HEAD`, which is\n * the answer in every case except a caller that already knows better.\n */\n readonly commit?: string;\n}\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 readonly note: (page: EvaluatingPage, subjectId: string, complete: boolean) => Promise<void>;\n /** Merge the run into the index, or say on stderr why it could not. */\n readonly close: () => 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 root = repositoryRoot(ran);\n const storyFiles = await storyFilesFrom(index, ran, root);\n const observed: ObservedSubject[] = [];\n const cases: ObservedCase[] = [];\n let seen = false;\n\n return {\n note: async (page, subjectId, complete) => {\n const journal = await drainExecution(page);\n if (journal === undefined) return;\n seen = true;\n const storyId = subjectId.replace(/^story:/, '');\n const story = storyFiles.get(storyId);\n const precondition =\n story === undefined ? undefined : await preconditionOf(root, story.file);\n observed.push({\n owner: subjectId,\n journal,\n complete,\n ...(precondition === undefined ? {} : { preconditions: [precondition] }),\n });\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 if (story !== undefined) {\n cases.push({ file: story.file, name: `${story.title}/${story.name}`, id: storyId, stopped: !complete, journal });\n }\n },\n\n close: async () => {\n if (!seen) {\n process.stderr.write(\n 'variance-authority: `tests` is on and the Storybook preview has no execution ' +\n 'collector in it — build it with `testSelectionProbes()` from ' +\n '`@variance-authority/sense/journal`, or the next `--since` will run ' +\n 'everything\\n',\n );\n return;\n }\n // A refusal is written and the run continues. Recording is an addition to\n // a visual run: losing it costs the *next* selection its narrowing, which\n // is a full suite — the safe direction — while failing the run over it\n // would cost this one its baselines, which is not.\n //\n // That holds for a throw as much as for a refusal. The recorder reports\n // what it declined in `because`, but it is a file format, a lock and a\n // filesystem underneath, and any of them can raise something it has no\n // sentence for. Letting that escape ends the run *after* every subject\n // has been rendered and compared — the most expensive possible moment to\n // lose a set of baselines, and over the half of the work that was only\n // ever an addition.\n let record: Awaited<ReturnType<typeof recordExecution>>;\n try {\n record = await recordExecution({\n root,\n subjects: observed,\n ...(options.label === undefined ? {} : { label: options.label }),\n ...(options.cacheRoot === undefined ? {} : { cacheRoot: options.cacheRoot }),\n // Against `root`, as the runner seams resolve them, never the checkout.\n ...(options.coverageFile === undefined\n ? {}\n : { coverageFile: resolve(ran, options.coverageFile) }),\n ...(options.mode === undefined ? {} : { mode: options.mode }),\n ...(options.preconditions === undefined\n ? {}\n : { preconditions: options.preconditions }),\n ...(options.heads === undefined || options.heads.length === 0\n ? {}\n : { heads: options.heads }),\n ...(options.commit === undefined ? {} : { commit: options.commit }),\n ...(cases.length === 0 ? {} : { cases }),\n ...(options.executionFile === undefined\n ? {}\n : { executionFile: resolve(ran, options.executionFile) }),\n });\n } catch (error) {\n process.stderr.write(\n 'variance-authority: recorded no story execution — the recorder failed with ' +\n `${error instanceof Error ? error.message : String(error)}; this run is ` +\n 'unaffected and the next `--since` will run everything\\n',\n );\n return;\n }\n if (!record.recorded) {\n process.stderr.write(\n `variance-authority: recorded no story execution — ${record.because}\\n`,\n );\n }\n },\n };\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](../../../docs/specs/0028-the-instrument.md) states. The preview is\n * built with `testSelectionProbes()`, 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 drainExecution,\n preconditionOf,\n recordExecution,\n type EvaluatingPage,\n type InstrumentMode,\n type ObservedCase,\n type ObservedSubject,\n} from '@variance-authority/sense/journal';\nimport { parseStoryIndex } from '@variance-authority/storybook';\nimport { repositoryRoot } from '@variance-authority/sense/test-selection';\nimport { storyIdOf } from './parameters.js';\n\n/** Where a story run's execution evidence is joined, read and kept. */\nexport interface StoryExecutionOptions {\n /**\n * A directory inside the repository; defaults to the cwd. Recorded paths are\n * relative to the checkout it sits in, never to it. A story's `importPath` is\n * read from here, because it is relative to the directory Storybook ran in.\n */\n readonly root?: string;\n /** Matches the `label` the build's `testSelectionProbes()` used. Defaults to `build`. */\n readonly label?: string;\n /** Where the build wrote its block records. Defaults to the repository's cache. */\n readonly cacheRoot?: string;\n /** Coverage index. Defaults to the repository-keyed cache the runner seams share. */\n readonly coverageFile?: string;\n /** The suite this run is, as the root `variance.config.json` declares it under `suites`. */\n readonly suite?: string;\n /**\n * Where the execution index — which individual story entered which region —\n * goes. Defaults beside the snapshot, as the Vitest seam's does.\n *\n * A story is the case this costs nothing to name: the driver shows one at a\n * time, so the per-case grain a unit runner needs a custom runner for is\n * already here.\n */\n readonly executionFile?: string;\n /**\n * The probe recipe the preview was built with, matching\n * `testSelectionProbes()`'s `mode`. `presence` when absent, as it is there.\n *\n * Both sides answer the same or neither works: a journal cut by one recipe\n * and folded as another is refused, and a snapshot a runner seam also writes\n * under a different recipe has each run retire the other's evidence.\n */\n readonly mode?: InstrumentMode;\n /**\n * Files whose contents are preconditions of every story this run recorded.\n *\n * A `.storybook/preview` file, a theme module every decorator reads — a\n * story *file* is already a precondition of its own stories, and this is for\n * the ones no story declares and nothing enters.\n */\n readonly preconditions?: readonly string[];\n /**\n * Other builds this same run drove, by the label each instrumented under.\n *\n * A preview and the application behind it are two builds of overlapping\n * source, and a story that reaches both is one observation. Their stores\n * join this recording rather than getting one of their own.\n */\n readonly heads?: readonly string[];\n /**\n * Where this recording stands. Defaults to the checkout's `HEAD`, which is\n * the answer in every case except a caller that already knows better.\n */\n readonly commit?: string;\n}\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 readonly note: (page: EvaluatingPage, subjectId: string, complete: boolean) => 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 root = repositoryRoot(ran);\n const storyFiles = await storyFilesFrom(index, ran, root);\n const observed: ObservedSubject[] = [];\n // Each case with the subject it was noted for, so the run's time finds it.\n const cases: { readonly subject: string; readonly observed: ObservedCase }[] = [];\n let seen = false;\n\n return {\n note: async (page, subjectId, complete) => {\n const journal = await drainExecution(page);\n if (journal === undefined) return;\n seen = true;\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 observed.push({\n owner: subjectId,\n journal,\n complete,\n ...(precondition === undefined ? {} : { preconditions: [precondition] }),\n });\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 if (story !== undefined) {\n cases.push({\n subject: subjectId,\n observed: {\n file: story.file,\n name: `${story.title}/${story.name}${widened.slice(storyId.length)}`,\n id: widened,\n stopped: !complete,\n journal,\n },\n });\n }\n },\n\n close: async (costs) => {\n if (!seen) {\n process.stderr.write(\n 'variance-authority: `tests` is on and the Storybook preview has no execution ' +\n 'collector in it — build it with `testSelectionProbes()` from ' +\n '`@variance-authority/sense/journal`, or the next `--since` will run ' +\n 'everything\\n',\n );\n return;\n }\n // A refusal is written and the run continues. Recording is an addition to\n // a visual run: losing it costs the *next* selection its narrowing, which\n // is a full suite — the safe direction — while failing the run over it\n // would cost this one its baselines, which is not.\n //\n // That holds for a throw as much as for a refusal. The recorder reports\n // what it declined in `because`, but it is a file format, a lock and a\n // filesystem underneath, and any of them can raise something it has no\n // sentence for. Letting that escape ends the run *after* every subject\n // has been rendered and compared — the most expensive possible moment to\n // lose a set of baselines, and over the half of the work that was only\n // ever an addition.\n let record: Awaited<ReturnType<typeof recordExecution>>;\n try {\n record = await recordExecution({\n root,\n subjects: zip(observed, timedOnce(observed.map((one) => one.owner), costs)),\n ...(options.label === undefined ? {} : { label: options.label }),\n ...(options.cacheRoot === undefined ? {} : { cacheRoot: options.cacheRoot }),\n // Against `root`, as the runner seams resolve them, never the checkout.\n ...(options.coverageFile === undefined\n ? {}\n : { coverageFile: resolve(ran, options.coverageFile) }),\n ...(options.suite === undefined ? {} : { suite: options.suite }),\n ...(options.mode === undefined ? {} : { mode: options.mode }),\n ...(options.preconditions === undefined\n ? {}\n : { preconditions: options.preconditions }),\n ...(options.heads === undefined || options.heads.length === 0\n ? {}\n : { heads: options.heads }),\n ...(options.commit === undefined ? {} : { commit: options.commit }),\n ...(cases.length === 0\n ? {}\n : { cases: zip(cases.map((one) => one.observed), timedOnce(cases.map((one) => one.subject), costs)) }),\n ...(options.executionFile === undefined\n ? {}\n : { executionFile: resolve(ran, options.executionFile) }),\n });\n } catch (error) {\n process.stderr.write(\n 'variance-authority: recorded no story execution — the recorder failed with ' +\n `${error instanceof Error ? error.message : String(error)}; this run is ` +\n 'unaffected and the next `--since` will run everything\\n',\n );\n return;\n }\n if (!record.recorded) {\n process.stderr.write(\n `variance-authority: recorded no story execution — ${record.because}\\n`,\n );\n }\n },\n };\n}\n\n/**\n * The run's time for each noted subject, given to the first note of that\n * subject only.\n *\n * A subject read again — once more in the same world, once alone — is noted\n * each time, and the record sums what its entries carry; the run's time\n * already covers every reading, from the first collection to the decision.\n */\nfunction timedOnce(\n subjects: readonly string[],\n costs: ReadonlyMap<string, number> | undefined,\n): { readonly duration?: number }[] {\n const given = new Set<string>();\n return subjects.map((subject) => {\n const duration = costs?.get(subject);\n if (duration === undefined || given.has(subject)) return {};\n given.add(subject);\n return { duration };\n });\n}\n\nfunction zip<T>(entries: readonly T[], timed: readonly { readonly duration?: number }[]): T[] {\n return entries.map((entry, index) => ({ ...entry, ...timed[index] }));\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
|
@@ -5,6 +5,8 @@ import { fileURLToPath } from 'node:url';
|
|
|
5
5
|
import { createCallSiteResolver, overlaySourceIndex } from '@variance-authority/core/attribute';
|
|
6
6
|
import { createDeclarationReader, fetchModules } from '@variance-authority/playwright';
|
|
7
7
|
import { createStoryRecorder } from './execution.js';
|
|
8
|
+
import { harnessPage, readParameters } from '@variance-authority/storybook';
|
|
9
|
+
import { withParameters } from './parameters.js';
|
|
8
10
|
import { readStory } from './read.js';
|
|
9
11
|
import { openWorld } from './world.js';
|
|
10
12
|
import { operatorError } from './operator.js';
|
|
@@ -194,12 +196,12 @@ export function storybookCollector(options = {}) {
|
|
|
194
196
|
return collected;
|
|
195
197
|
return { ...collected, source: overlaySourceIndex(collected.source ?? {}, engine) };
|
|
196
198
|
}
|
|
197
|
-
|
|
199
|
+
const collector = {
|
|
198
200
|
async plan() {
|
|
199
201
|
if (plan === undefined) {
|
|
200
202
|
throw operatorError(WRONG_KIND);
|
|
201
203
|
}
|
|
202
|
-
return plan;
|
|
204
|
+
return withParameters(plan, await readParameters(harnessPage({ page: world.page })), config.viewport);
|
|
203
205
|
},
|
|
204
206
|
async collect(planned) {
|
|
205
207
|
return withDeclared(await readStory(world, reading, planned, recorder));
|
|
@@ -242,16 +244,38 @@ export function storybookCollector(options = {}) {
|
|
|
242
244
|
await alone.close();
|
|
243
245
|
}
|
|
244
246
|
},
|
|
247
|
+
/**
|
|
248
|
+
* A second standing world for a worker: the same recipe, so the same
|
|
249
|
+
* server, bundle and fonts, and therefore the key the run's own world
|
|
250
|
+
* produces. Its engine reader is its own, because what an engine has met is
|
|
251
|
+
* a fact about one page. The recorder is shared — it drains the page it is
|
|
252
|
+
* handed, so a story's crossings are its own whichever world ran it — and
|
|
253
|
+
* is closed once, by this collector.
|
|
254
|
+
*/
|
|
255
|
+
async openWorker() {
|
|
256
|
+
const lane = await openWorld(recipe);
|
|
257
|
+
const declaredHere = createDeclarationReader(lane.page);
|
|
258
|
+
return {
|
|
259
|
+
plan: async () => collector.plan(),
|
|
260
|
+
collect: async (planned) => withDeclared(await readStory(lane, reading, planned, recorder), declaredHere),
|
|
261
|
+
callSites,
|
|
262
|
+
async close() {
|
|
263
|
+
await declaredHere.close();
|
|
264
|
+
await lane.close();
|
|
265
|
+
},
|
|
266
|
+
};
|
|
267
|
+
},
|
|
245
268
|
callSites,
|
|
246
|
-
async close() {
|
|
247
|
-
await recorder?.close();
|
|
269
|
+
async close(costs) {
|
|
270
|
+
await recorder?.close(costs);
|
|
248
271
|
await declared.close();
|
|
249
272
|
// Only the run's own world. An isolated one is opened and closed inside
|
|
250
|
-
// `collectAlone`,
|
|
273
|
+
// `collectAlone`, and a worker's by the run before this is called.
|
|
251
274
|
await world.close();
|
|
252
275
|
await served?.close();
|
|
253
276
|
},
|
|
254
277
|
};
|
|
278
|
+
return collector;
|
|
255
279
|
};
|
|
256
280
|
}
|
|
257
281
|
//# sourceMappingURL=index.js.map
|
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 { createDeclarationReader, fetchModules } from '@variance-authority/playwright';\nimport { createStoryRecorder } from './execution.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} from './contract.js';\nimport type { Collected, Collector, CollectorContext, Plan, PlannedSubject } 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`](../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 // 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 ...(options.headless !== undefined ? { headless: options.headless } : {}),\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 },\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 return {\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> {\n return withDeclared(await readStory(world, reading, planned, recorder));\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 the run gets `incomparable` back — a clean collection\n * that cannot be compared to the dirty one 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);\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 callSites,\n\n async close(): Promise<void> {\n await recorder?.close();\n await declared.close();\n // Only the run's own world. An isolated one is opened and closed inside\n // `collectAlone`, so there is never a second world alive at this point.\n await world.close();\n await served?.close();\n },\n };\n };\n}\n\nexport type { StorybookCollectorOptions } from './options.js';\nexport type { StoryExecutionOptions } from './execution.js';\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":"AAAA,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AAChG,OAAO,EAAE,uBAAuB,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AACvF,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC5E,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEjD,OAAO,EAAE,SAAS,EAAgB,MAAM,WAAW,CAAC;AACpD,OAAO,EAAE,SAAS,EAAgC,MAAM,YAAY,CAAC;AAUrE,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAE9C,6EAA6E;AAC7E,MAAM,UAAU,GACd,sFAAsF,CAAC;AACzF,OAAO,EAAE,WAAW,EAAqB,MAAM,YAAY,CAAC;AAC5D,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,MAAM,WAAW,GAAG,CAAC,iBAAiB,EAAE,OAAO,CAAC,CAAC;AAEjD;;;;;;;GAOG;AACH,KAAK,UAAU,eAAe;IAC5B,MAAM,UAAU,GAAG;QACjB,IAAI,GAAG,CAAC,wBAAwB,EAAE,OAAO,IAAI,CAAC,GAAG,CAAC;QAClD,IAAI,GAAG,CAAC,8BAA8B,EAAE,OAAO,IAAI,CAAC,GAAG,CAAC;KACzD,CAAC;IAEF,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;QAC9B,IAAI,CAAC;YACH,OAAO,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACtC,CAAC;QAAC,MAAM,CAAC;YACP,qEAAqE;YACrE,uEAAuE;QACzE,CAAC;IACH,CAAC;IAED,MAAM,IAAI,KAAK,CACb,wDAAwD;QACtD,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK;QACnE,iFAAiF;QACjF,4CAA4C,CAC/C,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,kBAAkB,CAChC,OAAO,GAA8B,EAAE;IAEvC,OAAO,KAAK,UAAU,eAAe,CAAC,OAAyB;QAC7D,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC;QACjC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,WAAW,CAAC;QAE3C,oEAAoE;QACpE,2EAA2E;QAC3E,yEAAyE;QACzE,gCAAgC;QAChC,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;QACpC,IAAI,KAAK,KAAK,SAAS;YAAE,MAAM,aAAa,CAAC,UAAU,CAAC,CAAC;QAEzD,2EAA2E;QAC3E,8EAA8E;QAC9E,6EAA6E;QAC7E,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;QAEjC,IAAI,OAAO,CAAC,OAAO,KAAK,SAAS,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC;YAChF,MAAM,aAAa,CACjB,GAAG,SAAS,0DAA0D;gBACpE,yCAAyC,CAC5C,CAAC;QACJ,CAAC;QAED,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;QAEpG,4EAA4E;QAC5E,4EAA4E;QAC5E,uBAAuB;QACvB,MAAM,QAAQ,GACZ,OAAO,CAAC,KAAK,KAAK,SAAS,IAAI,OAAO,CAAC,KAAK,KAAK,KAAK;YACpD,CAAC,CAAC,SAAS;YACX,CAAC,CAAC,MAAM,mBAAmB,CAAC,KAAK,EAAE,OAAO,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QACpF,MAAM,MAAM,GAAG,MAAM,eAAe,EAAE,CAAC;QAEvC,IAAI,MAAgC,CAAC;QACrC,IAAI,OAAO,CAAC,OAAO,KAAK,SAAS;YAAE,MAAM,GAAG,MAAM,WAAW,CAAC,SAAS,CAAC,CAAC;QACzE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,MAAO,CAAC,OAAO,CAAC;QAEnD,4DAA4D;QAC5D,EAAE;QACF,2EAA2E;QAC3E,wEAAwE;QACxE,uEAAuE;QACvE,4EAA4E;QAC5E,qEAAqE;QACrE,EAAE;QACF,2EAA2E;QAC3E,2EAA2E;QAC3E,wEAAwE;QACxE,6EAA6E;QAC7E,8EAA8E;QAC9E,MAAM,MAAM,GAAgB;YAC1B,OAAO,EAAE;gBACP,GAAG,EAAE,GAAG,OAAO,cAAc;gBAC7B,MAAM;gBACN,QAAQ,EAAE,MAAM,CAAC,QAAQ;gBACzB,GAAG,CAAC,MAAM,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC9D,GAAG,CAAC,OAAO,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAC1E;YACD,qEAAqE;YACrE,uEAAuE;YACvE,2EAA2E;YAC3E,GAAG,CAAC,OAAO,CAAC,OAAO,KAAK,KAAK;gBAC3B,CAAC,CAAC,EAAE;gBACJ,CAAC,CAAC;oBACE,OAAO,EAAE;wBACP,GAAG,CAAC,MAAM,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;wBAC9D,GAAG,CAAC,OAAO,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;qBAChF;iBACF,CAAC;SACP,CAAC;QAEF,IAAI,KAAY,CAAC;QACjB,IAAI,CAAC;YACH,KAAK,GAAG,MAAM,SAAS,CAAC,MAAM,CAAC,CAAC;QAClC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,MAAM,EAAE,KAAK,EAAE,CAAC;YACtB,MAAM,KAAK,CAAC;QACd,CAAC;QAED,wEAAwE;QACxE,4EAA4E;QAC5E,uDAAuD;QACvD,MAAM,OAAO,GAAY;YACvB,MAAM;YACN,OAAO;YACP,OAAO;YACP,KAAK;YACL,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC5C,CAAC;QAEF,2EAA2E;QAC3E,4EAA4E;QAC5E,2EAA2E;QAC3E,6EAA6E;QAC7E,6EAA6E;QAC7E,MAAM,SAAS,GAAG,sBAAsB,CAAC,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,UAAU,EAAE,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QAElG,8EAA8E;QAC9E,yEAAyE;QACzE,2EAA2E;QAC3E,6EAA6E;QAC7E,EAAE;QACF,wEAAwE;QACxE,yEAAyE;QACzE,yEAAyE;QACzE,0EAA0E;QAC1E,qEAAqE;QACrE,MAAM,QAAQ,GAAG,uBAAuB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACrD,KAAK,UAAU,YAAY,CAAC,SAAoB,EAAE,MAAM,GAAG,QAAQ;YACjE,IAAI,CAAC,SAAS,CAAC,EAAE;gBAAE,OAAO,SAAS,CAAC;YACpC,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;YACnC,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,SAAS,CAAC;YACvD,OAAO,EAAE,GAAG,SAAS,EAAE,MAAM,EAAE,kBAAkB,CAAC,SAAS,CAAC,MAAM,IAAI,EAAE,EAAE,MAAM,CAAC,EAAE,CAAC;QACtF,CAAC;QAED,MAAM,SAAS,GAAc;YAC3B,KAAK,CAAC,IAAI;gBACR,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;oBACvB,MAAM,aAAa,CAAC,UAAU,CAAC,CAAC;gBAClC,CAAC;gBACD,OAAO,cAAc,CAAC,IAAI,EAAE,MAAM,cAAc,CAAC,WAAW,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;YACxG,CAAC;YAED,KAAK,CAAC,OAAO,CAAC,OAAuB;gBACnC,OAAO,YAAY,CAAC,MAAM,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC;YAC1E,CAAC;YAED;;;;;;;;;;;;;;;;;;;;eAoBG;YACH,KAAK,CAAC,YAAY,CAAC,OAAuB;gBACxC,MAAM,KAAK,GAAG,MAAM,SAAS,CAAC,MAAM,CAAC,CAAC;gBACtC,qEAAqE;gBACrE,yEAAyE;gBACzE,MAAM,aAAa,GAAG,uBAAuB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBAC1D,IAAI,CAAC;oBACH,OAAO,MAAM,YAAY,CAAC,MAAM,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,aAAa,CAAC,CAAC;gBACrF,CAAC;wBAAS,CAAC;oBACT,kEAAkE;oBAClE,kEAAkE;oBAClE,uEAAuE;oBACvE,WAAW;oBACX,MAAM,aAAa,CAAC,KAAK,EAAE,CAAC;oBAC5B,MAAM,KAAK,CAAC,KAAK,EAAE,CAAC;gBACtB,CAAC;YACH,CAAC;YAED;;;;;;;eAOG;YACH,KAAK,CAAC,UAAU;gBACd,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,MAAM,CAAC,CAAC;gBACrC,MAAM,YAAY,GAAG,uBAAuB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACxD,OAAO;oBACL,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE;oBAClC,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC,YAAY,CAAC,MAAM,SAAS,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,YAAY,CAAC;oBACzG,SAAS;oBACT,KAAK,CAAC,KAAK;wBACT,MAAM,YAAY,CAAC,KAAK,EAAE,CAAC;wBAC3B,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;oBACrB,CAAC;iBACF,CAAC;YACJ,CAAC;YAED,SAAS;YAET,KAAK,CAAC,KAAK,CAAC,KAAmC;gBAC7C,MAAM,QAAQ,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;gBAC7B,MAAM,QAAQ,CAAC,KAAK,EAAE,CAAC;gBACvB,wEAAwE;gBACxE,mEAAmE;gBACnE,MAAM,KAAK,CAAC,KAAK,EAAE,CAAC;gBACpB,MAAM,MAAM,EAAE,KAAK,EAAE,CAAC;YACxB,CAAC;SACF,CAAC;QACF,OAAO,SAAS,CAAC;IACnB,CAAC,CAAC;AACJ,CAAC","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 { createDeclarationReader, fetchModules } from '@variance-authority/playwright';\nimport { createStoryRecorder } from './execution.js';\nimport { harnessPage, readParameters } from '@variance-authority/storybook';\nimport { withParameters } 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} from './contract.js';\nimport type { Collected, Collector, CollectorContext, Plan, PlannedSubject } 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`](../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 // 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 ...(options.headless !== undefined ? { headless: options.headless } : {}),\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 },\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 const collector: Collector = {\n async plan(): Promise<Plan> {\n if (plan === undefined) {\n throw operatorError(WRONG_KIND);\n }\n return withParameters(plan, await readParameters(harnessPage({ page: world.page })), config.viewport);\n },\n\n async collect(planned: PlannedSubject): Promise<Collected> {\n return withDeclared(await readStory(world, reading, planned, recorder));\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 the run gets `incomparable` back — a clean collection\n * that cannot be compared to the dirty one 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);\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) => withDeclared(await readStory(lane, reading, planned, recorder), declaredHere),\n callSites,\n async close(): Promise<void> {\n await declaredHere.close();\n await lane.close();\n },\n };\n },\n\n callSites,\n\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 type { SourceScan } from './source.js';\nexport type { AcquireRequest, Acquired } from './page-agent.js';\n"]}
|
|
@@ -223,6 +223,7 @@
|
|
|
223
223
|
function digestValue(value) {
|
|
224
224
|
return digestString(canonicalize(value));
|
|
225
225
|
}
|
|
226
|
+
var FILE_NAME = new RegExp(`^${PREFIX}[-:]([0-9a-f]{${HEX_LENGTH}})$`);
|
|
226
227
|
|
|
227
228
|
// packages/core/dist/format/provenance.js
|
|
228
229
|
var JSX_SOURCE = Symbol.for("@variance-authority/jsx-source");
|
|
@@ -1891,6 +1892,31 @@
|
|
|
1891
1892
|
}
|
|
1892
1893
|
|
|
1893
1894
|
// packages/dom/dist/media.js
|
|
1895
|
+
var DEVICE_FEATURES = /* @__PURE__ */ new Set(["pointer", "any-pointer", "hover", "any-hover"]);
|
|
1896
|
+
function deviceProbe(view) {
|
|
1897
|
+
const matchMedia = view?.matchMedia?.bind(view);
|
|
1898
|
+
if (matchMedia === void 0)
|
|
1899
|
+
return null;
|
|
1900
|
+
try {
|
|
1901
|
+
if (matchMedia("all").matches !== true || matchMedia("not all").matches !== false)
|
|
1902
|
+
return null;
|
|
1903
|
+
} catch {
|
|
1904
|
+
return null;
|
|
1905
|
+
}
|
|
1906
|
+
return (feature) => {
|
|
1907
|
+
try {
|
|
1908
|
+
return matchMedia(`(${feature})`).matches;
|
|
1909
|
+
} catch {
|
|
1910
|
+
return null;
|
|
1911
|
+
}
|
|
1912
|
+
};
|
|
1913
|
+
}
|
|
1914
|
+
function askDevice(name, feature, environment) {
|
|
1915
|
+
if (!DEVICE_FEATURES.has(name) || environment.device === void 0)
|
|
1916
|
+
return null;
|
|
1917
|
+
const answer = environment.device(feature);
|
|
1918
|
+
return answer === null ? null : { matches: answer, uncertain: false };
|
|
1919
|
+
}
|
|
1894
1920
|
var CERTAIN_MATCH = { matches: true, uncertain: false };
|
|
1895
1921
|
function evaluateMedia(query, environment) {
|
|
1896
1922
|
const normalized = query.trim().toLowerCase();
|
|
@@ -1948,7 +1974,7 @@
|
|
|
1948
1974
|
function evaluateFeature(feature, environment) {
|
|
1949
1975
|
const colon = feature.indexOf(":");
|
|
1950
1976
|
if (colon < 0) {
|
|
1951
|
-
return { matches: true, uncertain: true };
|
|
1977
|
+
return askDevice(feature.trim(), feature.trim(), environment) ?? { matches: true, uncertain: true };
|
|
1952
1978
|
}
|
|
1953
1979
|
const name = feature.slice(0, colon).trim();
|
|
1954
1980
|
const value = feature.slice(colon + 1).trim();
|
|
@@ -1980,7 +2006,7 @@
|
|
|
1980
2006
|
const declared2 = environment.features?.[name];
|
|
1981
2007
|
if (declared2 !== void 0)
|
|
1982
2008
|
return { matches: declared2 === value, uncertain: false };
|
|
1983
|
-
return { matches: true, uncertain: true };
|
|
2009
|
+
return askDevice(name, `${name}: ${value}`, environment) ?? { matches: true, uncertain: true };
|
|
1984
2010
|
}
|
|
1985
2011
|
}
|
|
1986
2012
|
}
|
|
@@ -2549,7 +2575,8 @@
|
|
|
2549
2575
|
`scale=${environment.deviceScaleFactor}`,
|
|
2550
2576
|
`scheme=${environment.colorScheme}`,
|
|
2551
2577
|
`features=${features.join(",")}`,
|
|
2552
|
-
`supports=${environment.supports ? "probed" : "assumed"}
|
|
2578
|
+
`supports=${environment.supports ? "probed" : "assumed"}`,
|
|
2579
|
+
`device=${environment.device ? "probed" : "assumed"}`
|
|
2553
2580
|
].join(" ");
|
|
2554
2581
|
}
|
|
2555
2582
|
function isStabilizationSheet(sheet) {
|
|
@@ -2887,13 +2914,15 @@
|
|
|
2887
2914
|
// packages/dom/dist/collect.js
|
|
2888
2915
|
function conditionsFor(document, viewport, features) {
|
|
2889
2916
|
const supports = supportsProbe(document.defaultView);
|
|
2917
|
+
const device = deviceProbe(document.defaultView);
|
|
2890
2918
|
return {
|
|
2891
2919
|
width: viewport.width,
|
|
2892
2920
|
height: viewport.height,
|
|
2893
2921
|
deviceScaleFactor: viewport.deviceScaleFactor,
|
|
2894
2922
|
colorScheme: viewport.colorScheme,
|
|
2895
2923
|
...features ? { features } : {},
|
|
2896
|
-
...supports ? { supports } : {}
|
|
2924
|
+
...supports ? { supports } : {},
|
|
2925
|
+
...device ? { device } : {}
|
|
2897
2926
|
};
|
|
2898
2927
|
}
|
|
2899
2928
|
function collect(root, options) {
|
|
@@ -3165,12 +3194,14 @@
|
|
|
3165
3194
|
const ownerDocument = root.ownerDocument;
|
|
3166
3195
|
const view = ownerDocument.defaultView;
|
|
3167
3196
|
const diagnostics = [];
|
|
3197
|
+
const device = deviceProbe(view);
|
|
3168
3198
|
const conditions = {
|
|
3169
3199
|
width: options.viewport.width,
|
|
3170
3200
|
height: options.viewport.height,
|
|
3171
3201
|
deviceScaleFactor: options.viewport.deviceScaleFactor,
|
|
3172
3202
|
colorScheme: options.viewport.colorScheme,
|
|
3173
|
-
...options.features ? { features: options.features } : {}
|
|
3203
|
+
...options.features ? { features: options.features } : {},
|
|
3204
|
+
...device ? { device } : {}
|
|
3174
3205
|
};
|
|
3175
3206
|
const index = options.index ?? indexStyleSheets(ownerDocument, conditions);
|
|
3176
3207
|
diagnostics.push(...index.diagnostics);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Viewport } from '@variance-authority/core/format';
|
|
2
|
+
import { type ParametersRead } from '@variance-authority/storybook';
|
|
3
|
+
import type { Plan } from './contract.js';
|
|
4
|
+
/**
|
|
5
|
+
* The story a subject id names.
|
|
6
|
+
*
|
|
7
|
+
* Storybook replaces every character outside `[a-z0-9-]` when it makes an id,
|
|
8
|
+
* so an `@` in a subject id is always the width suffix `widthsOf` added.
|
|
9
|
+
*/
|
|
10
|
+
export declare function storyIdOf(subjectId: string): string;
|
|
11
|
+
/**
|
|
12
|
+
* A story's own parameters, laid over the plan the index produced.
|
|
13
|
+
*
|
|
14
|
+
* The index plans every story once at the run's viewport, because tags are all
|
|
15
|
+
* it carries. `read` is what the running preview says each story declared, and
|
|
16
|
+
* it decides which stories are read, at what size and at how many widths. An
|
|
17
|
+
* exclusion lands in `notObserved` with its reason, so a story that asked not to
|
|
18
|
+
* be read is still in the report.
|
|
19
|
+
*/
|
|
20
|
+
export declare function withParameters(plan: Plan, read: ParametersRead, run: Viewport): Plan;
|
|
21
|
+
//# sourceMappingURL=parameters.d.ts.map
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { widthsOf } from '@variance-authority/playwright';
|
|
2
|
+
import { declarationOf, PARAMETERS_KEY } from '@variance-authority/storybook';
|
|
3
|
+
/**
|
|
4
|
+
* The story a subject id names.
|
|
5
|
+
*
|
|
6
|
+
* Storybook replaces every character outside `[a-z0-9-]` when it makes an id,
|
|
7
|
+
* so an `@` in a subject id is always the width suffix `widthsOf` added.
|
|
8
|
+
*/
|
|
9
|
+
export function storyIdOf(subjectId) {
|
|
10
|
+
const id = subjectId.replace(/^story:/, '');
|
|
11
|
+
const at = id.indexOf('@');
|
|
12
|
+
return at === -1 ? id : id.slice(0, at);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* A story's own parameters, laid over the plan the index produced.
|
|
16
|
+
*
|
|
17
|
+
* The index plans every story once at the run's viewport, because tags are all
|
|
18
|
+
* it carries. `read` is what the running preview says each story declared, and
|
|
19
|
+
* it decides which stories are read, at what size and at how many widths. An
|
|
20
|
+
* exclusion lands in `notObserved` with its reason, so a story that asked not to
|
|
21
|
+
* be read is still in the report.
|
|
22
|
+
*/
|
|
23
|
+
export function withParameters(plan, read, run) {
|
|
24
|
+
if ('unread' in read) {
|
|
25
|
+
return {
|
|
26
|
+
...plan,
|
|
27
|
+
warnings: [
|
|
28
|
+
...plan.warnings,
|
|
29
|
+
`story parameters were not read — ${read.unread}. Every story is read at the run's ` +
|
|
30
|
+
`viewport, and no story's \`parameters.${PARAMETERS_KEY}\` applies to this run`,
|
|
31
|
+
],
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
const subjects = [];
|
|
35
|
+
const notObserved = [...plan.notObserved];
|
|
36
|
+
for (const planned of plan.subjects) {
|
|
37
|
+
const raw = read.parameters[storyIdOf(planned.subject.id)];
|
|
38
|
+
if (raw === undefined) {
|
|
39
|
+
subjects.push(planned);
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
const declared = declarationOf(raw, run);
|
|
43
|
+
if ('excluded' in declared) {
|
|
44
|
+
notObserved.push({ subject: planned.subject.id, kind: 'excluded', because: declared.excluded });
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
// The widths are laid over the story's own viewport, not the run's: a story
|
|
48
|
+
// that declares a dark, 2x viewport and three widths is read dark and 2x at
|
|
49
|
+
// each of them. `widthsOf` keeps a subject that already has a viewport, so
|
|
50
|
+
// the declared one is handed in as the base rather than set on the subject.
|
|
51
|
+
const base = declared.viewport ?? run;
|
|
52
|
+
subjects.push(...(declared.widths === undefined || declared.widths.length === 0
|
|
53
|
+
? [declared.viewport === undefined ? planned : { ...planned, viewport: declared.viewport }]
|
|
54
|
+
: widthsOf({ subjects: [planned] }, declared.widths, base).subjects));
|
|
55
|
+
}
|
|
56
|
+
return { ...plan, subjects, notObserved };
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=parameters.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parameters.js","sourceRoot":"","sources":["../src/parameters.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,gCAAgC,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,cAAc,EAAuB,MAAM,+BAA+B,CAAC;AAGnG;;;;;GAKG;AACH,MAAM,UAAU,SAAS,CAAC,SAAiB;IACzC,MAAM,EAAE,GAAG,SAAS,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;IAC5C,MAAM,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC3B,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAC1C,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAAC,IAAU,EAAE,IAAoB,EAAE,GAAa;IAC5E,IAAI,QAAQ,IAAI,IAAI,EAAE,CAAC;QACrB,OAAO;YACL,GAAG,IAAI;YACP,QAAQ,EAAE;gBACR,GAAG,IAAI,CAAC,QAAQ;gBAChB,oCAAoC,IAAI,CAAC,MAAM,qCAAqC;oBAClF,yCAAyC,cAAc,wBAAwB;aAClF;SACF,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAqB,EAAE,CAAC;IACtC,MAAM,WAAW,GAAc,CAAC,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC;IAErD,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QACpC,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC;QAC3D,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;YACtB,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACvB,SAAS;QACX,CAAC;QAED,MAAM,QAAQ,GAAG,aAAa,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QACzC,IAAI,UAAU,IAAI,QAAQ,EAAE,CAAC;YAC3B,WAAW,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;YAChG,SAAS;QACX,CAAC;QAED,4EAA4E;QAC5E,4EAA4E;QAC5E,2EAA2E;QAC3E,4EAA4E;QAC5E,MAAM,IAAI,GAAG,QAAQ,CAAC,QAAQ,IAAI,GAAG,CAAC;QACtC,QAAQ,CAAC,IAAI,CACX,GAAG,CAAC,QAAQ,CAAC,MAAM,KAAK,SAAS,IAAI,QAAQ,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC;YAC/D,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,OAAO,EAAE,QAAQ,EAAE,QAAQ,CAAC,QAAQ,EAAE,CAAC;YAC3F,CAAC,CAAC,QAAQ,CAAC,EAAE,QAAQ,EAAE,CAAC,OAAO,CAAC,EAAE,EAAE,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,QAAQ,CAAC,CACvE,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,GAAG,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC;AAC5C,CAAC","sourcesContent":["import type { Viewport } from '@variance-authority/core/format';\nimport { widthsOf } from '@variance-authority/playwright';\nimport { declarationOf, PARAMETERS_KEY, type ParametersRead } from '@variance-authority/storybook';\nimport type { Plan, PlannedSubject } from './contract.js';\n\n/**\n * The story a subject id names.\n *\n * Storybook replaces every character outside `[a-z0-9-]` when it makes an id,\n * so an `@` in a subject id is always the width suffix `widthsOf` added.\n */\nexport function storyIdOf(subjectId: string): string {\n const id = subjectId.replace(/^story:/, '');\n const at = id.indexOf('@');\n return at === -1 ? id : id.slice(0, at);\n}\n\n/**\n * A story's own parameters, laid over the plan the index produced.\n *\n * The index plans every story once at the run's viewport, because tags are all\n * it carries. `read` is what the running preview says each story declared, and\n * it decides which stories are read, at what size and at how many widths. An\n * exclusion lands in `notObserved` with its reason, so a story that asked not to\n * be read is still in the report.\n */\nexport function withParameters(plan: Plan, read: ParametersRead, run: Viewport): Plan {\n if ('unread' in read) {\n return {\n ...plan,\n warnings: [\n ...plan.warnings,\n `story parameters were not read — ${read.unread}. Every story is read at the run's ` +\n `viewport, and no story's \\`parameters.${PARAMETERS_KEY}\\` applies to this run`,\n ],\n };\n }\n\n const subjects: PlannedSubject[] = [];\n const notObserved: unknown[] = [...plan.notObserved];\n\n for (const planned of plan.subjects) {\n const raw = read.parameters[storyIdOf(planned.subject.id)];\n if (raw === undefined) {\n subjects.push(planned);\n continue;\n }\n\n const declared = declarationOf(raw, run);\n if ('excluded' in declared) {\n notObserved.push({ subject: planned.subject.id, kind: 'excluded', because: declared.excluded });\n continue;\n }\n\n // The widths are laid over the story's own viewport, not the run's: a story\n // that declares a dark, 2x viewport and three widths is read dark and 2x at\n // each of them. `widthsOf` keeps a subject that already has a viewport, so\n // the declared one is handed in as the base rather than set on the subject.\n const base = declared.viewport ?? run;\n subjects.push(\n ...(declared.widths === undefined || declared.widths.length === 0\n ? [declared.viewport === undefined ? planned : { ...planned, viewport: declared.viewport }]\n : widthsOf({ subjects: [planned] }, declared.widths, base).subjects),\n );\n }\n\n return { ...plan, subjects, notObserved };\n}\n"]}
|
package/dist/read.js
CHANGED
|
@@ -3,6 +3,7 @@ import { normalize as normalizeCapture } from '@variance-authority/core/rules';
|
|
|
3
3
|
import { acquireFromAgent, unresizable } from '@variance-authority/playwright';
|
|
4
4
|
import { suspenseRefusal } from '@variance-authority/react';
|
|
5
5
|
import { collectStory, harnessPage } from '@variance-authority/storybook';
|
|
6
|
+
import { storyIdOf } from './parameters.js';
|
|
6
7
|
export async function readStory(world, reading, planned,
|
|
7
8
|
/**
|
|
8
9
|
* Absent for an isolated reading, and deliberately.
|
|
@@ -17,7 +18,7 @@ export async function readStory(world, reading, planned,
|
|
|
17
18
|
recorder) {
|
|
18
19
|
const { config, options, baseUrl, roots, source } = reading;
|
|
19
20
|
const { page, engine, network } = world;
|
|
20
|
-
const storyId = planned.subject.id
|
|
21
|
+
const storyId = storyIdOf(planned.subject.id);
|
|
21
22
|
const viewport = planned.viewport ?? config.viewport;
|
|
22
23
|
const readySelector = options.ready?.[storyId];
|
|
23
24
|
// Applied, not merely recorded. A story that declares its own viewport was
|
package/dist/read.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"read.js","sourceRoot":"","sources":["../src/read.ts"],"names":[],"mappings":"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","sourcesContent":["import type { SourceIndex } from '@variance-authority/core/attribute';\nimport { matchesGlob } from '@variance-authority/core/judge';\nimport { normalize as normalizeCapture } from '@variance-authority/core/rules';\nimport { acquireFromAgent, unresizable } from '@variance-authority/playwright';\nimport { suspenseRefusal } from '@variance-authority/react';\nimport { collectStory, harnessPage } from '@variance-authority/storybook';\nimport type { StoryRecorder } from './execution.js';\nimport type { Collected, CollectorConfig, PlannedSubject } from './contract.js';\nimport type { StorybookCollectorOptions } from './options.js';\nimport type { AcquireRequest, Acquired } from './page-agent.js';\nimport type { World } from './world.js';\n\n/**\n * One story, read out of a world — which world being the only variable.\n *\n * Split out of `index.ts` so `collect` and `collectAlone` are the *same*\n * reading. Every input to a `Collected` other than the world is in\n * {@link Reading}, computed once per collector and handed to both: the\n * viewport, the ready selector, the ignore rules narrowed to this subject, the\n * loading declaration, the wiring and holdings flags, the suspense budget, the\n * roots, the source index, the base URL and therefore every asset URL. A second\n * copy of this body would let one of them drift, and a drifted input produces a\n * difference an operator reads as pollution when it is really a different recipe.\n */\n\nexport interface Reading {\n readonly config: CollectorConfig;\n readonly options: StorybookCollectorOptions;\n\n /**\n * Where the preview is served, shared by both worlds on purpose.\n *\n * A second static server would answer on a second port, which changes every\n * asset URL, which changes the environment key — so the clean collection would\n * come back `incomparable` and the run would learn nothing about order.\n */\n readonly baseUrl: string;\n readonly roots: readonly string[];\n readonly source?: SourceIndex;\n}\n\nexport async function readStory(\n world: World,\n reading: Reading,\n planned: PlannedSubject,\n /**\n * Absent for an isolated reading, and deliberately.\n *\n * Nothing the recorder produces reaches `Collected` — it writes crossings into\n * the shared test-selection index, keyed by owner. A second window for the\n * same story out of a page that is about to be thrown away would count that\n * story's blocks twice, so a run that happened to ask about order would move\n * the selection index for the next run. The reading is identical; what is\n * skipped is a side effect on a file.\n */\n recorder?: StoryRecorder,\n): Promise<Collected> {\n const { config, options, baseUrl, roots, source } = reading;\n const { page, engine, network } = world;\n const storyId = planned.subject.id.replace(/^story:/, '');\n\n const viewport = planned.viewport ?? config.viewport;\n const readySelector = options.ready?.[storyId];\n\n // Applied, not merely recorded. A story that declares its own viewport was\n // being painted at the run's width while its environment key said otherwise —\n // a baseline whose key describes a render that never happened, and a verdict\n // over it that is green for the wrong reason. The resize precedes the mount,\n // so a component that reads `matchMedia` when it mounts reads the width it is\n // about to be shown at.\n const fixed = unresizable(viewport, config.viewport);\n if (fixed !== undefined) return { ok: false, because: fixed };\n\n const current = page.viewportSize();\n if (current?.width !== viewport.width || current?.height !== viewport.height) {\n await page.setViewportSize({ width: viewport.width, height: viewport.height });\n }\n\n const outcome = await collectStory(harnessPage({ page }), storyId, {\n baseUrl,\n ...(options.readyTimeoutMs !== undefined ? { timeoutMs: options.readyTimeoutMs } : {}),\n ...(readySelector !== undefined ? { readySelector } : {}),\n });\n\n // A story that did not render is a hole in this run's coverage, and it travels\n // as a value rather than an exception: one component that throws must not cost\n // the others their observations, and must not be silently absent either.\n if (outcome.status !== 'rendered') {\n // A story that rendered a fallback still executed code, and counters left in\n // the page would be handed to whichever story drained next.\n await recorder?.note(page, planned.subject.id, false);\n return {\n ok: false,\n because:\n `the story did not render (${outcome.status}, readiness ${outcome.readiness})` +\n (outcome.error === undefined ? '' : `: ${outcome.error.message}`),\n };\n }\n\n // After the story has mounted and before it is read. A story whose image is\n // still in flight is a story whose bytes nobody hashed, and the wait is the\n // driver's because the page cannot see its own requests. Never a failure: a\n // page that keeps fetching is reported, not refused.\n await network?.settle();\n\n // Either form matches. A subject is `story:case-surface--x` and a story is\n // `case-surface--x`, and `ready` above is keyed by the second — so a\n // declaration written the way the neighbouring option is written has to work,\n // or the escape hatch fails silently and the story it was written for is\n // refused anyway.\n const declaredLoading = (options.loading ?? []).some(\n (pattern) => matchesGlob(planned.subject.id, pattern) || matchesGlob(storyId, pattern),\n );\n\n const worn = new Set(planned.tags ?? []);\n const selectable = (config.ignore ?? []).flatMap((rule) =>\n rule.select === undefined ||\n (rule.subjects !== undefined &&\n !rule.subjects.some((pattern) => matchesGlob(planned.subject.id, pattern))) ||\n (rule.tags !== undefined && !rule.tags.some((tag) => worn.has(tag)))\n ? []\n : [{ id: rule.id, select: rule.select }],\n );\n\n const request: AcquireRequest = {\n subjectId: planned.subject.id,\n viewport,\n engine,\n ...(config.fonts !== undefined ? { fonts: config.fonts } : {}),\n // Only the rules that name a selector cross into the page. A fingerprint\n // rule has nothing for a document to resolve, and sending one would put a\n // digest in a browser that cannot use it.\n ...(selectable.length > 0 ? { ignore: selectable } : {}),\n // A story declared as a loading capture waits for nothing: the whole point\n // of it is the fallback, and paying the timeout to be told the boundary is\n // still there would cost five seconds per story to learn what the\n // declaration already said.\n ...(declaredLoading\n ? { suspense: { timeoutMs: 0 } }\n : options.suspenseTimeoutMs !== undefined\n ? { suspense: { timeoutMs: options.suspenseTimeoutMs } }\n : {}),\n // Sent only when the caller said something. The page agent holds the\n // defaults, so a bundle and a driver built from different checkouts cannot\n // disagree about what absent meant.\n ...(options.wiring !== undefined ? { wiring: options.wiring } : {}),\n ...(options.holdings !== undefined ? { holdings: options.holdings } : {}),\n roots,\n };\n\n // The page's whole observed set goes in there, narrowed inside the page to the\n // URLs this story references: the driver cannot know which of them the story\n // uses, and the page cannot know what the bytes were. Never reset between\n // stories — one page serves the whole run, and a story whose asset was fetched\n // during an earlier story still references it.\n const raw = await acquireFromAgent(page, network, request);\n\n const acquired = JSON.parse(raw) as Acquired;\n\n // The symptom of a page agent older than this driver, named rather than read\n // past: `undefined` here would make `suspenseRefusal` throw inside one story\n // and take the run with it, and defaulting it to \"settled\" would silently\n // restore the behaviour this exists to end.\n if (acquired.suspense === undefined) {\n await recorder?.note(page, planned.subject.id, false);\n return {\n ok: false,\n because:\n 'the page agent returned no Suspense reading, which means the bundle in ' +\n '`@variance-authority/storybook-collector` predates it — rebuild the package',\n };\n }\n\n // The forced decision. A story still showing a fallback is refused with the\n // boundary named, unless somebody declared that this is the subject.\n const unsettled = suspenseRefusal(acquired.suspense, {\n subjectId: planned.subject.id,\n declaredLoading,\n });\n if (unsettled !== undefined) {\n await recorder?.note(page, planned.subject.id, false);\n return { ok: false, because: unsettled };\n }\n\n await recorder?.note(page, planned.subject.id, true);\n\n // No frames are spent here, and that is deliberate: a story that settles on\n // its document digest has nobody to hand a location to. They ride the snapshot\n // as provenance — which no hash projects — and `locateSites` spends them for\n // the few nodes a region or a finding names.\n return {\n ok: true,\n document: acquired.document,\n // Normalized here rather than in the page: the ruleset is the one the jsdom\n // path uses, and running it in the browser would make the two profiles two\n // implementations of it.\n // `sourceRoot` for the same reason `scanSource` is rooted at the cwd: both\n // answers name files, and a report that mixes a repository-relative\n // declaration with an absolute call site is one nobody can paste into\n // anything.\n snapshot: normalizeCapture(acquired.capture, { sourceRoot: process.cwd() }),\n ...(acquired.stabilization !== undefined && acquired.stabilization.length > 0\n ? { stabilization: acquired.stabilization }\n : {}),\n // What the preview said about the reading, carried rather than dropped. One\n // code, because the adapter's warnings are sentences for a person and\n // inventing a taxonomy here would mean parsing them; identity includes the\n // message, so two different things said are two diagnostics.\n ...(outcome.warnings.length > 0\n ? {\n diagnostics: outcome.warnings.map((message) => ({\n severity: 'warn' as const,\n code: 'story-preview',\n message,\n })),\n }\n : {}),\n ...(source !== undefined ? { source } : {}),\n // No `causes`. Naming the roots of a change needs the *previous* snapshot,\n // and a durable run has a baseline image without one — so ranking falls back\n // to area, which `rankRegions` documents as honest and not good.\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"read.js","sourceRoot":"","sources":["../src/read.ts"],"names":[],"mappings":"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","sourcesContent":["import type { SourceIndex } from '@variance-authority/core/attribute';\nimport { matchesGlob } from '@variance-authority/core/judge';\nimport { normalize as normalizeCapture } from '@variance-authority/core/rules';\nimport { acquireFromAgent, unresizable } from '@variance-authority/playwright';\nimport { suspenseRefusal } from '@variance-authority/react';\nimport { collectStory, harnessPage } from '@variance-authority/storybook';\nimport type { StoryRecorder } from './execution.js';\nimport type { Collected, CollectorConfig, PlannedSubject } from './contract.js';\nimport { storyIdOf } from './parameters.js';\nimport type { StorybookCollectorOptions } from './options.js';\nimport type { AcquireRequest, Acquired } from './page-agent.js';\nimport type { World } from './world.js';\n\n/**\n * One story, read out of a world — which world being the only variable.\n *\n * Split out of `index.ts` so `collect` and `collectAlone` are the *same*\n * reading. Every input to a `Collected` other than the world is in\n * {@link Reading}, computed once per collector and handed to both: the\n * viewport, the ready selector, the ignore rules narrowed to this subject, the\n * loading declaration, the wiring and holdings flags, the suspense budget, the\n * roots, the source index, the base URL and therefore every asset URL. A second\n * copy of this body would let one of them drift, and a drifted input produces a\n * difference an operator reads as pollution when it is really a different recipe.\n */\n\nexport interface Reading {\n readonly config: CollectorConfig;\n readonly options: StorybookCollectorOptions;\n\n /**\n * Where the preview is served, shared by both worlds on purpose.\n *\n * A second static server would answer on a second port, which changes every\n * asset URL, which changes the environment key — so the clean collection would\n * come back `incomparable` and the run would learn nothing about order.\n */\n readonly baseUrl: string;\n readonly roots: readonly string[];\n readonly source?: SourceIndex;\n}\n\nexport async function readStory(\n world: World,\n reading: Reading,\n planned: PlannedSubject,\n /**\n * Absent for an isolated reading, and deliberately.\n *\n * Nothing the recorder produces reaches `Collected` — it writes crossings into\n * the shared test-selection index, keyed by owner. A second window for the\n * same story out of a page that is about to be thrown away would count that\n * story's blocks twice, so a run that happened to ask about order would move\n * the selection index for the next run. The reading is identical; what is\n * skipped is a side effect on a file.\n */\n recorder?: StoryRecorder,\n): Promise<Collected> {\n const { config, options, baseUrl, roots, source } = reading;\n const { page, engine, network } = world;\n const storyId = storyIdOf(planned.subject.id);\n\n const viewport = planned.viewport ?? config.viewport;\n const readySelector = options.ready?.[storyId];\n\n // Applied, not merely recorded. A story that declares its own viewport was\n // being painted at the run's width while its environment key said otherwise —\n // a baseline whose key describes a render that never happened, and a verdict\n // over it that is green for the wrong reason. The resize precedes the mount,\n // so a component that reads `matchMedia` when it mounts reads the width it is\n // about to be shown at.\n const fixed = unresizable(viewport, config.viewport);\n if (fixed !== undefined) return { ok: false, because: fixed };\n\n const current = page.viewportSize();\n if (current?.width !== viewport.width || current?.height !== viewport.height) {\n await page.setViewportSize({ width: viewport.width, height: viewport.height });\n }\n\n const outcome = await collectStory(harnessPage({ page }), storyId, {\n baseUrl,\n ...(options.readyTimeoutMs !== undefined ? { timeoutMs: options.readyTimeoutMs } : {}),\n ...(readySelector !== undefined ? { readySelector } : {}),\n });\n\n // A story that did not render is a hole in this run's coverage, and it travels\n // as a value rather than an exception: one component that throws must not cost\n // the others their observations, and must not be silently absent either.\n if (outcome.status !== 'rendered') {\n // A story that rendered a fallback still executed code, and counters left in\n // the page would be handed to whichever story drained next.\n await recorder?.note(page, planned.subject.id, false);\n return {\n ok: false,\n because:\n `the story did not render (${outcome.status}, readiness ${outcome.readiness})` +\n (outcome.error === undefined ? '' : `: ${outcome.error.message}`),\n };\n }\n\n // After the story has mounted and before it is read. A story whose image is\n // still in flight is a story whose bytes nobody hashed, and the wait is the\n // driver's because the page cannot see its own requests. Never a failure: a\n // page that keeps fetching is reported, not refused.\n await network?.settle();\n\n // Either form matches. A subject is `story:case-surface--x` and a story is\n // `case-surface--x`, and `ready` above is keyed by the second — so a\n // declaration written the way the neighbouring option is written has to work,\n // or the escape hatch fails silently and the story it was written for is\n // refused anyway.\n const declaredLoading = (options.loading ?? []).some(\n (pattern) => matchesGlob(planned.subject.id, pattern) || matchesGlob(storyId, pattern),\n );\n\n const worn = new Set(planned.tags ?? []);\n const selectable = (config.ignore ?? []).flatMap((rule) =>\n rule.select === undefined ||\n (rule.subjects !== undefined &&\n !rule.subjects.some((pattern) => matchesGlob(planned.subject.id, pattern))) ||\n (rule.tags !== undefined && !rule.tags.some((tag) => worn.has(tag)))\n ? []\n : [{ id: rule.id, select: rule.select }],\n );\n\n const request: AcquireRequest = {\n subjectId: planned.subject.id,\n viewport,\n engine,\n ...(config.fonts !== undefined ? { fonts: config.fonts } : {}),\n // Only the rules that name a selector cross into the page. A fingerprint\n // rule has nothing for a document to resolve, and sending one would put a\n // digest in a browser that cannot use it.\n ...(selectable.length > 0 ? { ignore: selectable } : {}),\n // A story declared as a loading capture waits for nothing: the whole point\n // of it is the fallback, and paying the timeout to be told the boundary is\n // still there would cost five seconds per story to learn what the\n // declaration already said.\n ...(declaredLoading\n ? { suspense: { timeoutMs: 0 } }\n : options.suspenseTimeoutMs !== undefined\n ? { suspense: { timeoutMs: options.suspenseTimeoutMs } }\n : {}),\n // Sent only when the caller said something. The page agent holds the\n // defaults, so a bundle and a driver built from different checkouts cannot\n // disagree about what absent meant.\n ...(options.wiring !== undefined ? { wiring: options.wiring } : {}),\n ...(options.holdings !== undefined ? { holdings: options.holdings } : {}),\n roots,\n };\n\n // The page's whole observed set goes in there, narrowed inside the page to the\n // URLs this story references: the driver cannot know which of them the story\n // uses, and the page cannot know what the bytes were. Never reset between\n // stories — one page serves the whole run, and a story whose asset was fetched\n // during an earlier story still references it.\n const raw = await acquireFromAgent(page, network, request);\n\n const acquired = JSON.parse(raw) as Acquired;\n\n // The symptom of a page agent older than this driver, named rather than read\n // past: `undefined` here would make `suspenseRefusal` throw inside one story\n // and take the run with it, and defaulting it to \"settled\" would silently\n // restore the behaviour this exists to end.\n if (acquired.suspense === undefined) {\n await recorder?.note(page, planned.subject.id, false);\n return {\n ok: false,\n because:\n 'the page agent returned no Suspense reading, which means the bundle in ' +\n '`@variance-authority/storybook-collector` predates it — rebuild the package',\n };\n }\n\n // The forced decision. A story still showing a fallback is refused with the\n // boundary named, unless somebody declared that this is the subject.\n const unsettled = suspenseRefusal(acquired.suspense, {\n subjectId: planned.subject.id,\n declaredLoading,\n });\n if (unsettled !== undefined) {\n await recorder?.note(page, planned.subject.id, false);\n return { ok: false, because: unsettled };\n }\n\n await recorder?.note(page, planned.subject.id, true);\n\n // No frames are spent here, and that is deliberate: a story that settles on\n // its document digest has nobody to hand a location to. They ride the snapshot\n // as provenance — which no hash projects — and `locateSites` spends them for\n // the few nodes a region or a finding names.\n return {\n ok: true,\n document: acquired.document,\n // Normalized here rather than in the page: the ruleset is the one the jsdom\n // path uses, and running it in the browser would make the two profiles two\n // implementations of it.\n // `sourceRoot` for the same reason `scanSource` is rooted at the cwd: both\n // answers name files, and a report that mixes a repository-relative\n // declaration with an absolute call site is one nobody can paste into\n // anything.\n snapshot: normalizeCapture(acquired.capture, { sourceRoot: process.cwd() }),\n ...(acquired.stabilization !== undefined && acquired.stabilization.length > 0\n ? { stabilization: acquired.stabilization }\n : {}),\n // What the preview said about the reading, carried rather than dropped. One\n // code, because the adapter's warnings are sentences for a person and\n // inventing a taxonomy here would mean parsing them; identity includes the\n // message, so two different things said are two diagnostics.\n ...(outcome.warnings.length > 0\n ? {\n diagnostics: outcome.warnings.map((message) => ({\n severity: 'warn' as const,\n code: 'story-preview',\n message,\n })),\n }\n : {}),\n ...(source !== undefined ? { source } : {}),\n // No `causes`. Naming the roots of a change needs the *previous* snapshot,\n // and a durable run has a baseline image without one — so ranking falls back\n // to area, which `rankRegions` documents as honest and not good.\n };\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@variance-authority/storybook-collector",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Turn a built or already-served Storybook into Variance Authority subjects.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"variance-authority",
|
|
@@ -40,11 +40,11 @@
|
|
|
40
40
|
"build": "tsc --build"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@variance-authority/core": "^0.
|
|
44
|
-
"@variance-authority/dom": "^0.
|
|
45
|
-
"@variance-authority/playwright": "^0.
|
|
46
|
-
"@variance-authority/react": "^0.
|
|
47
|
-
"@variance-authority/sense": "^0.
|
|
48
|
-
"@variance-authority/storybook": "^0.
|
|
43
|
+
"@variance-authority/core": "^0.10.0",
|
|
44
|
+
"@variance-authority/dom": "^0.10.0",
|
|
45
|
+
"@variance-authority/playwright": "^0.10.0",
|
|
46
|
+
"@variance-authority/react": "^0.10.0",
|
|
47
|
+
"@variance-authority/sense": "^0.10.0",
|
|
48
|
+
"@variance-authority/storybook": "^0.10.0"
|
|
49
49
|
}
|
|
50
50
|
}
|