@infograb/docker-slim-advisor 0.1.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/LICENSE +21 -0
- package/README.md +274 -0
- package/build/base-image-db-loader.d.ts +16 -0
- package/build/base-image-db-loader.js +72 -0
- package/build/base-image-db-loader.js.map +1 -0
- package/build/cli.d.ts +47 -0
- package/build/cli.js +142 -0
- package/build/cli.js.map +1 -0
- package/build/data/base-image-db-schema.d.ts +117 -0
- package/build/data/base-image-db-schema.js +108 -0
- package/build/data/base-image-db-schema.js.map +1 -0
- package/build/data/base-image-db.d.ts +21 -0
- package/build/data/base-image-db.js +190 -0
- package/build/data/base-image-db.js.map +1 -0
- package/build/exit-codes.d.ts +25 -0
- package/build/exit-codes.js +31 -0
- package/build/exit-codes.js.map +1 -0
- package/build/formatters/formatter-dispatcher.d.ts +15 -0
- package/build/formatters/formatter-dispatcher.js +27 -0
- package/build/formatters/formatter-dispatcher.js.map +1 -0
- package/build/formatters/json-formatter.d.ts +10 -0
- package/build/formatters/json-formatter.js +52 -0
- package/build/formatters/json-formatter.js.map +1 -0
- package/build/formatters/json-schema.d.ts +57 -0
- package/build/formatters/json-schema.js +13 -0
- package/build/formatters/json-schema.js.map +1 -0
- package/build/formatters/markdown-formatter.d.ts +12 -0
- package/build/formatters/markdown-formatter.js +97 -0
- package/build/formatters/markdown-formatter.js.map +1 -0
- package/build/formatters/terminal-formatter.d.ts +12 -0
- package/build/formatters/terminal-formatter.js +142 -0
- package/build/formatters/terminal-formatter.js.map +1 -0
- package/build/image-size-lookup.d.ts +35 -0
- package/build/image-size-lookup.js +187 -0
- package/build/image-size-lookup.js.map +1 -0
- package/build/multi-stage-detector.d.ts +29 -0
- package/build/multi-stage-detector.js +29 -0
- package/build/multi-stage-detector.js.map +1 -0
- package/build/output.d.ts +46 -0
- package/build/output.js +62 -0
- package/build/output.js.map +1 -0
- package/build/parser.d.ts +7 -0
- package/build/parser.js +123 -0
- package/build/parser.js.map +1 -0
- package/build/rules/alpine-swap.d.ts +10 -0
- package/build/rules/alpine-swap.js +81 -0
- package/build/rules/alpine-swap.js.map +1 -0
- package/build/rules/dockerignore-missing.d.ts +19 -0
- package/build/rules/dockerignore-missing.js +107 -0
- package/build/rules/dockerignore-missing.js.map +1 -0
- package/build/rules/index.d.ts +11 -0
- package/build/rules/index.js +22 -0
- package/build/rules/index.js.map +1 -0
- package/build/rules/package-cache-cleanup.d.ts +15 -0
- package/build/rules/package-cache-cleanup.js +89 -0
- package/build/rules/package-cache-cleanup.js.map +1 -0
- package/build/rules/run-merge.d.ts +12 -0
- package/build/rules/run-merge.js +67 -0
- package/build/rules/run-merge.js.map +1 -0
- package/build/rules/unnecessary-packages.d.ts +23 -0
- package/build/rules/unnecessary-packages.js +184 -0
- package/build/rules/unnecessary-packages.js.map +1 -0
- package/build/severity-filter.d.ts +22 -0
- package/build/severity-filter.js +31 -0
- package/build/severity-filter.js.map +1 -0
- package/build/size-estimator.d.ts +35 -0
- package/build/size-estimator.js +238 -0
- package/build/size-estimator.js.map +1 -0
- package/build/tty-detection.d.ts +20 -0
- package/build/tty-detection.js +33 -0
- package/build/tty-detection.js.map +1 -0
- package/build/types.d.ts +82 -0
- package/build/types.js +6 -0
- package/build/types.js.map +1 -0
- package/package.json +52 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json-formatter.js","sourceRoot":"","sources":["../../src/formatters/json-formatter.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAEvD,6CAA6C;AAC7C,SAAS,WAAW,CAAC,KAAa;IAChC,IAAI,KAAK,IAAI,aAAa;QAAE,OAAO,GAAG,CAAC,KAAK,GAAG,aAAa,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;IAC7E,IAAI,KAAK,IAAI,SAAS;QAAE,OAAO,GAAG,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;IACrE,IAAI,KAAK,IAAI,KAAK;QAAE,OAAO,GAAG,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;IAC7D,OAAO,GAAG,KAAK,GAAG,CAAC;AACrB,CAAC;AAED,mDAAmD;AACnD,SAAS,aAAa,CAAC,GAAmB;IACxC,OAAO;QACL,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,QAAQ,EAAE,GAAG,CAAC,QAAQ;QACtB,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,KAAK,EAAE,GAAG,CAAC,KAAK;QAChB,WAAW,EAAE,GAAG,CAAC,WAAW;QAC5B,GAAG,EAAE,GAAG,CAAC,GAAG;QACZ,qBAAqB,EAAE,GAAG,CAAC,qBAAqB;KACjD,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU,CACxB,MAAsB,EACtB,uBAAyC;IAEzC,MAAM,MAAM,GAAe;QACzB,aAAa,EAAE,mBAAmB;QAClC,cAAc,EAAE,MAAM,CAAC,cAAc;QACrC,YAAY,EAAE,MAAM,CAAC,YAAY;QACjC,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,mBAAmB,EAAE;YACnB,UAAU,EAAE,MAAM,CAAC,wBAAwB;YAC3C,aAAa,EAAE,WAAW,CAAC,MAAM,CAAC,wBAAwB,CAAC;SAC5D;QACD,kBAAkB,EAAE;YAClB,UAAU,EAAE,MAAM,CAAC,uBAAuB;YAC1C,aAAa,EAAE,WAAW,CAAC,MAAM,CAAC,uBAAuB,CAAC;SAC3D;QACD,oBAAoB,EAAE,MAAM,CAAC,oBAAoB;QACjD,aAAa,EAAE,MAAM,CAAC,eAAe,CAAC,MAAM;QAC5C,QAAQ,EAAE,uBAAuB,CAAC,GAAG,CAAC,aAAa,CAAC;KACrD,CAAC;IAEF,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AACzC,CAAC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Versioned JSON output schema for docker-slim-advisor.
|
|
3
|
+
*
|
|
4
|
+
* This module defines the wire format emitted when --format=json is used.
|
|
5
|
+
* Schema is versioned so downstream consumers (CI pipelines, GitHub Actions)
|
|
6
|
+
* can detect breaking changes.
|
|
7
|
+
*
|
|
8
|
+
* Schema version changelog:
|
|
9
|
+
* 1 — initial release
|
|
10
|
+
*/
|
|
11
|
+
/** Current schema version — bump on breaking changes to the JSON shape */
|
|
12
|
+
export declare const JSON_SCHEMA_VERSION = 1;
|
|
13
|
+
/** A single optimization finding in JSON output */
|
|
14
|
+
export interface JsonFinding {
|
|
15
|
+
/** Rule identifier, e.g. "DSA001" */
|
|
16
|
+
ruleId: string;
|
|
17
|
+
/** Severity level */
|
|
18
|
+
severity: 'HIGH' | 'MEDIUM' | 'LOW';
|
|
19
|
+
/** 1-indexed Dockerfile line where the issue was found */
|
|
20
|
+
line: number;
|
|
21
|
+
/** Short summary of the finding */
|
|
22
|
+
title: string;
|
|
23
|
+
/** Detailed explanation */
|
|
24
|
+
description: string;
|
|
25
|
+
/** Concrete Dockerfile fix suggestion */
|
|
26
|
+
fix: string;
|
|
27
|
+
/** Estimated bytes saved by applying this fix */
|
|
28
|
+
estimatedSavingsBytes: number;
|
|
29
|
+
}
|
|
30
|
+
/** Size estimate section in JSON output */
|
|
31
|
+
export interface JsonSizeEstimate {
|
|
32
|
+
/** Total estimated size in bytes */
|
|
33
|
+
totalBytes: number;
|
|
34
|
+
/** Human-readable size string, e.g. "245MB" */
|
|
35
|
+
humanReadable: string;
|
|
36
|
+
}
|
|
37
|
+
/** Top-level versioned JSON output */
|
|
38
|
+
export interface JsonOutput {
|
|
39
|
+
/** Schema version for forward compatibility */
|
|
40
|
+
schemaVersion: number;
|
|
41
|
+
/** Path to the analyzed Dockerfile */
|
|
42
|
+
dockerfilePath: string;
|
|
43
|
+
/** Whether this is a multi-stage build (already optimized) */
|
|
44
|
+
isMultiStage: boolean;
|
|
45
|
+
/** Base image detected from FROM instruction */
|
|
46
|
+
baseImage: string;
|
|
47
|
+
/** Estimated size before optimizations */
|
|
48
|
+
estimatedBeforeSize: JsonSizeEstimate;
|
|
49
|
+
/** Estimated size after applying all recommendations */
|
|
50
|
+
estimatedAfterSize: JsonSizeEstimate;
|
|
51
|
+
/** Percentage reduction from before to after */
|
|
52
|
+
sizeReductionPercent: number;
|
|
53
|
+
/** Total number of findings (before severity filtering) */
|
|
54
|
+
totalFindings: number;
|
|
55
|
+
/** Optimization findings (filtered by --severity) */
|
|
56
|
+
findings: JsonFinding[];
|
|
57
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Versioned JSON output schema for docker-slim-advisor.
|
|
3
|
+
*
|
|
4
|
+
* This module defines the wire format emitted when --format=json is used.
|
|
5
|
+
* Schema is versioned so downstream consumers (CI pipelines, GitHub Actions)
|
|
6
|
+
* can detect breaking changes.
|
|
7
|
+
*
|
|
8
|
+
* Schema version changelog:
|
|
9
|
+
* 1 — initial release
|
|
10
|
+
*/
|
|
11
|
+
/** Current schema version — bump on breaking changes to the JSON shape */
|
|
12
|
+
export const JSON_SCHEMA_VERSION = 1;
|
|
13
|
+
//# sourceMappingURL=json-schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json-schema.js","sourceRoot":"","sources":["../../src/formatters/json-schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,0EAA0E;AAC1E,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markdown formatter — renders AnalysisResult as a Markdown report
|
|
3
|
+
* with table layout for Before/After size predictions.
|
|
4
|
+
*
|
|
5
|
+
* Designed for PR comments, CI artifacts, and documentation embedding.
|
|
6
|
+
*/
|
|
7
|
+
import type { AnalysisResult, Recommendation } from '../types.js';
|
|
8
|
+
/**
|
|
9
|
+
* Format an AnalysisResult as Markdown with table layout for size predictions.
|
|
10
|
+
* Output is plain Markdown — no TTY detection or color codes needed.
|
|
11
|
+
*/
|
|
12
|
+
export declare function formatMarkdown(result: AnalysisResult, filteredRecommendations: Recommendation[]): string;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markdown formatter — renders AnalysisResult as a Markdown report
|
|
3
|
+
* with table layout for Before/After size predictions.
|
|
4
|
+
*
|
|
5
|
+
* Designed for PR comments, CI artifacts, and documentation embedding.
|
|
6
|
+
*/
|
|
7
|
+
// --- Formatting Helpers ---
|
|
8
|
+
/** Convert bytes to human-readable string (e.g. "245.3MB") */
|
|
9
|
+
function formatBytes(bytes) {
|
|
10
|
+
if (bytes >= 1_000_000_000)
|
|
11
|
+
return `${(bytes / 1_000_000_000).toFixed(1)}GB`;
|
|
12
|
+
if (bytes >= 1_000_000)
|
|
13
|
+
return `${(bytes / 1_000_000).toFixed(1)}MB`;
|
|
14
|
+
if (bytes >= 1_000)
|
|
15
|
+
return `${(bytes / 1_000).toFixed(1)}KB`;
|
|
16
|
+
return `${bytes}B`;
|
|
17
|
+
}
|
|
18
|
+
/** Severity emoji for Markdown output */
|
|
19
|
+
function severityEmoji(severity) {
|
|
20
|
+
switch (severity) {
|
|
21
|
+
case 'HIGH': return '\u{1F534}'; // 🔴
|
|
22
|
+
case 'MEDIUM': return '\u{1F7E1}'; // 🟡
|
|
23
|
+
case 'LOW': return '\u{1F535}'; // 🔵
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
// --- Public API ---
|
|
27
|
+
/**
|
|
28
|
+
* Format an AnalysisResult as Markdown with table layout for size predictions.
|
|
29
|
+
* Output is plain Markdown — no TTY detection or color codes needed.
|
|
30
|
+
*/
|
|
31
|
+
export function formatMarkdown(result, filteredRecommendations) {
|
|
32
|
+
const lines = [];
|
|
33
|
+
// --- Header ---
|
|
34
|
+
lines.push('# Docker Slim Advisor Report');
|
|
35
|
+
lines.push('');
|
|
36
|
+
lines.push(`- **File:** \`${result.dockerfilePath}\``);
|
|
37
|
+
lines.push(`- **Base Image:** \`${result.baseImage}\``);
|
|
38
|
+
lines.push('');
|
|
39
|
+
// --- Multi-stage detection ---
|
|
40
|
+
if (result.isMultiStage) {
|
|
41
|
+
lines.push('> **Multi-stage build detected** — already optimized!');
|
|
42
|
+
lines.push('');
|
|
43
|
+
return lines.join('\n');
|
|
44
|
+
}
|
|
45
|
+
// --- Size Prediction Table ---
|
|
46
|
+
lines.push('## Size Prediction');
|
|
47
|
+
lines.push('');
|
|
48
|
+
lines.push('| Metric | Size |');
|
|
49
|
+
lines.push('|--------|------|');
|
|
50
|
+
const beforeStr = formatBytes(result.estimatedBeforeSizeBytes);
|
|
51
|
+
const afterStr = formatBytes(result.estimatedAfterSizeBytes);
|
|
52
|
+
const savedBytes = result.estimatedBeforeSizeBytes - result.estimatedAfterSizeBytes;
|
|
53
|
+
const savedStr = formatBytes(Math.abs(savedBytes));
|
|
54
|
+
const pct = result.sizeReductionPercent;
|
|
55
|
+
lines.push(`| Before | ${beforeStr} |`);
|
|
56
|
+
lines.push(`| After | ${afterStr} |`);
|
|
57
|
+
lines.push(`| Savings | ${savedBytes > 0 ? `**${savedStr}** (${pct}% reduction)` : 'None'} |`);
|
|
58
|
+
lines.push('');
|
|
59
|
+
// --- Findings ---
|
|
60
|
+
if (filteredRecommendations.length === 0) {
|
|
61
|
+
lines.push('## Findings');
|
|
62
|
+
lines.push('');
|
|
63
|
+
lines.push('No optimization findings at this severity level.');
|
|
64
|
+
lines.push('');
|
|
65
|
+
return lines.join('\n');
|
|
66
|
+
}
|
|
67
|
+
lines.push(`## Findings (${filteredRecommendations.length})`);
|
|
68
|
+
lines.push('');
|
|
69
|
+
lines.push('| Severity | Line | Rule | Title | Savings |');
|
|
70
|
+
lines.push('|----------|------|------|-------|---------|');
|
|
71
|
+
for (const rec of filteredRecommendations) {
|
|
72
|
+
const emoji = severityEmoji(rec.severity);
|
|
73
|
+
const savings = rec.estimatedSavingsBytes > 0
|
|
74
|
+
? `~${formatBytes(rec.estimatedSavingsBytes)}`
|
|
75
|
+
: '-';
|
|
76
|
+
lines.push(`| ${emoji} ${rec.severity} | ${rec.line} | \`${rec.ruleId}\` | ${rec.title} | ${savings} |`);
|
|
77
|
+
}
|
|
78
|
+
lines.push('');
|
|
79
|
+
// --- Detailed Findings ---
|
|
80
|
+
lines.push('### Details');
|
|
81
|
+
lines.push('');
|
|
82
|
+
for (const rec of filteredRecommendations) {
|
|
83
|
+
const emoji = severityEmoji(rec.severity);
|
|
84
|
+
lines.push(`#### ${emoji} ${rec.title} (\`${rec.ruleId}\`) — Line ${rec.line}`);
|
|
85
|
+
lines.push('');
|
|
86
|
+
lines.push(rec.description);
|
|
87
|
+
lines.push('');
|
|
88
|
+
lines.push(`**Fix:** ${rec.fix}`);
|
|
89
|
+
if (rec.estimatedSavingsBytes > 0) {
|
|
90
|
+
lines.push('');
|
|
91
|
+
lines.push(`**Estimated savings:** ~${formatBytes(rec.estimatedSavingsBytes)}`);
|
|
92
|
+
}
|
|
93
|
+
lines.push('');
|
|
94
|
+
}
|
|
95
|
+
return lines.join('\n');
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=markdown-formatter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown-formatter.js","sourceRoot":"","sources":["../../src/formatters/markdown-formatter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal formatter — renders AnalysisResult as human-readable colored output.
|
|
3
|
+
*
|
|
4
|
+
* TTY-aware: emoji and color only when isatty()=true.
|
|
5
|
+
* Respects NO_COLOR env var per https://no-color.org/
|
|
6
|
+
*/
|
|
7
|
+
import type { AnalysisResult, Recommendation } from '../types.js';
|
|
8
|
+
/**
|
|
9
|
+
* Format an AnalysisResult as terminal-friendly text.
|
|
10
|
+
* Output goes to stdout; respects TTY and NO_COLOR.
|
|
11
|
+
*/
|
|
12
|
+
export declare function formatTerminal(result: AnalysisResult, filteredRecommendations: Recommendation[]): string;
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal formatter — renders AnalysisResult as human-readable colored output.
|
|
3
|
+
*
|
|
4
|
+
* TTY-aware: emoji and color only when isatty()=true.
|
|
5
|
+
* Respects NO_COLOR env var per https://no-color.org/
|
|
6
|
+
*/
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import { shouldUseColor, shouldUseEmoji } from '../tty-detection.js';
|
|
9
|
+
// --- Formatting Helpers ---
|
|
10
|
+
/** Convert bytes to human-readable string (e.g. "245.3MB") */
|
|
11
|
+
function formatBytes(bytes) {
|
|
12
|
+
if (bytes >= 1_000_000_000)
|
|
13
|
+
return `${(bytes / 1_000_000_000).toFixed(1)}GB`;
|
|
14
|
+
if (bytes >= 1_000_000)
|
|
15
|
+
return `${(bytes / 1_000_000).toFixed(1)}MB`;
|
|
16
|
+
if (bytes >= 1_000)
|
|
17
|
+
return `${(bytes / 1_000).toFixed(1)}KB`;
|
|
18
|
+
return `${bytes}B`;
|
|
19
|
+
}
|
|
20
|
+
/** Severity badge with optional color */
|
|
21
|
+
function severityBadge(severity, useColor) {
|
|
22
|
+
const label = `[${severity}]`;
|
|
23
|
+
if (!useColor)
|
|
24
|
+
return label;
|
|
25
|
+
switch (severity) {
|
|
26
|
+
case 'HIGH': return chalk.red.bold(label);
|
|
27
|
+
case 'MEDIUM': return chalk.yellow(label);
|
|
28
|
+
case 'LOW': return chalk.blue(label);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/** Severity icon (emoji when TTY, text fallback otherwise) */
|
|
32
|
+
function severityIcon(severity, useEmoji) {
|
|
33
|
+
if (!useEmoji)
|
|
34
|
+
return '';
|
|
35
|
+
switch (severity) {
|
|
36
|
+
case 'HIGH': return '\u{1F6A8} '; // 🚨
|
|
37
|
+
case 'MEDIUM': return '\u{26A0}\u{FE0F} '; // ⚠️
|
|
38
|
+
case 'LOW': return '\u{1F4A1} '; // 💡
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/** Build a simple horizontal bar for size comparison */
|
|
42
|
+
function sizeBar(beforeBytes, afterBytes, useColor) {
|
|
43
|
+
const barWidth = 30;
|
|
44
|
+
const ratio = beforeBytes > 0 ? afterBytes / beforeBytes : 1;
|
|
45
|
+
const afterWidth = Math.max(1, Math.round(barWidth * ratio));
|
|
46
|
+
const savedWidth = barWidth - afterWidth;
|
|
47
|
+
const afterBar = '\u{2588}'.repeat(afterWidth); // █
|
|
48
|
+
const savedBar = '\u{2591}'.repeat(savedWidth); // ░
|
|
49
|
+
if (!useColor)
|
|
50
|
+
return `[${afterBar}${savedBar}]`;
|
|
51
|
+
return `[${chalk.green(afterBar)}${chalk.gray(savedBar)}]`;
|
|
52
|
+
}
|
|
53
|
+
// --- Public API ---
|
|
54
|
+
/**
|
|
55
|
+
* Format an AnalysisResult as terminal-friendly text.
|
|
56
|
+
* Output goes to stdout; respects TTY and NO_COLOR.
|
|
57
|
+
*/
|
|
58
|
+
export function formatTerminal(result, filteredRecommendations) {
|
|
59
|
+
const useColor = shouldUseColor();
|
|
60
|
+
const useEmoji = shouldUseEmoji();
|
|
61
|
+
const lines = [];
|
|
62
|
+
// --- Header ---
|
|
63
|
+
const headerIcon = useEmoji ? '\u{1F433} ' : ''; // 🐳
|
|
64
|
+
const title = `${headerIcon}Docker Slim Advisor`;
|
|
65
|
+
lines.push(useColor ? chalk.bold.cyan(title) : title);
|
|
66
|
+
lines.push(useColor ? chalk.dim('-'.repeat(50)) : '-'.repeat(50));
|
|
67
|
+
lines.push('');
|
|
68
|
+
// --- File & Base Image ---
|
|
69
|
+
lines.push(` File: ${result.dockerfilePath}`);
|
|
70
|
+
lines.push(` Base Image: ${useColor ? chalk.white.bold(result.baseImage) : result.baseImage}`);
|
|
71
|
+
// --- Multi-stage detection ---
|
|
72
|
+
if (result.isMultiStage) {
|
|
73
|
+
const msIcon = useEmoji ? '\u{2705} ' : ''; // ✅
|
|
74
|
+
const msg = `${msIcon}Multi-stage build detected — already optimized!`;
|
|
75
|
+
lines.push('');
|
|
76
|
+
lines.push(useColor ? chalk.green(msg) : msg);
|
|
77
|
+
lines.push('');
|
|
78
|
+
return lines.join('\n');
|
|
79
|
+
}
|
|
80
|
+
lines.push('');
|
|
81
|
+
// --- Size Predictions ---
|
|
82
|
+
const sizeIcon = useEmoji ? '\u{1F4E6} ' : ''; // 📦
|
|
83
|
+
const sizeTitle = `${sizeIcon}Size Prediction`;
|
|
84
|
+
lines.push(useColor ? chalk.bold(sizeTitle) : sizeTitle);
|
|
85
|
+
lines.push('');
|
|
86
|
+
const beforeStr = formatBytes(result.estimatedBeforeSizeBytes);
|
|
87
|
+
const afterStr = formatBytes(result.estimatedAfterSizeBytes);
|
|
88
|
+
const savedBytes = result.estimatedBeforeSizeBytes - result.estimatedAfterSizeBytes;
|
|
89
|
+
const savedStr = formatBytes(Math.abs(savedBytes));
|
|
90
|
+
const beforeLabel = useColor ? chalk.red(` Before: ${beforeStr}`) : ` Before: ${beforeStr}`;
|
|
91
|
+
const afterLabel = useColor ? chalk.green(` After: ${afterStr}`) : ` After: ${afterStr}`;
|
|
92
|
+
lines.push(beforeLabel);
|
|
93
|
+
lines.push(afterLabel);
|
|
94
|
+
lines.push('');
|
|
95
|
+
// Size bar visualization
|
|
96
|
+
lines.push(` ${sizeBar(result.estimatedBeforeSizeBytes, result.estimatedAfterSizeBytes, useColor)}`);
|
|
97
|
+
lines.push('');
|
|
98
|
+
// Savings summary
|
|
99
|
+
const reductionPct = result.sizeReductionPercent;
|
|
100
|
+
const savingsIcon = useEmoji ? '\u{1F4C9} ' : ''; // 📉
|
|
101
|
+
const savingsText = savedBytes > 0
|
|
102
|
+
? `${savingsIcon}Estimated savings: ${savedStr} (${reductionPct}% reduction)`
|
|
103
|
+
: `${savingsIcon}No size reduction estimated`;
|
|
104
|
+
lines.push(useColor && savedBytes > 0
|
|
105
|
+
? chalk.green.bold(` ${savingsText}`)
|
|
106
|
+
: ` ${savingsText}`);
|
|
107
|
+
lines.push('');
|
|
108
|
+
// --- Findings ---
|
|
109
|
+
if (filteredRecommendations.length === 0) {
|
|
110
|
+
const noFindIcon = useEmoji ? '\u{2728} ' : ''; // ✨
|
|
111
|
+
const noFindMsg = `${noFindIcon}No optimization findings at this severity level.`;
|
|
112
|
+
lines.push(useColor ? chalk.green(noFindMsg) : noFindMsg);
|
|
113
|
+
lines.push('');
|
|
114
|
+
return lines.join('\n');
|
|
115
|
+
}
|
|
116
|
+
const findingsIcon = useEmoji ? '\u{1F50D} ' : ''; // 🔍
|
|
117
|
+
const findingsTitle = `${findingsIcon}Findings (${filteredRecommendations.length})`;
|
|
118
|
+
lines.push(useColor ? chalk.bold(findingsTitle) : findingsTitle);
|
|
119
|
+
lines.push('');
|
|
120
|
+
for (const rec of filteredRecommendations) {
|
|
121
|
+
const icon = severityIcon(rec.severity, useEmoji);
|
|
122
|
+
const badge = severityBadge(rec.severity, useColor);
|
|
123
|
+
const ruleTag = useColor ? chalk.dim(`(${rec.ruleId})`) : `(${rec.ruleId})`;
|
|
124
|
+
// Title line: [SEVERITY] Rule Title (DSA-001) Line 5
|
|
125
|
+
const lineRef = useColor ? chalk.dim(`Line ${rec.line}`) : `Line ${rec.line}`;
|
|
126
|
+
lines.push(` ${icon}${badge} ${rec.title} ${ruleTag} ${lineRef}`);
|
|
127
|
+
// Description
|
|
128
|
+
lines.push(` ${rec.description}`);
|
|
129
|
+
// Fix suggestion
|
|
130
|
+
const fixLabel = useColor ? chalk.yellow(' Fix:') : ' Fix:';
|
|
131
|
+
lines.push(`${fixLabel} ${rec.fix}`);
|
|
132
|
+
// Estimated savings for this finding
|
|
133
|
+
if (rec.estimatedSavingsBytes > 0) {
|
|
134
|
+
const savStr = formatBytes(rec.estimatedSavingsBytes);
|
|
135
|
+
const savLine = ` Saves ~${savStr}`;
|
|
136
|
+
lines.push(useColor ? chalk.dim(savLine) : savLine);
|
|
137
|
+
}
|
|
138
|
+
lines.push('');
|
|
139
|
+
}
|
|
140
|
+
return lines.join('\n');
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=terminal-formatter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"terminal-formatter.js","sourceRoot":"","sources":["../../src/formatters/terminal-formatter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image size lookup module with fuzzy matching.
|
|
3
|
+
*
|
|
4
|
+
* Queries base image sizes by name:tag from the static JSON DB.
|
|
5
|
+
* Implements a multi-strategy fallback chain:
|
|
6
|
+
* 1. Exact match — "node:22" hits directly
|
|
7
|
+
* 2. Scratch special case — always 0 bytes
|
|
8
|
+
* 3. Latest fallback — bare "node" resolves to "node:latest"
|
|
9
|
+
* 4. Major-version prefix — "node:22.1.0" matches "node:22"
|
|
10
|
+
* 5. Distro-variant stripping — "node:22-bookworm" matches "node:22"
|
|
11
|
+
* 6. Partial tag search — scans keys sharing the same image name
|
|
12
|
+
*/
|
|
13
|
+
import type { ImageSizeEntry, BaseImageDb } from './data/base-image-db-schema.js';
|
|
14
|
+
export interface LookupResult {
|
|
15
|
+
/** The DB key that matched (may differ from query due to fuzzy match) */
|
|
16
|
+
matchedKey: string;
|
|
17
|
+
/** Size entry from the DB */
|
|
18
|
+
entry: ImageSizeEntry;
|
|
19
|
+
/** Whether this was an exact or fuzzy match */
|
|
20
|
+
matchType: 'exact' | 'fuzzy';
|
|
21
|
+
}
|
|
22
|
+
/** Load the static JSON DB, cached after first call */
|
|
23
|
+
export declare function loadDb(): BaseImageDb;
|
|
24
|
+
/** Reset DB cache (for testing) */
|
|
25
|
+
export declare function resetDbCache(): void;
|
|
26
|
+
/**
|
|
27
|
+
* Look up image size by name and tag with fuzzy fallback chain.
|
|
28
|
+
* Returns undefined only when no reasonable match exists.
|
|
29
|
+
*/
|
|
30
|
+
export declare function lookupImageSize(image: string, tag: string, images?: Record<string, ImageSizeEntry>): LookupResult | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* Convenience wrapper matching the RuleContext.getImageSize signature.
|
|
33
|
+
* Returns compressedBytes (or undefined) for backward compatibility.
|
|
34
|
+
*/
|
|
35
|
+
export declare function getImageSizeFromLookup(image: string, tag: string, images?: Record<string, ImageSizeEntry>): number | undefined;
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image size lookup module with fuzzy matching.
|
|
3
|
+
*
|
|
4
|
+
* Queries base image sizes by name:tag from the static JSON DB.
|
|
5
|
+
* Implements a multi-strategy fallback chain:
|
|
6
|
+
* 1. Exact match — "node:22" hits directly
|
|
7
|
+
* 2. Scratch special case — always 0 bytes
|
|
8
|
+
* 3. Latest fallback — bare "node" resolves to "node:latest"
|
|
9
|
+
* 4. Major-version prefix — "node:22.1.0" matches "node:22"
|
|
10
|
+
* 5. Distro-variant stripping — "node:22-bookworm" matches "node:22"
|
|
11
|
+
* 6. Partial tag search — scans keys sharing the same image name
|
|
12
|
+
*/
|
|
13
|
+
import { readFileSync } from 'node:fs';
|
|
14
|
+
import { fileURLToPath } from 'node:url';
|
|
15
|
+
import { dirname, join } from 'node:path';
|
|
16
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
17
|
+
const __dirname = dirname(__filename);
|
|
18
|
+
// --- Known distro variants to strip during fuzzy matching ---
|
|
19
|
+
const DISTRO_VARIANTS = [
|
|
20
|
+
'bookworm', 'bullseye', 'buster', 'stretch', 'jessie', // Debian
|
|
21
|
+
'jammy', 'focal', 'bionic', 'noble', // Ubuntu
|
|
22
|
+
'el7', 'el8', 'el9', // RHEL/CentOS
|
|
23
|
+
];
|
|
24
|
+
// --- Module-level cache ---
|
|
25
|
+
let cachedDb = null;
|
|
26
|
+
/** Load the static JSON DB, cached after first call */
|
|
27
|
+
export function loadDb() {
|
|
28
|
+
if (cachedDb)
|
|
29
|
+
return cachedDb;
|
|
30
|
+
const dbPath = join(__dirname, 'data', 'base-image-db.json');
|
|
31
|
+
cachedDb = JSON.parse(readFileSync(dbPath, 'utf-8'));
|
|
32
|
+
return cachedDb;
|
|
33
|
+
}
|
|
34
|
+
/** Reset DB cache (for testing) */
|
|
35
|
+
export function resetDbCache() {
|
|
36
|
+
cachedDb = null;
|
|
37
|
+
}
|
|
38
|
+
// --- Public API ---
|
|
39
|
+
/**
|
|
40
|
+
* Look up image size by name and tag with fuzzy fallback chain.
|
|
41
|
+
* Returns undefined only when no reasonable match exists.
|
|
42
|
+
*/
|
|
43
|
+
export function lookupImageSize(image, tag, images) {
|
|
44
|
+
const db = images ?? loadDb().images;
|
|
45
|
+
// Strategy 1: Exact match
|
|
46
|
+
const exactKey = buildKey(image, tag);
|
|
47
|
+
if (db[exactKey]) {
|
|
48
|
+
return { matchedKey: exactKey, entry: db[exactKey], matchType: 'exact' };
|
|
49
|
+
}
|
|
50
|
+
// Strategy 2: Scratch — always 0 bytes
|
|
51
|
+
if (image === 'scratch') {
|
|
52
|
+
return {
|
|
53
|
+
matchedKey: 'scratch',
|
|
54
|
+
entry: { compressedBytes: 0, uncompressedBytes: 0 },
|
|
55
|
+
matchType: 'exact',
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
// Strategy 3: Latest fallback — bare image resolves to image:latest
|
|
59
|
+
if (tag === 'latest') {
|
|
60
|
+
const latestKey = `${image}:latest`;
|
|
61
|
+
if (db[latestKey]) {
|
|
62
|
+
return { matchedKey: latestKey, entry: db[latestKey], matchType: 'exact' };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// Strategy 4: Major-version prefix — "node:22.1.0" → "node:22"
|
|
66
|
+
const majorMatch = tryMajorVersionMatch(image, tag, db);
|
|
67
|
+
if (majorMatch)
|
|
68
|
+
return majorMatch;
|
|
69
|
+
// Strategy 5: Distro-variant stripping — "node:22-bookworm" → "node:22"
|
|
70
|
+
const strippedMatch = tryVariantStripping(image, tag, db);
|
|
71
|
+
if (strippedMatch)
|
|
72
|
+
return strippedMatch;
|
|
73
|
+
// Strategy 6: Partial tag search — find closest tag for same image name
|
|
74
|
+
const partialMatch = tryPartialTagSearch(image, tag, db);
|
|
75
|
+
if (partialMatch)
|
|
76
|
+
return partialMatch;
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Convenience wrapper matching the RuleContext.getImageSize signature.
|
|
81
|
+
* Returns compressedBytes (or undefined) for backward compatibility.
|
|
82
|
+
*/
|
|
83
|
+
export function getImageSizeFromLookup(image, tag, images) {
|
|
84
|
+
const result = lookupImageSize(image, tag, images);
|
|
85
|
+
return result?.entry.compressedBytes;
|
|
86
|
+
}
|
|
87
|
+
// --- Internal Strategies ---
|
|
88
|
+
/** Build a canonical "image:tag" key */
|
|
89
|
+
function buildKey(image, tag) {
|
|
90
|
+
return `${image}:${tag}`;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Strategy 4: Try matching by major version prefix.
|
|
94
|
+
* "python:3.12.4" → try "python:3.12" → try "python:3"
|
|
95
|
+
*/
|
|
96
|
+
function tryMajorVersionMatch(image, tag, db) {
|
|
97
|
+
// Extract version-like prefix before any dash suffix
|
|
98
|
+
const dashIdx = tag.indexOf('-');
|
|
99
|
+
const versionPart = dashIdx >= 0 ? tag.slice(0, dashIdx) : tag;
|
|
100
|
+
const suffix = dashIdx >= 0 ? tag.slice(dashIdx) : '';
|
|
101
|
+
// Split on dots: "3.12.4" → ["3", "12", "4"]
|
|
102
|
+
const parts = versionPart.split('.');
|
|
103
|
+
if (parts.length <= 1)
|
|
104
|
+
return undefined;
|
|
105
|
+
// Try progressively shorter version prefixes
|
|
106
|
+
// "3.12.4-slim" → "3.12-slim" → "3-slim" → "3.12.4" → "3.12" → "3"
|
|
107
|
+
const candidates = [];
|
|
108
|
+
// With suffix first (more specific)
|
|
109
|
+
if (suffix) {
|
|
110
|
+
for (let i = parts.length - 1; i >= 1; i--) {
|
|
111
|
+
candidates.push(buildKey(image, parts.slice(0, i).join('.') + suffix));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// Without suffix
|
|
115
|
+
for (let i = parts.length - 1; i >= 1; i--) {
|
|
116
|
+
candidates.push(buildKey(image, parts.slice(0, i).join('.')));
|
|
117
|
+
}
|
|
118
|
+
for (const key of candidates) {
|
|
119
|
+
if (db[key]) {
|
|
120
|
+
return { matchedKey: key, entry: db[key], matchType: 'fuzzy' };
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Strategy 5: Strip known distro variants from tag.
|
|
127
|
+
* "node:22-bookworm-slim" → try "node:22-slim" → try "node:22"
|
|
128
|
+
* "python:3.12-bullseye" → try "python:3.12"
|
|
129
|
+
*/
|
|
130
|
+
function tryVariantStripping(image, tag, db) {
|
|
131
|
+
const tagParts = tag.split('-');
|
|
132
|
+
if (tagParts.length <= 1)
|
|
133
|
+
return undefined;
|
|
134
|
+
// Find and remove distro variant segments
|
|
135
|
+
const filtered = tagParts.filter((part) => !DISTRO_VARIANTS.includes(part));
|
|
136
|
+
// Only proceed if we actually stripped something
|
|
137
|
+
if (filtered.length === tagParts.length)
|
|
138
|
+
return undefined;
|
|
139
|
+
// Try the stripped tag
|
|
140
|
+
if (filtered.length > 0) {
|
|
141
|
+
const strippedKey = buildKey(image, filtered.join('-'));
|
|
142
|
+
if (db[strippedKey]) {
|
|
143
|
+
return { matchedKey: strippedKey, entry: db[strippedKey], matchType: 'fuzzy' };
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// Try just the version part (first segment)
|
|
147
|
+
const versionOnly = buildKey(image, tagParts[0]);
|
|
148
|
+
if (db[versionOnly]) {
|
|
149
|
+
return { matchedKey: versionOnly, entry: db[versionOnly], matchType: 'fuzzy' };
|
|
150
|
+
}
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Strategy 6: Partial tag search — find the best matching tag for the same image.
|
|
155
|
+
* Scores candidates by shared tag prefix length.
|
|
156
|
+
* Only used as last resort to avoid surprising mismatches.
|
|
157
|
+
*/
|
|
158
|
+
function tryPartialTagSearch(image, tag, db) {
|
|
159
|
+
const prefix = `${image}:`;
|
|
160
|
+
let bestKey;
|
|
161
|
+
let bestScore = 0;
|
|
162
|
+
for (const key of Object.keys(db)) {
|
|
163
|
+
if (!key.startsWith(prefix))
|
|
164
|
+
continue;
|
|
165
|
+
const candidateTag = key.slice(prefix.length);
|
|
166
|
+
const score = sharedPrefixLength(tag, candidateTag);
|
|
167
|
+
// Require at least 1 char of shared prefix to avoid random matches
|
|
168
|
+
if (score > bestScore) {
|
|
169
|
+
bestScore = score;
|
|
170
|
+
bestKey = key;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
// Require reasonable overlap — at least the major version digit
|
|
174
|
+
if (bestKey && bestScore >= 1) {
|
|
175
|
+
return { matchedKey: bestKey, entry: db[bestKey], matchType: 'fuzzy' };
|
|
176
|
+
}
|
|
177
|
+
return undefined;
|
|
178
|
+
}
|
|
179
|
+
/** Count shared prefix characters between two strings */
|
|
180
|
+
function sharedPrefixLength(a, b) {
|
|
181
|
+
const len = Math.min(a.length, b.length);
|
|
182
|
+
let i = 0;
|
|
183
|
+
while (i < len && a[i] === b[i])
|
|
184
|
+
i++;
|
|
185
|
+
return i;
|
|
186
|
+
}
|
|
187
|
+
//# sourceMappingURL=image-size-lookup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-size-lookup.js","sourceRoot":"","sources":["../src/image-size-lookup.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-stage build detection - counts FROM instructions to determine
|
|
3
|
+
* if a Dockerfile uses multi-stage builds. Multi-stage Dockerfiles
|
|
4
|
+
* (2+ FROM instructions) are flagged as "already optimized" since
|
|
5
|
+
* multi-stage is itself a key size optimization technique.
|
|
6
|
+
*/
|
|
7
|
+
import type { Instruction } from './types.js';
|
|
8
|
+
/** Result of multi-stage detection analysis */
|
|
9
|
+
export interface MultiStageResult {
|
|
10
|
+
/** Whether the Dockerfile uses multi-stage builds (2+ FROM instructions) */
|
|
11
|
+
isMultiStage: boolean;
|
|
12
|
+
/** Total number of FROM instructions (build stages) */
|
|
13
|
+
stageCount: number;
|
|
14
|
+
/** The FROM instructions found, with line numbers and aliases */
|
|
15
|
+
stages: Array<{
|
|
16
|
+
image: string;
|
|
17
|
+
tag: string;
|
|
18
|
+
alias?: string;
|
|
19
|
+
line: number;
|
|
20
|
+
}>;
|
|
21
|
+
/** Human-readable message about multi-stage status */
|
|
22
|
+
message: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Detect multi-stage builds by counting FROM instructions in the parsed AST.
|
|
26
|
+
* A Dockerfile with 2+ FROM instructions is considered multi-stage and
|
|
27
|
+
* "already optimized" for image size via build stages.
|
|
28
|
+
*/
|
|
29
|
+
export declare function detectMultiStage(instructions: Instruction[]): MultiStageResult;
|