95 lines
3.7 KiB
JavaScript
95 lines
3.7 KiB
JavaScript
#!/usr/bin/env node
|
|
"use strict";
|
|
|
|
const fs = require("node:fs");
|
|
const path = require("node:path");
|
|
const packageMetadata = require("../../package.json");
|
|
const { canonicalJson, sha256 } = require("../lib/aas-v1/canonical-json");
|
|
const versions = require("../lib/aas-v1/versions");
|
|
|
|
const ROOT = path.resolve(__dirname, "../..");
|
|
const OUTPUT_ROOT = path.join(ROOT, "data", "aas-v1");
|
|
const CONTENT_RELATIVE = "data/aas-v1/skill-content.v1.ndjson";
|
|
const INDEX_RELATIVE = "data/aas-v1/skill-content-index.v1.json";
|
|
const MANIFEST_RELATIVE = "data/aas-v1/catalog-manifest.v1.json";
|
|
const STATIC_ASSETS = [
|
|
"data/catalog.json",
|
|
"tools/lib/aas-v1/ontology.v1.json",
|
|
];
|
|
|
|
function compareStrings(left, right) {
|
|
return left < right ? -1 : (left > right ? 1 : 0);
|
|
}
|
|
|
|
function record(relativePath, bytes) {
|
|
return { path: relativePath, size: bytes.length, sha256: sha256(bytes) };
|
|
}
|
|
|
|
function buildArtifacts() {
|
|
const index = JSON.parse(fs.readFileSync(path.join(ROOT, "skills_index.json"), "utf8"));
|
|
const entries = [...index].sort((left, right) => compareStrings(left.id, right.id));
|
|
const seen = new Set();
|
|
const lines = [];
|
|
const contentIndex = { schemaVersion: 1, entries: {} };
|
|
let offset = 0;
|
|
for (const entry of entries) {
|
|
if (seen.has(entry.id)) throw new Error(`Duplicate canonical skill ID: ${entry.id}`);
|
|
seen.add(entry.id);
|
|
const relativeSkillPath = `${entry.path}/SKILL.md`;
|
|
const skillPath = path.resolve(ROOT, ...relativeSkillPath.split("/"));
|
|
if (skillPath !== ROOT && !skillPath.startsWith(`${ROOT}${path.sep}`)) throw new Error(`Skill path escaped root: ${entry.id}`);
|
|
const text = fs.readFileSync(skillPath, "utf8");
|
|
const line = Buffer.from(`${canonicalJson({ id: entry.id, text, sha256: sha256(Buffer.from(text)) })}\n`);
|
|
lines.push(line);
|
|
contentIndex.entries[entry.id] = {
|
|
offset,
|
|
length: line.length,
|
|
sha256: sha256(line),
|
|
};
|
|
offset += line.length;
|
|
}
|
|
const contentBytes = Buffer.concat(lines);
|
|
const indexBytes = Buffer.from(`${canonicalJson(contentIndex)}\n`);
|
|
const generated = new Map([
|
|
[CONTENT_RELATIVE, contentBytes],
|
|
[INDEX_RELATIVE, indexBytes],
|
|
]);
|
|
const assets = [
|
|
...STATIC_ASSETS.map((relativePath) => record(relativePath, fs.readFileSync(path.join(ROOT, ...relativePath.split("/"))))),
|
|
...[...generated].map(([relativePath, bytes]) => record(relativePath, bytes)),
|
|
].sort((left, right) => compareStrings(left.path, right.path));
|
|
const catalogDigest = sha256(canonicalJson({ digestVersion: 1, assets }));
|
|
const manifest = {
|
|
schemaVersion: 1,
|
|
package: packageMetadata.name,
|
|
packageVersion: packageMetadata.version,
|
|
catalogSchemaVersion: versions.catalogSchemaVersion,
|
|
digestVersion: 1,
|
|
catalogDigest,
|
|
assets,
|
|
skillCount: entries.length,
|
|
};
|
|
generated.set(MANIFEST_RELATIVE, Buffer.from(`${canonicalJson(manifest)}\n`));
|
|
return { catalogDigest, generated, manifest };
|
|
}
|
|
|
|
function main() {
|
|
const check = process.argv.includes("--check");
|
|
const { generated, manifest } = buildArtifacts();
|
|
const stale = [];
|
|
for (const [relativePath, bytes] of generated) {
|
|
const outputPath = path.join(ROOT, ...relativePath.split("/"));
|
|
if (!fs.existsSync(outputPath) || !fs.readFileSync(outputPath).equals(bytes)) stale.push(relativePath);
|
|
if (!check) {
|
|
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
fs.writeFileSync(outputPath, bytes, { mode: 0o644 });
|
|
}
|
|
}
|
|
if (check && stale.length) throw new Error(`Offline catalog assets are stale: ${stale.join(", ")}`);
|
|
process.stdout.write(`${check ? "Validated" : "Wrote"} ${manifest.skillCount} skill records; catalog ${manifest.catalogDigest}.\n`);
|
|
}
|
|
|
|
if (require.main === module) main();
|
|
|
|
module.exports = { buildArtifacts };
|