Skip to content

Commit 0de3266

Browse files
Merge branch 'main' into patch-1
2 parents 77240e0 + 3d2cdeb commit 0de3266

File tree

591 files changed

+34206
-12685
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

591 files changed

+34206
-12685
lines changed

.editorconfig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,9 @@ dotnet_diagnostic.SA1623.severity = none
463463
# https://github.com/DotNetAnalyzers/StyleCopAnalyzers
464464
##########################################
465465

466+
dotnet_diagnostic.SA1009.severity = none
467+
dotnet_diagnostic.SA1111.severity = none
468+
466469
# https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1600.md
467470
# Elements should be documented
468471
dotnet_diagnostic.SA1600.severity = suggestion
@@ -697,6 +700,12 @@ dotnet_diagnostic.CA1851.severity = suggestion
697700
# CA1861: Avoid constant arrays as arguments
698701
dotnet_diagnostic.CA1861.severity = suggestion
699702

703+
# IDE0240: Nullable directive is redundant
704+
dotnet_diagnostic.IDE0240.severity = suggestion
705+
706+
# IDE0241: Nullable directive is unnecessary
707+
dotnet_diagnostic.IDE0241.severity = suggestion
708+
700709
# Workaround for https://github.com/dotnet/roslyn-analyzers/issues/5628
701710
[Program.cs]
702711
dotnet_diagnostic.ca1812.severity = none

