@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/README.md
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
Your Dockerfiles are probably wrong. Docker Doctor finds out why.
|
|
16
16
|
|
|
17
|
-
Docker Doctor is an opinionated static analysis tool for Dockerfile and Docker Compose files. It scans your project, runs
|
|
17
|
+
Docker Doctor is an opinionated static analysis tool for Dockerfile and Docker Compose files. It scans your project, runs 33 rules across security, performance, best practices, Compose, and image size — then gives you a health score and fix guidance.
|
|
18
18
|
|
|
19
19
|
Works with any project that uses Docker.
|
|
20
20
|
|
package/dist/cli.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const require_src = require('./src-
|
|
2
|
+
const require_src = require('./src-BpAv-H0Z.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");
|
|
@@ -587,6 +587,10 @@ const writeDiagnosticsDirectory = async (diagnostics, report, rootDir) => {
|
|
|
587
587
|
...d,
|
|
588
588
|
file: sanitizePath(d.file),
|
|
589
589
|
message: sanitizeMessage(d.message)
|
|
590
|
+
})),
|
|
591
|
+
failures: report.failures.map((failure) => ({
|
|
592
|
+
file: sanitizePath(failure.file),
|
|
593
|
+
message: sanitizeMessage(failure.message)
|
|
590
594
|
}))
|
|
591
595
|
};
|
|
592
596
|
await node_fs_promises.default.writeFile(node_path.default.join(dir, "diagnostics.json"), JSON.stringify({
|
|
@@ -797,13 +801,24 @@ const printDiagnostics = (diagnostics, verbose, fileContents, categoryIssueCount
|
|
|
797
801
|
console.log(` ${cat} › ${chalk.dim(issueLabel)}`);
|
|
798
802
|
}
|
|
799
803
|
console.log(`\n Run ${chalk.cyan("docker-doctor --verbose")} to list every error and warning`);
|
|
800
|
-
const
|
|
801
|
-
for (const d of diagnostics)
|
|
802
|
-
|
|
804
|
+
const findingsByRule = /* @__PURE__ */ new Map();
|
|
805
|
+
for (const d of diagnostics) {
|
|
806
|
+
const findings = findingsByRule.get(d.rule) ?? {
|
|
807
|
+
count: 0,
|
|
808
|
+
files: /* @__PURE__ */ new Set()
|
|
809
|
+
};
|
|
810
|
+
findings.count += 1;
|
|
811
|
+
findings.files.add(d.file);
|
|
812
|
+
findingsByRule.set(d.rule, findings);
|
|
813
|
+
}
|
|
814
|
+
const migrationRules = [...findingsByRule].filter(([, { count }]) => count >= 5);
|
|
803
815
|
if (migrationRules.length > 0) {
|
|
804
816
|
console.log();
|
|
805
817
|
console.log(` ${chalk.yellow("⚠ Migration-scale change: sample before you sweep")}`);
|
|
806
|
-
for (const [rule, count] of migrationRules)
|
|
818
|
+
for (const [rule, { count, files }] of migrationRules) {
|
|
819
|
+
const fileWord = files.size === 1 ? "file" : "files";
|
|
820
|
+
console.log(` ${chalk.cyan(rule)} ×${count} across ${files.size} ${fileWord}`);
|
|
821
|
+
}
|
|
807
822
|
console.log(` Fixing all of them at once is hard to review and prone to`);
|
|
808
823
|
console.log(` subtle mistakes across the whole repo. Fix a representative`);
|
|
809
824
|
console.log(` few first and confirm the recipe holds. Then get the code`);
|
|
@@ -858,7 +873,7 @@ const getWhaleMascot = (score, border) => {
|
|
|
858
873
|
];
|
|
859
874
|
};
|
|
860
875
|
const easeOutCubic = (x) => 1 - (1 - x) ** 3;
|
|
861
|
-
const printScoreBox = async (score, label,
|
|
876
|
+
const printScoreBox = async (score, label, warningsCount, errorsCount) => {
|
|
862
877
|
const { isTTY } = process.stdout;
|
|
863
878
|
const shouldAnimate = isTTY && !process.env.CI && !process.env.NO_ANIMATION && process.env.TERM !== "dumb" && process.env.NODE_ENV !== "test";
|
|
864
879
|
let scoreColor = chalk.red.bold;
|
|
@@ -932,7 +947,7 @@ const formatTerminal = async (diagnostics, score, label, project, verbose = fals
|
|
|
932
947
|
else if (d.severity === "error") errorsCount += 1;
|
|
933
948
|
}
|
|
934
949
|
printDiagnostics(diagnostics, verbose, fileContents, categoryIssueCounts);
|
|
935
|
-
await printScoreBox(score, label,
|
|
950
|
+
await printScoreBox(score, label, warningsCount, errorsCount);
|
|
936
951
|
};
|
|
937
952
|
|
|
938
953
|
//#endregion
|
|
@@ -958,11 +973,12 @@ permissions:
|
|
|
958
973
|
contents: read
|
|
959
974
|
pull-requests: write
|
|
960
975
|
issues: write
|
|
976
|
+
statuses: write
|
|
961
977
|
jobs:
|
|
962
978
|
docker-doctor:
|
|
963
979
|
runs-on: ubuntu-latest
|
|
964
980
|
steps:
|
|
965
|
-
- uses: actions/checkout@
|
|
981
|
+
- uses: actions/checkout@v7
|
|
966
982
|
- uses: ${options.actionRef}
|
|
967
983
|
`;
|
|
968
984
|
await node_fs_promises.default.mkdir(workflowDir, { recursive: true });
|
|
@@ -1267,6 +1283,31 @@ const reportScanFailures = (failures) => {
|
|
|
1267
1283
|
console.error(`Docker Doctor could not analyze ${failures.length} ${fileWord}; the score below covers only the files that were analyzed.`);
|
|
1268
1284
|
for (const failure of failures) console.error(` - ${failure.file}: ${flattenMessage(failure.message)}`);
|
|
1269
1285
|
};
|
|
1286
|
+
const pathExists = async (filePath) => {
|
|
1287
|
+
try {
|
|
1288
|
+
await node_fs_promises.default.access(filePath);
|
|
1289
|
+
return true;
|
|
1290
|
+
} catch {
|
|
1291
|
+
return false;
|
|
1292
|
+
}
|
|
1293
|
+
};
|
|
1294
|
+
const resolveConfigPath = async (configOption, rootDir) => {
|
|
1295
|
+
if (!configOption) return;
|
|
1296
|
+
const fromCwd = node_path.default.resolve(process.cwd(), configOption);
|
|
1297
|
+
if (await pathExists(fromCwd)) return fromCwd;
|
|
1298
|
+
const fromRoot = node_path.default.resolve(rootDir, configOption);
|
|
1299
|
+
if (fromRoot !== fromCwd && await pathExists(fromRoot)) {
|
|
1300
|
+
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.`);
|
|
1301
|
+
return fromRoot;
|
|
1302
|
+
}
|
|
1303
|
+
return fromCwd;
|
|
1304
|
+
};
|
|
1305
|
+
const findScanTargetProblem = async (rootDir) => {
|
|
1306
|
+
const rootStat = await node_fs_promises.default.stat(rootDir).catch(() => null);
|
|
1307
|
+
if (rootStat === null) return `Directory not found: ${rootDir}`;
|
|
1308
|
+
if (!rootStat.isDirectory()) return `${rootDir} is not a directory. Pass the directory that contains your Dockerfile or Compose file.`;
|
|
1309
|
+
return null;
|
|
1310
|
+
};
|
|
1270
1311
|
const program = new commander.Command();
|
|
1271
1312
|
program.name("docker-doctor").description("Static analysis for Dockerfile and Docker Compose files").version(require_src.version, "-V, --version", "display the version number");
|
|
1272
1313
|
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) => {
|
|
@@ -1285,6 +1326,12 @@ program.argument("[dir]", "directory to scan", ".").option("-v, --verbose", "sho
|
|
|
1285
1326
|
});
|
|
1286
1327
|
try {
|
|
1287
1328
|
const rootDir = node_path.default.resolve(dir);
|
|
1329
|
+
const scanTargetProblem = await findScanTargetProblem(rootDir);
|
|
1330
|
+
if (scanTargetProblem !== null) {
|
|
1331
|
+
console.error(`Error: ${scanTargetProblem}`);
|
|
1332
|
+
process.exitCode = 1;
|
|
1333
|
+
return;
|
|
1334
|
+
}
|
|
1288
1335
|
const startTime = Date.now();
|
|
1289
1336
|
let statusText = "Discovering workspace...";
|
|
1290
1337
|
const spinnerFrames = [
|
|
@@ -1315,11 +1362,17 @@ program.argument("[dir]", "directory to scan", ".").option("-v, --verbose", "sho
|
|
|
1315
1362
|
try {
|
|
1316
1363
|
if (process.stdout.isTTY && !isSilent) await (0, node_timers_promises.setTimeout)(150);
|
|
1317
1364
|
setStatus("Loading configuration...");
|
|
1318
|
-
const
|
|
1365
|
+
const configPath = await resolveConfigPath(options.config, rootDir);
|
|
1366
|
+
const config = await require_src.loadConfig(rootDir, configPath, (message) => {
|
|
1319
1367
|
console.error(`Warning: ${message}`);
|
|
1320
1368
|
});
|
|
1321
1369
|
setStatus("Scanning workspace files...");
|
|
1322
|
-
const project = await require_src.discoverProject(rootDir, {
|
|
1370
|
+
const project = await require_src.discoverProject(rootDir, {
|
|
1371
|
+
ignoreFiles: config.ignore?.files,
|
|
1372
|
+
onSkippedDirectory: (skippedDir) => {
|
|
1373
|
+
console.error(`Warning: skipped unreadable directory ${skippedDir} (permission denied)`);
|
|
1374
|
+
}
|
|
1375
|
+
});
|
|
1323
1376
|
const fileContents = {};
|
|
1324
1377
|
const projectFilesList = [
|
|
1325
1378
|
...project.dockerfiles,
|
|
@@ -1344,7 +1397,7 @@ program.argument("[dir]", "directory to scan", ".").option("-v, --verbose", "sho
|
|
|
1344
1397
|
process.exitCode = scanExitCode(scanIncomplete, hasErrors);
|
|
1345
1398
|
return;
|
|
1346
1399
|
} else if (options.json) {
|
|
1347
|
-
const report = require_src.toJsonReport(diagnostics, score, label, project);
|
|
1400
|
+
const report = require_src.toJsonReport(diagnostics, score, label, project, failures);
|
|
1348
1401
|
console.log(JSON.stringify(report, null, 2));
|
|
1349
1402
|
process.exitCode = scanExitCode(scanIncomplete, hasErrors);
|
|
1350
1403
|
return;
|
|
@@ -1353,7 +1406,7 @@ program.argument("[dir]", "directory to scan", ".").option("-v, --verbose", "sho
|
|
|
1353
1406
|
let wizardOk = true;
|
|
1354
1407
|
if (process.stdout.isTTY && process.stdin.isTTY) wizardOk = await runInteractiveWizard({
|
|
1355
1408
|
diagnostics,
|
|
1356
|
-
report: require_src.toJsonReport(diagnostics, score, label, project),
|
|
1409
|
+
report: require_src.toJsonReport(diagnostics, score, label, project, failures),
|
|
1357
1410
|
rootDir
|
|
1358
1411
|
});
|
|
1359
1412
|
const exitCode = scanExitCode(scanIncomplete, hasErrors);
|
|
@@ -1441,7 +1494,7 @@ rules.command("list").description("list all available rules").action(() => {
|
|
|
1441
1494
|
}
|
|
1442
1495
|
});
|
|
1443
1496
|
rules.command("explain <rule>").description("explain a specific rule in detail").action((ruleKey) => {
|
|
1444
|
-
const rule = require_src.findRule(ruleKey);
|
|
1497
|
+
const rule = require_src.findRule(ruleKey) ?? require_src.findRule(`docker-doctor/${ruleKey}`);
|
|
1445
1498
|
if (!rule) {
|
|
1446
1499
|
console.error(`Rule '${ruleKey}' not found.`);
|
|
1447
1500
|
process.exitCode = 1;
|