Skip to content

Commit

Permalink
Refactor public api (#379)
Browse files Browse the repository at this point in the history
* chore: stage work

* refactor: revamp public API & refactor bin/execute
  • Loading branch information
fraxken authored Jul 8, 2024
1 parent b0afc0e commit 3253517
Show file tree
Hide file tree
Showing 8 changed files with 128 additions and 121 deletions.
57 changes: 50 additions & 7 deletions bin/commands/execute.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,61 @@
// Import Node.js Dependencies
import fs from "node:fs/promises";

// Import Third-party Dependencies
import * as rc from "@nodesecure/rc";
import kleur from "kleur";

// Import Internal Dependencies
import { store } from "../../src/localStorage.js";
import * as nreport from "../../src/index.js";

import { fetchPackagesAndRepositoriesData } from "../../src/analysis/fetch.js";
import * as CONSTANTS from "../../src/constants.js";
import * as reporting from "../../src/reporting/index.js";

export async function execute() {
const config = await rc.read(
process.cwd()
const [configResult] = await Promise.all([
rc.read(
process.cwd()
),
init()
]);

const config = configResult.unwrap();
const { report } = config;
if (report.reporters.length === 0) {
throw new Error("At least one reporter must be selected (either 'HTML' or 'PDF')");
}

console.log(`>> title: ${kleur.cyan().bold(report.title)}`);
console.log(`>> reporters: ${kleur.magenta().bold(report.reporters.join(","))}\n`);

store.run(config, () => {
fetchPackagesAndRepositoriesData()
.then((data) => reporting.proceed(data))
.catch((error) => {
console.error(error);
process.exit(0);
})
.finally(teardown);
});
}

function init() {
const directoriesToInitialize = [
CONSTANTS.DIRS.JSON,
CONSTANTS.DIRS.CLONES,
CONSTANTS.DIRS.REPORTS
];

return Promise.all(
directoriesToInitialize.map((dir) => fs.mkdir(dir, { recursive: true }))
);
}

function teardown() {
console.log(kleur.green().bold("\n>> Security report successfully generated! Enjoy 🚀.\n"));

store.run(config.unwrap(), () => {
nreport
.execute()
.catch(console.error);
return fs.rm(CONSTANTS.DIRS.CLONES, {
recursive: true, force: true
});
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
},
"exports": {
".": {
"import": "./src/api/index.js"
"import": "./src/index.js"
}
},
"scripts": {
Expand Down
27 changes: 20 additions & 7 deletions src/analysis/fetch.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ import * as localStorage from "../localStorage.js";
import * as utils from "../utils/index.js";
import * as CONSTANTS from "../constants.js";

export async function fetchPackagesAndRepositoriesData() {
export async function fetchPackagesAndRepositoriesData(
verbose = true
) {
const config = localStorage.getConfig().report;

const fetchNpm = config.npm?.packages.length > 0;
Expand All @@ -27,26 +29,32 @@ export async function fetchPackagesAndRepositoriesData() {
utils.formatNpmPackages(
config.npm.organizationPrefix,
config.npm.packages
)
),
verbose
) :
null;

const { repositories, organizationUrl } = config.git;
const repoStats = fetchGit ?
await fetchRepositoriesStats(
repositories,
organizationUrl
organizationUrl,
verbose
) :
null;

return { pkgStats, repoStats };
}

async function fetchPackagesStats(packages) {
async function fetchPackagesStats(
packages,
verbose = true
) {
const jsonFiles = await utils.runInSpinner(
{
title: `[Fetcher: ${kleur.yellow().bold("NPM")}]`,
start: "Fetching NPM packages metadata on the NPM Registry"
start: "Fetching NPM packages metadata on the NPM Registry",
verbose
},
async() => Promise.all(packages.map(scanner.from))
);
Expand All @@ -56,11 +64,16 @@ async function fetchPackagesStats(packages) {
);
}

async function fetchRepositoriesStats(repositories, organizationUrl) {
async function fetchRepositoriesStats(
repositories,
organizationUrl,
verbose = true
) {
const jsonFiles = await utils.runInSpinner(
{
title: `[Fetcher: ${kleur.yellow().bold("GIT")}]`,
start: "Cloning GIT repositories"
start: "Cloning GIT repositories",
verbose
},
async(spinner) => {
const repos = await Promise.all(
Expand Down
34 changes: 0 additions & 34 deletions src/api/index.js

This file was deleted.

37 changes: 37 additions & 0 deletions src/api/report.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Import Node.js Dependencies
import path from "node:path";
import os from "node:os";
import fs from "node:fs/promises";

// Import Internal Dependencies
import { buildStatsFromNsecurePayloads } from "../analysis/extractScannerData.js";
import { HTML, PDF } from "../reporting/index.js";

export async function report(
reportOptions,
scannerPayload
) {
const [pkgStats, reportOutputLocation] = await Promise.all([
buildStatsFromNsecurePayloads(scannerPayload, {
isJson: true,
reportOptions
}),
fs.mkdtemp(
path.join(os.tmpdir(), "nsecure-report-")
)
]);

const reportHTMLPath = await HTML(
{
pkgStats,
repoStats: null
},
reportOptions,
reportOutputLocation
);

return PDF(reportHTMLPath, {
title: reportOptions.title,
saveOnDisk: false
});
}
56 changes: 1 addition & 55 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,55 +1 @@
// Import Node.js Dependencies
import fs from "node:fs/promises";

// Import Third-party Dependencies
import kleur from "kleur";

// Import Internal Dependencies
import { fetchPackagesAndRepositoriesData } from "./analysis/fetch.js";
import * as localStorage from "./localStorage.js";
import * as CONSTANTS from "./constants.js";
import * as reporting from "./reporting/index.js";

export async function execute(options = Object.create(null)) {
const { exitOnError = true } = options;

try {
await prepareExecute();

const config = localStorage.getConfig().report;
if (config.reporters.length === 0) {
throw new Error("At least one reporter must be selected (either 'HTML' or 'PDF')");
}

console.log(`>> ${kleur.cyan().bold(config.title)}`);
console.log(`>> reporters: ${kleur.magenta().bold(config.reporters.join(","))}\n`);

const data = await fetchPackagesAndRepositoriesData();
await reporting.proceed(data);

console.log(kleur.green().bold("\n>> Security report successfully generated! Enjoy 🚀.\n"));
}
catch (error) {
console.log(kleur.bold().red(error.message));

if (!exitOnError) {
throw error;
}
setImmediate(() => process.exit(0));
}
finally {
await fs.rm(CONSTANTS.DIRS.CLONES, {
recursive: true, force: true
});
}
}

async function prepareExecute() {
const directoriesToInitialize = [
CONSTANTS.DIRS.JSON, CONSTANTS.DIRS.CLONES, CONSTANTS.DIRS.REPORTS
];

await Promise.all(
directoriesToInitialize.map((dir) => fs.mkdir(dir, { recursive: true }))
);
}
export * from "./api/report.js";
24 changes: 11 additions & 13 deletions src/reporting/html.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { readdirSync, promises as fs } from "node:fs";

// Import Third-party Dependencies
import esbuild from "esbuild";
import kleur from "kleur";

// Import Internal Dependencies
import * as utils from "../utils/index.js";
Expand Down Expand Up @@ -52,7 +51,7 @@ export async function HTML(
reportOptions = null,
reportOutputLocation = CONSTANTS.DIRS.REPORTS
) {
const { pkgStats, repoStats, spinner } = data;
const { pkgStats, repoStats } = data;

const config = reportOptions ?? localStorage.getConfig().report;
const assetsOutputLocation = path.join(reportOutputLocation, "..", "dist");
Expand All @@ -62,7 +61,6 @@ export async function HTML(
utils.cleanReportName(config.title, ".html")
);

spinner.text = "Building view with zup";
const charts = config.charts
.flatMap(({ display, name, help = null }) => (display ? [{ name, help }] : []));

Expand All @@ -78,16 +76,16 @@ export async function HTML(
}
).render();

await fs.writeFile(
reportFinalOutputLocation,
HTMLReport
);

spinner.text = kleur.yellow().bold("Bundling assets with esbuild");
await buildFrontAssets(
assetsOutputLocation,
{ theme: reportTheme }
);
await Promise.all([
fs.writeFile(
reportFinalOutputLocation,
HTMLReport
),
buildFrontAssets(
assetsOutputLocation,
{ theme: reportTheme }
)
]);

return reportFinalOutputLocation;
}
Expand Down
12 changes: 8 additions & 4 deletions src/reporting/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@ import { HTML } from "./html.js";
import { PDF } from "./pdf.js";

export async function proceed(
data
data,
verbose = true
) {
const reportHTMLPath = await utils.runInSpinner(
{
title: `[Reporter: ${kleur.yellow().bold("HTML")}]`
title: `[Reporter: ${kleur.yellow().bold("HTML")}]`,
start: "Building template and assets",
verbose
},
async(spinner) => HTML({ ...data, spinner })
async() => HTML(data)
);

const { reporters, title } = localStorage.getConfig().report;
Expand All @@ -27,7 +30,8 @@ export async function proceed(
await utils.runInSpinner(
{
title: `[Reporter: ${kleur.yellow().bold("PDF")}]`,
start: "Using puppeteer to convert HTML content to PDF"
start: "Using puppeteer to convert HTML content to PDF",
verbose
},
async() => PDF(reportHTMLPath, { title })
);
Expand Down

0 comments on commit 3253517

Please sign in to comment.