#!/usr/bin/env bash set -euo pipefail # Collect third-party license texts into LICENSES.md # Usage: scripts/collect_licenses.sh [output-path] # output-path defaults to LICENSES.md at the repo root REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" OUT="${1:-$REPO_ROOT/LICENSES.md}" # ── Backend (Python) — uses pip-licenses ───────────────────────────── backend_licenses() { cd "$REPO_ROOT" # Extract direct dependency names from pyproject.toml local packages packages=$(uv run python3 -c " import re, tomllib with open('pyproject.toml', 'rb') as f: deps = tomllib.load(f)['project']['dependencies'] names = [re.split(r'[\[><=!;]', d)[0].strip() for d in deps] print(' '.join(names)) ") # shellcheck disable=SC2086 uv run pip-licenses \ --packages $packages \ --with-license-file \ --no-license-path \ --format=json \ | uv run python3 -c " import json, sys data = sorted(json.load(sys.stdin), key=lambda d: d['Name'].lower()) for d in data: name = d['Name'] version = d['Version'] lic = d.get('License', 'Unknown') text = d.get('LicenseText', '').strip() print(f'### {name} ({version}) — {lic}\n') if text and text != 'UNKNOWN': print('
') print('Full license text') print() print('\`\`\`') print(text) print('\`\`\`') print() print('
') else: print('*License file not found in package metadata.*') print() " } # ── Frontend (npm) ─────────────────────────────────────────────────── frontend_licenses() { cd "$REPO_ROOT/frontend" node -e " const fs = require('fs'); const path = require('path'); const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8')); const depNames = Object.keys(pkg.dependencies || {}).sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()) ); for (const name of depNames) { const pkgDir = path.join('node_modules', name); let version = 'unknown'; let licenseType = 'Unknown'; let licenseText = null; // Read package.json for version + license type try { const depPkg = JSON.parse(fs.readFileSync(path.join(pkgDir, 'package.json'), 'utf8')); version = depPkg.version || version; licenseType = depPkg.license || licenseType; } catch {} // Find license file (case-insensitive search) try { const files = fs.readdirSync(pkgDir); const licFile = files.find(f => /^(licen[sc]e|copying)/i.test(f)); if (licFile) { licenseText = fs.readFileSync(path.join(pkgDir, licFile), 'utf8').trim(); } } catch {} console.log('### ' + name + ' (' + version + ') — ' + licenseType + '\n'); if (licenseText) { console.log('
'); console.log('Full license text'); console.log(); console.log('\`\`\`'); console.log(licenseText); console.log('\`\`\`'); console.log(); console.log('
'); } else { console.log('*License file not found in package.*'); } console.log(); } " } # ── Assemble ───────────────────────────────────────────────────────── { echo "# Third-Party Licenses" echo echo "Auto-generated by \`scripts/collect_licenses.sh\` — do not edit by hand." echo echo "## Backend (Python) Dependencies" echo backend_licenses echo "## Frontend (npm) Dependencies" echo frontend_licenses } > "$OUT" echo "Wrote $OUT" >&2