everyscreen 0.0.0-stage → 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.
Files changed (46) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +241 -2
  3. package/dist/cli.d.mts +1 -0
  4. package/dist/cli.mjs +2958 -0
  5. package/dist/commands-logic-DVWfSe3O.mjs +217 -0
  6. package/dist/dashboard/assets/button-N-Nucq_K.js +1 -0
  7. package/dist/dashboard/assets/cn-DMYzmXtt.js +1 -0
  8. package/dist/dashboard/assets/device-status-logic-Silf3YXA.js +1 -0
  9. package/dist/dashboard/assets/devices-5pEUg6rA.js +1 -0
  10. package/dist/dashboard/assets/dist-CnPFbxes.js +1 -0
  11. package/dist/dashboard/assets/focus._deviceId-DGel-kWw.js +1 -0
  12. package/dist/dashboard/assets/frame-layout-logic-H_x71NvW.js +1 -0
  13. package/dist/dashboard/assets/index-DNHZ-i1B.css +2 -0
  14. package/dist/dashboard/assets/index-DwPQ0eY_.js +10 -0
  15. package/dist/dashboard/assets/modal-dialog-BaO9xkRr.js +1 -0
  16. package/dist/dashboard/assets/rolldown-runtime-WkwZ5CU-.js +1 -0
  17. package/dist/dashboard/assets/routes-CUFa9tL0.js +1 -0
  18. package/dist/dashboard/assets/run-origin-badge-BeFqg0AC.js +1 -0
  19. package/dist/dashboard/assets/runs-queries-BYBE7R9S.js +2 -0
  20. package/dist/dashboard/assets/runs._runId-CokpJ1Rr.js +1 -0
  21. package/dist/dashboard/assets/runs.index-ZsynzkKU.js +1 -0
  22. package/dist/dashboard/assets/schemas-BPFqmBjG.js +55 -0
  23. package/dist/dashboard/assets/screen-boundary-BauyD9pu.js +1 -0
  24. package/dist/dashboard/assets/status-chip-yqTTTuyZ.js +1 -0
  25. package/dist/dashboard/assets/tile-status-chip-DisgbNnH.js +1 -0
  26. package/dist/dashboard/assets/toggle-nciug_di.js +1 -0
  27. package/dist/dashboard/assets/use-run-totals-B3G4yamQ.js +1 -0
  28. package/dist/dashboard/assets/useNavigate-8mkM-ffY.js +1 -0
  29. package/dist/dashboard/assets/useParams-D2lK5BEg.js +1 -0
  30. package/dist/dashboard/index.html +29 -0
  31. package/dist/devices-store-edScJDie.mjs +1228 -0
  32. package/dist/playwright-logic-QFlBFspE.d.mts +138 -0
  33. package/dist/playwright.d.mts +2 -0
  34. package/dist/playwright.mjs +18 -0
  35. package/dist/read-hub-file-N4rZ-6Wr.mjs +180 -0
  36. package/dist/report-hub-trouble-BjbcuK21.d.mts +2 -0
  37. package/dist/report-hub-trouble-CaUiyteI.mjs +41 -0
  38. package/dist/reporter.d.mts +47 -0
  39. package/dist/reporter.mjs +250 -0
  40. package/dist/run-one-at-a-time-BeV_a3Fm.mjs +48 -0
  41. package/dist/runs-store-Bd_ITPFj.mjs +283 -0
  42. package/dist/test.d.mts +11 -0
  43. package/dist/test.mjs +69 -0
  44. package/package.json +106 -4
  45. package/src/dashboard/ui/theme.css +28 -0
  46. package/src/skill/SKILL.md +15 -0
