@trustify-da/trustify-da-javascript-client 0.3.0-ea.0e9ba23 → 0.3.0-ea.29f6867
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 +30 -0
- package/dist/package.json +4 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/license/index.d.ts +2 -2
- package/dist/src/license/index.js +4 -4
- package/dist/src/license/license_utils.d.ts +40 -0
- package/dist/src/license/license_utils.js +134 -0
- package/dist/src/license/project_license.d.ts +9 -6
- package/dist/src/license/project_license.js +12 -78
- package/dist/src/providers/base_javascript.js +7 -5
- package/dist/src/providers/golang_gomodules.js +2 -1
- package/dist/src/providers/java_gradle.js +2 -1
- package/dist/src/providers/java_maven.d.ts +1 -1
- package/dist/src/providers/java_maven.js +10 -9
- package/dist/src/providers/python_pip.js +2 -1
- package/dist/src/providers/requirements_parser.js +5 -8
- package/dist/src/providers/tree-sitter-requirements.wasm +0 -0
- package/package.json +5 -2
- package/dist/src/license/compatibility.d.ts +0 -18
- package/dist/src/license/compatibility.js +0 -45
package/README.md
CHANGED
|
@@ -43,6 +43,21 @@ let imageAnalysisWithArch = await client.imageAnalysis(['httpd:2.4.49^^amd64'])
|
|
|
43
43
|
```
|
|
44
44
|
</li>
|
|
45
45
|
</ul>
|
|
46
|
+
|
|
47
|
+
<h3>License Detection</h3>
|
|
48
|
+
<p>
|
|
49
|
+
The client automatically detects your project's license with intelligent fallback:
|
|
50
|
+
</p>
|
|
51
|
+
<ul>
|
|
52
|
+
<li><strong>Manifest-first:</strong> For ecosystems with license support (Maven, JavaScript), reads from manifest file (<code>pom.xml</code>, <code>package.json</code>)</li>
|
|
53
|
+
<li><strong>LICENSE file fallback:</strong> If no license in manifest, or for ecosystems without license support (Gradle, Go, Python), automatically reads from <code>LICENSE</code>, <code>LICENSE.md</code>, or <code>LICENSE.txt</code></li>
|
|
54
|
+
<li><strong>SBOM integration:</strong> Detected licenses are included in generated SBOMs for all ecosystems</li>
|
|
55
|
+
<li><strong>SPDX support:</strong> Automatically detects common licenses (Apache-2.0, MIT, GPL, BSD) from LICENSE file content</li>
|
|
56
|
+
</ul>
|
|
57
|
+
<p>
|
|
58
|
+
See <a href="./docs/license-resolution-and-compliance.md">License Resolution and Compliance</a> for detailed documentation.
|
|
59
|
+
</p>
|
|
60
|
+
|
|
46
61
|
<ul>
|
|
47
62
|
<li>
|
|
48
63
|
Use as ESM Module from Common-JS module
|
|
@@ -183,6 +198,21 @@ $ trustify-da-javascript-client license /path/to/package.json
|
|
|
183
198
|
<li><a href="https://gradle.org/">Gradle (Groovy and Kotlin DSL)</a> - <a href="https://gradle.org/install/">Gradle Installation</a></li>
|
|
184
199
|
</ul>
|
|
185
200
|
|
|
201
|
+
<h3>License Detection</h3>
|
|
202
|
+
<p>
|
|
203
|
+
The client automatically detects your project's license with intelligent fallback:
|
|
204
|
+
</p>
|
|
205
|
+
<ul>
|
|
206
|
+
<li><strong>Manifest-first:</strong> For ecosystems with license support (Maven, JavaScript), reads from manifest file (<code>pom.xml</code>, <code>package.json</code>)</li>
|
|
207
|
+
<li><strong>LICENSE file fallback:</strong> If no license in manifest, or for ecosystems without license support (Gradle, Go, Python), automatically reads from <code>LICENSE</code>, <code>LICENSE.md</code>, or <code>LICENSE.txt</code></li>
|
|
208
|
+
<li><strong>SBOM integration:</strong> Detected licenses are included in generated SBOMs for all ecosystems</li>
|
|
209
|
+
<li><strong>SPDX support:</strong> Automatically detects common licenses (Apache-2.0, MIT, GPL, BSD) from LICENSE file content</li>
|
|
210
|
+
</ul>
|
|
211
|
+
<p>
|
|
212
|
+
See <a href="./docs/license-resolution-and-compliance.md">License Resolution and Compliance</a> for detailed documentation.
|
|
213
|
+
</p>
|
|
214
|
+
|
|
215
|
+
|
|
186
216
|
<h3>Excluding Packages</h3>
|
|
187
217
|
<p>
|
|
188
218
|
Excluding a package from any analysis can be achieved by marking the package for exclusion.
|
package/dist/package.json
CHANGED
|
@@ -40,8 +40,11 @@
|
|
|
40
40
|
"test": "c8 npm run tests",
|
|
41
41
|
"tests": "mocha --config .mocharc.json --grep \".*analysis module.*\" --invert",
|
|
42
42
|
"tests:rep": "mocha --reporter-option maxDiffSize=0 --reporter json > unit-tests-result.json",
|
|
43
|
+
"pretest": "cp node_modules/tree-sitter-requirements/tree-sitter-requirements.wasm src/providers/tree-sitter-requirements.wasm",
|
|
43
44
|
"precompile": "rm -rf dist",
|
|
44
|
-
"compile": "tsc -p tsconfig.json"
|
|
45
|
+
"compile": "tsc -p tsconfig.json",
|
|
46
|
+
"compile:dev": "tsc -p tsconfig.dev.json",
|
|
47
|
+
"postcompile": "cp node_modules/tree-sitter-requirements/tree-sitter-requirements.wasm dist/src/providers/tree-sitter-requirements.wasm"
|
|
45
48
|
},
|
|
46
49
|
"dependencies": {
|
|
47
50
|
"@babel/core": "^7.23.2",
|
package/dist/src/index.d.ts
CHANGED
|
@@ -131,4 +131,4 @@ declare function imageAnalysis(imageRefs: Array<string>, html?: boolean | undefi
|
|
|
131
131
|
* @throws {Error} if the backend request failed.
|
|
132
132
|
*/
|
|
133
133
|
declare function validateToken(opts?: Options): Promise<object>;
|
|
134
|
-
export { getProjectLicense, findLicenseFilePath,
|
|
134
|
+
export { getProjectLicense, findLicenseFilePath, identifyLicense, getLicenseDetails, licensesFromReport, normalizeLicensesResponse, runLicenseCheck, getCompatibility } from "./license/index.js";
|
package/dist/src/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import.meta.dirname;
|
|
|
8
8
|
import * as url from 'url';
|
|
9
9
|
export { parseImageRef } from "./oci_image/utils.js";
|
|
10
10
|
export { ImageRef } from "./oci_image/images.js";
|
|
11
|
-
export { getProjectLicense, findLicenseFilePath,
|
|
11
|
+
export { getProjectLicense, findLicenseFilePath, identifyLicense, getLicenseDetails, licensesFromReport, normalizeLicensesResponse, runLicenseCheck, getCompatibility } from "./license/index.js";
|
|
12
12
|
export default { componentAnalysis, stackAnalysis, imageAnalysis, validateToken };
|
|
13
13
|
/**
|
|
14
14
|
* @typedef {{
|
|
@@ -23,6 +23,6 @@ export function runLicenseCheck(sbomContent: string, manifestPath: string, url:
|
|
|
23
23
|
}>;
|
|
24
24
|
error?: string;
|
|
25
25
|
}>;
|
|
26
|
-
export { getCompatibility } from "./
|
|
27
|
-
export { getProjectLicense, findLicenseFilePath, identifyLicense
|
|
26
|
+
export { getCompatibility } from "./license_utils.js";
|
|
27
|
+
export { getProjectLicense, findLicenseFilePath, identifyLicense } from "./project_license.js";
|
|
28
28
|
export { licensesFromReport, normalizeLicensesResponse, getLicenseDetails } from "./licenses_api.js";
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { getProjectLicense, findLicenseFilePath, identifyLicense } from './project_license.js';
|
|
5
5
|
import { licensesFromReport, getLicenseDetails } from './licenses_api.js';
|
|
6
|
-
import { getCompatibility } from './
|
|
7
|
-
export { getProjectLicense, findLicenseFilePath, identifyLicense
|
|
6
|
+
import { getCompatibility } from './license_utils.js';
|
|
7
|
+
export { getProjectLicense, findLicenseFilePath, identifyLicense } from './project_license.js';
|
|
8
8
|
export { licensesFromReport, normalizeLicensesResponse, getLicenseDetails } from './licenses_api.js';
|
|
9
|
-
export { getCompatibility } from './
|
|
9
|
+
export { getCompatibility } from './license_utils.js';
|
|
10
10
|
/**
|
|
11
11
|
* Run full license check: resolve project license (with backend identification and details),
|
|
12
12
|
* get dependency licenses from analysis report, and compute incompatibilities.
|
|
@@ -20,7 +20,7 @@ export { getCompatibility } from './compatibility.js';
|
|
|
20
20
|
*/
|
|
21
21
|
export async function runLicenseCheck(sbomContent, manifestPath, url, opts = {}, analysisResult = null) {
|
|
22
22
|
// Resolve project license from manifest and LICENSE file
|
|
23
|
-
const projectLicense = getProjectLicense(manifestPath
|
|
23
|
+
const projectLicense = getProjectLicense(manifestPath);
|
|
24
24
|
// Try backend identification for LICENSE file (more accurate than local pattern matching)
|
|
25
25
|
const licenseFilePath = findLicenseFilePath(manifestPath);
|
|
26
26
|
let backendFileId = null;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Find LICENSE file path in the same directory as the manifest.
|
|
3
|
+
* @param {string} manifestPath
|
|
4
|
+
* @returns {string|null} - path to LICENSE file or null if not found
|
|
5
|
+
*/
|
|
6
|
+
export function findLicenseFilePath(manifestPath: string): string | null;
|
|
7
|
+
/**
|
|
8
|
+
* Very simple SPDX detection from common license text (first ~500 chars).
|
|
9
|
+
* @param {string} text
|
|
10
|
+
* @returns {string|null}
|
|
11
|
+
*/
|
|
12
|
+
export function detectSpdxFromText(text: string): string | null;
|
|
13
|
+
/**
|
|
14
|
+
* Read LICENSE file and detect SPDX identifier.
|
|
15
|
+
* @param {string} manifestPath - path to manifest
|
|
16
|
+
* @returns {string|null} - SPDX identifier from LICENSE file or null
|
|
17
|
+
*/
|
|
18
|
+
export function readLicenseFile(manifestPath: string): string | null;
|
|
19
|
+
/**
|
|
20
|
+
* Get project license from manifest or LICENSE file.
|
|
21
|
+
* Returns manifestLicense if provided, otherwise tries LICENSE file.
|
|
22
|
+
* @param {string|null} manifestLicense - license from manifest (or null)
|
|
23
|
+
* @param {string} manifestPath - path to manifest
|
|
24
|
+
* @returns {string|null} - SPDX identifier or null
|
|
25
|
+
*/
|
|
26
|
+
export function getLicense(manifestLicense: string | null, manifestPath: string): string | null;
|
|
27
|
+
/**
|
|
28
|
+
* Normalize SPDX identifier for comparison (lowercase, strip common suffixes).
|
|
29
|
+
* @param {string} spdxOrName
|
|
30
|
+
* @returns {string}
|
|
31
|
+
*/
|
|
32
|
+
export function normalizeSpdx(spdxOrName: string): string;
|
|
33
|
+
/**
|
|
34
|
+
* Check if a dependency's license is compatible with the project license based on backend categories.
|
|
35
|
+
*
|
|
36
|
+
* @param {string} [projectCategory] - backend category for project license: PERMISSIVE | WEAK_COPYLEFT | STRONG_COPYLEFT | UNKNOWN
|
|
37
|
+
* @param {string} [dependencyCategory] - backend category for dependency license: PERMISSIVE | WEAK_COPYLEFT | STRONG_COPYLEFT | UNKNOWN
|
|
38
|
+
* @returns {'compatible'|'incompatible'|'unknown'}
|
|
39
|
+
*/
|
|
40
|
+
export function getCompatibility(projectCategory?: string, dependencyCategory?: string): "compatible" | "incompatible" | "unknown";
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* License utilities: file reading, SPDX detection, normalization, compatibility.
|
|
3
|
+
* This module has NO dependencies on providers or backend to avoid circular dependencies.
|
|
4
|
+
*/
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
const LICENSE_FILES = ['LICENSE', 'LICENSE.md', 'LICENSE.txt'];
|
|
8
|
+
/**
|
|
9
|
+
* Find LICENSE file path in the same directory as the manifest.
|
|
10
|
+
* @param {string} manifestPath
|
|
11
|
+
* @returns {string|null} - path to LICENSE file or null if not found
|
|
12
|
+
*/
|
|
13
|
+
export function findLicenseFilePath(manifestPath) {
|
|
14
|
+
const manifestDir = path.dirname(path.resolve(manifestPath));
|
|
15
|
+
for (const name of LICENSE_FILES) {
|
|
16
|
+
const filePath = path.join(manifestDir, name);
|
|
17
|
+
try {
|
|
18
|
+
if (fs.statSync(filePath).isFile()) {
|
|
19
|
+
return filePath;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
// skip
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Very simple SPDX detection from common license text (first ~500 chars).
|
|
30
|
+
* @param {string} text
|
|
31
|
+
* @returns {string|null}
|
|
32
|
+
*/
|
|
33
|
+
export function detectSpdxFromText(text) {
|
|
34
|
+
const head = text.slice(0, 500);
|
|
35
|
+
if (/Apache License,?\s*Version 2\.0/i.test(head)) {
|
|
36
|
+
return 'Apache-2.0';
|
|
37
|
+
}
|
|
38
|
+
if (/MIT License/i.test(head) && /Permission is hereby granted/i.test(head)) {
|
|
39
|
+
return 'MIT';
|
|
40
|
+
}
|
|
41
|
+
if (/GNU AFFERO GENERAL PUBLIC LICENSE\s+Version 3/i.test(head)) {
|
|
42
|
+
return 'AGPL-3.0-only';
|
|
43
|
+
}
|
|
44
|
+
if (/GNU LESSER GENERAL PUBLIC LICENSE\s+Version 3/i.test(head)) {
|
|
45
|
+
return 'LGPL-3.0-only';
|
|
46
|
+
}
|
|
47
|
+
if (/GNU LESSER GENERAL PUBLIC LICENSE\s+Version 2\.1/i.test(head)) {
|
|
48
|
+
return 'LGPL-2.1-only';
|
|
49
|
+
}
|
|
50
|
+
if (/GNU GENERAL PUBLIC LICENSE\s+Version 2/i.test(head)) {
|
|
51
|
+
return 'GPL-2.0-only';
|
|
52
|
+
}
|
|
53
|
+
if (/GNU GENERAL PUBLIC LICENSE\s+Version 3/i.test(head)) {
|
|
54
|
+
return 'GPL-3.0-only';
|
|
55
|
+
}
|
|
56
|
+
if (/BSD 2-Clause/i.test(head)) {
|
|
57
|
+
return 'BSD-2-Clause';
|
|
58
|
+
}
|
|
59
|
+
if (/BSD 3-Clause/i.test(head)) {
|
|
60
|
+
return 'BSD-3-Clause';
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Read LICENSE file and detect SPDX identifier.
|
|
66
|
+
* @param {string} manifestPath - path to manifest
|
|
67
|
+
* @returns {string|null} - SPDX identifier from LICENSE file or null
|
|
68
|
+
*/
|
|
69
|
+
export function readLicenseFile(manifestPath) {
|
|
70
|
+
const licenseFilePath = findLicenseFilePath(manifestPath);
|
|
71
|
+
if (!licenseFilePath) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
const content = fs.readFileSync(licenseFilePath, 'utf-8');
|
|
76
|
+
return detectSpdxFromText(content) || content.split('\n')[0]?.trim() || null;
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Get project license from manifest or LICENSE file.
|
|
84
|
+
* Returns manifestLicense if provided, otherwise tries LICENSE file.
|
|
85
|
+
* @param {string|null} manifestLicense - license from manifest (or null)
|
|
86
|
+
* @param {string} manifestPath - path to manifest
|
|
87
|
+
* @returns {string|null} - SPDX identifier or null
|
|
88
|
+
*/
|
|
89
|
+
export function getLicense(manifestLicense, manifestPath) {
|
|
90
|
+
return manifestLicense || readLicenseFile(manifestPath) || null;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Normalize SPDX identifier for comparison (lowercase, strip common suffixes).
|
|
94
|
+
* @param {string} spdxOrName
|
|
95
|
+
* @returns {string}
|
|
96
|
+
*/
|
|
97
|
+
export function normalizeSpdx(spdxOrName) {
|
|
98
|
+
const s = String(spdxOrName).trim().toLowerCase();
|
|
99
|
+
if (s.endsWith(' license')) {
|
|
100
|
+
return s.slice(0, -8);
|
|
101
|
+
}
|
|
102
|
+
return s;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Check if a dependency's license is compatible with the project license based on backend categories.
|
|
106
|
+
*
|
|
107
|
+
* @param {string} [projectCategory] - backend category for project license: PERMISSIVE | WEAK_COPYLEFT | STRONG_COPYLEFT | UNKNOWN
|
|
108
|
+
* @param {string} [dependencyCategory] - backend category for dependency license: PERMISSIVE | WEAK_COPYLEFT | STRONG_COPYLEFT | UNKNOWN
|
|
109
|
+
* @returns {'compatible'|'incompatible'|'unknown'}
|
|
110
|
+
*/
|
|
111
|
+
export function getCompatibility(projectCategory, dependencyCategory) {
|
|
112
|
+
if (!projectCategory || !dependencyCategory) {
|
|
113
|
+
return 'unknown';
|
|
114
|
+
}
|
|
115
|
+
const proj = projectCategory.toUpperCase();
|
|
116
|
+
const dep = dependencyCategory.toUpperCase();
|
|
117
|
+
if (proj === 'UNKNOWN' || dep === 'UNKNOWN') {
|
|
118
|
+
return 'unknown';
|
|
119
|
+
}
|
|
120
|
+
const restrictiveness = {
|
|
121
|
+
'PERMISSIVE': 1,
|
|
122
|
+
'WEAK_COPYLEFT': 2,
|
|
123
|
+
'STRONG_COPYLEFT': 3
|
|
124
|
+
};
|
|
125
|
+
const projLevel = restrictiveness[proj];
|
|
126
|
+
const depLevel = restrictiveness[dep];
|
|
127
|
+
if (projLevel === undefined || depLevel === undefined) {
|
|
128
|
+
return 'unknown';
|
|
129
|
+
}
|
|
130
|
+
if (depLevel > projLevel) {
|
|
131
|
+
return 'incompatible';
|
|
132
|
+
}
|
|
133
|
+
return 'compatible';
|
|
134
|
+
}
|
|
@@ -10,12 +10,6 @@ export function getProjectLicense(manifestPath: string): {
|
|
|
10
10
|
fromFile: string | null;
|
|
11
11
|
mismatch: boolean;
|
|
12
12
|
};
|
|
13
|
-
/**
|
|
14
|
-
* Find LICENSE file path in the same directory as the manifest.
|
|
15
|
-
* @param {string} manifestPath
|
|
16
|
-
* @returns {string|null} - path to LICENSE file or null if not found
|
|
17
|
-
*/
|
|
18
|
-
export function findLicenseFilePath(manifestPath: string): string | null;
|
|
19
13
|
/**
|
|
20
14
|
* Call backend /licenses/identify endpoint to identify license from file.
|
|
21
15
|
* @param {string} licenseFilePath - path to LICENSE file
|
|
@@ -23,3 +17,12 @@ export function findLicenseFilePath(manifestPath: string): string | null;
|
|
|
23
17
|
* @returns {Promise<string|null>} - SPDX identifier or null
|
|
24
18
|
*/
|
|
25
19
|
export function identifyLicense(licenseFilePath: string, opts?: {}): Promise<string | null>;
|
|
20
|
+
/**
|
|
21
|
+
* Get license for SBOM root component with fallback to LICENSE file.
|
|
22
|
+
* Priority: manifest license > LICENSE file > null
|
|
23
|
+
* This is used by providers to populate the license field in the SBOM.
|
|
24
|
+
* @param {string} manifestPath - path to manifest
|
|
25
|
+
* @returns {string|null} - SPDX identifier or null
|
|
26
|
+
*/
|
|
27
|
+
export function getLicenseForSbom(manifestPath: string): string | null;
|
|
28
|
+
export { findLicenseFilePath, readLicenseFile } from "./license_utils.js";
|
|
@@ -7,7 +7,7 @@ import path from 'node:path';
|
|
|
7
7
|
import { selectTrustifyDABackend } from '../index.js';
|
|
8
8
|
import { matchForLicense, availableProviders } from '../provider.js';
|
|
9
9
|
import { addProxyAgent, getTokenHeaders } from '../tools.js';
|
|
10
|
-
|
|
10
|
+
import { normalizeSpdx, readLicenseFile } from './license_utils.js';
|
|
11
11
|
/**
|
|
12
12
|
* Resolve project license from manifest and from LICENSE / LICENSE.md in manifest dir or git root.
|
|
13
13
|
* Uses local pattern matching for LICENSE file identification (synchronous).
|
|
@@ -19,7 +19,7 @@ export function getProjectLicense(manifestPath) {
|
|
|
19
19
|
const resolved = path.resolve(manifestPath);
|
|
20
20
|
const provider = matchForLicense(resolved, availableProviders);
|
|
21
21
|
const fromManifest = provider.readLicenseFromManifest(resolved);
|
|
22
|
-
const fromFile =
|
|
22
|
+
const fromFile = readLicenseFile(resolved);
|
|
23
23
|
const mismatch = Boolean(fromManifest && fromFile && normalizeSpdx(fromManifest) !== normalizeSpdx(fromFile));
|
|
24
24
|
return {
|
|
25
25
|
fromManifest: fromManifest || null,
|
|
@@ -27,26 +27,7 @@ export function getProjectLicense(manifestPath) {
|
|
|
27
27
|
mismatch
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
|
-
|
|
31
|
-
* Find LICENSE file path in the same directory as the manifest.
|
|
32
|
-
* @param {string} manifestPath
|
|
33
|
-
* @returns {string|null} - path to LICENSE file or null if not found
|
|
34
|
-
*/
|
|
35
|
-
export function findLicenseFilePath(manifestPath) {
|
|
36
|
-
const manifestDir = path.dirname(path.resolve(manifestPath));
|
|
37
|
-
for (const name of LICENSE_FILES) {
|
|
38
|
-
const filePath = path.join(manifestDir, name);
|
|
39
|
-
try {
|
|
40
|
-
if (fs.statSync(filePath).isFile()) {
|
|
41
|
-
return filePath;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
catch {
|
|
45
|
-
// skip
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
30
|
+
export { findLicenseFilePath, readLicenseFile } from './license_utils.js';
|
|
50
31
|
/**
|
|
51
32
|
* Call backend /licenses/identify endpoint to identify license from file.
|
|
52
33
|
* @param {string} licenseFilePath - path to LICENSE file
|
|
@@ -57,7 +38,7 @@ export async function identifyLicense(licenseFilePath, opts = {}) {
|
|
|
57
38
|
try {
|
|
58
39
|
const fileContent = fs.readFileSync(licenseFilePath);
|
|
59
40
|
const backendUrl = selectTrustifyDABackend(opts);
|
|
60
|
-
const url = new URL(`${backendUrl}/licenses/identify`);
|
|
41
|
+
const url = new URL(`${backendUrl}/api/v5/licenses/identify`);
|
|
61
42
|
const tokenHeaders = getTokenHeaders(opts);
|
|
62
43
|
const fetchOptions = addProxyAgent({
|
|
63
44
|
method: 'POST',
|
|
@@ -80,60 +61,13 @@ export async function identifyLicense(licenseFilePath, opts = {}) {
|
|
|
80
61
|
}
|
|
81
62
|
}
|
|
82
63
|
/**
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const licenseFilePath = findLicenseFilePath(manifestPath);
|
|
89
|
-
if (!licenseFilePath) {
|
|
90
|
-
return null;
|
|
91
|
-
}
|
|
92
|
-
try {
|
|
93
|
-
const content = fs.readFileSync(licenseFilePath, 'utf-8');
|
|
94
|
-
return detectSpdxFromText(content) || content.split('\n')[0]?.trim() || null;
|
|
95
|
-
}
|
|
96
|
-
catch {
|
|
97
|
-
return null;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* Very simple SPDX detection from common license text (first ~500 chars).
|
|
102
|
-
* @param {string} text
|
|
103
|
-
* @returns {string|null}
|
|
104
|
-
*/
|
|
105
|
-
function detectSpdxFromText(text) {
|
|
106
|
-
const head = text.slice(0, 500);
|
|
107
|
-
if (/Apache License,?\s*Version 2\.0/i.test(head)) {
|
|
108
|
-
return 'Apache-2.0';
|
|
109
|
-
}
|
|
110
|
-
if (/MIT License/i.test(head) && /Permission is hereby granted/i.test(head)) {
|
|
111
|
-
return 'MIT';
|
|
112
|
-
}
|
|
113
|
-
if (/GNU GENERAL PUBLIC LICENSE\s+Version 2/i.test(head)) {
|
|
114
|
-
return 'GPL-2.0-only';
|
|
115
|
-
}
|
|
116
|
-
if (/GNU GENERAL PUBLIC LICENSE\s+Version 3/i.test(head)) {
|
|
117
|
-
return 'GPL-3.0-only';
|
|
118
|
-
}
|
|
119
|
-
if (/BSD 2-Clause/i.test(head)) {
|
|
120
|
-
return 'BSD-2-Clause';
|
|
121
|
-
}
|
|
122
|
-
if (/BSD 3-Clause/i.test(head)) {
|
|
123
|
-
return 'BSD-3-Clause';
|
|
124
|
-
}
|
|
125
|
-
return null;
|
|
126
|
-
}
|
|
127
|
-
/**
|
|
128
|
-
* Normalize for comparison (lowercase, strip common suffixes).
|
|
129
|
-
* @param {string} spdxOrName
|
|
130
|
-
* @returns {string}
|
|
64
|
+
* Get license for SBOM root component with fallback to LICENSE file.
|
|
65
|
+
* Priority: manifest license > LICENSE file > null
|
|
66
|
+
* This is used by providers to populate the license field in the SBOM.
|
|
67
|
+
* @param {string} manifestPath - path to manifest
|
|
68
|
+
* @returns {string|null} - SPDX identifier or null
|
|
131
69
|
*/
|
|
132
|
-
function
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
if (s.endsWith(' license')) {
|
|
136
|
-
return s.slice(0, -8);
|
|
137
|
-
}
|
|
138
|
-
return s;
|
|
70
|
+
export function getLicenseForSbom(manifestPath) {
|
|
71
|
+
const { fromManifest, fromFile } = getProjectLicense(manifestPath);
|
|
72
|
+
return fromManifest || fromFile || null;
|
|
139
73
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from 'node:path';
|
|
4
|
+
import { getLicense } from '../license/license_utils.js';
|
|
4
5
|
import Sbom from '../sbom.js';
|
|
5
6
|
import { getCustom, getCustomPath, invokeCommand, toPurl, toPurlFromString } from "../tools.js";
|
|
6
7
|
import Manifest from './manifest.js';
|
|
@@ -138,21 +139,22 @@ export default class Base_javascript {
|
|
|
138
139
|
* @returns {string|null}
|
|
139
140
|
*/
|
|
140
141
|
readLicenseFromManifest(manifestPath) {
|
|
142
|
+
let manifestLicense;
|
|
141
143
|
try {
|
|
142
144
|
const content = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
|
|
143
145
|
if (typeof content.license === 'string') {
|
|
144
|
-
|
|
146
|
+
manifestLicense = content.license.trim() || null;
|
|
145
147
|
}
|
|
146
|
-
if (Array.isArray(content.licenses) && content.licenses.length > 0) {
|
|
148
|
+
else if (Array.isArray(content.licenses) && content.licenses.length > 0) {
|
|
147
149
|
const first = content.licenses[0];
|
|
148
150
|
const name = first.type || first.name;
|
|
149
|
-
|
|
151
|
+
manifestLicense = (typeof name === 'string' ? name.trim() : null);
|
|
150
152
|
}
|
|
151
|
-
return null;
|
|
152
153
|
}
|
|
153
154
|
catch {
|
|
154
|
-
|
|
155
|
+
manifestLicense = null;
|
|
155
156
|
}
|
|
157
|
+
return getLicense(manifestLicense, manifestPath);
|
|
156
158
|
}
|
|
157
159
|
/**
|
|
158
160
|
* Builds the dependency tree for the project
|
|
@@ -2,6 +2,7 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { EOL } from "os";
|
|
4
4
|
import { PackageURL } from 'packageurl-js';
|
|
5
|
+
import { readLicenseFile } from '../license/license_utils.js';
|
|
5
6
|
import Sbom from '../sbom.js';
|
|
6
7
|
import { getCustom, getCustomPath, invokeCommand } from "../tools.js";
|
|
7
8
|
export default { isSupported, validateLockFile, provideComponent, provideStack, readLicenseFromManifest };
|
|
@@ -28,7 +29,7 @@ function isSupported(manifestName) {
|
|
|
28
29
|
* @returns {string|null}
|
|
29
30
|
*/
|
|
30
31
|
// eslint-disable-next-line no-unused-vars
|
|
31
|
-
function readLicenseFromManifest(manifestPath) { return
|
|
32
|
+
function readLicenseFromManifest(manifestPath) { return readLicenseFile(manifestPath); }
|
|
32
33
|
/**
|
|
33
34
|
* @param {string} manifestDir - the directory where the manifest lies
|
|
34
35
|
*/
|
|
@@ -2,6 +2,7 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { EOL } from 'os';
|
|
4
4
|
import TOML from 'fast-toml';
|
|
5
|
+
import { readLicenseFile } from '../license/license_utils.js';
|
|
5
6
|
import Sbom from '../sbom.js';
|
|
6
7
|
import Base_java, { ecosystem_gradle } from "./base_java.js";
|
|
7
8
|
/** @typedef {import('../provider.js').Provider} */
|
|
@@ -55,7 +56,7 @@ export default class Java_gradle extends Base_java {
|
|
|
55
56
|
* @returns {null}
|
|
56
57
|
*/
|
|
57
58
|
// eslint-disable-next-line no-unused-vars
|
|
58
|
-
readLicenseFromManifest(manifestPath) { return
|
|
59
|
+
readLicenseFromManifest(manifestPath) { return readLicenseFile(manifestPath); }
|
|
59
60
|
/**
|
|
60
61
|
* Provide content and content type for stack analysis.
|
|
61
62
|
* @param {string} manifest - the manifest path or name
|
|
@@ -28,7 +28,7 @@ export default class Java_maven extends Base_java {
|
|
|
28
28
|
*/
|
|
29
29
|
provideComponent(manifest: string, opts?: {}): Provided;
|
|
30
30
|
/**
|
|
31
|
-
* Read license from pom.xml manifest
|
|
31
|
+
* Read license from pom.xml manifest, with fallback to LICENSE file
|
|
32
32
|
* @param {string} manifestPath - path to pom.xml
|
|
33
33
|
* @returns {string|null}
|
|
34
34
|
*/
|
|
@@ -3,6 +3,7 @@ import os from 'node:os';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { EOL } from 'os';
|
|
5
5
|
import { XMLParser } from 'fast-xml-parser';
|
|
6
|
+
import { getLicense } from '../license/license_utils.js';
|
|
6
7
|
import Sbom from '../sbom.js';
|
|
7
8
|
import { getCustom } from '../tools.js';
|
|
8
9
|
import Base_java, { ecosystem_maven } from "./base_java.js";
|
|
@@ -52,28 +53,28 @@ export default class Java_maven extends Base_java {
|
|
|
52
53
|
};
|
|
53
54
|
}
|
|
54
55
|
/**
|
|
55
|
-
* Read license from pom.xml manifest
|
|
56
|
+
* Read license from pom.xml manifest, with fallback to LICENSE file
|
|
56
57
|
* @param {string} manifestPath - path to pom.xml
|
|
57
58
|
* @returns {string|null}
|
|
58
59
|
*/
|
|
59
60
|
readLicenseFromManifest(manifestPath) {
|
|
61
|
+
let fromPom = null;
|
|
60
62
|
try {
|
|
61
63
|
const xml = fs.readFileSync(manifestPath, 'utf-8');
|
|
62
64
|
const parser = new XMLParser({ ignoreAttributes: false });
|
|
63
65
|
const obj = parser.parse(xml);
|
|
64
66
|
const project = obj?.project;
|
|
65
|
-
if (
|
|
66
|
-
|
|
67
|
+
if (project?.licenses?.license) {
|
|
68
|
+
const license = Array.isArray(project.licenses.license)
|
|
69
|
+
? project.licenses.license[0]
|
|
70
|
+
: project.licenses.license;
|
|
71
|
+
fromPom = (license?.name && license.name.trim()) || null;
|
|
67
72
|
}
|
|
68
|
-
const license = Array.isArray(project.licenses.license)
|
|
69
|
-
? project.licenses.license[0]
|
|
70
|
-
: project.licenses.license;
|
|
71
|
-
const name = (license?.name && license.name.trim()) || null;
|
|
72
|
-
return name || null;
|
|
73
73
|
}
|
|
74
74
|
catch {
|
|
75
|
-
|
|
75
|
+
// leave fromPom as null
|
|
76
76
|
}
|
|
77
|
+
return getLicense(fromPom, manifestPath);
|
|
77
78
|
}
|
|
78
79
|
/**
|
|
79
80
|
* Create a Dot Graph dependency tree for a manifest path.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import { PackageURL } from 'packageurl-js';
|
|
3
|
+
import { readLicenseFile } from '../license/license_utils.js';
|
|
3
4
|
import Sbom from '../sbom.js';
|
|
4
5
|
import { environmentVariableIsPopulated, getCustom, getCustomPath, invokeCommand } from "../tools.js";
|
|
5
6
|
import Python_controller from './python_controller.js';
|
|
@@ -24,7 +25,7 @@ function isSupported(manifestName) {
|
|
|
24
25
|
* @returns {string|null}
|
|
25
26
|
*/
|
|
26
27
|
// eslint-disable-next-line no-unused-vars
|
|
27
|
-
function readLicenseFromManifest(manifestPath) { return
|
|
28
|
+
function readLicenseFromManifest(manifestPath) { return readLicenseFile(manifestPath); }
|
|
28
29
|
/**
|
|
29
30
|
* @param {string} manifestDir - the directory where the manifest lies
|
|
30
31
|
*/
|
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
2
|
import { Language, Parser, Query } from 'web-tree-sitter';
|
|
3
|
-
const
|
|
3
|
+
const wasmUrl = new URL('./tree-sitter-requirements.wasm', import.meta.url);
|
|
4
4
|
async function init() {
|
|
5
|
-
await Parser.init(
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
9
|
-
});
|
|
10
|
-
return await Language.load(require.resolve('tree-sitter-requirements/tree-sitter-requirements.wasm'));
|
|
5
|
+
await Parser.init();
|
|
6
|
+
const wasmBytes = new Uint8Array(await readFile(wasmUrl));
|
|
7
|
+
return await Language.load(wasmBytes);
|
|
11
8
|
}
|
|
12
9
|
export async function getParser() {
|
|
13
10
|
const language = await init();
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trustify-da/trustify-da-javascript-client",
|
|
3
|
-
"version": "0.3.0-ea.
|
|
3
|
+
"version": "0.3.0-ea.29f6867",
|
|
4
4
|
"description": "Code-Ready Dependency Analytics JavaScript API.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://github.com/guacsec/trustify-da-javascript-client#README.md",
|
|
@@ -40,8 +40,11 @@
|
|
|
40
40
|
"test": "c8 npm run tests",
|
|
41
41
|
"tests": "mocha --config .mocharc.json --grep \".*analysis module.*\" --invert",
|
|
42
42
|
"tests:rep": "mocha --reporter-option maxDiffSize=0 --reporter json > unit-tests-result.json",
|
|
43
|
+
"pretest": "cp node_modules/tree-sitter-requirements/tree-sitter-requirements.wasm src/providers/tree-sitter-requirements.wasm",
|
|
43
44
|
"precompile": "rm -rf dist",
|
|
44
|
-
"compile": "tsc -p tsconfig.json"
|
|
45
|
+
"compile": "tsc -p tsconfig.json",
|
|
46
|
+
"compile:dev": "tsc -p tsconfig.dev.json",
|
|
47
|
+
"postcompile": "cp node_modules/tree-sitter-requirements/tree-sitter-requirements.wasm dist/src/providers/tree-sitter-requirements.wasm"
|
|
45
48
|
},
|
|
46
49
|
"dependencies": {
|
|
47
50
|
"@babel/core": "^7.23.2",
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* License compatibility: whether a dependency license is compatible with the project license.
|
|
3
|
-
* Relies on backend-provided license categories.
|
|
4
|
-
*
|
|
5
|
-
* Compatibility is based on restrictiveness hierarchy:
|
|
6
|
-
* PERMISSIVE < WEAK_COPYLEFT < STRONG_COPYLEFT
|
|
7
|
-
*
|
|
8
|
-
* A dependency is compatible if it's equal or less restrictive than the project license.
|
|
9
|
-
* A dependency is incompatible if it's more restrictive than the project license.
|
|
10
|
-
*/
|
|
11
|
-
/**
|
|
12
|
-
* Check if a dependency's license is compatible with the project license based on backend categories.
|
|
13
|
-
*
|
|
14
|
-
* @param {string} [projectCategory] - backend category for project license: PERMISSIVE | WEAK_COPYLEFT | STRONG_COPYLEFT | UNKNOWN
|
|
15
|
-
* @param {string} [dependencyCategory] - backend category for dependency license: PERMISSIVE | WEAK_COPYLEFT | STRONG_COPYLEFT | UNKNOWN
|
|
16
|
-
* @returns {'compatible'|'incompatible'|'unknown'}
|
|
17
|
-
*/
|
|
18
|
-
export function getCompatibility(projectCategory?: string, dependencyCategory?: string): "compatible" | "incompatible" | "unknown";
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* License compatibility: whether a dependency license is compatible with the project license.
|
|
3
|
-
* Relies on backend-provided license categories.
|
|
4
|
-
*
|
|
5
|
-
* Compatibility is based on restrictiveness hierarchy:
|
|
6
|
-
* PERMISSIVE < WEAK_COPYLEFT < STRONG_COPYLEFT
|
|
7
|
-
*
|
|
8
|
-
* A dependency is compatible if it's equal or less restrictive than the project license.
|
|
9
|
-
* A dependency is incompatible if it's more restrictive than the project license.
|
|
10
|
-
*/
|
|
11
|
-
/**
|
|
12
|
-
* Check if a dependency's license is compatible with the project license based on backend categories.
|
|
13
|
-
*
|
|
14
|
-
* @param {string} [projectCategory] - backend category for project license: PERMISSIVE | WEAK_COPYLEFT | STRONG_COPYLEFT | UNKNOWN
|
|
15
|
-
* @param {string} [dependencyCategory] - backend category for dependency license: PERMISSIVE | WEAK_COPYLEFT | STRONG_COPYLEFT | UNKNOWN
|
|
16
|
-
* @returns {'compatible'|'incompatible'|'unknown'}
|
|
17
|
-
*/
|
|
18
|
-
export function getCompatibility(projectCategory, dependencyCategory) {
|
|
19
|
-
if (!projectCategory || !dependencyCategory) {
|
|
20
|
-
return 'unknown';
|
|
21
|
-
}
|
|
22
|
-
const proj = projectCategory.toUpperCase();
|
|
23
|
-
const dep = dependencyCategory.toUpperCase();
|
|
24
|
-
// Unknown categories
|
|
25
|
-
if (proj === 'UNKNOWN' || dep === 'UNKNOWN') {
|
|
26
|
-
return 'unknown';
|
|
27
|
-
}
|
|
28
|
-
// Define restrictiveness levels (higher number = more restrictive)
|
|
29
|
-
const restrictiveness = {
|
|
30
|
-
'PERMISSIVE': 1,
|
|
31
|
-
'WEAK_COPYLEFT': 2,
|
|
32
|
-
'STRONG_COPYLEFT': 3
|
|
33
|
-
};
|
|
34
|
-
const projLevel = restrictiveness[proj];
|
|
35
|
-
const depLevel = restrictiveness[dep];
|
|
36
|
-
if (projLevel === undefined || depLevel === undefined) {
|
|
37
|
-
return 'unknown';
|
|
38
|
-
}
|
|
39
|
-
// Dependency is more restrictive than project → incompatible
|
|
40
|
-
if (depLevel > projLevel) {
|
|
41
|
-
return 'incompatible';
|
|
42
|
-
}
|
|
43
|
-
// Dependency is equal or less restrictive → compatible
|
|
44
|
-
return 'compatible';
|
|
45
|
-
}
|