UNPKG

@nodesecure/tarball

Version:
109 lines 4.17 kB
// Import Node.js Dependencies import path from "node:path"; // Import Third-party Dependencies import * as conformance from "@nodesecure/conformance"; import { ManifestManager } from "@nodesecure/mama"; import { AstAnalyser, DefaultCollectableSet, warnings, TsSourceParser } from "@nodesecure/js-x-ray"; // Import Internal Dependencies import { SourceCodeReport, SourceCodeScanner } from "./SourceCodeScanner.class.js"; import { getTarballComposition } from "../utils/index.js"; import { DnsResolver } from "./DnsResolver.class.js"; export class NpmTarball { static JS_EXTENSIONS = new Set([ ".js", ".mjs", ".cjs", ".ts", ".mts", ".cts", ".jsx", ".tsx" ]); manifest; #resolver; #astAnalyser; constructor(mama, options = {}) { if (!ManifestManager.isLocated(mama)) { throw new Error("ManifestManager must have a location"); } this.manifest = mama; this.#resolver = options?.resolver ?? new DnsResolver(); this.#astAnalyser = options?.astAnalyser ?? null; } async scanFiles(astAnalyserOptions, options = {}) { const { exclude = [] } = options; const location = this.manifest.location; const [composition, spdx] = await Promise.all([ getTarballComposition(location), conformance.extractLicenses(location) ]); let code; if (composition.files.length === 1 && composition.files.includes("package.json")) { code = new SourceCodeReport(); } else { const options = this.#optionsWithHostnameSet(astAnalyserOptions ?? {}); const astAnalyser = this.#astAnalyser ?? new AstAnalyser(options); const hostNameSet = astAnalyser.getCollectableSet("hostname"); code = await new SourceCodeScanner(this.manifest, { astAnalyser }).iterate({ manifest: [...this.manifest.getEntryFiles()] .flatMap(filterJavaScriptFiles(exclude)), javascript: composition.files .flatMap(filterJavaScriptFiles(exclude)) }); if (hostNameSet instanceof DefaultCollectableSet) { const operationQueue = Array.from(hostNameSet) .map(({ value, locations }) => this.#resolver.isPrivateHost(value) .then((isPrivate) => { if (isPrivate) { locations.forEach(({ file, location }) => { code.warnings.push({ kind: "shady-link", ...warnings["shady-link"], file: file ?? undefined, location, value, source: "Scanner" }); }); } })); await Promise.allSettled(operationQueue); } } return { conformance: spdx, composition, code }; } #optionsWithHostnameSet(options) { const hasHostnameSet = options?.collectables?.some((collectable) => collectable.type === "hostname"); if (hasHostnameSet) { return options; } return { ...options, collectables: [ ...options.collectables ?? [], new DefaultCollectableSet("hostname") ] }; } } function filterJavaScriptFiles(exclude = []) { return (file) => { // Exclude .d.ts files if (file.includes("d.ts")) { return []; } // Exclude files matching any glob pattern if (exclude.some((pattern) => path.matchesGlob(file, pattern))) { return []; } const fileExt = path.extname(file); if (NpmTarball.JS_EXTENSIONS.has(fileExt)) { return file; } if (TsSourceParser.FileExtensions.has(fileExt)) { return file; } return []; }; } //# sourceMappingURL=NpmTarball.class.js.map