@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.
Files changed (72) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/LICENSE +21 -0
  3. package/README.md +164 -0
  4. package/analyzerrc.example.json +3 -0
  5. package/dist/index.d.ts +2 -0
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +35 -0
  8. package/dist/package.json +81 -0
  9. package/dist/src/analyzers/image-analyzer.d.ts +8 -0
  10. package/dist/src/analyzers/image-analyzer.d.ts.map +1 -0
  11. package/dist/src/analyzers/image-analyzer.js +61 -0
  12. package/dist/src/analyzers/network-analyzer.d.ts +8 -0
  13. package/dist/src/analyzers/network-analyzer.d.ts.map +1 -0
  14. package/dist/src/analyzers/network-analyzer.js +48 -0
  15. package/dist/src/analyzers/reliability-analyzer.d.ts +8 -0
  16. package/dist/src/analyzers/reliability-analyzer.d.ts.map +1 -0
  17. package/dist/src/analyzers/reliability-analyzer.js +84 -0
  18. package/dist/src/analyzers/resource-analyzer.d.ts +8 -0
  19. package/dist/src/analyzers/resource-analyzer.d.ts.map +1 -0
  20. package/dist/src/analyzers/resource-analyzer.js +34 -0
  21. package/dist/src/analyzers/security-analyzer.d.ts +9 -0
  22. package/dist/src/analyzers/security-analyzer.d.ts.map +1 -0
  23. package/dist/src/analyzers/security-analyzer.js +82 -0
  24. package/dist/src/cli/options.d.ts +16 -0
  25. package/dist/src/cli/options.d.ts.map +1 -0
  26. package/dist/src/cli/options.js +129 -0
  27. package/dist/src/cli/runner.d.ts +6 -0
  28. package/dist/src/cli/runner.d.ts.map +1 -0
  29. package/dist/src/cli/runner.js +234 -0
  30. package/dist/src/collectors/compose-collector.d.ts +19 -0
  31. package/dist/src/collectors/compose-collector.d.ts.map +1 -0
  32. package/dist/src/collectors/compose-collector.js +140 -0
  33. package/dist/src/collectors/docker-collector.d.ts +8 -0
  34. package/dist/src/collectors/docker-collector.d.ts.map +1 -0
  35. package/dist/src/collectors/docker-collector.js +96 -0
  36. package/dist/src/config/loader.d.ts +3 -0
  37. package/dist/src/config/loader.d.ts.map +1 -0
  38. package/dist/src/config/loader.js +41 -0
  39. package/dist/src/constants.d.ts +23 -0
  40. package/dist/src/constants.d.ts.map +1 -0
  41. package/dist/src/constants.js +70 -0
  42. package/dist/src/health-score.d.ts +3 -0
  43. package/dist/src/health-score.d.ts.map +1 -0
  44. package/dist/src/health-score.js +14 -0
  45. package/dist/src/interactive/display.d.ts +11 -0
  46. package/dist/src/interactive/display.d.ts.map +1 -0
  47. package/dist/src/interactive/display.js +117 -0
  48. package/dist/src/interactive/index.d.ts +15 -0
  49. package/dist/src/interactive/index.d.ts.map +1 -0
  50. package/dist/src/interactive/index.js +218 -0
  51. package/dist/src/interactive/menus.d.ts +68 -0
  52. package/dist/src/interactive/menus.d.ts.map +1 -0
  53. package/dist/src/interactive/menus.js +32 -0
  54. package/dist/src/reporters/diff-reporter.d.ts +21 -0
  55. package/dist/src/reporters/diff-reporter.d.ts.map +1 -0
  56. package/dist/src/reporters/diff-reporter.js +87 -0
  57. package/dist/src/reporters/html-reporter.d.ts +12 -0
  58. package/dist/src/reporters/html-reporter.d.ts.map +1 -0
  59. package/dist/src/reporters/html-reporter.js +226 -0
  60. package/dist/src/reporters/report-generator.d.ts +23 -0
  61. package/dist/src/reporters/report-generator.d.ts.map +1 -0
  62. package/dist/src/reporters/report-generator.js +326 -0
  63. package/dist/src/types.d.ts +198 -0
  64. package/dist/src/types.d.ts.map +1 -0
  65. package/dist/src/types.js +2 -0
  66. package/dist/src/utils/format.d.ts +6 -0
  67. package/dist/src/utils/format.d.ts.map +1 -0
  68. package/dist/src/utils/format.js +32 -0
  69. package/dist/src/utils/print.d.ts +8 -0
  70. package/dist/src/utils/print.d.ts.map +1 -0
  71. package/dist/src/utils/print.js +42 -0
  72. package/package.json +80 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,24 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [1.0.0] - 2026-06-14
