@essential-apps/shopify-test-runner 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (265) hide show
  1. package/dist/contracts/normalize.d.ts +61 -0
  2. package/dist/contracts/normalize.d.ts.map +1 -0
  3. package/dist/contracts/normalize.js +99 -0
  4. package/dist/contracts/normalize.js.map +1 -0
  5. package/dist/contracts/normalizeHtml.d.ts +37 -0
  6. package/dist/contracts/normalizeHtml.d.ts.map +1 -0
  7. package/dist/contracts/normalizeHtml.js +89 -0
  8. package/dist/contracts/normalizeHtml.js.map +1 -0
  9. package/dist/edge/cert.d.ts +44 -0
  10. package/dist/edge/cert.d.ts.map +1 -0
  11. package/dist/edge/cert.js +117 -0
  12. package/dist/edge/cert.js.map +1 -0
  13. package/dist/edge/edgeProxy.d.ts +43 -0
  14. package/dist/edge/edgeProxy.d.ts.map +1 -0
  15. package/dist/edge/edgeProxy.js +297 -0
  16. package/dist/edge/edgeProxy.js.map +1 -0
  17. package/dist/edge/nodeShim.d.ts +2 -0
  18. package/dist/edge/nodeShim.d.ts.map +1 -0
  19. package/dist/edge/nodeShim.js +217 -0
  20. package/dist/edge/nodeShim.js.map +1 -0
  21. package/dist/index.d.ts +39 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +36 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/lib/buildSourceBundle.d.ts +56 -0
  26. package/dist/lib/buildSourceBundle.d.ts.map +1 -0
  27. package/dist/lib/buildSourceBundle.js +153 -0
  28. package/dist/lib/buildSourceBundle.js.map +1 -0
  29. package/dist/lib/freePort.d.ts +5 -0
  30. package/dist/lib/freePort.d.ts.map +1 -0
  31. package/dist/lib/freePort.js +33 -0
  32. package/dist/lib/freePort.js.map +1 -0
  33. package/dist/lib/functionBuild.d.ts +99 -0
  34. package/dist/lib/functionBuild.d.ts.map +1 -0
  35. package/dist/lib/functionBuild.js +413 -0
  36. package/dist/lib/functionBuild.js.map +1 -0
  37. package/dist/lib/neonWsProxy.d.ts +41 -0
  38. package/dist/lib/neonWsProxy.d.ts.map +1 -0
  39. package/dist/lib/neonWsProxy.js +101 -0
  40. package/dist/lib/neonWsProxy.js.map +1 -0
  41. package/dist/lib/sourceZipUpload.d.ts +45 -0
  42. package/dist/lib/sourceZipUpload.d.ts.map +1 -0
  43. package/dist/lib/sourceZipUpload.js +129 -0
  44. package/dist/lib/sourceZipUpload.js.map +1 -0
  45. package/dist/lib/stealthLaunch.d.ts +35 -0
  46. package/dist/lib/stealthLaunch.d.ts.map +1 -0
  47. package/dist/lib/stealthLaunch.js +46 -0
  48. package/dist/lib/stealthLaunch.js.map +1 -0
  49. package/dist/lib/storeAutomation.d.ts +22 -0
  50. package/dist/lib/storeAutomation.d.ts.map +1 -0
  51. package/dist/lib/storeAutomation.js +85 -0
  52. package/dist/lib/storeAutomation.js.map +1 -0
  53. package/dist/playwright/baseConfig.d.ts +62 -0
  54. package/dist/playwright/baseConfig.d.ts.map +1 -0
  55. package/dist/playwright/baseConfig.js +68 -0
  56. package/dist/playwright/baseConfig.js.map +1 -0
  57. package/dist/playwright/globalSetup.d.ts +2 -0
  58. package/dist/playwright/globalSetup.d.ts.map +1 -0
  59. package/dist/playwright/globalSetup.js +139 -0
  60. package/dist/playwright/globalSetup.js.map +1 -0
  61. package/dist/playwright/index.d.ts +9 -0
  62. package/dist/playwright/index.d.ts.map +1 -0
  63. package/dist/playwright/index.js +9 -0
  64. package/dist/playwright/index.js.map +1 -0
  65. package/dist/probes/fonts.d.ts +4 -0
  66. package/dist/probes/fonts.d.ts.map +1 -0
  67. package/dist/probes/fonts.js +255 -0
  68. package/dist/probes/fonts.js.map +1 -0
  69. package/dist/probes/mirror.d.ts +4 -0
  70. package/dist/probes/mirror.d.ts.map +1 -0
  71. package/dist/probes/mirror.js +260 -0
  72. package/dist/probes/mirror.js.map +1 -0
  73. package/dist/probes/runProbe.d.ts +3 -0
  74. package/dist/probes/runProbe.d.ts.map +1 -0
  75. package/dist/probes/runProbe.js +219 -0
  76. package/dist/probes/runProbe.js.map +1 -0
  77. package/dist/probes/types.d.ts +72 -0
  78. package/dist/probes/types.d.ts.map +1 -0
  79. package/dist/probes/types.js +2 -0
  80. package/dist/probes/types.js.map +1 -0
  81. package/dist/scripts/_probeSourceUrl.d.ts +3 -0
  82. package/dist/scripts/_probeSourceUrl.d.ts.map +1 -0
  83. package/dist/scripts/_probeSourceUrl.js +119 -0
  84. package/dist/scripts/_probeSourceUrl.js.map +1 -0
  85. package/dist/scripts/addStore.d.ts +3 -0
  86. package/dist/scripts/addStore.d.ts.map +1 -0
  87. package/dist/scripts/addStore.js +46 -0
  88. package/dist/scripts/addStore.js.map +1 -0
  89. package/dist/scripts/buildDockerImage.d.ts +3 -0
  90. package/dist/scripts/buildDockerImage.d.ts.map +1 -0
  91. package/dist/scripts/buildDockerImage.js +60 -0
  92. package/dist/scripts/buildDockerImage.js.map +1 -0
  93. package/dist/scripts/captureAuth.d.ts +3 -0
  94. package/dist/scripts/captureAuth.d.ts.map +1 -0
  95. package/dist/scripts/captureAuth.js +124 -0
  96. package/dist/scripts/captureAuth.js.map +1 -0
  97. package/dist/scripts/captureContracts.d.ts +3 -0
  98. package/dist/scripts/captureContracts.d.ts.map +1 -0
  99. package/dist/scripts/captureContracts.js +517 -0
  100. package/dist/scripts/captureContracts.js.map +1 -0
  101. package/dist/scripts/captureRestContracts.d.ts +3 -0
  102. package/dist/scripts/captureRestContracts.d.ts.map +1 -0
  103. package/dist/scripts/captureRestContracts.js +245 -0
  104. package/dist/scripts/captureRestContracts.js.map +1 -0
  105. package/dist/scripts/checkOperationCoverage.d.ts +3 -0
  106. package/dist/scripts/checkOperationCoverage.d.ts.map +1 -0
  107. package/dist/scripts/checkOperationCoverage.js +302 -0
  108. package/dist/scripts/checkOperationCoverage.js.map +1 -0
  109. package/dist/scripts/cleanupStores.d.ts +3 -0
  110. package/dist/scripts/cleanupStores.d.ts.map +1 -0
  111. package/dist/scripts/cleanupStores.js +77 -0
  112. package/dist/scripts/cleanupStores.js.map +1 -0
  113. package/dist/scripts/createStores.d.ts +3 -0
  114. package/dist/scripts/createStores.d.ts.map +1 -0
  115. package/dist/scripts/createStores.js +66 -0
  116. package/dist/scripts/createStores.js.map +1 -0
  117. package/dist/scripts/deployAppVersion.d.ts +3 -0
  118. package/dist/scripts/deployAppVersion.d.ts.map +1 -0
  119. package/dist/scripts/deployAppVersion.js +591 -0
  120. package/dist/scripts/deployAppVersion.js.map +1 -0
  121. package/dist/scripts/devE2eBackend.d.ts +3 -0
  122. package/dist/scripts/devE2eBackend.d.ts.map +1 -0
  123. package/dist/scripts/devE2eBackend.js +117 -0
  124. package/dist/scripts/devE2eBackend.js.map +1 -0
  125. package/dist/scripts/devOnlineBackend.d.ts +3 -0
  126. package/dist/scripts/devOnlineBackend.d.ts.map +1 -0
  127. package/dist/scripts/devOnlineBackend.js +117 -0
  128. package/dist/scripts/devOnlineBackend.js.map +1 -0
  129. package/dist/scripts/installApp.d.ts +3 -0
  130. package/dist/scripts/installApp.d.ts.map +1 -0
  131. package/dist/scripts/installApp.js +163 -0
  132. package/dist/scripts/installApp.js.map +1 -0
  133. package/dist/scripts/listStores.d.ts +3 -0
  134. package/dist/scripts/listStores.d.ts.map +1 -0
  135. package/dist/scripts/listStores.js +18 -0
  136. package/dist/scripts/listStores.js.map +1 -0
  137. package/dist/scripts/runDocker.d.ts +3 -0
  138. package/dist/scripts/runDocker.d.ts.map +1 -0
  139. package/dist/scripts/runDocker.js +88 -0
  140. package/dist/scripts/runDocker.js.map +1 -0
  141. package/dist/scripts/runDockerAuth.d.ts +3 -0
  142. package/dist/scripts/runDockerAuth.d.ts.map +1 -0
  143. package/dist/scripts/runDockerAuth.js +108 -0
  144. package/dist/scripts/runDockerAuth.js.map +1 -0
  145. package/dist/scripts/runDockerOffline.d.ts +3 -0
  146. package/dist/scripts/runDockerOffline.d.ts.map +1 -0
  147. package/dist/scripts/runDockerOffline.js +129 -0
  148. package/dist/scripts/runDockerOffline.js.map +1 -0
  149. package/dist/scripts/runDockerOfflineExplore.d.ts +3 -0
  150. package/dist/scripts/runDockerOfflineExplore.d.ts.map +1 -0
  151. package/dist/scripts/runDockerOfflineExplore.js +116 -0
  152. package/dist/scripts/runDockerOfflineExplore.js.map +1 -0
  153. package/dist/scripts/runIsolatedDockerOffline.d.ts +3 -0
  154. package/dist/scripts/runIsolatedDockerOffline.d.ts.map +1 -0
  155. package/dist/scripts/runIsolatedDockerOffline.js +351 -0
  156. package/dist/scripts/runIsolatedDockerOffline.js.map +1 -0
  157. package/dist/scripts/runOffline.d.ts +3 -0
  158. package/dist/scripts/runOffline.d.ts.map +1 -0
  159. package/dist/scripts/runOffline.js +521 -0
  160. package/dist/scripts/runOffline.js.map +1 -0
  161. package/dist/scripts/runOfflineE2e.d.ts +3 -0
  162. package/dist/scripts/runOfflineE2e.d.ts.map +1 -0
  163. package/dist/scripts/runOfflineE2e.js +408 -0
  164. package/dist/scripts/runOfflineE2e.js.map +1 -0
  165. package/dist/scripts/runOfflineFullTests.d.ts +3 -0
  166. package/dist/scripts/runOfflineFullTests.d.ts.map +1 -0
  167. package/dist/scripts/runOfflineFullTests.js +1456 -0
  168. package/dist/scripts/runOfflineFullTests.js.map +1 -0
  169. package/dist/scripts/runSupermachine.d.ts +3 -0
  170. package/dist/scripts/runSupermachine.d.ts.map +1 -0
  171. package/dist/scripts/runSupermachine.js +474 -0
  172. package/dist/scripts/runSupermachine.js.map +1 -0
  173. package/dist/scripts/runSupermachineAuth.d.ts +3 -0
  174. package/dist/scripts/runSupermachineAuth.d.ts.map +1 -0
  175. package/dist/scripts/runSupermachineAuth.js +454 -0
  176. package/dist/scripts/runSupermachineAuth.js.map +1 -0
  177. package/dist/scripts/runTests.d.ts +3 -0
  178. package/dist/scripts/runTests.d.ts.map +1 -0
  179. package/dist/scripts/runTests.js +278 -0
  180. package/dist/scripts/runTests.js.map +1 -0
  181. package/dist/scripts/runVm.d.ts +3 -0
  182. package/dist/scripts/runVm.d.ts.map +1 -0
  183. package/dist/scripts/runVm.js +524 -0
  184. package/dist/scripts/runVm.js.map +1 -0
  185. package/dist/scripts/runVmAuth.d.ts +3 -0
  186. package/dist/scripts/runVmAuth.d.ts.map +1 -0
  187. package/dist/scripts/runVmAuth.js +475 -0
  188. package/dist/scripts/runVmAuth.js.map +1 -0
  189. package/dist/scripts/runVmScript.d.ts +3 -0
  190. package/dist/scripts/runVmScript.d.ts.map +1 -0
  191. package/dist/scripts/runVmScript.js +242 -0
  192. package/dist/scripts/runVmScript.js.map +1 -0
  193. package/dist/scripts/setupTestDb.d.ts +3 -0
  194. package/dist/scripts/setupTestDb.d.ts.map +1 -0
  195. package/dist/scripts/setupTestDb.js +61 -0
  196. package/dist/scripts/setupTestDb.js.map +1 -0
  197. package/dist/scripts/verifyContracts.d.ts +3 -0
  198. package/dist/scripts/verifyContracts.d.ts.map +1 -0
  199. package/dist/scripts/verifyContracts.js +258 -0
  200. package/dist/scripts/verifyContracts.js.map +1 -0
  201. package/dist/scripts/verifyRestContracts.d.ts +3 -0
  202. package/dist/scripts/verifyRestContracts.d.ts.map +1 -0
  203. package/dist/scripts/verifyRestContracts.js +237 -0
  204. package/dist/scripts/verifyRestContracts.js.map +1 -0
  205. package/dist/vite/offlineConfig.d.ts +34 -0
  206. package/dist/vite/offlineConfig.d.ts.map +1 -0
  207. package/dist/vite/offlineConfig.js +61 -0
  208. package/dist/vite/offlineConfig.js.map +1 -0
  209. package/dist/vite/onlineConfig.d.ts +42 -0
  210. package/dist/vite/onlineConfig.d.ts.map +1 -0
  211. package/dist/vite/onlineConfig.js +56 -0
  212. package/dist/vite/onlineConfig.js.map +1 -0
  213. package/docker/Dockerfile +67 -0
  214. package/docker/Dockerfile.vm +137 -0
  215. package/docker/README.md +50 -0
  216. package/docker/entrypoint.sh +198 -0
  217. package/package.json +85 -0
  218. package/src/contracts/normalize.ts +96 -0
  219. package/src/contracts/normalizeHtml.ts +98 -0
  220. package/src/edge/ca.cnf +14 -0
  221. package/src/edge/ca.crt +22 -0
  222. package/src/edge/ca.key +28 -0
  223. package/src/edge/cert.ts +117 -0
  224. package/src/edge/edgeProxy.ts +390 -0
  225. package/src/edge/server.cnf +28 -0
  226. package/src/edge/server.crt +26 -0
  227. package/src/edge/server.key +28 -0
  228. package/src/index.ts +67 -0
  229. package/src/lib/buildSourceBundle.ts +197 -0
  230. package/src/lib/freePort.ts +33 -0
  231. package/src/lib/functionBuild.ts +490 -0
  232. package/src/lib/neonWsProxy.ts +124 -0
  233. package/src/lib/sourceZipUpload.ts +168 -0
  234. package/src/lib/stealthLaunch.ts +57 -0
  235. package/src/lib/storeAutomation.ts +110 -0
  236. package/src/playwright/baseConfig.ts +120 -0
  237. package/src/playwright/globalSetup.ts +179 -0
  238. package/src/playwright/index.ts +11 -0
  239. package/src/probes/fonts.ts +279 -0
  240. package/src/probes/mirror.ts +283 -0
  241. package/src/probes/runProbe.ts +257 -0
  242. package/src/probes/types.ts +73 -0
  243. package/src/scripts/addStore.ts +59 -0
  244. package/src/scripts/buildDockerImage.ts +66 -0
  245. package/src/scripts/captureAuth.ts +145 -0
  246. package/src/scripts/captureContracts.ts +675 -0
  247. package/src/scripts/captureRestContracts.ts +319 -0
  248. package/src/scripts/checkOperationCoverage.ts +365 -0
  249. package/src/scripts/cleanupStores.ts +91 -0
  250. package/src/scripts/createStores.ts +77 -0
  251. package/src/scripts/deployAppVersion.ts +692 -0
  252. package/src/scripts/devOnlineBackend.ts +141 -0
  253. package/src/scripts/installApp.ts +188 -0
  254. package/src/scripts/listStores.ts +19 -0
  255. package/src/scripts/runDockerAuth.ts +120 -0
  256. package/src/scripts/runOffline.ts +577 -0
  257. package/src/scripts/runOfflineFullTests.ts +1634 -0
  258. package/src/scripts/runTests.ts +306 -0
  259. package/src/scripts/runVm.ts +562 -0
  260. package/src/scripts/runVmAuth.ts +541 -0
  261. package/src/scripts/runVmScript.ts +282 -0
  262. package/src/scripts/setupTestDb.ts +71 -0
  263. package/src/scripts/verifyContracts.ts +310 -0
  264. package/src/scripts/verifyRestContracts.ts +275 -0
  265. package/src/vite/onlineConfig.ts +60 -0
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Response normalisation for operation contracts.
3
+ *
4
+ * The contract-conformance system (see docs/CONTRACTS.md) compares
5
+ * GraphQL responses captured from two different sources — the
6
+ * offline mock and live Shopify — and asserts they're "the same".
7
+ * But "the same" can mean two different things:
8
+ *
9
+ * - **Shape-equal**: same keys, same value types, same array
10
+ * lengths. Ignores actual values that Shopify generates per
11
+ * request (IDs, timestamps, CDN-cache-busted image URLs).
12
+ * - **Value-equal**: byte-for-byte JSON equality.
13
+ *
14
+ * Live verification REQUIRES shape-equal — real Shopify hands out
15
+ * different numeric IDs and cache-bust hashes on every store. If
16
+ * we compare raw values, every live diff is noise.
17
+ *
18
+ * This module implements shape-equal by walking a response tree
19
+ * and replacing volatile substrings with stable tokens. Two
20
+ * responses that differ ONLY in IDs/timestamps/CDN-URLs become
21
+ * byte-equal after normalisation; real structural drift survives.
22
+ *
23
+ * Replacement rules (each preserves the surrounding context so the
24
+ * normalised value remains parseable):
25
+ *
26
+ * gid://shopify/<Type>/<digits> → gid://shopify/<Type>/<ID>
27
+ * gid://shopify/<Type>/<digits>?<rest> → gid://shopify/<Type>/<ID>?<rest>
28
+ * 2026-05-14T12:34:56Z → <DATETIME>
29
+ * 2026-05-14T12:34:56.789Z → <DATETIME>
30
+ * 2026-05-14 → <DATE>
31
+ * https://cdn.shopify.com/... → <CDN_URL>
32
+ * https://<shop>.myshopify.com/cdn/... → <CDN_URL>
33
+ * base64-ish cursor (eyJ... starting) → <CURSOR>
34
+ *
35
+ * The replacement is INTENTIONALLY GENEROUS. We'd rather over-
36
+ * normalise (treat two distinct values as equal) than under (treat
37
+ * two equivalent values as different and produce noisy diffs).
38
+ * Genuine drift (a field that's now missing, a type that changed)
39
+ * survives because we only rewrite well-formed volatile patterns.
40
+ *
41
+ * For offline-only verification (Layer 3), normalisation is a
42
+ * no-op — the offline mock returns deterministic values, so raw
43
+ * equality already works. For live verification (Layer 4),
44
+ * normalisation is essential.
45
+ */
46
+ export declare function normaliseString(s: string): string;
47
+ /**
48
+ * Walk a JSON-able value and replace volatile substrings with
49
+ * stable tokens. Returns a deep-cloned tree — never mutates the
50
+ * input. Objects and arrays recurse; scalars (string / number /
51
+ * boolean / null) get inspected directly.
52
+ *
53
+ * Number IDs (e.g. `legacyResourceId: 12345`) — we DON'T normalise
54
+ * these. They're explicit values the consuming app may compare on.
55
+ * If a contract relies on a specific numeric ID, the live and
56
+ * offline values won't match and the diff fires (correctly — it's
57
+ * a fixture-mismatch issue, fix via fixtures.json or by re-
58
+ * capturing against a known seeded live store).
59
+ */
60
+ export declare function normaliseResponse(value: unknown): unknown;
61
+ //# sourceMappingURL=normalize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalize.d.ts","sourceRoot":"","sources":["../../src/contracts/normalize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAaH,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAWjD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAYzD"}
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Response normalisation for operation contracts.
3
+ *
4
+ * The contract-conformance system (see docs/CONTRACTS.md) compares
5
+ * GraphQL responses captured from two different sources — the
6
+ * offline mock and live Shopify — and asserts they're "the same".
7
+ * But "the same" can mean two different things:
8
+ *
9
+ * - **Shape-equal**: same keys, same value types, same array
10
+ * lengths. Ignores actual values that Shopify generates per
11
+ * request (IDs, timestamps, CDN-cache-busted image URLs).
12
+ * - **Value-equal**: byte-for-byte JSON equality.
13
+ *
14
+ * Live verification REQUIRES shape-equal — real Shopify hands out
15
+ * different numeric IDs and cache-bust hashes on every store. If
16
+ * we compare raw values, every live diff is noise.
17
+ *
18
+ * This module implements shape-equal by walking a response tree
19
+ * and replacing volatile substrings with stable tokens. Two
20
+ * responses that differ ONLY in IDs/timestamps/CDN-URLs become
21
+ * byte-equal after normalisation; real structural drift survives.
22
+ *
23
+ * Replacement rules (each preserves the surrounding context so the
24
+ * normalised value remains parseable):
25
+ *
26
+ * gid://shopify/<Type>/<digits> → gid://shopify/<Type>/<ID>
27
+ * gid://shopify/<Type>/<digits>?<rest> → gid://shopify/<Type>/<ID>?<rest>
28
+ * 2026-05-14T12:34:56Z → <DATETIME>
29
+ * 2026-05-14T12:34:56.789Z → <DATETIME>
30
+ * 2026-05-14 → <DATE>
31
+ * https://cdn.shopify.com/... → <CDN_URL>
32
+ * https://<shop>.myshopify.com/cdn/... → <CDN_URL>
33
+ * base64-ish cursor (eyJ... starting) → <CURSOR>
34
+ *
35
+ * The replacement is INTENTIONALLY GENEROUS. We'd rather over-
36
+ * normalise (treat two distinct values as equal) than under (treat
37
+ * two equivalent values as different and produce noisy diffs).
38
+ * Genuine drift (a field that's now missing, a type that changed)
39
+ * survives because we only rewrite well-formed volatile patterns.
40
+ *
41
+ * For offline-only verification (Layer 3), normalisation is a
42
+ * no-op — the offline mock returns deterministic values, so raw
43
+ * equality already works. For live verification (Layer 4),
44
+ * normalisation is essential.
45
+ */
46
+ const ID_GID_RE = /^(gid:\/\/shopify\/[A-Za-z]+)\/(\d+)(\?.*)?$/;
47
+ const ISO_DATE_TIME_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})$/;
48
+ const ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
49
+ const CDN_URL_RE = /^https?:\/\/(?:cdn\.shopify\.com|[^./]+\.myshopify\.com\/cdn\/)/;
50
+ // Base64-ish cursors are tough to detect without false-positives. A
51
+ // loose heuristic: starts with `eyJ` (the base64 prefix of `{"...`)
52
+ // and is at least 40 chars. Real Shopify cursors are JWT-ish.
53
+ const CURSOR_RE = /^eyJ[A-Za-z0-9_+/=-]{37,}$/;
54
+ export function normaliseString(s) {
55
+ const gid = s.match(ID_GID_RE);
56
+ if (gid) {
57
+ const [, typePrefix, , suffix] = gid;
58
+ return `${typePrefix}/<ID>${suffix ?? ''}`;
59
+ }
60
+ if (ISO_DATE_TIME_RE.test(s))
61
+ return '<DATETIME>';
62
+ if (ISO_DATE_RE.test(s))
63
+ return '<DATE>';
64
+ if (CDN_URL_RE.test(s))
65
+ return '<CDN_URL>';
66
+ if (CURSOR_RE.test(s))
67
+ return '<CURSOR>';
68
+ return s;
69
+ }
70
+ /**
71
+ * Walk a JSON-able value and replace volatile substrings with
72
+ * stable tokens. Returns a deep-cloned tree — never mutates the
73
+ * input. Objects and arrays recurse; scalars (string / number /
74
+ * boolean / null) get inspected directly.
75
+ *
76
+ * Number IDs (e.g. `legacyResourceId: 12345`) — we DON'T normalise
77
+ * these. They're explicit values the consuming app may compare on.
78
+ * If a contract relies on a specific numeric ID, the live and
79
+ * offline values won't match and the diff fires (correctly — it's
80
+ * a fixture-mismatch issue, fix via fixtures.json or by re-
81
+ * capturing against a known seeded live store).
82
+ */
83
+ export function normaliseResponse(value) {
84
+ if (value === null || value === undefined)
85
+ return value;
86
+ if (typeof value === 'string')
87
+ return normaliseString(value);
88
+ if (Array.isArray(value))
89
+ return value.map(normaliseResponse);
90
+ if (typeof value === 'object') {
91
+ const out = {};
92
+ for (const [k, v] of Object.entries(value)) {
93
+ out[k] = normaliseResponse(v);
94
+ }
95
+ return out;
96
+ }
97
+ return value;
98
+ }
99
+ //# sourceMappingURL=normalize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalize.js","sourceRoot":"","sources":["../../src/contracts/normalize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAEH,MAAM,SAAS,GAAG,8CAA8C,CAAC;AACjE,MAAM,gBAAgB,GACpB,uEAAuE,CAAC;AAC1E,MAAM,WAAW,GAAG,qBAAqB,CAAC;AAC1C,MAAM,UAAU,GACd,iEAAiE,CAAC;AACpE,oEAAoE;AACpE,oEAAoE;AACpE,8DAA8D;AAC9D,MAAM,SAAS,GAAG,4BAA4B,CAAC;AAE/C,MAAM,UAAU,eAAe,CAAC,CAAS;IACvC,MAAM,GAAG,GAAG,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IAC/B,IAAI,GAAG,EAAE,CAAC;QACR,MAAM,CAAC,EAAE,UAAU,EAAE,AAAD,EAAG,MAAM,CAAC,GAAG,GAAG,CAAC;QACrC,OAAO,GAAG,UAAU,QAAQ,MAAM,IAAI,EAAE,EAAE,CAAC;IAC7C,CAAC;IACD,IAAI,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,OAAO,YAAY,CAAC;IAClD,IAAI,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,OAAO,QAAQ,CAAC;IACzC,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,OAAO,WAAW,CAAC;IAC3C,IAAI,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,OAAO,UAAU,CAAC;IACzC,OAAO,CAAC,CAAC;AACX,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAc;IAC9C,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACxD,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,eAAe,CAAC,KAAK,CAAC,CAAC;IAC7D,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IAC9D,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,MAAM,GAAG,GAA4B,EAAE,CAAC;QACxC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAgC,CAAC,EAAE,CAAC;YACtE,GAAG,CAAC,CAAC,CAAC,GAAG,iBAAiB,CAAC,CAAC,CAAC,CAAC;QAChC,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * HTML normalisation for Liquid output contracts.
3
+ *
4
+ * Liquid renders produce HTML with three kinds of volatile content
5
+ * that would otherwise diff-spam every capture against live:
6
+ *
7
+ * 1. Generated identifiers — UUIDs in data-* attributes,
8
+ * auto-incremented `id="..."` values, cursor-y opaque tokens.
9
+ * 2. Asset URLs with cache-busters — `?v=1234567890` on every
10
+ * CDN-served image / CSS.
11
+ * 3. Whitespace + attribute order — both sides may render the
12
+ * same logical HTML with different formatting.
13
+ *
14
+ * The normaliser collapses these to stable tokens, preserving the
15
+ * structural skeleton that conformance actually cares about. Two
16
+ * outputs that differ ONLY in these volatile dimensions become
17
+ * byte-equal after normalisation; genuine structural drift
18
+ * (a missing element, a renamed attribute) survives.
19
+ *
20
+ * Implementation: regex passes on the raw HTML string. We could
21
+ * use a real HTML parser (parse5 / node-html-parser) for attribute
22
+ * sorting, but that's a substantial dep for marginal gain — both
23
+ * the offline mock and live Shopify emit attributes in source-
24
+ * declaration order, so post-render orders match anyway. Switch
25
+ * to a parser later if a real ordering mismatch shows up.
26
+ *
27
+ * Keep the patterns in `normaliseHtmlString` aligned with whatever
28
+ * volatile values surface during live captures — drift caught here
29
+ * (false positive) means add a rule; drift missed (false negative)
30
+ * means tighten an existing one.
31
+ */
32
+ /**
33
+ * Single entry point. Apply each normalisation in a deterministic
34
+ * order — later passes assume earlier ones already ran.
35
+ */
36
+ export declare function normaliseHtmlString(html: string): string;
37
+ //# sourceMappingURL=normalizeHtml.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalizeHtml.d.ts","sourceRoot":"","sources":["../../src/contracts/normalizeHtml.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAgDH;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAexD"}
@@ -0,0 +1,89 @@
1
+ /**
2
+ * HTML normalisation for Liquid output contracts.
3
+ *
4
+ * Liquid renders produce HTML with three kinds of volatile content
5
+ * that would otherwise diff-spam every capture against live:
6
+ *
7
+ * 1. Generated identifiers — UUIDs in data-* attributes,
8
+ * auto-incremented `id="..."` values, cursor-y opaque tokens.
9
+ * 2. Asset URLs with cache-busters — `?v=1234567890` on every
10
+ * CDN-served image / CSS.
11
+ * 3. Whitespace + attribute order — both sides may render the
12
+ * same logical HTML with different formatting.
13
+ *
14
+ * The normaliser collapses these to stable tokens, preserving the
15
+ * structural skeleton that conformance actually cares about. Two
16
+ * outputs that differ ONLY in these volatile dimensions become
17
+ * byte-equal after normalisation; genuine structural drift
18
+ * (a missing element, a renamed attribute) survives.
19
+ *
20
+ * Implementation: regex passes on the raw HTML string. We could
21
+ * use a real HTML parser (parse5 / node-html-parser) for attribute
22
+ * sorting, but that's a substantial dep for marginal gain — both
23
+ * the offline mock and live Shopify emit attributes in source-
24
+ * declaration order, so post-render orders match anyway. Switch
25
+ * to a parser later if a real ordering mismatch shows up.
26
+ *
27
+ * Keep the patterns in `normaliseHtmlString` aligned with whatever
28
+ * volatile values surface during live captures — drift caught here
29
+ * (false positive) means add a rule; drift missed (false negative)
30
+ * means tighten an existing one.
31
+ */
32
+ /** UUID v4-ish — case-insensitive, hyphenated. Used for funnel / variant / extension IDs. */
33
+ const UUID_RE = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi;
34
+ /** Long base-N numeric IDs — Shopify uses these everywhere (product IDs, variant IDs). */
35
+ const NUMERIC_ID_8PLUS_RE = /\b\d{8,}\b/g;
36
+ /** Asset cache-buster `?v=12345` (with surrounding URL chars preserved). */
37
+ const CACHE_BUSTER_RE = /\?v=\d+/g;
38
+ /** Token used by Shopify analytics — `__st.*` payloads in inline script tags. */
39
+ const ANALYTICS_TOKEN_RE = /"(s|t|a|p|c|e)t":\s*\d+/g;
40
+ /** Long base64-ish blob (40+ chars) — cursors, signed URLs, etc. */
41
+ const LONG_BASE64_RE = /\b[A-Za-z0-9+/]{40,}={0,2}\b/g;
42
+ /**
43
+ * Replace the value of any `data-…="…"` attribute whose key looks
44
+ * "id-like" with a token. Catches `data-funnel-id="abc-..."`,
45
+ * `data-product-id="123"`, etc. without us having to enumerate every
46
+ * possible attribute name. Conservative — only matches keys ending
47
+ * in `-id` to avoid stomping on legitimate data attributes like
48
+ * `data-trigger-type`.
49
+ */
50
+ const DATA_ID_ATTR_RE = /(data-[\w-]*-id)="[^"]+"/gi;
51
+ /** `id="..."` value — any HTML id attribute. */
52
+ const ID_ATTR_RE = /\bid="([^"]+)"/g;
53
+ /**
54
+ * Collapse runs of whitespace BETWEEN HTML tags. Preserves
55
+ * whitespace WITHIN text content (which can be semantically
56
+ * meaningful, e.g. between words in a sentence).
57
+ */
58
+ function collapseInterTagWhitespace(html) {
59
+ return html.replace(/>\s+</g, '><');
60
+ }
61
+ /**
62
+ * Trim leading/trailing whitespace and collapse all-whitespace
63
+ * runs to single spaces inside text nodes that span attribute
64
+ * boundaries. Safe: applied after the inter-tag pass.
65
+ */
66
+ function collapseTextWhitespace(html) {
67
+ return html.replace(/\s{2,}/g, ' ').trim();
68
+ }
69
+ /**
70
+ * Single entry point. Apply each normalisation in a deterministic
71
+ * order — later passes assume earlier ones already ran.
72
+ */
73
+ export function normaliseHtmlString(html) {
74
+ let out = html;
75
+ // Order matters — narrower patterns first, so they consume the
76
+ // input before generic ones (`NUMERIC_ID_8PLUS_RE`) eat the
77
+ // numeric tail of a cache-buster or UUID.
78
+ out = out.replace(CACHE_BUSTER_RE, '?v=<CACHE_BUSTER>');
79
+ out = out.replace(UUID_RE, '<UUID>');
80
+ out = out.replace(DATA_ID_ATTR_RE, '$1="<ID>"');
81
+ out = out.replace(ID_ATTR_RE, 'id="<ID>"');
82
+ out = out.replace(LONG_BASE64_RE, '<BASE64>');
83
+ out = out.replace(ANALYTICS_TOKEN_RE, (_m, k) => `"${k}t":<TS>`);
84
+ out = out.replace(NUMERIC_ID_8PLUS_RE, '<NUMERIC_ID>');
85
+ out = collapseInterTagWhitespace(out);
86
+ out = collapseTextWhitespace(out);
87
+ return out;
88
+ }
89
+ //# sourceMappingURL=normalizeHtml.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalizeHtml.js","sourceRoot":"","sources":["../../src/contracts/normalizeHtml.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,6FAA6F;AAC7F,MAAM,OAAO,GAAG,gEAAgE,CAAC;AAEjF,0FAA0F;AAC1F,MAAM,mBAAmB,GAAG,aAAa,CAAC;AAE1C,4EAA4E;AAC5E,MAAM,eAAe,GAAG,UAAU,CAAC;AAEnC,iFAAiF;AACjF,MAAM,kBAAkB,GAAG,0BAA0B,CAAC;AAEtD,oEAAoE;AACpE,MAAM,cAAc,GAAG,+BAA+B,CAAC;AAEvD;;;;;;;GAOG;AACH,MAAM,eAAe,GAAG,4BAA4B,CAAC;AAErD,gDAAgD;AAChD,MAAM,UAAU,GAAG,iBAAiB,CAAC;AAErC;;;;GAIG;AACH,SAAS,0BAA0B,CAAC,IAAY;IAC9C,OAAO,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;AACtC,CAAC;AAED;;;;GAIG;AACH,SAAS,sBAAsB,CAAC,IAAY;IAC1C,OAAO,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;AAC7C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,IAAI,GAAG,GAAG,IAAI,CAAC;IACf,+DAA+D;IAC/D,4DAA4D;IAC5D,0CAA0C;IAC1C,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,eAAe,EAAE,mBAAmB,CAAC,CAAC;IACxD,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IACrC,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,eAAe,EAAE,WAAW,CAAC,CAAC;IAChD,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;IAC3C,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,cAAc,EAAE,UAAU,CAAC,CAAC;IAC9C,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,kBAAkB,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACjE,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,mBAAmB,EAAE,cAAc,CAAC,CAAC;IACvD,GAAG,GAAG,0BAA0B,CAAC,GAAG,CAAC,CAAC;IACtC,GAAG,GAAG,sBAAsB,CAAC,GAAG,CAAC,CAAC;IAClC,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Self-signed TLS cert pair used by the edge proxy in offline tests.
3
+ *
4
+ * TWO certs:
5
+ * - **CA cert** (`TEST_CA_CERT_PEM`): the trust anchor. Installed
6
+ * into the container's system CA store + Chrome's NSS DB at
7
+ * boot. Long-lived (10 years), self-signed, CA:TRUE.
8
+ * - **Server cert** (`TEST_SERVER_CERT_PEM` + `TEST_SERVER_KEY_PEM`):
9
+ * what the edge proxy presents to clients. Signed BY the CA
10
+ * above (so Chrome's chain validation finds the trusted root).
11
+ * Has Subject Alternative Names for every Shopify hostname we
12
+ * route: localhost, *.myshopify.com, *.shopify.com, etc.
13
+ *
14
+ * Why two certs instead of one self-signed-with-SANs:
15
+ * Modern Chrome (≥130 or so) rejects single self-signed certs
16
+ * that are also used as their own CA with ERR_CERT_INVALID, even
17
+ * when the cert is trust-anchored in NSS. Proper CA → server
18
+ * hierarchy mirrors how real publicly-trusted certs work (Let's
19
+ * Encrypt root → intermediate → leaf) and passes every modern
20
+ * verifier we've tested.
21
+ *
22
+ * Inlined as TS constants (rather than separate .pem files) so the
23
+ * compiled package ships everything in dist/ — no postbuild copy.
24
+ *
25
+ * This is a TEST-ONLY cert pair. The CA's private key is committed
26
+ * to the repo; anyone who checks out this repo can issue certs
27
+ * signed by it. That's fine in offline test mode where Chromium is
28
+ * launched with the CA explicitly added to its trust store — no
29
+ * production system should ever encounter this CA. If you ever see
30
+ * a real service presenting a cert with issuer
31
+ * "essential-apps offline test CA", that's a serious
32
+ * misconfiguration.
33
+ *
34
+ * To regenerate (e.g. expired, new SAN needed):
35
+ * cd src/edge
36
+ * openssl req -x509 -newkey rsa:2048 -nodes -keyout ca.key -out ca.crt -days 3650 -config ca.cnf
37
+ * openssl req -newkey rsa:2048 -nodes -keyout server.key -out server.csr -config server.cnf
38
+ * openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 3650 -extfile server.cnf -extensions v3_server
39
+ * # then paste ca.crt, server.crt, server.key into the constants below
40
+ */
41
+ export declare const TEST_CA_CERT_PEM = "-----BEGIN CERTIFICATE-----\nMIIDkDCCAnigAwIBAgIUIKGQzgV0DVnoczi/+0ku6XxtXpUwDQYJKoZIhvcNAQEL\nBQAwYDELMAkGA1UEBhMCVVMxJDAiBgNVBAoMG2Vzc2VudGlhbC1hcHBzIHNob3Bp\nZnktdGVzdDErMCkGA1UEAwwiZXNzZW50aWFsLWFwcHMgb2ZmbGluZSBFMkUgdGVz\ndCBDQTAeFw0yNjA1MTExODIwNTBaFw0zNjA1MDgxODIwNTBaMGAxCzAJBgNVBAYT\nAlVTMSQwIgYDVQQKDBtlc3NlbnRpYWwtYXBwcyBzaG9waWZ5LXRlc3QxKzApBgNV\nBAMMImVzc2VudGlhbC1hcHBzIG9mZmxpbmUgRTJFIHRlc3QgQ0EwggEiMA0GCSqG\nSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCVqEmQ3OULDil0xGp+s5Be9bqo/irOT4/x\nIksWc+ul1oREVJ1F+EXINn2YUZZbrQ8i1tJg3xz/6u2EiGXozTuDsAX4VFa9Ew60\nfvp+MsxKvIDZgQ54xizvlGEbFFIUcTMr0czzyRzhoPsvx0vZxMv+mnlU/nKSuRTv\n0+2rlVi19MjOmlmiF7imaJcZeTkzjAMy5tKgIu1QLk+dTNe7wXdr45gWLK959POy\nJT7juKbpR2kgNhh91yQhvPnUTWibrln2pk/boTBM6s17BX28DwjRJM/UPAT9f+pC\nh3p4Uan9NBHdPaOM9Wvj8cZqXWEz3ErYJmf9xUxYURStakozkFFrAgMBAAGjQjBA\nMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBQvul3h\nwRTsFPS1GZbOxAPssSEDfzANBgkqhkiG9w0BAQsFAAOCAQEAMDGe/lf/7x66nchI\n3KQcuX3OQ+hPeODw9F+7zY4KUf4+z1Me89M07kFWVR3cBplGIGUzQm6Sj3BCrShC\nJgskN/jkGT7s5FHssHWRQ5m6uMTbDMctyVZYoW71Ip4pQloyh88fyWRejrJyVRhO\nmAqB7iPEPQz9iSxaAeUJA/RU/zyBukUrP1ppU0qe6SFOTM+mfCMFZrITEJ2pbc9g\nX9SbEk44qgQlOqfrj9FazxJp9Pjj19WLVXsY7JrNglpgzA8jwPWJSIPIU4gF0eTZ\n2B5ORtmHnN2uBnxe4sRf8eOHZy+7SZIR+6y1q/zmwbubVV7rF76ukw/I/kg+gAm+\neRz9lQ==\n-----END CERTIFICATE-----";
42
+ export declare const TEST_SERVER_CERT_PEM = "-----BEGIN CERTIFICATE-----\nMIIEYjCCA0qgAwIBAgIURPMxG/s+J/RbB610ES+g6+4uz1YwDQYJKoZIhvcNAQEL\nBQAwYDELMAkGA1UEBhMCVVMxJDAiBgNVBAoMG2Vzc2VudGlhbC1hcHBzIHNob3Bp\nZnktdGVzdDErMCkGA1UEAwwiZXNzZW50aWFsLWFwcHMgb2ZmbGluZSBFMkUgdGVz\ndCBDQTAeFw0yNjA1MTExODIwNTZaFw0zNjA1MDgxODIwNTZaMFUxCzAJBgNVBAYT\nAlVTMSQwIgYDVQQKDBtlc3NlbnRpYWwtYXBwcyBzaG9waWZ5LXRlc3QxIDAeBgNV\nBAMMF3Rlc3Qtc2hvcC5teXNob3BpZnkuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOC\nAQ8AMIIBCgKCAQEAqKKCIJ+3Z311vVrAzS9O8BFoDuFcS71UhXFOFnGv+32sWkv5\n15FiqsEUA9Y38TWMmATselKFmseqgZo1EgvNe6rFrnjyyJgEeYrLv40bz3JEp/Hs\nGrgID4XaGJFx/tDsMLBtWLJykbkowRcJnQK0NoeYNC9E1JKfmU9WnT0eqOzc9ryn\niXj9pptDfd4BndYYSW4lvooshdyrVNcLnm67EXdyNukFk37ru9vSu12Oo+QynnZ7\n6Ijghpxjim5HHG3V5VF9fGSTcEKWqgw1l9FRESs6qFtG3GE5CSbg1J15uuUHBCof\nqIxUJwk6foHqD1V+bMUbxZt1Aj05pZ2NJVAjpQIDAQABo4IBHTCCARkwDAYDVR0T\nAQH/BAIwADAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwgaMG\nA1UdEQSBmzCBmIIJbG9jYWxob3N0gg8qLm15c2hvcGlmeS5jb22CDW15c2hvcGlm\neS5jb22CDSouc2hvcGlmeS5jb22CC3Nob3BpZnkuY29tghEqLnNob3BpZnlhcHBz\nLmNvbYISKi5zaG9waWZ5Y2xvdWQuY29tghAqLnNob3BpZnljZG4uY29thwR/AAAB\nhxAAAAAAAAAAAAAAAAAAAAABMB0GA1UdDgQWBBTU9DyGKwRHIMACdy1hFqmWj2CW\n6DAfBgNVHSMEGDAWgBQvul3hwRTsFPS1GZbOxAPssSEDfzANBgkqhkiG9w0BAQsF\nAAOCAQEAUJ5MwZ6iRLuOImmWMiiNTvjxYq7e8+/0zYkqXreQpQEah0muzHnoolhp\nA4aolovZGdAJnAQXG38O8mRwIWxtJqcPPXEITz0O2H2Bdli4r+ikcIkUBNlvsuH2\n1QVwDvyWhgp2gW6wyYzQc5GSbZ9wjca0bUZx+CQsuyRMjkerEVMlnTlxyCVIRIuE\n6WxqZeoHiUskowUc7hIYzUIjadOzddFfdePW1nE69Se4aOM4lbvJ5HNwiNim+3nE\n1RTDsQCMcNcrMdY4g30cv9zm0+rjRB+Rmd3K4EBVaMrnbrMq9guXnZsyE8hkbKn3\nVCXk3ZMrF4yyr/5s7FLeAsE8b9PYtw==\n-----END CERTIFICATE-----";
43
+ export declare const TEST_SERVER_KEY_PEM = "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCoooIgn7dnfXW9\nWsDNL07wEWgO4VxLvVSFcU4Wca/7faxaS/nXkWKqwRQD1jfxNYyYBOx6UoWax6qB\nmjUSC817qsWuePLImAR5isu/jRvPckSn8ewauAgPhdoYkXH+0OwwsG1YsnKRuSjB\nFwmdArQ2h5g0L0TUkp+ZT1adPR6o7Nz2vKeJeP2mm0N93gGd1hhJbiW+iiyF3KtU\n1wuebrsRd3I26QWTfuu729K7XY6j5DKednvoiOCGnGOKbkccbdXlUX18ZJNwQpaq\nDDWX0VERKzqoW0bcYTkJJuDUnXm65QcEKh+ojFQnCTp+geoPVX5sxRvFm3UCPTml\nnY0lUCOlAgMBAAECggEAFzC17W+ZZKl7qg0Ta4QgemIiab1zGFVSjMFOqEZ9GXwo\nWgiNtKfhJjNEIdzxN4ISMgunS5ESn3zqxUTkHHW0DdgntD0cwhopr183csGge/Au\nYdwiiHAbZ6sUGYHS5+RqPq3cc7CikcihQqB86XMoPkF6XF7Nu9/oA8jF0/zGPRuQ\nBiR722XwemloFMfNybnhYzLx5vr0EFs5R8UtDCZcRveWAbP5ggbkEfiz73zGA0gr\nVGq0rQaovLntjc8Gtb93Q57pM7NOahgTQy6DXK42Of1gqkWTvFERXHjS5XH9NdzM\n/ahXz6G/8ms2w6Xaj8uhZHxwEW6hF8S1D8q2Llf/AQKBgQDtXY0wFtI4dWE3MhHO\npQ6v21gMFP7bmAkN3DTbcAJYsDbZiGlzaH+FF+S1Uz8gbwCKlj9FYR4r0oU0ccfF\nh023BQaxQXNCvfYunApxw1XG2NEkXvGWzD9DX/Xhj4z0WWve9Zh0tRVGMEkETjqT\nxzOhGfxl1g2/CixrzTvKpD8+JQKBgQC136TOwQMs3EyRe1YwSWM0wlN7BcPehOPx\nsTQNkyZsxE3u7NkKa6LXK7gdgC+oelWV6+UXbuEs9ZLWT0yh22T5ViO3sLWiEL7P\nyE6y2j8nTucK3RynN8cEFuolBs3V+/B2K/yy3M9y2oahKmyDYnjy7AxI3UFQm9N7\nhxCX4YKXgQKBgQDr2ulPv11jfD78+WN4UcomM21pk/MpgAh/HS/oW4P5XB8kR8eA\nRXVwai13fyBaufFvw5ta9QVlxelWEzjNrYQrN3NO7hn5V4gnCCXYpJ+21fn6idzE\nWm8CI3fOiTUmFzR4dtDmJojdFV14ScMq0+UZTxjcl7VQ/mrlMykWUd4FgQKBgBfU\nfNimS482MkYhnfJnuzrvd1a4M6jVSrShXkulCzTXJ8r1d5646bY9wTsET7pIhSxG\no1bFrXVhm+K+szDF+V3+HmH0Imhgv0+kVEN0+y9gVD+FJzr1wPrVMcq2MIQoJaKm\nMs8QxZGr9lXppBw269gQe6+UZfl04WnfEZqE7sKBAoGAQrP3o4fqVUb200Bn0dO+\nF8fzgHT1lZAoz5NAyo4VOW4KZ6EgDhuxM2kG3WelGhYb0CwYzWS3WzAw/jxwSHMx\nQhJtvjhtZzYM3P4RDHjSCvQm0X8xv6OMMOF3I0RmdZQFHLo7xIAkehPIpbmBaxSf\n2ojRh6FIbwe+526P8Qcpstk=\n-----END PRIVATE KEY-----";
44
+ //# sourceMappingURL=cert.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cert.d.ts","sourceRoot":"","sources":["../../src/edge/cert.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH,eAAO,MAAM,gBAAgB,2yCAqBH,CAAC;AAC3B,eAAO,MAAM,oBAAoB,2kDAyBP,CAAC;AAC3B,eAAO,MAAM,mBAAmB,usDA2BN,CAAC"}
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Self-signed TLS cert pair used by the edge proxy in offline tests.
3
+ *
4
+ * TWO certs:
5
+ * - **CA cert** (`TEST_CA_CERT_PEM`): the trust anchor. Installed
6
+ * into the container's system CA store + Chrome's NSS DB at
7
+ * boot. Long-lived (10 years), self-signed, CA:TRUE.
8
+ * - **Server cert** (`TEST_SERVER_CERT_PEM` + `TEST_SERVER_KEY_PEM`):
9
+ * what the edge proxy presents to clients. Signed BY the CA
10
+ * above (so Chrome's chain validation finds the trusted root).
11
+ * Has Subject Alternative Names for every Shopify hostname we
12
+ * route: localhost, *.myshopify.com, *.shopify.com, etc.
13
+ *
14
+ * Why two certs instead of one self-signed-with-SANs:
15
+ * Modern Chrome (≥130 or so) rejects single self-signed certs
16
+ * that are also used as their own CA with ERR_CERT_INVALID, even
17
+ * when the cert is trust-anchored in NSS. Proper CA → server
18
+ * hierarchy mirrors how real publicly-trusted certs work (Let's
19
+ * Encrypt root → intermediate → leaf) and passes every modern
20
+ * verifier we've tested.
21
+ *
22
+ * Inlined as TS constants (rather than separate .pem files) so the
23
+ * compiled package ships everything in dist/ — no postbuild copy.
24
+ *
25
+ * This is a TEST-ONLY cert pair. The CA's private key is committed
26
+ * to the repo; anyone who checks out this repo can issue certs
27
+ * signed by it. That's fine in offline test mode where Chromium is
28
+ * launched with the CA explicitly added to its trust store — no
29
+ * production system should ever encounter this CA. If you ever see
30
+ * a real service presenting a cert with issuer
31
+ * "essential-apps offline test CA", that's a serious
32
+ * misconfiguration.
33
+ *
34
+ * To regenerate (e.g. expired, new SAN needed):
35
+ * cd src/edge
36
+ * openssl req -x509 -newkey rsa:2048 -nodes -keyout ca.key -out ca.crt -days 3650 -config ca.cnf
37
+ * openssl req -newkey rsa:2048 -nodes -keyout server.key -out server.csr -config server.cnf
38
+ * openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 3650 -extfile server.cnf -extensions v3_server
39
+ * # then paste ca.crt, server.crt, server.key into the constants below
40
+ */
41
+ export const TEST_CA_CERT_PEM = `-----BEGIN CERTIFICATE-----
42
+ MIIDkDCCAnigAwIBAgIUIKGQzgV0DVnoczi/+0ku6XxtXpUwDQYJKoZIhvcNAQEL
43
+ BQAwYDELMAkGA1UEBhMCVVMxJDAiBgNVBAoMG2Vzc2VudGlhbC1hcHBzIHNob3Bp
44
+ ZnktdGVzdDErMCkGA1UEAwwiZXNzZW50aWFsLWFwcHMgb2ZmbGluZSBFMkUgdGVz
45
+ dCBDQTAeFw0yNjA1MTExODIwNTBaFw0zNjA1MDgxODIwNTBaMGAxCzAJBgNVBAYT
46
+ AlVTMSQwIgYDVQQKDBtlc3NlbnRpYWwtYXBwcyBzaG9waWZ5LXRlc3QxKzApBgNV
47
+ BAMMImVzc2VudGlhbC1hcHBzIG9mZmxpbmUgRTJFIHRlc3QgQ0EwggEiMA0GCSqG
48
+ SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCVqEmQ3OULDil0xGp+s5Be9bqo/irOT4/x
49
+ IksWc+ul1oREVJ1F+EXINn2YUZZbrQ8i1tJg3xz/6u2EiGXozTuDsAX4VFa9Ew60
50
+ fvp+MsxKvIDZgQ54xizvlGEbFFIUcTMr0czzyRzhoPsvx0vZxMv+mnlU/nKSuRTv
51
+ 0+2rlVi19MjOmlmiF7imaJcZeTkzjAMy5tKgIu1QLk+dTNe7wXdr45gWLK959POy
52
+ JT7juKbpR2kgNhh91yQhvPnUTWibrln2pk/boTBM6s17BX28DwjRJM/UPAT9f+pC
53
+ h3p4Uan9NBHdPaOM9Wvj8cZqXWEz3ErYJmf9xUxYURStakozkFFrAgMBAAGjQjBA
54
+ MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBQvul3h
55
+ wRTsFPS1GZbOxAPssSEDfzANBgkqhkiG9w0BAQsFAAOCAQEAMDGe/lf/7x66nchI
56
+ 3KQcuX3OQ+hPeODw9F+7zY4KUf4+z1Me89M07kFWVR3cBplGIGUzQm6Sj3BCrShC
57
+ JgskN/jkGT7s5FHssHWRQ5m6uMTbDMctyVZYoW71Ip4pQloyh88fyWRejrJyVRhO
58
+ mAqB7iPEPQz9iSxaAeUJA/RU/zyBukUrP1ppU0qe6SFOTM+mfCMFZrITEJ2pbc9g
59
+ X9SbEk44qgQlOqfrj9FazxJp9Pjj19WLVXsY7JrNglpgzA8jwPWJSIPIU4gF0eTZ
60
+ 2B5ORtmHnN2uBnxe4sRf8eOHZy+7SZIR+6y1q/zmwbubVV7rF76ukw/I/kg+gAm+
61
+ eRz9lQ==
62
+ -----END CERTIFICATE-----`;
63
+ export const TEST_SERVER_CERT_PEM = `-----BEGIN CERTIFICATE-----
64
+ MIIEYjCCA0qgAwIBAgIURPMxG/s+J/RbB610ES+g6+4uz1YwDQYJKoZIhvcNAQEL
65
+ BQAwYDELMAkGA1UEBhMCVVMxJDAiBgNVBAoMG2Vzc2VudGlhbC1hcHBzIHNob3Bp
66
+ ZnktdGVzdDErMCkGA1UEAwwiZXNzZW50aWFsLWFwcHMgb2ZmbGluZSBFMkUgdGVz
67
+ dCBDQTAeFw0yNjA1MTExODIwNTZaFw0zNjA1MDgxODIwNTZaMFUxCzAJBgNVBAYT
68
+ AlVTMSQwIgYDVQQKDBtlc3NlbnRpYWwtYXBwcyBzaG9waWZ5LXRlc3QxIDAeBgNV
69
+ BAMMF3Rlc3Qtc2hvcC5teXNob3BpZnkuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOC
70
+ AQ8AMIIBCgKCAQEAqKKCIJ+3Z311vVrAzS9O8BFoDuFcS71UhXFOFnGv+32sWkv5
71
+ 15FiqsEUA9Y38TWMmATselKFmseqgZo1EgvNe6rFrnjyyJgEeYrLv40bz3JEp/Hs
72
+ GrgID4XaGJFx/tDsMLBtWLJykbkowRcJnQK0NoeYNC9E1JKfmU9WnT0eqOzc9ryn
73
+ iXj9pptDfd4BndYYSW4lvooshdyrVNcLnm67EXdyNukFk37ru9vSu12Oo+QynnZ7
74
+ 6Ijghpxjim5HHG3V5VF9fGSTcEKWqgw1l9FRESs6qFtG3GE5CSbg1J15uuUHBCof
75
+ qIxUJwk6foHqD1V+bMUbxZt1Aj05pZ2NJVAjpQIDAQABo4IBHTCCARkwDAYDVR0T
76
+ AQH/BAIwADAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwgaMG
77
+ A1UdEQSBmzCBmIIJbG9jYWxob3N0gg8qLm15c2hvcGlmeS5jb22CDW15c2hvcGlm
78
+ eS5jb22CDSouc2hvcGlmeS5jb22CC3Nob3BpZnkuY29tghEqLnNob3BpZnlhcHBz
79
+ LmNvbYISKi5zaG9waWZ5Y2xvdWQuY29tghAqLnNob3BpZnljZG4uY29thwR/AAAB
80
+ hxAAAAAAAAAAAAAAAAAAAAABMB0GA1UdDgQWBBTU9DyGKwRHIMACdy1hFqmWj2CW
81
+ 6DAfBgNVHSMEGDAWgBQvul3hwRTsFPS1GZbOxAPssSEDfzANBgkqhkiG9w0BAQsF
82
+ AAOCAQEAUJ5MwZ6iRLuOImmWMiiNTvjxYq7e8+/0zYkqXreQpQEah0muzHnoolhp
83
+ A4aolovZGdAJnAQXG38O8mRwIWxtJqcPPXEITz0O2H2Bdli4r+ikcIkUBNlvsuH2
84
+ 1QVwDvyWhgp2gW6wyYzQc5GSbZ9wjca0bUZx+CQsuyRMjkerEVMlnTlxyCVIRIuE
85
+ 6WxqZeoHiUskowUc7hIYzUIjadOzddFfdePW1nE69Se4aOM4lbvJ5HNwiNim+3nE
86
+ 1RTDsQCMcNcrMdY4g30cv9zm0+rjRB+Rmd3K4EBVaMrnbrMq9guXnZsyE8hkbKn3
87
+ VCXk3ZMrF4yyr/5s7FLeAsE8b9PYtw==
88
+ -----END CERTIFICATE-----`;
89
+ export const TEST_SERVER_KEY_PEM = `-----BEGIN PRIVATE KEY-----
90
+ MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCoooIgn7dnfXW9
91
+ WsDNL07wEWgO4VxLvVSFcU4Wca/7faxaS/nXkWKqwRQD1jfxNYyYBOx6UoWax6qB
92
+ mjUSC817qsWuePLImAR5isu/jRvPckSn8ewauAgPhdoYkXH+0OwwsG1YsnKRuSjB
93
+ FwmdArQ2h5g0L0TUkp+ZT1adPR6o7Nz2vKeJeP2mm0N93gGd1hhJbiW+iiyF3KtU
94
+ 1wuebrsRd3I26QWTfuu729K7XY6j5DKednvoiOCGnGOKbkccbdXlUX18ZJNwQpaq
95
+ DDWX0VERKzqoW0bcYTkJJuDUnXm65QcEKh+ojFQnCTp+geoPVX5sxRvFm3UCPTml
96
+ nY0lUCOlAgMBAAECggEAFzC17W+ZZKl7qg0Ta4QgemIiab1zGFVSjMFOqEZ9GXwo
97
+ WgiNtKfhJjNEIdzxN4ISMgunS5ESn3zqxUTkHHW0DdgntD0cwhopr183csGge/Au
98
+ YdwiiHAbZ6sUGYHS5+RqPq3cc7CikcihQqB86XMoPkF6XF7Nu9/oA8jF0/zGPRuQ
99
+ BiR722XwemloFMfNybnhYzLx5vr0EFs5R8UtDCZcRveWAbP5ggbkEfiz73zGA0gr
100
+ VGq0rQaovLntjc8Gtb93Q57pM7NOahgTQy6DXK42Of1gqkWTvFERXHjS5XH9NdzM
101
+ /ahXz6G/8ms2w6Xaj8uhZHxwEW6hF8S1D8q2Llf/AQKBgQDtXY0wFtI4dWE3MhHO
102
+ pQ6v21gMFP7bmAkN3DTbcAJYsDbZiGlzaH+FF+S1Uz8gbwCKlj9FYR4r0oU0ccfF
103
+ h023BQaxQXNCvfYunApxw1XG2NEkXvGWzD9DX/Xhj4z0WWve9Zh0tRVGMEkETjqT
104
+ xzOhGfxl1g2/CixrzTvKpD8+JQKBgQC136TOwQMs3EyRe1YwSWM0wlN7BcPehOPx
105
+ sTQNkyZsxE3u7NkKa6LXK7gdgC+oelWV6+UXbuEs9ZLWT0yh22T5ViO3sLWiEL7P
106
+ yE6y2j8nTucK3RynN8cEFuolBs3V+/B2K/yy3M9y2oahKmyDYnjy7AxI3UFQm9N7
107
+ hxCX4YKXgQKBgQDr2ulPv11jfD78+WN4UcomM21pk/MpgAh/HS/oW4P5XB8kR8eA
108
+ RXVwai13fyBaufFvw5ta9QVlxelWEzjNrYQrN3NO7hn5V4gnCCXYpJ+21fn6idzE
109
+ Wm8CI3fOiTUmFzR4dtDmJojdFV14ScMq0+UZTxjcl7VQ/mrlMykWUd4FgQKBgBfU
110
+ fNimS482MkYhnfJnuzrvd1a4M6jVSrShXkulCzTXJ8r1d5646bY9wTsET7pIhSxG
111
+ o1bFrXVhm+K+szDF+V3+HmH0Imhgv0+kVEN0+y9gVD+FJzr1wPrVMcq2MIQoJaKm
112
+ Ms8QxZGr9lXppBw269gQe6+UZfl04WnfEZqE7sKBAoGAQrP3o4fqVUb200Bn0dO+
113
+ F8fzgHT1lZAoz5NAyo4VOW4KZ6EgDhuxM2kG3WelGhYb0CwYzWS3WzAw/jxwSHMx
114
+ QhJtvjhtZzYM3P4RDHjSCvQm0X8xv6OMMOF3I0RmdZQFHLo7xIAkehPIpbmBaxSf
115
+ 2ojRh6FIbwe+526P8Qcpstk=
116
+ -----END PRIVATE KEY-----`;
117
+ //# sourceMappingURL=cert.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cert.js","sourceRoot":"","sources":["../../src/edge/cert.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH,MAAM,CAAC,MAAM,gBAAgB,GAAG;;;;;;;;;;;;;;;;;;;;;0BAqBN,CAAC;AAC3B,MAAM,CAAC,MAAM,oBAAoB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;0BAyBV,CAAC;AAC3B,MAAM,CAAC,MAAM,mBAAmB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;0BA2BT,CAAC"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Internal mock URLs the edge dispatches to. Each is an HTTP origin
3
+ * like `http://127.0.0.1:34567`. The edge speaks HTTPS to the
4
+ * browser, HTTP to the internal mocks (they're loopback-only;
5
+ * TLS between in-process services is pointless ceremony).
6
+ */
7
+ export interface EdgeBackends {
8
+ /** Mock storefront (Liquid renderer, cart, /cart/add.js, etc.). */
9
+ storefront: string;
10
+ /** Mock Admin GraphQL API. */
11
+ adminApi: string;
12
+ /** Mock Storefront GraphQL API. */
13
+ storefrontApi: string;
14
+ /** Mock admin shell (the admin.shopify.com iframe host). */
15
+ adminShell: string;
16
+ }
17
+ export interface EdgeOptions {
18
+ /** Backend mock URLs. */
19
+ backends: EdgeBackends;
20
+ /**
21
+ * Hostname of the test shop, e.g. `test-shop.myshopify.com`.
22
+ * Used to recognize storefront requests vs cdn/admin.
23
+ */
24
+ shopDomain: string;
25
+ /** TCP port to listen on. 0 → OS-assigned. */
26
+ port?: number;
27
+ /** Bind hostname (default `127.0.0.1`). */
28
+ hostname?: string;
29
+ }
30
+ export interface StartedEdge {
31
+ /** The actual port we bound to (resolved when port=0). */
32
+ port: number;
33
+ /** `https://127.0.0.1:<port>` — for diagnostic logging only. */
34
+ baseUrl: string;
35
+ /** Stop the proxy. */
36
+ close: () => Promise<void>;
37
+ }
38
+ /**
39
+ * Boot the edge HTTPS proxy. Returns a handle with the bound port
40
+ * and a `close()` to tear it down.
41
+ */
42
+ export declare function startEdgeProxy(opts: EdgeOptions): Promise<StartedEdge>;
43
+ //# sourceMappingURL=edgeProxy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"edgeProxy.d.ts","sourceRoot":"","sources":["../../src/edge/edgeProxy.ts"],"names":[],"mappings":"AA4CA;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,mEAAmE;IACnE,UAAU,EAAE,MAAM,CAAC;IACnB,8BAA8B;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,mCAAmC;IACnC,aAAa,EAAE,MAAM,CAAC;IACtB,4DAA4D;IAC5D,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,yBAAyB;IACzB,QAAQ,EAAE,YAAY,CAAC;IACvB;;;OAGG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB,8CAA8C;IAC9C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,2CAA2C;IAC3C,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,0DAA0D;IAC1D,IAAI,EAAE,MAAM,CAAC;IACb,gEAAgE;IAChE,OAAO,EAAE,MAAM,CAAC;IAChB,sBAAsB;IACtB,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B;AAoKD;;;GAGG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CA2I5E"}