Skip to content

Commit 61fe812

Browse files
committed
feat(contacts): implemented parser
1 parent 2bae16f commit 61fe812

File tree

7 files changed

+165
-9
lines changed

7 files changed

+165
-9
lines changed

bin/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,8 @@ function defaultScannerCommand(name, options = {}) {
127127

128128
const cmd = prog.command(name)
129129
.option("-d, --depth", i18n.getTokenSync("cli.commands.option_depth"), Infinity)
130-
.option("--silent", i18n.getTokenSync("cli.commands.option_silent"), false);
130+
.option("--silent", i18n.getTokenSync("cli.commands.option_silent"), false)
131+
.option("-c, --contacts", i18n.getTokenSync("cli.commands.option_contacts"), "[]");
131132

132133
if (includeOutput) {
133134
cmd.option("-o, --output", i18n.getTokenSync("cli.commands.option_output"), "nsecure-result");

i18n/english.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const cli = {
1414
option_depth: "Maximum dependencies depth to fetch",
1515
option_output: "Json file output name",
1616
option_silent: "enable silent mode which disable CLI spinners",
17+
option_contacts: "List of contacts to hightlight",
1718
strategy: "Vulnerabilities source to use",
1819
cwd: {
1920
desc: "Run security analysis on the current working dir",
@@ -70,6 +71,13 @@ const cli = {
7071
startHttp: {
7172
invalidScannerVersion: tS`the payload has been scanned with version '${0}' and do not satisfies the required CLI range '${1}'`,
7273
regenerate: "please re-generate a new JSON payload using the CLI"
74+
},
75+
errors: {
76+
contacts: {
77+
should_be_valid_json: tS`Contacts: ${0}`,
78+
should_be_array: "Contacts should be an array",
79+
should_be_defined: tS`Contact at index ${0} should not be null`
80+
}
7381
}
7482
};
7583

i18n/french.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const cli = {
1414
option_depth: "Niveau de profondeur de dépendances maximum à aller chercher",
1515
option_output: "Nom de sortie du fichier json",
1616
option_silent: "Activer le mode silencieux qui désactive les spinners du CLI",
17+
option_contacts: "Liste des contacts à mettre en évidence",
1718
strategy: "Source de vulnérabilités à utiliser",
1819
cwd: {
1920
desc: "Démarre une analyse de sécurité sur le dossier courant",
@@ -70,6 +71,13 @@ const cli = {
7071
startHttp: {
7172
invalidScannerVersion: tS`le fichier d'analyse correspond à la version '${0}' du scanner et ne satisfait pas la range '${1}' attendu par la CLI`,
7273
regenerate: "veuillez re-générer un nouveau fichier d'analyse JSON en utilisant votre CLI"
74+
},
75+
errors: {
76+
contacts: {
77+
should_be_valid_json: tS`Contacts: ${0}`,
78+
should_be_array: "Contacts doit etre un array",
79+
should_be_defined: tS`Contact à index ${0} ne doit pas etre null`
80+
}
7381
}
7482
};
7583

public/components/views/home/maintainers/maintainers.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,24 @@ export class Maintainers {
2626
}
2727

2828
render() {
29-
const authors = [...this.secureDataSet.authors.entries()]
30-
.sort((left, right) => right[1].packages.size - left[1].packages.size);
29+
const authors = this.#highlightContacts([...this.secureDataSet.authors.entries()]
30+
.sort((left, right) => right[1].packages.size - left[1].packages.size));
3131

3232
document.getElementById("authors-count").innerHTML = authors.length;
3333
document.querySelector(".home--maintainers")
3434
.appendChild(this.generate(authors));
3535
}
3636

37+
#highlightContacts(authors) {
38+
const highlightedContacts = new Set(this.secureDataSet.data.highlighted.contacts
39+
.map(({ name }) => name));
40+
41+
const highlightedAuthors = authors.filter(([name]) => highlightedContacts.has(name));
42+
const authorsRest = authors.filter(([name]) => !highlightedContacts.has(name));
43+
44+
return [...highlightedAuthors, ...authorsRest];
45+
}
46+
3747
generate(authors) {
3848
const fragment = document.createDocumentFragment();
3949
const hideItems = authors.length > this.maximumMaintainers;

src/commands/parsers/contacts.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
export function createContactsParser({ logError, exit }) {
2+
return (json) => {
3+
const contacts = parseContacts({ json, logError, exit });
4+
if (!Array.isArray(contacts)) {
5+
logError("cli.errors.contacts.should_be_array");
6+
exit();
7+
}
8+
let hasError = false;
9+
contacts.forEach((contact, i) => {
10+
if (!contact) {
11+
hasError = true;
12+
logError("cli.errors.contacts.should_be_defined", i);
13+
}
14+
});
15+
if (hasError) {
16+
exit();
17+
}
18+
19+
return contacts;
20+
};
21+
}
22+
23+
function parseContacts({ json, logError, exit }) {
24+
try {
25+
return JSON.parse(json);
26+
}
27+
catch (err) {
28+
logError("cli.errors.contacts.should_be_valid_json", err.message);
29+
exit();
30+
31+
return null;
32+
}
33+
}

src/commands/scanner.js

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,28 @@ import * as Scanner from "@nodesecure/scanner";
1414
// Import Internal Dependencies
1515
import * as http from "./http.js";
1616
import { appCache } from "../cache.js";
17+
import { createContactsParser } from "./parsers/contacts.js";
18+
19+
const parseContacts = createContactsParser({
20+
logError: (tokenName, param) => console.log(kleur.red().bold(param ? i18n.getTokenSync(tokenName, param)
21+
: i18n.getTokenSync(tokenName))),
22+
exit: () => process.exit()
23+
});
1724

1825
export async function auto(spec, options) {
1926
const { keep, ...commandOptions } = options;
2027

28+
const optionsWithContacts = {
29+
...commandOptions,
30+
highlight: {
31+
contacts: parseContacts(options.contacts)
32+
}
33+
};
34+
2135
const payloadFile = await (
2236
typeof spec === "string" ?
23-
from(spec, commandOptions) :
24-
cwd(commandOptions)
37+
from(spec, optionsWithContacts) :
38+
cwd(optionsWithContacts)
2539
);
2640
try {
2741
if (payloadFile !== null) {
@@ -55,24 +69,28 @@ export async function cwd(options) {
5569
nolock,
5670
full,
5771
vulnerabilityStrategy,
58-
silent
72+
silent,
73+
contacts
5974
} = options;
6075

6176
const payload = await Scanner.cwd(
6277
process.cwd(),
63-
{ maxDepth, usePackageLock: !nolock, fullLockMode: full, vulnerabilityStrategy },
78+
{ maxDepth, usePackageLock: !nolock, fullLockMode: full, vulnerabilityStrategy, highlight:
79+
{ contacts: parseContacts(contacts) } },
6480
initLogger(void 0, !silent)
6581
);
6682

6783
return await logAndWrite(payload, output, { local: true });
6884
}
6985

7086
export async function from(spec, options) {
71-
const { depth: maxDepth = Infinity, output, silent } = options;
87+
const { depth: maxDepth = Infinity, output, silent, contacts } = options;
7288

7389
const payload = await Scanner.from(
7490
spec,
75-
{ maxDepth },
91+
{ maxDepth, highlight: {
92+
contacts: parseContacts(contacts)
93+
} },
7694
initLogger(spec, !silent)
7795
);
7896

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Import Node.js Dependencies
2+
import { it, describe, beforeEach } from "node:test";
3+
import assert from "node:assert/strict";
4+
5+
// Import Internal Dependencies
6+
import { createContactsParser } from "../../../src/commands/parsers/contacts.js";
7+
8+
let errors = [];
9+
10+
function exit() {
11+
throw new Error("process exited");
12+
}
13+
14+
beforeEach(() => {
15+
errors = [];
16+
});
17+
18+
function logError(token, param) {
19+
if (param) {
20+
errors.push(`${token} ${param}`);
21+
}
22+
else {
23+
errors.push(token);
24+
}
25+
}
26+
27+
describe("contacts parser", () => {
28+
it("should successfully parse the contacts to highlight", () => {
29+
const parseContacts = createContactsParser({
30+
logError,
31+
exit
32+
});
33+
34+
const contactsJson = "[{\"name\": \"contact1\"},{\"name\":\"contact2\",\"url\":\"url2\",\"email\":\"[email protected]\"}]";
35+
const result = [{ name: "contact1" }, { name: "contact2", url: "url2", email: "[email protected]" }];
36+
assert.deepEqual(parseContacts(contactsJson), result);
37+
assert.deepEqual(errors, []);
38+
});
39+
40+
describe("errors", () => {
41+
it("should display an error and exit the process when the contacts is not valid json", () => {
42+
const parseContacts = createContactsParser({
43+
logError,
44+
exit
45+
});
46+
47+
const unvalidJson = "][";
48+
49+
assert.throws(() => parseContacts(unvalidJson), { message: "process exited" });
50+
assert.deepEqual(errors, ["cli.errors.contacts.should_be_valid_json Unexpected token ']', \"][\" is not valid JSON"]);
51+
});
52+
53+
it("should display an error and exit the process when the contacts is not an array", () => {
54+
const parseContacts = createContactsParser({
55+
logError,
56+
exit
57+
});
58+
59+
const contactsJson = "{\"name\":\"contact1\"}";
60+
61+
assert.throws(() => parseContacts(contactsJson), { message: "process exited" });
62+
assert.deepEqual(errors, ["cli.errors.contacts.should_be_array"]);
63+
});
64+
65+
it("should display an error when a contact is null", () => {
66+
const parseContacts = createContactsParser({
67+
logError,
68+
exit
69+
});
70+
71+
const contactsJson = "[{\"name\": \"contact1\"},null,{\"name\":\"contact2\"," +
72+
"\"url\":\"url2\",\"email\":\"[email protected]\"},null]";
73+
assert.throws(() => parseContacts(contactsJson), { message: "process exited" });
74+
assert.deepEqual(errors, ["cli.errors.contacts.should_be_defined 1", "cli.errors.contacts.should_be_defined 3"]);
75+
});
76+
});
77+
});
78+

0 commit comments

Comments
 (0)