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,106 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import {
|
|
6
|
+
clearDevSessionLock,
|
|
7
|
+
isPidAlive,
|
|
8
|
+
parseDevSessionLock,
|
|
9
|
+
readDevSessionLock,
|
|
10
|
+
resolveDevOwnership,
|
|
11
|
+
writeDevSessionLock,
|
|
12
|
+
type DevSessionLock,
|
|
13
|
+
} from "./dev-session-lock.ts";
|
|
14
|
+
|
|
15
|
+
describe("dev-session-lock", () => {
|
|
16
|
+
let dir: string | undefined;
|
|
17
|
+
|
|
18
|
+
afterEach(async () => {
|
|
19
|
+
if (dir) await rm(dir, { recursive: true, force: true });
|
|
20
|
+
dir = undefined;
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("parseDevSessionLock accepts well-formed payloads", () => {
|
|
24
|
+
const lock: DevSessionLock = {
|
|
25
|
+
pid: 1,
|
|
26
|
+
startedAt: "2026-01-01T00:00:00.000Z",
|
|
27
|
+
cwd: "/tmp/app",
|
|
28
|
+
ports: { app: 6530, console: 6533, mcp: 6535, docsMcp: 6536 },
|
|
29
|
+
};
|
|
30
|
+
expect(parseDevSessionLock(lock)).toEqual(lock);
|
|
31
|
+
expect(parseDevSessionLock(null)).toBeNull();
|
|
32
|
+
expect(parseDevSessionLock({ pid: "x" })).toBeNull();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("isPidAlive recognizes this process", () => {
|
|
36
|
+
expect(isPidAlive(process.pid)).toBe(true);
|
|
37
|
+
expect(isPidAlive(999_999_999)).toBe(false);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("write/read/clear round-trip", async () => {
|
|
41
|
+
dir = await mkdtemp(join(tmpdir(), "oke-dev-lock-"));
|
|
42
|
+
const lock: DevSessionLock = {
|
|
43
|
+
pid: process.pid,
|
|
44
|
+
startedAt: new Date().toISOString(),
|
|
45
|
+
cwd: dir,
|
|
46
|
+
ports: { app: 6530, console: 6533, mcp: 6535, docsMcp: 6536 },
|
|
47
|
+
};
|
|
48
|
+
await writeDevSessionLock(dir, lock);
|
|
49
|
+
expect(await readDevSessionLock(dir)).toEqual(lock);
|
|
50
|
+
await clearDevSessionLock(dir);
|
|
51
|
+
expect(await readDevSessionLock(dir)).toBeNull();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("resolveDevOwnership: managed when lock pid alive and cwd matches", async () => {
|
|
55
|
+
dir = await mkdtemp(join(tmpdir(), "oke-dev-lock-"));
|
|
56
|
+
await writeDevSessionLock(dir, {
|
|
57
|
+
pid: process.pid,
|
|
58
|
+
startedAt: new Date().toISOString(),
|
|
59
|
+
cwd: dir,
|
|
60
|
+
ports: { app: 6530, console: 6533, mcp: 6535, docsMcp: 6536 },
|
|
61
|
+
});
|
|
62
|
+
const resolved = await resolveDevOwnership(
|
|
63
|
+
dir,
|
|
64
|
+
{ app: 6530, console: 6533, mcp: 6535, docsMcp: 6536 },
|
|
65
|
+
async () => false,
|
|
66
|
+
);
|
|
67
|
+
expect(resolved.ownership).toBe("managed");
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("resolveDevOwnership: external when ports busy without our lock", async () => {
|
|
71
|
+
dir = await mkdtemp(join(tmpdir(), "oke-dev-lock-"));
|
|
72
|
+
const resolved = await resolveDevOwnership(
|
|
73
|
+
dir,
|
|
74
|
+
{ app: 6530, console: 6533, mcp: 6535, docsMcp: 6536 },
|
|
75
|
+
async (port) => port === 6530,
|
|
76
|
+
);
|
|
77
|
+
expect(resolved.ownership).toBe("external");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("resolveDevOwnership: stopped when free and no lock", async () => {
|
|
81
|
+
dir = await mkdtemp(join(tmpdir(), "oke-dev-lock-"));
|
|
82
|
+
const resolved = await resolveDevOwnership(
|
|
83
|
+
dir,
|
|
84
|
+
{ app: 6530, console: 6533, mcp: 6535, docsMcp: 6536 },
|
|
85
|
+
async () => false,
|
|
86
|
+
);
|
|
87
|
+
expect(resolved.ownership).toBe("stopped");
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("stale lock (dead pid) is cleared and treated as stopped when ports free", async () => {
|
|
91
|
+
dir = await mkdtemp(join(tmpdir(), "oke-dev-lock-"));
|
|
92
|
+
await writeDevSessionLock(dir, {
|
|
93
|
+
pid: 999_999_999,
|
|
94
|
+
startedAt: new Date().toISOString(),
|
|
95
|
+
cwd: dir,
|
|
96
|
+
ports: { app: 6530, console: 6533, mcp: 6535, docsMcp: 6536 },
|
|
97
|
+
});
|
|
98
|
+
const resolved = await resolveDevOwnership(
|
|
99
|
+
dir,
|
|
100
|
+
{ app: 6530, console: 6533, mcp: 6535, docsMcp: 6536 },
|
|
101
|
+
async () => false,
|
|
102
|
+
);
|
|
103
|
+
expect(resolved.ownership).toBe("stopped");
|
|
104
|
+
expect(await readDevSessionLock(dir)).toBeNull();
|
|
105
|
+
});
|
|
106
|
+
});
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `.oke/dev.json` — session lock for owned `oke dev` / TUI sessions.
|
|
3
|
+
*
|
|
4
|
+
* Distinguishes managed (our pid) vs external (ports live, not our lock)
|
|
5
|
+
* so stop/restart never kills a foreign process.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { mkdir } from "node:fs/promises";
|
|
9
|
+
import { dirname, resolve } from "node:path";
|
|
10
|
+
import { isPortInUse } from "./ports.ts";
|
|
11
|
+
|
|
12
|
+
/** Relative path under the project root. */
|
|
13
|
+
export const DEV_SESSION_LOCK_REL = ".oke/dev.json";
|
|
14
|
+
|
|
15
|
+
/** Bound ports recorded in the session lock. */
|
|
16
|
+
export type DevSessionPorts = {
|
|
17
|
+
readonly app: number;
|
|
18
|
+
readonly console: number;
|
|
19
|
+
readonly mcp: number;
|
|
20
|
+
readonly docsMcp: number;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/** On-disk lock written when this process owns the dev session. */
|
|
24
|
+
export type DevSessionLock = {
|
|
25
|
+
readonly pid: number;
|
|
26
|
+
readonly ports: DevSessionPorts;
|
|
27
|
+
readonly startedAt: string;
|
|
28
|
+
readonly cwd: string;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/** How the TUI / controller sees the live stack. */
|
|
32
|
+
export type DevOwnership = "managed" | "external" | "stopped";
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Absolute path to the lock file for a project root.
|
|
36
|
+
*
|
|
37
|
+
* @param cwd - Project root
|
|
38
|
+
*/
|
|
39
|
+
export function devSessionLockPath(cwd: string): string {
|
|
40
|
+
return resolve(cwd, DEV_SESSION_LOCK_REL);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* True when `pid` looks alive on this host (signal 0).
|
|
45
|
+
*
|
|
46
|
+
* @param pid - Process id
|
|
47
|
+
*/
|
|
48
|
+
export function isPidAlive(pid: number): boolean {
|
|
49
|
+
if (!Number.isFinite(pid) || pid <= 0) return false;
|
|
50
|
+
try {
|
|
51
|
+
process.kill(pid, 0);
|
|
52
|
+
return true;
|
|
53
|
+
} catch {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Parse and validate a lock object from JSON.
|
|
60
|
+
*
|
|
61
|
+
* @param raw - Unknown JSON value
|
|
62
|
+
*/
|
|
63
|
+
export function parseDevSessionLock(raw: unknown): DevSessionLock | null {
|
|
64
|
+
if (raw === null || typeof raw !== "object") return null;
|
|
65
|
+
const o = raw as Record<string, unknown>;
|
|
66
|
+
if (typeof o["pid"] !== "number" || !Number.isFinite(o["pid"])) return null;
|
|
67
|
+
if (typeof o["startedAt"] !== "string" || typeof o["cwd"] !== "string") return null;
|
|
68
|
+
const ports = o["ports"];
|
|
69
|
+
if (ports === null || typeof ports !== "object") return null;
|
|
70
|
+
const p = ports as Record<string, unknown>;
|
|
71
|
+
for (const key of ["app", "console", "mcp", "docsMcp"] as const) {
|
|
72
|
+
if (typeof p[key] !== "number" || !Number.isFinite(p[key])) return null;
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
pid: o["pid"],
|
|
76
|
+
startedAt: o["startedAt"],
|
|
77
|
+
cwd: o["cwd"],
|
|
78
|
+
ports: {
|
|
79
|
+
app: p["app"] as number,
|
|
80
|
+
console: p["console"] as number,
|
|
81
|
+
mcp: p["mcp"] as number,
|
|
82
|
+
docsMcp: p["docsMcp"] as number,
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Read the session lock if present and well-formed.
|
|
89
|
+
*
|
|
90
|
+
* @param cwd - Project root
|
|
91
|
+
*/
|
|
92
|
+
export async function readDevSessionLock(cwd: string): Promise<DevSessionLock | null> {
|
|
93
|
+
const path = devSessionLockPath(cwd);
|
|
94
|
+
if (!(await Bun.file(path).exists())) return null;
|
|
95
|
+
try {
|
|
96
|
+
const raw: unknown = await Bun.file(path).json();
|
|
97
|
+
return parseDevSessionLock(raw);
|
|
98
|
+
} catch {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Write the session lock (creates `.oke/` as needed).
|
|
105
|
+
*
|
|
106
|
+
* @param cwd - Project root
|
|
107
|
+
* @param lock - Lock payload
|
|
108
|
+
*/
|
|
109
|
+
export async function writeDevSessionLock(cwd: string, lock: DevSessionLock): Promise<void> {
|
|
110
|
+
const path = devSessionLockPath(cwd);
|
|
111
|
+
await mkdir(dirname(path), { recursive: true });
|
|
112
|
+
await Bun.write(path, `${JSON.stringify(lock, null, 2)}\n`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Remove the lock file if present (best-effort).
|
|
117
|
+
*
|
|
118
|
+
* @param cwd - Project root
|
|
119
|
+
*/
|
|
120
|
+
export async function clearDevSessionLock(cwd: string): Promise<void> {
|
|
121
|
+
const path = devSessionLockPath(cwd);
|
|
122
|
+
try {
|
|
123
|
+
await Bun.file(path)
|
|
124
|
+
.exists()
|
|
125
|
+
.then(async (ok) => {
|
|
126
|
+
if (ok) await unlinkQuiet(path);
|
|
127
|
+
});
|
|
128
|
+
} catch {
|
|
129
|
+
// ignore
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* @param path - Absolute path
|
|
135
|
+
*/
|
|
136
|
+
async function unlinkQuiet(path: string): Promise<void> {
|
|
137
|
+
const { unlink } = await import("node:fs/promises");
|
|
138
|
+
try {
|
|
139
|
+
await unlink(path);
|
|
140
|
+
} catch {
|
|
141
|
+
// already gone
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Probe whether preferred ports are in use.
|
|
147
|
+
*
|
|
148
|
+
* @param ports - Ports to check
|
|
149
|
+
* @param probe - Injectable busy check
|
|
150
|
+
*/
|
|
151
|
+
export async function probeDevPortsBusy(
|
|
152
|
+
ports: DevSessionPorts,
|
|
153
|
+
probe: (port: number) => Promise<boolean> = isPortInUse,
|
|
154
|
+
): Promise<{
|
|
155
|
+
readonly app: boolean;
|
|
156
|
+
readonly console: boolean;
|
|
157
|
+
readonly mcp: boolean;
|
|
158
|
+
readonly docsMcp: boolean;
|
|
159
|
+
readonly any: boolean;
|
|
160
|
+
}> {
|
|
161
|
+
const app = ports.app !== 0 && (await probe(ports.app));
|
|
162
|
+
const consolePort = ports.console !== 0 && (await probe(ports.console));
|
|
163
|
+
const mcp = ports.mcp !== 0 && (await probe(ports.mcp));
|
|
164
|
+
const docsMcp = ports.docsMcp !== 0 && (await probe(ports.docsMcp));
|
|
165
|
+
return {
|
|
166
|
+
app,
|
|
167
|
+
console: consolePort,
|
|
168
|
+
mcp,
|
|
169
|
+
docsMcp,
|
|
170
|
+
any: app || consolePort || mcp || docsMcp,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Resolve ownership for a project: managed / external / stopped.
|
|
176
|
+
*
|
|
177
|
+
* @param cwd - Project root
|
|
178
|
+
* @param preferredPorts - Ports to probe when lock is missing/stale
|
|
179
|
+
* @param probe - Injectable busy check
|
|
180
|
+
*/
|
|
181
|
+
export async function resolveDevOwnership(
|
|
182
|
+
cwd: string,
|
|
183
|
+
preferredPorts: DevSessionPorts,
|
|
184
|
+
probe: (port: number) => Promise<boolean> = isPortInUse,
|
|
185
|
+
): Promise<{
|
|
186
|
+
readonly ownership: DevOwnership;
|
|
187
|
+
readonly lock: DevSessionLock | null;
|
|
188
|
+
readonly busy: Awaited<ReturnType<typeof probeDevPortsBusy>>;
|
|
189
|
+
}> {
|
|
190
|
+
const root = resolve(cwd);
|
|
191
|
+
const lock = await readDevSessionLock(root);
|
|
192
|
+
const ports = lock?.ports ?? preferredPorts;
|
|
193
|
+
const busy = await probeDevPortsBusy(ports, probe);
|
|
194
|
+
|
|
195
|
+
if (lock && isPidAlive(lock.pid) && resolve(lock.cwd) === root) {
|
|
196
|
+
return { ownership: "managed", lock, busy };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Stale lock (dead pid) — ignore for ownership; treat as free or external by ports.
|
|
200
|
+
if (lock && !isPidAlive(lock.pid)) {
|
|
201
|
+
await clearDevSessionLock(root);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (busy.app || busy.console) {
|
|
205
|
+
return { ownership: "external", lock: null, busy };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return { ownership: "stopped", lock: null, busy };
|
|
209
|
+
}
|