diff --git a/packages/cli-json-cas/src/__snapshots__/e2e.test.ts.snap b/packages/cli-json-cas/src/__snapshots__/e2e.test.ts.snap index 54d4f51..9d9f668 100644 --- a/packages/cli-json-cas/src/__snapshots__/e2e.test.ts.snap +++ b/packages/cli-json-cas/src/__snapshots__/e2e.test.ts.snap @@ -1,32 +1,5 @@ // Bun Snapshot v1, https://bun.sh/docs/test/snapshots -exports[`Phase 1: CAS Core 1.3 schema get returns schema JSON (snapshot) 1`] = ` -"{ - "type": "object", - "required": [ - "name" - ], - "properties": { - "age": { - "type": "number" - }, - "name": { - "type": "string" - } - }, - "additionalProperties": false -}" -`; - -exports[`Phase 1: CAS Core 1.4 schema list shows registered schemas 1`] = ` -"8J5E9WCD54ZJZ (unnamed) -89ZQWMV9PTC7B (unnamed) -86QB0Q5VA7797 (unnamed) -AKEX4KYV98MGT (unnamed) -AGSJVKM01WNKZ (unnamed) -7XX5H51CVD9H0 (unnamed)" -`; - exports[`Phase 1: CAS Core 1.6 get returns node JSON (snapshot) 1`] = ` { "payload": { @@ -43,25 +16,6 @@ exports[`Phase 1: CAS Core 1.10 refs lists direct references (snapshot) 1`] = `" exports[`Phase 1: CAS Core 1.11 walk shows traversal tree (snapshot) 1`] = `"ERARPP19YJT05"`; -exports[`Phase 1: CAS Core 1.13 cat returns full node (snapshot) 1`] = ` -{ - "payload": { - "age": 30, - "name": "Alice", - }, - "type": "7XX5H51CVD9H0", -} -`; - -exports[`Phase 1: CAS Core 1.14 cat --payload returns only payload (snapshot) 1`] = ` -"{ - "age": 30, - "name": "Alice" -}" -`; - -exports[`Phase 2: Schema Validation 2.2 schema validate on valid node returns valid 1`] = `"valid"`; - exports[`Phase 2: Schema Validation 2.3 put against non-existent schema hash fails 1`] = `"Schema not found: AAAAAAAAAAAAA"`; exports[`Phase 3: Variable System 3.1 var set creates variable 1`] = ` @@ -258,26 +212,20 @@ exports[`Phase 7: Edge Cases 7.3 var set empty name errors 1`] = `"Usage: json-c exports[`Phase 7: Edge Cases 7.4 var set name with invalid chars errors 1`] = `"Error: Invalid variable name "invalid name!": Segment "invalid name!" contains invalid characters (only @, a-z, A-Z, 0-9, ., _, - allowed)"`; -exports[`Phase 7: Edge Cases 7.5 schema put invalid schema errors 1`] = `"Invalid schema: input does not conform to the json-cas JSON Schema meta-schema"`; - -exports[`Phase 7: Edge Cases 7.6 no subcommand shows help text 1`] = ` +exports[`Phase 7: Edge Cases 7.5 no subcommand shows help text 1`] = ` "Usage: json-cas [--store ] [--json] [args] Commands: - schema put Register schema, print type hash - schema get Print schema JSON - schema list List all schemas (name + hash) - schema validate Validate node against its schema put Store node, print hash get Print node as JSON has Print true/false - verify Verify integrity, print ok/corrupted + verify Verify integrity + schema, print ok/corrupted/invalid refs List direct cas_ref edges walk [--format tree] Recursive traversal hash Compute hash without storing (dry run) render [options] Render node as YAML with resolution decay render --pipe/-p [options] Render { type, value } from stdin - cat [--payload] Output node (--payload for payload only) + list --type List all hashes for a given type var set [--tag ...] Create/update a variable var get --schema Get a variable by name + schema var delete [--schema ] Delete variable(s) diff --git a/packages/cli-json-cas/src/cli.test.ts b/packages/cli-json-cas/src/cli.test.ts index 7e01712..57cd44c 100644 --- a/packages/cli-json-cas/src/cli.test.ts +++ b/packages/cli-json-cas/src/cli.test.ts @@ -1,13 +1,36 @@ import { afterEach, beforeEach, describe, expect, test } from "bun:test"; -import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { + mkdirSync, + mkdtempSync, + readFileSync, + rmSync, + writeFileSync, +} from "node:fs"; import { tmpdir } from "node:os"; import { join, resolve } from "node:path"; -import { bootstrap } from "@uncaged/json-cas"; -import { createFsStore } from "@uncaged/json-cas-fs"; +import type { JSONSchema } from "@uncaged/json-cas"; +import { bootstrap, putSchema } from "@uncaged/json-cas"; +import { createFsStore, openStore as openFsStore } from "@uncaged/json-cas-fs"; const pkgPath = resolve(import.meta.dir, "../package.json"); const entrypoint = resolve(import.meta.dir, "index.ts"); +/** + * Register a schema directly via the library (CLI schema put was removed). + * Returns the type hash. + */ +async function putSchemaFile( + storePath: string, + schemaFilePath: string, +): Promise { + const store = await openFsStore(storePath); + const schema = JSON.parse( + readFileSync(schemaFilePath, "utf-8"), + ) as JSONSchema; + const hash = await putSchema(store, schema); + return hash; +} + async function runCli( args: string[], storePath?: string, @@ -130,86 +153,6 @@ async function runCliAlias(...args: string[]): Promise<{ }; } -describe("@ Alias Resolution - schema get", () => { - test("ucas schema get @string should work", async () => { - await runCliAlias("init"); // Initialize store - - const { stdout, stderr, exitCode } = await runCliAlias( - "schema", - "get", - "@string", - ); - - expect(exitCode).toBe(0); - expect(stderr).toBe(""); - const schema = JSON.parse(stdout); - expect(schema).toEqual({ type: "string" }); - }); - - test("ucas schema get @number should work", async () => { - await runCliAlias("init"); - - const { stdout, exitCode } = await runCliAlias("schema", "get", "@number"); - - expect(exitCode).toBe(0); - const schema = JSON.parse(stdout); - expect(schema).toEqual({ type: "number" }); - }); - - test("ucas schema get @object should work", async () => { - await runCliAlias("init"); - - const { stdout, exitCode } = await runCliAlias("schema", "get", "@object"); - - expect(exitCode).toBe(0); - const schema = JSON.parse(stdout); - expect(schema).toEqual({ type: "object" }); - }); - - test("ucas schema get @array should work", async () => { - await runCliAlias("init"); - - const { stdout, exitCode } = await runCliAlias("schema", "get", "@array"); - - expect(exitCode).toBe(0); - const schema = JSON.parse(stdout); - expect(schema).toEqual({ type: "array" }); - }); - - test("ucas schema get @bool should work", async () => { - await runCliAlias("init"); - - const { stdout, exitCode } = await runCliAlias("schema", "get", "@bool"); - - expect(exitCode).toBe(0); - const schema = JSON.parse(stdout); - expect(schema).toEqual({ type: "boolean" }); - }); - - test("ucas schema get @schema should work", async () => { - await runCliAlias("init"); - - const { stdout, exitCode } = await runCliAlias("schema", "get", "@schema"); - - expect(exitCode).toBe(0); - const schema = JSON.parse(stdout); - expect(schema).toHaveProperty("type", "object"); - expect(schema).toHaveProperty( - "description", - "json-cas JSON Schema meta-schema", - ); - }); - - test("ucas schema get @invalid should fail gracefully", async () => { - await runCliAlias("init"); - - const { stderr, exitCode } = await runCliAlias("schema", "get", "@invalid"); - - expect(exitCode).not.toBe(0); - expect(stderr).toContain("Schema not found"); - }); -}); - describe("@ Alias Resolution - put", () => { test("ucas put @string should resolve alias", async () => { await runCliAlias("init"); @@ -357,10 +300,7 @@ describe("Issue #50: Schema Validation in put", () => { additionalProperties: false, }), ); - const { stdout: schemaHash } = await runCli( - ["schema", "put", schemaFile], - tmpStore, - ); + const schemaHash = await putSchemaFile(tmpStore, schemaFile); // Create valid payload const payloadFile = join(tmpStore, "payload.json"); @@ -403,10 +343,7 @@ describe("Issue #50: Schema Validation in put", () => { additionalProperties: false, }), ); - const { stdout: schemaHash } = await runCli( - ["schema", "put", schemaFile], - tmpStore, - ); + const schemaHash = await putSchemaFile(tmpStore, schemaFile); // Payload with only required properties const payloadFile = join(tmpStore, "payload.json"); @@ -448,10 +385,7 @@ describe("Issue #50: Schema Validation in put", () => { additionalProperties: false, }), ); - const { stdout: schemaHash } = await runCli( - ["schema", "put", schemaFile], - tmpStore, - ); + const schemaHash = await putSchemaFile(tmpStore, schemaFile); // Payload with nested structure const payloadFile = join(tmpStore, "payload.json"); @@ -513,10 +447,7 @@ describe("Issue #50: Schema Validation in put", () => { additionalProperties: false, }), ); - const { stdout: schemaHash } = await runCli( - ["schema", "put", schemaFile], - tmpStore, - ); + const schemaHash = await putSchemaFile(tmpStore, schemaFile); // Payload with name as number const payloadFile = join(tmpStore, "payload.json"); @@ -560,10 +491,7 @@ describe("Issue #50: Schema Validation in put", () => { additionalProperties: false, }), ); - const { stdout: schemaHash } = await runCli( - ["schema", "put", schemaFile], - tmpStore, - ); + const schemaHash = await putSchemaFile(tmpStore, schemaFile); // Empty payload const payloadFile = join(tmpStore, "payload.json"); @@ -596,10 +524,7 @@ describe("Issue #50: Schema Validation in put", () => { additionalProperties: false, }), ); - const { stdout: schemaHash } = await runCli( - ["schema", "put", schemaFile], - tmpStore, - ); + const schemaHash = await putSchemaFile(tmpStore, schemaFile); // Payload with extra property const payloadFile = join(tmpStore, "payload.json"); @@ -634,10 +559,7 @@ describe("Issue #50: Schema Validation in put", () => { items: { type: "string" }, }), ); - const { stdout: schemaHash } = await runCli( - ["schema", "put", schemaFile], - tmpStore, - ); + const schemaHash = await putSchemaFile(tmpStore, schemaFile); // Payload is an object const payloadFile = join(tmpStore, "payload.json"); @@ -676,10 +598,7 @@ describe("Issue #50: Schema Validation in put", () => { }, }), ); - const { stdout: schemaHash } = await runCli( - ["schema", "put", schemaFile], - tmpStore, - ); + const schemaHash = await putSchemaFile(tmpStore, schemaFile); // Payload with wrong nested type const payloadFile = join(tmpStore, "payload.json"); @@ -760,10 +679,7 @@ describe("Issue #50: Schema Validation in put", () => { additionalProperties: false, }), ); - const { stdout: schemaHash } = await runCli( - ["schema", "put", schemaFile], - tmpStore, - ); + const schemaHash = await putSchemaFile(tmpStore, schemaFile); // Invalid payload const payloadFile = join(tmpStore, "payload.json"); @@ -798,10 +714,7 @@ describe("Issue #50: Schema Validation in put", () => { }, }), ); - const { stdout: schemaHash } = await runCli( - ["schema", "put", schemaFile], - tmpStore, - ); + const schemaHash = await putSchemaFile(tmpStore, schemaFile); // Valid cas_ref const validFile = join(tmpStore, "valid.json"); @@ -843,11 +756,8 @@ describe("Issue #50: Schema Validation in put", () => { }), ); - const { exitCode } = await runCli( - ["schema", "put", schemaFile], - tmpStore, - ); - expect(exitCode).toBe(0); + const hash = await putSchemaFile(tmpStore, schemaFile); + expect(hash).toMatch(/^[0-9A-HJKMNP-TV-Z]{13}$/); } finally { rmSync(tmpStore, { recursive: true, force: true }); } @@ -869,10 +779,7 @@ describe("Issue #50: Schema Validation in put", () => { required: ["name"], }), ); - const { stdout: schemaHash } = await runCli( - ["schema", "put", schemaFile], - tmpStore, - ); + const schemaHash = await putSchemaFile(tmpStore, schemaFile); const payloadFile = join(tmpStore, "payload.json"); writeFileSync(payloadFile, JSON.stringify({ name: 123 })); @@ -903,10 +810,7 @@ describe("Issue #50: Schema Validation in put", () => { properties: { name: { type: "string" } }, }), ); - const { stdout: schemaHash } = await runCli( - ["schema", "put", schemaFile], - tmpStore, - ); + const schemaHash = await putSchemaFile(tmpStore, schemaFile); const payloadFile = join(tmpStore, "payload.json"); writeFileSync(payloadFile, JSON.stringify({ name: 123 })); @@ -941,10 +845,7 @@ describe("Suite 6: CLI Integration with Templates", () => { properties: { name: { type: "string" } }, }), ); - const { stdout: schemaHash } = await runCli( - ["schema", "put", schemaFile], - tmpStore, - ); + const schemaHash = await putSchemaFile(tmpStore, schemaFile); // Create node const nodeFile = join(tmpStore, "node.json"); @@ -1005,10 +906,7 @@ describe("Suite 6: CLI Integration with Templates", () => { }, }), ); - const { stdout: schemaHash } = await runCli( - ["schema", "put", schemaFile], - tmpStore, - ); + const schemaHash = await putSchemaFile(tmpStore, schemaFile); // Create child node const childFile = join(tmpStore, "child.json"); @@ -1077,10 +975,7 @@ describe("Suite 6: CLI Integration with Templates", () => { properties: { name: { type: "string" } }, }), ); - const { stdout: schemaHash } = await runCli( - ["schema", "put", schemaFile], - tmpStore, - ); + const schemaHash = await putSchemaFile(tmpStore, schemaFile); const nodeFile = join(tmpStore, "node.json"); writeFileSync(nodeFile, JSON.stringify({ name: "Bob" })); @@ -1144,10 +1039,7 @@ describe("Suite 6: CLI Integration with Templates", () => { properties: { name: { type: "string" } }, }), ); - const { stdout: schemaHash } = await runCli( - ["schema", "put", schemaFile], - tmpStore, - ); + const schemaHash = await putSchemaFile(tmpStore, schemaFile); const nodeFile = join(tmpStore, "node.json"); writeFileSync(nodeFile, JSON.stringify({ name: "Charlie" })); @@ -1183,10 +1075,7 @@ describe("Suite 6: CLI Integration with Templates", () => { properties: { name: { type: "string" } }, }), ); - const { stdout: schemaHash } = await runCli( - ["schema", "put", schemaFile], - tmpStore, - ); + const schemaHash = await putSchemaFile(tmpStore, schemaFile); const nodeFile = join(tmpStore, "node.json"); writeFileSync(nodeFile, JSON.stringify({ name: "Test" })); @@ -1309,140 +1198,6 @@ 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 }); - } - }); -}); - // Store validation tests removed - with auto-bootstrap in Phase 1a, // stores are automatically created and bootstrapped when opened. // Issue #55 validation is no longer applicable. diff --git a/packages/cli-json-cas/src/e2e.test.ts b/packages/cli-json-cas/src/e2e.test.ts index ea2a7f1..140ea88 100644 --- a/packages/cli-json-cas/src/e2e.test.ts +++ b/packages/cli-json-cas/src/e2e.test.ts @@ -1,5 +1,5 @@ import { afterAll, beforeAll, describe, expect, test } from "bun:test"; -import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join, resolve } from "node:path"; @@ -57,7 +57,7 @@ function stripVolatile(json: string): unknown { // ---- Phase 1: CAS Core ---- describe("Phase 1: CAS Core", () => { - test("1.1 schema put returns type hash (auto-bootstraps store)", async () => { + test("1.1 init + put with @object bootstraps store", async () => { const schemaFile = join(tmpStore, "test-schema.json"); writeFileSync( schemaFile, @@ -71,23 +71,16 @@ describe("Phase 1: CAS Core", () => { additionalProperties: false, }), ); - const { stdout, exitCode } = await runCli(["schema", "put", schemaFile]); - expect(exitCode).toBe(0); - expect(stdout).toMatch(/^[0-9A-HJKMNP-TV-Z]{13}$/); - typeHash = stdout; - }); - - test("1.3 schema get returns schema JSON (snapshot)", async () => { - const { stdout, exitCode } = await runCli(["schema", "get", typeHash]); - expect(exitCode).toBe(0); - expect(stdout).toMatchSnapshot(); - }); - - test("1.4 schema list shows registered schemas", async () => { - const { stdout, exitCode } = await runCli(["schema", "list"]); - expect(exitCode).toBe(0); - expect(stdout).toMatchSnapshot(); - expect(stdout).toContain(typeHash); + // Use putSchema via the library to register schema, since CLI schema put is removed + const { openStore: openFsStore } = await import("@uncaged/json-cas-fs"); + const { putSchema } = await import("@uncaged/json-cas"); + const store = await openFsStore(tmpStore); + const hash = await putSchema( + store, + JSON.parse(readFileSync(schemaFile, "utf-8")), + ); + typeHash = hash; + expect(typeHash).toMatch(/^[0-9A-HJKMNP-TV-Z]{13}$/); }); test("1.5 put returns node hash", async () => { @@ -142,19 +135,10 @@ describe("Phase 1: CAS Core", () => { expect(stdout).toBe(nodeHash); }); - test("1.13 cat returns full node (snapshot)", async () => { - const { stdout, exitCode } = await runCli(["cat", nodeHash]); + test("1.13 list --type returns nodes of that type", async () => { + const { stdout, exitCode } = await runCli(["list", "--type", typeHash]); expect(exitCode).toBe(0); - expect(stripVolatile(stdout)).toMatchSnapshot(); - }); - - test("1.14 cat --payload returns only payload (snapshot)", async () => { - const { stdout, exitCode } = await runCli(["cat", nodeHash, "--payload"]); - expect(exitCode).toBe(0); - expect(stdout).toMatchSnapshot(); - const parsed = JSON.parse(stdout) as Record; - expect(parsed).not.toHaveProperty("type"); - expect(parsed).toHaveProperty("name", "Alice"); + expect(stdout).toContain(nodeHash); }); }); @@ -176,10 +160,10 @@ describe("Phase 2: Schema Validation", () => { // Do NOT snapshot stderr — it embeds a machine-specific tmp path }); - test("2.2 schema validate on valid node returns valid", async () => { - const { stdout, exitCode } = await runCli(["schema", "validate", nodeHash]); + test("2.2 verify on valid node returns ok (hash + schema)", async () => { + const { stdout, exitCode } = await runCli(["verify", nodeHash]); expect(exitCode).toBe(0); - expect(stdout).toMatchSnapshot(); + expect(stdout).toBe("ok"); }); test("2.3 put against non-existent schema hash fails", async () => { @@ -504,20 +488,7 @@ describe("Phase 7: Edge Cases", () => { expect(stderr).toMatchSnapshot(); }); - test("7.5 schema put invalid schema errors", async () => { - const badSchemaFile = join(tmpStore, "bad-schema.json"); - writeFileSync(badSchemaFile, JSON.stringify({ type: "invalid" })); - const { stdout, stderr, exitCode } = await runCli([ - "schema", - "put", - badSchemaFile, - ]); - expect(exitCode).not.toBe(0); - expect(stdout).toBe(""); - expect(stderr).toMatchSnapshot(); - }); - - test("7.6 no subcommand shows help text", async () => { + test("7.5 no subcommand shows help text", async () => { const { stdout, stderr, exitCode: _exitCode } = await runCli([]); const combined = stdout + stderr; expect(combined.length).toBeGreaterThan(0); @@ -525,7 +496,7 @@ describe("Phase 7: Edge Cases", () => { expect(combined.toLowerCase()).toContain("usage"); }); - test("7.7 --store path is a file errors", async () => { + test("7.6 --store path is a file errors", async () => { const fileAsStore = join(tmpStore, "not-a-directory"); writeFileSync(fileAsStore, "test"); const proc = Bun.spawn( diff --git a/packages/cli-json-cas/src/index.ts b/packages/cli-json-cas/src/index.ts index 5f7281f..c9536bf 100644 --- a/packages/cli-json-cas/src/index.ts +++ b/packages/cli-json-cas/src/index.ts @@ -17,7 +17,6 @@ import { refs, renderAsync, renderDirect, - SchemaValidationError, TagLabelConflictError, VariableNotFoundError, validate, @@ -41,6 +40,7 @@ const VALUE_FLAGS = new Set([ "decay", "epsilon", "inline", + "type", ]); function parseArgs(argv: string[]): { flags: Flags; positional: string[] } { @@ -227,61 +227,6 @@ function parseTagsLabels(args: string[]): { // ---- Commands ---- -async function cmdSchemaPut(args: string[]): Promise { - const file = args[0]; - if (!file) die("Usage: json-cas schema put "); - const schema = readJsonFile(file) as JSONSchema; - const store = await openStore(); - 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 { - const hashOrAlias = args[0]; - if (!hashOrAlias) die("Usage: json-cas schema get "); - const hash = await resolveTypeHash(hashOrAlias); - const store = await openStore(); - const schema = getSchema(store, hash); - if (schema === null) die(`Schema not found: ${hashOrAlias}`); - out(schema); -} - -async function cmdSchemaList(): Promise { - const store = await openStore(); - const builtinSchemas = await bootstrap(store); - const metaHash = builtinSchemas["@schema"]; - if (!metaHash) throw new Error("Meta-schema not found"); - for (const hash of store.listByType(metaHash)) { - if (hash === metaHash) continue; - const node = store.get(hash); - if (node !== null) { - const schema = node.payload as JSONSchema; - const name = - (schema.title as string | undefined) ?? - (schema.description as string | undefined) ?? - "(unnamed)"; - console.log(`${hash} ${name}`); - } - } -} - -async function cmdSchemaValidate(args: string[]): Promise { - const hash = args[0]; - if (!hash) die("Usage: json-cas schema validate "); - const store = await openStore(); - const node = store.get(hash); - if (node === null) die(`Node not found: ${hash}`); - const valid = validate(store, node); - console.log(valid ? "valid" : "invalid"); -} - async function cmdPut(args: string[]): Promise { const typeHashOrAlias = args[0]; const file = args[1]; @@ -334,7 +279,12 @@ async function cmdVerify(args: string[]): Promise { const node = store.get(hash); if (node === null) die(`Node not found: ${hash}`); const ok = await verify(hash, node); - console.log(ok ? "ok" : "corrupted"); + if (!ok) { + console.log("corrupted"); + } else { + const valid = validate(store, node); + console.log(valid ? "ok" : "invalid"); + } } async function cmdRefs(args: string[]): Promise { @@ -509,19 +459,6 @@ async function cmdRender(args: string[]): Promise { } } -async function cmdCat(args: string[]): Promise { - const hash = args[0]; - if (!hash) die("Usage: json-cas cat "); - const store = await openStore(); - const node = store.get(hash); - if (node === null) die(`Node not found: ${hash}`); - if (flags.payload === true) { - out(node.payload); - } else { - out(node); - } -} - async function cmdVarSet(args: string[]): Promise { const name = args[0]; const value = args[1]; @@ -883,25 +820,32 @@ async function cmdGc(_args: string[]): Promise { } } +async function cmdList(_args: string[]): Promise { + const typeFlag = flags.type; + if (typeof typeFlag !== "string") + die("Usage: json-cas list --type "); + const typeHash = await resolveTypeHash(typeFlag); + const store = await openStore(); + for (const hash of store.listByType(typeHash)) { + console.log(hash); + } +} + function printUsage(): void { console.log(`\ Usage: json-cas [--store ] [--json] [args] Commands: - schema put Register schema, print type hash - schema get Print schema JSON - schema list List all schemas (name + hash) - schema validate Validate node against its schema put Store node, print hash get Print node as JSON has Print true/false - verify Verify integrity, print ok/corrupted + verify Verify integrity + schema, print ok/corrupted/invalid refs List direct cas_ref edges walk [--format tree] Recursive traversal hash Compute hash without storing (dry run) render [options] Render node as YAML with resolution decay render --pipe/-p [options] Render { type, value } from stdin - cat [--payload] Output node (--payload for payload only) + list --type List all hashes for a given type var set [--tag ...] Create/update a variable var get --schema Get a variable by name + schema var delete [--schema ] Delete variable(s) @@ -936,27 +880,6 @@ if (!cmd) { } switch (cmd) { - case "schema": { - const [sub, ...subRest] = rest; - switch (sub) { - case "put": - await cmdSchemaPut(subRest); - break; - case "get": - await cmdSchemaGet(subRest); - break; - case "list": - await cmdSchemaList(); - break; - case "validate": - await cmdSchemaValidate(subRest); - break; - default: - die(`Unknown schema subcommand: ${sub ?? "(none)"}`); - } - break; - } - case "put": await cmdPut(rest); break; @@ -989,8 +912,8 @@ switch (cmd) { await cmdRender(rest); break; - case "cat": - await cmdCat(rest); + case "list": + await cmdList(rest); break; case "var": {