@greenarmor/ges 1.5.3 → 1.5.5
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 +19 -2
- package/dist/commands/governance.js +72 -4
- package/dist/commands/update.js +83 -3
- package/dist/utils/ui.d.ts +2 -0
- package/dist/utils/ui.js +21 -0
- package/dist/utils/update-check.d.ts +14 -0
- package/dist/utils/update-check.js +117 -0
- package/dist/utils/update-notice.d.ts +1 -0
- package/dist/utils/update-notice.js +50 -0
- package/dist/utils/version.d.ts +4 -0
- package/dist/utils/version.js +4 -0
- package/package.json +14 -14
package/dist/cli.js
CHANGED
|
@@ -19,12 +19,29 @@ import { hooksCommand } from "./commands/hooks.js";
|
|
|
19
19
|
import { dashboardCommand } from "./commands/dashboard.js";
|
|
20
20
|
import { governanceCommand } from "./commands/governance.js";
|
|
21
21
|
import { assignCommand } from "./commands/assign.js";
|
|
22
|
-
import { CLI_VERSION } from "./utils/version.js";
|
|
22
|
+
import { CLI_VERSION, AUTHOR, RELEASE_DATE, DONATE_URL, HOMEPAGE } from "./utils/version.js";
|
|
23
|
+
import { showUpdateNoticeIfNeeded } from "./utils/update-notice.js";
|
|
24
|
+
import { BOLD, DIM, CYAN, GRAY, MAGENTA } from "./utils/ui.js";
|
|
25
|
+
const versionDisplay = [
|
|
26
|
+
``,
|
|
27
|
+
` ${BOLD(MAGENTA("GESF"))} ${BOLD(`v${CLI_VERSION}`)}`,
|
|
28
|
+
` ${GRAY("Green Engineering Standard Framework")}`,
|
|
29
|
+
``,
|
|
30
|
+
` ${DIM("Author:")} ${AUTHOR}`,
|
|
31
|
+
` ${DIM("Released:")} ${RELEASE_DATE}`,
|
|
32
|
+
``,
|
|
33
|
+
` ${DIM("Support:")} ${CYAN(DONATE_URL)}`,
|
|
34
|
+
` ${DIM("GitHub:")} ${GRAY(HOMEPAGE)}`,
|
|
35
|
+
``,
|
|
36
|
+
].join("\n");
|
|
23
37
|
const program = new Command();
|
|
24
38
|
program
|
|
25
39
|
.name("ges")
|
|
26
40
|
.description("Green Engineering Standard Framework - Compliance-as-Code CLI")
|
|
27
|
-
.version(
|
|
41
|
+
.version(versionDisplay, "-v, --version", "Output the version number");
|
|
42
|
+
program.hook("preAction", async (_thisCommand, actionCommand) => {
|
|
43
|
+
await showUpdateNoticeIfNeeded(actionCommand.name());
|
|
44
|
+
});
|
|
28
45
|
program.addCommand(initCommand);
|
|
29
46
|
program.addCommand(auditCommand);
|
|
30
47
|
program.addCommand(scoreCommand);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import { ensureGESInitialized } from "../utils/project.js";
|
|
3
3
|
import { input, select } from "../utils/prompts.js";
|
|
4
|
-
import { banner, divider, blank, success, error, warn, info, kv, statusBadge, severityBadge, BOLD, DIM, GREEN, RED, YELLOW, GRAY, } from "../utils/ui.js";
|
|
4
|
+
import { banner, divider, blank, success, error, warn, info, kv, label, statusBadge, severityBadge, BOLD, DIM, GREEN, RED, YELLOW, GRAY, } from "../utils/ui.js";
|
|
5
5
|
import { loadGovernanceRecords, createGovernanceRecord, addGovernanceRecord, findGovernanceRecord, setGovernanceApproval, addGovernanceEvidence, createEvidenceRef, verifyGovernanceRecord, deleteGovernanceRecord, setGovernanceRiskAssessment, setGovernancePolicyBasis, setGovernanceReviewCycle, setGovernanceDataInventory, setGovernanceComplianceLinks, setGovernanceCommittee, } from "@greenarmor/ges-core";
|
|
6
6
|
import { recordActivity } from "@greenarmor/ges-core";
|
|
7
7
|
const STATUS_BADGE = {
|
|
@@ -32,6 +32,70 @@ function printRecordSummary(record) {
|
|
|
32
32
|
}
|
|
33
33
|
console.log(` ${DIM("Ev")} ${record.evidence.length} reference(s)`);
|
|
34
34
|
}
|
|
35
|
+
async function showGovernanceNextAction(root, records, lastShownId) {
|
|
36
|
+
if (process.stdin.isTTY !== true || process.stdout.isTTY !== true)
|
|
37
|
+
return;
|
|
38
|
+
const choices = [];
|
|
39
|
+
if (lastShownId) {
|
|
40
|
+
choices.push({ name: `Verify this record ${DIM("— check provenance completeness")}`, value: `ges governance verify ${lastShownId}` });
|
|
41
|
+
if (records.length > 1) {
|
|
42
|
+
choices.push({ name: `Show another record ${DIM("— pick from list")}`, value: `__pick_show__` });
|
|
43
|
+
}
|
|
44
|
+
choices.push({ name: `Record an approval ${DIM("— add approval decision")}`, value: `ges governance approve ${lastShownId}` });
|
|
45
|
+
choices.push({ name: `Add evidence reference ${DIM("— link to Jira/Confluence/etc")}`, value: `ges governance evidence ${lastShownId}` });
|
|
46
|
+
choices.push({ name: `Link risk assessment ${DIM("— assessor, methodology, score")}`, value: `ges governance risk-assessment ${lastShownId}` });
|
|
47
|
+
choices.push({ name: `Document policy basis ${DIM("— which policy applies")}`, value: `ges governance policy-basis ${lastShownId}` });
|
|
48
|
+
}
|
|
49
|
+
else if (records.length > 0) {
|
|
50
|
+
choices.push({ name: `Show a record's full provenance chain ${DIM("— all dimensions in detail")}`, value: `__pick_show__` });
|
|
51
|
+
choices.push({ name: `Verify a record's completeness ${DIM("— check all 8 dimensions")}`, value: `__pick_verify__` });
|
|
52
|
+
}
|
|
53
|
+
choices.push({ name: `Create a new governance record ${DIM("— start a new approval chain")}`, value: `ges governance add` });
|
|
54
|
+
if (records.length === 0) {
|
|
55
|
+
choices.length = 0;
|
|
56
|
+
choices.push({ name: `Create a new governance record ${DIM("— start a new approval chain")}`, value: `ges governance add` });
|
|
57
|
+
}
|
|
58
|
+
choices.push({ name: `${YELLOW("Exit")} ${DIM("— return to terminal")}`, value: `exit` });
|
|
59
|
+
divider();
|
|
60
|
+
label("What would you like to do next?");
|
|
61
|
+
const answer = await select({
|
|
62
|
+
message: "Choose your next action:",
|
|
63
|
+
choices,
|
|
64
|
+
});
|
|
65
|
+
if (answer === "exit") {
|
|
66
|
+
blank();
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
let cmd = answer;
|
|
70
|
+
if (answer === "__pick_show__" || answer === "__pick_verify__") {
|
|
71
|
+
let recordChoice;
|
|
72
|
+
if (records.length === 1) {
|
|
73
|
+
recordChoice = records[0].id;
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
recordChoice = await select({
|
|
77
|
+
message: "Select a record:",
|
|
78
|
+
choices: records.map(r => ({
|
|
79
|
+
name: `${r.system_name} ${GRAY(`(${r.status}, ${r.risk_level})`)}`,
|
|
80
|
+
value: r.id,
|
|
81
|
+
})),
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
const sub = answer === "__pick_show__" ? "show" : "verify";
|
|
85
|
+
cmd = `ges governance ${sub} ${recordChoice}`;
|
|
86
|
+
}
|
|
87
|
+
blank();
|
|
88
|
+
info("Running", GREEN(cmd));
|
|
89
|
+
divider();
|
|
90
|
+
blank();
|
|
91
|
+
const { execSync } = await import("node:child_process");
|
|
92
|
+
try {
|
|
93
|
+
execSync(cmd, { stdio: "inherit" });
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
35
99
|
export const governanceCommand = new Command("governance")
|
|
36
100
|
.description("Manage governance approval provenance chains")
|
|
37
101
|
.action(async () => {
|
|
@@ -332,12 +396,13 @@ export const governanceCommand = new Command("governance")
|
|
|
332
396
|
}))
|
|
333
397
|
.addCommand(new Command("list")
|
|
334
398
|
.description("List all governance records")
|
|
335
|
-
.action(() => {
|
|
399
|
+
.action(async () => {
|
|
336
400
|
const root = ensureGESInitialized();
|
|
337
401
|
const records = loadGovernanceRecords(root);
|
|
338
402
|
if (records.length === 0) {
|
|
339
403
|
info("No governance records found.");
|
|
340
404
|
console.log(` ${DIM("Create one with:")} ${GREEN("ges governance add")}\n`);
|
|
405
|
+
await showGovernanceNextAction(root, records);
|
|
341
406
|
return;
|
|
342
407
|
}
|
|
343
408
|
blank();
|
|
@@ -347,11 +412,12 @@ export const governanceCommand = new Command("governance")
|
|
|
347
412
|
printRecordSummary(r);
|
|
348
413
|
console.log();
|
|
349
414
|
});
|
|
415
|
+
await showGovernanceNextAction(root, records);
|
|
350
416
|
}))
|
|
351
417
|
.addCommand(new Command("show")
|
|
352
418
|
.description("Show full provenance chain for a governance record")
|
|
353
419
|
.argument("<id>", "Record ID or system name")
|
|
354
|
-
.action((id) => {
|
|
420
|
+
.action(async (id) => {
|
|
355
421
|
const root = ensureGESInitialized();
|
|
356
422
|
const record = findGovernanceRecord(root, id);
|
|
357
423
|
if (!record) {
|
|
@@ -494,11 +560,12 @@ export const governanceCommand = new Command("governance")
|
|
|
494
560
|
}
|
|
495
561
|
console.log(`\n Created: ${record.created_at} by ${record.created_by}`);
|
|
496
562
|
console.log(` Updated: ${record.updated_at} by ${record.updated_by} (v${record.record_version})\n`);
|
|
563
|
+
await showGovernanceNextAction(root, [record], record.id);
|
|
497
564
|
}))
|
|
498
565
|
.addCommand(new Command("verify")
|
|
499
566
|
.description("Verify the provenance chain completeness of a governance record")
|
|
500
567
|
.argument("<id>", "Record ID or system name")
|
|
501
|
-
.action((id) => {
|
|
568
|
+
.action(async (id) => {
|
|
502
569
|
const root = ensureGESInitialized();
|
|
503
570
|
const record = findGovernanceRecord(root, id);
|
|
504
571
|
if (!record) {
|
|
@@ -543,6 +610,7 @@ export const governanceCommand = new Command("governance")
|
|
|
543
610
|
result.warnings.forEach(w => console.log(` ${YELLOW("△")} ${w}`));
|
|
544
611
|
}
|
|
545
612
|
console.log();
|
|
613
|
+
await showGovernanceNextAction(root, [record], record.id);
|
|
546
614
|
}))
|
|
547
615
|
.addCommand(new Command("delete")
|
|
548
616
|
.description("Delete a governance record")
|
package/dist/commands/update.js
CHANGED
|
@@ -1,10 +1,90 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import { CLI_VERSION } from "../utils/version.js";
|
|
3
3
|
import { showNextStepsMenu } from "../utils/next-steps.js";
|
|
4
|
+
import { checkForUpdate, compareVersions, dismissVersion, disableUpdateChecks, enableUpdateChecks, isInteractive, NPM_PACKAGE, } from "../utils/update-check.js";
|
|
5
|
+
import { banner, blank, divider, success, error, warn, info, kv, label, GREEN, YELLOW, DIM, CYAN, } from "../utils/ui.js";
|
|
6
|
+
import { confirm } from "../utils/prompts.js";
|
|
4
7
|
export const updateCommand = new Command("update")
|
|
5
8
|
.description("Check for GESF updates")
|
|
6
|
-
.
|
|
7
|
-
|
|
8
|
-
|
|
9
|
+
.option("--check", "Only check for updates, don't install")
|
|
10
|
+
.option("--disable-checks", "Disable automatic update notifications")
|
|
11
|
+
.option("--enable-checks", "Re-enable automatic update notifications")
|
|
12
|
+
.action(async (options) => {
|
|
13
|
+
if (options.disableChecks) {
|
|
14
|
+
disableUpdateChecks();
|
|
15
|
+
success("Update checks disabled.", "You won't see update notifications anymore.");
|
|
16
|
+
info("Re-enable", "ges update --enable-checks");
|
|
17
|
+
blank();
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (options.enableChecks) {
|
|
21
|
+
enableUpdateChecks();
|
|
22
|
+
success("Update checks enabled.", "You'll see update notifications when new versions are available.");
|
|
23
|
+
blank();
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
banner("GESF Update", "Version check & upgrade");
|
|
27
|
+
info("Current version", CLI_VERSION);
|
|
28
|
+
kv("Package", NPM_PACKAGE);
|
|
29
|
+
blank();
|
|
30
|
+
label("Checking npm registry for latest version...");
|
|
31
|
+
const result = await checkForUpdate(true);
|
|
32
|
+
if (!result.latestVersion) {
|
|
33
|
+
warn("Could not reach npm registry.", "Check your network connection and try again.");
|
|
34
|
+
console.log(` ${DIM("Or check manually:")} ${CYAN("npm view @greenarmor/ges version")}\n`);
|
|
35
|
+
await showNextStepsMenu("update");
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
kv("Latest version", result.latestVersion);
|
|
39
|
+
blank();
|
|
40
|
+
if (compareVersions(result.latestVersion, CLI_VERSION) <= 0) {
|
|
41
|
+
success("You're up to date!", `Running ${CLI_VERSION} (latest).`);
|
|
42
|
+
blank();
|
|
43
|
+
await showNextStepsMenu("update");
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
divider();
|
|
47
|
+
console.log(` ${YELLOW("↻")} ${YELLOW(`Update available: ${CLI_VERSION} → ${result.latestVersion}`)}`);
|
|
48
|
+
divider();
|
|
49
|
+
blank();
|
|
50
|
+
if (options.check || !isInteractive()) {
|
|
51
|
+
console.log(` ${DIM("To update:")}`);
|
|
52
|
+
console.log(` ${GREEN("npm install -g @greenarmor/ges@latest")}`);
|
|
53
|
+
console.log(` ${DIM("or")}`);
|
|
54
|
+
console.log(` ${GREEN("pnpm add -g @greenarmor/ges@latest")}\n`);
|
|
55
|
+
await showNextStepsMenu("update");
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const shouldUpdate = await confirm({
|
|
59
|
+
message: `Install @greenarmor/ges@${result.latestVersion} now?`,
|
|
60
|
+
default: true,
|
|
61
|
+
});
|
|
62
|
+
if (!shouldUpdate) {
|
|
63
|
+
const dismiss = await confirm({
|
|
64
|
+
message: `Skip this version (${result.latestVersion})?`,
|
|
65
|
+
default: false,
|
|
66
|
+
});
|
|
67
|
+
if (dismiss) {
|
|
68
|
+
dismissVersion(result.latestVersion);
|
|
69
|
+
success("Version skipped.", `You won't be reminded about ${result.latestVersion} again.`);
|
|
70
|
+
}
|
|
71
|
+
blank();
|
|
72
|
+
await showNextStepsMenu("update");
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
blank();
|
|
76
|
+
info("Installing", `${CYAN(`${NPM_PACKAGE}@latest`)}`);
|
|
77
|
+
divider();
|
|
78
|
+
const { execSync } = await import("node:child_process");
|
|
79
|
+
try {
|
|
80
|
+
execSync(`npm install -g ${NPM_PACKAGE}@latest`, { stdio: "inherit" });
|
|
81
|
+
blank();
|
|
82
|
+
success("Update complete!", `Installed ${result.latestVersion}.`);
|
|
83
|
+
blank();
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
error("Update failed.", "Try manually: npm install -g @greenarmor/ges@latest");
|
|
87
|
+
blank();
|
|
88
|
+
}
|
|
9
89
|
await showNextStepsMenu("update");
|
|
10
90
|
});
|
package/dist/utils/ui.d.ts
CHANGED
|
@@ -34,4 +34,6 @@ export declare function progressBar(current: number, total: number, width?: numb
|
|
|
34
34
|
export declare function statusBadge(status: string): string;
|
|
35
35
|
export declare function severityBadge(severity: string): string;
|
|
36
36
|
export declare function gradeColor(grade: string): string;
|
|
37
|
+
export declare function updateNoticeBox(current: string, latest: string): void;
|
|
38
|
+
export declare function updateNoticeLine(current: string, latest: string): void;
|
|
37
39
|
export { chalk, icons, DIM, BOLD, GREEN, RED, YELLOW, CYAN, MAGENTA, GRAY };
|
package/dist/utils/ui.js
CHANGED
|
@@ -112,4 +112,25 @@ export function gradeColor(grade) {
|
|
|
112
112
|
return MAGENTA(BOLD(grade));
|
|
113
113
|
return RED(BOLD(grade));
|
|
114
114
|
}
|
|
115
|
+
export function updateNoticeBox(current, latest) {
|
|
116
|
+
const inner = ` ${YELLOW(BOLD("↻"))} ${BOLD("Update available")} ${DIM(GRAY(current))} ${GRAY("→")} ${GREEN(latest)}`;
|
|
117
|
+
const innerLine2 = ` ${DIM("Run")} ${CYAN("ges update")} ${DIM("to upgrade, or")} ${CYAN("npm i -g @greenarmor/ges@latest")}`;
|
|
118
|
+
const width = Math.max(inner.replace(/\x1b\[[0-9;]*m/g, "").length, 56) + 4;
|
|
119
|
+
const top = GRAY(` ┌${"─".repeat(width)}┐`);
|
|
120
|
+
const mid1 = GRAY(" │") + " ".repeat(Math.max(0, (width - stripAnsi(inner).length) / 2 | 0)) + inner + " ".repeat(Math.max(0, width - stripAnsi(inner).length - ((width - stripAnsi(inner).length) / 2 | 0))) + GRAY("│");
|
|
121
|
+
const mid2 = GRAY(" │") + " ".repeat(Math.max(0, (width - stripAnsi(innerLine2).length) / 2 | 0)) + innerLine2 + " ".repeat(Math.max(0, width - stripAnsi(innerLine2).length - ((width - stripAnsi(innerLine2).length) / 2 | 0))) + GRAY("│");
|
|
122
|
+
const bot = GRAY(` └${"─".repeat(width)}┘`);
|
|
123
|
+
console.log();
|
|
124
|
+
console.log(top);
|
|
125
|
+
console.log(mid1);
|
|
126
|
+
console.log(mid2);
|
|
127
|
+
console.log(bot);
|
|
128
|
+
console.log();
|
|
129
|
+
}
|
|
130
|
+
function stripAnsi(str) {
|
|
131
|
+
return str.replace(/\x1b\[[0-9;]*m/g, "");
|
|
132
|
+
}
|
|
133
|
+
export function updateNoticeLine(current, latest) {
|
|
134
|
+
console.error(` ${YELLOW("↻")} ${DIM("Update available:")} ${current} ${GRAY("→")} ${latest} ${DIM("Run")} ${CYAN("ges update")}`);
|
|
135
|
+
}
|
|
115
136
|
export { chalk, icons, DIM, BOLD, GREEN, RED, YELLOW, CYAN, MAGENTA, GRAY };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
declare const NPM_PACKAGE = "@greenarmor/ges";
|
|
2
|
+
export declare function compareVersions(a: string, b: string): number;
|
|
3
|
+
export declare function isInteractive(): boolean;
|
|
4
|
+
export interface UpdateCheckResult {
|
|
5
|
+
currentVersion: string;
|
|
6
|
+
latestVersion: string | null;
|
|
7
|
+
updateAvailable: boolean;
|
|
8
|
+
dismissed: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare function checkForUpdate(force?: boolean): Promise<UpdateCheckResult>;
|
|
11
|
+
export declare function dismissVersion(version: string): void;
|
|
12
|
+
export declare function disableUpdateChecks(): void;
|
|
13
|
+
export declare function enableUpdateChecks(): void;
|
|
14
|
+
export { NPM_PACKAGE };
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import * as os from "node:os";
|
|
4
|
+
import { execSync } from "node:child_process";
|
|
5
|
+
import { CLI_VERSION } from "./version.js";
|
|
6
|
+
const CACHE_DIR = path.join(os.homedir(), ".ges");
|
|
7
|
+
const CACHE_FILE = path.join(CACHE_DIR, "update-check.json");
|
|
8
|
+
const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
9
|
+
const TIMEOUT_MS = 3000;
|
|
10
|
+
const NPM_PACKAGE = "@greenarmor/ges";
|
|
11
|
+
function defaultCache() {
|
|
12
|
+
return {
|
|
13
|
+
latest_version: null,
|
|
14
|
+
checked_at: null,
|
|
15
|
+
dismissed_versions: [],
|
|
16
|
+
disabled: false,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function readCache() {
|
|
20
|
+
try {
|
|
21
|
+
if (!fs.existsSync(CACHE_FILE))
|
|
22
|
+
return defaultCache();
|
|
23
|
+
const data = JSON.parse(fs.readFileSync(CACHE_FILE, "utf-8"));
|
|
24
|
+
return { ...defaultCache(), ...data };
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return defaultCache();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function writeCache(cache) {
|
|
31
|
+
try {
|
|
32
|
+
if (!fs.existsSync(CACHE_DIR))
|
|
33
|
+
fs.mkdirSync(CACHE_DIR, { recursive: true });
|
|
34
|
+
fs.writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2) + "\n");
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// best-effort cache — silent on failure
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export function compareVersions(a, b) {
|
|
41
|
+
const pa = a.replace(/^[v]/, "").split(".").map((n) => parseInt(n, 10) || 0);
|
|
42
|
+
const pb = b.replace(/^[v]/, "").split(".").map((n) => parseInt(n, 10) || 0);
|
|
43
|
+
const len = Math.max(pa.length, pb.length);
|
|
44
|
+
for (let i = 0; i < len; i++) {
|
|
45
|
+
const va = pa[i] || 0;
|
|
46
|
+
const vb = pb[i] || 0;
|
|
47
|
+
if (va > vb)
|
|
48
|
+
return 1;
|
|
49
|
+
if (va < vb)
|
|
50
|
+
return -1;
|
|
51
|
+
}
|
|
52
|
+
return 0;
|
|
53
|
+
}
|
|
54
|
+
export function isInteractive() {
|
|
55
|
+
return process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
56
|
+
}
|
|
57
|
+
function fetchLatestVersion() {
|
|
58
|
+
try {
|
|
59
|
+
const output = execSync(`npm view ${NPM_PACKAGE} version`, {
|
|
60
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
61
|
+
timeout: TIMEOUT_MS,
|
|
62
|
+
encoding: "utf-8",
|
|
63
|
+
});
|
|
64
|
+
const version = output.trim().split("\n").pop()?.trim() || "";
|
|
65
|
+
return version.match(/^\d+\.\d+\.\d+/) ? version : null;
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
export async function checkForUpdate(force = false) {
|
|
72
|
+
const cache = readCache();
|
|
73
|
+
if (cache.disabled) {
|
|
74
|
+
return { currentVersion: CLI_VERSION, latestVersion: null, updateAvailable: false, dismissed: false };
|
|
75
|
+
}
|
|
76
|
+
const now = Date.now();
|
|
77
|
+
const checkedAt = cache.checked_at ? new Date(cache.checked_at).getTime() : 0;
|
|
78
|
+
const isStale = now - checkedAt > CHECK_INTERVAL_MS;
|
|
79
|
+
let latest = cache.latest_version;
|
|
80
|
+
if (isStale || force) {
|
|
81
|
+
const shouldFetch = force || isInteractive();
|
|
82
|
+
if (shouldFetch) {
|
|
83
|
+
latest = fetchLatestVersion();
|
|
84
|
+
writeCache({
|
|
85
|
+
...cache,
|
|
86
|
+
latest_version: latest ?? cache.latest_version,
|
|
87
|
+
checked_at: new Date().toISOString(),
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const updateAvailable = latest ? compareVersions(latest, CLI_VERSION) > 0 : false;
|
|
92
|
+
const dismissed = latest ? cache.dismissed_versions.includes(latest) : false;
|
|
93
|
+
return {
|
|
94
|
+
currentVersion: CLI_VERSION,
|
|
95
|
+
latestVersion: latest,
|
|
96
|
+
updateAvailable,
|
|
97
|
+
dismissed,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
export function dismissVersion(version) {
|
|
101
|
+
const cache = readCache();
|
|
102
|
+
if (!cache.dismissed_versions.includes(version)) {
|
|
103
|
+
cache.dismissed_versions.push(version);
|
|
104
|
+
writeCache(cache);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
export function disableUpdateChecks() {
|
|
108
|
+
const cache = readCache();
|
|
109
|
+
cache.disabled = true;
|
|
110
|
+
writeCache(cache);
|
|
111
|
+
}
|
|
112
|
+
export function enableUpdateChecks() {
|
|
113
|
+
const cache = readCache();
|
|
114
|
+
cache.disabled = false;
|
|
115
|
+
writeCache(cache);
|
|
116
|
+
}
|
|
117
|
+
export { NPM_PACKAGE };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function showUpdateNoticeIfNeeded(currentCommandName?: string): Promise<void>;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { checkForUpdate, dismissVersion, isInteractive, NPM_PACKAGE } from "./update-check.js";
|
|
2
|
+
import { select } from "./prompts.js";
|
|
3
|
+
import { updateNoticeBox, updateNoticeLine, blank, divider, info, success, error, GREEN, DIM, CYAN, YELLOW, } from "./ui.js";
|
|
4
|
+
const SKIP_COMMANDS = new Set(["update", "mcp", "start"]);
|
|
5
|
+
export async function showUpdateNoticeIfNeeded(currentCommandName) {
|
|
6
|
+
if (currentCommandName && SKIP_COMMANDS.has(currentCommandName))
|
|
7
|
+
return;
|
|
8
|
+
const result = await checkForUpdate();
|
|
9
|
+
if (!result.updateAvailable || result.dismissed || !result.latestVersion)
|
|
10
|
+
return;
|
|
11
|
+
if (!isInteractive()) {
|
|
12
|
+
updateNoticeLine(result.currentVersion, result.latestVersion);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
updateNoticeBox(result.currentVersion, result.latestVersion);
|
|
16
|
+
const action = await select({
|
|
17
|
+
message: "An update is available. What would you like to do?",
|
|
18
|
+
choices: [
|
|
19
|
+
{ name: `${GREEN("Update now")} ${DIM("— install latest and exit")}`, value: "update" },
|
|
20
|
+
{ name: `Skip for now ${DIM("— continue with current version")}`, value: "skip" },
|
|
21
|
+
{ name: `${YELLOW("Skip this version")} ${DIM(`— don't remind me about ${result.latestVersion} again`)}`, value: "dismiss" },
|
|
22
|
+
],
|
|
23
|
+
});
|
|
24
|
+
if (action === "skip") {
|
|
25
|
+
blank();
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (action === "dismiss") {
|
|
29
|
+
dismissVersion(result.latestVersion);
|
|
30
|
+
blank();
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (action === "update") {
|
|
34
|
+
blank();
|
|
35
|
+
info("Installing", `${CYAN(NPM_PACKAGE)}@${result.latestVersion}`);
|
|
36
|
+
divider();
|
|
37
|
+
const { execSync } = await import("node:child_process");
|
|
38
|
+
try {
|
|
39
|
+
execSync(`npm install -g ${NPM_PACKAGE}@latest`, { stdio: "inherit" });
|
|
40
|
+
blank();
|
|
41
|
+
success("Update complete!", `Now running ${result.latestVersion}. Please re-run your command.`);
|
|
42
|
+
blank();
|
|
43
|
+
process.exit(0);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
error("Update failed.", "You can update manually: npm install -g @greenarmor/ges@latest");
|
|
47
|
+
blank();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
package/dist/utils/version.d.ts
CHANGED
package/dist/utils/version.js
CHANGED
|
@@ -6,3 +6,7 @@ const __dirname = path.dirname(__filename);
|
|
|
6
6
|
const require = createRequire(import.meta.url);
|
|
7
7
|
const pkg = require(path.join(__dirname, "..", "..", "package.json"));
|
|
8
8
|
export const CLI_VERSION = pkg.version;
|
|
9
|
+
export const AUTHOR = "greenarmor";
|
|
10
|
+
export const RELEASE_DATE = "2026-06-20";
|
|
11
|
+
export const DONATE_URL = "https://ko-fi.com/greenarmor";
|
|
12
|
+
export const HOMEPAGE = "https://github.com/greenarmor/gesf";
|
package/package.json
CHANGED
|
@@ -3,19 +3,19 @@
|
|
|
3
3
|
"ges": "./dist/cli.js"
|
|
4
4
|
},
|
|
5
5
|
"dependencies": {
|
|
6
|
-
"@greenarmor/ges-audit-engine": "1.5.
|
|
7
|
-
"@greenarmor/ges-cicd-generator": "1.5.
|
|
8
|
-
"@greenarmor/ges-compliance-engine": "1.5.
|
|
9
|
-
"@greenarmor/ges-core": "1.5.
|
|
10
|
-
"@greenarmor/ges-doc-generator": "1.5.
|
|
11
|
-
"@greenarmor/ges-git-hooks": "1.5.
|
|
12
|
-
"@greenarmor/ges-mcp-server": "1.5.
|
|
13
|
-
"@greenarmor/ges-policy-engine": "1.5.
|
|
14
|
-
"@greenarmor/ges-report-generator": "1.5.
|
|
15
|
-
"@greenarmor/ges-rules-engine": "1.5.
|
|
16
|
-
"@greenarmor/ges-scanner-integration": "1.5.
|
|
17
|
-
"@greenarmor/ges-scoring-engine": "1.5.
|
|
18
|
-
"@greenarmor/ges-web-dashboard": "1.5.
|
|
6
|
+
"@greenarmor/ges-audit-engine": "1.5.5",
|
|
7
|
+
"@greenarmor/ges-cicd-generator": "1.5.5",
|
|
8
|
+
"@greenarmor/ges-compliance-engine": "1.5.5",
|
|
9
|
+
"@greenarmor/ges-core": "1.5.5",
|
|
10
|
+
"@greenarmor/ges-doc-generator": "1.5.5",
|
|
11
|
+
"@greenarmor/ges-git-hooks": "1.5.5",
|
|
12
|
+
"@greenarmor/ges-mcp-server": "1.5.5",
|
|
13
|
+
"@greenarmor/ges-policy-engine": "1.5.5",
|
|
14
|
+
"@greenarmor/ges-report-generator": "1.5.5",
|
|
15
|
+
"@greenarmor/ges-rules-engine": "1.5.5",
|
|
16
|
+
"@greenarmor/ges-scanner-integration": "1.5.5",
|
|
17
|
+
"@greenarmor/ges-scoring-engine": "1.5.5",
|
|
18
|
+
"@greenarmor/ges-web-dashboard": "1.5.5",
|
|
19
19
|
"chalk": "^5.6.2",
|
|
20
20
|
"commander": "^13.0.0"
|
|
21
21
|
},
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
},
|
|
61
61
|
"type": "module",
|
|
62
62
|
"types": "./dist/index.d.ts",
|
|
63
|
-
"version": "1.5.
|
|
63
|
+
"version": "1.5.5",
|
|
64
64
|
"scripts": {
|
|
65
65
|
"build": "tsc",
|
|
66
66
|
"clean": "rm -rf dist tsconfig.tsbuildinfo",
|