@slopware/sloppy-linux-x64 0.1.0-alpha.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 (434) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +5 -0
  3. package/bin/sloppy +0 -0
  4. package/bin/sloppyc +0 -0
  5. package/docs/KNOWN_LIMITATIONS.md +16 -0
  6. package/docs/LICENSES.md +6 -0
  7. package/docs/NOTICE.md +8 -0
  8. package/examples/README.md +140 -0
  9. package/examples/auth-api/README.md +20 -0
  10. package/examples/auth-api/app.js +61 -0
  11. package/examples/auth-api/appsettings.json +7 -0
  12. package/examples/auth-api/sloppy.json +5 -0
  13. package/examples/cache-basic/README.md +9 -0
  14. package/examples/cache-basic/app.js +32 -0
  15. package/examples/cache-hybrid-postgres/README.md +10 -0
  16. package/examples/cache-hybrid-postgres/app.js +27 -0
  17. package/examples/cache-output-api/README.md +10 -0
  18. package/examples/cache-output-api/app.js +35 -0
  19. package/examples/codec-base64-hex/README.md +14 -0
  20. package/examples/codec-base64-hex/app.js +15 -0
  21. package/examples/codec-checksums/README.md +15 -0
  22. package/examples/codec-checksums/app.js +8 -0
  23. package/examples/codec-compression/README.md +13 -0
  24. package/examples/codec-compression/app.js +9 -0
  25. package/examples/codec-streaming-compression/README.md +19 -0
  26. package/examples/codec-streaming-compression/app.js +16 -0
  27. package/examples/codec-text-binary/README.md +16 -0
  28. package/examples/codec-text-binary/app.js +17 -0
  29. package/examples/compiler-hello/README.md +71 -0
  30. package/examples/compiler-hello/app.js +7 -0
  31. package/examples/compiler-hello/expected/app.js +8 -0
  32. package/examples/compiler-hello/expected/app.js.map +53 -0
  33. package/examples/compiler-hello/expected/app.plan.json +229 -0
  34. package/examples/compiler-hello/expected/routes.slrt +0 -0
  35. package/examples/config-basic/README.md +13 -0
  36. package/examples/config-basic/app.js +13 -0
  37. package/examples/config-basic/appsettings.json +7 -0
  38. package/examples/config-secrets-redaction/README.md +9 -0
  39. package/examples/config-secrets-redaction/app.js +9 -0
  40. package/examples/config-secrets-redaction/appsettings.json +5 -0
  41. package/examples/config-strict-mode/README.md +7 -0
  42. package/examples/config-strict-mode/app.js +10 -0
  43. package/examples/config-strict-mode/appsettings.json +7 -0
  44. package/examples/configured-api/README.md +38 -0
  45. package/examples/configured-api/app.js +12 -0
  46. package/examples/configured-api/appsettings.Development.json +5 -0
  47. package/examples/configured-api/appsettings.json +6 -0
  48. package/examples/configured-api/sloppy.json +5 -0
  49. package/examples/core-config-secrets/README.md +10 -0
  50. package/examples/core-config-secrets/app.js +15 -0
  51. package/examples/core-fs-time-codec/README.md +9 -0
  52. package/examples/core-fs-time-codec/app.js +8 -0
  53. package/examples/core-network-time-codec/README.md +11 -0
  54. package/examples/core-network-time-codec/app.js +20 -0
  55. package/examples/core-policy-audit/README.md +7 -0
  56. package/examples/core-policy-audit/app.js +22 -0
  57. package/examples/core-process-time-codec/README.md +8 -0
  58. package/examples/core-process-time-codec/app.js +28 -0
  59. package/examples/core-worker-time/README.md +8 -0
  60. package/examples/core-worker-time/app.js +17 -0
  61. package/examples/crypto-hash-hmac/README.md +17 -0
  62. package/examples/crypto-hash-hmac/app.js +29 -0
  63. package/examples/crypto-password/README.md +21 -0
  64. package/examples/crypto-password/app.js +12 -0
  65. package/examples/crypto-random-token/README.md +16 -0
  66. package/examples/crypto-random-token/app.js +12 -0
  67. package/examples/crypto-secret-constant-time/README.md +21 -0
  68. package/examples/crypto-secret-constant-time/app.js +15 -0
  69. package/examples/data-foundation/README.md +39 -0
  70. package/examples/data-foundation/app.js +63 -0
  71. package/examples/dependency-graph/README.md +19 -0
  72. package/examples/dependency-graph/fixtures/graph-helper/index.js +3 -0
  73. package/examples/dependency-graph/fixtures/graph-helper/package.json +6 -0
  74. package/examples/dependency-graph/package.json +7 -0
  75. package/examples/dependency-graph/public/message.txt +1 -0
  76. package/examples/dependency-graph/sloppy.json +9 -0
  77. package/examples/dependency-graph/src/main.ts +8 -0
  78. package/examples/dogfood/README.md +23 -0
  79. package/examples/dogfood/dogfood.json +136 -0
  80. package/examples/dynamic-module-include/README.md +20 -0
  81. package/examples/dynamic-module-include/public/readme.txt +1 -0
  82. package/examples/dynamic-module-include/sloppy.json +12 -0
  83. package/examples/dynamic-module-include/src/main.ts +6 -0
  84. package/examples/dynamic-module-include/src/plugins/alpha.js +3 -0
  85. package/examples/dynamic-module-include/src/plugins/beta.js +3 -0
  86. package/examples/ergonomics/README.md +42 -0
  87. package/examples/ergonomics/app.js +38 -0
  88. package/examples/framework-controller/README.md +12 -0
  89. package/examples/framework-controller/app.js +31 -0
  90. package/examples/framework-di-services/README.md +17 -0
  91. package/examples/framework-di-services/app.ts +40 -0
  92. package/examples/framework-explicit-binding/README.md +12 -0
  93. package/examples/framework-explicit-binding/app.ts +34 -0
  94. package/examples/framework-hello/README.md +16 -0
  95. package/examples/framework-hello/app.ts +16 -0
  96. package/examples/framework-postgres-crud/README.md +73 -0
  97. package/examples/framework-postgres-crud/app.ts +64 -0
  98. package/examples/framework-sqlite-crud/README.md +52 -0
  99. package/examples/framework-sqlite-crud/app.ts +90 -0
  100. package/examples/framework-sqlite-crud/appsettings.json +11 -0
  101. package/examples/framework-sqlserver-crud/README.md +73 -0
  102. package/examples/framework-sqlserver-crud/app.ts +64 -0
  103. package/examples/framework-validation-errors/README.md +12 -0
  104. package/examples/framework-validation-errors/app.ts +16 -0
  105. package/examples/fs-basic/README.md +24 -0
  106. package/examples/fs-basic/app.js +12 -0
  107. package/examples/fs-roots-policy/README.md +14 -0
  108. package/examples/fs-roots-policy/app.js +4 -0
  109. package/examples/fs-streams/README.md +18 -0
  110. package/examples/fs-streams/app.js +11 -0
  111. package/examples/fs-watch/README.md +19 -0
  112. package/examples/fs-watch/app.js +11 -0
  113. package/examples/hello/README.md +63 -0
  114. package/examples/hello/app.js +19 -0
  115. package/examples/hello-minimal/README.md +51 -0
  116. package/examples/hello-minimal/sloppy.json +5 -0
  117. package/examples/hello-minimal/src/main.ts +9 -0
  118. package/examples/http-client-basic/README.md +11 -0
  119. package/examples/http-client-basic/app.js +46 -0
  120. package/examples/http-client-generated/README.md +22 -0
  121. package/examples/http-client-generated/openapi.json +45 -0
  122. package/examples/http-client-resilience/README.md +4 -0
  123. package/examples/http-client-resilience/app.js +38 -0
  124. package/examples/http-client-runtime-loopback/README.md +24 -0
  125. package/examples/http-client-testhost/README.md +4 -0
  126. package/examples/http-client-testhost/app.js +27 -0
  127. package/examples/http-client-testhost-package-mock/README.md +26 -0
  128. package/examples/http-client-typed/README.md +5 -0
  129. package/examples/http-client-typed/app.js +33 -0
  130. package/examples/modules-api/README.md +30 -0
  131. package/examples/modules-api/app.js +9 -0
  132. package/examples/modules-api/modules/routes.js +16 -0
  133. package/examples/modules-api/sloppy.json +5 -0
  134. package/examples/modules-basic/README.md +32 -0
  135. package/examples/modules-basic/app.js +41 -0
  136. package/examples/net-deadline-cancel/README.md +13 -0
  137. package/examples/net-deadline-cancel/app.js +34 -0
  138. package/examples/net-local-ipc/README.md +12 -0
  139. package/examples/net-local-ipc/app.js +46 -0
  140. package/examples/net-policy-strict/README.md +12 -0
  141. package/examples/net-policy-strict/app.js +34 -0
  142. package/examples/net-tcp-client/README.md +10 -0
  143. package/examples/net-tcp-client/app.js +23 -0
  144. package/examples/net-tcp-echo/README.md +11 -0
  145. package/examples/net-tcp-echo/app.js +45 -0
  146. package/examples/net-tcp-server/README.md +10 -0
  147. package/examples/net-tcp-server/app.js +28 -0
  148. package/examples/node-compat-path-events/README.md +15 -0
  149. package/examples/node-compat-path-events/sloppy.json +6 -0
  150. package/examples/node-compat-path-events/src/main.ts +15 -0
  151. package/examples/ops-compiler/README.md +9 -0
  152. package/examples/ops-compiler/app.js +26 -0
  153. package/examples/ops-health-metrics-management/README.md +14 -0
  154. package/examples/ops-health-metrics-management/app.js +24 -0
  155. package/examples/orm-basic/README.md +17 -0
  156. package/examples/orm-basic/app.js +82 -0
  157. package/examples/orm-cursor-export/README.md +16 -0
  158. package/examples/orm-cursor-export/app.js +28 -0
  159. package/examples/orm-migrations/README.md +14 -0
  160. package/examples/orm-migrations/migrations/.gitkeep +1 -0
  161. package/examples/orm-migrations/sloppy.json +9 -0
  162. package/examples/orm-migrations/src/app.ts +34 -0
  163. package/examples/orm-relations-includes/README.md +10 -0
  164. package/examples/orm-relations-includes/app.js +47 -0
  165. package/examples/orm-testservices/README.md +37 -0
  166. package/examples/orm-testservices/test.mjs +32 -0
  167. package/examples/os-runtime-api/README.md +11 -0
  168. package/examples/os-runtime-api/app.js +44 -0
  169. package/examples/package-zod-like/README.md +28 -0
  170. package/examples/package-zod-like/fixtures/zod-like/index.js +48 -0
  171. package/examples/package-zod-like/fixtures/zod-like/package.json +12 -0
  172. package/examples/package-zod-like/package.json +7 -0
  173. package/examples/package-zod-like/sloppy.json +6 -0
  174. package/examples/package-zod-like/src/main.ts +16 -0
  175. package/examples/postgres-basic/README.md +31 -0
  176. package/examples/postgres-basic/app.js +50 -0
  177. package/examples/prealpha-control-plane/README.md +50 -0
  178. package/examples/prealpha-control-plane/appsettings.Development.json +11 -0
  179. package/examples/prealpha-control-plane/appsettings.json +15 -0
  180. package/examples/prealpha-control-plane/sloppy.json +5 -0
  181. package/examples/prealpha-control-plane/src/db/schema.js +7 -0
  182. package/examples/prealpha-control-plane/src/db/seed.js +6 -0
  183. package/examples/prealpha-control-plane/src/main.js +21 -0
  184. package/examples/prealpha-control-plane/src/routes/apps.js +34 -0
  185. package/examples/prealpha-control-plane/src/routes/builds.js +25 -0
  186. package/examples/prealpha-control-plane/src/routes/deployments.js +19 -0
  187. package/examples/prealpha-control-plane/src/routes/diagnostics.js +11 -0
  188. package/examples/prealpha-control-plane/src/routes/health.js +27 -0
  189. package/examples/prealpha-control-plane/src/routes/projects.js +38 -0
  190. package/examples/prealpha-control-plane/src/services/diagnosticsSink.js +11 -0
  191. package/examples/prealpha-control-plane/src/services/repositories.js +9 -0
  192. package/examples/prealpha-control-plane/src/validation/schemas.js +6 -0
  193. package/examples/program-fs-process/README.md +31 -0
  194. package/examples/program-fs-process/sloppy.json +9 -0
  195. package/examples/program-fs-process/src/main.ts +27 -0
  196. package/examples/program-hello/README.md +32 -0
  197. package/examples/program-hello/main.ts +8 -0
  198. package/examples/program-hello/message.ts +1 -0
  199. package/examples/program-hello/sloppy.json +5 -0
  200. package/examples/rate-limit-auth/README.md +3 -0
  201. package/examples/rate-limit-auth/app.js +14 -0
  202. package/examples/rate-limit-basic/README.md +3 -0
  203. package/examples/rate-limit-basic/app.js +13 -0
  204. package/examples/rate-limit-redis/README.md +5 -0
  205. package/examples/rate-limit-redis/app.js +20 -0
  206. package/examples/rate-limit-testhost/README.md +4 -0
  207. package/examples/rate-limit-testhost/app.js +13 -0
  208. package/examples/rate-limit-websocket/README.md +3 -0
  209. package/examples/rate-limit-websocket/app.js +16 -0
  210. package/examples/realtime-auth/README.md +8 -0
  211. package/examples/realtime-auth/app.js +25 -0
  212. package/examples/realtime-auth/test.mjs +43 -0
  213. package/examples/realtime-chat/README.md +8 -0
  214. package/examples/realtime-chat/app.js +32 -0
  215. package/examples/realtime-chat/test.mjs +52 -0
  216. package/examples/realtime-dashboard/README.md +20 -0
  217. package/examples/realtime-dashboard/app.js +37 -0
  218. package/examples/realtime-presence/README.md +8 -0
  219. package/examples/realtime-presence/app.js +32 -0
  220. package/examples/realtime-presence/test.mjs +50 -0
  221. package/examples/realtime-testhost/README.md +8 -0
  222. package/examples/realtime-testhost/test.mjs +31 -0
  223. package/examples/redis-basic/README.md +17 -0
  224. package/examples/redis-basic/app.js +39 -0
  225. package/examples/redis-cache/README.md +14 -0
  226. package/examples/redis-cache/app.js +36 -0
  227. package/examples/redis-locks/README.md +13 -0
  228. package/examples/redis-locks/app.js +49 -0
  229. package/examples/request-context/README.md +32 -0
  230. package/examples/request-context/app.js +15 -0
  231. package/examples/sqlite-basic/README.md +52 -0
  232. package/examples/sqlite-basic/app.js +56 -0
  233. package/examples/sqlserver-basic/README.md +36 -0
  234. package/examples/sqlserver-basic/app.js +59 -0
  235. package/examples/static-files-basic/README.md +11 -0
  236. package/examples/static-files-basic/app.js +12 -0
  237. package/examples/static-files-basic/public/app.js +1 -0
  238. package/examples/static-files-basic/public/site.css +3 -0
  239. package/examples/static-files-package/README.md +12 -0
  240. package/examples/static-files-package/app.js +10 -0
  241. package/examples/static-files-package/public/index.html +2 -0
  242. package/examples/static-files-precompressed/README.md +12 -0
  243. package/examples/static-files-precompressed/app.js +11 -0
  244. package/examples/static-files-precompressed/public/app.js +1 -0
  245. package/examples/static-files-precompressed/public/app.js.br +0 -0
  246. package/examples/static-files-precompressed/public/app.js.gz +0 -0
  247. package/examples/static-files-spa/README.md +12 -0
  248. package/examples/static-files-spa/app.js +16 -0
  249. package/examples/static-files-spa/dist/assets/app.js +1 -0
  250. package/examples/static-files-spa/dist/index.html +4 -0
  251. package/examples/static-files-testhost/README.md +8 -0
  252. package/examples/static-files-testhost/app.js +13 -0
  253. package/examples/static-files-testhost/public/app.js +1 -0
  254. package/examples/static-files-testhost/public/app.js.gz +0 -0
  255. package/examples/static-files-testhost/test.mjs +38 -0
  256. package/examples/testhost-basic/README.md +26 -0
  257. package/examples/testhost-db/README.md +31 -0
  258. package/examples/testservices-postgres/README.md +68 -0
  259. package/examples/testservices-redis/README.md +71 -0
  260. package/examples/testservices-sqlserver/README.md +75 -0
  261. package/examples/time-basic/README.md +18 -0
  262. package/examples/time-basic/app.js +12 -0
  263. package/examples/time-deadline-cancellation/README.md +11 -0
  264. package/examples/time-deadline-cancellation/app.js +27 -0
  265. package/examples/time-fake-clock/README.md +14 -0
  266. package/examples/time-fake-clock/app.js +25 -0
  267. package/examples/time-interval-schedule/README.md +13 -0
  268. package/examples/time-interval-schedule/app.js +60 -0
  269. package/examples/users-api-sqlite/README.md +74 -0
  270. package/examples/users-api-sqlite/app.js +11 -0
  271. package/examples/users-api-sqlite/appsettings.Development.json +11 -0
  272. package/examples/users-api-sqlite/appsettings.json +11 -0
  273. package/examples/users-api-sqlite/modules/users.js +40 -0
  274. package/examples/users-api-sqlite/sloppy.json +5 -0
  275. package/examples/validation-errors/README.md +36 -0
  276. package/examples/validation-errors/app.js +14 -0
  277. package/examples/validation-errors/invalid-user.http +6 -0
  278. package/examples/validation-errors/sloppy.json +5 -0
  279. package/examples/web-dynamic-routes/README.md +17 -0
  280. package/examples/web-dynamic-routes/app.ts +27 -0
  281. package/examples/webhooks-basic/README.md +11 -0
  282. package/examples/webhooks-basic/app.js +48 -0
  283. package/examples/websocket-auth/README.md +8 -0
  284. package/examples/websocket-auth/app.js +16 -0
  285. package/examples/websocket-echo/README.md +9 -0
  286. package/examples/websocket-echo/app.js +36 -0
  287. package/examples/websocket-json-schema/README.md +5 -0
  288. package/examples/websocket-json-schema/app.js +25 -0
  289. package/examples/websocket-testhost/README.md +11 -0
  290. package/examples/websocket-testhost/test.mjs +49 -0
  291. package/examples/workers-background-service/README.md +7 -0
  292. package/examples/workers-background-service/app.js +16 -0
  293. package/examples/workers-js-isolate/README.md +8 -0
  294. package/examples/workers-js-isolate/app.js +19 -0
  295. package/examples/workers-js-isolate/workers/parser.ts +11 -0
  296. package/examples/workers-shutdown/README.md +6 -0
  297. package/examples/workers-shutdown/app.js +26 -0
  298. package/examples/workers-workerpool/README.md +6 -0
  299. package/examples/workers-workerpool/app.js +23 -0
  300. package/examples/workers-workqueue/README.md +8 -0
  301. package/examples/workers-workqueue/app.js +24 -0
  302. package/manifest.json +59 -0
  303. package/package.json +34 -0
  304. package/stdlib/sloppy/README.md +177 -0
  305. package/stdlib/sloppy/app.js +2142 -0
  306. package/stdlib/sloppy/auth.js +1813 -0
  307. package/stdlib/sloppy/bootstrap.manifest.json +83 -0
  308. package/stdlib/sloppy/cache.js +1542 -0
  309. package/stdlib/sloppy/codec.js +1153 -0
  310. package/stdlib/sloppy/config.js +61 -0
  311. package/stdlib/sloppy/crypto.js +312 -0
  312. package/stdlib/sloppy/data.js +2945 -0
  313. package/stdlib/sloppy/ffi.js +185 -0
  314. package/stdlib/sloppy/fs.js +795 -0
  315. package/stdlib/sloppy/health.js +603 -0
  316. package/stdlib/sloppy/http.js +1595 -0
  317. package/stdlib/sloppy/index.js +59 -0
  318. package/stdlib/sloppy/internal/bytes.js +31 -0
  319. package/stdlib/sloppy/internal/capabilities.js +155 -0
  320. package/stdlib/sloppy/internal/config.js +640 -0
  321. package/stdlib/sloppy/internal/disposable.js +31 -0
  322. package/stdlib/sloppy/internal/headers.js +63 -0
  323. package/stdlib/sloppy/internal/intrinsics.js +2 -0
  324. package/stdlib/sloppy/internal/json.js +20 -0
  325. package/stdlib/sloppy/internal/logging.js +278 -0
  326. package/stdlib/sloppy/internal/modules.js +405 -0
  327. package/stdlib/sloppy/internal/redaction.js +87 -0
  328. package/stdlib/sloppy/internal/routes.js +2279 -0
  329. package/stdlib/sloppy/internal/runtime-classic.js +19837 -0
  330. package/stdlib/sloppy/internal/services.js +690 -0
  331. package/stdlib/sloppy/internal/shared.js +32 -0
  332. package/stdlib/sloppy/internal/testhost-diagnostics.js +88 -0
  333. package/stdlib/sloppy/internal/testhost-http-server.js +238 -0
  334. package/stdlib/sloppy/internal/testhost-http.js +118 -0
  335. package/stdlib/sloppy/internal/testhost-loopback.js +50 -0
  336. package/stdlib/sloppy/internal/testservices-docker.js +154 -0
  337. package/stdlib/sloppy/internal/validation.js +117 -0
  338. package/stdlib/sloppy/metrics.js +427 -0
  339. package/stdlib/sloppy/net.js +5208 -0
  340. package/stdlib/sloppy/node/assert/strict.js +39 -0
  341. package/stdlib/sloppy/node/assert.js +228 -0
  342. package/stdlib/sloppy/node/buffer.js +247 -0
  343. package/stdlib/sloppy/node/console.js +33 -0
  344. package/stdlib/sloppy/node/constants.js +9 -0
  345. package/stdlib/sloppy/node/crypto.js +89 -0
  346. package/stdlib/sloppy/node/diagnostics_channel.js +41 -0
  347. package/stdlib/sloppy/node/events.js +113 -0
  348. package/stdlib/sloppy/node/fs/promises.js +27 -0
  349. package/stdlib/sloppy/node/fs.js +280 -0
  350. package/stdlib/sloppy/node/http.js +11 -0
  351. package/stdlib/sloppy/node/https.js +11 -0
  352. package/stdlib/sloppy/node/module.js +40 -0
  353. package/stdlib/sloppy/node/os.js +22 -0
  354. package/stdlib/sloppy/node/path.js +78 -0
  355. package/stdlib/sloppy/node/perf_hooks.js +12 -0
  356. package/stdlib/sloppy/node/process.js +129 -0
  357. package/stdlib/sloppy/node/querystring.js +21 -0
  358. package/stdlib/sloppy/node/stream/promises.js +3 -0
  359. package/stdlib/sloppy/node/stream.js +132 -0
  360. package/stdlib/sloppy/node/string_decoder.js +23 -0
  361. package/stdlib/sloppy/node/timers.js +26 -0
  362. package/stdlib/sloppy/node/tty.js +18 -0
  363. package/stdlib/sloppy/node/url.js +17 -0
  364. package/stdlib/sloppy/node/util.js +95 -0
  365. package/stdlib/sloppy/node/zlib.js +72 -0
  366. package/stdlib/sloppy/orm.js +2188 -0
  367. package/stdlib/sloppy/os.js +580 -0
  368. package/stdlib/sloppy/problem-details.js +29 -0
  369. package/stdlib/sloppy/providers/sqlite.js +26 -0
  370. package/stdlib/sloppy/rate-limit.js +856 -0
  371. package/stdlib/sloppy/realtime.js +1508 -0
  372. package/stdlib/sloppy/redis.js +1272 -0
  373. package/stdlib/sloppy/request-id.js +184 -0
  374. package/stdlib/sloppy/request-logging.js +101 -0
  375. package/stdlib/sloppy/results.js +933 -0
  376. package/stdlib/sloppy/schema.js +546 -0
  377. package/stdlib/sloppy/testing.js +4081 -0
  378. package/stdlib/sloppy/testservices.js +1041 -0
  379. package/stdlib/sloppy/time.js +894 -0
  380. package/stdlib/sloppy/webhooks.js +1330 -0
  381. package/stdlib/sloppy/workers.js +986 -0
  382. package/templates/api/README.md +82 -0
  383. package/templates/api/appsettings.Development.json +14 -0
  384. package/templates/api/appsettings.json +13 -0
  385. package/templates/api/data/.gitkeep +1 -0
  386. package/templates/api/gitignore +4 -0
  387. package/templates/api/migrations/0001_create_users.sql +1 -0
  388. package/templates/api/package.json +16 -0
  389. package/templates/api/public/hello.txt +1 -0
  390. package/templates/api/sloppy.json +14 -0
  391. package/templates/api/src/config.ts +1 -0
  392. package/templates/api/src/db/migrate.ts +14 -0
  393. package/templates/api/src/db/schema.ts +4 -0
  394. package/templates/api/src/db/usersRepository.ts +23 -0
  395. package/templates/api/src/main.ts +18 -0
  396. package/templates/api/src/models/user.ts +7 -0
  397. package/templates/api/src/routes/health.ts +20 -0
  398. package/templates/api/src/routes/users.ts +40 -0
  399. package/templates/api/src/services/usersService.ts +21 -0
  400. package/templates/api/tsconfig.json +15 -0
  401. package/templates/cli/README.md +16 -0
  402. package/templates/cli/gitignore +2 -0
  403. package/templates/cli/package.json +13 -0
  404. package/templates/cli/sloppy.json +6 -0
  405. package/templates/cli/src/commands/echo.ts +9 -0
  406. package/templates/cli/src/commands/inspect.ts +20 -0
  407. package/templates/cli/src/main.ts +50 -0
  408. package/templates/cli/tsconfig.json +15 -0
  409. package/templates/minimal-api/README.md +14 -0
  410. package/templates/minimal-api/gitignore +3 -0
  411. package/templates/minimal-api/package.json +14 -0
  412. package/templates/minimal-api/sloppy.json +5 -0
  413. package/templates/minimal-api/src/main.ts +9 -0
  414. package/templates/minimal-api/tsconfig.json +15 -0
  415. package/templates/node-compat/README.md +40 -0
  416. package/templates/node-compat/gitignore +2 -0
  417. package/templates/node-compat/package.json +11 -0
  418. package/templates/node-compat/sloppy.json +6 -0
  419. package/templates/node-compat/src/main.ts +40 -0
  420. package/templates/package-api/README.md +44 -0
  421. package/templates/package-api/fixtures/validator-lite/index.js +7 -0
  422. package/templates/package-api/fixtures/validator-lite/package.json +6 -0
  423. package/templates/package-api/gitignore +3 -0
  424. package/templates/package-api/package.json +17 -0
  425. package/templates/package-api/sloppy.json +5 -0
  426. package/templates/package-api/src/main.ts +10 -0
  427. package/templates/package-api/src/routes/health.ts +5 -0
  428. package/templates/package-api/src/routes/users.ts +12 -0
  429. package/templates/package-api/tsconfig.json +15 -0
  430. package/templates/program/README.md +12 -0
  431. package/templates/program/gitignore +1 -0
  432. package/templates/program/package.json +10 -0
  433. package/templates/program/sloppy.json +6 -0
  434. package/templates/program/src/main.ts +9 -0
