@greenarmor/ges 0.3.4 → 0.4.0
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/dist/cli.js +2 -0
- package/dist/commands/audit.js +1 -1
- package/dist/commands/badge.d.ts +2 -0
- package/dist/commands/badge.js +42 -0
- package/dist/commands/compliance.js +4 -1
- package/dist/commands/init.js +1 -1
- package/dist/commands/mcp-setup.js +1 -1
- package/dist/commands/score.js +3 -12
- package/dist/utils/next-steps.js +1 -1
- package/dist/utils/prompts.d.ts +19 -0
- package/dist/utils/prompts.js +94 -0
- package/package.json +12 -15
package/dist/cli.js
CHANGED
|
@@ -12,6 +12,7 @@ import { generateCommand } from "./commands/generate.js";
|
|
|
12
12
|
import { policyCommand } from "./commands/policy.js";
|
|
13
13
|
import { updateCommand } from "./commands/update.js";
|
|
14
14
|
import { mcpCommand } from "./commands/mcp.js";
|
|
15
|
+
import { badgeCommand } from "./commands/badge.js";
|
|
15
16
|
import { GESF_VERSION } from "@greenarmor/ges-core";
|
|
16
17
|
const program = new Command();
|
|
17
18
|
program
|
|
@@ -30,4 +31,5 @@ program.addCommand(generateCommand);
|
|
|
30
31
|
program.addCommand(policyCommand);
|
|
31
32
|
program.addCommand(updateCommand);
|
|
32
33
|
program.addCommand(mcpCommand);
|
|
34
|
+
program.addCommand(badgeCommand);
|
|
33
35
|
program.parse();
|
package/dist/commands/audit.js
CHANGED
|
@@ -21,7 +21,7 @@ export const auditCommand = new Command("audit")
|
|
|
21
21
|
const frameworks = (config?.frameworks || ["GDPR", "OWASP"]);
|
|
22
22
|
const controls = getAllPacks().flatMap(p => p.controls);
|
|
23
23
|
const updatedControls = updateControlsFromFindings(controls, findings);
|
|
24
|
-
const scoreData = generateScoreFile(updatedControls, frameworks);
|
|
24
|
+
const scoreData = generateScoreFile(updatedControls, frameworks, findings);
|
|
25
25
|
writeJsonFile(path.join(root, ".ges", "score.json"), scoreData);
|
|
26
26
|
const critical = findings.filter(f => f.severity === "critical");
|
|
27
27
|
const high = findings.filter(f => f.severity === "high");
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { ensureGESInitialized, readJsonFile } from "../utils/project.js";
|
|
3
|
+
import { generateBadgeSvg, injectBadgeIntoReadme, computeGrade } from "@greenarmor/ges-scoring-engine";
|
|
4
|
+
import { showNextStepsMenu } from "../utils/next-steps.js";
|
|
5
|
+
import * as fs from "node:fs";
|
|
6
|
+
import * as path from "node:path";
|
|
7
|
+
export const badgeCommand = new Command("badge")
|
|
8
|
+
.description("Generate compliance score badge for README")
|
|
9
|
+
.option("-o, --output <path>", "Output path for badge SVG", ".ges/badge.svg")
|
|
10
|
+
.option("--readme <path>", "README file to inject badge into", "README.md")
|
|
11
|
+
.option("--no-readme", "Do not inject badge into README")
|
|
12
|
+
.action(async (options) => {
|
|
13
|
+
const root = ensureGESInitialized();
|
|
14
|
+
const scorePath = path.join(root, ".ges", "score.json");
|
|
15
|
+
const score = readJsonFile(scorePath);
|
|
16
|
+
if (!score || !score.frameworks || Object.keys(score.frameworks).length === 0) {
|
|
17
|
+
console.log("\n No compliance score available. Run 'ges audit' first.\n");
|
|
18
|
+
await showNextStepsMenu("badge");
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const svg = generateBadgeSvg(score);
|
|
22
|
+
const outputPath = path.resolve(root, options.output);
|
|
23
|
+
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
24
|
+
fs.writeFileSync(outputPath, svg);
|
|
25
|
+
console.log(`\n Badge generated: ${options.output}`);
|
|
26
|
+
console.log(` Score: ${score.overall}% (${score.overall_grade ?? computeGrade(score.overall)})`);
|
|
27
|
+
if (options.readme !== false) {
|
|
28
|
+
const readmePath = path.resolve(root, options.readme);
|
|
29
|
+
if (fs.existsSync(readmePath)) {
|
|
30
|
+
const readmeContent = fs.readFileSync(readmePath, "utf-8");
|
|
31
|
+
const relativeBadgePath = path.relative(path.dirname(readmePath), outputPath);
|
|
32
|
+
const updated = injectBadgeIntoReadme(readmeContent, relativeBadgePath);
|
|
33
|
+
fs.writeFileSync(readmePath, updated);
|
|
34
|
+
console.log(` Badge injected into ${options.readme}`);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
console.log(` ${options.readme} not found — skipping badge injection`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
console.log("");
|
|
41
|
+
await showNextStepsMenu("badge");
|
|
42
|
+
});
|
|
@@ -25,7 +25,10 @@ export const complianceCommand = new Command("compliance")
|
|
|
25
25
|
const controls = readJsonFile(controlsFile);
|
|
26
26
|
const total = controls?.length || 0;
|
|
27
27
|
const passed = controls?.filter(c => c.status === "pass").length || 0;
|
|
28
|
-
|
|
28
|
+
const failed = controls?.filter(c => c.status === "fail").length || 0;
|
|
29
|
+
const criticalFailed = controls?.filter(c => c.status === "fail" && c.severity === "critical").length || 0;
|
|
30
|
+
const statusTag = criticalFailed > 0 ? " ⚠" : "";
|
|
31
|
+
console.log(` ${pack.id.padEnd(15)} ${passed}/${total} passed ${failed} failed${statusTag}`);
|
|
29
32
|
}
|
|
30
33
|
}
|
|
31
34
|
console.log("");
|
package/dist/commands/init.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
import { input, select, checkbox } from "
|
|
2
|
+
import { input, select, checkbox } from "../utils/prompts.js";
|
|
3
3
|
import { PROJECT_TYPES, FRAMEWORKS, DEFAULT_FRAMEWORKS, GESF_VERSION, GES_DIR, COMPLIANCE_DIR, SECURITY_DIR, CONTROLS_DIR, POLICIES_DIR, CHECKLISTS_DIR, DOCS_DIR, REPORTS_DIR, } from "@greenarmor/ges-core";
|
|
4
4
|
import { getPacksForProjectType } from "@greenarmor/ges-policy-engine";
|
|
5
5
|
import { generateComplianceDocs, generateSecurityDocs, generateConfigYaml, generateConfigJson, generateMetadataJson, generateFrameworkVersionJson, generateScoreJson, } from "@greenarmor/ges-doc-generator";
|
|
@@ -3,7 +3,7 @@ import * as fs from "node:fs";
|
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import * as os from "node:os";
|
|
5
5
|
import * as url from "node:url";
|
|
6
|
-
import { select } from "
|
|
6
|
+
import { select } from "../utils/prompts.js";
|
|
7
7
|
import { showNextStepsMenu } from "../utils/next-steps.js";
|
|
8
8
|
const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
|
|
9
9
|
const SERVER_NAME = "gesf";
|
package/dist/commands/score.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import { ensureGESInitialized, readJsonFile } from "../utils/project.js";
|
|
3
|
+
import { formatScoreOutput } from "@greenarmor/ges-scoring-engine";
|
|
3
4
|
import { showNextStepsMenu } from "../utils/next-steps.js";
|
|
4
5
|
import * as path from "node:path";
|
|
5
6
|
export const scoreCommand = new Command("score")
|
|
@@ -18,18 +19,8 @@ export const scoreCommand = new Command("score")
|
|
|
18
19
|
console.log(JSON.stringify(score, null, 2));
|
|
19
20
|
}
|
|
20
21
|
else {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const padding = Math.max(1, 20 - fw.length);
|
|
24
|
-
const dots = ".".repeat(padding);
|
|
25
|
-
lines.push(` ${fw} ${dots} ${data.score}%`);
|
|
26
|
-
}
|
|
27
|
-
const overallPadding = Math.max(1, 20 - "Overall".length);
|
|
28
|
-
lines.push(` Overall ${".".repeat(overallPadding)} ${score.overall}%`);
|
|
29
|
-
lines.push("");
|
|
30
|
-
lines.push(` Last evaluated: ${score.evaluated_at}`);
|
|
31
|
-
lines.push("");
|
|
32
|
-
console.log(lines.join("\n"));
|
|
22
|
+
console.log(formatScoreOutput(score));
|
|
23
|
+
console.log(` Last evaluated: ${score.evaluated_at}\n`);
|
|
33
24
|
}
|
|
34
25
|
await showNextStepsMenu("score");
|
|
35
26
|
});
|
package/dist/utils/next-steps.js
CHANGED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export declare function input(options: {
|
|
2
|
+
message: string;
|
|
3
|
+
default?: string;
|
|
4
|
+
}): Promise<string>;
|
|
5
|
+
export declare function select<T = string>(options: {
|
|
6
|
+
message: string;
|
|
7
|
+
choices: {
|
|
8
|
+
name: string;
|
|
9
|
+
value: T;
|
|
10
|
+
}[];
|
|
11
|
+
}): Promise<T>;
|
|
12
|
+
export declare function checkbox<T = string>(options: {
|
|
13
|
+
message: string;
|
|
14
|
+
choices: {
|
|
15
|
+
name: string;
|
|
16
|
+
value: T;
|
|
17
|
+
checked?: boolean;
|
|
18
|
+
}[];
|
|
19
|
+
}): Promise<T[]>;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import * as readline from "node:readline";
|
|
2
|
+
function isInteractive() {
|
|
3
|
+
return process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
4
|
+
}
|
|
5
|
+
function createRL() {
|
|
6
|
+
return readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
7
|
+
}
|
|
8
|
+
let cachedInquirer;
|
|
9
|
+
async function getInquirer() {
|
|
10
|
+
if (cachedInquirer !== undefined)
|
|
11
|
+
return cachedInquirer;
|
|
12
|
+
try {
|
|
13
|
+
const mod = await import(String("@inquirer/prompts"));
|
|
14
|
+
cachedInquirer = mod;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
cachedInquirer = null;
|
|
18
|
+
}
|
|
19
|
+
return cachedInquirer;
|
|
20
|
+
}
|
|
21
|
+
export async function input(options) {
|
|
22
|
+
if (!isInteractive()) {
|
|
23
|
+
return options.default ?? "";
|
|
24
|
+
}
|
|
25
|
+
const inquirer = await getInquirer();
|
|
26
|
+
if (inquirer) {
|
|
27
|
+
return inquirer.input({ message: options.message, default: options.default });
|
|
28
|
+
}
|
|
29
|
+
const rl = createRL();
|
|
30
|
+
const suffix = options.default ? ` (${options.default})` : "";
|
|
31
|
+
return new Promise((resolve) => {
|
|
32
|
+
rl.question(` ${options.message}${suffix}: `, (answer) => {
|
|
33
|
+
rl.close();
|
|
34
|
+
resolve(answer.trim() || options.default || "");
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
export async function select(options) {
|
|
39
|
+
if (!isInteractive()) {
|
|
40
|
+
return options.choices[0].value;
|
|
41
|
+
}
|
|
42
|
+
const inquirer = await getInquirer();
|
|
43
|
+
if (inquirer) {
|
|
44
|
+
return inquirer.select({ message: options.message, choices: options.choices });
|
|
45
|
+
}
|
|
46
|
+
console.log(`\n ${options.message}:\n`);
|
|
47
|
+
options.choices.forEach((c, i) => {
|
|
48
|
+
console.log(` ${i + 1}) ${c.name}`);
|
|
49
|
+
});
|
|
50
|
+
const rl = createRL();
|
|
51
|
+
return new Promise((resolve) => {
|
|
52
|
+
rl.question(`\n Enter choice [1-${options.choices.length}]: `, (answer) => {
|
|
53
|
+
rl.close();
|
|
54
|
+
const num = parseInt(answer.trim(), 10);
|
|
55
|
+
if (num >= 1 && num <= options.choices.length) {
|
|
56
|
+
resolve(options.choices[num - 1].value);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
resolve(options.choices[0].value);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
export async function checkbox(options) {
|
|
65
|
+
if (!isInteractive()) {
|
|
66
|
+
return options.choices.filter(c => c.checked).map(c => c.value);
|
|
67
|
+
}
|
|
68
|
+
const inquirer = await getInquirer();
|
|
69
|
+
if (inquirer) {
|
|
70
|
+
return inquirer.checkbox({ message: options.message, choices: options.choices });
|
|
71
|
+
}
|
|
72
|
+
console.log(`\n ${options.message} (comma-separated numbers):\n`);
|
|
73
|
+
options.choices.forEach((c, i) => {
|
|
74
|
+
const marker = c.checked ? "[x]" : "[ ]";
|
|
75
|
+
console.log(` ${marker} ${i + 1}) ${c.name}`);
|
|
76
|
+
});
|
|
77
|
+
const rl = createRL();
|
|
78
|
+
return new Promise((resolve) => {
|
|
79
|
+
rl.question(`\n Enter choices [1-${options.choices.length}]: `, (answer) => {
|
|
80
|
+
rl.close();
|
|
81
|
+
const trimmed = answer.trim();
|
|
82
|
+
if (!trimmed) {
|
|
83
|
+
resolve(options.choices.filter(c => c.checked).map(c => c.value));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const indices = trimmed.split(",").map(s => parseInt(s.trim(), 10) - 1).filter(n => n >= 0 && n < options.choices.length);
|
|
87
|
+
if (indices.length === 0) {
|
|
88
|
+
resolve(options.choices.filter(c => c.checked).map(c => c.value));
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
resolve([...new Set(indices)].map(i => options.choices[i].value));
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@greenarmor/ges",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Green Engineering Standard Framework - Compliance-as-Code CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -12,23 +12,20 @@
|
|
|
12
12
|
"dist"
|
|
13
13
|
],
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@inquirer/prompts": "^8.0.0",
|
|
16
15
|
"commander": "^13.0.0",
|
|
17
|
-
"
|
|
18
|
-
"@greenarmor/ges-
|
|
19
|
-
"@greenarmor/ges-
|
|
20
|
-
"@greenarmor/ges-
|
|
21
|
-
"@greenarmor/ges-
|
|
22
|
-
"@greenarmor/ges-
|
|
23
|
-
"@greenarmor/ges-
|
|
24
|
-
"@greenarmor/ges-
|
|
25
|
-
"@greenarmor/ges-
|
|
26
|
-
"@greenarmor/ges-
|
|
27
|
-
"@greenarmor/ges-
|
|
28
|
-
"@greenarmor/ges-mcp-server": "0.3.4"
|
|
16
|
+
"@greenarmor/ges-audit-engine": "0.4.0",
|
|
17
|
+
"@greenarmor/ges-cicd-generator": "0.4.0",
|
|
18
|
+
"@greenarmor/ges-policy-engine": "0.4.0",
|
|
19
|
+
"@greenarmor/ges-core": "0.4.0",
|
|
20
|
+
"@greenarmor/ges-report-generator": "0.4.0",
|
|
21
|
+
"@greenarmor/ges-compliance-engine": "0.4.0",
|
|
22
|
+
"@greenarmor/ges-scanner-integration": "0.4.0",
|
|
23
|
+
"@greenarmor/ges-scoring-engine": "0.4.0",
|
|
24
|
+
"@greenarmor/ges-rules-engine": "0.4.0",
|
|
25
|
+
"@greenarmor/ges-mcp-server": "0.4.0",
|
|
26
|
+
"@greenarmor/ges-doc-generator": "0.4.0"
|
|
29
27
|
},
|
|
30
28
|
"devDependencies": {
|
|
31
|
-
"@types/js-yaml": "^4.0.0",
|
|
32
29
|
"@types/node": "^22.0.0",
|
|
33
30
|
"typescript": "^6.0.0"
|
|
34
31
|
},
|