okengine 0.2.6 → 0.3.2

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 (681) hide show
  1. package/AGENTS.md +33 -30
  2. package/README.md +74 -340
  3. package/{spec/manifest.v1.schema.json → manifest.v1.schema.json} +40 -9
  4. package/package.json +47 -44
  5. package/site/content/docs/ai/llms-txt.mdx +54 -0
  6. package/site/content/docs/ai/mcp.mdx +123 -0
  7. package/site/content/docs/ai/meta.json +5 -0
  8. package/site/content/docs/ai/skills.mdx +53 -0
  9. package/site/content/docs/console/access.mdx +29 -0
  10. package/site/content/docs/console/ai.mdx +35 -0
  11. package/site/content/docs/console/architecture.mdx +35 -0
  12. package/site/content/docs/console/channels.mdx +37 -0
  13. package/site/content/docs/console/clock.mdx +31 -0
  14. package/site/content/docs/console/flows.mdx +31 -0
  15. package/site/content/docs/console/gates.mdx +35 -0
  16. package/site/content/docs/console/manifest-diff.mdx +34 -0
  17. package/site/content/docs/console/meta.json +23 -0
  18. package/site/content/docs/console/overview.mdx +40 -0
  19. package/site/content/docs/console/plugins.mdx +41 -0
  20. package/site/content/docs/console/privacy.mdx +32 -0
  21. package/site/content/docs/console/runs.mdx +40 -0
  22. package/site/content/docs/console/signals.mdx +31 -0
  23. package/site/content/docs/console/store.mdx +32 -0
  24. package/site/content/docs/console/tenancy.mdx +32 -0
  25. package/site/content/docs/console/traces.mdx +34 -0
  26. package/site/content/docs/console/vault.mdx +37 -0
  27. package/site/content/docs/elements/ai.mdx +180 -0
  28. package/site/content/docs/elements/channel.mdx +167 -0
  29. package/site/content/docs/elements/clock.mdx +182 -0
  30. package/site/content/docs/elements/flow.mdx +288 -0
  31. package/site/content/docs/elements/gate.mdx +171 -0
  32. package/site/content/docs/elements/meta.json +5 -0
  33. package/site/content/docs/elements/signal.mdx +171 -0
  34. package/site/content/docs/elements/store.mdx +320 -0
  35. package/site/content/docs/elements/vault.mdx +263 -0
  36. package/site/content/docs/get-started/basic-usage.mdx +124 -0
  37. package/site/content/docs/get-started/comparison.mdx +65 -0
  38. package/site/content/docs/get-started/installation.mdx +113 -0
  39. package/site/content/docs/get-started/introduction.mdx +123 -0
  40. package/site/content/docs/get-started/meta.json +5 -0
  41. package/site/content/docs/index.mdx +63 -0
  42. package/site/content/docs/meta.json +5 -0
  43. package/site/content/docs/plugins/compression.mdx +60 -0
  44. package/site/content/docs/plugins/cors.mdx +92 -0
  45. package/site/content/docs/plugins/csrf.mdx +96 -0
  46. package/site/content/docs/plugins/ip-allowlist.mdx +92 -0
  47. package/site/content/docs/plugins/maintenance-mode.mdx +101 -0
  48. package/site/content/docs/plugins/meta.json +15 -0
  49. package/site/content/docs/plugins/security-headers.mdx +136 -0
  50. package/site/content/docs/reference/cli.md +101 -0
  51. package/site/content/docs/reference/configuration.mdx +159 -0
  52. package/site/content/docs/reference/environment-variables.mdx +87 -0
  53. package/site/content/docs/reference/errors.mdx +80 -0
  54. package/site/content/docs/reference/fx.mdx +117 -0
  55. package/site/content/docs/reference/meta.json +5 -0
  56. package/site/content/docs/reference/plugins.mdx +249 -0
  57. package/site/content/docs/reference/security.md +63 -0
  58. package/src/auth/api-keys.ts +2 -5
  59. package/src/auth/attenuation.ts +2 -6
  60. package/src/auth/auth.test.ts +56 -31
  61. package/src/auth/invoke-as.ts +1 -3
  62. package/src/auth/operator.ts +8 -26
  63. package/src/auth/planes.ts +2 -7
  64. package/src/auth/plugin.ts +4 -7
  65. package/src/auth/roles.ts +1 -5
  66. package/src/auth/sessions.ts +10 -39
  67. package/src/cli/args.ts +1 -5
  68. package/src/cli/ask-dev-mode.ts +40 -0
  69. package/src/cli/build.ts +1 -2
  70. package/src/cli/client-add.test.ts +8 -15
  71. package/src/cli/client-add.ts +5 -14
  72. package/src/cli/completion.test.ts +1 -5
  73. package/src/cli/completion.ts +4 -17
  74. package/src/cli/db-auto-push.test.ts +64 -0
  75. package/src/cli/db-auto-push.ts +72 -0
  76. package/src/cli/db.test.ts +189 -0
  77. package/src/cli/db.ts +456 -0
  78. package/src/cli/dev-app-runner.ts +2 -8
  79. package/src/cli/dev-db-push.test.ts +193 -0
  80. package/src/cli/dev-mode.test.ts +45 -0
  81. package/src/cli/dev-mode.ts +78 -0
  82. package/src/cli/dev-schema-sync.test.ts +66 -0
  83. package/src/cli/dev-schema-sync.ts +139 -0
  84. package/src/cli/dev.test.ts +264 -15
  85. package/src/cli/dev.ts +355 -139
  86. package/src/cli/doc-staleness.test.ts +69 -0
  87. package/src/cli/docker-cli.test.ts +27 -8
  88. package/src/cli/docker.ts +11 -3
  89. package/src/cli/doctor-diff.test.ts +1 -5
  90. package/src/cli/doctor-diff.ts +17 -46
  91. package/src/cli/doctor-pii.ts +2 -7
  92. package/src/cli/doctor.test.ts +26 -4
  93. package/src/cli/doctor.ts +35 -11
  94. package/src/cli/drizzle-env.test.ts +67 -0
  95. package/src/cli/drizzle-env.ts +78 -0
  96. package/src/cli/ensure-drizzle-config.ts +50 -0
  97. package/src/cli/eval.ts +4 -14
  98. package/src/cli/gates-list.ts +1 -4
  99. package/src/cli/hero-meta.test.ts +17 -19
  100. package/src/cli/hero-meta.ts +20 -30
  101. package/src/cli/images.ts +3 -8
  102. package/src/cli/index.ts +11 -2
  103. package/src/cli/load-config.images.test.ts +14 -14
  104. package/src/cli/load-config.ts +47 -47
  105. package/src/cli/mcp-from-console.ts +1 -4
  106. package/src/cli/mode.ts +65 -0
  107. package/src/cli/openbao-bootstrap.test.ts +147 -0
  108. package/src/cli/openbao-bootstrap.ts +280 -0
  109. package/src/cli/openbao-restart.integration.test.ts +136 -0
  110. package/src/cli/ports.test.ts +7 -14
  111. package/src/cli/ports.ts +7 -5
  112. package/src/cli/privacy-erase.ts +1 -3
  113. package/src/cli/registry.ts +75 -25
  114. package/src/cli/resolve-dev-sql-env.test.ts +48 -0
  115. package/src/cli/resolve-dev-sql-env.ts +42 -0
  116. package/src/cli/schema.ts +3 -10
  117. package/src/cli/stack.ts +8 -10
  118. package/src/cli/start.ts +4 -15
  119. package/src/cli/upgrade.ts +1 -4
  120. package/src/cli/vault-cmd.ts +63 -0
  121. package/src/client/budget-entry.ts +1 -6
  122. package/src/client/create.test.ts +6 -12
  123. package/src/client/create.ts +2 -8
  124. package/src/client/errors.ts +1 -4
  125. package/src/client/index.ts +1 -6
  126. package/src/client/notes-contract.test.ts +4 -13
  127. package/src/client/transport.ts +9 -30
  128. package/src/client/types.ts +15 -30
  129. package/src/compiler/aot.test.ts +2 -9
  130. package/src/compiler/aot.ts +1 -4
  131. package/src/compiler/differential.test.ts +3 -11
  132. package/src/compiler/dynamic.ts +1 -4
  133. package/src/compiler/effects-infer.ts +8 -34
  134. package/src/compiler/emit.ts +1 -3
  135. package/src/compiler/extract.test.ts +136 -30
  136. package/src/compiler/extract.ts +485 -157
  137. package/src/compiler/fixtures/skyport/oke.config.ts +3 -3
  138. package/src/compiler/fixtures/skyport/src/flows/bookings/index.ts +7 -3
  139. package/src/compiler/fixtures/skyport/src/flows/payments/index.ts +1 -3
  140. package/src/compiler/fixtures/skyport/src/flows/support/index.ts +4 -1
  141. package/src/compiler/fixtures/skyport.expected.json +24 -83
  142. package/src/compiler/fixtures/triggers/five-triggers.ts +1 -2
  143. package/src/compiler/http-parse.ts +2 -9
  144. package/src/compiler/index.ts +1 -5
  145. package/src/compiler/response.ts +16 -7
  146. package/src/compiler/sucrose.ts +11 -31
  147. package/src/config/define-config.test.ts +55 -34
  148. package/src/config/index.ts +177 -46
  149. package/src/config/resolve-driver.test.ts +28 -17
  150. package/src/console/budget.test.ts +1 -4
  151. package/src/console/index.ts +1 -4
  152. package/src/console/server/access.test.ts +15 -43
  153. package/src/console/server/access.ts +19 -49
  154. package/src/console/server/ai.test.ts +3 -11
  155. package/src/console/server/ai.ts +23 -46
  156. package/src/console/server/app.ts +16 -45
  157. package/src/console/server/auth-rate.test.ts +4 -12
  158. package/src/console/server/bind.ts +1 -5
  159. package/src/console/server/channels.test.ts +2 -6
  160. package/src/console/server/channels.ts +10 -35
  161. package/src/console/server/claim.ts +2 -8
  162. package/src/console/server/clock.test.ts +2 -6
  163. package/src/console/server/clock.ts +8 -26
  164. package/src/console/server/console.test.ts +1 -3
  165. package/src/console/server/diff.test.ts +5 -17
  166. package/src/console/server/diff.ts +10 -31
  167. package/src/console/server/dry-run.audit.test.ts +2 -8
  168. package/src/console/server/flows.ts +64 -233
  169. package/src/console/server/gates.test.ts +14 -50
  170. package/src/console/server/gates.ts +13 -48
  171. package/src/console/server/index.ts +2 -9
  172. package/src/console/server/live.ts +3 -12
  173. package/src/console/server/operator-db.test.ts +4 -15
  174. package/src/console/server/operator-db.ts +13 -32
  175. package/src/console/server/plugin.ts +50 -53
  176. package/src/console/server/plugins.test.ts +2 -6
  177. package/src/console/server/plugins.ts +17 -56
  178. package/src/console/server/runs-pii.ts +2 -7
  179. package/src/console/server/security.gate.test.ts +18 -40
  180. package/src/console/server/serve.ts +10 -25
  181. package/src/console/server/signals.test.ts +1 -3
  182. package/src/console/server/signals.ts +17 -40
  183. package/src/console/server/state.ts +21 -59
  184. package/src/console/server/store.test.ts +20 -22
  185. package/src/console/server/store.ts +31 -76
  186. package/src/console/server/vault.test.ts +12 -29
  187. package/src/console/server/vault.ts +44 -66
  188. package/src/console/ui/access/AccessA11yView.tsx +6 -21
  189. package/src/console/ui/access/a11y.test.tsx +3 -12
  190. package/src/console/ui/access/acknowledgement.test.ts +1 -4
  191. package/src/console/ui/access/acknowledgement.ts +1 -3
  192. package/src/console/ui/access/blast-radius.ts +1 -3
  193. package/src/console/ui/access/confirmation.test.ts +1 -5
  194. package/src/console/ui/access/confirmation.ts +1 -4
  195. package/src/console/ui/access/fixture.ts +1 -5
  196. package/src/console/ui/access/grantable.test.ts +1 -3
  197. package/src/console/ui/access/grantable.ts +2 -7
  198. package/src/console/ui/access/index.ts +2 -9
  199. package/src/console/ui/access/provenance.test.ts +1 -3
  200. package/src/console/ui/access/provenance.ts +2 -6
  201. package/src/console/ui/access/search.test.ts +1 -5
  202. package/src/console/ui/access/search.ts +2 -6
  203. package/src/console/ui/ai/AiA11yView.tsx +6 -17
  204. package/src/console/ui/ai/a11y.test.tsx +2 -5
  205. package/src/console/ui/ai/fixture.ts +2 -7
  206. package/src/console/ui/ai/format.ts +1 -3
  207. package/src/console/ui/ai/group.test.ts +1 -3
  208. package/src/console/ui/ai/group.ts +4 -12
  209. package/src/console/ui/ai/index.ts +2 -9
  210. package/src/console/ui/ai/promotion.test.ts +2 -7
  211. package/src/console/ui/ai/promotion.ts +4 -13
  212. package/src/console/ui/ai/search.test.ts +2 -11
  213. package/src/console/ui/ai/search.ts +3 -13
  214. package/src/console/ui/ai/types.ts +1 -8
  215. package/src/console/ui/architecture/ArchitectureA11yView.tsx +8 -29
  216. package/src/console/ui/architecture/a11y.test.tsx +1 -2
  217. package/src/console/ui/architecture/declared.ts +1 -5
  218. package/src/console/ui/architecture/index.ts +7 -35
  219. package/src/console/ui/architecture/layout.ts +1 -3
  220. package/src/console/ui/architecture/pathologies.ts +10 -19
  221. package/src/console/ui/architecture/search.ts +6 -15
  222. package/src/console/ui/architecture/traffic.ts +4 -14
  223. package/src/console/ui/architecture/types.ts +2 -10
  224. package/src/console/ui/architecture/view.test.ts +7 -19
  225. package/src/console/ui/architecture/view.ts +9 -36
  226. package/src/console/ui/channels/ChannelsA11yView.tsx +4 -13
  227. package/src/console/ui/channels/a11y.test.tsx +1 -2
  228. package/src/console/ui/channels/confirmation.test.ts +1 -4
  229. package/src/console/ui/channels/confirmation.ts +1 -4
  230. package/src/console/ui/channels/findings.ts +1 -5
  231. package/src/console/ui/channels/index.ts +2 -8
  232. package/src/console/ui/channels/locale.test.ts +1 -3
  233. package/src/console/ui/channels/locale.ts +2 -8
  234. package/src/console/ui/channels/search.ts +4 -13
  235. package/src/console/ui/channels/taxonomy.ts +1 -3
  236. package/src/console/ui/clock/ClockA11yView.tsx +9 -26
  237. package/src/console/ui/clock/a11y.test.tsx +2 -5
  238. package/src/console/ui/clock/confirmation.test.ts +1 -3
  239. package/src/console/ui/clock/confirmation.ts +1 -4
  240. package/src/console/ui/clock/findings.ts +1 -4
  241. package/src/console/ui/clock/health.ts +2 -7
  242. package/src/console/ui/clock/index.ts +2 -10
  243. package/src/console/ui/clock/search.test.ts +1 -3
  244. package/src/console/ui/clock/search.ts +2 -6
  245. package/src/console/ui/clock/timeline.test.ts +1 -3
  246. package/src/console/ui/clock/waiting-on.test.ts +1 -5
  247. package/src/console/ui/clock/waiting-on.ts +2 -7
  248. package/src/console/ui/diff/DiffA11yView.tsx +8 -17
  249. package/src/console/ui/diff/a11y.test.tsx +2 -5
  250. package/src/console/ui/diff/group.test.ts +3 -13
  251. package/src/console/ui/diff/group.ts +2 -8
  252. package/src/console/ui/diff/index.ts +2 -8
  253. package/src/console/ui/diff/search.test.ts +1 -5
  254. package/src/console/ui/diff/search.ts +4 -16
  255. package/src/console/ui/diff/summary.ts +1 -2
  256. package/src/console/ui/dist/assets/{index-Dy4jht9P.js → index-BWo8R7NR.js} +2 -2
  257. package/src/console/ui/dist/assets/{panel-access-Dd37LU2c.js → panel-access-C0J2D-a2.js} +1 -1
  258. package/src/console/ui/dist/assets/{panel-ai-CC7LR6-J.js → panel-ai-D_m6WQI8.js} +1 -1
  259. package/src/console/ui/dist/assets/{panel-architecture-B5b3iKCz.js → panel-architecture-CKnXFyUx.js} +1 -1
  260. package/src/console/ui/dist/assets/{panel-channels-CeNjTKXp.js → panel-channels-BOmQ-onL.js} +1 -1
  261. package/src/console/ui/dist/assets/{panel-clock-DgFTLoHV.js → panel-clock-giAq0Ccv.js} +1 -1
  262. package/src/console/ui/dist/assets/{panel-diff-DxehccqB.js → panel-diff-cdonmH8c.js} +1 -1
  263. package/src/console/ui/dist/assets/{panel-flows-BtrVn-Eg.js → panel-flows-DlCU5zjA.js} +2 -2
  264. package/src/console/ui/dist/assets/{panel-gates-Z9MKRGdH.js → panel-gates-XclZxWD5.js} +1 -1
  265. package/src/console/ui/dist/assets/{panel-overview-Dt_AeXgd.js → panel-overview-BznEOTnb.js} +1 -1
  266. package/src/console/ui/dist/assets/{panel-plugins-DWd0TowH.js → panel-plugins-CcGM1g64.js} +1 -1
  267. package/src/console/ui/dist/assets/{panel-runs-DGstFHeq.js → panel-runs-CGWNHLR4.js} +1 -1
  268. package/src/console/ui/dist/assets/{panel-signals-DzEa2Fnt.js → panel-signals-CNywkdak.js} +1 -1
  269. package/src/console/ui/dist/assets/{panel-store-BJkbNgxx.js → panel-store-KmTbFHMH.js} +1 -1
  270. package/src/console/ui/dist/assets/{panel-traces-BaRVO3gM.js → panel-traces-DBLx2ilD.js} +1 -1
  271. package/src/console/ui/dist/assets/panel-vault-CEnFc0dk.js +1 -0
  272. package/src/console/ui/dist/index.html +1 -1
  273. package/src/console/ui/flows/FlowsA11yView.tsx +7 -22
  274. package/src/console/ui/flows/a11y.test.tsx +2 -8
  275. package/src/console/ui/flows/buffer.test.ts +3 -11
  276. package/src/console/ui/flows/buffer.ts +4 -11
  277. package/src/console/ui/flows/confirmation.ts +1 -3
  278. package/src/console/ui/flows/contract.test.ts +3 -7
  279. package/src/console/ui/flows/contract.ts +12 -36
  280. package/src/console/ui/flows/graph.test.ts +3 -11
  281. package/src/console/ui/flows/graph.ts +5 -15
  282. package/src/console/ui/flows/index.ts +1 -6
  283. package/src/console/ui/flows/save-as-test.ts +1 -3
  284. package/src/console/ui/flows/search.ts +8 -25
  285. package/src/console/ui/flows/tiers.ts +2 -9
  286. package/src/console/ui/gates/GatesA11yView.tsx +4 -13
  287. package/src/console/ui/gates/a11y.test.tsx +2 -5
  288. package/src/console/ui/gates/audit.ts +1 -3
  289. package/src/console/ui/gates/denial.test.ts +1 -3
  290. package/src/console/ui/gates/denial.ts +4 -12
  291. package/src/console/ui/gates/findings.ts +1 -3
  292. package/src/console/ui/gates/group.test.ts +3 -3
  293. package/src/console/ui/gates/group.ts +3 -11
  294. package/src/console/ui/gates/index.ts +2 -10
  295. package/src/console/ui/gates/search.ts +3 -10
  296. package/src/console/ui/overview/OverviewA11yView.tsx +9 -27
  297. package/src/console/ui/overview/a11y.test.tsx +1 -2
  298. package/src/console/ui/overview/busiest.ts +1 -3
  299. package/src/console/ui/overview/compose.ts +1 -13
  300. package/src/console/ui/overview/cost.ts +34 -38
  301. package/src/console/ui/overview/fixture.ts +21 -26
  302. package/src/console/ui/overview/golden.ts +4 -14
  303. package/src/console/ui/overview/index.ts +2 -9
  304. package/src/console/ui/overview/rank.test.ts +3 -10
  305. package/src/console/ui/overview/rank.ts +2 -7
  306. package/src/console/ui/overview/slo.test.ts +5 -9
  307. package/src/console/ui/overview/slo.ts +6 -23
  308. package/src/console/ui/overview/verdict.ts +3 -10
  309. package/src/console/ui/plugins/PluginsA11yView.tsx +8 -18
  310. package/src/console/ui/plugins/a11y.test.tsx +1 -2
  311. package/src/console/ui/plugins/fixture.ts +2 -4
  312. package/src/console/ui/plugins/group.test.ts +1 -3
  313. package/src/console/ui/plugins/group.ts +1 -6
  314. package/src/console/ui/plugins/index.ts +3 -13
  315. package/src/console/ui/plugins/search.ts +7 -19
  316. package/src/console/ui/runs/RunsA11yView.tsx +5 -20
  317. package/src/console/ui/runs/a11y.test.tsx +2 -5
  318. package/src/console/ui/runs/explain.ts +2 -6
  319. package/src/console/ui/runs/group.ts +3 -12
  320. package/src/console/ui/runs/histogram.test.ts +2 -11
  321. package/src/console/ui/runs/histogram.ts +1 -4
  322. package/src/console/ui/runs/index.ts +4 -18
  323. package/src/console/ui/runs/project.ts +2 -6
  324. package/src/console/ui/runs/query.test.ts +1 -3
  325. package/src/console/ui/runs/query.ts +12 -35
  326. package/src/console/ui/runs/search.ts +5 -19
  327. package/src/console/ui/runs/trace-link.test.ts +2 -6
  328. package/src/console/ui/runs/trace-link.ts +4 -16
  329. package/src/console/ui/runs/types.ts +1 -3
  330. package/src/console/ui/shell/App.tsx +2 -10
  331. package/src/console/ui/shell/client.ts +22 -115
  332. package/src/console/ui/shell/components/ui.tsx +1 -4
  333. package/src/console/ui/shell/layout/Shell.tsx +2 -4
  334. package/src/console/ui/shell/main.tsx +31 -96
  335. package/src/console/ui/shell/panels/access/AccessPanel.tsx +34 -98
  336. package/src/console/ui/shell/panels/ai/AiPanel.tsx +35 -106
  337. package/src/console/ui/shell/panels/architecture/ArchitecturePanel.tsx +18 -66
  338. package/src/console/ui/shell/panels/channels/ChannelsPanel.tsx +20 -57
  339. package/src/console/ui/shell/panels/clock/ClockPanel.tsx +22 -54
  340. package/src/console/ui/shell/panels/diff/DiffPanel.tsx +20 -61
  341. package/src/console/ui/shell/panels/flows/ContractEditor.tsx +6 -23
  342. package/src/console/ui/shell/panels/flows/FlowDrawer.tsx +13 -28
  343. package/src/console/ui/shell/panels/flows/FlowsPanel.tsx +9 -29
  344. package/src/console/ui/shell/panels/gates/GatesPanel.tsx +24 -80
  345. package/src/console/ui/shell/panels/overview/OverviewPanel.tsx +20 -58
  346. package/src/console/ui/shell/panels/plugins/PluginsPanel.tsx +16 -41
  347. package/src/console/ui/shell/panels/runs/RunsPanel.tsx +31 -99
  348. package/src/console/ui/shell/panels/signals/SignalsPanel.tsx +38 -101
  349. package/src/console/ui/shell/panels/store/StorePanel.tsx +27 -66
  350. package/src/console/ui/shell/panels/traces/TracesPanel.tsx +28 -89
  351. package/src/console/ui/shell/panels/vault/VaultPanel.tsx +22 -53
  352. package/src/console/ui/shell/setup/Wizard.tsx +3 -5
  353. package/src/console/ui/shell/styles.css +1 -2
  354. package/src/console/ui/signals/SignalsA11yView.tsx +9 -33
  355. package/src/console/ui/signals/a11y.test.tsx +2 -5
  356. package/src/console/ui/signals/confirmation.test.ts +1 -5
  357. package/src/console/ui/signals/confirmation.ts +2 -6
  358. package/src/console/ui/signals/dry-run.ts +1 -3
  359. package/src/console/ui/signals/durable.ts +1 -3
  360. package/src/console/ui/signals/findings.ts +1 -3
  361. package/src/console/ui/signals/group.test.ts +2 -8
  362. package/src/console/ui/signals/group.ts +2 -10
  363. package/src/console/ui/signals/index.ts +1 -4
  364. package/src/console/ui/signals/monitor.ts +2 -7
  365. package/src/console/ui/signals/schema-form.test.ts +3 -14
  366. package/src/console/ui/signals/schema-form.ts +1 -4
  367. package/src/console/ui/signals/search.ts +4 -13
  368. package/src/console/ui/store/StoreA11yView.tsx +3 -9
  369. package/src/console/ui/store/a11y.test.tsx +2 -5
  370. package/src/console/ui/store/confirmation.test.ts +1 -3
  371. package/src/console/ui/store/confirmation.ts +1 -4
  372. package/src/console/ui/store/dry-run.ts +2 -5
  373. package/src/console/ui/store/fixture.ts +1 -2
  374. package/src/console/ui/store/group.test.ts +1 -6
  375. package/src/console/ui/store/group.ts +1 -4
  376. package/src/console/ui/store/index.ts +1 -5
  377. package/src/console/ui/store/search.ts +2 -6
  378. package/src/console/ui/store/will-not-fire.ts +1 -2
  379. package/src/console/ui/traces/TracesA11yView.tsx +10 -29
  380. package/src/console/ui/traces/a11y.test.tsx +1 -2
  381. package/src/console/ui/traces/chain.test.ts +5 -19
  382. package/src/console/ui/traces/chain.ts +1 -3
  383. package/src/console/ui/traces/critical-path.test.ts +1 -3
  384. package/src/console/ui/traces/critical-path.ts +2 -6
  385. package/src/console/ui/traces/filter.test.ts +2 -6
  386. package/src/console/ui/traces/filter.ts +5 -13
  387. package/src/console/ui/traces/fold.test.ts +4 -12
  388. package/src/console/ui/traces/fold.ts +1 -3
  389. package/src/console/ui/traces/mini.ts +1 -3
  390. package/src/console/ui/traces/replay.test.ts +1 -3
  391. package/src/console/ui/traces/replay.ts +1 -3
  392. package/src/console/ui/traces/sampling.ts +1 -4
  393. package/src/console/ui/traces/search.ts +4 -17
  394. package/src/console/ui/traces/tier.ts +1 -3
  395. package/src/console/ui/vault/VaultA11yView.tsx +5 -17
  396. package/src/console/ui/vault/a11y.test.tsx +1 -2
  397. package/src/console/ui/vault/blast-radius.ts +1 -3
  398. package/src/console/ui/vault/confirmation.test.ts +1 -5
  399. package/src/console/ui/vault/confirmation.ts +1 -4
  400. package/src/console/ui/vault/dormant.test.ts +1 -4
  401. package/src/console/ui/vault/export-safe.test.ts +1 -5
  402. package/src/console/ui/vault/export-safe.ts +1 -4
  403. package/src/console/ui/vault/fixture.ts +3 -3
  404. package/src/console/ui/vault/group.ts +1 -2
  405. package/src/console/ui/vault/index.ts +2 -10
  406. package/src/console/ui/vault/search.ts +2 -6
  407. package/src/console/ui/vault/types.ts +1 -1
  408. package/src/console/xss.gate.test.ts +1 -3
  409. package/src/docker/compose.ts +215 -27
  410. package/src/docker/derive.ts +6 -14
  411. package/src/docker/docker.test.ts +118 -15
  412. package/src/docker/dockerfile.integration.test.ts +12 -21
  413. package/src/docker/helpers.ts +5 -4
  414. package/src/docker/index.ts +9 -6
  415. package/src/docker/pin.ts +1 -9
  416. package/src/docker/recipes/index.ts +6 -6
  417. package/src/docker/recipes/mailpit.ts +22 -0
  418. package/src/docker/recipes/openbao.ts +47 -0
  419. package/src/docker/recipes/postgres.ts +17 -6
  420. package/src/docker/recipes/redis.ts +11 -2
  421. package/src/docker/recipes/rustfs.ts +32 -0
  422. package/src/docker/stack-id.test.ts +73 -5
  423. package/src/docker/stack-id.ts +124 -33
  424. package/src/docker/stack.integration.test.ts +6 -21
  425. package/src/docker/stack.ts +36 -4
  426. package/src/docker/types.ts +16 -4
  427. package/src/docs-origin.ts +4 -4
  428. package/src/drivers/ai-anthropic.ts +5 -14
  429. package/src/drivers/ai-mock.ts +2 -5
  430. package/src/drivers/ai-openai-compatible.ts +4 -10
  431. package/src/drivers/ai-providers.test.ts +6 -13
  432. package/src/drivers/bun-native-completeness.test.ts +6 -15
  433. package/src/drivers/channel-console.ts +6 -7
  434. package/src/drivers/channel-fcm.ts +20 -30
  435. package/src/drivers/channel-resend.ts +1 -3
  436. package/src/drivers/channel-smtp.ts +2 -6
  437. package/src/drivers/channel-types.ts +2 -12
  438. package/src/drivers/channel-unifonic.ts +2 -6
  439. package/src/drivers/channel-wa-cloud.ts +2 -6
  440. package/src/drivers/channel-webpush.ts +21 -60
  441. package/src/drivers/conformance.test.ts +2 -10
  442. package/src/drivers/conformance.ts +8 -21
  443. package/src/drivers/drizzle-dialect.test.ts +20 -0
  444. package/src/drivers/drizzle-dialect.ts +37 -0
  445. package/src/drivers/fs.ts +2 -8
  446. package/src/drivers/index.ts +12 -57
  447. package/src/drivers/memory.ts +287 -75
  448. package/src/drivers/pgvector.ts +7 -11
  449. package/src/drivers/postgres.ts +7 -13
  450. package/src/drivers/redis.ts +13 -37
  451. package/src/drivers/s3.ts +15 -18
  452. package/src/drivers/signal-engine.ts +12 -49
  453. package/src/drivers/signal-memory.ts +2 -8
  454. package/src/drivers/signal-nats.ts +4 -9
  455. package/src/drivers/signal-postgres.ts +62 -129
  456. package/src/drivers/signal-redis.ts +7 -21
  457. package/src/drivers/signal-types.ts +1 -4
  458. package/src/drivers/sqlite.ts +3 -12
  459. package/src/drivers/types.ts +4 -23
  460. package/src/drivers/vault-driver-removal.test.ts +55 -0
  461. package/src/drivers/vault-env.ts +2 -6
  462. package/src/drivers/vault-infisical.ts +1 -5
  463. package/src/drivers/vault-managed.ts +1 -5
  464. package/src/drivers/vault-memory.ts +1 -5
  465. package/src/drivers/vault-openbao.test.ts +97 -0
  466. package/src/drivers/vault-openbao.ts +103 -40
  467. package/src/drivers/vault-types.ts +3 -16
  468. package/src/elements/ai/declare.ts +4 -14
  469. package/src/elements/ai/eval.ts +2 -6
  470. package/src/elements/ai/pii.ts +1 -3
  471. package/src/elements/ai/runtime.ts +12 -50
  472. package/src/elements/ai/schema.ts +4 -15
  473. package/src/elements/ai.test.ts +13 -24
  474. package/src/elements/ai.ts +1 -5
  475. package/src/elements/channel/consent.ts +3 -12
  476. package/src/elements/channel/costs.ts +4 -15
  477. package/src/elements/channel/declare.ts +1 -4
  478. package/src/elements/channel/email-auth.ts +1 -4
  479. package/src/elements/channel/locale.ts +2 -10
  480. package/src/elements/channel/mask.ts +1 -3
  481. package/src/elements/channel/mime.ts +2 -12
  482. package/src/elements/channel/outcomes.test.ts +1 -5
  483. package/src/elements/channel/outcomes.ts +5 -15
  484. package/src/elements/channel/receipts.ts +2 -7
  485. package/src/elements/channel/runtime.ts +28 -74
  486. package/src/elements/channel/suppression.ts +8 -14
  487. package/src/elements/channel.test.ts +8 -31
  488. package/src/elements/channel.ts +3 -14
  489. package/src/elements/clock/actions.ts +6 -18
  490. package/src/elements/clock/declare.ts +1 -3
  491. package/src/elements/clock/dst.ts +1 -5
  492. package/src/elements/clock/durable.ts +4 -16
  493. package/src/elements/clock/health.ts +2 -7
  494. package/src/elements/clock/leader.ts +2 -9
  495. package/src/elements/clock/reconcile.ts +4 -15
  496. package/src/elements/clock/runtime.ts +2 -5
  497. package/src/elements/clock/schedule.ts +4 -20
  498. package/src/elements/clock.test.ts +2 -7
  499. package/src/elements/clock.ts +5 -23
  500. package/src/elements/gate/permissions.ts +1 -4
  501. package/src/elements/gate/runtime.ts +7 -28
  502. package/src/elements/gate.ts +1 -5
  503. package/src/elements/index.ts +9 -7
  504. package/src/elements/signal/declare.ts +2 -7
  505. package/src/elements/signal/reconcile.test.ts +1 -4
  506. package/src/elements/signal/runtime.ts +1 -3
  507. package/src/elements/signal.test.ts +3 -15
  508. package/src/elements/signal.ts +2 -8
  509. package/src/elements/store/cache.test.ts +46 -0
  510. package/src/elements/store/cache.ts +3 -11
  511. package/src/elements/store/classify.ts +5 -22
  512. package/src/elements/store/declare.ts +11 -17
  513. package/src/elements/store/domain-ddl.test.ts +86 -0
  514. package/src/elements/store/emit-drizzle.ts +363 -0
  515. package/src/elements/store/files-policy.test.ts +1 -5
  516. package/src/elements/store/files-policy.ts +4 -12
  517. package/src/elements/store/load-plugin-tables.ts +103 -0
  518. package/src/elements/store/missing-relation.test.ts +83 -0
  519. package/src/elements/store/missing-relation.ts +38 -0
  520. package/src/elements/store/replica.ts +1 -4
  521. package/src/elements/store/resource-list-docs.fixture.ts +56 -0
  522. package/src/elements/store/resource-list-docs.test.ts +79 -0
  523. package/src/elements/store/resource.test.ts +253 -0
  524. package/src/elements/store/resource.ts +786 -0
  525. package/src/elements/store/runtime.ts +25 -28
  526. package/src/elements/store/schema-decl.test.ts +227 -0
  527. package/src/elements/store/schema-decl.ts +516 -0
  528. package/src/elements/store/sql-condition.test.ts +132 -0
  529. package/src/elements/store/sql-condition.ts +293 -65
  530. package/src/elements/store/sql-session.test.ts +135 -0
  531. package/src/elements/store/sql-session.ts +241 -84
  532. package/src/elements/store/table.ts +69 -23
  533. package/src/elements/store.test.ts +17 -39
  534. package/src/elements/store.ts +53 -31
  535. package/src/elements/vault/chain.ts +34 -0
  536. package/src/elements/vault/declare.ts +22 -32
  537. package/src/elements/vault/fingerprint.ts +2 -7
  538. package/src/elements/vault/redact.ts +2 -6
  539. package/src/elements/vault/runtime.ts +19 -33
  540. package/src/elements/vault.test.ts +12 -55
  541. package/src/elements/vault.ts +9 -13
  542. package/src/index.ts +5 -2
  543. package/src/kernel/adopt-routes.ts +15 -29
  544. package/src/kernel/app.ts +92 -118
  545. package/src/kernel/auth-resolve.ts +2 -9
  546. package/src/kernel/boot-bind/ai.ts +1 -4
  547. package/src/kernel/boot-bind/channel.test.ts +60 -0
  548. package/src/kernel/boot-bind/channel.ts +61 -5
  549. package/src/kernel/boot-bind/clock.ts +2 -6
  550. package/src/kernel/boot-bind/gate.ts +2 -8
  551. package/src/kernel/boot-bind/runs.ts +1 -3
  552. package/src/kernel/boot-bind/signal.ts +3 -10
  553. package/src/kernel/boot-bind/store.test.ts +27 -18
  554. package/src/kernel/boot-bind/store.ts +93 -52
  555. package/src/kernel/boot-bind/vault.ts +2 -8
  556. package/src/kernel/boot.test.ts +4 -10
  557. package/src/kernel/boot.ts +30 -77
  558. package/src/kernel/budget.test.ts +1 -3
  559. package/src/kernel/capability.ts +3 -10
  560. package/src/kernel/dry-run.test.ts +1 -4
  561. package/src/kernel/dry-run.ts +3 -11
  562. package/src/kernel/edge.test.ts +68 -0
  563. package/src/kernel/effects.test.ts +1 -5
  564. package/src/kernel/effects.ts +4 -11
  565. package/src/kernel/errors.registry.test.ts +2 -8
  566. package/src/kernel/errors.ts +23 -32
  567. package/src/kernel/flow.test.ts +12 -25
  568. package/src/kernel/flow.ts +13 -25
  569. package/src/kernel/fx.test.ts +31 -40
  570. package/src/kernel/fx.ts +135 -90
  571. package/src/kernel/hook-timing.test.ts +3 -6
  572. package/src/kernel/hook-timing.ts +5 -14
  573. package/src/kernel/hooks.test.ts +43 -10
  574. package/src/kernel/hooks.ts +25 -12
  575. package/src/kernel/index.ts +11 -17
  576. package/src/kernel/journal.ts +7 -31
  577. package/src/kernel/on.ts +45 -9
  578. package/src/kernel/pipeline.test.ts +10 -19
  579. package/src/kernel/pipeline.ts +5 -17
  580. package/src/kernel/plug.ts +4 -11
  581. package/src/kernel/plugin/conflicts.test.ts +13 -25
  582. package/src/kernel/plugin/decorate.test.ts +8 -13
  583. package/src/kernel/plugin/scoping.test.ts +4 -11
  584. package/src/kernel/plugin.ts +81 -27
  585. package/src/kernel/registry-isolation.test.ts +74 -0
  586. package/src/kernel/registry.ts +102 -30
  587. package/src/kernel/router.test.ts +15 -13
  588. package/src/kernel/router.ts +9 -29
  589. package/src/kernel/routing-budget.test.ts +1 -3
  590. package/src/kernel/run-telemetry.ts +1 -3
  591. package/src/kernel/triggers.ts +63 -21
  592. package/src/manifest/diff.test.ts +26 -91
  593. package/src/manifest/diff.ts +95 -225
  594. package/src/manifest/fixtures/skyport.excerpt.json +1 -1
  595. package/src/manifest/fixtures/skyport.manifest.json +1 -1
  596. package/src/manifest/index.ts +3 -10
  597. package/src/manifest/types.ts +30 -6
  598. package/src/manifest/undeclared.test.ts +4 -12
  599. package/src/manifest/undeclared.ts +1 -2
  600. package/src/manifest/validate.test.ts +2 -6
  601. package/src/manifest/validate.ts +5 -15
  602. package/src/mcp/authorization.ts +8 -27
  603. package/src/mcp/confirmation.ts +3 -10
  604. package/src/mcp/data.ts +2 -6
  605. package/src/mcp/docs-index.ts +4 -7
  606. package/src/mcp/docs-mcp.test.ts +3 -12
  607. package/src/mcp/docs-server.ts +7 -27
  608. package/src/mcp/docs-tools.ts +2 -6
  609. package/src/mcp/index.ts +1 -6
  610. package/src/mcp/injection.gate.test.ts +4 -18
  611. package/src/mcp/mcp.test.ts +18 -49
  612. package/src/mcp/protocol.ts +3 -10
  613. package/src/mcp/server.ts +11 -52
  614. package/src/mcp/session.ts +4 -14
  615. package/src/mcp/tools.ts +14 -50
  616. package/src/plugins/catalogue.test.ts +3 -7
  617. package/src/plugins/catalogue.ts +1 -3
  618. package/src/plugins/compression.test.ts +127 -0
  619. package/src/plugins/compression.ts +94 -0
  620. package/src/plugins/config-source.test.ts +204 -0
  621. package/src/plugins/config-source.ts +209 -0
  622. package/src/plugins/cors.test.ts +138 -0
  623. package/src/plugins/cors.ts +129 -0
  624. package/src/plugins/csrf.test.ts +102 -0
  625. package/src/plugins/csrf.ts +86 -0
  626. package/src/plugins/headers.ts +54 -0
  627. package/src/plugins/index.ts +26 -0
  628. package/src/plugins/ip-allowlist.test.ts +105 -0
  629. package/src/plugins/ip-allowlist.ts +76 -0
  630. package/src/plugins/maintenance-mode.test.ts +91 -0
  631. package/src/plugins/maintenance-mode.ts +85 -0
  632. package/src/plugins/node-import-scan.ts +2 -5
  633. package/src/plugins/security-headers.test.ts +243 -0
  634. package/src/plugins/security-headers.ts +255 -0
  635. package/src/plugins/supply-chain.ts +5 -17
  636. package/src/release/exports.ts +3 -8
  637. package/src/release/measure.exports.test.ts +1 -3
  638. package/src/release/measure.ts +16 -45
  639. package/src/runs/bench.test.ts +1 -3
  640. package/src/runs/collect.ts +8 -25
  641. package/src/runs/drivers/clickhouse.ts +2 -9
  642. package/src/runs/drivers/files.ts +9 -31
  643. package/src/runs/drivers/memory.ts +5 -22
  644. package/src/runs/drivers/postgres.ts +8 -20
  645. package/src/runs/duckdb.ts +2 -8
  646. package/src/runs/index.ts +3 -14
  647. package/src/runs/outlier.ts +3 -10
  648. package/src/runs/parquet.ts +8 -26
  649. package/src/runs/privacy.ts +2 -8
  650. package/src/runs/runs.test.ts +3 -9
  651. package/src/runs/runtime.ts +7 -32
  652. package/src/runs/shred.ts +10 -29
  653. package/src/runs/types.ts +1 -4
  654. package/src/runtime/bun.ts +4 -15
  655. package/src/runtime/cold-start.bench.ts +1 -3
  656. package/src/runtime/dev-request-log.test.ts +1 -4
  657. package/src/runtime/dev-request-log.ts +1 -4
  658. package/src/runtime/index.ts +1 -6
  659. package/src/runtime/primitives.ts +5 -4
  660. package/src/runtime/security.ts +3 -13
  661. package/src/runtime/serve.test.ts +4 -7
  662. package/src/runtime/types.ts +1 -4
  663. package/src/runtime/web-standard.ts +3 -15
  664. package/src/term.test.ts +4 -6
  665. package/src/term.ts +49 -98
  666. package/src/test/create-test-app.test.ts +4 -14
  667. package/src/test/create-test-app.ts +27 -33
  668. package/src/test/provisions.integration.test.ts +3 -11
  669. package/src/upgrade/codemods.test.ts +25 -6
  670. package/src/upgrade/codemods.ts +116 -3
  671. package/src/validation/standard-schema.test.ts +2 -5
  672. package/src/validation/standard-schema.ts +13 -33
  673. package/docs/spec/console.md +0 -739
  674. package/docs/spec/example.md +0 -1196
  675. package/docs/spec/four-applications.md +0 -1195
  676. package/docs/spec/unified-theory.md +0 -489
  677. package/src/cli/doc-drift.test.ts +0 -146
  678. package/src/cli/doc-drift.ts +0 -430
  679. package/src/cli/doctor-diff-examples.ts +0 -92
  680. package/src/console/ui/dist/assets/panel-vault-BbfWdox0.js +0 -1
  681. package/src/drivers/vault-sops.ts +0 -271
