UNPKG

dd-trace

Version:

Datadog APM tracing client for JavaScript

134 lines (119 loc) 5.34 kB
'use strict' const crypto = require('node:crypto') const fs = require('node:fs') const path = require('node:path') const { parseBoundedJson } = require('./bounded-json') const { validateManifest } = require('./manifest-schema') const MAX_MANIFEST_BYTES = 5 * 1024 * 1024 const MAX_MANIFEST_COLLECTION_ENTRIES = 50_000 const MAX_MANIFEST_ERROR_BYTES = 16 * 1024 const MAX_MANIFEST_NESTING_DEPTH = 64 const MAX_MANIFEST_STRING_BYTES = 256 * 1024 function loadManifest (manifestPath) { const resolvedPath = path.resolve(manifestPath) const stat = fs.lstatSync(resolvedPath) if (stat.isSymbolicLink() || !stat.isFile()) { throw new Error(`Validation manifest must be a regular file, not a symbolic link: ${resolvedPath}`) } if (stat.size > MAX_MANIFEST_BYTES) { throw new Error(`Validation manifest exceeds the ${MAX_MANIFEST_BYTES}-byte size limit: ${resolvedPath}`) } const raw = fs.readFileSync(resolvedPath) const manifest = parseBoundedJson(raw, { label: 'Validation manifest JSON', maxCollectionEntries: MAX_MANIFEST_COLLECTION_ENTRIES, maxNestingDepth: MAX_MANIFEST_NESTING_DEPTH, maxStringBytes: MAX_MANIFEST_STRING_BYTES, }).value const errors = validateManifest(manifest) if (errors.length > 0) { throw new Error(`Invalid validation manifest:\n- ${renderErrors(errors)}`) } manifest.__path = resolvedPath Object.defineProperty(manifest, '__sourceSha256', { configurable: false, enumerable: false, value: crypto.createHash('sha256').update(raw).digest('hex'), writable: false, }) if (path.dirname(resolvedPath) !== path.resolve(manifest.repository.root)) { throw new Error(`Validation manifest must be stored directly in repository.root: ${resolvedPath}`) } validatePhysicalManifestPaths(manifest) return manifest } function renderErrors (errors) { const rendered = errors.join('\n- ') if (Buffer.byteLength(rendered) <= MAX_MANIFEST_ERROR_BYTES) return rendered return `${Buffer.from(rendered).subarray(0, MAX_MANIFEST_ERROR_BYTES).toString('utf8')}\n- Additional errors omitted.` } function validatePhysicalManifestPaths (manifest) { const root = fs.realpathSync(manifest.repository.root) for (const [label, candidate] of getManifestPaths(manifest)) { if (typeof candidate !== 'string' || !path.isAbsolute(candidate)) continue let existing = candidate while (!fs.existsSync(existing) && path.dirname(existing) !== existing) existing = path.dirname(existing) const physical = fs.realpathSync(existing) if (!isPathInside(root, physical)) { throw new Error(`Validation manifest ${label} resolves outside repository.root: ${candidate}`) } } } function getManifestPaths (manifest) { const paths = [] for (const [frameworkIndex, framework] of (manifest.frameworks || []).entries()) { const prefix = `frameworks[${frameworkIndex}]` paths.push( [`${prefix}.project.root`, framework.project?.root], [`${prefix}.project.packageJson`, framework.project?.packageJson], [`${prefix}.ciWiring.configFile`, framework.ciWiring?.configFile], [`${prefix}.ciWiring.workingDirectory`, framework.ciWiring?.workingDirectory] ) for (const [index, configFile] of (framework.project?.configFiles || []).entries()) { paths.push([`${prefix}.project.configFiles[${index}]`, configFile]) } for (const [name, command] of getCommands(framework)) { paths.push([`${prefix}.${name}.cwd`, command.cwd]) for (const [outputIndex, outputPath] of (command.outputPaths || []).entries()) { paths.push([`${prefix}.${name}.outputPaths[${outputIndex}]`, outputPath]) } } const strategy = framework.generatedTestStrategy paths.push([`${prefix}.generatedTestStrategy.testDirectory`, strategy?.testDirectory]) for (const [index, file] of (strategy?.files || []).entries()) { paths.push([`${prefix}.generatedTestStrategy.files[${index}].path`, file.path]) } for (const [index, cleanupPath] of (strategy?.cleanupPaths || []).entries()) { paths.push([`${prefix}.generatedTestStrategy.cleanupPaths[${index}]`, cleanupPath]) } for (const [scenarioIndex, scenario] of (strategy?.scenarios || []).entries()) { for (const [identityIndex, identity] of (scenario?.testIdentities || []).entries()) { paths.push([ `${prefix}.generatedTestStrategy.scenarios[${scenarioIndex}].testIdentities[${identityIndex}].file`, identity.file, ]) } } } return paths } function getCommands (framework) { const commands = [] for (const name of ['existingTestCommand', 'ciWiringCommand']) { if (framework[name]) commands.push([name, framework[name]]) } for (const [index, command] of (framework.setup?.commands || []).entries()) { commands.push([`setup.commands[${index}]`, command]) } for (const [index, scenario] of (framework.generatedTestStrategy?.scenarios || []).entries()) { if (scenario?.runCommand) { commands.push([`generatedTestStrategy.scenarios[${index}].runCommand`, scenario.runCommand]) } } return commands } function isPathInside (root, filename) { const relative = path.relative(root, filename) return relative === '' || (relative && !relative.startsWith('..') && !path.isAbsolute(relative)) } module.exports = { loadManifest }