11
+
12
+ ### Added
13
+
14
+ - Docker Compose analyzer CLI with full, health, image, security, reliability, resource, and network commands
15
+ - YAML parsing for Compose v2, v3, and versionless files
16
+ - Image analysis for unpinned images and `:latest` tags
17
+ - Security analysis for secrets in environment variables, privileged containers, and exposed database ports
18
+ - Reliability analysis for healthchecks, `depends_on` conditions, and restart policies
19
+ - Resource analysis for missing CPU and memory limits
20
+ - Network analysis for default-network usage and random host port publishing
21
+ - Health score (0–100), Markdown/JSON/HTML reports, and report diffing
22
+ - Optional Docker daemon runtime enrichment with `--with-docker`
23
+ - Interactive CLI flows for analysis, reports, and settings
24
+ - Node built-in tests, GitHub Actions CI workflow, publish workflow, and OSS project metadata
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Denis Cuciuc
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,164 @@
1
+ # Docker Compose Analyzer
2
+
3
+ [![Node.js 20+](https://img.shields.io/badge/node-20%2B-339933?logo=node.js)](https://nodejs.org/)
4
+ [![npm version](https://img.shields.io/npm/v/@deniscuciuc/compose-analyzer?logo=npm&color=cb3837)](https://www.npmjs.com/package/@deniscuciuc/compose-analyzer)
5
+ [![npm downloads](https://img.shields.io/npm/dm/@deniscuciuc/compose-analyzer)](https://www.npmjs.com/package/@deniscuciuc/compose-analyzer)
6
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)
7
+ [![TypeScript](https://img.shields.io/badge/types-TypeScript-3178C6?logo=typescript)](https://www.typescriptlang.org/)
8
+ [![CI](https://github.com/deniscuciuc/compose-analyzer/actions/workflows/ci.yml/badge.svg)](https://github.com/deniscuciuc/compose-analyzer/actions/workflows/ci.yml)
9
+
10
+ A CLI that analyzes Docker Compose files for image hygiene, security risk, reliability gaps, resource limits, and network exposure. It emits JSON to stdout with `-j`, or writes Markdown/JSON/HTML reports to `./reports`.
11
+
12
+ ## Quick start
13
+
14
+ No installation required:
15
+
16
+ ```bash
17
+ npx @deniscuciuc/compose-analyzer -f docker-compose.yml -c health
18
+ npx @deniscuciuc/compose-analyzer -f docker-compose.yml -c full --html
19
+ ```
20
+
21
+ Or install globally:
22
+
23
+ ```bash
24
+ npm install -g @deniscuciuc/compose-analyzer
25
+ compose-analyzer -f docker-compose.yml -c security
26
+ ```
27
+
28
+ ---
29
+
30
+ ## Features
31
+
32
+ - Unpinned image and `:latest` detection
33
+ - Plain-text secret detection in Compose environment variables
34
+ - Privileged container and exposed database port checks
35
+ - Missing healthcheck, weak `depends_on`, and restart policy analysis
36
+ - Missing CPU / memory limit detection for both legacy and deploy-based formats
37
+ - Default-network and random host-port publishing checks
38
+ - Markdown, JSON, HTML, and diffable report output
39
+ - Optional Docker runtime enrichment via `--with-docker`
40
+ - Interactive CLI mode for browsing analysis and report flows
41
+
42
+ ## Requirements
43
+
44
+ - Node.js >= 20
45
+ - pnpm >= 10
46
+ - Any Docker Compose YAML file (`docker-compose.yml`, `compose.yml`, etc.)
47
+ - Optional: Docker socket access for `--with-docker`
48
+
49
+ ## Usage
50
+
51
+ ### Interactive mode
52
+
53
+ ```bash
54
+ pnpm start -- --file docker-compose.yml
55
+ ```
56
+
57
+ ### npm scripts
58
+
59
+ ```bash
60
+ pnpm analyze -- --file docker-compose.yml
61
+ pnpm analyze:health -- --file docker-compose.yml
62
+ pnpm analyze:security -- --file docker-compose.yml
63
+ pnpm analyze:html -- --file docker-compose.yml
64
+ pnpm test
65
+ ```
66
+
67
+ ### Direct CLI
68
+
69
+ ```bash
70
+ node -r ts-node/register index.ts -f docker-compose.yml -j -c full
71
+ node -r ts-node/register index.ts -f docker-compose.yml -c images
72
+ node -r ts-node/register index.ts -f docker-compose.yml --with-docker -c full --html
73
+ ```
74
+
75
+ ## Commands
76
+
77
+ | Command | Description |
78
+ | --- | --- |
79
+ | `full` | Complete analysis and report generation (default) |
80
+ | `health` | Health score and issue summary |
81
+ | `images` | Image pinning and tag analysis |
82
+ | `security` | Secrets in env, privileged containers, and exposed DB ports |
83
+ | `reliability` | Healthchecks, `depends_on`, and restart policy analysis |
84
+ | `resources` | CPU and memory limit analysis |
85
+ | `networks` | Default network usage and risky port publishing |
86
+
87
+ ## CLI options
88
+
89
+ | Option | Short | Description | Default |
90
+ | --- | --- | --- | --- |
91
+ | `--file` | `-f` | Path to Compose file | `docker-compose.yml` |
92
+ | `--with-docker` | | Enrich the report with Docker daemon runtime status | `false` |
93
+ | `--compare` | | Previous JSON report to diff against | — |
94
+ | `--html` | | Also generate HTML for `full` | `false` |
95
+ | `--command` | `-c` | Command to run | `full` |
96
+ | `--json` | `-j` | Print JSON to stdout | `false` |
97
+ | `--output` | `-o` | Reports directory | `./reports` |
98
+ | `--interactive` | `-i` | Interactive menu | `false` |
99
+ | `--config` | | Path to a config file (non-connection settings only) | auto-search |
100
+
101
+ > This tool intentionally has **no** watch mode and **no** connection/profile handling. It analyzes static files only.
102
+
103
+ ## Configuration
104
+
105
+ Copy `analyzerrc.example.json` to `.analyzerrc.json` if you want a default reports directory:
106
+
107
+ ```bash
108
+ cp analyzerrc.example.json .analyzerrc.json
109
+ ```
110
+
111
+ Example:
112
+
113
+ ```json
114
+ {
115
+ "output": "./reports"
116
+ }
117
+ ```
118
+
119
+ ## Output formats
120
+
121
+ - **JSON** to stdout with `-j`
122
+ - **Markdown** report on `full`
123
+ - **HTML** report on `full --html`
124
+ - **Diff** summary with `--compare previous-report.json`
125
+
126
+ ## Health score
127
+
128
+ Health starts at `100` and deductions are applied per issue severity:
129
+
130
+ - `critical`: -15
131
+ - `high`: -8
132
+ - `medium`: -4
133
+ - `low`: -1
134
+
135
+ Interpretation:
136
+
137
+ | Score | Status |
138
+ | --- | --- |
139
+ | `90–100` | Excellent |
140
+ | `70–89` | Good |
141
+ | `50–69` | Warning |
142
+ | `0–49` | Critical |
143
+
144
+ ## Architecture
145
+
146
+ ```text
147
+ index.ts
148
+ src/cli/{options,runner}.ts
149
+ src/config/loader.ts
150
+ src/collectors/{compose-collector,docker-collector}.ts
151
+ src/analyzers/*.ts
152
+ src/reporters/{report-generator,html-reporter,diff-reporter}.ts
153
+ src/interactive/{index,menus,display}.ts
154
+ src/utils/{format,print}.ts
155
+ ```
156
+
157
+ ## Development
158
+
159
+ ```bash
160
+ pnpm install
161
+ pnpm lint
162
+ pnpm build
163
+ pnpm test
164
+ ```
@@ -0,0 +1,3 @@
1
+ {
2
+ "output": "./reports"
3
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const package_json_1 = __importDefault(require("./package.json"));
8
+ const options_1 = require("./src/cli/options");
9
+ const runner_1 = require("./src/cli/runner");
10
+ const loader_1 = require("./src/config/loader");
11
+ const constants_1 = require("./src/constants");
12
+ const interactive_1 = require("./src/interactive");
13
+ async function main() {
14
+ const options = (0, options_1.parseOptions)();
15
+ if (options.version) {
16
+ console.log(package_json_1.default.version);
17
+ return;
18
+ }
19
+ const config = (0, loader_1.loadConfig)(options.config);
20
+ const runtimeOptions = {
21
+ ...options,
22
+ outputDir: options.outputDir ?? config.output ?? constants_1.DEFAULTS.output,
23
+ };
24
+ if (runtimeOptions.interactive) {
25
+ const cli = new interactive_1.InteractiveCLI(runtimeOptions);
26
+ await cli.start();
27
+ return;
28
+ }
29
+ await (0, runner_1.executeCommand)(runtimeOptions);
30
+ }
31
+ main().catch((error) => {
32
+ const message = error instanceof Error ? error.message : String(error);
33
+ console.error("Error during analysis:", message);
34
+ process.exit(1);
35
+ });
@@ -0,0 +1,81 @@
1
+ {
2
+ "name": "@deniscuciuc/compose-analyzer",
3
+ "version": "1.0.0",
4
+ "description": "CLI tool that analyzes Docker Compose files for image hygiene, security, reliability, resource limits, and network exposure.",
5
+ "keywords": [
6
+ "docker",
7
+ "docker-compose",
8
+ "compose",
9
+ "analyzer",
10
+ "security",
11
+ "devops",
12
+ "cli",
13
+ "containers"
14
+ ],
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/deniscuciuc/compose-analyzer.git"
18
+ },
19
+ "bugs": {
20
+ "url": "https://github.com/deniscuciuc/compose-analyzer/issues"
21
+ },
22
+ "license": "MIT",
23
+ "author": "Denis Cuciuc <denis@deniscuciuc.dev>",
24
+ "homepage": "https://github.com/deniscuciuc/compose-analyzer#readme",
25
+ "private": false,
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
29
+ "bin": {
30
+ "compose-analyzer": "./dist/index.js"
31
+ },
32
+ "main": "./dist/index.js",
33
+ "types": "./dist/index.d.ts",
34
+ "files": [
35
+ "dist/",
36
+ "README.md",
37
+ "LICENSE",
38
+ "CHANGELOG.md",
39
+ "analyzerrc.example.json"
40
+ ],
41
+ "scripts": {
42
+ "start": "node -r ts-node/register index.ts start",
43
+ "analyze": "node -r ts-node/register index.ts",
44
+ "analyze:help": "node -r ts-node/register index.ts --help",
45
+ "analyze:health": "node -r ts-node/register index.ts -j -c health",
46
+ "analyze:images": "node -r ts-node/register index.ts -j -c images",
47
+ "analyze:security": "node -r ts-node/register index.ts -j -c security",
48
+ "analyze:reliability": "node -r ts-node/register index.ts -j -c reliability",
49
+ "analyze:resources": "node -r ts-node/register index.ts -j -c resources",
50
+ "analyze:networks": "node -r ts-node/register index.ts -j -c networks",
51
+ "analyze:html": "node -r ts-node/register index.ts --html -c full",
52
+ "build": "tsc",
53
+ "postbuild": "node scripts/add-shebang.js",
54
+ "lint": "biome check .",
55
+ "lint:fix": "biome check --write .",
56
+ "test": "node -r ts-node/register --test test/*.test.ts",
57
+ "prepublishOnly": "pnpm lint && pnpm build && pnpm test",
58
+ "release:patch": "npm version patch && git push && git push --tags",
59
+ "release:minor": "npm version minor && git push && git push --tags",
60
+ "release:major": "npm version major && git push && git push --tags"
61
+ },
62
+ "dependencies": {
63
+ "@inquirer/prompts": "^8.1.0",
64
+ "js-yaml": "^4.1.0"
65
+ },
66
+ "optionalDependencies": {
67
+ "dockerode": "^4.0.9"
68
+ },
69
+ "devDependencies": {
70
+ "@biomejs/biome": "2.3.11",
71
+ "@types/dockerode": "^3.3.38",
72
+ "@types/js-yaml": "^4.0.9",
73
+ "@types/node": "^22.15.0",
74
+ "ts-node": "^10.9.2",
75
+ "typescript": "^5.9.3"
76
+ },
77
+ "engines": {
78
+ "node": ">=20",
79
+ "pnpm": ">=10"
80
+ }
81
+ }
@@ -0,0 +1,8 @@
1
+ import type { ComposeService, ImageAnalysis } from "../types";
2
+ export declare class ImageAnalyzer {
3
+ analyze(services: Array<{
4
+ name: string;
5
+ service: ComposeService;
6
+ }>): ImageAnalysis;
7
+ }
8
+ //# sourceMappingURL=image-analyzer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-analyzer.d.ts","sourceRoot":"","sources":["../../../src/analyzers/image-analyzer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAgB,cAAc,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE5E,qBAAa,aAAa;IACzB,OAAO,CACN,QAAQ,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,cAAc,CAAA;KAAE,CAAC,GACxD,aAAa;CA6DhB"}
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ImageAnalyzer = void 0;
4
+ const compose_collector_1 = require("../collectors/compose-collector");
5
+ class ImageAnalyzer {
6
+ analyze(services) {
7
+ const unpinnedImages = [];
8
+ const latestTagImages = [];
9
+ const issues = [];
10
+ for (const { name, service } of services) {
11
+ if (!service.image) {
12
+ if (service.build) {
13
+ continue;
14
+ }
15
+ issues.push({
16
+ service: name,
17
+ severity: "high",
18
+ category: "image",
19
+ title: "No image or build context",
20
+ detail: `Service "${name}" has neither an image nor a build context.`,
21
+ fix: "Add an image reference or a build.context definition.",
22
+ });
23
+ continue;
24
+ }
25
+ if (compose_collector_1.ComposeCollector.hasDigest(service.image)) {
26
+ continue;
27
+ }
28
+ const tag = compose_collector_1.ComposeCollector.imageTag(service.image);
29
+ const base = compose_collector_1.ComposeCollector.baseImageName(service.image);
30
+ if (!tag) {
31
+ unpinnedImages.push({
32
+ service: name,
33
+ image: service.image,
34
+ recommendation: `Pin ${service.image} to a stable version such as ${base}:<version>.`,
35
+ });
36
+ issues.push({
37
+ service: name,
38
+ severity: "medium",
39
+ category: "image",
40
+ title: "Image not version-pinned",
41
+ detail: `${service.image} has no explicit tag or digest, which hurts reproducibility.`,
42
+ fix: `Change to image: ${service.image}:<version> or pin by digest.`,
43
+ });
44
+ continue;
45
+ }
46
+ if (tag === "latest") {
47
+ latestTagImages.push({ service: name, image: service.image });
48
+ issues.push({
49
+ service: name,
50
+ severity: "medium",
51
+ category: "image",
52
+ title: "Image uses :latest tag",
53
+ detail: `${service.image} can change between deploys without notice.`,
54
+ fix: `Pin to a specific version tag such as ${base}:<version>.`,
55
+ });
56
+ }
57
+ }
58
+ return { unpinnedImages, latestTagImages, issues };
59
+ }
60
+ }
61
+ exports.ImageAnalyzer = ImageAnalyzer;
@@ -0,0 +1,8 @@
1
+ import type { ComposeService, NetworkAnalysis } from "../types";
2
+ export declare class NetworkAnalyzer {
3
+ analyze(services: Array<{
4
+ name: string;
5
+ service: ComposeService;
6
+ }>, fileNetworks: Record<string, unknown> | undefined): NetworkAnalysis;
7
+ }
8
+ //# sourceMappingURL=network-analyzer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"network-analyzer.d.ts","sourceRoot":"","sources":["../../../src/analyzers/network-analyzer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAgB,cAAc,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAE9E,qBAAa,eAAe;IAC3B,OAAO,CACN,QAAQ,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,cAAc,CAAA;KAAE,CAAC,EAC1D,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GAC/C,eAAe;CAkDlB"}
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NetworkAnalyzer = void 0;
4
+ const compose_collector_1 = require("../collectors/compose-collector");
5
+ class NetworkAnalyzer {
6
+ analyze(services, fileNetworks) {
7
+ const servicesOnDefaultNetwork = [];
8
+ const exposedInternalPorts = [];
9
+ const issues = [];
10
+ const hasTopLevelNetworks = Boolean(fileNetworks && Object.keys(fileNetworks).length > 0);
11
+ for (const { name, service } of services) {
12
+ const hasExplicitNetwork = Array.isArray(service.networks)
13
+ ? service.networks.length > 0
14
+ : Boolean(service.networks && Object.keys(service.networks).length > 0);
15
+ if (!hasExplicitNetwork && hasTopLevelNetworks) {
16
+ servicesOnDefaultNetwork.push({ service: name });
17
+ issues.push({
18
+ service: name,
19
+ severity: "low",
20
+ category: "network",
21
+ title: "Service uses default network",
22
+ detail: `Service "${name}" is not attached to an explicit top-level network.`,
23
+ fix: "Assign the service to a named network so network intent is explicit.",
24
+ });
25
+ }
26
+ for (const port of compose_collector_1.ComposeCollector.normalizePorts(service)) {
27
+ if (!port.randomHostBinding) {
28
+ continue;
29
+ }
30
+ exposedInternalPorts.push({
31
+ service: name,
32
+ port: port.raw,
33
+ note: "Published without an explicit host port or host IP binding.",
34
+ });
35
+ issues.push({
36
+ service: name,
37
+ severity: "low",
38
+ category: "network",
39
+ title: "Published port uses random host binding",
40
+ detail: `Service "${name}" publishes ${port.raw}, which lets Docker choose a host port on all interfaces.`,
41
+ fix: "Prefer explicit mappings such as 127.0.0.1:8080:80, or remove the published port for internal-only services.",
42
+ });
43
+ }
44
+ }
45
+ return { servicesOnDefaultNetwork, exposedInternalPorts, issues };
46
+ }
47
+ }
48
+ exports.NetworkAnalyzer = NetworkAnalyzer;
@@ -0,0 +1,8 @@
1
+ import type { ComposeService, ReliabilityAnalysis } from "../types";
2
+ export declare class ReliabilityAnalyzer {
3
+ analyze(services: Array<{
4
+ name: string;
5
+ service: ComposeService;
6
+ }>): ReliabilityAnalysis;
7
+ }
8
+ //# sourceMappingURL=reliability-analyzer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reliability-analyzer.d.ts","sourceRoot":"","sources":["../../../src/analyzers/reliability-analyzer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEX,cAAc,EACd,mBAAmB,EACnB,MAAM,UAAU,CAAC;AAElB,qBAAa,mBAAmB;IAC/B,OAAO,CACN,QAAQ,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,cAAc,CAAA;KAAE,CAAC,GACxD,mBAAmB;CAyFtB"}
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ReliabilityAnalyzer = void 0;
4
+ const constants_1 = require("../constants");
5
+ class ReliabilityAnalyzer {
6
+ analyze(services) {
7
+ const missingHealthcheck = [];
8
+ const unsafeDepends = [];
9
+ const missingRestart = [];
10
+ const insecureRestartPolicy = [];
11
+ const issues = [];
12
+ const servicesWithHealthcheck = new Set(services
13
+ .filter(({ service }) => service.healthcheck && !service.healthcheck.disable)
14
+ .map(({ name }) => name));
15
+ for (const { name, service } of services) {
16
+ if (!service.healthcheck || service.healthcheck.disable) {
17
+ missingHealthcheck.push({ service: name });
18
+ issues.push({
19
+ service: name,
20
+ severity: "medium",
21
+ category: "reliability",
22
+ title: "No healthcheck defined",
23
+ detail: `Service "${name}" does not define a usable healthcheck.`,
24
+ fix: "Add healthcheck.test, interval, timeout, and retries so readiness can be verified.",
25
+ });
26
+ }
27
+ if (service.depends_on) {
28
+ const dependencies = Array.isArray(service.depends_on)
29
+ ? service.depends_on.map((dependency) => ({
30
+ dependency,
31
+ condition: "service_started",
32
+ }))
33
+ : Object.entries(service.depends_on).map(([dependency, details]) => ({
34
+ dependency,
35
+ condition: details.condition ?? "service_started",
36
+ }));
37
+ for (const { dependency, condition } of dependencies) {
38
+ if (condition !== "service_healthy" &&
39
+ servicesWithHealthcheck.has(dependency)) {
40
+ unsafeDepends.push({ service: name, dependency, condition });
41
+ issues.push({
42
+ service: name,
43
+ severity: "high",
44
+ category: "reliability",
45
+ title: `depends_on "${dependency}" uses ${condition}`,
46
+ detail: `Service "${name}" may start before "${dependency}" is actually ready.`,
47
+ fix: `Use depends_on.${dependency}.condition: service_healthy.`,
48
+ });
49
+ }
50
+ }
51
+ }
52
+ if (!service.restart) {
53
+ missingRestart.push({ service: name });
54
+ issues.push({
55
+ service: name,
56
+ severity: "low",
57
+ category: "reliability",
58
+ title: "No restart policy",
59
+ detail: `Service "${name}" has no restart policy and will not recover automatically after a crash.`,
60
+ fix: "Set restart: unless-stopped or restart: on-failure.",
61
+ });
62
+ }
63
+ else if (constants_1.WEAK_RESTART_POLICIES.has(service.restart)) {
64
+ insecureRestartPolicy.push({ service: name, policy: service.restart });
65
+ issues.push({
66
+ service: name,
67
+ severity: "low",
68
+ category: "reliability",
69
+ title: `Weak restart policy: ${service.restart}`,
70
+ detail: `Service "${name}" uses restart: ${service.restart}, which provides poor crash recovery.`,
71
+ fix: "Use restart: unless-stopped or restart: on-failure.",
72
+ });
73
+ }
74
+ }
75
+ return {
76
+ missingHealthcheck,
77
+ unsafeDepends,
78
+ missingRestart,
79
+ insecureRestartPolicy,
80
+ issues,
81
+ };
82
+ }
83
+ }
84
+ exports.ReliabilityAnalyzer = ReliabilityAnalyzer;
@@ -0,0 +1,8 @@
1
+ import type { ComposeService, ResourceAnalysis } from "../types";
2
+ export declare class ResourceAnalyzer {
3
+ analyze(services: Array<{
4
+ name: string;
5
+ service: ComposeService;
6
+ }>): ResourceAnalysis;
7
+ }
8
+ //# sourceMappingURL=resource-analyzer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resource-analyzer.d.ts","sourceRoot":"","sources":["../../../src/analyzers/resource-analyzer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgB,cAAc,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAE/E,qBAAa,gBAAgB;IAC5B,OAAO,CACN,QAAQ,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,cAAc,CAAA;KAAE,CAAC,GACxD,gBAAgB;CAmCnB"}
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ResourceAnalyzer = void 0;
4
+ class ResourceAnalyzer {
5
+ analyze(services) {
6
+ const missingLimits = [];
7
+ const issues = [];
8
+ for (const { name, service } of services) {
9
+ const cpuLimit = service.deploy?.resources?.limits?.cpus ?? service.cpus;
10
+ const memoryLimit = service.deploy?.resources?.limits?.memory ?? service.mem_limit;
11
+ const missing = [];
12
+ if (cpuLimit === undefined || cpuLimit === "") {
13
+ missing.push("cpu");
14
+ }
15
+ if (!memoryLimit) {
16
+ missing.push("memory");
17
+ }
18
+ if (missing.length === 0) {
19
+ continue;
20
+ }
21
+ missingLimits.push({ service: name, missing });
22
+ issues.push({
23
+ service: name,
24
+ severity: "medium",
25
+ category: "resources",
26
+ title: "Missing resource limits",
27
+ detail: `Service "${name}" is missing ${missing.join(" and ")} limits. Unbounded containers can starve neighboring workloads.`,
28
+ fix: "Add deploy.resources.limits for both memory and cpus, or set mem_limit and cpus in legacy Compose syntax.",
29
+ });
30
+ }
31
+ return { missingLimits, issues };
32
+ }
33
+ }
34
+ exports.ResourceAnalyzer = ResourceAnalyzer;
@@ -0,0 +1,9 @@
1
+ import type { ComposeService, SecurityAnalysis } from "../types";
2
+ export declare class SecurityAnalyzer {
3
+ analyze(services: Array<{
4
+ name: string;
5
+ service: ComposeService;
6
+ }>): SecurityAnalysis;
7
+ private isInterpolatedValue;
8
+ }
9
+ //# sourceMappingURL=security-analyzer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"security-analyzer.d.ts","sourceRoot":"","sources":["../../../src/analyzers/security-analyzer.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAgB,cAAc,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAE/E,qBAAa,gBAAgB;IAC5B,OAAO,CACN,QAAQ,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,cAAc,CAAA;KAAE,CAAC,GACxD,gBAAgB;IAoFnB,OAAO,CAAC,mBAAmB;CAG3B"}