Skip to content

Commit 6f5f6e9

Browse files
committed
ci: setup danger.js
1 parent c406d57 commit 6f5f6e9

File tree

12 files changed

+4276
-1
lines changed

12 files changed

+4276
-1
lines changed

.github/workflows/ci.yml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,33 @@ on:
99
tags:
1010
- "v*.*.*"
1111

12+
# pull_request runs the matrix/build on the PR head with the fork-scoped token
13+
# (no comment perms on base repo).
1214
pull_request:
1315
branches:
1416
- develop
1517

18+
# pull_request_target runs repo-owned checks (e.g., Danger comments) on the base ref with the base repo token;
19+
# never executes PR code.
20+
pull_request_target:
21+
branches:
22+
- develop
23+
1624
concurrency:
1725
group: ${{format('{0}:{1}', github.repository, github.ref)}}
1826
cancel-in-progress: true
1927

2028
jobs:
2129
cpp-matrix:
30+
if: github.event_name != 'pull_request_target'
2231
runs-on: ubuntu-24.04
2332
container:
2433
image: ubuntu:24.04
2534
name: Generate Test Matrix
35+
# Permissions allow Danger to read PR context and post comments.
36+
permissions:
37+
contents: read
38+
pull-requests: write
2639
outputs:
2740
matrix: ${{ steps.cpp-matrix.outputs.matrix }}
2841
llvm-matrix: ${{ steps.llvm-matrix.outputs.llvm-matrix }}
@@ -120,6 +133,7 @@ jobs:
120133
node .github/releases-matrix.js
121134
122135
build:
136+
if: github.event_name != 'pull_request_target'
123137
needs: cpp-matrix
124138

125139
strategy:
@@ -1293,3 +1307,33 @@ jobs:
12931307
llvm_dir="/var/www/mrdox.com/llvm+clang"
12941308
chmod 755 ${{ matrix.llvm-archive-filename }}
12951309
scp -o StrictHostKeyChecking=no $(pwd)/${{ matrix.llvm-archive-filename }} ubuntu@dev-websites.cpp.al:$llvm_dir/
1310+
1311+
repo-checks:
1312+
name: Repo checks
1313+
# Run under pull_request_target so we can use the base-repo token to comment on forked PRs
1314+
# without executing forked code in this job. Declared after the matrix job so the matrix stays first in the UI.
1315+
if: github.event_name == 'pull_request_target'
1316+
runs-on: ubuntu-24.04
1317+
permissions:
1318+
contents: read
1319+
pull-requests: write
1320+
issues: write
1321+
statuses: write
1322+
steps:
1323+
- name: Checkout base revision
1324+
uses: actions/checkout@v4
1325+
with:
1326+
fetch-depth: 0
1327+
1328+
- name: Setup Node.js
1329+
uses: actions/setup-node@v4
1330+
with:
1331+
node-version: 20
1332+
1333+
- name: Install repo-check tools
1334+
run: npm --prefix util/danger ci
1335+
1336+
- name: Repo checks (Danger)
1337+
env:
1338+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
1339+
run: npx --prefix util/danger danger ci --dangerfile util/danger/dangerfile.ts

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@
2020
/share/mrdocs/libcxx/
2121
/share/mrdocs/clang/
2222
/docs/modules/reference
23+
/node_modules
2324
/.gdbinit
2425
/.lldbinit
25-
/.github/node_modules/
26+
/.github/node_modules/
27+
/util/danger/node_modules/
28+
/.roadmap
29+
/AGENTS.md

