@crvy/strybk 0.0.1 → 0.0.3
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 +47 -0
- package/README.md +16 -4
- package/dist/src/cli.js +13 -6
- package/dist/src/config.d.ts +0 -6
- package/dist/src/generate/discover.d.ts +3 -2
- package/dist/src/generate/discover.js +4 -11
- package/dist/src/generate/index.d.ts +2 -1
- package/dist/src/generate/index.js +17 -6
- package/dist/src/generate/render.d.ts +0 -2
- package/dist/src/generate/render.js +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/playwright/index.d.ts +4 -1
- package/dist/src/playwright/index.js +4 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -5,4 +5,51 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.0.3] - 2026-06-18
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Match stories by importPath and resolve globs via configDir
|
|
13
|
+
## [0.0.2] - 2026-06-11
|
|
14
|
+
## [0.0.1] - 2026-06-11
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- Add lean config and generator core
|
|
19
|
+
- Support creevey skip metadata extraction
|
|
20
|
+
- Add Storybook channel driver and switchStory helper
|
|
21
|
+
- Add shared-page Playwright fixtures
|
|
22
|
+
- Add strybk CLI
|
|
23
|
+
- Simplify generated spec imports to use @crvy/strybk directly
|
|
24
|
+
|
|
25
|
+
### Documentation
|
|
26
|
+
|
|
27
|
+
- Correct Yarn 4 local link instructions
|
|
28
|
+
- Add minimal config example and improve fetch error message
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
|
|
32
|
+
- Default generated region marker in renderer
|
|
33
|
+
- Harden creevey metadata filtering
|
|
34
|
+
- Support meta-level creevey skip
|
|
35
|
+
- Harden Storybook channel driver
|
|
36
|
+
- Keep switch timeout through font readiness
|
|
37
|
+
- Preserve sharedPage fixture typing
|
|
38
|
+
- Hide internal worker page fixture
|
|
39
|
+
- Preserve built-in playwright fixture types
|
|
40
|
+
- Harden linked playwright runtime and generation
|
|
41
|
+
|
|
42
|
+
### Miscellaneous
|
|
43
|
+
|
|
44
|
+
- Bootstrap strybk package
|
|
45
|
+
- Align strybk bootstrap with plan
|
|
46
|
+
- Fix strybk bin entry
|
|
47
|
+
- Make strybk bootstrap manifest honest
|
|
48
|
+
- Tighten strybk bootstrap packaging
|
|
49
|
+
- Ignore strybk build artifacts
|
|
50
|
+
- Sync strybk lockfile
|
|
51
|
+
- Build strybk before packing
|
|
52
|
+
- Sync current workspace
|
|
53
|
+
- Align publish setup with crvy-rprtr
|
|
54
|
+
- Bump version to 0.0.1 and update bin field format
|
|
8
55
|
## [Unreleased]
|
package/README.md
CHANGED
|
@@ -25,17 +25,29 @@ bun run check
|
|
|
25
25
|
Generate screenshot specs from a Storybook index:
|
|
26
26
|
|
|
27
27
|
```sh
|
|
28
|
-
strybk generate --config ./strybk.config.ts
|
|
28
|
+
crvy-strybk generate --config ./strybk.config.ts
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
Use `--dry-run` to compute outputs without writing files:
|
|
32
32
|
|
|
33
33
|
```sh
|
|
34
|
-
strybk generate --config ./strybk.config.ts --dry-run
|
|
34
|
+
crvy-strybk generate --config ./strybk.config.ts --dry-run
|
|
35
35
|
```
|
|
36
36
|
|
|
37
37
|
The config module should export a `StrybkConfig` object, typically as the default export from `defineConfig(...)`.
|
|
38
38
|
|
|
39
|
+
Minimal `strybk.config.ts`:
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import { defineConfig } from "@crvy/strybk";
|
|
43
|
+
|
|
44
|
+
export default defineConfig({
|
|
45
|
+
storybookUrl: "http://localhost:6006",
|
|
46
|
+
storyGlobs: ["src/**/*.stories.tsx"],
|
|
47
|
+
resolveSpecPath: ({ storyFilePath }) => storyFilePath.replace(/\.stories\.tsx$/, ".spec.ts"),
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
39
51
|
## Changelog
|
|
40
52
|
|
|
41
53
|
Preview the next changelog entry:
|
|
@@ -69,7 +81,7 @@ Link it into the consumer project:
|
|
|
69
81
|
|
|
70
82
|
```sh
|
|
71
83
|
cd /path/to/consumer-project
|
|
72
|
-
bun link strybk
|
|
84
|
+
bun link @crvy/strybk
|
|
73
85
|
```
|
|
74
86
|
|
|
75
87
|
If you want to persist the link in the consumer's manifest, Bun supports a `link:` dependency entry:
|
|
@@ -77,7 +89,7 @@ If you want to persist the link in the consumer's manifest, Bun supports a `link
|
|
|
77
89
|
```json
|
|
78
90
|
{
|
|
79
91
|
"dependencies": {
|
|
80
|
-
"strybk": "link:strybk"
|
|
92
|
+
"@crvy/strybk": "link:strybk"
|
|
81
93
|
}
|
|
82
94
|
}
|
|
83
95
|
```
|
package/dist/src/cli.js
CHANGED
|
@@ -32,8 +32,7 @@ const resolveConfigExport = (moduleNamespace) => {
|
|
|
32
32
|
const isStrybkConfig = (value) => isRecord(value) &&
|
|
33
33
|
typeof value.storybookUrl === "string" &&
|
|
34
34
|
Array.isArray(value.storyGlobs) &&
|
|
35
|
-
typeof value.resolveSpecPath === "function"
|
|
36
|
-
typeof value.resolveHarnessImports === "function";
|
|
35
|
+
typeof value.resolveSpecPath === "function";
|
|
37
36
|
const getIndexEntriesRecord = (payload) => {
|
|
38
37
|
if (!isRecord(payload)) {
|
|
39
38
|
throw new Error("Storybook index response must be an object");
|
|
@@ -54,14 +53,15 @@ const toStoryIndexEntry = (value) => {
|
|
|
54
53
|
}
|
|
55
54
|
if (typeof value.id !== "string" ||
|
|
56
55
|
typeof value.title !== "string" ||
|
|
57
|
-
typeof value.name !== "string"
|
|
56
|
+
typeof value.name !== "string" ||
|
|
57
|
+
typeof value.importPath !== "string") {
|
|
58
58
|
return null;
|
|
59
59
|
}
|
|
60
60
|
return {
|
|
61
61
|
id: value.id,
|
|
62
62
|
title: value.title,
|
|
63
63
|
name: value.name,
|
|
64
|
-
importPath:
|
|
64
|
+
importPath: value.importPath,
|
|
65
65
|
exportName: typeof value.exportName === "string" ? value.exportName : undefined,
|
|
66
66
|
};
|
|
67
67
|
};
|
|
@@ -77,9 +77,15 @@ const loadConfig = async (configPath) => {
|
|
|
77
77
|
};
|
|
78
78
|
const fetchStoryIndex = async (config) => {
|
|
79
79
|
const indexUrl = resolveStorybookIndexUrl(config.storybookUrl);
|
|
80
|
-
|
|
80
|
+
let response;
|
|
81
|
+
try {
|
|
82
|
+
response = await fetch(indexUrl);
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
throw new Error(`Failed to fetch Storybook index at ${indexUrl.toString()} — is Storybook running?`, { cause: error });
|
|
86
|
+
}
|
|
81
87
|
if (!response.ok) {
|
|
82
|
-
throw new Error(`Failed to fetch ${indexUrl.toString()}: ${response.status} ${response.statusText}`.trim());
|
|
88
|
+
throw new Error(`Failed to fetch Storybook index at ${indexUrl.toString()}: ${response.status} ${response.statusText}`.trim());
|
|
83
89
|
}
|
|
84
90
|
const payload = asUnknown(await response.json());
|
|
85
91
|
const entries = getIndexEntriesRecord(payload);
|
|
@@ -139,6 +145,7 @@ export async function runCli(argv) {
|
|
|
139
145
|
const indexEntries = await fetchStoryIndex(config);
|
|
140
146
|
const outputs = await generateScreenshots({
|
|
141
147
|
config,
|
|
148
|
+
configDir: dirname(resolve(cliArgs.configPath)),
|
|
142
149
|
indexEntries,
|
|
143
150
|
readExistingFile,
|
|
144
151
|
});
|
package/dist/src/config.d.ts
CHANGED
|
@@ -7,12 +7,6 @@ export interface StrybkConfig {
|
|
|
7
7
|
resolveSpecPath: (args: {
|
|
8
8
|
storyFilePath: string;
|
|
9
9
|
}) => string;
|
|
10
|
-
resolveHarnessImports: (args: {
|
|
11
|
-
outputPath: string;
|
|
12
|
-
}) => {
|
|
13
|
-
fixturesImport: string;
|
|
14
|
-
switchStoryImport: string;
|
|
15
|
-
};
|
|
16
10
|
generatedRegionName?: string;
|
|
17
11
|
deleteOrphans?: boolean;
|
|
18
12
|
metadataExtractors?: "creevey"[];
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export interface StoryFile {
|
|
2
2
|
filePath: string;
|
|
3
|
-
title: string;
|
|
4
3
|
}
|
|
5
|
-
export declare function discoverStoryFiles(patterns: string[]
|
|
4
|
+
export declare function discoverStoryFiles(patterns: string[], options?: {
|
|
5
|
+
cwd?: string;
|
|
6
|
+
}): Promise<StoryFile[]>;
|
|
@@ -1,13 +1,6 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
1
|
import { glob } from "glob";
|
|
3
|
-
export async function discoverStoryFiles(patterns) {
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const titleMatch = content.match(/title:\s*['"]([^'"]+)['"]/u);
|
|
8
|
-
if (!titleMatch) {
|
|
9
|
-
return [];
|
|
10
|
-
}
|
|
11
|
-
return [{ filePath, title: titleMatch[1] }];
|
|
12
|
-
});
|
|
2
|
+
export async function discoverStoryFiles(patterns, options = {}) {
|
|
3
|
+
const cwd = options.cwd ?? process.cwd();
|
|
4
|
+
const files = await glob(patterns, { absolute: true, cwd });
|
|
5
|
+
return files.map((filePath) => ({ filePath }));
|
|
13
6
|
}
|
|
@@ -3,13 +3,14 @@ export interface StoryIndexEntry {
|
|
|
3
3
|
id: string;
|
|
4
4
|
title: string;
|
|
5
5
|
name: string;
|
|
6
|
-
importPath
|
|
6
|
+
importPath: string;
|
|
7
7
|
exportName?: string;
|
|
8
8
|
}
|
|
9
9
|
export declare function generateScreenshots(args: {
|
|
10
10
|
config: StrybkConfig;
|
|
11
11
|
indexEntries: StoryIndexEntry[];
|
|
12
12
|
readExistingFile?: (filePath: string) => string | null;
|
|
13
|
+
configDir?: string;
|
|
13
14
|
}): Promise<Array<{
|
|
14
15
|
outputPath: string;
|
|
15
16
|
content: string;
|
|
@@ -2,6 +2,16 @@ import { readFileSync } from "node:fs";
|
|
|
2
2
|
import { discoverStoryFiles } from "./discover.js";
|
|
3
3
|
import { extractCreeveyMetadata, FILE_POLICY_KEY } from "./metadata.js";
|
|
4
4
|
import { renderScreenshotSpec } from "./render.js";
|
|
5
|
+
const normalizePath = (value) => value.replace(/\\/gu, "/").replace(/^\.\//u, "");
|
|
6
|
+
const matchesByImportPath = (filePath, importPath) => {
|
|
7
|
+
const normalizedImport = normalizePath(importPath);
|
|
8
|
+
const normalizedFile = normalizePath(filePath);
|
|
9
|
+
return normalizedFile === normalizedImport || normalizedFile.endsWith(`/${normalizedImport}`);
|
|
10
|
+
};
|
|
11
|
+
const resolveStoryTitle = (storyFile, indexEntries) => {
|
|
12
|
+
const pathMatch = indexEntries.find((entry) => matchesByImportPath(storyFile.filePath, entry.importPath));
|
|
13
|
+
return pathMatch?.title ?? null;
|
|
14
|
+
};
|
|
5
15
|
const escapeForRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
6
16
|
const toStoryIdSegment = (value) => value
|
|
7
17
|
.replace(/([a-z0-9])([A-Z])/gu, "$1-$2")
|
|
@@ -16,12 +26,16 @@ const isStorySkipped = (story, storyMetadata) => {
|
|
|
16
26
|
return Object.entries(storyMetadata).some(([exportName, policy]) => policy.skip === true && toStoryIdSegment(exportName) === storyIdSegment);
|
|
17
27
|
};
|
|
18
28
|
export async function generateScreenshots(args) {
|
|
19
|
-
const storyFiles = await discoverStoryFiles(args.config.storyGlobs);
|
|
29
|
+
const storyFiles = await discoverStoryFiles(args.config.storyGlobs, args.configDir === undefined ? {} : { cwd: args.configDir });
|
|
20
30
|
const generatedRegionName = args.config.generatedRegionName ?? "auto-screenshots";
|
|
21
31
|
const manualRegionPattern = new RegExp(`// @generated-end ${escapeForRegExp(generatedRegionName)}\\s*([\\s\\S]*)$`, "u");
|
|
22
32
|
const shouldExtractCreeveyMetadata = args.config.metadataExtractors?.includes("creevey") ?? false;
|
|
23
33
|
return storyFiles.flatMap((storyFile) => {
|
|
24
|
-
const
|
|
34
|
+
const title = resolveStoryTitle(storyFile, args.indexEntries);
|
|
35
|
+
if (title === null) {
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
const stories = args.indexEntries.filter((entry) => entry.title === title);
|
|
25
39
|
const storyMetadata = shouldExtractCreeveyMetadata
|
|
26
40
|
? extractCreeveyMetadata(readFileSync(storyFile.filePath, "utf8"))
|
|
27
41
|
: {};
|
|
@@ -35,15 +49,12 @@ export async function generateScreenshots(args) {
|
|
|
35
49
|
if (filteredStories.length === 0 && manualRegion.length === 0) {
|
|
36
50
|
return [];
|
|
37
51
|
}
|
|
38
|
-
const harnessImports = args.config.resolveHarnessImports({ outputPath });
|
|
39
52
|
return [
|
|
40
53
|
{
|
|
41
54
|
outputPath,
|
|
42
55
|
content: renderScreenshotSpec({
|
|
43
56
|
config: args.config,
|
|
44
|
-
|
|
45
|
-
switchStoryImport: harnessImports.switchStoryImport,
|
|
46
|
-
title: storyFile.title,
|
|
57
|
+
title,
|
|
47
58
|
stories: filteredStories,
|
|
48
59
|
manualRegion,
|
|
49
60
|
}),
|
|
@@ -4,5 +4,5 @@ export function renderScreenshotSpec(args) {
|
|
|
4
4
|
const tests = args.stories
|
|
5
5
|
.map((story) => ` test('${escapeSingleQuotes(story.name)}', async ({ sharedPage }) => {\n await switchStory(sharedPage, '${story.id}');\n await expect(sharedPage).toHaveScreenshot();\n });`)
|
|
6
6
|
.join("\n\n");
|
|
7
|
-
return `import { test, expect
|
|
7
|
+
return `import { test, expect, switchStory } from '@crvy/strybk';\n\n// @generated-begin ${generatedRegionName}\ntest.describe('${escapeSingleQuotes(args.title)}', () => {\n${tests}\n});\n// @generated-end ${generatedRegionName}\n\n${args.manualRegion}`;
|
|
8
8
|
}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { defineConfig } from "./config.js";
|
|
2
2
|
export type { StrybkConfig, StorybookGlobals } from "./config.js";
|
|
3
3
|
export { generateScreenshots } from "./generate/index.js";
|
|
4
|
-
export {
|
|
4
|
+
export { test, expect, switchStory } from "./playwright/index.js";
|
package/dist/src/index.js
CHANGED
|
@@ -1,2 +1,5 @@
|
|
|
1
|
-
export { createStrybkFixtures } from "./fixtures.js";
|
|
2
1
|
export { switchStory } from "./switchStory.js";
|
|
2
|
+
export declare const test: import("playwright/test").TestType<import("playwright/test").PlaywrightTestArgs & import("playwright/test").PlaywrightTestOptions & {
|
|
3
|
+
sharedPage: import("playwright-core").Page;
|
|
4
|
+
}, import("playwright/test").PlaywrightWorkerArgs & import("playwright/test").PlaywrightWorkerOptions>;
|
|
5
|
+
export declare const expect: import("playwright/test").Expect<{}>;
|
|
@@ -1,2 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
import { createStrybkFixtures } from "./fixtures.js";
|
|
2
2
|
export { switchStory } from "./switchStory.js";
|
|
3
|
+
const fixtures = createStrybkFixtures();
|
|
4
|
+
export const test = fixtures.test;
|
|
5
|
+
export const expect = fixtures.expect;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crvy/strybk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "Generator-first Playwright screenshot testing for Storybook.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"crvy",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"url": "git+https://github.com/creevey/strybk.git"
|
|
21
21
|
},
|
|
22
22
|
"bin": {
|
|
23
|
-
"strybk": "./dist/src/cli.js"
|
|
23
|
+
"crvy-strybk": "./dist/src/cli.js"
|
|
24
24
|
},
|
|
25
25
|
"files": [
|
|
26
26
|
"dist/",
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
},
|
|
90
90
|
"engines": {
|
|
91
91
|
"bun": ">=1.3.0",
|
|
92
|
-
"node": ">=
|
|
92
|
+
"node": ">=22"
|
|
93
93
|
},
|
|
94
94
|
"packageManager": "bun@1.3.13"
|
|
95
95
|
}
|