@variance-authority/storybook-collector 0.5.10 → 0.6.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 +20 -0
- package/dist/execution.d.ts +4 -1
- package/dist/execution.js +2 -2
- package/dist/execution.js.map +1 -1
- package/package.json +7 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @variance-authority/storybook-collector
|
|
2
2
|
|
|
3
|
+
## 0.6.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 16f6dd6: Recorded file names are relative to the repository root
|
|
8
|
+
|
|
9
|
+
The Jest, Vitest and Rstest wrappers, the Playwright reporter and the Storybook
|
|
10
|
+
collector now name every file relative to the root of the git checkout the run
|
|
11
|
+
starts in. Before, when the config was inside a package, file names were
|
|
12
|
+
relative to Jest's `rootDir` or Vitest's `root`. A recording made from
|
|
13
|
+
`packages/cart` said `src/cart.ts` where a diff says `packages/cart/src/cart.ts`,
|
|
14
|
+
and the two never matched. `rootDir` and `root` still resolve the config and the
|
|
15
|
+
relative paths in its options. Outside a git checkout, names are relative to the
|
|
16
|
+
directory the run starts in.
|
|
17
|
+
|
|
18
|
+
A recording that an earlier version made from a package-level config uses the
|
|
19
|
+
old names, so record it again. `repositoryRoot`, exported from
|
|
20
|
+
`@variance-authority/sense/test-selection`, returns the directory that file
|
|
21
|
+
names are relative to.
|
|
22
|
+
|
|
3
23
|
## 0.5.10
|
|
4
24
|
|
|
5
25
|
Lockstep release — nothing in this package changed. Every `@variance-authority/*` package shares one version.
|
package/dist/execution.d.ts
CHANGED
|
@@ -15,7 +15,10 @@
|
|
|
15
15
|
import { type EvaluatingPage, type InstrumentMode } from '@variance-authority/sense/journal';
|
|
16
16
|
/** Where a story run's execution evidence is joined, read and kept. */
|
|
17
17
|
export interface StoryExecutionOptions {
|
|
18
|
-
/**
|
|
18
|
+
/**
|
|
19
|
+
* A directory inside the repository; defaults to the cwd. Recorded paths are
|
|
20
|
+
* relative to the checkout it sits in, never to it.
|
|
21
|
+
*/
|
|
19
22
|
readonly root?: string;
|
|
20
23
|
/** Matches the `label` the build's `testSelectionProbes()` used. Defaults to `build`. */
|
|
21
24
|
readonly label?: string;
|
package/dist/execution.js
CHANGED
|
@@ -15,9 +15,9 @@
|
|
|
15
15
|
import { readFile } from 'node:fs/promises';
|
|
16
16
|
import { drainExecution, preconditionOf, recordExecution, } from '@variance-authority/sense/journal';
|
|
17
17
|
import { parseStoryIndex } from '@variance-authority/storybook';
|
|
18
|
-
import {
|
|
18
|
+
import { repositoryRoot } from '@variance-authority/sense/test-selection';
|
|
19
19
|
export async function createStoryRecorder(index, options) {
|
|
20
|
-
const root =
|
|
20
|
+
const root = repositoryRoot(options.root ?? process.cwd());
|
|
21
21
|
const storyFiles = await storyFilesFrom(index);
|
|
22
22
|
const observed = [];
|
|
23
23
|
const cases = [];
|
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 {\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 { resolve } from 'node:path';\n\n/** Where a story run's execution evidence is joined, read and kept. */\nexport interface StoryExecutionOptions {\n /** Repository root the recorded paths are relative to. Defaults to the cwd. */\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 user cache. */\n readonly cacheRoot?: string;\n /** Coverage index. Defaults to the repository-keyed cache the runner seams share. */\n readonly coverageFile?: string;\n /**\n * Also write the execution index: which individual story entered which region.\n *\n * Off by default, and the default is the point. The index is what answers\n * *which stories walk this branch* — the question `variance covering` and\n * `@variance-authority/distill` are asked, and the one a component library\n * with a few hundred stories is worth asking. It is a row per story per\n * region either way, so a preview that indexes ten thousand stories to answer\n * a question nobody asks has bought a large file and nothing else.\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 an async scope and a custom\n * runner for is already here.\n */\n readonly cases?: boolean;\n /** Where that index goes. Defaults beside the snapshot, as the Vitest seam's does. */\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 root = resolve(options.root ?? process.cwd());\n const storyFiles = await storyFilesFrom(index);\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 (options.cases === true && story !== undefined) {\n cases.push({ file: story.file, name: `${story.title}/${story.name}`, id: storyId, 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 ...(options.coverageFile === undefined ? {} : { coverageFile: 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: 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 */\nasync function storyFilesFrom(index: string): 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 { file: story.importPath, title: story.title, name: story.name },\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 {\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.\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 user cache. */\n readonly cacheRoot?: string;\n /** Coverage index. Defaults to the repository-keyed cache the runner seams share. */\n readonly coverageFile?: string;\n /**\n * Also write the execution index: which individual story entered which region.\n *\n * Off by default, and the default is the point. The index is what answers\n * *which stories walk this branch* — the question `variance covering` and\n * `@variance-authority/distill` are asked, and the one a component library\n * with a few hundred stories is worth asking. It is a row per story per\n * region either way, so a preview that indexes ten thousand stories to answer\n * a question nobody asks has bought a large file and nothing else.\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 an async scope and a custom\n * runner for is already here.\n */\n readonly cases?: boolean;\n /** Where that index goes. Defaults beside the snapshot, as the Vitest seam's does. */\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 root = repositoryRoot(options.root ?? process.cwd());\n const storyFiles = await storyFilesFrom(index);\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 (options.cases === true && story !== undefined) {\n cases.push({ file: story.file, name: `${story.title}/${story.name}`, id: storyId, 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 ...(options.coverageFile === undefined ? {} : { coverageFile: 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: 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 */\nasync function storyFilesFrom(index: string): 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 { file: story.importPath, title: story.title, name: story.name },\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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@variance-authority/storybook-collector",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.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.6.0",
|
|
44
|
+
"@variance-authority/dom": "^0.6.0",
|
|
45
|
+
"@variance-authority/playwright": "^0.6.0",
|
|
46
|
+
"@variance-authority/react": "^0.6.0",
|
|
47
|
+
"@variance-authority/sense": "^0.6.0",
|
|
48
|
+
"@variance-authority/storybook": "^0.6.0"
|
|
49
49
|
}
|
|
50
50
|
}
|