@greenarmor/ges 1.5.4 → 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 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(CLI_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,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
- .action(async () => {
7
- console.log(`\n GESF Version: ${CLI_VERSION}`);
8
- console.log(" Update check: Run 'npm update -g @greenarmor/ges' or 'pnpm update -g @greenarmor/ges'\n");
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
  });
@@ -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
+ }
@@ -1 +1,5 @@
1
1
  export declare const CLI_VERSION: string;
2
+ export declare const AUTHOR: string;
3
+ export declare const RELEASE_DATE: string;
4
+ export declare const DONATE_URL: string;
5
+ export declare const HOMEPAGE: string;
@@ -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.4",
7
- "@greenarmor/ges-cicd-generator": "1.5.4",
8
- "@greenarmor/ges-compliance-engine": "1.5.4",
9
- "@greenarmor/ges-core": "1.5.4",
10
- "@greenarmor/ges-doc-generator": "1.5.4",
11
- "@greenarmor/ges-git-hooks": "1.5.4",
12
- "@greenarmor/ges-mcp-server": "1.5.4",
13
- "@greenarmor/ges-policy-engine": "1.5.4",
14
- "@greenarmor/ges-report-generator": "1.5.4",
15
- "@greenarmor/ges-rules-engine": "1.5.4",
16
- "@greenarmor/ges-scanner-integration": "1.5.4",
17
- "@greenarmor/ges-scoring-engine": "1.5.4",
18
- "@greenarmor/ges-web-dashboard": "1.5.4",
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.4",
63
+ "version": "1.5.5",
64
64
  "scripts": {
65
65
  "build": "tsc",
66
66
  "clean": "rm -rf dist tsconfig.tsbuildinfo",