okengine 0.10.2 → 0.11.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 (343) hide show
  1. package/package.json +76 -10
  2. package/site/content/docs/ai/mcp.mdx +0 -1
  3. package/site/content/docs/deployment/docker-swarm.mdx +48 -111
  4. package/site/content/docs/deployment/docker.mdx +43 -36
  5. package/site/content/docs/deployment/index.mdx +9 -9
  6. package/site/content/docs/deployment/kubernetes.mdx +4 -4
  7. package/site/content/docs/elements/ai.mdx +7 -13
  8. package/site/content/docs/elements/channel.mdx +9 -13
  9. package/site/content/docs/elements/clock.mdx +8 -14
  10. package/site/content/docs/elements/flow.mdx +46 -54
  11. package/site/content/docs/elements/gate.mdx +6 -11
  12. package/site/content/docs/elements/signal.mdx +2 -8
  13. package/site/content/docs/elements/store.mdx +79 -93
  14. package/site/content/docs/elements/vault.mdx +89 -76
  15. package/site/content/docs/get-started/basic-usage.mdx +8 -14
  16. package/site/content/docs/get-started/installation.mdx +36 -36
  17. package/site/content/docs/get-started/introduction.mdx +11 -0
  18. package/site/content/docs/get-started/why.mdx +3 -4
  19. package/site/content/docs/index.mdx +0 -1
  20. package/site/content/docs/meta.json +0 -1
  21. package/site/content/docs/plugins/anonymous.mdx +1 -1
  22. package/site/content/docs/plugins/magic-link.mdx +11 -11
  23. package/site/content/docs/plugins/otp.mdx +2 -2
  24. package/site/content/docs/plugins/passkey.mdx +1 -1
  25. package/site/content/docs/plugins/two-factor.mdx +1 -1
  26. package/site/content/docs/plugins/username.mdx +1 -1
  27. package/site/content/docs/recipes/caddy.mdx +1 -1
  28. package/site/content/docs/recipes/cockroachdb.mdx +2 -2
  29. package/site/content/docs/recipes/dragonfly.mdx +1 -1
  30. package/site/content/docs/recipes/index.mdx +1 -2
  31. package/site/content/docs/recipes/llama-cpp.mdx +21 -23
  32. package/site/content/docs/recipes/mailpit.mdx +11 -11
  33. package/site/content/docs/recipes/meilisearch.mdx +10 -10
  34. package/site/content/docs/recipes/meta.json +0 -1
  35. package/site/content/docs/recipes/ollama.mdx +1 -1
  36. package/site/content/docs/recipes/pgdog.mdx +12 -9
  37. package/site/content/docs/recipes/postgres.mdx +3 -3
  38. package/site/content/docs/recipes/redis.mdx +2 -2
  39. package/site/content/docs/recipes/rustfs.mdx +10 -7
  40. package/site/content/docs/recipes/sglang.mdx +1 -1
  41. package/site/content/docs/recipes/supabase-docker.mdx +1 -1
  42. package/site/content/docs/recipes/timescale.mdx +2 -2
  43. package/site/content/docs/recipes/valkey.mdx +1 -1
  44. package/site/content/docs/recipes/vllm.mdx +1 -1
  45. package/site/content/docs/recipes/yugabytedb.mdx +2 -2
  46. package/site/content/docs/reference/cli.md +61 -38
  47. package/site/content/docs/reference/configuration.mdx +145 -70
  48. package/site/content/docs/reference/environment-variables.mdx +36 -28
  49. package/site/content/docs/reference/errors.mdx +28 -29
  50. package/site/content/docs/reference/fx.mdx +14 -7
  51. package/site/content/docs/reference/i18n.mdx +17 -6
  52. package/site/content/docs/reference/index.mdx +5 -0
  53. package/site/content/docs/reference/meta.json +1 -0
  54. package/site/content/docs/reference/migrating-environments.mdx +158 -0
  55. package/site/content/docs/reference/plugins.mdx +2 -2
  56. package/src/auth/gate-auth.test.ts +12 -10
  57. package/src/cli/ai-setup/ai-setup.test.ts +30 -16
  58. package/src/cli/ai-setup/apply.ts +111 -15
  59. package/src/cli/ai-setup/catalog.ts +3 -3
  60. package/src/cli/ai-setup/index.ts +1 -1
  61. package/src/cli/ai-setup/prompts.ts +1 -1
  62. package/src/cli/ai-setup/recommend.test.ts +1 -1
  63. package/src/cli/ask-seed.test.ts +3 -3
  64. package/src/cli/build.ts +8 -9
  65. package/src/cli/db-auto-push.test.ts +6 -3
  66. package/src/cli/db-auto-push.ts +7 -5
  67. package/src/cli/db-seed.ts +7 -8
  68. package/src/cli/db.test.ts +70 -71
  69. package/src/cli/db.ts +86 -12
  70. package/src/cli/dev-controller.test.ts +35 -0
  71. package/src/cli/dev-controller.ts +298 -0
  72. package/src/cli/dev-controls.ts +11 -65
  73. package/src/cli/dev-db-push.test.ts +47 -15
  74. package/src/cli/dev-schema-sync.test.ts +11 -11
  75. package/src/cli/dev-schema-sync.ts +10 -10
  76. package/src/cli/dev-session-lock.test.ts +106 -0
  77. package/src/cli/dev-session-lock.ts +209 -0
  78. package/src/cli/dev.test.ts +246 -114
  79. package/src/cli/dev.ts +153 -205
  80. package/src/cli/doc-staleness.test.ts +3 -6
  81. package/src/cli/docker-clean.test.ts +33 -1
  82. package/src/cli/docker-clean.ts +28 -33
  83. package/src/cli/docker-cli.test.ts +12 -11
  84. package/src/cli/docker.ts +95 -20
  85. package/src/cli/doctor.test.ts +40 -0
  86. package/src/cli/doctor.ts +142 -9
  87. package/src/cli/drizzle-env.test.ts +11 -13
  88. package/src/cli/drizzle-env.ts +18 -11
  89. package/src/cli/ensure-drizzle-config.ts +11 -14
  90. package/src/cli/hero-meta.test.ts +15 -15
  91. package/src/cli/hero-meta.ts +10 -9
  92. package/src/cli/index.ts +36 -6
  93. package/src/cli/load-config.images.test.ts +44 -10
  94. package/src/cli/load-config.source.test.ts +50 -0
  95. package/src/cli/load-config.ts +35 -29
  96. package/src/cli/meilisearch-local.ts +1 -2
  97. package/src/cli/mode.ts +7 -58
  98. package/src/cli/registry.ts +175 -19
  99. package/src/cli/resolve-dev-sql-env.test.ts +8 -16
  100. package/src/cli/resolve-dev-sql-env.ts +13 -15
  101. package/src/cli/safe-defaults.test.ts +29 -11
  102. package/src/cli/schema.ts +65 -16
  103. package/src/cli/test.ts +58 -0
  104. package/src/cli/tui/App.tsx +252 -0
  105. package/src/cli/tui/DevLive.tsx +116 -0
  106. package/src/cli/tui/components/ConfirmDialog.tsx +48 -0
  107. package/src/cli/tui/components/HeaderBar.tsx +67 -0
  108. package/src/cli/tui/components/LogStream.tsx +47 -0
  109. package/src/cli/tui/components/PanelCard.tsx +39 -0
  110. package/src/cli/tui/components/SelectRow.tsx +37 -0
  111. package/src/cli/tui/components/SlashPalette.tsx +172 -0
  112. package/src/cli/tui/components/Spinner.tsx +32 -0
  113. package/src/cli/tui/components/StatusBar.tsx +51 -0
  114. package/src/cli/tui/components/StatusDot.tsx +37 -0
  115. package/src/cli/tui/components/TabBar.tsx +49 -0
  116. package/src/cli/tui/components/Wordmark.tsx +43 -0
  117. package/src/cli/tui/format.ts +38 -0
  118. package/src/cli/tui/keys.test.ts +18 -0
  119. package/src/cli/tui/keys.ts +26 -0
  120. package/src/cli/tui/launch.test.ts +18 -0
  121. package/src/cli/tui/launch.ts +57 -0
  122. package/src/cli/tui/panels/Dashboard.tsx +201 -0
  123. package/src/cli/tui/panels/Database.tsx +198 -0
  124. package/src/cli/tui/panels/Dev.tsx +257 -0
  125. package/src/cli/tui/panels/Docker.tsx +357 -0
  126. package/src/cli/tui/panels/Navigator.test.ts +22 -0
  127. package/src/cli/tui/panels/Navigator.tsx +213 -0
  128. package/src/cli/tui/slash-catalog.test.ts +31 -0
  129. package/src/cli/tui/slash-catalog.ts +184 -0
  130. package/src/cli/tui/slash-run.ts +54 -0
  131. package/src/cli/tui/state/ports.ts +95 -0
  132. package/src/cli/tui/theme.ts +24 -0
  133. package/src/cli/vault-cmd.test.ts +402 -0
  134. package/src/cli/vault-cmd.ts +727 -69
  135. package/src/cli/vault-secure-input.test.ts +56 -0
  136. package/src/cli/vault-secure-input.ts +124 -0
  137. package/src/compiler/effects-infer.ts +7 -0
  138. package/src/compiler/extract.ts +12 -0
  139. package/src/compiler/fixtures/skyport/oke.config.ts +7 -5
  140. package/src/compiler/fixtures/skyport/src/flows/payments/index.ts +1 -1
  141. package/src/compiler/fixtures/skyport.expected.json +1 -1
  142. package/src/compiler/generate-adopt.ts +27 -1
  143. package/src/compiler-entry.ts +7 -0
  144. package/src/config/define-config.test.ts +111 -47
  145. package/src/config/driver-defaults.test.ts +115 -0
  146. package/src/config/driver-defaults.ts +156 -0
  147. package/src/config/index.ts +246 -131
  148. package/src/config/resolve-driver.test.ts +14 -19
  149. package/src/console/server/app.ts +4 -2
  150. package/src/console/server/console.test.ts +58 -1
  151. package/src/console/server/flows.ts +22 -2
  152. package/src/console/server/index.ts +3 -0
  153. package/src/console/server/operator-db.test.ts +55 -13
  154. package/src/console/server/operator-db.ts +305 -263
  155. package/src/console/server/serve.ts +16 -7
  156. package/src/console/server/state.ts +31 -9
  157. package/src/console/server/vault.test.ts +120 -5
  158. package/src/console/server/vault.ts +208 -12
  159. package/src/console/ui/dist/assets/{index-D-9KFce5.js → index--Y0Uaz-m.js} +2 -2
  160. package/src/console/ui/dist/assets/panel-vault-BkZdJPX8.js +1 -0
  161. package/src/console/ui/dist/assets/style-C3L349iZ.css +3 -0
  162. package/src/console/ui/dist/index.html +2 -2
  163. package/src/console/ui/shell/client.ts +16 -0
  164. package/src/console/ui/shell/panels/vault/VaultPanel.tsx +54 -1
  165. package/src/console/ui/vault/VaultA11yView.tsx +33 -1
  166. package/src/console/ui/vault/backend.test.ts +75 -0
  167. package/src/console/ui/vault/backend.ts +117 -0
  168. package/src/console/ui/vault/fixture.ts +21 -2
  169. package/src/console/ui/vault/index.ts +11 -1
  170. package/src/console/ui/vault/types.ts +26 -0
  171. package/src/docker/compose.ts +164 -61
  172. package/src/docker/derive.ts +106 -12
  173. package/src/docker/docker.test.ts +156 -110
  174. package/src/docker/helpers.ts +98 -0
  175. package/src/docker/images-config.test.ts +163 -0
  176. package/src/docker/index.ts +30 -5
  177. package/src/docker/recipes/index.ts +0 -3
  178. package/src/docker/recipes/llama-cpp.ts +4 -4
  179. package/src/docker/recipes/pgdog.ts +9 -2
  180. package/src/docker/resources.test.ts +71 -0
  181. package/src/docker/resources.ts +176 -0
  182. package/src/docker/stack-id.test.ts +11 -4
  183. package/src/docker/stack-id.ts +42 -32
  184. package/src/docker/stack.integration.test.ts +4 -15
  185. package/src/docker/types.ts +35 -3
  186. package/src/drivers/ai-ollama.integration.test.ts +2 -2
  187. package/src/drivers/conformance.test.ts +11 -12
  188. package/src/drivers/drizzle-dialect.test.ts +0 -4
  189. package/src/drivers/drizzle-dialect.ts +7 -13
  190. package/src/drivers/index.ts +24 -7
  191. package/src/drivers/journal-postgres.test.ts +3 -1
  192. package/src/drivers/journal-postgres.ts +4 -2
  193. package/src/drivers/meilisearch.ts +1 -1
  194. package/src/drivers/pglite.ts +28 -5
  195. package/src/drivers/types.ts +9 -11
  196. package/src/drivers/vault-aws-secrets-manager.test.ts +159 -0
  197. package/src/drivers/vault-aws-secrets-manager.ts +333 -0
  198. package/src/drivers/vault-builtin.test.ts +110 -0
  199. package/src/drivers/vault-builtin.ts +184 -0
  200. package/src/drivers/vault-driver-removal.test.ts +13 -0
  201. package/src/drivers/vault-managed.test.ts +95 -0
  202. package/src/drivers/vault-managed.ts +101 -25
  203. package/src/drivers/vault-types.ts +25 -5
  204. package/src/elements/ai/runtime.ts +1 -1
  205. package/src/elements/clock/chaos.test.ts +4 -1
  206. package/src/elements/clock/durable.ts +57 -4
  207. package/src/elements/clock.test.ts +13 -14
  208. package/src/elements/gate/constants.ts +18 -0
  209. package/src/elements/gate.test.ts +1 -1
  210. package/src/elements/gate.ts +2 -2
  211. package/src/elements/store/domain-ddl.test.ts +5 -12
  212. package/src/elements/store/emit-drizzle.ts +44 -19
  213. package/src/elements/store/index-boot.test.ts +15 -116
  214. package/src/elements/store/load-plugin-tables.ts +1 -1
  215. package/src/elements/store/resource-list-docs.fixture.ts +12 -6
  216. package/src/elements/store/resource-list-docs.test.ts +64 -44
  217. package/src/elements/store/resource.test.ts +17 -10
  218. package/src/elements/store/runtime.ts +9 -12
  219. package/src/elements/store/schema-decl.test.ts +16 -32
  220. package/src/elements/store/seed.test.ts +1 -2
  221. package/src/elements/store/seed.ts +7 -8
  222. package/src/elements/store/sql-condition.test.ts +2 -2
  223. package/src/elements/store/sql-session.test.ts +8 -10
  224. package/src/elements/store/sql-session.ts +10 -2
  225. package/src/elements/store.test.ts +13 -13
  226. package/src/elements/vault/audit.ts +233 -0
  227. package/src/elements/vault/boot-chain.test.ts +78 -0
  228. package/src/elements/vault/boot-chain.ts +43 -39
  229. package/src/elements/vault/builtin-adapter.test.ts +479 -0
  230. package/src/elements/vault/builtin-adapter.ts +1184 -0
  231. package/src/elements/vault/crypto.test.ts +237 -0
  232. package/src/elements/vault/crypto.ts +431 -0
  233. package/src/elements/vault/declare.ts +164 -3
  234. package/src/elements/vault/errors.ts +56 -0
  235. package/src/elements/vault/kms-unseal.test.ts +76 -0
  236. package/src/elements/vault/kms-unseal.ts +181 -0
  237. package/src/elements/vault/path.ts +90 -0
  238. package/src/elements/vault/resilience.test.ts +102 -0
  239. package/src/elements/vault/resilience.ts +107 -0
  240. package/src/elements/vault/runtime.ts +36 -4
  241. package/src/elements/vault/security-checklist.test.ts +130 -0
  242. package/src/elements/vault/storage.ts +448 -0
  243. package/src/elements/vault/types.ts +288 -0
  244. package/src/elements/vault/unseal.ts +150 -0
  245. package/src/elements/vault.test.ts +72 -4
  246. package/src/elements/vault.ts +91 -2
  247. package/src/full.ts +248 -0
  248. package/src/http.ts +41 -0
  249. package/src/i18n/interpolate-simple.ts +37 -0
  250. package/src/i18n/messages.ts +51 -4
  251. package/src/i18n-entry.ts +7 -0
  252. package/src/index.ts +35 -145
  253. package/src/journal-entry.ts +20 -0
  254. package/src/kernel/adopt-barrel-fresh.test.ts +25 -10
  255. package/src/kernel/app.ts +40 -42
  256. package/src/kernel/auto-registry.test.ts +2 -2
  257. package/src/kernel/boot-bind/ai.test.ts +5 -5
  258. package/src/kernel/boot-bind/ai.ts +2 -1
  259. package/src/kernel/boot-bind/channel.test.ts +6 -6
  260. package/src/kernel/boot-bind/channel.ts +5 -4
  261. package/src/kernel/boot-bind/clock.ts +5 -5
  262. package/src/kernel/boot-bind/gate.ts +6 -7
  263. package/src/kernel/boot-bind/honor-config.test.ts +42 -42
  264. package/src/kernel/boot-bind/journal.ts +5 -5
  265. package/src/kernel/boot-bind/signal.ts +3 -1
  266. package/src/kernel/boot-bind/store.test.ts +55 -37
  267. package/src/kernel/boot-bind/store.ts +24 -34
  268. package/src/kernel/boot-bind/vault.ts +19 -4
  269. package/src/kernel/boot.test.ts +12 -4
  270. package/src/kernel/boot.ts +31 -19
  271. package/src/kernel/budget-entry.ts +1 -1
  272. package/src/kernel/compensate.test.ts +288 -1
  273. package/src/kernel/compensate.ts +126 -0
  274. package/src/kernel/concurrency.test.ts +10 -9
  275. package/src/kernel/effects-stamping.test.ts +27 -2
  276. package/src/kernel/element-registries.ts +5 -3
  277. package/src/kernel/flow.ts +4 -2
  278. package/src/kernel/fx.test.ts +119 -3
  279. package/src/kernel/fx.ts +215 -40
  280. package/src/kernel/index.ts +14 -0
  281. package/src/kernel/journal.ts +130 -7
  282. package/src/kernel/pipeline.test.ts +20 -6
  283. package/src/kernel/redacted.test.ts +35 -0
  284. package/src/kernel/redacted.ts +24 -2
  285. package/src/kernel/router/create-default.ts +16 -0
  286. package/src/kernel/router/create-edge.ts +18 -0
  287. package/src/kernel/router/create.ts +23 -0
  288. package/src/kernel/router/index.ts +23 -0
  289. package/src/kernel/router/linear.ts +88 -0
  290. package/src/kernel/router/regexp.ts +188 -0
  291. package/src/kernel/router/smart.ts +120 -0
  292. package/src/kernel/router/trie.ts +108 -0
  293. package/src/kernel/router/types.ts +56 -0
  294. package/src/kernel/router.ts +18 -532
  295. package/src/kernel-entry.ts +43 -0
  296. package/src/manifest/fixtures/skyport.excerpt.json +1 -1
  297. package/src/manifest/fixtures/skyport.manifest.json +1 -1
  298. package/src/plugins/config-source.test.ts +7 -7
  299. package/src/release/build-lib.ts +58 -0
  300. package/src/release/exports.test.ts +4 -2
  301. package/src/release/exports.ts +30 -4
  302. package/src/release/measure.ts +101 -22
  303. package/src/runs/duckdb.ts +7 -2
  304. package/src/shared/optional-peer.ts +41 -0
  305. package/src/term.test.ts +3 -44
  306. package/src/test/create-test-app.ts +12 -0
  307. package/src/test/provisions.integration.test.ts +1 -1
  308. package/src/test/reset-element-registries.ts +2 -0
  309. package/src/upgrade/codemods.test.ts +7 -20
  310. package/src/upgrade/codemods.ts +93 -60
  311. package/site/content/docs/console/access.mdx +0 -29
  312. package/site/content/docs/console/ai.mdx +0 -35
  313. package/site/content/docs/console/architecture.mdx +0 -35
  314. package/site/content/docs/console/channels.mdx +0 -37
  315. package/site/content/docs/console/clock.mdx +0 -51
  316. package/site/content/docs/console/flows.mdx +0 -31
  317. package/site/content/docs/console/gates.mdx +0 -73
  318. package/site/content/docs/console/index.mdx +0 -54
  319. package/site/content/docs/console/manifest-diff.mdx +0 -34
  320. package/site/content/docs/console/meta.json +0 -24
  321. package/site/content/docs/console/overview.mdx +0 -40
  322. package/site/content/docs/console/plugins.mdx +0 -41
  323. package/site/content/docs/console/privacy.mdx +0 -32
  324. package/site/content/docs/console/runs.mdx +0 -50
  325. package/site/content/docs/console/signals.mdx +0 -40
  326. package/site/content/docs/console/store.mdx +0 -32
  327. package/site/content/docs/console/tenancy.mdx +0 -32
  328. package/site/content/docs/console/traces.mdx +0 -38
  329. package/site/content/docs/console/vault.mdx +0 -41
  330. package/site/content/docs/recipes/openbao.mdx +0 -141
  331. package/src/cli/ask-dev-mode.ts +0 -40
  332. package/src/cli/dev-mode.test.ts +0 -45
  333. package/src/cli/dev-mode.ts +0 -78
  334. package/src/cli/openbao-bootstrap.test.ts +0 -150
  335. package/src/cli/openbao-bootstrap.ts +0 -321
  336. package/src/cli/openbao-restart.integration.test.ts +0 -145
  337. package/src/console/ui/dist/assets/panel-vault-Za1GyJfM.js +0 -1
  338. package/src/console/ui/dist/assets/style-C8MxEWPd.css +0 -3
  339. package/src/docker/recipes/openbao.ts +0 -47
  340. package/src/drivers/libsql.ts +0 -179
  341. package/src/drivers/sqlite.ts +0 -44
  342. package/src/drivers/vault-openbao.test.ts +0 -97
  343. package/src/drivers/vault-openbao.ts +0 -143
