blob: f5b5eda0b0cec65951b10db9a8efac74aeb43e26 [file]
#!/usr/bin/env node
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const crypto = require("crypto");
const fs = require("fs");
const path = require("path");
function parseArguments(args) {
let root = process.cwd();
for (let index = 0; index < args.length; index++) {
if (args[index] !== "--root" || index + 1 >= args.length) {
throw new Error(`Unknown or incomplete argument: ${args[index]}`);
}
root = args[++index];
}
return path.resolve(root);
}
function readJson(file) {
return JSON.parse(fs.readFileSync(file, "utf8"));
}
function listFiles(root, directory = root) {
return fs.readdirSync(directory, { withFileTypes: true }).flatMap(entry => {
const absolutePath = path.join(directory, entry.name);
return entry.isDirectory() ? listFiles(root, absolutePath) :
[path.relative(root, absolutePath).split(path.sep).join("/")];
});
}
function resolvePackagedPath(root, relativePath) {
const normalizedPath = path.posix.normalize(relativePath);
if (normalizedPath !== relativePath || normalizedPath.startsWith("../") || path.isAbsolute(relativePath)) {
throw new Error(`Invalid packaged path: ${relativePath}`);
}
return path.join(root, ...relativePath.split("/"));
}
function main() {
const root = parseArguments(process.argv.slice(2));
const index = readJson(path.join(root, "index.json"));
const mxml = readJson(path.join(root, "mxml.json"));
if (index.schemaVersion !== "1.0" || mxml.schemaVersion !== index.schemaVersion) {
throw new Error("Codegraph index schema versions are missing or inconsistent");
}
if (!index.version || mxml.version !== index.version || !Array.isArray(index.modules)) {
throw new Error("Codegraph index versions are missing or inconsistent");
}
const targets = mxml.targets && Object.keys(mxml.targets);
if (!targets || targets.length === 0 || targets.some(target =>
(target !== "js" && target !== "swf") || !Array.isArray(mxml.targets[target]))) {
throw new Error("MXML index must contain at least one supported target mapping");
}
const expectedFiles = new Set(["index.json", "mxml.json"]);
const moduleNames = new Set();
index.modules.forEach(module => {
if (!module.name || moduleNames.has(module.name)) {
throw new Error(`Missing or duplicate module name: ${module.name}`);
}
moduleNames.add(module.name);
targets.forEach(target => {
const targetIndex = module.targets && module.targets[target];
if (!targetIndex || !targetIndex.path || !targetIndex.sha256) {
throw new Error(`${module.name} is missing its ${target} graph index`);
}
const graphFile = resolvePackagedPath(root, targetIndex.path);
const contents = fs.readFileSync(graphFile);
const digest = crypto.createHash("sha256").update(contents).digest("hex");
if (digest !== targetIndex.sha256) {
throw new Error(`${targetIndex.path} does not match its SHA-256 index`);
}
const graph = JSON.parse(contents.toString("utf8"));
if (graph.schemaVersion !== index.schemaVersion || graph.module !== module.name || graph.target !== target) {
throw new Error(`${targetIndex.path} has inconsistent graph identity`);
}
if (!Array.isArray(graph.symbols) || graph.symbols.length !== targetIndex.symbolCount) {
throw new Error(`${targetIndex.path} has an inconsistent symbol count`);
}
const classCount = graph.symbols.filter(symbol => symbol.kind === "class").length;
if (classCount !== targetIndex.classCount) {
throw new Error(`${targetIndex.path} has an inconsistent class count`);
}
expectedFiles.add(targetIndex.path);
});
});
const actualFiles = listFiles(root);
const unexpectedFiles = actualFiles.filter(file => !expectedFiles.has(file));
const missingFiles = Array.from(expectedFiles).filter(file => !actualFiles.includes(file));
if (unexpectedFiles.length || missingFiles.length) {
throw new Error(`Package membership mismatch; missing: ${missingFiles.join(", ") || "none"}; ` +
`unexpected: ${unexpectedFiles.join(", ") || "none"}`);
}
console.log(`Verified packaged codegraphs for ${index.modules.length} module(s) and ${actualFiles.length} file(s).`);
}
try {
main();
} catch (error) {
console.error(error.message);
process.exitCode = 1;
}