@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 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",
@@ -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, identifyLicenseViaBackend, getLicenseDetails, licensesFromReport, normalizeLicensesResponse, runLicenseCheck, getCompatibility } from "./license/index.js";
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, identifyLicenseViaBackend, getLicenseDetails, licensesFromReport, normalizeLicensesResponse, runLicenseCheck, getCompatibility } from "./license/index.js";
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 "./compatibility.js";
27
- export { getProjectLicense, findLicenseFilePath, identifyLicense as identifyLicenseViaBackend } from "./project_license.js";
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 './compatibility.js';
7
- export { getProjectLicense, findLicenseFilePath, identifyLicense as identifyLicenseViaBackend } from './project_license.js';
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 './compatibility.js';
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, opts);
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
- const LICENSE_FILES = ['LICENSE', 'LICENSE.md', 'LICENSE.txt'];
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 = readLicenseFromFile(resolved);
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
- * Find and read LICENSE or LICENSE.md; use local pattern matching for identification.
84
- * @param {string} manifestPath
85
- * @returns {string|null}
86
- */
87
- function readLicenseFromFile(manifestPath) {
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 normalizeSpdx(spdxOrName) {
133
- const s = String(spdxOrName).trim().toLowerCase();
134
- // e.g. "MIT" vs "MIT License"
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
- return content.license.trim() || null;
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
- return typeof name === 'string' ? name.trim() : null;
151
+ manifestLicense = (typeof name === 'string' ? name.trim() : null);
150
152
  }
151
- return null;
152
153
  }
153
154
  catch {
154
- return null;
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 null; }
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 null; }
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 (!project?.licenses?.license) {
66
- return null;
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
- return null;
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 null; }
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 { createRequire } from 'node:module';
1
+ import { readFile } from 'node:fs/promises';
2
2
  import { Language, Parser, Query } from 'web-tree-sitter';
3
- const require = createRequire(import.meta.url);
3
+ const wasmUrl = new URL('./tree-sitter-requirements.wasm', import.meta.url);
4
4
  async function init() {
5
- await Parser.init({
6
- locateFile() {
7
- return require.resolve('web-tree-sitter/web-tree-sitter.wasm');
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();
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.0e9ba23",
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
- }