@trustify-da/trustify-da-javascript-client 0.3.0-ea.b8af0f8 → 0.3.0-ea.bbe2094
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -8
- package/dist/package.json +6 -5
- package/dist/src/analysis.js +3 -2
- package/dist/src/cli.js +51 -2
- package/dist/src/cyclone_dx_sbom.d.ts +14 -1
- package/dist/src/cyclone_dx_sbom.js +32 -5
- package/dist/src/index.d.ts +74 -3
- package/dist/src/index.js +89 -6
- package/dist/src/oci_image/utils.js +11 -2
- package/dist/src/provider.js +8 -0
- package/dist/src/providers/base_java.d.ts +0 -9
- package/dist/src/providers/base_java.js +2 -38
- package/dist/src/providers/base_javascript.d.ts +6 -0
- package/dist/src/providers/base_javascript.js +37 -6
- package/dist/src/providers/base_pyproject.d.ts +153 -0
- package/dist/src/providers/base_pyproject.js +315 -0
- package/dist/src/providers/golang_gomodules.d.ts +21 -12
- package/dist/src/providers/golang_gomodules.js +164 -118
- package/dist/src/providers/gomod_parser.d.ts +4 -0
- package/dist/src/providers/gomod_parser.js +16 -0
- package/dist/src/providers/java_gradle.d.ts +19 -0
- package/dist/src/providers/java_gradle.js +116 -2
- package/dist/src/providers/java_maven.d.ts +8 -0
- package/dist/src/providers/java_maven.js +93 -1
- package/dist/src/providers/javascript_bun.d.ts +10 -0
- package/dist/src/providers/javascript_bun.js +100 -0
- package/dist/src/providers/javascript_npm.d.ts +1 -0
- package/dist/src/providers/javascript_npm.js +21 -0
- package/dist/src/providers/javascript_pnpm.js +6 -2
- package/dist/src/providers/manifest.d.ts +2 -0
- package/dist/src/providers/manifest.js +22 -4
- package/dist/src/providers/marker_evaluator.d.ts +14 -0
- package/dist/src/providers/marker_evaluator.js +191 -0
- package/dist/src/providers/processors/yarn_berry_processor.js +88 -5
- package/dist/src/providers/python_controller.d.ts +5 -1
- package/dist/src/providers/python_controller.js +8 -4
- package/dist/src/providers/python_pip.d.ts +4 -0
- package/dist/src/providers/python_pip.js +5 -5
- package/dist/src/providers/python_pip_pyproject.d.ts +61 -0
- package/dist/src/providers/python_pip_pyproject.js +146 -0
- package/dist/src/providers/python_poetry.d.ts +75 -0
- package/dist/src/providers/python_poetry.js +238 -0
- package/dist/src/providers/python_uv.d.ts +55 -0
- package/dist/src/providers/python_uv.js +227 -0
- package/dist/src/providers/tree-sitter-gomod.wasm +0 -0
- package/dist/src/sbom.d.ts +14 -1
- package/dist/src/sbom.js +13 -2
- package/dist/src/tools.d.ts +26 -0
- package/dist/src/tools.js +58 -0
- package/dist/src/workspace.d.ts +9 -0
- package/dist/src/workspace.js +1 -1
- package/package.json +7 -6
package/dist/src/index.js
CHANGED
|
@@ -6,14 +6,18 @@ import analysis from './analysis.js';
|
|
|
6
6
|
import fs from 'node:fs';
|
|
7
7
|
import { getCustom } from "./tools.js";
|
|
8
8
|
import { resolveBatchMetadata, resolveContinueOnError } from './batch_opts.js';
|
|
9
|
+
import { discoverMavenModules } from './providers/java_maven.js';
|
|
10
|
+
import { discoverGradleSubprojects } from './providers/java_gradle.js';
|
|
11
|
+
import { discoverGoWorkspaceModules } from './providers/golang_gomodules.js';
|
|
12
|
+
import { discoverUvWorkspaceMembers } from './providers/python_uv.js';
|
|
9
13
|
import { discoverWorkspaceCrates, discoverWorkspacePackages, filterManifestPathsByDiscoveryIgnore, resolveWorkspaceDiscoveryIgnore, validatePackageJson, } from './workspace.js';
|
|
10
14
|
import.meta.dirname;
|
|
11
15
|
import * as url from 'url';
|
|
12
16
|
export { parseImageRef } from "./oci_image/utils.js";
|
|
13
17
|
export { ImageRef } from "./oci_image/images.js";
|
|
14
18
|
export { getProjectLicense, findLicenseFilePath, identifyLicense, getLicenseDetails, licensesFromReport, normalizeLicensesResponse, runLicenseCheck, getCompatibility } from "./license/index.js";
|
|
15
|
-
export default { componentAnalysis, stackAnalysis, stackAnalysisBatch, imageAnalysis, validateToken };
|
|
16
|
-
export { discoverWorkspacePackages, discoverWorkspaceCrates, validatePackageJson, resolveWorkspaceDiscoveryIgnore, filterManifestPathsByDiscoveryIgnore, resolveContinueOnError, resolveBatchMetadata, };
|
|
19
|
+
export default { componentAnalysis, stackAnalysis, stackAnalysisBatch, imageAnalysis, validateToken, generateSbom };
|
|
20
|
+
export { discoverMavenModules, discoverGradleSubprojects, discoverGoWorkspaceModules, discoverUvWorkspaceMembers, discoverWorkspacePackages, discoverWorkspaceCrates, validatePackageJson, resolveWorkspaceDiscoveryIgnore, filterManifestPathsByDiscoveryIgnore, resolveContinueOnError, resolveBatchMetadata, };
|
|
17
21
|
/**
|
|
18
22
|
* @typedef {{
|
|
19
23
|
* TRUSTIFY_DA_CARGO_PATH?: string | undefined,
|
|
@@ -56,13 +60,15 @@ export { discoverWorkspacePackages, discoverWorkspaceCrates, validatePackageJson
|
|
|
56
60
|
* TRUSTIFY_DA_CONTINUE_ON_ERROR?: string | undefined,
|
|
57
61
|
* batchMetadata?: boolean | undefined,
|
|
58
62
|
* TRUSTIFY_DA_BATCH_METADATA?: string | undefined,
|
|
63
|
+
* TRUSTIFY_DA_UV_PATH?: string | undefined,
|
|
64
|
+
* TRUSTIFY_DA_POETRY_PATH?: string | undefined,
|
|
59
65
|
* [key: string]: string | number | boolean | string[] | undefined,
|
|
60
66
|
* }} Options
|
|
61
67
|
*/
|
|
62
68
|
/**
|
|
63
69
|
* @typedef {{
|
|
64
70
|
* workspaceRoot: string,
|
|
65
|
-
* ecosystem: 'javascript' | 'cargo' | 'unknown',
|
|
71
|
+
* ecosystem: 'javascript' | 'cargo' | 'pyproject' | 'unknown',
|
|
66
72
|
* total: number,
|
|
67
73
|
* successful: number,
|
|
68
74
|
* failed: number,
|
|
@@ -235,6 +241,22 @@ function buildBatchAnalysisMetadata(root, ecosystem, totalSbomAttempts, successf
|
|
|
235
241
|
errors: [...errors],
|
|
236
242
|
};
|
|
237
243
|
}
|
|
244
|
+
/**
|
|
245
|
+
* Generate a CycloneDX SBOM from a manifest file. No backend HTTP request is made.
|
|
246
|
+
*
|
|
247
|
+
* @param {string} manifestPath - path to the manifest file (e.g. pom.xml, package.json)
|
|
248
|
+
* @param {Options} [opts={}] - optional options (e.g. workspace dir, tool paths)
|
|
249
|
+
* @returns {Promise<object>} parsed CycloneDX SBOM JSON object
|
|
250
|
+
* @throws {Error} if the manifest is unsupported or SBOM generation fails
|
|
251
|
+
*/
|
|
252
|
+
export async function generateSbom(manifestPath, opts = {}) {
|
|
253
|
+
fs.accessSync(manifestPath, fs.constants.R_OK);
|
|
254
|
+
const result = await generateOneSbom(manifestPath, opts);
|
|
255
|
+
if (!result.ok) {
|
|
256
|
+
throw new Error(`Failed to generate SBOM for ${result.manifestPath}: ${result.reason}`);
|
|
257
|
+
}
|
|
258
|
+
return result.sbom;
|
|
259
|
+
}
|
|
238
260
|
/**
|
|
239
261
|
* @typedef {{ ok: true, purl: string, sbom: object } | { ok: false, manifestPath: string, reason: string }} SbomResult
|
|
240
262
|
*/
|
|
@@ -261,17 +283,45 @@ async function generateOneSbom(manifestPath, workspaceOpts) {
|
|
|
261
283
|
*
|
|
262
284
|
* @param {string} root - Resolved workspace root
|
|
263
285
|
* @param {Options} opts
|
|
264
|
-
* @returns {Promise<{ ecosystem: 'javascript' | 'cargo' | 'unknown', manifestPaths: string[] }>}
|
|
286
|
+
* @returns {Promise<{ ecosystem: 'javascript' | 'cargo' | 'maven' | 'gradle' | 'gomodules' | 'pyproject' | 'unknown', manifestPaths: string[] }>}
|
|
265
287
|
* @private
|
|
266
288
|
*/
|
|
267
289
|
async function detectWorkspaceManifests(root, opts) {
|
|
268
290
|
const cargoToml = path.join(root, 'Cargo.toml');
|
|
269
291
|
const cargoLock = path.join(root, 'Cargo.lock');
|
|
270
292
|
const packageJson = path.join(root, 'package.json');
|
|
293
|
+
const pomXml = path.join(root, 'pom.xml');
|
|
271
294
|
if (fs.existsSync(cargoToml) && fs.existsSync(cargoLock)) {
|
|
272
295
|
return { ecosystem: 'cargo', manifestPaths: await discoverWorkspaceCrates(root, opts) };
|
|
273
296
|
}
|
|
274
|
-
|
|
297
|
+
if (fs.existsSync(pomXml)) {
|
|
298
|
+
const manifestPaths = await discoverMavenModules(root, opts);
|
|
299
|
+
if (manifestPaths.length > 0) {
|
|
300
|
+
return { ecosystem: 'maven', manifestPaths };
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
const hasGradleSettings = fs.existsSync(path.join(root, 'settings.gradle'))
|
|
304
|
+
|| fs.existsSync(path.join(root, 'settings.gradle.kts'));
|
|
305
|
+
if (hasGradleSettings) {
|
|
306
|
+
const manifestPaths = await discoverGradleSubprojects(root, opts);
|
|
307
|
+
if (manifestPaths.length > 0) {
|
|
308
|
+
return { ecosystem: 'gradle', manifestPaths };
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
if (fs.existsSync(path.join(root, 'go.work'))) {
|
|
312
|
+
const manifestPaths = await discoverGoWorkspaceModules(root, opts);
|
|
313
|
+
if (manifestPaths.length > 0) {
|
|
314
|
+
return { ecosystem: 'gomodules', manifestPaths };
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
if (fs.existsSync(path.join(root, 'pyproject.toml')) && fs.existsSync(path.join(root, 'uv.lock'))) {
|
|
318
|
+
const manifestPaths = await discoverUvWorkspaceMembers(root, opts);
|
|
319
|
+
if (manifestPaths.length > 0) {
|
|
320
|
+
return { ecosystem: 'pyproject', manifestPaths };
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
const hasJsLock = fs.existsSync(path.join(root, 'bun.lock'))
|
|
324
|
+
|| fs.existsSync(path.join(root, 'pnpm-lock.yaml'))
|
|
275
325
|
|| fs.existsSync(path.join(root, 'yarn.lock'))
|
|
276
326
|
|| fs.existsSync(path.join(root, 'package-lock.json'));
|
|
277
327
|
if (fs.existsSync(packageJson) && hasJsLock) {
|
|
@@ -380,12 +430,45 @@ function batchError(message, wantMetadata, metadata) {
|
|
|
380
430
|
}
|
|
381
431
|
return err;
|
|
382
432
|
}
|
|
433
|
+
/**
|
|
434
|
+
* @overload
|
|
435
|
+
* @param {string} workspaceRoot
|
|
436
|
+
* @param {true} html
|
|
437
|
+
* @param {Options & { batchMetadata: true }} opts
|
|
438
|
+
* @returns {Promise<{ analysis: string, metadata: BatchAnalysisMetadata }>}
|
|
439
|
+
* @throws {Error}
|
|
440
|
+
*/
|
|
441
|
+
/**
|
|
442
|
+
* @overload
|
|
443
|
+
* @param {string} workspaceRoot
|
|
444
|
+
* @param {true} html
|
|
445
|
+
* @param {Options & { batchMetadata?: false }} [opts={}]
|
|
446
|
+
* @returns {Promise<string>}
|
|
447
|
+
* @throws {Error}
|
|
448
|
+
*/
|
|
449
|
+
/**
|
|
450
|
+
* @overload
|
|
451
|
+
* @param {string} workspaceRoot
|
|
452
|
+
* @param {false} html
|
|
453
|
+
* @param {Options & { batchMetadata: true }} opts
|
|
454
|
+
* @returns {Promise<{ analysis: Object.<string, import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport').AnalysisReport>, metadata: BatchAnalysisMetadata }>}
|
|
455
|
+
* @throws {Error}
|
|
456
|
+
*/
|
|
457
|
+
/**
|
|
458
|
+
* @overload
|
|
459
|
+
* @param {string} workspaceRoot
|
|
460
|
+
* @param {false} html
|
|
461
|
+
* @param {Options & { batchMetadata?: false }} [opts={}]
|
|
462
|
+
* @returns {Promise<Object.<string, import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport').AnalysisReport>>}
|
|
463
|
+
* @throws {Error}
|
|
464
|
+
*/
|
|
383
465
|
/**
|
|
384
466
|
* Get stack analysis for all workspace packages/crates (batch).
|
|
385
467
|
* Detects ecosystem from workspace root: Cargo (Cargo.toml + Cargo.lock) or JS/TS (package.json + lock file).
|
|
386
468
|
* SBOMs are generated in parallel (see `batchConcurrency`) unless `continueOnError: false` (fail-fast sequential).
|
|
387
469
|
* With `opts.batchMetadata` / `TRUSTIFY_DA_BATCH_METADATA`, returns `{ analysis, metadata }` including validation and SBOM errors.
|
|
388
470
|
*
|
|
471
|
+
* @overload
|
|
389
472
|
* @param {string} workspaceRoot - Path to workspace root (containing lock file and workspace config)
|
|
390
473
|
* @param {boolean} [html=false] - true returns HTML, false returns JSON report
|
|
391
474
|
* @param {Options} [opts={}] - `batchConcurrency`, discovery ignores, `continueOnError` (default true), `batchMetadata` (default false)
|
|
@@ -416,7 +499,7 @@ async function stackAnalysisBatch(workspaceRoot, html = false, opts = {}) {
|
|
|
416
499
|
}
|
|
417
500
|
}
|
|
418
501
|
if (manifestPaths.length === 0) {
|
|
419
|
-
throw new Error(`No workspace manifests found at ${root}. Ensure Cargo.toml+Cargo.lock or package.json+lock file
|
|
502
|
+
throw new Error(`No workspace manifests found at ${root}. Ensure a supported workspace root exists (Cargo.toml+Cargo.lock, pom.xml, build.gradle, go.work, pyproject.toml+uv.lock, or package.json+lock file).`);
|
|
420
503
|
}
|
|
421
504
|
const workspaceOpts = { ...opts, TRUSTIFY_DA_WORKSPACE_DIR: root };
|
|
422
505
|
const concurrency = resolveBatchConcurrency(opts);
|
|
@@ -135,7 +135,7 @@ function execSyft(imageRef, opts = {}) {
|
|
|
135
135
|
function getSyftEnvs(dockerPath, podmanPath) {
|
|
136
136
|
let path = null;
|
|
137
137
|
if (dockerPath && podmanPath) {
|
|
138
|
-
path = `${dockerPath}${
|
|
138
|
+
path = `${dockerPath}${delimiter}${podmanPath}`;
|
|
139
139
|
}
|
|
140
140
|
else if (dockerPath) {
|
|
141
141
|
path = dockerPath;
|
|
@@ -276,7 +276,16 @@ function podmanGetVariant(opts = {}) {
|
|
|
276
276
|
* @returns {string} - The information
|
|
277
277
|
*/
|
|
278
278
|
function dockerPodmanInfo(dockerSupplier, podmanSupplier, opts = {}) {
|
|
279
|
-
|
|
279
|
+
try {
|
|
280
|
+
const result = dockerSupplier(opts);
|
|
281
|
+
if (result) {
|
|
282
|
+
return result;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
catch (_) {
|
|
286
|
+
// docker not available, fall through to podman
|
|
287
|
+
}
|
|
288
|
+
return podmanSupplier(opts);
|
|
280
289
|
}
|
|
281
290
|
/**
|
|
282
291
|
* Gets the digests for an image
|
package/dist/src/provider.js
CHANGED
|
@@ -3,10 +3,14 @@ import golangGomodulesProvider from './providers/golang_gomodules.js';
|
|
|
3
3
|
import Java_gradle_groovy from "./providers/java_gradle_groovy.js";
|
|
4
4
|
import Java_gradle_kotlin from "./providers/java_gradle_kotlin.js";
|
|
5
5
|
import Java_maven from "./providers/java_maven.js";
|
|
6
|
+
import Javascript_bun from './providers/javascript_bun.js';
|
|
6
7
|
import Javascript_npm from './providers/javascript_npm.js';
|
|
7
8
|
import Javascript_pnpm from './providers/javascript_pnpm.js';
|
|
8
9
|
import Javascript_yarn from './providers/javascript_yarn.js';
|
|
9
10
|
import pythonPipProvider from './providers/python_pip.js';
|
|
11
|
+
import Python_pip_pyproject from './providers/python_pip_pyproject.js';
|
|
12
|
+
import Python_poetry from './providers/python_poetry.js';
|
|
13
|
+
import Python_uv from './providers/python_uv.js';
|
|
10
14
|
import rustCargoProvider from './providers/rust_cargo.js';
|
|
11
15
|
/** @typedef {{ecosystem: string, contentType: string, content: string}} Provided */
|
|
12
16
|
/** @typedef {{isSupported: function(string): boolean, validateLockFile: function(string, Object): void, provideComponent: function(string, {}): Provided | Promise<Provided>, provideStack: function(string, {}): Provided | Promise<Provided>, readLicenseFromManifest: function(string): string | null}} Provider */
|
|
@@ -18,11 +22,15 @@ export const availableProviders = [
|
|
|
18
22
|
new Java_maven(),
|
|
19
23
|
new Java_gradle_groovy(),
|
|
20
24
|
new Java_gradle_kotlin(),
|
|
25
|
+
new Javascript_bun(),
|
|
21
26
|
new Javascript_pnpm(),
|
|
22
27
|
new Javascript_yarn(),
|
|
23
28
|
new Javascript_npm(),
|
|
24
29
|
golangGomodulesProvider,
|
|
25
30
|
pythonPipProvider,
|
|
31
|
+
new Python_poetry(),
|
|
32
|
+
new Python_uv(),
|
|
33
|
+
new Python_pip_pyproject(),
|
|
26
34
|
rustCargoProvider
|
|
27
35
|
];
|
|
28
36
|
/**
|
|
@@ -57,15 +57,6 @@ export default class Base_Java {
|
|
|
57
57
|
* @returns string
|
|
58
58
|
*/
|
|
59
59
|
selectToolBinary(manifestPath: string, opts: {}): string | null;
|
|
60
|
-
/**
|
|
61
|
-
*
|
|
62
|
-
* @param {string} startingManifest - the path of the manifest from which to start searching for the wrapper
|
|
63
|
-
* @param {string} repoRoot - the root of the repository at which point to stop searching for mvnw, derived via git if unset and then fallsback
|
|
64
|
-
* to the root of the drive the manifest is on (assumes absolute path is given)
|
|
65
|
-
* @returns {string|undefined}
|
|
66
|
-
*/
|
|
67
|
-
traverseForWrapper(startingManifest: string, repoRoot?: string): string | undefined;
|
|
68
|
-
normalizePath(thePath: any): string;
|
|
69
60
|
#private;
|
|
70
61
|
}
|
|
71
62
|
export type Provided = import("../provider").Provided;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
1
|
import path from 'node:path';
|
|
3
2
|
import { PackageURL } from 'packageurl-js';
|
|
4
|
-
import { getCustomPath,
|
|
3
|
+
import { getCustomPath, getWrapperPreference, invokeCommand, traverseForWrapper } from "../tools.js";
|
|
5
4
|
/** @typedef {import('../provider').Provider} */
|
|
6
5
|
/** @typedef {import('../provider').Provided} Provided */
|
|
7
6
|
/** @typedef {{name: string, version: string}} Package */
|
|
@@ -131,7 +130,7 @@ export default class Base_Java {
|
|
|
131
130
|
const toolPath = getCustomPath(this.globalBinary, opts);
|
|
132
131
|
const useWrapper = getWrapperPreference(this.globalBinary, opts);
|
|
133
132
|
if (useWrapper) {
|
|
134
|
-
const wrapper = this.
|
|
133
|
+
const wrapper = traverseForWrapper(manifestDir, this.localWrapper);
|
|
135
134
|
if (wrapper !== undefined) {
|
|
136
135
|
try {
|
|
137
136
|
this._invokeCommand(wrapper, ['--version'], { cwd: manifestDir });
|
|
@@ -156,39 +155,4 @@ export default class Base_Java {
|
|
|
156
155
|
}
|
|
157
156
|
return toolPath;
|
|
158
157
|
}
|
|
159
|
-
/**
|
|
160
|
-
*
|
|
161
|
-
* @param {string} startingManifest - the path of the manifest from which to start searching for the wrapper
|
|
162
|
-
* @param {string} repoRoot - the root of the repository at which point to stop searching for mvnw, derived via git if unset and then fallsback
|
|
163
|
-
* to the root of the drive the manifest is on (assumes absolute path is given)
|
|
164
|
-
* @returns {string|undefined}
|
|
165
|
-
*/
|
|
166
|
-
traverseForWrapper(startingManifest, repoRoot = undefined) {
|
|
167
|
-
const normalizedManifest = this.normalizePath(startingManifest);
|
|
168
|
-
const currentDir = this.normalizePath(path.dirname(normalizedManifest));
|
|
169
|
-
repoRoot = repoRoot || getGitRootDir(currentDir) || path.parse(normalizedManifest).root;
|
|
170
|
-
const wrapperPath = path.join(currentDir, this.localWrapper);
|
|
171
|
-
try {
|
|
172
|
-
fs.accessSync(wrapperPath, fs.constants.X_OK);
|
|
173
|
-
return wrapperPath;
|
|
174
|
-
}
|
|
175
|
-
catch (error) {
|
|
176
|
-
if (error.code === 'ENOENT') {
|
|
177
|
-
const rootDir = path.parse(currentDir).root;
|
|
178
|
-
if (currentDir === repoRoot || currentDir === rootDir) {
|
|
179
|
-
return undefined;
|
|
180
|
-
}
|
|
181
|
-
const parentDir = path.dirname(currentDir);
|
|
182
|
-
if (parentDir === currentDir || parentDir === rootDir) {
|
|
183
|
-
return undefined;
|
|
184
|
-
}
|
|
185
|
-
return this.traverseForWrapper(path.join(parentDir, path.basename(normalizedManifest)), repoRoot);
|
|
186
|
-
}
|
|
187
|
-
throw new Error(`failure searching for ${this.localWrapper}`, { cause: error });
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
normalizePath(thePath) {
|
|
191
|
-
const normalized = path.resolve(thePath).normalize();
|
|
192
|
-
return process.platform === 'win32' ? normalized.toLowerCase() : normalized;
|
|
193
|
-
}
|
|
194
158
|
}
|
|
@@ -136,6 +136,12 @@ export default class Base_javascript {
|
|
|
136
136
|
*/
|
|
137
137
|
protected _version(): string;
|
|
138
138
|
/**
|
|
139
|
+
* Creates or updates the lock file for the package manager
|
|
140
|
+
* @param {string} manifestDir - Directory containing the manifest file
|
|
141
|
+
* @protected
|
|
142
|
+
*/
|
|
143
|
+
protected _createLockFile(manifestDir: string): void;
|
|
144
|
+
/**
|
|
139
145
|
* Parses the dependency tree output
|
|
140
146
|
* @param {string} output - The output to parse
|
|
141
147
|
* @returns {string} The parsed output
|
|
@@ -217,7 +217,7 @@ export default class Base_javascript {
|
|
|
217
217
|
this._version();
|
|
218
218
|
const manifestDir = path.dirname(this.#manifest.manifestPath);
|
|
219
219
|
const cmdDir = this._findLockFileDir(manifestDir, opts) || manifestDir;
|
|
220
|
-
this
|
|
220
|
+
this._createLockFile(cmdDir);
|
|
221
221
|
let output = this.#executeListCmd(includeTransitive, cmdDir);
|
|
222
222
|
output = this._parseDepTreeOutput(output);
|
|
223
223
|
return JSON.parse(output);
|
|
@@ -235,9 +235,32 @@ export default class Base_javascript {
|
|
|
235
235
|
let sbom = new Sbom();
|
|
236
236
|
sbom.addRoot(mainComponent, license);
|
|
237
237
|
this._addDependenciesToSbom(sbom, depsObject);
|
|
238
|
+
this.#ensurePeerAndOptionalDeps(sbom);
|
|
238
239
|
sbom.filterIgnoredDeps(this.#manifest.ignored);
|
|
239
240
|
return sbom.getAsJsonString(opts);
|
|
240
241
|
}
|
|
242
|
+
/**
|
|
243
|
+
* Ensures peer and optional dependencies declared in the manifest are
|
|
244
|
+
* present in the SBOM, even when the package manager does not resolve them
|
|
245
|
+
* (e.g. yarn does not include peer deps in its dependency listing).
|
|
246
|
+
* @param {Sbom} sbom - The SBOM to supplement
|
|
247
|
+
* @private
|
|
248
|
+
*/
|
|
249
|
+
#ensurePeerAndOptionalDeps(sbom) {
|
|
250
|
+
const rootPurl = toPurl(purlType, this.#manifest.name, this.#manifest.version);
|
|
251
|
+
const depSources = [this.#manifest.peerDependencies, this.#manifest.optionalDependencies];
|
|
252
|
+
for (const source of depSources) {
|
|
253
|
+
for (const [name, version] of Object.entries(source)) {
|
|
254
|
+
// Build the purl prefix for exact matching (e.g. "pkg:npm/minimist@"
|
|
255
|
+
// or "pkg:npm/%40hapi/joi@") to avoid substring false positives
|
|
256
|
+
const probe = toPurl(purlType, name, version);
|
|
257
|
+
const purlPrefix = probe.toString().replace(/@[^@]*$/, '@');
|
|
258
|
+
if (!sbom.checkDependsOnByPurlPrefix(rootPurl, purlPrefix)) {
|
|
259
|
+
sbom.addDependency(rootPurl, probe);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
241
264
|
/**
|
|
242
265
|
* Recursively builds the Sbom from the JSON that npm listing returns
|
|
243
266
|
* @param {Sbom} sbom - The SBOM object to add dependencies to
|
|
@@ -245,7 +268,10 @@ export default class Base_javascript {
|
|
|
245
268
|
* @protected
|
|
246
269
|
*/
|
|
247
270
|
_addDependenciesToSbom(sbom, depTree) {
|
|
248
|
-
const dependencies =
|
|
271
|
+
const dependencies = {
|
|
272
|
+
...depTree["dependencies"],
|
|
273
|
+
...depTree["optionalDependencies"],
|
|
274
|
+
};
|
|
249
275
|
Object.entries(dependencies)
|
|
250
276
|
.forEach(entry => {
|
|
251
277
|
const [name, artifact] = entry;
|
|
@@ -295,6 +321,7 @@ export default class Base_javascript {
|
|
|
295
321
|
const rootPurl = toPurlFromString(sbom.getRoot().purl);
|
|
296
322
|
sbom.addDependency(rootPurl, rootDeps.get(key));
|
|
297
323
|
}
|
|
324
|
+
this.#ensurePeerAndOptionalDeps(sbom);
|
|
298
325
|
sbom.filterIgnoredDeps(this.#manifest.ignored);
|
|
299
326
|
return sbom.getAsJsonString(opts);
|
|
300
327
|
}
|
|
@@ -305,10 +332,14 @@ export default class Base_javascript {
|
|
|
305
332
|
* @protected
|
|
306
333
|
*/
|
|
307
334
|
_getRootDependencies(depTree) {
|
|
308
|
-
|
|
335
|
+
const allDeps = {
|
|
336
|
+
...depTree.dependencies,
|
|
337
|
+
...depTree.optionalDependencies,
|
|
338
|
+
};
|
|
339
|
+
if (Object.keys(allDeps).length === 0) {
|
|
309
340
|
return new Map();
|
|
310
341
|
}
|
|
311
|
-
return new Map(Object.entries(
|
|
342
|
+
return new Map(Object.entries(allDeps).map(([key, value]) => [key, toPurl(purlType, key, value.version)]));
|
|
312
343
|
}
|
|
313
344
|
/**
|
|
314
345
|
* Executes the list command to get dependencies
|
|
@@ -332,9 +363,9 @@ export default class Base_javascript {
|
|
|
332
363
|
/**
|
|
333
364
|
* Creates or updates the lock file for the package manager
|
|
334
365
|
* @param {string} manifestDir - Directory containing the manifest file
|
|
335
|
-
* @
|
|
366
|
+
* @protected
|
|
336
367
|
*/
|
|
337
|
-
|
|
368
|
+
_createLockFile(manifestDir) {
|
|
338
369
|
const originalDir = process.cwd();
|
|
339
370
|
const isWindows = os.platform() === 'win32';
|
|
340
371
|
if (isWindows) {
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/** @typedef {{name: string, version: string, children: string[], hashes?: Array<{alg: string, content: string}>}} GraphEntry */
|
|
2
|
+
/** @typedef {{name: string, version: string, dependencies: DepTreeEntry[]}} DepTreeEntry */
|
|
3
|
+
/** @typedef {{directDeps: string[], graph: Map<string, GraphEntry>}} DependencyData */
|
|
4
|
+
/** @typedef {{ecosystem: string, content: string, contentType: string}} Provided */
|
|
5
|
+
export default class Base_pyproject {
|
|
6
|
+
/**
|
|
7
|
+
* @param {string} manifestName
|
|
8
|
+
* @returns {boolean}
|
|
9
|
+
*/
|
|
10
|
+
isSupported(manifestName: string): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* @param {string} manifestDir
|
|
13
|
+
* @param {Object} [opts={}]
|
|
14
|
+
* @returns {boolean}
|
|
15
|
+
*/
|
|
16
|
+
validateLockFile(manifestDir: string, opts?: any): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Walk up from manifestDir to find the directory containing the lock file.
|
|
19
|
+
* Follows the same pattern as Base_javascript._findLockFileDir().
|
|
20
|
+
* @param {string} manifestDir
|
|
21
|
+
* @param {Object} [opts={}]
|
|
22
|
+
* @returns {string|null}
|
|
23
|
+
* @protected
|
|
24
|
+
*/
|
|
25
|
+
protected _findLockFileDir(manifestDir: string, opts?: any): string | null;
|
|
26
|
+
/**
|
|
27
|
+
* Detect workspace root boundaries.
|
|
28
|
+
* Currently only uv has native workspace support ([tool.uv.workspace] in pyproject.toml).
|
|
29
|
+
* Poetry has no workspace/monorepo support (python-poetry/poetry#2270), so each
|
|
30
|
+
* poetry project is treated independently — see Python_poetry._findLockFileDir().
|
|
31
|
+
* @param {string} dir
|
|
32
|
+
* @returns {boolean}
|
|
33
|
+
* @protected
|
|
34
|
+
*/
|
|
35
|
+
protected _isWorkspaceRoot(dir: string): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Read project license from pyproject.toml, with fallback to LICENSE file.
|
|
38
|
+
* @param {string} manifestPath
|
|
39
|
+
* @returns {string|null}
|
|
40
|
+
*/
|
|
41
|
+
readLicenseFromManifest(manifestPath: string): string | null;
|
|
42
|
+
/**
|
|
43
|
+
* @param {string} manifest - path to pyproject.toml
|
|
44
|
+
* @param {Object} [opts={}]
|
|
45
|
+
* @returns {Promise<Provided>}
|
|
46
|
+
*/
|
|
47
|
+
provideStack(manifest: string, opts?: any): Promise<Provided>;
|
|
48
|
+
/**
|
|
49
|
+
* @param {string} manifest - path to pyproject.toml
|
|
50
|
+
* @param {Object} [opts={}]
|
|
51
|
+
* @returns {Promise<Provided>}
|
|
52
|
+
*/
|
|
53
|
+
provideComponent(manifest: string, opts?: any): Promise<Provided>;
|
|
54
|
+
/**
|
|
55
|
+
* @returns {string}
|
|
56
|
+
* @protected
|
|
57
|
+
*/
|
|
58
|
+
protected _lockFileName(): string;
|
|
59
|
+
/**
|
|
60
|
+
* @returns {string}
|
|
61
|
+
* @protected
|
|
62
|
+
*/
|
|
63
|
+
protected _cmdName(): string;
|
|
64
|
+
/**
|
|
65
|
+
* Resolve dependencies using the tool-specific command and parser.
|
|
66
|
+
*
|
|
67
|
+
* @param {string} manifestDir - directory containing the target pyproject.toml
|
|
68
|
+
* @param {string} workspaceDir - workspace root (where the lock file lives);
|
|
69
|
+
* only used by providers that need workspace-level resolution (e.g. uv)
|
|
70
|
+
* @param {object} parsed - parsed pyproject.toml
|
|
71
|
+
* @param {Object} opts
|
|
72
|
+
* @returns {Promise<DependencyData>}
|
|
73
|
+
* @protected
|
|
74
|
+
*/
|
|
75
|
+
protected _getDependencyData(manifestDir: string, workspaceDir: string, parsed: object, opts: any): Promise<DependencyData>;
|
|
76
|
+
/**
|
|
77
|
+
* Canonicalize a Python package name per PEP 503.
|
|
78
|
+
* @param {string} name
|
|
79
|
+
* @returns {string}
|
|
80
|
+
* @protected
|
|
81
|
+
*/
|
|
82
|
+
protected _canonicalize(name: string): string;
|
|
83
|
+
/**
|
|
84
|
+
* Get the project name from pyproject.toml.
|
|
85
|
+
* @param {object} parsed
|
|
86
|
+
* @returns {string|null}
|
|
87
|
+
* @protected
|
|
88
|
+
*/
|
|
89
|
+
protected _getProjectName(parsed: object): string | null;
|
|
90
|
+
/**
|
|
91
|
+
* Get the project version from pyproject.toml.
|
|
92
|
+
* @param {object} parsed
|
|
93
|
+
* @returns {string|null}
|
|
94
|
+
* @protected
|
|
95
|
+
*/
|
|
96
|
+
protected _getProjectVersion(parsed: object): string | null;
|
|
97
|
+
/**
|
|
98
|
+
* Scan raw pyproject.toml text for dependencies with ignore markers.
|
|
99
|
+
* @param {string} manifestPath
|
|
100
|
+
* @returns {Set<string>}
|
|
101
|
+
* @protected
|
|
102
|
+
*/
|
|
103
|
+
protected _getIgnoredDeps(manifestPath: string): Set<string>;
|
|
104
|
+
/**
|
|
105
|
+
* Compute the set of graph nodes reachable from direct deps, excluding ignored.
|
|
106
|
+
* @param {Map<string, GraphEntry>} graph
|
|
107
|
+
* @param {string[]} directDeps
|
|
108
|
+
* @param {Set<string>} ignoredDeps
|
|
109
|
+
* @returns {Set<string>}
|
|
110
|
+
* @protected
|
|
111
|
+
*/
|
|
112
|
+
protected _reachableNodes(graph: Map<string, GraphEntry>, directDeps: string[], ignoredDeps: Set<string>): Set<string>;
|
|
113
|
+
/**
|
|
114
|
+
* @param {string} name
|
|
115
|
+
* @param {string} version
|
|
116
|
+
* @returns {PackageURL}
|
|
117
|
+
* @protected
|
|
118
|
+
*/
|
|
119
|
+
protected _toPurl(name: string, version: string): PackageURL;
|
|
120
|
+
/**
|
|
121
|
+
* Create SBOM json string for a pyproject.toml project.
|
|
122
|
+
* @param {string} manifest - path to pyproject.toml
|
|
123
|
+
* @param {Object} opts
|
|
124
|
+
* @param {boolean} includeTransitive
|
|
125
|
+
* @returns {Promise<string>}
|
|
126
|
+
* @private
|
|
127
|
+
*/
|
|
128
|
+
private _createSbom;
|
|
129
|
+
}
|
|
130
|
+
export type GraphEntry = {
|
|
131
|
+
name: string;
|
|
132
|
+
version: string;
|
|
133
|
+
children: string[];
|
|
134
|
+
hashes?: Array<{
|
|
135
|
+
alg: string;
|
|
136
|
+
content: string;
|
|
137
|
+
}>;
|
|
138
|
+
};
|
|
139
|
+
export type DepTreeEntry = {
|
|
140
|
+
name: string;
|
|
141
|
+
version: string;
|
|
142
|
+
dependencies: DepTreeEntry[];
|
|
143
|
+
};
|
|
144
|
+
export type DependencyData = {
|
|
145
|
+
directDeps: string[];
|
|
146
|
+
graph: Map<string, GraphEntry>;
|
|
147
|
+
};
|
|
148
|
+
export type Provided = {
|
|
149
|
+
ecosystem: string;
|
|
150
|
+
content: string;
|
|
151
|
+
contentType: string;
|
|
152
|
+
};
|
|
153
|
+
import { PackageURL } from 'packageurl-js';
|