@effinrich/forgekit-storybook-plugin 2.0.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.
@@ -0,0 +1,206 @@
1
+ interface PropInfo {
2
+ name: string;
3
+ type: string;
4
+ required: boolean;
5
+ defaultValue?: string;
6
+ description?: string;
7
+ isCallback: boolean;
8
+ unionValues?: string[];
9
+ }
10
+ interface ComponentAnalysis {
11
+ name: string;
12
+ fileName: string;
13
+ filePath: string;
14
+ props: PropInfo[];
15
+ hasChildren: boolean;
16
+ imports: ImportInfo[];
17
+ usesRouter: boolean;
18
+ usesReactQuery: boolean;
19
+ usesChakra: boolean;
20
+ exportType: 'default' | 'named' | 'both';
21
+ }
22
+ interface ImportInfo {
23
+ source: string;
24
+ specifiers: string[];
25
+ }
26
+ interface ForgeStoryOptions {
27
+ componentPath: string;
28
+ storyTitle?: string;
29
+ skipInteractionTests?: boolean;
30
+ overwrite?: boolean;
31
+ dryRun?: boolean;
32
+ quiet?: boolean;
33
+ }
34
+ interface ForgeStoriesOptions {
35
+ dir: string;
36
+ skipInteractionTests?: boolean;
37
+ overwrite?: boolean;
38
+ dryRun?: boolean;
39
+ includeA11y?: boolean;
40
+ includeComponentTests?: boolean;
41
+ quiet?: boolean;
42
+ }
43
+ interface ForgeTestOptions {
44
+ componentPath: string;
45
+ overwrite?: boolean;
46
+ dryRun?: boolean;
47
+ quiet?: boolean;
48
+ }
49
+ interface WatchOptions {
50
+ dir: string;
51
+ ignore?: string[];
52
+ debounceMs?: number;
53
+ skipInteractionTests?: boolean;
54
+ quiet?: boolean;
55
+ }
56
+ interface ScanResult {
57
+ components: ScannedComponent[];
58
+ withStories: string[];
59
+ withoutStories: string[];
60
+ notAnalyzable: string[];
61
+ total: number;
62
+ }
63
+ interface ScannedComponent {
64
+ filePath: string;
65
+ analysis: ComponentAnalysis | null;
66
+ hasStory: boolean;
67
+ }
68
+ interface CoverageReport {
69
+ covered: number;
70
+ total: number;
71
+ percentage: number;
72
+ grade: 'A' | 'B' | 'C' | 'D' | 'F';
73
+ }
74
+ interface StoryContentOptions {
75
+ analysis: ComponentAnalysis;
76
+ storyTitle: string;
77
+ importPath: string;
78
+ skipInteractionTests: boolean;
79
+ }
80
+ interface PlaywrightTestOptions {
81
+ analysis: ComponentAnalysis;
82
+ importPath: string;
83
+ hasStories: boolean;
84
+ storyImportPath?: string;
85
+ }
86
+
87
+ /**
88
+ * Analyze a React component file to extract metadata for story generation.
89
+ *
90
+ * @param filePath - Absolute or relative path to the component file
91
+ * @param content - Optional file content (reads from disk if omitted)
92
+ */
93
+ declare function analyzeComponent(filePath: string, content?: string): ComponentAnalysis | null;
94
+
95
+ /**
96
+ * Scan a directory for React components and classify them by story coverage.
97
+ */
98
+ declare function scanDirectory(dir: string): Promise<ScanResult>;
99
+
100
+ /**
101
+ * Generate the full content of a .stories.tsx file from component analysis.
102
+ */
103
+ declare function generateStoryContent(options: StoryContentOptions): string;
104
+
105
+ /**
106
+ * Generate interaction test stories (play functions) based on component analysis.
107
+ * Returns an array of lines to append to the story file.
108
+ */
109
+ declare function generateInteractionTests(analysis: ComponentAnalysis): string[];
110
+
111
+ /**
112
+ * Generate a co-located Playwright component test (.ct.tsx) that:
113
+ * - Mounts the component using @playwright/experimental-ct-react
114
+ * - Tests rendering, visual regression (screenshot), interactions, and a11y
115
+ * - Optionally imports stories for story-driven testing
116
+ */
117
+ declare function generatePlaywrightTest(options: PlaywrightTestOptions): string;
118
+
119
+ /**
120
+ * Calculate a coverage score and letter grade from covered/total counts.
121
+ */
122
+ declare function scoreCoverage(covered: number, total: number): CoverageReport;
123
+
124
+ /**
125
+ * Infer a Storybook title from a component's file path.
126
+ *
127
+ * Strategy:
128
+ * 1. Compute relative path from the target directory
129
+ * 2. Strip common prefixes: src/, lib/, components/
130
+ * 3. Convert directory segments to PascalCase
131
+ * 4. Deduplicate consecutive identical segments
132
+ *
133
+ * Examples:
134
+ * src/components/Button/Button.tsx → "Components / Button"
135
+ * src/components/forms/TextInput.tsx → "Components / Forms / TextInput"
136
+ * src/lib/ui/Modal/Modal.tsx → "UI / Modal"
137
+ * libs/shared/ui/src/lib/button/button.tsx → "Button"
138
+ */
139
+ declare function inferStoryTitle(filePath: string, baseDir?: string): string;
140
+
141
+ interface WatchHandle {
142
+ close(): Promise<void>;
143
+ }
144
+ type WatchEvent = {
145
+ type: 'ready';
146
+ } | {
147
+ type: 'generate';
148
+ file: string;
149
+ storyPath: string;
150
+ } | {
151
+ type: 'update';
152
+ file: string;
153
+ storyPath: string;
154
+ } | {
155
+ type: 'error';
156
+ file: string;
157
+ error: Error;
158
+ };
159
+ type WatchCallback = (event: WatchEvent) => void;
160
+ /**
161
+ * Watch a directory for component changes and auto-generate stories.
162
+ * Returns a handle with .close() for cleanup.
163
+ */
164
+ declare function watchDirectory(options: WatchOptions, callback?: WatchCallback): WatchHandle;
165
+
166
+ interface ForgeStoryResult {
167
+ storyPath: string;
168
+ content: string;
169
+ analysis: ComponentAnalysis;
170
+ storiesGenerated: string[];
171
+ written: boolean;
172
+ }
173
+ /**
174
+ * High-level: analyze a component, generate a story, and write it to disk.
175
+ */
176
+ declare function forgeStory(options: ForgeStoryOptions): Promise<ForgeStoryResult>;
177
+
178
+ interface ForgeStoriesResult {
179
+ generated: number;
180
+ failed: number;
181
+ alreadyCovered: number;
182
+ notAnalyzable: number;
183
+ total: number;
184
+ coverage: CoverageReport;
185
+ errors: Array<{
186
+ file: string;
187
+ error: string;
188
+ }>;
189
+ }
190
+ /**
191
+ * High-level: scan a directory, generate stories for all uncovered components.
192
+ */
193
+ declare function forgeStories(options: ForgeStoriesOptions): Promise<ForgeStoriesResult>;
194
+
195
+ interface ForgeTestResult {
196
+ testPath: string;
197
+ content: string;
198
+ analysis: ComponentAnalysis;
199
+ written: boolean;
200
+ }
201
+ /**
202
+ * High-level: analyze a component, generate a Playwright component test, and write it.
203
+ */
204
+ declare function forgeTest(options: ForgeTestOptions): Promise<ForgeTestResult>;
205
+
206
+ export { type ComponentAnalysis, type CoverageReport, type ForgeStoriesOptions, type ForgeStoriesResult, type ForgeStoryOptions, type ForgeStoryResult, type ForgeTestOptions, type ForgeTestResult, type ImportInfo, type PlaywrightTestOptions, type PropInfo, type ScanResult, type ScannedComponent, type StoryContentOptions, type WatchCallback, type WatchEvent, type WatchHandle, type WatchOptions, analyzeComponent, forgeStories, forgeStory, forgeTest, generateInteractionTests, generatePlaywrightTest, generateStoryContent, inferStoryTitle, scanDirectory, scoreCoverage, watchDirectory };
@@ -0,0 +1,206 @@
1
+ interface PropInfo {
2
+ name: string;
3
+ type: string;
4
+ required: boolean;
5
+ defaultValue?: string;
6
+ description?: string;
7
+ isCallback: boolean;
8
+ unionValues?: string[];
9
+ }
10
+ interface ComponentAnalysis {
11
+ name: string;
12
+ fileName: string;
13
+ filePath: string;
14
+ props: PropInfo[];
15
+ hasChildren: boolean;
16
+ imports: ImportInfo[];
17
+ usesRouter: boolean;
18
+ usesReactQuery: boolean;
19
+ usesChakra: boolean;
20
+ exportType: 'default' | 'named' | 'both';
21
+ }
22
+ interface ImportInfo {
23
+ source: string;
24
+ specifiers: string[];
25
+ }
26
+ interface ForgeStoryOptions {
27
+ componentPath: string;
28
+ storyTitle?: string;
29
+ skipInteractionTests?: boolean;
30
+ overwrite?: boolean;
31
+ dryRun?: boolean;
32
+ quiet?: boolean;
33
+ }
34
+ interface ForgeStoriesOptions {
35
+ dir: string;
36
+ skipInteractionTests?: boolean;
37
+ overwrite?: boolean;
38
+ dryRun?: boolean;
39
+ includeA11y?: boolean;
40
+ includeComponentTests?: boolean;
41
+ quiet?: boolean;
42
+ }
43
+ interface ForgeTestOptions {
44
+ componentPath: string;
45
+ overwrite?: boolean;
46
+ dryRun?: boolean;
47
+ quiet?: boolean;
48
+ }
49
+ interface WatchOptions {
50
+ dir: string;
51
+ ignore?: string[];
52
+ debounceMs?: number;
53
+ skipInteractionTests?: boolean;
54
+ quiet?: boolean;
55
+ }
56
+ interface ScanResult {
57
+ components: ScannedComponent[];
58
+ withStories: string[];
59
+ withoutStories: string[];
60
+ notAnalyzable: string[];
61
+ total: number;
62
+ }
63
+ interface ScannedComponent {
64
+ filePath: string;
65
+ analysis: ComponentAnalysis | null;
66
+ hasStory: boolean;
67
+ }
68
+ interface CoverageReport {
69
+ covered: number;
70
+ total: number;
71
+ percentage: number;
72
+ grade: 'A' | 'B' | 'C' | 'D' | 'F';
73
+ }
74
+ interface StoryContentOptions {
75
+ analysis: ComponentAnalysis;
76
+ storyTitle: string;
77
+ importPath: string;
78
+ skipInteractionTests: boolean;
79
+ }
80
+ interface PlaywrightTestOptions {
81
+ analysis: ComponentAnalysis;
82
+ importPath: string;
83
+ hasStories: boolean;
84
+ storyImportPath?: string;
85
+ }
86
+
87
+ /**
88
+ * Analyze a React component file to extract metadata for story generation.
89
+ *
90
+ * @param filePath - Absolute or relative path to the component file
91
+ * @param content - Optional file content (reads from disk if omitted)
92
+ */
93
+ declare function analyzeComponent(filePath: string, content?: string): ComponentAnalysis | null;
94
+
95
+ /**
96
+ * Scan a directory for React components and classify them by story coverage.
97
+ */
98
+ declare function scanDirectory(dir: string): Promise<ScanResult>;
99
+
100
+ /**
101
+ * Generate the full content of a .stories.tsx file from component analysis.
102
+ */
103
+ declare function generateStoryContent(options: StoryContentOptions): string;
104
+
105
+ /**
106
+ * Generate interaction test stories (play functions) based on component analysis.
107
+ * Returns an array of lines to append to the story file.
108
+ */
109
+ declare function generateInteractionTests(analysis: ComponentAnalysis): string[];
110
+
111
+ /**
112
+ * Generate a co-located Playwright component test (.ct.tsx) that:
113
+ * - Mounts the component using @playwright/experimental-ct-react
114
+ * - Tests rendering, visual regression (screenshot), interactions, and a11y
115
+ * - Optionally imports stories for story-driven testing
116
+ */
117
+ declare function generatePlaywrightTest(options: PlaywrightTestOptions): string;
118
+
119
+ /**
120
+ * Calculate a coverage score and letter grade from covered/total counts.
121
+ */
122
+ declare function scoreCoverage(covered: number, total: number): CoverageReport;
123
+
124
+ /**
125
+ * Infer a Storybook title from a component's file path.
126
+ *
127
+ * Strategy:
128
+ * 1. Compute relative path from the target directory
129
+ * 2. Strip common prefixes: src/, lib/, components/
130
+ * 3. Convert directory segments to PascalCase
131
+ * 4. Deduplicate consecutive identical segments
132
+ *
133
+ * Examples:
134
+ * src/components/Button/Button.tsx → "Components / Button"
135
+ * src/components/forms/TextInput.tsx → "Components / Forms / TextInput"
136
+ * src/lib/ui/Modal/Modal.tsx → "UI / Modal"
137
+ * libs/shared/ui/src/lib/button/button.tsx → "Button"
138
+ */
139
+ declare function inferStoryTitle(filePath: string, baseDir?: string): string;
140
+
141
+ interface WatchHandle {
142
+ close(): Promise<void>;
143
+ }
144
+ type WatchEvent = {
145
+ type: 'ready';
146
+ } | {
147
+ type: 'generate';
148
+ file: string;
149
+ storyPath: string;
150
+ } | {
151
+ type: 'update';
152
+ file: string;
153
+ storyPath: string;
154
+ } | {
155
+ type: 'error';
156
+ file: string;
157
+ error: Error;
158
+ };
159
+ type WatchCallback = (event: WatchEvent) => void;
160
+ /**
161
+ * Watch a directory for component changes and auto-generate stories.
162
+ * Returns a handle with .close() for cleanup.
163
+ */
164
+ declare function watchDirectory(options: WatchOptions, callback?: WatchCallback): WatchHandle;
165
+
166
+ interface ForgeStoryResult {
167
+ storyPath: string;
168
+ content: string;
169
+ analysis: ComponentAnalysis;
170
+ storiesGenerated: string[];
171
+ written: boolean;
172
+ }
173
+ /**
174
+ * High-level: analyze a component, generate a story, and write it to disk.
175
+ */
176
+ declare function forgeStory(options: ForgeStoryOptions): Promise<ForgeStoryResult>;
177
+
178
+ interface ForgeStoriesResult {
179
+ generated: number;
180
+ failed: number;
181
+ alreadyCovered: number;
182
+ notAnalyzable: number;
183
+ total: number;
184
+ coverage: CoverageReport;
185
+ errors: Array<{
186
+ file: string;
187
+ error: string;
188
+ }>;
189
+ }
190
+ /**
191
+ * High-level: scan a directory, generate stories for all uncovered components.
192
+ */
193
+ declare function forgeStories(options: ForgeStoriesOptions): Promise<ForgeStoriesResult>;
194
+
195
+ interface ForgeTestResult {
196
+ testPath: string;
197
+ content: string;
198
+ analysis: ComponentAnalysis;
199
+ written: boolean;
200
+ }
201
+ /**
202
+ * High-level: analyze a component, generate a Playwright component test, and write it.
203
+ */
204
+ declare function forgeTest(options: ForgeTestOptions): Promise<ForgeTestResult>;
205
+
206
+ export { type ComponentAnalysis, type CoverageReport, type ForgeStoriesOptions, type ForgeStoriesResult, type ForgeStoryOptions, type ForgeStoryResult, type ForgeTestOptions, type ForgeTestResult, type ImportInfo, type PlaywrightTestOptions, type PropInfo, type ScanResult, type ScannedComponent, type StoryContentOptions, type WatchCallback, type WatchEvent, type WatchHandle, type WatchOptions, analyzeComponent, forgeStories, forgeStory, forgeTest, generateInteractionTests, generatePlaywrightTest, generateStoryContent, inferStoryTitle, scanDirectory, scoreCoverage, watchDirectory };
package/dist/index.js ADDED
@@ -0,0 +1,29 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
+
3
+
4
+
5
+
6
+
7
+
8
+
9
+
10
+ var _chunkT4UFXGMCjs = require('./chunk-T4UFXGMC.js');
11
+
12
+
13
+
14
+
15
+ var _chunkWUKJNZOFjs = require('./chunk-WUKJNZOF.js');
16
+
17
+
18
+
19
+
20
+
21
+
22
+
23
+
24
+
25
+
26
+
27
+
28
+ exports.analyzeComponent = _chunkWUKJNZOFjs.analyzeComponent; exports.forgeStories = _chunkT4UFXGMCjs.forgeStories; exports.forgeStory = _chunkT4UFXGMCjs.forgeStory; exports.forgeTest = _chunkWUKJNZOFjs.forgeTest; exports.generateInteractionTests = _chunkT4UFXGMCjs.generateInteractionTests; exports.generatePlaywrightTest = _chunkWUKJNZOFjs.generatePlaywrightTest; exports.generateStoryContent = _chunkT4UFXGMCjs.generateStoryContent; exports.inferStoryTitle = _chunkT4UFXGMCjs.inferStoryTitle; exports.scanDirectory = _chunkT4UFXGMCjs.scanDirectory; exports.scoreCoverage = _chunkT4UFXGMCjs.scoreCoverage; exports.watchDirectory = _chunkT4UFXGMCjs.watchDirectory;
29
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/richtillman/Documents/GitHub/forgekit-storybook-plugin-1/dist/index.js"],"names":[],"mappings":"AAAA;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,sDAA4B;AAC5B;AACE;AACA;AACA;AACF,sDAA4B;AAC5B;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,ypBAAC","file":"/Users/richtillman/Documents/GitHub/forgekit-storybook-plugin-1/dist/index.js"}
package/dist/index.mjs ADDED
@@ -0,0 +1,29 @@
1
+ import {
2
+ forgeStories,
3
+ forgeStory,
4
+ generateInteractionTests,
5
+ generateStoryContent,
6
+ inferStoryTitle,
7
+ scanDirectory,
8
+ scoreCoverage,
9
+ watchDirectory
10
+ } from "./chunk-C2HX5UGS.mjs";
11
+ import {
12
+ analyzeComponent,
13
+ forgeTest,
14
+ generatePlaywrightTest
15
+ } from "./chunk-D2RQPIRR.mjs";
16
+ export {
17
+ analyzeComponent,
18
+ forgeStories,
19
+ forgeStory,
20
+ forgeTest,
21
+ generateInteractionTests,
22
+ generatePlaywrightTest,
23
+ generateStoryContent,
24
+ inferStoryTitle,
25
+ scanDirectory,
26
+ scoreCoverage,
27
+ watchDirectory
28
+ };
29
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/package.json ADDED
@@ -0,0 +1,81 @@
1
+ {
2
+ "name": "@effinrich/forgekit-storybook-plugin",
3
+ "version": "2.0.0",
4
+ "description": "Auto-generate Storybook stories, interaction tests, Playwright component tests, and accessibility audits from React component analysis",
5
+ "author": "Rich Tillman",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/effinrich/forgekit-storybook-plugin"
10
+ },
11
+ "keywords": [
12
+ "storybook",
13
+ "react",
14
+ "testing",
15
+ "code-generation",
16
+ "interaction-tests",
17
+ "playwright",
18
+ "accessibility",
19
+ "a11y",
20
+ "story-generation"
21
+ ],
22
+ "bin": {
23
+ "forgekit-storybook-plugin": "./bin/forgekit.js"
24
+ },
25
+ "main": "./dist/index.js",
26
+ "module": "./dist/index.mjs",
27
+ "types": "./dist/index.d.ts",
28
+ "exports": {
29
+ ".": {
30
+ "types": "./dist/index.d.ts",
31
+ "import": "./dist/index.mjs",
32
+ "require": "./dist/index.js"
33
+ }
34
+ },
35
+ "files": [
36
+ "dist",
37
+ "bin"
38
+ ],
39
+ "engines": {
40
+ "node": ">=18.17.1"
41
+ },
42
+ "scripts": {
43
+ "build": "tsup",
44
+ "dev": "tsup --watch",
45
+ "test": "vitest run",
46
+ "test:watch": "vitest",
47
+ "lint": "tsc --noEmit",
48
+ "prepublishOnly": "npm run build",
49
+ "typecheck": "npx tsc --noEmit",
50
+ "release": "commit-and-tag-version",
51
+ "release:minor": "commit-and-tag-version --release-as minor",
52
+ "release:major": "commit-and-tag-version --release-as major",
53
+ "postrelease": "git push --follow-tags origin main && npm publish --access public"
54
+ },
55
+ "dependencies": {
56
+ "chalk": "^5.4.1",
57
+ "chokidar": "^4.0.3",
58
+ "fast-glob": "^3.3.3",
59
+ "yargs": "^17.7.2"
60
+ },
61
+ "devDependencies": {
62
+ "@types/node": "^22.13.0",
63
+ "@types/yargs": "^17.0.33",
64
+ "commit-and-tag-version": "^12.7.1",
65
+ "tsup": "^8.4.0",
66
+ "typescript": "^5.7.3",
67
+ "vitest": "^3.0.0"
68
+ },
69
+ "peerDependencies": {
70
+ "storybook": ">=8.0.0",
71
+ "typescript": ">=5.0.0"
72
+ },
73
+ "peerDependenciesMeta": {
74
+ "storybook": {
75
+ "optional": true
76
+ },
77
+ "typescript": {
78
+ "optional": true
79
+ }
80
+ }
81
+ }