@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
package/src/index.ts ADDED
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Public API of @essential-apps/shopify-test-runner.
3
+ *
4
+ * The CLI binaries (shopify-test-run-docker, etc.) are exposed via
5
+ * the `bin` field in package.json — they don't import from this
6
+ * file. This module re-exports a few utilities that consuming apps
7
+ * occasionally use programmatically (port checking, stealth browser
8
+ * launch).
9
+ *
10
+ * Most apps will only import from this package's `/playwright`
11
+ * subpath:
12
+ * import { definePlaywrightConfig } from
13
+ * '@essential-apps/shopify-test-runner/playwright';
14
+ */
15
+
16
+ export { isPortFree, findFreePort } from './lib/freePort.js';
17
+
18
+ export { launchStealthBrowser, stealthLaunchArgs } from './lib/stealthLaunch.js';
19
+
20
+ /**
21
+ * Shopify Functions JS-source → deployable .wasm pipeline. Builds
22
+ * without the Shopify CLI; downloads stock Javy + the Shopify-CDN
23
+ * plugin .wasm on first use. See `./lib/functionBuild.ts` for the
24
+ * full rationale.
25
+ */
26
+ export {
27
+ buildFunctionWasm,
28
+ ensureJavy,
29
+ ensureJavyPlugin,
30
+ parseFunctionToml,
31
+ JAVY_VERSION,
32
+ PLUGIN_URL,
33
+ } from './lib/functionBuild.js';
34
+ export type {
35
+ BuildFunctionWasmOptions,
36
+ BuildFunctionWasmResult,
37
+ FunctionTarget,
38
+ ParsedFunctionToml,
39
+ } from './lib/functionBuild.js';
40
+
41
+ /**
42
+ * App Management API source-zip upload helpers — used together
43
+ * when deploying app versions that contain a function or
44
+ * post-purchase extension (their binary artifacts don't fit
45
+ * inline in the `source` manifest).
46
+ */
47
+ export {
48
+ requestSourceUploadUrl,
49
+ uploadSourceToSignedUrl,
50
+ } from './lib/sourceZipUpload.js';
51
+ export type {
52
+ RequestSourceUploadUrlOptions,
53
+ UploadSourceOptions,
54
+ } from './lib/sourceZipUpload.js';
55
+
56
+ /**
57
+ * Source-bundle (zip) assembly. Produces the artifact uploaded to
58
+ * GCS and referenced via `AppVersionInput.sourceUrl`.
59
+ */
60
+ export {
61
+ buildSourceBundle,
62
+ readExtensionFilesAsBuffers,
63
+ } from './lib/buildSourceBundle.js';
64
+ export type {
65
+ BuildSourceBundleOptions,
66
+ ManifestModule,
67
+ } from './lib/buildSourceBundle.js';
@@ -0,0 +1,197 @@
1
+ /**
2
+ * Assemble the source archive uploaded for `appVersionCreate` via
3
+ * the `sourceUrl` path.
4
+ *
5
+ * What format does Shopify actually accept?
6
+ * -----------------------------------------
7
+ * Empirically (May 2026, App Management /unstable/ endpoint): the
8
+ * server accepts **tar + brotli** (`.tar.br`), AND requires
9
+ * `sourceExtension: BR` when minting the signed-upload URL via
10
+ * `appRequestSourceUploadUrl`. The legacy `sourceExtension: ZIP`
11
+ * branch exists in the schema and returns a working signed URL, but
12
+ * something downstream chokes on zips that contain a per-extension
13
+ * folder layout — the server 500s on any zip whose top level has a
14
+ * `<uid>/...` directory entry. Tar+brotli works.
15
+ *
16
+ * This was reverse-engineered from `Shopify/cli`'s bundle pipeline:
17
+ * packages/app/src/cli/services/bundle.ts
18
+ * packages/cli-kit/src/public/node/archiver.ts
19
+ * The CLI ALWAYS uses tar+brotli; the ZIP code path appears to be
20
+ * unused / vestigial.
21
+ *
22
+ * Layout inside the archive
23
+ * -------------------------
24
+ * manifest.json ← top-level module list
25
+ * <module_uid>/dist/index.wasm ← function WASM, BASE64-
26
+ * ENCODED AS TEXT (not raw
27
+ * binary — see below)
28
+ * <theme_uid>/assets/<file> ← theme files, raw bytes
29
+ * <theme_uid>/blocks/<file>
30
+ * <theme_uid>/locales/<file>
31
+ * <theme_uid>/snippets/<file>
32
+ *
33
+ * The base64-encoding-of-wasm is THE critical detail that gets you
34
+ * past `"Wasm file is not present"`. The Shopify CLI does this in
35
+ * `bundleFunctionExtension(wasmPath, bundlePath)` which calls
36
+ * `readFile(path, {encoding: 'base64'})` and `writeFile(bundlePath, b64)`.
37
+ * The server reads the file at `<uid>/dist/index.wasm` and expects
38
+ * base64 text content, decoding it back to WASM bytes on its side.
39
+ *
40
+ * Manifest format
41
+ * ---------------
42
+ * Mirrors the CLI's `app.manifest()` builder
43
+ * (`packages/app/src/cli/models/app/app.ts`). Each module entry has:
44
+ * {
45
+ * type: <externalIdentifier — e.g. 'function' for plain function>,
46
+ * handle: <toml handle>,
47
+ * uid: <stable uuid>,
48
+ * assets: <uid>, ← same string as uid; folder name in archive
49
+ * target: <contextValue>, ← single-target handle, or '' for multi
50
+ * config: { … per-spec deployConfig output … },
51
+ * }
52
+ */
53
+ import { readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs';
54
+ import { resolve } from 'node:path';
55
+ import { brotliCompressSync, constants as zlibConst } from 'node:zlib';
56
+ import { pack as tarPack } from 'tar-stream';
57
+
58
+ /** A single entry in the manifest.json's `modules` array. Matches
59
+ * the CLI's `ManifestModule` interface from app.ts. */
60
+ export interface ManifestModule {
61
+ type: string;
62
+ handle: string;
63
+ uid: string;
64
+ /** Folder name inside the archive that holds this module's
65
+ * artifacts. Conventionally equal to `uid`. */
66
+ assets?: string;
67
+ /** Single-target handle (e.g. "cart.lines.discounts.generate.run").
68
+ * Empty string for multi-target functions, omitted for non-targeted
69
+ * modules like app_home / branding / app_access. */
70
+ target?: string;
71
+ config: Record<string, unknown>;
72
+ }
73
+
74
+ export interface BuildSourceBundleOptions {
75
+ /** The app's top-level display name. Goes into `manifest.name`. */
76
+ name: string;
77
+ /** All modules for this deploy. */
78
+ modules: ManifestModule[];
79
+ /**
80
+ * Files to embed in the archive, keyed by their path-inside-the-
81
+ * archive. Function .wasm files MUST be base64-encoded as text by
82
+ * the caller (we'd do it here, but the caller already has the
83
+ * raw wasm and may or may not have other text/binary content —
84
+ * doing it here would obscure the contract).
85
+ */
86
+ files: Record<string, Buffer | string>;
87
+ /** Output path. Caller owns the path — typically /tmp/<…>.tar.br. */
88
+ outPath: string;
89
+ /**
90
+ * Brotli quality (0-11). Default 7 matches the CLI's choice. Higher
91
+ * = smaller + slower. Functions + theme bundles fit comfortably in
92
+ * a few hundred KB at quality 7.
93
+ */
94
+ brotliQuality?: number;
95
+ }
96
+
97
+ /**
98
+ * Build a tar+brotli source bundle and write it to `outPath`. Returns
99
+ * the path written.
100
+ *
101
+ * Implementation note: we tar in-memory (chunked into Node Buffers)
102
+ * then brotli-compress in one shot. Suitable for the < 10MB bundles
103
+ * we deal with; switch to streaming if that changes.
104
+ */
105
+ export async function buildSourceBundle(
106
+ opts: BuildSourceBundleOptions,
107
+ ): Promise<string> {
108
+ const manifestJson = JSON.stringify(
109
+ { name: opts.name, handle: '', modules: opts.modules },
110
+ null,
111
+ 2,
112
+ );
113
+ const entries: Array<{ name: string; content: Buffer }> = [
114
+ { name: 'manifest.json', content: Buffer.from(manifestJson, 'utf8') },
115
+ ];
116
+ for (const [name, content] of Object.entries(opts.files)) {
117
+ entries.push({
118
+ name,
119
+ content: Buffer.isBuffer(content) ? content : Buffer.from(content, 'utf8'),
120
+ });
121
+ }
122
+
123
+ // tar-stream pack: collect chunks, finalize, then brotli-compress.
124
+ const tarBuf = await new Promise<Buffer>((resolve_, reject) => {
125
+ const pack = tarPack();
126
+ const chunks: Buffer[] = [];
127
+ pack.on('data', (c: Buffer) => chunks.push(c));
128
+ pack.on('end', () => resolve_(Buffer.concat(chunks)));
129
+ pack.on('error', reject);
130
+ (async () => {
131
+ for (const e of entries) {
132
+ await new Promise<void>((res, rej) => {
133
+ pack.entry({ name: e.name }, e.content, (err) => (err ? rej(err) : res()));
134
+ });
135
+ }
136
+ pack.finalize();
137
+ })().catch(reject);
138
+ });
139
+
140
+ const brBuf = brotliCompressSync(tarBuf, {
141
+ params: {
142
+ [zlibConst.BROTLI_PARAM_QUALITY]: opts.brotliQuality ?? 7,
143
+ },
144
+ });
145
+ writeFileSync(opts.outPath, brBuf);
146
+ return opts.outPath;
147
+ }
148
+
149
+ /**
150
+ * Walk an extension directory and return its file contents keyed by
151
+ * the path-inside-the-archive-folder (i.e. NOT prefixed with the
152
+ * uid; the caller prepends that). Returns raw Buffers.
153
+ *
154
+ * Skips dirs/files that aren't shipped (same list as the inline
155
+ * theme bundler in deployAppVersion.ts).
156
+ */
157
+ export function readExtensionFilesAsBuffers(
158
+ extensionDir: string,
159
+ ): Record<string, Buffer> {
160
+ const SKIP_DIRS = new Set([
161
+ 'node_modules',
162
+ '.git',
163
+ 'src',
164
+ 'frontend',
165
+ 'generated',
166
+ ]);
167
+ const SKIP_FILES = new Set([
168
+ 'shopify.extension.toml',
169
+ 'package.json',
170
+ 'package-lock.json',
171
+ 'tsconfig.json',
172
+ '.shopifyignore',
173
+ '.gitignore',
174
+ 'README.md',
175
+ ]);
176
+ const out: Record<string, Buffer> = {};
177
+ function walk(dir: string, rel: string): void {
178
+ for (const name of readdirSync(dir)) {
179
+ const full = resolve(dir, name);
180
+ const relPath = rel ? `${rel}/${name}` : name;
181
+ const st = statSync(full);
182
+ if (st.isDirectory()) {
183
+ if (SKIP_DIRS.has(name) || name.startsWith('.')) continue;
184
+ walk(full, relPath);
185
+ continue;
186
+ }
187
+ if (SKIP_FILES.has(name)) continue;
188
+ if (name.endsWith('.config.ts') || name.endsWith('.config.mts')) continue;
189
+ if (name.endsWith('.config.js') || name.endsWith('.config.cjs')) continue;
190
+ if (name.endsWith('.js.map')) continue; // CLI excludes these
191
+ if (name.endsWith('.metafile.json')) continue; // CLI excludes these
192
+ out[relPath] = readFileSync(full);
193
+ }
194
+ }
195
+ walk(extensionDir, '');
196
+ return out;
197
+ }
@@ -0,0 +1,33 @@
1
+ import { createServer } from 'node:net';
2
+
3
+ /** Asks the OS for a random free TCP port. */
4
+ export function findFreePort(): Promise<number> {
5
+ return new Promise((resolve, reject) => {
6
+ const srv = createServer();
7
+ srv.unref();
8
+ srv.on('error', reject);
9
+ srv.listen(0, '127.0.0.1', () => {
10
+ const addr = srv.address();
11
+ if (addr && typeof addr === 'object') {
12
+ const port = addr.port;
13
+ srv.close(() => resolve(port));
14
+ } else {
15
+ srv.close();
16
+ reject(new Error('Could not determine free port'));
17
+ }
18
+ });
19
+ });
20
+ }
21
+
22
+ /** Returns true if the given port is free to bind. */
23
+ export function isPortFree(port: number): Promise<boolean> {
24
+ return new Promise((resolve) => {
25
+ const srv = createServer();
26
+ srv.unref();
27
+ srv.once('error', () => resolve(false));
28
+ srv.once('listening', () => {
29
+ srv.close(() => resolve(true));
30
+ });
31
+ srv.listen(port, '127.0.0.1');
32
+ });
33
+ }