util/danger/README.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Danger.js checks for MrDocs
2+
3+
This directory contains the Danger.js rules and fixtures used in CI to add scoped summaries and hygiene warnings on pull requests.
4+
5+
## What runs in CI
6+
7+
- Danger executes via `npx danger ci --dangerfile util/danger/dangerfile.ts` in the `Repo checks` job.
8+
- That job runs on `pull_request_target` so forked PRs get comments/status updates using the base-repo token without executing forked code.
9+
- Permissions are scoped to comment and set statuses (`contents: read`, `pull-requests: write`, `issues: write`, `statuses: write`).
10+
- The job lives in `.github/workflows/ci.yml` after the matrix generator so matrix jobs stay first in the UI.
11+
12+
## Local usage
13+
14+
```bash
15+
npm --prefix util/danger ci # install dev deps (without touching the repo root)
16+
npm --prefix util/danger test # run Vitest unit tests for rule logic
17+
npm --prefix util/danger run danger:local # print the fixture report from util/danger/fixtures/sample-pr.json
18+
npm --prefix util/danger run danger:ci # run Danger in CI mode (requires GitHub PR context)
19+
```
20+
21+
## Key files
22+
23+
- `logic.ts` — Pure rule logic: scope mapping, conventional commit validation, size and hygiene warnings.
24+
- `runner.ts` — Minimal Danger runtime glue (fetches commits/files and feeds `logic.ts`).
25+
- `dangerfile.ts` — Entry point passed to `danger ci`; keep thin.
26+
- `fixtures/` — Sample PR payloads for local runs; update alongside rule changes.
27+
- `logic.test.ts` — Vitest coverage for the rule logic.
28+
- `package.json`, `package-lock.json`, `tsconfig.json` — Localized Node setup to avoid polluting the repository root.
29+
30+
## Conventions
31+
32+
- Scopes reflect the MrDocs tree: `source`, `tests`, `golden-tests`, `docs`, `ci`, `build`, `tooling`, `third-party`, `other`.
33+
- Conventional commit types allowed: `feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert`.
34+
- Non-test commit size warning triggers around 800 lines of churn (tests and golden fixtures ignored).
35+
36+
## Updating rules
37+
38+
- Edit `logic.ts`, refresh `fixtures/` as needed, and run `npm test`.
39+
- Keep warnings human-readable; prefer `warn()` over `fail()` until the team decides otherwise.
40+
41+
> Note: The Danger.js rules for MrDocs are still experimental, so some warnings may be rough or occasionally fire as false positives—feedback is welcome.

util/danger/dangerfile.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//
2+
// Licensed under the Apache License v2.0 with LLVM Exceptions.
3+
// See https://llvm.org/LICENSE.txt for license information.
4+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5+
//
6+
// Copyright (c) 2025 Alan de Freitas (alandefreitas@gmail.com)
7+
//
8+
// Official repository: https://github.com/cppalliance/mrdocs
9+
//
10+
import { runDanger } from "./runner";
11+
12+
// Provided globally by Danger at runtime; declared here for editors/typecheckers.
13+
declare function warn(message: string, file?: string, line?: number): void;
14+
15+
/**
16+
* Entrypoint for Danger; delegates to the rule runner.
17+
* Wraps execution to surface unexpected errors as warnings instead of failing CI.
18+
*/
19+
export default async function dangerfile(): Promise<void> {
20+
try {
21+
await runDanger();
22+
} catch (error) {
23+
warn(`Danger checks hit an unexpected error: ${String(error)}`);
24+
}
25+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"files": [
3+
{ "filename": "src/lib/example.cpp", "additions": 120, "deletions": 20 },
4+
{ "filename": "include/mrdocs/example.hpp", "additions": 30, "deletions": 5 },
5+
{ "filename": "test-files/golden-tests/foo/output.xml", "additions": 400, "deletions": 10 },
6+
{ "filename": "docs/modules/ROOT/pages/contribute.adoc", "additions": 12, "deletions": 3 },
7+
{ "filename": ".github/workflows/ci.yml", "additions": 5, "deletions": 1 }
8+
],
9+
"commits": [
10+
{
11+
"sha": "abc1234",
12+
"message": "fix(core): handle edge case",
13+
"files": [
14+
{ "filename": "src/lib/example.cpp", "additions": 120, "deletions": 20 },
15+
{ "filename": "include/mrdocs/example.hpp", "additions": 30, "deletions": 5 }
16+
]
17+
},
18+
{
19+
"sha": "def5678",
20+
"message": "docs: update contributing notes",
21+
"files": [
22+
{ "filename": "docs/modules/ROOT/pages/contribute.adoc", "additions": 12, "deletions": 3 }
23+
]
24+
},
25+
{
26+
"sha": "9999999",
27+
"message": "feat: massive change without tests",
28+
"files": [
29+
{ "filename": "src/lib/another.cpp", "additions": 900, "deletions": 200 }
30+
]
31+
}
32+
],
33+
"prBody": "Sample rationale\\n\\nTesting: unit tests locally",
34+
"prTitle": "Sample Danger fixture",
35+
"labels": []
36+
}

