PR Metrics is a cross-platform GitHub Action and Azure DevOps task that analyzes
pull request size and test coverage, updating PR titles with indicators (XS, S,
M, L, XL, 2XL, etc) and test coverage symbols (✔ or
The codebase uses a unique dual-platform architecture with dependency injection:
- Runners:
src/task/src/runners/- Abstracts GitHub Actions vs Azure Pipelines executionrunnerInvoker.tsroutes togitHubRunnerInvoker.tsorazurePipelinesRunnerInvoker.ts- Detection:
process.env.GITHUB_ACTIONdetermines platform
- Repos:
src/task/src/repos/- Abstracts GitHub vs Azure DevOps repository APIsreposInvoker.tsroutes togitHubReposInvoker.tsorazureReposInvoker.ts- Both extend
baseReposInvoker.ts, which provides shared API error handling (e.g., mapping 401/403/404 to access-error messages) tokenManager.tshandles workload identity federation as an alternative to Personal Access Tokens- Single codebase handles both GitHub and Azure DevOps repositories
- Entry Point:
src/task/index.ts- Simple DI container resolution and error handling - Main Logic:
src/task/src/pullRequestMetrics.ts- Orchestrates the workflow, callingcodeMetricsCalculator.updateDetails()andcodeMetricsCalculator.updateComments()in parallel viaPromise.all() - Metrics Engine:
src/task/src/metrics/codeMetricsCalculator.ts- Validates conditions, calculates PR metrics, and definesupdateDetails()andupdateComments() - Git Integration:
src/task/src/git/gitInvoker.ts- Executes Git commands for diff analysis (numstat-based metrics) - Diff Parsing:
src/task/src/git/octokitGitDiffParser.ts- Uses theparse-git-difflibrary to determine, for each file, the first diff hunk start line (a diff line suitable as a comment anchor) for precise comment placement on GitHub PRs
- Dependency Injection: Uses
tsyringewith@singleton()and@injectable()decorators - Interface Abstraction: All major components implement interfaces (e.g.,
ReposInvokerInterface,RunnerInvokerInterface) - Localization: Resource strings in the
src/task/Strings/resources.resjson/folder withrunnerInvoker.loc()calls
npm run build:debug # Builds with source maps for debugging
npm run build:release # Production build with minification
npm run build:package # Creates dist/ for GitHub Action
npm run lint # ESLint with autofix (strict TypeScript rules)
npm run test:fast # Quick test run during development- Production:
src/task/src/- Main source code - Tests:
src/task/tests/- Mirror source structure for test files - Interfaces: Separate
.d.tsfiles for all major interfaces - Build Outputs:
debug/,release/(git-ignored),dist/(committed for GitHub Action)
The core functionality relies on:
git diff --numstat --ignore-all-space origin/<target>...pull/<pull_request_id>/merge
.github/workflow-scripts/Update-Version.ps1 (run by the release-initiate.yml
workflow) updates the version-bearing manifests and docs to the version
being released (for example, 1.7.16): package.json,
src/vss-extension.json, the task.json and task.loc.json task definitions,
the resources.resjson strings, constants.ts, and README.md. A separate
release-initiate.yml step, not the script, writes
.github/release-publish-trigger.txt with that same release version.
.github/release-version.json is the deliberate exception. It holds the
next release version, so the script sets its patch one higher than the
release (for example, 17 while everything else is 1.7.16).
So on a release pull request, .github/release-version.json reading one patch
ahead of every other version reference is expected, not a mismatch, drift,
or inconsistency. Do not flag it or try to "align" the patch numbers; lowering
it would break the next release. See the "next release version" comment in
Update-Version.ps1.
- Strict ESLint: Flat config (
eslint.config.mjs) extendingstrictTypeCheckedandstylisticTypeCheckedpresets with extensive custom rules - Explicit Types:
@typescript-eslint/explicit-function-return-typeenforced - Member Ordering: Specific ordering enforced (fields → constructor → methods)
- ES Modules: Uses
.jsextensions in imports for Node.js ESM compatibility
- Framework: Mocha + ts-mockito + c8 for coverage + fast-check for property-based testing
- Pattern: Arrange-Act-Assert structure
- Coverage: Maintain high coverage rates (critical for edge cases)
- Test Files:
.spec.tsfiles mirror source structure;.property.spec.tsfiles contain property-based (fuzz) tests
- Early return pattern for validation failures
- Localized error messages via
runnerInvoker.loc() - Status setting:
setStatusFailed(),setStatusSkipped(),setStatusSucceeded()
PR_METRICS_ACCESS_TOKEN - The only token variable read by the source code.
Typically set to ${{ secrets.GITHUB_TOKEN }} in workflows or to an Azure
DevOps PAT. Can also be populated automatically by workload identity federation
via TokenManager.
- GitHub: Octokit for REST API calls
(
@octokit/plugin-rest-endpoint-methods) - Azure DevOps:
azure-devops-node-apifor TFS API calls - Git: Shell execution via runner abstraction
- GitHub Action:
action.ymldefines inputs/outputs - Azure Task:
src/task/task.jsondefines task metadata and inputs - Both configs must stay synchronized for input definitions
- Use
npm run test:fastfor rapid iteration during development - Debug builds include source maps; check
debug/folder - Manual testing instructions in
src/task/tests/manualTests/Instructions.md - Set
system.debug: truein pipelines for verbose logging - The localization system requires
locInitialize()before usingloc()calls
