diff --git a/CONVENTIONS.md b/CONVENTIONS.md index 42ff789a..d0fd37d7 100644 --- a/CONVENTIONS.md +++ b/CONVENTIONS.md @@ -8,7 +8,8 @@ - **TypeScript**: Configuration present but code is primarily JavaScript with JSDoc - **Node.js**: Requires Node >= 20.0.0, npm >= 11.5.1 - **CLI**: `yargs` for command-line argument parsing -- **Parsing Libraries**: `fast-xml-parser`, `fast-toml`, `smol-toml`, `tree-sitter-requirements` +- **Parsing Libraries**: `fast-xml-parser`, `fast-toml`, `smol-toml`, `web-tree-sitter` with grammar packages (`tree-sitter-requirements`, `tree-sitter-gomod`, `tree-sitter-containerfile`) +- **Prefer proper parsers over regex**: When parsing structured file formats (e.g., go.mod, requirements.txt, Dockerfile), use tree-sitter grammars rather than hand-written regex. Tree-sitter grammars already exist for several formats and provide a well-defined AST that handles edge cases. Follow the existing pattern: add the grammar package, create a `*_parser.js` module under `src/providers/`, copy the `.wasm` file in `pretest`/`postcompile` scripts, and use queries to extract data from the syntax tree. ## Code Style diff --git a/README.md b/README.md index b02d4161..5b47a0d1 100644 --- a/README.md +++ b/README.md @@ -695,7 +695,7 @@ By default, The API algorithm will use native commands of PIP installer as data It's also possible to use the lightweight Python PIP utility [pipdeptree](https://pypi.org/project/pipdeptree/) as data source instead. In order to activate this, you need to set the environment variable/option `TRUSTIFY_DA_PIP_USE_DEP_TREE` to 'true'. #### Toggle Red Hat Trusted Content recommendations -Both the HTML-based report and JSON response will by default contain recommendations for migrating to Red Hat-based Trusted Content repositories. This feature can be disabled by setting `TRUSTIFY_DA_RECOMMENDATIONS_ENABLED` to 'false' via environment variables or options. +Both the HTML-based report and JSON response will by default contain recommendations for migrating to Red Hat-based Trusted Content repositories. This feature can be disabled by setting `TRUSTIFY_DA_RECOMMEND` to 'false' via environment variables or options. #### Additional CLI arguments For some ecosystems we support passing additional CLI arguments to the underlying tools. The following table outlines the supported ecosystems and the environment variable/option that configures this. Note that the arguments are expected to be in the format of a JSON array. diff --git a/package-lock.json b/package-lock.json index 2081c945..6d1db3e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "p-limit": "^7.3.0", "packageurl-js": "~1.0.2", "smol-toml": "^1.6.0", + "tree-sitter-containerfile": "^0.8.1", "tree-sitter-gomod": "github:strum355/tree-sitter-go-mod#56326f2ad478892ace58ff247a97d492a3cbcdda", "tree-sitter-requirements": "github:Strum355/tree-sitter-requirements#d0261ee76b84253997fe70d7d397e78c006c3801", "web-tree-sitter": "^0.26.7", @@ -785,14 +786,14 @@ "license": "MIT" }, "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", - "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", + "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "@tybys/wasm-util": "^0.10.1" + "@tybys/wasm-util": "^0.10.3" }, "funding": { "type": "github", @@ -804,9 +805,9 @@ } }, "node_modules/@nodable/entities": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.1.1.tgz", - "integrity": "sha512-Pig3HxDIoMgjdEH8OCf/dkcTmLFjJRjWuq8jSnklu284/TKOPibSRERmOykiwmyXTtv61mP+44f3GMx0tLAyjg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-9uGyhaQavEUMC8AIddIjau4NsnsXhou+j5sBAGojCM1oxmQpVKTWR/9JxABD6UAv12vpIms55fPZKFQEhG6uBg==", "funding": [ { "type": "github", @@ -875,13 +876,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@package-json/types": { - "version": "0.0.12", - "resolved": "https://registry.npmjs.org/@package-json/types/-/types-0.0.12.tgz", - "integrity": "sha512-uu43FGU34B5VM9mCNjXCwLaGHYjXdNincqKLaraaCW+7S2+SmiBg1Nv8bPnmschrIfZmfKNY9f3fC376MRrObw==", - "dev": true, - "license": "MIT" - }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -935,16 +929,16 @@ } }, "node_modules/@trustify-da/trustify-da-api-model": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/@trustify-da/trustify-da-api-model/-/trustify-da-api-model-2.0.9.tgz", - "integrity": "sha512-mBVl97HkEfGBtLZSJMLySS6toK4Se+nfBi1g8iVHwH/A+wikU/KzM3HAbTW7f4FLfhx5saoZi+Asf0chng7TPQ==", + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@trustify-da/trustify-da-api-model/-/trustify-da-api-model-2.0.10.tgz", + "integrity": "sha512-QGJP4fmu+dKm8BIsuEVCwEQAli7ED9QT/8Xz7XScv81etxJNXivHjIKXPz/5e5xz1vS2zpkYELE32QwywYc6hQ==", "dev": true, "license": "Apache-2.0" }, "node_modules/@tybys/wasm-util": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", - "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", "dev": true, "license": "MIT", "optional": true, @@ -981,9 +975,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.9.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.2.tgz", - "integrity": "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw==", + "version": "25.9.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.4.tgz", + "integrity": "sha512-dszCsrKb5U7ZsVZBWiHFklTloVl0mSEnWH/iZXfZUlI4rzCUnsvGmgqfuVRHL54ugE7/wRuxEIXRa2iMZ+BG6g==", "dev": true, "license": "MIT", "dependencies": { @@ -1015,9 +1009,9 @@ "license": "MIT" }, "node_modules/@typescript-eslint/types": { - "version": "8.60.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.60.1.tgz", - "integrity": "sha512-4h0tY8ppCkdCzcrl2YM5M3my0xsE1Tf8om3owEu5oPWmXwkKRmk0j0LGDzYBGUcAlesEbxBhazqu/K4cu3Ug7w==", + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.62.1.tgz", + "integrity": "sha512-ooCzJFaf+Hg+uG6fA3NRFGuFjlfNlDhBthbv4ZPU/0elCAFUfnyXUvf/WOpHz/jYwSmvU2GkR2LtyUfy1AxZ1Q==", "dev": true, "license": "MIT", "engines": { @@ -1351,9 +1345,9 @@ } }, "node_modules/acorn": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", "dev": true, "license": "MIT", "bin": { @@ -1382,6 +1376,23 @@ "node": ">= 20" } }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/ansi-regex": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", @@ -1410,6 +1421,18 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/anynum": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/anynum/-/anynum-1.0.1.tgz", + "integrity": "sha512-N6//FLET/tXYNM/F6ABca1oH6fWB+KlTt909Le28WMDBk8oaT4vY17DCrwg2MvmuqUKt3Ni4N5dGJ/EoBgcO6A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -1434,9 +1457,9 @@ } }, "node_modules/baseline-browser-mapping": { - "version": "2.10.33", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.33.tgz", - "integrity": "sha512-bA6+tcSLpz2tIEdDXZPpPTIuxBcC4+w6SieaYyfigIa4h8GlFxbA17v22Vx3JUtuZQj9SgOsnbK+aTBzyDyEuw==", + "version": "2.10.40", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.40.tgz", + "integrity": "sha512-BSSLZ9/Cjjv7Gtj5B68ZzXcXUg8iOf3fme+FCuh8rC/Go+Kmh8cox7M3A8dolou16s64QjLPOSdngh7GxXvkSw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -1447,9 +1470,9 @@ } }, "node_modules/brace-expansion": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", - "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", + "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", "dev": true, "license": "MIT", "dependencies": { @@ -1479,9 +1502,9 @@ "license": "ISC" }, "node_modules/browserslist": { - "version": "4.28.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", - "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "version": "4.28.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.4.tgz", + "integrity": "sha512-MTc8i/x9jBQd1iMw2CFGS+rwMa07eYjLR0CCTLDACl9xhxy+nIs3KeML/biicXtk9JrZ6dnnTatmc7ErPXIxqw==", "dev": true, "funding": [ { @@ -1499,10 +1522,10 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.10.12", - "caniuse-lite": "^1.0.30001782", - "electron-to-chromium": "^1.5.328", - "node-releases": "^2.0.36", + "baseline-browser-mapping": "^2.10.38", + "caniuse-lite": "^1.0.30001799", + "electron-to-chromium": "^1.5.376", + "node-releases": "^2.0.48", "update-browserslist-db": "^1.2.3" }, "bin": { @@ -1625,9 +1648,9 @@ } }, "node_modules/c8/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", "dev": true, "license": "MIT", "dependencies": { @@ -1657,9 +1680,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001793", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz", - "integrity": "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==", + "version": "1.0.30001800", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001800.tgz", + "integrity": "sha512-MMHtuAz9Ys840zAY5F4k6fV5GaivZ9sPk+nz0mY+GYVzRBnYkN0mpqkSR92oWRQ19yQWo4HvBV/FnC16AJX8MA==", "dev": true, "funding": [ { @@ -1958,9 +1981,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.365", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.365.tgz", - "integrity": "sha512-xfip4u1QF1s+URFqpA6N+OeFpDGpN7VJz1f3MO3bVL0QYBjpGiZ5/Of7kugvM+o8TTqmanUlviHN3c8M9vYWCw==", + "version": "1.5.383", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.383.tgz", + "integrity": "sha512-I2484/KkAvl8lm9VyjH2JnbOIV0d/UCqT7gbzs6l+o6Vmn9wgB66uVcKX+Vk6HrXtY6fbWTOEXuv8waDTuFNCw==", "dev": true, "license": "ISC" }, @@ -2003,11 +2026,14 @@ } }, "node_modules/eslint": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.4.1.tgz", - "integrity": "sha512-AyIKhnOBuOAdueD7RB3xB+YeAWScb9jHsJBgH2Hcde8InP5JYhqrRR6iTMHyTEwgENK54Cp44e4v8BwNhsuHuw==", + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.6.0.tgz", + "integrity": "sha512-6lVbcqSodALYo+4ELD0heG6lFiFxnLMuLkiMi2qV8LMp54N8tE8FT1GMH+ev4Ti00nFjNze2+Su6DsV5OQW3Dg==", "dev": true, "license": "MIT", + "workspaces": [ + "packages/*" + ], "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", @@ -2119,13 +2145,12 @@ } }, "node_modules/eslint-plugin-import-x": { - "version": "4.16.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-import-x/-/eslint-plugin-import-x-4.16.2.tgz", - "integrity": "sha512-rM9K8UBHcWKpzQzStn1YRN2T5NvdeIfSVoKu/lKF41znQXHAUcBbYXe5wd6GNjZjTrP7viQ49n1D83x/2gYgIw==", + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import-x/-/eslint-plugin-import-x-4.17.1.tgz", + "integrity": "sha512-4cdstYkKCyjumM2Q9NSI03K8D2a9F4Ssz33K2lv2hQa4KmR9jPLwk3uWGtNvclfqBrPGfGuMBwsGMbe6dMRbfg==", "dev": true, "license": "MIT", "dependencies": { - "@package-json/types": "^0.0.12", "@typescript-eslint/types": "^8.56.0", "comment-parser": "^1.4.1", "debug": "^4.4.1", @@ -2157,9 +2182,9 @@ } }, "node_modules/eslint-plugin-import-x/node_modules/semver": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", - "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { @@ -2201,30 +2226,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/ajv": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", - "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/eslint/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, "node_modules/esmock": { "version": "2.7.6", "resolved": "https://registry.npmjs.org/esmock/-/esmock-2.7.6.tgz", @@ -2309,7 +2310,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/fast-glob": { @@ -2344,7 +2345,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/fast-levenshtein": { @@ -2398,9 +2399,9 @@ } }, "node_modules/fast-xml-parser": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.8.0.tgz", - "integrity": "sha512-6bIM7fsJxeo3uXv7OncQYsBAMPJ7V16Slahl/6M98C/i2q+vB1+4a0MtrvYwDFEUrwDSbAmeLDRXsOBwrL7yAg==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.9.3.tgz", + "integrity": "sha512-brCNCeScma/kqa54J4PIDriSSSLssRkuYaUCpvHJulGc3HGI/xxKUCTDcYkAdqJsyb//ydpbxecjC3hB9+tb/g==", "funding": [ { "type": "github", @@ -2409,10 +2410,11 @@ ], "license": "MIT", "dependencies": { - "@nodable/entities": "^2.1.0", + "@nodable/entities": "^2.2.0", "fast-xml-builder": "^1.2.0", + "is-unsafe": "^1.0.1", "path-expression-matcher": "^1.5.0", - "strnum": "^2.3.0", + "strnum": "^2.4.1", "xml-naming": "^0.1.0" }, "bin": { @@ -2666,9 +2668,9 @@ } }, "node_modules/globals": { - "version": "17.6.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-17.6.0.tgz", - "integrity": "sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA==", + "version": "17.7.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.7.0.tgz", + "integrity": "sha512-Czmyns5dUsq4seFBR/Kdydhmo8y9kC79hiSkPn0YcGtNnYWnrgt0vjrSjx9tspoDGWm2CMarffRuLjM4xUz8xg==", "dev": true, "license": "MIT", "engines": { @@ -2685,9 +2687,9 @@ "license": "ISC" }, "node_modules/graphql": { - "version": "16.14.1", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.14.1.tgz", - "integrity": "sha512-cQOsSMS/IrDz82PVyRDvf/Q1F/bRbBVjJlh+xYOkI1qw2bWRvWGiWc+m2O0d6l4Bt1fyY+8kzJ8JFWGJqNeDBg==", + "version": "16.14.2", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.14.2.tgz", + "integrity": "sha512-Chq1s4CY7jmh8gO2qvLIJyfCDIN+EHLFW/9iShnp1z8FjBQMoodWP1kDC36VAMXXIvAjj4ARa7ntfAV2BrjsbA==", "dev": true, "license": "MIT", "engines": { @@ -2783,9 +2785,9 @@ } }, "node_modules/is-bun-module/node_modules/semver": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", - "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { @@ -2875,6 +2877,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-unsafe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-unsafe/-/is-unsafe-1.0.1.tgz", + "integrity": "sha512-CLK2+VdgERgD96EYm5lUQssZYlRg2tkZnbsxZoacmSiRxiFJ4Nk4SzjCl+Ur+v3kXIY9dTIdb3IH22y1mZ56LA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, "node_modules/isexe": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", @@ -2947,9 +2961,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", - "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", + "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", "funding": [ { "type": "github", @@ -2988,6 +3002,13 @@ "dev": true, "license": "MIT" }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "devOptional": true, + "license": "MIT" + }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", @@ -3098,9 +3119,9 @@ } }, "node_modules/make-dir/node_modules/semver": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", - "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { @@ -3334,9 +3355,9 @@ } }, "node_modules/mocha/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", "dev": true, "license": "MIT", "dependencies": { @@ -3482,9 +3503,9 @@ } }, "node_modules/msw/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", "dev": true, "license": "MIT", "dependencies": { @@ -3534,9 +3555,9 @@ "license": "MIT" }, "node_modules/node-addon-api": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.8.0.tgz", - "integrity": "sha512-c5Ko1fZJIJmzhFIkhRN76WTq+fC6tWnGy9CXA0fA+XygsWZmEwG8vmbkNqxMyoaa0Tin4djul49NzdVcJJcjeA==", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.9.0.tgz", + "integrity": "sha512-ekZMeaaIzSQTSpr7X2X3iJM7lTzgnx8ahAG9pJfT/7+14mlEM8ZYQ9cgCDvSSRbReFK0oHli3WrZdCiRsgAT9Q==", "license": "MIT", "engines": { "node": "^18 || ^20 || >= 21" @@ -3581,9 +3602,9 @@ } }, "node_modules/node-gyp": { - "version": "12.3.0", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.3.0.tgz", - "integrity": "sha512-QNcUWM+HgJplcPzBvFBZ9VXacyGZ4+VTOb80PwWR+TlVzoHbRKULNEzpRsnaoxG3Wzr7Qh7BYxGDU3CbKib2Yg==", + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.4.0.tgz", + "integrity": "sha512-OMcPNvqTCFUnNaBlmdgq+lfNqY7gTiSmNRDjY3uAXRyudeKZEZxu3CLtjMQrx4zZxCX2b/mpNqTtwuCJgXhHkw==", "license": "MIT", "dependencies": { "env-paths": "^2.2.0", @@ -3616,9 +3637,9 @@ } }, "node_modules/node-gyp/node_modules/semver": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", - "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -3643,9 +3664,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.47", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.47.tgz", - "integrity": "sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==", + "version": "2.0.50", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.50.tgz", + "integrity": "sha512-J6l92tKHX6w8Jy5nO1Vuc01NoIiRGi/d6qBKVxh+IQ8Cr3b6HbVNfKiF8ZpFKufTwpwxMmce2W3iQZ861ZRyTg==", "dev": true, "license": "MIT", "engines": { @@ -3776,9 +3797,9 @@ } }, "node_modules/path-expression-matcher": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.5.0.tgz", - "integrity": "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.6.1.tgz", + "integrity": "sha512-h7bxdzhHk8Knyc4Tj+jMaa7fEEoUJy7p1qtbVgkYg1Uhpe5Np5VuGXCRZnkZvU+Q42M1vStt0ifa3ueykRJPmQ==", "funding": [ { "type": "github", @@ -3890,7 +3911,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6" @@ -4042,9 +4063,9 @@ } }, "node_modules/set-cookie-parser": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-3.1.0.tgz", - "integrity": "sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-3.1.1.tgz", + "integrity": "sha512-vM9SUhjsUYs6UeJUmygc5Ofm5eQGe85riob5ju6XCgFGJI5PLV4nrDAQpQjd+LkFBpAkADn5BQQpZ9EUNkyLuA==", "dev": true, "license": "MIT" }, @@ -4123,9 +4144,9 @@ } }, "node_modules/smol-toml": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.6.1.tgz", - "integrity": "sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.7.0.tgz", + "integrity": "sha512-aqVvWoyO21L23mb+drl4RmMXbf6N7FdHjAhTRA9ZBL7apWBgfWC16KjrASI+1p9GAroljyMHj6fK67i0UiTNvQ==", "license": "BSD-3-Clause", "engines": { "node": ">= 18" @@ -4278,16 +4299,19 @@ } }, "node_modules/strnum": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.3.0.tgz", - "integrity": "sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.4.1.tgz", + "integrity": "sha512-M9eUSMT2dCB2cTNPG7UYj6KuK7RJR2SN2+yCV/fTW3xzTCS6EaGZ5pSMgDIjB7r8zSfTGk+dvvn9rTjpVS9Mwg==", "funding": [ { "type": "github", "url": "https://github.com/sponsors/NaturalIntelligence" } ], - "license": "MIT" + "license": "MIT", + "dependencies": { + "anynum": "^1.0.1" + } }, "node_modules/supports-color": { "version": "7.2.0", @@ -4316,9 +4340,9 @@ } }, "node_modules/tar": { - "version": "7.5.16", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.16.tgz", - "integrity": "sha512-56adEpPMouktRlBLXiaYFFzZ/3+JXa8P9n7WbR+ibIjtviN55mEaOkiysCnPnWm+7kkui1Dn8J9l+g6zV8731w==", + "version": "7.5.19", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.19.tgz", + "integrity": "sha512-4LeEWl96twnS2Q7Bz4MGqgazLqO+hJN63GZxXoIqh1T3VweYD997gbU1ItNsQafqqXTXd5WFyFdReLtwvRBNiw==", "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/fs-minipass": "^4.0.0", @@ -4446,22 +4470,22 @@ } }, "node_modules/tldts": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.2.tgz", - "integrity": "sha512-kCwffuaH8ntKtygnWe1b4BJKWiCUH30n5KfoTr6IchcXOwR7chAOFJxFrH3vjANafUYrIA4a7SDL+nn7SiR4Sw==", + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.5.tgz", + "integrity": "sha512-RfEzKWcq5fHUOFq7J3rl3Oz6ylKGtcHqUznzj4EcXsxLSIjJcvpbXAQtWGeJQ0xKnimR5e0Cn+cn9TssfMzm+g==", "dev": true, "license": "MIT", "dependencies": { - "tldts-core": "^7.4.2" + "tldts-core": "^7.4.5" }, "bin": { "tldts": "bin/cli.js" } }, "node_modules/tldts-core": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.2.tgz", - "integrity": "sha512-nwEyF4vl4RSJjwSjBUmOSxc3BFPoIFdlRthJ6e+5v9P3bHNsoD06UjuqMUspqp7vsEZ1beaHi1km+optiE17yA==", + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.5.tgz", + "integrity": "sha512-pGrwzZDvPwKe+7NNUqAunb6rqTfynr0VOUhCMdqbu5xlvNiszsAJygRzwvpVycdzejlbpY+SWJOn+s75Og7FEA==", "dev": true, "license": "MIT" }, @@ -4502,10 +4526,29 @@ "node-gyp-build": "^4.8.4" } }, + "node_modules/tree-sitter-containerfile": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/tree-sitter-containerfile/-/tree-sitter-containerfile-0.8.1.tgz", + "integrity": "sha512-aNhSJ802MLT6zYmBqZmZtJkY2lPg9W6Fllct3aNJ8tBQlttobEgnhN4j9B6PfRWIjQX9rh42750gXMTgDuS8Tg==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-addon-api": "^8.5.0", + "node-gyp-build": "^4.8.4" + }, + "peerDependencies": { + "tree-sitter": ">=0.25.0" + }, + "peerDependenciesMeta": { + "tree-sitter": { + "optional": true + } + } + }, "node_modules/tree-sitter-gomod": { "version": "1.1.1", "resolved": "git+ssh://git@github.com/strum355/tree-sitter-go-mod.git#56326f2ad478892ace58ff247a97d492a3cbcdda", - "integrity": "sha512-NQ/6pAjcjy7cmhQGOMFMXO3mf0PEwwKHXir0yz3h82NX/04Z6Q0FtAq2bAFNZz6bQ+kzX1snJAMRVy+NT9+w5A==", + "integrity": "sha512-SH9N9VFOMuuKuOpsxnEqEfHk9rVEbrBGr4MSAyMaqT1Re/ewwyH7YDcDlE1wumsxNEYMbsZ8MeNoQeEYgoaC4g==", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -4525,7 +4568,7 @@ "node_modules/tree-sitter-requirements": { "version": "0.5.0", "resolved": "git+ssh://git@github.com/Strum355/tree-sitter-requirements.git#d0261ee76b84253997fe70d7d397e78c006c3801", - "integrity": "sha512-ish3EH9P4sin4LkDeYLHfnC5+LjMv/is42ZG/y1DYno677NIQvM3Xej4vah8O3SQQSoDG51sAnIr7bjDpHqWRA==", + "integrity": "sha512-n+Eb8J10s3adrdvAt3aW+w6WsgIPkDw+itHWC4FY7YPE7P6TwmZXHwKaifJfAJRrpxTR+my8/mM3AWkAskgCxA==", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -4704,7 +4747,7 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, + "devOptional": true, "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" @@ -4735,9 +4778,9 @@ } }, "node_modules/web-tree-sitter": { - "version": "0.26.9", - "resolved": "https://registry.npmjs.org/web-tree-sitter/-/web-tree-sitter-0.26.9.tgz", - "integrity": "sha512-YJwSHANl6XFgeEjB8nitgj0qZYt5gkIesJ4w2srS2wcLB4GUa4xcOkM0YaMsU6WNR53YVIkDSY7Ej4pf3IXtCA==", + "version": "0.26.10", + "resolved": "https://registry.npmjs.org/web-tree-sitter/-/web-tree-sitter-0.26.10.tgz", + "integrity": "sha512-vengBGYS7FpAerkR3o04oBL4L8MkVmjawK50AFBu7v0HZBkmF9ZavPGKoXLSSmRhp7T/YgsJ7joAS3yAxHPEqQ==", "license": "MIT" }, "node_modules/which": { diff --git a/package.json b/package.json index c3333264..663f9cb2 100644 --- a/package.json +++ b/package.json @@ -40,11 +40,11 @@ "test": "c8 npm run tests", "tests": "mocha --config .mocharc.json", "tests:rep": "mocha --reporter-option maxDiffSize=0 --reporter json > unit-tests-result.json", - "pretest": "cp node_modules/tree-sitter-requirements/tree-sitter-requirements.wasm src/providers/tree-sitter-requirements.wasm && cp node_modules/tree-sitter-gomod/tree-sitter-gomod.wasm src/providers/tree-sitter-gomod.wasm", + "pretest": "cp node_modules/tree-sitter-requirements/tree-sitter-requirements.wasm src/providers/tree-sitter-requirements.wasm && cp node_modules/tree-sitter-gomod/tree-sitter-gomod.wasm src/providers/tree-sitter-gomod.wasm && cp node_modules/tree-sitter-containerfile/tree-sitter-containerfile.wasm src/providers/tree-sitter-containerfile.wasm", "precompile": "rm -rf dist", "compile": "tsc -p tsconfig.json", "compile:dev": "tsc -p tsconfig.dev.json", - "postcompile": "cp node_modules/tree-sitter-requirements/tree-sitter-requirements.wasm dist/src/providers/tree-sitter-requirements.wasm && cp node_modules/tree-sitter-gomod/tree-sitter-gomod.wasm dist/src/providers/tree-sitter-gomod.wasm" + "postcompile": "cp node_modules/tree-sitter-requirements/tree-sitter-requirements.wasm dist/src/providers/tree-sitter-requirements.wasm && cp node_modules/tree-sitter-gomod/tree-sitter-gomod.wasm dist/src/providers/tree-sitter-gomod.wasm && cp node_modules/tree-sitter-containerfile/tree-sitter-containerfile.wasm dist/src/providers/tree-sitter-containerfile.wasm" }, "dependencies": { "@babel/core": "^7.23.2", @@ -61,6 +61,7 @@ "p-limit": "^7.3.0", "packageurl-js": "~1.0.2", "smol-toml": "^1.6.0", + "tree-sitter-containerfile": "^0.8.1", "tree-sitter-gomod": "github:strum355/tree-sitter-go-mod#56326f2ad478892ace58ff247a97d492a3cbcdda", "tree-sitter-requirements": "github:Strum355/tree-sitter-requirements#d0261ee76b84253997fe70d7d397e78c006c3801", "web-tree-sitter": "^0.26.7", @@ -68,18 +69,18 @@ }, "devDependencies": { "@babel/core": "^7.23.2", + "@eslint/js": "^10.0.0", "@trustify-da/trustify-da-api-model": "^2.0.9", "@types/node": "^25.9.1", "@types/which": "^3.0.4", "babel-plugin-rewire": "^1.2.0", "c8": "^11.0.0", "chai": "^6.2.2", - "@eslint/js": "^10.0.0", "eslint": "^10.4.1", "eslint-import-resolver-typescript": "^4.4.4", "eslint-plugin-import-x": "^4.16.2", - "globals": "^17.6.0", "esmock": "^2.6.2", + "globals": "^17.6.0", "mocha": "^11.7.6", "msw": "^2.12.7", "sinon": "^22.0.0", @@ -117,6 +118,9 @@ "text" ] }, + "overrides": { + "tree-sitter": "0.22.4" + }, "resolutions": { "@hapi/joi": "17.1.1" } diff --git a/src/analysis.js b/src/analysis.js index 1ac516ae..512ea08e 100644 --- a/src/analysis.js +++ b/src/analysis.js @@ -44,7 +44,7 @@ async function requestStack(provider, manifest, url, html = false, opts = {}) { }, opts); const finalUrl = new URL(`${url}/api/v5/analysis`); - if (opts['TRUSTIFY_DA_RECOMMENDATIONS_ENABLED'] === 'false') { + if (getCustom('TRUSTIFY_DA_RECOMMEND', 'true', opts) === 'false') { finalUrl.searchParams.append('recommend', 'false'); } @@ -106,7 +106,7 @@ async function requestComponent(provider, manifest, url, opts = {}) { }, opts); const finalUrl = new URL(`${url}/api/v5/analysis`); - if (opts['TRUSTIFY_DA_RECOMMENDATIONS_ENABLED'] === 'false') { + if (getCustom('TRUSTIFY_DA_RECOMMEND', 'true', opts) === 'false') { finalUrl.searchParams.append('recommend', 'false'); } @@ -150,7 +150,7 @@ async function requestComponent(provider, manifest, url, opts = {}) { */ async function requestStackBatch(sbomByPurl, url, html = false, opts = {}) { const finalUrl = new URL(`${url}/api/v5/batch-analysis`) - if (opts['TRUSTIFY_DA_RECOMMENDATIONS_ENABLED'] === 'false') { + if (getCustom('TRUSTIFY_DA_RECOMMEND', 'true', opts) === 'false') { finalUrl.searchParams.append('recommend', 'false') } @@ -203,7 +203,7 @@ async function requestImages(imageRefs, url, html = false, opts = {}) { } const finalUrl = new URL(`${url}/api/v5/batch-analysis`); - if (opts['TRUSTIFY_DA_RECOMMENDATIONS_ENABLED'] === 'false') { + if (getCustom('TRUSTIFY_DA_RECOMMEND', 'true', opts) === 'false') { finalUrl.searchParams.append('recommend', 'false'); } diff --git a/src/index.js b/src/index.js index 9c280a58..f4aaa2c0 100644 --- a/src/index.js +++ b/src/index.js @@ -61,7 +61,7 @@ export { * TRUSTIFY_DA_PYTHON_PATH?: string | undefined, * TRUSTIFY_DA_PYTHON_VIRTUAL_ENV?: string | undefined, * TRUSTIFY_DA_PYTHON3_PATH?: string | undefined, - * TRUSTIFY_DA_RECOMMENDATIONS_ENABLED?: string | undefined, + * TRUSTIFY_DA_RECOMMEND?: string | undefined, * TRUSTIFY_DA_SKOPEO_CONFIG_PATH?: string | undefined, * TRUSTIFY_DA_SKOPEO_PATH?: string | undefined, * TRUSTIFY_DA_SYFT_CONFIG_PATH?: string | undefined, diff --git a/src/oci_image/images.js b/src/oci_image/images.js index d600f4bc..ec9b86c9 100644 --- a/src/oci_image/images.js +++ b/src/oci_image/images.js @@ -266,9 +266,23 @@ export class ImageRef { getPackageURL() { /** @type {Object.} */ const qualifiers = {}; - const repositoryUrl = this.image.getNameWithoutTag(); + let repositoryUrl = this.image.getNameWithoutTag(); const simpleName = this.image.getSimpleName(); + // Normalize Docker Hub image references so all forms produce the same PURL: + // node → docker.io/node + // docker.io/library/node → docker.io/node + if (repositoryUrl != null) { + const lower = repositoryUrl.toLowerCase(); + if (lower === simpleName.toLowerCase()) { + // Bare name (e.g. "node") — default to docker.io + repositoryUrl = `docker.io/${simpleName}`; + } else if (lower.startsWith('docker.io/library/')) { + // Official image with library/ prefix — strip it + repositoryUrl = `docker.io/${lower.substring('docker.io/library/'.length)}`; + } + } + if (repositoryUrl != null && repositoryUrl.toLowerCase() !== simpleName.toLowerCase()) { qualifiers[ImageRef.REPOSITORY_QUALIFIER] = repositoryUrl.toLowerCase(); } diff --git a/src/provider.js b/src/provider.js index fc494d38..2f035ddc 100644 --- a/src/provider.js +++ b/src/provider.js @@ -8,6 +8,7 @@ import Javascript_bun from './providers/javascript_bun.js'; import Javascript_npm from './providers/javascript_npm.js'; import Javascript_pnpm from './providers/javascript_pnpm.js'; import Javascript_yarn from './providers/javascript_yarn.js'; +import dockerfileProvider from './providers/oci_dockerfile.js' import pythonPipProvider from './providers/python_pip.js' import Python_pip_pyproject from './providers/python_pip_pyproject.js' import Python_poetry from './providers/python_poetry.js' @@ -34,7 +35,8 @@ export const availableProviders = [ new Python_poetry(), new Python_uv(), new Python_pip_pyproject(), - rustCargoProvider] + rustCargoProvider, + dockerfileProvider] /** * Match a provider by manifest type only (no lock file check). Used for license reading. diff --git a/src/providers/containerfile_parser.js b/src/providers/containerfile_parser.js new file mode 100644 index 00000000..5ebfc90d --- /dev/null +++ b/src/providers/containerfile_parser.js @@ -0,0 +1,21 @@ +import { readFile } from 'node:fs/promises'; + +import { Language, Parser, Query } from 'web-tree-sitter'; + +const wasmUrl = new URL('./tree-sitter-containerfile.wasm', import.meta.url); + +async function init() { + await Parser.init(); + const wasmBytes = new Uint8Array(await readFile(wasmUrl)); + return await Language.load(wasmBytes); +} + +export async function getParser() { + const language = await init(); + return new Parser().setLanguage(language); +} + +export async function getFromQuery() { + const language = await init(); + return new Query(language, '(from_instruction (image_spec) @image)'); +} diff --git a/src/providers/oci_dockerfile.js b/src/providers/oci_dockerfile.js new file mode 100644 index 00000000..66a395eb --- /dev/null +++ b/src/providers/oci_dockerfile.js @@ -0,0 +1,118 @@ +import fs from 'node:fs' + +import { generateImageSBOM, parseImageRef } from '../oci_image/utils.js' + +import { getFromQuery, getParser } from './containerfile_parser.js' + +export default { isSupported, validateLockFile, provideComponent, provideStack, readLicenseFromManifest, packageManagerName() { return 'oci' } } + +/** @typedef {import('../provider').Provider} */ + +/** @typedef {import('../provider').Provided} Provided */ + +/** + * @type {string} ecosystem identifier for OCI image packages + * @private + */ +const ecosystem = 'oci' + +/** + * Check if the given manifest name is a Dockerfile or Containerfile. + * Supports dot-suffixed variants such as Dockerfile.dev or Containerfile.prod. + * @param {string} manifestName the manifest file name to check + * @returns {boolean} true if the manifest is a Dockerfile or Containerfile + */ +function isSupported(manifestName) { + return /^(Dockerfile|Containerfile)(\..+)?$/.test(manifestName) +} + +/** + * Dockerfiles have no lock file, so validation always passes. + * @returns {boolean} always true + */ +function validateLockFile() { return true; } + +/** + * Check whether a syntax node contains any expansion (variable substitution) children. + * @param {import('web-tree-sitter').SyntaxNode} node + * @returns {boolean} + * @private + */ +function containsExpansion(node) { + if (node.type === 'expansion') { + return true + } + for (let i = 0; i < node.childCount; i++) { + if (containsExpansion(node.child(i))) { + return true + } + } + return false +} + +/** + * Parse the last FROM instruction from a Dockerfile using tree-sitter to extract the base image reference. + * In multi-stage builds, the last FROM represents the final stage. + * @param {string} manifestContent the content of the Dockerfile + * @returns {Promise} the image reference from the last FROM instruction + * @throws {Error} when no FROM instruction is found or when ARG substitution is used + */ +export async function parseFromImage(manifestContent) { + const [parser, fromQuery] = await Promise.all([getParser(), getFromQuery()]) + const tree = parser.parse(manifestContent) + const matches = fromQuery.matches(tree.rootNode) + if (matches.length === 0) { + throw new Error('No FROM line found in Dockerfile') + } + const lastMatch = matches[matches.length - 1] + const imageSpec = lastMatch.captures.find(c => c.name === 'image').node + if (containsExpansion(imageSpec)) { + throw new Error('Dockerfile uses ARG substitution in FROM line — cannot resolve variable references') + } + return imageSpec.text +} + +/** + * Generate an image SBOM from a Dockerfile manifest using syft. + * @param {string} manifest path to the Dockerfile + * @param {{}} [opts={}] optional various options to pass along the application + * @returns {Promise<{ecosystem: string, content: string, contentType: string}>} + * @private + */ +async function getImageSBOM(manifest, opts = {}) { + const manifestContent = fs.readFileSync(manifest, 'utf-8') + const image = await parseFromImage(manifestContent) + const imageRef = parseImageRef(image, opts) + const sbom = generateImageSBOM(imageRef, opts) + return { + ecosystem, + content: JSON.stringify(sbom), + contentType: 'application/vnd.cyclonedx+json' + } +} + +/** + * Provide content and content type for Dockerfile component analysis. + * @param {string} manifest path to the Dockerfile + * @param {{}} [opts={}] optional various options to pass along the application + * @returns {Promise} + */ +async function provideComponent(manifest, opts = {}) { + return getImageSBOM(manifest, opts) +} + +/** + * Provide content and content type for Dockerfile stack analysis. + * @param {string} manifest path to the Dockerfile + * @param {{}} [opts={}] optional various options to pass along the application + * @returns {Promise} + */ +async function provideStack(manifest, opts = {}) { + return getImageSBOM(manifest, opts) +} + +/** + * Dockerfiles contain no license information. + * @returns {null} always null + */ +function readLicenseFromManifest() { return null; } diff --git a/test/providers/oci_dockerfile.test.js b/test/providers/oci_dockerfile.test.js new file mode 100644 index 00000000..c6636fc8 --- /dev/null +++ b/test/providers/oci_dockerfile.test.js @@ -0,0 +1,118 @@ +import { expect } from 'chai' + +import dockerfileProvider, { parseFromImage } from '../../src/providers/oci_dockerfile.js' + +suite('testing the Dockerfile/Containerfile data provider', () => { + + suite('isSupported', () => { + /** Verifies that isSupported returns true for Dockerfile and Containerfile, including suffixed variants. */ + ['Dockerfile', 'Containerfile', 'Dockerfile.dev', 'Dockerfile.prod', 'Containerfile.backend'].forEach(name => { + test(`returns true for ${name}`, () => { + expect(dockerfileProvider.isSupported(name)).to.equal(true) + }) + }); + + ['package.json', 'go.mod', 'Cargo.toml', 'dockerfile', 'containerfile', 'Dockerfilesomething', 'Containerfilesomething', 'Dockerfile.', 'Containerfile.'].forEach(name => { + test(`returns false for ${name}`, () => { + expect(dockerfileProvider.isSupported(name)).to.equal(false) + }) + }) + }) + + suite('validateLockFile', () => { + /** Verifies that validateLockFile always returns true since Dockerfiles have no lock file. */ + test('always returns true', () => { + expect(dockerfileProvider.validateLockFile()).to.equal(true) + }) + }) + + suite('readLicenseFromManifest', () => { + /** Verifies that readLicenseFromManifest returns null since Dockerfiles have no license info. */ + test('returns null', () => { + expect(dockerfileProvider.readLicenseFromManifest()).to.equal(null) + }) + }) + + suite('packageManagerName', () => { + /** Verifies that packageManagerName returns oci. */ + test('returns oci', () => { + expect(dockerfileProvider.packageManagerName()).to.equal('oci') + }) + }) + + suite('parseFromImage', () => { + /** Verifies that a single FROM line extracts the correct image reference. */ + test('extracts image from single-stage Dockerfile', async () => { + const content = 'FROM node:18\nRUN npm install\n' + expect(await parseFromImage(content)).to.equal('node:18') + }) + + /** Verifies that the last FROM line is used in multi-stage Dockerfiles. */ + test('uses last FROM in multi-stage Dockerfile', async () => { + const content = [ + 'FROM node:18 AS builder', + 'RUN npm run build', + '', + 'FROM nginx:alpine', + 'COPY --from=builder /app/dist /usr/share/nginx/html', + ].join('\n') + expect(await parseFromImage(content)).to.equal('nginx:alpine') + }) + + /** Verifies that a single --platform flag is skipped when parsing FROM lines. */ + test('handles --platform flag', async () => { + const content = 'FROM --platform=linux/amd64 ubuntu:22.04\n' + expect(await parseFromImage(content)).to.equal('ubuntu:22.04') + }) + + /** Verifies that multiple flags before the image reference are all skipped. */ + test('handles multiple flags before image', async () => { + const content = 'FROM --platform=linux/amd64 --some-flag=value ubuntu:22.04 AS base\n' + expect(await parseFromImage(content)).to.equal('ubuntu:22.04') + }) + + /** Verifies that image references with digests are parsed correctly. */ + test('handles image with digest', async () => { + const content = 'FROM httpd@sha256:abc123\n' + expect(await parseFromImage(content)).to.equal('httpd@sha256:abc123') + }) + + /** Verifies that ARG-substituted FROM targets are rejected with a clear error. */ + test('throws when FROM target uses ARG substitution', async () => { + const content = 'ARG BASE_IMAGE=ubuntu:22.04\nFROM ${BASE_IMAGE}\n' + try { + await parseFromImage(content) + expect.fail('should have thrown') + } catch (e) { + expect(e.message).to.include('Dockerfile uses ARG substitution in FROM line') + } + }) + + /** Verifies that an error is thrown when no FROM line is present. */ + test('throws when no FROM line found', async () => { + const content = 'RUN echo hello\n' + try { + await parseFromImage(content) + expect.fail('should have thrown') + } catch (e) { + expect(e.message).to.include('No FROM line found in Dockerfile') + } + }) + + /** Verifies that FROM line parsing is case-insensitive. */ + test('handles case-insensitive FROM keyword', async () => { + const content = 'from alpine:3.18\n' + expect(await parseFromImage(content)).to.equal('alpine:3.18') + }) + + /** Verifies that comment lines and blank lines are ignored. */ + test('ignores comments and blank lines', async () => { + const content = [ + '# This is a comment', + '', + 'FROM registry.example.com/myapp:latest', + ].join('\n') + expect(await parseFromImage(content)).to.equal('registry.example.com/myapp:latest') + }) + }) +})