Skip to content

Commit 4490b21

Browse files
authored
Merge branch 'main' into TC-4182
Signed-off-by: Noah S-C <nsantsch@redhat.com>
2 parents 2542dd0 + 93faa7c commit 4490b21

16 files changed

Lines changed: 820 additions & 1 deletion

.github/dependabot.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ updates:
5252
- package-ecosystem: "pip"
5353
directories:
5454
- "/test/providers/tst_manifests/pip/**"
55+
- "/test/providers/tst_manifests/pyproject/**"
5556
schedule:
5657
interval: "monthly"
5758
labels: []

src/providers/javascript_npm.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,26 @@ export default class Javascript_npm extends Base_javascript {
1717
_updateLockFileCmdArgs() {
1818
return ['install', '--package-lock-only'];
1919
}
20+
21+
_buildDependencyTree(includeTransitive, opts = {}) {
22+
// npm ls --json returns a single tree rooted at the workspace root.
23+
// When analyzing a workspace member, its deps are nested under the
24+
// root's dependencies keyed by the member name — extract that subtree
25+
// so downstream analysis sees only the member's dependencies.
26+
const tree = super._buildDependencyTree(includeTransitive, opts);
27+
const memberName = this._getManifest().name;
28+
if (tree.name === memberName) {
29+
return tree;
30+
}
31+
const memberEntry = tree.dependencies?.[memberName];
32+
if (memberEntry) {
33+
return {
34+
name: memberName,
35+
version: memberEntry.version || this._getManifest().version,
36+
dependencies: memberEntry.dependencies,
37+
optionalDependencies: memberEntry.optionalDependencies,
38+
};
39+
}
40+
return tree;
41+
}
2042
}

src/providers/python_uv.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export default class Python_uv extends Base_pyproject {
4444
return Buffer.from(process.env['TRUSTIFY_DA_UV_EXPORT'], 'base64').toString('ascii')
4545
}
4646
let uvBin = getCustomPath('uv', opts)
47-
return invokeCommand(uvBin, ['export', '--format', 'requirements.txt', '--frozen', '--no-hashes', '--no-dev'], { cwd: manifestDir }).toString()
47+
return invokeCommand(uvBin, ['export', '--format', 'requirements.txt', '--frozen', '--no-hashes', '--no-dev', '--no-emit-project'], { cwd: manifestDir }).toString()
4848
}
4949

