feat: export portable native road packages

This commit is contained in:
2026-08-26 11:59:48 +08:00
parent 3b4befb025
commit 81e02c670d
9 changed files with 118 additions and 7 deletions

View File

@@ -0,0 +1,56 @@
"use strict";
const fs = require("fs");
const path = require("path");
const { zipSync } = require("fflate");
const { LAYER_REGISTRY, validatePublishedLayers } = require("../compile/layer-manifest");
const ROOT_FILES = [
"manifest.json",
"compiled.json",
"diagnostics.json",
"comparison.json",
"traffic-signal-assemblies.json",
"traffic-signals.json",
];
const GENERATOR = Object.freeze({ name: "road-compiler", version: "0.3.0" });
function packageEntries(directory) {
const manifestPath = path.join(directory, "manifest.json");
if (!fs.existsSync(manifestPath)) throw new Error(`Native road package manifest is missing: ${manifestPath}`);
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
if (manifest.contract !== "native-road-package/v1.1") throw new Error("Native road package requires native-road-package/v1.1.");
if (!manifest.areaId || typeof manifest.areaId !== "string") throw new Error("Native road package manifest areaId is required.");
validatePublishedLayers(directory, manifest);
const files = [];
for (const relative of ROOT_FILES) files.push(relative);
for (const layer of LAYER_REGISTRY) files.push(`layers/${layer.source}.geojson`);
for (const relative of files) {
const file = path.join(directory, relative);
if (!fs.existsSync(file) || !fs.statSync(file).isFile()) throw new Error(`Native road package file is missing: ${relative}`);
}
return { manifest, files: [...new Set(files)].sort() };
}
function exportNativeRoadPackage(directory, destination = null) {
const { manifest, files } = packageEntries(directory);
const entries = Object.fromEntries(files.map((relative) => [relative, fs.readFileSync(path.join(directory, relative))]));
// The model/movements remain needed by host preview generation, but absolute
// compiler-workspace paths are not part of the exported package contract.
const compiled = JSON.parse(entries["compiled.json"].toString("utf8"));
delete compiled.source;
entries["compiled.json"] = Buffer.from(`${JSON.stringify(compiled, null, 2)}\n`);
entries["manifest.json"] = Buffer.from(`${JSON.stringify({ ...manifest, generator: GENERATOR }, null, 2)}\n`);
// ZIP timestamps start at 1980; a fixed value keeps repeated exports byte-stable.
const bytes = zipSync(entries, { level: 6, mtime: new Date("1980-01-01T00:00:00Z") });
if (destination) {
fs.mkdirSync(path.dirname(destination), { recursive: true });
const temporary = `${destination}.tmp-${process.pid}`;
fs.writeFileSync(temporary, bytes);
fs.renameSync(temporary, destination);
}
return { bytes, manifest, files };
}
module.exports = { ROOT_FILES, GENERATOR, packageEntries, exportNativeRoadPackage };

View File

@@ -10,6 +10,7 @@ module.exports = {
nativeTrafficSignals: require("./native-traffic-signals"),
nativeRoad: require("./compile/native-road"),
layerManifest: require("./compile/layer-manifest"),
nativeRoadPackage: require("./export/native-road-package"),
compiler: require("./compile/compiler"),
check: require("./check"),
};