@docker-doctor/cli 0.2.0 → 0.2.1

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/README.md CHANGED
@@ -1,9 +1,15 @@
1
- # Docker Doctor
1
+ <picture>
2
+ <source media="(prefers-color-scheme: dark)" srcset="https://docker-doctor.vercel.app/docker-doctor-readme-logo-dark.svg">
3
+ <source media="(prefers-color-scheme: light)" srcset="https://docker-doctor.vercel.app/docker-doctor-readme-logo-light.svg">
4
+ <img alt="Docker Doctor" src="https://docker-doctor.vercel.app/docker-doctor-readme-logo-light.svg" width="134" height="36">
5
+ </picture>
6
+
7
+ <br />
2
8
 
3
9
  <status>
4
- <a href="https://www.npmjs.com/package/@docker-doctor/cli"><picture><source media="(prefers-color-scheme: dark)" srcset="https://shieldcn.dev/npm/@docker-doctor/cli.svg?size=xs&amp;statusDot=true&amp;mode=dark"><img alt="badge" src="https://shieldcn.dev/npm/@docker-doctor/cli.svg?size=xs&amp;statusDot=true&amp;mode=light"></picture></a>
5
- <a href="https://www.npmjs.com/package/@docker-doctor/cli"><picture><source media="(prefers-color-scheme: dark)" srcset="https://shieldcn.dev/npm/dm/@docker-doctor/cli.svg?size=xs&amp;statusDot=true&amp;mode=dark"><img alt="badge" src="https://shieldcn.dev/npm/dm/@docker-doctor/cli.svg?size=xs&amp;statusDot=true&amp;mode=light"></picture></a>
6
- <a href="https://github.com/PunGrumpy/docker-doctor"><picture><source media="(prefers-color-scheme: dark)" srcset="https://shieldcn.dev/github/PunGrumpy/docker-doctor/license.svg?size=xs&amp;statusDot=true&amp;mode=dark"><img alt="badge" src="https://shieldcn.dev/github/PunGrumpy/docker-doctor/license.svg?size=xs&amp;statusDot=true&amp;mode=light"></picture></a>
10
+ <a href="https://www.npmjs.com/package/@docker-doctor/cli"><picture><source media="(prefers-color-scheme: dark)" srcset="https://shieldcn.dev/npm/@docker-doctor/cli.svg?size=xs&amp;statusDot=true&amp;mode=dark"><img alt="badge" src="https://shieldcn.dev/npm/@docker-doctor/cli.svg?size=xs&amp;statusDot=true&amp;mode=light"></picture></a>
11
+ <a href="https://www.npmjs.com/package/@docker-doctor/cli"><picture><source media="(prefers-color-scheme: dark)" srcset="https://shieldcn.dev/npm/dm/@docker-doctor/cli.svg?size=xs&amp;statusDot=true&amp;mode=dark"><img alt="badge" src="https://shieldcn.dev/npm/dm/@docker-doctor/cli.svg?size=xs&amp;statusDot=true&amp;mode=light"></picture></a>
12
+ <a href="https://github.com/PunGrumpy/docker-doctor"><picture><source media="(prefers-color-scheme: dark)" srcset="https://shieldcn.dev/github/PunGrumpy/docker-doctor/license.svg?size=xs&amp;statusDot=true&amp;mode=dark"><img alt="badge" src="https://shieldcn.dev/github/PunGrumpy/docker-doctor/license.svg?size=xs&amp;statusDot=true&amp;mode=light"></picture></a>
7
13
  </status>
8
14
 
9
15
  Your Dockerfiles are probably wrong. Docker Doctor finds out why.
package/dist/cli.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- const require_src = require('./src-BUYNqQd8.cjs');
2
+ const require_src = require('./src-O8FXMq8O.cjs');
3
3
  let node_fs_promises = require("node:fs/promises");
4
4
  node_fs_promises = require_src.__toESM(node_fs_promises, 1);
5
5
  let node_path = require("node:path");
@@ -113,7 +113,7 @@ const getWhaleMascot = (score, border) => {
113
113
  ];
114
114
  };
115
115
  const easeOutCubic = (x) => 1 - (1 - x) ** 3;
