@trustify-da/trustify-da-javascript-client 0.3.0-ea.f501753 → 0.3.0-ea.fa16f06

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.
Files changed (64) hide show
  1. package/README.md +2 -2
  2. package/dist/package.json +25 -22
  3. package/dist/src/analysis.d.ts +14 -7
  4. package/dist/src/analysis.js +29 -13
  5. package/dist/src/cli.js +106 -2
  6. package/dist/src/cyclone_dx_sbom.js +11 -3
  7. package/dist/src/index.d.ts +17 -12
  8. package/dist/src/index.js +21 -7
  9. package/dist/src/license/index.d.ts +3 -3
  10. package/dist/src/license/index.js +4 -1
  11. package/dist/src/license/license_utils.js +4 -1
  12. package/dist/src/license/licenses_api.d.ts +2 -2
  13. package/dist/src/oci_image/images.d.ts +1 -1
  14. package/dist/src/oci_image/images.js +15 -1
  15. package/dist/src/oci_image/utils.d.ts +5 -5
  16. package/dist/src/provider.d.ts +4 -3
  17. package/dist/src/provider.js +6 -2
  18. package/dist/src/providers/base_java.d.ts +14 -3
  19. package/dist/src/providers/base_java.js +16 -6
  20. package/dist/src/providers/base_javascript.d.ts +20 -9
  21. package/dist/src/providers/base_javascript.js +10 -3
  22. package/dist/src/providers/base_pyproject.d.ts +10 -5
  23. package/dist/src/providers/base_pyproject.js +7 -0
  24. package/dist/src/providers/containerfile_parser.d.ts +5 -0
  25. package/dist/src/providers/containerfile_parser.js +20 -0
  26. package/dist/src/providers/golang_gomodules.d.ts +2 -1
  27. package/dist/src/providers/golang_gomodules.js +1 -1
  28. package/dist/src/providers/java_gradle.js +2 -2
  29. package/dist/src/providers/java_gradle_groovy.d.ts +1 -1
  30. package/dist/src/providers/java_gradle_kotlin.d.ts +1 -1
  31. package/dist/src/providers/java_maven.d.ts +2 -2
  32. package/dist/src/providers/java_maven.js +72 -0
  33. package/dist/src/providers/javascript_bun.d.ts +10 -0
  34. package/dist/src/providers/javascript_bun.js +100 -0
  35. package/dist/src/providers/javascript_npm.d.ts +6 -1
  36. package/dist/src/providers/oci_dockerfile.d.ts +51 -0
  37. package/dist/src/providers/oci_dockerfile.js +177 -0
  38. package/dist/src/providers/processors/yarn_berry_processor.d.ts +2 -2
  39. package/dist/src/providers/processors/yarn_classic_processor.d.ts +2 -2
  40. package/dist/src/providers/python_controller.js +61 -4
  41. package/dist/src/providers/python_pip.d.ts +1 -0
  42. package/dist/src/providers/python_pip.js +1 -1
  43. package/dist/src/providers/python_pip_pyproject.js +3 -1
  44. package/dist/src/providers/python_poetry.d.ts +31 -8
  45. package/dist/src/providers/python_poetry.js +62 -14
  46. package/dist/src/providers/python_uv.d.ts +18 -14
  47. package/dist/src/providers/python_uv.js +111 -2
  48. package/dist/src/providers/rust_cargo.d.ts +6 -2
  49. package/dist/src/providers/rust_cargo.js +105 -19
  50. package/dist/src/providers/tree-sitter-containerfile.wasm +0 -0
  51. package/dist/src/remediate.d.ts +21 -0
  52. package/dist/src/remediate.js +149 -0
  53. package/dist/src/remediation.d.ts +57 -0
  54. package/dist/src/remediation.js +436 -0
  55. package/dist/src/remediation_report.d.ts +40 -0
  56. package/dist/src/remediation_report.js +159 -0
  57. package/dist/src/tools.d.ts +2 -2
  58. package/dist/src/updaters/maven_updater.d.ts +26 -0
  59. package/dist/src/updaters/maven_updater.js +322 -0
  60. package/dist/src/updaters/toml_updater.d.ts +33 -0
  61. package/dist/src/updaters/toml_updater.js +244 -0
  62. package/dist/src/workspace.d.ts +9 -0
  63. package/dist/src/workspace.js +1 -1
  64. package/package.json +26 -23
