Skip to content

Commit 9356259

Browse files
Copilotfuxingloh
andcommitted
refactor: use Zod for state/config schemas; log auto-update errors
Co-authored-by: fuxingloh <4266087+fuxingloh@users.noreply.github.com>
1 parent 4011449 commit 9356259

5 files changed

Lines changed: 34 additions & 17 deletions

File tree

packages/use-agently/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@
2929
"@x402/fetch": "^2.4.0",
3030
"commander": "^14.0.3",
3131
"viem": "^2.46.3",
32-
"yaml": "^2.8.2"
32+
"yaml": "^2.8.2",
33+
"zod": "^4"
3334
},
3435
"devDependencies": {
3536
"@types/bun": "^1.3.9",

packages/use-agently/src/commands/update.test.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,10 +186,13 @@ describe("checkAutoUpdate", () => {
186186
expect(mockSaveState).toHaveBeenCalledTimes(1);
187187
});
188188

189-
test("silently swallows errors", async () => {
189+
test("logs warning but does not throw on errors", async () => {
190190
fetchSpy.mockResolvedValue({ ok: false, status: 500 } as Response);
191191
mockLoadState.mockImplementation(async () => ({}));
192+
const warnSpy = spyOn(console, "warn").mockImplementation(() => {});
192193

193194
await expect(checkAutoUpdate()).resolves.toBeUndefined();
195+
expect(warnSpy).toHaveBeenCalledWith("Auto-update failed:", expect.any(String));
196+
warnSpy.mockRestore();
194197
});
195198
});

packages/use-agently/src/commands/update.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ export async function checkAutoUpdate(): Promise<void> {
6161
if (hoursSinceLastCheck < 24) return;
6262

6363
await checkAndUpdate();
64-
} catch {
65-
// Auto-update errors are silently swallowed
64+
} catch (err) {
65+
console.warn("Auto-update failed:", err instanceof Error ? err.message : String(err));
6666
}
6767
}
6868

packages/use-agently/src/config.ts

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
import { homedir } from "node:os";
22
import { join } from "node:path";
33
import { mkdir, rename, readFile, writeFile } from "node:fs/promises";
4+
import { z } from "zod";
45

56
export type ConfigScope = "global" | "local";
67

7-
export interface WalletConfig {
8-
type: string;
9-
[key: string]: unknown;
10-
}
8+
export const WalletConfigSchema = z.object({ type: z.string() }).passthrough();
119

12-
export interface Config {
13-
wallet: WalletConfig;
14-
env?: Record<string, number | string>;
15-
}
10+
export const ConfigSchema = z.object({
11+
wallet: WalletConfigSchema,
12+
env: z.record(z.string(), z.union([z.number(), z.string()])).optional(),
13+
});
14+
15+
export type WalletConfig = z.infer<typeof WalletConfigSchema>;
16+
export type Config = z.infer<typeof ConfigSchema>;
1617

1718
function getConfigDir(scope: ConfigScope): string {
1819
return scope === "local" ? join(process.cwd(), ".use-agently") : join(homedir(), ".use-agently");
@@ -29,13 +30,21 @@ async function loadConfigFromPath(configPath: string): Promise<Config | undefine
2930
} catch {
3031
return undefined;
3132
}
33+
let raw: unknown;
3234
try {
33-
return JSON.parse(contents) as Config;
35+
raw = JSON.parse(contents);
3436
} catch {
3537
throw new Error(
3638
`Config file at ${configPath} contains invalid JSON. Please fix or delete it and run \`use-agently init\`.`,
3739
);
3840
}
41+
const result = ConfigSchema.safeParse(raw);
42+
if (!result.success) {
43+
throw new Error(
44+
`Config file at ${configPath} has an invalid format. Please fix or delete it and run \`use-agently init\`.`,
45+
);
46+
}
47+
return result.data;
3948
}
4049

4150
export async function loadConfig(scope?: ConfigScope): Promise<Config | undefined> {

packages/use-agently/src/state.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import { homedir } from "node:os";
22
import { join } from "node:path";
33
import { mkdir, readFile, writeFile } from "node:fs/promises";
4+
import { z } from "zod";
45

5-
export interface State {
6-
lastUpdateCheck?: string;
7-
}
6+
export const StateSchema = z.object({
7+
lastUpdateCheck: z.string().optional(),
8+
});
9+
10+
export type State = z.infer<typeof StateSchema>;
811

912
function getStateDir(): string {
1013
return join(homedir(), ".use-agently");
@@ -17,7 +20,8 @@ function getStatePath(): string {
1720
export async function loadState(): Promise<State> {
1821
try {
1922
const contents = await readFile(getStatePath(), "utf8");
20-
return JSON.parse(contents) as State;
23+
const result = StateSchema.safeParse(JSON.parse(contents));
24+
return result.success ? result.data : {};
2125
} catch {
2226
return {};
2327
}

0 commit comments

Comments
 (0)