okengine 0.6.0 → 0.6.1
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/README.md +148 -13
- package/package.json +3 -3
- package/site/content/docs/elements/channel.mdx +71 -7
- package/site/content/docs/reference/configuration.mdx +5 -4
- package/site/content/docs/reference/environment-variables.mdx +32 -8
- package/src/cli/openbao-restart.integration.test.ts +106 -97
- package/src/docker/dockerfile.integration.test.ts +126 -119
- package/src/docker/stack.integration.test.ts +118 -102
- package/src/drivers/ai-ollama-tools.integration.test.ts +8 -6
- package/src/drivers/ai-ollama.integration.test.ts +3 -19
- package/src/drivers/channel-fcm.ts +49 -53
- package/src/drivers/channel-msegat.ts +61 -0
- package/src/drivers/channel-sently-map.ts +57 -0
- package/src/drivers/channel-sently.test.ts +99 -0
- package/src/drivers/channel-sndr.ts +28 -0
- package/src/drivers/channel-taqnyat.ts +57 -0
- package/src/drivers/channel-types.ts +79 -2
- package/src/drivers/channel-unifonic.ts +26 -43
- package/src/drivers/channel-wa-cloud.ts +33 -47
- package/src/drivers/channel-webpush.ts +39 -239
- package/src/drivers/index.ts +4 -0
- package/src/elements/channel/costs.test.ts +2 -2
- package/src/elements/channel/costs.ts +14 -2
- package/src/elements/channel/mime.ts +11 -0
- package/src/elements/channel/runtime.ts +94 -0
- package/src/elements/channel/sndr-webhooks.test.ts +26 -0
- package/src/elements/channel.ts +10 -1
- package/src/elements/index.ts +9 -0
- package/src/kernel/boot-bind/channel.test.ts +68 -3
- package/src/kernel/boot-bind/channel.ts +93 -2
- package/src/plugins/auth-delivery.mailpit.integration.test.ts +10 -4
- package/src/release/exports.test.ts +26 -0
- package/src/release/exports.ts +64 -5
- package/src/release/index.ts +5 -0
- package/src/release/measure.exports.test.ts +13 -1
- package/src/release/measure.ts +76 -13
- package/src/release/official-plugins.ts +46 -0
- package/src/release/readme.test.ts +30 -2
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Integration: generated Dockerfile builds (and optionally runs).
|
|
3
|
+
*
|
|
4
|
+
* Opt-in via `OKE_TEST_DOCKER=1` plus a live Docker daemon. Never an empty pass.
|
|
3
5
|
*/
|
|
4
6
|
|
|
5
7
|
import { describe, expect, test } from "bun:test";
|
|
@@ -8,47 +10,50 @@ import { tmpdir } from "node:os";
|
|
|
8
10
|
import { join } from "node:path";
|
|
9
11
|
import { deriveInfrastructure, writeDerivedFiles } from "./index.ts";
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
function dockerAvailable(): boolean {
|
|
12
14
|
try {
|
|
13
|
-
|
|
14
|
-
stdout: "pipe",
|
|
15
|
-
stderr: "pipe",
|
|
16
|
-
});
|
|
17
|
-
const code = await proc.exited;
|
|
18
|
-
return code === 0;
|
|
15
|
+
return Bun.spawnSync(["docker", "info"], { stdout: "pipe", stderr: "pipe" }).exitCode === 0;
|
|
19
16
|
} catch {
|
|
20
17
|
return false;
|
|
21
18
|
}
|
|
22
19
|
}
|
|
23
20
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
21
|
+
const WANT = process.env.OKE_TEST_DOCKER === "1";
|
|
22
|
+
const DOCKER = WANT && dockerAvailable();
|
|
23
|
+
if (!DOCKER) {
|
|
24
|
+
console.log(
|
|
25
|
+
WANT
|
|
26
|
+
? "skip: generated Dockerfile e2e (docker daemon not available)"
|
|
27
|
+
: "skip: generated Dockerfile e2e (OKE_TEST_DOCKER≠1)",
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
const live = DOCKER ? test : test.skip;
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
32
|
+
describe("generated Dockerfile integration", () => {
|
|
33
|
+
live(
|
|
34
|
+
"oke docker output builds and runs",
|
|
35
|
+
async () => {
|
|
36
|
+
const dir = await mkdtemp(join(tmpdir(), "oke-df-build-"));
|
|
37
|
+
try {
|
|
38
|
+
// Minimal app that `oke start` can import.
|
|
39
|
+
await Bun.write(
|
|
40
|
+
join(dir, "package.json"),
|
|
41
|
+
JSON.stringify(
|
|
42
|
+
{
|
|
43
|
+
name: "oke-docker-fixture",
|
|
44
|
+
private: true,
|
|
45
|
+
type: "module",
|
|
46
|
+
bin: { oke: "./oke.ts" },
|
|
47
|
+
scripts: { start: "bun ./app.ts" },
|
|
48
|
+
okengine: { entry: "./app.ts" },
|
|
49
|
+
},
|
|
50
|
+
null,
|
|
51
|
+
2,
|
|
52
|
+
),
|
|
53
|
+
);
|
|
54
|
+
await Bun.write(
|
|
55
|
+
join(dir, "oke.ts"),
|
|
56
|
+
`#!/usr/bin/env bun
|
|
52
57
|
const [cmd] = process.argv.slice(2);
|
|
53
58
|
if (cmd === "start") {
|
|
54
59
|
await import("./app.ts");
|
|
@@ -57,10 +62,10 @@ if (cmd === "start") {
|
|
|
57
62
|
process.exit(1);
|
|
58
63
|
}
|
|
59
64
|
`,
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
65
|
+
);
|
|
66
|
+
await Bun.write(
|
|
67
|
+
join(dir, "app.ts"),
|
|
68
|
+
`const server = Bun.serve({
|
|
64
69
|
port: Number(process.env.PORT ?? 6530),
|
|
65
70
|
hostname: "0.0.0.0",
|
|
66
71
|
fetch() {
|
|
@@ -69,95 +74,97 @@ if (cmd === "start") {
|
|
|
69
74
|
});
|
|
70
75
|
console.log("listening", server.port);
|
|
71
76
|
`,
|
|
72
|
-
|
|
77
|
+
);
|
|
73
78
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
// No lockfile — adjust Dockerfile install for the fixture.
|
|
80
|
+
const derived = deriveInfrastructure({
|
|
81
|
+
images: { "store.sql": "postgres:18-alpine" },
|
|
82
|
+
credentials: {
|
|
83
|
+
"store.sql": {
|
|
84
|
+
user: "oke",
|
|
85
|
+
password: "fixture-pass",
|
|
86
|
+
database: "oke",
|
|
87
|
+
},
|
|
82
88
|
},
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
await writeDerivedFiles(derived, dir);
|
|
89
|
+
app: "fixture",
|
|
90
|
+
// Flat fixture root (build context `.`).
|
|
91
|
+
composeDir: ".",
|
|
92
|
+
});
|
|
93
|
+
await writeDerivedFiles(derived, dir);
|
|
89
94
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
95
|
+
// Fixture has no bun.lock — rewrite install step.
|
|
96
|
+
let df = await Bun.file(join(dir, "Dockerfile")).text();
|
|
97
|
+
df = df.replace(
|
|
98
|
+
"RUN bun install --frozen-lockfile --production",
|
|
99
|
+
"RUN bun install --production",
|
|
100
|
+
);
|
|
101
|
+
await Bun.write(join(dir, "Dockerfile"), df);
|
|
97
102
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
103
|
+
const tag = `oke-docker-fixture:${Date.now()}`;
|
|
104
|
+
const build = Bun.spawn(["docker", "build", "-t", tag, dir], {
|
|
105
|
+
stdout: "pipe",
|
|
106
|
+
stderr: "pipe",
|
|
107
|
+
});
|
|
108
|
+
const [buildOut, buildErr, buildCode] = await Promise.all([
|
|
109
|
+
new Response(build.stdout).text(),
|
|
110
|
+
new Response(build.stderr).text(),
|
|
111
|
+
build.exited,
|
|
112
|
+
]);
|
|
113
|
+
expect(buildCode).toBe(0);
|
|
114
|
+
if (buildCode !== 0) {
|
|
115
|
+
console.error(buildOut, buildErr);
|
|
116
|
+
}
|
|
112
117
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
118
|
+
const name = `oke-fixture-run-${Date.now()}`;
|
|
119
|
+
const run = Bun.spawn(["docker", "run", "-d", "--name", name, "-p", "0:6530", tag], {
|
|
120
|
+
stdout: "pipe",
|
|
121
|
+
stderr: "pipe",
|
|
122
|
+
});
|
|
123
|
+
const [runOut, runErr, runCode] = await Promise.all([
|
|
124
|
+
new Response(run.stdout).text(),
|
|
125
|
+
new Response(run.stderr).text(),
|
|
126
|
+
run.exited,
|
|
127
|
+
]);
|
|
128
|
+
expect(runCode).toBe(0);
|
|
129
|
+
if (runCode !== 0) console.error(runOut, runErr);
|
|
125
130
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
131
|
+
// Resolve published port
|
|
132
|
+
const portProc = Bun.spawn(["docker", "port", name, "6530"], {
|
|
133
|
+
stdout: "pipe",
|
|
134
|
+
stderr: "pipe",
|
|
135
|
+
});
|
|
136
|
+
const portOut = await new Response(portProc.stdout).text();
|
|
137
|
+
await portProc.exited;
|
|
138
|
+
const m = portOut.match(/:(\d+)/);
|
|
139
|
+
expect(m).toBeTruthy();
|
|
140
|
+
const hostPort = m![1]!;
|
|
136
141
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
142
|
+
let body: { ok?: boolean } | null = null;
|
|
143
|
+
for (let i = 0; i < 20; i++) {
|
|
144
|
+
try {
|
|
145
|
+
const res = await fetch(`http://127.0.0.1:${hostPort}/`);
|
|
146
|
+
if (res.ok) {
|
|
147
|
+
body = (await res.json()) as { ok?: boolean };
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
} catch {
|
|
151
|
+
await Bun.sleep(250);
|
|
144
152
|
}
|
|
145
|
-
} catch {
|
|
146
|
-
await Bun.sleep(250);
|
|
147
153
|
}
|
|
148
|
-
|
|
149
|
-
expect(body?.ok).toBe(true);
|
|
154
|
+
expect(body?.ok).toBe(true);
|
|
150
155
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
156
|
+
await Bun.spawn(["docker", "rm", "-f", name], {
|
|
157
|
+
stdout: "pipe",
|
|
158
|
+
stderr: "pipe",
|
|
159
|
+
}).exited;
|
|
160
|
+
await Bun.spawn(["docker", "rmi", "-f", tag], {
|
|
161
|
+
stdout: "pipe",
|
|
162
|
+
stderr: "pipe",
|
|
163
|
+
}).exited;
|
|
164
|
+
} finally {
|
|
165
|
+
await rm(dir, { recursive: true, force: true }).catch(() => {});
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
180_000,
|
|
169
|
+
);
|
|
163
170
|
});
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Integration: generated compose brings up postgres; app URL talks to it.
|
|
3
|
+
*
|
|
4
|
+
* Opt-in via `OKE_TEST_DOCKER=1` plus a live Docker daemon. Never an empty pass.
|
|
3
5
|
*/
|
|
4
6
|
|
|
5
7
|
import { describe, expect, test } from "bun:test";
|
|
@@ -8,127 +10,141 @@ import { tmpdir } from "node:os";
|
|
|
8
10
|
import { join } from "node:path";
|
|
9
11
|
import { deriveInfrastructure, formatStackEnv, writeDerivedFiles } from "./index.ts";
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
function dockerAvailable(): boolean {
|
|
12
14
|
try {
|
|
13
|
-
|
|
14
|
-
stdout: "pipe",
|
|
15
|
-
stderr: "pipe",
|
|
16
|
-
});
|
|
17
|
-
return (await proc.exited) === 0;
|
|
15
|
+
return Bun.spawnSync(["docker", "info"], { stdout: "pipe", stderr: "pipe" }).exitCode === 0;
|
|
18
16
|
} catch {
|
|
19
17
|
return false;
|
|
20
18
|
}
|
|
21
19
|
}
|
|
22
20
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
try {
|
|
34
|
-
const derived = deriveInfrastructure({
|
|
35
|
-
images: { "store.sql": "postgres:18-alpine" },
|
|
36
|
-
credentials: {
|
|
37
|
-
"store.sql": {
|
|
38
|
-
user: "oke",
|
|
39
|
-
password: "stack-integration-pass",
|
|
40
|
-
database: "oke",
|
|
41
|
-
},
|
|
42
|
-
},
|
|
43
|
-
app: "stacktest",
|
|
44
|
-
host: "127.0.0.1",
|
|
45
|
-
includeApp: false,
|
|
46
|
-
composeDir: "docker",
|
|
47
|
-
});
|
|
48
|
-
await writeDerivedFiles(derived, dockerDir, {
|
|
49
|
-
writeStackEnv: true,
|
|
50
|
-
});
|
|
21
|
+
const WANT = process.env.OKE_TEST_DOCKER === "1";
|
|
22
|
+
const DOCKER = WANT && dockerAvailable();
|
|
23
|
+
if (!DOCKER) {
|
|
24
|
+
console.log(
|
|
25
|
+
WANT
|
|
26
|
+
? "skip: docker postgres stack e2e (docker daemon not available)"
|
|
27
|
+
: "skip: docker postgres stack e2e (OKE_TEST_DOCKER≠1)",
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
const live = DOCKER ? test : test.skip;
|
|
51
31
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
32
|
+
describe("oke dev --docker postgres integration", () => {
|
|
33
|
+
live(
|
|
34
|
+
"compose brings up postgres and the app talks to it",
|
|
35
|
+
async () => {
|
|
36
|
+
const dir = await mkdtemp(join(tmpdir(), "oke-stack-pg-"));
|
|
37
|
+
const dockerDir = join(dir, "docker");
|
|
38
|
+
const project = `oke-pg-${Date.now()}`;
|
|
39
|
+
try {
|
|
40
|
+
const derived = deriveInfrastructure({
|
|
41
|
+
images: { "store.sql": "postgres:18-alpine" },
|
|
42
|
+
credentials: {
|
|
43
|
+
"store.sql": {
|
|
44
|
+
user: "oke",
|
|
45
|
+
password: "stack-integration-pass",
|
|
46
|
+
database: "oke",
|
|
47
|
+
},
|
|
63
48
|
},
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (upCode !== 0) console.error(upOut, upErr);
|
|
49
|
+
app: "stacktest",
|
|
50
|
+
host: "127.0.0.1",
|
|
51
|
+
includeApp: false,
|
|
52
|
+
composeDir: "docker",
|
|
53
|
+
});
|
|
54
|
+
await writeDerivedFiles(derived, dockerDir, {
|
|
55
|
+
writeStackEnv: true,
|
|
56
|
+
});
|
|
73
57
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const ps = Bun.spawn(
|
|
58
|
+
// Infra-only: network + role compose (no app build).
|
|
59
|
+
const composeFiles = ["compose.yml", "compose.store.sql.yml"];
|
|
60
|
+
const up = Bun.spawn(
|
|
78
61
|
[
|
|
79
62
|
"docker",
|
|
80
63
|
"compose",
|
|
81
64
|
"-p",
|
|
82
65
|
project,
|
|
83
66
|
...composeFiles.flatMap((f) => ["-f", f]),
|
|
84
|
-
"
|
|
85
|
-
"
|
|
86
|
-
"json",
|
|
67
|
+
"up",
|
|
68
|
+
"-d",
|
|
87
69
|
],
|
|
88
|
-
{
|
|
70
|
+
{
|
|
71
|
+
cwd: dockerDir,
|
|
72
|
+
stdout: "pipe",
|
|
73
|
+
stderr: "pipe",
|
|
74
|
+
env: {
|
|
75
|
+
...process.env,
|
|
76
|
+
...derived.stackEnv,
|
|
77
|
+
},
|
|
78
|
+
},
|
|
89
79
|
);
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
80
|
+
const [upOut, upErr, upCode] = await Promise.all([
|
|
81
|
+
new Response(up.stdout).text(),
|
|
82
|
+
new Response(up.stderr).text(),
|
|
83
|
+
up.exited,
|
|
84
|
+
]);
|
|
85
|
+
expect(upCode).toBe(0);
|
|
86
|
+
if (upCode !== 0) console.error(upOut, upErr);
|
|
87
|
+
|
|
88
|
+
// Wait for healthy
|
|
89
|
+
let healthy = false;
|
|
90
|
+
for (let i = 0; i < 40; i++) {
|
|
91
|
+
const ps = Bun.spawn(
|
|
92
|
+
[
|
|
93
|
+
"docker",
|
|
94
|
+
"compose",
|
|
95
|
+
"-p",
|
|
96
|
+
project,
|
|
97
|
+
...composeFiles.flatMap((f) => ["-f", f]),
|
|
98
|
+
"ps",
|
|
99
|
+
"--format",
|
|
100
|
+
"json",
|
|
101
|
+
],
|
|
102
|
+
{ cwd: dockerDir, stdout: "pipe", stderr: "pipe" },
|
|
103
|
+
);
|
|
104
|
+
const text = await new Response(ps.stdout).text();
|
|
105
|
+
await ps.exited;
|
|
106
|
+
if (/healthy/i.test(text)) {
|
|
107
|
+
healthy = true;
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
await Bun.sleep(500);
|
|
95
111
|
}
|
|
96
|
-
|
|
97
|
-
}
|
|
98
|
-
expect(healthy).toBe(true);
|
|
112
|
+
expect(healthy).toBe(true);
|
|
99
113
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
114
|
+
const url = derived.stackEnv.DATABASE_URL!;
|
|
115
|
+
// App talks to postgres via recipe URL (Bun.SQL) — kernel never sees env-var names.
|
|
116
|
+
const sql = new Bun.SQL(url);
|
|
117
|
+
try {
|
|
118
|
+
const rows = (await sql`select 1::int as n`) as Array<{ n: number }>;
|
|
119
|
+
expect(rows[0]?.n).toBe(1);
|
|
120
|
+
} finally {
|
|
121
|
+
await sql.close();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Prove credentials live in docker/.env.docker, not YAML.
|
|
125
|
+
const yml = await Bun.file(join(dockerDir, "compose.store.sql.yml")).text();
|
|
126
|
+
expect(yml).not.toContain("stack-integration-pass");
|
|
127
|
+
expect(formatStackEnv(derived.stackEnv)).toContain("stack-integration-pass");
|
|
128
|
+
expect(await Bun.file(join(dockerDir, ".env.docker")).exists()).toBe(true);
|
|
106
129
|
} finally {
|
|
107
|
-
await
|
|
130
|
+
await Bun.spawn(
|
|
131
|
+
[
|
|
132
|
+
"docker",
|
|
133
|
+
"compose",
|
|
134
|
+
"-p",
|
|
135
|
+
project,
|
|
136
|
+
"-f",
|
|
137
|
+
"compose.yml",
|
|
138
|
+
"-f",
|
|
139
|
+
"compose.store.sql.yml",
|
|
140
|
+
"down",
|
|
141
|
+
"-v",
|
|
142
|
+
],
|
|
143
|
+
{ cwd: dockerDir, stdout: "pipe", stderr: "pipe" },
|
|
144
|
+
).exited.catch(() => {});
|
|
145
|
+
await rm(dir, { recursive: true, force: true }).catch(() => {});
|
|
108
146
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
expect(yml).not.toContain("stack-integration-pass");
|
|
113
|
-
expect(formatStackEnv(derived.stackEnv)).toContain("stack-integration-pass");
|
|
114
|
-
expect(await Bun.file(join(dockerDir, ".env.docker")).exists()).toBe(true);
|
|
115
|
-
} finally {
|
|
116
|
-
await Bun.spawn(
|
|
117
|
-
[
|
|
118
|
-
"docker",
|
|
119
|
-
"compose",
|
|
120
|
-
"-p",
|
|
121
|
-
project,
|
|
122
|
-
"-f",
|
|
123
|
-
"compose.yml",
|
|
124
|
-
"-f",
|
|
125
|
-
"compose.store.sql.yml",
|
|
126
|
-
"down",
|
|
127
|
-
"-v",
|
|
128
|
-
],
|
|
129
|
-
{ cwd: dockerDir, stdout: "pipe", stderr: "pipe" },
|
|
130
|
-
).exited.catch(() => {});
|
|
131
|
-
await rm(dir, { recursive: true, force: true }).catch(() => {});
|
|
132
|
-
}
|
|
133
|
-
}, 120_000);
|
|
147
|
+
},
|
|
148
|
+
120_000,
|
|
149
|
+
);
|
|
134
150
|
});
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Live Ollama tool-calling through fx.ask → fx.call (capability + ledger).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Opt-in via `OKE_TEST_OLLAMA_URL` (reachable Ollama). A local daemon on
|
|
5
|
+
* :11434 is not enough — set the URL explicitly. Never an empty pass.
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
8
|
import { describe, expect, test } from "bun:test";
|
|
@@ -11,7 +12,6 @@ import { createGateRuntime, gate } from "../elements/gate.ts";
|
|
|
11
12
|
import { createFxContext } from "../kernel/fx.ts";
|
|
12
13
|
import { OLLAMA_DEFAULT_MODEL, openOllama } from "./ai-ollama.ts";
|
|
13
14
|
|
|
14
|
-
const DEFAULT_LOCAL = "http://127.0.0.1:11434";
|
|
15
15
|
const ENV_URL = process.env.OKE_TEST_OLLAMA_URL?.trim();
|
|
16
16
|
|
|
17
17
|
async function probeOllama(url: string): Promise<boolean> {
|
|
@@ -23,12 +23,14 @@ async function probeOllama(url: string): Promise<boolean> {
|
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
const
|
|
27
|
-
const canLive = Boolean(ENV_URL)
|
|
26
|
+
const reachable = ENV_URL ? await probeOllama(ENV_URL) : false;
|
|
27
|
+
const canLive = Boolean(ENV_URL) && reachable;
|
|
28
28
|
|
|
29
29
|
if (!canLive) {
|
|
30
30
|
console.log(
|
|
31
|
-
|
|
31
|
+
ENV_URL
|
|
32
|
+
? `skip: live ollama tool-calling e2e (unreachable at ${ENV_URL})`
|
|
33
|
+
: "skip: live ollama tool-calling e2e (OKE_TEST_OLLAMA_URL not set)",
|
|
32
34
|
);
|
|
33
35
|
}
|
|
34
36
|
const live = canLive ? test : test.skip;
|
|
@@ -37,7 +39,7 @@ describe("ollama live — tool-calling via fx.call", () => {
|
|
|
37
39
|
live(
|
|
38
40
|
"gated ask → real tool call → capability ledger portal entry",
|
|
39
41
|
async () => {
|
|
40
|
-
const url = ENV_URL
|
|
42
|
+
const url = ENV_URL!;
|
|
41
43
|
const model = process.env.OKE_AI_MODEL?.trim() || OLLAMA_DEFAULT_MODEL;
|
|
42
44
|
|
|
43
45
|
const kv = await memoryKvDriver.open({ name: "ollama-tools-rate" });
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Opt-in (never auto-pull a multi-GB model in ordinary `bun test`):
|
|
5
5
|
* 1. `OKE_TEST_OLLAMA_URL` — point at any reachable Ollama
|
|
6
|
-
* 2.
|
|
7
|
-
* 3. `OKE_TEST_OLLAMA_DOCKER=1` — start `ollama/ollama` via the recipe and pull
|
|
6
|
+
* 2. `OKE_TEST_OLLAMA_DOCKER=1` — start `ollama/ollama` via the recipe and pull
|
|
8
7
|
* `qwen3.5:9b` (slow first run)
|
|
9
8
|
*
|
|
10
9
|
* Without one of those, the suite skips with a visible reason (never an empty pass).
|
|
10
|
+
* A local daemon on :11434 is not enough — set `OKE_TEST_OLLAMA_URL` explicitly.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { afterAll, describe, expect, test } from "bun:test";
|
|
@@ -20,7 +20,6 @@ import { bindAi } from "../kernel/boot-bind/ai.ts";
|
|
|
20
20
|
import { OLLAMA_DEFAULT_MODEL, openOllama } from "./ai-ollama.ts";
|
|
21
21
|
|
|
22
22
|
const OLLAMA_IMAGE = "ollama/ollama:latest";
|
|
23
|
-
const DEFAULT_LOCAL = "http://127.0.0.1:11434";
|
|
24
23
|
const ENV_URL = process.env.OKE_TEST_OLLAMA_URL?.trim();
|
|
25
24
|
const WANT_DOCKER = process.env.OKE_TEST_OLLAMA_DOCKER === "1";
|
|
26
25
|
|
|
@@ -32,23 +31,12 @@ function dockerAvailable(): boolean {
|
|
|
32
31
|
}
|
|
33
32
|
}
|
|
34
33
|
|
|
35
|
-
async function probeOllama(url: string): Promise<boolean> {
|
|
36
|
-
try {
|
|
37
|
-
const res = await fetch(`${url}/api/tags`, { signal: AbortSignal.timeout(1_500) });
|
|
38
|
-
return res.ok;
|
|
39
|
-
} catch {
|
|
40
|
-
return false;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
34
|
const DOCKER = dockerAvailable();
|
|
45
|
-
const
|
|
46
|
-
const canLive = Boolean(ENV_URL) || localUp || (WANT_DOCKER && DOCKER);
|
|
35
|
+
const canLive = Boolean(ENV_URL) || (WANT_DOCKER && DOCKER);
|
|
47
36
|
|
|
48
37
|
if (!canLive) {
|
|
49
38
|
const reasons: string[] = [];
|
|
50
39
|
if (!ENV_URL) reasons.push("OKE_TEST_OLLAMA_URL not set");
|
|
51
|
-
if (!localUp) reasons.push("no Ollama on :11434");
|
|
52
40
|
if (!WANT_DOCKER) reasons.push("OKE_TEST_OLLAMA_DOCKER≠1");
|
|
53
41
|
else if (!DOCKER) reasons.push("docker daemon not available");
|
|
54
42
|
console.log(`skip: live ollama e2e (${reasons.join("; ")})`);
|
|
@@ -94,10 +82,6 @@ async function resolveLiveUrl(): Promise<{ url: string; model: string }> {
|
|
|
94
82
|
return { url: ENV_URL, model: process.env.OKE_AI_MODEL?.trim() || OLLAMA_DEFAULT_MODEL };
|
|
95
83
|
}
|
|
96
84
|
|
|
97
|
-
if (localUp) {
|
|
98
|
-
return { url: DEFAULT_LOCAL, model: process.env.OKE_AI_MODEL?.trim() || OLLAMA_DEFAULT_MODEL };
|
|
99
|
-
}
|
|
100
|
-
|
|
101
85
|
const dir = await mkdtemp(join(tmpdir(), "oke-ollama-"));
|
|
102
86
|
const dockerDir = join(dir, "docker");
|
|
103
87
|
const project = `oke-ollama-${Date.now()}`;
|