@deniscuciuc/compose-analyzer 1.0.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/CHANGELOG.md +24 -0
- package/LICENSE +21 -0
- package/README.md +164 -0
- package/analyzerrc.example.json +3 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +35 -0
- package/dist/package.json +81 -0
- package/dist/src/analyzers/image-analyzer.d.ts +8 -0
- package/dist/src/analyzers/image-analyzer.d.ts.map +1 -0
- package/dist/src/analyzers/image-analyzer.js +61 -0
- package/dist/src/analyzers/network-analyzer.d.ts +8 -0
- package/dist/src/analyzers/network-analyzer.d.ts.map +1 -0
- package/dist/src/analyzers/network-analyzer.js +48 -0
- package/dist/src/analyzers/reliability-analyzer.d.ts +8 -0
- package/dist/src/analyzers/reliability-analyzer.d.ts.map +1 -0
- package/dist/src/analyzers/reliability-analyzer.js +84 -0
- package/dist/src/analyzers/resource-analyzer.d.ts +8 -0
- package/dist/src/analyzers/resource-analyzer.d.ts.map +1 -0
- package/dist/src/analyzers/resource-analyzer.js +34 -0
- package/dist/src/analyzers/security-analyzer.d.ts +9 -0
- package/dist/src/analyzers/security-analyzer.d.ts.map +1 -0
- package/dist/src/analyzers/security-analyzer.js +82 -0
- package/dist/src/cli/options.d.ts +16 -0
- package/dist/src/cli/options.d.ts.map +1 -0
- package/dist/src/cli/options.js +129 -0
- package/dist/src/cli/runner.d.ts +6 -0
- package/dist/src/cli/runner.d.ts.map +1 -0
- package/dist/src/cli/runner.js +234 -0
- package/dist/src/collectors/compose-collector.d.ts +19 -0
- package/dist/src/collectors/compose-collector.d.ts.map +1 -0
- package/dist/src/collectors/compose-collector.js +140 -0
- package/dist/src/collectors/docker-collector.d.ts +8 -0
- package/dist/src/collectors/docker-collector.d.ts.map +1 -0
- package/dist/src/collectors/docker-collector.js +96 -0
- package/dist/src/config/loader.d.ts +3 -0
- package/dist/src/config/loader.d.ts.map +1 -0
- package/dist/src/config/loader.js +41 -0
- package/dist/src/constants.d.ts +23 -0
- package/dist/src/constants.d.ts.map +1 -0
- package/dist/src/constants.js +70 -0
- package/dist/src/health-score.d.ts +3 -0
- package/dist/src/health-score.d.ts.map +1 -0
- package/dist/src/health-score.js +14 -0
- package/dist/src/interactive/display.d.ts +11 -0
- package/dist/src/interactive/display.d.ts.map +1 -0
- package/dist/src/interactive/display.js +117 -0
- package/dist/src/interactive/index.d.ts +15 -0
- package/dist/src/interactive/index.d.ts.map +1 -0
- package/dist/src/interactive/index.js +218 -0
- package/dist/src/interactive/menus.d.ts +68 -0
- package/dist/src/interactive/menus.d.ts.map +1 -0
- package/dist/src/interactive/menus.js +32 -0
- package/dist/src/reporters/diff-reporter.d.ts +21 -0
- package/dist/src/reporters/diff-reporter.d.ts.map +1 -0
- package/dist/src/reporters/diff-reporter.js +87 -0
- package/dist/src/reporters/html-reporter.d.ts +12 -0
- package/dist/src/reporters/html-reporter.d.ts.map +1 -0
- package/dist/src/reporters/html-reporter.js +226 -0
- package/dist/src/reporters/report-generator.d.ts +23 -0
- package/dist/src/reporters/report-generator.d.ts.map +1 -0
- package/dist/src/reporters/report-generator.js +326 -0
- package/dist/src/types.d.ts +198 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +2 -0
- package/dist/src/utils/format.d.ts +6 -0
- package/dist/src/utils/format.d.ts.map +1 -0
- package/dist/src/utils/format.js +32 -0
- package/dist/src/utils/print.d.ts +8 -0
- package/dist/src/utils/print.d.ts.map +1 -0
- package/dist/src/utils/print.js +42 -0
- package/package.json +80 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.DockerCollector = void 0;
|
|
37
|
+
class DockerCollector {
|
|
38
|
+
async collect(services) {
|
|
39
|
+
try {
|
|
40
|
+
const DockerModule = await Promise.resolve().then(() => __importStar(require("dockerode")));
|
|
41
|
+
const Docker = DockerModule.default;
|
|
42
|
+
const docker = new Docker();
|
|
43
|
+
const containers = await docker.listContainers({ all: true });
|
|
44
|
+
const runtimeServices = [];
|
|
45
|
+
for (const { name } of services) {
|
|
46
|
+
const container = containers.find((entry) => {
|
|
47
|
+
const composeService = entry.Labels?.["com.docker.compose.service"];
|
|
48
|
+
if (composeService === name) {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
return (entry.Names ?? []).some((containerName) => {
|
|
52
|
+
const normalized = containerName.replace(/^\//, "");
|
|
53
|
+
return normalized === name || normalized.includes(`_${name}_`);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
if (!container) {
|
|
57
|
+
runtimeServices.push({
|
|
58
|
+
service: name,
|
|
59
|
+
state: "not-found",
|
|
60
|
+
status: "No matching container found",
|
|
61
|
+
});
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
let health;
|
|
65
|
+
try {
|
|
66
|
+
const inspect = await docker.getContainer(container.Id).inspect();
|
|
67
|
+
health = inspect.State?.Health?.Status;
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
health = undefined;
|
|
71
|
+
}
|
|
72
|
+
runtimeServices.push({
|
|
73
|
+
service: name,
|
|
74
|
+
containerName: container.Names?.[0]?.replace(/^\//, ""),
|
|
75
|
+
image: container.Image,
|
|
76
|
+
state: container.State,
|
|
77
|
+
status: container.Status,
|
|
78
|
+
health,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
available: true,
|
|
83
|
+
services: runtimeServices,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
88
|
+
return {
|
|
89
|
+
available: false,
|
|
90
|
+
error: `Docker runtime information unavailable: ${message}`,
|
|
91
|
+
services: [],
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
exports.DockerCollector = DockerCollector;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../../src/config/loader.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAM/C,wBAAgB,UAAU,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,cAAc,CAsC9D"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.loadConfig = loadConfig;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const node_os_1 = require("node:os");
|
|
6
|
+
const node_path_1 = require("node:path");
|
|
7
|
+
function parseConfig(candidate) {
|
|
8
|
+
return JSON.parse((0, node_fs_1.readFileSync)(candidate, "utf-8"));
|
|
9
|
+
}
|
|
10
|
+
function loadConfig(configPath) {
|
|
11
|
+
if (configPath) {
|
|
12
|
+
const explicitPath = (0, node_path_1.resolve)(configPath);
|
|
13
|
+
if (!(0, node_fs_1.existsSync)(explicitPath)) {
|
|
14
|
+
throw new Error(`Config file not found: ${explicitPath}`);
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
return parseConfig(explicitPath);
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
21
|
+
throw new Error(`Could not parse config file at ${explicitPath}: ${message}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const candidates = [
|
|
25
|
+
(0, node_path_1.join)(process.cwd(), ".analyzerrc.json"),
|
|
26
|
+
(0, node_path_1.join)((0, node_os_1.homedir)(), ".config", "compose-analyzer", "config.json"),
|
|
27
|
+
];
|
|
28
|
+
for (const candidate of candidates) {
|
|
29
|
+
if (!(0, node_fs_1.existsSync)(candidate)) {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
return parseConfig(candidate);
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
37
|
+
console.warn(`Warning: could not parse config file at ${candidate}: ${message}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return {};
|
|
41
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export declare const SENSITIVE_IMAGES: Set<string>;
|
|
2
|
+
export declare const SECRET_PATTERNS: RegExp[];
|
|
3
|
+
export declare const WEAK_RESTART_POLICIES: Set<string>;
|
|
4
|
+
export declare const SCORE_DEDUCTIONS: {
|
|
5
|
+
readonly critical: 15;
|
|
6
|
+
readonly high: 8;
|
|
7
|
+
readonly medium: 4;
|
|
8
|
+
readonly low: 1;
|
|
9
|
+
};
|
|
10
|
+
export declare const COMMANDS: readonly ["full", "health", "images", "security", "reliability", "resources", "networks"];
|
|
11
|
+
export declare const FULL_ANALYSIS_COMMANDS: readonly ["health", "images", "security", "reliability", "resources", "networks"];
|
|
12
|
+
export type Command = (typeof COMMANDS)[number];
|
|
13
|
+
export declare const DEFAULTS: {
|
|
14
|
+
readonly composeFile: "docker-compose.yml";
|
|
15
|
+
readonly output: "./reports";
|
|
16
|
+
};
|
|
17
|
+
export declare const SEVERITY_ORDER: {
|
|
18
|
+
readonly critical: 0;
|
|
19
|
+
readonly high: 1;
|
|
20
|
+
readonly medium: 2;
|
|
21
|
+
readonly low: 3;
|
|
22
|
+
};
|
|
23
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,gBAAgB,aAiB3B,CAAC;AAEH,eAAO,MAAM,eAAe,EAAE,MAAM,EAcnC,CAAC;AAEF,eAAO,MAAM,qBAAqB,aAA8B,CAAC;AAEjE,eAAO,MAAM,gBAAgB;;;;;CAKnB,CAAC;AAEX,eAAO,MAAM,QAAQ,2FAQX,CAAC;AAEX,eAAO,MAAM,sBAAsB,mFAOzB,CAAC;AAEX,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC;AAEhD,eAAO,MAAM,QAAQ;;;CAGX,CAAC;AAEX,eAAO,MAAM,cAAc;;;;;CAKjB,CAAC"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SEVERITY_ORDER = exports.DEFAULTS = exports.FULL_ANALYSIS_COMMANDS = exports.COMMANDS = exports.SCORE_DEDUCTIONS = exports.WEAK_RESTART_POLICIES = exports.SECRET_PATTERNS = exports.SENSITIVE_IMAGES = void 0;
|
|
4
|
+
exports.SENSITIVE_IMAGES = new Set([
|
|
5
|
+
"postgres",
|
|
6
|
+
"postgresql",
|
|
7
|
+
"mysql",
|
|
8
|
+
"mariadb",
|
|
9
|
+
"mongo",
|
|
10
|
+
"mongodb",
|
|
11
|
+
"redis",
|
|
12
|
+
"elasticsearch",
|
|
13
|
+
"opensearch",
|
|
14
|
+
"cassandra",
|
|
15
|
+
"rabbitmq",
|
|
16
|
+
"kafka",
|
|
17
|
+
"zookeeper",
|
|
18
|
+
"clickhouse",
|
|
19
|
+
"mssql",
|
|
20
|
+
"mssql-server",
|
|
21
|
+
]);
|
|
22
|
+
exports.SECRET_PATTERNS = [
|
|
23
|
+
/PASSWORD/i,
|
|
24
|
+
/PASSWD/i,
|
|
25
|
+
/SECRET/i,
|
|
26
|
+
/API_KEY/i,
|
|
27
|
+
/APIKEY/i,
|
|
28
|
+
/TOKEN/i,
|
|
29
|
+
/PRIVATE_KEY/i,
|
|
30
|
+
/PRIVATE_TOKEN/i,
|
|
31
|
+
/AUTH/i,
|
|
32
|
+
/CREDENTIALS/i,
|
|
33
|
+
/WEBHOOK/i,
|
|
34
|
+
/ACCESS_KEY/i,
|
|
35
|
+
/ACCESS_SECRET/i,
|
|
36
|
+
];
|
|
37
|
+
exports.WEAK_RESTART_POLICIES = new Set(["no", "none", ""]);
|
|
38
|
+
exports.SCORE_DEDUCTIONS = {
|
|
39
|
+
critical: 15,
|
|
40
|
+
high: 8,
|
|
41
|
+
medium: 4,
|
|
42
|
+
low: 1,
|
|
43
|
+
};
|
|
44
|
+
exports.COMMANDS = [
|
|
45
|
+
"full",
|
|
46
|
+
"health",
|
|
47
|
+
"images",
|
|
48
|
+
"security",
|
|
49
|
+
"reliability",
|
|
50
|
+
"resources",
|
|
51
|
+
"networks",
|
|
52
|
+
];
|
|
53
|
+
exports.FULL_ANALYSIS_COMMANDS = [
|
|
54
|
+
"health",
|
|
55
|
+
"images",
|
|
56
|
+
"security",
|
|
57
|
+
"reliability",
|
|
58
|
+
"resources",
|
|
59
|
+
"networks",
|
|
60
|
+
];
|
|
61
|
+
exports.DEFAULTS = {
|
|
62
|
+
composeFile: "docker-compose.yml",
|
|
63
|
+
output: "./reports",
|
|
64
|
+
};
|
|
65
|
+
exports.SEVERITY_ORDER = {
|
|
66
|
+
critical: 0,
|
|
67
|
+
high: 1,
|
|
68
|
+
medium: 2,
|
|
69
|
+
low: 3,
|
|
70
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health-score.d.ts","sourceRoot":"","sources":["../../src/health-score.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE5C,wBAAgB,kBAAkB,CACjC,MAAM,EAAE,YAAY,EAAE,EACtB,aAAa,EAAE,MAAM,GACnB,MAAM,CAWR"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.computeHealthScore = computeHealthScore;
|
|
4
|
+
const constants_1 = require("./constants");
|
|
5
|
+
function computeHealthScore(issues, totalServices) {
|
|
6
|
+
if (totalServices === 0) {
|
|
7
|
+
return 100;
|
|
8
|
+
}
|
|
9
|
+
let score = 100;
|
|
10
|
+
for (const issue of issues) {
|
|
11
|
+
score -= constants_1.SCORE_DEDUCTIONS[issue.severity];
|
|
12
|
+
}
|
|
13
|
+
return Math.max(0, score);
|
|
14
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { FullComposeReport, ImageAnalysis, NetworkAnalysis, ReliabilityAnalysis, ResourceAnalysis, SecurityAnalysis } from "../types";
|
|
2
|
+
export declare function showHealth(report: FullComposeReport): void;
|
|
3
|
+
export declare function showFullReportSummary(report: FullComposeReport): void;
|
|
4
|
+
export declare function showImages(analysis: ImageAnalysis): void;
|
|
5
|
+
export declare function showSecurity(analysis: SecurityAnalysis): void;
|
|
6
|
+
export declare function showReliability(analysis: ReliabilityAnalysis): void;
|
|
7
|
+
export declare function showResources(analysis: ResourceAnalysis): void;
|
|
8
|
+
export declare function showNetworks(analysis: NetworkAnalysis): void;
|
|
9
|
+
export declare function showCurrentSettings(composeFile: string, outputDir: string, withDocker: boolean): void;
|
|
10
|
+
export declare function showDockerRuntime(report: FullComposeReport): void;
|
|
11
|
+
//# sourceMappingURL=display.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"display.d.ts","sourceRoot":"","sources":["../../../src/interactive/display.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,iBAAiB,EACjB,aAAa,EACb,eAAe,EACf,mBAAmB,EACnB,gBAAgB,EAChB,gBAAgB,EAChB,MAAM,UAAU,CAAC;AAQlB,wBAAgB,UAAU,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAmB1D;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAcrE;AAED,wBAAgB,UAAU,CAAC,QAAQ,EAAE,aAAa,GAAG,IAAI,CAkBxD;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,gBAAgB,GAAG,IAAI,CAW7D;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,mBAAmB,GAAG,IAAI,CAWnE;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,gBAAgB,GAAG,IAAI,CAY9D;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,eAAe,GAAG,IAAI,CAW5D;AAED,wBAAgB,mBAAmB,CAClC,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,OAAO,GACjB,IAAI,CAKN;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAiBjE"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.showHealth = showHealth;
|
|
4
|
+
exports.showFullReportSummary = showFullReportSummary;
|
|
5
|
+
exports.showImages = showImages;
|
|
6
|
+
exports.showSecurity = showSecurity;
|
|
7
|
+
exports.showReliability = showReliability;
|
|
8
|
+
exports.showResources = showResources;
|
|
9
|
+
exports.showNetworks = showNetworks;
|
|
10
|
+
exports.showCurrentSettings = showCurrentSettings;
|
|
11
|
+
exports.showDockerRuntime = showDockerRuntime;
|
|
12
|
+
const print_1 = require("../utils/print");
|
|
13
|
+
function showHealth(report) {
|
|
14
|
+
(0, print_1.printSection)("Health");
|
|
15
|
+
(0, print_1.printRow)("Health score", `${report.healthScore}/100`);
|
|
16
|
+
(0, print_1.printRow)("Services", report.metrics.totalServices);
|
|
17
|
+
(0, print_1.printRow)("Issues", report.metrics.totalIssues);
|
|
18
|
+
(0, print_1.printRow)("Critical / high", `${report.metrics.criticalIssues} / ${report.metrics.highIssues}`);
|
|
19
|
+
if (report.allIssues.length === 0) {
|
|
20
|
+
(0, print_1.printBullet)("No issues detected.");
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
(0, print_1.printBullet)("Top issues:");
|
|
24
|
+
for (const issue of report.allIssues.slice(0, 5)) {
|
|
25
|
+
(0, print_1.printSubBullet)(`[${issue.severity}] ${issue.service} — ${issue.title}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function showFullReportSummary(report) {
|
|
29
|
+
showHealth(report);
|
|
30
|
+
if (report.recommendations.length > 0) {
|
|
31
|
+
(0, print_1.printSection)("Recommendations");
|
|
32
|
+
for (const recommendation of report.recommendations.slice(0, 10)) {
|
|
33
|
+
(0, print_1.printBullet)(`${recommendation.priority.toUpperCase()} ${recommendation.service}: ${recommendation.message}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (report.dockerRuntime) {
|
|
37
|
+
showDockerRuntime(report);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function showImages(analysis) {
|
|
41
|
+
(0, print_1.printSection)("Images");
|
|
42
|
+
if (analysis.unpinnedImages.length === 0 &&
|
|
43
|
+
analysis.latestTagImages.length === 0) {
|
|
44
|
+
(0, print_1.printBullet)("No image pinning issues found.");
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
for (const entry of analysis.unpinnedImages) {
|
|
48
|
+
(0, print_1.printBullet)(`Unpinned: ${entry.service} → ${entry.image}`);
|
|
49
|
+
(0, print_1.printSubBullet)(entry.recommendation);
|
|
50
|
+
}
|
|
51
|
+
for (const entry of analysis.latestTagImages) {
|
|
52
|
+
(0, print_1.printBullet)(`Latest tag: ${entry.service} → ${entry.image}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function showSecurity(analysis) {
|
|
56
|
+
(0, print_1.printSection)("Security");
|
|
57
|
+
if (analysis.issues.length === 0) {
|
|
58
|
+
(0, print_1.printBullet)("No security issues found.");
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
for (const issue of analysis.issues) {
|
|
62
|
+
(0, print_1.printBullet)(`[${issue.severity}] ${issue.service} — ${issue.title}`);
|
|
63
|
+
(0, print_1.printSubBullet)(issue.detail);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function showReliability(analysis) {
|
|
67
|
+
(0, print_1.printSection)("Reliability");
|
|
68
|
+
if (analysis.issues.length === 0) {
|
|
69
|
+
(0, print_1.printBullet)("No reliability issues found.");
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
for (const issue of analysis.issues) {
|
|
73
|
+
(0, print_1.printBullet)(`[${issue.severity}] ${issue.service} — ${issue.title}`);
|
|
74
|
+
(0, print_1.printSubBullet)(issue.detail);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function showResources(analysis) {
|
|
78
|
+
(0, print_1.printSection)("Resources");
|
|
79
|
+
if (analysis.issues.length === 0) {
|
|
80
|
+
(0, print_1.printBullet)("All services define CPU and memory limits.");
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
for (const missing of analysis.missingLimits) {
|
|
84
|
+
(0, print_1.printBullet)(`${missing.service} missing ${missing.missing.join(" and ")} limits`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function showNetworks(analysis) {
|
|
88
|
+
(0, print_1.printSection)("Networks");
|
|
89
|
+
if (analysis.issues.length === 0) {
|
|
90
|
+
(0, print_1.printBullet)("No network issues found.");
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
for (const issue of analysis.issues) {
|
|
94
|
+
(0, print_1.printBullet)(`[${issue.severity}] ${issue.service} — ${issue.title}`);
|
|
95
|
+
(0, print_1.printSubBullet)(issue.detail);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function showCurrentSettings(composeFile, outputDir, withDocker) {
|
|
99
|
+
(0, print_1.printSection)("Current Settings");
|
|
100
|
+
(0, print_1.printRow)("Compose file", composeFile);
|
|
101
|
+
(0, print_1.printRow)("Output dir", outputDir);
|
|
102
|
+
(0, print_1.printRow)("Docker runtime", withDocker ? "enabled" : "disabled");
|
|
103
|
+
}
|
|
104
|
+
function showDockerRuntime(report) {
|
|
105
|
+
if (!report.dockerRuntime) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
(0, print_1.printSection)("Docker Runtime");
|
|
109
|
+
if (!report.dockerRuntime.available) {
|
|
110
|
+
(0, print_1.printBullet)(report.dockerRuntime.error ?? "Runtime unavailable.");
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
for (const service of report.dockerRuntime.services) {
|
|
114
|
+
(0, print_1.printBullet)(`${service.service} → ${service.state ?? "unknown"}`);
|
|
115
|
+
(0, print_1.printSubBullet)(`${service.containerName ?? "no container"} | ${service.status ?? "no status"}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ParsedOptions } from "../cli/options";
|
|
2
|
+
export declare class InteractiveCLI {
|
|
3
|
+
private readonly baseOptions;
|
|
4
|
+
private composeFile;
|
|
5
|
+
private outputDir;
|
|
6
|
+
private withDocker;
|
|
7
|
+
constructor(baseOptions: ParsedOptions);
|
|
8
|
+
start(): Promise<void>;
|
|
9
|
+
private analysisMenu;
|
|
10
|
+
private reportsMenu;
|
|
11
|
+
private settingsMenu;
|
|
12
|
+
private safeBuildReport;
|
|
13
|
+
private createOptions;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/interactive/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAapD,qBAAa,cAAc;IAKd,OAAO,CAAC,QAAQ,CAAC,WAAW;IAJxC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,UAAU,CAAU;gBAEC,WAAW,EAAE,aAAa;IAMjD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YA8Bd,YAAY;YAwCZ,WAAW;YA0CX,YAAY;YAmDZ,eAAe;IAS7B,OAAO,CAAC,aAAa;CAarB"}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.InteractiveCLI = void 0;
|
|
37
|
+
const node_fs_1 = require("node:fs");
|
|
38
|
+
const prompts_1 = require("@inquirer/prompts");
|
|
39
|
+
const runner_1 = require("../cli/runner");
|
|
40
|
+
const diff_reporter_1 = require("../reporters/diff-reporter");
|
|
41
|
+
const report_generator_1 = require("../reporters/report-generator");
|
|
42
|
+
const display = __importStar(require("./display"));
|
|
43
|
+
const menus_1 = require("./menus");
|
|
44
|
+
class InteractiveCLI {
|
|
45
|
+
baseOptions;
|
|
46
|
+
composeFile;
|
|
47
|
+
outputDir;
|
|
48
|
+
withDocker;
|
|
49
|
+
constructor(baseOptions) {
|
|
50
|
+
this.baseOptions = baseOptions;
|
|
51
|
+
this.composeFile = baseOptions.composeFile;
|
|
52
|
+
this.outputDir = baseOptions.outputDir ?? "./reports";
|
|
53
|
+
this.withDocker = baseOptions.withDocker ?? false;
|
|
54
|
+
}
|
|
55
|
+
async start() {
|
|
56
|
+
console.clear();
|
|
57
|
+
console.log("\n Compose Analyzer\n");
|
|
58
|
+
let running = true;
|
|
59
|
+
while (running) {
|
|
60
|
+
const choice = await (0, prompts_1.select)({
|
|
61
|
+
message: "Main menu",
|
|
62
|
+
choices: menus_1.MAIN_MENU_CHOICES,
|
|
63
|
+
});
|
|
64
|
+
switch (choice) {
|
|
65
|
+
case "analysis":
|
|
66
|
+
await this.analysisMenu();
|
|
67
|
+
break;
|
|
68
|
+
case "reports":
|
|
69
|
+
await this.reportsMenu();
|
|
70
|
+
break;
|
|
71
|
+
case "settings":
|
|
72
|
+
await this.settingsMenu();
|
|
73
|
+
break;
|
|
74
|
+
case "exit":
|
|
75
|
+
running = false;
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
console.log("\n Goodbye!\n");
|
|
80
|
+
}
|
|
81
|
+
async analysisMenu() {
|
|
82
|
+
const choice = await (0, prompts_1.select)({
|
|
83
|
+
message: "Run analysis",
|
|
84
|
+
choices: menus_1.ANALYSIS_MENU_CHOICES,
|
|
85
|
+
});
|
|
86
|
+
if (choice === "back") {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const report = await this.safeBuildReport();
|
|
90
|
+
if (!report) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
switch (choice) {
|
|
94
|
+
case "full":
|
|
95
|
+
display.showFullReportSummary(report);
|
|
96
|
+
break;
|
|
97
|
+
case "health":
|
|
98
|
+
display.showHealth(report);
|
|
99
|
+
break;
|
|
100
|
+
case "images":
|
|
101
|
+
display.showImages(report.images);
|
|
102
|
+
break;
|
|
103
|
+
case "security":
|
|
104
|
+
display.showSecurity(report.security);
|
|
105
|
+
break;
|
|
106
|
+
case "reliability":
|
|
107
|
+
display.showReliability(report.reliability);
|
|
108
|
+
break;
|
|
109
|
+
case "resources":
|
|
110
|
+
display.showResources(report.resources);
|
|
111
|
+
break;
|
|
112
|
+
case "networks":
|
|
113
|
+
display.showNetworks(report.networks);
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async reportsMenu() {
|
|
118
|
+
const choice = await (0, prompts_1.select)({
|
|
119
|
+
message: "Generate report",
|
|
120
|
+
choices: menus_1.REPORTS_MENU_CHOICES,
|
|
121
|
+
});
|
|
122
|
+
if (choice === "back") {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const report = await this.safeBuildReport();
|
|
126
|
+
if (!report) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const reporter = new report_generator_1.ReportGenerator(this.outputDir, {
|
|
130
|
+
outputDir: this.outputDir,
|
|
131
|
+
withDocker: this.withDocker,
|
|
132
|
+
});
|
|
133
|
+
if (choice === "markdown" || choice === "html") {
|
|
134
|
+
const markdownPath = await reporter.generateFullReport(report);
|
|
135
|
+
const jsonPath = await reporter.generateJsonReport(report);
|
|
136
|
+
console.log(` ✅ Markdown: ${markdownPath}`);
|
|
137
|
+
console.log(` ✅ JSON: ${jsonPath}`);
|
|
138
|
+
if (choice === "html") {
|
|
139
|
+
const htmlPath = await reporter.generateHtmlReport(report);
|
|
140
|
+
console.log(` ✅ HTML: ${htmlPath}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (choice === "diff") {
|
|
144
|
+
const previousPath = await (0, prompts_1.input)({
|
|
145
|
+
message: "Path to previous JSON report:",
|
|
146
|
+
validate: (value) => ((0, node_fs_1.existsSync)(value) ? true : "File not found"),
|
|
147
|
+
});
|
|
148
|
+
const previous = (0, runner_1.loadPreviousReport)(previousPath);
|
|
149
|
+
diff_reporter_1.DiffReporter.print(diff_reporter_1.DiffReporter.diff(report, previous));
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
async settingsMenu() {
|
|
153
|
+
const choice = await (0, prompts_1.select)({
|
|
154
|
+
message: "Settings",
|
|
155
|
+
choices: menus_1.SETTINGS_MENU_CHOICES,
|
|
156
|
+
});
|
|
157
|
+
switch (choice) {
|
|
158
|
+
case "compose-file": {
|
|
159
|
+
const nextFile = await (0, prompts_1.input)({
|
|
160
|
+
message: "Compose file path:",
|
|
161
|
+
default: this.composeFile,
|
|
162
|
+
validate: (value) => ((0, node_fs_1.existsSync)(value) ? true : "File not found"),
|
|
163
|
+
});
|
|
164
|
+
this.composeFile = nextFile;
|
|
165
|
+
console.log(` ✅ Compose file set to ${this.composeFile}`);
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
case "output": {
|
|
169
|
+
const nextOutput = await (0, prompts_1.input)({
|
|
170
|
+
message: "Reports output directory:",
|
|
171
|
+
default: this.outputDir,
|
|
172
|
+
});
|
|
173
|
+
this.outputDir = nextOutput.trim() || this.outputDir;
|
|
174
|
+
console.log(` ✅ Output directory set to ${this.outputDir}`);
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
case "with-docker": {
|
|
178
|
+
const toggle = await (0, prompts_1.confirm)({
|
|
179
|
+
message: `Docker runtime checks are currently ${this.withDocker ? "enabled" : "disabled"}. Toggle?`,
|
|
180
|
+
default: true,
|
|
181
|
+
});
|
|
182
|
+
if (toggle) {
|
|
183
|
+
this.withDocker = !this.withDocker;
|
|
184
|
+
}
|
|
185
|
+
console.log(` ✅ Docker runtime checks ${this.withDocker ? "enabled" : "disabled"}`);
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
case "show":
|
|
189
|
+
display.showCurrentSettings(this.composeFile, this.outputDir, this.withDocker);
|
|
190
|
+
break;
|
|
191
|
+
case "back":
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
async safeBuildReport() {
|
|
196
|
+
try {
|
|
197
|
+
return await (0, runner_1.buildFullReport)(this.createOptions());
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
console.log(` ❌ Error: ${error}`);
|
|
201
|
+
return undefined;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
createOptions() {
|
|
205
|
+
return {
|
|
206
|
+
...this.baseOptions,
|
|
207
|
+
composeFile: this.composeFile,
|
|
208
|
+
outputDir: this.outputDir,
|
|
209
|
+
withDocker: this.withDocker,
|
|
210
|
+
interactive: false,
|
|
211
|
+
json: false,
|
|
212
|
+
quiet: false,
|
|
213
|
+
version: false,
|
|
214
|
+
command: "full",
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
exports.InteractiveCLI = InteractiveCLI;
|