UNPKG

next

Version:

The React Framework

291 lines (290 loc) • 12 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "dumpAnalyzeGraph", { enumerable: true, get: function() { return dumpAnalyzeGraph; } }); const _nodefs = require("node:fs"); const _nodepath = require("node:path"); const UINT32_LIMIT = 0x100000000; function integer(value, limit) { return Number.isSafeInteger(value) && value >= 0 && value < limit; } function requireIndex(value, limit, label) { if (!integer(value, limit)) throw new Error(`Invalid ${label} index: ${value}`); } function readData(file) { const data = (0, _nodefs.readFileSync)(file); if (data.length < 4) throw new Error(`Truncated analyzer header: ${file}`); const length = data.readUInt32BE(0); if (length === 0 || length > data.length - 4) { throw new Error(`Invalid analyzer header length: ${file}`); } const header = JSON.parse(data.toString('utf8', 4, 4 + length)); if (!header || typeof header !== 'object') { throw new Error(`Invalid analyzer header: ${file}`); } if (header.schema_version !== 1) { throw new Error(`Unsupported analyzer schema ${String(header.schema_version)}: ${file}`); } return { header, binary: data.subarray(4 + length) }; } /** Read the consumed dependency rows, checking their binary ranges and targets. */ function validateEdges(binary, ref, rows, targets, label) { if (!ref || !integer(ref.offset, binary.length + 1) || !integer(ref.length, binary.length + 1) || ref.length < 4 || ref.offset + ref.length > binary.length) { throw new Error(`Invalid ${label} section`); } const count = binary.readUInt32BE(ref.offset); if (count !== rows || ref.length < 4 + 4 * count) { throw new Error(`Invalid ${label} row count`); } const offsets = []; let previous = 0; for(let i = 0; i < count; i++){ const offset = binary.readUInt32BE(ref.offset + 4 + i * 4); if (offset < previous || 4 + 4 * count + 4 * offset > ref.length) { throw new Error(`Invalid ${label} offsets`); } offsets.push(offset); previous = offset; } if (4 + 4 * count + 4 * previous !== ref.length) { throw new Error(`Invalid ${label} section length`); } const dataStart = ref.offset + 4 + 4 * count; for(let i = 0; i < previous; i++){ requireIndex(binary.readUInt32BE(dataStart + i * 4), targets, label); } return { row (index) { const start = index === 0 ? 0 : offsets[index - 1]; const result = []; for(let i = start; i < offsets[index]; i++){ result.push(binary.readUInt32BE(dataStart + i * 4)); } return result; } }; } function sourcePaths(sources) { const paths = Array(sources.length); const visiting = new Set(); function path(index) { requireIndex(index, sources.length, 'source'); if (paths[index] !== undefined) return paths[index]; if (visiting.has(index)) throw new Error('Cyclic analyzer source tree'); visiting.add(index); const source = sources[index]; if (typeof source.path !== 'string') throw new Error('Invalid source path'); const parent = source.parent_source_index; paths[index] = parent === null ? source.path : path(parent) + source.path; visiting.delete(index); return paths[index]; } return sources.map((_, i)=>path(i)); } function routeFile(directory, route) { if (!route.startsWith('/') || route.includes('\\') || route.includes('\0') || route.split('/').some((segment)=>segment === '..' || segment === '.')) { throw new Error(`Invalid analyzer route: ${route}`); } const file = (0, _nodepath.resolve)(directory, route.slice(1), 'analyze.data'); if (!file.startsWith((0, _nodepath.resolve)(directory) + _nodepath.sep)) { throw new Error(`Analyzer route escapes snapshot: ${route}`); } return file; } function parseModules(file) { const { header, binary } = readData(file); if (!Array.isArray(header.modules)) throw new Error('Missing analyzer modules'); const modules = header.modules; const ids = new Set(); for (const module of modules){ if (typeof module.ident !== 'string' || typeof module.path !== 'string' || ids.has(module.ident)) { throw new Error('Invalid or duplicated analyzer module identity'); } ids.add(module.ident); } const edges = {}; for (const [kind, field] of [ [ 'sync', 'module_dependencies' ], [ 'async', 'async_module_dependencies' ], [ 'traced', 'traced_module_dependencies' ] ]){ edges[kind] = validateEdges(binary, header[field], modules.length, modules.length, field); } if (typeof header.module_index_hash !== 'string' || !header.module_index_hash) throw new Error('Missing analyzer module-index fingerprint'); return { modules, edges, hash: header.module_index_hash }; } function parseRoutes(file, modules) { const { header, binary } = readData(file); if (!Array.isArray(header.sources) || !Array.isArray(header.output_files) || !Array.isArray(header.chunk_parts)) { throw new Error('Invalid analyzer route header'); } const paths = sourcePaths(header.sources); const outputs = header.output_files; const parts = header.chunk_parts; for (const part of parts){ requireIndex(part.source_index, paths.length, 'part source'); requireIndex(part.output_file_index, outputs.length, 'part output'); if (!integer(part.size, UINT32_LIMIT) || !integer(part.compressed_size, UINT32_LIMIT)) { throw new Error('Invalid analyzer part size'); } } if (header.module_index_hash !== modules.hash) { throw new Error('Analyzer module-index fingerprint mismatch'); } const membership = validateEdges(binary, header.output_file_modules, outputs.length, modules.modules.length, 'output modules'); const asyncLoaders = validateEdges(binary, header.output_file_async_loaders, outputs.length, modules.modules.length, 'output async loaders'); if (!Array.isArray(header.output_file_module_coverage) || header.output_file_module_coverage.length !== outputs.length) { throw new Error('Missing analyzer output coverage'); } if (header.output_file_module_coverage.some((coverage)=>coverage !== 'exact' && coverage !== 'unsupported' && coverage !== 'not_a_chunk')) { throw new Error('Invalid analyzer output coverage'); } for (const output of outputs)if (typeof output.filename !== 'string') throw new Error('Invalid output filename'); return { paths, header, entries: header.route_entries ?? null, membership, asyncLoaders, groups: groupRecords(header, modules) }; } function groupRecords(header, modules) { if (header.chunk_groups === undefined) return null; if (!Array.isArray(header.chunk_groups)) throw new Error('Invalid analyzer chunk groups'); const seen = new Set(); return header.chunk_groups.map((group)=>{ if (!group || typeof group !== 'object' || !integer(group.id, UINT32_LIMIT) || seen.has(group.id) || typeof group.kind !== 'string' || !Array.isArray(group.output_file_indices)) { throw new Error('Invalid analyzer chunk group'); } seen.add(group.id); let trigger_module_ident = null; let trigger_join = 'none'; if (group.trigger_module_index !== undefined) { requireIndex(group.trigger_module_index, modules.modules.length, 'trigger module'); trigger_module_ident = modules.modules[group.trigger_module_index].ident; trigger_join = 'joined'; } return { id: group.id, kind: group.kind, trigger_module_ident, trigger_join, outputs: group.output_file_indices.map((index)=>{ requireIndex(index, header.output_files.length, 'group output'); return header.output_files[index].filename; }) }; }); } function writeRecord(record) { const data = Buffer.from(JSON.stringify(record) + '\n'); let offset = 0; while(offset < data.length){ const written = (0, _nodefs.writeSync)(1, data, offset, data.length - offset); if (written === 0) throw new Error('Could not write analyzer export to stdout'); offset += written; } } function dumpAnalyzeGraph(directory, snapshotName, routeFilter) { // Pipe stdout must block so writeSync supplies backpressure rather than EAGAIN. if (process.stdout._handle != null) { // @ts-ignore Node's internal handle, also used by the native bindings loader. process.stdout._handle.setBlocking == null ? void 0 : process.stdout._handle.setBlocking.call(process.stdout._handle, true); } const routes = JSON.parse((0, _nodefs.readFileSync)((0, _nodepath.join)(directory, 'routes.json'), 'utf8')); if (!Array.isArray(routes) || routes.some((route)=>typeof route !== 'string')) { throw new Error('Invalid analyzer routes'); } const selected = routes.filter((route)=>routeFilter === undefined || route === routeFilter); if (routeFilter !== undefined && selected.length === 0) throw new Error(`Unknown analyzer route: ${routeFilter}`); const moduleData = parseModules((0, _nodepath.join)(directory, 'modules.data')); const { modules, edges, hash } = moduleData; writeRecord({ type: 'meta', schema_version: 1, snapshot_name: snapshotName, module_index_hash: hash, route_count: routes.length, selected_routes: selected.length }); for (const [index, module] of modules.entries()){ writeRecord({ type: 'module', ident: module.ident, path: module.path, dependencies: Object.fromEntries([ 'sync', 'async', 'traced' ].map((kind)=>[ kind, edges[kind].row(index).map((i)=>modules[i].ident) ])) }); } // Retain only the current route's data. A later validation error may leave // partial output; callers must check the exit status before using it. for (const route of selected){ const { paths, header, entries, membership, asyncLoaders, groups } = parseRoutes(routeFile(directory, route), moduleData); const prefix = { route }; writeRecord({ type: 'route', ...prefix, entries, coverage: { entries: entries ? 'exact' : 'unknown', groups: groups ? 'exact' : 'unknown' } }); for(let i = 0; i < header.output_files.length; i++){ writeRecord({ type: 'output', ...prefix, filename: header.output_files[i].filename, modules: membership.row(i).map((id)=>modules[id].ident).sort(), async_loaders: asyncLoaders.row(i).map((id)=>modules[id].ident).sort(), coverage: header.output_file_module_coverage[i] }); } for (const part of header.chunk_parts){ writeRecord({ type: 'part', ...prefix, filename: header.output_files[part.output_file_index].filename, source_path: paths[part.source_index], size: part.size, compressed_size: part.compressed_size }); } for (const group of groups ?? [])writeRecord({ type: 'group', ...prefix, ...group }); } } //# sourceMappingURL=graph-dump.js.map