sootsim 0.1.83 → 0.1.85
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/README.md +0 -1
- package/detox/colors.ts +54 -0
- package/detox/config-loader.ts +135 -0
- package/detox/element-types.ts +36 -0
- package/detox/expectations.ts +477 -0
- package/detox/gestures.ts +442 -0
- package/detox/index.ts +1403 -0
- package/detox/jest-environment.ts +86 -0
- package/detox/jest-preset.cjs +50 -0
- package/detox/matchers.ts +29 -0
- package/detox/navigation.ts +43 -0
- package/detox/run-test.ts +113 -0
- package/detox/screenshots/animated-color-test-rest-norngh.png +0 -0
- package/detox/screenshots/color-test-after-drag-norngh.png +0 -0
- package/detox/screenshots/color-test-rest-norngh.png +0 -0
- package/detox/screenshots/theme-blue-toggle.png +0 -0
- package/detox/screenshots/theme-blue.png +0 -0
- package/detox/screenshots/theme-red-toggle.png +0 -0
- package/detox/screenshots/theme-red.png +0 -0
- package/dist-cli/bin.js +3 -3
- package/dist-cli/chunks/{agent-MQ7GLVIB.js → agent-T3DUH5YJ.js} +2 -2
- package/dist-cli/chunks/{agent-wrapper-7KAFDQCN.js → agent-wrapper-NSBF4THI.js} +2 -2
- package/dist-cli/chunks/{assert-TV46GUNU.js → assert-X3F7TRCZ.js} +2 -2
- package/dist-cli/chunks/auto-bootstrap-47RN2V5G.js +2 -0
- package/dist-cli/chunks/beta-BRCGAF2N.js +2 -0
- package/dist-cli/chunks/chunk-36RPD6JI.js +2 -0
- package/dist-cli/chunks/{chunk-PM5NVKLP.js → chunk-3WGHC7JN.js} +2 -2
- package/dist-cli/chunks/chunk-4DBPNLGI.js +1 -0
- package/dist-cli/chunks/{chunk-J2GYISVJ.js → chunk-4EVSIUNB.js} +2 -2
- package/dist-cli/chunks/{chunk-JHJNODXN.js → chunk-4QZHZ6BC.js} +2 -2
- package/dist-cli/chunks/{chunk-F3HP444U.js → chunk-5DIGWOY7.js} +1 -1
- package/dist-cli/chunks/{chunk-DP7O5MHK.js → chunk-5N3V7OCG.js} +2 -2
- package/dist-cli/chunks/{chunk-Y4BUVURT.js → chunk-5S6D7K4L.js} +2 -2
- package/dist-cli/chunks/{chunk-ECJBV65H.js → chunk-7LKUN46F.js} +2 -2
- package/dist-cli/chunks/{chunk-WTKTOL3C.js → chunk-AC6QGW22.js} +2 -2
- package/dist-cli/chunks/{chunk-IBNRRAES.js → chunk-AFNDVS4E.js} +2 -2
- package/dist-cli/chunks/{chunk-6TNANCQC.js → chunk-BESAZ2HA.js} +2 -2
- package/dist-cli/chunks/{chunk-WN7M3QON.js → chunk-BHZJ6RIH.js} +2 -2
- package/dist-cli/chunks/{chunk-277XAALA.js → chunk-BZL6D4TV.js} +3 -3
- package/dist-cli/chunks/{chunk-CYV6Y6YV.js → chunk-CF2LPRXD.js} +2 -2
- package/dist-cli/chunks/chunk-DWTLRPEN.js +79 -0
- package/dist-cli/chunks/{chunk-CJY3AVI7.js → chunk-E2QE5FFP.js} +1 -1
- package/dist-cli/chunks/chunk-EBEL6TTJ.js +4 -0
- package/dist-cli/chunks/{chunk-DM6WT7QM.js → chunk-EFM53PZ5.js} +1 -1
- package/dist-cli/chunks/{chunk-YUELRHGB.js → chunk-EKXK3SWK.js} +2 -2
- package/dist-cli/chunks/{chunk-4LS5MZAI.js → chunk-G7CIZ5S3.js} +3 -3
- package/dist-cli/chunks/{chunk-6NN2D4EJ.js → chunk-GTAD6IUV.js} +1 -1
- package/dist-cli/chunks/{chunk-OYMFNU3M.js → chunk-H44IQHKZ.js} +1 -1
- package/dist-cli/chunks/{chunk-IP3QJLRH.js → chunk-HQDJ5BOF.js} +1 -1
- package/dist-cli/chunks/{chunk-5DJXZIFZ.js → chunk-KUSQ4NNJ.js} +1 -1
- package/dist-cli/chunks/{chunk-HAWOAQAG.js → chunk-MAO7F5PH.js} +3 -3
- package/dist-cli/chunks/{chunk-572VSFNP.js → chunk-NVTL3JQG.js} +1 -1
- package/dist-cli/chunks/{chunk-6XZOEBTZ.js → chunk-O6N2CEET.js} +2 -2
- package/dist-cli/chunks/{chunk-HNWEELAE.js → chunk-OISHLFON.js} +1 -1
- package/dist-cli/chunks/{chunk-2PY3UZVO.js → chunk-OUNLJM56.js} +2 -2
- package/dist-cli/chunks/chunk-OXOARRKR.js +67 -0
- package/dist-cli/chunks/{chunk-NXATOWWF.js → chunk-PHPXGLME.js} +1 -1
- package/dist-cli/chunks/{chunk-JQ7ZXOXJ.js → chunk-PQFFUJR6.js} +2 -2
- package/dist-cli/chunks/{chunk-KASUZ5XV.js → chunk-QLJNSOS7.js} +1 -1
- package/dist-cli/chunks/chunk-QQAECG5B.js +2 -0
- package/dist-cli/chunks/{chunk-FJYT7XL2.js → chunk-RZHREO3M.js} +2 -2
- package/dist-cli/chunks/{chunk-FRM355UL.js → chunk-SBGOUA6F.js} +2 -2
- package/dist-cli/chunks/chunk-SSCA2AEA.js +1 -0
- package/dist-cli/chunks/{chunk-Y2VJBRSP.js → chunk-UYRGCJ4N.js} +1 -1
- package/dist-cli/chunks/{chunk-2AWQ7OB2.js → chunk-WGDL5V6C.js} +1 -1
- package/dist-cli/chunks/{chunk-VMXWC2JO.js → chunk-Y5PLPEEU.js} +2 -2
- package/dist-cli/chunks/chunk-ZFAM4N5B.js +1 -0
- package/dist-cli/chunks/{chunk-RH4F2TF7.js → chunk-ZO3VHP6W.js} +1 -1
- package/dist-cli/chunks/cli-version-WPFDM2A6.js +2 -0
- package/dist-cli/chunks/{compat-QLLWBTS3.js → compat-PCXGGZBZ.js} +3 -3
- package/dist-cli/chunks/{config-2DSLDCXV.js → config-LULEVEYL.js} +2 -2
- package/dist-cli/chunks/control-6P6HY7UF.js +2 -0
- package/dist-cli/chunks/{cpu-profile-GEIKHCPC.js → cpu-profile-NOK73ZYW.js} +2 -2
- package/dist-cli/chunks/{daemon-4EBUFN4D.js → daemon-4A3DMUYL.js} +2 -2
- package/dist-cli/chunks/{debug-WGD6XWOF.js → debug-74BWB2ZG.js} +3 -3
- package/dist-cli/chunks/{detox-LNKGRZU6.js → detox-HEOMINSC.js} +2 -2
- package/dist-cli/chunks/{device-AYKXKVIQ.js → device-TTXXBJFZ.js} +2 -2
- package/dist-cli/chunks/{diagnose-TMXSDOOC.js → diagnose-QZ3GOHSE.js} +2 -2
- package/dist-cli/chunks/drivers-QRPWNOIT.js +2 -0
- package/dist-cli/chunks/{electron-QFPF7TBY.js → electron-QVOWV44R.js} +3 -3
- package/dist-cli/chunks/flow-QMA7GVN6.js +2 -0
- package/dist-cli/chunks/{hints-MXKRR4TG.js → hints-YKWRNMJC.js} +2 -2
- package/dist-cli/chunks/{home-paths-REMWQDAO.js → home-paths-SFADSTJM.js} +2 -2
- package/dist-cli/chunks/{inspect-XGSQNFV7.js → inspect-LEWGQCIU.js} +3 -3
- package/dist-cli/chunks/install-7N2N7Q32.js +2 -0
- package/dist-cli/chunks/{install-desktop-NQG3RZSA.js → install-desktop-22HYQZ2G.js} +3 -3
- package/dist-cli/chunks/{keys-5QZWXL3F.js → keys-3ZT3MICU.js} +2 -2
- package/dist-cli/chunks/{launch-SBXOZWKO.js → launch-ZXW2NFLG.js} +3 -3
- package/dist-cli/chunks/{login-EACQXE24.js → login-NJKJ7GZO.js} +4 -4
- package/dist-cli/chunks/{logout-IBQLMUML.js → logout-VMMQL7CB.js} +2 -2
- package/dist-cli/chunks/{maestro-LFYXUX7O.js → maestro-OJY4MTI7.js} +2 -2
- package/dist-cli/chunks/{preview-U4SBOEGQ.js → preview-QU2GXTEV.js} +2 -2
- package/dist-cli/chunks/{profile-GWS5ECMY.js → profile-7APWK47T.js} +2 -2
- package/dist-cli/chunks/{react-QDHLMVYL.js → react-RSVO5JZZ.js} +2 -2
- package/dist-cli/chunks/{record-BUEUWPDI.js → record-UWH4MDEO.js} +2 -2
- package/dist-cli/chunks/runtime-3FUENRHM.js +2 -0
- package/dist-cli/chunks/{runtime-delivery-G7L6RVZ7.js → runtime-delivery-QMKGRV7N.js} +2 -2
- package/dist-cli/chunks/{screenshot-T2HBA3VI.js → screenshot-43M27ALE.js} +2 -2
- package/dist-cli/chunks/{screenshot-mode-EG5HMIH3.js → screenshot-mode-EBYYN6TY.js} +2 -2
- package/dist-cli/chunks/{screenshots-S52AFHTV.js → screenshots-7TQZL6Z6.js} +2 -2
- package/dist-cli/chunks/{server-MFFVYUGG.js → server-VCFM25Z6.js} +2 -2
- package/dist-cli/chunks/setup-repo-HFH4VKJQ.js +2 -0
- package/dist-cli/chunks/{skills-HQGWBS2O.js → skills-RQA6EJQL.js} +2 -2
- package/dist-cli/chunks/{start-E3DRYY7W.js → start-ZT6MBYND.js} +4 -4
- package/dist-cli/chunks/store-BJBTDSZE.js +2 -0
- package/dist-cli/chunks/telemetry-ZZZKTILZ.js +2 -0
- package/dist-cli/chunks/{test-ZY3EF62K.js → test-RNRX5SWV.js} +3 -3
- package/dist-cli/chunks/{three-mode-WSPKQCJ5.js → three-mode-TQZH25ZO.js} +2 -2
- package/dist-cli/chunks/{timeline-3XAB5EWZ.js → timeline-GGN3AY6P.js} +2 -2
- package/dist-cli/chunks/{upgrade-WNENPFM5.js → upgrade-XT22D67C.js} +2 -2
- package/dist-cli/chunks/upload-NC2AYLC5.js +2 -0
- package/dist-cli/chunks/{web-D2AOZY44.js → web-KEHVF5MB.js} +2 -2
- package/dist-cli/chunks/{what-happened-F43KNSG6.js → what-happened-PATQRJ5T.js} +2 -2
- package/dist-cli/chunks/{whoami-T22VBR7C.js → whoami-CXVY26VV.js} +2 -2
- package/dist-lib/agent-daemon-client.cjs +1 -1
- package/dist-lib/agent-events.cjs +1 -1
- package/dist-lib/agent-sessions.cjs +1 -1
- package/dist-lib/attached-projects.cjs +1 -1
- package/dist-lib/auth/shared-session.cjs +1 -1
- package/dist-lib/backend-origin.cjs +1 -1
- package/dist-lib/beta.cjs +44 -0
- package/dist-lib/bridge-constants.cjs +1 -1
- package/dist-lib/cli-constants.cjs +1 -1
- package/dist-lib/config.cjs +1 -1
- package/dist-lib/detox/index.cjs +1770 -0
- package/dist-lib/detox/jest-preset.cjs +50 -0
- package/dist-lib/dev-bundle-resolution.cjs +1 -1
- package/dist-lib/home-paths.cjs +1 -1
- package/dist-lib/host/bridge-host.cjs +1 -1
- package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
- package/dist-lib/index.cjs +136 -138
- package/dist-lib/metro.cjs +31 -26
- package/dist-lib/profiles.cjs +1 -1
- package/dist-lib/render-mode.cjs +1 -1
- package/dist-lib/scripts/demo-app-registry.cjs +809 -0
- package/dist-lib/scripts/dev-server-scanner.cjs +1269 -0
- package/dist-lib/skills.cjs +17766 -0
- package/dist-lib/vite.cjs +129 -39
- package/package.json +39 -14
- package/scripts/demo-app-registry.ts +989 -0
- package/scripts/dev-server-scanner.ts +674 -0
- package/src/agent-daemon-client.ts +390 -0
- package/src/agent-events.ts +71 -0
- package/src/agent-prompt.ts +71 -0
- package/src/agent-sessions.ts +572 -0
- package/src/attached-projects.ts +536 -0
- package/src/auth/shared-session.ts +199 -0
- package/src/backend-origin.ts +49 -0
- package/src/beta.ts +21 -0
- package/src/bridge-constants.ts +10 -0
- package/src/cli-constants.ts +1 -0
- package/src/cli-version.ts +30 -0
- package/src/codex-client.ts +215 -0
- package/src/config.ts +110 -0
- package/src/dev-bundle-resolution.ts +180 -0
- package/src/home-paths.ts +382 -0
- package/src/host/agent-host.ts +576 -0
- package/src/host/bridge-host.ts +2293 -0
- package/src/host/fetch-proxy-handler.ts +288 -0
- package/src/host/fetch-proxy-overrides.ts +39 -0
- package/src/host/open-url.ts +234 -0
- package/src/index.ts +9 -0
- package/src/metro-plugin.ts +139 -0
- package/src/native-dev-bundle-url.ts +62 -0
- package/src/native-seam-manifest.ts +313 -0
- package/src/profiles.ts +179 -0
- package/src/render-mode.ts +27 -0
- package/src/runtime-assets.ts +84 -0
- package/src/runtime-delivery.ts +334 -0
- package/src/screenshots/compose.ts +422 -0
- package/src/screenshots/frame-compose.ts +438 -0
- package/src/screenshots/orchestrate.ts +244 -0
- package/src/screenshots/registry.ts +58 -0
- package/src/screenshots/schema.ts +364 -0
- package/src/skills/builtin/a11y-review.ts +126 -0
- package/src/skills/builtin/compat-check.ts +104 -0
- package/src/skills/builtin/perf-profile.ts +84 -0
- package/src/skills/builtin/screenshot-all.ts +46 -0
- package/src/skills/builtin/test-flow.ts +118 -0
- package/src/skills/builtin/visual-diff.ts +94 -0
- package/src/skills/registry.ts +107 -0
- package/src/skills/types.ts +41 -0
- package/src/vite-plugin-one.ts +189 -0
- package/src/vite-plugin.ts +1381 -0
- package/src/worklets-babel.ts +132 -0
- package/dist-cli/chunks/auto-bootstrap-FQS4ZD2K.js +0 -2
- package/dist-cli/chunks/beta-VG7CDY2U.js +0 -2
- package/dist-cli/chunks/chunk-2OIBDYHW.js +0 -1
- package/dist-cli/chunks/chunk-6BNLVMXA.js +0 -1
- package/dist-cli/chunks/chunk-6XD6CBJM.js +0 -2
- package/dist-cli/chunks/chunk-CHQTO426.js +0 -1
- package/dist-cli/chunks/chunk-FAPYGVIU.js +0 -4
- package/dist-cli/chunks/chunk-PEHFE3LG.js +0 -64
- package/dist-cli/chunks/chunk-RXH2SLKF.js +0 -2
- package/dist-cli/chunks/chunk-UXQWC5ZR.js +0 -79
- package/dist-cli/chunks/chunk-XFQL74PF.js +0 -5
- package/dist-cli/chunks/cli-version-PWF6I6LY.js +0 -2
- package/dist-cli/chunks/control-UIOXGYXU.js +0 -2
- package/dist-cli/chunks/demo-app-registry-G3BDOFWC.js +0 -2
- package/dist-cli/chunks/drivers-IDQF34HP.js +0 -2
- package/dist-cli/chunks/flow-3JN3Y7RF.js +0 -2
- package/dist-cli/chunks/install-2N3YOOSN.js +0 -2
- package/dist-cli/chunks/runtime-PVB4VGUH.js +0 -2
- package/dist-cli/chunks/setup-repo-YOF7NV5D.js +0 -2
- package/dist-cli/chunks/store-MAI6D3UO.js +0 -2
- package/dist-cli/chunks/telemetry-RCQKCJTH.js +0 -2
- package/dist-cli/chunks/upload-YLJ4RA73.js +0 -2
- package/dist-lib/vite-base.cjs +0 -6937
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// composer-facing registry. shared tokens live in
|
|
2
|
+
// `sootsim-engine/src/screenshots/tokens.ts` so the live browser
|
|
3
|
+
// screenshot mode and the post-process composer render from the same
|
|
4
|
+
// canvas / background / text / pose / animation / clamp vocabulary.
|
|
5
|
+
//
|
|
6
|
+
// this module re-exports those tokens plus the composer-specific
|
|
7
|
+
// `FrameComposeSpec` shape.
|
|
8
|
+
|
|
9
|
+
import type { DeviceModel } from 'sootsim-engine/settings'
|
|
10
|
+
|
|
11
|
+
export {
|
|
12
|
+
BACKGROUND_PRESETS,
|
|
13
|
+
DEFAULT_FRAME_SHADOW,
|
|
14
|
+
DEFAULT_FRAME_STYLE,
|
|
15
|
+
SCREENSHOT_CANVASES,
|
|
16
|
+
SCREENSHOT_MODE_ANIM,
|
|
17
|
+
SCREENSHOT_TEXT_CLAMP,
|
|
18
|
+
SCREENSHOT_TEXT_SAFE_AREA,
|
|
19
|
+
cloneBackgroundSpec,
|
|
20
|
+
resolveBackgroundPreset,
|
|
21
|
+
resolveCanvasPreset,
|
|
22
|
+
} from 'sootsim-engine/screenshots/tokens'
|
|
23
|
+
|
|
24
|
+
export type {
|
|
25
|
+
BackgroundPresetName,
|
|
26
|
+
BackgroundSpec,
|
|
27
|
+
CanvasPreset,
|
|
28
|
+
FrameShadowSpec,
|
|
29
|
+
GradientStop,
|
|
30
|
+
PosePresetName,
|
|
31
|
+
ScreenshotCanvasName,
|
|
32
|
+
TextPresetName,
|
|
33
|
+
} from 'sootsim-engine/screenshots/tokens'
|
|
34
|
+
|
|
35
|
+
import type { FrameShadowSpec, PosePresetName } from 'sootsim-engine/screenshots/tokens'
|
|
36
|
+
|
|
37
|
+
export interface FrameComposeSpec {
|
|
38
|
+
show: boolean
|
|
39
|
+
style: DeviceModel
|
|
40
|
+
pose: PosePresetName
|
|
41
|
+
scale: number
|
|
42
|
+
offsetY: number
|
|
43
|
+
shadow: FrameShadowSpec
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function toAssetKey(value: string): string {
|
|
47
|
+
const normalized = value
|
|
48
|
+
.replace(/\\/g, '/')
|
|
49
|
+
.replace(/\.png$/i, '')
|
|
50
|
+
.trim()
|
|
51
|
+
.split('/')
|
|
52
|
+
.filter(Boolean)
|
|
53
|
+
.join('--')
|
|
54
|
+
.replace(/[^A-Za-z0-9-]+/g, '-')
|
|
55
|
+
.replace(/-+/g, '-')
|
|
56
|
+
.replace(/^-|-$/g, '')
|
|
57
|
+
return normalized || 'slide'
|
|
58
|
+
}
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
import { readFileSync } from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import { devices, type DeviceModel } from 'sootsim-engine/settings'
|
|
4
|
+
import yaml from 'yaml'
|
|
5
|
+
import {
|
|
6
|
+
cloneBackgroundSpec,
|
|
7
|
+
DEFAULT_FRAME_SHADOW,
|
|
8
|
+
DEFAULT_FRAME_STYLE,
|
|
9
|
+
resolveBackgroundPreset,
|
|
10
|
+
resolveCanvasPreset,
|
|
11
|
+
toAssetKey,
|
|
12
|
+
type BackgroundPresetName,
|
|
13
|
+
type BackgroundSpec,
|
|
14
|
+
type CanvasPreset,
|
|
15
|
+
type FrameComposeSpec,
|
|
16
|
+
type PosePresetName,
|
|
17
|
+
type TextPresetName,
|
|
18
|
+
} from './registry'
|
|
19
|
+
|
|
20
|
+
export type CaptureMode = 'raw' | 'framed' | 'raw+framed'
|
|
21
|
+
export type CapturePathMode = 'auto' | 'plan' | 'flow'
|
|
22
|
+
|
|
23
|
+
export interface NormalizedScreenshotSlide {
|
|
24
|
+
id: string
|
|
25
|
+
assetKey: string
|
|
26
|
+
screenshot: string
|
|
27
|
+
headline: string
|
|
28
|
+
subheadline: string
|
|
29
|
+
eyebrow: string
|
|
30
|
+
pose?: PosePresetName
|
|
31
|
+
scale?: number
|
|
32
|
+
offsetY?: number
|
|
33
|
+
background?: BackgroundSpec
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface NormalizedCapturePlan {
|
|
37
|
+
flowPath: string | null
|
|
38
|
+
fromDir: string | null
|
|
39
|
+
outDir: string
|
|
40
|
+
rawDir: string
|
|
41
|
+
framedDir: string
|
|
42
|
+
mode: CaptureMode
|
|
43
|
+
pathMode: CapturePathMode
|
|
44
|
+
simId: string | null
|
|
45
|
+
openInNewSim: boolean
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface NormalizedComposePlan {
|
|
49
|
+
outDir: string
|
|
50
|
+
locale: string
|
|
51
|
+
canvases: CanvasPreset[]
|
|
52
|
+
frame: FrameComposeSpec
|
|
53
|
+
background: BackgroundSpec
|
|
54
|
+
text: {
|
|
55
|
+
preset: TextPresetName
|
|
56
|
+
color: string
|
|
57
|
+
subColor: string
|
|
58
|
+
eyebrowColor: string
|
|
59
|
+
}
|
|
60
|
+
slides: NormalizedScreenshotSlide[]
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface NormalizedScreenshotsPlan {
|
|
64
|
+
planPath: string
|
|
65
|
+
planDir: string
|
|
66
|
+
appTarget: string | null
|
|
67
|
+
deviceModel: DeviceModel | null
|
|
68
|
+
capture: NormalizedCapturePlan
|
|
69
|
+
compose: NormalizedComposePlan
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
73
|
+
return !!value && typeof value === 'object' && !Array.isArray(value)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function expectRecord(value: unknown, label: string): Record<string, unknown> {
|
|
77
|
+
if (!isRecord(value)) throw new Error(`${label} must be an object`)
|
|
78
|
+
return value
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function readString(value: unknown, fallback = ''): string {
|
|
82
|
+
return typeof value === 'string' ? value : fallback
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function readOptionalString(value: unknown): string | null {
|
|
86
|
+
return typeof value === 'string' && value.trim().length > 0 ? value.trim() : null
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function readBoolean(value: unknown, fallback: boolean): boolean {
|
|
90
|
+
return typeof value === 'boolean' ? value : fallback
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function readNumber(value: unknown, fallback: number): number {
|
|
94
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : fallback
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function readDeviceModel(
|
|
98
|
+
value: unknown,
|
|
99
|
+
fallback: DeviceModel | null,
|
|
100
|
+
): DeviceModel | null {
|
|
101
|
+
if (typeof value !== 'string' || value.trim().length === 0) return fallback
|
|
102
|
+
return Object.prototype.hasOwnProperty.call(devices, value)
|
|
103
|
+
? (value as DeviceModel)
|
|
104
|
+
: null
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function normalizeBackground(value: unknown, fallback: BackgroundSpec): BackgroundSpec {
|
|
108
|
+
if (typeof value === 'string') {
|
|
109
|
+
const preset = resolveBackgroundPreset(value)
|
|
110
|
+
if (!preset) throw new Error(`unknown background preset: ${value}`)
|
|
111
|
+
return preset
|
|
112
|
+
}
|
|
113
|
+
if (!isRecord(value)) return cloneBackgroundSpec(fallback)
|
|
114
|
+
const type =
|
|
115
|
+
value.type === 'solid' || value.type === 'gradient' ? value.type : fallback.type
|
|
116
|
+
if (type === 'solid') {
|
|
117
|
+
return {
|
|
118
|
+
type,
|
|
119
|
+
color: readString(value.color, fallback.color ?? '#000000'),
|
|
120
|
+
glow: readOptionalString(value.glow) ?? fallback.glow,
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const stops = Array.isArray(value.stops)
|
|
124
|
+
? value.stops
|
|
125
|
+
.map((stop) => {
|
|
126
|
+
if (!isRecord(stop)) return null
|
|
127
|
+
const offset = readNumber(stop.offset, Number.NaN)
|
|
128
|
+
const color = readOptionalString(stop.color)
|
|
129
|
+
if (!Number.isFinite(offset) || !color) return null
|
|
130
|
+
return { offset, color }
|
|
131
|
+
})
|
|
132
|
+
.filter((stop): stop is { offset: number; color: string } => !!stop)
|
|
133
|
+
: (fallback.stops?.map((stop) => ({ ...stop })) ?? [])
|
|
134
|
+
return {
|
|
135
|
+
type,
|
|
136
|
+
direction: readNumber(value.direction, fallback.direction ?? 180),
|
|
137
|
+
glow: readOptionalString(value.glow) ?? fallback.glow,
|
|
138
|
+
stops:
|
|
139
|
+
stops.length > 0 ? stops : (fallback.stops?.map((stop) => ({ ...stop })) ?? []),
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function normalizeCanvasList(value: unknown): CanvasPreset[] {
|
|
144
|
+
const names = Array.isArray(value) ? value : ['iphone-6-9']
|
|
145
|
+
const presets = names.map((entry) => {
|
|
146
|
+
if (typeof entry !== 'string')
|
|
147
|
+
throw new Error('compose.canvases entries must be strings')
|
|
148
|
+
const preset = resolveCanvasPreset(entry)
|
|
149
|
+
if (!preset) throw new Error(`unknown canvas preset: ${entry}`)
|
|
150
|
+
return preset
|
|
151
|
+
})
|
|
152
|
+
if (presets.length === 0)
|
|
153
|
+
throw new Error('compose.canvases must include at least one preset')
|
|
154
|
+
return presets
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function normalizeTextPreset(value: unknown): TextPresetName {
|
|
158
|
+
switch (value) {
|
|
159
|
+
case 'editorial-left':
|
|
160
|
+
case 'minimal-bottom':
|
|
161
|
+
case 'none':
|
|
162
|
+
case 'bold-top':
|
|
163
|
+
return value
|
|
164
|
+
default:
|
|
165
|
+
return 'bold-top'
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function normalizeCapturePathMode(value: unknown): CapturePathMode {
|
|
170
|
+
switch (value) {
|
|
171
|
+
case 'plan':
|
|
172
|
+
case 'flow':
|
|
173
|
+
case 'auto':
|
|
174
|
+
return value
|
|
175
|
+
default:
|
|
176
|
+
return 'auto'
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function normalizePose(value: unknown): PosePresetName | undefined {
|
|
181
|
+
switch (value) {
|
|
182
|
+
case 'straight':
|
|
183
|
+
case 'tilted-left':
|
|
184
|
+
case 'tilted-right':
|
|
185
|
+
case 'cut-bottom':
|
|
186
|
+
case 'cut-top':
|
|
187
|
+
return value
|
|
188
|
+
default:
|
|
189
|
+
return undefined
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function resolvePlanPath(planDir: string, value: string): string {
|
|
194
|
+
return path.isAbsolute(value) ? value : path.resolve(planDir, value)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function normalizeSlides(
|
|
198
|
+
value: unknown,
|
|
199
|
+
fallbackBackground: BackgroundSpec,
|
|
200
|
+
): NormalizedScreenshotSlide[] {
|
|
201
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
202
|
+
throw new Error('compose.slides must be a non-empty array')
|
|
203
|
+
}
|
|
204
|
+
return value.map((entry, index) => {
|
|
205
|
+
const slide = expectRecord(entry, `compose.slides[${index}]`)
|
|
206
|
+
const screenshot = readOptionalString(slide.screenshot)
|
|
207
|
+
if (!screenshot) {
|
|
208
|
+
throw new Error(`compose.slides[${index}].screenshot is required`)
|
|
209
|
+
}
|
|
210
|
+
const explicitId = readOptionalString(slide.id)
|
|
211
|
+
const assetKey = toAssetKey(explicitId || screenshot)
|
|
212
|
+
return {
|
|
213
|
+
id: explicitId || assetKey,
|
|
214
|
+
assetKey,
|
|
215
|
+
screenshot,
|
|
216
|
+
headline: readString(slide.headline),
|
|
217
|
+
subheadline: readString(slide.subheadline),
|
|
218
|
+
eyebrow: readString(slide.eyebrow),
|
|
219
|
+
pose: normalizePose(slide.pose),
|
|
220
|
+
scale:
|
|
221
|
+
typeof slide.scale === 'number' && Number.isFinite(slide.scale)
|
|
222
|
+
? slide.scale
|
|
223
|
+
: undefined,
|
|
224
|
+
offsetY:
|
|
225
|
+
typeof slide.offsetY === 'number' && Number.isFinite(slide.offsetY)
|
|
226
|
+
? slide.offsetY
|
|
227
|
+
: undefined,
|
|
228
|
+
background:
|
|
229
|
+
slide.theme || slide.background
|
|
230
|
+
? normalizeBackground(slide.theme ?? slide.background, fallbackBackground)
|
|
231
|
+
: undefined,
|
|
232
|
+
}
|
|
233
|
+
})
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export function loadScreenshotsPlan(planPath: string): NormalizedScreenshotsPlan {
|
|
237
|
+
const resolvedPlanPath = path.resolve(planPath)
|
|
238
|
+
const planDir = path.dirname(resolvedPlanPath)
|
|
239
|
+
const raw = yaml.parse(readFileSync(resolvedPlanPath, 'utf8'))
|
|
240
|
+
const root = expectRecord(raw, 'screenshots plan')
|
|
241
|
+
const capture = expectRecord(root.capture ?? {}, 'capture')
|
|
242
|
+
const compose = expectRecord(root.compose ?? {}, 'compose')
|
|
243
|
+
|
|
244
|
+
const appTarget =
|
|
245
|
+
typeof root.app === 'number' ? String(root.app) : readOptionalString(root.app)
|
|
246
|
+
const deviceModel = readDeviceModel(root.device, null)
|
|
247
|
+
const captureOutDir = resolvePlanPath(
|
|
248
|
+
planDir,
|
|
249
|
+
readString(capture.out, path.join('.sootsim', 'screenshots', 'capture')),
|
|
250
|
+
)
|
|
251
|
+
const captureFrom = readOptionalString(capture.from)
|
|
252
|
+
const flowPath = readOptionalString(capture.flow)
|
|
253
|
+
if (!captureFrom && !flowPath) {
|
|
254
|
+
throw new Error('capture.flow or capture.from is required')
|
|
255
|
+
}
|
|
256
|
+
const captureMode =
|
|
257
|
+
capture.mode === 'raw' || capture.mode === 'framed' || capture.mode === 'raw+framed'
|
|
258
|
+
? capture.mode
|
|
259
|
+
: ('raw+framed' as CaptureMode)
|
|
260
|
+
const capturePathMode = normalizeCapturePathMode(capture.pathMode)
|
|
261
|
+
const defaultBackground = normalizeBackground(
|
|
262
|
+
compose.background ?? ('cyan' as BackgroundPresetName),
|
|
263
|
+
resolveBackgroundPreset('cyan')!,
|
|
264
|
+
)
|
|
265
|
+
const frameConfig: FrameComposeSpec = {
|
|
266
|
+
show: readBoolean(
|
|
267
|
+
compose.frame && isRecord(compose.frame) ? compose.frame.show : undefined,
|
|
268
|
+
true,
|
|
269
|
+
),
|
|
270
|
+
style:
|
|
271
|
+
readDeviceModel(
|
|
272
|
+
compose.frame && isRecord(compose.frame) ? compose.frame.style : null,
|
|
273
|
+
null,
|
|
274
|
+
) ||
|
|
275
|
+
deviceModel ||
|
|
276
|
+
DEFAULT_FRAME_STYLE,
|
|
277
|
+
pose:
|
|
278
|
+
normalizePose(
|
|
279
|
+
compose.frame && isRecord(compose.frame) ? compose.frame.pose : undefined,
|
|
280
|
+
) || 'straight',
|
|
281
|
+
scale: readNumber(
|
|
282
|
+
compose.frame && isRecord(compose.frame) ? compose.frame.scale : undefined,
|
|
283
|
+
1,
|
|
284
|
+
),
|
|
285
|
+
offsetY: readNumber(
|
|
286
|
+
compose.frame && isRecord(compose.frame) ? compose.frame.offsetY : undefined,
|
|
287
|
+
0,
|
|
288
|
+
),
|
|
289
|
+
shadow: {
|
|
290
|
+
color: readString(
|
|
291
|
+
compose.frame && isRecord(compose.frame) && isRecord(compose.frame.shadow)
|
|
292
|
+
? compose.frame.shadow.color
|
|
293
|
+
: undefined,
|
|
294
|
+
DEFAULT_FRAME_SHADOW.color,
|
|
295
|
+
),
|
|
296
|
+
blur: readNumber(
|
|
297
|
+
compose.frame && isRecord(compose.frame) && isRecord(compose.frame.shadow)
|
|
298
|
+
? compose.frame.shadow.blur
|
|
299
|
+
: undefined,
|
|
300
|
+
DEFAULT_FRAME_SHADOW.blur,
|
|
301
|
+
),
|
|
302
|
+
spread: readNumber(
|
|
303
|
+
compose.frame && isRecord(compose.frame) && isRecord(compose.frame.shadow)
|
|
304
|
+
? compose.frame.shadow.spread
|
|
305
|
+
: undefined,
|
|
306
|
+
DEFAULT_FRAME_SHADOW.spread,
|
|
307
|
+
),
|
|
308
|
+
opacity: readNumber(
|
|
309
|
+
compose.frame && isRecord(compose.frame) && isRecord(compose.frame.shadow)
|
|
310
|
+
? compose.frame.shadow.opacity
|
|
311
|
+
: undefined,
|
|
312
|
+
DEFAULT_FRAME_SHADOW.opacity,
|
|
313
|
+
),
|
|
314
|
+
},
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return {
|
|
318
|
+
planPath: resolvedPlanPath,
|
|
319
|
+
planDir,
|
|
320
|
+
appTarget,
|
|
321
|
+
deviceModel,
|
|
322
|
+
capture: {
|
|
323
|
+
flowPath: flowPath ? resolvePlanPath(planDir, flowPath) : null,
|
|
324
|
+
fromDir: captureFrom ? resolvePlanPath(planDir, captureFrom) : null,
|
|
325
|
+
outDir: captureOutDir,
|
|
326
|
+
rawDir: captureFrom
|
|
327
|
+
? resolvePlanPath(planDir, captureFrom)
|
|
328
|
+
: path.join(captureOutDir, 'raw'),
|
|
329
|
+
framedDir: path.join(captureOutDir, 'framed'),
|
|
330
|
+
mode: captureMode,
|
|
331
|
+
pathMode: capturePathMode,
|
|
332
|
+
simId: readOptionalString(capture.sim),
|
|
333
|
+
openInNewSim: readBoolean(capture.new, false),
|
|
334
|
+
},
|
|
335
|
+
compose: {
|
|
336
|
+
outDir: resolvePlanPath(
|
|
337
|
+
planDir,
|
|
338
|
+
readString(compose.out, path.join('.sootsim', 'screenshots', 'exports')),
|
|
339
|
+
),
|
|
340
|
+
locale: readString(compose.locale, 'en'),
|
|
341
|
+
canvases: normalizeCanvasList(compose.canvases),
|
|
342
|
+
frame: frameConfig,
|
|
343
|
+
background: defaultBackground,
|
|
344
|
+
text: {
|
|
345
|
+
preset: normalizeTextPreset(
|
|
346
|
+
compose.text && isRecord(compose.text) ? compose.text.preset : undefined,
|
|
347
|
+
),
|
|
348
|
+
color: readString(
|
|
349
|
+
compose.text && isRecord(compose.text) ? compose.text.color : undefined,
|
|
350
|
+
'#ffffff',
|
|
351
|
+
),
|
|
352
|
+
subColor: readString(
|
|
353
|
+
compose.text && isRecord(compose.text) ? compose.text.subColor : undefined,
|
|
354
|
+
'rgba(232,240,247,0.86)',
|
|
355
|
+
),
|
|
356
|
+
eyebrowColor: readString(
|
|
357
|
+
compose.text && isRecord(compose.text) ? compose.text.eyebrowColor : undefined,
|
|
358
|
+
'rgba(229,242,255,0.72)',
|
|
359
|
+
),
|
|
360
|
+
},
|
|
361
|
+
slides: normalizeSlides(compose.slides, defaultBackground),
|
|
362
|
+
},
|
|
363
|
+
}
|
|
364
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// built-in skill: accessibility audit
|
|
2
|
+
|
|
3
|
+
import type { SootSimSkill } from '../types'
|
|
4
|
+
|
|
5
|
+
export const skill: SootSimSkill = {
|
|
6
|
+
name: 'a11y-review',
|
|
7
|
+
description: 'Check the app for accessibility issues',
|
|
8
|
+
type: 'review',
|
|
9
|
+
version: '1.0.0',
|
|
10
|
+
triggers: ['accessibility', 'a11y', 'check accessibility', 'audit accessibility'],
|
|
11
|
+
tools: [
|
|
12
|
+
{
|
|
13
|
+
name: 'audit_accessibility',
|
|
14
|
+
description: 'Run an accessibility audit on the current screen',
|
|
15
|
+
parameters: {
|
|
16
|
+
screen: { type: 'string', description: 'Screen name or URL to audit' },
|
|
17
|
+
},
|
|
18
|
+
async execute(params, context) {
|
|
19
|
+
const { chromium } = await import('playwright')
|
|
20
|
+
const path = await import('path')
|
|
21
|
+
const fs = await import('fs')
|
|
22
|
+
|
|
23
|
+
const browser = await chromium.launch({ headless: true })
|
|
24
|
+
const page = await browser.newPage({ viewport: { width: 500, height: 900 } })
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
await page.goto(context.url, { waitUntil: 'networkidle' })
|
|
28
|
+
await page.waitForFunction('window.__sootsimTest', { timeout: 10000 })
|
|
29
|
+
await page.waitForTimeout(2000)
|
|
30
|
+
|
|
31
|
+
// get accessibility tree
|
|
32
|
+
const tree = await page.evaluate(
|
|
33
|
+
'window.__sootsimTest.dumpAccessibilityTree(10)',
|
|
34
|
+
)
|
|
35
|
+
const allNodes = await page.evaluate('window.__sootsimTest.queryAll({})')
|
|
36
|
+
|
|
37
|
+
// analyze for common issues
|
|
38
|
+
const issues: {
|
|
39
|
+
severity: 'error' | 'warning' | 'info'
|
|
40
|
+
message: string
|
|
41
|
+
node?: string
|
|
42
|
+
}[] = []
|
|
43
|
+
|
|
44
|
+
if (Array.isArray(allNodes)) {
|
|
45
|
+
for (const node of allNodes) {
|
|
46
|
+
// check for missing accessibility labels on interactive elements
|
|
47
|
+
if (
|
|
48
|
+
(node.accessibilityRole === 'button' ||
|
|
49
|
+
node.accessibilityRole === 'link' ||
|
|
50
|
+
node.type === 'TouchableOpacity' ||
|
|
51
|
+
node.type === 'Pressable') &&
|
|
52
|
+
!node.accessibilityLabel &&
|
|
53
|
+
!node.text
|
|
54
|
+
) {
|
|
55
|
+
issues.push({
|
|
56
|
+
severity: 'error',
|
|
57
|
+
message: `Interactive element missing accessibility label`,
|
|
58
|
+
node: node.testID || node.id || node.type,
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// check for missing roles
|
|
63
|
+
if (
|
|
64
|
+
(node.type === 'TouchableOpacity' || node.type === 'Pressable') &&
|
|
65
|
+
!node.accessibilityRole
|
|
66
|
+
) {
|
|
67
|
+
issues.push({
|
|
68
|
+
severity: 'warning',
|
|
69
|
+
message: `Touchable element missing accessibility role`,
|
|
70
|
+
node: node.testID || node.id || node.type,
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// check for images without alt text
|
|
75
|
+
if (node.type === 'Image' && !node.accessibilityLabel && !node.accessible) {
|
|
76
|
+
issues.push({
|
|
77
|
+
severity: 'warning',
|
|
78
|
+
message: `Image missing accessibility label`,
|
|
79
|
+
node: node.testID || node.id || 'Image',
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// check for small touch targets
|
|
84
|
+
if (
|
|
85
|
+
node.layout &&
|
|
86
|
+
(node.accessibilityRole === 'button' ||
|
|
87
|
+
node.type === 'TouchableOpacity') &&
|
|
88
|
+
(node.layout.width < 44 || node.layout.height < 44)
|
|
89
|
+
) {
|
|
90
|
+
issues.push({
|
|
91
|
+
severity: 'warning',
|
|
92
|
+
message: `Touch target too small (${Math.round(node.layout.width)}x${Math.round(node.layout.height)}pt, minimum 44x44pt)`,
|
|
93
|
+
node: node.testID || node.text || node.type,
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// write report
|
|
100
|
+
const report = {
|
|
101
|
+
timestamp: new Date().toISOString(),
|
|
102
|
+
screen: params.screen || 'current',
|
|
103
|
+
nodeCount: Array.isArray(allNodes) ? allNodes.length : 0,
|
|
104
|
+
issues,
|
|
105
|
+
errors: issues.filter((i) => i.severity === 'error').length,
|
|
106
|
+
warnings: issues.filter((i) => i.severity === 'warning').length,
|
|
107
|
+
tree: typeof tree === 'string' ? tree : JSON.stringify(tree),
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const reportPath = path.join(context.outputDir, 'a11y-report.json')
|
|
111
|
+
fs.mkdirSync(path.dirname(reportPath), { recursive: true })
|
|
112
|
+
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2))
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
success: issues.filter((i) => i.severity === 'error').length === 0,
|
|
116
|
+
message: `${report.errors} errors, ${report.warnings} warnings across ${report.nodeCount} nodes`,
|
|
117
|
+
artifacts: [{ type: 'report', name: 'a11y-report', path: reportPath }],
|
|
118
|
+
data: report,
|
|
119
|
+
}
|
|
120
|
+
} finally {
|
|
121
|
+
await browser.close()
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// built-in skill: package compatibility check
|
|
2
|
+
|
|
3
|
+
import type { SootSimSkill } from '../types'
|
|
4
|
+
|
|
5
|
+
function getCompatStatus(entry: {
|
|
6
|
+
stubType: 'native' | 'works' | 'build-only'
|
|
7
|
+
versions: Array<{ coverage: number }>
|
|
8
|
+
}): 'full' | 'partial' | 'auto-stub' {
|
|
9
|
+
if (entry.stubType === 'works') return 'full'
|
|
10
|
+
if (entry.stubType === 'build-only') return 'full'
|
|
11
|
+
return entry.versions.some((version) => version.coverage >= 1) ? 'full' : 'auto-stub'
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const skill: SootSimSkill = {
|
|
15
|
+
name: 'compat-check',
|
|
16
|
+
description: 'Check package compatibility with sootsim',
|
|
17
|
+
type: 'review',
|
|
18
|
+
version: '1.0.0',
|
|
19
|
+
triggers: ['compatibility', 'compat', 'check package', 'supported packages'],
|
|
20
|
+
tools: [
|
|
21
|
+
{
|
|
22
|
+
name: 'check_compatibility',
|
|
23
|
+
description: 'Scan project dependencies for sootsim compatibility',
|
|
24
|
+
parameters: {
|
|
25
|
+
packageName: {
|
|
26
|
+
type: 'string',
|
|
27
|
+
description: 'Specific package to check (optional)',
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
async execute(params, context) {
|
|
31
|
+
const fs = await import('fs')
|
|
32
|
+
const path = await import('path')
|
|
33
|
+
|
|
34
|
+
const { POLYFILL_REGISTRY } = await import('../../../../compat/src/web.ts')
|
|
35
|
+
|
|
36
|
+
if (params.packageName) {
|
|
37
|
+
const entry = POLYFILL_REGISTRY[params.packageName]
|
|
38
|
+
if (!entry) {
|
|
39
|
+
return {
|
|
40
|
+
success: true,
|
|
41
|
+
message: `${params.packageName}: not in registry (may be auto-stubbed if native)`,
|
|
42
|
+
data: { package: params.packageName, status: 'unknown' },
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const status = getCompatStatus(entry)
|
|
46
|
+
return {
|
|
47
|
+
success: true,
|
|
48
|
+
message: `${params.packageName}: ${status}`,
|
|
49
|
+
data: { package: params.packageName, status, ...entry },
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// scan project deps
|
|
54
|
+
const pkgPath = path.join(context.projectDir, 'package.json')
|
|
55
|
+
if (!fs.existsSync(pkgPath)) {
|
|
56
|
+
return { success: false, message: 'No package.json found' }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'))
|
|
60
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies }
|
|
61
|
+
|
|
62
|
+
const results: Record<string, string[]> = {
|
|
63
|
+
full: [],
|
|
64
|
+
partial: [],
|
|
65
|
+
'auto-stub': [],
|
|
66
|
+
unsupported: [],
|
|
67
|
+
unknown: [],
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
for (const dep of Object.keys(allDeps)) {
|
|
71
|
+
const entry = POLYFILL_REGISTRY[dep]
|
|
72
|
+
if (entry) {
|
|
73
|
+
const status = getCompatStatus(entry)
|
|
74
|
+
if (results[status]) results[status].push(dep)
|
|
75
|
+
} else if (isNative(dep)) {
|
|
76
|
+
results.unknown.push(dep)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const reportPath = path.join(context.outputDir, 'compat-report.json')
|
|
81
|
+
fs.mkdirSync(path.dirname(reportPath), { recursive: true })
|
|
82
|
+
fs.writeFileSync(reportPath, JSON.stringify(results, null, 2))
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
success: results.unsupported.length === 0,
|
|
86
|
+
message: `${results.full.length} full, ${results.partial.length} partial, ${results.unsupported.length} unsupported, ${results.unknown.length} unknown`,
|
|
87
|
+
artifacts: [{ type: 'report', name: 'compat-report', path: reportPath }],
|
|
88
|
+
data: results,
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function isNative(name: string): boolean {
|
|
96
|
+
return (
|
|
97
|
+
name.startsWith('expo-') ||
|
|
98
|
+
name.startsWith('react-native-') ||
|
|
99
|
+
name.startsWith('@react-native/') ||
|
|
100
|
+
name.startsWith('@react-native-community/') ||
|
|
101
|
+
name.startsWith('@expo/') ||
|
|
102
|
+
name === 'expo'
|
|
103
|
+
)
|
|
104
|
+
}
|