Skip to content

Commit db8acc9

Browse files
committed
Add dependency license reporting
1 parent 525855c commit db8acc9

File tree

10 files changed

+222
-0
lines changed

10 files changed

+222
-0
lines changed
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
name: License Report
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
schedule:
8+
- cron: "36 3 * * 2"
9+
workflow_dispatch:
10+
11+
permissions:
12+
contents: read
13+
14+
jobs:
15+
license-report:
16+
name: License Report
17+
runs-on: ubuntu-latest
18+
steps:
19+
- name: Checkout
20+
uses: actions/checkout@v4
21+
22+
- name: Setup Node
23+
uses: actions/setup-node@v4
24+
with:
25+
node-version-file: .nvmrc
26+
cache: npm
27+
cache-dependency-path: |
28+
package-lock.json
29+
frontend/package-lock.json
30+
31+
- name: Setup Python
32+
uses: actions/setup-python@v5
33+
with:
34+
python-version: "3.12"
35+
cache: pip
36+
cache-dependency-path: backend/pyproject.toml
37+
38+
- name: Install root tooling
39+
run: npm ci
40+
41+
- name: Install frontend dependencies
42+
run: npm ci
43+
working-directory: frontend
44+
45+
- name: Install backend dependencies
46+
run: |
47+
python -m pip install --upgrade pip
48+
python -m pip install -e ./backend[dev]
49+
50+
- name: Generate license reports
51+
run: npm run report:licenses
52+
53+
- name: Upload license reports
54+
uses: actions/upload-artifact@v4
55+
with:
56+
name: license-reports
57+
path: reports/licenses/
58+
if-no-files-found: error

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,4 @@ Thumbs.db
2121

2222
__reference_nextjs_go_monorepo_kit/
2323
deep-research-report.md
24+
reports/licenses/

