Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .github/workflows/pr-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,24 @@ jobs:
- name: Build ${{ matrix.configuration }}
run: dotnet build ModularityKit.Mutator.slnx -c ${{ matrix.configuration }} --no-restore

dependency-health:
name: dependency health
runs-on: ubuntu-latest
needs: build

steps:
- uses: actions/checkout@v5

- uses: actions/setup-dotnet@v5
with:
dotnet-version: 10.0.x

- name: Restore
run: dotnet restore ModularityKit.Mutator.slnx

- name: Check dependency health
run: python3 -m scripts.dependencies.check_package_health --solution ModularityKit.Mutator.slnx

tests:
name: ${{ matrix.name }}
runs-on: ubuntu-latest
Expand Down Expand Up @@ -158,6 +176,7 @@ jobs:
runs-on: ubuntu-latest
needs:
- build
- dependency-health
- tests
- examples-core
- examples-governance
Expand All @@ -168,12 +187,14 @@ jobs:
- name: Report job results
run: |
echo "build: ${{ needs.build.result }}"
echo "dependency-health: ${{ needs.dependency-health.result }}"
echo "tests: ${{ needs.tests.result }}"
echo "examples-core: ${{ needs.examples-core.result }}"
echo "examples-governance: ${{ needs.examples-governance.result }}"
echo "examples-governance-redis: ${{ needs.examples-governance-redis.result }}"

if [ "${{ needs.build.result }}" != "success" ] || \
[ "${{ needs.dependency-health.result }}" != "success" ] || \
[ "${{ needs.tests.result }}" != "success" ] || \
[ "${{ needs.examples-core.result }}" != "success" ] || \
[ "${{ needs.examples-governance.result }}" != "success" ] || \
Expand Down
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.idea/
bin/
obj/
obj/
__pycache__/
*.pyc
5 changes: 5 additions & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<Project>
<PropertyGroup>
<BaseIntermediateOutputPath>$(MSBuildProjectDirectory)/obj/$(MSBuildProjectName)/</BaseIntermediateOutputPath>
</PropertyGroup>
</Project>
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,13 @@
```bash
dotnet build ModularityKit.Mutator.slnx -c Release
```

## Dependency checks

Run these after `dotnet restore ModularityKit.Mutator.slnx`:

```bash
python3 -m scripts.dependencies.check_package_health --solution ModularityKit.Mutator.slnx
```

The check reports vulnerable packages as a failing condition and prints outdated packages for review. When a package needs attention, update the affected `PackageReference` version in the owning project and rerun the check.
1 change: 1 addition & 0 deletions scripts/dependencies/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Dependency check helpers."""
104 changes: 104 additions & 0 deletions scripts/dependencies/check_package_health.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
#!/usr/bin/env python3

from __future__ import annotations

import argparse
import os
import pathlib
import subprocess
import sys
from typing import Sequence


VULNERABLE_HEADING = "has the following vulnerable packages"
OUTDATED_HEADING = "has the following updates to its packages"


def repository_root() -> pathlib.Path:
return pathlib.Path(__file__).resolve().parents[2]


def run_dotnet_list(solution: str, mode: str) -> subprocess.CompletedProcess[str]:
env = os.environ.copy()
env.setdefault("DOTNET_CLI_UI_LANGUAGE", "en-US")

return subprocess.run(
[
"dotnet",
"list",
solution,
"package",
f"--{mode}",
"--include-transitive",
"--format",
"console",
],
cwd=repository_root(),
env=env,
text=True,
capture_output=True,
)


def emit_output(result: subprocess.CompletedProcess[str]) -> None:
if result.stdout:
sys.stdout.write(result.stdout)
if not result.stdout.endswith("\n"):
sys.stdout.write("\n")

if result.stderr:
sys.stderr.write(result.stderr)
if not result.stderr.endswith("\n"):
sys.stderr.write("\n")


def check_mode(solution: str, mode: str, heading: str) -> tuple[bool, int]:
result = run_dotnet_list(solution, mode)
emit_output(result)

found = heading in result.stdout
if result.returncode != 0 and not found:
return False, result.returncode

return found, 0


def parse_args(argv: Sequence[str]) -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Check package health for a solution.")
parser.add_argument(
"--solution",
default="ModularityKit.Mutator.slnx",
help="Path to the solution file to inspect.",
)
return parser.parse_args(argv)


def main(argv: Sequence[str]) -> int:
args = parse_args(argv)

vulnerable_found, vulnerable_error = check_mode(args.solution, "vulnerable", VULNERABLE_HEADING)
if vulnerable_error:
return vulnerable_error

outdated_found, outdated_error = check_mode(args.solution, "outdated", OUTDATED_HEADING)
if outdated_error:
return outdated_error

if vulnerable_found:
print(
"Vulnerable packages were reported above. Update the affected package reference and rerun the check.",
file=sys.stderr,
)
return 1

if outdated_found:
print(
"Outdated packages were reported above. Update the affected package references when you are ready.",
file=sys.stderr,
)

return 0


if __name__ == "__main__":
raise SystemExit(main(sys.argv[1:]))
Loading