@chvor/cli 0.1.2 → 0.1.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/dist/lib/process.js +5 -3
- package/dist/lib/process.test.js +71 -0
- package/package.json +7 -2
package/dist/lib/process.js
CHANGED
|
@@ -97,8 +97,9 @@ export async function spawnServer(opts = {}) {
|
|
|
97
97
|
}
|
|
98
98
|
if (opts.foreground) {
|
|
99
99
|
console.log(`Starting Chvor on port ${port} (foreground)...`);
|
|
100
|
-
const child = spawn("node", [serverEntry], {
|
|
100
|
+
const child = spawn("node", ["--import", "tsx", serverEntry], {
|
|
101
101
|
env,
|
|
102
|
+
cwd: getAppDir(),
|
|
102
103
|
stdio: "inherit",
|
|
103
104
|
});
|
|
104
105
|
if (child.pid === undefined) {
|
|
@@ -149,8 +150,9 @@ export async function spawnServer(opts = {}) {
|
|
|
149
150
|
ensureDir(logsDir);
|
|
150
151
|
const logPath = join(logsDir, "server.log");
|
|
151
152
|
const logFd = openSync(logPath, "a");
|
|
152
|
-
const child = spawn("node", [serverEntry], {
|
|
153
|
+
const child = spawn("node", ["--import", "tsx", serverEntry], {
|
|
153
154
|
env,
|
|
155
|
+
cwd: getAppDir(),
|
|
154
156
|
stdio: ["ignore", logFd, logFd],
|
|
155
157
|
detached: true,
|
|
156
158
|
});
|
|
@@ -232,7 +234,7 @@ export async function pollHealth(port, token, timeoutMs = 30000, intervalMs = 50
|
|
|
232
234
|
function sleep(ms) {
|
|
233
235
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
234
236
|
}
|
|
235
|
-
function filterEnv(env) {
|
|
237
|
+
export function filterEnv(env) {
|
|
236
238
|
const result = {};
|
|
237
239
|
for (const [key, value] of Object.entries(env)) {
|
|
238
240
|
if (value !== undefined) {
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { filterEnv, pollHealth } from "./process.js";
|
|
3
|
+
describe("filterEnv", () => {
|
|
4
|
+
it("removes entries with undefined values", () => {
|
|
5
|
+
const input = { A: "1", B: undefined, C: "3" };
|
|
6
|
+
const result = filterEnv(input);
|
|
7
|
+
expect(result).toEqual({ A: "1", C: "3" });
|
|
8
|
+
});
|
|
9
|
+
it("returns empty object for empty input", () => {
|
|
10
|
+
expect(filterEnv({})).toEqual({});
|
|
11
|
+
});
|
|
12
|
+
it("keeps all entries when none are undefined", () => {
|
|
13
|
+
const input = { X: "x", Y: "y" };
|
|
14
|
+
expect(filterEnv(input)).toEqual({ X: "x", Y: "y" });
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
describe("pollHealth", () => {
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
vi.useFakeTimers({ shouldAdvanceTime: true });
|
|
20
|
+
vi.stubGlobal("fetch", vi.fn());
|
|
21
|
+
});
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
vi.useRealTimers();
|
|
24
|
+
vi.restoreAllMocks();
|
|
25
|
+
});
|
|
26
|
+
it("returns true when server responds with ok: true", async () => {
|
|
27
|
+
const mockFetch = vi.mocked(fetch);
|
|
28
|
+
mockFetch.mockResolvedValueOnce(new Response(JSON.stringify({ ok: true }), { status: 200 }));
|
|
29
|
+
const result = await pollHealth("3001");
|
|
30
|
+
expect(result).toBe(true);
|
|
31
|
+
});
|
|
32
|
+
it("sends Authorization header when token is provided", async () => {
|
|
33
|
+
const mockFetch = vi.mocked(fetch);
|
|
34
|
+
mockFetch.mockResolvedValueOnce(new Response(JSON.stringify({ ok: true }), { status: 200 }));
|
|
35
|
+
await pollHealth("3001", "my-secret-token");
|
|
36
|
+
expect(mockFetch).toHaveBeenCalledWith("http://localhost:3001/api/health", { headers: { Authorization: "Bearer my-secret-token" } });
|
|
37
|
+
});
|
|
38
|
+
it("retries on fetch failure and succeeds eventually", async () => {
|
|
39
|
+
const mockFetch = vi.mocked(fetch);
|
|
40
|
+
mockFetch
|
|
41
|
+
.mockRejectedValueOnce(new Error("ECONNREFUSED"))
|
|
42
|
+
.mockRejectedValueOnce(new Error("ECONNREFUSED"))
|
|
43
|
+
.mockResolvedValueOnce(new Response(JSON.stringify({ ok: true }), { status: 200 }));
|
|
44
|
+
const result = await pollHealth("3001", undefined, 30000, 100);
|
|
45
|
+
expect(result).toBe(true);
|
|
46
|
+
expect(mockFetch).toHaveBeenCalledTimes(3);
|
|
47
|
+
});
|
|
48
|
+
it("returns false when timeout expires", async () => {
|
|
49
|
+
const mockFetch = vi.mocked(fetch);
|
|
50
|
+
mockFetch.mockRejectedValue(new Error("ECONNREFUSED"));
|
|
51
|
+
const result = await pollHealth("3001", undefined, 1000, 200);
|
|
52
|
+
expect(result).toBe(false);
|
|
53
|
+
});
|
|
54
|
+
it("retries when response is not ok", async () => {
|
|
55
|
+
const mockFetch = vi.mocked(fetch);
|
|
56
|
+
mockFetch
|
|
57
|
+
.mockResolvedValueOnce(new Response("Internal Server Error", { status: 500 }))
|
|
58
|
+
.mockResolvedValueOnce(new Response(JSON.stringify({ ok: true }), { status: 200 }));
|
|
59
|
+
const result = await pollHealth("3001", undefined, 30000, 100);
|
|
60
|
+
expect(result).toBe(true);
|
|
61
|
+
expect(mockFetch).toHaveBeenCalledTimes(2);
|
|
62
|
+
});
|
|
63
|
+
it("retries when body.ok is not true", async () => {
|
|
64
|
+
const mockFetch = vi.mocked(fetch);
|
|
65
|
+
mockFetch
|
|
66
|
+
.mockResolvedValueOnce(new Response(JSON.stringify({ ok: false }), { status: 200 }))
|
|
67
|
+
.mockResolvedValueOnce(new Response(JSON.stringify({ ok: true }), { status: 200 }));
|
|
68
|
+
const result = await pollHealth("3001", undefined, 30000, 100);
|
|
69
|
+
expect(result).toBe(true);
|
|
70
|
+
});
|
|
71
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chvor/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Your own AI — install and run chvor.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
6
6
|
"type": "module",
|
|
@@ -17,16 +17,21 @@
|
|
|
17
17
|
"scripts": {
|
|
18
18
|
"build": "tsc",
|
|
19
19
|
"prepublishOnly": "tsc",
|
|
20
|
-
"typecheck": "tsc --noEmit"
|
|
20
|
+
"typecheck": "tsc --noEmit",
|
|
21
|
+
"test": "vitest run"
|
|
21
22
|
},
|
|
22
23
|
"dependencies": {
|
|
23
24
|
"commander": "^13",
|
|
24
25
|
"@inquirer/prompts": "^7",
|
|
26
|
+
"tsx": "^4",
|
|
25
27
|
"yaml": "^2"
|
|
26
28
|
},
|
|
27
29
|
"optionalDependencies": {
|
|
28
30
|
"better-sqlite3": "^11"
|
|
29
31
|
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"vitest": "^4.1.0"
|
|
34
|
+
},
|
|
30
35
|
"repository": {
|
|
31
36
|
"type": "git",
|
|
32
37
|
"url": "https://github.com/luka-zivkovic/chvor.git",
|