CONTRIBUTING.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,14 @@ For a pre-commit style check on staged content, run:
8787
npm run check:secrets -- --staged
8888
```
8989

90+
If you want a full dependency license inventory locally, run:
91+
92+
```bash
93+
npm run report:licenses
94+
```
95+
96+
That command writes generated reports into `reports/licenses/`.
97+
9098
Dependency review also runs automatically on pull requests to catch newly introduced vulnerable dependency changes.
9199

92100
## Changing the API Contract

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ npm run dev:down
9696
npm run api:types
9797
npm run check:contract
9898
npm run check:images
99+
npm run report:licenses
99100
npm run check:secrets
100101
npm run check:workflows
101102
npm run check
@@ -114,6 +115,8 @@ The root check runs:
114115

115116
`check:images` is separate and intended for environments where a Docker daemon is available.
116117

118+
`report:licenses` generates local npm and Python license inventories in `reports/licenses/`.
119+
117120
`check:secrets` scans tracked git content with a pinned `gitleaks` version via Go.
118121

119122
`check:workflows` lints `.github/workflows/` with a pinned `actionlint` version via Go.
@@ -122,6 +125,8 @@ CodeQL code scanning also runs on GitHub for `javascript-typescript`, `python`,
122125

123126
Pull requests also run GitHub dependency review so new vulnerable dependency changes are easier to catch before merge.
124127

128+
A separate GitHub workflow generates license-report artifacts for the root workspace, frontend workspace, and backend Python environment.
129+
125130
## Releases
126131

127132
- Release Drafter keeps a draft release updated from merged pull requests on `main` and can auto-label incoming pull requests by path.

SECURITY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ The repository also uses automated scanning to help catch common security issues
3232
- `gitleaks` in CI for tracked git content
3333
- CodeQL code scanning on GitHub for JavaScript/TypeScript, Python, and workflow files
3434
- GitHub dependency review on pull requests for newly introduced vulnerable dependency changes
35+
- GitHub license-report artifacts for npm and Python dependency inventories
3536

3637
Those checks do not replace private disclosure. If you believe a vulnerability is real or
3738
exploitable, please still report it through a private advisory.

backend/pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ dependencies = [
2020
[project.optional-dependencies]
2121
dev = [
2222
"httpx>=0.28,<1.0",
23+
"pip-licenses>=5.5,<6.0",
2324
"pytest>=8.3,<9.0",
2425
"ruff>=0.11,<1.0",
2526
]

future-reference-feature.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ The template-grade layer is the part worth reusing almost anywhere:
2222
- split CI
2323
- release automation
2424
- security scanning
25+
- license reporting
2526
- repo governance
2627
- image/build verification
2728
- smoke testing after release
@@ -65,6 +66,7 @@ Files:
6566
- `scripts/check-release-smoke.mjs`
6667
- `scripts/check-actionlint.mjs`
6768
- `scripts/check-secrets.mjs`
69+
- `scripts/report-licenses.mjs`
6870
- `package.json`
6971

7072
Why it matters:
@@ -213,14 +215,17 @@ Generic takeaway:
213215
Files:
214216

215217
- `scripts/check-secrets.mjs`
218+
- `scripts/report-licenses.mjs`
216219
- `.github/workflows/template-ci.yml`
217220
- `.github/workflows/codeql.yml`
221+
- `.github/workflows/license-report.yml`
218222
- `SECURITY.md`
219223

220224
What it covers here:
221225

222226
- tracked git content scanned with `gitleaks`
223227
- CodeQL scanning for JavaScript/TypeScript, Python, and workflow files
228+
- generated license inventories for npm and Python dependencies
224229
- private disclosure guidance
225230

226231
Why it matters:
@@ -233,6 +238,7 @@ Generic takeaway:
233238

234239
- secret scanning is a near-default for public repos
235240
- CodeQL or equivalent static analysis is a strong baseline for maintained starters
241+
- non-blocking license reporting is a good bridge before stricter allowlist enforcement
236242

237243
### 9. Workflow Linting
238244

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"check:release-smoke": "node scripts/check-release-smoke.mjs",
1212
"check:secrets": "node scripts/check-secrets.mjs",
1313
"check:workflows": "node scripts/check-actionlint.mjs",
14+
"report:licenses": "node scripts/report-licenses.mjs",
1415
"check": "node scripts/check.mjs"
1516
},
1617
"keywords": [

scripts/report-licenses.mjs

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
2+
import { spawnSync } from "node:child_process";
3+
import path from "node:path";
4+
import process from "node:process";
5+
6+
const root = process.cwd();
7+
const frontendDir = path.join(root, "frontend");
8+
const backendDir = path.join(root, "backend");
9+
const reportDir = path.join(root, "reports", "licenses");
10+
const npmLicenseTool = "[email protected]";
11+
12+
function resolvePythonCommand(workingDir) {
13+
const localPython =
14+
process.platform === "win32"
15+
? path.join(workingDir, ".venv", "Scripts", "python.exe")
16+
: path.join(workingDir, ".venv", "bin", "python");
17+
18+
return existsSync(localPython) ? localPython : "python";
19+
}
20+
21+
function runAndCapture(command, args, cwd) {
22+
const result = spawnSync(command, args, {
23+
cwd,
24+
encoding: "utf8",
25+
stdio: "pipe",
26+
shell: process.platform === "win32",
27+
});
28+
29+
if (result.error) {
30+
throw result.error;
31+
}
32+
33+
if (result.status !== 0) {
34+
const details = [result.stdout, result.stderr].filter(Boolean).join("\n").trim();
35+
throw new Error(
36+
details
37+
? `Command failed: ${command} ${args.join(" ")}\n${details}`
38+
: `Command failed: ${command} ${args.join(" ")}`,
39+
);
40+
}
41+
42+
return result.stdout;
43+
}
44+
45+
function writeReport(filename, content) {
46+
const outputPath = path.join(reportDir, filename);
47+
const normalized = content.endsWith("\n") ? content : `${content}\n`;
48+
writeFileSync(outputPath, normalized, "utf8");
49+
}
50+
51+
const backendPython = resolvePythonCommand(backendDir);
52+
53+
rmSync(reportDir, { recursive: true, force: true });
54+
mkdirSync(reportDir, { recursive: true });
55+
56+
writeReport(
57+
"root-npm.json",
58+
runAndCapture(
59+
"npx",
60+
[
61+
"--yes",
62+
npmLicenseTool,
63+
"--json",
64+
"--relativeLicensePath",
65+
"--relativeModulePath",
66+
],
67+
root,
68+
),
69+
);
70+
71+
writeReport(
72+
"root-npm-summary.txt",
73+
runAndCapture("npx", ["--yes", npmLicenseTool, "--summary"], root),
74+
);
75+
76+
writeReport(
77+
"frontend-npm.json",
78+
runAndCapture(
79+
"npx",
80+
[
81+
"--yes",
82+
npmLicenseTool,
83+
"--json",
84+
"--relativeLicensePath",
85+
"--relativeModulePath",
86+
],
87+
frontendDir,
88+
),
89+
);
90+
91+
writeReport(
92+
"frontend-npm-summary.txt",
93+
runAndCapture("npx", ["--yes", npmLicenseTool, "--summary"], frontendDir),
94+
);
95+
96+
writeReport(
97+
"backend-python.json",
98+
runAndCapture(
99+
backendPython,
100+
["-m", "piplicenses", "--from=mixed", "--with-urls", "--format=json"],
101+
backendDir,
102+
),
103+
);
104+
105+
writeReport(
106+
"backend-python-summary.txt",
107+
runAndCapture(
108+
backendPython,
109+
["-m", "piplicenses", "--from=mixed", "--summary", "--order=license"],
110+
backendDir,
111+
),
112+
);
113+
114+
writeReport(
115+
"README.md",
116+
[
117+
"# License Reports",
118+
"",
119+
`Generated from \`${path.basename(root)}\` on ${new Date().toISOString()}.`,
120+
"",
121+
"Files:",
122+
"",
123+
"- `root-npm.json`: root workspace npm dependency license inventory.",
124+
"- `root-npm-summary.txt`: aggregated license counts for the root workspace.",
125+
"- `frontend-npm.json`: frontend workspace npm dependency license inventory.",
126+
"- `frontend-npm-summary.txt`: aggregated license counts for the frontend workspace.",
127+
"- `backend-python.json`: backend Python dependency license inventory.",
128+
"- `backend-python-summary.txt`: aggregated license counts for the backend Python environment.",
129+
"",
130+
"These reports are generated artifacts and should not be committed.",
131+
].join("\n"),
132+
);
133+
134+
console.log(`Created license reports in ${path.relative(root, reportDir)}`);

