UNPKG

@nodesecure/cli

Version:
333 lines (283 loc) 10.3 kB
// Import Third-party Dependencies import { NodeSecureDataSet, getFlagsEmojisInlined, getJSON } from "@nodesecure/vis-network"; import { licenseIdConformance } from "@nodesecure/licenses-conformance"; import { getScoreColor, getVCSRepositoryPathAndPlatform } from "@nodesecure/utils"; // Import Internal Dependencies import * as utils from "../../../common/utils.js"; import { Gauge } from "../../gauge/gauge.js"; import { createExpandableSpan } from "../../expandable/expandable.js"; import { fetchScorecardData, getScorecardLink } from "../../../common/scorecard.js"; // Import Components import { Maintainers } from "./maintainers/maintainers.js"; import { PopupReport } from "./report/report.js"; // CONSTANTS const kFlagsToWatch = new Set([ "hasBannedFile", "isDeprecated", "hasVulnerabilities", "hasScript" ]); const kEmojiDescription = { "📦": "scripts", "⚔️": "sensitive files", "🚨": "vulnerabilities", "⛔️": "deprecated" }; export class HomeView { /** * @param {!NodeSecureDataSet} secureDataSet */ constructor( secureDataSet, nsn ) { this.secureDataSet = secureDataSet; this.nsn = nsn; this.lang = utils.currentLang(); const homeViewHTMLElement = document.getElementById("home--view"); homeViewHTMLElement.innerHTML = ""; homeViewHTMLElement.appendChild(this.render()); this.generateScorecard(); this.generateHeader(); this.generateOverview(); this.generatePackagesToWatch(); this.generateWarnings(); this.generateExtensions(); this.generateLicenses(); this.generateMaintainers(); this.handleReport(); } render() { console.log("[HOME] cloning new template"); const template = document.getElementById("home-view-content"); if (!template) { throw new Error("Unable to find HTML template with ID 'home-view-content'"); } /** @type {HTMLTemplateElement} */ const clone = document.importNode(template.content, true); return clone; } generateScorecard() { const { name, version } = this.secureDataSet.linker.get(0); const pkg = this.secureDataSet.data.dependencies[name]; const { repository } = pkg.versions[version].links; if (repository === null || repository === undefined) { return; } const [repoName, platform] = getVCSRepositoryPathAndPlatform(repository) ?? []; fetchScorecardData(repoName, platform).then((data) => { if (data !== null) { document .querySelector(".home--header--scorecard .score") .classList.add(getScoreColor(data.score)); document.getElementById("home-scorecard-score").innerHTML = data.score; const scorescardElement = document.querySelector(".home--header--scorecard"); scorescardElement.addEventListener("click", () => { window.open(getScorecardLink(repoName, platform), "_blank"); }); scorescardElement.style.display = "flex"; } }); } generateHeader() { const { name, version, description, repository } = this.secureDataSet.linker.get(0); document.getElementById("project-name").textContent = name; document.getElementById("project-version").textContent = version; document.getElementById("project-description").textContent = description; const linksFragment = document.createDocumentFragment(); const links = { NPM: { src: "npm-icon.svg", href: `https://www.npmjs.com/package/${name}/v/${version}` }, Github: { src: "github-black.png", href: utils.parseRepositoryUrl(repository) } }; for (const [linkName, config] of Object.entries(links)) { const liElement = utils.createDOMElement("li", { childs: [ utils.createDOMElement("img", { attributes: { src: config.src } }), utils.createDOMElement("p", { text: linkName }) ] }); if (config.href === null) { liElement.style.display = "none"; } else { liElement.addEventListener("click", () => window.open(config.href, "_blank")); } linksFragment.appendChild(liElement); } document.getElementById("project-links").appendChild(linksFragment); } async generateOverview() { const fragment = document.createDocumentFragment(); function createOverviewDiv(icon, title, value) { const titleDiv = utils.createDOMElement("div", { className: "title", childs: [ utils.createDOMElement("i", { className: icon }), utils.createDOMElement("p", { text: title }) ] }); return utils.createDOMElement("div", { childs: [ titleDiv, utils.createDOMElement("span", { text: value }) ] }); } const { name } = this.secureDataSet.linker.get(0); let directDependencies = 0; for (const { usedBy } of this.secureDataSet.linker.values()) { if (name in usedBy) { directDependencies++; } } fragment.appendChild(createOverviewDiv( "icon-cubes", `# ${window.i18n[this.lang].home.overview.dependencies}`, this.secureDataSet.dependenciesCount )); fragment.appendChild(createOverviewDiv( "icon-archive", window.i18n[this.lang].home.overview.totalSize, this.secureDataSet.prettySize )); fragment.appendChild(createOverviewDiv( "icon-link", `# ${window.i18n[this.lang].home.overview.directDeps}`, directDependencies )); fragment.appendChild(createOverviewDiv( "icon-sitemap", `# ${window.i18n[this.lang].home.overview.transitiveDeps}`, this.secureDataSet.indirectDependencies )); const homeOverviewElement = document.querySelector(".home--overview"); homeOverviewElement.appendChild(fragment); try { const { downloads } = await getJSON(`/downloads/${name.replaceAll("/", "%2F")}`); if (downloads) { const formattedNumber = new Intl.NumberFormat("de-DE").format(downloads); homeOverviewElement.appendChild(createOverviewDiv( "icon-chart-bar", window.i18n[this.lang].home.overview.downloadsLastWeek, formattedNumber )); } } catch { // DO NOTHING } } generatePackagesToWatch() { const maxPackages = 4; const fragment = document.createDocumentFragment(); const deps = []; for (const dependency of Object.values(this.secureDataSet.data.dependencies)) { for (const dependencyVer of Object.values(dependency.versions)) { const { flags } = dependencyVer; const hasFlag = flags.some((name) => kFlagsToWatch.has(name)); if (hasFlag) { deps.push(dependencyVer); } } } const hideItems = deps.length > maxPackages; for (let id = 0; id < deps.length; id++) { const dependency = deps[id]; const element = this.renderPackage(dependency); element.addEventListener("click", () => { window.navigation.setNavByName("network--view"); setTimeout(() => this.nsn.focusNodeByNameAndVersion(dependency.name, dependency.version), 25); }); if (hideItems && id >= maxPackages) { element.classList.add("hidden"); } fragment.appendChild(element); } if (fragment.children.length === 0) { document.getElementById("homewatch").style.display = "none"; } else { if (hideItems) { fragment.appendChild(createExpandableSpan(maxPackages)); } document.querySelector(".home--packages--overview") .appendChild(fragment); } } renderPackage(dependencyVer) { const { name, version, flags } = dependencyVer; const inlinedEmojis = getFlagsEmojisInlined( flags.filter((name) => kFlagsToWatch.has(name)), new Set(window.settings.config.ignore.flags) ); const childs = utils.extractEmojis(inlinedEmojis) .map((emoji) => utils.createDOMElement("p", { text: `${emoji} ${kEmojiDescription[emoji]}` })); return utils.createDOMElement("div", { childs: [ utils.createDOMElement("p", { childs: [ document.createTextNode(name), utils.createDOMElement("span", { text: `v${version}` }) ] }), utils.createDOMElement("div", { className: "chips", childs }) ] }); } generateWarnings() { const warningsModuleElement = document.getElementById("warnings-module"); if (this.secureDataSet.warnings.length === 0) { warningsModuleElement.style.display = "none"; return; } const fragment = document.createDocumentFragment(); for (const text of this.secureDataSet.warnings) { fragment.appendChild(utils.createDOMElement("p", { text })); } warningsModuleElement.querySelector(".count").textContent = this.secureDataSet.warnings.length; warningsModuleElement.querySelector(".home--warnings").appendChild(fragment); } generateExtensions() { const extensions = [...Object.entries(this.secureDataSet.extensions)] .sort(([, left], [, right]) => right - left) .map(([name, value]) => { return { name, value }; }); document.getElementById("home-extensions").appendChild( new Gauge(extensions).render() ); } generateLicenses() { const licenses = [...Object.entries(this.secureDataSet.licenses)] .sort(([, left], [, right]) => right - left) .flatMap(([name, value]) => { const result = licenseIdConformance(name); if (!result.ok) { return []; } return [ { name, value, link: result.value.spdxLicenseLinks?.[0] ?? null, chips: Object.entries(result.value.spdx) .filter(([key]) => key !== "includesDeprecated") .map(([key, value]) => `${value ? "✔️" : "❌"} ${key}`) } ]; }); document.getElementById("home-licenses").appendChild( new Gauge(licenses).render() ); } generateMaintainers() { new Maintainers(this.secureDataSet, this.nsn) .render(); } handleReport() { document.querySelector(".home--header--report").addEventListener("click", async() => { window.popup.open( new PopupReport(this.secureDataSet.data.rootDependencyName).render() ); }); } }