@chvor/cli 0.1.2 → 0.1.3

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.
@@ -97,7 +97,7 @@ 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
102
  stdio: "inherit",
103
103
  });
@@ -149,7 +149,7 @@ export async function spawnServer(opts = {}) {
149
149
  ensureDir(logsDir);
150
150
  const logPath = join(logsDir, "server.log");
151
151
  const logFd = openSync(logPath, "a");
152
- const child = spawn("node", [serverEntry], {
152
+ const child = spawn("node", ["--import", "tsx", serverEntry], {
153
153
  env,
154
154
  stdio: ["ignore", logFd, logFd],
155
155
  detached: true,
@@ -232,7 +232,7 @@ export async function pollHealth(port, token, timeoutMs = 30000, intervalMs = 50
232
232
  function sleep(ms) {
233
233
  return new Promise((resolve) => setTimeout(resolve, ms));
234
234
  }
235
- function filterEnv(env) {
235
+ export function filterEnv(env) {
236
236
  const result = {};
237
237
  for (const [key, value] of Object.entries(env)) {
238
238
  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.2",
3
+ "version": "0.1.3",
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",