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 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 by running standard package manager tooling (npm, pnpm, or yarn)
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), run collectors:
45
- - Dependency tree (`npm ls` / `pnpm list` / `yarn list`)
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
- 5. Normalize tool outputs into one internal shape and merge workspace package results.
50
- - PNPM dependency trees are filtered to installed-only packages (non-installed optional/platform variants are dropped)
51
- 6. Resolve and crawl installed package directories in `node_modules` to collect local metadata:
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
- 7. Aggregate dependency records by enriching each installed package with:
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
- 8. Write final output as either:
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
- 9. Remove `.dependency-radar/` unless `--keep-temp` is set.
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 now verifies PNPM entries against installed artifacts (`node_modules/.pnpm` and workspace-linked `node_modules` paths) before including them in the report.
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 ls depth fallbacks for large projects).
230
- - Yarn Classic (v1, node_modules linker): Supported for dependency tree, audit, outdated, and workspaces.
231
- - Yarn Berry (v2+, node-modules linker): Dependency tree and audit work; outdated support depends on available Yarn commands/plugins and may be unavailable.
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/?source=standalone-report';
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()
@@ -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',
@@ -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,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;"):""}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,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;"):""}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
- ${report_assets_1.CSS_CONTENT}
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
- <script>
298
- ${report_assets_1.JS_CONTENT}
299
- </script>
331
+ <script>
332
+ ${safeJsContent}
333
+ </script>
300
334
  </body>
301
335
  </html>`;
302
336
  }