@@ -8,7 +8,12 @@ import { tmpdir } from "node:os";
8
8
  import { join } from "node:path";
9
9
  import { composeProjectName, type DockerRunner } from "../docker/cleanup.ts";
10
10
  import { dockerCli } from "./docker.ts";
11
- import { dockerCleanHelp, parseDockerCleanArgs, runDockerClean } from "./docker-clean.ts";
11
+ import {
12
+ dockerCleanHelp,
13
+ parseDockerCleanArgs,
14
+ runDockerClean,
15
+ selectionEntries,
16
+ } from "./docker-clean.ts";
12
17
  import { EXIT_OK, EXIT_USAGE } from "./exit.ts";
13
18
  import { formatOkeHelp, flagTokensFor, OKE_COMMANDS } from "./registry.ts";
14
19
 
@@ -176,4 +181,31 @@ describe("oke docker clean", () => {
176
181
  expect(flagTokensFor(docker, "clean")).toContain("--all");
177
182
  expect(formatOkeHelp()).toContain("clean");
178
183
  });
184
+
185
+ test("selectionEntries is one row per project (not per container)", () => {
186
+ const entries = selectionEntries([
187
+ {
188
+ project: "oke-dev-aaaaaa",
189
+ containers: [
190
+ {
191
+ id: "1",
192
+ name: "oke-dev-aaaaaa-redis-1",
193
+ service: "redis",
194
+ state: "running",
195
+ project: "oke-dev-aaaaaa",
196
+ },
197
+ {
198
+ id: "2",
199
+ name: "oke-dev-aaaaaa-postgres-1",
200
+ service: "postgres",
201
+ state: "running",
202
+ project: "oke-dev-aaaaaa",
203
+ },
204
+ ],
205
+ },
206
+ ]);
207
+ expect(entries).toHaveLength(1);
208
+ expect(entries[0]?.label).toBe("oke-dev-aaaaaa");
209
+ expect(entries[0]?.hint).toContain("2 container");
210
+ });
179
211
  });
