Skip to content

Commit 980e8b2

Browse files
committed
feat: auto-add $schema to config files during verification
1 parent cbc6723 commit 980e8b2

2 files changed

Lines changed: 106 additions & 2 deletions

File tree

src/core/verify-config.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* Verify user and project scope config files for safety-net.
33
*/
44

5-
import { existsSync } from "node:fs";
5+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
66
import { resolve } from "node:path";
77
import {
88
getProjectConfigPath,
@@ -18,6 +18,8 @@ export interface VerifyConfigOptions {
1818

1919
const HEADER = "Safety Net Config";
2020
const SEPARATOR = "═".repeat(HEADER.length);
21+
const SCHEMA_URL =
22+
"https://raw.githubusercontent.com/kenryu42/claude-code-safety-net/main/assets/cc-safety-net.schema.json";
2123

2224
function printHeader(): void {
2325
console.log(HEADER);
@@ -58,6 +60,23 @@ function printInvalidConfig(
5860
}
5961
}
6062

63+
function addSchemaIfMissing(path: string): boolean {
64+
try {
65+
const content = readFileSync(path, "utf-8");
66+
const parsed = JSON.parse(content) as Record<string, unknown>;
67+
68+
if (parsed.$schema) {
69+
return false;
70+
}
71+
72+
const updated = { $schema: SCHEMA_URL, ...parsed };
73+
writeFileSync(path, JSON.stringify(updated, null, 2), "utf-8");
74+
return true;
75+
} catch {
76+
return false;
77+
}
78+
}
79+
6180
/**
6281
* Verify config files and print results.
6382
* @returns Exit code (0 = success, 1 = errors found)
@@ -104,6 +123,9 @@ export function verifyConfig(options: VerifyConfigOptions = {}): number {
104123
if (result.errors.length > 0) {
105124
printInvalidConfig(scope, path, result.errors);
106125
} else {
126+
if (addSchemaIfMissing(path)) {
127+
console.log(`\nAdded $schema to ${scope.toLowerCase()} config.`);
128+
}
107129
printValidConfig(scope, path, result);
108130
}
109131
}

tests/verify-config.test.ts

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2-
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
2+
import {
3+
existsSync,
4+
mkdirSync,
5+
readFileSync,
6+
rmSync,
7+
writeFileSync,
8+
} from "node:fs";
39
import { tmpdir } from "node:os";
410
import { join } from "node:path";
511
import {
@@ -286,4 +292,80 @@ describe("verify-config", () => {
286292
expect(stdout).toContain("✓ Project config:");
287293
});
288294
});
295+
296+
describe("schema auto-add", () => {
297+
function readProjectConfig(): Record<string, unknown> {
298+
return JSON.parse(readFileSync(projectConfigPath, "utf-8"));
299+
}
300+
301+
function readUserConfig(): Record<string, unknown> {
302+
return JSON.parse(readFileSync(userConfigPath, "utf-8"));
303+
}
304+
305+
test("adds $schema to valid project config missing it", () => {
306+
writeProjectConfig('{"version": 1}');
307+
runMain();
308+
const config = readProjectConfig();
309+
expect(config.$schema).toBe(
310+
"https://raw.githubusercontent.com/kenryu42/claude-code-safety-net/main/assets/cc-safety-net.schema.json",
311+
);
312+
});
313+
314+
test("adds $schema to valid user config missing it", () => {
315+
writeUserConfig('{"version": 1}');
316+
runMain();
317+
const config = readUserConfig();
318+
expect(config.$schema).toBe(
319+
"https://raw.githubusercontent.com/kenryu42/claude-code-safety-net/main/assets/cc-safety-net.schema.json",
320+
);
321+
});
322+
323+
test("does not modify config that already has $schema", () => {
324+
const originalConfig = {
325+
$schema:
326+
"https://raw.githubusercontent.com/kenryu42/claude-code-safety-net/main/assets/cc-safety-net.schema.json",
327+
version: 1,
328+
};
329+
writeProjectConfig(JSON.stringify(originalConfig, null, 2));
330+
runMain();
331+
const config = readProjectConfig();
332+
expect(config).toEqual(originalConfig);
333+
});
334+
335+
test("preserves existing rules when adding $schema", () => {
336+
const originalConfig = {
337+
version: 1,
338+
rules: [
339+
{
340+
name: "block-foo",
341+
command: "foo",
342+
block_args: ["-x"],
343+
reason: "Blocked",
344+
},
345+
],
346+
};
347+
writeProjectConfig(JSON.stringify(originalConfig));
348+
runMain();
349+
const config = readProjectConfig();
350+
expect(config.$schema).toBe(
351+
"https://raw.githubusercontent.com/kenryu42/claude-code-safety-net/main/assets/cc-safety-net.schema.json",
352+
);
353+
expect(config.version).toBe(1);
354+
expect(config.rules).toEqual(originalConfig.rules);
355+
});
356+
357+
test("does not add $schema to invalid config", () => {
358+
writeProjectConfig('{"version": 2}');
359+
runMain();
360+
const config = readProjectConfig();
361+
expect(config.$schema).toBeUndefined();
362+
});
363+
364+
test("prints message when $schema is added", () => {
365+
writeProjectConfig('{"version": 1}');
366+
runMain();
367+
const output = getStdout();
368+
expect(output).toContain("Added $schema");
369+
});
370+
});
289371
});

0 commit comments

Comments
 (0)