← Files DataARCHIVED FILE

skills/visualize-data/scripts/render-inline-sources.mjs

3.37 KB · Sep 30, 2026 · 23:19 UTC

↓ Download file

#!/usr/bin/env node
import { readFile, realpath, stat } from "node:fs/promises";
import { realpathSync } from "node:fs";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";

import { DATA_PLUGIN_ROOT, prepareInlineRuntime } from "./inline-chart-build.mjs";
import { MAX_INLINE_INPUT_BYTES } from "./inline-chart-input.mjs";
import { normalizeInlineSourcesInput } from "./inline-sources-input.mjs";
import { assembleInlineFragment, inlineOutputPath, loadInlineTheme, writeInlineOutput } from "./render-inline-chart.mjs";

export async function renderInlineSources({ input, output, pluginRoot = DATA_PLUGIN_ROOT, onProgress = () => {} }) {
  pluginRoot = await realpath(resolve(pluginRoot));
  const normalized = normalizeInlineSourcesInput(input);
  const requestedOutput = await inlineOutputPath(output, pluginRoot);
  const runtime = await prepareInlineRuntime({ pluginRoot, artifactName: "receipt", onProgress });
  const theme = await loadInlineTheme(normalized.theme, { pluginRoot });
  const template = await readFile(join(pluginRoot, "skills/visualize-data/assets/inline-sources-fragment.html"), "utf8");
  const { fragment, bytes, rootId } = assembleInlineFragment({ normalized, theme, runtimeCode: runtime.code, template });
  const path = await writeInlineOutput(requestedOutput, fragment, { pluginRoot, legacyCacheDir: runtime.legacyCacheDir });
  return {
    path, bytes, rootId, itemCount: normalized.items.length,
    rowCount: normalized.items.reduce((total, item) => total + item.queries.reduce((sum, query) => sum + (query.rows?.length ?? 0), 0), 0),
    prebuilt: true, runtimeKey: runtime.metadata.key, runtimeBytes: runtime.metadata.bytes,
  };
}

export async function main(argv = process.argv.slice(2)) {
  const args = {};
  for (let index = 0; index < argv.length; index++) {
    const flag = argv[index];
    if (["--help", "--example", "--prepare"].includes(flag)) args[flag.slice(2)] = true;
    else if (["--input", "--output"].includes(flag) && argv[index + 1] && !argv[index + 1].startsWith("--"))
      args[flag.slice(2)] = argv[++index];
    else throw new Error(`Unknown or incomplete option: ${flag}`);
  }
  if (args.help) {
    process.stdout.write("Render one Sources receipt for an evidence-backed inline answer.\n"
      + "node render-inline-sources.mjs --input sources.json --output /absolute/path/answer-sources.html\n"
      + "node render-inline-sources.mjs --example | --prepare\n");
    return;
  }
  if (args.example) {
    process.stdout.write(await readFile(join(dirname(fileURLToPath(import.meta.url)), "../assets/inline-sources-example.json"), "utf8"));
    return;
  }
  if (args.prepare) {
    const runtime = await prepareInlineRuntime({ artifactName: "receipt" });
    process.stdout.write(`${JSON.stringify({ prebuilt: true, bytes: runtime.metadata.bytes })}\n`);
    return;
  }
  if (!args.input || !args.output) throw new Error("Provide --input and --output.");
  if ((await stat(args.input)).size > MAX_INLINE_INPUT_BYTES) throw new Error("Sources input exceeds 2 MB.");
  const result = await renderInlineSources({ input: JSON.parse(await readFile(args.input, "utf8")), output: args.output });
  process.stdout.write(`${JSON.stringify(result)}\n`);
}

if (process.argv[1] && realpathSync(process.argv[1]) === fileURLToPath(import.meta.url)) {
  main().catch((error) => { process.stderr.write(`${error.message}\n`); process.exitCode = 1; });
}

SHA-256: dc7867997852693d51afacf54e5cb405cae687257b4de280ace9dc90bdd61eb6