util/danger/logic.test.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
//
2+
// Licensed under the Apache License v2.0 with LLVM Exceptions.
3+
// See https://llvm.org/LICENSE.txt for license information.
4+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5+
//
6+
// Copyright (c) 2025 Alan de Freitas (alandefreitas@gmail.com)
7+
//
8+
// Official repository: https://github.com/cppalliance/mrdocs
9+
//
10+
import { describe, expect, it } from "vitest";
11+
import {
12+
commitSizeWarnings,
13+
parseCommitSummary,
14+
basicChecks,
15+
summarizeScopes,
16+
validateCommits,
17+
type CommitInfo,
18+
type DangerInputs,
19+
} from "./logic";
20+
21+
describe("parseCommitSummary", () => {
22+
// Ensures we correctly extract type, scope, and subject when format is valid.
23+
it("parses valid commit summaries", () => {
24+
const parsed = parseCommitSummary("fix(core): handle edge case");
25+
expect(parsed?.type).toBe("fix");
26+
expect(parsed?.scope).toBe("core");
27+
expect(parsed?.subject).toBe("handle edge case");
28+
});
29+
30+
// Guards against accepting malformed commit first lines.
31+
it("rejects invalid format", () => {
32+
expect(parseCommitSummary("invalid summary")).toBeNull();
33+
});
34+
});
35+
36+
describe("summarizeScopes", () => {
37+
// Verifies file paths are bucketed into the correct scopes and totals are tallied.
38+
it("aggregates by scope", () => {
39+
const report = summarizeScopes([
40+
{ filename: "src/lib/file.cpp", additions: 10, deletions: 2 },
41+
{ filename: "src/test/file.cpp", additions: 5, deletions: 1 },
42+
{ filename: "test-files/golden-tests/out.xml", additions: 100, deletions: 0 },
43+
{ filename: "docs/index.adoc", additions: 4, deletions: 0 },
44+
]);
45+
46+
expect(report.totals.source.files).toBe(1);
47+
expect(report.totals.tests.files).toBe(1);
48+
expect(report.totals["golden-tests"].files).toBe(1);
49+
expect(report.totals.docs.files).toBe(1);
50+
expect(report.overall.files).toBe(4);
51+
});
52+
});
53+
54+
describe("commitSizeWarnings", () => {
55+
// Confirms that large non-test churn triggers a warning while ignoring test fixtures.
56+
it("flags large non-test commits", () => {
57+
const commits: CommitInfo[] = [
58+
{
59+
sha: "abc",
60+
message: "feat: huge change",
61+
files: [
62+
{ filename: "src/lib/large.cpp", additions: 900, deletions: 200 },
63+
{ filename: "test-files/golden-tests/out.xml", additions: 1000, deletions: 0 },
64+
],
65+
},
66+
];
67+
const warnings = commitSizeWarnings(commits);
68+
expect(warnings.length).toBe(1);
69+
});
70+
});
71+
72+
describe("starterChecks", () => {
73+
// Checks that source changes without accompanying tests produce a warning.
74+
it("requests tests when source changes without coverage", () => {
75+
const inputs: DangerInputs = {
76+
files: [],
77+
commits: [],
78+
prBody: "Summary\n\nTesting: pending",
79+
prTitle: "Test PR",
80+
labels: [],
81+
};
82+
const summary = summarizeScopes([{ filename: "src/lib/file.cpp", additions: 1, deletions: 0 }]);
83+
const parsed = validateCommits([{ sha: "1", message: "fix: change" }]).parsed;
84+
const warnings = basicChecks(inputs, summary, parsed);
85+
expect(warnings.some((message) => message.includes("Source changed"))).toBe(true);
86+
});
87+
});

0 commit comments

Comments
 (0)