5050
/**
@@ -81,6 +81,7 @@ export default class Python_uv extends Base_pyproject {
8181
let version = memberParsed.project?.version || memberParsed.tool?.poetry?.version
8282
if (name && version) {
8383
let key = this._canonicalize(name)
84+
if (key === canonProjectName) { continue }
8485
currentPkg = { name, version, parents: new Set() }
8586
packages.set(key, currentPkg)
8687
collectingVia = false

test/providers/javascript.test.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,39 @@ suite('testing the javascript-npm data provider', async () => {
129129
let providedDataForComponent = provider.provideComponent(manifestPath);
130130

131131
compareSboms(providedDataForComponent.content, expectedSbom);
132+
}).timeout(15000);
133+
134+
[
135+
{ providerName: 'npm', testCase: 'workspace_member' },
136+
].forEach(({ providerName, testCase }) => {
137+
/// Verifies that stack analysis resolves transitive dependencies for a workspace member.
138+
test(`verify workspace member data provided for ${providerName} - stack analysis`, async () => {
139+
// Given a workspace member manifest and mock listing from the workspace root
140+
const listing = fs.readFileSync(`test/providers/tst_manifests/${providerName}/${testCase}/listing_stack.json`).toString();
141+
const expectedSbom = fs.readFileSync(`test/providers/tst_manifests/${providerName}/${testCase}/stack_expected_sbom.json`).toString();
142+
const provider = await createMockProvider(providerName, listing);
143+
const manifestPath = `test/providers/tst_manifests/${providerName}/${testCase}/packages/member-a/package.json`;
144+
145+
// When running stack analysis on the workspace member
146+
const result = provider.provideStack(manifestPath);
147+
148+
// Then the SBOM should contain the member's transitive dependencies
149+
compareSboms(result.content, expectedSbom);
150+
}).timeout(30000);
151+
152+
/// Verifies that component analysis resolves direct dependencies for a workspace member.
153+
test(`verify workspace member data provided for ${providerName} - component analysis`, async () => {
154+
// Given a workspace member manifest and mock listing from the workspace root
155+
const listing = fs.readFileSync(`test/providers/tst_manifests/${providerName}/${testCase}/listing_component.json`).toString();
156+
const expectedSbom = fs.readFileSync(`test/providers/tst_manifests/${providerName}/${testCase}/component_expected_sbom.json`).toString();
157+
const provider = await createMockProvider(providerName, listing);
158+
const manifestPath = `test/providers/tst_manifests/${providerName}/${testCase}/packages/member-a/package.json`;
159+
160+
// When running component analysis on the workspace member
161+
const result = provider.provideComponent(manifestPath);
162+
163+
// Then the SBOM should contain only the member's direct dependencies
164+
compareSboms(result.content, expectedSbom);
132165
}).timeout(15000);
133166
});
134167

test/providers/python_pyproject.test.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,24 @@ suite('testing the python-pyproject data provider', () => {
121121
}).timeout(TIMEOUT)
122122
})
123123

124+
suite('uv projects - self-reference excluded (TC-4097)', () => {
125+
const fixtureDir = `${MANIFESTS}/uv_self_ref`
126+
127+
/** Verifies stack and component SBOM output excludes the project itself. */
128+
SBOM_CASES.forEach(({type, method, fixture}) => {
129+
test(`project self-reference excluded from ${type} analysis`, async () => {
130+
let expectedSbom = fs.readFileSync(path.join(fixtureDir, fixture)).toString().trim()
131+
expectedSbom = JSON.stringify(JSON.parse(expectedSbom))
132+
let result = await uvProvider[method](path.join(fixtureDir, 'pyproject.toml'))
133+
expect(result).to.deep.equal({
134+
ecosystem: 'pip',
135+
contentType: 'application/vnd.cyclonedx+json',
136+
content: expectedSbom
137+
})
138+
}).timeout(TIMEOUT)
139+
})
140+
})
141+
124142
suite('uv projects - uv_lock manifest', () => {
125143
const fixtureDir = `${MANIFESTS}/uv_lock`
126144

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"bomFormat": "CycloneDX",
3+
"specVersion": "1.4",
4+
"version": 1,
5+
"metadata": {
6+
"timestamp": "2023-08-07T00:00:00.000Z",
7+
"component": {
8+
"name": "member-a",
9+
"version": "1.0.0",
10+
"purl": "pkg:npm/member-a@1.0.0",
11+
"type": "application",
12+
"bom-ref": "pkg:npm/member-a@1.0.0"
13+
}
14+
},
15+
"components": [
16+
{
17+
"name": "axios",
18+
"version": "0.21.1",
19+
"purl": "pkg:npm/axios@0.21.1",
20+
"type": "library",
21+
"bom-ref": "pkg:npm/axios@0.21.1"
22+
}
23+
],
24+
"dependencies": [
25+
{
26+
"ref": "pkg:npm/member-a@1.0.0",
27+
"dependsOn": [
28+
"pkg:npm/axios@0.21.1"
29+
]
30+
},
31+
{
32+
"ref": "pkg:npm/axios@0.21.1",
33+
"dependsOn": []
34+
}
35+
]
36+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"name": "test-workspace-root",
3+
"dependencies": {
4+
"member-a": {
5+
"version": "1.0.0",
6+
"resolved": "file:../packages/member-a",
7+
"overridden": false,
8+
"dependencies": {
9+
"axios": {
10+
"version": "0.21.1",
11+
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
12+
"overridden": false
13+
}
14+
}
15+
}
16+
}
17+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"name": "test-workspace-root",
3+
"dependencies": {
4+
"member-a": {
5+
"version": "1.0.0",
6+
"resolved": "file:../packages/member-a",
7+
"overridden": false,
8+
"dependencies": {
9+
"axios": {
10+
"version": "0.21.1",
11+
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
12+
"overridden": false,
13+
"dependencies": {
14+
"follow-redirects": {
15+
"version": "1.16.0",
16+
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz",
17+
"overridden": false
18+
}
19+
}
20+
}
21+
}
22+
}
23+
}
24+
}

test/providers/tst_manifests/npm/workspace_member/package-lock.json

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"name": "test-workspace-root",
3+
"private": true,
4+
"workspaces": ["packages/*"]
5+
}

0 commit comments

Comments
 (0)