@coder/pixel-storybook 0.1.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/LICENSE +21 -0
- package/README.md +19 -0
- package/dist/api.d.ts +56 -0
- package/dist/api.js +238 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +102 -0
- package/dist/checkDifferences.d.ts +63 -0
- package/dist/checkDifferences.js +67 -0
- package/dist/compare/compare.d.ts +5 -0
- package/dist/compare/compare.js +80 -0
- package/dist/compare/pixelmatch.d.ts +7 -0
- package/dist/compare/pixelmatch.js +46 -0
- package/dist/compare/utils.d.ts +2 -0
- package/dist/compare/utils.js +18 -0
- package/dist/compare/worker.d.ts +1 -0
- package/dist/compare/worker.js +6 -0
- package/dist/concurrency.d.ts +19 -0
- package/dist/concurrency.js +61 -0
- package/dist/config.d.ts +3795 -0
- package/dist/config.js +472 -0
- package/dist/configHelper.d.ts +2 -0
- package/dist/configHelper.js +34 -0
- package/dist/constants.d.ts +2 -0
- package/dist/constants.js +2 -0
- package/dist/crawler/storybook.d.ts +51 -0
- package/dist/crawler/storybook.js +317 -0
- package/dist/crawler/utils.d.ts +6 -0
- package/dist/crawler/utils.js +20 -0
- package/dist/createShots.d.ts +30 -0
- package/dist/createShots.js +54 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/log.d.ts +26 -0
- package/dist/log.js +99 -0
- package/dist/runner.d.ts +4 -0
- package/dist/runner.js +186 -0
- package/dist/schemas.d.ts +174 -0
- package/dist/schemas.js +73 -0
- package/dist/shard.d.ts +3 -0
- package/dist/shard.js +17 -0
- package/dist/shots/shots.d.ts +3 -0
- package/dist/shots/shots.js +196 -0
- package/dist/shots/utils.d.ts +33 -0
- package/dist/shots/utils.js +177 -0
- package/dist/types.d.ts +12 -0
- package/dist/types.js +1 -0
- package/dist/upload.d.ts +10 -0
- package/dist/upload.js +32 -0
- package/dist/uploadStorybook.d.ts +2 -0
- package/dist/uploadStorybook.js +56 -0
- package/dist/utils.d.ts +50 -0
- package/dist/utils.js +194 -0
- package/package.json +64 -0
package/dist/config.js
ADDED
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import * as z from 'zod';
|
|
4
|
+
import { defaultShotConcurrency } from './concurrency.js';
|
|
5
|
+
import { loadProjectConfigFile, loadTSProjectConfigFile, } from './configHelper.js';
|
|
6
|
+
import { log } from './log.js';
|
|
7
|
+
import { BrowserSchema, MaskSchema, ShotModeSchema } from './schemas.js';
|
|
8
|
+
const StorybookShotsSchema = z.object({
|
|
9
|
+
/**
|
|
10
|
+
* URL of the Storybook instance or local folder
|
|
11
|
+
* @default 'storybook-static'
|
|
12
|
+
*/
|
|
13
|
+
storybookUrl: z.string(),
|
|
14
|
+
/**
|
|
15
|
+
* Define areas for all stories where differences will be ignored
|
|
16
|
+
*/
|
|
17
|
+
mask: z.array(MaskSchema).optional(),
|
|
18
|
+
/**
|
|
19
|
+
* Define custom breakpoints for all Storybook shots as width in pixels
|
|
20
|
+
* @default []
|
|
21
|
+
* @example
|
|
22
|
+
* [ 320, 768, 1280 ]
|
|
23
|
+
*/
|
|
24
|
+
breakpoints: z.array(z.number()).optional(),
|
|
25
|
+
/**
|
|
26
|
+
* Target specific element on page with a selector
|
|
27
|
+
*/
|
|
28
|
+
elementLocator: z.string().optional(),
|
|
29
|
+
/**
|
|
30
|
+
* Wait for a specific selector before taking a screenshot
|
|
31
|
+
* @example '[data-storyloaded]'
|
|
32
|
+
*/
|
|
33
|
+
waitForSelector: z.string().optional(),
|
|
34
|
+
});
|
|
35
|
+
const StoryLikeSchema = z.object({
|
|
36
|
+
shotMode: ShotModeSchema,
|
|
37
|
+
id: z.string().optional(),
|
|
38
|
+
kind: z.string().optional(),
|
|
39
|
+
story: z.string().optional(),
|
|
40
|
+
shotName: z.string().optional(),
|
|
41
|
+
parameters: z.record(z.unknown()).optional(),
|
|
42
|
+
filePathBaseline: z.string().optional(),
|
|
43
|
+
filePathCurrent: z.string().optional(),
|
|
44
|
+
filePathDifference: z.string().optional(),
|
|
45
|
+
});
|
|
46
|
+
const ShotItem = z.object({
|
|
47
|
+
shotMode: ShotModeSchema,
|
|
48
|
+
id: z.string(),
|
|
49
|
+
shotName: z.string(),
|
|
50
|
+
url: z.string(),
|
|
51
|
+
filePathBaseline: z.string(),
|
|
52
|
+
filePathCurrent: z.string(),
|
|
53
|
+
filePathDifference: z.string(),
|
|
54
|
+
browserConfig: z.custom().optional(),
|
|
55
|
+
threshold: z.number(),
|
|
56
|
+
waitBeforeScreenshot: z.number().optional(),
|
|
57
|
+
stabilizeBeforeScreenshot: z.boolean().optional(),
|
|
58
|
+
importPath: z.string().optional(),
|
|
59
|
+
mask: z.array(MaskSchema).optional(),
|
|
60
|
+
viewport: z
|
|
61
|
+
.object({
|
|
62
|
+
width: z.number(),
|
|
63
|
+
height: z.number().optional(),
|
|
64
|
+
})
|
|
65
|
+
.optional(),
|
|
66
|
+
breakpoint: z.number().optional(),
|
|
67
|
+
breakpointGroup: z.string().optional(),
|
|
68
|
+
elementLocator: z.string().optional(),
|
|
69
|
+
waitForSelector: z.string().optional(),
|
|
70
|
+
});
|
|
71
|
+
const TimeoutsSchema = z.object({
|
|
72
|
+
/**
|
|
73
|
+
* Timeout for fetching stories
|
|
74
|
+
* @default 30_000
|
|
75
|
+
*/
|
|
76
|
+
fetchStories: z.number().default(30_000),
|
|
77
|
+
/**
|
|
78
|
+
* Timeout for loading the state of the page
|
|
79
|
+
* @default 30_000
|
|
80
|
+
*/
|
|
81
|
+
loadState: z.number().default(30_000),
|
|
82
|
+
/**
|
|
83
|
+
* Timeout for waiting for network requests to finish
|
|
84
|
+
* @default 30_000
|
|
85
|
+
*/
|
|
86
|
+
networkRequests: z.number().default(30_000),
|
|
87
|
+
});
|
|
88
|
+
const BaseConfigSchema = z.object({
|
|
89
|
+
/**
|
|
90
|
+
* Browser to use: chromium, firefox, or webkit
|
|
91
|
+
* @default 'chromium'
|
|
92
|
+
*/
|
|
93
|
+
browser: z
|
|
94
|
+
.union([BrowserSchema, z.array(BrowserSchema).default(['chromium'])])
|
|
95
|
+
.default('chromium'),
|
|
96
|
+
/**
|
|
97
|
+
* Enable Storybook mode
|
|
98
|
+
*/
|
|
99
|
+
storybookShots: StorybookShotsSchema.optional(),
|
|
100
|
+
/**
|
|
101
|
+
* Path to the current image folder
|
|
102
|
+
* @default '.lostpixel/current/'
|
|
103
|
+
*/
|
|
104
|
+
imagePathCurrent: z.string().default('.lostpixel/current/'),
|
|
105
|
+
/**
|
|
106
|
+
* Define custom breakpoints for all tests as width in pixels
|
|
107
|
+
* @default []
|
|
108
|
+
* @example
|
|
109
|
+
* [ 320, 768, 1280 ]
|
|
110
|
+
*/
|
|
111
|
+
breakpoints: z.array(z.number()).optional(),
|
|
112
|
+
/**
|
|
113
|
+
* Number of concurrent shots to take. Auto-scales with the number of CPUs
|
|
114
|
+
* available to the process by default (floor `10`, ceiling `32`). Set to
|
|
115
|
+
* `0` to opt back into the auto default from an explicit config.
|
|
116
|
+
* @default auto
|
|
117
|
+
*/
|
|
118
|
+
shotConcurrency: z
|
|
119
|
+
.number()
|
|
120
|
+
.nonnegative()
|
|
121
|
+
.default(() => defaultShotConcurrency())
|
|
122
|
+
.transform((value) => (value === 0 ? defaultShotConcurrency() : value)),
|
|
123
|
+
/**
|
|
124
|
+
* Timeouts for various stages of the test
|
|
125
|
+
*/
|
|
126
|
+
timeouts: TimeoutsSchema.default({
|
|
127
|
+
fetchStories: 30_000,
|
|
128
|
+
loadState: 30_000,
|
|
129
|
+
networkRequests: 30_000,
|
|
130
|
+
}),
|
|
131
|
+
/**
|
|
132
|
+
* Maximum time to wait before taking a screenshot. When
|
|
133
|
+
* `stabilizeBeforeScreenshot` is `true` (the default), this is the cap on
|
|
134
|
+
* the adaptive stability wait. When `false`, this is the fixed sleep.
|
|
135
|
+
* @default 1_000
|
|
136
|
+
*/
|
|
137
|
+
waitBeforeScreenshot: z.number().default(1000),
|
|
138
|
+
/**
|
|
139
|
+
* When `true`, the wait before taking a screenshot is adaptive: lost-pixel
|
|
140
|
+
* waits for web fonts, pending images, one committed frame, and a quiet
|
|
141
|
+
* DOM, bounded above by `waitBeforeScreenshot`. When `false`, falls back
|
|
142
|
+
* to a fixed sleep of `waitBeforeScreenshot` milliseconds.
|
|
143
|
+
* @default true
|
|
144
|
+
*/
|
|
145
|
+
stabilizeBeforeScreenshot: z.boolean().default(true),
|
|
146
|
+
/**
|
|
147
|
+
* Time to wait for the first network request to start
|
|
148
|
+
* @default 200
|
|
149
|
+
*/
|
|
150
|
+
waitForFirstRequest: z.number().default(200),
|
|
151
|
+
/**
|
|
152
|
+
* Time to wait for the last network request to start
|
|
153
|
+
* @default 1_000
|
|
154
|
+
*/
|
|
155
|
+
waitForLastRequest: z.number().default(1000),
|
|
156
|
+
/**
|
|
157
|
+
* Threshold for the difference between the baseline and current image
|
|
158
|
+
*
|
|
159
|
+
* Values between 0 and 1 are interpreted as percentage of the image size
|
|
160
|
+
*
|
|
161
|
+
* Values greater or equal to 1 are interpreted as pixel count.
|
|
162
|
+
* @default 0
|
|
163
|
+
*/
|
|
164
|
+
threshold: z.number().default(0),
|
|
165
|
+
/**
|
|
166
|
+
* How often to retry a shot for a stable result
|
|
167
|
+
* @default 0
|
|
168
|
+
*/
|
|
169
|
+
flakynessRetries: z.number().default(0),
|
|
170
|
+
/**
|
|
171
|
+
* Time to wait between flakyness retries
|
|
172
|
+
* @default 2_000
|
|
173
|
+
*/
|
|
174
|
+
waitBetweenFlakynessRetries: z.number().default(2000),
|
|
175
|
+
/**
|
|
176
|
+
* Global shot filter
|
|
177
|
+
*/
|
|
178
|
+
filterShot: z
|
|
179
|
+
.function()
|
|
180
|
+
.args(StoryLikeSchema)
|
|
181
|
+
.returns(z.boolean())
|
|
182
|
+
.optional(),
|
|
183
|
+
/**
|
|
184
|
+
* Shot and file name generator for images
|
|
185
|
+
*/
|
|
186
|
+
shotNameGenerator: z
|
|
187
|
+
.function()
|
|
188
|
+
.args(StoryLikeSchema)
|
|
189
|
+
.returns(z.string())
|
|
190
|
+
.optional(),
|
|
191
|
+
/**
|
|
192
|
+
* Configure browser context options
|
|
193
|
+
*/
|
|
194
|
+
configureBrowser: z
|
|
195
|
+
.function()
|
|
196
|
+
.args(StoryLikeSchema)
|
|
197
|
+
.returns(z.custom())
|
|
198
|
+
.optional(),
|
|
199
|
+
/**
|
|
200
|
+
* Configure page before screenshot
|
|
201
|
+
*/
|
|
202
|
+
beforeScreenshot: z
|
|
203
|
+
.function()
|
|
204
|
+
.args(z.custom(), StoryLikeSchema)
|
|
205
|
+
.returns(z.promise(z.void()))
|
|
206
|
+
.optional(),
|
|
207
|
+
/**
|
|
208
|
+
* Perform actions after screenshot was taken
|
|
209
|
+
*/
|
|
210
|
+
afterScreenshot: z
|
|
211
|
+
.function()
|
|
212
|
+
.args(z.custom(), StoryLikeSchema)
|
|
213
|
+
.returns(z.promise(z.void()))
|
|
214
|
+
.optional(),
|
|
215
|
+
/**
|
|
216
|
+
* Launch options for the browser
|
|
217
|
+
*/
|
|
218
|
+
browserLaunchOptions: z
|
|
219
|
+
.object({
|
|
220
|
+
chromium: z.custom().optional(),
|
|
221
|
+
firefox: z.custom().optional(),
|
|
222
|
+
webkit: z.custom().optional(),
|
|
223
|
+
})
|
|
224
|
+
.optional(),
|
|
225
|
+
});
|
|
226
|
+
export const PlatformModeConfigSchema = BaseConfigSchema.extend({
|
|
227
|
+
/**
|
|
228
|
+
* URL of the Lost Pixel API endpoint
|
|
229
|
+
* @default 'https://api.lost-pixel.com'
|
|
230
|
+
*/
|
|
231
|
+
lostPixelPlatform: z.string().default('https://api.lost-pixel.com'),
|
|
232
|
+
/**
|
|
233
|
+
* API key for the Lost Pixel platform
|
|
234
|
+
*/
|
|
235
|
+
apiKey: z.string(),
|
|
236
|
+
/**
|
|
237
|
+
* Project ID
|
|
238
|
+
*/
|
|
239
|
+
lostPixelProjectId: z.string(),
|
|
240
|
+
/**
|
|
241
|
+
* CI build ID
|
|
242
|
+
*/
|
|
243
|
+
ciBuildId: z
|
|
244
|
+
.string({
|
|
245
|
+
required_error: 'Required (can be set via `CI_BUILD_ID` env variable)',
|
|
246
|
+
})
|
|
247
|
+
// @ts-expect-error If not set, it will be caught during config validation
|
|
248
|
+
.default(process.env.CI_BUILD_ID),
|
|
249
|
+
/**
|
|
250
|
+
* CI build number
|
|
251
|
+
*/
|
|
252
|
+
ciBuildNumber: z
|
|
253
|
+
.string({
|
|
254
|
+
required_error: 'Required (can be set via `CI_BUILD_NUMBER` env variable)',
|
|
255
|
+
})
|
|
256
|
+
// @ts-expect-error If not set, it will be caught during config validation
|
|
257
|
+
.default(process.env.CI_BUILD_NUMBER),
|
|
258
|
+
/**
|
|
259
|
+
* Git repository name (e.g. 'lost-pixel/lost-pixel-storybook')
|
|
260
|
+
*/
|
|
261
|
+
repository: z
|
|
262
|
+
.string({
|
|
263
|
+
required_error: 'Required (can be set via `REPOSITORY` env variable)',
|
|
264
|
+
})
|
|
265
|
+
// @ts-expect-error If not set, it will be caught during config validation
|
|
266
|
+
.default(process.env.REPOSITORY),
|
|
267
|
+
/**
|
|
268
|
+
* Git branch name (e.g. 'main')
|
|
269
|
+
*/
|
|
270
|
+
commitRefName: z
|
|
271
|
+
.string({
|
|
272
|
+
required_error: 'Required (can be set via `COMMIT_REF_NAME` env variable)',
|
|
273
|
+
})
|
|
274
|
+
// @ts-expect-error If not set, it will be caught during config validation
|
|
275
|
+
.default(process.env.COMMIT_REF_NAME),
|
|
276
|
+
/**
|
|
277
|
+
* Git commit SHA (e.g. 'b9b8b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9')
|
|
278
|
+
*/
|
|
279
|
+
commitHash: z
|
|
280
|
+
.string({
|
|
281
|
+
required_error: 'Required (can be set via `COMMIT_HASH` env variable)',
|
|
282
|
+
})
|
|
283
|
+
// @ts-expect-error If not set, it will be caught during config validation
|
|
284
|
+
.default(process.env.COMMIT_HASH),
|
|
285
|
+
/**
|
|
286
|
+
* File path to event.json file
|
|
287
|
+
*/
|
|
288
|
+
eventFilePath: z.string().optional(),
|
|
289
|
+
/**
|
|
290
|
+
* Whether to set the GitHub status check on process start or not
|
|
291
|
+
*
|
|
292
|
+
* Setting this option to `true` makes only sense if the repository settings have pending status checks disabled
|
|
293
|
+
* @default false
|
|
294
|
+
*/
|
|
295
|
+
setPendingStatusCheck: z.boolean().default(false),
|
|
296
|
+
/**
|
|
297
|
+
* Skip taking screenshots and only upload existing images to the platform.
|
|
298
|
+
*
|
|
299
|
+
* When enabled, lost-pixel reads `.png` files from `imagePathCurrent`
|
|
300
|
+
* instead of launching a browser. Useful when screenshots are produced
|
|
301
|
+
* by an external process.
|
|
302
|
+
* @default false
|
|
303
|
+
*/
|
|
304
|
+
uploadOnly: z.boolean().default(false),
|
|
305
|
+
});
|
|
306
|
+
export const GenerateOnlyModeConfigSchema = BaseConfigSchema.extend({
|
|
307
|
+
/**
|
|
308
|
+
* Run in local mode
|
|
309
|
+
* @deprecated Defaults to running in generateOnly mode
|
|
310
|
+
*/
|
|
311
|
+
generateOnly: z.boolean().optional(),
|
|
312
|
+
/**
|
|
313
|
+
* Skip taking screenshots and only upload them to the platform.
|
|
314
|
+
*/
|
|
315
|
+
uploadOnly: z.boolean().optional(),
|
|
316
|
+
/**
|
|
317
|
+
* Flag that decides if process should exit if a difference is found
|
|
318
|
+
*/
|
|
319
|
+
failOnDifference: z.boolean().optional(),
|
|
320
|
+
/**
|
|
321
|
+
* Path to the baseline image folder
|
|
322
|
+
* @default '.lostpixel/baseline/'
|
|
323
|
+
*/
|
|
324
|
+
imagePathBaseline: z.string().default('.lostpixel/baseline/'),
|
|
325
|
+
/**
|
|
326
|
+
* Path to the difference image folder
|
|
327
|
+
* @default '.lostpixel/difference/'
|
|
328
|
+
*/
|
|
329
|
+
imagePathDifference: z.string().default('.lostpixel/difference/'),
|
|
330
|
+
/**
|
|
331
|
+
* Number of concurrent screenshots to compare
|
|
332
|
+
* @default 10
|
|
333
|
+
*/
|
|
334
|
+
compareConcurrency: z.number().default(10),
|
|
335
|
+
/**
|
|
336
|
+
* Which comparison engine to use for diffing images
|
|
337
|
+
* @default 'pixelmatch'
|
|
338
|
+
*/
|
|
339
|
+
compareEngine: z.enum(['pixelmatch', 'odiff']).default('pixelmatch'),
|
|
340
|
+
/**
|
|
341
|
+
* Filter stories to take screenshots of and run comparisons on (existing shots remain untouched)
|
|
342
|
+
*/
|
|
343
|
+
filterItemsToCheck: z
|
|
344
|
+
.function()
|
|
345
|
+
.args(ShotItem)
|
|
346
|
+
.returns(z.boolean())
|
|
347
|
+
.optional(),
|
|
348
|
+
});
|
|
349
|
+
export const ConfigSchema = z.union([
|
|
350
|
+
PlatformModeConfigSchema,
|
|
351
|
+
GenerateOnlyModeConfigSchema,
|
|
352
|
+
]);
|
|
353
|
+
// use partial() specifically for the inferred type
|
|
354
|
+
export const FlexibleConfigSchema = z.union([
|
|
355
|
+
PlatformModeConfigSchema.extend({
|
|
356
|
+
timeouts: TimeoutsSchema.partial(),
|
|
357
|
+
}).partial(),
|
|
358
|
+
GenerateOnlyModeConfigSchema.extend({
|
|
359
|
+
timeouts: TimeoutsSchema.partial(),
|
|
360
|
+
}).partial(),
|
|
361
|
+
]);
|
|
362
|
+
export let config;
|
|
363
|
+
export const isPlatformModeConfig = (userConfig) => ('apiKey' in userConfig && typeof userConfig.apiKey === 'string') ||
|
|
364
|
+
('lostPixelProjectId' in userConfig &&
|
|
365
|
+
typeof userConfig.lostPixelProjectId === 'string');
|
|
366
|
+
const printConfigErrors = (error) => {
|
|
367
|
+
for (const issue of error.issues) {
|
|
368
|
+
log.process('error', 'config', [
|
|
369
|
+
'Configuration error:',
|
|
370
|
+
` - Path: ${issue.path.join('.')}`,
|
|
371
|
+
` - Message: ${issue.message}`,
|
|
372
|
+
].join('\n'));
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
export const parseConfig = (userConfig) => {
|
|
376
|
+
if (isPlatformModeConfig(userConfig)) {
|
|
377
|
+
const platformCheck = PlatformModeConfigSchema.safeParse(userConfig);
|
|
378
|
+
if (platformCheck.success) {
|
|
379
|
+
return platformCheck.data;
|
|
380
|
+
}
|
|
381
|
+
printConfigErrors(platformCheck.error);
|
|
382
|
+
}
|
|
383
|
+
else {
|
|
384
|
+
const generateOnlyCheck = GenerateOnlyModeConfigSchema.safeParse(userConfig);
|
|
385
|
+
if (generateOnlyCheck.success) {
|
|
386
|
+
return generateOnlyCheck.data;
|
|
387
|
+
}
|
|
388
|
+
printConfigErrors(generateOnlyCheck.error);
|
|
389
|
+
}
|
|
390
|
+
throw new Error('Configuration error');
|
|
391
|
+
};
|
|
392
|
+
const configDirBase = process.env.LOST_PIXEL_CONFIG_DIR ?? process.cwd();
|
|
393
|
+
const configFileNameBase = path.join(path.isAbsolute(configDirBase) ? '' : process.cwd(), configDirBase, 'lostpixel.config');
|
|
394
|
+
const loadProjectConfig = async () => {
|
|
395
|
+
log.process('info', 'config', 'Loading project config ...');
|
|
396
|
+
log.process('info', 'config', 'Current working directory:', process.cwd());
|
|
397
|
+
if (process.env.LOST_PIXEL_CONFIG_DIR) {
|
|
398
|
+
log.process('info', 'config', 'Defined config directory:', process.env.LOST_PIXEL_CONFIG_DIR);
|
|
399
|
+
}
|
|
400
|
+
const configExtensions = ['ts', 'js', 'cjs', 'mjs'];
|
|
401
|
+
const configExtensionsString = configExtensions.join('|');
|
|
402
|
+
log.process('info', 'config', 'Looking for config file:', `${configFileNameBase}.(${configExtensionsString})`);
|
|
403
|
+
const configFiles = configExtensions
|
|
404
|
+
.map((ext) => `${configFileNameBase}.${ext}`)
|
|
405
|
+
.filter((file) => existsSync(file));
|
|
406
|
+
if (configFiles.length === 0) {
|
|
407
|
+
log.process('error', 'config', `Couldn't find project config file 'lostpixel.config.(${configExtensionsString})'`);
|
|
408
|
+
process.exit(1);
|
|
409
|
+
}
|
|
410
|
+
if (configFiles.length > 1) {
|
|
411
|
+
log.process('info', 'config', '✅ Found multiple config files, taking:', configFiles[0]);
|
|
412
|
+
}
|
|
413
|
+
else {
|
|
414
|
+
log.process('info', 'config', '✅ Found config file:', configFiles[0]);
|
|
415
|
+
}
|
|
416
|
+
const configFile = configFiles[0];
|
|
417
|
+
try {
|
|
418
|
+
const imported = (await loadProjectConfigFile(configFile));
|
|
419
|
+
return imported;
|
|
420
|
+
}
|
|
421
|
+
catch {
|
|
422
|
+
log.process('error', 'config', 'Loading config using ESBuild failed, using fallback option');
|
|
423
|
+
try {
|
|
424
|
+
if (existsSync(`${configFileNameBase}.js`)) {
|
|
425
|
+
const projectConfig = (await import(`${configFileNameBase}.js`));
|
|
426
|
+
const resolved = projectConfig?.default ?? projectConfig;
|
|
427
|
+
log.process('info', 'config', '✅ Successfully loaded configuration from:', `${configFileNameBase}.js`);
|
|
428
|
+
return resolved;
|
|
429
|
+
}
|
|
430
|
+
if (existsSync(`${configFileNameBase}.ts`)) {
|
|
431
|
+
const imported = (await loadTSProjectConfigFile(configFile));
|
|
432
|
+
log.process('info', 'config', '✅ Successfully loaded configuration from:', `${configFileNameBase}.ts`);
|
|
433
|
+
return imported;
|
|
434
|
+
}
|
|
435
|
+
log.process('error', 'config', "Couldn't find project config file 'lostpixel.config.js'");
|
|
436
|
+
process.exit(1);
|
|
437
|
+
}
|
|
438
|
+
catch (error) {
|
|
439
|
+
log.process('error', 'config', `Failed to load config file: ${configFile}`);
|
|
440
|
+
log.process('error', 'config', error);
|
|
441
|
+
process.exit(1);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
};
|
|
445
|
+
export const configure = async ({ customProjectConfig, localDebugMode, }) => {
|
|
446
|
+
if (customProjectConfig) {
|
|
447
|
+
config = parseConfig(customProjectConfig);
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
let loadedProjectConfig = await loadProjectConfig();
|
|
451
|
+
if (localDebugMode) {
|
|
452
|
+
let localDebugConfig = loadedProjectConfig;
|
|
453
|
+
if (isPlatformModeConfig(loadedProjectConfig)) {
|
|
454
|
+
localDebugConfig = {
|
|
455
|
+
...loadedProjectConfig,
|
|
456
|
+
generateOnly: true,
|
|
457
|
+
// @ts-expect-error Force it into generateOnly mode by dropping the platform specific properties
|
|
458
|
+
lostPixelProjectId: undefined,
|
|
459
|
+
// @ts-expect-error Force it into generateOnly mode by dropping the platform specific properties
|
|
460
|
+
apiKey: undefined,
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
loadedProjectConfig = localDebugConfig;
|
|
464
|
+
}
|
|
465
|
+
// Default to Storybook mode if no mode is defined
|
|
466
|
+
if (!loadedProjectConfig.storybookShots) {
|
|
467
|
+
loadedProjectConfig.storybookShots = {
|
|
468
|
+
storybookUrl: 'storybook-static',
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
config = parseConfig(loadedProjectConfig);
|
|
472
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { log } from './log.js';
|
|
2
|
+
export const loadProjectConfigFile = async (configFilepath) => {
|
|
3
|
+
try {
|
|
4
|
+
const mod = (await import(configFilepath));
|
|
5
|
+
return mod?.default ?? mod?.config ?? mod;
|
|
6
|
+
}
|
|
7
|
+
catch (error) {
|
|
8
|
+
log.process('error', 'config', error);
|
|
9
|
+
throw error;
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
let unregisterTsx;
|
|
13
|
+
const setupTsx = async () => {
|
|
14
|
+
if (unregisterTsx) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
const tsx = await import('tsx/esm/api');
|
|
19
|
+
unregisterTsx = tsx.register();
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
// @ts-expect-error Error type definition is missing 'code'
|
|
23
|
+
if (['ERR_MODULE_NOT_FOUND', 'MODULE_NOT_FOUND'].includes(error.code)) {
|
|
24
|
+
log.process('error', 'config', `Please install "tsx" to use a TypeScript configuration file`);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
export const loadTSProjectConfigFile = async (configFilepath) => {
|
|
31
|
+
await setupTsx();
|
|
32
|
+
const imported = (await import(configFilepath));
|
|
33
|
+
return imported?.default ?? imported?.config;
|
|
34
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { BrowserContext, BrowserType } from 'playwright-core';
|
|
2
|
+
import type { Mask, ShotItem } from '../types.js';
|
|
3
|
+
type ExtraShots = {
|
|
4
|
+
name?: string;
|
|
5
|
+
args?: Record<string, unknown>;
|
|
6
|
+
prefix?: string;
|
|
7
|
+
suffix?: string;
|
|
8
|
+
};
|
|
9
|
+
export type StoryParameters = {
|
|
10
|
+
lostpixel?: {
|
|
11
|
+
disable?: boolean;
|
|
12
|
+
threshold?: number;
|
|
13
|
+
waitBeforeScreenshot?: number;
|
|
14
|
+
stabilizeBeforeScreenshot?: boolean;
|
|
15
|
+
mask?: Mask[];
|
|
16
|
+
breakpoints?: number[];
|
|
17
|
+
args?: Record<string, unknown>;
|
|
18
|
+
extraShots?: ExtraShots[];
|
|
19
|
+
elementLocator?: string;
|
|
20
|
+
};
|
|
21
|
+
viewport?: {
|
|
22
|
+
width?: number;
|
|
23
|
+
height?: number;
|
|
24
|
+
};
|
|
25
|
+
fileName?: string;
|
|
26
|
+
};
|
|
27
|
+
export type Story = {
|
|
28
|
+
id: string;
|
|
29
|
+
kind: string;
|
|
30
|
+
story: string;
|
|
31
|
+
name?: string;
|
|
32
|
+
title?: string;
|
|
33
|
+
importPath?: string;
|
|
34
|
+
parameters?: StoryParameters & {
|
|
35
|
+
storyshots?: {
|
|
36
|
+
disable?: boolean;
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
type CrawlerResult = {
|
|
41
|
+
stories: Story[] | undefined;
|
|
42
|
+
};
|
|
43
|
+
export declare const getStoryBookUrl: (url: string) => string;
|
|
44
|
+
export declare const getIframeUrl: (url: string) => string;
|
|
45
|
+
export declare const collectStoriesViaWindowApi: (context: BrowserContext, url: string, isIframeUrl?: boolean) => Promise<CrawlerResult>;
|
|
46
|
+
export declare const collectStoriesViaStoriesJson: (context: BrowserContext, url: string) => Promise<{
|
|
47
|
+
stories: Story[];
|
|
48
|
+
}>;
|
|
49
|
+
export declare const collectStories: (url: string) => Promise<CrawlerResult>;
|
|
50
|
+
export declare const generateStorybookShotItems: (baseUrl: string, stories: Story[], mask?: Mask[], modeBreakpoints?: number[], browser?: BrowserType) => ShotItem[];
|
|
51
|
+
export {};
|