V2 frontend license checker (#3944)

# Added scripts for checking the licenses of dependencies similar to the backend app
This commit is contained in:
Anthony Stirling 2025-07-18 13:50:40 +01:00 committed by GitHub
parent 9af09c6ac2
commit 0742364a03
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 1042 additions and 17 deletions

View File

@ -0,0 +1,217 @@
name: Frontend License Report Workflow
on:
push:
branches:
- V2
paths:
- "frontend/package.json"
- "frontend/package-lock.json"
- "frontend/scripts/generate-licenses.js"
pull_request:
branches:
- V2
paths:
- "frontend/package.json"
- "frontend/package-lock.json"
- "frontend/scripts/generate-licenses.js"
permissions:
contents: read
jobs:
generate-frontend-license-report:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
repository-projects: write # Required for enabling automerge
steps:
- name: Harden Runner
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
with:
egress-policy: audit
- name: Check out code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
- name: Setup GitHub App Bot
id: setup-bot
uses: ./.github/actions/setup-bot
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- name: Set up Node.js
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with:
node-version: '18'
cache: 'npm'
cache-dependency-path: frontend/package-lock.json
- name: Install frontend dependencies
working-directory: frontend
run: npm ci
- name: Generate frontend license report
working-directory: frontend
run: npm run generate-licenses
- name: Check for license warnings
run: |
if [ -f "frontend/src/assets/license-warnings.json" ]; then
echo "LICENSE_WARNINGS_EXIST=true" >> $GITHUB_ENV
else
echo "LICENSE_WARNINGS_EXIST=false" >> $GITHUB_ENV
fi
# PR Event: Check licenses and comment on PR
- name: Delete previous license check comments
if: github.event_name == 'pull_request'
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
github-token: ${{ steps.setup-bot.outputs.token }}
script: |
const { owner, repo } = context.repo;
const prNumber = context.issue.number;
// Get all comments on the PR
const { data: comments } = await github.rest.issues.listComments({
owner,
repo,
issue_number: prNumber,
per_page: 100
});
// Filter for license check comments
const licenseComments = comments.filter(comment =>
comment.body.includes('## ✅ Frontend License Check Passed') ||
comment.body.includes('## ❌ Frontend License Check Failed')
);
// Delete old license check comments
for (const comment of licenseComments) {
console.log(`Deleting old license check comment: ${comment.id}`);
await github.rest.issues.deleteComment({
owner,
repo,
comment_id: comment.id
});
}
- name: Comment on PR - License Check Results
if: github.event_name == 'pull_request'
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
github-token: ${{ steps.setup-bot.outputs.token }}
script: |
const { owner, repo } = context.repo;
const prNumber = context.issue.number;
const hasWarnings = process.env.LICENSE_WARNINGS_EXIST === 'true';
let commentBody;
if (hasWarnings) {
// Read warnings file to get specific issues
const fs = require('fs');
let warningDetails = '';
try {
const warnings = JSON.parse(fs.readFileSync('frontend/src/assets/license-warnings.json', 'utf8'));
warningDetails = warnings.warnings.map(w => `- ${w.message}`).join('\n');
} catch (e) {
warningDetails = 'Unable to read warning details';
}
commentBody = `## ❌ Frontend License Check Failed
The frontend license check has detected compatibility warnings that require review:
${warningDetails}
**Action Required:** Please review these licenses to ensure they are acceptable for your use case before merging.
_This check will fail the PR until license issues are resolved._`;
} else {
commentBody = `## ✅ Frontend License Check Passed
All frontend licenses have been validated and no compatibility warnings were detected.
The frontend license report has been updated successfully.`;
}
await github.rest.issues.createComment({
owner,
repo,
issue_number: prNumber,
body: commentBody
});
- name: Fail workflow if license warnings exist (PR only)
if: github.event_name == 'pull_request' && env.LICENSE_WARNINGS_EXIST == 'true'
run: |
echo "❌ License warnings detected. Failing the workflow."
exit 1
# Push Event: Commit license files and create PR
- name: Commit changes (Push only)
if: github.event_name == 'push'
run: |
git add frontend/src/assets/3rdPartyLicenses.json
# Note: Do NOT commit license-warnings.json - it's only for PR review
git diff --staged --quiet || echo "CHANGES_DETECTED=true" >> $GITHUB_ENV
- name: Prepare PR body (Push only)
if: github.event_name == 'push'
run: |
PR_BODY="Auto-generated by ${{ steps.setup-bot.outputs.app-slug }}[bot]
This PR updates the frontend license report based on changes to package.json dependencies."
if [ "${{ env.LICENSE_WARNINGS_EXIST }}" = "true" ]; then
PR_BODY="$PR_BODY
## ⚠️ License Compatibility Warnings
The following licenses may require review for corporate compatibility:
$(cat frontend/src/assets/license-warnings.json | jq -r '.warnings[].message')
Please review these licenses to ensure they are acceptable for your use case."
fi
echo "PR_BODY<<EOF" >> $GITHUB_ENV
echo "$PR_BODY" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
- name: Create Pull Request (Push only)
id: cpr
if: github.event_name == 'push' && env.CHANGES_DETECTED == 'true'
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
with:
token: ${{ steps.setup-bot.outputs.token }}
commit-message: "Update Frontend 3rd Party Licenses"
committer: ${{ steps.setup-bot.outputs.committer }}
author: ${{ steps.setup-bot.outputs.committer }}
signoff: true
branch: update-frontend-3rd-party-licenses
base: V2
title: "Update Frontend 3rd Party Licenses"
body: ${{ env.PR_BODY }}
labels: Licenses,github-actions,frontend
draft: false
delete-branch: true
sign-commits: true
- name: Enable Pull Request Automerge (Push only)
if: github.event_name == 'push' && steps.cpr.outputs.pull-request-operation == 'created' && env.LICENSE_WARNINGS_EXIST == 'false'
run: gh pr merge --squash --auto "${{ steps.cpr.outputs.pull-request-number }}"
env:
GH_TOKEN: ${{ steps.setup-bot.outputs.token }}
- name: Add review required label (Push only)
if: github.event_name == 'push' && steps.cpr.outputs.pull-request-operation == 'created' && env.LICENSE_WARNINGS_EXIST == 'true'
run: gh pr edit "${{ steps.cpr.outputs.pull-request-number }}" --add-label "license-review-required"
env:
GH_TOKEN: ${{ steps.setup-bot.outputs.token }}

View File

@ -7,6 +7,7 @@
"": {
"name": "frontend",
"version": "0.1.0",
"license": "SEE LICENSE IN https://raw.githubusercontent.com/Stirling-Tools/Stirling-PDF/refs/heads/main/proprietary/LICENSE",
"dependencies": {
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0",
@ -39,6 +40,7 @@
"@types/react": "^19.1.4",
"@types/react-dom": "^19.1.5",
"@vitejs/plugin-react": "^4.5.0",
"license-checker": "^25.0.1",
"postcss": "^8.5.3",
"postcss-cli": "^11.0.1",
"postcss-preset-mantine": "^1.17.0",
@ -2241,8 +2243,8 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
"license": "ISC",
"optional": true
"devOptional": true,
"license": "ISC"
},
"node_modules/agent-base": {
"version": "6.0.2",
@ -2326,6 +2328,21 @@
"dequal": "^2.0.3"
}
},
"node_modules/array-find-index": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz",
"integrity": "sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/asap": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
"integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==",
"dev": true
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@ -2408,8 +2425,8 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"license": "MIT",
"optional": true
"devOptional": true,
"license": "MIT"
},
"node_modules/binary-extensions": {
"version": "2.3.0",
@ -2428,8 +2445,8 @@
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"devOptional": true,
"license": "MIT",
"optional": true,
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -2665,8 +2682,8 @@
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"license": "MIT",
"optional": true
"devOptional": true,
"license": "MIT"
},
"node_modules/console-control-strings": {
"version": "1.1.0",
@ -2770,6 +2787,16 @@
}
}
},
"node_modules/debuglog": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/debuglog/-/debuglog-1.0.1.tgz",
"integrity": "sha512-syBZ+rnAK3EgMsH2aYEOLUW7mZSY9Gb+0wUMCFsZvcmiz+HigA0LOcq/HoQqVuGG+EKykunc7QG2bzrponfaSw==",
"deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
"dev": true,
"engines": {
"node": "*"
}
},
"node_modules/decompress-response": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz",
@ -2833,6 +2860,16 @@
"integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==",
"license": "MIT"
},
"node_modules/dezalgo": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz",
"integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==",
"dev": true,
"dependencies": {
"asap": "^2.0.0",
"wrappy": "1"
}
},
"node_modules/dom-accessibility-api": {
"version": "0.5.16",
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
@ -3154,8 +3191,8 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
"license": "ISC",
"optional": true
"devOptional": true,
"license": "ISC"
},
"node_modules/fsevents": {
"version": "2.3.3",
@ -3274,8 +3311,8 @@
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"deprecated": "Glob versions prior to v9 are no longer supported",
"devOptional": true,
"license": "ISC",
"optional": true,
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
@ -3388,6 +3425,12 @@
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"license": "MIT"
},
"node_modules/hosted-git-info": {
"version": "2.8.9",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
"integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
"dev": true
},
"node_modules/html-parse-stringify": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
@ -3491,8 +3534,8 @@
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
"deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
"devOptional": true,
"license": "ISC",
"optional": true,
"dependencies": {
"once": "^1.3.0",
"wrappy": "1"
@ -3683,6 +3726,141 @@
"safe-buffer": "~5.1.0"
}
},
"node_modules/license-checker": {
"version": "25.0.1",
"resolved": "https://registry.npmjs.org/license-checker/-/license-checker-25.0.1.tgz",
"integrity": "sha512-mET5AIwl7MR2IAKYYoVBBpV0OnkKQ1xGj2IMMeEFIs42QAkEVjRtFZGWmQ28WeU7MP779iAgOaOy93Mn44mn6g==",
"dev": true,
"dependencies": {
"chalk": "^2.4.1",
"debug": "^3.1.0",
"mkdirp": "^0.5.1",
"nopt": "^4.0.1",
"read-installed": "~4.0.3",
"semver": "^5.5.0",
"spdx-correct": "^3.0.0",
"spdx-expression-parse": "^3.0.0",
"spdx-satisfies": "^4.0.0",
"treeify": "^1.1.0"
},
"bin": {
"license-checker": "bin/license-checker"
}
},
"node_modules/license-checker/node_modules/ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"dependencies": {
"color-convert": "^1.9.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/license-checker/node_modules/chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true,
"dependencies": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/license-checker/node_modules/color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"dev": true,
"dependencies": {
"color-name": "1.1.3"
}
},
"node_modules/license-checker/node_modules/color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
"dev": true
},
"node_modules/license-checker/node_modules/debug": {
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
"dev": true,
"dependencies": {
"ms": "^2.1.1"
}
},
"node_modules/license-checker/node_modules/escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
"dev": true,
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/license-checker/node_modules/has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
"dev": true,
"engines": {
"node": ">=4"
}
},
"node_modules/license-checker/node_modules/mkdirp": {
"version": "0.5.6",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
"dev": true,
"dependencies": {
"minimist": "^1.2.6"
},
"bin": {
"mkdirp": "bin/cmd.js"
}
},
"node_modules/license-checker/node_modules/nopt": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz",
"integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==",
"dev": true,
"dependencies": {
"abbrev": "1",
"osenv": "^0.1.4"
},
"bin": {
"nopt": "bin/nopt.js"
}
},
"node_modules/license-checker/node_modules/semver": {
"version": "5.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
"dev": true,
"bin": {
"semver": "bin/semver"
}
},
"node_modules/license-checker/node_modules/supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"dependencies": {
"has-flag": "^3.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/lie": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
@ -4077,8 +4255,8 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"devOptional": true,
"license": "ISC",
"optional": true,
"dependencies": {
"brace-expansion": "^1.1.7"
},
@ -4086,6 +4264,15 @@
"node": "*"
}
},
"node_modules/minimist": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"dev": true,
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/minipass": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
@ -4239,6 +4426,27 @@
"node": ">=6"
}
},
"node_modules/normalize-package-data": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
"integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
"dev": true,
"dependencies": {
"hosted-git-info": "^2.1.4",
"resolve": "^1.10.0",
"semver": "2 || 3 || 4 || 5",
"validate-npm-package-license": "^3.0.1"
}
},
"node_modules/normalize-package-data/node_modules/semver": {
"version": "5.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
"dev": true,
"bin": {
"semver": "bin/semver"
}
},
"node_modules/normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
@ -4258,6 +4466,12 @@
"node": ">=0.10.0"
}
},
"node_modules/npm-normalize-package-bin": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz",
"integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==",
"dev": true
},
"node_modules/npmlog": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz",
@ -4285,12 +4499,41 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"devOptional": true,
"license": "ISC",
"optional": true,
"dependencies": {
"wrappy": "1"
}
},
"node_modules/os-homedir": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
"integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/os-tmpdir": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
"integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/osenv": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz",
"integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==",
"deprecated": "This package is no longer supported.",
"dev": true,
"dependencies": {
"os-homedir": "^1.0.0",
"os-tmpdir": "^1.0.0"
}
},
"node_modules/pako": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
@ -4330,8 +4573,8 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
"devOptional": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=0.10.0"
}
@ -5078,6 +5321,46 @@
"pify": "^2.3.0"
}
},
"node_modules/read-installed": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/read-installed/-/read-installed-4.0.3.tgz",
"integrity": "sha512-O03wg/IYuV/VtnK2h/KXEt9VIbMUFbk3ERG0Iu4FhLZw0EP0T9znqrYDGn6ncbEsXUFaUjiVAWXHzxwt3lhRPQ==",
"deprecated": "This package is no longer supported.",
"dev": true,
"dependencies": {
"debuglog": "^1.0.1",
"read-package-json": "^2.0.0",
"readdir-scoped-modules": "^1.0.0",
"semver": "2 || 3 || 4 || 5",
"slide": "~1.1.3",
"util-extend": "^1.0.1"
},
"optionalDependencies": {
"graceful-fs": "^4.1.2"
}
},
"node_modules/read-installed/node_modules/semver": {
"version": "5.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
"dev": true,
"bin": {
"semver": "bin/semver"
}
},
"node_modules/read-package-json": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-2.1.2.tgz",
"integrity": "sha512-D1KmuLQr6ZSJS0tW8hf3WGpRlwszJOXZ3E8Yd/DNRaM5d+1wVRZdHlpGBLAuovjr28LbWvjpWkBHMxpRGGjzNA==",
"deprecated": "This package is no longer supported. Please use @npmcli/package-json instead.",
"dev": true,
"dependencies": {
"glob": "^7.1.1",
"json-parse-even-better-errors": "^2.3.0",
"normalize-package-data": "^2.0.0",
"npm-normalize-package-bin": "^1.0.0"
}
},
"node_modules/readable-stream": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
@ -5093,6 +5376,19 @@
"node": ">= 6"
}
},
"node_modules/readdir-scoped-modules": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz",
"integrity": "sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw==",
"deprecated": "This functionality has been moved to @npmcli/fs",
"dev": true,
"dependencies": {
"debuglog": "^1.0.1",
"dezalgo": "^1.0.0",
"graceful-fs": "^4.1.2",
"once": "^1.3.0"
}
},
"node_modules/readdirp": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
@ -5348,6 +5644,15 @@
"simple-concat": "^1.0.0"
}
},
"node_modules/slide": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz",
"integrity": "sha512-NwrtjCg+lZoqhFU8fOwl4ay2ei8PaqCBOUV3/ektPY9trO1yQ1oXEfmHAhKArUVUr/hOHvy5f6AdP17dCM0zMw==",
"dev": true,
"engines": {
"node": "*"
}
},
"node_modules/source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
@ -5366,6 +5671,66 @@
"node": ">=0.10.0"
}
},
"node_modules/spdx-compare": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/spdx-compare/-/spdx-compare-1.0.0.tgz",
"integrity": "sha512-C1mDZOX0hnu0ep9dfmuoi03+eOdDoz2yvK79RxbcrVEG1NO1Ph35yW102DHWKN4pk80nwCgeMmSY5L25VE4D9A==",
"dev": true,
"dependencies": {
"array-find-index": "^1.0.2",
"spdx-expression-parse": "^3.0.0",
"spdx-ranges": "^2.0.0"
}
},
"node_modules/spdx-correct": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz",
"integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==",
"dev": true,
"dependencies": {
"spdx-expression-parse": "^3.0.0",
"spdx-license-ids": "^3.0.0"
}
},
"node_modules/spdx-exceptions": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz",
"integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==",
"dev": true
},
"node_modules/spdx-expression-parse": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz",
"integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==",
"dev": true,
"dependencies": {
"spdx-exceptions": "^2.1.0",
"spdx-license-ids": "^3.0.0"
}
},
"node_modules/spdx-license-ids": {
"version": "3.0.21",
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz",
"integrity": "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==",
"dev": true
},
"node_modules/spdx-ranges": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/spdx-ranges/-/spdx-ranges-2.1.1.tgz",
"integrity": "sha512-mcdpQFV7UDAgLpXEE/jOMqvK4LBoO0uTQg0uvXUewmEFhpiZx5yJSZITHB8w1ZahKdhfZqP5GPEOKLyEq5p8XA==",
"dev": true
},
"node_modules/spdx-satisfies": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/spdx-satisfies/-/spdx-satisfies-4.0.1.tgz",
"integrity": "sha512-WVzZ/cXAzoNmjCWiEluEA3BjHp5tiUmmhn9MK+X0tBbR9sOqtC6UQwmgCNrAIZvNlMuBUYAaHYfb2oqlF9SwKA==",
"dev": true,
"dependencies": {
"spdx-compare": "^1.0.0",
"spdx-expression-parse": "^3.0.0",
"spdx-ranges": "^2.0.0"
}
},
"node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
@ -5604,6 +5969,15 @@
"node": ">=8.0"
}
},
"node_modules/treeify": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/treeify/-/treeify-1.1.0.tgz",
"integrity": "sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A==",
"dev": true,
"engines": {
"node": ">=0.6"
}
},
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
@ -5770,6 +6144,22 @@
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT"
},
"node_modules/util-extend": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/util-extend/-/util-extend-1.0.3.tgz",
"integrity": "sha512-mLs5zAK+ctllYBj+iAQvlDCwoxU/WDOUaJkcFudeiAX6OajC6BKXJUa9a+tbtkC11dz2Ufb7h0lyvIOVn4LADA==",
"dev": true
},
"node_modules/validate-npm-package-license": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
"integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
"dev": true,
"dependencies": {
"spdx-correct": "^3.0.0",
"spdx-expression-parse": "^3.0.0"
}
},
"node_modules/vite": {
"version": "6.3.5",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz",
@ -5919,8 +6309,8 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"license": "ISC",
"optional": true
"devOptional": true,
"license": "ISC"
},
"node_modules/y18n": {
"version": "5.0.8",

View File

@ -2,6 +2,7 @@
"name": "frontend",
"version": "0.1.0",
"private": true,
"license": "SEE LICENSE IN https://raw.githubusercontent.com/Stirling-Tools/Stirling-PDF/refs/heads/main/proprietary/LICENSE",
"proxy": "http://localhost:8080",
"dependencies": {
"@emotion/react": "^11.14.0",
@ -34,7 +35,8 @@
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
"preview": "vite preview",
"generate-licenses": "node scripts/generate-licenses.js"
},
"eslintConfig": {
"extends": [
@ -58,6 +60,7 @@
"@types/react": "^19.1.4",
"@types/react-dom": "^19.1.5",
"@vitejs/plugin-react": "^4.5.0",
"license-checker": "^25.0.1",
"postcss": "^8.5.3",
"postcss-cli": "^11.0.1",
"postcss-preset-mantine": "^1.17.0",

View File

@ -0,0 +1,415 @@
#!/usr/bin/env node
const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');
/**
* Generate 3rd party licenses for frontend dependencies
* This script creates a JSON file similar to the Java backend's 3rdPartyLicenses.json
*/
const OUTPUT_FILE = path.join(__dirname, '..', 'src', 'assets', '3rdPartyLicenses.json');
const PACKAGE_JSON = path.join(__dirname, '..', 'package.json');
// Ensure the output directory exists
const outputDir = path.dirname(OUTPUT_FILE);
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
console.log('🔍 Generating frontend license report...');
try {
// Install license-checker if not present
try {
require.resolve('license-checker');
} catch (e) {
console.log('📦 Installing license-checker...');
execSync('npm install --save-dev license-checker', { stdio: 'inherit' });
}
// Generate license report using license-checker (more reliable)
const licenseReport = execSync('npx license-checker --production --json', {
encoding: 'utf8',
cwd: path.dirname(PACKAGE_JSON)
});
let licenseData;
try {
licenseData = JSON.parse(licenseReport);
} catch (parseError) {
console.error('❌ Failed to parse license data:', parseError.message);
console.error('Raw output:', licenseReport.substring(0, 500) + '...');
process.exit(1);
}
if (!licenseData || typeof licenseData !== 'object') {
console.error('❌ Invalid license data structure');
process.exit(1);
}
// Convert license-checker format to array
const licenseArray = Object.entries(licenseData).map(([key, value]) => {
let name, version;
// Handle scoped packages like @mantine/core@1.0.0
if (key.startsWith('@')) {
const parts = key.split('@');
name = `@${parts[1]}`;
version = parts[2];
} else {
// Handle regular packages like react@18.0.0
const lastAtIndex = key.lastIndexOf('@');
name = key.substring(0, lastAtIndex);
version = key.substring(lastAtIndex + 1);
}
// Normalize license types for edge cases
let licenseType = value.licenses;
// Handle missing or null licenses
if (!licenseType || licenseType === null || licenseType === undefined) {
licenseType = 'Unknown';
}
// Handle empty string licenses
if (licenseType === '') {
licenseType = 'Unknown';
}
// Handle array licenses (rare but possible)
if (Array.isArray(licenseType)) {
licenseType = licenseType.join(' AND ');
}
// Handle object licenses (fallback)
if (typeof licenseType === 'object' && licenseType !== null) {
licenseType = 'Unknown';
}
return {
name: name,
version: version || value.version || 'unknown',
licenseType: licenseType,
repository: value.repository,
url: value.url,
link: value.licenseUrl
};
});
// Transform to match Java backend format
const transformedData = {
dependencies: licenseArray.map(dep => {
const licenseType = Array.isArray(dep.licenseType) ? dep.licenseType.join(', ') : (dep.licenseType || 'Unknown');
const licenseUrl = dep.link || getLicenseUrl(licenseType);
return {
moduleName: dep.name,
moduleUrl: dep.repository || dep.url || `https://www.npmjs.com/package/${dep.name}`,
moduleVersion: dep.version,
moduleLicense: licenseType,
moduleLicenseUrl: licenseUrl
};
})
};
// Log summary of license types found
const licenseSummary = licenseArray.reduce((acc, dep) => {
const license = Array.isArray(dep.licenseType) ? dep.licenseType.join(', ') : (dep.licenseType || 'Unknown');
acc[license] = (acc[license] || 0) + 1;
return acc;
}, {});
console.log('📊 License types found:');
Object.entries(licenseSummary).forEach(([license, count]) => {
console.log(` ${license}: ${count} packages`);
});
// Log any complex or unusual license formats for debugging
const complexLicenses = licenseArray.filter(dep =>
dep.licenseType && (
dep.licenseType.includes('AND') ||
dep.licenseType.includes('OR') ||
dep.licenseType === 'Unknown' ||
dep.licenseType.includes('SEE LICENSE')
)
);
if (complexLicenses.length > 0) {
console.log('\n🔍 Complex/Edge case licenses detected:');
complexLicenses.forEach(dep => {
console.log(` ${dep.name}@${dep.version}: "${dep.licenseType}"`);
});
}
// Check for potentially problematic licenses
const problematicLicenses = checkLicenseCompatibility(licenseSummary, licenseArray);
if (problematicLicenses.length > 0) {
console.log('\n⚠ License compatibility warnings:');
problematicLicenses.forEach(warning => {
console.log(` ${warning.message}`);
});
// Write license warnings to a separate file for CI/CD
const warningsFile = path.join(__dirname, '..', 'src', 'assets', 'license-warnings.json');
fs.writeFileSync(warningsFile, JSON.stringify({
warnings: problematicLicenses,
generated: new Date().toISOString()
}, null, 2));
console.log(`⚠️ License warnings saved to: ${warningsFile}`);
} else {
console.log('\n✅ All licenses appear to be corporate-friendly');
}
// Write to file
fs.writeFileSync(OUTPUT_FILE, JSON.stringify(transformedData, null, 4));
console.log(`✅ License report generated successfully!`);
console.log(`📄 Found ${transformedData.dependencies.length} dependencies`);
console.log(`💾 Saved to: ${OUTPUT_FILE}`);
} catch (error) {
console.error('❌ Error generating license report:', error.message);
process.exit(1);
}
/**
* Get standard license URLs for common licenses
*/
function getLicenseUrl(licenseType) {
if (!licenseType || licenseType === 'Unknown') return '';
const licenseUrls = {
'MIT': 'https://opensource.org/licenses/MIT',
'Apache-2.0': 'https://www.apache.org/licenses/LICENSE-2.0',
'Apache License 2.0': 'https://www.apache.org/licenses/LICENSE-2.0',
'BSD-3-Clause': 'https://opensource.org/licenses/BSD-3-Clause',
'BSD-2-Clause': 'https://opensource.org/licenses/BSD-2-Clause',
'BSD': 'https://opensource.org/licenses/BSD-3-Clause',
'GPL-3.0': 'https://www.gnu.org/licenses/gpl-3.0.html',
'GPL-2.0': 'https://www.gnu.org/licenses/gpl-2.0.html',
'LGPL-2.1': 'https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html',
'LGPL-3.0': 'https://www.gnu.org/licenses/lgpl-3.0.html',
'ISC': 'https://opensource.org/licenses/ISC',
'CC0-1.0': 'https://creativecommons.org/publicdomain/zero/1.0/',
'Unlicense': 'https://unlicense.org/',
'MPL-2.0': 'https://www.mozilla.org/en-US/MPL/2.0/',
'WTFPL': 'http://www.wtfpl.net/',
'Zlib': 'https://opensource.org/licenses/Zlib',
'Artistic-2.0': 'https://opensource.org/licenses/Artistic-2.0',
'EPL-1.0': 'https://www.eclipse.org/legal/epl-v10.html',
'EPL-2.0': 'https://www.eclipse.org/legal/epl-2.0/',
'CDDL-1.0': 'https://opensource.org/licenses/CDDL-1.0',
'Ruby': 'https://www.ruby-lang.org/en/about/license.txt',
'Python-2.0': 'https://www.python.org/download/releases/2.0/license/',
'Public Domain': 'https://creativecommons.org/publicdomain/zero/1.0/',
'UNLICENSED': ''
};
// Try exact match first
if (licenseUrls[licenseType]) {
return licenseUrls[licenseType];
}
// Try case-insensitive match
const lowerType = licenseType.toLowerCase();
for (const [key, url] of Object.entries(licenseUrls)) {
if (key.toLowerCase() === lowerType) {
return url;
}
}
// Handle complex SPDX expressions like "(MIT AND Zlib)" or "(MIT OR CC0-1.0)"
if (licenseType.includes('AND') || licenseType.includes('OR')) {
// Extract the first license from compound expressions for URL
const match = licenseType.match(/\(?\s*([A-Za-z0-9\-\.]+)/);
if (match && licenseUrls[match[1]]) {
return licenseUrls[match[1]];
}
}
// For non-standard licenses, return empty string (will use package link if available)
return '';
}
/**
* Check for potentially problematic licenses that may not be MIT/corporate compatible
*/
function checkLicenseCompatibility(licenseSummary, licenseArray) {
const warnings = [];
// Define problematic license patterns
const problematicLicenses = {
// Copyleft licenses
'GPL-2.0': 'Strong copyleft license - requires derivative works to be GPL',
'GPL-3.0': 'Strong copyleft license - requires derivative works to be GPL',
'LGPL-2.1': 'Weak copyleft license - may require source disclosure for modifications',
'LGPL-3.0': 'Weak copyleft license - may require source disclosure for modifications',
'AGPL-3.0': 'Network copyleft license - requires source disclosure for network use',
'AGPL-1.0': 'Network copyleft license - requires source disclosure for network use',
// Other potentially problematic licenses
'WTFPL': 'Potentially problematic license - legal uncertainty',
'CC-BY-SA-4.0': 'ShareAlike license - requires derivative works to use same license',
'CC-BY-SA-3.0': 'ShareAlike license - requires derivative works to use same license',
'CC-BY-NC-4.0': 'Non-commercial license - prohibits commercial use',
'CC-BY-NC-3.0': 'Non-commercial license - prohibits commercial use',
'OSL-3.0': 'Copyleft license - requires derivative works to be OSL',
'EPL-1.0': 'Weak copyleft license - may require source disclosure',
'EPL-2.0': 'Weak copyleft license - may require source disclosure',
'CDDL-1.0': 'Weak copyleft license - may require source disclosure',
'CDDL-1.1': 'Weak copyleft license - may require source disclosure',
'CPL-1.0': 'Weak copyleft license - may require source disclosure',
'MPL-1.1': 'Weak copyleft license - may require source disclosure',
'EUPL-1.1': 'Copyleft license - requires derivative works to be EUPL',
'EUPL-1.2': 'Copyleft license - requires derivative works to be EUPL',
'UNLICENSED': 'No license specified - usage rights unclear',
'Unknown': 'License not detected - manual review required'
};
// Known good licenses (no warnings needed)
const goodLicenses = new Set([
'MIT', 'Apache-2.0', 'Apache License 2.0', 'BSD-2-Clause', 'BSD-3-Clause', 'BSD',
'ISC', 'CC0-1.0', 'Public Domain', 'Unlicense', '0BSD', 'BlueOak-1.0.0',
'Zlib', 'Artistic-2.0', 'Python-2.0', 'Ruby', 'MPL-2.0', 'CC-BY-4.0',
'SEE LICENSE IN https://raw.githubusercontent.com/Stirling-Tools/Stirling-PDF/refs/heads/main/proprietary/LICENSE'
]);
// Helper function to normalize license names for comparison
function normalizeLicense(license) {
return license
.replace(/-or-later$/, '') // Remove -or-later suffix
.replace(/\+$/, '') // Remove + suffix
.trim();
}
// Check each license type
Object.entries(licenseSummary).forEach(([license, count]) => {
// Skip known good licenses
if (goodLicenses.has(license)) {
return;
}
// Check if this license only affects our own packages
const affectedPackages = licenseArray.filter(dep => {
const depLicense = Array.isArray(dep.licenseType) ? dep.licenseType.join(', ') : dep.licenseType;
return depLicense === license;
});
const isOnlyOurPackages = affectedPackages.every(dep =>
dep.name === 'frontend' ||
dep.name.toLowerCase().includes('stirling-pdf') ||
dep.name.toLowerCase().includes('stirling_pdf') ||
dep.name.toLowerCase().includes('stirlingpdf')
);
if (isOnlyOurPackages && (license === 'UNLICENSED' || license.startsWith('SEE LICENSE IN'))) {
return; // Skip warnings for our own Stirling-PDF packages
}
// Check for compound licenses like "(MIT AND Zlib)" or "(MIT OR CC0-1.0)"
if (license.includes('AND') || license.includes('OR')) {
// For OR licenses, check if there's at least one acceptable license option
if (license.includes('OR')) {
// Extract license components from OR expression
const orComponents = license
.replace(/[()]/g, '') // Remove parentheses
.split(' OR ')
.map(component => component.trim());
// Check if any component is in the goodLicenses set (with normalization)
const hasGoodLicense = orComponents.some(component => {
const normalized = normalizeLicense(component);
return goodLicenses.has(component) || goodLicenses.has(normalized);
});
if (hasGoodLicense) {
return; // Skip warning - can use the good license option
}
}
// For AND licenses or OR licenses with no good options, check for problematic components
const hasProblematicComponent = Object.keys(problematicLicenses).some(problematic =>
license.includes(problematic)
);
if (hasProblematicComponent) {
const affectedPackages = licenseArray
.filter(dep => {
const depLicense = Array.isArray(dep.licenseType) ? dep.licenseType.join(', ') : dep.licenseType;
return depLicense === license;
})
.map(dep => ({
name: dep.name,
version: dep.version,
url: dep.repository || dep.url || `https://www.npmjs.com/package/${dep.name}`
}));
const licenseType = license.includes('AND') ? 'AND' : 'OR';
const reason = licenseType === 'AND'
? 'Compound license with AND requirement - all components must be compatible'
: 'Compound license with potentially problematic components and no good fallback options';
warnings.push({
message: `📋 This PR contains ${count} package${count > 1 ? 's' : ''} with compound license "${license}" - manual review recommended`,
licenseType: license,
licenseUrl: '',
reason: reason,
packageCount: count,
affectedDependencies: affectedPackages
});
}
return;
}
// Check for exact matches with problematic licenses
if (problematicLicenses[license]) {
const affectedPackages = licenseArray
.filter(dep => {
const depLicense = Array.isArray(dep.licenseType) ? dep.licenseType.join(', ') : dep.licenseType;
return depLicense === license;
})
.map(dep => ({
name: dep.name,
version: dep.version,
url: dep.repository || dep.url || `https://www.npmjs.com/package/${dep.name}`
}));
const packageList = affectedPackages.map(pkg => pkg.name).slice(0, 5).join(', ') + (affectedPackages.length > 5 ? `, and ${affectedPackages.length - 5} more` : '');
const licenseUrl = getLicenseUrl(license) || 'https://opensource.org/licenses';
warnings.push({
message: `⚠️ This PR contains ${count} package${count > 1 ? 's' : ''} with license type [${license}](${licenseUrl}) - ${problematicLicenses[license]}. Affected packages: ${packageList}`,
licenseType: license,
licenseUrl: licenseUrl,
reason: problematicLicenses[license],
packageCount: count,
affectedDependencies: affectedPackages
});
} else {
// Unknown license type - flag for manual review
const affectedPackages = licenseArray
.filter(dep => {
const depLicense = Array.isArray(dep.licenseType) ? dep.licenseType.join(', ') : dep.licenseType;
return depLicense === license;
})
.map(dep => ({
name: dep.name,
version: dep.version,
url: dep.repository || dep.url || `https://www.npmjs.com/package/${dep.name}`
}));
warnings.push({
message: `❓ This PR contains ${count} package${count > 1 ? 's' : ''} with unknown license type "${license}" - manual review required`,
licenseType: license,
licenseUrl: '',
reason: 'Unknown license type',
packageCount: count,
affectedDependencies: affectedPackages
});
}
});
return warnings;
}