116
- const printScoreBox = async (score, label, categoryIssueCounts) => {
116
+ const printScoreBox = async (score, label, categoryIssueCounts, warningsCount, errorsCount) => {
117
117
  const { isTTY } = process.stdout;
118
118
  const shouldAnimate = isTTY && !process.env.CI && !process.env.NO_ANIMATION && process.env.TERM !== "dumb" && process.env.NODE_ENV !== "test";
119
119
  let scoreColor = chalk.default.red.bold;
@@ -121,7 +121,7 @@ const printScoreBox = async (score, label, categoryIssueCounts) => {
121
121
  else if (score >= 75) scoreColor = chalk.default.yellow.bold;
122
122
  else if (score >= 50) scoreColor = chalk.default.magenta.bold;
123
123
  const whaleLines = getWhaleMascot(score, scoreColor);
124
- const shareUrl = `https://github.com/PunGrumpy/docker-doctor/share?s=${score}&w=${Object.values(categoryIssueCounts).reduce((a, b) => a + b, 0)}`;
124
+ const shareUrl = `https://docker-doctor.vercel.app/share?s=${score}&w=${warningsCount}&e=${errorsCount}`;
125
125
  if (shouldAnimate) {
126
126
  const frameCount = 20;
127
127
  const frameDelay = 15;
@@ -135,7 +135,7 @@ const printScoreBox = async (score, label, categoryIssueCounts) => {
135
135
  const bar = scoreColor("█".repeat(filledBlocks)) + chalk.default.dim("░".repeat(emptyBlocks));
136
136
  if (frame > 0) process.stdout.write("\x1B[4A\r");
137
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`);
138
+ process.stdout.write(` ${whaleLines[0]} ${scoreColor(`${currentScore} / 100`)} ${scoreColor(label)}\n ${whaleLines[1]} ${bar}\n ${whaleLines[2]} ${chalk.default.dim("Docker Doctor (https://docker-doctor.vercel.app)")}\n ${whaleLines[3]}\n`);
139
139
  if (frame < frameCount) await (0, node_timers_promises.setTimeout)(frameDelay);
140
140
  }
141
141
  } finally {
@@ -147,7 +147,7 @@ const printScoreBox = async (score, label, categoryIssueCounts) => {
147
147
  const bar = scoreColor("█".repeat(filledBlocks)) + chalk.default.dim("░".repeat(emptyBlocks));
148
148
  console.log(`\n ${whaleLines[0]} ${scoreColor(`${score} / 100`)} ${scoreColor(label)}`);
149
149
  console.log(` ${whaleLines[1]} ${bar}`);
150
- console.log(` ${whaleLines[2]} ${chalk.default.dim("Docker Doctor (https://github.com/PunGrumpy/docker-doctor)")}`);
150
+ console.log(` ${whaleLines[2]} ${chalk.default.dim("Docker Doctor (https://docker-doctor.vercel.app)")}`);
151
151
  console.log(` ${whaleLines[3]}`);
152
152
  }
153
153
  console.log(`\n ${chalk.default.dim("────────────────────────────────────────────────────────────")}\n`);
@@ -172,12 +172,16 @@ const formatTerminal = async (diagnostics, score, label, project, verbose = fals
172
172
  Performance: 0,
173
173
  Security: 0
174
174
  };
175
+ let warningsCount = 0;
176
+ let errorsCount = 0;
175
177
  for (const d of diagnostics) {
176
178
  const category = require_src.findRule(d.rule)?.category || "Best Practices";
177
179
  categoryIssueCounts[category] = (categoryIssueCounts[category] || 0) + 1;
180
+ if (d.severity === "warning") warningsCount += 1;
181
+ else if (d.severity === "error") errorsCount += 1;
178
182
  }
179
183
  printDiagnostics(diagnostics, verbose, fileContents, categoryIssueCounts);
180
- await printScoreBox(score, label, categoryIssueCounts);
184
+ await printScoreBox(score, label, categoryIssueCounts, warningsCount, errorsCount);
181
185
  };
182
186
 
183
187
  //#endregion
package/dist/cli.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.cjs","names":["border","findRule","path","fs","allRules","runDockerfileRules","parseDockerfile","runComposeRules","parseCompose","findRule","Command","packageJson","loadConfig","discoverProject","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 shouldAnimate =\n isTTY &&\n !process.env.CI &&\n !process.env.NO_ANIMATION &&\n process.env.TERM !== \"dumb\" &&\n process.env.NODE_ENV !== \"test\";\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 (shouldAnimate) {\n const frameCount = 20;\n const frameDelay = 15;\n\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 readline from \"node:readline\";\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\";\n\ninterface KeypressKey {\n name?: string;\n ctrl?: boolean;\n meta?: boolean;\n shift?: boolean;\n}\n\nconst askConfirm = (question: string, defaultYes = false): Promise<boolean> => {\n const isRaw = process.stdin.isTTY;\n if (!isRaw) {\n return Promise.resolve(defaultYes);\n }\n\n /* eslint-disable promise/avoid-new */\n return new Promise((resolve) => {\n let value = defaultYes;\n\n readline.emitKeypressEvents(process.stdin);\n process.stdin.setRawMode(true);\n process.stdin.resume();\n\n // Hide cursor during prompt\n process.stdout.write(\"\\u001B[?25l\");\n\n const render = (firstTime = false) => {\n if (!firstTime) {\n process.stdout.write(\"\\u001B[3A\\r\");\n }\n\n process.stdout.write(\n `\\r\\u001B[K ${chalk.green(\"✔\")} ${chalk.bold(question)}\\n`\n );\n\n // Print options vertically\n const yesPrefix = value ? chalk.cyan(\"❯ \") : \" \";\n const yesText = value ? chalk.cyan.bold(\"Yes\") : chalk.dim(\"Yes\");\n process.stdout.write(`\\r\\u001B[K${yesPrefix}${yesText}\\n`);\n\n const noPrefix = value ? \" \" : chalk.cyan(\"❯ \");\n const noText = value ? chalk.dim(\"No\") : chalk.cyan.bold(\"No\");\n process.stdout.write(`\\r\\u001B[K${noPrefix}${noText}\\n`);\n };\n\n render(true);\n\n const handleKeypress = (str: string, key: KeypressKey) => {\n const cleanup = () => {\n process.stdin.removeListener(\"keypress\", handleKeypress);\n if (process.stdin.isTTY) {\n process.stdin.setRawMode(false);\n }\n process.stdin.pause();\n process.stdout.write(\"\\u001B[?25h\");\n };\n\n if (\n key.name === \"left\" ||\n key.name === \"right\" ||\n key.name === \"up\" ||\n key.name === \"down\" ||\n key.name === \"h\" ||\n key.name === \"l\" ||\n key.name === \"j\" ||\n key.name === \"k\"\n ) {\n value = !value;\n render();\n } else if (str === \"y\" || str === \"Y\") {\n value = true;\n render();\n } else if (str === \"n\" || str === \"N\") {\n value = false;\n render();\n } else if (\n key.name === \"return\" ||\n key.name === \"enter\" ||\n str === \"\\r\" ||\n str === \"\\n\"\n ) {\n cleanup();\n // Overwrite and resolve vertical layout cleanly\n process.stdout.write(\"\\u001B[3A\\r\\u001B[K\");\n process.stdout.write(\n ` ${chalk.green(\"✔\")} ${chalk.bold(question)} › ${value ? chalk.cyan(\"Yes\") : chalk.dim(\"No\")}\\n`\n );\n process.stdout.write(\"\\r\\u001B[K\\n\");\n process.stdout.write(\"\\r\\u001B[K\\n\");\n process.stdout.write(\"\\u001B[2A\");\n resolve(value);\n } else if (key.ctrl && key.name === \"c\") {\n cleanup();\n process.stdout.write(\"\\n\");\n process.exit(130);\n }\n };\n\n process.stdin.on(\"keypress\", handleKeypress);\n });\n /* eslint-enable promise/avoid-new */\n};\n\nconst askSelect = (\n question: string,\n options: string[],\n defaultIndex = 0\n): Promise<number> => {\n const isRaw = process.stdin.isTTY;\n if (!isRaw) {\n return Promise.resolve(defaultIndex);\n }\n\n /* eslint-disable promise/avoid-new */\n return new Promise((resolve) => {\n let index = defaultIndex;\n\n readline.emitKeypressEvents(process.stdin);\n process.stdin.setRawMode(true);\n process.stdin.resume();\n\n // Hide cursor during prompt\n process.stdout.write(\"\\u001B[?25l\");\n\n const render = (firstTime = false) => {\n if (!firstTime) {\n // Move back up to overwrite previous render\n process.stdout.write(`\\u001B[${options.length + 1}A\\r`);\n }\n\n process.stdout.write(\n `\\r\\u001B[K ${chalk.green(\"✔\")} ${chalk.bold(question)}\\n`\n );\n\n // Print options\n let i = 0;\n for (const option of options) {\n const isSelected = i === index;\n const prefix = isSelected ? chalk.cyan(\"❯ \") : \" \";\n const text = isSelected ? chalk.cyan.bold(option) : chalk.dim(option);\n process.stdout.write(`\\r\\u001B[K${prefix}${text}\\n`);\n i += 1;\n }\n };\n\n render(true);\n\n const handleKeypress = (str: string, key: KeypressKey) => {\n const cleanup = () => {\n process.stdin.removeListener(\"keypress\", handleKeypress);\n if (process.stdin.isTTY) {\n process.stdin.setRawMode(false);\n }\n process.stdin.pause();\n process.stdout.write(\"\\u001B[?25h\");\n };\n\n if (key.name === \"up\" || key.name === \"k\") {\n index = (index - 1 + options.length) % options.length;\n render();\n } else if (key.name === \"down\" || key.name === \"j\") {\n index = (index + 1) % options.length;\n render();\n } else if (\n key.name === \"return\" ||\n key.name === \"enter\" ||\n str === \"\\r\" ||\n str === \"\\n\"\n ) {\n cleanup();\n // Overwrite and resolve\n process.stdout.write(`\\u001B[${options.length + 1}A\\r\\u001B[K`);\n process.stdout.write(\n ` ${chalk.green(\"✔\")} ${chalk.bold(question)} › ${chalk.cyan(options[index])}\\n`\n );\n for (const _ of options) {\n process.stdout.write(\"\\r\\u001B[K\\n\");\n }\n process.stdout.write(`\\u001B[${options.length}A`);\n resolve(index);\n } else if (key.ctrl && key.name === \"c\") {\n cleanup();\n process.stdout.write(\"\\n\");\n process.exit(130);\n }\n };\n\n process.stdin.on(\"keypress\", handleKeypress);\n });\n /* eslint-enable promise/avoid-new */\n};\n\nconst runInteractiveWizard = async (): Promise<void> => {\n try {\n const addGhActions = await askConfirm(\n \"Add Docker Doctor to GitHub Actions?\"\n );\n if (addGhActions) {\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 nextChoice = await askSelect(\"What would you like to do next?\", [\n \"View rules list\",\n \"Skip\",\n ]);\n\n if (nextChoice === 0) {\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 }\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 filterDiagnostics = (\n diagnostics: Diagnostic[],\n categories?: Record<string, RuleSeverity>\n): Diagnostic[] => {\n if (!categories) {\n return diagnostics;\n }\n return diagnostics.filter((d) => {\n const ruleDef = findRule(d.rule);\n if (ruleDef) {\n const catSeverity = categories[ruleDef.category];\n if (catSeverity === \"off\") {\n return false;\n }\n }\n return true;\n });\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 const isSilent = options.score || options.json;\n\n // Helper to safely show terminal cursor\n const restoreCursor = (): void => {\n if (process.stdout.isTTY && !isSilent) {\n process.stdout.write(\"\\u001B[?25h\");\n }\n };\n\n // Set signal handlers to restore cursor on aborts\n process.on(\"exit\", restoreCursor);\n process.once(\"SIGINT\", () => {\n restoreCursor();\n process.exit(130);\n });\n process.once(\"SIGTERM\", () => {\n restoreCursor();\n process.exit(143);\n });\n\n try {\n const rootDir = path.resolve(dir);\n const startTime = Date.now();\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 // Hide cursor during progress/spinner\n process.stdout.write(\"\\u001B[?25l\");\n process.stdout.write(`${chalk.cyan(spinnerFrames[0])} ${statusText}`);\n spinnerInterval = setInterval(() => {\n process.stdout.write(\n `\\r\\u001B[K${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 ...(project.dockerignores || []),\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 const filteredDiagnostics = filterDiagnostics(\n diagnostics,\n config.categories\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 spinner line and restore cursor\n process.stdout.write(\"\\r\\u001B[K\\u001B[?25h\");\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 && process.stdin.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\\u001B[?25h\");\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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"}
1
+ {"version":3,"file":"cli.cjs","names":["border","findRule","path","fs","allRules","runDockerfileRules","parseDockerfile","runComposeRules","parseCompose","findRule","Command","packageJson","loadConfig","discoverProject","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 warningsCount: number,\n errorsCount: number\n): Promise<void> => {\n const { isTTY } = process.stdout;\n const shouldAnimate =\n isTTY &&\n !process.env.CI &&\n !process.env.NO_ANIMATION &&\n process.env.TERM !== \"dumb\" &&\n process.env.NODE_ENV !== \"test\";\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 shareUrl = `https://docker-doctor.vercel.app/share?s=${score}&w=${warningsCount}&e=${errorsCount}`;\n\n if (shouldAnimate) {\n const frameCount = 20;\n const frameDelay = 15;\n\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://docker-doctor.vercel.app)\")}\\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://docker-doctor.vercel.app)\")}`\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 let warningsCount = 0;\n let errorsCount = 0;\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 if (d.severity === \"warning\") {\n warningsCount += 1;\n } else if (d.severity === \"error\") {\n errorsCount += 1;\n }\n }\n\n printDiagnostics(diagnostics, verbose, fileContents, categoryIssueCounts);\n\n await printScoreBox(\n score,\n label,\n categoryIssueCounts,\n warningsCount,\n errorsCount\n );\n};\n","import fs from \"node:fs/promises\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport readline from \"node:readline\";\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\";\n\ninterface KeypressKey {\n name?: string;\n ctrl?: boolean;\n meta?: boolean;\n shift?: boolean;\n}\n\nconst askConfirm = (question: string, defaultYes = false): Promise<boolean> => {\n const isRaw = process.stdin.isTTY;\n if (!isRaw) {\n return Promise.resolve(defaultYes);\n }\n\n /* eslint-disable promise/avoid-new */\n return new Promise((resolve) => {\n let value = defaultYes;\n\n readline.emitKeypressEvents(process.stdin);\n process.stdin.setRawMode(true);\n process.stdin.resume();\n\n // Hide cursor during prompt\n process.stdout.write(\"\\u001B[?25l\");\n\n const render = (firstTime = false) => {\n if (!firstTime) {\n process.stdout.write(\"\\u001B[3A\\r\");\n }\n\n process.stdout.write(\n `\\r\\u001B[K ${chalk.green(\"✔\")} ${chalk.bold(question)}\\n`\n );\n\n // Print options vertically\n const yesPrefix = value ? chalk.cyan(\"❯ \") : \" \";\n const yesText = value ? chalk.cyan.bold(\"Yes\") : chalk.dim(\"Yes\");\n process.stdout.write(`\\r\\u001B[K${yesPrefix}${yesText}\\n`);\n\n const noPrefix = value ? \" \" : chalk.cyan(\"❯ \");\n const noText = value ? chalk.dim(\"No\") : chalk.cyan.bold(\"No\");\n process.stdout.write(`\\r\\u001B[K${noPrefix}${noText}\\n`);\n };\n\n render(true);\n\n const handleKeypress = (str: string, key: KeypressKey) => {\n const cleanup = () => {\n process.stdin.removeListener(\"keypress\", handleKeypress);\n if (process.stdin.isTTY) {\n process.stdin.setRawMode(false);\n }\n process.stdin.pause();\n process.stdout.write(\"\\u001B[?25h\");\n };\n\n if (\n key.name === \"left\" ||\n key.name === \"right\" ||\n key.name === \"up\" ||\n key.name === \"down\" ||\n key.name === \"h\" ||\n key.name === \"l\" ||\n key.name === \"j\" ||\n key.name === \"k\"\n ) {\n value = !value;\n render();\n } else if (str === \"y\" || str === \"Y\") {\n value = true;\n render();\n } else if (str === \"n\" || str === \"N\") {\n value = false;\n render();\n } else if (\n key.name === \"return\" ||\n key.name === \"enter\" ||\n str === \"\\r\" ||\n str === \"\\n\"\n ) {\n cleanup();\n // Overwrite and resolve vertical layout cleanly\n process.stdout.write(\"\\u001B[3A\\r\\u001B[K\");\n process.stdout.write(\n ` ${chalk.green(\"✔\")} ${chalk.bold(question)} › ${value ? chalk.cyan(\"Yes\") : chalk.dim(\"No\")}\\n`\n );\n process.stdout.write(\"\\r\\u001B[K\\n\");\n process.stdout.write(\"\\r\\u001B[K\\n\");\n process.stdout.write(\"\\u001B[2A\");\n resolve(value);\n } else if (key.ctrl && key.name === \"c\") {\n cleanup();\n process.stdout.write(\"\\n\");\n process.exit(130);\n }\n };\n\n process.stdin.on(\"keypress\", handleKeypress);\n });\n /* eslint-enable promise/avoid-new */\n};\n\nconst askSelect = (\n question: string,\n options: string[],\n defaultIndex = 0\n): Promise<number> => {\n const isRaw = process.stdin.isTTY;\n if (!isRaw) {\n return Promise.resolve(defaultIndex);\n }\n\n /* eslint-disable promise/avoid-new */\n return new Promise((resolve) => {\n let index = defaultIndex;\n\n readline.emitKeypressEvents(process.stdin);\n process.stdin.setRawMode(true);\n process.stdin.resume();\n\n // Hide cursor during prompt\n process.stdout.write(\"\\u001B[?25l\");\n\n const render = (firstTime = false) => {\n if (!firstTime) {\n // Move back up to overwrite previous render\n process.stdout.write(`\\u001B[${options.length + 1}A\\r`);\n }\n\n process.stdout.write(\n `\\r\\u001B[K ${chalk.green(\"✔\")} ${chalk.bold(question)}\\n`\n );\n\n // Print options\n let i = 0;\n for (const option of options) {\n const isSelected = i === index;\n const prefix = isSelected ? chalk.cyan(\"❯ \") : \" \";\n const text = isSelected ? chalk.cyan.bold(option) : chalk.dim(option);\n process.stdout.write(`\\r\\u001B[K${prefix}${text}\\n`);\n i += 1;\n }\n };\n\n render(true);\n\n const handleKeypress = (str: string, key: KeypressKey) => {\n const cleanup = () => {\n process.stdin.removeListener(\"keypress\", handleKeypress);\n if (process.stdin.isTTY) {\n process.stdin.setRawMode(false);\n }\n process.stdin.pause();\n process.stdout.write(\"\\u001B[?25h\");\n };\n\n if (key.name === \"up\" || key.name === \"k\") {\n index = (index - 1 + options.length) % options.length;\n render();\n } else if (key.name === \"down\" || key.name === \"j\") {\n index = (index + 1) % options.length;\n render();\n } else if (\n key.name === \"return\" ||\n key.name === \"enter\" ||\n str === \"\\r\" ||\n str === \"\\n\"\n ) {\n cleanup();\n // Overwrite and resolve\n process.stdout.write(`\\u001B[${options.length + 1}A\\r\\u001B[K`);\n process.stdout.write(\n ` ${chalk.green(\"✔\")} ${chalk.bold(question)} › ${chalk.cyan(options[index])}\\n`\n );\n for (const _ of options) {\n process.stdout.write(\"\\r\\u001B[K\\n\");\n }\n process.stdout.write(`\\u001B[${options.length}A`);\n resolve(index);\n } else if (key.ctrl && key.name === \"c\") {\n cleanup();\n process.stdout.write(\"\\n\");\n process.exit(130);\n }\n };\n\n process.stdin.on(\"keypress\", handleKeypress);\n });\n /* eslint-enable promise/avoid-new */\n};\n\nconst runInteractiveWizard = async (): Promise<void> => {\n try {\n const addGhActions = await askConfirm(\n \"Add Docker Doctor to GitHub Actions?\"\n );\n if (addGhActions) {\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 nextChoice = await askSelect(\"What would you like to do next?\", [\n \"View rules list\",\n \"Skip\",\n ]);\n\n if (nextChoice === 0) {\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 }\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 filterDiagnostics = (\n diagnostics: Diagnostic[],\n categories?: Record<string, RuleSeverity>\n): Diagnostic[] => {\n if (!categories) {\n return diagnostics;\n }\n return diagnostics.filter((d) => {\n const ruleDef = findRule(d.rule);\n if (ruleDef) {\n const catSeverity = categories[ruleDef.category];\n if (catSeverity === \"off\") {\n return false;\n }\n }\n return true;\n });\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 const isSilent = options.score || options.json;\n\n // Helper to safely show terminal cursor\n const restoreCursor = (): void => {\n if (process.stdout.isTTY && !isSilent) {\n process.stdout.write(\"\\u001B[?25h\");\n }\n };\n\n // Set signal handlers to restore cursor on aborts\n process.on(\"exit\", restoreCursor);\n process.once(\"SIGINT\", () => {\n restoreCursor();\n process.exit(130);\n });\n process.once(\"SIGTERM\", () => {\n restoreCursor();\n process.exit(143);\n });\n\n try {\n const rootDir = path.resolve(dir);\n const startTime = Date.now();\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 // Hide cursor during progress/spinner\n process.stdout.write(\"\\u001B[?25l\");\n process.stdout.write(`${chalk.cyan(spinnerFrames[0])} ${statusText}`);\n spinnerInterval = setInterval(() => {\n process.stdout.write(\n `\\r\\u001B[K${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 ...(project.dockerignores || []),\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 const filteredDiagnostics = filterDiagnostics(\n diagnostics,\n config.categories\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 spinner line and restore cursor\n process.stdout.write(\"\\r\\u001B[K\\u001B[?25h\");\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 && process.stdin.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\\u001B[?25h\");\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.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { a as runDockerfileRules, c as parseCompose, d as package_default, i as runComposeRules, l as parseDockerfile, n as calculateScore, o as allRules, r as loadConfig, s as findRule, t as toJsonReport, u as discoverProject } from "./src-Bt3AB-Xl.mjs";
2
+ import { a as runDockerfileRules, c as parseCompose, d as package_default, i as runComposeRules, l as parseDockerfile, n as calculateScore, o as allRules, r as loadConfig, s as findRule, t as toJsonReport, u as discoverProject } from "./src-DvNFg6Nz.mjs";
3
3
  import fs from "node:fs/promises";
4
4
  import path from "node:path";
5
5
  import os from "node:os";
@@ -108,7 +108,7 @@ const getWhaleMascot = (score, border) => {
108
108
  ];
109
109
  };
110
110
  const easeOutCubic = (x) => 1 - (1 - x) ** 3;
111
- const printScoreBox = async (score, label, categoryIssueCounts) => {
111
+ const printScoreBox = async (score, label, categoryIssueCounts, warningsCount, errorsCount) => {
112
112
  const { isTTY } = process.stdout;
113
113
  const shouldAnimate = isTTY && !process.env.CI && !process.env.NO_ANIMATION && process.env.TERM !== "dumb" && process.env.NODE_ENV !== "test";
114
114
  let scoreColor = chalk.red.bold;
@@ -116,7 +116,7 @@ const printScoreBox = async (score, label, categoryIssueCounts) => {
116
116
  else if (score >= 75) scoreColor = chalk.yellow.bold;
117
117
  else if (score >= 50) scoreColor = chalk.magenta.bold;
118
118
  const whaleLines = getWhaleMascot(score, scoreColor);
119
- const shareUrl = `https://github.com/PunGrumpy/docker-doctor/share?s=${score}&w=${Object.values(categoryIssueCounts).reduce((a, b) => a + b, 0)}`;
119
+ const shareUrl = `https://docker-doctor.vercel.app/share?s=${score}&w=${warningsCount}&e=${errorsCount}`;
120
120
  if (shouldAnimate) {
121
121
  const frameCount = 20;
122
122
  const frameDelay = 15;
@@ -130,7 +130,7 @@ const printScoreBox = async (score, label, categoryIssueCounts) => {
130
130
  const bar = scoreColor("█".repeat(filledBlocks)) + chalk.dim("░".repeat(emptyBlocks));
131
131
  if (frame > 0) process.stdout.write("\x1B[4A\r");
132
132
  else console.log();
133
- process.stdout.write(` ${whaleLines[0]} ${scoreColor(`${currentScore} / 100`)} ${scoreColor(label)}\n ${whaleLines[1]} ${bar}\n ${whaleLines[2]} ${chalk.dim("Docker Doctor (https://github.com/PunGrumpy/docker-doctor)")}\n ${whaleLines[3]}\n`);
133
+ process.stdout.write(` ${whaleLines[0]} ${scoreColor(`${currentScore} / 100`)} ${scoreColor(label)}\n ${whaleLines[1]} ${bar}\n ${whaleLines[2]} ${chalk.dim("Docker Doctor (https://docker-doctor.vercel.app)")}\n ${whaleLines[3]}\n`);
134
134
  if (frame < frameCount) await setTimeout(frameDelay);
135
135
  }
136
136
  } finally {
@@ -142,7 +142,7 @@ const printScoreBox = async (score, label, categoryIssueCounts) => {
142
142
  const bar = scoreColor("█".repeat(filledBlocks)) + chalk.dim("░".repeat(emptyBlocks));
143
143
  console.log(`\n ${whaleLines[0]} ${scoreColor(`${score} / 100`)} ${scoreColor(label)}`);
144
144
  console.log(` ${whaleLines[1]} ${bar}`);
145
- console.log(` ${whaleLines[2]} ${chalk.dim("Docker Doctor (https://github.com/PunGrumpy/docker-doctor)")}`);
145
+ console.log(` ${whaleLines[2]} ${chalk.dim("Docker Doctor (https://docker-doctor.vercel.app)")}`);
146
146
  console.log(` ${whaleLines[3]}`);
147
147
  }
148
148
  console.log(`\n ${chalk.dim("────────────────────────────────────────────────────────────")}\n`);
@@ -167,12 +167,16 @@ const formatTerminal = async (diagnostics, score, label, project, verbose = fals
167
167
  Performance: 0,
168
168
  Security: 0
169
169
  };
170
+ let warningsCount = 0;
171
+ let errorsCount = 0;
170
172
  for (const d of diagnostics) {
171
173
  const category = findRule(d.rule)?.category || "Best Practices";
172
174
  categoryIssueCounts[category] = (categoryIssueCounts[category] || 0) + 1;
175
+ if (d.severity === "warning") warningsCount += 1;
176
+ else if (d.severity === "error") errorsCount += 1;
173
177
  }
174
178
  printDiagnostics(diagnostics, verbose, fileContents, categoryIssueCounts);
175
- await printScoreBox(score, label, categoryIssueCounts);
179
+ await printScoreBox(score, label, categoryIssueCounts, warningsCount, errorsCount);
176
180
  };
177
181
 
178
182
  //#endregion
package/dist/cli.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.mjs","names":["border","packageJson"],"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 shouldAnimate =\n isTTY &&\n !process.env.CI &&\n !process.env.NO_ANIMATION &&\n process.env.TERM !== \"dumb\" &&\n process.env.NODE_ENV !== \"test\";\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 (shouldAnimate) {\n const frameCount = 20;\n const frameDelay = 15;\n\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 readline from \"node:readline\";\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\";\n\ninterface KeypressKey {\n name?: string;\n ctrl?: boolean;\n meta?: boolean;\n shift?: boolean;\n}\n\nconst askConfirm = (question: string, defaultYes = false): Promise<boolean> => {\n const isRaw = process.stdin.isTTY;\n if (!isRaw) {\n return Promise.resolve(defaultYes);\n }\n\n /* eslint-disable promise/avoid-new */\n return new Promise((resolve) => {\n let value = defaultYes;\n\n readline.emitKeypressEvents(process.stdin);\n process.stdin.setRawMode(true);\n process.stdin.resume();\n\n // Hide cursor during prompt\n process.stdout.write(\"\\u001B[?25l\");\n\n const render = (firstTime = false) => {\n if (!firstTime) {\n process.stdout.write(\"\\u001B[3A\\r\");\n }\n\n process.stdout.write(\n `\\r\\u001B[K ${chalk.green(\"✔\")} ${chalk.bold(question)}\\n`\n );\n\n // Print options vertically\n const yesPrefix = value ? chalk.cyan(\"❯ \") : \" \";\n const yesText = value ? chalk.cyan.bold(\"Yes\") : chalk.dim(\"Yes\");\n process.stdout.write(`\\r\\u001B[K${yesPrefix}${yesText}\\n`);\n\n const noPrefix = value ? \" \" : chalk.cyan(\"❯ \");\n const noText = value ? chalk.dim(\"No\") : chalk.cyan.bold(\"No\");\n process.stdout.write(`\\r\\u001B[K${noPrefix}${noText}\\n`);\n };\n\n render(true);\n\n const handleKeypress = (str: string, key: KeypressKey) => {\n const cleanup = () => {\n process.stdin.removeListener(\"keypress\", handleKeypress);\n if (process.stdin.isTTY) {\n process.stdin.setRawMode(false);\n }\n process.stdin.pause();\n process.stdout.write(\"\\u001B[?25h\");\n };\n\n if (\n key.name === \"left\" ||\n key.name === \"right\" ||\n key.name === \"up\" ||\n key.name === \"down\" ||\n key.name === \"h\" ||\n key.name === \"l\" ||\n key.name === \"j\" ||\n key.name === \"k\"\n ) {\n value = !value;\n render();\n } else if (str === \"y\" || str === \"Y\") {\n value = true;\n render();\n } else if (str === \"n\" || str === \"N\") {\n value = false;\n render();\n } else if (\n key.name === \"return\" ||\n key.name === \"enter\" ||\n str === \"\\r\" ||\n str === \"\\n\"\n ) {\n cleanup();\n // Overwrite and resolve vertical layout cleanly\n process.stdout.write(\"\\u001B[3A\\r\\u001B[K\");\n process.stdout.write(\n ` ${chalk.green(\"✔\")} ${chalk.bold(question)} › ${value ? chalk.cyan(\"Yes\") : chalk.dim(\"No\")}\\n`\n );\n process.stdout.write(\"\\r\\u001B[K\\n\");\n process.stdout.write(\"\\r\\u001B[K\\n\");\n process.stdout.write(\"\\u001B[2A\");\n resolve(value);\n } else if (key.ctrl && key.name === \"c\") {\n cleanup();\n process.stdout.write(\"\\n\");\n process.exit(130);\n }\n };\n\n process.stdin.on(\"keypress\", handleKeypress);\n });\n /* eslint-enable promise/avoid-new */\n};\n\nconst askSelect = (\n question: string,\n options: string[],\n defaultIndex = 0\n): Promise<number> => {\n const isRaw = process.stdin.isTTY;\n if (!isRaw) {\n return Promise.resolve(defaultIndex);\n }\n\n /* eslint-disable promise/avoid-new */\n return new Promise((resolve) => {\n let index = defaultIndex;\n\n readline.emitKeypressEvents(process.stdin);\n process.stdin.setRawMode(true);\n process.stdin.resume();\n\n // Hide cursor during prompt\n process.stdout.write(\"\\u001B[?25l\");\n\n const render = (firstTime = false) => {\n if (!firstTime) {\n // Move back up to overwrite previous render\n process.stdout.write(`\\u001B[${options.length + 1}A\\r`);\n }\n\n process.stdout.write(\n `\\r\\u001B[K ${chalk.green(\"✔\")} ${chalk.bold(question)}\\n`\n );\n\n // Print options\n let i = 0;\n for (const option of options) {\n const isSelected = i === index;\n const prefix = isSelected ? chalk.cyan(\"❯ \") : \" \";\n const text = isSelected ? chalk.cyan.bold(option) : chalk.dim(option);\n process.stdout.write(`\\r\\u001B[K${prefix}${text}\\n`);\n i += 1;\n }\n };\n\n render(true);\n\n const handleKeypress = (str: string, key: KeypressKey) => {\n const cleanup = () => {\n process.stdin.removeListener(\"keypress\", handleKeypress);\n if (process.stdin.isTTY) {\n process.stdin.setRawMode(false);\n }\n process.stdin.pause();\n process.stdout.write(\"\\u001B[?25h\");\n };\n\n if (key.name === \"up\" || key.name === \"k\") {\n index = (index - 1 + options.length) % options.length;\n render();\n } else if (key.name === \"down\" || key.name === \"j\") {\n index = (index + 1) % options.length;\n render();\n } else if (\n key.name === \"return\" ||\n key.name === \"enter\" ||\n str === \"\\r\" ||\n str === \"\\n\"\n ) {\n cleanup();\n // Overwrite and resolve\n process.stdout.write(`\\u001B[${options.length + 1}A\\r\\u001B[K`);\n process.stdout.write(\n ` ${chalk.green(\"✔\")} ${chalk.bold(question)} › ${chalk.cyan(options[index])}\\n`\n );\n for (const _ of options) {\n process.stdout.write(\"\\r\\u001B[K\\n\");\n }\n process.stdout.write(`\\u001B[${options.length}A`);\n resolve(index);\n } else if (key.ctrl && key.name === \"c\") {\n cleanup();\n process.stdout.write(\"\\n\");\n process.exit(130);\n }\n };\n\n process.stdin.on(\"keypress\", handleKeypress);\n });\n /* eslint-enable promise/avoid-new */\n};\n\nconst runInteractiveWizard = async (): Promise<void> => {\n try {\n const addGhActions = await askConfirm(\n \"Add Docker Doctor to GitHub Actions?\"\n );\n if (addGhActions) {\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 nextChoice = await askSelect(\"What would you like to do next?\", [\n \"View rules list\",\n \"Skip\",\n ]);\n\n if (nextChoice === 0) {\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 }\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 filterDiagnostics = (\n diagnostics: Diagnostic[],\n categories?: Record<string, RuleSeverity>\n): Diagnostic[] => {\n if (!categories) {\n return diagnostics;\n }\n return diagnostics.filter((d) => {\n const ruleDef = findRule(d.rule);\n if (ruleDef) {\n const catSeverity = categories[ruleDef.category];\n if (catSeverity === \"off\") {\n return false;\n }\n }\n return true;\n });\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 const isSilent = options.score || options.json;\n\n // Helper to safely show terminal cursor\n const restoreCursor = (): void => {\n if (process.stdout.isTTY && !isSilent) {\n process.stdout.write(\"\\u001B[?25h\");\n }\n };\n\n // Set signal handlers to restore cursor on aborts\n process.on(\"exit\", restoreCursor);\n process.once(\"SIGINT\", () => {\n restoreCursor();\n process.exit(130);\n });\n process.once(\"SIGTERM\", () => {\n restoreCursor();\n process.exit(143);\n });\n\n try {\n const rootDir = path.resolve(dir);\n const startTime = Date.now();\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 // Hide cursor during progress/spinner\n process.stdout.write(\"\\u001B[?25l\");\n process.stdout.write(`${chalk.cyan(spinnerFrames[0])} ${statusText}`);\n spinnerInterval = setInterval(() => {\n process.stdout.write(\n `\\r\\u001B[K${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 ...(project.dockerignores || []),\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 const filteredDiagnostics = filterDiagnostics(\n diagnostics,\n config.categories\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 spinner line and restore cursor\n process.stdout.write(\"\\r\\u001B[K\\u001B[?25h\");\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 && process.stdin.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\\u001B[?25h\");\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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"}
1
+ {"version":3,"file":"cli.mjs","names":["border","packageJson"],"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 warningsCount: number,\n errorsCount: number\n): Promise<void> => {\n const { isTTY } = process.stdout;\n const shouldAnimate =\n isTTY &&\n !process.env.CI &&\n !process.env.NO_ANIMATION &&\n process.env.TERM !== \"dumb\" &&\n process.env.NODE_ENV !== \"test\";\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 shareUrl = `https://docker-doctor.vercel.app/share?s=${score}&w=${warningsCount}&e=${errorsCount}`;\n\n if (shouldAnimate) {\n const frameCount = 20;\n const frameDelay = 15;\n\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://docker-doctor.vercel.app)\")}\\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://docker-doctor.vercel.app)\")}`\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 let warningsCount = 0;\n let errorsCount = 0;\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 if (d.severity === \"warning\") {\n warningsCount += 1;\n } else if (d.severity === \"error\") {\n errorsCount += 1;\n }\n }\n\n printDiagnostics(diagnostics, verbose, fileContents, categoryIssueCounts);\n\n await printScoreBox(\n score,\n label,\n categoryIssueCounts,\n warningsCount,\n errorsCount\n );\n};\n","import fs from \"node:fs/promises\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport readline from \"node:readline\";\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\";\n\ninterface KeypressKey {\n name?: string;\n ctrl?: boolean;\n meta?: boolean;\n shift?: boolean;\n}\n\nconst askConfirm = (question: string, defaultYes = false): Promise<boolean> => {\n const isRaw = process.stdin.isTTY;\n if (!isRaw) {\n return Promise.resolve(defaultYes);\n }\n\n /* eslint-disable promise/avoid-new */\n return new Promise((resolve) => {\n let value = defaultYes;\n\n readline.emitKeypressEvents(process.stdin);\n process.stdin.setRawMode(true);\n process.stdin.resume();\n\n // Hide cursor during prompt\n process.stdout.write(\"\\u001B[?25l\");\n\n const render = (firstTime = false) => {\n if (!firstTime) {\n process.stdout.write(\"\\u001B[3A\\r\");\n }\n\n process.stdout.write(\n `\\r\\u001B[K ${chalk.green(\"✔\")} ${chalk.bold(question)}\\n`\n );\n\n // Print options vertically\n const yesPrefix = value ? chalk.cyan(\"❯ \") : \" \";\n const yesText = value ? chalk.cyan.bold(\"Yes\") : chalk.dim(\"Yes\");\n process.stdout.write(`\\r\\u001B[K${yesPrefix}${yesText}\\n`);\n\n const noPrefix = value ? \" \" : chalk.cyan(\"❯ \");\n const noText = value ? chalk.dim(\"No\") : chalk.cyan.bold(\"No\");\n process.stdout.write(`\\r\\u001B[K${noPrefix}${noText}\\n`);\n };\n\n render(true);\n\n const handleKeypress = (str: string, key: KeypressKey) => {\n const cleanup = () => {\n process.stdin.removeListener(\"keypress\", handleKeypress);\n if (process.stdin.isTTY) {\n process.stdin.setRawMode(false);\n }\n process.stdin.pause();\n process.stdout.write(\"\\u001B[?25h\");\n };\n\n if (\n key.name === \"left\" ||\n key.name === \"right\" ||\n key.name === \"up\" ||\n key.name === \"down\" ||\n key.name === \"h\" ||\n key.name === \"l\" ||\n key.name === \"j\" ||\n key.name === \"k\"\n ) {\n value = !value;\n render();\n } else if (str === \"y\" || str === \"Y\") {\n value = true;\n render();\n } else if (str === \"n\" || str === \"N\") {\n value = false;\n render();\n } else if (\n key.name === \"return\" ||\n key.name === \"enter\" ||\n str === \"\\r\" ||\n str === \"\\n\"\n ) {\n cleanup();\n // Overwrite and resolve vertical layout cleanly\n process.stdout.write(\"\\u001B[3A\\r\\u001B[K\");\n process.stdout.write(\n ` ${chalk.green(\"✔\")} ${chalk.bold(question)} › ${value ? chalk.cyan(\"Yes\") : chalk.dim(\"No\")}\\n`\n );\n process.stdout.write(\"\\r\\u001B[K\\n\");\n process.stdout.write(\"\\r\\u001B[K\\n\");\n process.stdout.write(\"\\u001B[2A\");\n resolve(value);\n } else if (key.ctrl && key.name === \"c\") {\n cleanup();\n process.stdout.write(\"\\n\");\n process.exit(130);\n }\n };\n\n process.stdin.on(\"keypress\", handleKeypress);\n });\n /* eslint-enable promise/avoid-new */\n};\n\nconst askSelect = (\n question: string,\n options: string[],\n defaultIndex = 0\n): Promise<number> => {\n const isRaw = process.stdin.isTTY;\n if (!isRaw) {\n return Promise.resolve(defaultIndex);\n }\n\n /* eslint-disable promise/avoid-new */\n return new Promise((resolve) => {\n let index = defaultIndex;\n\n readline.emitKeypressEvents(process.stdin);\n process.stdin.setRawMode(true);\n process.stdin.resume();\n\n // Hide cursor during prompt\n process.stdout.write(\"\\u001B[?25l\");\n\n const render = (firstTime = false) => {\n if (!firstTime) {\n // Move back up to overwrite previous render\n process.stdout.write(`\\u001B[${options.length + 1}A\\r`);\n }\n\n process.stdout.write(\n `\\r\\u001B[K ${chalk.green(\"✔\")} ${chalk.bold(question)}\\n`\n );\n\n // Print options\n let i = 0;\n for (const option of options) {\n const isSelected = i === index;\n const prefix = isSelected ? chalk.cyan(\"❯ \") : \" \";\n const text = isSelected ? chalk.cyan.bold(option) : chalk.dim(option);\n process.stdout.write(`\\r\\u001B[K${prefix}${text}\\n`);\n i += 1;\n }\n };\n\n render(true);\n\n const handleKeypress = (str: string, key: KeypressKey) => {\n const cleanup = () => {\n process.stdin.removeListener(\"keypress\", handleKeypress);\n if (process.stdin.isTTY) {\n process.stdin.setRawMode(false);\n }\n process.stdin.pause();\n process.stdout.write(\"\\u001B[?25h\");\n };\n\n if (key.name === \"up\" || key.name === \"k\") {\n index = (index - 1 + options.length) % options.length;\n render();\n } else if (key.name === \"down\" || key.name === \"j\") {\n index = (index + 1) % options.length;\n render();\n } else if (\n key.name === \"return\" ||\n key.name === \"enter\" ||\n str === \"\\r\" ||\n str === \"\\n\"\n ) {\n cleanup();\n // Overwrite and resolve\n process.stdout.write(`\\u001B[${options.length + 1}A\\r\\u001B[K`);\n process.stdout.write(\n ` ${chalk.green(\"✔\")} ${chalk.bold(question)} › ${chalk.cyan(options[index])}\\n`\n );\n for (const _ of options) {\n process.stdout.write(\"\\r\\u001B[K\\n\");\n }\n process.stdout.write(`\\u001B[${options.length}A`);\n resolve(index);\n } else if (key.ctrl && key.name === \"c\") {\n cleanup();\n process.stdout.write(\"\\n\");\n process.exit(130);\n }\n };\n\n process.stdin.on(\"keypress\", handleKeypress);\n });\n /* eslint-enable promise/avoid-new */\n};\n\nconst runInteractiveWizard = async (): Promise<void> => {\n try {\n const addGhActions = await askConfirm(\n \"Add Docker Doctor to GitHub Actions?\"\n );\n if (addGhActions) {\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 nextChoice = await askSelect(\"What would you like to do next?\", [\n \"View rules list\",\n \"Skip\",\n ]);\n\n if (nextChoice === 0) {\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 }\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 filterDiagnostics = (\n diagnostics: Diagnostic[],\n categories?: Record<string, RuleSeverity>\n): Diagnostic[] => {\n if (!categories) {\n return diagnostics;\n }\n return diagnostics.filter((d) => {\n const ruleDef = findRule(d.rule);\n if (ruleDef) {\n const catSeverity = categories[ruleDef.category];\n if (catSeverity === \"off\") {\n return false;\n }\n }\n return true;\n });\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 const isSilent = options.score || options.json;\n\n // Helper to safely show terminal cursor\n const restoreCursor = (): void => {\n if (process.stdout.isTTY && !isSilent) {\n process.stdout.write(\"\\u001B[?25h\");\n }\n };\n\n // Set signal handlers to restore cursor on aborts\n process.on(\"exit\", restoreCursor);\n process.once(\"SIGINT\", () => {\n restoreCursor();\n process.exit(130);\n });\n process.once(\"SIGTERM\", () => {\n restoreCursor();\n process.exit(143);\n });\n\n try {\n const rootDir = path.resolve(dir);\n const startTime = Date.now();\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 // Hide cursor during progress/spinner\n process.stdout.write(\"\\u001B[?25l\");\n process.stdout.write(`${chalk.cyan(spinnerFrames[0])} ${statusText}`);\n spinnerInterval = setInterval(() => {\n process.stdout.write(\n `\\r\\u001B[K${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 ...(project.dockerignores || []),\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 const filteredDiagnostics = filterDiagnostics(\n diagnostics,\n config.categories\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 spinner line and restore cursor\n process.stdout.write(\"\\r\\u001B[K\\u001B[?25h\");\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 && process.stdin.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\\u001B[?25h\");\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/index.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
3
- const require_src = require('./src-BUYNqQd8.cjs');
3
+ const require_src = require('./src-O8FXMq8O.cjs');
4
4
 
5
5
  //#region src/index.ts
6
6
  const { version } = require_src.package_default;
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { a as runDockerfileRules, c as parseCompose, d as package_default, i as runComposeRules, l as parseDockerfile, n as calculateScore, o as allRules, r as loadConfig, s as findRule, t as toJsonReport, u as discoverProject } from "./src-Bt3AB-Xl.mjs";
2
+ import { a as runDockerfileRules, c as parseCompose, d as package_default, i as runComposeRules, l as parseDockerfile, n as calculateScore, o as allRules, r as loadConfig, s as findRule, t as toJsonReport, u as discoverProject } from "./src-DvNFg6Nz.mjs";
3
3
 
4
4
  //#region src/index.ts
5
5
  const { version } = package_default;
@@ -8,7 +8,7 @@ import * as Schema from "effect/Schema";
8
8
  //#region package.json
9
9
  var package_default = {
10
10
  name: "@docker-doctor/cli",
11
- version: "0.2.0",
11
+ version: "0.2.1",
12
12
  description: "Static analysis for Dockerfile and Docker Compose files",
13
13
  keywords: [
14
14
  "best-practices",
@@ -891,4 +891,4 @@ const toJsonReport = (diagnostics, score, label, project) => ({
891
891
 
892
892
  //#endregion
893
893
  export { runDockerfileRules as a, parseCompose as c, package_default as d, runComposeRules as i, parseDockerfile as l, calculateScore as n, allRules as o, loadConfig as r, findRule as s, toJsonReport as t, discoverProject as u };
894
- //# sourceMappingURL=src-Bt3AB-Xl.mjs.map
894
+ //# sourceMappingURL=src-DvNFg6Nz.mjs.map