@@ -1,4 +1,5 @@
1
1
  export default class Python_poetry extends Base_pyproject {
2
+ _verifyPoetryAccessible(poetryBin: any): void;
2
3
  /**
3
4
  * @param {string} manifestDir
4
5
  * @param {string} _workspaceDir - unused (poetry has no workspace support)
@@ -6,7 +7,7 @@ export default class Python_poetry extends Base_pyproject {
6
7
  * @param {Object} opts
7
8
  * @returns {Promise<{directDeps: string[], graph: Map<string, {name: string, version: string, children: string[]}>}>}
8
9
  */
9
- _getDependencyData(manifestDir: string, _workspaceDir: string, parsed: object, opts: any): Promise<{
10
+ _getDependencyData(manifestDir: string, _workspaceDir: string, parsed: object, opts: Object): Promise<{
10
11
  directDeps: string[];
11
12
  graph: Map<string, {
12
13
  name: string;
@@ -18,17 +19,17 @@ export default class Python_poetry extends Base_pyproject {
18
19
  * Get poetry show --tree output.
19
20
  * @param {string} manifestDir
20
21
  * @param {boolean} hasDevGroup
21
- * @param {Object} opts
22
+ * @param {string} poetryBin
22
23
  * @returns {string}
23
24
  */
24
- _getPoetryShowTreeOutput(manifestDir: string, hasDevGroup: boolean, opts: any): string;
25
+ _getPoetryShowTreeOutput(manifestDir: string, hasDevGroup: boolean, poetryBin: string): string;
25
26
  /**
26
27
  * Get poetry show --all output (flat list with resolved versions).
27
28
  * @param {string} manifestDir
28
- * @param {Object} opts
29
+ * @param {string} poetryBin
29
30
  * @returns {string}
30
31
  */
31
- _getPoetryShowAllOutput(manifestDir: string, opts: any): string;
32
+ _getPoetryShowAllOutput(manifestDir: string, poetryBin: string): string;
32
33
  /**
33
34
  * Parse poetry show --all output into a version map.
34
35
  * Lines look like: "name (!) 1.2.3 Description text..."
@@ -46,29 +47,51 @@ export default class Python_poetry extends Base_pyproject {
46
47
  * → transitiveMarkers['click']['colorama'] = "sys_platform == 'win32'"
47
48
  * @param {string|null} lockDir
48
49
  * @param {object} parsed - parsed pyproject.toml
49
- * @returns {{directMarkers: Map<string, string>, transitiveMarkers: Map<string, Map<string, string>>}}
50
+ * @returns {{directMarkers: Map<string, string>, transitiveMarkers: Map<string, Map<string, string>>, hashMap: Map<string, Array<{alg: string, content: string}>>}}
50
51
  */
51
52
  _extractMarkerData(lockDir: string | null, parsed: object): {
52
53
  directMarkers: Map<string, string>;
53
54
  transitiveMarkers: Map<string, Map<string, string>>;
55
+ hashMap: Map<string, Array<{
56
+ alg: string;
57
+ content: string;
58
+ }>>;
54
59
  };
60
+ /**
61
+ * Extract a SHA-256 hex digest from poetry.lock files array.
62
+ * Prefers the sdist (.tar.gz) hash; falls back to the first wheel hash.
63
+ * @param {Array<{file: string, hash: string}>} [files]
64
+ * @returns {string|null}
65
+ */
66
+ _extractSha256FromFiles(files?: Array<{
67
+ file: string;
68
+ hash: string;
69
+ }>): string | null;
55
70
  /**
56
71
  * Parse poetry show --tree output into a dependency graph structure.
57
72
  *
58
73
  * @param {string} treeOutput
59
74
  * @param {Map<string, string>} versionMap - canonical name -> resolved version
60
- * @param {{directMarkers: Map<string, string>, transitiveMarkers: Map<string, Map<string, string>>}} markerData
61
- * @returns {{directDeps: string[], graph: Map<string, {name: string, version: string, children: string[]}>}}
75
+ * @param {{directMarkers: Map<string, string>, transitiveMarkers: Map<string, Map<string, string>>, hashMap: Map<string, Array<{alg: string, content: string}>>}} markerData
76
+ * @returns {{directDeps: string[], graph: Map<string, {name: string, version: string, children: string[], hashes?: Array<{alg: string, content: string}>}>}}
62
77
  */
63
78
  _parsePoetryTree(treeOutput: string, versionMap: Map<string, string>, markerData: {
64
79
  directMarkers: Map<string, string>;
65
80
  transitiveMarkers: Map<string, Map<string, string>>;
81
+ hashMap: Map<string, Array<{
82
+ alg: string;
83
+ content: string;
84
+ }>>;
66
85
  }): {
67
86
  directDeps: string[];
68
87
  graph: Map<string, {
69
88
  name: string;
70
89
  version: string;
71
90
  children: string[];
91
+ hashes?: Array<{
92
+ alg: string;
93
+ content: string;
94
+ }>;
72
95
  }>;
73
96
  };
74
97
  }
@@ -32,6 +32,17 @@ export default class Python_poetry extends Base_pyproject {
32
32
  _cmdName() {
33
33
  return 'poetry';
34
34
  }
35
+ _verifyPoetryAccessible(poetryBin) {
36
+ try {
37
+ invokeCommand(poetryBin, ['--version']);
38
+ }
39
+ catch (error) {
40
+ if (error.code === 'ENOENT') {
41
+ throw new Error(`poetry is not accessible at "${poetryBin}"`, { cause: error });
42
+ }
43
+ throw new Error('failed to check for poetry binary', { cause: error });
44
+ }
45
+ }
35
46
  /**
36
47
  * @param {string} manifestDir
37
48
  * @param {string} _workspaceDir - unused (poetry has no workspace support)
@@ -41,9 +52,15 @@ export default class Python_poetry extends Base_pyproject {
41
52
  */
42
53
  // eslint-disable-next-line no-unused-vars
43
54
  async _getDependencyData(manifestDir, _workspaceDir, parsed, opts) {
55
+ let poetryBin = getCustomPath('poetry', opts);
56
+ let envBypass = environmentVariableIsPopulated('TRUSTIFY_DA_POETRY_SHOW_TREE')
57
+ && environmentVariableIsPopulated('TRUSTIFY_DA_POETRY_SHOW_ALL');
58
+ if (!envBypass) {
59
+ this._verifyPoetryAccessible(poetryBin);
60
+ }
44
61
  let hasDevGroup = !!(parsed.tool?.poetry?.group?.dev || parsed.tool?.poetry?.['dev-dependencies']);
45
- let treeOutput = this._getPoetryShowTreeOutput(manifestDir, hasDevGroup, opts);
46
- let showAllOutput = this._getPoetryShowAllOutput(manifestDir, opts);
62
+ let treeOutput = this._getPoetryShowTreeOutput(manifestDir, hasDevGroup, poetryBin);
63
+ let showAllOutput = this._getPoetryShowAllOutput(manifestDir, poetryBin);
47
64
  let versionMap = this._parsePoetryShowAll(showAllOutput);
48
65
  let lockDir = this._findLockFileDir(manifestDir, opts);
49
66
  let markerData = this._extractMarkerData(lockDir, parsed);
@@ -53,14 +70,13 @@ export default class Python_poetry extends Base_pyproject {
53
70
  * Get poetry show --tree output.
54
71
  * @param {string} manifestDir
55
72
  * @param {boolean} hasDevGroup
56
- * @param {Object} opts
73
+ * @param {string} poetryBin
57
74
  * @returns {string}
58
75
  */
59
- _getPoetryShowTreeOutput(manifestDir, hasDevGroup, opts) {
76
+ _getPoetryShowTreeOutput(manifestDir, hasDevGroup, poetryBin) {
60
77
  if (environmentVariableIsPopulated('TRUSTIFY_DA_POETRY_SHOW_TREE')) {
61
78
  return Buffer.from(process.env['TRUSTIFY_DA_POETRY_SHOW_TREE'], 'base64').toString('utf-8');
62
79
  }
63
- let poetryBin = getCustomPath('poetry', opts);
64
80
  let args = ['show', '--tree', '--no-ansi'];
65
81
  if (hasDevGroup) {
66
82
  args.push('--without', 'dev');
@@ -70,14 +86,13 @@ export default class Python_poetry extends Base_pyproject {
70
86
  /**
71
87
  * Get poetry show --all output (flat list with resolved versions).
72
88
  * @param {string} manifestDir
73
- * @param {Object} opts
89
+ * @param {string} poetryBin
74
90
  * @returns {string}
75
91
  */
76
- _getPoetryShowAllOutput(manifestDir, opts) {
92
+ _getPoetryShowAllOutput(manifestDir, poetryBin) {
77
93
  if (environmentVariableIsPopulated('TRUSTIFY_DA_POETRY_SHOW_ALL')) {
78
94
  return Buffer.from(process.env['TRUSTIFY_DA_POETRY_SHOW_ALL'], 'base64').toString('utf-8');
79
95
  }
80
- let poetryBin = getCustomPath('poetry', opts);
81
96
  return invokeCommand(poetryBin, ['show', '--no-ansi', '--all'], { cwd: manifestDir }).toString();
82
97
  }
83
98
  /**
@@ -111,11 +126,12 @@ export default class Python_poetry extends Base_pyproject {
111
126
  * → transitiveMarkers['click']['colorama'] = "sys_platform == 'win32'"
112
127
  * @param {string|null} lockDir
113
128
  * @param {object} parsed - parsed pyproject.toml
114
- * @returns {{directMarkers: Map<string, string>, transitiveMarkers: Map<string, Map<string, string>>}}
129
+ * @returns {{directMarkers: Map<string, string>, transitiveMarkers: Map<string, Map<string, string>>, hashMap: Map<string, Array<{alg: string, content: string}>>}}
115
130
  */
116
131
  _extractMarkerData(lockDir, parsed) {
117
132
  let directMarkers = new Map();
118
133
  let transitiveMarkers = new Map();
134
+ let hashMap = new Map();
119
135
  // Extract markers from PEP 621 dependency strings: "name[extras]>=ver ; marker"
120
136
  let deps = parsed.project?.dependencies || [];
121
137
  for (let dep of deps) {
@@ -142,18 +158,40 @@ export default class Python_poetry extends Base_pyproject {
142
158
  transitiveMarkers.get(pkgKey).set(this._canonicalize(depName), markers);
143
159
  }
144
160
  }
161
+ let sha256Hex = this._extractSha256FromFiles(pkg.files);
162
+ if (sha256Hex) {
163
+ hashMap.set(pkgKey, [{ alg: "SHA-256", content: sha256Hex }]);
164
+ }
145
165
  }
146
166
  }
147
167
  }
148
- return { directMarkers, transitiveMarkers };
168
+ return { directMarkers, transitiveMarkers, hashMap };
169
+ }
170
+ /**
171
+ * Extract a SHA-256 hex digest from poetry.lock files array.
172
+ * Prefers the sdist (.tar.gz) hash; falls back to the first wheel hash.
173
+ * @param {Array<{file: string, hash: string}>} [files]
174
+ * @returns {string|null}
175
+ */
176
+ _extractSha256FromFiles(files) {
177
+ if (!Array.isArray(files) || files.length === 0) {
178
+ return null;
179
+ }
180
+ let sdist = files.find(f => f.file.endsWith('.tar.gz'));
181
+ let entry = sdist || files[0];
182
+ let hash = entry.hash;
183
+ if (hash && hash.startsWith('sha256:')) {
184
+ return hash.slice(7);
185
+ }
186
+ return null;
149
187
  }
150
188
  /**
151
189
  * Parse poetry show --tree output into a dependency graph structure.
152
190
  *
153
191
  * @param {string} treeOutput
154
192
  * @param {Map<string, string>} versionMap - canonical name -> resolved version
155
- * @param {{directMarkers: Map<string, string>, transitiveMarkers: Map<string, Map<string, string>>}} markerData
156
- * @returns {{directDeps: string[], graph: Map<string, {name: string, version: string, children: string[]}>}}
193
+ * @param {{directMarkers: Map<string, string>, transitiveMarkers: Map<string, Map<string, string>>, hashMap: Map<string, Array<{alg: string, content: string}>>}} markerData
194
+ * @returns {{directDeps: string[], graph: Map<string, {name: string, version: string, children: string[], hashes?: Array<{alg: string, content: string}>}>}}
157
195
  */
158
196
  _parsePoetryTree(treeOutput, versionMap, markerData) {
159
197
  let lines = treeOutput.split(/\r?\n/);
@@ -179,7 +217,12 @@ export default class Python_poetry extends Base_pyproject {
179
217
  }
180
218
  directDeps.push(key);
181
219
  if (!graph.has(key)) {
182
- graph.set(key, { name, version, children: [] });
220
+ let entry = { name, version, children: [] };
221
+ let hashes = markerData.hashMap.get(key);
222
+ if (hashes) {
223
+ entry.hashes = hashes;
224
+ }
225
+ graph.set(key, entry);
183
226
  }
184
227
  currentDirectDep = key;
185
228
  stack = [{ key, depth: -1 }];
@@ -223,7 +266,12 @@ export default class Python_poetry extends Base_pyproject {
223
266
  throw new Error(`poetry: package '${depName}' has no resolved version`);
224
267
  }
225
268
  if (!graph.has(depKey)) {
226
- graph.set(depKey, { name: depName, version, children: [] });
269
+ let entry = { name: depName, version, children: [] };
270
+ let hashes = markerData.hashMap.get(depKey);
271
+ if (hashes) {
272
+ entry.hashes = hashes;
273
+ }
274
+ graph.set(depKey, entry);
227
275
  }
228
276
  if (parentKey) {
229
277
  let parentEntry = graph.get(parentKey);
@@ -1,26 +1,30 @@
1
+ /**
2
+ * Discover all pyproject.toml manifest paths in a uv workspace.
3
+ * Parses `[tool.uv.workspace]` from root pyproject.toml and glob-expands member patterns.
4
+ *
5
+ * @param {string} workspaceRoot - Absolute or relative path to workspace root (must contain pyproject.toml and uv.lock)
6
+ * @param {{ workspaceDiscoveryIgnore?: string[], TRUSTIFY_DA_WORKSPACE_DISCOVERY_IGNORE?: string, [key: string]: unknown }} [opts={}]
7
+ * @returns {Promise<string[]>} Paths to pyproject.toml files (absolute)
8
+ */
9
+ export function discoverUvWorkspaceMembers(workspaceRoot: string, opts?: {
10
+ workspaceDiscoveryIgnore?: string[];
11
+ TRUSTIFY_DA_WORKSPACE_DISCOVERY_IGNORE?: string;
12
+ [key: string]: unknown;
13
+ }): Promise<string[]>;
1
14
  export default class Python_uv extends Base_pyproject {
2
15
  /**
3
- * @param {string} manifestDir - directory containing the target pyproject.toml
4
- * @param {string} workspaceDir - workspace root (for resolving editable install paths)
5
- * @param {object} parsed - parsed pyproject.toml
6
- * @param {Object} opts
7
- * @returns {Promise<{directDeps: string[], graph: Map<string, {name: string, version: string, children: string[]}>}>}
16
+ * Parse uv.lock and attach SHA-256 hashes to graph entries.
17
+ * @param {string} lockFilePath - path to uv.lock
18
+ * @param {Map<string, import('./base_pyproject.js').GraphEntry>} graph
8
19
  */
9
- _getDependencyData(manifestDir: string, workspaceDir: string, parsed: object, opts: any): Promise<{
10
- directDeps: string[];
11
- graph: Map<string, {
12
- name: string;
13
- version: string;
14
- children: string[];
15
- }>;
16
- }>;
20
+ _attachHashesFromLockFile(lockFilePath: string, graph: Map<string, import("./base_pyproject.js").GraphEntry>): Promise<void>;
17
21
  /**
18
22
  * Get the uv export output, either from env var or by running the command.
19
23
  * @param {string} manifestDir
20
24
  * @param {Object} opts
21
25
  * @returns {string}
22
26
  */
23
- _getUvExportOutput(manifestDir: string, opts: any): string;
27
+ _getUvExportOutput(manifestDir: string, opts: Object): string;
24
28
  /**
25
29
  * Parse uv export output into a dependency graph using tree-sitter-requirements
26
30
  * for package/version extraction and string parsing for "# via" comments.
@@ -1,7 +1,9 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
+ import fg from 'fast-glob';
3
4
  import { parse as parseToml } from 'smol-toml';
4
5
  import { environmentVariableIsPopulated, getCustomPath, invokeCommand } from '../tools.js';
6
+ import { filterManifestPathsByDiscoveryIgnore, resolveWorkspaceDiscoveryIgnore, toManifestGlobPatterns } from '../workspace.js';
5
7
  import Base_pyproject from './base_pyproject.js';
6
8
  import { evaluateMarker } from './marker_evaluator.js';
7
9
  import { getParser, getPinnedVersionQuery } from './requirements_parser.js';
@@ -19,12 +21,54 @@ export default class Python_uv extends Base_pyproject {
19
21
  * @param {string} workspaceDir - workspace root (for resolving editable install paths)
20
22
  * @param {object} parsed - parsed pyproject.toml
21
23
  * @param {Object} opts
22
- * @returns {Promise<{directDeps: string[], graph: Map<string, {name: string, version: string, children: string[]}>}>}
24
+ * @returns {Promise<{directDeps: string[], graph: Map<string, import('./base_pyproject.js').GraphEntry>}>}
23
25
  */
24
26
  async _getDependencyData(manifestDir, workspaceDir, parsed, opts) {
25
27
  let projectName = this._getProjectName(parsed);
26
28
  let uvOutput = this._getUvExportOutput(manifestDir, opts);
27
- return this._parseUvExport(uvOutput, projectName, workspaceDir);
29
+ let { directDeps, graph } = await this._parseUvExport(uvOutput, projectName, workspaceDir);
30
+ await this._attachHashesFromLockFile(path.join(workspaceDir, 'uv.lock'), graph);
31
+ return { directDeps, graph };
32
+ }
33
+ /**
34
+ * Parse uv.lock and attach SHA-256 hashes to graph entries.
35
+ * @param {string} lockFilePath - path to uv.lock
36
+ * @param {Map<string, import('./base_pyproject.js').GraphEntry>} graph
37
+ */
38
+ async _attachHashesFromLockFile(lockFilePath, graph) {
39
+ let lockContent;
40
+ try {
41
+ lockContent = await fs.promises.readFile(lockFilePath, 'utf-8');
42
+ }
43
+ catch {
44
+ return;
45
+ }
46
+ let parsed;
47
+ try {
48
+ parsed = parseToml(lockContent);
49
+ }
50
+ catch {
51
+ return;
52
+ }
53
+ let packages = parsed.package;
54
+ if (!Array.isArray(packages)) {
55
+ return;
56
+ }
57
+ for (let pkg of packages) {
58
+ if (!pkg.name) {
59
+ continue;
60
+ }
61
+ let hashStr = pkg.sdist?.hash || pkg.wheels?.[0]?.hash;
62
+ if (!hashStr || !hashStr.startsWith('sha256:')) {
63
+ continue;
64
+ }
65
+ let key = this._canonicalize(pkg.name);
66
+ let entry = graph.get(key);
67
+ if (!entry || entry.hashes) {
68
+ continue;
69
+ }
70
+ entry.hashes = [{ alg: "SHA-256", content: hashStr.slice(7) }];
71
+ }
28
72
  }
29
73
  /**
30
74
  * Get the uv export output, either from env var or by running the command.
@@ -158,3 +202,68 @@ export default class Python_uv extends Base_pyproject {
158
202
  return { directDeps, graph };
159
203
  }
160
204
  }
205
+ const DEFAULT_UV_DISCOVERY_IGNORE = [
206
+ '**/__pycache__/**',
207
+ '**/.venv/**',
208
+ ];
209
+ /**
210
+ * Discover all pyproject.toml manifest paths in a uv workspace.
211
+ * Parses `[tool.uv.workspace]` from root pyproject.toml and glob-expands member patterns.
212
+ *
213
+ * @param {string} workspaceRoot - Absolute or relative path to workspace root (must contain pyproject.toml and uv.lock)
214
+ * @param {{ workspaceDiscoveryIgnore?: string[], TRUSTIFY_DA_WORKSPACE_DISCOVERY_IGNORE?: string, [key: string]: unknown }} [opts={}]
215
+ * @returns {Promise<string[]>} Paths to pyproject.toml files (absolute)
216
+ */
217
+ export async function discoverUvWorkspaceMembers(workspaceRoot, opts = {}) {
218
+ const root = path.resolve(workspaceRoot);
219
+ const rootPyproject = path.join(root, 'pyproject.toml');
220
+ const uvLock = path.join(root, 'uv.lock');
221
+ if (!fs.existsSync(rootPyproject) || !fs.existsSync(uvLock)) {
222
+ return [];
223
+ }
224
+ let parsed;
225
+ try {
226
+ parsed = parseToml(fs.readFileSync(rootPyproject, 'utf-8'));
227
+ }
228
+ catch {
229
+ return [];
230
+ }
231
+ const workspaceConfig = parsed?.tool?.uv?.workspace;
232
+ if (!workspaceConfig) {
233
+ return [];
234
+ }
235
+ const memberPatterns = workspaceConfig.members;
236
+ if (!Array.isArray(memberPatterns) || memberPatterns.length === 0) {
237
+ return [];
238
+ }
239
+ const excludePatterns = Array.isArray(workspaceConfig.exclude) ? workspaceConfig.exclude : [];
240
+ const excludeGlobs = excludePatterns
241
+ .filter(p => typeof p === 'string' && p.trim())
242
+ .map(p => `${p.trim()}/pyproject.toml`);
243
+ const ignorePatterns = [...resolveWorkspaceDiscoveryIgnore(opts), ...DEFAULT_UV_DISCOVERY_IGNORE];
244
+ const globOpts = {
245
+ cwd: root,
246
+ absolute: true,
247
+ onlyFiles: true,
248
+ ignore: [...ignorePatterns, ...excludeGlobs],
249
+ followSymbolicLinks: false,
250
+ };
251
+ const patterns = toManifestGlobPatterns(memberPatterns.filter(p => typeof p === 'string'), 'pyproject.toml');
252
+ const manifestPaths = await fg(patterns, globOpts);
253
+ if (!manifestPaths.includes(rootPyproject) && hasProjectMetadata(parsed)) {
254
+ manifestPaths.unshift(rootPyproject);
255
+ }
256
+ return filterManifestPathsByDiscoveryIgnore(manifestPaths, root, ignorePatterns);
257
+ }
258
+ /**
259
+ * @param {import('smol-toml').TomlTable} parsedPyProject
260
+ * @returns {boolean}
261
+ */
262
+ function hasProjectMetadata(parsedPyProject) {
263
+ try {
264
+ return typeof parsedPyProject?.project?.name === 'string' && parsedPyProject.project.name.trim() !== '';
265
+ }
266
+ catch {
267
+ return false;
268
+ }
269
+ }
@@ -4,9 +4,10 @@ declare namespace _default {
4
4
  export { provideComponent };
5
5
  export { provideStack };
6
6
  export { readLicenseFromManifest };
7
+ export function packageManagerName(): string;
7
8
  }
8
9
  export default _default;
9
- export type Provided = import("../provider").Provided;
10
+ export type Provided = any;
10
11
  /**
11
12
  * @param {string} manifestName - the subject manifest name-type
12
13
  * @returns {boolean} - return true if `Cargo.toml` is the manifest name-type
@@ -46,7 +47,10 @@ declare function provideStack(manifest: string, opts?: {}): Provided;
46
47
  * Read project license from Cargo.toml, with fallback to LICENSE file.
47
48
  * Supports the `license` field under `[package]` (single crate / workspace
48
49
  * with root) and under `[workspace.package]` (virtual workspaces).
50
+ * When a member crate uses `license = { workspace = true }`, the actual
51
+ * license is resolved from the workspace root Cargo.toml.
49
52
  * @param {string} manifestPath - path to Cargo.toml
53
+ * @param {object} [metadata] - cargo metadata output (needed for workspace license resolution)
50
54
  * @returns {string|null} SPDX identifier or null
51
55
  */
52
- declare function readLicenseFromManifest(manifestPath: string): string | null;
56
+ declare function readLicenseFromManifest(manifestPath: string, metadata?: object): string | null;