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.
- package/package.json +76 -10
- package/site/content/docs/ai/mcp.mdx +0 -1
- 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 -13
- package/site/content/docs/elements/channel.mdx +9 -13
- package/site/content/docs/elements/clock.mdx +8 -14
- package/site/content/docs/elements/flow.mdx +46 -54
- package/site/content/docs/elements/gate.mdx +6 -11
- package/site/content/docs/elements/signal.mdx +2 -8
- package/site/content/docs/elements/store.mdx +79 -93
- package/site/content/docs/elements/vault.mdx +89 -76
- package/site/content/docs/get-started/basic-usage.mdx +8 -14
- 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 +3 -4
- package/site/content/docs/index.mdx +0 -1
- package/site/content/docs/meta.json +0 -1
- 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 +14 -7
- 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 +30 -16
- 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 +8 -9
- 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 +246 -114
- package/src/cli/dev.ts +153 -205
- 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 +142 -9
- 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 +44 -10
- package/src/cli/load-config.source.test.ts +50 -0
- package/src/cli/load-config.ts +35 -29
- 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 +29 -11
- 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/extract.ts +12 -0
- package/src/compiler/fixtures/skyport/oke.config.ts +7 -5
- package/src/compiler/fixtures/skyport/src/flows/payments/index.ts +1 -1
- package/src/compiler/fixtures/skyport.expected.json +1 -1
- package/src/compiler/generate-adopt.ts +27 -1
- package/src/compiler-entry.ts +7 -0
- package/src/config/define-config.test.ts +111 -47
- package/src/config/driver-defaults.test.ts +115 -0
- package/src/config/driver-defaults.ts +156 -0
- package/src/config/index.ts +246 -131
- 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 +163 -0
- 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 +5 -4
- package/src/kernel/boot-bind/clock.ts +5 -5
- package/src/kernel/boot-bind/gate.ts +6 -7
- package/src/kernel/boot-bind/honor-config.test.ts +42 -42
- package/src/kernel/boot-bind/journal.ts +5 -5
- package/src/kernel/boot-bind/signal.ts +3 -1
- package/src/kernel/boot-bind/store.test.ts +55 -37
- package/src/kernel/boot-bind/store.ts +24 -34
- package/src/kernel/boot-bind/vault.ts +19 -4
- 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/console/access.mdx +0 -29
- package/site/content/docs/console/ai.mdx +0 -35
- package/site/content/docs/console/architecture.mdx +0 -35
- package/site/content/docs/console/channels.mdx +0 -37
- package/site/content/docs/console/clock.mdx +0 -51
- package/site/content/docs/console/flows.mdx +0 -31
- package/site/content/docs/console/gates.mdx +0 -73
- package/site/content/docs/console/index.mdx +0 -54
- package/site/content/docs/console/manifest-diff.mdx +0 -34
- package/site/content/docs/console/meta.json +0 -24
- package/site/content/docs/console/overview.mdx +0 -40
- package/site/content/docs/console/plugins.mdx +0 -41
- package/site/content/docs/console/privacy.mdx +0 -32
- package/site/content/docs/console/runs.mdx +0 -50
- package/site/content/docs/console/signals.mdx +0 -40
- package/site/content/docs/console/store.mdx +0 -32
- package/site/content/docs/console/tenancy.mdx +0 -32
- package/site/content/docs/console/traces.mdx +0 -38
- package/site/content/docs/console/vault.mdx +0 -41
- 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
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OKE ASCII wordmark — same art as `oke dev` / {@link formatOkeWordmark}.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Box, Text } from "ink";
|
|
6
|
+
import type { ReactElement } from "react";
|
|
7
|
+
import { TUI_BRAND, TUI_MUTED } from "../theme.ts";
|
|
8
|
+
|
|
9
|
+
/** Plain wordmark lines (no ANSI) — matches `formatOkeWordmark`. */
|
|
10
|
+
export const OKE_WORDMARK_LINES: readonly string[] = [
|
|
11
|
+
" ██████╗ ██╗ ██╗███████╗",
|
|
12
|
+
" ██╔═══██╗██║ ██╔╝██╔════╝",
|
|
13
|
+
" ██║ ██║█████╔╝ █████╗ ",
|
|
14
|
+
" ██║ ██║██╔═██╗ ██╔══╝ ",
|
|
15
|
+
" ╚██████╔╝██║ ██╗███████╗",
|
|
16
|
+
" ╚═════╝ ╚═╝ ╚═╝╚══════╝",
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
/** Props for {@link Wordmark}. */
|
|
20
|
+
export type WordmarkProps = {
|
|
21
|
+
readonly version?: string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Green block-letter OKE + version.
|
|
26
|
+
*
|
|
27
|
+
* @param props - Optional pinned version
|
|
28
|
+
*/
|
|
29
|
+
export function Wordmark(props: WordmarkProps): ReactElement {
|
|
30
|
+
const version = props.version ?? "0.10.3";
|
|
31
|
+
return (
|
|
32
|
+
<Box flexDirection="column">
|
|
33
|
+
{OKE_WORDMARK_LINES.map((line) => (
|
|
34
|
+
<Text key={line} bold color={TUI_BRAND}>
|
|
35
|
+
{line}
|
|
36
|
+
</Text>
|
|
37
|
+
))}
|
|
38
|
+
<Text color={TUI_MUTED}>
|
|
39
|
+
{" "}v{version}
|
|
40
|
+
</Text>
|
|
41
|
+
</Box>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared TUI format helpers.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Format ms as compact uptime (`4m 21s`).
|
|
7
|
+
*
|
|
8
|
+
* @param ms - Elapsed milliseconds
|
|
9
|
+
*/
|
|
10
|
+
export function formatUptime(ms: number): string {
|
|
11
|
+
const s = Math.max(0, Math.floor(ms / 1000));
|
|
12
|
+
const m = Math.floor(s / 60);
|
|
13
|
+
const h = Math.floor(m / 60);
|
|
14
|
+
if (h > 0) return `${h}h ${m % 60}m`;
|
|
15
|
+
if (m > 0) return `${m}m ${s % 60}s`;
|
|
16
|
+
return `${s}s`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Open a URL in the default browser (best-effort).
|
|
21
|
+
*
|
|
22
|
+
* @param url - http(s) URL
|
|
23
|
+
*/
|
|
24
|
+
export async function openUrl(url: string): Promise<void> {
|
|
25
|
+
const platform = process.platform;
|
|
26
|
+
const cmd =
|
|
27
|
+
platform === "darwin"
|
|
28
|
+
? ["open", url]
|
|
29
|
+
: platform === "win32"
|
|
30
|
+
? ["cmd", "/c", "start", "", url]
|
|
31
|
+
: ["xdg-open", url];
|
|
32
|
+
try {
|
|
33
|
+
const proc = Bun.spawn(cmd, { stdout: "ignore", stderr: "ignore" });
|
|
34
|
+
await proc.exited;
|
|
35
|
+
} catch {
|
|
36
|
+
// ignore — TUI still shows the link
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { mapDevControlInput } from "./keys.ts";
|
|
3
|
+
|
|
4
|
+
describe("mapDevControlInput", () => {
|
|
5
|
+
test("maps ? r q u x and aliases", () => {
|
|
6
|
+
expect(mapDevControlInput("?", {})).toBe("?");
|
|
7
|
+
expect(mapDevControlInput("h", {})).toBe("?");
|
|
8
|
+
expect(mapDevControlInput("r", {})).toBe("r");
|
|
9
|
+
expect(mapDevControlInput("q", {})).toBe("q");
|
|
10
|
+
expect(mapDevControlInput("u", {})).toBe("u");
|
|
11
|
+
expect(mapDevControlInput("x", {})).toBe("x");
|
|
12
|
+
expect(mapDevControlInput("c", {})).toBeNull();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test("maps Ctrl+C to quit", () => {
|
|
16
|
+
expect(mapDevControlInput("c", { ctrl: true })).toBe("q");
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared keyboard mapping for Ink `useInput` and pure tests.
|
|
3
|
+
* Parity with legacy `parseDevControlKey` (`? r q u x`).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** Dev control action from a keypress. */
|
|
7
|
+
export type DevControlKey = "?" | "r" | "q" | "u" | "x";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Map Ink input (+ ctrl) to a control key.
|
|
11
|
+
*
|
|
12
|
+
* @param input - Character from useInput
|
|
13
|
+
* @param key - Ink key modifiers
|
|
14
|
+
*/
|
|
15
|
+
export function mapDevControlInput(
|
|
16
|
+
input: string,
|
|
17
|
+
key: { readonly ctrl?: boolean },
|
|
18
|
+
): DevControlKey | null {
|
|
19
|
+
if (key.ctrl && input === "c") return "q";
|
|
20
|
+
if (input === "?" || input === "h" || input === "H") return "?";
|
|
21
|
+
if (input === "q" || input === "Q") return "q";
|
|
22
|
+
if (input === "r" || input === "R") return "r";
|
|
23
|
+
if (input === "u" || input === "U") return "u";
|
|
24
|
+
if (input === "x" || input === "X") return "x";
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { clearTerminalScreen } from "../../term.ts";
|
|
3
|
+
import { canRenderTui, clearTerminalViewport } from "./launch.ts";
|
|
4
|
+
|
|
5
|
+
describe("canRenderTui", () => {
|
|
6
|
+
test("requires stdout.isTTY", () => {
|
|
7
|
+
expect(canRenderTui({ isTTY: true })).toBe(true);
|
|
8
|
+
expect(canRenderTui({ isTTY: false })).toBe(false);
|
|
9
|
+
expect(canRenderTui({})).toBe(false);
|
|
10
|
+
});
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
describe("clearTerminalViewport", () => {
|
|
14
|
+
test("writes the same clear sequence as oke dev", () => {
|
|
15
|
+
expect(clearTerminalScreen()).toContain("\u001b[2J");
|
|
16
|
+
expect(typeof clearTerminalViewport).toBe("function");
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Launch the interactive Ink TUI (dynamic-imported from the bin).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { clearTerminalScreen } from "../../term.ts";
|
|
6
|
+
import { EXIT_OK, EXIT_RUNTIME, EXIT_USAGE } from "../exit.ts";
|
|
7
|
+
import { formatOkeHelp } from "../registry.ts";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* True when stdout can host an interactive Ink app.
|
|
11
|
+
*
|
|
12
|
+
* @param stream - stdout-like
|
|
13
|
+
*/
|
|
14
|
+
export function canRenderTui(stream: { readonly isTTY?: boolean } = process.stdout): boolean {
|
|
15
|
+
return stream.isTTY === true;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Wipe the viewport so boot logs never sit above the Ink frame.
|
|
20
|
+
*/
|
|
21
|
+
export function clearTerminalViewport(): void {
|
|
22
|
+
process.stdout.write(clearTerminalScreen());
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Start the TUI. Call only after {@link canRenderTui} is true.
|
|
27
|
+
*
|
|
28
|
+
* @param cwd - Project root
|
|
29
|
+
*/
|
|
30
|
+
export async function launchTui(cwd: string = process.cwd()): Promise<number> {
|
|
31
|
+
try {
|
|
32
|
+
clearTerminalViewport();
|
|
33
|
+
const React = await import("react");
|
|
34
|
+
const { render } = await import("ink");
|
|
35
|
+
const { OkeTuiApp } = await import("./App.tsx");
|
|
36
|
+
const instance = render(React.createElement(OkeTuiApp, { cwd }), {
|
|
37
|
+
exitOnCtrlC: false,
|
|
38
|
+
});
|
|
39
|
+
await instance.waitUntilExit();
|
|
40
|
+
return EXIT_OK;
|
|
41
|
+
} catch (err) {
|
|
42
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
43
|
+
const missing =
|
|
44
|
+
/Cannot find (module|package)|ERR_MODULE_NOT_FOUND|Cannot find package/i.test(msg) ||
|
|
45
|
+
msg.includes("'ink'") ||
|
|
46
|
+
msg.includes('"ink"');
|
|
47
|
+
if (missing) {
|
|
48
|
+
console.error("oke: interactive TUI requires optional deps `ink` and `react`.");
|
|
49
|
+
console.error(" bun add ink react");
|
|
50
|
+
console.error(" Or run `oke --help` / `oke <command>` without the TUI.");
|
|
51
|
+
console.log(formatOkeHelp());
|
|
52
|
+
return EXIT_USAGE;
|
|
53
|
+
}
|
|
54
|
+
console.error(`oke: TUI failed — ${msg}`);
|
|
55
|
+
return EXIT_RUNTIME;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Landing panel — OKE wordmark, session, aligned services, quick actions.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Box, Text, useInput } from "ink";
|
|
6
|
+
import { useEffect, useState, type ReactElement } from "react";
|
|
7
|
+
import type { FooterStatus } from "../components/StatusBar.tsx";
|
|
8
|
+
import { StatusDot, statusWord } from "../components/StatusDot.tsx";
|
|
9
|
+
import { Wordmark } from "../components/Wordmark.tsx";
|
|
10
|
+
import { formatUptime, openUrl } from "../format.ts";
|
|
11
|
+
import { pollPortSnapshot, type PortSnapshot } from "../state/ports.ts";
|
|
12
|
+
import { TUI_ACCENT, TUI_MUTED, TUI_OK, TUI_WARN } from "../theme.ts";
|
|
13
|
+
|
|
14
|
+
/** Props for {@link DashboardPanel}. */
|
|
15
|
+
export type DashboardPanelProps = {
|
|
16
|
+
readonly cwd: string;
|
|
17
|
+
readonly active: boolean;
|
|
18
|
+
readonly onJumpPanel: (panelId: string) => void;
|
|
19
|
+
readonly onStatus: (status: FooterStatus) => void;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const ACTIONS = [
|
|
23
|
+
{ id: "open", label: "[O]pen Console", key: "o" },
|
|
24
|
+
{ id: "mcp", label: "[M]CP Docs", key: "m" },
|
|
25
|
+
{ id: "dev", label: "[D]ev Panel", key: "d" },
|
|
26
|
+
] as const;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Dashboard panel — polls ports every 1.5s.
|
|
30
|
+
*
|
|
31
|
+
* @param props - cwd + focus/actions
|
|
32
|
+
*/
|
|
33
|
+
export function DashboardPanel(props: DashboardPanelProps): ReactElement {
|
|
34
|
+
const [snap, setSnap] = useState<PortSnapshot | null>(null);
|
|
35
|
+
const [startedAt, setStartedAt] = useState<number | null>(null);
|
|
36
|
+
const [now, setNow] = useState(Date.now());
|
|
37
|
+
const [selected, setSelected] = useState(0);
|
|
38
|
+
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
let cancelled = false;
|
|
41
|
+
const tick = async (): Promise<void> => {
|
|
42
|
+
const next = await pollPortSnapshot(props.cwd);
|
|
43
|
+
if (cancelled) return;
|
|
44
|
+
setSnap(next);
|
|
45
|
+
if (next.ownership === "managed") {
|
|
46
|
+
setStartedAt((prev) => prev ?? Date.now());
|
|
47
|
+
} else {
|
|
48
|
+
setStartedAt(null);
|
|
49
|
+
}
|
|
50
|
+
setNow(Date.now());
|
|
51
|
+
};
|
|
52
|
+
void tick();
|
|
53
|
+
const id = setInterval(() => void tick(), 1500);
|
|
54
|
+
return () => {
|
|
55
|
+
cancelled = true;
|
|
56
|
+
clearInterval(id);
|
|
57
|
+
};
|
|
58
|
+
}, [props.cwd]);
|
|
59
|
+
|
|
60
|
+
// Prefer lock startedAt when available via re-read
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
let cancelled = false;
|
|
63
|
+
void (async () => {
|
|
64
|
+
const { readDevSessionLock } = await import("../../dev-session-lock.ts");
|
|
65
|
+
const lock = await readDevSessionLock(props.cwd);
|
|
66
|
+
if (!cancelled && lock) {
|
|
67
|
+
const t = Date.parse(lock.startedAt);
|
|
68
|
+
if (Number.isFinite(t)) setStartedAt(t);
|
|
69
|
+
}
|
|
70
|
+
})();
|
|
71
|
+
return () => {
|
|
72
|
+
cancelled = true;
|
|
73
|
+
};
|
|
74
|
+
}, [props.cwd, snap?.ownership]);
|
|
75
|
+
|
|
76
|
+
useInput(
|
|
77
|
+
(input, key) => {
|
|
78
|
+
if (!props.active) return;
|
|
79
|
+
if (key.upArrow) setSelected((i) => Math.max(0, i - 1));
|
|
80
|
+
if (key.downArrow) setSelected((i) => Math.min(ACTIONS.length - 1, i + 1));
|
|
81
|
+
const lower = input.toLowerCase();
|
|
82
|
+
if (lower === "o") void runAction("open");
|
|
83
|
+
else if (lower === "m") void runAction("mcp");
|
|
84
|
+
else if (lower === "d") void runAction("dev");
|
|
85
|
+
else if (key.return) {
|
|
86
|
+
const a = ACTIONS[selected];
|
|
87
|
+
if (a) void runAction(a.id);
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
{ isActive: props.active },
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const runAction = async (id: (typeof ACTIONS)[number]["id"]): Promise<void> => {
|
|
94
|
+
if (id === "dev") {
|
|
95
|
+
props.onJumpPanel("dev");
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const consoleSurf = snap?.surfaces.find((s) => s.id === "console");
|
|
99
|
+
const docsSurf = snap?.surfaces.find((s) => s.id === "docsMcp");
|
|
100
|
+
if (id === "open") {
|
|
101
|
+
const url = consoleSurf?.url ?? "http://127.0.0.1:6533";
|
|
102
|
+
props.onStatus({ kind: "running", label: `Opening ${url}…` });
|
|
103
|
+
await openUrl(url);
|
|
104
|
+
props.onStatus({ kind: "done", label: "Opened Console" });
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const url = docsSurf?.url ?? "http://127.0.0.1:6536";
|
|
108
|
+
props.onStatus({ kind: "running", label: `Opening ${url}…` });
|
|
109
|
+
await openUrl(url);
|
|
110
|
+
props.onStatus({ kind: "done", label: "Opened MCP Docs" });
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const ownership = snap?.ownership ?? "stopped";
|
|
114
|
+
const ownershipLabel =
|
|
115
|
+
ownership === "managed"
|
|
116
|
+
? "Managed"
|
|
117
|
+
: ownership === "external"
|
|
118
|
+
? "Connected (external)"
|
|
119
|
+
: "Stopped";
|
|
120
|
+
const ownershipColor = ownership === "managed" || ownership === "external" ? TUI_OK : TUI_WARN;
|
|
121
|
+
const uptime = startedAt && ownership === "managed" ? formatUptime(now - startedAt) : null;
|
|
122
|
+
|
|
123
|
+
return (
|
|
124
|
+
<Box flexDirection="column" gap={1} flexGrow={1}>
|
|
125
|
+
<Box justifyContent="space-between" alignItems="flex-start">
|
|
126
|
+
<Wordmark version="0.10.3" />
|
|
127
|
+
</Box>
|
|
128
|
+
|
|
129
|
+
<Box flexDirection="column">
|
|
130
|
+
<Text bold color={TUI_ACCENT}>
|
|
131
|
+
Session
|
|
132
|
+
</Text>
|
|
133
|
+
<Box gap={1}>
|
|
134
|
+
<StatusDot
|
|
135
|
+
status={ownership === "managed" || ownership === "external" ? "ready" : "idle"}
|
|
136
|
+
/>
|
|
137
|
+
<Text bold color={ownershipColor}>
|
|
138
|
+
{ownershipLabel}
|
|
139
|
+
</Text>
|
|
140
|
+
{uptime ? <Text color={TUI_MUTED}>· uptime {uptime}</Text> : null}
|
|
141
|
+
</Box>
|
|
142
|
+
{ownership === "external" ? (
|
|
143
|
+
<Text color={TUI_WARN}>Read-only — will not start a second stack</Text>
|
|
144
|
+
) : null}
|
|
145
|
+
{ownership === "stopped" ? (
|
|
146
|
+
<Text color={TUI_MUTED}>No live oke dev — press [D] or open Dev panel</Text>
|
|
147
|
+
) : null}
|
|
148
|
+
</Box>
|
|
149
|
+
|
|
150
|
+
<Box flexDirection="column" flexGrow={1}>
|
|
151
|
+
<Box>
|
|
152
|
+
<Box width={14}>
|
|
153
|
+
<Text bold color={TUI_ACCENT}>
|
|
154
|
+
Services
|
|
155
|
+
</Text>
|
|
156
|
+
</Box>
|
|
157
|
+
<Box width={8}>
|
|
158
|
+
<Text color={TUI_MUTED}>Port</Text>
|
|
159
|
+
</Box>
|
|
160
|
+
<Box width={10}>
|
|
161
|
+
<Text color={TUI_MUTED}>Status</Text>
|
|
162
|
+
</Box>
|
|
163
|
+
<Text color={TUI_MUTED}>URL</Text>
|
|
164
|
+
</Box>
|
|
165
|
+
<Text color={TUI_MUTED}>{"─".repeat(56)}</Text>
|
|
166
|
+
{(snap?.surfaces ?? []).map((s) => {
|
|
167
|
+
const sw = statusWord(s.status);
|
|
168
|
+
return (
|
|
169
|
+
<Box key={s.id}>
|
|
170
|
+
<Box width={14} gap={1}>
|
|
171
|
+
<StatusDot status={s.status} />
|
|
172
|
+
<Text>{s.label.padEnd(9)}</Text>
|
|
173
|
+
</Box>
|
|
174
|
+
<Box width={8}>
|
|
175
|
+
<Text color={TUI_ACCENT}>{`:${String(s.port).padEnd(5)}`}</Text>
|
|
176
|
+
</Box>
|
|
177
|
+
<Box width={10}>
|
|
178
|
+
<Text color={sw.color}>{sw.word}</Text>
|
|
179
|
+
</Box>
|
|
180
|
+
<Text color={TUI_MUTED}>{s.url}</Text>
|
|
181
|
+
</Box>
|
|
182
|
+
);
|
|
183
|
+
})}
|
|
184
|
+
{!snap ? <Text color={TUI_MUTED}>Probing ports…</Text> : null}
|
|
185
|
+
</Box>
|
|
186
|
+
|
|
187
|
+
<Box gap={2}>
|
|
188
|
+
{ACTIONS.map((a, i) => (
|
|
189
|
+
<Text
|
|
190
|
+
key={a.id}
|
|
191
|
+
bold={props.active && i === selected}
|
|
192
|
+
color={props.active && i === selected ? TUI_ACCENT : TUI_MUTED}
|
|
193
|
+
>
|
|
194
|
+
{props.active && i === selected ? "› " : " "}
|
|
195
|
+
{a.label}
|
|
196
|
+
</Text>
|
|
197
|
+
))}
|
|
198
|
+
</Box>
|
|
199
|
+
</Box>
|
|
200
|
+
);
|
|
201
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database panel — 3-column action cards, no nested footer.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Box, Text, useInput } from "ink";
|
|
6
|
+
import { useState, type ReactElement } from "react";
|
|
7
|
+
import { ConfirmDialog } from "../components/ConfirmDialog.tsx";
|
|
8
|
+
import type { FooterStatus } from "../components/StatusBar.tsx";
|
|
9
|
+
import { TUI_ACCENT, TUI_MUTED, TUI_WARN } from "../theme.ts";
|
|
10
|
+
|
|
11
|
+
/** Props for {@link DatabasePanel}. */
|
|
12
|
+
export type DatabasePanelProps = {
|
|
13
|
+
readonly cwd: string;
|
|
14
|
+
readonly active: boolean;
|
|
15
|
+
readonly onLog: (line: string) => void;
|
|
16
|
+
readonly onStatus: (status: FooterStatus) => void;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
type DbAction = "push" | "generate" | "migrate" | "seed" | "studio";
|
|
20
|
+
|
|
21
|
+
type Card = {
|
|
22
|
+
readonly id: DbAction;
|
|
23
|
+
readonly key: string;
|
|
24
|
+
readonly title: string;
|
|
25
|
+
readonly blurb: string;
|
|
26
|
+
readonly confirm: boolean;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const CARDS: readonly Card[] = [
|
|
30
|
+
{
|
|
31
|
+
id: "push",
|
|
32
|
+
key: "P",
|
|
33
|
+
title: "Push",
|
|
34
|
+
blurb: "sync schema → live DB",
|
|
35
|
+
confirm: true,
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: "generate",
|
|
39
|
+
key: "G",
|
|
40
|
+
title: "Generate",
|
|
41
|
+
blurb: "write SQL under drizzle/",
|
|
42
|
+
confirm: false,
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: "migrate",
|
|
46
|
+
key: "M",
|
|
47
|
+
title: "Migrate",
|
|
48
|
+
blurb: "apply pending migrations",
|
|
49
|
+
confirm: true,
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: "seed",
|
|
53
|
+
key: "E",
|
|
54
|
+
title: "Seed",
|
|
55
|
+
blurb: "run defineSeed",
|
|
56
|
+
confirm: true,
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: "studio",
|
|
60
|
+
key: "T",
|
|
61
|
+
title: "Studio",
|
|
62
|
+
blurb: "drizzle-kit (browser)",
|
|
63
|
+
confirm: false,
|
|
64
|
+
},
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Database actions as a selectable card grid.
|
|
69
|
+
*
|
|
70
|
+
* @param props - cwd + log sink
|
|
71
|
+
*/
|
|
72
|
+
export function DatabasePanel(props: DatabasePanelProps): ReactElement {
|
|
73
|
+
const [selected, setSelected] = useState(0);
|
|
74
|
+
const [pending, setPending] = useState<DbAction | null>(null);
|
|
75
|
+
const [busy, setBusy] = useState(false);
|
|
76
|
+
|
|
77
|
+
useInput(
|
|
78
|
+
(input, key) => {
|
|
79
|
+
if (!props.active || pending || busy) return;
|
|
80
|
+
if (key.leftArrow) setSelected((i) => Math.max(0, i - 1));
|
|
81
|
+
if (key.rightArrow) setSelected((i) => Math.min(CARDS.length - 1, i + 1));
|
|
82
|
+
if (key.upArrow) setSelected((i) => Math.max(0, i - 3));
|
|
83
|
+
if (key.downArrow) setSelected((i) => Math.min(CARDS.length - 1, i + 3));
|
|
84
|
+
const upper = input.toUpperCase();
|
|
85
|
+
const byKey = CARDS.findIndex((c) => c.key === upper);
|
|
86
|
+
if (byKey >= 0) {
|
|
87
|
+
setSelected(byKey);
|
|
88
|
+
const card = CARDS[byKey]!;
|
|
89
|
+
if (card.confirm) setPending(card.id);
|
|
90
|
+
else void run(card.id);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (key.return) {
|
|
94
|
+
const action = CARDS[selected];
|
|
95
|
+
if (!action) return;
|
|
96
|
+
if (action.confirm) setPending(action.id);
|
|
97
|
+
else void run(action.id);
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
{ isActive: props.active && !pending && !busy },
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
const run = async (id: DbAction): Promise<void> => {
|
|
104
|
+
setBusy(true);
|
|
105
|
+
setPending(null);
|
|
106
|
+
props.onStatus({ kind: "running", label: `Running oke db ${id}…` });
|
|
107
|
+
props.onLog(`oke db ${id}\n`);
|
|
108
|
+
const prevCwd = process.cwd();
|
|
109
|
+
try {
|
|
110
|
+
process.chdir(props.cwd);
|
|
111
|
+
const { dbCli } = await import("../../db.ts");
|
|
112
|
+
const writes: string[] = [];
|
|
113
|
+
const origLog = console.log;
|
|
114
|
+
const origErr = console.error;
|
|
115
|
+
console.log = (...args: unknown[]) => {
|
|
116
|
+
writes.push(args.map(String).join(" ") + "\n");
|
|
117
|
+
};
|
|
118
|
+
console.error = (...args: unknown[]) => {
|
|
119
|
+
writes.push(args.map(String).join(" ") + "\n");
|
|
120
|
+
};
|
|
121
|
+
let code = 1;
|
|
122
|
+
try {
|
|
123
|
+
code = await dbCli([id, "--force"]);
|
|
124
|
+
} finally {
|
|
125
|
+
console.log = origLog;
|
|
126
|
+
console.error = origErr;
|
|
127
|
+
}
|
|
128
|
+
for (const w of writes) props.onLog(w);
|
|
129
|
+
props.onStatus({
|
|
130
|
+
kind: "done",
|
|
131
|
+
label: code === 0 ? `db ${id} ok` : `db ${id} exited ${code}`,
|
|
132
|
+
});
|
|
133
|
+
} catch (err) {
|
|
134
|
+
const m = err instanceof Error ? err.message : String(err);
|
|
135
|
+
props.onLog(`${m}\n`);
|
|
136
|
+
props.onStatus({ kind: "done", label: m });
|
|
137
|
+
} finally {
|
|
138
|
+
process.chdir(prevCwd);
|
|
139
|
+
setBusy(false);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
if (pending) {
|
|
144
|
+
return (
|
|
145
|
+
<ConfirmDialog
|
|
146
|
+
message={`Run oke db ${pending}? This may change the live database.`}
|
|
147
|
+
onConfirm={() => void run(pending)}
|
|
148
|
+
onCancel={() => setPending(null)}
|
|
149
|
+
/>
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const row1 = CARDS.slice(0, 3);
|
|
154
|
+
const row2 = CARDS.slice(3);
|
|
155
|
+
|
|
156
|
+
return (
|
|
157
|
+
<Box flexDirection="column" gap={1} flexGrow={1}>
|
|
158
|
+
<Text bold color={TUI_ACCENT}>
|
|
159
|
+
Database
|
|
160
|
+
</Text>
|
|
161
|
+
<Text color={TUI_MUTED} dimColor>
|
|
162
|
+
Domain schema via drizzle-kit
|
|
163
|
+
</Text>
|
|
164
|
+
<Box gap={1}>
|
|
165
|
+
{row1.map((card, i) => (
|
|
166
|
+
<ActionCard key={card.id} card={card} selected={props.active && selected === i} />
|
|
167
|
+
))}
|
|
168
|
+
</Box>
|
|
169
|
+
<Box gap={1}>
|
|
170
|
+
{row2.map((card, i) => (
|
|
171
|
+
<ActionCard key={card.id} card={card} selected={props.active && selected === i + 3} />
|
|
172
|
+
))}
|
|
173
|
+
</Box>
|
|
174
|
+
<Text color={TUI_WARN}>! Push & Migrate modify the database — confirmation required</Text>
|
|
175
|
+
</Box>
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* One database action card.
|
|
181
|
+
*/
|
|
182
|
+
function ActionCard(props: { readonly card: Card; readonly selected: boolean }): ReactElement {
|
|
183
|
+
const { card, selected } = props;
|
|
184
|
+
return (
|
|
185
|
+
<Box
|
|
186
|
+
flexDirection="column"
|
|
187
|
+
borderStyle="single"
|
|
188
|
+
borderColor={selected ? TUI_ACCENT : TUI_MUTED}
|
|
189
|
+
width="32%"
|
|
190
|
+
paddingX={1}
|
|
191
|
+
>
|
|
192
|
+
<Text bold color={selected ? TUI_ACCENT : undefined}>
|
|
193
|
+
{selected ? "› " : " "}[{card.key}] {card.title}
|
|
194
|
+
</Text>
|
|
195
|
+
<Text color={TUI_MUTED}>{card.blurb}</Text>
|
|
196
|
+
</Box>
|
|
197
|
+
);
|
|
198
|
+
}
|