Merge pull request 'fix: uwf cas — JSON output + meta-schema in schema list' (#326) from fix/319-cas-json-output into main

This commit is contained in:
2026-05-18 13:25:16 +00:00
2 changed files with 71 additions and 89 deletions
+31 -20
View File
@@ -184,10 +184,11 @@ cas
.command("get")
.description("Read a CAS node as JSON")
.argument("<hash>", "CAS hash (13 char)")
.option("--json", "Compact JSON output")
.action((hash: string, opts: { json?: boolean }) => {
.action((hash: string) => {
const storageRoot = resolveStorageRoot();
runAction(() => cmdCasGet(storageRoot, hash, opts));
runAction(async () => {
writeJson(await cmdCasGet(storageRoot, hash));
});
});
cas
@@ -195,10 +196,11 @@ cas
.description("Output a CAS node (--payload for payload only)")
.argument("<hash>", "CAS hash (13 char)")
.option("--payload", "Output only the payload")
.option("--json", "Compact JSON output")
.action((hash: string, opts: { payload?: boolean; json?: boolean }) => {
.action((hash: string, opts: { payload?: boolean }) => {
const storageRoot = resolveStorageRoot();
runAction(() => cmdCasCat(storageRoot, hash, opts));
runAction(async () => {
writeJson(await cmdCasCat(storageRoot, hash, opts));
});
});
cas
@@ -206,19 +208,22 @@ cas
.description("Store a node, print its hash")
.argument("<type-hash>", "Type (schema) hash")
.argument("<data>", "JSON file path or inline JSON string")
.option("--json", "Compact JSON output")
.action((typeHash: string, data: string, opts: { json?: boolean }) => {
.action((typeHash: string, data: string) => {
const storageRoot = resolveStorageRoot();
runAction(() => cmdCasPut(storageRoot, typeHash, data, opts));
runAction(async () => {
writeJson(await cmdCasPut(storageRoot, typeHash, data));
});
});
cas
.command("has")
.description("Check if a hash exists (prints true/false)")
.description("Check if a hash exists")
.argument("<hash>", "CAS hash (13 char)")
.action((hash: string) => {
const storageRoot = resolveStorageRoot();
runAction(() => cmdCasHas(storageRoot, hash));
runAction(async () => {
writeJson(await cmdCasHas(storageRoot, hash));
});
});
cas
@@ -227,37 +232,43 @@ cas
.argument("<hash>", "CAS hash (13 char)")
.action((hash: string) => {
const storageRoot = resolveStorageRoot();
runAction(() => cmdCasRefs(storageRoot, hash));
runAction(async () => {
writeJson(await cmdCasRefs(storageRoot, hash));
});
});
cas
.command("walk")
.description("Recursive traversal from a node")
.argument("<hash>", "CAS hash (13 char)")
.option("--format <fmt>", "Output format: flat (default) or tree")
.action((hash: string, opts: { format?: string }) => {
.action((hash: string) => {
const storageRoot = resolveStorageRoot();
runAction(() => cmdCasWalk(storageRoot, hash, opts));
runAction(async () => {
writeJson(await cmdCasWalk(storageRoot, hash));
});
});
const casSchema = cas.command("schema").description("CAS schema operations");
casSchema
.command("list")
.description("List all registered schemas (hash + name)")
.description("List all registered schemas")
.action(() => {
const storageRoot = resolveStorageRoot();
runAction(() => cmdCasSchemaList(storageRoot));
runAction(async () => {
writeJson(await cmdCasSchemaList(storageRoot));
});
});
casSchema
.command("get")
.description("Show a schema by its type hash")
.argument("<hash>", "Schema type hash")
.option("--json", "Compact JSON output")
.action((hash: string, opts: { json?: boolean }) => {
.action((hash: string) => {
const storageRoot = resolveStorageRoot();
runAction(() => cmdCasSchemaGet(storageRoot, hash, opts));
runAction(async () => {
writeJson(await cmdCasSchemaGet(storageRoot, hash));
});
});
program.parseAsync(process.argv).catch((e: unknown) => {
+40 -69
View File
@@ -11,12 +11,7 @@ function openStore(storageRoot: string): Store {
return createFsStore(join(storageRoot, "cas"));
}
function out(data: unknown, compact = false): void {
console.log(compact ? JSON.stringify(data) : JSON.stringify(data, null, 2));
}
function readJsonArg(fileOrInline: string): unknown {
// Try as inline JSON first, then as file path
try {
return JSON.parse(fileOrInline);
} catch {
@@ -28,138 +23,114 @@ function readJsonArg(fileOrInline: string): unknown {
}
}
// ---- Commands ----
// ---- Commands (all return JSON-serializable data) ----
export async function cmdCasGet(
storageRoot: string,
hash: string,
opts: { json?: boolean },
): Promise<void> {
): Promise<unknown> {
const store = openStore(storageRoot);
const node = store.get(hash);
if (node === null) {
throw new Error(`Node not found: ${hash}`);
}
out(node, opts.json);
return node;
}
export async function cmdCasCat(
storageRoot: string,
hash: string,
opts: { payload?: boolean; json?: boolean },
): Promise<void> {
opts: { payload?: boolean },
): Promise<unknown> {
const store = openStore(storageRoot);
const node = store.get(hash);
if (node === null) {
throw new Error(`Node not found: ${hash}`);
}
out(opts.payload ? node.payload : node, opts.json);
return opts.payload ? node.payload : node;
}
export async function cmdCasPut(
storageRoot: string,
typeHash: string,
data: string,
opts: { json?: boolean },
): Promise<void> {
): Promise<{ hash: string }> {
const store = openStore(storageRoot);
const payload = readJsonArg(data);
const hash = store.put(typeHash, payload);
console.log(hash);
const hash = await store.put(typeHash, payload);
return { hash };
}
export async function cmdCasHas(
storageRoot: string,
hash: string,
): Promise<void> {
): Promise<{ exists: boolean }> {
const store = openStore(storageRoot);
console.log(String(store.has(hash)));
return { exists: store.has(hash) };
}
export async function cmdCasList(storageRoot: string): Promise<void> {
const store = openStore(storageRoot);
for (const hash of store.list()) {
console.log(hash);
}
}
export async function cmdCasRefs(storageRoot: string, hash: string): Promise<void> {
export async function cmdCasRefs(
storageRoot: string,
hash: string,
): Promise<{ refs: string[] }> {
const store = openStore(storageRoot);
const node = store.get(hash);
if (node === null) {
throw new Error(`Node not found: ${hash}`);
}
const refHashes = refs(store, node);
for (const r of refHashes) {
console.log(r);
}
return { refs: refs(store, node) };
}
export async function cmdCasWalk(
storageRoot: string,
hash: string,
opts: { format?: string },
): Promise<void> {
): Promise<{ hashes: string[] }> {
const store = openStore(storageRoot);
if (opts.format === "tree") {
const childMap = new Map<Hash, Hash[]>();
walk(store, hash, (h, node) => {
childMap.set(h, refs(store, node));
});
const printed = new Set<Hash>();
function printNode(h: Hash, prefix: string, isLast: boolean): void {
const connector = prefix === "" ? "" : isLast ? "└── " : "├── ";
if (printed.has(h)) {
console.log(`${prefix}${connector}${h} (seen)`);
return;
}
printed.add(h);
console.log(`${prefix}${connector}${h}`);
const kids = childMap.get(h) ?? [];
const childPrefix = prefix === "" ? "" : prefix + (isLast ? " " : "│ ");
for (let i = 0; i < kids.length; i++) {
printNode(kids[i] as Hash, childPrefix, i === kids.length - 1);
}
}
printNode(hash, "", true);
} else {
walk(store, hash, (h) => {
console.log(h);
});
}
const result: string[] = [];
walk(store, hash, (h) => {
result.push(h);
});
return { hashes: result };
}
export async function cmdCasSchemaList(storageRoot: string): Promise<void> {
export type SchemaListEntry = {
hash: string;
title: string;
};
export async function cmdCasSchemaList(
storageRoot: string,
): Promise<SchemaListEntry[]> {
const store = openStore(storageRoot);
const metaHash = await bootstrap(store);
const entries: SchemaListEntry[] = [];
// Include meta-schema itself
entries.push({ hash: metaHash, title: "(meta-schema)" });
for (const hash of store.list()) {
if (hash === metaHash) continue;
const node = store.get(hash);
if (node !== null && node.type === metaHash) {
const schema = node.payload as JSONSchema;
const name =
const title =
(schema.title as string | undefined) ??
(schema.description as string | undefined) ??
"(unnamed)";
console.log(`${hash} ${name}`);
entries.push({ hash, title });
}
}
return entries;
}
export async function cmdCasSchemaGet(
storageRoot: string,
hash: string,
opts: { json?: boolean },
): Promise<void> {
): Promise<unknown> {
const store = openStore(storageRoot);
const schema = getSchema(store, hash);
if (schema === null) {
throw new Error(`Schema not found: ${hash}`);
}
out(schema, opts.json);
return schema;
}