@@ -0,0 +1,32 @@
1
+ function isPlainObject(value) {
2
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
3
+ return false;
4
+ }
5
+
6
+ const prototype = Object.getPrototypeOf(value);
7
+ return prototype === Object.prototype || prototype === null;
8
+ }
9
+
10
+ function createMutationGuard(subject) {
11
+ let frozen = false;
12
+
13
+ return Object.freeze({
14
+ assertMutable() {
15
+ if (frozen) {
16
+ throw new Error(`Sloppy ${subject} is frozen and cannot be modified.`);
17
+ }
18
+ },
19
+ freeze() {
20
+ frozen = true;
21
+ },
22
+ isFrozen() {
23
+ return frozen;
24
+ },
25
+ });
26
+ }
27
+
28
+ function isPromiseLike(value) {
29
+ return value !== null && typeof value === "object" && typeof value.then === "function";
30
+ }
31
+
32
+ export { createMutationGuard, isPlainObject, isPromiseLike };
@@ -0,0 +1,88 @@
1
+ import { redactObject, redactTextSecrets } from "./redaction.js";
2
+ import { isPlainObject, requirePlainObject } from "./validation.js";
3
+
4
+ function redactConfiguredSecrets(value, secretTexts) {
5
+ return redactTextSecrets(value, secretTexts);
6
+ }
7
+
8
+ function redactedValue(key, value, secretTexts = []) {
9
+ return redactObject({ [key]: value }, {
10
+ secrets: secretTexts,
11
+ redactText: (text) => redactConfiguredSecrets(text, secretTexts),
12
+ stringifyPrimitives: true,
13
+ })[key];
14
+ }
15
+
16
+ export function normalizeOverrideMap(value, subject) {
17
+ if (value === undefined) {
18
+ return Object.freeze({});
19
+ }
20
+ requirePlainObject(value, `Sloppy TestHost ${subject} overrides must be a plain object.`);
21
+ return Object.freeze({ ...value });
22
+ }
23
+
24
+ export function createDiagnosticsStore(secrets = []) {
25
+ const records = [];
26
+ const secretTexts = secrets
27
+ .filter((value) => typeof value === "string" && value.length > 0)
28
+ .map((value) => String(value));
29
+
30
+ function sanitizeRecord(record) {
31
+ const fields = {};
32
+ for (const [key, value] of Object.entries(record.fields ?? {})) {
33
+ fields[key] = redactedValue(key, value, secretTexts);
34
+ }
35
+ return Object.freeze({
36
+ code: String(record.code),
37
+ subsystem: record.subsystem ?? "testhost",
38
+ severity: record.severity ?? "info",
39
+ message: redactConfiguredSecrets(record.message ?? record.code, secretTexts),
40
+ fields: Object.freeze(fields),
41
+ });
42
+ }
43
+
44
+ const diagnostics = {
45
+ record(record) {
46
+ records.push(sanitizeRecord(record));
47
+ },
48
+ snapshot() {
49
+ return Object.freeze([...records]);
50
+ },
51
+ latest() {
52
+ return records.at(-1);
53
+ },
54
+ filter(criteria = {}) {
55
+ if (!isPlainObject(criteria)) {
56
+ throw new TypeError("Sloppy TestHost diagnostics filter criteria must be a plain object.");
57
+ }
58
+ return Object.freeze(records.filter((record) => {
59
+ if (criteria.code !== undefined && record.code !== criteria.code) {
60
+ return false;
61
+ }
62
+ if (criteria.subsystem !== undefined && record.subsystem !== criteria.subsystem) {
63
+ return false;
64
+ }
65
+ if (criteria.severity !== undefined && record.severity !== criteria.severity) {
66
+ return false;
67
+ }
68
+ return true;
69
+ }));
70
+ },
71
+ expectCode(code) {
72
+ if (!records.some((record) => record.code === code)) {
73
+ throw new Error(`Sloppy TestHost expected diagnostic code '${code}'.`);
74
+ }
75
+ return diagnostics;
76
+ },
77
+ expectNoSecretLeaks() {
78
+ const text = JSON.stringify(records);
79
+ for (const secret of secretTexts) {
80
+ if (text.includes(secret)) {
81
+ throw new Error("Sloppy TestHost diagnostics leaked a configured secret value.");
82
+ }
83
+ }
84
+ return diagnostics;
85
+ },
86
+ };
87
+ return Object.freeze(diagnostics);
88
+ }
@@ -0,0 +1,238 @@
1
+ import { Text } from "../codec.js";
2
+ import { createHeaderLookup } from "./headers.js";
3
+ import { loopbackAuthority, reserveLoopbackPort } from "./testhost-loopback.js";
4
+
5
+ const PROBLEM_CONTENT_TYPE = "application/problem+json; charset=utf-8";
6
+
7
+ function statusText(status) {
8
+ const titles = {
9
+ 200: "OK",
10
+ 201: "Created",
11
+ 202: "Accepted",
12
+ 204: "No Content",
13
+ 400: "Bad Request",
14
+ 404: "Not Found",
15
+ 500: "Internal Server Error",
16
+ 503: "Service Unavailable",
17
+ };
18
+ return titles[status] ?? "Mock Response";
19
+ }
20
+
21
+ function concatBytes(parts) {
22
+ const total = parts.reduce((sum, part) => sum + part.byteLength, 0);
23
+ const result = new Uint8Array(total);
24
+ let offset = 0;
25
+ for (const part of parts) {
26
+ result.set(part, offset);
27
+ offset += part.byteLength;
28
+ }
29
+ return result;
30
+ }
31
+
32
+ function indexOfBytes(buffer, needle) {
33
+ outer:
34
+ for (let index = 0; index <= buffer.byteLength - needle.byteLength; index += 1) {
35
+ for (let offset = 0; offset < needle.byteLength; offset += 1) {
36
+ if (buffer[index + offset] !== needle[offset]) {
37
+ continue outer;
38
+ }
39
+ }
40
+ return index;
41
+ }
42
+ return -1;
43
+ }
44
+
45
+ async function readMockServerRequest(connection, options = {}) {
46
+ const chunks = [];
47
+ const headerEndBytes = Text.utf8.encode("\r\n\r\n");
48
+ const maxHeaderBytes = options.maxHeaderBytes ?? 64 * 1024;
49
+ let combined = new Uint8Array(0);
50
+ let headerEnd = -1;
51
+ while (headerEnd < 0) {
52
+ const chunk = await connection.read(8192);
53
+ if (chunk.byteLength === 0) {
54
+ throw new Error("Mock HTTP server received a closed connection before request headers.");
55
+ }
56
+ chunks.push(chunk);
57
+ combined = concatBytes(chunks);
58
+ if (combined.byteLength > maxHeaderBytes) {
59
+ throw new Error("Mock HTTP server request headers exceeded the configured limit.");
60
+ }
61
+ headerEnd = indexOfBytes(combined, headerEndBytes);
62
+ }
63
+ const head = Text.utf8.decode(combined.slice(0, headerEnd));
64
+ const lines = head.split("\r\n");
65
+ const requestLine = lines.shift() ?? "";
66
+ const requestMatch = /^([A-Z]+)\s+(\S+)\s+HTTP\/1\.[01]$/u.exec(requestLine);
67
+ if (requestMatch === null) {
68
+ throw new Error(`Mock HTTP server received an invalid request line: ${requestLine}`);
69
+ }
70
+ const headers = {};
71
+ for (const line of lines) {
72
+ const colon = line.indexOf(":");
73
+ if (colon > 0) {
74
+ headers[line.slice(0, colon).trim()] = line.slice(colon + 1).trim();
75
+ }
76
+ }
77
+ const headerLookup = createHeaderLookup(headers);
78
+ const contentLengthText = headerLookup.get("content-length") ?? "0";
79
+ if (!/^[0-9]+$/u.test(contentLengthText)) {
80
+ throw new Error("Mock HTTP server received an invalid Content-Length header.");
81
+ }
82
+ const contentLength = Number(contentLengthText);
83
+ const maxBodyBytes = options.maxBodyBytes ?? 4 * 1024 * 1024;
84
+ if (!Number.isSafeInteger(contentLength) || contentLength > maxBodyBytes) {
85
+ throw new Error("Mock HTTP server request body exceeded the configured limit.");
86
+ }
87
+ const bodyChunks = [];
88
+ const firstBodyChunk = combined.slice(headerEnd + headerEndBytes.byteLength);
89
+ let bodyBytesRead = Math.min(firstBodyChunk.byteLength, contentLength);
90
+ if (bodyBytesRead > 0) {
91
+ bodyChunks.push(firstBodyChunk.slice(0, bodyBytesRead));
92
+ }
93
+ while (bodyBytesRead < contentLength) {
94
+ const chunk = await connection.read(Math.min(8192, contentLength - bodyBytesRead));
95
+ if (chunk.byteLength === 0) {
96
+ throw new Error("Mock HTTP server connection closed before the request body finished.");
97
+ }
98
+ bodyChunks.push(chunk);
99
+ bodyBytesRead += chunk.byteLength;
100
+ }
101
+ const body = concatBytes(bodyChunks).slice(0, contentLength);
102
+ const contentType = headerLookup.get("content-type") ?? "";
103
+ const text = body.byteLength === 0 ? undefined : Text.utf8.decode(body);
104
+ const mediaType = contentType.split(";", 1)[0].trim().toLowerCase();
105
+ let json;
106
+ if (
107
+ text !== undefined &&
108
+ (mediaType === "application/json" || (mediaType.startsWith("application/") && mediaType.endsWith("+json")))
109
+ ) {
110
+ json = JSON.parse(text);
111
+ }
112
+ return Object.freeze({
113
+ method: requestMatch[1],
114
+ url: requestMatch[2],
115
+ headers,
116
+ json,
117
+ text,
118
+ bytes: body.byteLength === 0 ? undefined : body,
119
+ });
120
+ }
121
+
122
+ async function writeMockServerResponse(connection, response) {
123
+ const headers = { ...response.headers };
124
+ const headerLookup = createHeaderLookup(headers);
125
+ if (headerLookup.get("content-length") === undefined) {
126
+ headers["content-length"] = String(response.body.byteLength);
127
+ }
128
+ if (headerLookup.get("connection") === undefined) {
129
+ headers.connection = "close";
130
+ }
131
+ const head = [
132
+ `HTTP/1.1 ${response.status} ${statusText(response.status)}`,
133
+ ...Object.entries(headers).map(([name, value]) => `${name}: ${value}`),
134
+ "",
135
+ "",
136
+ ].join("\r\n");
137
+ await connection.write(Text.utf8.encode(head));
138
+ if (response.body.byteLength !== 0) {
139
+ await connection.write(response.body);
140
+ }
141
+ }
142
+
143
+ export async function startHttpMockServer(name, mock, options = {}) {
144
+ const host = options.httpMockHost ?? options.host ?? "127.0.0.1";
145
+ const reservation = await reserveLoopbackPort(host, {
146
+ portReservationAttempts: options.httpMockPortReservationAttempts ?? options.portReservationAttempts,
147
+ });
148
+ const listener = reservation.listener;
149
+ const connections = new Set();
150
+ const activeHandlers = new Set();
151
+ const timeoutWaiters = new Set();
152
+ let closed = false;
153
+
154
+ function waitForMockTimeout() {
155
+ if (closed) {
156
+ return Promise.resolve();
157
+ }
158
+ return new Promise((resolve) => {
159
+ let wake;
160
+ const timer = setTimeout(() => {
161
+ timeoutWaiters.delete(wake);
162
+ resolve();
163
+ }, options.httpMockTimeoutHoldMs ?? 60000);
164
+ wake = () => {
165
+ clearTimeout(timer);
166
+ resolve();
167
+ };
168
+ timeoutWaiters.add(wake);
169
+ });
170
+ }
171
+
172
+ async function handleConnection(connection) {
173
+ connections.add(connection);
174
+ try {
175
+ const request = await readMockServerRequest(connection, options);
176
+ const response = await mock._dispatchRaw(request);
177
+ await writeMockServerResponse(connection, response);
178
+ } catch (error) {
179
+ if (closed || error?.code === "SLOPPY_E_HTTP_TIMEOUT") {
180
+ await waitForMockTimeout();
181
+ return;
182
+ }
183
+ const body = Text.utf8.encode(JSON.stringify({
184
+ code: error?.code ?? "SLOPPY_E_HTTP_MOCK_SERVER",
185
+ message: "Outbound HTTP mock failed.",
186
+ }));
187
+ await writeMockServerResponse(connection, Object.freeze({
188
+ status: 500,
189
+ headers: Object.freeze({ "content-type": PROBLEM_CONTENT_TYPE }),
190
+ body,
191
+ })).catch(() => {});
192
+ } finally {
193
+ await connection.close().catch(() => {});
194
+ connections.delete(connection);
195
+ }
196
+ }
197
+
198
+ const serving = (async () => {
199
+ while (!closed) {
200
+ try {
201
+ const connection = await listener.accept({ timeoutMs: 250 });
202
+ const active = handleConnection(connection).finally(() => {
203
+ activeHandlers.delete(active);
204
+ });
205
+ activeHandlers.add(active);
206
+ } catch (error) {
207
+ if (closed) {
208
+ break;
209
+ }
210
+ if (/timeout/iu.test(String(error?.message ?? error))) {
211
+ continue;
212
+ }
213
+ break;
214
+ }
215
+ }
216
+ })();
217
+
218
+ return Object.freeze({
219
+ name,
220
+ baseUrl: `http://${loopbackAuthority(host, reservation.port)}`,
221
+ async close() {
222
+ if (closed) {
223
+ return;
224
+ }
225
+ closed = true;
226
+ for (const wake of [...timeoutWaiters]) {
227
+ timeoutWaiters.delete(wake);
228
+ wake();
229
+ }
230
+ await listener.abort().catch(async () => listener.close().catch(() => {}));
231
+ for (const connection of [...connections]) {
232
+ await connection.abort().catch(() => {});
233
+ }
234
+ await Promise.allSettled([...activeHandlers]);
235
+ await serving.catch(() => {});
236
+ },
237
+ });
238
+ }
@@ -0,0 +1,118 @@
1
+ import { copyUint8Array } from "./bytes.js";
2
+ import {
3
+ assertHeaderToken as assertSharedHeaderToken,
4
+ assertHeaderValue as assertSharedHeaderValue,
5
+ } from "./headers.js";
6
+ import { isPlainObject, requirePlainObject } from "./validation.js";
7
+
8
+ export function assertHeaderName(name, subject) {
9
+ assertSharedHeaderToken(name, `Sloppy test host ${subject}`);
10
+ }
11
+
12
+ export function assertHeaderValue(value, subject) {
13
+ assertSharedHeaderValue(value, `Sloppy test host ${subject}`);
14
+ }
15
+
16
+ export function copyBytes(value, subject) {
17
+ const bytes = copyUint8Array(value);
18
+ if (bytes === undefined) {
19
+ throw new TypeError(`Sloppy test host ${subject} must be a Uint8Array, ArrayBuffer, or ArrayBuffer view.`);
20
+ }
21
+ return bytes;
22
+ }
23
+
24
+ export function headerEntriesFromObject(headers, subject) {
25
+ if (headers === undefined) {
26
+ return [];
27
+ }
28
+
29
+ requirePlainObject(headers, `Sloppy test host ${subject} headers must be a plain object.`);
30
+
31
+ const entries = [];
32
+ for (const [name, value] of Object.entries(headers)) {
33
+ assertHeaderName(name, subject);
34
+ assertHeaderValue(value, subject);
35
+ entries.push([name, value]);
36
+ }
37
+ return entries;
38
+ }
39
+
40
+ export function normalizeHeaderEntries(entries) {
41
+ const normalized = [];
42
+ const indexes = new Map();
43
+ for (const [name, value] of entries) {
44
+ const lower = name.toLowerCase();
45
+ if (lower === "set-cookie") {
46
+ normalized.push([lower, value]);
47
+ continue;
48
+ }
49
+ const existingIndex = indexes.get(lower);
50
+ if (existingIndex === undefined) {
51
+ indexes.set(lower, normalized.length);
52
+ normalized.push([lower, value]);
53
+ } else {
54
+ normalized[existingIndex][1] = `${normalized[existingIndex][1]}, ${value}`;
55
+ }
56
+ }
57
+ return normalized;
58
+ }
59
+
60
+ export function createHeadersLike(entries) {
61
+ const normalized = Object.freeze(normalizeHeaderEntries(entries).map(([name, value]) => Object.freeze([name, value])));
62
+ const byName = new Map();
63
+ for (const [name, value] of normalized) {
64
+ if (!byName.has(name)) {
65
+ byName.set(name, value);
66
+ }
67
+ }
68
+ return Object.freeze({
69
+ get(name) {
70
+ if (typeof name !== "string") {
71
+ throw new TypeError("Sloppy test host headers.get name must be a string.");
72
+ }
73
+ return byName.get(name.toLowerCase());
74
+ },
75
+ entries() {
76
+ return normalized[Symbol.iterator]();
77
+ },
78
+ [Symbol.iterator]() {
79
+ return normalized[Symbol.iterator]();
80
+ },
81
+ });
82
+ }
83
+
84
+ export function headersToEntries(headers) {
85
+ if (headers === undefined || headers === null) {
86
+ return [];
87
+ }
88
+ if (typeof headers.entries === "function") {
89
+ return Array.from(headers.entries());
90
+ }
91
+ if (isPlainObject(headers)) {
92
+ return Object.entries(headers);
93
+ }
94
+ throw new TypeError("Sloppy test host headers must be response headers or a plain object.");
95
+ }
96
+
97
+ export function responseHeaderEntries(response, omitEntityHeaders = false) {
98
+ const entries = [];
99
+ for (const [name, value] of response.headers.entries()) {
100
+ const lower = name.toLowerCase();
101
+ if (omitEntityHeaders && (lower === "content-type" || lower === "content-length")) {
102
+ continue;
103
+ }
104
+ entries.push([name, value]);
105
+ }
106
+ return entries;
107
+ }
108
+
109
+ function hasHeader(entries, name) {
110
+ const lower = name.toLowerCase();
111
+ return entries.some(([current]) => current.toLowerCase() === lower);
112
+ }
113
+
114
+ export function setDefaultHeader(entries, name, value) {
115
+ if (!hasHeader(entries, name)) {
116
+ entries.push([name, value]);
117
+ }
118
+ }
@@ -0,0 +1,50 @@
1
+ import { Random } from "../crypto.js";
2
+ import { TcpListener } from "../net.js";
3
+
4
+ const LOOPBACK_PORT_MIN = 49152;
5
+ const LOOPBACK_PORT_MAX = 65535;
6
+
7
+ export function validateLoopbackPort(port) {
8
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
9
+ throw new TypeError("Sloppy TestHost loopback port must be an integer from 1 to 65535.");
10
+ }
11
+ return port;
12
+ }
13
+
14
+ function randomLoopbackPort() {
15
+ const bytes = Random.bytes(2);
16
+ const value = (bytes[0] << 8) | bytes[1];
17
+ return LOOPBACK_PORT_MIN + (value % (LOOPBACK_PORT_MAX - LOOPBACK_PORT_MIN + 1));
18
+ }
19
+
20
+ export async function reserveLoopbackPort(host, options = {}) {
21
+ if (options.port !== undefined) {
22
+ const port = validateLoopbackPort(options.port);
23
+ const listener = await TcpListener.listen({ host, port, backlog: 1 });
24
+ return { port, listener };
25
+ }
26
+ const attempts = options.portReservationAttempts ?? 64;
27
+ for (let attempt = 0; attempt < attempts; attempt += 1) {
28
+ const port = randomLoopbackPort();
29
+ try {
30
+ const listener = await TcpListener.listen({ host, port, backlog: 1 });
31
+ return { port, listener };
32
+ } catch {
33
+ // A different process can own the sampled port; try another reserved candidate.
34
+ }
35
+ }
36
+ throw new Error("Sloppy TestHost could not reserve an available loopback port.");
37
+ }
38
+
39
+ export async function releaseLoopbackReservation(reservation) {
40
+ if (reservation?.listener === undefined) {
41
+ return;
42
+ }
43
+ await reservation.listener.close().catch(async () => {
44
+ await reservation.listener.abort().catch(() => {});
45
+ });
46
+ }
47
+
48
+ export function loopbackAuthority(host, port) {
49
+ return `${String(host).includes(":") ? `[${host}]` : host}:${port}`;
50
+ }
@@ -0,0 +1,154 @@
1
+ import { Text } from "../codec.js";
2
+ import { boundedText } from "./redaction.js";
3
+ import { Process as SloppyProcess } from "../os.js";
4
+
5
+ function processOutputText(value) {
6
+ if (value === undefined || value === null) {
7
+ return "";
8
+ }
9
+ if (typeof value === "string") {
10
+ return value;
11
+ }
12
+ if (value instanceof Uint8Array) {
13
+ return Text.utf8.decode(value);
14
+ }
15
+ return String(value);
16
+ }
17
+
18
+ export class DockerCliBackend {
19
+ constructor(options = {}) {
20
+ this.command = options.command ?? "docker";
21
+ this.cwd = options.cwd;
22
+ this.env = options.env;
23
+ }
24
+
25
+ async run(args, options = {}) {
26
+ const result = await SloppyProcess.run(this.command, args, {
27
+ cwd: options.cwd ?? this.cwd,
28
+ env: options.env ?? this.env,
29
+ capture: "bytes",
30
+ timeoutMs: options.timeoutMs ?? 30000,
31
+ maxStdoutBytes: options.maxStdoutBytes ?? 1024 * 1024,
32
+ maxStderrBytes: options.maxStderrBytes ?? 1024 * 1024,
33
+ });
34
+ return Object.freeze({
35
+ exitCode: result.exitCode,
36
+ stdout: processOutputText(result.stdout),
37
+ stderr: processOutputText(result.stderr),
38
+ timedOut: result.timedOut === true,
39
+ });
40
+ }
41
+ }
42
+
43
+ export function dockerBackend(options = undefined) {
44
+ if (options?.dockerBackend !== undefined) {
45
+ return options.dockerBackend;
46
+ }
47
+ return new DockerCliBackend(options?.docker);
48
+ }
49
+
50
+ export async function dockerRunOk(backend, args, options = {}) {
51
+ const result = await backend.run(args, options);
52
+ if (result.exitCode !== 0 || result.timedOut === true) {
53
+ const stderr = boundedText(result.stderr || result.stdout);
54
+ throw new Error(`docker ${args[0]} failed with exit code ${result.exitCode}.${stderr.length === 0 ? "" : `\n${stderr}`}`);
55
+ }
56
+ return result;
57
+ }
58
+
59
+ export function dockerUnavailableError(reason) {
60
+ const error = new Error(`SLOPPY_E_TESTSERVICES_DOCKER_UNAVAILABLE: Docker is unavailable for Sloppy TestServices.
61
+
62
+ Reason:
63
+ ${reason}
64
+
65
+ Fix:
66
+ Start Docker Desktop or a compatible Docker daemon, ensure the docker CLI is on PATH, then rerun the opt-in TestServices lane.`);
67
+ error.code = "SLOPPY_E_TESTSERVICES_DOCKER_UNAVAILABLE";
68
+ return error;
69
+ }
70
+
71
+ export async function dockerAvailable(options = {}) {
72
+ const backend = dockerBackend(options);
73
+ try {
74
+ const result = await backend.run(["version", "--format", "{{json .}}"], {
75
+ timeoutMs: options.timeoutMs ?? 5000,
76
+ maxStdoutBytes: 64 * 1024,
77
+ maxStderrBytes: 64 * 1024,
78
+ });
79
+ if (result.exitCode !== 0 || result.timedOut === true) {
80
+ const reason = result.timedOut === true
81
+ ? "docker version timed out"
82
+ : boundedText(result.stderr || result.stdout || "docker version failed", 1000);
83
+ return Object.freeze({ ok: false, available: false, reason });
84
+ }
85
+ let version = undefined;
86
+ try {
87
+ version = JSON.parse(result.stdout);
88
+ } catch {
89
+ version = result.stdout.trim();
90
+ }
91
+ return Object.freeze({ ok: true, available: true, reason: undefined, version });
92
+ } catch (error) {
93
+ return Object.freeze({
94
+ ok: false,
95
+ available: false,
96
+ reason: String(error?.message ?? error),
97
+ });
98
+ }
99
+ }
100
+
101
+ export async function dockerRequire(options = {}) {
102
+ const available = await dockerAvailable(options);
103
+ if (available.ok) {
104
+ return available;
105
+ }
106
+ throw dockerUnavailableError(available.reason);
107
+ }
108
+
109
+ export async function ensureImage(backend, image, options) {
110
+ const inspect = await backend.run(["image", "inspect", image], {
111
+ timeoutMs: options.dockerTimeoutMs ?? 15000,
112
+ maxStdoutBytes: 64 * 1024,
113
+ maxStderrBytes: 64 * 1024,
114
+ });
115
+ if (inspect.exitCode === 0) {
116
+ return;
117
+ }
118
+ await dockerRunOk(backend, ["pull", image], {
119
+ timeoutMs: options.pullTimeoutMs ?? 120000,
120
+ maxStdoutBytes: 256 * 1024,
121
+ maxStderrBytes: 256 * 1024,
122
+ });
123
+ }
124
+
125
+ export function parseInspectJson(text) {
126
+ const parsed = JSON.parse(text);
127
+ if (!Array.isArray(parsed) || parsed.length === 0 || parsed[0] === null) {
128
+ throw new Error("docker inspect returned no container metadata.");
129
+ }
130
+ return parsed[0];
131
+ }
132
+
133
+ export function mappedPortFromInspect(metadata, internalPort) {
134
+ const ports = metadata?.NetworkSettings?.Ports;
135
+ const entries = ports?.[`${internalPort}/tcp`];
136
+ if (!Array.isArray(entries) || entries.length === 0) {
137
+ throw new Error(`docker inspect did not report a mapped host port for ${internalPort}/tcp.`);
138
+ }
139
+ const hostPort = Number(entries[0].HostPort);
140
+ if (!Number.isInteger(hostPort) || hostPort < 1 || hostPort > 65535) {
141
+ throw new Error(`docker inspect returned an invalid host port for ${internalPort}/tcp.`);
142
+ }
143
+ return hostPort;
144
+ }
145
+
146
+ export async function inspectContainer(backend, containerId, internalPort, options) {
147
+ const result = await dockerRunOk(backend, ["inspect", containerId], {
148
+ timeoutMs: options.dockerTimeoutMs ?? 15000,
149
+ maxStdoutBytes: 256 * 1024,
150
+ maxStderrBytes: 64 * 1024,
151
+ });
152
+ const metadata = parseInspectJson(result.stdout);
153
+ return { metadata, port: mappedPortFromInspect(metadata, internalPort) };
154
+ }