@@ -12,7 +12,6 @@ import {
12
12
  resolveSelection,
13
13
  selectionKey,
14
14
  type DockerRunner,
15
- type OkeContainer,
16
15
  type OkeStack,
17
16
  } from "../docker/cleanup.ts";
18
17
  import { EXIT_OK, EXIT_RUNTIME, EXIT_USAGE } from "./exit.ts";
@@ -49,8 +48,8 @@ export function dockerCleanHelp(): string {
49
48
 
50
49
  Remove leftover OKE docker compose stacks (containers, networks, volumes).
51
50
 
52
- Inside a project: focuses the current stack (oke-dev-<id>); optionally
53
- include other stacks on this machine.
51
+ Selection is by compose project name (oke-dev-<id>) — never per-service.
52
+ Inside a project: focuses the current stack; optionally include others.
54
53
  Outside a project: list every oke-dev-* stack and pick what to remove.
55
54
 
56
55
  --yes, -y Non-interactive; required when stdin is not a TTY
@@ -235,10 +234,10 @@ async function runInteractive(options: {
235
234
  if (current.length === 0) {
236
235
  write(`No stack for this project (${currentProject}).\n`);
237
236
  } else {
238
- const keys = await pickContainerKeys({
237
+ const keys = await pickProjectKeys({
239
238
  stacks: current,
240
239
  scope: "current",
241
- message: `Containers for this project (${currentProject})`,
240
+ message: `Stack for this project (${currentProject})`,
242
241
  initialAll: true,
243
242
  pickKeys,
244
243
  });
@@ -256,7 +255,7 @@ async function runInteractive(options: {
256
255
  return EXIT_OK;
257
256
  }
258
257
  if (include) {
259
- const keys = await pickContainerKeys({
258
+ const keys = await pickProjectKeys({
260
259
  stacks: others,
261
260
  scope: "other",
262
261
  message: "Other OKE stacks on this machine",
@@ -271,7 +270,7 @@ async function runInteractive(options: {
271
270
  }
272
271
  }
273
272
  } else {
274
- const keys = await pickContainerKeys({
273
+ const keys = await pickProjectKeys({
275
274
  stacks,
276
275
  scope: "all",
277
276
  message: "OKE docker stacks on this machine",
@@ -330,7 +329,10 @@ async function askIncludeOthersPrompt(): Promise<boolean | null> {
330
329
  return value;
331
330
  }
332
331
 
333
- async function pickContainerKeys(options: {
332
+ /**
333
+ * Multiselect compose projects (one row per `oke-dev-*` name).
334
+ */
335
+ async function pickProjectKeys(options: {
334
336
  readonly stacks: readonly OkeStack[];
335
337
  readonly scope: "current" | "other" | "all";
336
338
  readonly message: string;
@@ -344,7 +346,7 @@ async function pickContainerKeys(options: {
344
346
  if (entries.length === 0) return [];
345
347
 
346
348
  const value = await multiselect({
347
- message: `${message} — selecting any container removes its whole stack`,
349
+ message: `${message} — removes the whole stack (containers, networks, volumes)`,
348
350
  options: entries.map((e) => ({
349
351
  value: e.key,
350
352
  label: e.label,
@@ -357,34 +359,27 @@ async function pickContainerKeys(options: {
357
359
  return value;
358
360
  }
359
361
 
360
- function selectionEntries(stacks: readonly OkeStack[]): readonly {
362
+ /**
363
+ * One selectable row per compose project.
364
+ *
365
+ * @param stacks - Discovered stacks
366
+ */
367
+ export function selectionEntries(stacks: readonly OkeStack[]): readonly {
361
368
  readonly key: string;
362
369
  readonly label: string;
363
370
  readonly hint?: string;
364
371
  }[] {
365
- const out: { key: string; label: string; hint?: string }[] = [];
366
- for (const stack of stacks) {
367
- if (stack.containers.length === 0) {
368
- out.push({
369
- key: selectionKey(stack.project),
370
- label: stack.project,
371
- hint: "no containers · volumes/networks may remain",
372
- });
373
- continue;
374
- }
375
- for (const c of stack.containers) {
376
- out.push({
377
- key: selectionKey(stack.project, c.name),
378
- label: `${stack.project} · ${displayContainer(c)}`,
379
- hint: c.state || undefined,
380
- });
381
- }
382
- }
383
- return out;
384
- }
385
-
386
- function displayContainer(c: OkeContainer): string {
387
- return c.service && c.service !== c.name ? `${c.service} (${c.name})` : c.name;
372
+ return stacks.map((stack) => {
373
+ const n = stack.containers.length;
374
+ return {
375
+ key: selectionKey(stack.project),
376
+ label: stack.project,
377
+ hint:
378
+ n === 0
379
+ ? "no containers · volumes/networks may remain"
380
+ : `${n} container${n === 1 ? "" : "s"}`,
381
+ };
382
+ });
388
383
  }
389
384
 
390
385
  async function tearDownProjects(
@@ -39,11 +39,12 @@ describe("oke docker CLI", () => {
39
39
  expect(code).toBe(0);
40
40
  expect(result).toBeDefined();
41
41
  expect(await Bun.file(join(dir, "docker/Dockerfile")).exists()).toBe(true);
42
- expect(await Bun.file(join(dir, "docker/compose.store.sql.yml")).exists()).toBe(true);
43
- const yml = await Bun.file(join(dir, "docker/compose.store.sql.yml")).text();
42
+ expect(await Bun.file(join(dir, "docker/docker-compose.yml")).exists()).toBe(true);
43
+ const yml = await Bun.file(join(dir, "docker/docker-compose.yml")).text();
44
44
  expect(yml).not.toContain("test-password-not-in-yaml");
45
45
  expect(yml).toContain(".env.docker");
46
- expect(logs.join("")).toContain("docker/compose.override.yml");
46
+ expect(yml).toContain("/_/ready");
47
+ expect(logs.join("")).toContain("docker-compose.override.yml");
47
48
  });
48
49
  });
49
50
 
@@ -136,7 +137,7 @@ describe("oke images list", () => {
136
137
  });
137
138
 
138
139
  describe("oke schema generate", () => {
139
- test("emits schema/oke.ts and --check catches drift", async () => {
140
+ test("emits .oke/schema/oke.ts and --check catches drift", async () => {
140
141
  const dir = await mkdtemp(join(tmpdir(), "oke-cli-schema-"));
141
142
  const code = await runSchemaGenerate({
142
143
  cwd: dir,
@@ -144,7 +145,7 @@ describe("oke schema generate", () => {
144
145
  write: () => {},
145
146
  });
146
147
  expect(code).toBe(0);
147
- const src = await Bun.file(join(dir, "schema/oke.ts")).text();
148
+ const src = await Bun.file(join(dir, ".oke/schema/oke.ts")).text();
148
149
  expect(src).toContain("bookings");
149
150
  expect(src).toContain("oke_roles");
150
151
 
@@ -156,7 +157,7 @@ describe("oke schema generate", () => {
156
157
  });
157
158
  expect(checkOk).toBe(0);
158
159
 
159
- await Bun.write(join(dir, "schema/oke.ts"), emitSchemaSource(["other"]));
160
+ await Bun.write(join(dir, ".oke/schema/oke.ts"), emitSchemaSource(["other"]));
160
161
  const checkFail = await runSchemaGenerate({
161
162
  cwd: dir,
162
163
  extraTables: ["bookings"],
@@ -167,7 +168,7 @@ describe("oke schema generate", () => {
167
168
  });
168
169
  });
169
170
 
170
- describe("oke dev --docker", () => {
171
+ describe("oke dev compose plan", () => {
171
172
  test("plans docker with postgres and writes nothing on dryRun", async () => {
172
173
  const dir = await mkdtemp(join(tmpdir(), "oke-cli-dev-dry-"));
173
174
  await Bun.write(join(dir, "src/app.ts"), "export {}\n");
@@ -182,11 +183,11 @@ describe("oke dev --docker", () => {
182
183
  });
183
184
  expect(code).toBe(0);
184
185
  expect(plan?.stackRoles).toEqual(["store.sql"]);
185
- expect(plan?.composeFiles?.some((f) => f.includes("store.sql"))).toBe(true);
186
- expect(plan?.composeFiles).toContain("compose.yml");
186
+ expect(plan?.composeFiles).toContain("docker-compose.yml");
187
+ expect(plan?.composeFiles?.some((f) => f.includes("store.sql"))).toBe(false);
187
188
  expect(plan?.stackEnv?.DATABASE_URL).toContain("postgres://");
188
- expect(await Bun.file(join(dir, "compose.yml")).exists()).toBe(false);
189
- expect(await Bun.file(join(dir, "docker/compose.yml")).exists()).toBe(false);
189
+ expect(await Bun.file(join(dir, "docker-compose.yml")).exists()).toBe(false);
190
+ expect(await Bun.file(join(dir, "docker/docker-compose.yml")).exists()).toBe(false);
190
191
  });
191
192
  });
192
193
 
package/src/cli/docker.ts CHANGED
@@ -1,12 +1,17 @@
1
1
  /**
2
- * `oke docker` — Dockerfile + per-role compose files · `clean` leftover stacks.
2
+ * `oke docker` — Dockerfile + compose artefacts · `clean` leftover stacks.
3
+ *
4
+ * Default layout is a single production-grade `docker-compose.yml`.
5
+ * Opt into `--split` (per-role files) or `--stack` (`docker-stack.yml`).
3
6
  */
4
7
 
5
8
  import { resolve } from "node:path";
6
9
  import {
7
10
  DEFAULT_DOCKER_DIR,
11
+ DEFAULT_SERVER_BUDGET,
8
12
  deriveInfrastructure,
9
13
  writeDerivedFiles,
14
+ type ComposeLayout,
10
15
  type DeriveOptions,
11
16
  type DeriveResult,
12
17
  } from "../docker/index.ts";
@@ -18,7 +23,14 @@ import { loadOkeConfig, loadManifest, resolveImages } from "./load-config.ts";
18
23
  export interface DockerCliOptions {
19
24
  readonly cwd?: string;
20
25
  readonly outDir?: string;
26
+ /** Production overlays + resource budget (default true for `oke docker`). */
21
27
  readonly prod?: boolean;
28
+ /** Compose layout (default `single`). */
29
+ readonly layout?: ComposeLayout;
30
+ /** Host CPUs for resource apportioning (default 4). */
31
+ readonly serverCpus?: number;
32
+ /** Host RAM GiB for resource apportioning (default 8). */
33
+ readonly serverMemoryGb?: number;
22
34
  readonly configPath?: string;
23
35
  readonly manifestPath?: string;
24
36
  readonly write?: (text: string) => void;
@@ -75,11 +87,19 @@ export async function runDockerDerive(
75
87
  return { code: 1 };
76
88
  }
77
89
 
90
+ const layout = options.layout ?? "single";
91
+ const prod = options.prod ?? true;
92
+ const serverCpus = options.serverCpus ?? DEFAULT_SERVER_BUDGET.cpus;
93
+ const serverMemoryGb = options.serverMemoryGb ?? DEFAULT_SERVER_BUDGET.memoryGb;
94
+
78
95
  try {
79
96
  const result = deriveInfrastructure({
80
97
  images,
81
98
  app,
82
- prod: options.prod ?? false,
99
+ prod,
100
+ layout,
101
+ serverCpus,
102
+ serverMemoryGb,
83
103
  outDir,
84
104
  composeDir,
85
105
  includeApp: true,
@@ -90,25 +110,22 @@ export async function runDockerDerive(
90
110
  await writeDerivedFiles(result, outDir, { writeStackEnv: false });
91
111
  }
92
112
 
93
- const vaultSpec = result.specs.find((s) => s.role === "vault");
94
- if (vaultSpec) {
95
- const rel = composeDir === "." ? ".oke/openbao/" : `${composeDir}/../.oke/openbao/`;
96
- write(
97
- `oke docker: OpenBao first boot — bring the stack up (e.g. \`oke dev -d\`), and oke ` +
98
- `will initialize + unseal it once. Root/unseal material stays on the host at ${rel} ` +
99
- `(0600, gitignored) — back it up; losing it loses every secret permanently.\n`,
100
- );
101
- }
102
-
103
113
  write(`oke docker: wrote ${result.files.length} file(s) → ${composeDir}/\n`);
104
114
  for (const f of result.files) write(` ${composeDir}/${f.path}\n`);
105
115
  write(
106
- `compose merge order (cwd ${composeDir}/):\n${result.composeFiles.map((f) => ` -f ${f}`).join("\n")}\n`,
116
+ `layout: ${layout} · server budget: ${serverCpus} CPU / ${serverMemoryGb} GiB` +
117
+ (prod ? " · production-grade\n" : "\n"),
107
118
  );
108
119
  write(
109
- `(${composeDir}/compose.override.yml is layer 4 — user-owned, never written by oke)\n` +
110
- `(${composeDir}/compose.all.yml is layers 1–3 merged — single-file alternative for Swarm / one \`-c\`)\n`,
120
+ `compose merge order (cwd ${composeDir}/):\n${result.composeFiles.map((f) => ` -f ${f}`).join("\n")}\n`,
111
121
  );
122
+ if (layout === "single") {
123
+ write(`(${composeDir}/docker-compose.override.yml is user-owned — never written by oke)\n`);
124
+ } else if (layout === "split") {
125
+ write(`(${composeDir}/compose.override.yml is user-owned — never written by oke)\n`);
126
+ } else {
127
+ write(`(Swarm: docker stack deploy -c ${composeDir}/docker-stack.yml <name>)\n`);
128
+ }
112
129
  return { code: 0, result };
113
130
  } catch (err) {
114
131
  console.error(err instanceof Error ? err.message : String(err));
@@ -127,21 +144,61 @@ export async function dockerCli(args: readonly string[]): Promise<number> {
127
144
  return dockerCleanCli(rest);
128
145
  }
129
146
 
130
- let prod = false;
147
+ let prod: boolean | undefined;
148
+ let layout: ComposeLayout | undefined;
149
+ let serverCpus: number | undefined;
150
+ let serverMemoryGb: number | undefined;
131
151
  let outDir: string | undefined;
132
152
  let configPath: string | undefined;
133
153
  let manifestPath: string | undefined;
134
154
  for (let i = 0; i < args.length; i++) {
135
155
  const a = args[i]!;
136
156
  if (a === "--prod" || a === "-p") prod = true;
137
- else if (a === "--out" || a === "-o") outDir = args[++i];
157
+ else if (a === "--no-prod") prod = false;
158
+ else if (a === "--split") layout = "split";
159
+ else if (a === "--stack") layout = "stack";
160
+ else if (a === "--single") layout = "single";
161
+ else if (a === "--cpus" || a === "--cpu") {
162
+ const raw = args[++i];
163
+ const n = Number(raw);
164
+ if (!raw || !(n > 0)) {
165
+ console.error("oke docker: --cpus requires a positive number");
166
+ return 1;
167
+ }
168
+ serverCpus = n;
169
+ } else if (a === "--memory" || a === "--mem" || a === "--memory-gb") {
170
+ const raw = args[++i];
171
+ const n = Number(raw);
172
+ if (!raw || !(n > 0)) {
173
+ console.error("oke docker: --memory requires a positive GiB number");
174
+ return 1;
175
+ }
176
+ serverMemoryGb = n;
177
+ } else if (a === "--out" || a === "-o") outDir = args[++i];
138
178
  else if (a === "--config" || a === "-c") configPath = args[++i];
139
179
  else if (a === "--manifest" || a === "-m") manifestPath = args[++i];
140
180
  else if (a === "--help" || a === "-h") {
141
- console.log(`oke docker [--prod|-p] [--out|-o docker] [--config|-c oke.config.ts]
181
+ console.log(`oke docker [options]
142
182
 
143
- Derive Dockerfile + compose.<role>.yml under docker/ (default).
144
- --prod is opt-in (never the default). Credentials are never written into YAML.
183
+ Derive production-grade Docker artefacts under docker/ (default).
184
+
185
+ Layouts (mutually exclusive; default --single):
186
+ --single docker-compose.yml + Dockerfile (+ .env.docker via oke dev)
187
+ --split compose.yml + compose.<role>.yml (+ compose.prod.yml)
188
+ --stack docker-stack.yml for docker stack deploy
189
+
190
+ Server budget (sizes deploy.resources; default 4 CPU / 8 GiB):
191
+ --cpus <n> host CPUs
192
+ --memory <gib> host RAM in GiB
193
+
194
+ Other:
195
+ --prod / -p production overlays (default on)
196
+ --no-prod skip readiness / deploy / resource budget
197
+ --out / -o output directory (default docker)
198
+ --config / -c oke.config.ts path
199
+ --manifest / -m Manifest path
200
+
201
+ Credentials are never written into YAML. PgDog configs land in docker/pgdog/.
145
202
 
146
203
  ${dockerCleanHelp()}`);
147
204
  return EXIT_OK;
@@ -149,10 +206,28 @@ ${dockerCleanHelp()}`);
149
206
  console.error(`oke docker: unknown subcommand ${a}`);
150
207
  console.error("Run `oke docker --help` for usage.");
151
208
  return 1;
209
+ } else {
210
+ console.error(`oke docker: unknown flag ${a}`);
211
+ console.error("Run `oke docker --help` for usage.");
212
+ return 1;
213
+ }
214
+ }
215
+ {
216
+ const layoutFlags = [
217
+ args.includes("--split"),
218
+ args.includes("--stack"),
219
+ args.includes("--single"),
220
+ ].filter(Boolean).length;
221
+ if (layoutFlags > 1) {
222
+ console.error("oke docker: use only one of --single, --split, --stack");
223
+ return 1;
152
224
  }
153
225
  }
154
226
  const { code } = await runDockerDerive({
155
227
  prod,
228
+ layout,
229
+ serverCpus,
230
+ serverMemoryGb,
156
231
  outDir,
157
232
  configPath,
158
233
  manifestPath,
@@ -84,6 +84,46 @@ describe("oke doctor", () => {
84
84
  expect(findings).toHaveLength(0);
85
85
  });
86
86
 
87
+ test("fails closed in prod when builtin vault master key is env-sourced", async () => {
88
+ const { code, findings } = await runDoctor({
89
+ secrets: [],
90
+ ports: [],
91
+ isPortInUse: async () => false,
92
+ skipDbDrift: true,
93
+ configEnv: "prod",
94
+ driversConfig: {
95
+ vault: { dev: "vault", test: "memory", prod: "vault" },
96
+ },
97
+ vaultConfig: {
98
+ encryption: { masterKey: { kind: "env", name: "OKE_VAULT_MASTER_KEY" } },
99
+ },
100
+ write: () => {},
101
+ });
102
+ expect(code).toBe(2);
103
+ const finding = findings.find((f) => f.code === "vault_master_key");
104
+ expect(finding?.severity).toBe("error");
105
+ expect(finding?.message).toContain("environment variable");
106
+ });
107
+
108
+ test("NODE_ENV=production selects prod env for the vault master-key gate", async () => {
109
+ const { code, findings } = await runDoctor({
110
+ secrets: [],
111
+ ports: [],
112
+ isPortInUse: async () => false,
113
+ skipDbDrift: true,
114
+ env: (k) => (k === "NODE_ENV" ? "production" : undefined),
115
+ driversConfig: {
116
+ vault: { dev: "vault", test: "memory", prod: "vault" },
117
+ },
118
+ vaultConfig: {
119
+ encryption: { masterKey: { kind: "env", name: "OKE_VAULT_MASTER_KEY" } },
120
+ },
121
+ write: () => {},
122
+ });
123
+ expect(code).toBe(2);
124
+ expect(findings.some((f) => f.code === "vault_master_key")).toBe(true);
125
+ });
126
+
87
127
  test("fails with flow name when PII reaches unguarded ask", async () => {
88
128
  const manifest: Manifest = {
89
129
  oke: "1.0",