fix(cli): retain nested facts in Markdown tables

This commit is contained in:
morluto committed 2026-10-24 03:16:29 +08:00
1 parent a4e9b61e9f
commit 3f5d879414
3 files changed
+63 -5

No files matched your search

+3 -1
View File
@@ -796,7 +796,9 @@ operation status.
`--format md` escapes backslashes and pipes in table headers and cells, and
renders cell line breaks as `<br>`. These are presentation changes; retained
Evidence and JSON output keep the original values. Table widths are measured
Evidence and JSON output keep the original values. Nested objects and arrays
use JSON inside their owning Markdown cells, retaining the field names and
values instead of replacing them with an object label. Table widths are measured
iteratively, so an admitted row page is not limited by the JavaScript function
argument count.
+12 -4
View File
@@ -77,10 +77,18 @@ const renderTable = (
return lines.join("\n");
};
/** Keep nested JSON facts inside their owning table cell. */
const markdownCell = (value: unknown): string =>
value !== null && typeof value === "object"
? JSON.stringify(value)
: value === undefined
? ""
: String(value);
const formatKeyValueTable = (value: Record<string, unknown>): string =>
renderTable(
["Key", "Value"],
Object.entries(value).map(([key, child]) => [key, String(child)]),
Object.entries(value).map(([key, child]) => [key, markdownCell(child)]),
);
const formatObjectTable = (
@@ -96,7 +104,7 @@ const formatObjectTable = (
}
return renderTable(
headers,
items.map((item) => headers.map((key) => String(item[key] ?? ""))),
items.map((item) => headers.map((key) => markdownCell(item[key]))),
);
};
@@ -114,7 +122,7 @@ const formatMarkdown = (
const table = formatObjectTable(value);
return path.length === 0 ? table : `## ${path.join(".")}\n\n${table}`;
}
return formatMarkdown(String(value), path);
return formatMarkdown(markdownCell(value), path);
}
const object = value as Record<string, unknown>;
@@ -144,7 +152,7 @@ const formatMarkdown = (
? `${heading}${formatKeyValueTable(nested)}`
: formatMarkdown(nested, childPath);
}
return `${heading}${String(child)}`;
return `${heading}${markdownCell(child)}`;
})
.join("\n\n");
};
@@ -0,0 +1,48 @@
import { join } from "node:path";
import { expect, it } from "vitest";
import { renderCliDocument } from "../../../src/cliOutput.js";
import { writeOrderedZip } from "../../fixtures/artifactEntryOrder.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import { workspaceCliTest } from "../../support/cli/workspaceCliFixture.js";
it("retains nested facts in standalone Markdown table cells and mixed arrays", () => {
const value = [
{
details: { path: "alpha.txt" },
entries: [{ path: "sub/beta.txt" }],
mixed: ["selected", { nested: "caller|value" }],
},
];
const markdown = renderCliDocument(value, "md", false);
expect(markdown).toContain('{"path":"alpha.txt"}');
expect(markdown).toContain('[{"path":"sub/beta.txt"}]');
expect(markdown).toContain("caller\\|value");
expect(markdown).not.toContain("[object Object]");
expect(
renderCliDocument({ mixed: [1, { path: "retained" }] }, "md", false),
).toContain('{"path":"retained"}');
expect(JSON.parse(renderCliDocument(value, "json", false))).toEqual(value);
});
workspaceCliTest(
"preserves archive inventory facts through public Markdown and JSON output",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-markdown-archive-facts-");
const archive = join(root, "fixture.zip");
await writeOrderedZip(archive, ["alpha.txt", "sub/beta.txt"]);
const markdown = await cli.run({
arguments: ["inspect-artifact", archive, "--format", "md"],
});
const json = await cli.run({
arguments: ["inspect-artifact", archive, "--json"],
});
expect(markdown.exitCode).toBe(0);
expect(json.exitCode).toBe(0);
for (const entry of ["alpha.txt", "sub/beta.txt"]) {
expect(markdown.stdout).toContain(entry);
expect(json.stdout).toContain(entry);
}
expect(markdown.stdout).not.toContain("[object Object]");
},
);