.github/copilot-instructions.md

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
# Component Detection - AI Coding Agent Instructions
2+
3+
## Project Overview
4+
Component Detection is a **package scanning tool** that detects open-source dependencies across 15+ ecosystems (npm, NuGet, Maven, Go, etc.) and outputs a **dependency graph**. It's designed for build-time scanning and can be used as a library or CLI tool.
5+
6+
## Architecture
7+
8+
### Core Concepts
9+
- **Detectors**: Ecosystem-specific parsers that discover and parse manifest files (e.g., `package.json`, `requirements.txt`)
10+
- **Component Recorders**: Immutable graph stores that track detected components and their relationships
11+
- **Typed Components**: Strongly-typed models for each ecosystem (e.g., `NpmComponent`, `PipComponent`) in `src/Microsoft.ComponentDetection.Contracts/TypedComponent/`
12+
13+
### Project Structure
14+
```
15+
src/
16+
├── Microsoft.ComponentDetection/ # CLI entry point (Program.cs)
17+
├── Microsoft.ComponentDetection.Orchestrator/ # Command execution, DI setup, detector coordination
18+
├── Microsoft.ComponentDetection.Contracts/ # Interfaces (IComponentDetector, IComponentRecorder) and TypedComponent models
19+
├── Microsoft.ComponentDetection.Common/ # Shared utilities (file I/O, Docker, CLI invocation)
20+
└── Microsoft.ComponentDetection.Detectors/ # Per-ecosystem detector implementations (npm/, pip/, nuget/, etc.)
21+
```
22+
23+
### Detector Lifecycle Stages
24+
All new detectors start as **IDefaultOffComponentDetector** (must be explicitly enabled via `DetectorArgs`). Maintainers promote through:
25+
1. **DefaultOff** → 2. **IExperimentalDetector** (enabled but output not captured) → 3. **Default** (fully integrated)
26+
27+
### Dependency Injection
28+
All services register via `ServiceCollectionExtensions.AddComponentDetection()` in Orchestrator using standard .NET DI. Detectors use constructor injection for dependencies.
29+
30+
## Creating a New Detector
31+
32+
### Required Steps
33+
1. **Define Component Type** (if new ecosystem):
34+
- Add enum to `DetectorClass` and `ComponentType` in Contracts
35+
- Create `YourEcosystemComponent : TypedComponent` with required properties
36+
- Use `ValidateRequiredInput()` for mandatory fields
37+
38+
2. **Implement Detector**:
39+
```csharp
40+
public class YourDetector : FileComponentDetector, IDefaultOffComponentDetector
41+
{
42+
public YourDetector(
43+
IComponentStreamEnumerableFactory componentStreamEnumerableFactory,
44+
IObservableDirectoryWalkerFactory walkerFactory,
45+
ILogger<YourDetector> logger)
46+
{
47+
this.ComponentStreamEnumerableFactory = componentStreamEnumerableFactory;
48+
this.Scanner = walkerFactory;
49+
this.Logger = logger;
50+
}
51+
52+
public override string Id => "YourEcosystem";
53+
public override IEnumerable<string> Categories => [DetectorClass.YourCategory];
54+
public override IEnumerable<ComponentType> SupportedComponentTypes => [ComponentType.YourType];
55+
public override IEnumerable<string> SearchPatterns => ["manifest.lock"]; // Glob patterns
56+
57+
protected override Task OnFileFoundAsync(ProcessRequest request, IDictionary<string, string> detectorArgs)
58+
{
59+
var recorder = request.SingleFileComponentRecorder;
60+
// Parse file, create components, call recorder.RegisterUsage()
61+
}
62+
}
63+
```
64+
65+
3. **Register Detector in DI**:
66+
Add to `ServiceCollectionExtensions.AddComponentDetection()` in Orchestrator:
67+
```csharp
68+
services.AddSingleton<IComponentDetector, YourDetector>();
69+
```
70+
71+
4. **Register Components in Code**:
72+
```csharp
73+
var component = new DetectedComponent(new YourComponent("name", "1.0.0"));
74+
recorder.RegisterUsage(
75+
component,
76+
isExplicitReferencedDependency: true, // Direct dependency?
77+
parentComponentId: parentId, // For graph edges (can be null)
78+
isDevelopmentDependency: false // Build-only dependency?
79+
);
80+
```
81+
82+
### Detector Lifecycle Methods
83+
- `OnPrepareDetection()` - **Optional**: Pre-processing (e.g., filter files before parsing)
84+
- `OnFileFoundAsync()` - **Required**: Main parsing logic for matched files
85+
- `OnDetectionFinished()` - **Optional**: Cleanup (e.g., delete temp files)
86+
87+
### Testing Pattern
88+
```csharp
89+
[TestClass]
90+
public class YourDetectorTests : BaseDetectorTest<YourDetector>
91+
{
92+
[TestMethod]
93+
public async Task TestBasicDetection()
94+
{
95+
var fileContent = "name: pkg\nversion: 1.0.0";
96+
var (scanResult, componentRecorder) = await this.DetectorTestUtility
97+
.WithFile("manifest.lock", fileContent, ["manifest.lock"])
98+
.ExecuteDetectorAsync();
99+
100+
scanResult.ResultCode.Should().Be(ProcessingResultCode.Success);
101+
var components = componentRecorder.GetDetectedComponents();
102+
components.Should().HaveCount(1);
103+
}
104+
}
105+
```
106+
107+
Use minimal file content needed to exercise specific scenarios. Avoid testing multiple features in one test.
108+
109+
### End-to-End Verification
110+
Add test resources to `test/Microsoft.ComponentDetection.VerificationTests/resources/[ecosystem]/` with real-world examples that fully exercise your detector. These run in CI to prevent regressions.
111+
112+
## Development Workflows
113+
114+
### Build & Run
115+
```bash
116+
# Build
117+
dotnet build
118+
119+
# Run scan with new detector (replace YourDetectorId)
120+
dotnet run --project src/Microsoft.ComponentDetection/Microsoft.ComponentDetection.csproj scan \
121+
--Verbosity Verbose \
122+
--SourceDirectory /path/to/scan \
123+
--DetectorArgs YourDetectorId=EnableIfDefaultOff
124+
```
125+
126+
### Testing
127+
```bash
128+
# Run all tests
129+
dotnet test
130+
131+
# Run specific test project
132+
dotnet test test/Microsoft.ComponentDetection.Detectors.Tests/
133+
```
134+
135+
### Debug Mode
136+
Add `--Debug` flag to wait for debugger attachment on startup (prints PID).
137+
138+
## Key Patterns
139+
140+
### File Discovery
141+
Detectors specify `SearchPatterns` (glob patterns like `*.csproj` or `package-lock.json`). The orchestrator handles file traversal; detectors receive matched files via `ProcessRequest.ComponentStream`.
142+
143+
### Graph Construction
144+
- Use `RegisterUsage()` to add nodes and edges
145+
- `isExplicitReferencedDependency: true` marks direct dependencies (like packages in `package.json`)
146+
- `parentComponentId` creates parent-child edges (omit for flat graphs)
147+
- Some ecosystems don't support graphs (e.g., Go modules) - register components without parents
148+
149+
### Component Immutability
150+
`TypedComponent` classes must be immutable (no setters). Validation happens in constructors via `ValidateRequiredInput()`.
151+
152+
### Directory Exclusion
153+
Detectors can filter directories in `OnPrepareDetection()`. Example: npm detector ignores `node_modules` if a root lockfile exists.
154+
155+
## Common Pitfalls
156+
157+
- **Don't** implement `IComponentDetector` directly unless doing one-shot scanning (like Linux detector). Use `FileComponentDetector` for manifest-based detection.
158+
- **Don't** guess parent relationships - only create edges if the manifest explicitly defines them.
159+
- **Don't** use setters on `TypedComponent` - pass required values to constructor.
160+
- **Always** test with `DetectorTestUtility` pattern, not manual `ComponentRecorder` setup.
161+
- **Remember** new detectors must implement `IDefaultOffComponentDetector` until promoted by maintainers.
162+
163+
## References
164+
- Detector implementation examples: `src/Microsoft.ComponentDetection.Detectors/npm/`, `pip/`, `nuget/`
165+
- Creating detectors: `docs/creating-a-new-detector.md`
166+
- CLI arguments: `docs/detector-arguments.md`
167+
- Test utilities: `test/Microsoft.ComponentDetection.TestsUtilities/DetectorTestUtility.cs`

