@docker-doctor/cli 0.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/dist/cli.cjs +372 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +2 -0
- package/dist/cli.d.mts +2 -0
- package/dist/cli.mjs +368 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/index.cjs +20 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +99 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +99 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +9 -0
- package/dist/index.mjs.map +1 -0
- package/dist/src-D3U-LUsr.cjs +983 -0
- package/dist/src-D3U-LUsr.cjs.map +1 -0
- package/dist/src-IXht-l0J.mjs +881 -0
- package/dist/src-IXht-l0J.mjs.map +1 -0
- package/package.json +72 -0
package/dist/cli.cjs
ADDED
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const require_src = require('./src-D3U-LUsr.cjs');
|
|
3
|
+
let node_fs_promises = require("node:fs/promises");
|
|
4
|
+
node_fs_promises = require_src.__toESM(node_fs_promises, 1);
|
|
5
|
+
let node_path = require("node:path");
|
|
6
|
+
node_path = require_src.__toESM(node_path, 1);
|
|
7
|
+
let node_os = require("node:os");
|
|
8
|
+
node_os = require_src.__toESM(node_os, 1);
|
|
9
|
+
let node_process = require("node:process");
|
|
10
|
+
let node_readline_promises = require("node:readline/promises");
|
|
11
|
+
node_readline_promises = require_src.__toESM(node_readline_promises, 1);
|
|
12
|
+
let node_timers_promises = require("node:timers/promises");
|
|
13
|
+
let chalk = require("chalk");
|
|
14
|
+
chalk = require_src.__toESM(chalk, 1);
|
|
15
|
+
let commander = require("commander");
|
|
16
|
+
|
|
17
|
+
//#region src/formatters/terminal.ts
|
|
18
|
+
const printCodeFrame = (content, line, severityColor) => {
|
|
19
|
+
if (!content || !line) return;
|
|
20
|
+
const lines = content.split(/\r?\n/u);
|
|
21
|
+
const start = Math.max(1, line - 1);
|
|
22
|
+
const end = Math.min(lines.length, line + 1);
|
|
23
|
+
for (let i = start; i <= end; i += 1) {
|
|
24
|
+
const rawLine = lines[i - 1];
|
|
25
|
+
const isTarget = i === line;
|
|
26
|
+
const lineNumberStr = String(i).padStart(5, " ");
|
|
27
|
+
if (isTarget) console.log(` ${severityColor(">")} ${chalk.default.bold(lineNumberStr)} │ ${chalk.default.white(rawLine)}`);
|
|
28
|
+
else console.log(` ${chalk.default.dim(lineNumberStr)} │ ${chalk.default.dim(rawLine)}`);
|
|
29
|
+
}
|
|
30
|
+
console.log();
|
|
31
|
+
};
|
|
32
|
+
const printDiscoveredFiles = (project) => {
|
|
33
|
+
console.log(`\nDiscovered Files:`);
|
|
34
|
+
console.log(` Dockerfile(s): ${project.dockerfiles.length ? project.dockerfiles.map((f) => chalk.default.cyan(f)).join(", ") : chalk.default.dim("None")}`);
|
|
35
|
+
console.log(` Compose file(s): ${project.composeFiles.length ? project.composeFiles.map((f) => chalk.default.cyan(f)).join(", ") : chalk.default.dim("None")}`);
|
|
36
|
+
};
|
|
37
|
+
const printDiagnostics = (diagnostics, verbose, fileContents, categoryIssueCounts) => {
|
|
38
|
+
if (diagnostics.length === 0) {
|
|
39
|
+
console.log(`\n${chalk.default.green.bold("✔ No issues found! Your Docker setup looks healthy.")}`);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (!verbose) {
|
|
43
|
+
console.log(`\n All ${chalk.default.bold(diagnostics.length)} issues\n`);
|
|
44
|
+
for (const cat of [
|
|
45
|
+
"Security",
|
|
46
|
+
"Performance",
|
|
47
|
+
"Best Practices",
|
|
48
|
+
"Compose",
|
|
49
|
+
"Image Size"
|
|
50
|
+
]) {
|
|
51
|
+
const count = categoryIssueCounts[cat];
|
|
52
|
+
const issueLabel = count === 1 ? "1 issue" : `${count} issues`;
|
|
53
|
+
console.log(` ${cat} › ${chalk.default.dim(issueLabel)}`);
|
|
54
|
+
}
|
|
55
|
+
console.log(`\n Run ${chalk.default.cyan("docker-doctor --verbose")} to list every error and warning`);
|
|
56
|
+
const ruleCounts = {};
|
|
57
|
+
for (const d of diagnostics) ruleCounts[d.rule] = (ruleCounts[d.rule] || 0) + 1;
|
|
58
|
+
const migrationRules = Object.entries(ruleCounts).filter(([_, count]) => count >= 5);
|
|
59
|
+
if (migrationRules.length > 0) {
|
|
60
|
+
console.log();
|
|
61
|
+
console.log(` ${chalk.default.yellow("⚠ Migration-scale change: sample before you sweep")}`);
|
|
62
|
+
for (const [rule, count] of migrationRules) console.log(` ${chalk.default.cyan(rule)} ×${count} across ${count} files`);
|
|
63
|
+
console.log(` Fixing all of them at once is hard to review and prone to`);
|
|
64
|
+
console.log(` subtle mistakes across the whole repo. Fix a representative`);
|
|
65
|
+
console.log(` few first and confirm the recipe holds. Then get the code`);
|
|
66
|
+
console.log(` owner's sign-off before changing the rest.`);
|
|
67
|
+
console.log(` Scope it down one area at a time: docker-doctor <path>`);
|
|
68
|
+
}
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
console.log(`\nFound ${chalk.default.bold(diagnostics.length)} issue(s):`);
|
|
72
|
+
const filesGrouped = {};
|
|
73
|
+
for (const d of diagnostics) {
|
|
74
|
+
if (!filesGrouped[d.file]) filesGrouped[d.file] = [];
|
|
75
|
+
filesGrouped[d.file].push(d);
|
|
76
|
+
}
|
|
77
|
+
for (const [file, fileDiags] of Object.entries(filesGrouped)) {
|
|
78
|
+
console.log(`\n${chalk.default.underline.bold(file)}`);
|
|
79
|
+
for (const d of fileDiags) {
|
|
80
|
+
let sevColor = chalk.default.cyan;
|
|
81
|
+
let prefix = "ℹ INFO";
|
|
82
|
+
if (d.severity === "error") {
|
|
83
|
+
sevColor = chalk.default.red.bold;
|
|
84
|
+
prefix = "✖ ERROR";
|
|
85
|
+
} else if (d.severity === "warning") {
|
|
86
|
+
sevColor = chalk.default.yellow;
|
|
87
|
+
prefix = "⚠ WARN";
|
|
88
|
+
}
|
|
89
|
+
const lineInfo = d.line ? `:${d.line}` : "";
|
|
90
|
+
console.log(` ${sevColor(prefix)} [${chalk.default.dim(d.rule)}]${lineInfo}`);
|
|
91
|
+
const content = fileContents[file];
|
|
92
|
+
printCodeFrame(content, d.line, sevColor);
|
|
93
|
+
console.log(` ${chalk.default.white(d.message)}`);
|
|
94
|
+
console.log(` ${chalk.default.dim("Help:")} ${d.help}`);
|
|
95
|
+
console.log();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
const getWhaleMascot = (score, border) => {
|
|
100
|
+
let eyes = "x x";
|
|
101
|
+
let spout = " ";
|
|
102
|
+
if (score >= 75) {
|
|
103
|
+
eyes = "◠ ◠";
|
|
104
|
+
spout = chalk.default.cyan(" \":\" ");
|
|
105
|
+
} else if (score >= 50) {
|
|
106
|
+
eyes = "• •";
|
|
107
|
+
spout = chalk.default.cyan(" . ");
|
|
108
|
+
}
|
|
109
|
+
return [
|
|
110
|
+
spout,
|
|
111
|
+
border(" .---. "),
|
|
112
|
+
border(`( ${eyes} )>`),
|
|
113
|
+
border(" \\___/ ")
|
|
114
|
+
];
|
|
115
|
+
};
|
|
116
|
+
const easeOutCubic = (x) => 1 - (1 - x) ** 3;
|
|
117
|
+
const printScoreBox = async (score, label, categoryIssueCounts) => {
|
|
118
|
+
const { isTTY } = process.stdout;
|
|
119
|
+
const frameCount = 40;
|
|
120
|
+
const frameDelay = 50;
|
|
121
|
+
let scoreColor = chalk.default.red.bold;
|
|
122
|
+
if (score >= 90) scoreColor = chalk.default.green.bold;
|
|
123
|
+
else if (score >= 75) scoreColor = chalk.default.yellow.bold;
|
|
124
|
+
else if (score >= 50) scoreColor = chalk.default.magenta.bold;
|
|
125
|
+
const whaleLines = getWhaleMascot(score, scoreColor);
|
|
126
|
+
const shareUrl = `https://github.com/PunGrumpy/docker-doctor/share?s=${score}&w=${Object.values(categoryIssueCounts).reduce((a, b) => a + b, 0)}`;
|
|
127
|
+
if (isTTY) {
|
|
128
|
+
process.stdout.write("\x1B[?25l");
|
|
129
|
+
try {
|
|
130
|
+
for (let frame = 0; frame <= frameCount; frame += 1) {
|
|
131
|
+
const progress = easeOutCubic(frame / frameCount);
|
|
132
|
+
const currentScore = Math.round(score * progress);
|
|
133
|
+
const filledBlocks = Math.round(currentScore / 2);
|
|
134
|
+
const emptyBlocks = 50 - filledBlocks;
|
|
135
|
+
const bar = scoreColor("█".repeat(filledBlocks)) + chalk.default.dim("░".repeat(emptyBlocks));
|
|
136
|
+
if (frame > 0) process.stdout.write("\x1B[4A\r");
|
|
137
|
+
else console.log();
|
|
138
|
+
process.stdout.write(` ${whaleLines[0]} ${scoreColor(`${currentScore} / 100`)} ${scoreColor(label)}\n ${whaleLines[1]} ${bar}\n ${whaleLines[2]} ${chalk.default.dim("Docker Doctor (https://github.com/PunGrumpy/docker-doctor)")}\n ${whaleLines[3]}\n`);
|
|
139
|
+
if (frame < frameCount) await (0, node_timers_promises.setTimeout)(frameDelay);
|
|
140
|
+
}
|
|
141
|
+
} finally {
|
|
142
|
+
process.stdout.write("\x1B[?25h");
|
|
143
|
+
}
|
|
144
|
+
} else {
|
|
145
|
+
const filledBlocks = Math.round(score / 2);
|
|
146
|
+
const emptyBlocks = 50 - filledBlocks;
|
|
147
|
+
const bar = scoreColor("█".repeat(filledBlocks)) + chalk.default.dim("░".repeat(emptyBlocks));
|
|
148
|
+
console.log(`\n ${whaleLines[0]} ${scoreColor(`${score} / 100`)} ${scoreColor(label)}`);
|
|
149
|
+
console.log(` ${whaleLines[1]} ${bar}`);
|
|
150
|
+
console.log(` ${whaleLines[2]} ${chalk.default.dim("Docker Doctor (https://github.com/PunGrumpy/docker-doctor)")}`);
|
|
151
|
+
console.log(` ${whaleLines[3]}`);
|
|
152
|
+
}
|
|
153
|
+
console.log(`\n ${chalk.default.dim("────────────────────────────────────────────────────────────")}\n`);
|
|
154
|
+
console.log(` Share: ${chalk.default.cyan(shareUrl)}`);
|
|
155
|
+
console.log(` Tell others how you did on socials\n`);
|
|
156
|
+
console.log(` Docs: ${chalk.default.cyan("https://github.com/PunGrumpy/docker-doctor/docs")}`);
|
|
157
|
+
console.log(` Learn more about fixing issues, setting up CI/CD, and`);
|
|
158
|
+
console.log(` configuring rules with a config file\n`);
|
|
159
|
+
console.log(` GitHub: ${chalk.default.cyan("https://github.com/PunGrumpy/docker-doctor")}`);
|
|
160
|
+
console.log(` Report issues and star the repository!`);
|
|
161
|
+
};
|
|
162
|
+
const formatTerminal = async (diagnostics, score, label, project, verbose = false, fileContents = {}) => {
|
|
163
|
+
if (verbose) {
|
|
164
|
+
console.log(`\n${chalk.default.bold("Docker Doctor Diagnostics")}`);
|
|
165
|
+
console.log(`=================================`);
|
|
166
|
+
printDiscoveredFiles(project);
|
|
167
|
+
}
|
|
168
|
+
const categoryIssueCounts = {
|
|
169
|
+
"Best Practices": 0,
|
|
170
|
+
Compose: 0,
|
|
171
|
+
"Image Size": 0,
|
|
172
|
+
Performance: 0,
|
|
173
|
+
Security: 0
|
|
174
|
+
};
|
|
175
|
+
for (const d of diagnostics) {
|
|
176
|
+
const category = require_src.findRule(d.rule)?.category || "Best Practices";
|
|
177
|
+
categoryIssueCounts[category] = (categoryIssueCounts[category] || 0) + 1;
|
|
178
|
+
}
|
|
179
|
+
printDiagnostics(diagnostics, verbose, fileContents, categoryIssueCounts);
|
|
180
|
+
await printScoreBox(score, label, categoryIssueCounts);
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
//#endregion
|
|
184
|
+
//#region src/cli.ts
|
|
185
|
+
const runInteractiveWizard = async () => {
|
|
186
|
+
const rl = node_readline_promises.default.createInterface({
|
|
187
|
+
input: node_process.stdin,
|
|
188
|
+
output: node_process.stdout
|
|
189
|
+
});
|
|
190
|
+
try {
|
|
191
|
+
if ((await rl.question(`\n ${chalk.default.green("✔")} ${chalk.default.bold("Add Docker Doctor to GitHub Actions?")}\n Scan every pull request to prevent new Docker issues while you fix the backlog.\n › (y/N) `)).trim().toLowerCase() === "y") {
|
|
192
|
+
const workflowDir = node_path.default.resolve(".github/workflows");
|
|
193
|
+
await node_fs_promises.default.mkdir(workflowDir, { recursive: true });
|
|
194
|
+
const workflowPath = node_path.default.join(workflowDir, "docker-doctor.yml");
|
|
195
|
+
await node_fs_promises.default.writeFile(workflowPath, `name: Docker Doctor Scan
|
|
196
|
+
on:
|
|
197
|
+
push:
|
|
198
|
+
branches: [ main, master ]
|
|
199
|
+
pull_request:
|
|
200
|
+
branches: [ main, master ]
|
|
201
|
+
jobs:
|
|
202
|
+
docker-doctor:
|
|
203
|
+
runs-on: ubuntu-latest
|
|
204
|
+
steps:
|
|
205
|
+
- uses: actions/checkout@v4
|
|
206
|
+
- name: Setup Bun
|
|
207
|
+
uses: oven-sh/setup-bun@v2
|
|
208
|
+
- name: Install dependencies
|
|
209
|
+
run: bun install
|
|
210
|
+
- name: Run docker-doctor
|
|
211
|
+
run: bunx docker-doctor .
|
|
212
|
+
`, "utf-8");
|
|
213
|
+
console.log(`\n ${chalk.default.green("✨")} Created ${chalk.default.cyan(".github/workflows/docker-doctor.yml")}!`);
|
|
214
|
+
console.log(` Scan every pull request to prevent new Docker issues while you fix the backlog.`);
|
|
215
|
+
}
|
|
216
|
+
if ((await rl.question(`\n ${chalk.default.green("✔")} ${chalk.default.bold("What would you like to do next?")}\n 1: View rules list\n 2: Skip\n › `)).trim() === "1") {
|
|
217
|
+
console.log(`\n ${chalk.default.bold("Available Rules:")}`);
|
|
218
|
+
for (const r of require_src.allRules) console.log(` - ${chalk.default.cyan(r.key)}: ${r.message} (${chalk.default.dim(r.category)})`);
|
|
219
|
+
}
|
|
220
|
+
} catch {} finally {
|
|
221
|
+
rl.close();
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
const runRulesEngine = async (rootDir, project, rulesConfig, projectFilesList, fileContents, options, setStatus) => {
|
|
225
|
+
const diagnostics = [];
|
|
226
|
+
const isSilent = options.score || options.json;
|
|
227
|
+
if (process.stdout.isTTY && !isSilent) {
|
|
228
|
+
setStatus(`Analyzing ${project.dockerfiles.length} Dockerfile(s)...`);
|
|
229
|
+
await (0, node_timers_promises.setTimeout)(100);
|
|
230
|
+
}
|
|
231
|
+
const dockerfileResults = await Promise.all(project.dockerfiles.map(async (df) => {
|
|
232
|
+
const fullPath = node_path.default.join(rootDir, df);
|
|
233
|
+
try {
|
|
234
|
+
const content = await node_fs_promises.default.readFile(fullPath, "utf-8");
|
|
235
|
+
fileContents[df] = content;
|
|
236
|
+
return require_src.runDockerfileRules(require_src.parseDockerfile(content), df, projectFilesList, rulesConfig);
|
|
237
|
+
} catch (error) {
|
|
238
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
239
|
+
console.error(`Failed to analyze Dockerfile ${df}: ${msg}`);
|
|
240
|
+
return [];
|
|
241
|
+
}
|
|
242
|
+
}));
|
|
243
|
+
for (const diags of dockerfileResults) diagnostics.push(...diags);
|
|
244
|
+
if (process.stdout.isTTY && !isSilent) {
|
|
245
|
+
setStatus(`Analyzing ${project.composeFiles.length} Compose file(s)...`);
|
|
246
|
+
await (0, node_timers_promises.setTimeout)(100);
|
|
247
|
+
}
|
|
248
|
+
const composeResults = await Promise.all(project.composeFiles.map(async (cf) => {
|
|
249
|
+
const fullPath = node_path.default.join(rootDir, cf);
|
|
250
|
+
try {
|
|
251
|
+
const content = await node_fs_promises.default.readFile(fullPath, "utf-8");
|
|
252
|
+
fileContents[cf] = content;
|
|
253
|
+
return require_src.runComposeRules(require_src.parseCompose(content, cf), cf, rulesConfig);
|
|
254
|
+
} catch (error) {
|
|
255
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
256
|
+
console.error(`Failed to analyze Compose file ${cf}: ${msg}`);
|
|
257
|
+
return [];
|
|
258
|
+
}
|
|
259
|
+
}));
|
|
260
|
+
for (const diags of composeResults) diagnostics.push(...diags);
|
|
261
|
+
return diagnostics;
|
|
262
|
+
};
|
|
263
|
+
const program = new commander.Command();
|
|
264
|
+
program.name("docker-doctor").description("Static analysis for Dockerfile and Docker Compose files").version(require_src.package_default.version, "-V, --version", "display the version number");
|
|
265
|
+
program.argument("[dir]", "directory to scan", ".").option("-v, --verbose", "show verbose diagnostics description", false).option("-s, --score", "only output numeric health score", false).option("-j, --json", "output results as JSON report", false).option("-c, --config <path>", "custom config file path").action(async (dir, options) => {
|
|
266
|
+
try {
|
|
267
|
+
const rootDir = node_path.default.resolve(dir);
|
|
268
|
+
const startTime = Date.now();
|
|
269
|
+
const isSilent = options.score || options.json;
|
|
270
|
+
let statusText = "Discovering workspace...";
|
|
271
|
+
const spinnerFrames = [
|
|
272
|
+
"⠋",
|
|
273
|
+
"⠙",
|
|
274
|
+
"⠹",
|
|
275
|
+
"⠸",
|
|
276
|
+
"⠼",
|
|
277
|
+
"⠴",
|
|
278
|
+
"⠦",
|
|
279
|
+
"⠧",
|
|
280
|
+
"⠇",
|
|
281
|
+
"⠏"
|
|
282
|
+
];
|
|
283
|
+
let frameIndex = 0;
|
|
284
|
+
let spinnerInterval = null;
|
|
285
|
+
const setStatus = (text) => {
|
|
286
|
+
statusText = text;
|
|
287
|
+
};
|
|
288
|
+
if (process.stdout.isTTY && !isSilent) {
|
|
289
|
+
process.stdout.write(`${chalk.default.cyan(spinnerFrames[0])} ${statusText}`);
|
|
290
|
+
spinnerInterval = setInterval(() => {
|
|
291
|
+
process.stdout.write(`\r${chalk.default.cyan(spinnerFrames[frameIndex])} ${statusText}`);
|
|
292
|
+
frameIndex = (frameIndex + 1) % spinnerFrames.length;
|
|
293
|
+
}, 80);
|
|
294
|
+
}
|
|
295
|
+
try {
|
|
296
|
+
if (process.stdout.isTTY && !isSilent) await (0, node_timers_promises.setTimeout)(150);
|
|
297
|
+
setStatus("Loading configuration...");
|
|
298
|
+
const config = await require_src.loadConfig(rootDir, options.config);
|
|
299
|
+
setStatus("Scanning workspace files...");
|
|
300
|
+
const project = await require_src.discoverProject(rootDir);
|
|
301
|
+
const fileContents = {};
|
|
302
|
+
const projectFilesList = [...project.dockerfiles, ...project.composeFiles];
|
|
303
|
+
const diagnostics = await runRulesEngine(rootDir, project, config.rules, projectFilesList, fileContents, options, setStatus);
|
|
304
|
+
let filteredDiagnostics = diagnostics;
|
|
305
|
+
if (config.categories) filteredDiagnostics = diagnostics.filter((d) => {
|
|
306
|
+
const ruleDef = require_src.findRule(d.rule);
|
|
307
|
+
if (ruleDef) {
|
|
308
|
+
if (config.categories?.[ruleDef.category] === "off") return false;
|
|
309
|
+
}
|
|
310
|
+
return true;
|
|
311
|
+
});
|
|
312
|
+
const { score, label } = require_src.calculateScore(filteredDiagnostics);
|
|
313
|
+
const duration = ((Date.now() - startTime) / 1e3).toFixed(1);
|
|
314
|
+
const concurrency = node_os.default.cpus().length;
|
|
315
|
+
if (spinnerInterval !== null) {
|
|
316
|
+
clearInterval(spinnerInterval);
|
|
317
|
+
spinnerInterval = null;
|
|
318
|
+
process.stdout.write("\r\x1B[K");
|
|
319
|
+
}
|
|
320
|
+
if (process.stdout.isTTY && !isSilent) console.log(`${chalk.default.green("✔")} Scanned ${projectFilesList.length} files in ${duration}s [~${concurrency} workers]`);
|
|
321
|
+
if (options.score) {
|
|
322
|
+
console.log(score);
|
|
323
|
+
process.exit(score < 50 ? 1 : 0);
|
|
324
|
+
} else if (options.json) {
|
|
325
|
+
const report = require_src.toJsonReport(filteredDiagnostics, score, label, project);
|
|
326
|
+
console.log(JSON.stringify(report, null, 2));
|
|
327
|
+
const hasErrors = filteredDiagnostics.some((d) => d.severity === "error");
|
|
328
|
+
process.exit(hasErrors ? 1 : 0);
|
|
329
|
+
} else {
|
|
330
|
+
await formatTerminal(filteredDiagnostics, score, label, project, options.verbose, fileContents);
|
|
331
|
+
const hasErrors = filteredDiagnostics.some((d) => d.severity === "error");
|
|
332
|
+
if (process.stdout.isTTY) await runInteractiveWizard();
|
|
333
|
+
process.exit(hasErrors ? 1 : 0);
|
|
334
|
+
}
|
|
335
|
+
} finally {
|
|
336
|
+
if (spinnerInterval !== null) {
|
|
337
|
+
clearInterval(spinnerInterval);
|
|
338
|
+
process.stdout.write("\r\x1B[K");
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
} catch (error) {
|
|
342
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
343
|
+
console.error(`Error: ${msg}`);
|
|
344
|
+
process.exit(1);
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
const rules = program.command("rules").description("manage and list configuration rules");
|
|
348
|
+
rules.command("list").description("list all available rules").action(() => {
|
|
349
|
+
console.log("\nAvailable Rules:");
|
|
350
|
+
console.log("================\n");
|
|
351
|
+
for (const rule of require_src.allRules) {
|
|
352
|
+
console.log(`- ${rule.key} (${rule.category})`);
|
|
353
|
+
console.log(` Default Severity: ${rule.defaultSeverity}`);
|
|
354
|
+
console.log(` Description: ${rule.message}\n`);
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
rules.command("explain <rule>").description("explain a specific rule in detail").action((ruleKey) => {
|
|
358
|
+
const rule = require_src.findRule(ruleKey);
|
|
359
|
+
if (!rule) {
|
|
360
|
+
console.error(`Rule '${ruleKey}' not found.`);
|
|
361
|
+
process.exit(1);
|
|
362
|
+
}
|
|
363
|
+
console.log(`\nRule: ${rule.key}`);
|
|
364
|
+
console.log(`Category: ${rule.category}`);
|
|
365
|
+
console.log(`Default Severity: ${rule.defaultSeverity}`);
|
|
366
|
+
console.log(`Description: ${rule.message}`);
|
|
367
|
+
console.log(`Help / Fix: ${rule.help}\n`);
|
|
368
|
+
});
|
|
369
|
+
program.parse(process.argv);
|
|
370
|
+
|
|
371
|
+
//#endregion
|
|
372
|
+
//# sourceMappingURL=cli.cjs.map
|
package/dist/cli.cjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.cjs","names":["border","findRule","readline","path","fs","allRules","runDockerfileRules","parseDockerfile","runComposeRules","parseCompose","Command","packageJson","loadConfig","discoverProject","findRule","calculateScore","os","toJsonReport"],"sources":["../src/formatters/terminal.ts","../src/cli.ts"],"sourcesContent":["import { setTimeout } from \"node:timers/promises\";\n\nimport type { Diagnostic, ProjectInfo } from \"@docker-doctor/core\";\nimport { findRule } from \"@docker-doctor/core\";\nimport chalk from \"chalk\";\n\nconst printCodeFrame = (\n content: string | undefined,\n line: number | undefined,\n severityColor: (msg: string) => string\n): void => {\n if (!content || !line) {\n return;\n }\n const lines = content.split(/\\r?\\n/u);\n const start = Math.max(1, line - 1);\n const end = Math.min(lines.length, line + 1);\n\n for (let i = start; i <= end; i += 1) {\n const rawLine = lines[i - 1];\n const isTarget = i === line;\n const lineNumberStr = String(i).padStart(5, \" \");\n if (isTarget) {\n console.log(\n ` ${severityColor(\">\")} ${chalk.bold(lineNumberStr)} │ ${chalk.white(rawLine)}`\n );\n } else {\n console.log(` ${chalk.dim(lineNumberStr)} │ ${chalk.dim(rawLine)}`);\n }\n }\n console.log();\n};\n\nconst printDiscoveredFiles = (project: ProjectInfo): void => {\n console.log(`\\nDiscovered Files:`);\n console.log(\n ` Dockerfile(s): ${project.dockerfiles.length ? project.dockerfiles.map((f) => chalk.cyan(f)).join(\", \") : chalk.dim(\"None\")}`\n );\n console.log(\n ` Compose file(s): ${project.composeFiles.length ? project.composeFiles.map((f) => chalk.cyan(f)).join(\", \") : chalk.dim(\"None\")}`\n );\n};\n\nconst printDiagnostics = (\n diagnostics: Diagnostic[],\n verbose: boolean,\n fileContents: Record<string, string>,\n categoryIssueCounts: Record<string, number>\n): void => {\n if (diagnostics.length === 0) {\n console.log(\n `\\n${chalk.green.bold(\"✔ No issues found! Your Docker setup looks healthy.\")}`\n );\n return;\n }\n\n if (!verbose) {\n console.log(`\\n All ${chalk.bold(diagnostics.length)} issues\\n`);\n\n const categories = [\n \"Security\",\n \"Performance\",\n \"Best Practices\",\n \"Compose\",\n \"Image Size\",\n ];\n\n for (const cat of categories) {\n const count = categoryIssueCounts[cat];\n const issueLabel = count === 1 ? \"1 issue\" : `${count} issues`;\n console.log(` ${cat} › ${chalk.dim(issueLabel)}`);\n }\n\n console.log(\n `\\n Run ${chalk.cyan(\"docker-doctor --verbose\")} to list every error and warning`\n );\n\n // Migration-scale checks\n const ruleCounts: Record<string, number> = {};\n for (const d of diagnostics) {\n ruleCounts[d.rule] = (ruleCounts[d.rule] || 0) + 1;\n }\n const migrationRules = Object.entries(ruleCounts).filter(\n ([_, count]) => count >= 5\n );\n if (migrationRules.length > 0) {\n console.log();\n console.log(\n ` ${chalk.yellow(\"⚠ Migration-scale change: sample before you sweep\")}`\n );\n for (const [rule, count] of migrationRules) {\n console.log(` ${chalk.cyan(rule)} ×${count} across ${count} files`);\n }\n console.log(\n ` Fixing all of them at once is hard to review and prone to`\n );\n console.log(\n ` subtle mistakes across the whole repo. Fix a representative`\n );\n console.log(\n ` few first and confirm the recipe holds. Then get the code`\n );\n console.log(` owner's sign-off before changing the rest.`);\n console.log(` Scope it down one area at a time: docker-doctor <path>`);\n }\n return;\n }\n\n console.log(`\\nFound ${chalk.bold(diagnostics.length)} issue(s):`);\n\n // Group by file\n const filesGrouped: Record<string, Diagnostic[]> = {};\n for (const d of diagnostics) {\n if (!filesGrouped[d.file]) {\n filesGrouped[d.file] = [];\n }\n filesGrouped[d.file].push(d);\n }\n\n for (const [file, fileDiags] of Object.entries(filesGrouped)) {\n console.log(`\\n${chalk.underline.bold(file)}`);\n for (const d of fileDiags) {\n let sevColor = chalk.cyan;\n let prefix = \"ℹ INFO\";\n if (d.severity === \"error\") {\n sevColor = chalk.red.bold;\n prefix = \"✖ ERROR\";\n } else if (d.severity === \"warning\") {\n sevColor = chalk.yellow;\n prefix = \"⚠ WARN\";\n }\n\n const lineInfo = d.line ? `:${d.line}` : \"\";\n console.log(` ${sevColor(prefix)} [${chalk.dim(d.rule)}]${lineInfo}`);\n\n // Print Code Frame\n const content = fileContents[file];\n printCodeFrame(content, d.line, sevColor);\n\n console.log(` ${chalk.white(d.message)}`);\n console.log(` ${chalk.dim(\"Help:\")} ${d.help}`);\n console.log();\n }\n }\n};\n\nconst getWhaleMascot = (\n score: number,\n border: (text: string) => string\n): string[] => {\n let eyes = \"x x\";\n let spout = \" \";\n if (score >= 75) {\n eyes = \"◠ ◠\";\n spout = chalk.cyan(' \":\" ');\n } else if (score >= 50) {\n eyes = \"• •\";\n spout = chalk.cyan(\" . \");\n }\n\n return [\n spout,\n border(\" .---. \"),\n border(`( ${eyes} )>`),\n border(\" \\\\___/ \"),\n ];\n};\n\nconst easeOutCubic = (x: number): number => 1 - (1 - x) ** 3;\n\nconst printScoreBox = async (\n score: number,\n label: string,\n categoryIssueCounts: Record<string, number>\n): Promise<void> => {\n const { isTTY } = process.stdout;\n const frameCount = 40;\n const frameDelay = 50;\n\n let scoreColor = chalk.red.bold;\n if (score >= 90) {\n scoreColor = chalk.green.bold;\n } else if (score >= 75) {\n scoreColor = chalk.yellow.bold;\n } else if (score >= 50) {\n scoreColor = chalk.magenta.bold;\n }\n const border = scoreColor;\n const whaleLines = getWhaleMascot(score, border);\n\n const totalIssues = Object.values(categoryIssueCounts).reduce(\n (a, b) => a + b,\n 0\n );\n const shareUrl = `https://github.com/PunGrumpy/docker-doctor/share?s=${score}&w=${totalIssues}`;\n\n if (isTTY) {\n // Hide cursor\n process.stdout.write(\"\\u001B[?25l\");\n try {\n for (let frame = 0; frame <= frameCount; frame += 1) {\n const progress = easeOutCubic(frame / frameCount);\n const currentScore = Math.round(score * progress);\n const filledBlocks = Math.round(currentScore / 2);\n const emptyBlocks = 50 - filledBlocks;\n\n const bar =\n scoreColor(\"█\".repeat(filledBlocks)) +\n chalk.dim(\"░\".repeat(emptyBlocks));\n\n if (frame > 0) {\n // Move cursor up 4 lines and carriage return\n process.stdout.write(\"\\u001B[4A\\r\");\n } else {\n // print an extra newline first to start\n console.log();\n }\n\n process.stdout.write(\n ` ${whaleLines[0]} ${scoreColor(`${currentScore} / 100`)} ${scoreColor(label)}\\n` +\n ` ${whaleLines[1]} ${bar}\\n` +\n ` ${whaleLines[2]} ${chalk.dim(\"Docker Doctor (https://github.com/PunGrumpy/docker-doctor)\")}\\n` +\n ` ${whaleLines[3]}\\n`\n );\n\n if (frame < frameCount) {\n // eslint-disable-next-line no-await-in-loop\n await setTimeout(frameDelay);\n }\n }\n } finally {\n // Show cursor\n process.stdout.write(\"\\u001B[?25h\");\n }\n } else {\n // Non-TTY fall back to static print\n const filledBlocks = Math.round(score / 2);\n const emptyBlocks = 50 - filledBlocks;\n const bar =\n scoreColor(\"█\".repeat(filledBlocks)) + chalk.dim(\"░\".repeat(emptyBlocks));\n\n console.log(\n `\\n ${whaleLines[0]} ${scoreColor(`${score} / 100`)} ${scoreColor(label)}`\n );\n console.log(` ${whaleLines[1]} ${bar}`);\n console.log(\n ` ${whaleLines[2]} ${chalk.dim(\"Docker Doctor (https://github.com/PunGrumpy/docker-doctor)\")}`\n );\n console.log(` ${whaleLines[3]}`);\n }\n\n console.log(\n `\\n ${chalk.dim(\"────────────────────────────────────────────────────────────\")}\\n`\n );\n console.log(` Share: ${chalk.cyan(shareUrl)}`);\n console.log(` Tell others how you did on socials\\n`);\n console.log(\n ` Docs: ${chalk.cyan(\"https://github.com/PunGrumpy/docker-doctor/docs\")}`\n );\n console.log(` Learn more about fixing issues, setting up CI/CD, and`);\n console.log(` configuring rules with a config file\\n`);\n console.log(\n ` GitHub: ${chalk.cyan(\"https://github.com/PunGrumpy/docker-doctor\")}`\n );\n console.log(` Report issues and star the repository!`);\n};\n\nexport const formatTerminal = async (\n diagnostics: Diagnostic[],\n score: number,\n label: string,\n project: ProjectInfo,\n verbose = false,\n fileContents: Record<string, string> = {}\n): Promise<void> => {\n if (verbose) {\n console.log(`\\n${chalk.bold(\"Docker Doctor Diagnostics\")}`);\n console.log(`=================================`);\n printDiscoveredFiles(project);\n }\n\n // Calculate scores per category once\n const categoryIssueCounts: Record<string, number> = {\n \"Best Practices\": 0,\n Compose: 0,\n \"Image Size\": 0,\n Performance: 0,\n Security: 0,\n };\n\n for (const d of diagnostics) {\n const ruleDef = findRule(d.rule);\n const category = ruleDef?.category || \"Best Practices\";\n categoryIssueCounts[category] = (categoryIssueCounts[category] || 0) + 1;\n }\n\n printDiagnostics(diagnostics, verbose, fileContents, categoryIssueCounts);\n\n await printScoreBox(score, label, categoryIssueCounts);\n};\n","import fs from \"node:fs/promises\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport { stdin as input, stdout as output } from \"node:process\";\nimport readline from \"node:readline/promises\";\nimport { setTimeout } from \"node:timers/promises\";\n\nimport type { Diagnostic, RuleSeverity } from \"@docker-doctor/core\";\nimport {\n discoverProject,\n parseDockerfile,\n parseCompose,\n runDockerfileRules,\n runComposeRules,\n calculateScore,\n loadConfig,\n allRules,\n findRule,\n toJsonReport,\n} from \"@docker-doctor/core\";\nimport chalk from \"chalk\";\nimport { Command } from \"commander\";\n\nimport packageJson from \"../package.json\" with { type: \"json\" };\nimport { formatTerminal } from \"./formatters/terminal.js\";\n\nconst runInteractiveWizard = async (): Promise<void> => {\n const rl = readline.createInterface({ input, output });\n try {\n const ghAnswer = await rl.question(\n `\\n ${chalk.green(\"✔\")} ${chalk.bold(\"Add Docker Doctor to GitHub Actions?\")}\\n` +\n ` Scan every pull request to prevent new Docker issues while you fix the backlog.\\n` +\n ` › (y/N) `\n );\n if (ghAnswer.trim().toLowerCase() === \"y\") {\n const workflowDir = path.resolve(\".github/workflows\");\n await fs.mkdir(workflowDir, { recursive: true });\n const workflowPath = path.join(workflowDir, \"docker-doctor.yml\");\n const workflowYaml = `name: Docker Doctor Scan\non:\n push:\n branches: [ main, master ]\n pull_request:\n branches: [ main, master ]\njobs:\n docker-doctor:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - name: Setup Bun\n uses: oven-sh/setup-bun@v2\n - name: Install dependencies\n run: bun install\n - name: Run docker-doctor\n run: bunx docker-doctor .\n`;\n await fs.writeFile(workflowPath, workflowYaml, \"utf-8\");\n console.log(\n `\\n ${chalk.green(\"✨\")} Created ${chalk.cyan(\".github/workflows/docker-doctor.yml\")}!`\n );\n console.log(\n ` Scan every pull request to prevent new Docker issues while you fix the backlog.`\n );\n }\n\n const nextAnswer = await rl.question(\n `\\n ${chalk.green(\"✔\")} ${chalk.bold(\"What would you like to do next?\")}\\n` +\n ` 1: View rules list\\n` +\n ` 2: Skip\\n` +\n ` › `\n );\n if (nextAnswer.trim() === \"1\") {\n console.log(`\\n ${chalk.bold(\"Available Rules:\")}`);\n for (const r of allRules) {\n console.log(\n ` - ${chalk.cyan(r.key)}: ${r.message} (${chalk.dim(r.category)})`\n );\n }\n }\n } catch {\n // Ignore prompt errors\n } finally {\n rl.close();\n }\n};\n\nconst runRulesEngine = async (\n rootDir: string,\n project: { dockerfiles: string[]; composeFiles: string[] },\n rulesConfig: Record<string, RuleSeverity> | undefined,\n projectFilesList: string[],\n fileContents: Record<string, string>,\n options: { score?: boolean; json?: boolean },\n setStatus: (text: string) => void\n): Promise<Diagnostic[]> => {\n const diagnostics: Diagnostic[] = [];\n\n const isSilent = options.score || options.json;\n\n if (process.stdout.isTTY && !isSilent) {\n setStatus(`Analyzing ${project.dockerfiles.length} Dockerfile(s)...`);\n await setTimeout(100);\n }\n\n // 1. Scan Dockerfiles in parallel\n const dockerfileResults = await Promise.all(\n project.dockerfiles.map(async (df) => {\n const fullPath = path.join(rootDir, df);\n try {\n const content = await fs.readFile(fullPath, \"utf-8\");\n fileContents[df] = content;\n const instructions = parseDockerfile(content);\n return runDockerfileRules(\n instructions,\n df,\n projectFilesList,\n rulesConfig\n );\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n console.error(`Failed to analyze Dockerfile ${df}: ${msg}`);\n return [];\n }\n })\n );\n for (const diags of dockerfileResults) {\n diagnostics.push(...diags);\n }\n\n if (process.stdout.isTTY && !isSilent) {\n setStatus(`Analyzing ${project.composeFiles.length} Compose file(s)...`);\n await setTimeout(100);\n }\n\n // 2. Scan Compose files in parallel\n const composeResults = await Promise.all(\n project.composeFiles.map(async (cf) => {\n const fullPath = path.join(rootDir, cf);\n try {\n const content = await fs.readFile(fullPath, \"utf-8\");\n fileContents[cf] = content;\n const composeObj = parseCompose(content, cf);\n return runComposeRules(composeObj, cf, rulesConfig);\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n console.error(`Failed to analyze Compose file ${cf}: ${msg}`);\n return [];\n }\n })\n );\n for (const diags of composeResults) {\n diagnostics.push(...diags);\n }\n\n return diagnostics;\n};\n\nconst program = new Command();\n\nprogram\n .name(\"docker-doctor\")\n .description(\"Static analysis for Dockerfile and Docker Compose files\")\n .version(packageJson.version, \"-V, --version\", \"display the version number\");\n\n// Default scan command\nprogram\n .argument(\"[dir]\", \"directory to scan\", \".\")\n .option(\"-v, --verbose\", \"show verbose diagnostics description\", false)\n .option(\"-s, --score\", \"only output numeric health score\", false)\n .option(\"-j, --json\", \"output results as JSON report\", false)\n .option(\"-c, --config <path>\", \"custom config file path\")\n .action(async (dir, options) => {\n try {\n const rootDir = path.resolve(dir);\n const startTime = Date.now();\n\n const isSilent = options.score || options.json;\n\n let statusText = \"Discovering workspace...\";\n const spinnerFrames = [\"⠋\", \"⠙\", \"⠹\", \"⠸\", \"⠼\", \"⠴\", \"⠦\", \"⠧\", \"⠇\", \"⠏\"];\n let frameIndex = 0;\n let spinnerInterval: NodeJS.Timeout | null = null;\n\n const setStatus = (text: string) => {\n statusText = text;\n };\n\n if (process.stdout.isTTY && !isSilent) {\n process.stdout.write(`${chalk.cyan(spinnerFrames[0])} ${statusText}`);\n spinnerInterval = setInterval(() => {\n process.stdout.write(\n `\\r${chalk.cyan(spinnerFrames[frameIndex])} ${statusText}`\n );\n frameIndex = (frameIndex + 1) % spinnerFrames.length;\n }, 80);\n }\n\n try {\n if (process.stdout.isTTY && !isSilent) {\n await setTimeout(150);\n }\n\n setStatus(\"Loading configuration...\");\n // Load config first\n const config = await loadConfig(rootDir, options.config);\n\n setStatus(\"Scanning workspace files...\");\n // Project discovery\n const project = await discoverProject(rootDir);\n\n // Collect all diagnostics\n const fileContents: Record<string, string> = {};\n\n const projectFilesList = [\n ...project.dockerfiles,\n ...project.composeFiles,\n ];\n\n const diagnostics = await runRulesEngine(\n rootDir,\n project,\n config.rules,\n projectFilesList,\n fileContents,\n options,\n setStatus\n );\n\n // Filter by category config if needed\n let filteredDiagnostics = diagnostics;\n if (config.categories) {\n filteredDiagnostics = diagnostics.filter((d) => {\n const ruleDef = findRule(d.rule);\n if (ruleDef) {\n const catSeverity = config.categories?.[ruleDef.category];\n if (catSeverity === \"off\") {\n return false;\n }\n }\n return true;\n });\n }\n\n // Calculate score\n const { score, label } = calculateScore(filteredDiagnostics);\n\n const duration = ((Date.now() - startTime) / 1000).toFixed(1);\n const concurrency = os.cpus().length;\n\n if (spinnerInterval !== null) {\n clearInterval(spinnerInterval);\n spinnerInterval = null;\n // Clear the spinner line\n process.stdout.write(\"\\r\\u001B[K\");\n }\n\n if (process.stdout.isTTY && !isSilent) {\n console.log(\n `${chalk.green(\"✔\")} Scanned ${projectFilesList.length} files in ${duration}s [~${concurrency} workers]`\n );\n }\n\n if (options.score) {\n console.log(score);\n process.exit(score < 50 ? 1 : 0);\n } else if (options.json) {\n const report = toJsonReport(\n filteredDiagnostics,\n score,\n label,\n project\n );\n console.log(JSON.stringify(report, null, 2));\n const hasErrors = filteredDiagnostics.some(\n (d) => d.severity === \"error\"\n );\n process.exit(hasErrors ? 1 : 0);\n } else {\n await formatTerminal(\n filteredDiagnostics,\n score,\n label,\n project,\n options.verbose,\n fileContents\n );\n\n // Exit with non-zero code if there are any error severity diagnostics\n const hasErrors = filteredDiagnostics.some(\n (d) => d.severity === \"error\"\n );\n\n if (process.stdout.isTTY) {\n await runInteractiveWizard();\n }\n process.exit(hasErrors ? 1 : 0);\n }\n } finally {\n if (spinnerInterval !== null) {\n clearInterval(spinnerInterval);\n process.stdout.write(\"\\r\\u001B[K\");\n }\n }\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n console.error(`Error: ${msg}`);\n process.exit(1);\n }\n });\n\n// Rules subcommand group\nconst rules = program\n .command(\"rules\")\n .description(\"manage and list configuration rules\");\n\nrules\n .command(\"list\")\n .description(\"list all available rules\")\n .action(() => {\n console.log(\"\\nAvailable Rules:\");\n console.log(\"================\\n\");\n for (const rule of allRules) {\n console.log(`- ${rule.key} (${rule.category})`);\n console.log(` Default Severity: ${rule.defaultSeverity}`);\n console.log(` Description: ${rule.message}\\n`);\n }\n });\n\nrules\n .command(\"explain <rule>\")\n .description(\"explain a specific rule in detail\")\n .action((ruleKey) => {\n const rule = findRule(ruleKey);\n if (!rule) {\n console.error(`Rule '${ruleKey}' not found.`);\n process.exit(1);\n }\n console.log(`\\nRule: ${rule.key}`);\n console.log(`Category: ${rule.category}`);\n console.log(`Default Severity: ${rule.defaultSeverity}`);\n console.log(`Description: ${rule.message}`);\n console.log(`Help / Fix: ${rule.help}\\n`);\n });\n\nprogram.parse(process.argv);\n"],"mappings":"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"}
|
package/dist/cli.d.cts
ADDED
package/dist/cli.d.mts
ADDED