const fs = require("node:fs"); const path = require("node:path"); const validator = require("gltf-validator"); function findGlbs(directory, root = directory) { return fs.readdirSync(directory, { withFileTypes: true }).flatMap((entry) => { const entryPath = path.join(directory, entry.name); if (entry.isDirectory()) return findGlbs(entryPath, root); if (!entry.isFile() || path.extname(entry.name).toLowerCase() !== ".glb") return []; return [{ file: path.relative(root, entryPath).replaceAll("\\", "/"), path: entryPath, }]; }); } async function main() { const directory = process.argv[2]; if (!directory) throw new Error("Usage: node validate-package-glbs.cjs "); const files = findGlbs(directory).sort((left, right) => left.file.localeCompare(right.file)); if (!files.length) throw new Error(`No GLB files found below ${path.resolve(directory)}.`); const rows = []; for (const file of files) { const report = await validator.validateBytes( new Uint8Array(fs.readFileSync(file.path)), { uri: file.file, maxIssues: 1_000 }, ); const validatorMessages = report.issues.messages; const hasBasisu = validatorMessages.some((issue) => ( issue.code === "UNSUPPORTED_EXTENSION" && issue.message.includes("'KHR_texture_basisu'") )); const actionable = validatorMessages.filter((issue) => { if (issue.code === "UNSUPPORTED_EXTENSION" && /'(?:EXT_mesh_gpu_instancing|KHR_texture_basisu)'/.test(issue.message)) return false; if (issue.code === "VALUE_NOT_IN_LIST" && hasBasisu && /^\/images\/\d+\/mimeType$/.test(issue.pointer ?? "") && issue.message.includes("'image/ktx2'")) return false; return true; }); rows.push({ file: file.file, errors: actionable.filter((issue) => issue.severity === 0).length, warnings: actionable.filter((issue) => issue.severity === 1).length, ignoredKnownToolingIssues: validatorMessages.length - actionable.length, }); } const result = { files: rows.length, errors: rows.reduce((sum, row) => sum + row.errors, 0), warnings: rows.reduce((sum, row) => sum + row.warnings, 0), ignoredKnownToolingIssues: rows.reduce((sum, row) => sum + row.ignoredKnownToolingIssues, 0), nonGreen: rows.filter((row) => row.errors || row.warnings), }; console.log(JSON.stringify(result, null, 2)); if (result.errors || result.warnings) process.exitCode = 1; } main().catch((error) => { console.error(error); process.exitCode = 1; });