package/src/docker/pin.ts CHANGED
@@ -61,15 +61,7 @@ export function formatImagesLock(lock: ImagesLock): string {
61
61
  */
62
62
  async function dockerDigest(image: string): Promise<string> {
63
63
  const proc = Bun.spawn(
64
- [
65
- "docker",
66
- "buildx",
67
- "imagetools",
68
- "inspect",
69
- "--format",
70
- "{{.Manifest.Digest}}",
71
- image,
72
- ],
64
+ ["docker", "buildx", "imagetools", "inspect", "--format", "{{.Manifest.Digest}}", image],
73
65
  { stdout: "pipe", stderr: "pipe" },
74
66
  );
75
67
  const [stdout, stderr, code] = await Promise.all([
@@ -3,13 +3,16 @@
3
3
  */
4
4
 
5
5
  import type { ImageRecipe } from "../types.ts";
6
+ import { mailpit } from "./mailpit.ts";
7
+ import { openbao } from "./openbao.ts";
6
8
  import { postgres } from "./postgres.ts";
7
9
  import { redis } from "./redis.ts";
10
+ import { rustfs } from "./rustfs.ts";
8
11
 
9
12
  /** Default recipe catalogue. */
10
- export const builtinRecipes: readonly ImageRecipe[] = [postgres, redis];
13
+ export const builtinRecipes: readonly ImageRecipe[] = [postgres, redis, mailpit, rustfs, openbao];
11
14
 
12
- export { postgres, redis };
15
+ export { mailpit, openbao, postgres, redis, rustfs };
13
16
 
14
17
  /**
15
18
  * Resolve the recipe for an image reference.
@@ -17,10 +20,7 @@ export { postgres, redis };
17
20
  * @param image - Image pin
18
21
  * @param extra - Plugin / test recipes (tried first)
19
22
  */
20
- export function recipeFor(
21
- image: string,
22
- extra: readonly ImageRecipe[] = [],
23
- ): ImageRecipe {
23
+ export function recipeFor(image: string, extra: readonly ImageRecipe[] = []): ImageRecipe {
24
24
  for (const r of extra) {
25
25
  if (r.match(image)) return r;
26
26
  }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Mailpit image recipe — local SMTP catcher for docker ≈ prod email.
3
+ */
4
+
5
+ import type { ImageRecipe } from "../types.ts";
6
+
7
+ /** SMTP catcher (Mailpit) — UI on 8025. */
8
+ export const mailpit: ImageRecipe = {
9
+ id: "mailpit",
10
+ port: 1025,
11
+ match: (i) => /mailpit/i.test(i),
12
+ apply: () => ({
13
+ extraPorts: [{ host: 8025, container: 8025 }],
14
+ healthcheck: {
15
+ test: ["CMD-SHELL", "wget -q -O /dev/null http://127.0.0.1:8025/api/v1/info || exit 1"],
16
+ interval: "5s",
17
+ timeout: "3s",
18
+ retries: 10,
19
+ },
20
+ }),
21
+ url: (_s, c) => `smtp://${c.host}:${c.port}`,
22
+ };
@@ -0,0 +1,47 @@
1
+ /**
2
+ * OpenBao image recipe — durable single-node secrets service (Raft storage).
3
+ *
4
+ * Never `-dev`: data lives on a named volume under `/openbao/data`; init +
5
+ * unseal is handled by the CLI bootstrap (keys stay on the host).
6
+ */
7
+
8
+ import type { ImageRecipe } from "../types.ts";
9
+
10
+ /**
11
+ * Server config passed via `BAO_LOCAL_CONFIG` (official image). Single-node
12
+ * Raft still requires `cluster_addr` / `api_addr` per the OpenBao docs.
13
+ */
14
+ const OPENBAO_LOCAL_CONFIG = JSON.stringify({
15
+ listener: { tcp: { address: "0.0.0.0:8200", tls_disable: true } },
16
+ // Raft under /openbao/file — the stock entrypoint chowns that dir for the
17
+ // `openbao` user; a root-owned fresh named volume stays writable. Raft does
18
+ // not create nested dirs, so the path must be the chowned dir itself.
19
+ storage: { raft: { path: "/openbao/file", node_id: "oke" } },
20
+ api_addr: "http://0.0.0.0:8200",
21
+ cluster_addr: "http://127.0.0.1:8201",
22
+ disable_mlock: true,
23
+ });
24
+
25
+ /** OpenBao secrets service. API on 8200. */
26
+ export const openbao: ImageRecipe = {
27
+ id: "openbao",
28
+ port: 8200,
29
+ match: (i) => /openbao/i.test(i),
30
+ apply: (s) => ({
31
+ environment: {
32
+ BAO_LOCAL_CONFIG: OPENBAO_LOCAL_CONFIG,
33
+ },
34
+ command: ["server"],
35
+ volumes: [`${s.serviceName}-data:/openbao/file`],
36
+ healthcheck: {
37
+ // `/sys/init` answers 200 even while sealed — only proves the server
38
+ // process is up; bootstrap owns the real unseal step.
39
+ test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:8200/v1/sys/init >/dev/null 2>&1 || exit 1"],
40
+ interval: "5s",
41
+ timeout: "5s",
42
+ retries: 12,
43
+ start_period: "5s",
44
+ },
45
+ }),
46
+ url: (_s, c) => `http://${c.host}:${c.port}`,
47
+ };
@@ -3,16 +3,27 @@
3
3
  */
4
4
 
5
5
  import { credEnv } from "../helpers.ts";
6
- import type { ImageRecipe } from "../types.ts";
6
+ import type { ImageRecipe, ServiceSpec } from "../types.ts";
7
+
8
+ const postgresEnv = (s: ServiceSpec) => ({
9
+ POSTGRES_USER: credEnv(s, "USER"),
10
+ POSTGRES_PASSWORD: credEnv(s, "PASSWORD"),
11
+ POSTGRES_DB: credEnv(s, "DB"),
12
+ });
13
+
14
+ const postgresHealth = {
15
+ test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER"],
16
+ interval: "5s",
17
+ timeout: "3s",
18
+ retries: 10,
19
+ };
7
20
 
8
21
  /** Postgres-protocol servers (Postgres · Neon · Supabase · pgvector · Timescale). */
9
22
  export const postgres: ImageRecipe = {
10
23
  id: "postgres",
11
24
  port: 5432,
12
25
  match: (i) => /postgres|pgvector|timescale/i.test(i),
13
- apply: (s) => ({
14
- environment: { POSTGRES_USER: credEnv(s, "USER"), POSTGRES_PASSWORD: credEnv(s, "PASSWORD"), POSTGRES_DB: credEnv(s, "DB") },
15
- healthcheck: { test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER"], interval: "5s", timeout: "3s", retries: 10 },
16
- }),
17
- url: (_s, c) => `postgres://${c.user}:${encodeURIComponent(c.password)}@${c.host}:${c.port}/${c.database}`,
26
+ apply: (s) => ({ environment: postgresEnv(s), healthcheck: postgresHealth }),
27
+ url: (_s, c) =>
28
+ `postgres://${c.user}:${encodeURIComponent(c.password)}@${c.host}:${c.port}/${c.database}`,
18
29
  };
@@ -11,8 +11,17 @@ export const redis: ImageRecipe = {
11
11
  port: 6379,
12
12
  match: (i) => /redis|valkey|dragonfly|keydb/i.test(i),
13
13
  apply: (s) => ({
14
- command: ["redis-server", "--requirepass", credEnv(s, "PASSWORD")],
15
- healthcheck: { test: ["CMD", "redis-cli", "-a", credEnv(s, "PASSWORD"), "ping"], interval: "5s", timeout: "3s", retries: 10 },
14
+ command: [
15
+ "sh",
16
+ "-c",
17
+ 'exec redis-server --requirepass "$$OKE_STORE_KV_PASSWORD" --maxmemory "$${OKE_STORE_KV_MAXMEMORY:-0}" --maxmemory-policy "$${OKE_STORE_KV_MAXMEMORY_POLICY:-noeviction}"',
18
+ ],
19
+ healthcheck: {
20
+ test: ["CMD", "redis-cli", "-a", credEnv(s, "PASSWORD"), "ping"],
21
+ interval: "5s",
22
+ timeout: "3s",
23
+ retries: 10,
24
+ },
16
25
  }),
17
26
  url: (_s, c) => `redis://:${encodeURIComponent(c.password)}@${c.host}:${c.port}`,
18
27
  };
@@ -0,0 +1,32 @@
1
+ /**
2
+ * RustFS image recipe — S3-compatible object store (Apache 2.0 MinIO alternative).
3
+ */
4
+
5
+ import type { ImageRecipe } from "../types.ts";
6
+
7
+ /** S3-protocol object storage (RustFS). Console on 9001. */
8
+ export const rustfs: ImageRecipe = {
9
+ id: "rustfs",
10
+ port: 9000,
11
+ match: (i) => /rustfs/i.test(i),
12
+ apply: (s) => ({
13
+ environment: {
14
+ RUSTFS_ACCESS_KEY: "${S3_ACCESS_KEY_ID}",
15
+ RUSTFS_SECRET_KEY: "${S3_SECRET_ACCESS_KEY}",
16
+ RUSTFS_CONSOLE_ENABLE: "true",
17
+ RUSTFS_ADDRESS: ":9000",
18
+ },
19
+ command: ["/data"],
20
+ volumes: [`${s.serviceName}-data:/data`],
21
+ extraPorts: [{ host: 9001, container: 9001 }],
22
+ healthcheck: {
23
+ test: ["CMD-SHELL", "curl -f http://127.0.0.1:9000/health || exit 1"],
24
+ interval: "5s",
25
+ timeout: "5s",
26
+ retries: 12,
27
+ start_period: "10s",
28
+ },
29
+ }),
30
+ url: (_s, c) =>
31
+ `http://${encodeURIComponent(c.user)}:${encodeURIComponent(c.password)}@${c.host}:${c.port}/${c.database}`,
32
+ };
@@ -4,7 +4,10 @@
4
4
 
5
5
  import { describe, expect, test } from "bun:test";
6
6
  import {
7
+ extraHostPortForInstance,
7
8
  hostPortForInstance,
9
+ instancePortOffset,
10
+ parseStackControls,
8
11
  parseStackCredentials,
9
12
  stackAppSlug,
10
13
  stackInstanceId,
@@ -28,14 +31,23 @@ describe("stackInstanceId", () => {
28
31
  });
29
32
 
30
33
  describe("hostPortForInstance", () => {
31
- test("offsets sql and kv into separate ranges", () => {
34
+ test("assigns built-in roles disjoint per-project ranges", () => {
32
35
  const id = "abcd12";
36
+ const n = instancePortOffset(id);
33
37
  const sql = hostPortForInstance("store.sql", 5432, id);
34
38
  const kv = hostPortForInstance("store.kv", 6379, id);
35
- expect(sql).toBeGreaterThanOrEqual(15_000);
36
- expect(sql).toBeLessThan(16_000);
37
- expect(kv).toBeGreaterThanOrEqual(16_000);
38
- expect(kv).toBeLessThan(17_000);
39
+ const files = hostPortForInstance("store.files", 9000, id);
40
+ const filesConsole = extraHostPortForInstance("store.files", 9001, id);
41
+ const email = hostPortForInstance("channel.email", 1025, id);
42
+ const emailUi = extraHostPortForInstance("channel.email", 8025, id);
43
+ expect([sql, kv, files, filesConsole, email, emailUi]).toEqual([
44
+ 15_000 + n,
45
+ 16_000 + n,
46
+ 18_000 + n,
47
+ 19_000 + n,
48
+ 20_000 + n,
49
+ 21_000 + n,
50
+ ]);
39
51
  });
40
52
  });
41
53
 
@@ -53,6 +65,26 @@ OKE_STORE_KV_DB=oke
53
65
  expect(creds["store.sql"]?.password).toBe("s3cret");
54
66
  expect(creds["store.kv"]?.password).toBe("kvpass");
55
67
  });
68
+
69
+ test("reads service-specific S3 credentials and optional controls", () => {
70
+ const text = `
71
+ S3_ACCESS_KEY_ID=files-key
72
+ S3_SECRET_ACCESS_KEY=files-secret
73
+ S3_REGION=eu-central-1
74
+ OKE_STORE_KV_MAXMEMORY=512mb
75
+ # SMTP_USER=commented-out
76
+ `;
77
+ const creds = parseStackCredentials(text, ["store.files"]);
78
+ expect(creds["store.files"]).toEqual({
79
+ user: "files-key",
80
+ password: "files-secret",
81
+ database: "oke",
82
+ });
83
+ expect(parseStackControls(text)).toEqual({
84
+ OKE_STORE_KV_MAXMEMORY: "512mb",
85
+ S3_REGION: "eu-central-1",
86
+ });
87
+ });
56
88
  });
57
89
 
58
90
  describe("deriveInfrastructure instanceId", () => {
@@ -83,4 +115,40 @@ describe("deriveInfrastructure instanceId", () => {
83
115
  expect(result.stackEnv.DATABASE_URL).toMatch(/:15\d{3}\//);
84
116
  expect(result.stackEnv.REDIS_URL).toMatch(/:16\d{3}/);
85
117
  });
118
+
119
+ test("offsets Mailpit UI and RustFS console so stacks do not share 8025/9001", () => {
120
+ // Regression: offset 975 previously mapped Mailpit's 8025 UI onto RustFS's 9000 API.
121
+ const id = "a3f791";
122
+ const n = instancePortOffset(id);
123
+ const result = deriveInfrastructure({
124
+ images: {
125
+ "channel.email": "axllent/mailpit:v1.22.3",
126
+ "store.files": "rustfs/rustfs:1.0.0-beta.11",
127
+ },
128
+ app: `dev-${id}`,
129
+ instanceId: id,
130
+ includeApp: false,
131
+ credentials: {
132
+ "channel.email": {
133
+ user: "oke",
134
+ password: "stack-id-test-mail-password",
135
+ database: "oke",
136
+ },
137
+ "store.files": {
138
+ user: "oke",
139
+ password: "stack-id-test-files-password",
140
+ database: "oke",
141
+ },
142
+ },
143
+ });
144
+ const mailYml = result.files.find((f) => f.path === "compose.channel.email.yml")!.content;
145
+ const filesYml = result.files.find((f) => f.path === "compose.store.files.yml")!.content;
146
+ expect(mailYml).toContain(`${20_000 + n}:1025`);
147
+ expect(mailYml).toContain(`${21_000 + n}:8025`);
148
+ expect(mailYml).not.toContain("8025:8025");
149
+ expect(filesYml).toContain(`${18_000 + n}:9000`);
150
+ expect(filesYml).toContain(`${19_000 + n}:9001`);
151
+ expect(filesYml).not.toContain("9001:9001");
152
+ expect(mailYml).not.toContain("9000:8025");
153
+ });
86
154
  });
@@ -1,13 +1,14 @@
1
1
  /**
2
2
  * Per-project local stack identity — unique compose project + host ports.
3
3
  *
4
- * `oke dev --stack` must not share one `oke-dev` Postgres across every app on
4
+ * `oke dev --docker` must not share one `oke-dev` Postgres across every app on
5
5
  * the machine. Identity is a stable short hash of the project cwd.
6
6
  */
7
7
 
8
8
  import { createHash } from "node:crypto";
9
9
  import { resolve } from "node:path";
10
10
  import type { ServiceCredentials } from "./types.ts";
11
+ import { DEFAULT_DOCKER_DIR } from "./types.ts";
11
12
  import { defaultHostPort, envPrefix } from "./helpers.ts";
12
13
 
13
14
  /**
@@ -16,10 +17,7 @@ import { defaultHostPort, envPrefix } from "./helpers.ts";
16
17
  * @param cwd - Project root
17
18
  */
18
19
  export function stackInstanceId(cwd: string): string {
19
- return createHash("sha256")
20
- .update(resolve(cwd))
21
- .digest("hex")
22
- .slice(0, 6);
20
+ return createHash("sha256").update(resolve(cwd)).digest("hex").slice(0, 6);
23
21
  }
24
22
 
25
23
  /**
@@ -31,6 +29,15 @@ export function stackAppSlug(cwd: string): string {
31
29
  return `dev-${stackInstanceId(cwd)}`;
32
30
  }
33
31
 
32
+ /**
33
+ * Stable 0–999 offset derived from a stack instance id.
34
+ *
35
+ * @param instanceId - 6-hex stack id
36
+ */
37
+ export function instancePortOffset(instanceId: string): number {
38
+ return Number.parseInt(instanceId.slice(0, 4), 16) % 1000;
39
+ }
40
+
34
41
  /**
35
42
  * Host port for a role, offset by instance id so two stacks can run at once.
36
43
  *
@@ -43,15 +50,39 @@ export function hostPortForInstance(
43
50
  containerPort: number,
44
51
  instanceId: string,
45
52
  ): number {
46
- const n = Number.parseInt(instanceId.slice(0, 4), 16) % 1000;
53
+ const n = instancePortOffset(instanceId);
47
54
  if (role === "store.sql") return 15_000 + n;
48
55
  if (role === "store.kv") return 16_000 + n;
49
56
  if (role === "signal") return 17_000 + n;
57
+ if (role === "store.files") return 18_000 + n;
58
+ if (role === "channel.email") return 20_000 + n;
59
+ if (role === "vault") return 22_000 + n;
50
60
  return defaultHostPort(role, containerPort) + n;
51
61
  }
52
62
 
53
63
  /**
54
- * Parse role credentials from an existing `.env.stack` body (reuse on restart).
64
+ * Host port for a recipe's additional published port.
65
+ *
66
+ * Built-in roles use disjoint ranges so an offset cannot move Mailpit's UI
67
+ * onto RustFS's API port (for example, `8025 + 975 = 9000`).
68
+ *
69
+ * @param role - Role key
70
+ * @param hostPort - Recipe's default additional host port
71
+ * @param instanceId - 6-hex stack id
72
+ */
73
+ export function extraHostPortForInstance(
74
+ role: string,
75
+ hostPort: number,
76
+ instanceId: string,
77
+ ): number {
78
+ const n = instancePortOffset(instanceId);
79
+ if (role === "store.files") return 19_000 + n;
80
+ if (role === "channel.email") return 21_000 + n;
81
+ return hostPort + n;
82
+ }
83
+
84
+ /**
85
+ * Parse role credentials from an existing `.env.docker` body (reuse on restart).
55
86
  *
56
87
  * @param text - Dotenv contents
57
88
  * @param roles - Roles to look up
@@ -60,29 +91,16 @@ export function parseStackCredentials(
60
91
  text: string,
61
92
  roles: readonly string[],
62
93
  ): Record<string, ServiceCredentials> {
63
- const map = new Map<string, string>();
64
- for (const line of text.split("\n")) {
65
- const trimmed = line.trim();
66
- if (!trimmed || trimmed.startsWith("#")) continue;
67
- const eq = trimmed.indexOf("=");
68
- if (eq <= 0) continue;
69
- const key = trimmed.slice(0, eq);
70
- let value = trimmed.slice(eq + 1);
71
- if (
72
- (value.startsWith('"') && value.endsWith('"')) ||
73
- (value.startsWith("'") && value.endsWith("'"))
74
- ) {
75
- value = value.slice(1, -1);
76
- }
77
- map.set(key, value);
78
- }
79
-
94
+ const map = parseDotenv(text);
80
95
  const out: Record<string, ServiceCredentials> = {};
81
96
  for (const role of roles) {
82
97
  const prefix = envPrefix(role);
83
- const user = map.get(`${prefix}_USER`);
84
- const password = map.get(`${prefix}_PASSWORD`);
85
- const database = map.get(`${prefix}_DB`);
98
+ const user =
99
+ map.get(`${prefix}_USER`) ?? (role === "store.files" ? map.get("S3_ACCESS_KEY_ID") : "oke");
100
+ const password =
101
+ map.get(`${prefix}_PASSWORD`) ??
102
+ (role === "store.files" ? map.get("S3_SECRET_ACCESS_KEY") : undefined);
103
+ const database = map.get(`${prefix}_DB`) ?? "oke";
86
104
  if (user && password && database) {
87
105
  out[role] = { user, password, database };
88
106
  }
@@ -90,8 +108,40 @@ export function parseStackCredentials(
90
108
  return out;
91
109
  }
92
110
 
111
+ /** Optional `.env.docker` keys preserved when generated files refresh. */
112
+ export const STACK_CONTROL_KEYS = [
113
+ "PGDATA",
114
+ "POSTGRES_INITDB_ARGS",
115
+ "OKE_STORE_KV_MAXMEMORY",
116
+ "OKE_STORE_KV_MAXMEMORY_POLICY",
117
+ "S3_REGION",
118
+ "S3_SESSION_TOKEN",
119
+ "SMTP_USER",
120
+ "SMTP_PASSWORD",
121
+ "MP_MAX_MESSAGES",
122
+ "MP_SMTP_AUTH_ACCEPT_ANY",
123
+ "MP_SMTP_AUTH_ALLOW_INSECURE",
124
+ ] as const;
125
+
93
126
  /**
94
- * Load credentials from project `.env.stack` when present.
127
+ * Read supported user controls from an existing `.env.docker`.
128
+ *
129
+ * @param text - Dotenv contents
130
+ */
131
+ export function parseStackControls(text: string): Record<string, string> {
132
+ const parsed = parseDotenv(text);
133
+ return Object.fromEntries(
134
+ STACK_CONTROL_KEYS.flatMap((key) => {
135
+ const value = parsed.get(key);
136
+ return value === undefined ? [] : [[key, value]];
137
+ }),
138
+ );
139
+ }
140
+
141
+ /**
142
+ * Load credentials from `docker/.env.docker` when present.
143
+ *
144
+ * Soft-compat: also reads legacy project-root `.env.docker`.
95
145
  *
96
146
  * @param cwd - Project root
97
147
  * @param roles - Image roles
@@ -100,9 +150,50 @@ export async function loadExistingStackCredentials(
100
150
  cwd: string,
101
151
  roles: readonly string[],
102
152
  ): Promise<Readonly<Record<string, ServiceCredentials>> | undefined> {
103
- const path = resolve(cwd, ".env.stack");
104
- const file = Bun.file(path);
105
- if (!(await file.exists())) return undefined;
106
- const parsed = parseStackCredentials(await file.text(), roles);
107
- return Object.keys(parsed).length > 0 ? parsed : undefined;
153
+ const candidates = [resolve(cwd, DEFAULT_DOCKER_DIR, ".env.docker"), resolve(cwd, ".env.docker")];
154
+ for (const path of candidates) {
155
+ const file = Bun.file(path);
156
+ if (!(await file.exists())) continue;
157
+ const parsed = parseStackCredentials(await file.text(), roles);
158
+ if (Object.keys(parsed).length > 0) return parsed;
159
+ }
160
+ return undefined;
161
+ }
162
+
163
+ /**
164
+ * Load optional controls from `docker/.env.docker` when present.
165
+ *
166
+ * @param cwd - Project root
167
+ */
168
+ export async function loadExistingStackControls(
169
+ cwd: string,
170
+ ): Promise<Readonly<Record<string, string>> | undefined> {
171
+ const candidates = [resolve(cwd, DEFAULT_DOCKER_DIR, ".env.docker"), resolve(cwd, ".env.docker")];
172
+ for (const path of candidates) {
173
+ const file = Bun.file(path);
174
+ if (!(await file.exists())) continue;
175
+ const controls = parseStackControls(await file.text());
176
+ if (Object.keys(controls).length > 0) return controls;
177
+ }
178
+ return undefined;
179
+ }
180
+
181
+ function parseDotenv(text: string): Map<string, string> {
182
+ const map = new Map<string, string>();
183
+ for (const line of text.split("\n")) {
184
+ const trimmed = line.trim();
185
+ if (!trimmed || trimmed.startsWith("#")) continue;
186
+ const eq = trimmed.indexOf("=");
187
+ if (eq <= 0) continue;
188
+ const key = trimmed.slice(0, eq);
189
+ let value = trimmed.slice(eq + 1);
190
+ if (
191
+ (value.startsWith('"') && value.endsWith('"')) ||
192
+ (value.startsWith("'") && value.endsWith("'"))
193
+ ) {
194
+ value = value.slice(1, -1);
195
+ }
196
+ map.set(key, value);
197
+ }
198
+ return map;
108
199
  }
@@ -6,11 +6,7 @@ import { describe, expect, test } from "bun:test";
6
6
  import { mkdtemp, rm } from "node:fs/promises";
7
7
  import { tmpdir } from "node:os";
8
8
  import { join } from "node:path";
9
- import {
10
- deriveInfrastructure,
11
- formatStackEnv,
12
- writeDerivedFiles,
13
- } from "./index.ts";
9
+ import { deriveInfrastructure, formatStackEnv, writeDerivedFiles } from "./index.ts";
14
10
 
15
11
  async function dockerAvailable(): Promise<boolean> {
16
12
  try {
@@ -24,7 +20,7 @@ async function dockerAvailable(): Promise<boolean> {
24
20
  }
25
21
  }
26
22
 
27
- describe("oke dev --stack postgres integration", () => {
23
+ describe("oke dev --docker postgres integration", () => {
28
24
  test("compose brings up postgres and the app talks to it", async () => {
29
25
  if (!(await dockerAvailable())) {
30
26
  console.warn("skipping: docker daemon not available");
@@ -51,21 +47,12 @@ describe("oke dev --stack postgres integration", () => {
51
47
  });
52
48
  await writeDerivedFiles(derived, dockerDir, {
53
49
  writeStackEnv: true,
54
- stackEnvDir: dir,
55
50
  });
56
51
 
57
52
  // Infra-only: network + role compose (no app build).
58
53
  const composeFiles = ["compose.yml", "compose.store.sql.yml"];
59
54
  const up = Bun.spawn(
60
- [
61
- "docker",
62
- "compose",
63
- "-p",
64
- project,
65
- ...composeFiles.flatMap((f) => ["-f", f]),
66
- "up",
67
- "-d",
68
- ],
55
+ ["docker", "compose", "-p", project, ...composeFiles.flatMap((f) => ["-f", f]), "up", "-d"],
69
56
  {
70
57
  cwd: dockerDir,
71
58
  stdout: "pipe",
@@ -120,13 +107,11 @@ describe("oke dev --stack postgres integration", () => {
120
107
  await sql.close();
121
108
  }
122
109
 
123
- // Prove credentials live in .env.stack, not YAML.
110
+ // Prove credentials live in docker/.env.docker, not YAML.
124
111
  const yml = await Bun.file(join(dockerDir, "compose.store.sql.yml")).text();
125
112
  expect(yml).not.toContain("stack-integration-pass");
126
- expect(formatStackEnv(derived.stackEnv)).toContain(
127
- "stack-integration-pass",
128
- );
129
- expect(await Bun.file(join(dir, ".env.stack")).exists()).toBe(true);
113
+ expect(formatStackEnv(derived.stackEnv)).toContain("stack-integration-pass");
114
+ expect(await Bun.file(join(dockerDir, ".env.docker")).exists()).toBe(true);
130
115
  } finally {
131
116
  await Bun.spawn(
132
117
  [
@@ -4,8 +4,15 @@
4
4
 
5
5
  import { buildSpecs } from "./compose.ts";
6
6
  import { recipeFor } from "./recipes/index.ts";
7
+ import { extraHostPortForInstance } from "./stack-id.ts";
7
8
  import type { DeriveOptions, ServiceSpec } from "./types.ts";
8
9
 
10
+ /** Extra published host↔container mapping for stack preview. */
11
+ export interface StackExtraPort {
12
+ readonly hostPort: number;
13
+ readonly containerPort: number;
14
+ }
15
+
9
16
  /** One resolved stack row for preview. */
10
17
  export interface StackRow {
11
18
  readonly role: string;
@@ -14,12 +21,14 @@ export interface StackRow {
14
21
  readonly service: string;
15
22
  readonly containerPort: number;
16
23
  readonly hostPort: number;
24
+ /** Additional published ports (Mailpit UI, RustFS console, …). */
25
+ readonly extraPorts: readonly StackExtraPort[];
17
26
  }
18
27
 
19
28
  /**
20
29
  * Resolve stack preview rows from image pins.
21
30
  *
22
- * @param options - Images (+ optional recipes)
31
+ * @param options - Images (+ optional recipes / instanceId)
23
32
  */
24
33
  export function resolveStack(options: DeriveOptions): readonly StackRow[] {
25
34
  const specs = buildSpecs(options);
@@ -38,13 +47,35 @@ export function formatStackPreview(rows: readonly StackRow[]): string {
38
47
  "-------------- --------- --------------------------------- --------------",
39
48
  ];
40
49
  for (const r of rows) {
41
- lines.push(
42
- `${pad(r.role, 14)} ${pad(r.recipe, 9)} ${pad(r.image, 33)} ${r.hostPort}:${r.containerPort}`,
43
- );
50
+ const ports = [
51
+ `${r.hostPort}:${r.containerPort}`,
52
+ ...r.extraPorts.map((p) => `${p.hostPort}:${p.containerPort}`),
53
+ ].join(", ");
54
+ lines.push(`${pad(r.role, 14)} ${pad(r.recipe, 9)} ${pad(r.image, 33)} ${ports}`);
44
55
  }
45
56
  return `${lines.join("\n")}\n`;
46
57
  }
47
58
 
59
+ /**
60
+ * Resolve additional published ports for a service (instance-aware).
61
+ *
62
+ * @param spec - Normalised service
63
+ * @param options - Derive options
64
+ */
65
+ export function resolveExtraPorts(
66
+ spec: ServiceSpec,
67
+ options: DeriveOptions,
68
+ ): readonly StackExtraPort[] {
69
+ const recipe = recipeFor(spec.image, options.recipes ?? []);
70
+ const applied = recipe.apply(spec);
71
+ return (applied.extraPorts ?? []).map((p) => ({
72
+ containerPort: p.container,
73
+ hostPort: options.instanceId
74
+ ? extraHostPortForInstance(spec.role, p.host, options.instanceId)
75
+ : p.host,
76
+ }));
77
+ }
78
+
48
79
  function toRow(spec: ServiceSpec, options: DeriveOptions): StackRow {
49
80
  const recipe = recipeFor(spec.image, options.recipes ?? []);
50
81
  return {
@@ -54,6 +85,7 @@ function toRow(spec: ServiceSpec, options: DeriveOptions): StackRow {
54
85
  service: spec.serviceName,
55
86
  containerPort: spec.port,
56
87
  hostPort: spec.hostPort,
88
+ extraPorts: resolveExtraPorts(spec, options),
57
89
  };
58
90
  }
59
91