@@ -0,0 +1,1228 @@
1
+ import { _ as DEVICES_FILE_PATH, g as CPU_TIERS } from "./commands-logic-DVWfSe3O.mjs";
2
+ import { i as writeStoredFile, r as readStoredFile, t as runOneAtATime } from "./run-one-at-a-time-BeV_a3Fm.mjs";
3
+ import { HTTPException } from "hono/http-exception";
4
+ import { z } from "zod";
5
+ import { join } from "node:path";
6
+ import { devices } from "playwright";
7
+ import { parse, stringify } from "yaml";
8
+ // src/devices/default-grid-ids.ts
9
+ const DEFAULT_GRID_IDS = [
10
+ "iphone-se",
11
+ "iphone-17",
12
+ "iphone-17-pro-max",
13
+ "galaxy-s25",
14
+ "pixel-10",
15
+ "xiaomi-15",
16
+ "ipad-mini",
17
+ "ipad-pro-11",
18
+ "galaxy-tab-s9",
19
+ "macbook-air-13",
20
+ "windows-laptop-1366",
21
+ "linux-1920"
22
+ ];
23
+ // src/devices/device-schema.ts
24
+ const BROWSER_NAMES = [
25
+ "chromium",
26
+ "webkit",
27
+ "firefox"
28
+ ];
29
+ const FRAME_SHAPES = [
30
+ "home-button",
31
+ "dynamic-island",
32
+ "punch-hole",
33
+ "tablet",
34
+ "laptop",
35
+ "kiosk"
36
+ ];
37
+ const VIEWPORT_MODES = ["full-screen", "browser"];
38
+ const DEVICE_NAME_PATTERN = /[\p{L}\p{N}]/u;
39
+ const MAX_DEVICE_NAME_LENGTH = 40;
40
+ const MIN_DEVICE_SIZE = 240;
41
+ const MAX_DEVICE_SIZE = 3840;
42
+ const MIN_PIXEL_RATIO = 1;
43
+ const MAX_PIXEL_RATIO = 4;
44
+ const DEFAULT_PIXEL_RATIO = 1;
45
+ const isOriginRelativeRoute = (route) => route.startsWith("/") && !route.startsWith("//");
46
+ const createSizeSchema = (flagName) => {
47
+ const sizeMessage = `--${flagName} must be a whole number from ${MIN_DEVICE_SIZE} to ${MAX_DEVICE_SIZE}`;
48
+ return z.number({ error: sizeMessage }).int({ error: sizeMessage }).min(MIN_DEVICE_SIZE, { error: sizeMessage }).max(MAX_DEVICE_SIZE, { error: sizeMessage });
49
+ };
50
+ const PIXEL_RATIO_MESSAGE = `--pixel-ratio must be a number from ${MIN_PIXEL_RATIO} to ${MAX_PIXEL_RATIO}`;
51
+ const customDeviceSchema = z.object({
52
+ name: z.string({ error: "needs --name <device name>" }).max(MAX_DEVICE_NAME_LENGTH, { error: `--name must be ${MAX_DEVICE_NAME_LENGTH} characters or less` }).regex(DEVICE_NAME_PATTERN, "--name needs a letter or digit"),
53
+ width: createSizeSchema("width"),
54
+ height: createSizeSchema("height"),
55
+ pixelRatio: z.number({ error: PIXEL_RATIO_MESSAGE }).min(MIN_PIXEL_RATIO, { error: PIXEL_RATIO_MESSAGE }).max(MAX_PIXEL_RATIO, { error: PIXEL_RATIO_MESSAGE }).default(DEFAULT_PIXEL_RATIO),
56
+ hasTouch: z.boolean().default(false),
57
+ frameShape: z.enum(FRAME_SHAPES).default("kiosk"),
58
+ browserName: z.enum(BROWSER_NAMES).default("chromium")
59
+ });
60
+ const devicesFileSchema = z.object({
61
+ devices: z.array(customDeviceSchema).default([]),
62
+ routes: z.array(z.string().refine(isOriginRelativeRoute, "Use a path that starts with a single /")).default([]),
63
+ grid: z.array(z.string()).default(DEFAULT_GRID_IDS),
64
+ viewportModes: z.record(z.string(), z.enum(VIEWPORT_MODES)).default({})
65
+ });
66
+ const gridUpdateSchema = z.object({ deviceIds: z.array(z.string().min(1)) });
67
+ // src/devices/desktop-user-agent-logic.ts
68
+ const REGISTRY_PLATFORM_PART = /\(Windows NT [^)]+\)/;
69
+ const PLATFORM_PARTS = {
70
+ macos: "(Macintosh; Intel Mac OS X 10_15_7)",
71
+ linux: "(X11; Linux x86_64)"
72
+ };
73
+ const createDesktopUserAgent = (desktopPlatform, registryUserAgent) => {
74
+ if (desktopPlatform === "windows") return registryUserAgent;
75
+ return registryUserAgent.replace(REGISTRY_PLATFORM_PART, PLATFORM_PARTS[desktopPlatform]);
76
+ };
77
+ // src/devices/frame-shape-insets.ts
78
+ const NO_INSETS = {
79
+ top: 0,
80
+ bottom: 0,
81
+ left: 0,
82
+ right: 0
83
+ };
84
+ const FRAME_SHAPE_INSETS = {
85
+ "home-button": NO_INSETS,
86
+ "dynamic-island": {
87
+ top: 59,
88
+ bottom: 34,
89
+ left: 0,
90
+ right: 0
91
+ },
92
+ "punch-hole": {
93
+ top: 24,
94
+ bottom: 0,
95
+ left: 0,
96
+ right: 0
97
+ },
98
+ tablet: NO_INSETS,
99
+ laptop: NO_INSETS,
100
+ kiosk: NO_INSETS
101
+ };
102
+ // src/devices/own-devices.ts
103
+ const ANDROID_USER_AGENT = devices["Pixel 10"].userAgent.replace(/Android [\d.]+; [^)]+\)/, "Android 10; K)");
104
+ const IPAD_USER_AGENT = devices["iPad Pro 11"].userAgent;
105
+ const REGISTRY_DESKTOP_USER_AGENT = devices["Desktop Chrome"].userAgent;
106
+ const UNMEASURED_CUTOUT = {
107
+ kind: "punch-hole-center",
108
+ width: null,
109
+ height: null,
110
+ top: null,
111
+ left: null
112
+ };
113
+ const NO_CUTOUT$1 = {
114
+ ...UNMEASURED_CUTOUT,
115
+ kind: "none"
116
+ };
117
+ const NOTCH$1 = {
118
+ ...UNMEASURED_CUTOUT,
119
+ kind: "notch"
120
+ };
121
+ const ANDROID_BASE$1 = {
122
+ registryName: null,
123
+ browserName: "chromium",
124
+ frameShape: "punch-hole",
125
+ cpuSlowdown: CPU_TIERS.phone,
126
+ platform: "android",
127
+ maker: "samsung",
128
+ userAgent: ANDROID_USER_AGENT,
129
+ isMobile: true,
130
+ hasTouch: true
131
+ };
132
+ const IPAD_BASE = {
133
+ registryName: null,
134
+ browserName: "webkit",
135
+ frameShape: "tablet",
136
+ cpuSlowdown: CPU_TIERS.tablet,
137
+ platform: "ipados",
138
+ maker: "apple",
139
+ userAgent: IPAD_USER_AGENT,
140
+ isMobile: true,
141
+ hasTouch: true
142
+ };
143
+ const MAC_BASE = {
144
+ registryName: null,
145
+ browserName: "chromium",
146
+ frameShape: "laptop",
147
+ cpuSlowdown: CPU_TIERS.desktop,
148
+ platform: "macos",
149
+ maker: "apple",
150
+ userAgent: createDesktopUserAgent("macos", REGISTRY_DESKTOP_USER_AGENT),
151
+ isMobile: false,
152
+ hasTouch: false
153
+ };
154
+ const WINDOWS_BASE = {
155
+ ...MAC_BASE,
156
+ platform: "windows",
157
+ maker: "generic",
158
+ userAgent: createDesktopUserAgent("windows", REGISTRY_DESKTOP_USER_AGENT)
159
+ };
160
+ const LINUX_BASE = {
161
+ ...MAC_BASE,
162
+ platform: "linux",
163
+ maker: "generic",
164
+ userAgent: createDesktopUserAgent("linux", REGISTRY_DESKTOP_USER_AGENT)
165
+ };
166
+ const createOwnDevice = (base, spec) => ({
167
+ ...base,
168
+ id: spec.id,
169
+ name: spec.name,
170
+ fullScreen: spec.size,
171
+ browserViewport: null,
172
+ deviceScaleFactor: spec.deviceScaleFactor,
173
+ cornerRadius: spec.cornerRadius,
174
+ cutout: spec.cutout,
175
+ safeAreaInsets: spec.safeAreaInsets
176
+ });
177
+ const createTabletSpec = (identity, size) => ({
178
+ ...identity,
179
+ size,
180
+ deviceScaleFactor: 2,
181
+ cutout: NO_CUTOUT$1,
182
+ safeAreaInsets: FRAME_SHAPE_INSETS.tablet
183
+ });
184
+ const createPhoneSpec = (identity, screen) => ({
185
+ ...identity,
186
+ size: screen.size,
187
+ deviceScaleFactor: screen.deviceScaleFactor,
188
+ cornerRadius: null,
189
+ cutout: UNMEASURED_CUTOUT,
190
+ safeAreaInsets: FRAME_SHAPE_INSETS["punch-hole"]
191
+ });
192
+ const createDesktopSpec = (identity, size) => ({
193
+ ...identity,
194
+ size,
195
+ deviceScaleFactor: 1,
196
+ cornerRadius: 0,
197
+ cutout: NO_CUTOUT$1,
198
+ safeAreaInsets: FRAME_SHAPE_INSETS.laptop
199
+ });
200
+ const IPAD_DEVICES = [
201
+ createOwnDevice(IPAD_BASE, createTabletSpec({
202
+ id: "ipad-mini",
203
+ name: "iPad mini",
204
+ cornerRadius: null
205
+ }, {
206
+ width: 744,
207
+ height: 1133
208
+ })),
209
+ createOwnDevice(IPAD_BASE, createTabletSpec({
210
+ id: "ipad-air-11",
211
+ name: "iPad Air 11",
212
+ cornerRadius: 18
213
+ }, {
214
+ width: 820,
215
+ height: 1180
216
+ })),
217
+ createOwnDevice(IPAD_BASE, createTabletSpec({
218
+ id: "ipad-air-13",
219
+ name: "iPad Air 13",
220
+ cornerRadius: 18
221
+ }, {
222
+ width: 1024,
223
+ height: 1366
224
+ })),
225
+ createOwnDevice(IPAD_BASE, createTabletSpec({
226
+ id: "ipad-pro-13",
227
+ name: "iPad Pro 13",
228
+ cornerRadius: null
229
+ }, {
230
+ width: 1032,
231
+ height: 1376
232
+ }))
233
+ ];
234
+ const SAMSUNG_DEVICES = [
235
+ createOwnDevice(ANDROID_BASE$1, createPhoneSpec({
236
+ id: "galaxy-s25",
237
+ name: "Galaxy S25"
238
+ }, {
239
+ size: {
240
+ width: 360,
241
+ height: 780
242
+ },
243
+ deviceScaleFactor: 3
244
+ })),
245
+ createOwnDevice(ANDROID_BASE$1, createPhoneSpec({
246
+ id: "galaxy-s25-plus",
247
+ name: "Galaxy S25+"
248
+ }, {
249
+ size: {
250
+ width: 412,
251
+ height: 891
252
+ },
253
+ deviceScaleFactor: 3.5
254
+ })),
255
+ createOwnDevice(ANDROID_BASE$1, createPhoneSpec({
256
+ id: "galaxy-s25-ultra",
257
+ name: "Galaxy S25 Ultra"
258
+ }, {
259
+ size: {
260
+ width: 412,
261
+ height: 891
262
+ },
263
+ deviceScaleFactor: 3.5
264
+ })),
265
+ createOwnDevice(ANDROID_BASE$1, createPhoneSpec({
266
+ id: "galaxy-s26",
267
+ name: "Galaxy S26"
268
+ }, {
269
+ size: {
270
+ width: 360,
271
+ height: 780
272
+ },
273
+ deviceScaleFactor: 3
274
+ })),
275
+ createOwnDevice(ANDROID_BASE$1, createPhoneSpec({
276
+ id: "galaxy-s26-plus",
277
+ name: "Galaxy S26+"
278
+ }, {
279
+ size: {
280
+ width: 412,
281
+ height: 891
282
+ },
283
+ deviceScaleFactor: 3.5
284
+ })),
285
+ createOwnDevice(ANDROID_BASE$1, createPhoneSpec({
286
+ id: "galaxy-s26-ultra",
287
+ name: "Galaxy S26 Ultra"
288
+ }, {
289
+ size: {
290
+ width: 412,
291
+ height: 891
292
+ },
293
+ deviceScaleFactor: 3.5
294
+ }))
295
+ ];
296
+ const XIAOMI_BASE = {
297
+ ...ANDROID_BASE$1,
298
+ maker: "xiaomi"
299
+ };
300
+ const ONEPLUS_BASE = {
301
+ ...ANDROID_BASE$1,
302
+ maker: "oneplus"
303
+ };
304
+ const XIAOMI_DEVICES = [
305
+ createOwnDevice(XIAOMI_BASE, {
306
+ ...createPhoneSpec({
307
+ id: "xiaomi-14",
308
+ name: "Xiaomi 14"
309
+ }, {
310
+ size: {
311
+ width: 400,
312
+ height: 890
313
+ },
314
+ deviceScaleFactor: 3
315
+ }),
316
+ cornerRadius: 41.33,
317
+ safeAreaInsets: {
318
+ ...FRAME_SHAPE_INSETS["punch-hole"],
319
+ top: 49
320
+ }
321
+ }),
322
+ createOwnDevice(XIAOMI_BASE, createPhoneSpec({
323
+ id: "xiaomi-15",
324
+ name: "Xiaomi 15"
325
+ }, {
326
+ size: {
327
+ width: 400,
328
+ height: 890
329
+ },
330
+ deviceScaleFactor: 3
331
+ })),
332
+ createOwnDevice(XIAOMI_BASE, createPhoneSpec({
333
+ id: "redmi-note-13",
334
+ name: "Redmi Note 13"
335
+ }, {
336
+ size: {
337
+ width: 393,
338
+ height: 873
339
+ },
340
+ deviceScaleFactor: 2.748
341
+ })),
342
+ createOwnDevice(XIAOMI_BASE, createPhoneSpec({
343
+ id: "redmi-note-14",
344
+ name: "Redmi Note 14"
345
+ }, {
346
+ size: {
347
+ width: 393,
348
+ height: 873
349
+ },
350
+ deviceScaleFactor: 2.748
351
+ }))
352
+ ];
353
+ const ONEPLUS_13 = createOwnDevice(ONEPLUS_BASE, {
354
+ id: "oneplus-13",
355
+ name: "OnePlus 13",
356
+ size: {
357
+ width: 412,
358
+ height: 905
359
+ },
360
+ deviceScaleFactor: 3.5,
361
+ cornerRadius: 32,
362
+ cutout: {
363
+ kind: "punch-hole-center",
364
+ width: 19.43,
365
+ height: 19.43,
366
+ top: 13.14,
367
+ left: 196
368
+ },
369
+ safeAreaInsets: {
370
+ ...FRAME_SHAPE_INSETS["punch-hole"],
371
+ top: 45.67
372
+ }
373
+ });
374
+ const MAC_DEVICES = [
375
+ createOwnDevice(MAC_BASE, {
376
+ ...createDesktopSpec({
377
+ id: "macbook-air-13",
378
+ name: "MacBook Air 13"
379
+ }, {
380
+ width: 1470,
381
+ height: 956
382
+ }),
383
+ cornerRadius: null,
384
+ cutout: NOTCH$1
385
+ }),
386
+ createOwnDevice(MAC_BASE, {
387
+ ...createDesktopSpec({
388
+ id: "macbook-air-15",
389
+ name: "MacBook Air 15"
390
+ }, {
391
+ width: 1710,
392
+ height: 1107
393
+ }),
394
+ cornerRadius: null,
395
+ cutout: NOTCH$1
396
+ }),
397
+ createOwnDevice(MAC_BASE, {
398
+ ...createDesktopSpec({
399
+ id: "macbook-pro-14",
400
+ name: "MacBook Pro 14"
401
+ }, {
402
+ width: 1512,
403
+ height: 982
404
+ }),
405
+ cornerRadius: null,
406
+ cutout: NOTCH$1
407
+ }),
408
+ createOwnDevice(MAC_BASE, {
409
+ ...createDesktopSpec({
410
+ id: "macbook-pro-16",
411
+ name: "MacBook Pro 16"
412
+ }, {
413
+ width: 1728,
414
+ height: 1117
415
+ }),
416
+ cornerRadius: null,
417
+ cutout: NOTCH$1
418
+ })
419
+ ];
420
+ const MONITOR_DEVICES = [
421
+ createOwnDevice(WINDOWS_BASE, createDesktopSpec({
422
+ id: "windows-laptop-1366",
423
+ name: "Windows laptop 1366"
424
+ }, {
425
+ width: 1366,
426
+ height: 768
427
+ })),
428
+ createOwnDevice(WINDOWS_BASE, createDesktopSpec({
429
+ id: "windows-laptop-1536",
430
+ name: "Windows laptop 1536"
431
+ }, {
432
+ width: 1536,
433
+ height: 864
434
+ })),
435
+ createOwnDevice(WINDOWS_BASE, createDesktopSpec({
436
+ id: "monitor-1920",
437
+ name: "Windows monitor 1920"
438
+ }, {
439
+ width: 1920,
440
+ height: 1080
441
+ })),
442
+ createOwnDevice(WINDOWS_BASE, createDesktopSpec({
443
+ id: "monitor-2560",
444
+ name: "Windows monitor 2560"
445
+ }, {
446
+ width: 2560,
447
+ height: 1440
448
+ })),
449
+ createOwnDevice(LINUX_BASE, createDesktopSpec({
450
+ id: "linux-1920",
451
+ name: "Linux monitor 1920"
452
+ }, {
453
+ width: 1920,
454
+ height: 1080
455
+ }))
456
+ ];
457
+ const LEGACY_DEVICES = [createOwnDevice(MAC_BASE, {
458
+ ...createDesktopSpec({
459
+ id: "laptop-1440",
460
+ name: "Laptop 1440"
461
+ }, {
462
+ width: 1440,
463
+ height: 900
464
+ }),
465
+ cutout: NO_CUTOUT$1
466
+ }), createOwnDevice({
467
+ ...LINUX_BASE,
468
+ frameShape: "kiosk",
469
+ hasTouch: true
470
+ }, createDesktopSpec({
471
+ id: "store-kiosk",
472
+ name: "Store kiosk"
473
+ }, {
474
+ width: 1080,
475
+ height: 1920
476
+ }))];
477
+ const OWN_DEVICES = [
478
+ ...IPAD_DEVICES,
479
+ ...SAMSUNG_DEVICES,
480
+ ...XIAOMI_DEVICES,
481
+ ONEPLUS_13,
482
+ ...MAC_DEVICES,
483
+ ...MONITOR_DEVICES,
484
+ ...LEGACY_DEVICES
485
+ ];
486
+ // src/devices/registry-logic.ts
487
+ const pickRegistryScreen = (registrySizes) => registrySizes.screen ?? registrySizes.viewport;
488
+ const createCatalogDeviceFromRegistry = (identity, registryName, registryDevice) => ({
489
+ ...identity,
490
+ registryName,
491
+ fullScreen: pickRegistryScreen(registryDevice),
492
+ browserViewport: registryDevice.viewport,
493
+ userAgent: registryDevice.userAgent,
494
+ deviceScaleFactor: registryDevice.deviceScaleFactor,
495
+ isMobile: registryDevice.isMobile,
496
+ hasTouch: registryDevice.hasTouch
497
+ });
498
+ // src/devices/registry-devices.ts
499
+ const IPHONE_BASE = {
500
+ browserName: "webkit",
501
+ frameShape: "dynamic-island",
502
+ cpuSlowdown: CPU_TIERS.phone,
503
+ platform: "ios",
504
+ maker: "apple"
505
+ };
506
+ const ANDROID_BASE = {
507
+ browserName: "chromium",
508
+ frameShape: "punch-hole",
509
+ cpuSlowdown: CPU_TIERS.phone,
510
+ platform: "android",
511
+ maker: "google"
512
+ };
513
+ const SAMSUNG_BASE = {
514
+ ...ANDROID_BASE,
515
+ maker: "samsung"
516
+ };
517
+ const TABLET_BASE = {
518
+ browserName: "webkit",
519
+ frameShape: "tablet",
520
+ cpuSlowdown: CPU_TIERS.tablet,
521
+ platform: "ipados",
522
+ maker: "apple"
523
+ };
524
+ const DYNAMIC_ISLAND = {
525
+ kind: "dynamic-island",
526
+ width: null,
527
+ height: null,
528
+ top: null,
529
+ left: null
530
+ };
531
+ const NOTCH = {
532
+ ...DYNAMIC_ISLAND,
533
+ kind: "notch"
534
+ };
535
+ const UNMEASURED_PUNCH_HOLE = {
536
+ ...DYNAMIC_ISLAND,
537
+ kind: "punch-hole-center"
538
+ };
539
+ const NO_CUTOUT = {
540
+ ...DYNAMIC_ISLAND,
541
+ kind: "none"
542
+ };
543
+ const ISLAND_INSETS_IPHONE_15 = {
544
+ top: 59,
545
+ bottom: 34,
546
+ left: 0,
547
+ right: 0
548
+ };
549
+ const ISLAND_INSETS_IPHONE_16_PRO = {
550
+ ...ISLAND_INSETS_IPHONE_15,
551
+ top: 62
552
+ };
553
+ const ISLAND_INSETS_IPHONE_AIR = {
554
+ ...ISLAND_INSETS_IPHONE_15,
555
+ top: 68
556
+ };
557
+ const NOTCH_INSETS_IPHONE_13 = {
558
+ ...ISLAND_INSETS_IPHONE_15,
559
+ top: 47
560
+ };
561
+ const createPunchHole = (hole) => ({
562
+ kind: "punch-hole-center",
563
+ width: hole.diameter,
564
+ height: hole.diameter,
565
+ top: hole.top,
566
+ left: hole.left
567
+ });
568
+ const createTopInset = (top) => ({
569
+ ...FRAME_SHAPE_INSETS["punch-hole"],
570
+ top
571
+ });
572
+ const REGISTRY_DEVICE_SPECS = [
573
+ {
574
+ registryName: "iPhone SE (3rd gen)",
575
+ identity: {
576
+ ...IPHONE_BASE,
577
+ id: "iphone-se",
578
+ name: "iPhone SE",
579
+ frameShape: "home-button",
580
+ cpuSlowdown: CPU_TIERS.budgetPhone,
581
+ cornerRadius: null,
582
+ cutout: {
583
+ ...NO_CUTOUT,
584
+ kind: "home-button"
585
+ },
586
+ safeAreaInsets: FRAME_SHAPE_INSETS["home-button"]
587
+ }
588
+ },
589
+ {
590
+ registryName: "iPhone 15",
591
+ identity: {
592
+ ...IPHONE_BASE,
593
+ id: "iphone-15",
594
+ name: "iPhone 15",
595
+ cornerRadius: 55,
596
+ cutout: DYNAMIC_ISLAND,
597
+ safeAreaInsets: ISLAND_INSETS_IPHONE_15
598
+ }
599
+ },
600
+ {
601
+ registryName: "iPhone 15 Plus",
602
+ identity: {
603
+ ...IPHONE_BASE,
604
+ id: "iphone-15-plus",
605
+ name: "iPhone 15 Plus",
606
+ cornerRadius: 55,
607
+ cutout: DYNAMIC_ISLAND,
608
+ safeAreaInsets: ISLAND_INSETS_IPHONE_15
609
+ }
610
+ },
611
+ {
612
+ registryName: "iPhone 15 Pro",
613
+ identity: {
614
+ ...IPHONE_BASE,
615
+ id: "iphone-15-pro",
616
+ name: "iPhone 15 Pro",
617
+ cornerRadius: 55,
618
+ cutout: DYNAMIC_ISLAND,
619
+ safeAreaInsets: ISLAND_INSETS_IPHONE_15
620
+ }
621
+ },
622
+ {
623
+ registryName: "iPhone 15 Pro Max",
624
+ identity: {
625
+ ...IPHONE_BASE,
626
+ id: "iphone-15-pro-max",
627
+ name: "iPhone 15 Pro Max",
628
+ cornerRadius: 55,
629
+ cutout: DYNAMIC_ISLAND,
630
+ safeAreaInsets: ISLAND_INSETS_IPHONE_15
631
+ }
632
+ },
633
+ {
634
+ registryName: "iPhone 16",
635
+ identity: {
636
+ ...IPHONE_BASE,
637
+ id: "iphone-16",
638
+ name: "iPhone 16",
639
+ cornerRadius: 55,
640
+ cutout: DYNAMIC_ISLAND,
641
+ safeAreaInsets: ISLAND_INSETS_IPHONE_15
642
+ }
643
+ },
644
+ {
645
+ registryName: "iPhone 16 Plus",
646
+ identity: {
647
+ ...IPHONE_BASE,
648
+ id: "iphone-16-plus",
649
+ name: "iPhone 16 Plus",
650
+ cornerRadius: 55,
651
+ cutout: DYNAMIC_ISLAND,
652
+ safeAreaInsets: ISLAND_INSETS_IPHONE_15
653
+ }
654
+ },
655
+ {
656
+ registryName: "iPhone 16 Pro",
657
+ identity: {
658
+ ...IPHONE_BASE,
659
+ id: "iphone-16-pro",
660
+ name: "iPhone 16 Pro",
661
+ cornerRadius: 62,
662
+ cutout: DYNAMIC_ISLAND,
663
+ safeAreaInsets: ISLAND_INSETS_IPHONE_16_PRO
664
+ }
665
+ },
666
+ {
667
+ registryName: "iPhone 16 Pro Max",
668
+ identity: {
669
+ ...IPHONE_BASE,
670
+ id: "iphone-16-pro-max",
671
+ name: "iPhone 16 Pro Max",
672
+ cornerRadius: 62,
673
+ cutout: DYNAMIC_ISLAND,
674
+ safeAreaInsets: ISLAND_INSETS_IPHONE_16_PRO
675
+ }
676
+ },
677
+ {
678
+ registryName: "iPhone 16e",
679
+ identity: {
680
+ ...IPHONE_BASE,
681
+ id: "iphone-16e",
682
+ name: "iPhone 16e",
683
+ cornerRadius: 47.33,
684
+ cutout: NOTCH,
685
+ safeAreaInsets: NOTCH_INSETS_IPHONE_13
686
+ }
687
+ },
688
+ {
689
+ registryName: "iPhone 17",
690
+ identity: {
691
+ ...IPHONE_BASE,
692
+ id: "iphone-17",
693
+ name: "iPhone 17",
694
+ cornerRadius: 62,
695
+ cutout: DYNAMIC_ISLAND,
696
+ safeAreaInsets: ISLAND_INSETS_IPHONE_16_PRO
697
+ }
698
+ },
699
+ {
700
+ registryName: "iPhone Air",
701
+ identity: {
702
+ ...IPHONE_BASE,
703
+ id: "iphone-air",
704
+ name: "iPhone Air",
705
+ cornerRadius: 62,
706
+ cutout: DYNAMIC_ISLAND,
707
+ safeAreaInsets: ISLAND_INSETS_IPHONE_AIR
708
+ }
709
+ },
710
+ {
711
+ registryName: "iPhone 17 Pro",
712
+ identity: {
713
+ ...IPHONE_BASE,
714
+ id: "iphone-17-pro",
715
+ name: "iPhone 17 Pro",
716
+ cornerRadius: 62,
717
+ cutout: DYNAMIC_ISLAND,
718
+ safeAreaInsets: ISLAND_INSETS_IPHONE_16_PRO
719
+ }
720
+ },
721
+ {
722
+ registryName: "iPhone 17 Pro Max",
723
+ identity: {
724
+ ...IPHONE_BASE,
725
+ id: "iphone-17-pro-max",
726
+ name: "iPhone 17 Pro Max",
727
+ cornerRadius: 62,
728
+ cutout: DYNAMIC_ISLAND,
729
+ safeAreaInsets: ISLAND_INSETS_IPHONE_16_PRO
730
+ }
731
+ },
732
+ {
733
+ registryName: "iPhone 17e",
734
+ identity: {
735
+ ...IPHONE_BASE,
736
+ id: "iphone-17e",
737
+ name: "iPhone 17e",
738
+ cornerRadius: null,
739
+ cutout: NOTCH,
740
+ safeAreaInsets: NOTCH_INSETS_IPHONE_13
741
+ }
742
+ },
743
+ {
744
+ registryName: "Pixel 8",
745
+ identity: {
746
+ ...ANDROID_BASE,
747
+ id: "pixel-8",
748
+ name: "Pixel 8",
749
+ cornerRadius: 38.86,
750
+ cutout: createPunchHole({
751
+ diameter: 27.62,
752
+ top: 11.24,
753
+ left: 191.81
754
+ }),
755
+ safeAreaInsets: createTopInset(50.29)
756
+ }
757
+ },
758
+ {
759
+ registryName: "Pixel 8 Pro",
760
+ identity: {
761
+ ...ANDROID_BASE,
762
+ id: "pixel-8-pro",
763
+ name: "Pixel 8 Pro",
764
+ cornerRadius: 30.33,
765
+ cutout: createPunchHole({
766
+ diameter: 28.83,
767
+ top: 10.58,
768
+ left: 209.58
769
+ }),
770
+ safeAreaInsets: createTopInset(50.33)
771
+ }
772
+ },
773
+ {
774
+ registryName: "Pixel 8a",
775
+ identity: {
776
+ ...ANDROID_BASE,
777
+ id: "pixel-8a",
778
+ name: "Pixel 8a",
779
+ cornerRadius: 48.76,
780
+ cutout: createPunchHole({
781
+ diameter: 25.69,
782
+ top: 13.33,
783
+ left: 192.69
784
+ }),
785
+ safeAreaInsets: createTopInset(46.1)
786
+ }
787
+ },
788
+ {
789
+ registryName: "Pixel 9",
790
+ identity: {
791
+ ...ANDROID_BASE,
792
+ id: "pixel-9",
793
+ name: "Pixel 9",
794
+ cornerRadius: 44,
795
+ cutout: createPunchHole({
796
+ diameter: 28,
797
+ top: 14.83,
798
+ left: 165.83
799
+ }),
800
+ safeAreaInsets: createTopInset(57.67)
801
+ }
802
+ },
803
+ {
804
+ registryName: "Pixel 9 Pro",
805
+ identity: {
806
+ ...ANDROID_BASE,
807
+ id: "pixel-9-pro",
808
+ name: "Pixel 9 Pro",
809
+ cornerRadius: 52.33,
810
+ cutout: createPunchHole({
811
+ diameter: 32.67,
812
+ top: 17.67,
813
+ left: 197
814
+ }),
815
+ safeAreaInsets: createTopInset(68)
816
+ }
817
+ },
818
+ {
819
+ registryName: "Pixel 9 Pro XL",
820
+ identity: {
821
+ ...ANDROID_BASE,
822
+ id: "pixel-9-pro-xl",
823
+ name: "Pixel 9 Pro XL",
824
+ cornerRadius: 51,
825
+ cutout: createPunchHole({
826
+ diameter: 31.33,
827
+ top: 17.5,
828
+ left: 208.33
829
+ }),
830
+ safeAreaInsets: createTopInset(66.33)
831
+ }
832
+ },
833
+ {
834
+ registryName: "Pixel 10",
835
+ identity: {
836
+ ...ANDROID_BASE,
837
+ id: "pixel-10",
838
+ name: "Pixel 10",
839
+ cornerRadius: null,
840
+ cutout: UNMEASURED_PUNCH_HOLE,
841
+ safeAreaInsets: FRAME_SHAPE_INSETS["punch-hole"]
842
+ }
843
+ },
844
+ {
845
+ registryName: "Pixel 10 Pro",
846
+ identity: {
847
+ ...ANDROID_BASE,
848
+ id: "pixel-10-pro",
849
+ name: "Pixel 10 Pro",
850
+ cornerRadius: null,
851
+ cutout: UNMEASURED_PUNCH_HOLE,
852
+ safeAreaInsets: FRAME_SHAPE_INSETS["punch-hole"]
853
+ }
854
+ },
855
+ {
856
+ registryName: "Pixel 10 Pro XL",
857
+ identity: {
858
+ ...ANDROID_BASE,
859
+ id: "pixel-10-pro-xl",
860
+ name: "Pixel 10 Pro XL",
861
+ cornerRadius: null,
862
+ cutout: UNMEASURED_PUNCH_HOLE,
863
+ safeAreaInsets: FRAME_SHAPE_INSETS["punch-hole"]
864
+ }
865
+ },
866
+ {
867
+ registryName: "Galaxy S24",
868
+ identity: {
869
+ ...SAMSUNG_BASE,
870
+ id: "galaxy-s24",
871
+ name: "Galaxy S24",
872
+ cornerRadius: null,
873
+ cutout: UNMEASURED_PUNCH_HOLE,
874
+ safeAreaInsets: FRAME_SHAPE_INSETS["punch-hole"]
875
+ }
876
+ },
877
+ {
878
+ registryName: "Galaxy A55",
879
+ identity: {
880
+ ...SAMSUNG_BASE,
881
+ id: "galaxy-a55",
882
+ name: "Galaxy A55",
883
+ cornerRadius: null,
884
+ cutout: UNMEASURED_PUNCH_HOLE,
885
+ safeAreaInsets: FRAME_SHAPE_INSETS["punch-hole"]
886
+ }
887
+ },
888
+ {
889
+ registryName: "Galaxy Z Fold 7",
890
+ identity: {
891
+ ...SAMSUNG_BASE,
892
+ id: "galaxy-z-fold-7",
893
+ name: "Galaxy Z Fold 7 (open)",
894
+ frameShape: "tablet",
895
+ cpuSlowdown: CPU_TIERS.tablet,
896
+ cornerRadius: null,
897
+ cutout: UNMEASURED_PUNCH_HOLE,
898
+ safeAreaInsets: FRAME_SHAPE_INSETS.tablet
899
+ }
900
+ },
901
+ {
902
+ registryName: "Galaxy Z Fold 7 Cover",
903
+ identity: {
904
+ ...SAMSUNG_BASE,
905
+ id: "galaxy-z-fold-7-cover",
906
+ name: "Galaxy Z Fold 7 (cover)",
907
+ cornerRadius: null,
908
+ cutout: UNMEASURED_PUNCH_HOLE,
909
+ safeAreaInsets: FRAME_SHAPE_INSETS["punch-hole"]
910
+ }
911
+ },
912
+ {
913
+ registryName: "Galaxy Z Flip 7",
914
+ identity: {
915
+ ...SAMSUNG_BASE,
916
+ id: "galaxy-z-flip-7",
917
+ name: "Galaxy Z Flip 7 (open)",
918
+ cornerRadius: null,
919
+ cutout: UNMEASURED_PUNCH_HOLE,
920
+ safeAreaInsets: FRAME_SHAPE_INSETS["punch-hole"]
921
+ }
922
+ },
923
+ {
924
+ registryName: "Galaxy Z Flip 7 Cover",
925
+ identity: {
926
+ ...SAMSUNG_BASE,
927
+ id: "galaxy-z-flip-7-cover",
928
+ name: "Galaxy Z Flip 7 (cover)",
929
+ cornerRadius: null,
930
+ cutout: NO_CUTOUT,
931
+ safeAreaInsets: FRAME_SHAPE_INSETS.tablet
932
+ }
933
+ },
934
+ {
935
+ registryName: "iPad Pro 11",
936
+ identity: {
937
+ ...TABLET_BASE,
938
+ id: "ipad-pro-11",
939
+ name: "iPad Pro 11",
940
+ cornerRadius: 18,
941
+ cutout: NO_CUTOUT,
942
+ safeAreaInsets: FRAME_SHAPE_INSETS.tablet
943
+ }
944
+ },
945
+ {
946
+ registryName: "iPad (gen 11)",
947
+ identity: {
948
+ ...TABLET_BASE,
949
+ id: "ipad-11",
950
+ name: "iPad (11th gen)",
951
+ cornerRadius: null,
952
+ cutout: NO_CUTOUT,
953
+ safeAreaInsets: FRAME_SHAPE_INSETS.tablet
954
+ }
955
+ },
956
+ {
957
+ registryName: "Galaxy Tab S9",
958
+ identity: {
959
+ ...SAMSUNG_BASE,
960
+ id: "galaxy-tab-s9",
961
+ name: "Galaxy Tab S9",
962
+ frameShape: "tablet",
963
+ cpuSlowdown: CPU_TIERS.tablet,
964
+ cornerRadius: null,
965
+ cutout: NO_CUTOUT,
966
+ safeAreaInsets: FRAME_SHAPE_INSETS.tablet
967
+ }
968
+ }
969
+ ];
970
+ const createRegistryCatalogDevice = (deviceSpec) => {
971
+ const registryDevice = devices[deviceSpec.registryName];
972
+ if (registryDevice === void 0) throw new Error(`Playwright has no device ${deviceSpec.registryName}; update the catalog`);
973
+ return createCatalogDeviceFromRegistry(deviceSpec.identity, deviceSpec.registryName, registryDevice);
974
+ };
975
+ // src/devices/device-catalog.ts
976
+ const DEVICE_CATALOG = [...REGISTRY_DEVICE_SPECS.map(createRegistryCatalogDevice), ...OWN_DEVICES];
977
+ // src/devices/devices-logic.ts
978
+ const UNKNOWN_DEVICE_STATUS = 422;
979
+ const NON_ID_CHARACTERS = /[^\p{L}\p{N}]+/gu;
980
+ const EDGE_DASHES = /^-|-$/g;
981
+ const pickDeviceId = (deviceName) => deviceName.toLowerCase().replace(NON_ID_CHARACTERS, "-").replace(EDGE_DASHES, "");
982
+ const hasDeviceId = (customDevice, deviceId) => pickDeviceId(customDevice.name) === deviceId;
983
+ const createPresetFromCustomDevice = (customDevice) => {
984
+ const isTouchDevice = customDevice.hasTouch;
985
+ const isFirefoxDevice = customDevice.browserName === "firefox";
986
+ const isMobile = isTouchDevice && !isFirefoxDevice;
987
+ const cpuSlowdown = isTouchDevice ? CPU_TIERS.phone : CPU_TIERS.desktop;
988
+ const { frameShape } = customDevice;
989
+ const safeAreaInsets = FRAME_SHAPE_INSETS[frameShape];
990
+ const statusBarHeight = pickStatusBarHeight({
991
+ frameShape,
992
+ platform: null,
993
+ cutout: null,
994
+ safeAreaInsets
995
+ });
996
+ const hasStatusBar = statusBarHeight > 0;
997
+ const screen = {
998
+ width: customDevice.width,
999
+ height: customDevice.height
1000
+ };
1001
+ return {
1002
+ id: pickDeviceId(customDevice.name),
1003
+ name: customDevice.name,
1004
+ browserName: customDevice.browserName,
1005
+ frameShape,
1006
+ cpuSlowdown,
1007
+ platform: null,
1008
+ maker: "generic",
1009
+ cornerRadius: null,
1010
+ cutout: null,
1011
+ safeAreaInsets,
1012
+ isCustom: true,
1013
+ viewportMode: "full-screen",
1014
+ use: {
1015
+ viewport: {
1016
+ width: screen.width,
1017
+ height: screen.height - statusBarHeight
1018
+ },
1019
+ ...hasStatusBar && { screen },
1020
+ deviceScaleFactor: customDevice.pixelRatio,
1021
+ isMobile,
1022
+ hasTouch: customDevice.hasTouch
1023
+ }
1024
+ };
1025
+ };
1026
+ const STATUS_BAR_HEIGHTS_BY_SHAPE = {
1027
+ "home-button": 20,
1028
+ "dynamic-island": 59,
1029
+ "punch-hole": 24,
1030
+ tablet: 24,
1031
+ laptop: 0,
1032
+ kiosk: 0
1033
+ };
1034
+ const STATUS_BAR_HEIGHTS_BY_CUTOUT = {
1035
+ none: null,
1036
+ notch: 47,
1037
+ "dynamic-island": 59,
1038
+ "punch-hole-center": null,
1039
+ "punch-hole-corner": null,
1040
+ "home-button": 20
1041
+ };
1042
+ const STATUS_BAR_PLATFORMS = [
1043
+ "ios",
1044
+ "ipados",
1045
+ "android"
1046
+ ];
1047
+ const PHONE_SHAPES = [
1048
+ "home-button",
1049
+ "dynamic-island",
1050
+ "punch-hole"
1051
+ ];
1052
+ const pickStatusBarHeight = (deviceIdentity) => {
1053
+ const { frameShape, platform, cutout, safeAreaInsets } = deviceIdentity;
1054
+ const isPlatformKnown = platform !== null;
1055
+ const isMobilePlatform = isPlatformKnown && STATUS_BAR_PLATFORMS.includes(platform);
1056
+ const isPhoneShape = PHONE_SHAPES.includes(frameShape);
1057
+ if (!(!(frameShape === "laptop" || frameShape === "kiosk") && (isPlatformKnown ? isMobilePlatform : isPhoneShape))) return 0;
1058
+ const publishedHeight = Math.round(safeAreaInsets.top);
1059
+ if (publishedHeight > 0) return publishedHeight;
1060
+ return (cutout === null ? null : STATUS_BAR_HEIGHTS_BY_CUTOUT[cutout.kind]) ?? STATUS_BAR_HEIGHTS_BY_SHAPE[frameShape];
1061
+ };
1062
+ const pickModeViewport = (catalogDevice, viewportMode) => {
1063
+ switch (viewportMode) {
1064
+ case "full-screen": {
1065
+ const { width, height } = catalogDevice.fullScreen;
1066
+ return {
1067
+ width,
1068
+ height: height - pickStatusBarHeight(catalogDevice)
1069
+ };
1070
+ }
1071
+ case "browser": return catalogDevice.browserViewport ?? catalogDevice.fullScreen;
1072
+ }
1073
+ };
1074
+ const createPresetFromCatalogDevice = (catalogDevice, viewportMode) => {
1075
+ return {
1076
+ id: catalogDevice.id,
1077
+ name: catalogDevice.name,
1078
+ browserName: catalogDevice.browserName,
1079
+ frameShape: catalogDevice.frameShape,
1080
+ cpuSlowdown: catalogDevice.cpuSlowdown,
1081
+ platform: catalogDevice.platform,
1082
+ maker: catalogDevice.maker,
1083
+ cornerRadius: catalogDevice.cornerRadius,
1084
+ cutout: catalogDevice.cutout,
1085
+ safeAreaInsets: catalogDevice.safeAreaInsets,
1086
+ isCustom: false,
1087
+ viewportMode,
1088
+ use: {
1089
+ viewport: pickModeViewport(catalogDevice, viewportMode),
1090
+ screen: catalogDevice.fullScreen,
1091
+ userAgent: catalogDevice.userAgent,
1092
+ deviceScaleFactor: catalogDevice.deviceScaleFactor,
1093
+ isMobile: catalogDevice.isMobile,
1094
+ hasTouch: catalogDevice.hasTouch
1095
+ }
1096
+ };
1097
+ };
1098
+ const findCatalogPreset = (deviceId, viewportMode) => {
1099
+ const catalogDevice = DEVICE_CATALOG.find(({ id }) => id === deviceId);
1100
+ if (catalogDevice === void 0) throw new Error(`No catalog device ${deviceId}`);
1101
+ return createPresetFromCatalogDevice(catalogDevice, viewportMode);
1102
+ };
1103
+ const pickGridPreset = (devicesFile, gridId) => {
1104
+ const customDevice = devicesFile.devices.find((device) => hasDeviceId(device, gridId));
1105
+ if (customDevice !== void 0) return createPresetFromCustomDevice(customDevice);
1106
+ return findCatalogPreset(gridId, devicesFile.viewportModes[gridId] ?? "full-screen");
1107
+ };
1108
+ const pickGridDevices = (devicesFile) => {
1109
+ const gridPresets = devicesFile.grid.map((gridId) => pickGridPreset(devicesFile, gridId));
1110
+ const gridIds = gridPresets.map(({ id }) => id);
1111
+ const otherCustomPresets = devicesFile.devices.map(createPresetFromCustomDevice).filter((customPreset) => !gridIds.includes(customPreset.id));
1112
+ return [...gridPresets, ...otherCustomPresets];
1113
+ };
1114
+ const listCatalogReplies = (devicesFile) => {
1115
+ const customIds = devicesFile.devices.map((customDevice) => pickDeviceId(customDevice.name));
1116
+ return DEVICE_CATALOG.map((catalogDevice) => ({
1117
+ ...catalogDevice,
1118
+ isHiddenByCustomDevice: customIds.includes(catalogDevice.id)
1119
+ }));
1120
+ };
1121
+ const checkNewDevice = (customDevice, takenDevices) => {
1122
+ const newDeviceId = pickDeviceId(customDevice.name);
1123
+ if (takenDevices.some((takenDevice) => takenDevice.id === newDeviceId)) throw new HTTPException(400, { message: `a device with id ${newDeviceId} already exists · use another name` });
1124
+ return customDevice;
1125
+ };
1126
+ const createMissingCustomDeviceMessage = (devicesFile, deviceId) => {
1127
+ const customIds = devicesFile.devices.map((customDevice) => pickDeviceId(customDevice.name));
1128
+ if (customIds.length === 0) return `no custom device ${deviceId} · there are no custom devices`;
1129
+ return `no custom device ${deviceId} · custom devices: ${customIds.join(", ")}`;
1130
+ };
1131
+ const checkDeviceRemoval = (devicesFile, deviceId) => {
1132
+ if (!devicesFile.devices.some((customDevice) => hasDeviceId(customDevice, deviceId))) throw new HTTPException(400, { message: createMissingCustomDeviceMessage(devicesFile, deviceId) });
1133
+ const keptDevices = devicesFile.devices.filter((customDevice) => !hasDeviceId(customDevice, deviceId));
1134
+ const isCatalogDevice = DEVICE_CATALOG.some(({ id }) => id === deviceId);
1135
+ const keptGrid = devicesFile.grid.filter((gridId) => isCatalogDevice || gridId !== deviceId);
1136
+ return {
1137
+ ...devicesFile,
1138
+ devices: keptDevices,
1139
+ grid: keptGrid
1140
+ };
1141
+ };
1142
+ const listUnknownIds = (devicesFile, deviceIds) => {
1143
+ const knownIds = [...DEVICE_CATALOG.map(({ id }) => id), ...devicesFile.devices.map((customDevice) => pickDeviceId(customDevice.name))];
1144
+ return deviceIds.filter((deviceId) => !knownIds.includes(deviceId));
1145
+ };
1146
+ const checkDeviceIds = (devicesFile) => {
1147
+ const fieldIds = {
1148
+ grid: devicesFile.grid,
1149
+ viewportModes: Object.keys(devicesFile.viewportModes)
1150
+ };
1151
+ for (const [field, deviceIds] of Object.entries(fieldIds)) {
1152
+ const [unknownId] = listUnknownIds(devicesFile, deviceIds);
1153
+ if (unknownId !== void 0) throw new HTTPException(UNKNOWN_DEVICE_STATUS, { message: `${field}: no device with id ${unknownId}` });
1154
+ }
1155
+ return devicesFile;
1156
+ };
1157
+ const pickDeviceViewport = (devicePreset) => {
1158
+ const { viewport } = devicePreset.use;
1159
+ if (viewport === void 0 || viewport === null) throw new Error(`Device ${devicePreset.id} has no viewport`);
1160
+ return {
1161
+ width: viewport.width,
1162
+ height: viewport.height
1163
+ };
1164
+ };
1165
+ const pickDeviceScreen = (devicePreset) => {
1166
+ const { screen } = devicePreset.use;
1167
+ if (screen === void 0) return pickDeviceViewport(devicePreset);
1168
+ return {
1169
+ width: screen.width,
1170
+ height: screen.height
1171
+ };
1172
+ };
1173
+ const replaceGrid = (devicesFile, deviceIds) => {
1174
+ const uniqueIds = [...new Set(deviceIds)];
1175
+ return checkDeviceIds({
1176
+ ...devicesFile,
1177
+ grid: uniqueIds
1178
+ });
1179
+ };
1180
+ const pickGridChanges = (runningDeviceIds, gridDevices) => {
1181
+ const gridDeviceIds = gridDevices.map(({ id }) => id);
1182
+ return {
1183
+ addDevices: gridDevices.filter(({ id }) => !runningDeviceIds.includes(id)),
1184
+ removeDeviceIds: runningDeviceIds.filter((runningDeviceId) => !gridDeviceIds.includes(runningDeviceId))
1185
+ };
1186
+ };
1187
+ // src/devices/devices-store.ts
1188
+ const BAD_DEVICES_FILE_STATUS = 422;
1189
+ const rejectDevicesFile = (reason, cause) => {
1190
+ throw new HTTPException(BAD_DEVICES_FILE_STATUS, {
1191
+ message: `${DEVICES_FILE_PATH} is not valid: ${reason}`,
1192
+ cause
1193
+ });
1194
+ };
1195
+ const parseYaml = (devicesYaml) => {
1196
+ try {
1197
+ return parse(devicesYaml);
1198
+ } catch (error) {
1199
+ const [firstLine] = (error instanceof Error ? error.message : "bad YAML").split("\n");
1200
+ return rejectDevicesFile(firstLine, error);
1201
+ }
1202
+ };
1203
+ const parseDevicesYaml = (devicesYaml) => {
1204
+ const parsedYaml = parseYaml(devicesYaml);
1205
+ const parsedFile = devicesFileSchema.safeParse(parsedYaml ?? {});
1206
+ if (parsedFile.success) return checkDeviceIds(parsedFile.data);
1207
+ const [firstIssue] = parsedFile.error.issues;
1208
+ const badField = firstIssue.path.join(".");
1209
+ return rejectDevicesFile(`${badField || "file"}: ${firstIssue.message}`, parsedFile.error);
1210
+ };
1211
+ const readDevicesYaml = async (devicesFilePath) => {
1212
+ const devicesYaml = await readStoredFile(devicesFilePath);
1213
+ if (devicesYaml === null) return devicesFileSchema.parse({});
1214
+ return parseDevicesYaml(devicesYaml);
1215
+ };
1216
+ const readDevicesFile = async (rootFolder) => {
1217
+ const devicesFilePath = join(rootFolder, DEVICES_FILE_PATH);
1218
+ return readDevicesYaml(devicesFilePath);
1219
+ };
1220
+ const updateDevicesFile = async (rootFolder, changeDevicesFile) => {
1221
+ const devicesFilePath = join(rootFolder, DEVICES_FILE_PATH);
1222
+ return runOneAtATime(devicesFilePath, async () => {
1223
+ const changedFile = changeDevicesFile(await readDevicesYaml(devicesFilePath));
1224
+ await writeStoredFile(devicesFilePath, stringify(changedFile));
1225
+ return changedFile;
1226
+ });
1227
+ };
1228
+ export { gridUpdateSchema as _, checkNewDevice as a, listCatalogReplies as c, pickGridChanges as d, pickGridDevices as f, devicesFileSchema as g, customDeviceSchema as h, checkDeviceRemoval as i, pickDeviceScreen as l, DEVICE_CATALOG as m, readDevicesFile as n, createPresetFromCustomDevice as o, replaceGrid as p, updateDevicesFile as r, findCatalogPreset as s, parseDevicesYaml as t, pickDeviceViewport as u };