template-playbook.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,13 @@ If a template only has code and no repo workflow, it is usually still a prototyp
3434
- `check:images` if the repo ships deployable containers
3535
- `check:workflows`
3636
- `check:secrets`
37+
- `report:licenses` if the repo has third-party dependencies worth auditing
3738

3839
### CI
3940

4041
- workflow lint
4142
- secret scan
43+
- dependency review on pull requests
4244
- app verification
4345
- cross-platform check if relevant
4446
- packaging or Docker build check if relevant
@@ -74,7 +76,9 @@ soon.md
7476
.github/ISSUE_TEMPLATE/*
7577
.github/release-drafter.yml
7678
.github/labels.json
79+
.github/dependency-review-config.yml
7780
.github/workflows/template-ci.yml
81+
.github/workflows/dependency-review.yml
7882
.github/workflows/release-drafter.yml
7983
.github/workflows/release.yml
8084
.github/workflows/release-smoke.yml
@@ -84,6 +88,7 @@ scripts/dev.mjs
8488
scripts/check.mjs
8589
scripts/check-actionlint.mjs
8690
scripts/check-secrets.mjs
91+
scripts/report-licenses.mjs
8792
```
8893

8994
Add these if relevant:
@@ -149,6 +154,8 @@ If you want the version that scales better for open source or long-term reuse, a
149154
- published artifacts should get smoke-tested
150155
- workflows should be linted
151156
- secrets should be scanned
157+
- dependency changes should be reviewed on pull requests
158+
- dependency licenses should be reportable without manual digging
152159
- release steps should be automated
153160
- docs should explain maintainer flow, not just user setup
154161

0 commit comments

Comments
 (0)