.github/workflows/build.yml

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,19 @@ jobs:
2020
MINVERBUILDMETADATA: build.${{github.run_number}}
2121

2222
steps:
23+
- name: Harden Runner
24+
uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
25+
with:
26+
egress-policy: audit
27+
2328
- name: Checkout repository
24-
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
29+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
2530
with:
31+
persist-credentials: false
2632
fetch-depth: 0
2733

2834
- name: Setup .NET
29-
uses: actions/setup-dotnet@3951f0dfe7a07e2313ec93c75700083e2005cbab # v4.3.0
35+
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
3036

3137
- name: Restore packages
3238
run: dotnet restore
@@ -35,9 +41,11 @@ jobs:
3541
run: dotnet build --no-restore --configuration Debug
3642

3743
- name: Run tests
38-
run: dotnet test --no-build --configuration Debug --collect:"Code Coverage;Format=cobertura;CoverageFileName=coverage.cobertura.xml"
44+
# The '--' separator forwards options to the Microsoft Testing Platform runner.
45+
# After upgrading to .NET 10 SDK, these can be passed directly without '--'.
46+
run: dotnet test --no-build --configuration Debug -- --coverage --coverage-output-format cobertura
3947

4048
- name: Upload coverage reports to Codecov
41-
uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3
49+
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
4250
env:
4351
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

.github/workflows/codeql-analysis.yml

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ name: "CodeQL"
22

33
on:
44
push:
5-
branches: [ main ]
5+
branches: [main]
66
pull_request:
7-
branches: [ main ]
7+
branches: [main]
88
schedule:
9-
- cron: '27 10 * * 1'
9+
- cron: "27 10 * * 1"
1010

1111
permissions:
1212
contents: read
@@ -21,18 +21,24 @@ jobs:
2121
security-events: write
2222

2323
steps:
24-
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
25-
with:
24+
- name: Harden Runner
25+
uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
26+
with:
27+
egress-policy: audit
28+
29+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
30+
with:
31+
persist-credentials: false
2632
fetch-depth: 0
2733

