dependency-radar 0.4.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -14
- package/dist/cli.js +19 -0
- package/dist/cta.js +7 -1
- package/dist/generated/spdx.js +3 -0
- package/dist/report-assets.js +1 -1
- package/dist/report.js +38 -4
- package/dist/runners/lockfileGraph.js +1187 -0
- package/dist/runners/npmLs.js +15 -1
- package/dist/utils.js +36 -1
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ This runs a scan against the current project and writes a self-contained `depend
|
|
|
12
12
|
|
|
13
13
|
## What it does
|
|
14
14
|
|
|
15
|
-
- Analyses installed dependencies
|
|
15
|
+
- Analyses installed dependencies from lockfiles first (`pnpm-lock.yaml`, `package-lock.json`/`npm-shrinkwrap.json`, `yarn.lock`), with package-manager CLI fallback when needed
|
|
16
16
|
- Combines multiple signals (audit results, dependency graph data, import usage, and heuristics) into a single report
|
|
17
17
|
- Shows direct vs transitive dependencies, dependency depth, and parent relationships
|
|
18
18
|
- Highlights licences, known vulnerabilities, install-time scripts, native modules, and package footprint (including installed file counts)
|
|
@@ -41,26 +41,29 @@ When you run `npx dependency-radar` (or `dependency-radar scan`), the CLI execut
|
|
|
41
41
|
- Package manager from `packageManager`, lockfiles, and installed metadata
|
|
42
42
|
- Yarn Plug'n'Play detection (`.pnp.cjs`/`.pnp.js` or `.yarnrc.yml nodeLinker: pnp`)
|
|
43
43
|
3. Create a temporary `.dependency-radar/` directory inside the scanned project.
|
|
44
|
-
4. For each workspace package (or just the project root in single-package mode),
|
|
45
|
-
-
|
|
44
|
+
4. For each workspace package (or just the project root in single-package mode), collect dependency graph data:
|
|
45
|
+
- Lockfile-first graph parsing (`pnpm-lock.yaml`, `npm-shrinkwrap.json`/`package-lock.json`, `yarn.lock`)
|
|
46
|
+
- Fallback to package-manager tree commands (`npm ls` / `pnpm list` / `yarn list`) only when lockfile parsing is unavailable
|
|
47
|
+
- PNPM CLI fallback keeps depth retries for very large trees
|
|
48
|
+
5. Run additional collectors:
|
|
46
49
|
- Vulnerabilities (`npm audit` / `pnpm audit` / `yarn audit` or `yarn npm audit`)
|
|
47
50
|
- Version drift (`npm outdated` / `pnpm outdated` / `yarn outdated`, where available)
|
|
48
51
|
- Source import graph (static import/require parsing in `src/` or project root)
|
|
49
|
-
|
|
50
|
-
- PNPM dependency trees are filtered to installed-only packages (non-installed optional/platform variants are dropped)
|
|
51
|
-
|
|
52
|
+
6. Normalize outputs into one internal shape and merge workspace package results.
|
|
53
|
+
- PNPM lock/CLI dependency trees are filtered to installed-only packages (non-installed optional/platform variants are dropped)
|
|
54
|
+
7. Resolve and crawl installed package directories in `node_modules` to collect local metadata:
|
|
52
55
|
- Resolve `package.json` paths via package-manager-aware lookups (including PNPM virtual store layouts)
|
|
53
56
|
- Read local package metadata and license artifacts from installed files
|
|
54
|
-
|
|
57
|
+
8. Aggregate dependency records by enriching each installed package with:
|
|
55
58
|
- License declaration + `LICENSE` file inference/validation
|
|
56
59
|
- Advisory summaries and severity/risk rollups
|
|
57
60
|
- Root-cause/origin and runtime-impact heuristics
|
|
58
61
|
- Install-time execution signals
|
|
59
62
|
- Local package metadata (`description`, links, deprecation, TypeScript type availability, installed file count, CLI `bin` presence)
|
|
60
|
-
|
|
63
|
+
9. Write final output as either:
|
|
61
64
|
- `dependency-radar.html` (self-contained report), or
|
|
62
65
|
- `dependency-radar.json` (raw aggregated model)
|
|
63
|
-
|
|
66
|
+
10. Remove `.dependency-radar/` unless `--keep-temp` is set.
|
|
64
67
|
|
|
65
68
|
The scan is local-first: package metadata is read from `node_modules`; only audit/outdated commands require registry access.
|
|
66
69
|
|
|
@@ -70,10 +73,17 @@ The scan is local-first: package metadata is read from `node_modules`; only audi
|
|
|
70
73
|
- Package resolution is workspace-aware and PNPM-aware, including `.pnpm` virtual store paths.
|
|
71
74
|
- License discovery checks common file variants such as `LICENSE`, `LICENCE`, `COPYING`, and `NOTICE` (with or without extensions like `.md`).
|
|
72
75
|
|
|
76
|
+
### Lockfile-first dependency graphing
|
|
77
|
+
|
|
78
|
+
- Dependency graph construction starts from lockfiles so deep transitive packages are captured without relying on large `* ls` JSON payloads.
|
|
79
|
+
- Lockfile detection is scoped to the scan root/workspace root (it does not walk outside the scanned project).
|
|
80
|
+
- If lockfile parsing cannot be used, Dependency Radar falls back to package-manager tree commands and continues with warnings when partial failures occur.
|
|
81
|
+
|
|
73
82
|
### PNPM workspace hardening (problems solved)
|
|
74
83
|
|
|
75
84
|
- In real PNPM workspaces, `pnpm list --json` can include optional platform dependencies that are not installed on the current machine (for example `@esbuild/linux-*` on macOS ARM64).
|
|
76
|
-
- Dependency Radar
|
|
85
|
+
- Dependency Radar verifies PNPM entries against installed artifacts (`node_modules/.pnpm` and workspace-linked `node_modules` paths) before including them in the report.
|
|
86
|
+
- Dependency Radar uses `pnpm-lock.yaml` as the primary graph source and only falls back to `pnpm list` when needed, reducing OOM/string-length failures on large workspaces.
|
|
77
87
|
- Result: reports now reflect only dependencies that actually exist on disk and can be inspected locally.
|
|
78
88
|
|
|
79
89
|
## Usage Heuristics (`usage.runtimeImpact` and `usage.introduction`)
|
|
@@ -225,10 +235,10 @@ npx dependency-radar --help
|
|
|
225
235
|
|
|
226
236
|
## Package Manager Support
|
|
227
237
|
|
|
228
|
-
- npm: Supported for dependency tree, audit, outdated, single-package, and workspaces.
|
|
229
|
-
- pnpm: Supported for dependency tree, audit, outdated, and workspaces (with
|
|
230
|
-
- Yarn Classic (v1, node_modules linker): Supported for dependency tree, audit, outdated, and workspaces.
|
|
231
|
-
- Yarn Berry (v2+, node-modules linker):
|
|
238
|
+
- npm: Supported for lockfile-first dependency tree (`npm-shrinkwrap.json` or `package-lock.json`), audit, outdated, single-package, and workspaces.
|
|
239
|
+
- pnpm: Supported for lockfile-first dependency tree (`pnpm-lock.yaml`), audit, outdated, and workspaces (with `pnpm list` fallback depth retries when required).
|
|
240
|
+
- Yarn Classic (v1, node_modules linker): Supported for lockfile-first dependency tree (`yarn.lock`), audit, outdated, and workspaces.
|
|
241
|
+
- Yarn Berry (v2+, node-modules linker): Supported for lockfile-first dependency tree (`yarn.lock`) and audit; outdated support depends on available Yarn commands/plugins and may be unavailable.
|
|
232
242
|
- Yarn Plug'n'Play (`nodeLinker: pnp`): Not supported yet.
|
|
233
243
|
|
|
234
244
|
## Scripts
|
package/dist/cli.js
CHANGED
|
@@ -993,6 +993,14 @@ function openInBrowser(filePath) {
|
|
|
993
993
|
});
|
|
994
994
|
child.unref();
|
|
995
995
|
}
|
|
996
|
+
/**
|
|
997
|
+
* Orchestrates the CLI "scan" command to collect, merge, and output dependency data for a project or workspace.
|
|
998
|
+
*
|
|
999
|
+
* Detects workspace type and package manager, runs per-package collectors (audit, dependency tree, import graph, outdated),
|
|
1000
|
+
* merges collected signals into a workspace-level model, and writes a JSON or HTML report to the configured output path.
|
|
1001
|
+
* Manages a temporary working directory (created under the project as .dependency-radar), respects CLI options such as
|
|
1002
|
+
* JSON output, audit/outdated toggles, keeping the temp directory, and optionally opening the generated output with the
|
|
1003
|
+
* system default application. Exits the process with a non-zero code on fatal errors. */
|
|
996
1004
|
async function run() {
|
|
997
1005
|
var _a;
|
|
998
1006
|
const opts = parseArgs(process.argv.slice(2));
|
|
@@ -1032,6 +1040,16 @@ async function run() {
|
|
|
1032
1040
|
process.exit(1);
|
|
1033
1041
|
return;
|
|
1034
1042
|
}
|
|
1043
|
+
const hasProjectNodeModules = await (0, utils_1.pathExists)(path_1.default.join(projectPath, "node_modules"));
|
|
1044
|
+
if (!hasProjectNodeModules) {
|
|
1045
|
+
const workspaceHint = workspace.type === "none"
|
|
1046
|
+
? "single project"
|
|
1047
|
+
: `${workspace.type.toUpperCase()} workspace`;
|
|
1048
|
+
const yarnHint = yarnPnP
|
|
1049
|
+
? " Yarn Plug'n'Play appears enabled; Dependency Radar currently requires node_modules linker."
|
|
1050
|
+
: "";
|
|
1051
|
+
console.warn(`⚠ node_modules was not found at ${projectPath}. Scan completeness may be reduced for this ${workspaceHint}. Run your package manager install (npm install, pnpm install, or yarn install) before scanning.${yarnHint}`);
|
|
1052
|
+
}
|
|
1035
1053
|
const rootPkg = await readJsonFile(path_1.default.join(projectPath, "package.json"));
|
|
1036
1054
|
const projectDependencyPolicy = workspace.pnpmWorkspaceOverrides
|
|
1037
1055
|
? {
|
|
@@ -1093,6 +1111,7 @@ async function run() {
|
|
|
1093
1111
|
: Promise.resolve(undefined),
|
|
1094
1112
|
(0, npmLs_1.runNpmLs)(meta.path, pkgTempDir, scanManager, {
|
|
1095
1113
|
contextLabel: meta.name,
|
|
1114
|
+
lockfileSearchRoot: projectPath,
|
|
1096
1115
|
onProgress: (line) => spinner.log(line),
|
|
1097
1116
|
}).catch((err) => ({ ok: false, error: String(err) })),
|
|
1098
1117
|
(0, importGraphRunner_1.runImportGraph)(meta.path, pkgTempDir).catch((err) => ({ ok: false, error: String(err) })),
|
package/dist/cta.js
CHANGED
|
@@ -2,7 +2,13 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.CTA_BASE_URL = void 0;
|
|
4
4
|
exports.buildCtaUrl = buildCtaUrl;
|
|
5
|
-
exports.CTA_BASE_URL = 'https://dependency-radar.com
|
|
5
|
+
exports.CTA_BASE_URL = 'https://dependency-radar.com/next-steps?source=standalone-report';
|
|
6
|
+
/**
|
|
7
|
+
* Builds a CTA URL that includes the CLI version as a `cli` query parameter.
|
|
8
|
+
*
|
|
9
|
+
* @param version - CLI version string to include; when `undefined`, empty, or whitespace-only, `unknown` is used
|
|
10
|
+
* @returns The CTA URL with an appended `cli` query parameter whose value is the URI-encoded CLI version (or `unknown` when version is missing or empty)
|
|
11
|
+
*/
|
|
6
12
|
function buildCtaUrl(version) {
|
|
7
13
|
const normalizedVersion = typeof version === 'string' && version.trim().length > 0
|
|
8
14
|
? version.trim()
|
package/dist/generated/spdx.js
CHANGED
|
@@ -71,6 +71,7 @@ exports.SPDX_LICENSE_IDS = new Set([
|
|
|
71
71
|
'BlueOak-1.0.0',
|
|
72
72
|
'Boehm-GC',
|
|
73
73
|
'Boehm-GC-without-fee',
|
|
74
|
+
'BOLA-1.1',
|
|
74
75
|
'Borceux',
|
|
75
76
|
'Brian-Gladman-2-Clause',
|
|
76
77
|
'Brian-Gladman-3-Clause',
|
|
@@ -565,6 +566,7 @@ exports.SPDX_LICENSE_IDS = new Set([
|
|
|
565
566
|
'OPL-1.0',
|
|
566
567
|
'OPL-UK-3.0',
|
|
567
568
|
'OPUBL-1.0',
|
|
569
|
+
'OSC-1.0',
|
|
568
570
|
'OSET-PL-2.1',
|
|
569
571
|
'OSL-1.0',
|
|
570
572
|
'OSL-1.1',
|
|
@@ -839,6 +841,7 @@ exports.SPDX_EXCEPTION_IDS = new Set([
|
|
|
839
841
|
'SHL-2.0',
|
|
840
842
|
'SHL-2.1',
|
|
841
843
|
'Simple-Library-Usage-exception',
|
|
844
|
+
'sqlitestudio-OpenSSL-exception',
|
|
842
845
|
'stunnel-exception',
|
|
843
846
|
'SWI-exception',
|
|
844
847
|
'Swift-exception',
|
package/dist/report-assets.js
CHANGED
|
@@ -14,5 +14,5 @@ exports.CSS_CONTENT = `*,*:before,*:after{box-sizing:border-box}:root{--bg-prima
|
|
|
14
14
|
* JavaScript content for the dependency radar HTML report
|
|
15
15
|
* Built from report-ui/main.ts
|
|
16
16
|
*/
|
|
17
|
-
exports.JS_CONTENT = `!function(){"use strict";const e={permissive:["MIT","ISC","BSD-2-Clause","BSD-3-Clause","Apache-2.0","Unlicense","0BSD","CC0-1.0","BSD","Apache","Apache 2.0","Apache License 2.0","MIT License","ISC License"],weakCopyleft:["LGPL-2.1","LGPL-3.0","LGPL-2.0","LGPL","MPL-2.0","MPL-1.1","MPL","EPL-1.0","EPL-2.0","EPL"],strongCopyleft:["GPL-2.0","GPL-3.0","GPL","AGPL-3.0","AGPL","GPL-2.0-only","GPL-3.0-only","GPL-2.0-or-later","GPL-3.0-or-later"]},t={"network-access":"Accesses the network during install","dynamic-exec":"Uses dynamic execution","child-process":"Spawns child processes",encoding:"Uses encoding/decoding logic",obfuscated:"Contains obfuscated/minified install logic","reads-env":"Reads environment variables","reads-home":"Reads user home directory","uses-ssh":"Uses SSH configuration/keys"};function n(e){return e?e.charAt(0).toUpperCase()+e.slice(1):e}function s(e){var t,n;const s=e.compliance.license,a=(null==(t=s.declared)?void 0:t.valid)?s.declared.spdxId:void 0,i=null==(n=s.inferred)?void 0:n.spdxId;return a?{value:a,isInferred:!1}:i?{value:i,isInferred:!0}:{value:"Unknown",isInferred:!1}}function a(e){const t=e.security;if(null==t?void 0:t.summary)return{summary:t.summary,advisories:t.advisories};if(null==t?void 0:t.vulnerabilities){const e=t.vulnerabilities;return{summary:{critical:Number(e.critical||0),high:Number(e.high||0),moderate:Number(e.moderate||0),low:Number(e.low||0),highest:e.highest||"none",risk:t.vulnRisk||t.risk||"green"},advisories:t.advisories}}return{summary:{critical:0,high:0,moderate:0,low:0,highest:"none",risk:"green"},advisories:null==t?void 0:t.advisories}}function i(e){return(null==e?void 0:e.highest)||"none"}function r(e){return e&&e.risk||"green"}const o={permissive:"green",weakCopyleft:"amber",strongCopyleft:"red",unknown:"gray"},c={none:0,low:1,moderate:2,high:3,critical:4},l=[{id:"type",label:"Type",sortKey:"type",getValue:e=>e.usage.direct?"Dependency":"Sub-Dependency",getTone:e=>e.usage.direct?"green":"amber",sortFn:(e,t)=>e.usage.direct===t.usage.direct?0:e.usage.direct?-1:1},{id:"scope",label:"Scope",sortKey:"scope",getValue:e=>{return"runtime"===(t=e.usage.scope)?"Runtime":"dev"===t?"Dev":"optional"===t?"Optional":"peer"===t?"Peer":t;var t},getTone:e=>"runtime"===e.usage.scope?"green":"dev"===e.usage.scope||"optional"===e.usage.scope?"amber":"gray",sortFn:(e,t)=>e.usage.scope.localeCompare(t.usage.scope)},{id:"license",label:"License",sortKey:"license",getValue:e=>{const t=s(e),n=t.isInferred?\`\${t.value} (inferred)\`:t.value;return"mismatch"===e.compliance.license.status?\`\${n} *\`:n},getTone:t=>{const n=function(t){if(!t)return"unknown";const n=t.toUpperCase();for(const[s,a]of Object.entries(e))if(a.some(e=>n.includes(e.toUpperCase())))return s;return"unknown"}(s(t).value);return o[n]},sortFn:(e,t)=>{const n=s(e).value,a=s(t).value;return n.localeCompare(a)}},{id:"vulns",label:"Vulnerabilities",sortKey:"severity",getValue:e=>n(i(a(e).summary)),getTone:e=>a(e).summary.risk,sortFn:(e,t)=>c[i(a(t).summary)]-c[i(a(e).summary)]},{id:"install",label:"Install",sortKey:"install",getValue:e=>{return(t=e.execution)?n(t.risk||"low"):"Low";var t},getTone:e=>r(e.execution),sortFn:(e,t)=>{const n={green:0,amber:1,red:2},s=r(e.execution),a=r(t.execution);return n[s]-n[a]}}],d=l.length;function u(e){var t,n;const s=e.compliance.license,a=(null==(t=s.declared)?void 0:t.valid)?s.declared.spdxId:void 0,i=null==(n=s.inferred)?void 0:n.spdxId;return a?{value:a,isInferred:!1}:i?{value:i,isInferred:!0}:{value:"Unknown",isInferred:!1}}function p(e){switch(e){case"declared-only":return"Declared";case"inferred-only":return"Inferred";case"match":return"Declared + Inferred (match)";case"mismatch":return"Declared + Inferred (mismatch)";case"invalid-spdx":return"Invalid SPDX";default:return"Unknown"}}const g={none:0,low:1,moderate:2,high:3,critical:4};function m(e){const t=e.security;if(null==t?void 0:t.summary)return{summary:t.summary,advisories:t.advisories};if(null==t?void 0:t.vulnerabilities){const e=t.vulnerabilities;return{summary:{critical:Number(e.critical||0),high:Number(e.high||0),moderate:Number(e.moderate||0),low:Number(e.low||0),highest:e.highest||"none",risk:t.vulnRisk||t.risk||"green"},advisories:t.advisories}}return{summary:{critical:0,high:0,moderate:0,low:0,highest:"none",risk:"green"},advisories:null==t?void 0:t.advisories}}function v(e){return e?String(e).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,"""):""}function h(e){return e?e.charAt(0).toUpperCase()+e.slice(1):e}function f(e){return e.split(/[\\s-_]+/).map(e=>e?h(e):e).join(" ")}function y(e){return l.map(t=>{return n=t.label,s=t.getValue(e),'<div class="badge-card '+t.getTone(e)+'"><span class="badge-label">'+v(n)+'</span><span class="badge-value">'+v(s)+"</span></div>";var n,s}).join("")}function k(e,t){const n=l.map(n=>function(e,t,n,s){const a=n===e,i=a?s?" ▲":" ▼":"",r=a?s?" sorted-asc":" sorted-desc":"";return'<button type="button" class="column-header'+(a?" sorted":"")+r+'" data-sort="'+v(e)+'"><span class="column-header-label">'+v(t)+'</span><span class="sort-indicator">'+i+"</span></button>"}(n.sortKey,n.label,e,t)).join("");return'<div class="column-headers" style="--column-count: '+d+'">'+n+"</div>"}function b(e,t,n){let s='<div class="kv-item">';return s+='<span class="kv-label">'+v(e)+"</span>",s+='<span class="kv-value">'+v(String(t))+"</span>",n&&(s+='<span class="kv-hint">'+v(n)+"</span>"),s+="</div>",s}function w(e,t){return'<span class="kv-value risk-value"><span class="risk-dot '+t+'"></span>'+v(String(e))+"</span>"}function C(e,t,n){let s='<div class="kv-item">';return s+='<span class="kv-label">'+v(e)+"</span>",s+=t,s+="</div>",s}function I(e,t){if(!e||0===e.length)return'<span class="kv-value">None</span>';const n=e.slice(0,t),s=e.length-t;let a='<div class="package-list">';return n.forEach(e=>{a+='<span class="package-tag">'+v(e)+"</span>"}),s>0&&(a+='<span class="package-tag">+'+s+" more</span>"),a+="</div>",a}function L(e,t,n,s){if(!e||0===e.length)return'<span class="kv-value">None</span>';const a=e.slice(0,t),i=e.length-t;let r='<div class="package-list">';return a.forEach(e=>{const t=P(e,n,s);r+=t?'<a class="package-tag package-tag-link root-package-link" href="#'+v(B(t))+'" data-dep-key="'+v(t)+'" aria-label="Jump to dependency '+v(t)+'">'+v(e)+"</a>":'<span class="package-tag">'+v(e)+"</span>"}),i>0&&(r+='<span class="package-tag">+'+i+" more</span>"),r+="</div>",r}function x(e,t){return e+"@"+t}const E=new WeakMap;function S(e){const t=e.lastIndexOf("@npm:");if(t>0)return{name:e.slice(0,t),version:e.slice(t+1)};const n=e.lastIndexOf("@");return n<=0?null:{name:e.slice(0,n),version:e.slice(n+1)}}function B(e){return"dep-"+encodeURIComponent(e).replace(/%/g,"_")}function D(e){const t=E.get(e);if(t)return t;const n=new Map;return e.forEach(e=>{const t=S(e);if(!t)return;const s=n.get(t.name)||[];s.push(e),n.set(t.name,s)}),E.set(e,n),n}function j(e,t,n){const s=((n||D(t)).get(e)||[]).filter(e=>t.has(e));return 1===s.length?s[0]:null}function P(e,t,n){if(t.has(e))return e;const s=S(e);if(!s)return j(e,t,n);if(s.version.startsWith("npm:")){const e=s.version.slice(4),n=s.name+(e.startsWith("@")?e:"@"+e);if(t.has(n))return n}return j(s.name,t,n)}function T(e,t,n,s){if(!e||0===e.length)return'<span class="kv-value">None</span>';const a=e.slice(0,t),i=e.length-t;let r='<div class="package-list">';return a.forEach(e=>{if("string"==typeof e){const t=P(e,n,s);return t?void(r+='<a class="package-tag package-tag-link root-package-link" href="#'+v(B(t))+'" data-dep-key="'+v(t)+'" aria-label="Jump to dependency '+v(t)+'">'+v(e)+"</a>"):void(r+='<span class="package-tag">'+v(e)+"</span>")}const t=x(e.name,e.version),a=e.name+"@"+e.version,i=P(t,n,s);r+=i?'<a class="package-tag package-tag-link root-package-link" href="#'+v(B(i))+'" data-dep-key="'+v(i)+'" aria-label="Jump to dependency '+v(i)+'">'+v(a)+"</a>":'<span class="package-tag">'+v(a)+"</span>"}),i>0&&(r+='<span class="package-tag">+'+i+" more</span>"),r+="</div>",r}function N(e,t,n){const s=e.graph.subDeps;if(!s)return"";const a=[{title:"Dependencies",key:"dep"},{title:"Optional",key:"opt"},{title:"Peer",key:"peer"},{title:"Dev Dependencies",key:"dev"}];let i=0,r=0;for(const l of a){const e=s[l.key];if(e)for(const t of Object.values(e))i+=1,t[1]&&(r+=1)}if(0===i)return"";const o='<div class="declared-summary">Total: '+i+" • Installed: "+r+" • Not installed: "+(i-r)+"</div>",c=a.map(e=>{const a=s[e.key];if(!a||0===Object.keys(a).length)return"";let i=0,r=0;const o=Object.entries(a).sort(([e],[t])=>e.localeCompare(t)).map(([e,[s,a]])=>{i+=1,a&&(r+=1);const o='<div class="declared-name">'+v(e)+"</div>",c='<div class="declared-range">'+v(s)+"</div>",l=a?function(e,t,n){const s=P(e,t,n);return s?'<a class="status-pill installed root-package-link" href="#'+v(B(s))+'" data-dep-key="'+v(s)+'" aria-label="Jump to dependency '+v(s)+'">Installed</a>':'<span class="status-pill installed">Installed</span>'}(a,t,n):'<span class="status-pill missing">Not installed</span>';return'<div class="declared-row">'+o+c+l+"</div>"}),c=r+" of "+i+" installed";return['<details class="declared-group">','<summary class="declared-group-summary"><span class="expand-icon" aria-hidden="true"></span><span class="declared-group-title">'+v(e.title)+' <span class="declared-count">('+c+")</span></span></summary>",'<div class="declared-table">'+o.join("")+"</div>","</details>"].join("")}).filter(Boolean);return H("Declared Dependencies","Dependencies declared by this package",o+'<div class="declared-deps">'+c.join("")+"</div>")}function H(e,t,n){let s='<div class="section">';return s+='<div class="section-header">',s+='<span class="section-title">'+v(e)+"</span>",t&&(s+='<span class="section-desc">'+v(t)+"</span>"),s+="</div>",s+=n,s+="</div>",s}function A(e,t,n,s){let a='<div class="subsection'+(s?" "+s:"")+'">';return a+='<div class="subsection-header">',a+='<span class="subsection-title">'+v(e)+"</span>",n&&(a+='<span class="subsection-desc">'+v(n)+"</span>"),a+="</div>",a+=t,a+="</div>",a}function U(e){if(!e)return;const t=e.trim();if(!t)return;const n=e=>e.replace(/\\.git\$/i,"");if(/^https?:\\/\\//i.test(t))return n(t);if(/^git\\+https?:\\/\\//i.test(t))return n(t.replace(/^git\\+/,""));if(/^git:\\/\\/github\\.com\\//i.test(t))return n(t.replace(/^git:\\/\\//i,"https://"));if(/^github:/i.test(t)){const e=t.slice(7).replace(/^\\/+/,"");return e?\`https://github.com/\${n(e)}\`:void 0}if(/^git@github\\.com:/i.test(t)){const e=t.slice(15);return e?\`https://github.com/\${n(e)}\`:void 0}return/^[A-Za-z0-9_.-]+\\/[A-Za-z0-9_.-]+\$/.test(t)?\`https://github.com/\${n(t)}\`:void 0}function M(e,t){const n=U(e);if(!n)return;let s;try{s=new URL(n)}catch{return}if("github.com"!==s.hostname.toLowerCase())return;const a=s.pathname.split("/").filter(Boolean);if(a.length<2)return;const i=a[0],r=a[1].replace(/\\.git\$/i,"");return i&&r?\`https://github.com/\${i}/\${r}/blob/HEAD/\${t}\`:void 0}function V(e,t){return t?v(e)+' <a class="kv-inline-link" href="'+v(t)+'" target="_blank" rel="noopener">GitHub<svg class="kv-inline-link-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M7 17 17 7"/><path d="M9 7h8v8"/></svg></a>':v(e)}function \$(e){const t='<svg viewBox="0 0 24 24" fill="currentColor"><path d="M0 7.334v8h6.666v1.332H12v-1.332h12v-8H0zm6.666 6.664H5.334v-4H3.999v4H1.335V8.667h5.331v5.331zm4 0v1.336H8.001V8.667h5.334v5.332h-2.669v-.001zm12.001 0h-1.33v-4h-1.336v4h-1.335v-4h-1.33v4h-2.671V8.667h8.002v5.331zM10.665 10H12v2.667h-1.335V10z"/></svg>',n='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"/></svg>',s='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>',a='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>';if(!(e.npm||e.repository||e.homepage||e.issues))return"";let i='<div class="package-links">';return e.npm&&(i+='<a href="'+v(e.npm)+'" target="_blank" rel="noopener" class="package-link">'+t+"npm</a>"),e.repository&&(i+='<a href="'+v(e.repository)+'" target="_blank" rel="noopener" class="package-link">'+n+"Repository</a>"),e.homepage&&(i+='<a href="'+v(e.homepage)+'" target="_blank" rel="noopener" class="package-link">'+a+"Homepage</a>"),e.issues&&(i+='<a href="'+v(e.issues)+'" target="_blank" rel="noopener" class="package-link">'+s+"Issues</a>"),i+="</div>",i}function O(e){const t=function(e,t){const n=[e.risk,t];return n.includes("red")?"red":n.includes("amber")?"amber":"green"}(m(e).summary,e.compliance.licenseRisk),n=x(e.package.name,e.package.version),s=B(n),a=y(e),i=['<summary class="dep-summary">','<span class="expand-icon" aria-hidden="true"></span>','<span class="dep-name">'+v(e.package.name)+'<span class="dep-version">@'+v(e.package.version)+"</span></span>",'<div class="dep-indicators" style="--column-count: '+d+'">',a,"</div>","</summary>"].join("");return['<details class="dep-card" data-risk="'+t+'" data-dep-key="'+v(n)+'" id="'+v(s)+'">',i,'<div class="dep-details" data-rendered="false"></div>',"</details>"].join("")}function R(e,n,s){var a,i,r,o,c;const l=m(e),d=l.summary,g=u(e),y=g.isInferred?\`\${g.value} (inferred)\`:g.value,k=function(e){var t;const n=(null==(t=e.package)?void 0:t.links)||{},s=e.links||{},a=U(n.repository||s.repository||s.repo);return{npm:n.npm||s.npm,repository:a||n.repository||s.repository||s.repo,homepage:n.homepage||s.homepage,issues:n.bugs||n.issues||s.bugs||s.issues}}(e),x=JSON.stringify(e,null,2),E=[e.usage.direct?"Direct dependency":"Indirect dependency (transitive)","Scope: "+(S=e.usage.scope,"runtime"===S?"Runtime":"dev"===S?"Dev":"optional"===S?"Optional":"peer"===S?"Peer":S)];var S;e.package.description&&E.unshift("Description: "+e.package.description),(null==(a=e.usage.origins.workspaces)?void 0:a.length)&&E.push("Used in "+e.usage.origins.workspaces.length+" workspaces"),e.usage.importUsage&&E.push("Imported in "+e.usage.importUsage.fileCount+" project files"),e.usage.introduction&&E.push("Introduced by: "+f(e.usage.introduction)),E.length<3&&E.push("Dependency depth: "+e.usage.depth);var B,D;const j=H("Overview","Summary and key context",'<div class="micro-summary">'+E.slice(0,5).map(e=>'<div class="micro-line">'+v(e)+"</div>").join("")+"</div>"+((null==(i=e.usage.origins.workspaces)?void 0:i.length)?'<div class="micro-sublist"><div class="micro-subtitle">Workspaces</div>'+I(e.usage.origins.workspaces,8)+"</div>":"")+('<div class="section-block"><div class="block-title">Key context</div><div class="kv-grid kv-grid-tight">'+[e.usage.runtimeImpact?b("Runtime impact",(D=e.usage.runtimeImpact,D?f(D):"")):"",b("Dependency depth",e.usage.depth),C("Introduced via root packages",T(e.usage.origins.topRootPackages,8,n,s)),b("Direct roots",e.usage.origins.rootPackageCount),C("Direct parents",L(e.usage.origins.topParentPackages,8,n,s)),b("Direct parents count",e.usage.origins.parentPackageCount??0),b("TypeScript types",(B=e.usage.tsTypes,"bundled"===B?"Bundled":"definitelyTyped"===B?"DefinitelyTyped":"none"===B?"None":"Unknown"))].filter(Boolean).join("")+"</div></div>")+function(e,t,n,s){if(!t||0===t.length)return"";const a=t.slice(0,n),i=t.length-n;let r='<div class="detail-list">';return r+='<div class="detail-title">'+v(e)+"</div>",r+='<ul class="detail-items '+s+'">',a.forEach(e=>{r+='<li class="detail-item">'+v(e)+"</li>"}),i>0&&(r+='<li class="detail-item muted">+'+i+" more</li>"),r+="</ul></div>",r}("Top import locations",null==(r=e.usage.importUsage)?void 0:r.topFiles,5,"mono")),P=e.compliance.license,O=M(k.repository,"package.json"),R=M(k.repository,"LICENSE"),F=[C("Primary license",w(y,e.compliance.licenseRisk)),b("Status",p(P.status))];if(P.declared){const e=[P.declared.valid?"valid":"invalid",P.declared.expression?"expression":void 0,P.declared.deprecated?"deprecated":void 0].filter(Boolean).join(", "),t=(null==(o=P.exception)?void 0:o.id)?\` WITH \${P.exception.id}\`:"";F.push(C("Declared SPDX in package.json",'<span class="kv-value">'+V(\`\${P.declared.spdxId}\${t}\${e?\` (\${e})\`:""}\`,O)+"</span>"))}P.inferred&&F.push(C("Inferred from LICENSE file",'<span class="kv-value">'+V(\`\${P.inferred.spdxId} (\${P.inferred.confidence})\`,R)+"</span>")),"mismatch"===P.status&&F.push(b("Mismatch","Declared SPDX and LICENSE text do not match")),"invalid-spdx"===P.status&&F.push(b("Invalid SPDX","Package.json license is not a valid SPDX identifier or expression"));const G=A("License",'<div class="kv-grid">'+F.join("")+"</div>"),K=d.critical+d.high+d.moderate+d.low,W=[C("Known vulnerabilities",w(0===K?"None":String(K),d.risk)),b("Highest severity","none"===d.highest?"None":f(d.highest))],q=K>0?'<div class="kv-grid kv-grid-tight">'+[b("Critical",d.critical),b("High",d.high),b("Moderate",d.moderate),b("Low",d.low)].join("")+"</div>":"",J=function(e){if(!e||0===e.length)return"";let t='<table class="vuln-table"><thead><tr>';return t+="<th>Title</th><th>Severity</th><th>Affected range</th><th>Fix available</th><th>Reference</th>",t+="</tr></thead><tbody>",e.forEach(e=>{const n=v(e.title),s=e.url?'<a href="'+v(e.url)+'" target="_blank" rel="noopener">Link</a>':"";t+='<tr data-severity="'+v(e.severity)+'">',t+='<td data-label="Title">'+n+"</td>",t+='<td data-label="Severity">'+v(h(e.severity))+"</td>",t+='<td data-label="Affected range">'+v(e.vulnerableRange)+"</td>",t+='<td data-label="Fix available">'+v(e.fixAvailable?"Yes":"No")+"</td>",t+='<td data-label="Reference">'+s+"</td>",t+="</tr>"}),t+="</tbody></table>",t}(l.advisories),_=A("VULNERABILITIES",['<div class="section-note">Based on npm audit findings (known disclosed issues).</div>','<div class="kv-grid">'+W.join("")+"</div>",q?'<div class="subtle-divider"></div>'+q:"",J?'<div class="subtle-divider"></div>'+J:""].join(""),"Known security issues from npm audit","vuln-block"),z=e.execution?function(e){var n,s,a,i,r;const o=[C("Execution risk",w((c=e.risk,"red"===c?"High":"amber"===c?"Medium":"Low"),e.risk))];var c;if(e.native&&o.push(b("Native build tooling detected (native)","Yes")),(null==(s=null==(n=e.scripts)?void 0:n.hooks)?void 0:s.length)&&o.push(C("Lifecycle hooks",I(e.scripts.hooks,6))),"number"==typeof(null==(a=e.scripts)?void 0:a.complexity)&&o.push(b("Heuristic complexity","Script complexity: "+e.scripts.complexity+" (complexity)")),null==(r=null==(i=e.scripts)?void 0:i.signals)?void 0:r.length){const n=e.scripts.signals.map(e=>\`\${t[e]} (\${e})\`);o.push(C("Install-time signals",I(n,6)))}return A("Install-time execution behaviour",'<div class="section-note">Install-time behaviour signals detected. These describe code that runs automatically during install and may warrant review in security-sensitive environments.</div><div class="kv-grid">'+o.join("")+"</div>")}(e.execution):"",X=H("Risk & Compliance","License, vulnerabilities, and install-time execution signals",G+_+z),Y=[b("Outdated status",(Z=e.upgrade.outdatedStatus,Z?"unknown"===Z?"Unknown":f(Z):"Not reported"))];var Z;e.upgrade.latestVersion&&Y.push(b("Latest version",e.upgrade.latestVersion));const Q=A("Version",'<div class="section-note">Based on npm outdated findings.</div><div class="kv-grid">'+Y.join("")+"</div>"),ee=e.package.deprecated?A("Deprecated",'<div class="kv-grid">'+b("Deprecated","Yes","Declared by the package author.")+"</div>",void 0,"warning"):"",te=[b("Node engine constraint",e.upgrade.nodeEngine||"Any")];void 0!==e.upgrade.blocksNodeMajor&&te.push(b("Blocks Node major upgrade",e.upgrade.blocksNodeMajor?"Yes":"No"));const ne=A("Constraints",'<div class="kv-grid">'+te.join("")+"</div>"),se=A("Blast radius",'<div class="kv-grid">'+[b("Used by other packages (fanIn)",e.graph.fanIn),b("Depends on packages (fanOut)",e.graph.fanOut)].join("")+"</div>"),ae={nodeEngine:"Node engine constraint",peerDependency:"Peer dependency constraints",nativeBindings:"Native bindings/build tooling",installScripts:"Install lifecycle scripts",deprecated:"Deprecated by author"},ie=H("Upgrade & Change Impact","Currency, constraints, and blast radius",Q+ee+ne+se+((null==(c=e.upgrade.blockers)?void 0:c.length)?'<div class="subsection"><div class="subsection-header"><span class="subsection-title">Upgrade blockers</span></div><ul class="bullet-list">'+e.upgrade.blockers.map(e=>"<li>"+v(ae[e]||e)+"</li>").join("")+"</ul></div>":"")),re=N(e,n,s);return[\$(k),j,X,ie,re,'<details class="raw-data-toggle"><summary><span class="expand-icon" aria-hidden="true"></span>View raw data</summary><div class="raw-data-pane"><pre>'+v(x)+'</pre><button type="button" class="copy-json-btn" aria-label="Copy raw JSON">Copy JSON</button></div></details>'].join("")}async function F(){var t,n;const s=await async function(){const e=document.getElementById("radar-data");return e&&e.textContent&&"{}"!==e.textContent.trim()?JSON.parse(e.textContent):(await fetch("./sample-data.json")).json()}(),a=document.getElementById("dependency-list"),i=document.getElementById("results-summary"),r=function(e){const t="string"==typeof e&&e.trim().length>0?e.trim():"unknown";return\`https://dependency-radar.com/?source=standalone-report&cli=\${encodeURIComponent(t)}\`}(s.dependencyRadarVersion),o=document.getElementById("project-path");o&&(o.textContent=s.project.projectDir);const c=document.getElementById("cta-primary-link"),d=document.getElementById("cta-secondary-link");c&&(c.href=r),d&&(d.href=r);const p=document.getElementById("git-branch-item"),v=document.getElementById("git-branch");(null==(t=s.git)?void 0:t.branch)&&s.git.branch&&p&&v&&(v.textContent=s.git.branch,p.style.display="");const h=document.getElementById("node-item"),f=document.getElementById("node-version"),y=document.getElementById("node-disclaimer");if(s.environment&&h&&f){const e=(null==(n=s.environment.runtimeVersion)?void 0:n.replace(/^v/,""))||"unknown",t=s.environment.minRequiredMajor;f.textContent=e+(t&&t>0?\` (requires ≥\${t})\`:""),h.style.display="",t&&t>0&&y&&(y.textContent="Node requirement derived from dependency engine ranges.",y.style.display="")}const b=document.getElementById("formatted-date");if(b&&s.generatedAt)try{const e=new Date(s.generatedAt),t=new Intl.DateTimeFormat(void 0,{day:"numeric",month:"short",year:"numeric",hour:"2-digit",minute:"2-digit"}).format(e);b.textContent=t}catch{b.textContent=s.generatedAt}const w={search:document.getElementById("search"),direct:document.getElementById("direct-filter"),runtime:document.getElementById("runtime-filter"),sort:document.getElementById("sort-by"),sortDirection:document.getElementById("sort-direction"),hasVulns:document.getElementById("has-vulns"),themeSwitch:document.getElementById("theme-switch"),licenseToggle:document.getElementById("license-toggle"),licensePanel:document.getElementById("license-panel"),licensePermissive:document.getElementById("license-permissive"),licenseWeakCopyleft:document.getElementById("license-weak-copyleft"),licenseStrongCopyleft:document.getElementById("license-strong-copyleft"),licenseUnknown:document.getElementById("license-unknown"),licenseAll:document.getElementById("license-all"),licenseFriendly:document.getElementById("license-friendly"),filtersToggle:document.getElementById("filters-toggle"),filterControls:document.getElementById("filter-controls"),columnHeadersContainer:document.getElementById("column-headers-container"),packageHeader:document.getElementById("package-header")};let C="name",I=!0;"light"===localStorage.getItem("dependency-radar-theme")&&(document.documentElement.classList.add("light"),w.themeSwitch.classList.add("light")),w.themeSwitch.addEventListener("click",()=>{document.documentElement.classList.toggle("light"),w.themeSwitch.classList.toggle("light");const e=document.documentElement.classList.contains("light");localStorage.setItem("dependency-radar-theme",e?"light":"dark")});const L=window.matchMedia("(max-width: 768px)");let E=L.matches;const B=e=>{w.filterControls&&w.filtersToggle&&(w.filterControls.classList.toggle("open",e),w.filtersToggle.classList.toggle("open",e),w.filtersToggle.setAttribute("aria-expanded",String(e)))},j=()=>{if(L.matches)return B(!1),w.licensePanel.classList.add("open"),w.licenseToggle.classList.add("open"),void(E=!0);E&&(B(!1),w.licensePanel.classList.remove("open"),w.licenseToggle.classList.remove("open")),E=!1};function P(){if(w.columnHeadersContainer&&(w.columnHeadersContainer.innerHTML=k(C,I)),w.packageHeader){const e=w.packageHeader.querySelector(".sort-indicator");e&&("name"===C?(e.textContent=I?" ▲":" ▼",w.packageHeader.classList.add("sorted")):(e.textContent="",w.packageHeader.classList.remove("sorted")))}}function T(e){const t=e.target.closest(".column-header");if(!t)return;const n=t.dataset.sort;n&&(C===n?I=!I:(C=n,I=!0),w.sort&&(w.sort.value=C,w.sortDirection.textContent=I?"↑":"↓"),P(),W())}w.licenseToggle.addEventListener("click",()=>{L.matches||(w.licenseToggle.classList.toggle("open"),w.licensePanel.classList.toggle("open"))}),w.filtersToggle&&w.filterControls&&w.filtersToggle.addEventListener("click",()=>{const e=!w.filterControls.classList.contains("open");B(e)}),window.addEventListener("resize",j),j(),w.sortDirection.addEventListener("click",()=>{I=!I,w.sortDirection.textContent=I?"↑":"↓",P(),W()}),w.sort.addEventListener("change",()=>{C=w.sort.value,P(),W()}),w.columnHeadersContainer&&w.columnHeadersContainer.addEventListener("click",T),w.packageHeader&&w.packageHeader.addEventListener("click",T),P(),w.licenseAll.addEventListener("click",()=>{w.licensePermissive.checked=!0,w.licenseWeakCopyleft.checked=!0,w.licenseStrongCopyleft.checked=!0,w.licenseUnknown.checked=!0,V.clear(),W()}),w.licenseFriendly.addEventListener("click",()=>{w.licensePermissive.checked=!0,w.licenseWeakCopyleft.checked=!1,w.licenseStrongCopyleft.checked=!1,w.licenseUnknown.checked=!1,V.clear(),W()});const N=Object.values(s.dependencies||{}),H=new Map;N.forEach(e=>{H.set(x(e.package.name,e.package.version),e)});const A=new Set(H.keys()),U=D(A),M=new Set,V=new Set,\$=new Map,F=(()=>{const e=document.getElementById("copy-announcer");if(e)return e;const t=document.createElement("div");return t.id="copy-announcer",t.className="sr-only",t.setAttribute("aria-live","polite"),document.body.appendChild(t),t})();function G(e){const t=e.dataset.depKey;if(!t)return;const n=e.querySelector(".dep-details");if(!n||"true"===n.dataset.rendered)return;const s=H.get(t);s&&(n.setAttribute("aria-busy","true"),n.innerHTML=['<div class="dep-loading" role="presentation">','<div class="dep-loading-bar"></div>',"</div>"].join(""),requestAnimationFrame(()=>{n.innerHTML=R(s,A,U),n.dataset.rendered="true",n.removeAttribute("aria-busy")}))}function K(){const t=(w.search.value||"").toLowerCase(),n=w.direct.value,s=w.runtime.value,a=w.hasVulns.checked,i=w.licensePermissive.checked,r=w.licenseWeakCopyleft.checked,o=w.licenseStrongCopyleft.checked,c=w.licenseUnknown.checked;return N.filter(l=>{var d,p;const v=x(l.package.name,l.package.version);if(V.has(v))return!0;const h=u(l),f=[h.value,null==(d=l.compliance.license.declared)?void 0:d.spdxId,null==(p=l.compliance.license.inferred)?void 0:p.spdxId].filter(Boolean).join(" ").toLowerCase();if(t&&!l.package.name.toLowerCase().includes(t)&&!f.includes(t))return!1;if("direct"===n&&!l.usage.direct)return!1;if("transitive"===n&&l.usage.direct)return!1;if("all"!==s&&l.usage.scope!==s)return!1;if(a&&0===g[(y=m(l).summary,(null==y?void 0:y.highest)||"none")])return!1;var y;const k=function(t){if(!t)return"unknown";const n=t.toUpperCase();for(const[s,a]of Object.entries(e))if(a.some(e=>n.includes(e.toUpperCase())))return s;return"unknown"}(h.value);return!("permissive"===k&&!i)&&(!("weakCopyleft"===k&&!r)&&(!("strongCopyleft"===k&&!o)&&!("unknown"===k&&!c)))})}function W(){var e;const t=function(e){const t=[...e];if("name"===C)t.sort((e,t)=>e.package.name.localeCompare(t.package.name));else if("depth"===C)t.sort((e,t)=>e.usage.depth-t.usage.depth);else{const e=l.find(e=>e.sortKey===C||e.id===C);(null==e?void 0:e.sortFn)?t.sort(e.sortFn):e&&t.sort((t,n)=>e.getValue(t).localeCompare(e.getValue(n)))}return I||t.reverse(),t}(K()),n=(null==(e=s.summary)?void 0:e.dependencyCount)||N.length;i.innerHTML="Showing <strong>"+t.length+"</strong> of <strong>"+n+"</strong> dependencies",0!==t.length?(a.innerHTML=t.map(O).join(""),\$.clear(),a.querySelectorAll("details.dep-card").forEach(e=>{const t=e.dataset.depKey;t&&\$.set(t,e)}),M.forEach(e=>{const t=\$.get(e);t&&(t.open||(t.open=!0),G(t))})):a.innerHTML='<div class="empty-state"><div class="empty-state-icon">📦</div><div class="empty-state-text">No dependencies match your filters</div></div>'}function q(e){const t=U.get(e)||[];return 1===t.length?t[0]:null}const J=[w.search,w.direct,w.runtime,w.sort,w.hasVulns,w.licensePermissive,w.licenseWeakCopyleft,w.licenseStrongCopyleft,w.licenseUnknown],_=()=>{V.clear(),W()};function z(e){const t=e.getAttribute("data-dep-key");if(!t)return;const n=function(e){if(H.has(e))return e;const t=S(e);if(!t)return q(e);if(t.version.startsWith("npm:")){const e=t.version.slice(4),n=t.name+(e.startsWith("@")?e:"@"+e);if(H.has(n))return n}return q(t.name)}(t);if(!n)return;let s=\$.get(n);if(s||(V.add(n),W(),s=\$.get(n)),!s)return;M.add(n),s.open||(s.open=!0),G(s);const a=()=>{s.scrollIntoView({behavior:"smooth",block:"start"});const e=s.querySelector("summary");e&&e.focus({preventScroll:!0})};requestAnimationFrame(()=>{a(),window.setTimeout(a,60)})}J.forEach(e=>{e&&(e.addEventListener("input",_),e.addEventListener("change",_))}),a.addEventListener("toggle",e=>{const t=e.target;if(!(t instanceof HTMLDetailsElement))return;if(!t.classList.contains("dep-card"))return;const n=t.dataset.depKey;n&&(t.open?(M.add(n),G(t)):M.delete(n))},!0),a.addEventListener("click",e=>{const t=e.target,n=t.closest(".root-package-link");if(n)return e.preventDefault(),void z(n);const s=t.closest(".copy-json-btn");s&&(e.preventDefault(),async function(e){var t;const n=e.closest(".raw-data-toggle"),s=null==n?void 0:n.querySelector("pre"),a=(null==s?void 0:s.textContent)??"";if(a)try{if(null==(t=navigator.clipboard)?void 0:t.writeText)await navigator.clipboard.writeText(a);else{const e=document.createElement("textarea");e.value=a,e.setAttribute("readonly","true"),e.style.position="absolute",e.style.left="-9999px",document.body.appendChild(e),e.select(),document.execCommand("copy"),document.body.removeChild(e)}const n=e.dataset.label||e.textContent||"Copy JSON";e.dataset.label=n,e.textContent="Copied",e.classList.add("copied"),F.textContent="Copied JSON to clipboard.",window.setTimeout(()=>{e.textContent=n,e.classList.remove("copied")},1500)}catch{F.textContent="Copy failed."}}(s))}),a.addEventListener("keydown",e=>{const t=e.target.closest(".root-package-link");t&&(" "!==e.key&&"Spacebar"!==e.key||(e.preventDefault(),z(t)))}),P(),W()}"loading"===document.readyState?document.addEventListener("DOMContentLoaded",F):F()}();
|
|
17
|
+
exports.JS_CONTENT = `!function(){"use strict";const e={permissive:["MIT","ISC","BSD-2-Clause","BSD-3-Clause","Apache-2.0","Unlicense","0BSD","CC0-1.0","BSD","Apache","Apache 2.0","Apache License 2.0","MIT License","ISC License"],weakCopyleft:["LGPL-2.1","LGPL-3.0","LGPL-2.0","LGPL","MPL-2.0","MPL-1.1","MPL","EPL-1.0","EPL-2.0","EPL"],strongCopyleft:["GPL-2.0","GPL-3.0","GPL","AGPL-3.0","AGPL","GPL-2.0-only","GPL-3.0-only","GPL-2.0-or-later","GPL-3.0-or-later"]},t={"network-access":"Accesses the network during install","dynamic-exec":"Uses dynamic execution","child-process":"Spawns child processes",encoding:"Uses encoding/decoding logic",obfuscated:"Contains obfuscated/minified install logic","reads-env":"Reads environment variables","reads-home":"Reads user home directory","uses-ssh":"Uses SSH configuration/keys"};function n(e){return e?e.charAt(0).toUpperCase()+e.slice(1):e}function s(e){var t,n;const s=e.compliance.license,a=(null==(t=s.declared)?void 0:t.valid)?s.declared.spdxId:void 0,i=null==(n=s.inferred)?void 0:n.spdxId;return a?{value:a,isInferred:!1}:i?{value:i,isInferred:!0}:{value:"Unknown",isInferred:!1}}function a(e){const t=e.security;if(null==t?void 0:t.summary)return{summary:t.summary,advisories:t.advisories};if(null==t?void 0:t.vulnerabilities){const e=t.vulnerabilities;return{summary:{critical:Number(e.critical||0),high:Number(e.high||0),moderate:Number(e.moderate||0),low:Number(e.low||0),highest:e.highest||"none",risk:t.vulnRisk||t.risk||"green"},advisories:t.advisories}}return{summary:{critical:0,high:0,moderate:0,low:0,highest:"none",risk:"green"},advisories:null==t?void 0:t.advisories}}function i(e){return(null==e?void 0:e.highest)||"none"}function r(e){return e&&e.risk||"green"}const o={permissive:"green",weakCopyleft:"amber",strongCopyleft:"red",unknown:"gray"},c={none:0,low:1,moderate:2,high:3,critical:4},l=[{id:"type",label:"Type",sortKey:"type",getValue:e=>e.usage.direct?"Dependency":"Sub-Dependency",getTone:e=>e.usage.direct?"green":"amber",sortFn:(e,t)=>e.usage.direct===t.usage.direct?0:e.usage.direct?-1:1},{id:"scope",label:"Scope",sortKey:"scope",getValue:e=>{return"runtime"===(t=e.usage.scope)?"Runtime":"dev"===t?"Dev":"optional"===t?"Optional":"peer"===t?"Peer":t;var t},getTone:e=>"runtime"===e.usage.scope?"green":"dev"===e.usage.scope||"optional"===e.usage.scope?"amber":"gray",sortFn:(e,t)=>e.usage.scope.localeCompare(t.usage.scope)},{id:"license",label:"License",sortKey:"license",getValue:e=>{const t=s(e),n=t.isInferred?\`\${t.value} (inferred)\`:t.value;return"mismatch"===e.compliance.license.status?\`\${n} *\`:n},getTone:t=>{const n=function(t){if(!t)return"unknown";const n=t.toUpperCase();for(const[s,a]of Object.entries(e))if(a.some(e=>n.includes(e.toUpperCase())))return s;return"unknown"}(s(t).value);return o[n]},sortFn:(e,t)=>{const n=s(e).value,a=s(t).value;return n.localeCompare(a)}},{id:"vulns",label:"Vulnerabilities",sortKey:"severity",getValue:e=>n(i(a(e).summary)),getTone:e=>a(e).summary.risk,sortFn:(e,t)=>c[i(a(t).summary)]-c[i(a(e).summary)]},{id:"install",label:"Install",sortKey:"install",getValue:e=>{return(t=e.execution)?n(t.risk||"low"):"Low";var t},getTone:e=>r(e.execution),sortFn:(e,t)=>{const n={green:0,amber:1,red:2},s=r(e.execution),a=r(t.execution);return n[s]-n[a]}}],d=l.length;function u(e){var t,n;const s=e.compliance.license,a=(null==(t=s.declared)?void 0:t.valid)?s.declared.spdxId:void 0,i=null==(n=s.inferred)?void 0:n.spdxId;return a?{value:a,isInferred:!1}:i?{value:i,isInferred:!0}:{value:"Unknown",isInferred:!1}}function p(e){switch(e){case"declared-only":return"Declared";case"inferred-only":return"Inferred";case"match":return"Declared + Inferred (match)";case"mismatch":return"Declared + Inferred (mismatch)";case"invalid-spdx":return"Invalid SPDX";default:return"Unknown"}}const g={none:0,low:1,moderate:2,high:3,critical:4};function m(e){const t=e.security;if(null==t?void 0:t.summary)return{summary:t.summary,advisories:t.advisories};if(null==t?void 0:t.vulnerabilities){const e=t.vulnerabilities;return{summary:{critical:Number(e.critical||0),high:Number(e.high||0),moderate:Number(e.moderate||0),low:Number(e.low||0),highest:e.highest||"none",risk:t.vulnRisk||t.risk||"green"},advisories:t.advisories}}return{summary:{critical:0,high:0,moderate:0,low:0,highest:"none",risk:"green"},advisories:null==t?void 0:t.advisories}}function v(e){return e?String(e).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,"""):""}function h(e){return e?e.charAt(0).toUpperCase()+e.slice(1):e}function f(e){return e.split(/[\\s-_]+/).map(e=>e?h(e):e).join(" ")}function y(e){return l.map(t=>{return n=t.label,s=t.getValue(e),'<div class="badge-card '+t.getTone(e)+'"><span class="badge-label">'+v(n)+'</span><span class="badge-value">'+v(s)+"</span></div>";var n,s}).join("")}function k(e,t){const n=l.map(n=>function(e,t,n,s){const a=n===e,i=a?s?" ▲":" ▼":"",r=a?s?" sorted-asc":" sorted-desc":"";return'<button type="button" class="column-header'+(a?" sorted":"")+r+'" data-sort="'+v(e)+'"><span class="column-header-label">'+v(t)+'</span><span class="sort-indicator">'+i+"</span></button>"}(n.sortKey,n.label,e,t)).join("");return'<div class="column-headers" style="--column-count: '+d+'">'+n+"</div>"}function b(e,t,n){let s='<div class="kv-item">';return s+='<span class="kv-label">'+v(e)+"</span>",s+='<span class="kv-value">'+v(String(t))+"</span>",n&&(s+='<span class="kv-hint">'+v(n)+"</span>"),s+="</div>",s}function w(e,t){return'<span class="kv-value risk-value"><span class="risk-dot '+t+'"></span>'+v(String(e))+"</span>"}function C(e,t,n){let s='<div class="kv-item">';return s+='<span class="kv-label">'+v(e)+"</span>",s+=t,s+="</div>",s}function I(e,t){if(!e||0===e.length)return'<span class="kv-value">None</span>';const n=e.slice(0,t),s=e.length-t;let a='<div class="package-list">';return n.forEach(e=>{a+='<span class="package-tag">'+v(e)+"</span>"}),s>0&&(a+='<span class="package-tag">+'+s+" more</span>"),a+="</div>",a}function L(e,t,n,s){if(!e||0===e.length)return'<span class="kv-value">None</span>';const a=e.slice(0,t),i=e.length-t;let r='<div class="package-list">';return a.forEach(e=>{const t=P(e,n,s);r+=t?'<a class="package-tag package-tag-link root-package-link" href="#'+v(B(t))+'" data-dep-key="'+v(t)+'" aria-label="Jump to dependency '+v(t)+'">'+v(e)+"</a>":'<span class="package-tag">'+v(e)+"</span>"}),i>0&&(r+='<span class="package-tag">+'+i+" more</span>"),r+="</div>",r}function x(e,t){return e+"@"+t}const E=new WeakMap;function S(e){const t=e.lastIndexOf("@npm:");if(t>0)return{name:e.slice(0,t),version:e.slice(t+1)};const n=e.lastIndexOf("@");return n<=0?null:{name:e.slice(0,n),version:e.slice(n+1)}}function B(e){return"dep-"+encodeURIComponent(e).replace(/%/g,"_")}function D(e){const t=E.get(e);if(t)return t;const n=new Map;return e.forEach(e=>{const t=S(e);if(!t)return;const s=n.get(t.name)||[];s.push(e),n.set(t.name,s)}),E.set(e,n),n}function j(e,t,n){const s=((n||D(t)).get(e)||[]).filter(e=>t.has(e));return 1===s.length?s[0]:null}function P(e,t,n){if(t.has(e))return e;const s=S(e);if(!s)return j(e,t,n);if(s.version.startsWith("npm:")){const e=s.version.slice(4),n=s.name+(e.startsWith("@")?e:"@"+e);if(t.has(n))return n}return j(s.name,t,n)}function T(e,t,n,s){if(!e||0===e.length)return'<span class="kv-value">None</span>';const a=e.slice(0,t),i=e.length-t;let r='<div class="package-list">';return a.forEach(e=>{if("string"==typeof e){const t=P(e,n,s);return t?void(r+='<a class="package-tag package-tag-link root-package-link" href="#'+v(B(t))+'" data-dep-key="'+v(t)+'" aria-label="Jump to dependency '+v(t)+'">'+v(e)+"</a>"):void(r+='<span class="package-tag">'+v(e)+"</span>")}const t=x(e.name,e.version),a=e.name+"@"+e.version,i=P(t,n,s);r+=i?'<a class="package-tag package-tag-link root-package-link" href="#'+v(B(i))+'" data-dep-key="'+v(i)+'" aria-label="Jump to dependency '+v(i)+'">'+v(a)+"</a>":'<span class="package-tag">'+v(a)+"</span>"}),i>0&&(r+='<span class="package-tag">+'+i+" more</span>"),r+="</div>",r}function N(e,t,n){const s=e.graph.subDeps;if(!s)return"";const a=[{title:"Dependencies",key:"dep"},{title:"Optional",key:"opt"},{title:"Peer",key:"peer"},{title:"Dev Dependencies",key:"dev"}];let i=0,r=0;for(const l of a){const e=s[l.key];if(e)for(const t of Object.values(e))i+=1,t[1]&&(r+=1)}if(0===i)return"";const o='<div class="declared-summary">Total: '+i+" • Installed: "+r+" • Not installed: "+(i-r)+"</div>",c=a.map(e=>{const a=s[e.key];if(!a||0===Object.keys(a).length)return"";let i=0,r=0;const o=Object.entries(a).sort(([e],[t])=>e.localeCompare(t)).map(([e,[s,a]])=>{i+=1,a&&(r+=1);const o='<div class="declared-name">'+v(e)+"</div>",c='<div class="declared-range">'+v(s)+"</div>",l=a?function(e,t,n){const s=P(e,t,n);return s?'<a class="status-pill installed root-package-link" href="#'+v(B(s))+'" data-dep-key="'+v(s)+'" aria-label="Jump to dependency '+v(s)+'">Installed</a>':'<span class="status-pill installed">Installed</span>'}(a,t,n):'<span class="status-pill missing">Not installed</span>';return'<div class="declared-row">'+o+c+l+"</div>"}),c=r+" of "+i+" installed";return['<details class="declared-group">','<summary class="declared-group-summary"><span class="expand-icon" aria-hidden="true"></span><span class="declared-group-title">'+v(e.title)+' <span class="declared-count">('+c+")</span></span></summary>",'<div class="declared-table">'+o.join("")+"</div>","</details>"].join("")}).filter(Boolean);return H("Declared Dependencies","Dependencies declared by this package",o+'<div class="declared-deps">'+c.join("")+"</div>")}function H(e,t,n){let s='<div class="section">';return s+='<div class="section-header">',s+='<span class="section-title">'+v(e)+"</span>",t&&(s+='<span class="section-desc">'+v(t)+"</span>"),s+="</div>",s+=n,s+="</div>",s}function A(e,t,n,s){let a='<div class="subsection'+(s?" "+s:"")+'">';return a+='<div class="subsection-header">',a+='<span class="subsection-title">'+v(e)+"</span>",n&&(a+='<span class="subsection-desc">'+v(n)+"</span>"),a+="</div>",a+=t,a+="</div>",a}function U(e){if(!e)return;const t=e.trim();if(!t)return;const n=e=>e.replace(/\\.git\$/i,"");if(/^https?:\\/\\//i.test(t))return n(t);if(/^git\\+https?:\\/\\//i.test(t))return n(t.replace(/^git\\+/,""));if(/^git:\\/\\/github\\.com\\//i.test(t))return n(t.replace(/^git:\\/\\//i,"https://"));if(/^github:/i.test(t)){const e=t.slice(7).replace(/^\\/+/,"");return e?\`https://github.com/\${n(e)}\`:void 0}if(/^git@github\\.com:/i.test(t)){const e=t.slice(15);return e?\`https://github.com/\${n(e)}\`:void 0}return/^[A-Za-z0-9_.-]+\\/[A-Za-z0-9_.-]+\$/.test(t)?\`https://github.com/\${n(t)}\`:void 0}function M(e,t){const n=U(e);if(!n)return;let s;try{s=new URL(n)}catch{return}if("github.com"!==s.hostname.toLowerCase())return;const a=s.pathname.split("/").filter(Boolean);if(a.length<2)return;const i=a[0],r=a[1].replace(/\\.git\$/i,"");return i&&r?\`https://github.com/\${i}/\${r}/blob/HEAD/\${t}\`:void 0}function V(e,t){return t?v(e)+' <a class="kv-inline-link" href="'+v(t)+'" target="_blank" rel="noopener">GitHub<svg class="kv-inline-link-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M7 17 17 7"/><path d="M9 7h8v8"/></svg></a>':v(e)}function \$(e){const t='<svg viewBox="0 0 24 24" fill="currentColor"><path d="M0 7.334v8h6.666v1.332H12v-1.332h12v-8H0zm6.666 6.664H5.334v-4H3.999v4H1.335V8.667h5.331v5.331zm4 0v1.336H8.001V8.667h5.334v5.332h-2.669v-.001zm12.001 0h-1.33v-4h-1.336v4h-1.335v-4h-1.33v4h-2.671V8.667h8.002v5.331zM10.665 10H12v2.667h-1.335V10z"/></svg>',n='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"/></svg>',s='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>',a='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>';if(!(e.npm||e.repository||e.homepage||e.issues))return"";let i='<div class="package-links">';return e.npm&&(i+='<a href="'+v(e.npm)+'" target="_blank" rel="noopener" class="package-link">'+t+"npm</a>"),e.repository&&(i+='<a href="'+v(e.repository)+'" target="_blank" rel="noopener" class="package-link">'+n+"Repository</a>"),e.homepage&&(i+='<a href="'+v(e.homepage)+'" target="_blank" rel="noopener" class="package-link">'+a+"Homepage</a>"),e.issues&&(i+='<a href="'+v(e.issues)+'" target="_blank" rel="noopener" class="package-link">'+s+"Issues</a>"),i+="</div>",i}function O(e){const t=function(e,t){const n=[e.risk,t];return n.includes("red")?"red":n.includes("amber")?"amber":"green"}(m(e).summary,e.compliance.licenseRisk),n=x(e.package.name,e.package.version),s=B(n),a=y(e),i=['<summary class="dep-summary">','<span class="expand-icon" aria-hidden="true"></span>','<span class="dep-name">'+v(e.package.name)+'<span class="dep-version">@'+v(e.package.version)+"</span></span>",'<div class="dep-indicators" style="--column-count: '+d+'">',a,"</div>","</summary>"].join("");return['<details class="dep-card" data-risk="'+t+'" data-dep-key="'+v(n)+'" id="'+v(s)+'">',i,'<div class="dep-details" data-rendered="false"></div>',"</details>"].join("")}function R(e,n,s){var a,i,r,o,c;const l=m(e),d=l.summary,g=u(e),y=g.isInferred?\`\${g.value} (inferred)\`:g.value,k=function(e){var t;const n=(null==(t=e.package)?void 0:t.links)||{},s=e.links||{},a=U(n.repository||s.repository||s.repo);return{npm:n.npm||s.npm,repository:a||n.repository||s.repository||s.repo,homepage:n.homepage||s.homepage,issues:n.bugs||n.issues||s.bugs||s.issues}}(e),x=JSON.stringify(e,null,2),E=[e.usage.direct?"Direct dependency":"Indirect dependency (transitive)","Scope: "+(S=e.usage.scope,"runtime"===S?"Runtime":"dev"===S?"Dev":"optional"===S?"Optional":"peer"===S?"Peer":S)];var S;e.package.description&&E.unshift("Description: "+e.package.description),(null==(a=e.usage.origins.workspaces)?void 0:a.length)&&E.push("Used in "+e.usage.origins.workspaces.length+" workspaces"),e.usage.importUsage&&E.push("Imported in "+e.usage.importUsage.fileCount+" project files"),e.usage.introduction&&E.push("Introduced by: "+f(e.usage.introduction)),E.length<3&&E.push("Dependency depth: "+e.usage.depth);var B,D;const j=H("Overview","Summary and key context",'<div class="micro-summary">'+E.slice(0,5).map(e=>'<div class="micro-line">'+v(e)+"</div>").join("")+"</div>"+((null==(i=e.usage.origins.workspaces)?void 0:i.length)?'<div class="micro-sublist"><div class="micro-subtitle">Workspaces</div>'+I(e.usage.origins.workspaces,8)+"</div>":"")+('<div class="section-block"><div class="block-title">Key context</div><div class="kv-grid kv-grid-tight">'+[e.usage.runtimeImpact?b("Runtime impact",(D=e.usage.runtimeImpact,D?f(D):"")):"",b("Dependency depth",e.usage.depth),C("Introduced via root packages",T(e.usage.origins.topRootPackages,8,n,s)),b("Direct roots",e.usage.origins.rootPackageCount),C("Direct parents",L(e.usage.origins.topParentPackages,8,n,s)),b("Direct parents count",e.usage.origins.parentPackageCount??0),b("TypeScript types",(B=e.usage.tsTypes,"bundled"===B?"Bundled":"definitelyTyped"===B?"DefinitelyTyped":"none"===B?"None":"Unknown"))].filter(Boolean).join("")+"</div></div>")+function(e,t,n,s){if(!t||0===t.length)return"";const a=t.slice(0,n),i=t.length-n;let r='<div class="detail-list">';return r+='<div class="detail-title">'+v(e)+"</div>",r+='<ul class="detail-items '+s+'">',a.forEach(e=>{r+='<li class="detail-item">'+v(e)+"</li>"}),i>0&&(r+='<li class="detail-item muted">+'+i+" more</li>"),r+="</ul></div>",r}("Top import locations",null==(r=e.usage.importUsage)?void 0:r.topFiles,5,"mono")),P=e.compliance.license,O=M(k.repository,"package.json"),R=M(k.repository,"LICENSE"),F=[C("Primary license",w(y,e.compliance.licenseRisk)),b("Status",p(P.status))];if(P.declared){const e=[P.declared.valid?"valid":"invalid",P.declared.expression?"expression":void 0,P.declared.deprecated?"deprecated":void 0].filter(Boolean).join(", "),t=(null==(o=P.exception)?void 0:o.id)?\` WITH \${P.exception.id}\`:"";F.push(C("Declared SPDX in package.json",'<span class="kv-value">'+V(\`\${P.declared.spdxId}\${t}\${e?\` (\${e})\`:""}\`,O)+"</span>"))}P.inferred&&F.push(C("Inferred from LICENSE file",'<span class="kv-value">'+V(\`\${P.inferred.spdxId} (\${P.inferred.confidence})\`,R)+"</span>")),"mismatch"===P.status&&F.push(b("Mismatch","Declared SPDX and LICENSE text do not match")),"invalid-spdx"===P.status&&F.push(b("Invalid SPDX","Package.json license is not a valid SPDX identifier or expression"));const G=A("License",'<div class="kv-grid">'+F.join("")+"</div>"),K=d.critical+d.high+d.moderate+d.low,W=[C("Known vulnerabilities",w(0===K?"None":String(K),d.risk)),b("Highest severity","none"===d.highest?"None":f(d.highest))],q=K>0?'<div class="kv-grid kv-grid-tight">'+[b("Critical",d.critical),b("High",d.high),b("Moderate",d.moderate),b("Low",d.low)].join("")+"</div>":"",J=function(e){if(!e||0===e.length)return"";let t='<table class="vuln-table"><thead><tr>';return t+="<th>Title</th><th>Severity</th><th>Affected range</th><th>Fix available</th><th>Reference</th>",t+="</tr></thead><tbody>",e.forEach(e=>{const n=v(e.title),s=e.url?'<a href="'+v(e.url)+'" target="_blank" rel="noopener">Link</a>':"";t+='<tr data-severity="'+v(e.severity)+'">',t+='<td data-label="Title">'+n+"</td>",t+='<td data-label="Severity">'+v(h(e.severity))+"</td>",t+='<td data-label="Affected range">'+v(e.vulnerableRange)+"</td>",t+='<td data-label="Fix available">'+v(e.fixAvailable?"Yes":"No")+"</td>",t+='<td data-label="Reference">'+s+"</td>",t+="</tr>"}),t+="</tbody></table>",t}(l.advisories),_=A("VULNERABILITIES",['<div class="section-note">Based on npm audit findings (known disclosed issues).</div>','<div class="kv-grid">'+W.join("")+"</div>",q?'<div class="subtle-divider"></div>'+q:"",J?'<div class="subtle-divider"></div>'+J:""].join(""),"Known security issues from npm audit","vuln-block"),z=e.execution?function(e){var n,s,a,i,r;const o=[C("Execution risk",w((c=e.risk,"red"===c?"High":"amber"===c?"Medium":"Low"),e.risk))];var c;if(e.native&&o.push(b("Native build tooling detected (native)","Yes")),(null==(s=null==(n=e.scripts)?void 0:n.hooks)?void 0:s.length)&&o.push(C("Lifecycle hooks",I(e.scripts.hooks,6))),"number"==typeof(null==(a=e.scripts)?void 0:a.complexity)&&o.push(b("Heuristic complexity","Script complexity: "+e.scripts.complexity+" (complexity)")),null==(r=null==(i=e.scripts)?void 0:i.signals)?void 0:r.length){const n=e.scripts.signals.map(e=>\`\${t[e]} (\${e})\`);o.push(C("Install-time signals",I(n,6)))}return A("Install-time execution behaviour",'<div class="section-note">Install-time behaviour signals detected. These describe code that runs automatically during install and may warrant review in security-sensitive environments.</div><div class="kv-grid">'+o.join("")+"</div>")}(e.execution):"",X=H("Risk & Compliance","License, vulnerabilities, and install-time execution signals",G+_+z),Y=[b("Outdated status",(Z=e.upgrade.outdatedStatus,Z?"unknown"===Z?"Unknown":f(Z):"Not reported"))];var Z;e.upgrade.latestVersion&&Y.push(b("Latest version",e.upgrade.latestVersion));const Q=A("Version",'<div class="section-note">Based on npm outdated findings.</div><div class="kv-grid">'+Y.join("")+"</div>"),ee=e.package.deprecated?A("Deprecated",'<div class="kv-grid">'+b("Deprecated","Yes","Declared by the package author.")+"</div>",void 0,"warning"):"",te=[b("Node engine constraint",e.upgrade.nodeEngine||"Any")];void 0!==e.upgrade.blocksNodeMajor&&te.push(b("Blocks Node major upgrade",e.upgrade.blocksNodeMajor?"Yes":"No"));const ne=A("Constraints",'<div class="kv-grid">'+te.join("")+"</div>"),se=A("Blast radius",'<div class="kv-grid">'+[b("Used by other packages (fanIn)",e.graph.fanIn),b("Depends on packages (fanOut)",e.graph.fanOut)].join("")+"</div>"),ae={nodeEngine:"Node engine constraint",peerDependency:"Peer dependency constraints",nativeBindings:"Native bindings/build tooling",installScripts:"Install lifecycle scripts",deprecated:"Deprecated by author"},ie=H("Upgrade & Change Impact","Currency, constraints, and blast radius",Q+ee+ne+se+((null==(c=e.upgrade.blockers)?void 0:c.length)?'<div class="subsection"><div class="subsection-header"><span class="subsection-title">Upgrade blockers</span></div><ul class="bullet-list">'+e.upgrade.blockers.map(e=>"<li>"+v(ae[e]||e)+"</li>").join("")+"</ul></div>":"")),re=N(e,n,s);return[\$(k),j,X,ie,re,'<details class="raw-data-toggle"><summary><span class="expand-icon" aria-hidden="true"></span>View raw data</summary><div class="raw-data-pane"><pre>'+v(x)+'</pre><button type="button" class="copy-json-btn" aria-label="Copy raw JSON">Copy JSON</button></div></details>'].join("")}async function F(){var t,n;const s=await async function(){const e=document.getElementById("radar-data");return e&&e.textContent&&"{}"!==e.textContent.trim()?JSON.parse(e.textContent):(await fetch("./sample-data.json")).json()}(),a=document.getElementById("dependency-list"),i=document.getElementById("results-summary"),r=function(e){const t="string"==typeof e&&e.trim().length>0?e.trim():"unknown";return\`https://dependency-radar.com/next-steps?source=standalone-report&cli=\${encodeURIComponent(t)}\`}(s.dependencyRadarVersion),o=document.getElementById("project-path");o&&(o.textContent=s.project.projectDir);const c=document.getElementById("cta-primary-link"),d=document.getElementById("cta-secondary-link");c&&(c.href=r),d&&(d.href=r);const p=document.getElementById("git-branch-item"),v=document.getElementById("git-branch");(null==(t=s.git)?void 0:t.branch)&&s.git.branch&&p&&v&&(v.textContent=s.git.branch,p.style.display="");const h=document.getElementById("node-item"),f=document.getElementById("node-version"),y=document.getElementById("node-disclaimer");if(s.environment&&h&&f){const e=(null==(n=s.environment.runtimeVersion)?void 0:n.replace(/^v/,""))||"unknown",t=s.environment.minRequiredMajor;f.textContent=e+(t&&t>0?\` (requires ≥\${t})\`:""),h.style.display="",t&&t>0&&y&&(y.textContent="Node requirement derived from dependency engine ranges.",y.style.display="")}const b=document.getElementById("formatted-date");if(b&&s.generatedAt)try{const e=new Date(s.generatedAt),t=new Intl.DateTimeFormat(void 0,{day:"numeric",month:"short",year:"numeric",hour:"2-digit",minute:"2-digit"}).format(e);b.textContent=t}catch{b.textContent=s.generatedAt}const w={search:document.getElementById("search"),direct:document.getElementById("direct-filter"),runtime:document.getElementById("runtime-filter"),sort:document.getElementById("sort-by"),sortDirection:document.getElementById("sort-direction"),hasVulns:document.getElementById("has-vulns"),themeSwitch:document.getElementById("theme-switch"),licenseToggle:document.getElementById("license-toggle"),licensePanel:document.getElementById("license-panel"),licensePermissive:document.getElementById("license-permissive"),licenseWeakCopyleft:document.getElementById("license-weak-copyleft"),licenseStrongCopyleft:document.getElementById("license-strong-copyleft"),licenseUnknown:document.getElementById("license-unknown"),licenseAll:document.getElementById("license-all"),licenseFriendly:document.getElementById("license-friendly"),filtersToggle:document.getElementById("filters-toggle"),filterControls:document.getElementById("filter-controls"),columnHeadersContainer:document.getElementById("column-headers-container"),packageHeader:document.getElementById("package-header")};let C="name",I=!0;"light"===localStorage.getItem("dependency-radar-theme")&&(document.documentElement.classList.add("light"),w.themeSwitch.classList.add("light")),w.themeSwitch.addEventListener("click",()=>{document.documentElement.classList.toggle("light"),w.themeSwitch.classList.toggle("light");const e=document.documentElement.classList.contains("light");localStorage.setItem("dependency-radar-theme",e?"light":"dark")});const L=window.matchMedia("(max-width: 768px)");let E=L.matches;const B=e=>{w.filterControls&&w.filtersToggle&&(w.filterControls.classList.toggle("open",e),w.filtersToggle.classList.toggle("open",e),w.filtersToggle.setAttribute("aria-expanded",String(e)))},j=()=>{if(L.matches)return B(!1),w.licensePanel.classList.add("open"),w.licenseToggle.classList.add("open"),void(E=!0);E&&(B(!1),w.licensePanel.classList.remove("open"),w.licenseToggle.classList.remove("open")),E=!1};function P(){if(w.columnHeadersContainer&&(w.columnHeadersContainer.innerHTML=k(C,I)),w.packageHeader){const e=w.packageHeader.querySelector(".sort-indicator");e&&("name"===C?(e.textContent=I?" ▲":" ▼",w.packageHeader.classList.add("sorted")):(e.textContent="",w.packageHeader.classList.remove("sorted")))}}function T(e){const t=e.target.closest(".column-header");if(!t)return;const n=t.dataset.sort;n&&(C===n?I=!I:(C=n,I=!0),w.sort&&(w.sort.value=C,w.sortDirection.textContent=I?"↑":"↓"),P(),W())}w.licenseToggle.addEventListener("click",()=>{L.matches||(w.licenseToggle.classList.toggle("open"),w.licensePanel.classList.toggle("open"))}),w.filtersToggle&&w.filterControls&&w.filtersToggle.addEventListener("click",()=>{const e=!w.filterControls.classList.contains("open");B(e)}),window.addEventListener("resize",j),j(),w.sortDirection.addEventListener("click",()=>{I=!I,w.sortDirection.textContent=I?"↑":"↓",P(),W()}),w.sort.addEventListener("change",()=>{C=w.sort.value,P(),W()}),w.columnHeadersContainer&&w.columnHeadersContainer.addEventListener("click",T),w.packageHeader&&w.packageHeader.addEventListener("click",T),P(),w.licenseAll.addEventListener("click",()=>{w.licensePermissive.checked=!0,w.licenseWeakCopyleft.checked=!0,w.licenseStrongCopyleft.checked=!0,w.licenseUnknown.checked=!0,V.clear(),W()}),w.licenseFriendly.addEventListener("click",()=>{w.licensePermissive.checked=!0,w.licenseWeakCopyleft.checked=!1,w.licenseStrongCopyleft.checked=!1,w.licenseUnknown.checked=!1,V.clear(),W()});const N=Object.values(s.dependencies||{}),H=new Map;N.forEach(e=>{H.set(x(e.package.name,e.package.version),e)});const A=new Set(H.keys()),U=D(A),M=new Set,V=new Set,\$=new Map,F=(()=>{const e=document.getElementById("copy-announcer");if(e)return e;const t=document.createElement("div");return t.id="copy-announcer",t.className="sr-only",t.setAttribute("aria-live","polite"),document.body.appendChild(t),t})();function G(e){const t=e.dataset.depKey;if(!t)return;const n=e.querySelector(".dep-details");if(!n||"true"===n.dataset.rendered)return;const s=H.get(t);s&&(n.setAttribute("aria-busy","true"),n.innerHTML=['<div class="dep-loading" role="presentation">','<div class="dep-loading-bar"></div>',"</div>"].join(""),requestAnimationFrame(()=>{n.innerHTML=R(s,A,U),n.dataset.rendered="true",n.removeAttribute("aria-busy")}))}function K(){const t=(w.search.value||"").toLowerCase(),n=w.direct.value,s=w.runtime.value,a=w.hasVulns.checked,i=w.licensePermissive.checked,r=w.licenseWeakCopyleft.checked,o=w.licenseStrongCopyleft.checked,c=w.licenseUnknown.checked;return N.filter(l=>{var d,p;const v=x(l.package.name,l.package.version);if(V.has(v))return!0;const h=u(l),f=[h.value,null==(d=l.compliance.license.declared)?void 0:d.spdxId,null==(p=l.compliance.license.inferred)?void 0:p.spdxId].filter(Boolean).join(" ").toLowerCase();if(t&&!l.package.name.toLowerCase().includes(t)&&!f.includes(t))return!1;if("direct"===n&&!l.usage.direct)return!1;if("transitive"===n&&l.usage.direct)return!1;if("all"!==s&&l.usage.scope!==s)return!1;if(a&&0===g[(y=m(l).summary,(null==y?void 0:y.highest)||"none")])return!1;var y;const k=function(t){if(!t)return"unknown";const n=t.toUpperCase();for(const[s,a]of Object.entries(e))if(a.some(e=>n.includes(e.toUpperCase())))return s;return"unknown"}(h.value);return!("permissive"===k&&!i)&&(!("weakCopyleft"===k&&!r)&&(!("strongCopyleft"===k&&!o)&&!("unknown"===k&&!c)))})}function W(){var e;const t=function(e){const t=[...e];if("name"===C)t.sort((e,t)=>e.package.name.localeCompare(t.package.name));else if("depth"===C)t.sort((e,t)=>e.usage.depth-t.usage.depth);else{const e=l.find(e=>e.sortKey===C||e.id===C);(null==e?void 0:e.sortFn)?t.sort(e.sortFn):e&&t.sort((t,n)=>e.getValue(t).localeCompare(e.getValue(n)))}return I||t.reverse(),t}(K()),n=(null==(e=s.summary)?void 0:e.dependencyCount)||N.length;i.innerHTML="Showing <strong>"+t.length+"</strong> of <strong>"+n+"</strong> dependencies",0!==t.length?(a.innerHTML=t.map(O).join(""),\$.clear(),a.querySelectorAll("details.dep-card").forEach(e=>{const t=e.dataset.depKey;t&&\$.set(t,e)}),M.forEach(e=>{const t=\$.get(e);t&&(t.open||(t.open=!0),G(t))})):a.innerHTML='<div class="empty-state"><div class="empty-state-icon">📦</div><div class="empty-state-text">No dependencies match your filters</div></div>'}function q(e){const t=U.get(e)||[];return 1===t.length?t[0]:null}const J=[w.search,w.direct,w.runtime,w.sort,w.hasVulns,w.licensePermissive,w.licenseWeakCopyleft,w.licenseStrongCopyleft,w.licenseUnknown],_=()=>{V.clear(),W()};function z(e){const t=e.getAttribute("data-dep-key");if(!t)return;const n=function(e){if(H.has(e))return e;const t=S(e);if(!t)return q(e);if(t.version.startsWith("npm:")){const e=t.version.slice(4),n=t.name+(e.startsWith("@")?e:"@"+e);if(H.has(n))return n}return q(t.name)}(t);if(!n)return;let s=\$.get(n);if(s||(V.add(n),W(),s=\$.get(n)),!s)return;M.add(n),s.open||(s.open=!0),G(s);const a=()=>{s.scrollIntoView({behavior:"smooth",block:"start"});const e=s.querySelector("summary");e&&e.focus({preventScroll:!0})};requestAnimationFrame(()=>{a(),window.setTimeout(a,60)})}J.forEach(e=>{e&&(e.addEventListener("input",_),e.addEventListener("change",_))}),a.addEventListener("toggle",e=>{const t=e.target;if(!(t instanceof HTMLDetailsElement))return;if(!t.classList.contains("dep-card"))return;const n=t.dataset.depKey;n&&(t.open?(M.add(n),G(t)):M.delete(n))},!0),a.addEventListener("click",e=>{const t=e.target,n=t.closest(".root-package-link");if(n)return e.preventDefault(),void z(n);const s=t.closest(".copy-json-btn");s&&(e.preventDefault(),async function(e){var t;const n=e.closest(".raw-data-toggle"),s=null==n?void 0:n.querySelector("pre"),a=(null==s?void 0:s.textContent)??"";if(a)try{if(null==(t=navigator.clipboard)?void 0:t.writeText)await navigator.clipboard.writeText(a);else{const e=document.createElement("textarea");e.value=a,e.setAttribute("readonly","true"),e.style.position="absolute",e.style.left="-9999px",document.body.appendChild(e),e.select(),document.execCommand("copy"),document.body.removeChild(e)}const n=e.dataset.label||e.textContent||"Copy JSON";e.dataset.label=n,e.textContent="Copied",e.classList.add("copied"),F.textContent="Copied JSON to clipboard.",window.setTimeout(()=>{e.textContent=n,e.classList.remove("copied")},1500)}catch{F.textContent="Copy failed."}}(s))}),a.addEventListener("keydown",e=>{const t=e.target.closest(".root-package-link");t&&(" "!==e.key&&"Spacebar"!==e.key||(e.preventDefault(),z(t)))}),P(),W()}"loading"===document.readyState?document.addEventListener("DOMContentLoaded",F):F()}();
|
|
18
18
|
`;
|
package/dist/report.js
CHANGED
|
@@ -8,14 +8,48 @@ const promises_1 = __importDefault(require("fs/promises"));
|
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
9
|
const cta_1 = require("./cta");
|
|
10
10
|
const report_assets_1 = require("./report-assets");
|
|
11
|
+
/**
|
|
12
|
+
* Escape occurrences of closing `</style` tags in a CSS payload to prevent premature termination when inlined into HTML.
|
|
13
|
+
*
|
|
14
|
+
* @param value - The CSS text to sanitize
|
|
15
|
+
* @returns The sanitized string with each `</style` sequence replaced by `<\/style` (case-insensitive)
|
|
16
|
+
*/
|
|
17
|
+
function sanitizeInlineStyleTagPayload(value) {
|
|
18
|
+
return value.replace(/<\/style/gi, '<\\/style');
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Escapes closing `</script` sequences so a string can be embedded safely inside an inline `<script>` tag.
|
|
22
|
+
*
|
|
23
|
+
* @param value - The script content to sanitize
|
|
24
|
+
* @returns The input with every `</script` (case-insensitive) replaced by `<\/script`
|
|
25
|
+
*/
|
|
26
|
+
function sanitizeInlineScriptTagPayload(value) {
|
|
27
|
+
return value.replace(/<\/script/gi, '<\\/script');
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Generate the HTML report from aggregated data and write it to the given file path.
|
|
31
|
+
*
|
|
32
|
+
* @param data - Aggregated radar data used to build the report
|
|
33
|
+
* @param outputPath - Filesystem path where the generated HTML report will be written; parent directories are created if missing
|
|
34
|
+
*/
|
|
11
35
|
async function renderReport(data, outputPath) {
|
|
12
36
|
const html = buildHtml(data);
|
|
13
37
|
await promises_1.default.mkdir(path_1.default.dirname(outputPath), { recursive: true });
|
|
14
38
|
await promises_1.default.writeFile(outputPath, html, 'utf8');
|
|
15
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Build a complete HTML report string populated from the provided aggregated data.
|
|
42
|
+
*
|
|
43
|
+
* The returned document embeds sanitized CSS and JS assets, a JSON-serialized copy of `data` (with `<` characters escaped), a computed CTA URL derived from `data.dependencyRadarVersion`, and a human-friendly formatted `generatedAt` timestamp when parsable. Dynamic interpolations that appear in the HTML (e.g., project path, formatted date, CTA URL) are HTML-escaped.
|
|
44
|
+
*
|
|
45
|
+
* @param data - Aggregated data used to populate the report (includes project metadata, generatedAt timestamp, dependencyRadarVersion, and dependency list)
|
|
46
|
+
* @returns The full HTML document for the dependency radar report as a string
|
|
47
|
+
*/
|
|
16
48
|
function buildHtml(data) {
|
|
17
49
|
const json = JSON.stringify(data).replace(/</g, '\\u003c');
|
|
18
50
|
const ctaUrl = (0, cta_1.buildCtaUrl)(data.dependencyRadarVersion);
|
|
51
|
+
const safeCssContent = sanitizeInlineStyleTagPayload(report_assets_1.CSS_CONTENT);
|
|
52
|
+
const safeJsContent = sanitizeInlineScriptTagPayload(report_assets_1.JS_CONTENT);
|
|
19
53
|
// Format the generated date
|
|
20
54
|
let formattedDate = data.generatedAt;
|
|
21
55
|
try {
|
|
@@ -86,7 +120,7 @@ function buildHtml(data) {
|
|
|
86
120
|
</svg>"
|
|
87
121
|
>
|
|
88
122
|
<style>
|
|
89
|
-
${
|
|
123
|
+
${safeCssContent}
|
|
90
124
|
</style>
|
|
91
125
|
</head>
|
|
92
126
|
<body>
|
|
@@ -294,9 +328,9 @@ ${report_assets_1.CSS_CONTENT}
|
|
|
294
328
|
</footer>
|
|
295
329
|
|
|
296
330
|
<script type="application/json" id="radar-data">${json}</script>
|
|
297
|
-
|
|
298
|
-
${
|
|
299
|
-
|
|
331
|
+
<script>
|
|
332
|
+
${safeJsContent}
|
|
333
|
+
</script>
|
|
300
334
|
</body>
|
|
301
335
|
</html>`;
|
|
302
336
|
}
|