@wenathlan/getry 2.0.154 → 2.0.155
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 +182 -11
- package/tests/cli.test.ts +300 -0
- package/tests/debonairlane.test.ts +66 -21
- package/tests/gateway.test.ts +962 -0
- package/tests/gatewayserver.ts +99 -0
- package/tests/intelligence.test.ts +62 -0
- package/tests/jsonpolicy.test.ts +289 -0
- package/tests/llm.test.ts +696 -0
- package/tests/modelroute.test.ts +112 -0
- package/tests/promptlibrary.test.ts +116 -0
- package/tests/provider.constants.test.ts +134 -0
- package/tests/provider.fingerprint.test.ts +149 -0
- package/tests/provider.models.test.ts +250 -0
- package/tests/provider.oauth.test.ts +150 -0
- package/tests/providercli.test.ts +84 -0
- package/tests/providerfixtures.ts +224 -0
- package/tests/researchfixtures.ts +4572 -0
- package/tests/server.auth.test.ts +394 -0
- package/tests/server.engine.test.ts +1074 -0
- package/tests/server.property.test.ts +220 -0
- package/tests/server.utils.test.ts +546 -0
- package/tests/torture.llm.test.ts +807 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wenathlan/getry",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.155",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/wenathlan/devthink"
|
|
@@ -20,18 +20,25 @@
|
|
|
20
20
|
"engines": {
|
|
21
21
|
"bare": "^1.18.0",
|
|
22
22
|
"bun": "^1.4.2",
|
|
23
|
+
"cargo": ">=1.85.0",
|
|
23
24
|
"chrome": "^16.0.912",
|
|
24
25
|
"cnpm": "^9.4.0",
|
|
25
26
|
"corepack": "^0.35.0",
|
|
26
27
|
"deno": "^2.9.5",
|
|
28
|
+
"dotnet": ">=8.0.0",
|
|
27
29
|
"ecmascript": "^5.0.0",
|
|
28
30
|
"firefox": ">=0.8.0",
|
|
31
|
+
"go": ">=1.24.0",
|
|
29
32
|
"iojs": "^1.0.0",
|
|
33
|
+
"java": ">=17",
|
|
30
34
|
"node": ">=26.13.0 <27",
|
|
31
35
|
"npm": "^12.1.0",
|
|
32
36
|
"pnpm": "^12.6.0",
|
|
37
|
+
"python": ">=3.11.0",
|
|
38
|
+
"rust": ">=1.85.0",
|
|
33
39
|
"teleport": ">=0.2.0",
|
|
34
40
|
"typescript": "^5.8.0",
|
|
41
|
+
"v": "^0.5.2",
|
|
35
42
|
"vscode": "^1.118.0",
|
|
36
43
|
"yarn": "^4.17.1"
|
|
37
44
|
},
|
|
@@ -40,9 +47,165 @@
|
|
|
40
47
|
"./thinking": {
|
|
41
48
|
"types": "./thinking.ts",
|
|
42
49
|
"default": "./thinking.ts"
|
|
50
|
+
},
|
|
51
|
+
"./engine": {
|
|
52
|
+
"types": "./engine.ts",
|
|
53
|
+
"import": "./engine.ts",
|
|
54
|
+
"default": "./engine.ts"
|
|
55
|
+
},
|
|
56
|
+
"./llm": {
|
|
57
|
+
"types": "./llm.ts",
|
|
58
|
+
"import": "./llm.ts",
|
|
59
|
+
"default": "./llm.ts"
|
|
60
|
+
},
|
|
61
|
+
"./streaming": {
|
|
62
|
+
"types": "./streaming.ts",
|
|
63
|
+
"import": "./streaming.ts",
|
|
64
|
+
"default": "./streaming.ts"
|
|
65
|
+
},
|
|
66
|
+
"./models": {
|
|
67
|
+
"types": "./models.ts",
|
|
68
|
+
"import": "./models.ts",
|
|
69
|
+
"default": "./models.ts"
|
|
70
|
+
},
|
|
71
|
+
"./request": {
|
|
72
|
+
"types": "./request.ts",
|
|
73
|
+
"import": "./request.ts",
|
|
74
|
+
"default": "./request.ts"
|
|
75
|
+
},
|
|
76
|
+
"./providers": {
|
|
77
|
+
"types": "./providers.ts",
|
|
78
|
+
"import": "./providers.ts",
|
|
79
|
+
"default": "./providers.ts"
|
|
80
|
+
},
|
|
81
|
+
"./antigravity": {
|
|
82
|
+
"types": "./antigravity.ts",
|
|
83
|
+
"import": "./antigravity.ts",
|
|
84
|
+
"default": "./antigravity.ts"
|
|
85
|
+
},
|
|
86
|
+
"./oauth": {
|
|
87
|
+
"types": "./oauth.ts",
|
|
88
|
+
"import": "./oauth.ts",
|
|
89
|
+
"default": "./oauth.ts"
|
|
90
|
+
},
|
|
91
|
+
"./accounts": {
|
|
92
|
+
"types": "./accounts.ts",
|
|
93
|
+
"import": "./accounts.ts",
|
|
94
|
+
"default": "./accounts.ts"
|
|
95
|
+
},
|
|
96
|
+
"./auth": {
|
|
97
|
+
"types": "./auth.ts",
|
|
98
|
+
"import": "./auth.ts",
|
|
99
|
+
"default": "./auth.ts"
|
|
100
|
+
},
|
|
101
|
+
"./server": {
|
|
102
|
+
"types": "./server.ts",
|
|
103
|
+
"import": "./server.ts",
|
|
104
|
+
"default": "./server.ts"
|
|
105
|
+
},
|
|
106
|
+
"./cli": {
|
|
107
|
+
"types": "./cli.ts",
|
|
108
|
+
"import": "./cli.ts",
|
|
109
|
+
"default": "./cli.ts"
|
|
110
|
+
},
|
|
111
|
+
"./providercli": {
|
|
112
|
+
"types": "./providercli.ts",
|
|
113
|
+
"import": "./providercli.ts",
|
|
114
|
+
"default": "./providercli.ts"
|
|
115
|
+
},
|
|
116
|
+
"./project": {
|
|
117
|
+
"types": "./project.ts",
|
|
118
|
+
"import": "./project.ts",
|
|
119
|
+
"default": "./project.ts"
|
|
120
|
+
},
|
|
121
|
+
"./quota": {
|
|
122
|
+
"types": "./quota.ts",
|
|
123
|
+
"import": "./quota.ts",
|
|
124
|
+
"default": "./quota.ts"
|
|
125
|
+
},
|
|
126
|
+
"./recovery": {
|
|
127
|
+
"types": "./recovery.ts",
|
|
128
|
+
"import": "./recovery.ts",
|
|
129
|
+
"default": "./recovery.ts"
|
|
130
|
+
},
|
|
131
|
+
"./fingerprint": {
|
|
132
|
+
"types": "./fingerprint.ts",
|
|
133
|
+
"import": "./fingerprint.ts",
|
|
134
|
+
"default": "./fingerprint.ts"
|
|
135
|
+
},
|
|
136
|
+
"./search": {
|
|
137
|
+
"types": "./search.ts",
|
|
138
|
+
"import": "./search.ts",
|
|
139
|
+
"default": "./search.ts"
|
|
140
|
+
},
|
|
141
|
+
"./utils": {
|
|
142
|
+
"types": "./utils.ts",
|
|
143
|
+
"import": "./utils.ts",
|
|
144
|
+
"default": "./utils.ts"
|
|
145
|
+
},
|
|
146
|
+
"./versionregistry": {
|
|
147
|
+
"types": "./versionregistry.ts",
|
|
148
|
+
"import": "./versionregistry.ts",
|
|
149
|
+
"default": "./versionregistry.ts"
|
|
150
|
+
},
|
|
151
|
+
"./intelligence": {
|
|
152
|
+
"types": "./intelligence.ts",
|
|
153
|
+
"import": "./intelligence.ts",
|
|
154
|
+
"default": "./intelligence.ts"
|
|
155
|
+
},
|
|
156
|
+
"./json": {
|
|
157
|
+
"types": "./json.ts",
|
|
158
|
+
"import": "./json.ts",
|
|
159
|
+
"default": "./json.ts"
|
|
160
|
+
},
|
|
161
|
+
"./jsonpolicy": {
|
|
162
|
+
"types": "./jsonpolicy.ts",
|
|
163
|
+
"import": "./jsonpolicy.ts",
|
|
164
|
+
"default": "./jsonpolicy.ts"
|
|
165
|
+
},
|
|
166
|
+
"./jsonschema": {
|
|
167
|
+
"types": "./jsonschema.ts",
|
|
168
|
+
"import": "./jsonschema.ts",
|
|
169
|
+
"default": "./jsonschema.ts"
|
|
170
|
+
},
|
|
171
|
+
"./gatewayconfig": {
|
|
172
|
+
"types": "./gatewayconfig.ts",
|
|
173
|
+
"import": "./gatewayconfig.ts",
|
|
174
|
+
"default": "./gatewayconfig.ts"
|
|
175
|
+
},
|
|
176
|
+
"./gatewayviewconfig": {
|
|
177
|
+
"types": "./gatewayviewconfig.ts",
|
|
178
|
+
"import": "./gatewayviewconfig.ts",
|
|
179
|
+
"default": "./gatewayviewconfig.ts"
|
|
180
|
+
},
|
|
181
|
+
"./gatewayprobe": {
|
|
182
|
+
"types": "./gatewayprobe.ts",
|
|
183
|
+
"import": "./gatewayprobe.ts",
|
|
184
|
+
"default": "./gatewayprobe.ts"
|
|
185
|
+
},
|
|
186
|
+
"./gatewaysmoke": {
|
|
187
|
+
"types": "./gatewaysmoke.ts",
|
|
188
|
+
"import": "./gatewaysmoke.ts",
|
|
189
|
+
"default": "./gatewaysmoke.ts"
|
|
190
|
+
},
|
|
191
|
+
"./nodeshim": {
|
|
192
|
+
"types": "./nodeshim.ts",
|
|
193
|
+
"import": "./nodeshim.ts",
|
|
194
|
+
"default": "./nodeshim.ts"
|
|
195
|
+
},
|
|
196
|
+
"./types": {
|
|
197
|
+
"types": "./types.ts",
|
|
198
|
+
"import": "./types.ts",
|
|
199
|
+
"default": "./types.ts"
|
|
200
|
+
},
|
|
201
|
+
"./constants": {
|
|
202
|
+
"types": "./constants.ts",
|
|
203
|
+
"import": "./constants.ts",
|
|
204
|
+
"default": "./constants.ts"
|
|
43
205
|
}
|
|
44
206
|
},
|
|
45
207
|
"scripts": {
|
|
208
|
+
"db:generate": "prisma generate --schema schema.prisma",
|
|
46
209
|
"dev": "vite --host",
|
|
47
210
|
"build": "vite build",
|
|
48
211
|
"start": "vite preview --host",
|
|
@@ -63,12 +226,18 @@
|
|
|
63
226
|
"@libsql/client": "^0.18.0",
|
|
64
227
|
"@prisma/adapter-libsql": "^7.10.1",
|
|
65
228
|
"@prisma/client": "^7.10.1",
|
|
66
|
-
"@wenathlan/
|
|
67
|
-
"@wenathlan/
|
|
229
|
+
"@wenathlan/cadria": "^2.0.154",
|
|
230
|
+
"@wenathlan/debonair": "^2.0.154",
|
|
231
|
+
"@wenathlan/devthink": "^2.0.154",
|
|
232
|
+
"@wenathlan/getry": "^2.0.154",
|
|
233
|
+
"@wenathlan/saddle": "^2.0.154",
|
|
234
|
+
"@wenathlan/vault": "^2.0.154",
|
|
235
|
+
"better-sqlite3": "^13.0.3",
|
|
236
|
+
"drizzle-orm": "^0.45.4",
|
|
68
237
|
"gsap": "^3.15.0",
|
|
69
238
|
"hono": "^4.13.16",
|
|
70
239
|
"ink": "^7.1.1",
|
|
71
|
-
"lucide-react": "^1.
|
|
240
|
+
"lucide-react": "^1.67.0",
|
|
72
241
|
"motion": "^12.43.0",
|
|
73
242
|
"react": "^19.3.1",
|
|
74
243
|
"react-devtools-core": "^8.0.0",
|
|
@@ -114,9 +283,9 @@
|
|
|
114
283
|
"@radix-ui/react-toggle": "^1.2.1",
|
|
115
284
|
"@radix-ui/react-toggle-group": "^1.2.1",
|
|
116
285
|
"@radix-ui/react-tooltip": "^1.4.1",
|
|
117
|
-
"@tailwindcss/postcss": "^4.3.
|
|
286
|
+
"@tailwindcss/postcss": "^4.3.4",
|
|
118
287
|
"@tailwindcss/typography": "^0.5.20",
|
|
119
|
-
"@tailwindcss/vite": "^4.3.
|
|
288
|
+
"@tailwindcss/vite": "^4.3.4",
|
|
120
289
|
"@tsconfig/node26": "^26.0.1",
|
|
121
290
|
"@types/better-sqlite3": "^9.6.0",
|
|
122
291
|
"@types/chrome": "^0.3.7",
|
|
@@ -126,7 +295,7 @@
|
|
|
126
295
|
"@types/react": "^19.3.0",
|
|
127
296
|
"@types/react-dom": "^19.3.0",
|
|
128
297
|
"@types/three": "^0.187.0",
|
|
129
|
-
"@vitejs/plugin-react": "^6.1
|
|
298
|
+
"@vitejs/plugin-react": "^6.2.1",
|
|
130
299
|
"add": "^2.0.9",
|
|
131
300
|
"autoprefixer": "^10.6.2",
|
|
132
301
|
"axios": "^1.21.0",
|
|
@@ -147,7 +316,7 @@
|
|
|
147
316
|
"hono": "^4.13.16",
|
|
148
317
|
"input-otp": "^1.5.0",
|
|
149
318
|
"jsdom": "26.1.0",
|
|
150
|
-
"lucide-react": "^1.
|
|
319
|
+
"lucide-react": "^1.67.0",
|
|
151
320
|
"mysql2": "^3.24.6",
|
|
152
321
|
"nanoid": "^6.0.2",
|
|
153
322
|
"next-themes": "^0.4.6",
|
|
@@ -162,12 +331,12 @@
|
|
|
162
331
|
"react-day-picker": "^10.0.2",
|
|
163
332
|
"react-dom": "^19.3.1",
|
|
164
333
|
"react-hook-form": "^7.91.0",
|
|
165
|
-
"react-resizable-panels": "^4.
|
|
334
|
+
"react-resizable-panels": "^4.18.0",
|
|
166
335
|
"recharts": "^3.10.1",
|
|
167
336
|
"sonner": "^2.0.8",
|
|
168
337
|
"streamdown": "^2.7.1",
|
|
169
338
|
"tailwind-merge": "^3.7.0",
|
|
170
|
-
"tailwindcss": "^4.3.
|
|
339
|
+
"tailwindcss": "^4.3.4",
|
|
171
340
|
"tailwindcss-animate": "^1.0.7",
|
|
172
341
|
"tsup": "^8.5.1",
|
|
173
342
|
"tsx": "^4.23.15",
|
|
@@ -176,7 +345,9 @@
|
|
|
176
345
|
"vaul": "^1.1.2",
|
|
177
346
|
"vite": "^8.3.5",
|
|
178
347
|
"vite-plugin-manus-runtime": "^0.0.59",
|
|
179
|
-
"vitest": "^5.0
|
|
348
|
+
"vitest": "^5.1.0",
|
|
349
|
+
"webpack": "^5.112.2",
|
|
350
|
+
"webpack-cli": "^6.0.1",
|
|
180
351
|
"wouter": "^3.13.0",
|
|
181
352
|
"yaml": "^2.9.1",
|
|
182
353
|
"zod": "^4.6.7"
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cli — the scaffolding cli under real subprocess execution (the getry copy
|
|
3
|
+
* of the devthink lane): every command runs as a child process exactly like
|
|
4
|
+
* a consumer types it: init with piped answers, list show validate models
|
|
5
|
+
* keys export help. the runner spawns the owner face directly (tsx cli.ts)
|
|
6
|
+
* — the devthink entry keeps answering the same family through its compat
|
|
7
|
+
* shim, and that lane stays covered by the devthink battery.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { execFile } from "node:child_process";
|
|
11
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
12
|
+
import { tmpdir } from "node:os";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { promisify } from "node:util";
|
|
15
|
+
import { afterAll, describe, expect, it } from "vitest";
|
|
16
|
+
|
|
17
|
+
const exec = promisify(execFile);
|
|
18
|
+
|
|
19
|
+
/** the tests village sits inside the getry app folder, so the cli runner
|
|
20
|
+
* and its tsx binary resolve through the app root one hop up (the owner
|
|
21
|
+
* face answers from its own root the same way). */
|
|
22
|
+
const tsxbin = join(import.meta.dirname, "..", "node_modules", ".bin", "tsx");
|
|
23
|
+
const clits = join(import.meta.dirname, "..", "cli.ts");
|
|
24
|
+
/** the tsx subprocess resolves the library specifiers through the explicit
|
|
25
|
+
* application tsconfig (the runner cwd sits in a scaffold workdir, and the
|
|
26
|
+
* tsconfig discovery that walks from the cwd would never find the app
|
|
27
|
+
* paths — the flag pins the resolution to the owner tree). */
|
|
28
|
+
const tsconfigpath = join(import.meta.dirname, "..", "tsconfig.json");
|
|
29
|
+
|
|
30
|
+
/** run the cli in a workdir with optional piped stdin */
|
|
31
|
+
async function runcli(
|
|
32
|
+
workdir: string,
|
|
33
|
+
args: string[],
|
|
34
|
+
input = "",
|
|
35
|
+
): Promise<{ stdout: string; stderr: string; code: number }> {
|
|
36
|
+
try {
|
|
37
|
+
const { stdout, stderr } = await exec(tsxbin, ["--tsconfig", tsconfigpath, clits, ...args], {
|
|
38
|
+
cwd: workdir,
|
|
39
|
+
env: { ...process.env, DEVTHINK_DATABASE_URL: `file:${join(workdir, "cli.db")}` },
|
|
40
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
41
|
+
});
|
|
42
|
+
if (input) {
|
|
43
|
+
// promisified execFile does not feed stdin — handled by the caller via runcliwithinput
|
|
44
|
+
}
|
|
45
|
+
return { stdout, stderr, code: 0 };
|
|
46
|
+
} catch (err) {
|
|
47
|
+
const e = err as { stdout?: string; stderr?: string; code?: number };
|
|
48
|
+
return { stdout: e.stdout ?? "", stderr: e.stderr ?? "", code: e.code ?? 1 };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** run the cli feeding piped stdin answers (the automation consumer flow) */
|
|
53
|
+
async function runcliwithinput(
|
|
54
|
+
workdir: string,
|
|
55
|
+
args: string[],
|
|
56
|
+
input: string,
|
|
57
|
+
): Promise<{ stdout: string; stderr: string; code: number }> {
|
|
58
|
+
return new Promise((resolve) => {
|
|
59
|
+
const child = execFile(tsxbin, ["--tsconfig", tsconfigpath, clits, ...args], {
|
|
60
|
+
cwd: workdir,
|
|
61
|
+
env: { ...process.env, DEVTHINK_DATABASE_URL: `file:${join(workdir, "cli.db")}` },
|
|
62
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
63
|
+
});
|
|
64
|
+
child.stdin?.write(input);
|
|
65
|
+
child.stdin?.end();
|
|
66
|
+
let stdout = "";
|
|
67
|
+
let stderr = "";
|
|
68
|
+
child.stdout?.on("data", (d: Buffer) => {
|
|
69
|
+
stdout += d.toString();
|
|
70
|
+
});
|
|
71
|
+
child.stderr?.on("data", (d: Buffer) => {
|
|
72
|
+
stderr += d.toString();
|
|
73
|
+
});
|
|
74
|
+
child.on("close", (code) => resolve({ stdout, stderr, code: code ?? 0 }));
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
// workspace — one temp dir hosts the whole consumer journey
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
|
|
82
|
+
const workdir = mkdtempSync(join(tmpdir(), "gateway-cli-"));
|
|
83
|
+
|
|
84
|
+
afterAll(() => {
|
|
85
|
+
rmSync(workdir, { recursive: true, force: true });
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
// help and unknown commands
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
|
|
92
|
+
describe("cli help", () => {
|
|
93
|
+
it("prints the usage banner with every command", async () => {
|
|
94
|
+
const { stdout, code } = await runcli(workdir, ["help"]);
|
|
95
|
+
expect(code).toBe(0);
|
|
96
|
+
expect(stdout).toContain("@wenathlan/devthink gateway");
|
|
97
|
+
expect(stdout).toContain("Usage: devthink gateway <command>");
|
|
98
|
+
for (const cmd of ["init", "add", "list", "show", "validate", "keys", "models", "serve", "export"]) {
|
|
99
|
+
expect(stdout).toContain(cmd);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("bare invocation prints help", async () => {
|
|
104
|
+
const { stdout, code } = await runcli(workdir, []);
|
|
105
|
+
expect(code).toBe(0);
|
|
106
|
+
expect(stdout).toContain("Usage: devthink gateway <command>");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("unknown commands fail with the usage", async () => {
|
|
110
|
+
const { stdout, code } = await runcli(workdir, ["definitely-not-a-command"]);
|
|
111
|
+
expect(code).toBe(1);
|
|
112
|
+
expect(stdout).toContain("unknown command");
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
// init — the piped automation consumer flow
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
|
|
120
|
+
describe("cli init", () => {
|
|
121
|
+
it("scaffolds the full project from piped default answers", async () => {
|
|
122
|
+
// 30 blank lines cover every question — each falls back to its default
|
|
123
|
+
const { stdout, code } = await runcliwithinput(workdir, ["init"], "\n".repeat(30));
|
|
124
|
+
expect(code).toBe(0);
|
|
125
|
+
expect(stdout).toContain("init");
|
|
126
|
+
expect(existsSync(join(workdir, "config.mjs"))).toBe(true);
|
|
127
|
+
expect(existsSync(join(workdir, "schema.prisma"))).toBe(true);
|
|
128
|
+
expect(existsSync(join(workdir, "prismaconfig.ts"))).toBe(true);
|
|
129
|
+
expect(existsSync(join(workdir, ".env.example"))).toBe(true);
|
|
130
|
+
expect(stdout).toContain("config.mjs");
|
|
131
|
+
expect(stdout).toContain("schema.prisma");
|
|
132
|
+
expect(stdout).toContain("prismaconfig.ts");
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("the scaffold is .mjs — loadable by plain node in a typeless package", () => {
|
|
136
|
+
const cfg = readFileSync(join(workdir, "config.mjs"), "utf8");
|
|
137
|
+
expect(cfg).toContain("export const config");
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("the generated config carries the version and meta model", () => {
|
|
141
|
+
const cfg = readFileSync(join(workdir, "config.mjs"), "utf8");
|
|
142
|
+
expect(cfg).toContain("v1");
|
|
143
|
+
expect(cfg).toContain("devthink");
|
|
144
|
+
expect(cfg).toContain("custom");
|
|
145
|
+
expect(cfg).toContain("rotation");
|
|
146
|
+
expect(cfg).toContain("bearer");
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("the generated schema defines the chat message and key models", () => {
|
|
150
|
+
const schema = readFileSync(join(workdir, "schema.prisma"), "utf8");
|
|
151
|
+
expect(schema).toContain("model ChatMessage");
|
|
152
|
+
expect(schema).toContain("model ApiKey");
|
|
153
|
+
expect(schema).toContain("model NvidiaKey");
|
|
154
|
+
expect(schema).toContain("datasource db");
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("the prisma config resolves the schema and datasource url", () => {
|
|
158
|
+
const prismaconfig = readFileSync(join(workdir, "prismaconfig.ts"), "utf8");
|
|
159
|
+
expect(prismaconfig).toContain("schema.prisma");
|
|
160
|
+
expect(prismaconfig).toContain("DEVTHINK_DATABASE_URL");
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("the env example lists the derived env var", () => {
|
|
164
|
+
const env = readFileSync(join(workdir, ".env.example"), "utf8");
|
|
165
|
+
expect(env).toContain("CUSTOM_API_KEY=");
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("init refuses to overwrite without a yes answer", async () => {
|
|
169
|
+
const before = readFileSync(join(workdir, "config.mjs"), "utf8");
|
|
170
|
+
const { stdout, code } = await runcliwithinput(workdir, ["init"], "n\n");
|
|
171
|
+
expect(code).toBe(0);
|
|
172
|
+
expect(stdout).toContain("keeping existing config");
|
|
173
|
+
expect(readFileSync(join(workdir, "config.mjs"), "utf8")).toBe(before);
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// ---------------------------------------------------------------------------
|
|
178
|
+
// list show validate models — reading the scaffolded config
|
|
179
|
+
// ---------------------------------------------------------------------------
|
|
180
|
+
|
|
181
|
+
describe("cli list show validate models", () => {
|
|
182
|
+
it("list shows the version summary", async () => {
|
|
183
|
+
const { stdout, code } = await runcli(workdir, ["list"]);
|
|
184
|
+
expect(code).toBe(0);
|
|
185
|
+
expect(stdout).toContain("1 versions");
|
|
186
|
+
expect(stdout).toContain("v1");
|
|
187
|
+
expect(stdout).toContain("custom");
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it("show prints one version detail", async () => {
|
|
191
|
+
const { stdout, code } = await runcli(workdir, ["show", "v1"]);
|
|
192
|
+
expect(code).toBe(0);
|
|
193
|
+
expect(stdout).toContain("providername");
|
|
194
|
+
expect(stdout).toContain("upstreams");
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("show of an unknown version fails", async () => {
|
|
198
|
+
const { code } = await runcli(workdir, ["show", "nope"]);
|
|
199
|
+
expect(code).toBe(1);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it("validate reports the scaffolded config valid", async () => {
|
|
203
|
+
const { stdout, code } = await runcli(workdir, ["validate"]);
|
|
204
|
+
expect(code).toBe(0);
|
|
205
|
+
expect(stdout).toContain("config is valid");
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it("models lists the catalog and rotation pool", async () => {
|
|
209
|
+
const { stdout, code } = await runcli(workdir, ["models", "v1"]);
|
|
210
|
+
expect(code).toBe(0);
|
|
211
|
+
expect(stdout).toContain("models for v1");
|
|
212
|
+
expect(stdout).toContain("default-model");
|
|
213
|
+
expect(stdout).toContain("rotation pool");
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// ---------------------------------------------------------------------------
|
|
218
|
+
// keys — env instructions and the db seed script
|
|
219
|
+
// ---------------------------------------------------------------------------
|
|
220
|
+
|
|
221
|
+
describe("cli keys", () => {
|
|
222
|
+
it("without a db model it documents the env var", async () => {
|
|
223
|
+
const { stdout, code } = await runcli(workdir, ["keys", "v1"]);
|
|
224
|
+
expect(code).toBe(0);
|
|
225
|
+
expect(stdout).toContain("CUSTOM_API_KEY");
|
|
226
|
+
expect(stdout).toContain("comma separated");
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it("keys of an unknown version fails", async () => {
|
|
230
|
+
const { code } = await runcli(workdir, ["keys", "nope"]);
|
|
231
|
+
expect(code).toBe(1);
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// ---------------------------------------------------------------------------
|
|
236
|
+
// export — the deployable scaffold
|
|
237
|
+
// ---------------------------------------------------------------------------
|
|
238
|
+
|
|
239
|
+
describe("cli export", () => {
|
|
240
|
+
it("exports the config and schema for deploy", async () => {
|
|
241
|
+
const { stdout, code } = await runcli(workdir, ["export", "--dir", "export"]);
|
|
242
|
+
expect(code).toBe(0);
|
|
243
|
+
expect(stdout).toContain("exported config.mjs");
|
|
244
|
+
expect(stdout).toContain("exported schema.prisma");
|
|
245
|
+
expect(existsSync(join(workdir, "export", "config.mjs"))).toBe(true);
|
|
246
|
+
expect(existsSync(join(workdir, "export", "schema.prisma"))).toBe(true);
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// ---------------------------------------------------------------------------
|
|
251
|
+
// db-backed keys — a second workspace with db key storage enabled
|
|
252
|
+
// ---------------------------------------------------------------------------
|
|
253
|
+
|
|
254
|
+
describe("cli init with db keys", () => {
|
|
255
|
+
const dbworkdir = mkdtempSync(join(tmpdir(), "gateway-cli-db-"));
|
|
256
|
+
|
|
257
|
+
it("scaffolds with database key storage and generates the seed script", async () => {
|
|
258
|
+
const answers = [
|
|
259
|
+
"1", // versions
|
|
260
|
+
"", // id → v1
|
|
261
|
+
"", // provider → custom
|
|
262
|
+
"", // baseurl → default
|
|
263
|
+
"", // auth → bearer
|
|
264
|
+
"", // required → default true
|
|
265
|
+
"", // envvar → CUSTOM_API_KEY
|
|
266
|
+
"y", // store keys in database
|
|
267
|
+
"apiKey", // db model
|
|
268
|
+
"1", // one model
|
|
269
|
+
"", // model id default
|
|
270
|
+
"", // context
|
|
271
|
+
"", // maxoutput
|
|
272
|
+
"n", // no rotation (single model)
|
|
273
|
+
"y", // retry
|
|
274
|
+
"", // max retries
|
|
275
|
+
"", // backoff base
|
|
276
|
+
"", // backoff cap
|
|
277
|
+
"n", // no shared context
|
|
278
|
+
"", // meta id → devthink
|
|
279
|
+
"", // gateway name
|
|
280
|
+
"", // description
|
|
281
|
+
].join("\n");
|
|
282
|
+
const { code } = await runcliwithinput(dbworkdir, ["init"], `${answers}\n`);
|
|
283
|
+
expect(code).toBe(0);
|
|
284
|
+
|
|
285
|
+
const { stdout, code: keyscode } = await runcliwithinput(
|
|
286
|
+
dbworkdir,
|
|
287
|
+
["keys", "v1"],
|
|
288
|
+
"2\nkey-aaaaaaaaaa\nkey-bbbbbbbbbb\n",
|
|
289
|
+
);
|
|
290
|
+
expect(keyscode).toBe(0);
|
|
291
|
+
expect(stdout).toContain("register keys");
|
|
292
|
+
const seed = join(dbworkdir, "tests", "seedkeys.mjs");
|
|
293
|
+
expect(existsSync(seed)).toBe(true);
|
|
294
|
+
const content = readFileSync(seed, "utf8");
|
|
295
|
+
expect(content).toContain("key-aaaaaaaaaa");
|
|
296
|
+
expect(content).toContain("key-bbbbbbbbbb");
|
|
297
|
+
expect(content).toContain("apiKey");
|
|
298
|
+
rmSync(dbworkdir, { recursive: true, force: true });
|
|
299
|
+
});
|
|
300
|
+
});
|