@docker-doctor/cli 0.5.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cli.cjs +66 -13
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.mjs +66 -13
- package/dist/cli.mjs.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +7 -1
- package/dist/index.d.mts +7 -1
- package/dist/index.mjs +1 -1
- package/dist/{src-BOGzkp17.mjs → src-BLeXr_hR.mjs} +352 -81
- package/dist/src-BLeXr_hR.mjs.map +1 -0
- package/dist/{src-D3jzc3GX.cjs → src-BpAv-H0Z.cjs} +352 -81
- package/dist/src-BpAv-H0Z.cjs.map +1 -0
- package/package.json +2 -1
- package/dist/src-BOGzkp17.mjs.map +0 -1
- package/dist/src-D3jzc3GX.cjs.map +0 -1
package/dist/cli.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as runDockerfileRules, c as createComposeLocator, d as discoverProject, f as version, i as runComposeRules, l as parseCompose, n as calculateScore, o as allRules, r as loadConfig, s as findRule, t as toJsonReport, u as parseDockerfile } from "./src-
|
|
2
|
+
import { a as runDockerfileRules, c as createComposeLocator, d as discoverProject, f as version, i as runComposeRules, l as parseCompose, n as calculateScore, o as allRules, r as loadConfig, s as findRule, t as toJsonReport, u as parseDockerfile } from "./src-BLeXr_hR.mjs";
|
|
3
3
|
import fs from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import os from "node:os";
|
|
@@ -580,6 +580,10 @@ const writeDiagnosticsDirectory = async (diagnostics, report, rootDir) => {
|
|
|
580
580
|
...d,
|
|
581
581
|
file: sanitizePath(d.file),
|
|
582
582
|
message: sanitizeMessage(d.message)
|
|
583
|
+
})),
|
|
584
|
+
failures: report.failures.map((failure) => ({
|
|
585
|
+
file: sanitizePath(failure.file),
|
|
586
|
+
message: sanitizeMessage(failure.message)
|
|
583
587
|
}))
|
|
584
588
|
};
|
|
585
589
|
await fs.writeFile(path.join(dir, "diagnostics.json"), JSON.stringify({
|
|
@@ -790,13 +794,24 @@ const printDiagnostics = (diagnostics, verbose, fileContents, categoryIssueCount
|
|
|
790
794
|
console.log(` ${cat} › ${chalk.dim(issueLabel)}`);
|
|
791
795
|
}
|
|
792
796
|
console.log(`\n Run ${chalk.cyan("docker-doctor --verbose")} to list every error and warning`);
|
|
793
|
-
const
|
|
794
|
-
for (const d of diagnostics)
|
|
795
|
-
|
|
797
|
+
const findingsByRule = /* @__PURE__ */ new Map();
|
|
798
|
+
for (const d of diagnostics) {
|
|
799
|
+
const findings = findingsByRule.get(d.rule) ?? {
|
|
800
|
+
count: 0,
|
|
801
|
+
files: /* @__PURE__ */ new Set()
|
|
802
|
+
};
|
|
803
|
+
findings.count += 1;
|
|
804
|
+
findings.files.add(d.file);
|
|
805
|
+
findingsByRule.set(d.rule, findings);
|
|
806
|
+
}
|
|
807
|
+
const migrationRules = [...findingsByRule].filter(([, { count }]) => count >= 5);
|
|
796
808
|
if (migrationRules.length > 0) {
|
|
797
809
|
console.log();
|
|
798
810
|
console.log(` ${chalk.yellow("⚠ Migration-scale change: sample before you sweep")}`);
|
|
799
|
-
for (const [rule, count] of migrationRules)
|
|
811
|
+
for (const [rule, { count, files }] of migrationRules) {
|
|
812
|
+
const fileWord = files.size === 1 ? "file" : "files";
|
|
813
|
+
console.log(` ${chalk.cyan(rule)} ×${count} across ${files.size} ${fileWord}`);
|
|
814
|
+
}
|
|
800
815
|
console.log(` Fixing all of them at once is hard to review and prone to`);
|
|
801
816
|
console.log(` subtle mistakes across the whole repo. Fix a representative`);
|
|
802
817
|
console.log(` few first and confirm the recipe holds. Then get the code`);
|
|
@@ -851,7 +866,7 @@ const getWhaleMascot = (score, border) => {
|
|
|
851
866
|
];
|
|
852
867
|
};
|
|
853
868
|
const easeOutCubic = (x) => 1 - (1 - x) ** 3;
|
|
854
|
-
const printScoreBox = async (score, label,
|
|
869
|
+
const printScoreBox = async (score, label, warningsCount, errorsCount) => {
|
|
855
870
|
const { isTTY } = process.stdout;
|
|
856
871
|
const shouldAnimate = isTTY && !process.env.CI && !process.env.NO_ANIMATION && process.env.TERM !== "dumb" && process.env.NODE_ENV !== "test";
|
|
857
872
|
let scoreColor = chalk.red.bold;
|
|
@@ -925,7 +940,7 @@ const formatTerminal = async (diagnostics, score, label, project, verbose = fals
|
|
|
925
940
|
else if (d.severity === "error") errorsCount += 1;
|
|
926
941
|
}
|
|
927
942
|
printDiagnostics(diagnostics, verbose, fileContents, categoryIssueCounts);
|
|
928
|
-
await printScoreBox(score, label,
|
|
943
|
+
await printScoreBox(score, label, warningsCount, errorsCount);
|
|
929
944
|
};
|
|
930
945
|
|
|
931
946
|
//#endregion
|
|
@@ -951,11 +966,12 @@ permissions:
|
|
|
951
966
|
contents: read
|
|
952
967
|
pull-requests: write
|
|
953
968
|
issues: write
|
|
969
|
+
statuses: write
|
|
954
970
|
jobs:
|
|
955
971
|
docker-doctor:
|
|
956
972
|
runs-on: ubuntu-latest
|
|
957
973
|
steps:
|
|
958
|
-
- uses: actions/checkout@
|
|
974
|
+
- uses: actions/checkout@v7
|
|
959
975
|
- uses: ${options.actionRef}
|
|
960
976
|
`;
|
|
961
977
|
await fs.mkdir(workflowDir, { recursive: true });
|
|
@@ -1260,6 +1276,31 @@ const reportScanFailures = (failures) => {
|
|
|
1260
1276
|
console.error(`Docker Doctor could not analyze ${failures.length} ${fileWord}; the score below covers only the files that were analyzed.`);
|
|
1261
1277
|
for (const failure of failures) console.error(` - ${failure.file}: ${flattenMessage(failure.message)}`);
|
|
1262
1278
|
};
|
|
1279
|
+
const pathExists = async (filePath) => {
|
|
1280
|
+
try {
|
|
1281
|
+
await fs.access(filePath);
|
|
1282
|
+
return true;
|
|
1283
|
+
} catch {
|
|
1284
|
+
return false;
|
|
1285
|
+
}
|
|
1286
|
+
};
|
|
1287
|
+
const resolveConfigPath = async (configOption, rootDir) => {
|
|
1288
|
+
if (!configOption) return;
|
|
1289
|
+
const fromCwd = path.resolve(process.cwd(), configOption);
|
|
1290
|
+
if (await pathExists(fromCwd)) return fromCwd;
|
|
1291
|
+
const fromRoot = path.resolve(rootDir, configOption);
|
|
1292
|
+
if (fromRoot !== fromCwd && await pathExists(fromRoot)) {
|
|
1293
|
+
console.error(`Warning: --config ${configOption} was found relative to the scanned directory, not the current directory. Pass a path relative to the current directory or an absolute path.`);
|
|
1294
|
+
return fromRoot;
|
|
1295
|
+
}
|
|
1296
|
+
return fromCwd;
|
|
1297
|
+
};
|
|
1298
|
+
const findScanTargetProblem = async (rootDir) => {
|
|
1299
|
+
const rootStat = await fs.stat(rootDir).catch(() => null);
|
|
1300
|
+
if (rootStat === null) return `Directory not found: ${rootDir}`;
|
|
1301
|
+
if (!rootStat.isDirectory()) return `${rootDir} is not a directory. Pass the directory that contains your Dockerfile or Compose file.`;
|
|
1302
|
+
return null;
|
|
1303
|
+
};
|
|
1263
1304
|
const program = new Command();
|
|
1264
1305
|
program.name("docker-doctor").description("Static analysis for Dockerfile and Docker Compose files").version(version, "-V, --version", "display the version number");
|
|
1265
1306
|
program.argument("[dir]", "directory to scan", ".").option("-v, --verbose", "show verbose diagnostics description", false).option("-s, --score", "only output numeric health score", false).option("-j, --json", "output results as JSON report", false).option("-c, --config <path>", "custom config file path").action(async (dir, options) => {
|
|
@@ -1278,6 +1319,12 @@ program.argument("[dir]", "directory to scan", ".").option("-v, --verbose", "sho
|
|
|
1278
1319
|
});
|
|
1279
1320
|
try {
|
|
1280
1321
|
const rootDir = path.resolve(dir);
|
|
1322
|
+
const scanTargetProblem = await findScanTargetProblem(rootDir);
|
|
1323
|
+
if (scanTargetProblem !== null) {
|
|
1324
|
+
console.error(`Error: ${scanTargetProblem}`);
|
|
1325
|
+
process.exitCode = 1;
|
|
1326
|
+
return;
|
|
1327
|
+
}
|
|
1281
1328
|
const startTime = Date.now();
|
|
1282
1329
|
let statusText = "Discovering workspace...";
|
|
1283
1330
|
const spinnerFrames = [
|
|
@@ -1308,11 +1355,17 @@ program.argument("[dir]", "directory to scan", ".").option("-v, --verbose", "sho
|
|
|
1308
1355
|
try {
|
|
1309
1356
|
if (process.stdout.isTTY && !isSilent) await setTimeout(150);
|
|
1310
1357
|
setStatus("Loading configuration...");
|
|
1311
|
-
const
|
|
1358
|
+
const configPath = await resolveConfigPath(options.config, rootDir);
|
|
1359
|
+
const config = await loadConfig(rootDir, configPath, (message) => {
|
|
1312
1360
|
console.error(`Warning: ${message}`);
|
|
1313
1361
|
});
|
|
1314
1362
|
setStatus("Scanning workspace files...");
|
|
1315
|
-
const project = await discoverProject(rootDir, {
|
|
1363
|
+
const project = await discoverProject(rootDir, {
|
|
1364
|
+
ignoreFiles: config.ignore?.files,
|
|
1365
|
+
onSkippedDirectory: (skippedDir) => {
|
|
1366
|
+
console.error(`Warning: skipped unreadable directory ${skippedDir} (permission denied)`);
|
|
1367
|
+
}
|
|
1368
|
+
});
|
|
1316
1369
|
const fileContents = {};
|
|
1317
1370
|
const projectFilesList = [
|
|
1318
1371
|
...project.dockerfiles,
|
|
@@ -1337,7 +1390,7 @@ program.argument("[dir]", "directory to scan", ".").option("-v, --verbose", "sho
|
|
|
1337
1390
|
process.exitCode = scanExitCode(scanIncomplete, hasErrors);
|
|
1338
1391
|
return;
|
|
1339
1392
|
} else if (options.json) {
|
|
1340
|
-
const report = toJsonReport(diagnostics, score, label, project);
|
|
1393
|
+
const report = toJsonReport(diagnostics, score, label, project, failures);
|
|
1341
1394
|
console.log(JSON.stringify(report, null, 2));
|
|
1342
1395
|
process.exitCode = scanExitCode(scanIncomplete, hasErrors);
|
|
1343
1396
|
return;
|
|
@@ -1346,7 +1399,7 @@ program.argument("[dir]", "directory to scan", ".").option("-v, --verbose", "sho
|
|
|
1346
1399
|
let wizardOk = true;
|
|
1347
1400
|
if (process.stdout.isTTY && process.stdin.isTTY) wizardOk = await runInteractiveWizard({
|
|
1348
1401
|
diagnostics,
|
|
1349
|
-
report: toJsonReport(diagnostics, score, label, project),
|
|
1402
|
+
report: toJsonReport(diagnostics, score, label, project, failures),
|
|
1350
1403
|
rootDir
|
|
1351
1404
|
});
|
|
1352
1405
|
const exitCode = scanExitCode(scanIncomplete, hasErrors);
|
|
@@ -1434,7 +1487,7 @@ rules.command("list").description("list all available rules").action(() => {
|
|
|
1434
1487
|
}
|
|
1435
1488
|
});
|
|
1436
1489
|
rules.command("explain <rule>").description("explain a specific rule in detail").action((ruleKey) => {
|
|
1437
|
-
const rule = findRule(ruleKey);
|
|
1490
|
+
const rule = findRule(ruleKey) ?? findRule(`docker-doctor/${ruleKey}`);
|
|
1438
1491
|
if (!rule) {
|
|
1439
1492
|
console.error(`Rule '${ruleKey}' not found.`);
|
|
1440
1493
|
process.exitCode = 1;
|