okengine 0.2.8 → 0.3.4
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/AGENTS.md +17 -15
- package/README.md +59 -31
- package/package.json +11 -16
- package/site/content/docs/ai/llms-txt.mdx +54 -0
- package/site/content/docs/ai/mcp.mdx +123 -0
- package/site/content/docs/ai/meta.json +5 -0
- package/site/content/docs/ai/skills.mdx +53 -0
- package/site/content/docs/console/access.mdx +29 -0
- package/site/content/docs/console/ai.mdx +35 -0
- package/site/content/docs/console/architecture.mdx +35 -0
- package/site/content/docs/console/channels.mdx +37 -0
- package/site/content/docs/console/clock.mdx +31 -0
- package/site/content/docs/console/flows.mdx +31 -0
- package/site/content/docs/console/gates.mdx +35 -0
- package/site/content/docs/console/manifest-diff.mdx +34 -0
- package/site/content/docs/console/meta.json +23 -0
- package/site/content/docs/console/overview.mdx +40 -0
- package/site/content/docs/console/plugins.mdx +41 -0
- package/site/content/docs/console/privacy.mdx +32 -0
- package/site/content/docs/console/runs.mdx +40 -0
- package/site/content/docs/console/signals.mdx +31 -0
- package/site/content/docs/console/store.mdx +32 -0
- package/site/content/docs/console/tenancy.mdx +32 -0
- package/site/content/docs/console/traces.mdx +34 -0
- package/site/content/docs/console/vault.mdx +37 -0
- package/site/content/docs/elements/ai.mdx +180 -0
- package/site/content/docs/elements/channel.mdx +167 -0
- package/site/content/docs/elements/clock.mdx +182 -0
- package/site/content/docs/elements/flow.mdx +288 -0
- package/site/content/docs/elements/gate.mdx +171 -0
- package/site/content/docs/elements/meta.json +5 -0
- package/site/content/docs/elements/signal.mdx +171 -0
- package/site/content/docs/elements/store.mdx +320 -0
- package/site/content/docs/elements/vault.mdx +263 -0
- package/site/content/docs/get-started/basic-usage.mdx +124 -0
- package/site/content/docs/get-started/comparison.mdx +65 -0
- package/site/content/docs/get-started/installation.mdx +113 -0
- package/site/content/docs/get-started/introduction.mdx +123 -0
- package/site/content/docs/get-started/meta.json +5 -0
- package/site/content/docs/index.mdx +63 -0
- package/site/content/docs/meta.json +5 -0
- package/site/content/docs/plugins/compression.mdx +60 -0
- package/site/content/docs/plugins/cors.mdx +98 -0
- package/site/content/docs/plugins/csrf.mdx +96 -0
- package/site/content/docs/plugins/ip-allowlist.mdx +93 -0
- package/site/content/docs/plugins/maintenance-mode.mdx +101 -0
- package/site/content/docs/plugins/meta.json +15 -0
- package/site/content/docs/plugins/security-headers.mdx +136 -0
- package/site/content/docs/reference/cli.md +101 -0
- package/site/content/docs/reference/configuration.mdx +159 -0
- package/site/content/docs/reference/environment-variables.mdx +87 -0
- package/site/content/docs/reference/errors.mdx +80 -0
- package/site/content/docs/reference/fx.mdx +117 -0
- package/site/content/docs/reference/meta.json +5 -0
- package/site/content/docs/reference/plugins.mdx +249 -0
- package/site/content/docs/reference/security.md +63 -0
- package/src/auth/auth.test.ts +3 -0
- package/src/cli/ask-dev-mode.ts +1 -1
- package/src/cli/db.ts +87 -17
- package/src/cli/dev-db-push.test.ts +32 -2
- package/src/cli/dev-schema-sync.test.ts +66 -0
- package/src/cli/dev-schema-sync.ts +139 -0
- package/src/cli/dev.test.ts +123 -1
- package/src/cli/dev.ts +150 -8
- package/src/cli/doc-staleness.test.ts +4 -4
- package/src/cli/docker-cli.test.ts +20 -0
- package/src/cli/docker.ts +10 -0
- package/src/cli/drizzle-env.test.ts +67 -0
- package/src/cli/drizzle-env.ts +78 -0
- package/src/cli/ensure-drizzle-config.ts +50 -0
- package/src/cli/hero-meta.test.ts +1 -1
- package/src/cli/load-config.ts +6 -0
- package/src/cli/mode.ts +24 -4
- package/src/cli/openbao-bootstrap.test.ts +149 -0
- package/src/cli/openbao-bootstrap.ts +314 -0
- package/src/cli/openbao-restart.integration.test.ts +136 -0
- package/src/cli/ports.test.ts +7 -5
- package/src/cli/ports.ts +6 -2
- package/src/cli/resolve-dev-sql-env.test.ts +48 -0
- package/src/cli/resolve-dev-sql-env.ts +42 -0
- package/src/cli/stack.ts +7 -4
- package/src/cli/vault-cmd.ts +63 -0
- package/src/client/types.ts +7 -1
- package/src/compiler/extract.test.ts +40 -0
- package/src/compiler/extract.ts +123 -1
- package/src/compiler/fixtures/skyport/oke.config.ts +2 -2
- package/src/compiler/fixtures/skyport.expected.json +1 -1
- package/src/compiler/response.ts +12 -0
- package/src/config/define-config.test.ts +4 -6
- package/src/config/index.ts +2 -15
- package/src/console/server/app.ts +2 -0
- package/src/console/server/vault.ts +21 -6
- package/src/docker/compose.ts +112 -16
- package/src/docker/derive.ts +7 -1
- package/src/docker/docker.test.ts +103 -0
- package/src/docker/index.ts +11 -1
- package/src/docker/recipes/index.ts +3 -2
- package/src/docker/recipes/openbao.ts +47 -0
- package/src/docker/recipes/redis.ts +5 -1
- package/src/docker/recipes/rustfs.ts +2 -3
- package/src/docker/stack-id.test.ts +43 -8
- package/src/docker/stack-id.ts +99 -20
- package/src/docker/stack.ts +36 -4
- package/src/docker/types.ts +3 -0
- package/src/docs-origin.ts +4 -4
- package/src/drivers/drizzle-dialect.test.ts +20 -0
- package/src/drivers/drizzle-dialect.ts +37 -0
- package/src/drivers/index.ts +1 -2
- package/src/drivers/memory.ts +278 -39
- package/src/drivers/s3.ts +10 -1
- package/src/drivers/vault-driver-removal.test.ts +55 -0
- package/src/drivers/vault-openbao.test.ts +97 -0
- package/src/drivers/vault-openbao.ts +102 -35
- package/src/drivers/vault-types.ts +3 -10
- package/src/elements/store/declare.ts +4 -1
- package/src/elements/store/resource-list-docs.fixture.ts +56 -0
- package/src/elements/store/resource-list-docs.test.ts +79 -0
- package/src/elements/store/resource.test.ts +253 -0
- package/src/elements/store/resource.ts +786 -0
- package/src/elements/store/sql-condition.test.ts +132 -0
- package/src/elements/store/sql-condition.ts +284 -46
- package/src/elements/store/sql-session.test.ts +86 -1
- package/src/elements/store/sql-session.ts +187 -27
- package/src/elements/store/table.ts +34 -4
- package/src/elements/store.ts +16 -0
- package/src/elements/vault/runtime.ts +1 -1
- package/src/elements/vault.test.ts +1 -28
- package/src/elements/vault.ts +1 -1
- package/src/kernel/app.ts +59 -25
- package/src/kernel/boot-bind/channel.test.ts +60 -0
- package/src/kernel/boot-bind/channel.ts +64 -2
- package/src/kernel/boot-bind/store.test.ts +10 -1
- package/src/kernel/boot-bind/store.ts +49 -2
- package/src/kernel/boot.test.ts +0 -1
- package/src/kernel/boot.ts +1 -1
- package/src/kernel/edge.test.ts +68 -0
- package/src/kernel/errors.registry.test.ts +1 -1
- package/src/kernel/flow.ts +8 -0
- package/src/kernel/fx.test.ts +23 -3
- package/src/kernel/fx.ts +115 -18
- package/src/kernel/hooks.test.ts +33 -0
- package/src/kernel/hooks.ts +22 -0
- package/src/kernel/index.ts +7 -0
- package/src/kernel/on.ts +44 -3
- package/src/kernel/plugin.ts +33 -3
- package/src/kernel/registry-isolation.test.ts +74 -0
- package/src/kernel/registry.ts +22 -1
- package/src/kernel/triggers.ts +59 -0
- package/src/manifest/fixtures/skyport.excerpt.json +1 -1
- package/src/manifest/fixtures/skyport.manifest.json +1 -1
- package/src/manifest/index.ts +1 -1
- package/src/manifest/types.ts +1 -1
- package/src/manifest/validate.ts +2 -2
- package/src/plugins/compression.test.ts +127 -0
- package/src/plugins/compression.ts +94 -0
- package/src/plugins/config-source.test.ts +204 -0
- package/src/plugins/config-source.ts +209 -0
- package/src/plugins/cors.test.ts +172 -0
- package/src/plugins/cors.ts +153 -0
- package/src/plugins/csrf.test.ts +102 -0
- package/src/plugins/csrf.ts +86 -0
- package/src/plugins/headers.ts +54 -0
- package/src/plugins/index.ts +26 -0
- package/src/plugins/ip-allowlist.test.ts +139 -0
- package/src/plugins/ip-allowlist.ts +129 -0
- package/src/plugins/maintenance-mode.test.ts +91 -0
- package/src/plugins/maintenance-mode.ts +85 -0
- package/src/plugins/security-headers.test.ts +243 -0
- package/src/plugins/security-headers.ts +255 -0
- package/src/release/measure.ts +1 -2
- package/src/test/create-test-app.ts +14 -2
- package/docs/spec/console.md +0 -762
- package/docs/spec/example.md +0 -1374
- package/docs/spec/four-applications.md +0 -1376
- package/docs/spec/unified-theory.md +0 -498
- package/src/cli/doc-drift.test.ts +0 -147
- package/src/cli/doc-drift.ts +0 -401
- package/src/cli/doctor-diff-examples.ts +0 -90
- package/src/drivers/vault-sops.ts +0 -246
- /package/{spec/manifest.v1.schema.json → manifest.v1.schema.json} +0 -0
|
@@ -6,7 +6,7 @@ import { afterEach, describe, expect, test } from "bun:test";
|
|
|
6
6
|
import { mkdir, mkdtemp, writeFile } from "node:fs/promises";
|
|
7
7
|
import { tmpdir } from "node:os";
|
|
8
8
|
import { join } from "node:path";
|
|
9
|
-
import { runDev, type DevSession } from "./dev.ts";
|
|
9
|
+
import { runDev, type DevSession, type DevWatchFn } from "./dev.ts";
|
|
10
10
|
|
|
11
11
|
const sessions: DevSession[] = [];
|
|
12
12
|
|
|
@@ -14,6 +14,30 @@ afterEach(() => {
|
|
|
14
14
|
for (const s of sessions.splice(0)) s.stop();
|
|
15
15
|
});
|
|
16
16
|
|
|
17
|
+
async function waitFor(predicate: () => boolean, timeoutMs = 3_000): Promise<void> {
|
|
18
|
+
const deadline = Date.now() + timeoutMs;
|
|
19
|
+
while (!predicate() && Date.now() < deadline) {
|
|
20
|
+
await Bun.sleep(20);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function controlledWatcher(): {
|
|
25
|
+
watchFs: DevWatchFn;
|
|
26
|
+
change(filename: string): void;
|
|
27
|
+
} {
|
|
28
|
+
let listener: Parameters<DevWatchFn>[2] | undefined;
|
|
29
|
+
return {
|
|
30
|
+
watchFs: (_path, _options, next) => {
|
|
31
|
+
listener = next;
|
|
32
|
+
return { close() {} };
|
|
33
|
+
},
|
|
34
|
+
change(filename) {
|
|
35
|
+
if (!listener) throw new Error("watcher not started");
|
|
36
|
+
listener("change", filename);
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
17
41
|
async function scaffoldProject(): Promise<string> {
|
|
18
42
|
const dir = await mkdtemp(join(tmpdir(), "oke-dev-db-"));
|
|
19
43
|
await mkdir(join(dir, "src"), { recursive: true });
|
|
@@ -51,6 +75,7 @@ describe("oke dev db auto-push", () => {
|
|
|
51
75
|
test("schema change triggers dbPush when auto-push is on", async () => {
|
|
52
76
|
const cwd = await scaffoldProject();
|
|
53
77
|
const pushes: string[] = [];
|
|
78
|
+
const watcher = controlledWatcher();
|
|
54
79
|
let resolveReady!: () => void;
|
|
55
80
|
const ready = new Promise<void>((r) => {
|
|
56
81
|
resolveReady = r;
|
|
@@ -88,6 +113,7 @@ describe("oke dev db auto-push", () => {
|
|
|
88
113
|
onDbAutoPush: () => {
|
|
89
114
|
resolveReady();
|
|
90
115
|
},
|
|
116
|
+
watchFs: watcher.watchFs,
|
|
91
117
|
onReady: async (session) => {
|
|
92
118
|
sessions.push(session);
|
|
93
119
|
},
|
|
@@ -108,13 +134,15 @@ export const entries = sqliteTable("entries", {
|
|
|
108
134
|
});
|
|
109
135
|
`,
|
|
110
136
|
);
|
|
111
|
-
|
|
137
|
+
watcher.change("schema.ts");
|
|
138
|
+
await waitFor(() => pushes.length > before);
|
|
112
139
|
expect(pushes.length).toBeGreaterThan(before);
|
|
113
140
|
});
|
|
114
141
|
|
|
115
142
|
test("--no-db-push / noDbPush never calls dbPush", async () => {
|
|
116
143
|
const cwd = await scaffoldProject();
|
|
117
144
|
const pushes: string[] = [];
|
|
145
|
+
const watcher = controlledWatcher();
|
|
118
146
|
|
|
119
147
|
const result = await runDev({
|
|
120
148
|
cwd,
|
|
@@ -145,6 +173,7 @@ export const entries = sqliteTable("entries", {
|
|
|
145
173
|
pushes.push("push");
|
|
146
174
|
return 0;
|
|
147
175
|
},
|
|
176
|
+
watchFs: watcher.watchFs,
|
|
148
177
|
onReady: async (session) => {
|
|
149
178
|
sessions.push(session);
|
|
150
179
|
},
|
|
@@ -157,6 +186,7 @@ export const entries = sqliteTable("entries", {
|
|
|
157
186
|
export const entries = sqliteTable("entries", { id: text("id").primaryKey(), x: text("x") });
|
|
158
187
|
`,
|
|
159
188
|
);
|
|
189
|
+
watcher.change("schema.ts");
|
|
160
190
|
await Bun.sleep(500);
|
|
161
191
|
expect(pushes).toHaveLength(0);
|
|
162
192
|
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mode-switch schema sync pipeline — ensure → emit → push, no data copy.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { afterEach, describe, expect, test } from "bun:test";
|
|
6
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
7
|
+
import { tmpdir } from "node:os";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import type { ConfigEnv } from "../config/index.ts";
|
|
10
|
+
import { sqlDialectForEnv, syncDevSchema } from "./dev-schema-sync.ts";
|
|
11
|
+
|
|
12
|
+
const dirs: string[] = [];
|
|
13
|
+
|
|
14
|
+
afterEach(async () => {
|
|
15
|
+
await Promise.all(dirs.splice(0).map((d) => rm(d, { recursive: true, force: true })));
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
async function tempDir(): Promise<string> {
|
|
19
|
+
const dir = await mkdtemp(join(tmpdir(), "oke-sync-"));
|
|
20
|
+
dirs.push(dir);
|
|
21
|
+
return dir;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
describe("sqlDialectForEnv", () => {
|
|
25
|
+
test("local → sqlite, docker → postgresql", async () => {
|
|
26
|
+
const config = {
|
|
27
|
+
drivers: {
|
|
28
|
+
store: { sql: { local: "sqlite", docker: "postgres", prod: "postgres", test: "memory" } },
|
|
29
|
+
},
|
|
30
|
+
} as never;
|
|
31
|
+
expect(sqlDialectForEnv(config, "local").dialect).toBe("sqlite");
|
|
32
|
+
expect(sqlDialectForEnv(config, "docker").dialect).toBe("postgresql");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("missing config → sqlite default", () => {
|
|
36
|
+
expect(sqlDialectForEnv(null, "local").dialect).toBe("sqlite");
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe("syncDevSchema", () => {
|
|
41
|
+
test("local: ensures drizzle config, emits, pushes with env", async () => {
|
|
42
|
+
const dir = await tempDir();
|
|
43
|
+
const calls: { cwd: string; env: ConfigEnv }[] = [];
|
|
44
|
+
const result = await syncDevSchema(dir, "local", {
|
|
45
|
+
write: () => {},
|
|
46
|
+
pushFn: async (cwd, env) => {
|
|
47
|
+
calls.push({ cwd, env });
|
|
48
|
+
return 0;
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
expect(result.dialect).toBe("sqlite");
|
|
52
|
+
expect(result.pushed).toBe(true);
|
|
53
|
+
expect(calls).toEqual([{ cwd: dir, env: "local" }]);
|
|
54
|
+
expect(await Bun.file(join(dir, "drizzle.config.ts")).exists()).toBe(true);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("push failure surfaces code without throwing", async () => {
|
|
58
|
+
const dir = await tempDir();
|
|
59
|
+
const result = await syncDevSchema(dir, "local", {
|
|
60
|
+
write: () => {},
|
|
61
|
+
pushFn: async () => 1,
|
|
62
|
+
});
|
|
63
|
+
expect(result.pushed).toBe(false);
|
|
64
|
+
expect(result.code).toBe(1);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared local↔docker schema sync pipeline for `oke mode` and `oke dev`
|
|
3
|
+
* one-shot start. Ensures artefacts, emits for the active dialect, then
|
|
4
|
+
* pushes via drizzle-kit. Data planes stay isolated — no row copying.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { resolve } from "node:path";
|
|
8
|
+
import { resolveDriverId, type ConfigEnv, type OkeConfig } from "../config/index.ts";
|
|
9
|
+
import {
|
|
10
|
+
DEFAULT_DOCKER_DIR,
|
|
11
|
+
deriveInfrastructure,
|
|
12
|
+
loadExistingStackControls,
|
|
13
|
+
loadExistingStackCredentials,
|
|
14
|
+
stackAppSlug,
|
|
15
|
+
stackInstanceId,
|
|
16
|
+
writeDerivedFiles,
|
|
17
|
+
} from "../docker/index.ts";
|
|
18
|
+
import { drizzleDialectFromSqlDriver } from "../drivers/drizzle-dialect.ts";
|
|
19
|
+
import { ensureDrizzleConfig } from "./ensure-drizzle-config.ts";
|
|
20
|
+
import { runDb } from "./db.ts";
|
|
21
|
+
import { loadOkeConfig, resolveImages } from "./load-config.ts";
|
|
22
|
+
|
|
23
|
+
/** Options for {@link syncDevSchema}. */
|
|
24
|
+
export interface DevSchemaSyncOptions {
|
|
25
|
+
/** Write output (default: stdout). */
|
|
26
|
+
readonly write?: (text: string) => void;
|
|
27
|
+
/** Skip emitting `schema.generated.ts` (tests). */
|
|
28
|
+
readonly skipEmit?: boolean;
|
|
29
|
+
/** Injectable push (tests). Default: `runDb("push", ...)`. */
|
|
30
|
+
readonly pushFn?: (cwd: string, env: ConfigEnv) => Promise<number>;
|
|
31
|
+
/** Derive docker infra without running compose (tests). */
|
|
32
|
+
readonly dryRun?: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Outcome of {@link syncDevSchema}. */
|
|
36
|
+
export interface DevSchemaSyncResult {
|
|
37
|
+
readonly env: ConfigEnv;
|
|
38
|
+
readonly dialect: "sqlite" | "postgresql";
|
|
39
|
+
readonly pushed: boolean;
|
|
40
|
+
readonly code: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Resolve the SQL driver id + drizzle dialect for an env from config.
|
|
45
|
+
*
|
|
46
|
+
* @param config - Loaded oke config
|
|
47
|
+
* @param env - Active env
|
|
48
|
+
*/
|
|
49
|
+
export function sqlDialectForEnv(
|
|
50
|
+
config: OkeConfig | null | undefined,
|
|
51
|
+
env: ConfigEnv,
|
|
52
|
+
): { readonly driverId: string; readonly dialect: "sqlite" | "postgresql" } {
|
|
53
|
+
const driverId = resolveDriverId(config?.drivers?.store?.sql, env) ?? "sqlite";
|
|
54
|
+
if (driverId !== "sqlite" && driverId !== "postgres") {
|
|
55
|
+
throw new Error(
|
|
56
|
+
`store.sql driver "${driverId}" is not supported by drizzle-kit (sqlite | postgres)`,
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
return { driverId, dialect: drizzleDialectFromSqlDriver(driverId) };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Ensure docker compose artefacts + credentials exist. Fails loudly with a
|
|
64
|
+
* clear hint when images are not configured (no silent retry loop).
|
|
65
|
+
*
|
|
66
|
+
* @param cwd - Project root
|
|
67
|
+
* @param config - Loaded oke config
|
|
68
|
+
* @param write - Output
|
|
69
|
+
*/
|
|
70
|
+
async function ensureDockerStack(
|
|
71
|
+
cwd: string,
|
|
72
|
+
config: OkeConfig,
|
|
73
|
+
write: (text: string) => void,
|
|
74
|
+
): Promise<void> {
|
|
75
|
+
const images = resolveImages(config);
|
|
76
|
+
if (Object.keys(images).length === 0) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
"docker mode: no images configured — set `images` in oke.config.ts (or prod drivers postgres/redis for defaults)",
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
const roles = Object.keys(images);
|
|
82
|
+
const instanceId = stackInstanceId(cwd);
|
|
83
|
+
const appSlug = stackAppSlug(cwd);
|
|
84
|
+
const credentials = await loadExistingStackCredentials(cwd, roles);
|
|
85
|
+
const controls = await loadExistingStackControls(cwd);
|
|
86
|
+
const derived = deriveInfrastructure({
|
|
87
|
+
images,
|
|
88
|
+
app: appSlug,
|
|
89
|
+
prod: false,
|
|
90
|
+
includeApp: false,
|
|
91
|
+
composeDir: DEFAULT_DOCKER_DIR,
|
|
92
|
+
instanceId,
|
|
93
|
+
...(credentials ? { credentials } : {}),
|
|
94
|
+
...(controls ? { controls } : {}),
|
|
95
|
+
host: "127.0.0.1",
|
|
96
|
+
});
|
|
97
|
+
await writeDerivedFiles(derived, resolve(cwd, DEFAULT_DOCKER_DIR), { writeStackEnv: true });
|
|
98
|
+
write(
|
|
99
|
+
"oke: docker compose env ready under docker/.env.docker (start containers with `oke dev -d` or docker compose up)\n",
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Ensure artefacts, emit for the active dialect, and push schema for an env.
|
|
105
|
+
* Returns a one-line summary outcome. Docker mode also ensures compose env
|
|
106
|
+
* exists (writes `docker/.env.docker`) before pushing to the live DB.
|
|
107
|
+
*
|
|
108
|
+
* @param cwd - Project root
|
|
109
|
+
* @param env - Target env (`local` | `docker`)
|
|
110
|
+
* @param options - Injectables
|
|
111
|
+
*/
|
|
112
|
+
export async function syncDevSchema(
|
|
113
|
+
cwd: string,
|
|
114
|
+
env: ConfigEnv,
|
|
115
|
+
options: DevSchemaSyncOptions = {},
|
|
116
|
+
): Promise<DevSchemaSyncResult> {
|
|
117
|
+
const write = options.write ?? ((t) => process.stdout.write(t));
|
|
118
|
+
const loaded = await loadOkeConfig(cwd).catch(() => null);
|
|
119
|
+
const config = loaded?.config;
|
|
120
|
+
|
|
121
|
+
await ensureDrizzleConfig(cwd);
|
|
122
|
+
|
|
123
|
+
const { dialect } = sqlDialectForEnv(config, env);
|
|
124
|
+
|
|
125
|
+
if (env === "docker") {
|
|
126
|
+
if (!config) {
|
|
127
|
+
throw new Error("docker mode: oke.config.ts not found");
|
|
128
|
+
}
|
|
129
|
+
await ensureDockerStack(cwd, config, write);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const push =
|
|
133
|
+
options.pushFn ??
|
|
134
|
+
(async (projectCwd, envArg) =>
|
|
135
|
+
runDb("push", { cwd: projectCwd, env: envArg, write, skipEmit: options.skipEmit }));
|
|
136
|
+
|
|
137
|
+
const code = await push(cwd, env);
|
|
138
|
+
return { env, dialect, pushed: code === 0, code };
|
|
139
|
+
}
|
package/src/cli/dev.test.ts
CHANGED
|
@@ -272,6 +272,121 @@ describe("oke dev MCP live wiring", () => {
|
|
|
272
272
|
});
|
|
273
273
|
});
|
|
274
274
|
|
|
275
|
+
describe("oke dev docs MCP", () => {
|
|
276
|
+
let session: DevSession | undefined;
|
|
277
|
+
|
|
278
|
+
afterEach(() => {
|
|
279
|
+
session?.stop();
|
|
280
|
+
session = undefined;
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
test("boots the real docs MCP on :6536 next to the runtime MCP — no auth, search works", async () => {
|
|
284
|
+
const dir = await mkdtemp(join(tmpdir(), "oke-dev-docs-mcp-"));
|
|
285
|
+
await Bun.write(join(dir, "src/app.ts"), "export {}\n");
|
|
286
|
+
|
|
287
|
+
const result = await runDev({
|
|
288
|
+
stdinIsTTY: false,
|
|
289
|
+
cwd: dir,
|
|
290
|
+
silentClaim: true,
|
|
291
|
+
keepAlive: false,
|
|
292
|
+
appPort: 0,
|
|
293
|
+
consolePort: 0,
|
|
294
|
+
mcpPort: 0,
|
|
295
|
+
docsMcpPort: 0,
|
|
296
|
+
startApp: async () => ({ stop() {} }),
|
|
297
|
+
regenClient: async () => {},
|
|
298
|
+
write: () => {},
|
|
299
|
+
// Docs MCP must boot even when Console state is absent (headless).
|
|
300
|
+
serveConsole: async () => ({ stop() {} }),
|
|
301
|
+
serveMcp: async () => ({ stop() {} }),
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
expect(result.code).toBe(0);
|
|
305
|
+
session = result.session;
|
|
306
|
+
expect(session).toBeDefined();
|
|
307
|
+
if (!session) return;
|
|
308
|
+
|
|
309
|
+
expect(session.docsMcpUrl).toBeTruthy();
|
|
310
|
+
const docsBase = session.docsMcpUrl!.origin;
|
|
311
|
+
const host = `127.0.0.1:${session.docsMcpPort}`;
|
|
312
|
+
|
|
313
|
+
const health = await fetch(`${docsBase}/health`, { headers: { host } });
|
|
314
|
+
expect(health.status).toBe(200);
|
|
315
|
+
expect(await health.json()).toEqual({ ok: true, surface: "docs-mcp" });
|
|
316
|
+
|
|
317
|
+
// No Authorization header — docs MCP serves public documentation.
|
|
318
|
+
const listRes = await fetch(`${docsBase}/mcp`, {
|
|
319
|
+
method: "POST",
|
|
320
|
+
headers: { host, "content-type": "application/json" },
|
|
321
|
+
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "tools/list", params: {} }),
|
|
322
|
+
});
|
|
323
|
+
expect(listRes.status).toBe(200);
|
|
324
|
+
const listBody = (await listRes.json()) as {
|
|
325
|
+
result: { tools: { name: string }[] };
|
|
326
|
+
};
|
|
327
|
+
const toolNames = listBody.result.tools.map((t) => t.name);
|
|
328
|
+
expect(toolNames).toContain("oke.docs.search");
|
|
329
|
+
expect(toolNames).toContain("oke.docs.get");
|
|
330
|
+
|
|
331
|
+
const searchRes = await fetch(`${docsBase}/mcp`, {
|
|
332
|
+
method: "POST",
|
|
333
|
+
headers: { host, "content-type": "application/json" },
|
|
334
|
+
body: JSON.stringify({
|
|
335
|
+
jsonrpc: "2.0",
|
|
336
|
+
id: 2,
|
|
337
|
+
method: "tools/call",
|
|
338
|
+
params: { name: "oke.docs.search", arguments: { query: "vault" } },
|
|
339
|
+
}),
|
|
340
|
+
});
|
|
341
|
+
expect(searchRes.status).toBe(200);
|
|
342
|
+
const searchBody = (await searchRes.json()) as {
|
|
343
|
+
result: {
|
|
344
|
+
structuredContent: { kind: string; content: { hits: { slug: string }[] } };
|
|
345
|
+
};
|
|
346
|
+
};
|
|
347
|
+
expect(searchBody.result.structuredContent.kind).toBe(MCP_DATA_KIND);
|
|
348
|
+
expect(searchBody.result.structuredContent.content.hits.length).toBeGreaterThan(0);
|
|
349
|
+
|
|
350
|
+
session.stop();
|
|
351
|
+
session = undefined;
|
|
352
|
+
await expect(fetch(`${docsBase}/health`, { headers: { host } })).rejects.toThrow();
|
|
353
|
+
}, 60_000);
|
|
354
|
+
|
|
355
|
+
test("skips docs MCP without killing oke dev when the surface fails to boot", async () => {
|
|
356
|
+
const dir = await mkdtemp(join(tmpdir(), "oke-dev-docs-skip-"));
|
|
357
|
+
await Bun.write(join(dir, "src/app.ts"), "export {}\n");
|
|
358
|
+
|
|
359
|
+
const writes: string[] = [];
|
|
360
|
+
const result = await runDev({
|
|
361
|
+
stdinIsTTY: false,
|
|
362
|
+
cwd: dir,
|
|
363
|
+
silentClaim: true,
|
|
364
|
+
keepAlive: false,
|
|
365
|
+
appPort: 0,
|
|
366
|
+
consolePort: 0,
|
|
367
|
+
mcpPort: 0,
|
|
368
|
+
docsMcpPort: 0,
|
|
369
|
+
startApp: async () => ({ stop() {} }),
|
|
370
|
+
regenClient: async () => {},
|
|
371
|
+
write: (t) => {
|
|
372
|
+
writes.push(t);
|
|
373
|
+
},
|
|
374
|
+
serveConsole: async () => ({ stop() {} }),
|
|
375
|
+
serveMcp: async () => ({ stop() {} }),
|
|
376
|
+
serveDocsMcp: async () => {
|
|
377
|
+
throw new Error("docs content missing");
|
|
378
|
+
},
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
expect(result.code).toBe(0);
|
|
382
|
+
session = result.session;
|
|
383
|
+
expect(session).toBeDefined();
|
|
384
|
+
if (!session) return;
|
|
385
|
+
expect(session.docsMcpUrl).toBeNull();
|
|
386
|
+
expect(writes.join("")).toContain("Docs MCP skipped");
|
|
387
|
+
}, 60_000);
|
|
388
|
+
});
|
|
389
|
+
|
|
275
390
|
/**
|
|
276
391
|
* Write a minimal HTTP flow app whose `do` returns `{ version }`.
|
|
277
392
|
*
|
|
@@ -311,7 +426,13 @@ export const app = oke({ name: "dev-hot", env: "test" }).adopt({ ping });
|
|
|
311
426
|
/** Stub Console + MCP so boot/hot tests isolate the app child. */
|
|
312
427
|
function stubSurfaces(): Pick<
|
|
313
428
|
DevOptions,
|
|
314
|
-
|
|
429
|
+
| "serveConsole"
|
|
430
|
+
| "serveMcp"
|
|
431
|
+
| "serveDocsMcp"
|
|
432
|
+
| "regenClient"
|
|
433
|
+
| "write"
|
|
434
|
+
| "silentClaim"
|
|
435
|
+
| "keepAlive"
|
|
315
436
|
> {
|
|
316
437
|
return {
|
|
317
438
|
silentClaim: true,
|
|
@@ -320,6 +441,7 @@ function stubSurfaces(): Pick<
|
|
|
320
441
|
write: () => {},
|
|
321
442
|
serveConsole: async () => ({ stop() {} }),
|
|
322
443
|
serveMcp: async () => ({ stop() {} }),
|
|
444
|
+
serveDocsMcp: async () => ({ stop() {} }),
|
|
323
445
|
};
|
|
324
446
|
}
|
|
325
447
|
|