28-
- name: Initialize CodeQL
29-
uses: github/codeql-action/init@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
30-
with:
31-
languages: 'csharp'
32-
debug: true
34+
- name: Initialize CodeQL
35+
uses: github/codeql-action/init@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6
36+
with:
37+
languages: "csharp"
38+
debug: true
3339

34-
- name: Autobuild
35-
uses: github/codeql-action/autobuild@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
40+
- name: Autobuild
41+
uses: github/codeql-action/autobuild@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6
3642

37-
- name: Perform CodeQL Analysis
38-
uses: github/codeql-action/analyze@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
43+
- name: Perform CodeQL Analysis
44+
uses: github/codeql-action/analyze@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6

.github/workflows/detector-version-bump-reminder.yml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,20 @@ name: "Detector version bump reminder"
22
on:
33
push:
44
paths:
5-
- 'src/Microsoft.ComponentDetection.Detectors/**'
6-
5+
- "src/Microsoft.ComponentDetection.Detectors/**"
6+
77
permissions:
88
pull-requests: write
99

1010
jobs:
1111
comment:
1212
runs-on: ubuntu-latest
1313
steps:
14+
- name: Harden Runner
15+
uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
16+
with:
17+
egress-policy: audit
18+
1419
- uses: mshick/add-pr-comment@b8f338c590a895d50bcbfa6c5859251edc8952fc # v2
1520
with:
1621
repo-token: ${{ secrets.GITHUB_TOKEN }}
@@ -20,5 +25,5 @@ jobs:
2025
* The detector detects more or fewer components than before
2126
* The detector generates different parent/child graph relationships than before
2227
* The detector generates different `devDependencies` values than before
23-
28+
2429
If none of the above scenarios apply, feel free to ignore this comment 🙂

.github/workflows/gen-docs.yml

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,43 @@
1-
name: 'Generate docs'
1+
name: "Generate docs"
22

33
on:
44
push:
55
branches:
66
- main
77
paths:
8-
- 'src/Microsoft.ComponentDetection.Orchestrator/ArgumentSets/*.cs'
8+
- "src/Microsoft.ComponentDetection.Orchestrator/ArgumentSets/*.cs"
99

1010
permissions:
1111
contents: read
1212

1313
jobs:
1414
gen-docs:
1515
permissions:
16-
contents: write # for stefanzweifel/git-auto-commit-action to push code in repo
16+
contents: write # for stefanzweifel/git-auto-commit-action to push code in repo
1717
runs-on: ubuntu-latest
1818
steps:
19-
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
19+
- name: Harden Runner
20+
uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
2021
with:
21-
fetch-depth: 0
22+
egress-policy: audit
23+
24+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
25+
with:
26+
fetch-depth: 0 # zizmor: ignore[artipacked] git-auto-commit-action needs credentials to push
2227

2328
- name: Setup .NET Core
24-
uses: actions/setup-dotnet@3951f0dfe7a07e2313ec93c75700083e2005cbab # v4.3.0
29+
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
2530

2631
- name: Generate docs
2732
run: |
2833
touch version.json
2934
touch version_dev.json
30-
35+
3136
# Run CLI
3237
dotnet run -p src/Microsoft.ComponentDetection help scan 2> help.txt || true
3338
cat <<EOF > docs/detector-arguments.md
3439
# Detector arguments
35-
40+
3641
\`\`\`shell
3742
dotnet run -p './src/Microsoft.ComponentDetection' help scan
3843
\`\`\`
@@ -43,7 +48,7 @@ jobs:
4348
EOF
4449
4550
- name: Commit
46-
uses: stefanzweifel/git-auto-commit-action@8621497c8c39c72f3e2a999a26b4ca1b5058a842 # v5.0.1
51+
uses: stefanzweifel/git-auto-commit-action@04702edda442b2e678b25b537cec683a1493fcb9 # v7.1.0
4752
with:
48-
commit_message: 'Update docs'
49-
file_pattern: '*.md'
53+
commit_message: "Update docs"
54+
file_pattern: "*.md"

0 commit comments

Comments
 (0)