storybook-onbook-plugin 0.2.1 → 0.2.2
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/package.json +4 -2
- package/dist/cli/generate-screenshots.d.ts +0 -3
- package/dist/cli/generate-screenshots.d.ts.map +0 -1
- package/dist/cli/generate-screenshots.js +0 -169
- package/dist/component-loc/component-loc.d.ts +0 -7
- package/dist/component-loc/component-loc.d.ts.map +0 -1
- package/dist/component-loc/component-loc.js +0 -58
- package/dist/component-loc/index.d.ts +0 -2
- package/dist/component-loc/index.d.ts.map +0 -1
- package/dist/component-loc/index.js +0 -1
- package/dist/handlers/handleStoryFileChange/handleStoryFileChange.d.ts +0 -3
- package/dist/handlers/handleStoryFileChange/handleStoryFileChange.d.ts.map +0 -1
- package/dist/handlers/handleStoryFileChange/handleStoryFileChange.js +0 -100
- package/dist/handlers/handleStoryFileChange/index.d.ts +0 -2
- package/dist/handlers/handleStoryFileChange/index.d.ts.map +0 -1
- package/dist/handlers/handleStoryFileChange/index.js +0 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -1
- package/dist/screenshot-service/constants.d.ts +0 -8
- package/dist/screenshot-service/constants.d.ts.map +0 -1
- package/dist/screenshot-service/constants.js +0 -11
- package/dist/screenshot-service/index.d.ts +0 -5
- package/dist/screenshot-service/index.d.ts.map +0 -1
- package/dist/screenshot-service/index.js +0 -5
- package/dist/screenshot-service/screenshot-service.d.ts +0 -8
- package/dist/screenshot-service/screenshot-service.d.ts.map +0 -1
- package/dist/screenshot-service/screenshot-service.js +0 -36
- package/dist/screenshot-service/types.d.ts +0 -18
- package/dist/screenshot-service/types.d.ts.map +0 -1
- package/dist/screenshot-service/types.js +0 -1
- package/dist/screenshot-service/utils/browser/browser.d.ts +0 -10
- package/dist/screenshot-service/utils/browser/browser.d.ts.map +0 -1
- package/dist/screenshot-service/utils/browser/browser.js +0 -29
- package/dist/screenshot-service/utils/browser/index.d.ts +0 -2
- package/dist/screenshot-service/utils/browser/index.d.ts.map +0 -1
- package/dist/screenshot-service/utils/browser/index.js +0 -1
- package/dist/screenshot-service/utils/screenshot/index.d.ts +0 -2
- package/dist/screenshot-service/utils/screenshot/index.d.ts.map +0 -1
- package/dist/screenshot-service/utils/screenshot/index.js +0 -1
- package/dist/screenshot-service/utils/screenshot/screenshot.d.ts +0 -26
- package/dist/screenshot-service/utils/screenshot/screenshot.d.ts.map +0 -1
- package/dist/screenshot-service/utils/screenshot/screenshot.js +0 -128
- package/dist/storybook-onlook-plugin.d.ts +0 -9
- package/dist/storybook-onlook-plugin.d.ts.map +0 -1
- package/dist/storybook-onlook-plugin.js +0 -148
- package/dist/utils/fileSystem/fileSystem.d.ts +0 -9
- package/dist/utils/fileSystem/fileSystem.d.ts.map +0 -1
- package/dist/utils/fileSystem/fileSystem.js +0 -24
- package/dist/utils/fileSystem/index.d.ts +0 -2
- package/dist/utils/fileSystem/index.d.ts.map +0 -1
- package/dist/utils/fileSystem/index.js +0 -1
- package/dist/utils/findGitRoot/findGitRoot.d.ts +0 -5
- package/dist/utils/findGitRoot/findGitRoot.d.ts.map +0 -1
- package/dist/utils/findGitRoot/findGitRoot.js +0 -15
- package/dist/utils/findGitRoot/index.d.ts +0 -2
- package/dist/utils/findGitRoot/index.d.ts.map +0 -1
- package/dist/utils/findGitRoot/index.js +0 -1
- package/dist/utils/manifest/index.d.ts +0 -2
- package/dist/utils/manifest/index.d.ts.map +0 -1
- package/dist/utils/manifest/index.js +0 -1
- package/dist/utils/manifest/manifest.d.ts +0 -21
- package/dist/utils/manifest/manifest.d.ts.map +0 -1
- package/dist/utils/manifest/manifest.js +0 -44
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "storybook-onbook-plugin",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
|
-
"
|
|
6
|
+
"storybook-onbook-plugin": "./dist/cli/index.js"
|
|
7
7
|
},
|
|
8
8
|
"exports": {
|
|
9
9
|
".": {
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
"@babel/parser": "^7.26.9",
|
|
36
36
|
"@babel/traverse": "^7.26.9",
|
|
37
37
|
"@babel/types": "^7.26.9",
|
|
38
|
+
"@drizzle-team/brocli": "^0.11.0",
|
|
38
39
|
"playwright": "^1.52.0"
|
|
39
40
|
},
|
|
40
41
|
"devDependencies": {
|
|
@@ -42,6 +43,7 @@
|
|
|
42
43
|
"@types/babel__generator": "^7.6.8",
|
|
43
44
|
"@types/babel__traverse": "^7.20.6",
|
|
44
45
|
"@types/node": "^22.15.32",
|
|
46
|
+
"bun-types": "^1.3.5",
|
|
45
47
|
"typescript": "5.8.3",
|
|
46
48
|
"vite": "^6.3.5"
|
|
47
49
|
},
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"generate-screenshots.d.ts","sourceRoot":"","sources":["../../src/cli/generate-screenshots.ts"],"names":[],"mappings":""}
|
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { spawn } from 'node:child_process';
|
|
3
|
-
import { generateAllScreenshots } from '../screenshot-service/index.js';
|
|
4
|
-
import { getBrowser } from '../screenshot-service/utils/browser/index.js';
|
|
5
|
-
function parseArgs() {
|
|
6
|
-
const args = process.argv.slice(2);
|
|
7
|
-
const options = {
|
|
8
|
-
url: 'http://localhost:6006',
|
|
9
|
-
startStorybook: false,
|
|
10
|
-
storybookCmd: 'npm run storybook',
|
|
11
|
-
};
|
|
12
|
-
for (let i = 0; i < args.length; i++) {
|
|
13
|
-
const arg = args[i];
|
|
14
|
-
const nextArg = args[i + 1];
|
|
15
|
-
if (arg === '--url' && nextArg) {
|
|
16
|
-
options.url = nextArg;
|
|
17
|
-
i++;
|
|
18
|
-
}
|
|
19
|
-
else if (arg === '--start') {
|
|
20
|
-
options.startStorybook = true;
|
|
21
|
-
}
|
|
22
|
-
else if (arg === '--cmd' && nextArg) {
|
|
23
|
-
options.storybookCmd = nextArg;
|
|
24
|
-
i++;
|
|
25
|
-
}
|
|
26
|
-
else if (arg === '--help' || arg === '-h') {
|
|
27
|
-
console.log(`
|
|
28
|
-
Usage: generate-screenshots [options]
|
|
29
|
-
|
|
30
|
-
Options:
|
|
31
|
-
--url <url> Storybook URL (default: http://localhost:6006)
|
|
32
|
-
--start Start Storybook before generating screenshots
|
|
33
|
-
--cmd <command> Command to start Storybook (default: npm run storybook)
|
|
34
|
-
--help, -h Show this help message
|
|
35
|
-
|
|
36
|
-
Examples:
|
|
37
|
-
# With Storybook already running
|
|
38
|
-
generate-screenshots
|
|
39
|
-
|
|
40
|
-
# Start Storybook automatically
|
|
41
|
-
generate-screenshots --start
|
|
42
|
-
|
|
43
|
-
# Custom URL
|
|
44
|
-
generate-screenshots --url http://localhost:9009
|
|
45
|
-
|
|
46
|
-
# Custom start command
|
|
47
|
-
generate-screenshots --start --cmd "bun run storybook"
|
|
48
|
-
`);
|
|
49
|
-
process.exit(0);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
return options;
|
|
53
|
-
}
|
|
54
|
-
async function fetchStoryIndex(url) {
|
|
55
|
-
const indexUrl = `${url}/index.json`;
|
|
56
|
-
try {
|
|
57
|
-
const response = await fetch(indexUrl);
|
|
58
|
-
if (!response.ok) {
|
|
59
|
-
throw new Error(`Failed to fetch story index: ${response.statusText}`);
|
|
60
|
-
}
|
|
61
|
-
const data = (await response.json());
|
|
62
|
-
return Object.values(data.entries);
|
|
63
|
-
}
|
|
64
|
-
catch (error) {
|
|
65
|
-
console.error('Error fetching story index:', error);
|
|
66
|
-
console.error(`Make sure Storybook is running at ${url}`);
|
|
67
|
-
process.exit(1);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
async function startStorybook(cmd) {
|
|
71
|
-
console.log(`🚀 Starting Storybook with: ${cmd}`);
|
|
72
|
-
const [command, ...args] = cmd.split(' ');
|
|
73
|
-
if (!command) {
|
|
74
|
-
throw new Error('Invalid storybook command');
|
|
75
|
-
}
|
|
76
|
-
const storybookProcess = spawn(command, [...args, '--', '--no-open'], {
|
|
77
|
-
cwd: process.cwd(),
|
|
78
|
-
stdio: 'pipe',
|
|
79
|
-
shell: true,
|
|
80
|
-
});
|
|
81
|
-
return new Promise((resolve, reject) => {
|
|
82
|
-
let started = false;
|
|
83
|
-
const timeout = setTimeout(() => {
|
|
84
|
-
if (!started) {
|
|
85
|
-
storybookProcess.kill();
|
|
86
|
-
reject(new Error('Storybook failed to start within 60 seconds'));
|
|
87
|
-
}
|
|
88
|
-
}, 60000);
|
|
89
|
-
storybookProcess.stdout?.on('data', (data) => {
|
|
90
|
-
const output = data.toString();
|
|
91
|
-
console.log(output);
|
|
92
|
-
if (output.includes('Local:') || output.includes('localhost:')) {
|
|
93
|
-
if (!started) {
|
|
94
|
-
started = true;
|
|
95
|
-
clearTimeout(timeout);
|
|
96
|
-
console.log('✅ Storybook is ready!');
|
|
97
|
-
resolve(storybookProcess);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
});
|
|
101
|
-
storybookProcess.stderr?.on('data', (data) => {
|
|
102
|
-
console.error(data.toString());
|
|
103
|
-
});
|
|
104
|
-
storybookProcess.on('error', (error) => {
|
|
105
|
-
clearTimeout(timeout);
|
|
106
|
-
reject(error);
|
|
107
|
-
});
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
async function warmupStorybook(url, firstStoryId) {
|
|
111
|
-
console.log('🔥 Warming up Storybook...');
|
|
112
|
-
const browser = await getBrowser();
|
|
113
|
-
const context = await browser.newContext();
|
|
114
|
-
const page = await context.newPage();
|
|
115
|
-
try {
|
|
116
|
-
const warmupUrl = `${url}/iframe.html?id=${firstStoryId}&viewMode=story`;
|
|
117
|
-
await page.goto(warmupUrl, { timeout: 15000 });
|
|
118
|
-
await page.waitForLoadState('networkidle');
|
|
119
|
-
console.log('✅ Storybook warmed up');
|
|
120
|
-
}
|
|
121
|
-
catch (error) {
|
|
122
|
-
console.log('⚠️ Warmup had issues, proceeding anyway:', error);
|
|
123
|
-
}
|
|
124
|
-
finally {
|
|
125
|
-
await context.close();
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
async function main() {
|
|
129
|
-
const options = parseArgs();
|
|
130
|
-
console.log('📸 Generating Storybook screenshots...');
|
|
131
|
-
let storybookProcess = null;
|
|
132
|
-
try {
|
|
133
|
-
if (options.startStorybook) {
|
|
134
|
-
storybookProcess = await startStorybook(options.storybookCmd);
|
|
135
|
-
}
|
|
136
|
-
console.log(`Using Storybook URL: ${options.url}`);
|
|
137
|
-
// Fetch all stories
|
|
138
|
-
const stories = await fetchStoryIndex(options.url);
|
|
139
|
-
console.log(`Found ${stories.length} stories`);
|
|
140
|
-
const firstStory = stories[0];
|
|
141
|
-
if (!firstStory) {
|
|
142
|
-
throw new Error('No stories found');
|
|
143
|
-
}
|
|
144
|
-
// Warm up Storybook
|
|
145
|
-
await warmupStorybook(options.url, firstStory.id);
|
|
146
|
-
// Generate screenshots
|
|
147
|
-
await generateAllScreenshots(stories.map((story) => ({
|
|
148
|
-
id: story.id,
|
|
149
|
-
importPath: story.importPath,
|
|
150
|
-
})), options.url);
|
|
151
|
-
console.log('✅ Screenshot generation complete!');
|
|
152
|
-
}
|
|
153
|
-
catch (error) {
|
|
154
|
-
console.error('❌ Error during screenshot generation:', error);
|
|
155
|
-
throw error;
|
|
156
|
-
}
|
|
157
|
-
finally {
|
|
158
|
-
if (storybookProcess) {
|
|
159
|
-
console.log('🛑 Stopping Storybook...');
|
|
160
|
-
storybookProcess.kill();
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
main()
|
|
165
|
-
.then(() => process.exit(0))
|
|
166
|
-
.catch((error) => {
|
|
167
|
-
console.error('Fatal error:', error);
|
|
168
|
-
process.exit(1);
|
|
169
|
-
});
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { Plugin } from 'vite';
|
|
2
|
-
type ComponentLocPluginOptions = {
|
|
3
|
-
include?: RegExp;
|
|
4
|
-
};
|
|
5
|
-
export declare function componentLocPlugin(options?: ComponentLocPluginOptions): Plugin;
|
|
6
|
-
export default componentLocPlugin;
|
|
7
|
-
//# sourceMappingURL=component-loc.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"component-loc.d.ts","sourceRoot":"","sources":["../../src/component-loc/component-loc.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAEnC,KAAK,yBAAyB,GAAG;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,yBAA8B,GAAG,MAAM,CAgFlF;AAED,eAAe,kBAAkB,CAAC"}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
import generateModule from '@babel/generator';
|
|
3
|
-
import { parse } from '@babel/parser';
|
|
4
|
-
import traverseModule from '@babel/traverse';
|
|
5
|
-
import * as t from '@babel/types';
|
|
6
|
-
export function componentLocPlugin(options = {}) {
|
|
7
|
-
const include = options.include ?? /\.(jsx|tsx)$/;
|
|
8
|
-
// @babel/traverse and @babel/generator are CommonJS modules with default exports.
|
|
9
|
-
// When imported in an ESM context, the default export may be on `.default` or directly on the module.
|
|
10
|
-
// This workaround handles both cases to ensure compatibility across different bundler configurations.
|
|
11
|
-
const traverse = traverseModule.default ??
|
|
12
|
-
traverseModule;
|
|
13
|
-
const generate = generateModule.default ??
|
|
14
|
-
generateModule;
|
|
15
|
-
let root;
|
|
16
|
-
return {
|
|
17
|
-
name: 'onbook-component-loc',
|
|
18
|
-
enforce: 'pre',
|
|
19
|
-
apply: 'serve',
|
|
20
|
-
configResolved(config) {
|
|
21
|
-
root = config.root;
|
|
22
|
-
},
|
|
23
|
-
transform(code, id) {
|
|
24
|
-
const filepath = id.split('?', 1)[0];
|
|
25
|
-
if (!filepath || filepath.includes('node_modules'))
|
|
26
|
-
return null;
|
|
27
|
-
if (!include.test(filepath))
|
|
28
|
-
return null;
|
|
29
|
-
const ast = parse(code, {
|
|
30
|
-
sourceType: 'module',
|
|
31
|
-
plugins: ['jsx', 'typescript'],
|
|
32
|
-
sourceFilename: filepath,
|
|
33
|
-
});
|
|
34
|
-
let mutated = false;
|
|
35
|
-
// Get relative path from project root
|
|
36
|
-
const relativePath = path.relative(root, filepath);
|
|
37
|
-
traverse(ast, {
|
|
38
|
-
JSXElement(nodePath) {
|
|
39
|
-
const opening = nodePath.node.openingElement;
|
|
40
|
-
const element = nodePath.node;
|
|
41
|
-
if (!opening.loc || !element.loc)
|
|
42
|
-
return;
|
|
43
|
-
const alreadyTagged = opening.attributes.some((attribute) => t.isJSXAttribute(attribute) &&
|
|
44
|
-
attribute.name.name === 'data-component-file');
|
|
45
|
-
if (alreadyTagged)
|
|
46
|
-
return;
|
|
47
|
-
opening.attributes.push(t.jsxAttribute(t.jsxIdentifier('data-component-file'), t.stringLiteral(relativePath)), t.jsxAttribute(t.jsxIdentifier('data-component-start'), t.stringLiteral(`${element.loc.start.line}:${element.loc.start.column}`)), t.jsxAttribute(t.jsxIdentifier('data-component-end'), t.stringLiteral(`${element.loc.end.line}:${element.loc.end.column}`)));
|
|
48
|
-
mutated = true;
|
|
49
|
-
},
|
|
50
|
-
});
|
|
51
|
-
if (!mutated)
|
|
52
|
-
return null;
|
|
53
|
-
const output = generate(ast, { retainLines: true, sourceMaps: true, sourceFileName: id }, code);
|
|
54
|
-
return { code: output.code, map: output.map };
|
|
55
|
-
},
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
export default componentLocPlugin;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/component-loc/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { componentLocPlugin } from './component-loc.js';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"handleStoryFileChange.d.ts","sourceRoot":"","sources":["../../../src/handlers/handleStoryFileChange/handleStoryFileChange.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AA4FvC,wBAAgB,qBAAqB,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,UAAU,2CAiClE"}
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
import { generateScreenshot } from '../../screenshot-service/index.js';
|
|
3
|
-
import { computeFileHash } from '../../utils/fileSystem/index.js';
|
|
4
|
-
import { updateManifest } from '../../utils/manifest/index.js';
|
|
5
|
-
// Cache for Storybook's index.json
|
|
6
|
-
let cachedIndex = null;
|
|
7
|
-
let indexFetchPromise = null;
|
|
8
|
-
// Debounce state
|
|
9
|
-
const pendingFiles = new Set();
|
|
10
|
-
let debounceTimer = null;
|
|
11
|
-
const DEBOUNCE_MS = 500;
|
|
12
|
-
async function fetchStorybookIndex() {
|
|
13
|
-
try {
|
|
14
|
-
const response = await fetch('http://localhost:6006/index.json');
|
|
15
|
-
if (response.ok) {
|
|
16
|
-
cachedIndex = await response.json();
|
|
17
|
-
console.log('[Screenshots] Cached Storybook index');
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
catch (error) {
|
|
21
|
-
console.error('[Screenshots] Failed to fetch Storybook index:', error);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
function getStoriesForFile(filePath) {
|
|
25
|
-
if (!cachedIndex)
|
|
26
|
-
return [];
|
|
27
|
-
// Normalize the file path to match Storybook's importPath format
|
|
28
|
-
const fileName = path.basename(filePath);
|
|
29
|
-
return Object.values(cachedIndex.entries)
|
|
30
|
-
.filter((entry) => entry.type === 'story' && entry.importPath.endsWith(fileName))
|
|
31
|
-
.map((entry) => entry.id);
|
|
32
|
-
}
|
|
33
|
-
async function regenerateScreenshotsForFiles(files) {
|
|
34
|
-
// Refresh index before regenerating (in case new stories were added)
|
|
35
|
-
await fetchStorybookIndex();
|
|
36
|
-
const allStoryIds = new Set();
|
|
37
|
-
const fileToStories = new Map();
|
|
38
|
-
for (const file of files) {
|
|
39
|
-
const storyIds = getStoriesForFile(file);
|
|
40
|
-
fileToStories.set(file, storyIds);
|
|
41
|
-
for (const id of storyIds) {
|
|
42
|
-
allStoryIds.add(id);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
if (allStoryIds.size === 0) {
|
|
46
|
-
console.log('[Screenshots] No stories found for changed files');
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
console.log(`[Screenshots] Regenerating ${allStoryIds.size} stories from ${files.length} files`);
|
|
50
|
-
const storybookUrl = 'http://localhost:6006';
|
|
51
|
-
// Regenerate screenshots for each story (both themes) and collect bounding boxes
|
|
52
|
-
const storyBoundingBoxes = new Map();
|
|
53
|
-
await Promise.all(Array.from(allStoryIds).map(async (storyId) => {
|
|
54
|
-
const [lightResult, _darkResult] = await Promise.all([
|
|
55
|
-
generateScreenshot(storyId, 'light', storybookUrl),
|
|
56
|
-
generateScreenshot(storyId, 'dark', storybookUrl),
|
|
57
|
-
]);
|
|
58
|
-
// Use bounding box from light theme
|
|
59
|
-
if (lightResult) {
|
|
60
|
-
storyBoundingBoxes.set(storyId, lightResult.boundingBox);
|
|
61
|
-
}
|
|
62
|
-
}));
|
|
63
|
-
// Update manifest with new hashes and bounding boxes
|
|
64
|
-
for (const [file, storyIds] of fileToStories) {
|
|
65
|
-
const fileHash = computeFileHash(file);
|
|
66
|
-
storyIds.forEach((storyId) => {
|
|
67
|
-
updateManifest(storyId, file, fileHash, storyBoundingBoxes.get(storyId));
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
console.log(`[Screenshots] ✓ Regenerated ${allStoryIds.size} stories`);
|
|
71
|
-
}
|
|
72
|
-
export function handleStoryFileChange({ file, modules }) {
|
|
73
|
-
// Detect story file changes
|
|
74
|
-
if (file.endsWith('.stories.tsx') || file.endsWith('.stories.ts')) {
|
|
75
|
-
console.log(`[Screenshots] Story file changed: ${file}`);
|
|
76
|
-
// Initialize index cache on first change
|
|
77
|
-
if (!cachedIndex && !indexFetchPromise) {
|
|
78
|
-
indexFetchPromise = fetchStorybookIndex();
|
|
79
|
-
}
|
|
80
|
-
// Add to pending files
|
|
81
|
-
pendingFiles.add(file);
|
|
82
|
-
// Debounce regeneration
|
|
83
|
-
if (debounceTimer) {
|
|
84
|
-
clearTimeout(debounceTimer);
|
|
85
|
-
}
|
|
86
|
-
debounceTimer = setTimeout(async () => {
|
|
87
|
-
const files = Array.from(pendingFiles);
|
|
88
|
-
pendingFiles.clear();
|
|
89
|
-
debounceTimer = null;
|
|
90
|
-
try {
|
|
91
|
-
await regenerateScreenshotsForFiles(files);
|
|
92
|
-
}
|
|
93
|
-
catch (error) {
|
|
94
|
-
console.error('[Screenshots] Error regenerating screenshots:', error);
|
|
95
|
-
}
|
|
96
|
-
}, DEBOUNCE_MS);
|
|
97
|
-
// Return modules to trigger HMR update in Storybook
|
|
98
|
-
return modules;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/handlers/handleStoryFileChange/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { handleStoryFileChange } from './handleStoryFileChange.js';
|
package/dist/index.d.ts
DELETED
package/dist/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,mBAAmB,EACxB,qBAAqB,GACtB,MAAM,8BAA8B,CAAC"}
|
package/dist/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { storybookOnlookPlugin, } from './storybook-onlook-plugin.js';
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
export declare const CACHE_DIR: string;
|
|
2
|
-
export declare const SCREENSHOTS_DIR: string;
|
|
3
|
-
export declare const MANIFEST_PATH: string;
|
|
4
|
-
export declare const VIEWPORT_WIDTH = 1920;
|
|
5
|
-
export declare const VIEWPORT_HEIGHT = 1080;
|
|
6
|
-
export declare const MIN_COMPONENT_WIDTH = 420;
|
|
7
|
-
export declare const MIN_COMPONENT_HEIGHT = 280;
|
|
8
|
-
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/screenshot-service/constants.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,SAAS,QAA+C,CAAC;AACtE,eAAO,MAAM,eAAe,QAAsC,CAAC;AACnE,eAAO,MAAM,aAAa,QAAwC,CAAC;AAInE,eAAO,MAAM,cAAc,OAAO,CAAC;AACnC,eAAO,MAAM,eAAe,OAAO,CAAC;AAGpC,eAAO,MAAM,mBAAmB,MAAM,CAAC;AACvC,eAAO,MAAM,oBAAoB,MAAM,CAAC"}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
export const CACHE_DIR = path.join(process.cwd(), '.storybook-cache');
|
|
3
|
-
export const SCREENSHOTS_DIR = path.join(CACHE_DIR, 'screenshots');
|
|
4
|
-
export const MANIFEST_PATH = path.join(CACHE_DIR, 'manifest.json');
|
|
5
|
-
// Full HD viewport so page-level mocks render correctly
|
|
6
|
-
// Screenshot clips to actual component size, so this doesn't affect file size
|
|
7
|
-
export const VIEWPORT_WIDTH = 1920;
|
|
8
|
-
export const VIEWPORT_HEIGHT = 1080;
|
|
9
|
-
// Minimum dimensions for components on canvas
|
|
10
|
-
export const MIN_COMPONENT_WIDTH = 420;
|
|
11
|
-
export const MIN_COMPONENT_HEIGHT = 280;
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
export { generateAllScreenshots } from './screenshot-service.js';
|
|
2
|
-
export type { Manifest, ScreenshotMetadata } from './types.js';
|
|
3
|
-
export { closeBrowser } from './utils/browser/index.js';
|
|
4
|
-
export { generateScreenshot, getScreenshotPath, screenshotExists, } from './utils/screenshot/index.js';
|
|
5
|
-
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/screenshot-service/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAEjE,YAAY,EAAE,QAAQ,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAE/D,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EACL,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,6BAA6B,CAAC"}
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
// Main API
|
|
2
|
-
export { generateAllScreenshots } from './screenshot-service.js';
|
|
3
|
-
// Re-export screenshot utilities
|
|
4
|
-
export { closeBrowser } from './utils/browser/index.js';
|
|
5
|
-
export { generateScreenshot, getScreenshotPath, screenshotExists, } from './utils/screenshot/index.js';
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Generate screenshots for all stories (parallelized for speed)
|
|
3
|
-
*/
|
|
4
|
-
export declare function generateAllScreenshots(stories: Array<{
|
|
5
|
-
id: string;
|
|
6
|
-
importPath: string;
|
|
7
|
-
}>, storybookUrl?: string): Promise<void>;
|
|
8
|
-
//# sourceMappingURL=screenshot-service.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"screenshot-service.d.ts","sourceRoot":"","sources":["../../src/screenshot-service/screenshot-service.ts"],"names":[],"mappings":"AAKA;;GAEG;AACH,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC,EAClD,YAAY,GAAE,MAAgC,GAC7C,OAAO,CAAC,IAAI,CAAC,CAsCf"}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { computeFileHash } from '../utils/fileSystem/index.js';
|
|
2
|
-
import { updateManifest } from '../utils/manifest/index.js';
|
|
3
|
-
import { closeBrowser } from './utils/browser/index.js';
|
|
4
|
-
import { generateScreenshot } from './utils/screenshot/index.js';
|
|
5
|
-
/**
|
|
6
|
-
* Generate screenshots for all stories (parallelized for speed)
|
|
7
|
-
*/
|
|
8
|
-
export async function generateAllScreenshots(stories, storybookUrl = 'http://localhost:6006') {
|
|
9
|
-
console.log(`Generating screenshots for ${stories.length} stories...`);
|
|
10
|
-
// Process stories in batches for better performance
|
|
11
|
-
// Higher than CPU count since work is I/O-bound (waiting for page render)
|
|
12
|
-
const BATCH_SIZE = 10;
|
|
13
|
-
const batches = [];
|
|
14
|
-
for (let i = 0; i < stories.length; i += BATCH_SIZE) {
|
|
15
|
-
batches.push(stories.slice(i, i + BATCH_SIZE));
|
|
16
|
-
}
|
|
17
|
-
let completed = 0;
|
|
18
|
-
for (const batch of batches) {
|
|
19
|
-
await Promise.all(batch.map(async (story) => {
|
|
20
|
-
// Generate both light and dark in parallel for each story
|
|
21
|
-
const [lightResult, darkResult] = await Promise.all([
|
|
22
|
-
generateScreenshot(story.id, 'light', storybookUrl),
|
|
23
|
-
generateScreenshot(story.id, 'dark', storybookUrl),
|
|
24
|
-
]);
|
|
25
|
-
if (lightResult && darkResult) {
|
|
26
|
-
const fileHash = computeFileHash(story.importPath);
|
|
27
|
-
// Use bounding box from light theme (should be same for both)
|
|
28
|
-
updateManifest(story.id, story.importPath, fileHash, lightResult.boundingBox);
|
|
29
|
-
}
|
|
30
|
-
completed++;
|
|
31
|
-
console.log(`[${completed}/${stories.length}] Generated screenshots for ${story.id}`);
|
|
32
|
-
}));
|
|
33
|
-
}
|
|
34
|
-
await closeBrowser();
|
|
35
|
-
console.log('Screenshot generation complete!');
|
|
36
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
export interface BoundingBox {
|
|
2
|
-
width: number;
|
|
3
|
-
height: number;
|
|
4
|
-
}
|
|
5
|
-
export interface ScreenshotMetadata {
|
|
6
|
-
fileHash: string;
|
|
7
|
-
lastGenerated: string;
|
|
8
|
-
sourcePath: string;
|
|
9
|
-
screenshots: {
|
|
10
|
-
light: string;
|
|
11
|
-
dark: string;
|
|
12
|
-
};
|
|
13
|
-
boundingBox?: BoundingBox;
|
|
14
|
-
}
|
|
15
|
-
export interface Manifest {
|
|
16
|
-
stories: Record<string, ScreenshotMetadata>;
|
|
17
|
-
}
|
|
18
|
-
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/screenshot-service/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE;QACX,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;IACF,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B;AAED,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;CAC7C"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { type Browser } from 'playwright';
|
|
2
|
-
/**
|
|
3
|
-
* Initialize browser instance
|
|
4
|
-
*/
|
|
5
|
-
export declare function getBrowser(): Promise<Browser>;
|
|
6
|
-
/**
|
|
7
|
-
* Close browser instance
|
|
8
|
-
*/
|
|
9
|
-
export declare function closeBrowser(): Promise<void>;
|
|
10
|
-
//# sourceMappingURL=browser.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../../../../src/screenshot-service/utils/browser/browser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,OAAO,EAAY,MAAM,YAAY,CAAC;AAIpD;;GAEG;AACH,wBAAsB,UAAU,IAAI,OAAO,CAAC,OAAO,CAAC,CAOnD;AAED;;GAEG;AACH,wBAAsB,YAAY,kBAUjC"}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { chromium } from 'playwright';
|
|
2
|
-
let browser = null;
|
|
3
|
-
/**
|
|
4
|
-
* Initialize browser instance
|
|
5
|
-
*/
|
|
6
|
-
export async function getBrowser() {
|
|
7
|
-
if (!browser) {
|
|
8
|
-
browser = await chromium.launch({
|
|
9
|
-
headless: true,
|
|
10
|
-
});
|
|
11
|
-
}
|
|
12
|
-
return browser;
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* Close browser instance
|
|
16
|
-
*/
|
|
17
|
-
export async function closeBrowser() {
|
|
18
|
-
if (browser) {
|
|
19
|
-
try {
|
|
20
|
-
await browser.close();
|
|
21
|
-
}
|
|
22
|
-
catch (error) {
|
|
23
|
-
console.error('Error closing browser:', error);
|
|
24
|
-
}
|
|
25
|
-
finally {
|
|
26
|
-
browser = null;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/screenshot-service/utils/browser/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { closeBrowser, getBrowser } from './browser.js';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/screenshot-service/utils/screenshot/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,uBAAuB,EACvB,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,iBAAiB,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { captureScreenshotBuffer, generateScreenshot, getScreenshotPath, screenshotExists, } from './screenshot.js';
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import type { BoundingBox } from '../../types.js';
|
|
2
|
-
export interface ScreenshotResult {
|
|
3
|
-
buffer: Buffer;
|
|
4
|
-
boundingBox: BoundingBox | null;
|
|
5
|
-
}
|
|
6
|
-
export interface GenerateScreenshotResult {
|
|
7
|
-
path: string;
|
|
8
|
-
boundingBox: BoundingBox | null;
|
|
9
|
-
}
|
|
10
|
-
/**
|
|
11
|
-
* Get screenshot file path
|
|
12
|
-
*/
|
|
13
|
-
export declare function getScreenshotPath(storyId: string, theme: 'light' | 'dark'): string;
|
|
14
|
-
/**
|
|
15
|
-
* Check if screenshot exists
|
|
16
|
-
*/
|
|
17
|
-
export declare function screenshotExists(storyId: string, theme: 'light' | 'dark'): boolean;
|
|
18
|
-
/**
|
|
19
|
-
* Capture a screenshot and return it as a Buffer with bounding box info
|
|
20
|
-
*/
|
|
21
|
-
export declare function captureScreenshotBuffer(storyId: string, theme: 'light' | 'dark', width?: number, height?: number, storybookUrl?: string): Promise<ScreenshotResult>;
|
|
22
|
-
/**
|
|
23
|
-
* Generate a screenshot for a story and save to disk
|
|
24
|
-
*/
|
|
25
|
-
export declare function generateScreenshot(storyId: string, theme: 'light' | 'dark', storybookUrl?: string): Promise<GenerateScreenshotResult | null>;
|
|
26
|
-
//# sourceMappingURL=screenshot.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"screenshot.d.ts","sourceRoot":"","sources":["../../../../src/screenshot-service/utils/screenshot/screenshot.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAGlD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,WAAW,GAAG,IAAI,CAAC;CACjC;AAED,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,WAAW,GAAG,IAAI,CAAC;CACjC;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAGlF;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,CAGlF;AAED;;GAEG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,OAAO,GAAG,MAAM,EACvB,KAAK,GAAE,MAAuB,EAC9B,MAAM,GAAE,MAAwB,EAChC,YAAY,GAAE,MAAgC,GAC7C,OAAO,CAAC,gBAAgB,CAAC,CAiG3B;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,OAAO,GAAG,MAAM,EACvB,YAAY,GAAE,MAAgC,GAC7C,OAAO,CAAC,wBAAwB,GAAG,IAAI,CAAC,CAwB1C"}
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { ensureCacheDirectories } from '../../../utils/fileSystem/index.js';
|
|
4
|
-
import { MIN_COMPONENT_HEIGHT, MIN_COMPONENT_WIDTH, SCREENSHOTS_DIR, VIEWPORT_HEIGHT, VIEWPORT_WIDTH, } from '../../constants.js';
|
|
5
|
-
import { getBrowser } from '../browser/index.js';
|
|
6
|
-
/**
|
|
7
|
-
* Get screenshot file path
|
|
8
|
-
*/
|
|
9
|
-
export function getScreenshotPath(storyId, theme) {
|
|
10
|
-
const storyDir = path.join(SCREENSHOTS_DIR, storyId);
|
|
11
|
-
return path.join(storyDir, `${theme}.png`);
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Check if screenshot exists
|
|
15
|
-
*/
|
|
16
|
-
export function screenshotExists(storyId, theme) {
|
|
17
|
-
const screenshotPath = getScreenshotPath(storyId, theme);
|
|
18
|
-
return fs.existsSync(screenshotPath);
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Capture a screenshot and return it as a Buffer with bounding box info
|
|
22
|
-
*/
|
|
23
|
-
export async function captureScreenshotBuffer(storyId, theme, width = VIEWPORT_WIDTH, height = VIEWPORT_HEIGHT, storybookUrl = 'http://localhost:6006') {
|
|
24
|
-
const browser = await getBrowser();
|
|
25
|
-
const context = await browser.newContext({
|
|
26
|
-
viewport: { width, height },
|
|
27
|
-
deviceScaleFactor: 2,
|
|
28
|
-
});
|
|
29
|
-
const page = await context.newPage();
|
|
30
|
-
try {
|
|
31
|
-
// Navigate to story iframe URL
|
|
32
|
-
const url = `${storybookUrl}/iframe.html?id=${storyId}&viewMode=story&globals=theme:${theme}`;
|
|
33
|
-
await page.goto(url, { timeout: 15000 });
|
|
34
|
-
// Wait for page to be fully ready (matching @storybook/test-runner's waitForPageReady)
|
|
35
|
-
await page.waitForLoadState('domcontentloaded');
|
|
36
|
-
await page.waitForLoadState('load');
|
|
37
|
-
await page.waitForLoadState('networkidle');
|
|
38
|
-
await page.evaluate(() => document.fonts.ready);
|
|
39
|
-
// Wait for all images to be fully loaded and decoded
|
|
40
|
-
await page.evaluate(async () => {
|
|
41
|
-
const images = document.querySelectorAll('img');
|
|
42
|
-
await Promise.all(Array.from(images).map((img) => {
|
|
43
|
-
if (img.complete)
|
|
44
|
-
return Promise.resolve();
|
|
45
|
-
return new Promise((resolve) => {
|
|
46
|
-
img.addEventListener('load', resolve);
|
|
47
|
-
img.addEventListener('error', resolve);
|
|
48
|
-
});
|
|
49
|
-
}));
|
|
50
|
-
});
|
|
51
|
-
// Calculate the bounding box of all content inside #storybook-root
|
|
52
|
-
const contentBounds = await page.evaluate(() => {
|
|
53
|
-
const root = document.querySelector('#storybook-root');
|
|
54
|
-
if (!root)
|
|
55
|
-
return null;
|
|
56
|
-
// Get the bounding rect of all children combined
|
|
57
|
-
const children = root.querySelectorAll('*');
|
|
58
|
-
if (children.length === 0)
|
|
59
|
-
return null;
|
|
60
|
-
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
61
|
-
children.forEach((child) => {
|
|
62
|
-
const rect = child.getBoundingClientRect();
|
|
63
|
-
if (rect.width === 0 || rect.height === 0)
|
|
64
|
-
return;
|
|
65
|
-
minX = Math.min(minX, rect.left);
|
|
66
|
-
minY = Math.min(minY, rect.top);
|
|
67
|
-
maxX = Math.max(maxX, rect.right);
|
|
68
|
-
maxY = Math.max(maxY, rect.bottom);
|
|
69
|
-
});
|
|
70
|
-
if (minX === Infinity)
|
|
71
|
-
return null;
|
|
72
|
-
return {
|
|
73
|
-
x: minX,
|
|
74
|
-
y: minY,
|
|
75
|
-
width: maxX - minX,
|
|
76
|
-
height: maxY - minY,
|
|
77
|
-
};
|
|
78
|
-
});
|
|
79
|
-
let screenshotBuffer;
|
|
80
|
-
let resultBoundingBox = null;
|
|
81
|
-
if (contentBounds && contentBounds.width > 0 && contentBounds.height > 0) {
|
|
82
|
-
const PADDING = 20; // 10px each side
|
|
83
|
-
const clippedWidth = Math.min(width, contentBounds.width + PADDING);
|
|
84
|
-
const clippedHeight = Math.min(height, contentBounds.height + PADDING);
|
|
85
|
-
// Store dimensions that match the actual screenshot (including padding)
|
|
86
|
-
resultBoundingBox = {
|
|
87
|
-
width: Math.max(MIN_COMPONENT_WIDTH, Math.round(clippedWidth)),
|
|
88
|
-
height: Math.max(MIN_COMPONENT_HEIGHT, Math.round(clippedHeight)),
|
|
89
|
-
};
|
|
90
|
-
screenshotBuffer = await page.screenshot({
|
|
91
|
-
type: 'png',
|
|
92
|
-
clip: {
|
|
93
|
-
x: Math.max(0, contentBounds.x - 10),
|
|
94
|
-
y: Math.max(0, contentBounds.y - 10),
|
|
95
|
-
width: clippedWidth,
|
|
96
|
-
height: clippedHeight,
|
|
97
|
-
},
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
else {
|
|
101
|
-
screenshotBuffer = await page.screenshot({ type: 'png' });
|
|
102
|
-
}
|
|
103
|
-
return { buffer: screenshotBuffer, boundingBox: resultBoundingBox };
|
|
104
|
-
}
|
|
105
|
-
finally {
|
|
106
|
-
await context.close();
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* Generate a screenshot for a story and save to disk
|
|
111
|
-
*/
|
|
112
|
-
export async function generateScreenshot(storyId, theme, storybookUrl = 'http://localhost:6006') {
|
|
113
|
-
try {
|
|
114
|
-
ensureCacheDirectories();
|
|
115
|
-
const storyDir = path.join(SCREENSHOTS_DIR, storyId);
|
|
116
|
-
if (!fs.existsSync(storyDir)) {
|
|
117
|
-
fs.mkdirSync(storyDir, { recursive: true });
|
|
118
|
-
}
|
|
119
|
-
const screenshotPath = getScreenshotPath(storyId, theme);
|
|
120
|
-
const { buffer, boundingBox } = await captureScreenshotBuffer(storyId, theme, VIEWPORT_WIDTH, VIEWPORT_HEIGHT, storybookUrl);
|
|
121
|
-
fs.writeFileSync(screenshotPath, buffer);
|
|
122
|
-
return { path: screenshotPath, boundingBox };
|
|
123
|
-
}
|
|
124
|
-
catch (error) {
|
|
125
|
-
console.error(`Error generating screenshot for ${storyId} (${theme}):`, error);
|
|
126
|
-
return null;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { PluginOption } from 'vite';
|
|
2
|
-
export type OnlookPluginOptions = {
|
|
3
|
-
/** Storybook port (default: 6006) */
|
|
4
|
-
port?: number;
|
|
5
|
-
/** Additional allowed origins for CORS (merged with defaults) */
|
|
6
|
-
allowedOrigins?: string[];
|
|
7
|
-
};
|
|
8
|
-
export declare function storybookOnlookPlugin(options?: OnlookPluginOptions): PluginOption[];
|
|
9
|
-
//# sourceMappingURL=storybook-onlook-plugin.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"storybook-onlook-plugin.d.ts","sourceRoot":"","sources":["../src/storybook-onlook-plugin.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAU,YAAY,EAAiB,MAAM,MAAM,CAAC;AAoBhE,MAAM,MAAM,mBAAmB,GAAG;IAChC,qCAAqC;IACrC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iEAAiE;IACjE,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B,CAAC;AAqHF,wBAAgB,qBAAqB,CAAC,OAAO,GAAE,mBAAwB,GAAG,YAAY,EAAE,CAwCvF"}
|
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path, { dirname, join, relative } from 'node:path';
|
|
3
|
-
import { fileURLToPath } from 'node:url';
|
|
4
|
-
import { componentLocPlugin } from './component-loc/index.js';
|
|
5
|
-
import { handleStoryFileChange } from './handlers/handleStoryFileChange/index.js';
|
|
6
|
-
import { captureScreenshotBuffer } from './screenshot-service/utils/screenshot/index.js';
|
|
7
|
-
import { findGitRoot } from './utils/findGitRoot/index.js';
|
|
8
|
-
// Calculate storybook location relative to git root
|
|
9
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
|
-
const storybookDir = join(__dirname, '..');
|
|
11
|
-
const gitRoot = findGitRoot(storybookDir);
|
|
12
|
-
const storybookLocation = gitRoot ? relative(gitRoot, storybookDir) : '';
|
|
13
|
-
const repoRoot = gitRoot || process.cwd();
|
|
14
|
-
// Default allowed origins for CORS
|
|
15
|
-
const DEFAULT_ALLOWED_ORIGINS = [
|
|
16
|
-
'https://app.onlook.ai',
|
|
17
|
-
'http://localhost:3000',
|
|
18
|
-
'http://localhost:6006',
|
|
19
|
-
];
|
|
20
|
-
const serveMetadataAndScreenshots = (req, res, next) => {
|
|
21
|
-
// Consolidated endpoint: Storybook index + metadata + boundingBox
|
|
22
|
-
if (req.url === '/onbook-index.json') {
|
|
23
|
-
const manifestPath = path.join(process.cwd(), '.storybook-cache', 'manifest.json');
|
|
24
|
-
// Fetch Storybook's index.json and enrich it
|
|
25
|
-
fetch('http://localhost:6006/index.json')
|
|
26
|
-
.then((response) => response.json())
|
|
27
|
-
// biome-ignore lint/suspicious/noExplicitAny: Storybook index.json structure
|
|
28
|
-
.then((indexData) => {
|
|
29
|
-
// Load manifest for bounding box data
|
|
30
|
-
const manifest = fs.existsSync(manifestPath)
|
|
31
|
-
? JSON.parse(fs.readFileSync(manifestPath, 'utf-8'))
|
|
32
|
-
: { stories: {} };
|
|
33
|
-
// Default viewport size as fallback
|
|
34
|
-
const defaultBoundingBox = { width: 1920, height: 1080 };
|
|
35
|
-
// Add boundingBox to each entry
|
|
36
|
-
for (const [storyId, entry] of Object.entries(indexData.entries || {})) {
|
|
37
|
-
const manifestEntry = manifest.stories?.[storyId];
|
|
38
|
-
// biome-ignore lint/suspicious/noExplicitAny: extending Storybook entry
|
|
39
|
-
entry.boundingBox = manifestEntry?.boundingBox || defaultBoundingBox;
|
|
40
|
-
}
|
|
41
|
-
// Add metadata
|
|
42
|
-
indexData.meta = { storybookLocation, repoRoot };
|
|
43
|
-
res.setHeader('Content-Type', 'application/json');
|
|
44
|
-
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
45
|
-
res.end(JSON.stringify(indexData));
|
|
46
|
-
})
|
|
47
|
-
.catch((error) => {
|
|
48
|
-
console.error('Failed to fetch/extend index.json:', error);
|
|
49
|
-
res.statusCode = 500;
|
|
50
|
-
res.setHeader('Content-Type', 'application/json');
|
|
51
|
-
res.end(JSON.stringify({ error: 'Failed to fetch index', details: String(error) }));
|
|
52
|
-
});
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
// On-demand screenshot capture API
|
|
56
|
-
if (req.url?.startsWith('/api/capture-screenshot')) {
|
|
57
|
-
const url = new URL(req.url, 'http://localhost');
|
|
58
|
-
const storyId = url.searchParams.get('storyId');
|
|
59
|
-
const theme = (url.searchParams.get('theme') || 'light');
|
|
60
|
-
const width = parseInt(url.searchParams.get('width') || '800', 10);
|
|
61
|
-
const height = parseInt(url.searchParams.get('height') || '600', 10);
|
|
62
|
-
if (!storyId) {
|
|
63
|
-
res.statusCode = 400;
|
|
64
|
-
res.setHeader('Content-Type', 'application/json');
|
|
65
|
-
res.end(JSON.stringify({ error: 'storyId is required' }));
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
// Validate theme
|
|
69
|
-
if (theme !== 'light' && theme !== 'dark') {
|
|
70
|
-
res.statusCode = 400;
|
|
71
|
-
res.setHeader('Content-Type', 'application/json');
|
|
72
|
-
res.end(JSON.stringify({ error: 'theme must be "light" or "dark"' }));
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
// Capture screenshot asynchronously
|
|
76
|
-
captureScreenshotBuffer(storyId, theme, width, height)
|
|
77
|
-
.then(({ buffer }) => {
|
|
78
|
-
res.setHeader('Content-Type', 'image/png');
|
|
79
|
-
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
80
|
-
res.setHeader('Cache-Control', 'no-cache');
|
|
81
|
-
res.end(buffer);
|
|
82
|
-
})
|
|
83
|
-
.catch((error) => {
|
|
84
|
-
console.error('Screenshot capture error:', error);
|
|
85
|
-
res.statusCode = 500;
|
|
86
|
-
res.setHeader('Content-Type', 'application/json');
|
|
87
|
-
res.end(JSON.stringify({
|
|
88
|
-
error: 'Failed to capture screenshot',
|
|
89
|
-
details: String(error),
|
|
90
|
-
}));
|
|
91
|
-
});
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
// Serve screenshots from cache
|
|
95
|
-
if (req.url?.startsWith('/screenshots/')) {
|
|
96
|
-
const screenshotPath = path.join(process.cwd(), '.storybook-cache', req.url.replace('/screenshots/', 'screenshots/'));
|
|
97
|
-
if (fs.existsSync(screenshotPath)) {
|
|
98
|
-
res.setHeader('Content-Type', 'image/png');
|
|
99
|
-
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
100
|
-
res.setHeader('Cache-Control', 'public, max-age=3600');
|
|
101
|
-
fs.createReadStream(screenshotPath).pipe(res);
|
|
102
|
-
}
|
|
103
|
-
else {
|
|
104
|
-
res.statusCode = 404;
|
|
105
|
-
res.end('Screenshot not found');
|
|
106
|
-
}
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
next();
|
|
110
|
-
};
|
|
111
|
-
export function storybookOnlookPlugin(options = {}) {
|
|
112
|
-
// Auto-disable in CI/Chromatic static builds
|
|
113
|
-
if (process.env.CHROMATIC || process.env.CI) {
|
|
114
|
-
return [];
|
|
115
|
-
}
|
|
116
|
-
const port = options.port ?? 6006;
|
|
117
|
-
const allowedOrigins = [...DEFAULT_ALLOWED_ORIGINS, ...(options.allowedOrigins ?? [])];
|
|
118
|
-
const mainPlugin = {
|
|
119
|
-
name: 'storybook-onlook-plugin',
|
|
120
|
-
config() {
|
|
121
|
-
return {
|
|
122
|
-
server: {
|
|
123
|
-
// E2B sandbox HMR configuration
|
|
124
|
-
hmr: {
|
|
125
|
-
// E2B sandboxes use HTTPS, so we need secure WebSocket
|
|
126
|
-
protocol: 'wss',
|
|
127
|
-
// E2B routes through standard HTTPS port 443
|
|
128
|
-
clientPort: 443,
|
|
129
|
-
// The actual Storybook server port inside the sandbox
|
|
130
|
-
port,
|
|
131
|
-
},
|
|
132
|
-
cors: {
|
|
133
|
-
origin: allowedOrigins,
|
|
134
|
-
},
|
|
135
|
-
},
|
|
136
|
-
};
|
|
137
|
-
},
|
|
138
|
-
configureServer(server) {
|
|
139
|
-
server.middlewares.use(serveMetadataAndScreenshots);
|
|
140
|
-
},
|
|
141
|
-
configurePreviewServer(server) {
|
|
142
|
-
server.middlewares.use(serveMetadataAndScreenshots);
|
|
143
|
-
},
|
|
144
|
-
handleHotUpdate: handleStoryFileChange,
|
|
145
|
-
};
|
|
146
|
-
// componentLocPlugin has enforce: 'pre' so it runs before other transforms
|
|
147
|
-
return [componentLocPlugin(), mainPlugin];
|
|
148
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"fileSystem.d.ts","sourceRoot":"","sources":["../../../src/utils/fileSystem/fileSystem.ts"],"names":[],"mappings":"AAIA;;GAEG;AACH,wBAAgB,sBAAsB,SAOrC;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAMxD"}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import crypto from 'node:crypto';
|
|
2
|
-
import fs from 'node:fs';
|
|
3
|
-
import { CACHE_DIR, SCREENSHOTS_DIR } from '../../screenshot-service/constants.js';
|
|
4
|
-
/**
|
|
5
|
-
* Ensure cache directories exist
|
|
6
|
-
*/
|
|
7
|
-
export function ensureCacheDirectories() {
|
|
8
|
-
if (!fs.existsSync(CACHE_DIR)) {
|
|
9
|
-
fs.mkdirSync(CACHE_DIR, { recursive: true });
|
|
10
|
-
}
|
|
11
|
-
if (!fs.existsSync(SCREENSHOTS_DIR)) {
|
|
12
|
-
fs.mkdirSync(SCREENSHOTS_DIR, { recursive: true });
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Compute file hash
|
|
17
|
-
*/
|
|
18
|
-
export function computeFileHash(filePath) {
|
|
19
|
-
if (!fs.existsSync(filePath)) {
|
|
20
|
-
return '';
|
|
21
|
-
}
|
|
22
|
-
const content = fs.readFileSync(filePath, 'utf-8');
|
|
23
|
-
return crypto.createHash('sha256').update(content).digest('hex');
|
|
24
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/fileSystem/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { computeFileHash, ensureCacheDirectories } from './fileSystem.js';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"findGitRoot.d.ts","sourceRoot":"","sources":["../../../src/utils/findGitRoot/findGitRoot.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAW5D"}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs';
|
|
2
|
-
import { dirname, join } from 'node:path';
|
|
3
|
-
/**
|
|
4
|
-
* Find the git repository root by walking up the directory tree
|
|
5
|
-
*/
|
|
6
|
-
export function findGitRoot(startPath) {
|
|
7
|
-
let currentPath = startPath;
|
|
8
|
-
while (currentPath !== dirname(currentPath)) {
|
|
9
|
-
if (existsSync(join(currentPath, '.git'))) {
|
|
10
|
-
return currentPath;
|
|
11
|
-
}
|
|
12
|
-
currentPath = dirname(currentPath);
|
|
13
|
-
}
|
|
14
|
-
return null;
|
|
15
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/findGitRoot/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { findGitRoot } from './findGitRoot.js';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/manifest/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,YAAY,EACZ,YAAY,EACZ,cAAc,GACf,MAAM,eAAe,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { getManifestEntry, loadManifest, saveManifest, updateManifest, } from './manifest.js';
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import type { Manifest, ScreenshotMetadata } from '../../screenshot-service/types.js';
|
|
2
|
-
/**
|
|
3
|
-
* Load manifest from disk
|
|
4
|
-
*/
|
|
5
|
-
export declare function loadManifest(): Manifest;
|
|
6
|
-
/**
|
|
7
|
-
* Save manifest to disk
|
|
8
|
-
*/
|
|
9
|
-
export declare function saveManifest(manifest: Manifest): void;
|
|
10
|
-
/**
|
|
11
|
-
* Get manifest entry for a story
|
|
12
|
-
*/
|
|
13
|
-
export declare function getManifestEntry(storyId: string): ScreenshotMetadata | null;
|
|
14
|
-
/**
|
|
15
|
-
* Update manifest for a story
|
|
16
|
-
*/
|
|
17
|
-
export declare function updateManifest(storyId: string, sourcePath: string, fileHash: string, boundingBox?: {
|
|
18
|
-
width: number;
|
|
19
|
-
height: number;
|
|
20
|
-
} | null): void;
|
|
21
|
-
//# sourceMappingURL=manifest.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../../src/utils/manifest/manifest.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAC;AAGtF;;GAEG;AACH,wBAAgB,YAAY,IAAI,QAAQ,CAMvC;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,QAAQ,QAG9C;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,kBAAkB,GAAG,IAAI,CAG3E;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,WAAW,CAAC,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,QAgBvD"}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import { MANIFEST_PATH } from '../../screenshot-service/constants.js';
|
|
3
|
-
import { ensureCacheDirectories } from '../fileSystem/index.js';
|
|
4
|
-
/**
|
|
5
|
-
* Load manifest from disk
|
|
6
|
-
*/
|
|
7
|
-
export function loadManifest() {
|
|
8
|
-
if (fs.existsSync(MANIFEST_PATH)) {
|
|
9
|
-
const content = fs.readFileSync(MANIFEST_PATH, 'utf-8');
|
|
10
|
-
return JSON.parse(content);
|
|
11
|
-
}
|
|
12
|
-
return { stories: {} };
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* Save manifest to disk
|
|
16
|
-
*/
|
|
17
|
-
export function saveManifest(manifest) {
|
|
18
|
-
ensureCacheDirectories();
|
|
19
|
-
fs.writeFileSync(MANIFEST_PATH, JSON.stringify(manifest, null, 2));
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Get manifest entry for a story
|
|
23
|
-
*/
|
|
24
|
-
export function getManifestEntry(storyId) {
|
|
25
|
-
const manifest = loadManifest();
|
|
26
|
-
return manifest.stories[storyId] || null;
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Update manifest for a story
|
|
30
|
-
*/
|
|
31
|
-
export function updateManifest(storyId, sourcePath, fileHash, boundingBox) {
|
|
32
|
-
const manifest = loadManifest();
|
|
33
|
-
manifest.stories[storyId] = {
|
|
34
|
-
fileHash,
|
|
35
|
-
lastGenerated: new Date().toISOString(),
|
|
36
|
-
sourcePath,
|
|
37
|
-
screenshots: {
|
|
38
|
-
light: `screenshots/${storyId}/light.png`,
|
|
39
|
-
dark: `screenshots/${storyId}/dark.png`,
|
|
40
|
-
},
|
|
41
|
-
boundingBox: boundingBox ?? undefined,
|
|
42
|
-
};
|
|
43
|
-
saveManifest(manifest);
|
|
44
|
-
}
|