@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,14 @@
1
+ [req]
2
+ distinguished_name = req_distinguished_name
3
+ prompt = no
4
+ x509_extensions = v3_ca
5
+
6
+ [req_distinguished_name]
7
+ C = US
8
+ O = essential-apps shopify-test
9
+ CN = essential-apps offline test CA
10
+
11
+ [v3_ca]
12
+ basicConstraints = critical, CA:TRUE
13
+ keyUsage = critical, keyCertSign, cRLSign, digitalSignature
14
+ subjectKeyIdentifier = hash
@@ -0,0 +1,22 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIDkDCCAnigAwIBAgIUIKGQzgV0DVnoczi/+0ku6XxtXpUwDQYJKoZIhvcNAQEL
3
+ BQAwYDELMAkGA1UEBhMCVVMxJDAiBgNVBAoMG2Vzc2VudGlhbC1hcHBzIHNob3Bp
4
+ ZnktdGVzdDErMCkGA1UEAwwiZXNzZW50aWFsLWFwcHMgb2ZmbGluZSBFMkUgdGVz
5
+ dCBDQTAeFw0yNjA1MTExODIwNTBaFw0zNjA1MDgxODIwNTBaMGAxCzAJBgNVBAYT
6
+ AlVTMSQwIgYDVQQKDBtlc3NlbnRpYWwtYXBwcyBzaG9waWZ5LXRlc3QxKzApBgNV
7
+ BAMMImVzc2VudGlhbC1hcHBzIG9mZmxpbmUgRTJFIHRlc3QgQ0EwggEiMA0GCSqG
8
+ SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCVqEmQ3OULDil0xGp+s5Be9bqo/irOT4/x
9
+ IksWc+ul1oREVJ1F+EXINn2YUZZbrQ8i1tJg3xz/6u2EiGXozTuDsAX4VFa9Ew60
10
+ fvp+MsxKvIDZgQ54xizvlGEbFFIUcTMr0czzyRzhoPsvx0vZxMv+mnlU/nKSuRTv
11
+ 0+2rlVi19MjOmlmiF7imaJcZeTkzjAMy5tKgIu1QLk+dTNe7wXdr45gWLK959POy
12
+ JT7juKbpR2kgNhh91yQhvPnUTWibrln2pk/boTBM6s17BX28DwjRJM/UPAT9f+pC
13
+ h3p4Uan9NBHdPaOM9Wvj8cZqXWEz3ErYJmf9xUxYURStakozkFFrAgMBAAGjQjBA
14
+ MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBQvul3h
15
+ wRTsFPS1GZbOxAPssSEDfzANBgkqhkiG9w0BAQsFAAOCAQEAMDGe/lf/7x66nchI
16
+ 3KQcuX3OQ+hPeODw9F+7zY4KUf4+z1Me89M07kFWVR3cBplGIGUzQm6Sj3BCrShC
17
+ JgskN/jkGT7s5FHssHWRQ5m6uMTbDMctyVZYoW71Ip4pQloyh88fyWRejrJyVRhO
18
+ mAqB7iPEPQz9iSxaAeUJA/RU/zyBukUrP1ppU0qe6SFOTM+mfCMFZrITEJ2pbc9g
19
+ X9SbEk44qgQlOqfrj9FazxJp9Pjj19WLVXsY7JrNglpgzA8jwPWJSIPIU4gF0eTZ
20
+ 2B5ORtmHnN2uBnxe4sRf8eOHZy+7SZIR+6y1q/zmwbubVV7rF76ukw/I/kg+gAm+
21
+ eRz9lQ==
22
+ -----END CERTIFICATE-----
@@ -0,0 +1,28 @@
1
+ -----BEGIN PRIVATE KEY-----
2
+ MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCVqEmQ3OULDil0
3
+ xGp+s5Be9bqo/irOT4/xIksWc+ul1oREVJ1F+EXINn2YUZZbrQ8i1tJg3xz/6u2E
4
+ iGXozTuDsAX4VFa9Ew60fvp+MsxKvIDZgQ54xizvlGEbFFIUcTMr0czzyRzhoPsv
5
+ x0vZxMv+mnlU/nKSuRTv0+2rlVi19MjOmlmiF7imaJcZeTkzjAMy5tKgIu1QLk+d
6
+ TNe7wXdr45gWLK959POyJT7juKbpR2kgNhh91yQhvPnUTWibrln2pk/boTBM6s17
7
+ BX28DwjRJM/UPAT9f+pCh3p4Uan9NBHdPaOM9Wvj8cZqXWEz3ErYJmf9xUxYURSt
8
+ akozkFFrAgMBAAECggEAE3H697Wb6mR8AniwQFI4hKjNCL7YSleQI7a0UZsyQn6t
9
+ pOs8RtEherLfKCFsYJu13G2pMX5hQudVgTjvyjXlxmXiq1zaAFXaPUhFEoyrJV16
10
+ KHNaXAWej7/py3OdI/F+wpzPUoW5GPVVGI9+otxX3SGueNqu1whGriVikOl7eKNo
11
+ qUVgD4F+SBWFYjjNzLLnsLrPgP6jukYu77W7mhdu4jaKxJ6OzMgJwVWthjnjCB8W
12
+ 5DEFbgUKj/ssTWBKdTQdLAxudtKgoWNERk3uSSSMeqY9cmeqOpARb1n4zLGU6Bfk
13
+ JjVLzEzeJtB/GrsxEby4ZGRgEkhxtA7/mvoEb5ZOAQKBgQDQp8Ng0iY+gKG7ik/g
14
+ RIIUJNGiRNIGN/nuHp5hj04+Ph+zuytHD/eKhSwic5kCu5GQP42AV3Y1RwekKhDn
15
+ DFIpwNtRl5MXkwO3pO/QdlpdZyKftLMAW8qcNSwqqqJieisQPHwd4cgsNfSYCla6
16
+ 2ZLoCrowh4ZFThPut1Kuv4j2gQKBgQC3nX0pm9jFeqlpWJSsUxa1hk0j0Ot3kmex
17
+ WLOIb/tC1Gn5EHfvcExNGGL9GEGD2aEc8VYcmRtEw87XBXiT6fQ83zjjwJXCV/Bq
18
+ RGlGhfntUTHTWR+ToGBixfbBTYr+qv7bkI70jf7G9FukkU0Qd/nBDECSF+RGx4Re
19
+ XGtcjWuJ6wKBgQCyKT0rc+UR41W1w7DWZsjHGHUjYC4Q/0TZ7K0B0pJVlUgOeFfI
20
+ srqEPZfkxt20tqHhEFLrbkLR1ReSNhT+o8eYPUNHlOwU6gP3j87xKc2ZCVJIGcvq
21
+ F3aWENTojZBgE76ne23jOgFotp1mIRXTL6o/lcFLZLzienuMjl38NjFlgQKBgQCR
22
+ EnmFmoDW5mdbuIUe8jcLDSV9mt+wBZiv4mlW70MSNknUY1Kfd5aRgycS2UtKJXTK
23
+ LVPgHIgS+LI/6S6vjzVNswB70fmBJ4HoNE0JT2l8O56mYdA1D42X/NlNOTsMo4Xh
24
+ bIHGbzpRb1fI2pSM4n4OLOQHiaDu20yWUWbyJTpGKQKBgEiWUgo8MwDnMl4eRdtI
25
+ q+F3Kkr+O+lZIodHy9jqUT2nINetZXmUTl/NNLDft1qyoZfFFTFgvlIdPJywTz7R
26
+ 5e0lapai/q5XGo+oGs19X4lz2S/2zV05HZiZHoGPf2lYv4/YRs6KqouNiPhuQwf/
27
+ 91gGPtdtikkIah5l+fOlyXox
28
+ -----END PRIVATE KEY-----
@@ -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
+
42
+ export const TEST_CA_CERT_PEM = `-----BEGIN CERTIFICATE-----
43
+ MIIDkDCCAnigAwIBAgIUIKGQzgV0DVnoczi/+0ku6XxtXpUwDQYJKoZIhvcNAQEL
44
+ BQAwYDELMAkGA1UEBhMCVVMxJDAiBgNVBAoMG2Vzc2VudGlhbC1hcHBzIHNob3Bp
45
+ ZnktdGVzdDErMCkGA1UEAwwiZXNzZW50aWFsLWFwcHMgb2ZmbGluZSBFMkUgdGVz
46
+ dCBDQTAeFw0yNjA1MTExODIwNTBaFw0zNjA1MDgxODIwNTBaMGAxCzAJBgNVBAYT
47
+ AlVTMSQwIgYDVQQKDBtlc3NlbnRpYWwtYXBwcyBzaG9waWZ5LXRlc3QxKzApBgNV
48
+ BAMMImVzc2VudGlhbC1hcHBzIG9mZmxpbmUgRTJFIHRlc3QgQ0EwggEiMA0GCSqG
49
+ SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCVqEmQ3OULDil0xGp+s5Be9bqo/irOT4/x
50
+ IksWc+ul1oREVJ1F+EXINn2YUZZbrQ8i1tJg3xz/6u2EiGXozTuDsAX4VFa9Ew60
51
+ fvp+MsxKvIDZgQ54xizvlGEbFFIUcTMr0czzyRzhoPsvx0vZxMv+mnlU/nKSuRTv
52
+ 0+2rlVi19MjOmlmiF7imaJcZeTkzjAMy5tKgIu1QLk+dTNe7wXdr45gWLK959POy
53
+ JT7juKbpR2kgNhh91yQhvPnUTWibrln2pk/boTBM6s17BX28DwjRJM/UPAT9f+pC
54
+ h3p4Uan9NBHdPaOM9Wvj8cZqXWEz3ErYJmf9xUxYURStakozkFFrAgMBAAGjQjBA
55
+ MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBQvul3h
56
+ wRTsFPS1GZbOxAPssSEDfzANBgkqhkiG9w0BAQsFAAOCAQEAMDGe/lf/7x66nchI
57
+ 3KQcuX3OQ+hPeODw9F+7zY4KUf4+z1Me89M07kFWVR3cBplGIGUzQm6Sj3BCrShC
58
+ JgskN/jkGT7s5FHssHWRQ5m6uMTbDMctyVZYoW71Ip4pQloyh88fyWRejrJyVRhO
59
+ mAqB7iPEPQz9iSxaAeUJA/RU/zyBukUrP1ppU0qe6SFOTM+mfCMFZrITEJ2pbc9g
60
+ X9SbEk44qgQlOqfrj9FazxJp9Pjj19WLVXsY7JrNglpgzA8jwPWJSIPIU4gF0eTZ
61
+ 2B5ORtmHnN2uBnxe4sRf8eOHZy+7SZIR+6y1q/zmwbubVV7rF76ukw/I/kg+gAm+
62
+ eRz9lQ==
63
+ -----END CERTIFICATE-----`;
64
+ export const TEST_SERVER_CERT_PEM = `-----BEGIN CERTIFICATE-----
65
+ MIIEYjCCA0qgAwIBAgIURPMxG/s+J/RbB610ES+g6+4uz1YwDQYJKoZIhvcNAQEL
66
+ BQAwYDELMAkGA1UEBhMCVVMxJDAiBgNVBAoMG2Vzc2VudGlhbC1hcHBzIHNob3Bp
67
+ ZnktdGVzdDErMCkGA1UEAwwiZXNzZW50aWFsLWFwcHMgb2ZmbGluZSBFMkUgdGVz
68
+ dCBDQTAeFw0yNjA1MTExODIwNTZaFw0zNjA1MDgxODIwNTZaMFUxCzAJBgNVBAYT
69
+ AlVTMSQwIgYDVQQKDBtlc3NlbnRpYWwtYXBwcyBzaG9waWZ5LXRlc3QxIDAeBgNV
70
+ BAMMF3Rlc3Qtc2hvcC5teXNob3BpZnkuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOC
71
+ AQ8AMIIBCgKCAQEAqKKCIJ+3Z311vVrAzS9O8BFoDuFcS71UhXFOFnGv+32sWkv5
72
+ 15FiqsEUA9Y38TWMmATselKFmseqgZo1EgvNe6rFrnjyyJgEeYrLv40bz3JEp/Hs
73
+ GrgID4XaGJFx/tDsMLBtWLJykbkowRcJnQK0NoeYNC9E1JKfmU9WnT0eqOzc9ryn
74
+ iXj9pptDfd4BndYYSW4lvooshdyrVNcLnm67EXdyNukFk37ru9vSu12Oo+QynnZ7
75
+ 6Ijghpxjim5HHG3V5VF9fGSTcEKWqgw1l9FRESs6qFtG3GE5CSbg1J15uuUHBCof
76
+ qIxUJwk6foHqD1V+bMUbxZt1Aj05pZ2NJVAjpQIDAQABo4IBHTCCARkwDAYDVR0T
77
+ AQH/BAIwADAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwgaMG
78
+ A1UdEQSBmzCBmIIJbG9jYWxob3N0gg8qLm15c2hvcGlmeS5jb22CDW15c2hvcGlm
79
+ eS5jb22CDSouc2hvcGlmeS5jb22CC3Nob3BpZnkuY29tghEqLnNob3BpZnlhcHBz
80
+ LmNvbYISKi5zaG9waWZ5Y2xvdWQuY29tghAqLnNob3BpZnljZG4uY29thwR/AAAB
81
+ hxAAAAAAAAAAAAAAAAAAAAABMB0GA1UdDgQWBBTU9DyGKwRHIMACdy1hFqmWj2CW
82
+ 6DAfBgNVHSMEGDAWgBQvul3hwRTsFPS1GZbOxAPssSEDfzANBgkqhkiG9w0BAQsF
83
+ AAOCAQEAUJ5MwZ6iRLuOImmWMiiNTvjxYq7e8+/0zYkqXreQpQEah0muzHnoolhp
84
+ A4aolovZGdAJnAQXG38O8mRwIWxtJqcPPXEITz0O2H2Bdli4r+ikcIkUBNlvsuH2
85
+ 1QVwDvyWhgp2gW6wyYzQc5GSbZ9wjca0bUZx+CQsuyRMjkerEVMlnTlxyCVIRIuE
86
+ 6WxqZeoHiUskowUc7hIYzUIjadOzddFfdePW1nE69Se4aOM4lbvJ5HNwiNim+3nE
87
+ 1RTDsQCMcNcrMdY4g30cv9zm0+rjRB+Rmd3K4EBVaMrnbrMq9guXnZsyE8hkbKn3
88
+ VCXk3ZMrF4yyr/5s7FLeAsE8b9PYtw==
89
+ -----END CERTIFICATE-----`;
90
+ export const TEST_SERVER_KEY_PEM = `-----BEGIN PRIVATE KEY-----
91
+ MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCoooIgn7dnfXW9
92
+ WsDNL07wEWgO4VxLvVSFcU4Wca/7faxaS/nXkWKqwRQD1jfxNYyYBOx6UoWax6qB
93
+ mjUSC817qsWuePLImAR5isu/jRvPckSn8ewauAgPhdoYkXH+0OwwsG1YsnKRuSjB
94
+ FwmdArQ2h5g0L0TUkp+ZT1adPR6o7Nz2vKeJeP2mm0N93gGd1hhJbiW+iiyF3KtU
95
+ 1wuebrsRd3I26QWTfuu729K7XY6j5DKednvoiOCGnGOKbkccbdXlUX18ZJNwQpaq
96
+ DDWX0VERKzqoW0bcYTkJJuDUnXm65QcEKh+ojFQnCTp+geoPVX5sxRvFm3UCPTml
97
+ nY0lUCOlAgMBAAECggEAFzC17W+ZZKl7qg0Ta4QgemIiab1zGFVSjMFOqEZ9GXwo
98
+ WgiNtKfhJjNEIdzxN4ISMgunS5ESn3zqxUTkHHW0DdgntD0cwhopr183csGge/Au
99
+ YdwiiHAbZ6sUGYHS5+RqPq3cc7CikcihQqB86XMoPkF6XF7Nu9/oA8jF0/zGPRuQ
100
+ BiR722XwemloFMfNybnhYzLx5vr0EFs5R8UtDCZcRveWAbP5ggbkEfiz73zGA0gr
101
+ VGq0rQaovLntjc8Gtb93Q57pM7NOahgTQy6DXK42Of1gqkWTvFERXHjS5XH9NdzM
102
+ /ahXz6G/8ms2w6Xaj8uhZHxwEW6hF8S1D8q2Llf/AQKBgQDtXY0wFtI4dWE3MhHO
103
+ pQ6v21gMFP7bmAkN3DTbcAJYsDbZiGlzaH+FF+S1Uz8gbwCKlj9FYR4r0oU0ccfF
104
+ h023BQaxQXNCvfYunApxw1XG2NEkXvGWzD9DX/Xhj4z0WWve9Zh0tRVGMEkETjqT
105
+ xzOhGfxl1g2/CixrzTvKpD8+JQKBgQC136TOwQMs3EyRe1YwSWM0wlN7BcPehOPx
106
+ sTQNkyZsxE3u7NkKa6LXK7gdgC+oelWV6+UXbuEs9ZLWT0yh22T5ViO3sLWiEL7P
107
+ yE6y2j8nTucK3RynN8cEFuolBs3V+/B2K/yy3M9y2oahKmyDYnjy7AxI3UFQm9N7
108
+ hxCX4YKXgQKBgQDr2ulPv11jfD78+WN4UcomM21pk/MpgAh/HS/oW4P5XB8kR8eA
109
+ RXVwai13fyBaufFvw5ta9QVlxelWEzjNrYQrN3NO7hn5V4gnCCXYpJ+21fn6idzE
110
+ Wm8CI3fOiTUmFzR4dtDmJojdFV14ScMq0+UZTxjcl7VQ/mrlMykWUd4FgQKBgBfU
111
+ fNimS482MkYhnfJnuzrvd1a4M6jVSrShXkulCzTXJ8r1d5646bY9wTsET7pIhSxG
112
+ o1bFrXVhm+K+szDF+V3+HmH0Imhgv0+kVEN0+y9gVD+FJzr1wPrVMcq2MIQoJaKm
113
+ Ms8QxZGr9lXppBw269gQe6+UZfl04WnfEZqE7sKBAoGAQrP3o4fqVUb200Bn0dO+
114
+ F8fzgHT1lZAoz5NAyo4VOW4KZ6EgDhuxM2kG3WelGhYb0CwYzWS3WzAw/jxwSHMx
115
+ QhJtvjhtZzYM3P4RDHjSCvQm0X8xv6OMMOF3I0RmdZQFHLo7xIAkehPIpbmBaxSf
116
+ 2ojRh6FIbwe+526P8Qcpstk=
117
+ -----END PRIVATE KEY-----`;
@@ -0,0 +1,390 @@
1
+ /**
2
+ * Edge proxy — a real HTTPS reverse-proxy that takes Chromium
3
+ * requests for Shopify-shaped hostnames (test-shop.myshopify.com,
4
+ * admin.shopify.com, cdn.shopify.com) and dispatches them to the
5
+ * right internal mock based on Host + path.
6
+ *
7
+ * Why this exists: we abandoned Playwright's `page.route()` for
8
+ * routing because `route.fulfill({status: 302, …})` causes
9
+ * Chromium to follow redirects via its native network stack,
10
+ * bypassing route handlers — and our `test-shop.myshopify.com`
11
+ * domain resolves to real Shopify on the public internet, so any
12
+ * un-intercepted request leaks out of the test harness.
13
+ *
14
+ * The fix: drop down a network layer. With Chromium launched via
15
+ *
16
+ * --host-resolver-rules="MAP test-shop.myshopify.com 127.0.0.1:<edgePort>, …"
17
+ * --ignore-certificate-errors
18
+ *
19
+ * ALL browser traffic to those hostnames (initial requests, redirect
20
+ * follow-ups, sub-resources, XHR, fetch, service workers) lands at
21
+ * this proxy. There is no per-request interception layer to bypass.
22
+ * Redirects are handled by Chromium natively. The browser's URL bar
23
+ * stays correct.
24
+ *
25
+ * The proxy itself uses Node's `http` module (raw — no undici/fetch,
26
+ * which hangs against sibling-process loopback ports), forwards the
27
+ * request to the right internal mock, and streams the response back.
28
+ * 302/307/etc. responses are passed through unchanged; Chromium does
29
+ * the right thing with them.
30
+ *
31
+ * Cert: a static self-signed cert lives in `src/edge/cert.pem` +
32
+ * `src/edge/key.pem`, with SANs covering `*.myshopify.com`,
33
+ * `*.shopify.com`, etc. (See `src/edge/openssl.cnf`.) The cert is
34
+ * trusted because Chromium runs with `--ignore-certificate-errors`.
35
+ */
36
+ import { createServer as createHttpsServer } from 'node:https';
37
+ import { request as httpRequest, type IncomingHttpHeaders } from 'node:http';
38
+ import type { AddressInfo } from 'node:net';
39
+ import {
40
+ TEST_CA_CERT_PEM,
41
+ TEST_SERVER_CERT_PEM,
42
+ TEST_SERVER_KEY_PEM,
43
+ } from './cert.js';
44
+
45
+ /**
46
+ * Internal mock URLs the edge dispatches to. Each is an HTTP origin
47
+ * like `http://127.0.0.1:34567`. The edge speaks HTTPS to the
48
+ * browser, HTTP to the internal mocks (they're loopback-only;
49
+ * TLS between in-process services is pointless ceremony).
50
+ */
51
+ export interface EdgeBackends {
52
+ /** Mock storefront (Liquid renderer, cart, /cart/add.js, etc.). */
53
+ storefront: string;
54
+ /** Mock Admin GraphQL API. */
55
+ adminApi: string;
56
+ /** Mock Storefront GraphQL API. */
57
+ storefrontApi: string;
58
+ /** Mock admin shell (the admin.shopify.com iframe host). */
59
+ adminShell: string;
60
+ }
61
+
62
+ export interface EdgeOptions {
63
+ /** Backend mock URLs. */
64
+ backends: EdgeBackends;
65
+ /**
66
+ * Hostname of the test shop, e.g. `test-shop.myshopify.com`.
67
+ * Used to recognize storefront requests vs cdn/admin.
68
+ */
69
+ shopDomain: string;
70
+ /** TCP port to listen on. 0 → OS-assigned. */
71
+ port?: number;
72
+ /** Bind hostname (default `127.0.0.1`). */
73
+ hostname?: string;
74
+ }
75
+
76
+ export interface StartedEdge {
77
+ /** The actual port we bound to (resolved when port=0). */
78
+ port: number;
79
+ /** `https://127.0.0.1:<port>` — for diagnostic logging only. */
80
+ baseUrl: string;
81
+ /** Stop the proxy. */
82
+ close: () => Promise<void>;
83
+ }
84
+
85
+ /**
86
+ * Resolve an incoming request to (internalBase, overridePath).
87
+ *
88
+ * Host-based first (admin/cdn pick a backend), then path-based on
89
+ * the shop domain (/admin/api/, /api/ go to dedicated mocks).
90
+ */
91
+ function dispatch(
92
+ hostHeader: string,
93
+ pathWithQuery: string,
94
+ shopDomain: string,
95
+ backends: EdgeBackends,
96
+ ): { internalBase: string; overridePath: string } {
97
+ // Strip ":port" — Host may include it; we only care about hostname.
98
+ const host = hostHeader.split(':')[0]?.toLowerCase() ?? '';
99
+ const path = pathWithQuery;
100
+
101
+ // ── admin.shopify.com → admin shell ─────────────────────
102
+ if (host === 'admin.shopify.com') {
103
+ return { internalBase: backends.adminShell, overridePath: path };
104
+ }
105
+
106
+ // ── fonts.shopifycdn.com → storefront (handles its /cdn/fonts route) ─
107
+ // The storefront mock owns the bundled font mirror produced by
108
+ // the `fonts` probe. Routing the real Shopify font-CDN hostname
109
+ // here means `font_face` filter output (which contains the real
110
+ // shopifycdn URL by design) just works in-browser.
111
+ if (host === 'fonts.shopifycdn.com') {
112
+ return { internalBase: backends.storefront, overridePath: `/__shopify-fonts${path}` };
113
+ }
114
+
115
+ // ── *.shopifycdn.com → storefront mirror ────────────────────
116
+ // Any other Shopify CDN subdomain not handled above falls back to
117
+ // the generic asset-mirror handler. Bundled by the `mirror` probe.
118
+ if (host.endsWith('.shopifycdn.com')) {
119
+ return {
120
+ internalBase: backends.storefront,
121
+ overridePath: `/__shopify-mirror/${host}${path}`,
122
+ };
123
+ }
124
+
125
+ // ── cdn.shopify.com → split by path ─────────────────────
126
+ if (host === 'cdn.shopify.com') {
127
+ // App Bridge + Polaris CSS etc. live under /shopifycloud
128
+ // and /static, both served by the admin shell mock (which
129
+ // mirrors Shopify's CDN snapshot at packages/mock-admin/
130
+ // cdn-mirror).
131
+ if (path.startsWith('/shopifycloud/')) {
132
+ return { internalBase: backends.adminShell, overridePath: path };
133
+ }
134
+ if (path.startsWith('/static/')) {
135
+ return { internalBase: backends.adminShell, overridePath: path };
136
+ }
137
+ // Extension assets and theme assets live on the storefront
138
+ // mock. cdn.shopify.com/foo/bar/assets/baz.js → /assets/baz.js
139
+ if (path.startsWith('/extensions/')) {
140
+ return { internalBase: backends.storefront, overridePath: path };
141
+ }
142
+ const assetIdx = path.lastIndexOf('/assets/');
143
+ if (assetIdx >= 0) {
144
+ return {
145
+ internalBase: backends.storefront,
146
+ overridePath: path.slice(assetIdx),
147
+ };
148
+ }
149
+ // Fall through: anything else on cdn.shopify.com → storefront.
150
+ return { internalBase: backends.storefront, overridePath: path };
151
+ }
152
+
153
+ // ── shop.myshopify.com (or any *.myshopify.com) ──────────
154
+ if (host === shopDomain.toLowerCase() || host.endsWith('.myshopify.com')) {
155
+ if (path.startsWith('/admin/api/')) {
156
+ return { internalBase: backends.adminApi, overridePath: path };
157
+ }
158
+ if (path.startsWith('/api/')) {
159
+ return { internalBase: backends.storefrontApi, overridePath: path };
160
+ }
161
+ // Per-shop CDN proxy: theme assets, shopifycloud scripts,
162
+ // product images, fonts. Real Shopify hosts them all under
163
+ // `<shop>/cdn/*`. The mirror probe captures these into the
164
+ // shared mirror dir; serve from there transparently so any
165
+ // shop hostname resolves to mirrored bytes.
166
+ if (path.startsWith('/cdn/')) {
167
+ return {
168
+ internalBase: backends.storefront,
169
+ overridePath: `/__shopify-mirror/${host}${path}`,
170
+ };
171
+ }
172
+ return { internalBase: backends.storefront, overridePath: path };
173
+ }
174
+
175
+ // ── unknown host: best-effort → storefront ─────────────
176
+ // Chromium with host-resolver-rules wouldn't deliver an
177
+ // unmapped host here, but if someone hits this directly (a
178
+ // test using page.request with a custom host header, etc.)
179
+ // returning storefront 404s with a useful body is friendlier
180
+ // than connection refused.
181
+ return { internalBase: backends.storefront, overridePath: path };
182
+ }
183
+
184
+ /**
185
+ * Read a request body into a Buffer. Streaming would be more
186
+ * memory-efficient, but mock-sized POSTs are tiny (cart items,
187
+ * form data) and buffering keeps the proxy logic simple.
188
+ */
189
+ function readBody(req: NodeJS.ReadableStream): Promise<Buffer> {
190
+ return new Promise((resolveBody, rejectBody) => {
191
+ const chunks: Buffer[] = [];
192
+ req.on('data', (c: Buffer) => chunks.push(c));
193
+ req.on('end', () => resolveBody(Buffer.concat(chunks)));
194
+ req.on('error', rejectBody);
195
+ });
196
+ }
197
+
198
+ /** Forward one request to the chosen internal mock and stream the response back. */
199
+ async function forwardOne(
200
+ internalBase: string,
201
+ overridePath: string,
202
+ method: string,
203
+ headers: IncomingHttpHeaders,
204
+ body: Buffer,
205
+ ): Promise<{ status: number; headers: IncomingHttpHeaders; body: Buffer }> {
206
+ const target = new URL(internalBase);
207
+ // Drop hop-by-hop headers and the original Host — we set Host
208
+ // to the internal mock's address so Hono's c.req.url is
209
+ // consistent.
210
+ const outHeaders: Record<string, string> = {};
211
+ for (const [k, v] of Object.entries(headers)) {
212
+ if (!v) continue;
213
+ const lower = k.toLowerCase();
214
+ if (lower === 'host' || lower === 'connection' || lower === 'keep-alive' || lower === 'transfer-encoding') continue;
215
+ if (typeof v === 'string') outHeaders[k] = v;
216
+ else if (Array.isArray(v)) outHeaders[k] = v.join(', ');
217
+ }
218
+
219
+ return new Promise((resolveReq, rejectReq) => {
220
+ const r = httpRequest(
221
+ {
222
+ hostname: target.hostname,
223
+ port: Number(target.port),
224
+ path: overridePath,
225
+ method,
226
+ headers: outHeaders,
227
+ },
228
+ (res) => {
229
+ const chunks: Buffer[] = [];
230
+ res.on('data', (c: Buffer) => chunks.push(c));
231
+ res.on('end', () =>
232
+ resolveReq({
233
+ status: res.statusCode ?? 502,
234
+ headers: res.headers,
235
+ body: Buffer.concat(chunks),
236
+ }),
237
+ );
238
+ res.on('error', rejectReq);
239
+ },
240
+ );
241
+ r.on('error', rejectReq);
242
+ if (body.length > 0) r.write(body);
243
+ r.end();
244
+ });
245
+ }
246
+
247
+ /**
248
+ * Boot the edge HTTPS proxy. Returns a handle with the bound port
249
+ * and a `close()` to tear it down.
250
+ */
251
+ export async function startEdgeProxy(opts: EdgeOptions): Promise<StartedEdge> {
252
+ const server = createHttpsServer(
253
+ {
254
+ // Send server cert + CA chain so Chrome's chain validation
255
+ // sees a complete path even without `intermediate_certs`.
256
+ // Some Chrome versions are stricter about chain
257
+ // completeness when the CA is "private" (NSS-trusted only).
258
+ cert: `${TEST_SERVER_CERT_PEM}\n${TEST_CA_CERT_PEM}`,
259
+ key: TEST_SERVER_KEY_PEM,
260
+ },
261
+ async (req, res) => {
262
+ try {
263
+ const hostHeader = req.headers.host ?? '';
264
+ const pathWithQuery = req.url ?? '/';
265
+ const { internalBase, overridePath } = dispatch(
266
+ hostHeader,
267
+ pathWithQuery,
268
+ opts.shopDomain,
269
+ opts.backends,
270
+ );
271
+
272
+ const body = await readBody(req);
273
+ const proxied = await forwardOne(
274
+ internalBase,
275
+ overridePath,
276
+ req.method ?? 'GET',
277
+ req.headers,
278
+ body,
279
+ );
280
+
281
+ // Strip headers that don't make sense to forward verbatim
282
+ // (Node decompressed the body for us; content-length will
283
+ // be wrong; transfer-encoding shouldn't survive a hop).
284
+ const outHeaders: Record<string, string | string[]> = {};
285
+ for (const [k, v] of Object.entries(proxied.headers)) {
286
+ if (!v) continue;
287
+ const lower = k.toLowerCase();
288
+ if (lower === 'content-encoding' || lower === 'content-length' || lower === 'transfer-encoding') continue;
289
+ if (typeof v === 'string') outHeaders[k] = v;
290
+ else if (Array.isArray(v)) outHeaders[k] = v;
291
+ }
292
+
293
+ // CORS — the offline stack lives behind ONE edge proxy serving
294
+ // multiple Shopify origins (admin.shopify.com, cdn.shopify.com,
295
+ // *.myshopify.com). Any cross-origin XHR/fetch issued by the
296
+ // embedded app or by storefront JS would otherwise be blocked
297
+ // by Chrome with "TypeError: Failed to fetch" (the symptom in
298
+ // offline-full-stack tests that try to `fetch('https://cdn.
299
+ // shopify.com/...')` from an admin.shopify.com page). Real
300
+ // Shopify sets these on cdn.shopify.com responses too. Allow
301
+ // everything because this is a test-only proxy bound to
302
+ // 127.0.0.1 — no real traffic ever reaches it.
303
+ if (!outHeaders['Access-Control-Allow-Origin']) {
304
+ outHeaders['Access-Control-Allow-Origin'] = '*';
305
+ }
306
+ if (!outHeaders['Access-Control-Allow-Methods']) {
307
+ outHeaders['Access-Control-Allow-Methods'] =
308
+ 'GET, POST, PUT, PATCH, DELETE, OPTIONS';
309
+ }
310
+ if (!outHeaders['Access-Control-Allow-Headers']) {
311
+ outHeaders['Access-Control-Allow-Headers'] = '*';
312
+ }
313
+ if (!outHeaders['Access-Control-Expose-Headers']) {
314
+ outHeaders['Access-Control-Expose-Headers'] = '*';
315
+ }
316
+
317
+ // Short-circuit CORS preflights (the upstream mock might not
318
+ // implement OPTIONS at all; serving them here is cheaper anyway).
319
+ if (req.method === 'OPTIONS') {
320
+ res.writeHead(204, outHeaders);
321
+ res.end();
322
+ return;
323
+ }
324
+
325
+ res.writeHead(proxied.status, outHeaders);
326
+ res.end(proxied.body);
327
+
328
+ if (process.env['TEST_OFFLINE_EDGE_DEBUG'] === 'true') {
329
+ console.log(
330
+ `[edge] ${req.method} https://${hostHeader}${pathWithQuery} → ${internalBase}${overridePath} → ${proxied.status}`,
331
+ );
332
+ }
333
+ } catch (err) {
334
+ console.error('[edge] error:', (err as Error).message);
335
+ res.writeHead(502, { 'content-type': 'text/plain' });
336
+ res.end(`edge proxy error: ${(err as Error).message}`);
337
+ }
338
+ });
339
+
340
+ // Wait for listen() to either succeed OR error. Without an error
341
+ // handler, a failed bind (EADDRINUSE, EACCES on <1024 without
342
+ // privileges, no IPv6 stack when hostname='::', etc.) leaves the
343
+ // promise unresolved and the whole offline test run hangs silently.
344
+ const targetHost = opts.hostname ?? '127.0.0.1';
345
+ const targetPort = opts.port ?? 0;
346
+ // Wait for listen() to either succeed OR fail. Without explicit
347
+ // error/timeout handling, a failed bind (EADDRINUSE; EACCES on
348
+ // <1024 ports without privileges; a kernel that hangs on `::`
349
+ // dual-stack bind — the VM's libkrun does this) leaves the
350
+ // promise unresolved and the whole offline run stalls silently.
351
+ // The 10 s timeout surfaces any "listen never called back" case
352
+ // as a real error instead of a wedge.
353
+ await new Promise<void>((resolveListen, rejectListen) => {
354
+ const timer = setTimeout(() => {
355
+ rejectListen(
356
+ new Error(
357
+ `edge proxy listen() did not call back within 10 s on ${targetHost}:${targetPort} ` +
358
+ `(no 'error' event, no 'listening' callback). Kernel issue? ` +
359
+ `Try '0.0.0.0' instead of '::'.`,
360
+ ),
361
+ );
362
+ }, 10_000);
363
+ const onError = (err: NodeJS.ErrnoException): void => {
364
+ clearTimeout(timer);
365
+ server.off('error', onError);
366
+ rejectListen(
367
+ new Error(
368
+ `edge proxy bind failed on ${targetHost}:${targetPort}: ` +
369
+ `${err.code ?? '?'} ${err.message}`,
370
+ ),
371
+ );
372
+ };
373
+ server.on('error', onError);
374
+ server.listen(targetPort, targetHost, () => {
375
+ clearTimeout(timer);
376
+ server.off('error', onError);
377
+ resolveListen();
378
+ });
379
+ });
380
+ const addr = server.address() as AddressInfo;
381
+
382
+ return {
383
+ port: addr.port,
384
+ baseUrl: `https://${opts.hostname ?? '127.0.0.1'}:${addr.port}`,
385
+ close: () =>
386
+ new Promise<void>((resolveClose, rejectClose) => {
387
+ server.close((err) => (err ? rejectClose(err) : resolveClose()));
388
+ }),
389
+ };
390
+ }
@@ -0,0 +1,28 @@
1
+ [req]
2
+ distinguished_name = req_distinguished_name
3
+ req_extensions = v3_server
4
+ prompt = no
5
+
6
+ [req_distinguished_name]
7
+ C = US
8
+ O = essential-apps shopify-test
9
+ CN = test-shop.myshopify.com
10
+
11
+ [v3_server]
12
+ basicConstraints = critical, CA:FALSE
13
+ keyUsage = critical, digitalSignature, keyEncipherment
14
+ extendedKeyUsage = serverAuth
15
+ subjectAltName = @alt_names
16
+
17
+
18
+ [alt_names]
19
+ DNS.1 = localhost
20
+ DNS.2 = *.myshopify.com
21
+ DNS.3 = myshopify.com
22
+ DNS.4 = *.shopify.com
23
+ DNS.5 = shopify.com
24
+ DNS.6 = *.shopifyapps.com
25
+ DNS.7 = *.shopifycloud.com
26
+ DNS.8 = *.shopifycdn.com
27
+ IP.1 = 127.0.0.1
28
+ IP.2 = ::1
@@ -0,0 +1,26 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIEYjCCA0qgAwIBAgIURPMxG/s+J/RbB610ES+g6+4uz1YwDQYJKoZIhvcNAQEL
3
+ BQAwYDELMAkGA1UEBhMCVVMxJDAiBgNVBAoMG2Vzc2VudGlhbC1hcHBzIHNob3Bp
4
+ ZnktdGVzdDErMCkGA1UEAwwiZXNzZW50aWFsLWFwcHMgb2ZmbGluZSBFMkUgdGVz
5
+ dCBDQTAeFw0yNjA1MTExODIwNTZaFw0zNjA1MDgxODIwNTZaMFUxCzAJBgNVBAYT
6
+ AlVTMSQwIgYDVQQKDBtlc3NlbnRpYWwtYXBwcyBzaG9waWZ5LXRlc3QxIDAeBgNV
7
+ BAMMF3Rlc3Qtc2hvcC5teXNob3BpZnkuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOC
8
+ AQ8AMIIBCgKCAQEAqKKCIJ+3Z311vVrAzS9O8BFoDuFcS71UhXFOFnGv+32sWkv5
9
+ 15FiqsEUA9Y38TWMmATselKFmseqgZo1EgvNe6rFrnjyyJgEeYrLv40bz3JEp/Hs
10
+ GrgID4XaGJFx/tDsMLBtWLJykbkowRcJnQK0NoeYNC9E1JKfmU9WnT0eqOzc9ryn
11
+ iXj9pptDfd4BndYYSW4lvooshdyrVNcLnm67EXdyNukFk37ru9vSu12Oo+QynnZ7
12
+ 6Ijghpxjim5HHG3V5VF9fGSTcEKWqgw1l9FRESs6qFtG3GE5CSbg1J15uuUHBCof
13
+ qIxUJwk6foHqD1V+bMUbxZt1Aj05pZ2NJVAjpQIDAQABo4IBHTCCARkwDAYDVR0T
14
+ AQH/BAIwADAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwgaMG
15
+ A1UdEQSBmzCBmIIJbG9jYWxob3N0gg8qLm15c2hvcGlmeS5jb22CDW15c2hvcGlm
16
+ eS5jb22CDSouc2hvcGlmeS5jb22CC3Nob3BpZnkuY29tghEqLnNob3BpZnlhcHBz
17
+ LmNvbYISKi5zaG9waWZ5Y2xvdWQuY29tghAqLnNob3BpZnljZG4uY29thwR/AAAB
18
+ hxAAAAAAAAAAAAAAAAAAAAABMB0GA1UdDgQWBBTU9DyGKwRHIMACdy1hFqmWj2CW
19
+ 6DAfBgNVHSMEGDAWgBQvul3hwRTsFPS1GZbOxAPssSEDfzANBgkqhkiG9w0BAQsF
20
+ AAOCAQEAUJ5MwZ6iRLuOImmWMiiNTvjxYq7e8+/0zYkqXreQpQEah0muzHnoolhp
21
+ A4aolovZGdAJnAQXG38O8mRwIWxtJqcPPXEITz0O2H2Bdli4r+ikcIkUBNlvsuH2
22
+ 1QVwDvyWhgp2gW6wyYzQc5GSbZ9wjca0bUZx+CQsuyRMjkerEVMlnTlxyCVIRIuE
23
+ 6WxqZeoHiUskowUc7hIYzUIjadOzddFfdePW1nE69Se4aOM4lbvJ5HNwiNim+3nE
24
+ 1RTDsQCMcNcrMdY4g30cv9zm0+rjRB+Rmd3K4EBVaMrnbrMq9guXnZsyE8hkbKn3
25
+ VCXk3ZMrF4yyr/5s7FLeAsE8b9PYtw==
26
+ -----END CERTIFICATE-----
@@ -0,0 +1,28 @@
1
+ -----BEGIN PRIVATE KEY-----
2
+ MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCoooIgn7dnfXW9
3
+ WsDNL07wEWgO4VxLvVSFcU4Wca/7faxaS/nXkWKqwRQD1jfxNYyYBOx6UoWax6qB
4
+ mjUSC817qsWuePLImAR5isu/jRvPckSn8ewauAgPhdoYkXH+0OwwsG1YsnKRuSjB
5
+ FwmdArQ2h5g0L0TUkp+ZT1adPR6o7Nz2vKeJeP2mm0N93gGd1hhJbiW+iiyF3KtU
6
+ 1wuebrsRd3I26QWTfuu729K7XY6j5DKednvoiOCGnGOKbkccbdXlUX18ZJNwQpaq
7
+ DDWX0VERKzqoW0bcYTkJJuDUnXm65QcEKh+ojFQnCTp+geoPVX5sxRvFm3UCPTml
8
+ nY0lUCOlAgMBAAECggEAFzC17W+ZZKl7qg0Ta4QgemIiab1zGFVSjMFOqEZ9GXwo
9
+ WgiNtKfhJjNEIdzxN4ISMgunS5ESn3zqxUTkHHW0DdgntD0cwhopr183csGge/Au
10
+ YdwiiHAbZ6sUGYHS5+RqPq3cc7CikcihQqB86XMoPkF6XF7Nu9/oA8jF0/zGPRuQ
11
+ BiR722XwemloFMfNybnhYzLx5vr0EFs5R8UtDCZcRveWAbP5ggbkEfiz73zGA0gr
12
+ VGq0rQaovLntjc8Gtb93Q57pM7NOahgTQy6DXK42Of1gqkWTvFERXHjS5XH9NdzM
13
+ /ahXz6G/8ms2w6Xaj8uhZHxwEW6hF8S1D8q2Llf/AQKBgQDtXY0wFtI4dWE3MhHO
14
+ pQ6v21gMFP7bmAkN3DTbcAJYsDbZiGlzaH+FF+S1Uz8gbwCKlj9FYR4r0oU0ccfF
15
+ h023BQaxQXNCvfYunApxw1XG2NEkXvGWzD9DX/Xhj4z0WWve9Zh0tRVGMEkETjqT
16
+ xzOhGfxl1g2/CixrzTvKpD8+JQKBgQC136TOwQMs3EyRe1YwSWM0wlN7BcPehOPx
17
+ sTQNkyZsxE3u7NkKa6LXK7gdgC+oelWV6+UXbuEs9ZLWT0yh22T5ViO3sLWiEL7P
18
+ yE6y2j8nTucK3RynN8cEFuolBs3V+/B2K/yy3M9y2oahKmyDYnjy7AxI3UFQm9N7
19
+ hxCX4YKXgQKBgQDr2ulPv11jfD78+WN4UcomM21pk/MpgAh/HS/oW4P5XB8kR8eA
20
+ RXVwai13fyBaufFvw5ta9QVlxelWEzjNrYQrN3NO7hn5V4gnCCXYpJ+21fn6idzE
21
+ Wm8CI3fOiTUmFzR4dtDmJojdFV14ScMq0+UZTxjcl7VQ/mrlMykWUd4FgQKBgBfU
22
+ fNimS482MkYhnfJnuzrvd1a4M6jVSrShXkulCzTXJ8r1d5646bY9wTsET7pIhSxG
23
+ o1bFrXVhm+K+szDF+V3+HmH0Imhgv0+kVEN0+y9gVD+FJzr1wPrVMcq2MIQoJaKm
24
+ Ms8QxZGr9lXppBw269gQe6+UZfl04WnfEZqE7sKBAoGAQrP3o4fqVUb200Bn0dO+
25
+ F8fzgHT1lZAoz5NAyo4VOW4KZ6EgDhuxM2kG3WelGhYb0CwYzWS3WzAw/jxwSHMx
26
+ QhJtvjhtZzYM3P4RDHjSCvQm0X8xv6OMMOF3I0RmdZQFHLo7xIAkehPIpbmBaxSf
27
+ 2ojRh6FIbwe+526P8Qcpstk=
28
+ -----END PRIVATE KEY-----