From 3c8b16d7b1222000c0d918040f07997e18b290e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E6=A9=98?= Date: Sun, 31 May 2026 08:14:23 +0000 Subject: [PATCH] fix: clean error message for invalid schema in `schema put` command Fixes #54 The `json-cas schema put` command now catches SchemaValidationError and displays a clean error message instead of showing a raw stack trace. Changes: - Import SchemaValidationError from @uncaged/json-cas - Wrap putSchema() call in try-catch in cmdSchemaPut - Catch SchemaValidationError specifically and call die(e.message) - Add 5 comprehensive tests for invalid schema error handling Test cases: 1. Invalid JSON Schema type value 2. Unknown schema keys 3. Invalid nested schema 4. Non-object root schema 5. Valid schema regression test Co-Authored-By: Claude Opus 4.6 --- packages/cli-json-cas/src/cli.test.ts | 134 ++++++++++++++++++++++++++ packages/cli-json-cas/src/index.ts | 12 ++- 2 files changed, 144 insertions(+), 2 deletions(-) diff --git a/packages/cli-json-cas/src/cli.test.ts b/packages/cli-json-cas/src/cli.test.ts index 90146d5..65479af 100644 --- a/packages/cli-json-cas/src/cli.test.ts +++ b/packages/cli-json-cas/src/cli.test.ts @@ -597,3 +597,137 @@ describe("Suite 6: CLI Integration with Templates", () => { } }); }); + +// ---- schema put - invalid schema error handling ---- + +describe("schema put - invalid schema error handling", () => { + test("invalid schema - unknown type value shows clean error", async () => { + const tmpStore = mkdtempSync(join(tmpdir(), "json-cas-test-")); + try { + await runCli(["init"], tmpStore); + + const schemaFile = join(tmpStore, "invalid-schema.json"); + writeFileSync(schemaFile, JSON.stringify({ type: "invalid" })); + + const { exitCode, stderr, stdout } = await runCli( + ["schema", "put", schemaFile], + tmpStore, + ); + + expect(exitCode).not.toBe(0); + expect(stderr).toContain("Invalid schema"); + expect(stderr).not.toContain("at "); + expect(stdout).toBe(""); + } finally { + rmSync(tmpStore, { recursive: true, force: true }); + } + }); + + test("invalid schema - unknown key shows clean error", async () => { + const tmpStore = mkdtempSync(join(tmpdir(), "json-cas-test-")); + try { + await runCli(["init"], tmpStore); + + const schemaFile = join(tmpStore, "invalid-schema.json"); + writeFileSync( + schemaFile, + JSON.stringify({ type: "string", unknownKey: true }), + ); + + const { exitCode, stderr, stdout } = await runCli( + ["schema", "put", schemaFile], + tmpStore, + ); + + expect(exitCode).not.toBe(0); + expect(stderr).toContain("Invalid schema"); + expect(stderr).not.toContain("at "); + expect(stdout).toBe(""); + } finally { + rmSync(tmpStore, { recursive: true, force: true }); + } + }); + + test("invalid schema - invalid nested schema shows clean error", async () => { + const tmpStore = mkdtempSync(join(tmpdir(), "json-cas-test-")); + try { + await runCli(["init"], tmpStore); + + const schemaFile = join(tmpStore, "invalid-schema.json"); + writeFileSync( + schemaFile, + JSON.stringify({ + type: "object", + properties: { + name: { type: "invalid" }, + }, + }), + ); + + const { exitCode, stderr, stdout } = await runCli( + ["schema", "put", schemaFile], + tmpStore, + ); + + expect(exitCode).not.toBe(0); + expect(stderr).toContain("Invalid schema"); + expect(stderr).not.toContain("at "); + expect(stdout).toBe(""); + } finally { + rmSync(tmpStore, { recursive: true, force: true }); + } + }); + + test("invalid schema - non-object root shows clean error", async () => { + const tmpStore = mkdtempSync(join(tmpdir(), "json-cas-test-")); + try { + await runCli(["init"], tmpStore); + + const schemaFile = join(tmpStore, "invalid-schema.json"); + writeFileSync(schemaFile, JSON.stringify(["type", "string"])); + + const { exitCode, stderr, stdout } = await runCli( + ["schema", "put", schemaFile], + tmpStore, + ); + + expect(exitCode).not.toBe(0); + expect(stderr).toContain("Invalid schema"); + expect(stderr).not.toContain("at "); + expect(stdout).toBe(""); + } finally { + rmSync(tmpStore, { recursive: true, force: true }); + } + }); + + test("valid schema still works (regression)", async () => { + const tmpStore = mkdtempSync(join(tmpdir(), "json-cas-test-")); + try { + await runCli(["init"], tmpStore); + + const schemaFile = join(tmpStore, "valid-schema.json"); + writeFileSync( + schemaFile, + JSON.stringify({ + type: "object", + properties: { + name: { type: "string" }, + age: { type: "number" }, + }, + required: ["name"], + }), + ); + + const { exitCode, stderr, stdout } = await runCli( + ["schema", "put", schemaFile], + tmpStore, + ); + + expect(exitCode).toBe(0); + expect(stderr).toBe(""); + expect(stdout.trim()).toMatch(/^[0-9A-HJKMNP-TV-Z]{13}$/); + } finally { + rmSync(tmpStore, { recursive: true, force: true }); + } + }); +}); diff --git a/packages/cli-json-cas/src/index.ts b/packages/cli-json-cas/src/index.ts index 51bdf4d..b3e5b8a 100644 --- a/packages/cli-json-cas/src/index.ts +++ b/packages/cli-json-cas/src/index.ts @@ -17,6 +17,7 @@ import { refs, renderAsync, renderDirect, + SchemaValidationError, TagLabelConflictError, VariableNotFoundError, validate, @@ -243,8 +244,15 @@ async function cmdSchemaPut(args: string[]): Promise { if (!file) die("Usage: json-cas schema put "); const schema = readJsonFile(file) as JSONSchema; const store = openStore(); - const hash = await putSchema(store, schema); - console.log(hash); + try { + const hash = await putSchema(store, schema); + console.log(hash); + } catch (e) { + if (e instanceof SchemaValidationError) { + die(e.message); + } + throw e; + } } async function cmdSchemaGet(args: string[]): Promise { -- 2.43.0