okengine 0.10.3 → 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.
- package/package.json +76 -10
- package/site/content/docs/deployment/docker-swarm.mdx +48 -111
- package/site/content/docs/deployment/docker.mdx +43 -36
- package/site/content/docs/deployment/index.mdx +9 -9
- package/site/content/docs/deployment/kubernetes.mdx +4 -4
- package/site/content/docs/elements/ai.mdx +7 -7
- package/site/content/docs/elements/channel.mdx +9 -7
- package/site/content/docs/elements/clock.mdx +8 -8
- package/site/content/docs/elements/flow.mdx +46 -47
- package/site/content/docs/elements/gate.mdx +6 -5
- package/site/content/docs/elements/signal.mdx +2 -2
- package/site/content/docs/elements/store.mdx +71 -78
- package/site/content/docs/elements/vault.mdx +89 -70
- package/site/content/docs/get-started/basic-usage.mdx +8 -8
- package/site/content/docs/get-started/installation.mdx +36 -36
- package/site/content/docs/get-started/introduction.mdx +11 -0
- package/site/content/docs/get-started/why.mdx +2 -2
- package/site/content/docs/plugins/anonymous.mdx +1 -1
- package/site/content/docs/plugins/magic-link.mdx +11 -11
- package/site/content/docs/plugins/otp.mdx +2 -2
- package/site/content/docs/plugins/passkey.mdx +1 -1
- package/site/content/docs/plugins/two-factor.mdx +1 -1
- package/site/content/docs/plugins/username.mdx +1 -1
- package/site/content/docs/recipes/caddy.mdx +1 -1
- package/site/content/docs/recipes/cockroachdb.mdx +2 -2
- package/site/content/docs/recipes/dragonfly.mdx +1 -1
- package/site/content/docs/recipes/index.mdx +1 -2
- package/site/content/docs/recipes/llama-cpp.mdx +21 -23
- package/site/content/docs/recipes/mailpit.mdx +11 -11
- package/site/content/docs/recipes/meilisearch.mdx +10 -10
- package/site/content/docs/recipes/meta.json +0 -1
- package/site/content/docs/recipes/ollama.mdx +1 -1
- package/site/content/docs/recipes/pgdog.mdx +12 -9
- package/site/content/docs/recipes/postgres.mdx +3 -3
- package/site/content/docs/recipes/redis.mdx +2 -2
- package/site/content/docs/recipes/rustfs.mdx +10 -7
- package/site/content/docs/recipes/sglang.mdx +1 -1
- package/site/content/docs/recipes/supabase-docker.mdx +1 -1
- package/site/content/docs/recipes/timescale.mdx +2 -2
- package/site/content/docs/recipes/valkey.mdx +1 -1
- package/site/content/docs/recipes/vllm.mdx +1 -1
- package/site/content/docs/recipes/yugabytedb.mdx +2 -2
- package/site/content/docs/reference/cli.md +61 -38
- package/site/content/docs/reference/configuration.mdx +145 -70
- package/site/content/docs/reference/environment-variables.mdx +36 -28
- package/site/content/docs/reference/errors.mdx +28 -29
- package/site/content/docs/reference/fx.mdx +11 -4
- package/site/content/docs/reference/i18n.mdx +17 -6
- package/site/content/docs/reference/index.mdx +5 -0
- package/site/content/docs/reference/meta.json +1 -0
- package/site/content/docs/reference/migrating-environments.mdx +158 -0
- package/site/content/docs/reference/plugins.mdx +2 -2
- package/src/auth/gate-auth.test.ts +12 -10
- package/src/cli/ai-setup/ai-setup.test.ts +27 -15
- package/src/cli/ai-setup/apply.ts +111 -15
- package/src/cli/ai-setup/catalog.ts +3 -3
- package/src/cli/ai-setup/index.ts +1 -1
- package/src/cli/ai-setup/prompts.ts +1 -1
- package/src/cli/ai-setup/recommend.test.ts +1 -1
- package/src/cli/ask-seed.test.ts +3 -3
- package/src/cli/build.ts +1 -1
- package/src/cli/db-auto-push.test.ts +6 -3
- package/src/cli/db-auto-push.ts +7 -5
- package/src/cli/db-seed.ts +7 -8
- package/src/cli/db.test.ts +70 -71
- package/src/cli/db.ts +86 -12
- package/src/cli/dev-controller.test.ts +35 -0
- package/src/cli/dev-controller.ts +298 -0
- package/src/cli/dev-controls.ts +11 -65
- package/src/cli/dev-db-push.test.ts +47 -15
- package/src/cli/dev-schema-sync.test.ts +11 -11
- package/src/cli/dev-schema-sync.ts +10 -10
- package/src/cli/dev-session-lock.test.ts +106 -0
- package/src/cli/dev-session-lock.ts +209 -0
- package/src/cli/dev.test.ts +166 -121
- package/src/cli/dev.ts +138 -192
- package/src/cli/doc-staleness.test.ts +3 -6
- package/src/cli/docker-clean.test.ts +33 -1
- package/src/cli/docker-clean.ts +28 -33
- package/src/cli/docker-cli.test.ts +12 -11
- package/src/cli/docker.ts +95 -20
- package/src/cli/doctor.test.ts +40 -0
- package/src/cli/doctor.ts +55 -15
- package/src/cli/drizzle-env.test.ts +11 -13
- package/src/cli/drizzle-env.ts +18 -11
- package/src/cli/ensure-drizzle-config.ts +11 -14
- package/src/cli/hero-meta.test.ts +15 -15
- package/src/cli/hero-meta.ts +10 -9
- package/src/cli/index.ts +36 -6
- package/src/cli/load-config.images.test.ts +16 -12
- package/src/cli/load-config.source.test.ts +50 -0
- package/src/cli/load-config.ts +34 -28
- package/src/cli/meilisearch-local.ts +1 -2
- package/src/cli/mode.ts +7 -58
- package/src/cli/registry.ts +175 -19
- package/src/cli/resolve-dev-sql-env.test.ts +8 -16
- package/src/cli/resolve-dev-sql-env.ts +13 -15
- package/src/cli/safe-defaults.test.ts +28 -10
- package/src/cli/schema.ts +65 -16
- package/src/cli/test.ts +58 -0
- package/src/cli/tui/App.tsx +252 -0
- package/src/cli/tui/DevLive.tsx +116 -0
- package/src/cli/tui/components/ConfirmDialog.tsx +48 -0
- package/src/cli/tui/components/HeaderBar.tsx +67 -0
- package/src/cli/tui/components/LogStream.tsx +47 -0
- package/src/cli/tui/components/PanelCard.tsx +39 -0
- package/src/cli/tui/components/SelectRow.tsx +37 -0
- package/src/cli/tui/components/SlashPalette.tsx +172 -0
- package/src/cli/tui/components/Spinner.tsx +32 -0
- package/src/cli/tui/components/StatusBar.tsx +51 -0
- package/src/cli/tui/components/StatusDot.tsx +37 -0
- package/src/cli/tui/components/TabBar.tsx +49 -0
- package/src/cli/tui/components/Wordmark.tsx +43 -0
- package/src/cli/tui/format.ts +38 -0
- package/src/cli/tui/keys.test.ts +18 -0
- package/src/cli/tui/keys.ts +26 -0
- package/src/cli/tui/launch.test.ts +18 -0
- package/src/cli/tui/launch.ts +57 -0
- package/src/cli/tui/panels/Dashboard.tsx +201 -0
- package/src/cli/tui/panels/Database.tsx +198 -0
- package/src/cli/tui/panels/Dev.tsx +257 -0
- package/src/cli/tui/panels/Docker.tsx +357 -0
- package/src/cli/tui/panels/Navigator.test.ts +22 -0
- package/src/cli/tui/panels/Navigator.tsx +213 -0
- package/src/cli/tui/slash-catalog.test.ts +31 -0
- package/src/cli/tui/slash-catalog.ts +184 -0
- package/src/cli/tui/slash-run.ts +54 -0
- package/src/cli/tui/state/ports.ts +95 -0
- package/src/cli/tui/theme.ts +24 -0
- package/src/cli/vault-cmd.test.ts +402 -0
- package/src/cli/vault-cmd.ts +727 -69
- package/src/cli/vault-secure-input.test.ts +56 -0
- package/src/cli/vault-secure-input.ts +124 -0
- package/src/compiler/effects-infer.ts +7 -0
- package/src/compiler/fixtures/skyport/oke.config.ts +3 -3
- package/src/compiler/fixtures/skyport/src/flows/payments/index.ts +1 -1
- package/src/compiler/fixtures/skyport.expected.json +1 -1
- package/src/compiler-entry.ts +7 -0
- package/src/config/define-config.test.ts +111 -47
- package/src/config/driver-defaults.test.ts +16 -23
- package/src/config/driver-defaults.ts +33 -63
- package/src/config/index.ts +169 -144
- package/src/config/resolve-driver.test.ts +14 -19
- package/src/console/server/app.ts +4 -2
- package/src/console/server/console.test.ts +58 -1
- package/src/console/server/flows.ts +22 -2
- package/src/console/server/index.ts +3 -0
- package/src/console/server/operator-db.test.ts +55 -13
- package/src/console/server/operator-db.ts +305 -263
- package/src/console/server/serve.ts +16 -7
- package/src/console/server/state.ts +31 -9
- package/src/console/server/vault.test.ts +120 -5
- package/src/console/server/vault.ts +208 -12
- package/src/console/ui/dist/assets/{index-D-9KFce5.js → index--Y0Uaz-m.js} +2 -2
- package/src/console/ui/dist/assets/panel-vault-BkZdJPX8.js +1 -0
- package/src/console/ui/dist/assets/style-C3L349iZ.css +3 -0
- package/src/console/ui/dist/index.html +2 -2
- package/src/console/ui/shell/client.ts +16 -0
- package/src/console/ui/shell/panels/vault/VaultPanel.tsx +54 -1
- package/src/console/ui/vault/VaultA11yView.tsx +33 -1
- package/src/console/ui/vault/backend.test.ts +75 -0
- package/src/console/ui/vault/backend.ts +117 -0
- package/src/console/ui/vault/fixture.ts +21 -2
- package/src/console/ui/vault/index.ts +11 -1
- package/src/console/ui/vault/types.ts +26 -0
- package/src/docker/compose.ts +164 -61
- package/src/docker/derive.ts +106 -12
- package/src/docker/docker.test.ts +156 -110
- package/src/docker/helpers.ts +98 -0
- package/src/docker/images-config.test.ts +3 -8
- package/src/docker/index.ts +30 -5
- package/src/docker/recipes/index.ts +0 -3
- package/src/docker/recipes/llama-cpp.ts +4 -4
- package/src/docker/recipes/pgdog.ts +9 -2
- package/src/docker/resources.test.ts +71 -0
- package/src/docker/resources.ts +176 -0
- package/src/docker/stack-id.test.ts +11 -4
- package/src/docker/stack-id.ts +42 -32
- package/src/docker/stack.integration.test.ts +4 -15
- package/src/docker/types.ts +35 -3
- package/src/drivers/ai-ollama.integration.test.ts +2 -2
- package/src/drivers/conformance.test.ts +11 -12
- package/src/drivers/drizzle-dialect.test.ts +0 -4
- package/src/drivers/drizzle-dialect.ts +7 -13
- package/src/drivers/index.ts +24 -7
- package/src/drivers/journal-postgres.test.ts +3 -1
- package/src/drivers/journal-postgres.ts +4 -2
- package/src/drivers/meilisearch.ts +1 -1
- package/src/drivers/pglite.ts +28 -5
- package/src/drivers/types.ts +9 -11
- package/src/drivers/vault-aws-secrets-manager.test.ts +159 -0
- package/src/drivers/vault-aws-secrets-manager.ts +333 -0
- package/src/drivers/vault-builtin.test.ts +110 -0
- package/src/drivers/vault-builtin.ts +184 -0
- package/src/drivers/vault-driver-removal.test.ts +13 -0
- package/src/drivers/vault-managed.test.ts +95 -0
- package/src/drivers/vault-managed.ts +101 -25
- package/src/drivers/vault-types.ts +25 -5
- package/src/elements/ai/runtime.ts +1 -1
- package/src/elements/clock/chaos.test.ts +4 -1
- package/src/elements/clock/durable.ts +57 -4
- package/src/elements/clock.test.ts +13 -14
- package/src/elements/gate/constants.ts +18 -0
- package/src/elements/gate.test.ts +1 -1
- package/src/elements/gate.ts +2 -2
- package/src/elements/store/domain-ddl.test.ts +5 -12
- package/src/elements/store/emit-drizzle.ts +44 -19
- package/src/elements/store/index-boot.test.ts +15 -116
- package/src/elements/store/load-plugin-tables.ts +1 -1
- package/src/elements/store/resource-list-docs.fixture.ts +12 -6
- package/src/elements/store/resource-list-docs.test.ts +64 -44
- package/src/elements/store/resource.test.ts +17 -10
- package/src/elements/store/runtime.ts +9 -12
- package/src/elements/store/schema-decl.test.ts +16 -32
- package/src/elements/store/seed.test.ts +1 -2
- package/src/elements/store/seed.ts +7 -8
- package/src/elements/store/sql-condition.test.ts +2 -2
- package/src/elements/store/sql-session.test.ts +8 -10
- package/src/elements/store/sql-session.ts +10 -2
- package/src/elements/store.test.ts +13 -13
- package/src/elements/vault/audit.ts +233 -0
- package/src/elements/vault/boot-chain.test.ts +78 -0
- package/src/elements/vault/boot-chain.ts +43 -39
- package/src/elements/vault/builtin-adapter.test.ts +479 -0
- package/src/elements/vault/builtin-adapter.ts +1184 -0
- package/src/elements/vault/crypto.test.ts +237 -0
- package/src/elements/vault/crypto.ts +431 -0
- package/src/elements/vault/declare.ts +164 -3
- package/src/elements/vault/errors.ts +56 -0
- package/src/elements/vault/kms-unseal.test.ts +76 -0
- package/src/elements/vault/kms-unseal.ts +181 -0
- package/src/elements/vault/path.ts +90 -0
- package/src/elements/vault/resilience.test.ts +102 -0
- package/src/elements/vault/resilience.ts +107 -0
- package/src/elements/vault/runtime.ts +36 -4
- package/src/elements/vault/security-checklist.test.ts +130 -0
- package/src/elements/vault/storage.ts +448 -0
- package/src/elements/vault/types.ts +288 -0
- package/src/elements/vault/unseal.ts +150 -0
- package/src/elements/vault.test.ts +72 -4
- package/src/elements/vault.ts +91 -2
- package/src/full.ts +248 -0
- package/src/http.ts +41 -0
- package/src/i18n/interpolate-simple.ts +37 -0
- package/src/i18n/messages.ts +51 -4
- package/src/i18n-entry.ts +7 -0
- package/src/index.ts +35 -145
- package/src/journal-entry.ts +20 -0
- package/src/kernel/adopt-barrel-fresh.test.ts +25 -10
- package/src/kernel/app.ts +40 -42
- package/src/kernel/auto-registry.test.ts +2 -2
- package/src/kernel/boot-bind/ai.test.ts +5 -5
- package/src/kernel/boot-bind/ai.ts +2 -1
- package/src/kernel/boot-bind/channel.test.ts +6 -6
- package/src/kernel/boot-bind/channel.ts +3 -10
- package/src/kernel/boot-bind/clock.ts +2 -2
- package/src/kernel/boot-bind/gate.ts +5 -9
- package/src/kernel/boot-bind/honor-config.test.ts +42 -42
- package/src/kernel/boot-bind/journal.ts +2 -2
- package/src/kernel/boot-bind/store.test.ts +43 -44
- package/src/kernel/boot-bind/store.ts +16 -41
- package/src/kernel/boot-bind/vault.ts +16 -1
- package/src/kernel/boot.test.ts +12 -4
- package/src/kernel/boot.ts +31 -19
- package/src/kernel/budget-entry.ts +1 -1
- package/src/kernel/compensate.test.ts +288 -1
- package/src/kernel/compensate.ts +126 -0
- package/src/kernel/concurrency.test.ts +10 -9
- package/src/kernel/effects-stamping.test.ts +27 -2
- package/src/kernel/element-registries.ts +5 -3
- package/src/kernel/flow.ts +4 -2
- package/src/kernel/fx.test.ts +119 -3
- package/src/kernel/fx.ts +215 -40
- package/src/kernel/index.ts +14 -0
- package/src/kernel/journal.ts +130 -7
- package/src/kernel/pipeline.test.ts +20 -6
- package/src/kernel/redacted.test.ts +35 -0
- package/src/kernel/redacted.ts +24 -2
- package/src/kernel/router/create-default.ts +16 -0
- package/src/kernel/router/create-edge.ts +18 -0
- package/src/kernel/router/create.ts +23 -0
- package/src/kernel/router/index.ts +23 -0
- package/src/kernel/router/linear.ts +88 -0
- package/src/kernel/router/regexp.ts +188 -0
- package/src/kernel/router/smart.ts +120 -0
- package/src/kernel/router/trie.ts +108 -0
- package/src/kernel/router/types.ts +56 -0
- package/src/kernel/router.ts +18 -532
- package/src/kernel-entry.ts +43 -0
- package/src/manifest/fixtures/skyport.excerpt.json +1 -1
- package/src/manifest/fixtures/skyport.manifest.json +1 -1
- package/src/plugins/config-source.test.ts +7 -7
- package/src/release/build-lib.ts +58 -0
- package/src/release/exports.test.ts +4 -2
- package/src/release/exports.ts +30 -4
- package/src/release/measure.ts +101 -22
- package/src/runs/duckdb.ts +7 -2
- package/src/shared/optional-peer.ts +41 -0
- package/src/term.test.ts +3 -44
- package/src/test/create-test-app.ts +12 -0
- package/src/test/provisions.integration.test.ts +1 -1
- package/src/test/reset-element-registries.ts +2 -0
- package/src/upgrade/codemods.test.ts +7 -20
- package/src/upgrade/codemods.ts +93 -60
- package/site/content/docs/recipes/openbao.mdx +0 -141
- package/src/cli/ask-dev-mode.ts +0 -40
- package/src/cli/dev-mode.test.ts +0 -45
- package/src/cli/dev-mode.ts +0 -78
- package/src/cli/openbao-bootstrap.test.ts +0 -150
- package/src/cli/openbao-bootstrap.ts +0 -321
- package/src/cli/openbao-restart.integration.test.ts +0 -145
- package/src/console/ui/dist/assets/panel-vault-Za1GyJfM.js +0 -1
- package/src/console/ui/dist/assets/style-C8MxEWPd.css +0 -3
- package/src/docker/recipes/openbao.ts +0 -47
- package/src/drivers/libsql.ts +0 -179
- package/src/drivers/sqlite.ts +0 -44
- package/src/drivers/vault-openbao.test.ts +0 -97
- package/src/drivers/vault-openbao.ts +0 -143
package/src/cli/docker-clean.ts
CHANGED
|
@@ -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
|
-
|
|
53
|
-
|
|
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
|
|
237
|
+
const keys = await pickProjectKeys({
|
|
239
238
|
stacks: current,
|
|
240
239
|
scope: "current",
|
|
241
|
-
message: `
|
|
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
|
|
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
|
|
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
|
-
|
|
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} —
|
|
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
|
-
|
|
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
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
}
|
|
375
|
-
|
|
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.
|
|
43
|
-
const yml = await Bun.file(join(dir, "docker/compose.
|
|
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(
|
|
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
|
|
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
|
|
186
|
-
expect(plan?.composeFiles).
|
|
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 +
|
|
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
|
|
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
|
-
`
|
|
116
|
+
`layout: ${layout} · server budget: ${serverCpus} CPU / ${serverMemoryGb} GiB` +
|
|
117
|
+
(prod ? " · production-grade\n" : "\n"),
|
|
107
118
|
);
|
|
108
119
|
write(
|
|
109
|
-
`(${composeDir}/
|
|
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
|
|
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 === "--
|
|
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 [
|
|
181
|
+
console.log(`oke docker [options]
|
|
142
182
|
|
|
143
|
-
Derive
|
|
144
|
-
|
|
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,
|
package/src/cli/doctor.test.ts
CHANGED
|
@@ -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",
|
package/src/cli/doctor.ts
CHANGED
|
@@ -3,7 +3,13 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { resolve } from "node:path";
|
|
6
|
-
import type {
|
|
6
|
+
import type {
|
|
7
|
+
ConfigEnv,
|
|
8
|
+
DriverRef,
|
|
9
|
+
DriversConfig,
|
|
10
|
+
EnvDriverMap,
|
|
11
|
+
VaultElementConfig,
|
|
12
|
+
} from "../config/index.ts";
|
|
7
13
|
import { resolveEffectiveDrivers, type EffectiveDriversConfig } from "../config/driver-defaults.ts";
|
|
8
14
|
import type { Manifest } from "../manifest/types.ts";
|
|
9
15
|
import { APP_PORT, CONSOLE_PORT, MCP_PORT } from "../runtime/types.ts";
|
|
@@ -61,7 +67,7 @@ function formatDriversSummary(drivers: EffectiveDriversConfig, configEnv: Config
|
|
|
61
67
|
const lines = [`drivers (env=${configEnv}):`];
|
|
62
68
|
for (const [name, map] of rows) {
|
|
63
69
|
const active = driverRefId(map[configEnv]);
|
|
64
|
-
const full = `
|
|
70
|
+
const full = `dev=${driverRefId(map.dev)} test=${driverRefId(map.test)} prod=${driverRefId(map.prod)}`;
|
|
65
71
|
lines.push(` ${name.padEnd(nameWidth)} ${active.padEnd(activeWidth)} { ${full} }`);
|
|
66
72
|
}
|
|
67
73
|
return lines.join("\n");
|
|
@@ -76,7 +82,8 @@ export interface DoctorFinding {
|
|
|
76
82
|
| "db_drift"
|
|
77
83
|
| "tenancy"
|
|
78
84
|
| "driver"
|
|
79
|
-
| "pii_ask"
|
|
85
|
+
| "pii_ask"
|
|
86
|
+
| "vault_master_key";
|
|
80
87
|
readonly severity: "error" | "warn";
|
|
81
88
|
readonly message: string;
|
|
82
89
|
}
|
|
@@ -116,9 +123,13 @@ export interface DoctorOptions {
|
|
|
116
123
|
*/
|
|
117
124
|
readonly driversConfig?: DriversConfig;
|
|
118
125
|
/**
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
|
|
126
|
+
* Inject the `vault` block directly (tests). When omitted, it is read
|
|
127
|
+
* from the same best-effort `oke.config.ts` load as {@link driversConfig}.
|
|
128
|
+
*/
|
|
129
|
+
readonly vaultConfig?: VaultElementConfig;
|
|
130
|
+
/**
|
|
131
|
+
* Active {@link ConfigEnv} for the drivers summary. Default: `dev` when
|
|
132
|
+
* `OKE_DOCKER=1` (via {@link env}), else `test`.
|
|
122
133
|
*/
|
|
123
134
|
readonly configEnv?: ConfigEnv;
|
|
124
135
|
readonly write?: (text: string) => void;
|
|
@@ -161,15 +172,20 @@ export async function runDoctor(options: DoctorOptions = {}): Promise<{
|
|
|
161
172
|
// Fully-resolved `drivers.*` — real defaults + every override merged, so
|
|
162
173
|
// the picture is complete even when `oke.config.ts` only pins one key.
|
|
163
174
|
let driversConfig = options.driversConfig;
|
|
164
|
-
|
|
175
|
+
let vaultConfig = options.vaultConfig;
|
|
176
|
+
if (driversConfig === undefined || vaultConfig === undefined) {
|
|
165
177
|
try {
|
|
166
|
-
|
|
178
|
+
const loaded = (await loadOkeConfig(cwd)).config;
|
|
179
|
+
driversConfig ??= loaded.drivers;
|
|
180
|
+
vaultConfig ??= loaded.vault;
|
|
167
181
|
} catch {
|
|
168
182
|
// No oke.config.ts (or it failed to load) — show real defaults only.
|
|
169
183
|
}
|
|
170
184
|
}
|
|
185
|
+
// Match `bootApplication`: production NODE_ENV → prod; compose → dev; else test.
|
|
171
186
|
const configEnv: ConfigEnv =
|
|
172
|
-
options.configEnv ??
|
|
187
|
+
options.configEnv ??
|
|
188
|
+
(env("NODE_ENV") === "production" ? "prod" : env("OKE_DOCKER") === "1" ? "dev" : "test");
|
|
173
189
|
const effectiveDrivers = resolveEffectiveDrivers(driversConfig);
|
|
174
190
|
|
|
175
191
|
for (const name of secretNames) {
|
|
@@ -184,6 +200,25 @@ export async function runDoctor(options: DoctorOptions = {}): Promise<{
|
|
|
184
200
|
}
|
|
185
201
|
}
|
|
186
202
|
|
|
203
|
+
// An env-var master key is only as protected as the process environment:
|
|
204
|
+
// it lands in shell history, orchestrator manifests, and crash dumps.
|
|
205
|
+
const usesBuiltinVault =
|
|
206
|
+
vaultConfig !== undefined || driverRefId(effectiveDrivers.vault[configEnv]) === "vault";
|
|
207
|
+
if (
|
|
208
|
+
configEnv === "prod" &&
|
|
209
|
+
usesBuiltinVault &&
|
|
210
|
+
(vaultConfig?.encryption?.masterKey?.kind ?? "env") === "env"
|
|
211
|
+
) {
|
|
212
|
+
findings.push({
|
|
213
|
+
code: "vault_master_key",
|
|
214
|
+
// Fail closed in prod: an env-sourced master key collapses envelope
|
|
215
|
+
// protection the moment DB + env are stolen together.
|
|
216
|
+
severity: "error",
|
|
217
|
+
message:
|
|
218
|
+
'vault master key comes from an environment variable in prod — set vault.encryption.masterKey = { kind: "kms", keyId } (or use managed aws-secrets-manager) so the key is never readable from the process environment',
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
187
222
|
const ports = options.ports ?? [APP_PORT, CONSOLE_PORT, MCP_PORT];
|
|
188
223
|
const probe = options.isPortInUse ?? isPortInUse;
|
|
189
224
|
for (const port of ports) {
|
|
@@ -213,13 +248,13 @@ export async function runDoctor(options: DoctorOptions = {}): Promise<{
|
|
|
213
248
|
code: "schema_drift",
|
|
214
249
|
severity: "error",
|
|
215
250
|
message:
|
|
216
|
-
"core stub schema drift — run `oke schema generate` (schema/oke.ts; not domain migrations)",
|
|
251
|
+
"core stub schema drift — run `oke schema generate` (.oke/schema/oke.ts; not domain migrations)",
|
|
217
252
|
});
|
|
218
253
|
} else if (expected !== null && current === null) {
|
|
219
254
|
findings.push({
|
|
220
255
|
code: "schema_drift",
|
|
221
256
|
severity: "error",
|
|
222
|
-
message: "schema/oke.ts missing — run `oke schema generate` (core/plugin stubs)",
|
|
257
|
+
message: ".oke/schema/oke.ts missing — run `oke schema generate` (core/plugin stubs)",
|
|
223
258
|
});
|
|
224
259
|
}
|
|
225
260
|
}
|
|
@@ -237,7 +272,7 @@ export async function runDoctor(options: DoctorOptions = {}): Promise<{
|
|
|
237
272
|
severity: "error",
|
|
238
273
|
message:
|
|
239
274
|
drift.detail ??
|
|
240
|
-
"domain schema differs from the database — run `oke db generate` then `oke db migrate` (or `oke db push` in
|
|
275
|
+
"domain schema differs from the database — run `oke db generate` then `oke db migrate` (or `oke db push` in dev)",
|
|
241
276
|
});
|
|
242
277
|
}
|
|
243
278
|
}
|
|
@@ -299,18 +334,23 @@ export async function doctorCli(args: readonly string[]): Promise<number> {
|
|
|
299
334
|
if (a === "--manifest" || a === "-m") manifestPath = args[++i];
|
|
300
335
|
else if (a === "--env" || a === "-e") {
|
|
301
336
|
const value = args[++i];
|
|
302
|
-
if (value === "
|
|
337
|
+
if (value === "dev" || value === "test" || value === "prod") {
|
|
303
338
|
configEnv = value;
|
|
339
|
+
} else if (value === "local") {
|
|
340
|
+
configEnv = "test";
|
|
341
|
+
} else if (value === "docker") {
|
|
342
|
+
configEnv = "dev";
|
|
304
343
|
}
|
|
305
344
|
} else if (a === "--help" || a === "-h") {
|
|
306
|
-
console.log(`oke doctor [--manifest|-m path] [--env|-e
|
|
345
|
+
console.log(`oke doctor [--manifest|-m path] [--env|-e dev|test|prod] [--json|-j]
|
|
307
346
|
oke doctor --diff|-d [--before|-b <path> --after|-a <path>] [--base|-B <branch>]
|
|
308
347
|
|
|
309
348
|
Verify secrets, ports, schema drift, and PII→model egress before serving.
|
|
310
349
|
Also prints drivers.* fully resolved (every default + every override merged)
|
|
311
350
|
for the active env, from oke.config.ts.
|
|
312
351
|
|
|
313
|
-
--env Env to resolve drivers for (default:
|
|
352
|
+
--env Env to resolve drivers for (default: prod when NODE_ENV=production,
|
|
353
|
+
dev when OKE_DOCKER=1 / \`oke dev\`, else test).
|
|
314
354
|
--diff CI gate: block undeclared contract breaks (Manifest Diff).
|
|
315
355
|
Default baseline is git merge-base (main/master) vs the working
|
|
316
356
|
tree; pass --before/--after for an explicit comparison.
|
|
@@ -24,22 +24,20 @@ async function tempDir(): Promise<string> {
|
|
|
24
24
|
const dualConfig = {
|
|
25
25
|
drivers: {
|
|
26
26
|
store: {
|
|
27
|
-
sql: {
|
|
27
|
+
sql: { dev: "postgres", test: "pglite", prod: "postgres" },
|
|
28
28
|
},
|
|
29
29
|
},
|
|
30
30
|
} as unknown as OkeConfig;
|
|
31
31
|
|
|
32
32
|
describe("resolveDrizzleKitEnv", () => {
|
|
33
|
-
test("
|
|
33
|
+
test("test env → postgresql dialect (pglite)", async () => {
|
|
34
34
|
const dir = await tempDir();
|
|
35
|
-
const ctx = await resolveDrizzleKitEnv(dir, dualConfig, "
|
|
36
|
-
expect(ctx.dialect).toBe("
|
|
37
|
-
expect(ctx.overlay.OKE_DRIZZLE_DIALECT).toBe("
|
|
38
|
-
expect(ctx.overlay.OKE_SQLITE_URL).toContain(".oke/app.sqlite");
|
|
39
|
-
expect(ctx.overlay.DATABASE_URL).toBeUndefined();
|
|
35
|
+
const ctx = await resolveDrizzleKitEnv(dir, dualConfig, "test");
|
|
36
|
+
expect(ctx.dialect).toBe("postgresql");
|
|
37
|
+
expect(ctx.overlay.OKE_DRIZZLE_DIALECT).toBe("postgresql");
|
|
40
38
|
});
|
|
41
39
|
|
|
42
|
-
test("
|
|
40
|
+
test("dev env → postgresql dialect + DATABASE_URL from compose env", async () => {
|
|
43
41
|
const dir = await tempDir();
|
|
44
42
|
await mkdir(join(dir, "docker"), { recursive: true });
|
|
45
43
|
await writeFile(
|
|
@@ -49,7 +47,7 @@ describe("resolveDrizzleKitEnv", () => {
|
|
|
49
47
|
const prevDb = process.env["DATABASE_URL"];
|
|
50
48
|
delete process.env["DATABASE_URL"];
|
|
51
49
|
try {
|
|
52
|
-
const ctx = await resolveDrizzleKitEnv(dir, dualConfig, "
|
|
50
|
+
const ctx = await resolveDrizzleKitEnv(dir, dualConfig, "dev");
|
|
53
51
|
expect(ctx.dialect).toBe("postgresql");
|
|
54
52
|
expect(ctx.overlay.OKE_DRIZZLE_DIALECT).toBe("postgresql");
|
|
55
53
|
expect(ctx.overlay.DATABASE_URL).toBe("postgres://u:p@127.0.0.1:5432/oke");
|
|
@@ -60,16 +58,16 @@ describe("resolveDrizzleKitEnv", () => {
|
|
|
60
58
|
}
|
|
61
59
|
});
|
|
62
60
|
|
|
63
|
-
test("
|
|
61
|
+
test("dev dialect is postgresql even when DATABASE_URL unset (no URL guessing)", async () => {
|
|
64
62
|
const dir = await tempDir();
|
|
65
|
-
const ctx = await resolveDrizzleKitEnv(dir, dualConfig, "
|
|
63
|
+
const ctx = await resolveDrizzleKitEnv(dir, dualConfig, "dev");
|
|
66
64
|
expect(ctx.dialect).toBe("postgresql");
|
|
67
65
|
});
|
|
68
66
|
|
|
69
67
|
test("unsupported driver id errors clearly", async () => {
|
|
70
68
|
const dir = await tempDir();
|
|
71
|
-
const bad = { drivers: { store: { sql: {
|
|
72
|
-
await expect(resolveDrizzleKitEnv(dir, bad, "
|
|
69
|
+
const bad = { drivers: { store: { sql: { test: "memory" } } } } as unknown as OkeConfig;
|
|
70
|
+
await expect(resolveDrizzleKitEnv(dir, bad, "test")).rejects.toThrow(/not supported/);
|
|
73
71
|
});
|
|
74
72
|
});
|
|
75
73
|
|