@docker-doctor/cli 0.5.1 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +37 -15
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.mjs +37 -15
- package/dist/cli.mjs.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +15 -13
- package/dist/index.d.mts +15 -13
- package/dist/index.mjs +1 -1
- package/dist/{src-NqSwkGiS.mjs → src-BOGzkp17.mjs} +119 -36
- package/dist/src-BOGzkp17.mjs.map +1 -0
- package/dist/{src-DYLANvt2.cjs → src-D3jzc3GX.cjs} +119 -36
- package/dist/src-D3jzc3GX.cjs.map +1 -0
- package/package.json +4 -4
- package/skill/docker-doctor/references/explain.md +6 -5
- package/dist/src-DYLANvt2.cjs.map +0 -1
- package/dist/src-NqSwkGiS.mjs.map +0 -1
package/dist/index.d.cts
CHANGED
|
@@ -13,6 +13,7 @@ interface Diagnostic {
|
|
|
13
13
|
severity: DiagnosticSeverity;
|
|
14
14
|
message: string;
|
|
15
15
|
help: string;
|
|
16
|
+
category?: RuleCategory;
|
|
16
17
|
line?: number;
|
|
17
18
|
column?: number;
|
|
18
19
|
}
|
|
@@ -45,34 +46,34 @@ interface DockerDoctorConfig {
|
|
|
45
46
|
}
|
|
46
47
|
//#endregion
|
|
47
48
|
//#region ../core/src/project-info/discover.d.ts
|
|
48
|
-
declare const discoverProject: (rootDir: string, options?: {
|
|
49
|
+
export declare const discoverProject: (rootDir: string, options?: {
|
|
49
50
|
ignoreFiles?: readonly string[];
|
|
50
51
|
}) => Promise<ProjectInfo>;
|
|
51
52
|
//#endregion
|
|
52
53
|
//#region ../core/src/parsers/dockerfile-parser.d.ts
|
|
53
|
-
declare const parseDockerfile: (content: string) => DockerfileInstruction[];
|
|
54
|
+
export declare const parseDockerfile: (content: string, file?: string) => DockerfileInstruction[];
|
|
54
55
|
//#endregion
|
|
55
56
|
//#region ../core/src/parsers/compose-parser.d.ts
|
|
56
|
-
declare const parseCompose: (content: string, filepath: string) => unknown;
|
|
57
|
+
export declare const parseCompose: (content: string, filepath: string) => unknown;
|
|
57
58
|
//#endregion
|
|
58
59
|
//#region ../core/src/runners/dockerfile-runner.d.ts
|
|
59
|
-
declare const runDockerfileRules: (instructions: DockerfileInstruction[], file: string, projectFiles: string[], rulesConfig?: Record<string, RuleSeverity>, categoriesConfig?: Record<string, RuleSeverity>) => Diagnostic[];
|
|
60
|
+
export declare const runDockerfileRules: (instructions: DockerfileInstruction[], file: string, projectFiles: string[], rulesConfig?: Record<string, RuleSeverity>, categoriesConfig?: Record<string, RuleSeverity>) => Diagnostic[];
|
|
60
61
|
//#endregion
|
|
61
62
|
//#region ../core/src/runners/compose-runner.d.ts
|
|
62
|
-
declare const runComposeRules: (composeContent: unknown, file: string, rulesConfig?: Record<string, RuleSeverity>, categoriesConfig?: Record<string, RuleSeverity>, locate?: ComposeLocator) => Diagnostic[];
|
|
63
|
+
export declare const runComposeRules: (composeContent: unknown, file: string, rulesConfig?: Record<string, RuleSeverity>, categoriesConfig?: Record<string, RuleSeverity>, locate?: ComposeLocator) => Diagnostic[];
|
|
63
64
|
//#endregion
|
|
64
65
|
//#region ../core/src/rules/index.d.ts
|
|
65
|
-
declare const allRules: RuleDefinition[];
|
|
66
|
-
declare const findRule: (key: string) => RuleDefinition | undefined;
|
|
66
|
+
export declare const allRules: RuleDefinition[];
|
|
67
|
+
export declare const findRule: (key: string) => RuleDefinition | undefined;
|
|
67
68
|
//#endregion
|
|
68
69
|
//#region ../core/src/config/define-config.d.ts
|
|
69
|
-
declare const defineConfig: (config: DockerDoctorConfig) => DockerDoctorConfig;
|
|
70
|
+
export declare const defineConfig: (config: DockerDoctorConfig) => DockerDoctorConfig;
|
|
70
71
|
//#endregion
|
|
71
72
|
//#region ../core/src/config/loader.d.ts
|
|
72
|
-
declare const loadConfig: (rootDir: string, customPath?: string, onWarning?: (message: string) => void) => Promise<DockerDoctorConfig>;
|
|
73
|
+
export declare const loadConfig: (rootDir: string, customPath?: string, onWarning?: (message: string) => void) => Promise<DockerDoctorConfig>;
|
|
73
74
|
//#endregion
|
|
74
75
|
//#region ../core/src/scoring.d.ts
|
|
75
|
-
declare const calculateScore: (diagnostics: Diagnostic[]) => {
|
|
76
|
+
export declare const calculateScore: (diagnostics: Diagnostic[]) => {
|
|
76
77
|
score: number;
|
|
77
78
|
label: string;
|
|
78
79
|
};
|
|
@@ -80,6 +81,7 @@ declare const calculateScore: (diagnostics: Diagnostic[]) => {
|
|
|
80
81
|
//#region ../core/src/report.d.ts
|
|
81
82
|
interface JsonReport {
|
|
82
83
|
diagnostics: {
|
|
84
|
+
category: RuleCategory;
|
|
83
85
|
column?: number;
|
|
84
86
|
file: string;
|
|
85
87
|
help: string;
|
|
@@ -94,10 +96,10 @@ interface JsonReport {
|
|
|
94
96
|
score: number;
|
|
95
97
|
timestamp: string;
|
|
96
98
|
}
|
|
97
|
-
declare const toJsonReport: (diagnostics: Diagnostic[], score: number, label: string, project: ProjectInfo) => JsonReport;
|
|
99
|
+
export declare const toJsonReport: (diagnostics: Diagnostic[], score: number, label: string, project: ProjectInfo) => JsonReport;
|
|
98
100
|
//#endregion
|
|
99
101
|
//#region src/index.d.ts
|
|
100
|
-
declare const version: string;
|
|
102
|
+
export declare const version: string;
|
|
101
103
|
//#endregion
|
|
102
|
-
export {
|
|
104
|
+
export type { Diagnostic, DockerDoctorConfig, RuleCategory, RuleSeverity };
|
|
103
105
|
//# sourceMappingURL=index.d.cts.map
|
package/dist/index.d.mts
CHANGED
|
@@ -13,6 +13,7 @@ interface Diagnostic {
|
|
|
13
13
|
severity: DiagnosticSeverity;
|
|
14
14
|
message: string;
|
|
15
15
|
help: string;
|
|
16
|
+
category?: RuleCategory;
|
|
16
17
|
line?: number;
|
|
17
18
|
column?: number;
|
|
18
19
|
}
|
|
@@ -45,34 +46,34 @@ interface DockerDoctorConfig {
|
|
|
45
46
|
}
|
|
46
47
|
//#endregion
|
|
47
48
|
//#region ../core/src/project-info/discover.d.ts
|
|
48
|
-
declare const discoverProject: (rootDir: string, options?: {
|
|
49
|
+
export declare const discoverProject: (rootDir: string, options?: {
|
|
49
50
|
ignoreFiles?: readonly string[];
|
|
50
51
|
}) => Promise<ProjectInfo>;
|
|
51
52
|
//#endregion
|
|
52
53
|
//#region ../core/src/parsers/dockerfile-parser.d.ts
|
|
53
|
-
declare const parseDockerfile: (content: string) => DockerfileInstruction[];
|
|
54
|
+
export declare const parseDockerfile: (content: string, file?: string) => DockerfileInstruction[];
|
|
54
55
|
//#endregion
|
|
55
56
|
//#region ../core/src/parsers/compose-parser.d.ts
|
|
56
|
-
declare const parseCompose: (content: string, filepath: string) => unknown;
|
|
57
|
+
export declare const parseCompose: (content: string, filepath: string) => unknown;
|
|
57
58
|
//#endregion
|
|
58
59
|
//#region ../core/src/runners/dockerfile-runner.d.ts
|
|
59
|
-
declare const runDockerfileRules: (instructions: DockerfileInstruction[], file: string, projectFiles: string[], rulesConfig?: Record<string, RuleSeverity>, categoriesConfig?: Record<string, RuleSeverity>) => Diagnostic[];
|
|
60
|
+
export declare const runDockerfileRules: (instructions: DockerfileInstruction[], file: string, projectFiles: string[], rulesConfig?: Record<string, RuleSeverity>, categoriesConfig?: Record<string, RuleSeverity>) => Diagnostic[];
|
|
60
61
|
//#endregion
|
|
61
62
|
//#region ../core/src/runners/compose-runner.d.ts
|
|
62
|
-
declare const runComposeRules: (composeContent: unknown, file: string, rulesConfig?: Record<string, RuleSeverity>, categoriesConfig?: Record<string, RuleSeverity>, locate?: ComposeLocator) => Diagnostic[];
|
|
63
|
+
export declare const runComposeRules: (composeContent: unknown, file: string, rulesConfig?: Record<string, RuleSeverity>, categoriesConfig?: Record<string, RuleSeverity>, locate?: ComposeLocator) => Diagnostic[];
|
|
63
64
|
//#endregion
|
|
64
65
|
//#region ../core/src/rules/index.d.ts
|
|
65
|
-
declare const allRules: RuleDefinition[];
|
|
66
|
-
declare const findRule: (key: string) => RuleDefinition | undefined;
|
|
66
|
+
export declare const allRules: RuleDefinition[];
|
|
67
|
+
export declare const findRule: (key: string) => RuleDefinition | undefined;
|
|
67
68
|
//#endregion
|
|
68
69
|
//#region ../core/src/config/define-config.d.ts
|
|
69
|
-
declare const defineConfig: (config: DockerDoctorConfig) => DockerDoctorConfig;
|
|
70
|
+
export declare const defineConfig: (config: DockerDoctorConfig) => DockerDoctorConfig;
|
|
70
71
|
//#endregion
|
|
71
72
|
//#region ../core/src/config/loader.d.ts
|
|
72
|
-
declare const loadConfig: (rootDir: string, customPath?: string, onWarning?: (message: string) => void) => Promise<DockerDoctorConfig>;
|
|
73
|
+
export declare const loadConfig: (rootDir: string, customPath?: string, onWarning?: (message: string) => void) => Promise<DockerDoctorConfig>;
|
|
73
74
|
//#endregion
|
|
74
75
|
//#region ../core/src/scoring.d.ts
|
|
75
|
-
declare const calculateScore: (diagnostics: Diagnostic[]) => {
|
|
76
|
+
export declare const calculateScore: (diagnostics: Diagnostic[]) => {
|
|
76
77
|
score: number;
|
|
77
78
|
label: string;
|
|
78
79
|
};
|
|
@@ -80,6 +81,7 @@ declare const calculateScore: (diagnostics: Diagnostic[]) => {
|
|
|
80
81
|
//#region ../core/src/report.d.ts
|
|
81
82
|
interface JsonReport {
|
|
82
83
|
diagnostics: {
|
|
84
|
+
category: RuleCategory;
|
|
83
85
|
column?: number;
|
|
84
86
|
file: string;
|
|
85
87
|
help: string;
|
|
@@ -94,10 +96,10 @@ interface JsonReport {
|
|
|
94
96
|
score: number;
|
|
95
97
|
timestamp: string;
|
|
96
98
|
}
|
|
97
|
-
declare const toJsonReport: (diagnostics: Diagnostic[], score: number, label: string, project: ProjectInfo) => JsonReport;
|
|
99
|
+
export declare const toJsonReport: (diagnostics: Diagnostic[], score: number, label: string, project: ProjectInfo) => JsonReport;
|
|
98
100
|
//#endregion
|
|
99
101
|
//#region src/index.d.ts
|
|
100
|
-
declare const version: string;
|
|
102
|
+
export declare const version: string;
|
|
101
103
|
//#endregion
|
|
102
|
-
export {
|
|
104
|
+
export type { Diagnostic, DockerDoctorConfig, RuleCategory, RuleSeverity };
|
|
103
105
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as runDockerfileRules, d as discoverProject, f as version$1, 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, d as discoverProject, f as version$1, 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-BOGzkp17.mjs";
|
|
3
3
|
|
|
4
4
|
//#region ../core/src/config/define-config.ts
|
|
5
5
|
const defineConfig = (config) => config;
|
|
@@ -4,7 +4,7 @@ import path from "node:path";
|
|
|
4
4
|
import { LineCounter, isAlias, isMap, isScalar, isSeq, parse, parseDocument } from "yaml";
|
|
5
5
|
|
|
6
6
|
//#region package.json
|
|
7
|
-
var version = "0.5.
|
|
7
|
+
var version = "0.5.2";
|
|
8
8
|
|
|
9
9
|
//#endregion
|
|
10
10
|
//#region ../core/src/project-info/ignore.ts
|
|
@@ -71,6 +71,7 @@ const walk = async (dir, fileList = []) => {
|
|
|
71
71
|
}));
|
|
72
72
|
return fileList;
|
|
73
73
|
};
|
|
74
|
+
const toPosixRelative = (rootDir, file) => path.relative(rootDir, file).split(path.sep).join("/");
|
|
74
75
|
const discoverProject = async (rootDir, options) => {
|
|
75
76
|
const allFiles = await walk(rootDir);
|
|
76
77
|
const dockerfiles = [];
|
|
@@ -78,10 +79,13 @@ const discoverProject = async (rootDir, options) => {
|
|
|
78
79
|
const dockerignores = [];
|
|
79
80
|
const isIgnored = createIgnoreMatcher(options?.ignoreFiles);
|
|
80
81
|
for (const file of allFiles) {
|
|
81
|
-
const relative =
|
|
82
|
+
const relative = toPosixRelative(rootDir, file);
|
|
82
83
|
if (isIgnored(relative)) continue;
|
|
83
84
|
const base = path.basename(file).toLowerCase();
|
|
84
|
-
if (base
|
|
85
|
+
if (base.endsWith(".dockerignore")) {
|
|
86
|
+
dockerignores.push(relative);
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
85
89
|
if (base === "dockerfile" || base.startsWith("dockerfile.") || base.endsWith(".dockerfile")) dockerfiles.push(relative);
|
|
86
90
|
if (base === "docker-compose.yml" || base === "docker-compose.yaml" || base === "compose.yml" || base === "compose.yaml" || (base.startsWith("docker-compose.") || base.startsWith("compose.")) && (base.endsWith(".yml") || base.endsWith(".yaml"))) composeFiles.push(relative);
|
|
87
91
|
}
|
|
@@ -92,6 +96,72 @@ const discoverProject = async (rootDir, options) => {
|
|
|
92
96
|
};
|
|
93
97
|
};
|
|
94
98
|
|
|
99
|
+
//#endregion
|
|
100
|
+
//#region ../core/src/errors/config-error.ts
|
|
101
|
+
var ConfigError = class extends Error {
|
|
102
|
+
_tag = "ConfigError";
|
|
103
|
+
constructor(options) {
|
|
104
|
+
super(options.message);
|
|
105
|
+
this.name = "ConfigError";
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
//#endregion
|
|
110
|
+
//#region ../core/src/errors/parse-error.ts
|
|
111
|
+
var ParseError = class extends Error {
|
|
112
|
+
_tag = "ParseError";
|
|
113
|
+
file;
|
|
114
|
+
constructor(options) {
|
|
115
|
+
super(options.message);
|
|
116
|
+
this.name = "ParseError";
|
|
117
|
+
this.file = options.file;
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
//#endregion
|
|
122
|
+
//#region ../core/src/parsers/shell-quotes.ts
|
|
123
|
+
/**
|
|
124
|
+
* Replaces every character inside a single- or double-quoted region with a
|
|
125
|
+
* space, keeping the quotes themselves and the string length, so callers can
|
|
126
|
+
* run position-sensitive regexes over shell text without matching inside
|
|
127
|
+
* string literals. A backslash inside double quotes escapes the next
|
|
128
|
+
* character; single quotes have no escapes (POSIX sh). An unclosed quote
|
|
129
|
+
* masks to the end of the text.
|
|
130
|
+
*
|
|
131
|
+
* What this deliberately does NOT do: it is not a tokenizer. It performs no
|
|
132
|
+
* variable or command expansion, knows nothing about subshells, backslash
|
|
133
|
+
* line continuations, `$'...'` or `<<` heredoc bodies, and does not split
|
|
134
|
+
* words. It only blanks quoted spans so regexes keep their positions.
|
|
135
|
+
*/
|
|
136
|
+
const maskQuotedText = (text) => {
|
|
137
|
+
let out = "";
|
|
138
|
+
let quote = null;
|
|
139
|
+
let index = 0;
|
|
140
|
+
while (index < text.length) {
|
|
141
|
+
const char = text[index];
|
|
142
|
+
if (quote === null) {
|
|
143
|
+
if (char === "\"" || char === "'") quote = char;
|
|
144
|
+
out += char;
|
|
145
|
+
index += 1;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (char === quote) {
|
|
149
|
+
quote = null;
|
|
150
|
+
out += char;
|
|
151
|
+
index += 1;
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
if (quote === "\"" && char === "\\" && index + 1 < text.length) {
|
|
155
|
+
out += " ";
|
|
156
|
+
index += 2;
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
out += " ";
|
|
160
|
+
index += 1;
|
|
161
|
+
}
|
|
162
|
+
return out;
|
|
163
|
+
};
|
|
164
|
+
|
|
95
165
|
//#endregion
|
|
96
166
|
//#region ../core/src/parsers/dockerfile-parser.ts
|
|
97
167
|
const DOCKERFILE_KEYWORDS = /* @__PURE__ */ new Set([
|
|
@@ -154,7 +224,10 @@ const matchInstructionKeyword = (lineContent) => {
|
|
|
154
224
|
};
|
|
155
225
|
return null;
|
|
156
226
|
};
|
|
157
|
-
const findHeredocDelimiters = (lineContent) =>
|
|
227
|
+
const findHeredocDelimiters = (lineContent) => {
|
|
228
|
+
const masked = maskQuotedText(lineContent);
|
|
229
|
+
return [...lineContent.matchAll(HEREDOC_OPENER_RE)].filter((match) => masked[match.index] === "<").map((match) => match.groups?.delim ?? "");
|
|
230
|
+
};
|
|
158
231
|
const processHeredocLine = (state, trimmed) => {
|
|
159
232
|
if (trimmed === state.heredocQueue[0]) state.heredocQueue.shift();
|
|
160
233
|
else state.currentArgs += (state.currentArgs ? " " : "") + trimmed;
|
|
@@ -177,7 +250,7 @@ const processInstructionLine = (state, trimmed, lineNum) => {
|
|
|
177
250
|
if (state.heredocQueue.length > 0) return;
|
|
178
251
|
if (!hasContinuation) closeInstruction(state);
|
|
179
252
|
};
|
|
180
|
-
const parseDockerfile = (content) => {
|
|
253
|
+
const parseDockerfile = (content, file = "Dockerfile") => {
|
|
181
254
|
const state = createParserState();
|
|
182
255
|
const lines = content.split(/\r?\n/u);
|
|
183
256
|
for (const [i, rawLine] of lines.entries()) {
|
|
@@ -190,32 +263,14 @@ const parseDockerfile = (content) => {
|
|
|
190
263
|
if (insideHeredoc) processHeredocLine(state, trimmed);
|
|
191
264
|
else processInstructionLine(state, trimmed, lineNum);
|
|
192
265
|
}
|
|
266
|
+
if (state.heredocQueue.length > 0) throw new ParseError({
|
|
267
|
+
file,
|
|
268
|
+
message: `Unterminated heredoc: delimiter "${state.heredocQueue[0]}" (opened on line ${state.startLine}) never closed. BuildKit rejects this Dockerfile too.`
|
|
269
|
+
});
|
|
193
270
|
closeInstruction(state);
|
|
194
271
|
return state.instructions;
|
|
195
272
|
};
|
|
196
273
|
|
|
197
|
-
//#endregion
|
|
198
|
-
//#region ../core/src/errors/config-error.ts
|
|
199
|
-
var ConfigError = class extends Error {
|
|
200
|
-
_tag = "ConfigError";
|
|
201
|
-
constructor(options) {
|
|
202
|
-
super(options.message);
|
|
203
|
-
this.name = "ConfigError";
|
|
204
|
-
}
|
|
205
|
-
};
|
|
206
|
-
|
|
207
|
-
//#endregion
|
|
208
|
-
//#region ../core/src/errors/parse-error.ts
|
|
209
|
-
var ParseError = class extends Error {
|
|
210
|
-
_tag = "ParseError";
|
|
211
|
-
file;
|
|
212
|
-
constructor(options) {
|
|
213
|
-
super(options.message);
|
|
214
|
-
this.name = "ParseError";
|
|
215
|
-
this.file = options.file;
|
|
216
|
-
}
|
|
217
|
-
};
|
|
218
|
-
|
|
219
274
|
//#endregion
|
|
220
275
|
//#region ../core/src/parsers/compose-parser.ts
|
|
221
276
|
const parseCompose = (content, filepath) => {
|
|
@@ -449,6 +504,7 @@ const combineAptUpdateInstall = {
|
|
|
449
504
|
};
|
|
450
505
|
const PIPEFAIL_SETTING_RE = /(?:^|\s)-[A-Za-z]*o\s+pipefail\b/u;
|
|
451
506
|
const HAS_PIPE_RE = /(?<!\|)\|(?!\|)/u;
|
|
507
|
+
const CD_COMMAND_RE = /(?:^|&&|\|\||;|\||\(|`|\$\()\s*cd(?=\s|$)/u;
|
|
452
508
|
const shellDirectiveEnablesPipefail = (args) => {
|
|
453
509
|
const argv = parseExecForm(args);
|
|
454
510
|
return argv !== null && PIPEFAIL_SETTING_RE.test(argv.join(" "));
|
|
@@ -476,8 +532,9 @@ const usePipefail = {
|
|
|
476
532
|
}
|
|
477
533
|
if (inst.instruction !== "RUN") continue;
|
|
478
534
|
const { args } = inst;
|
|
479
|
-
if (!HAS_PIPE_RE.test(args)) continue;
|
|
480
535
|
const execArgv = parseExecForm(args);
|
|
536
|
+
const pipeText = execArgv === null ? maskQuotedText(args) : execArgv.join(" ");
|
|
537
|
+
if (!HAS_PIPE_RE.test(pipeText)) continue;
|
|
481
538
|
if (!(execArgv === null ? shellHasPipefail || PIPEFAIL_SETTING_RE.test(args) : PIPEFAIL_SETTING_RE.test(execArgv.join(" ")))) diagnostics.push(createDiagnostic(file, this.key, this.defaultSeverity, "RUN instruction uses a pipe (|) but does not configure 'pipefail'. If a command in the pipe fails, the step may still succeed silently.", this.help, inst.line));
|
|
482
539
|
}
|
|
483
540
|
return diagnostics;
|
|
@@ -506,7 +563,7 @@ const avoidRunCd = {
|
|
|
506
563
|
category: "Best Practices",
|
|
507
564
|
check(instructions, file) {
|
|
508
565
|
const diagnostics = [];
|
|
509
|
-
for (const inst of instructions) if (inst.instruction === "RUN" &&
|
|
566
|
+
for (const inst of instructions) if (inst.instruction === "RUN" && CD_COMMAND_RE.test(maskQuotedText(inst.args))) diagnostics.push(createDiagnostic(file, this.key, this.defaultSeverity, "Avoid using 'cd' in RUN instructions. Use WORKDIR instead to change the working directory stably across layers.", this.help, inst.line));
|
|
510
567
|
return diagnostics;
|
|
511
568
|
},
|
|
512
569
|
defaultSeverity: "info",
|
|
@@ -569,12 +626,13 @@ const bestPracticesRules = [
|
|
|
569
626
|
* with a null body (`web:` with nothing under it) is returned as an empty
|
|
570
627
|
* config so rules still check it: it is the least-configured service in
|
|
571
628
|
* the file, not a service to skip. Scalar and array bodies are invalid
|
|
572
|
-
* compose and are dropped.
|
|
629
|
+
* compose and are dropped. A `services:` written as a YAML sequence is
|
|
630
|
+
* invalid Compose and yields no services.
|
|
573
631
|
*/
|
|
574
632
|
const composeServices = (composeContent) => {
|
|
575
633
|
if (!composeContent || typeof composeContent !== "object" || !("services" in composeContent)) return [];
|
|
576
634
|
const { services } = composeContent;
|
|
577
|
-
if (!services || typeof services !== "object") return [];
|
|
635
|
+
if (!services || typeof services !== "object" || Array.isArray(services)) return [];
|
|
578
636
|
const entries = [];
|
|
579
637
|
for (const [name, config] of Object.entries(services)) if (config === null || config === void 0) entries.push([name, {}]);
|
|
580
638
|
else if (typeof config === "object" && !Array.isArray(config)) entries.push([name, config]);
|
|
@@ -1242,12 +1300,16 @@ const resolveSeverity = (rule, rulesConfig, categoriesConfig) => rulesConfig?.[r
|
|
|
1242
1300
|
//#endregion
|
|
1243
1301
|
//#region ../core/src/runners/dockerfile-runner.ts
|
|
1244
1302
|
const runDockerfileRules = (instructions, file, projectFiles, rulesConfig, categoriesConfig) => {
|
|
1303
|
+
if (instructions.length === 0) return [];
|
|
1245
1304
|
const diagnostics = [];
|
|
1246
1305
|
for (const rule of allDockerfileRules) {
|
|
1247
1306
|
const severity = resolveSeverity(rule, rulesConfig, categoriesConfig);
|
|
1248
1307
|
if (severity === "off") continue;
|
|
1249
1308
|
const ruleDiagnostics = rule.check(instructions, file, { projectFiles });
|
|
1250
|
-
|
|
1309
|
+
for (const diag of ruleDiagnostics) {
|
|
1310
|
+
diag.category = rule.category;
|
|
1311
|
+
if (severity !== rule.defaultSeverity) diag.severity = severity;
|
|
1312
|
+
}
|
|
1251
1313
|
diagnostics.push(...ruleDiagnostics);
|
|
1252
1314
|
}
|
|
1253
1315
|
return diagnostics;
|
|
@@ -1261,7 +1323,10 @@ const runComposeRules = (composeContent, file, rulesConfig, categoriesConfig, lo
|
|
|
1261
1323
|
const severity = resolveSeverity(rule, rulesConfig, categoriesConfig);
|
|
1262
1324
|
if (severity === "off") continue;
|
|
1263
1325
|
const ruleDiagnostics = rule.check(composeContent, file, { locate });
|
|
1264
|
-
|
|
1326
|
+
for (const diag of ruleDiagnostics) {
|
|
1327
|
+
diag.category = rule.category;
|
|
1328
|
+
if (severity !== rule.defaultSeverity) diag.severity = severity;
|
|
1329
|
+
}
|
|
1265
1330
|
diagnostics.push(...ruleDiagnostics);
|
|
1266
1331
|
}
|
|
1267
1332
|
return diagnostics;
|
|
@@ -1371,6 +1436,16 @@ const parseConfigFile = async (filePath, format, parse) => {
|
|
|
1371
1436
|
throw new ConfigError({ message: `Failed to parse config ${format}: ${msg}` });
|
|
1372
1437
|
}
|
|
1373
1438
|
};
|
|
1439
|
+
const TYPESCRIPT_CONFIG_PATTERN = /\.[cm]?ts$/u;
|
|
1440
|
+
const TYPE_STRIPPING_FAILURES = {
|
|
1441
|
+
ERR_UNKNOWN_FILE_EXTENSION: "this Node.js is not stripping TypeScript types, which needs 22.18 or newer with stripping left on (--no-experimental-strip-types turns it off)",
|
|
1442
|
+
ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING: "Node.js never strips TypeScript types for files under node_modules, on any version, so the config has to live outside it"
|
|
1443
|
+
};
|
|
1444
|
+
const typeStrippingFailure = (filePath, error) => {
|
|
1445
|
+
if (!TYPESCRIPT_CONFIG_PATTERN.test(filePath)) return;
|
|
1446
|
+
if (typeof error !== "object" || error === null || !("code" in error) || typeof error.code !== "string") return;
|
|
1447
|
+
return TYPE_STRIPPING_FAILURES[error.code];
|
|
1448
|
+
};
|
|
1374
1449
|
const importConfig = async (filePath) => {
|
|
1375
1450
|
if (filePath.endsWith(".json")) return parseConfigFile(filePath, "JSON", JSON.parse);
|
|
1376
1451
|
if (filePath.endsWith(".yaml") || filePath.endsWith(".yml")) return parseConfigFile(filePath, "YAML", parse);
|
|
@@ -1378,6 +1453,8 @@ const importConfig = async (filePath) => {
|
|
|
1378
1453
|
const configModule = await import(filePath);
|
|
1379
1454
|
return configModule.default || configModule;
|
|
1380
1455
|
} catch (error) {
|
|
1456
|
+
const reason = typeStrippingFailure(filePath, error);
|
|
1457
|
+
if (reason) throw new ConfigError({ message: `Cannot load ${filePath} on Node.js ${process.version}: ${reason}. A docker-doctor.config.yaml or .json needs no TypeScript support at all.` });
|
|
1381
1458
|
const msg = error instanceof Error ? error.message : String(error);
|
|
1382
1459
|
throw new ConfigError({ message: `Failed to load config file ${filePath}: ${msg}` });
|
|
1383
1460
|
}
|
|
@@ -1472,9 +1549,15 @@ const calculateScore = (diagnostics) => {
|
|
|
1472
1549
|
|
|
1473
1550
|
//#endregion
|
|
1474
1551
|
//#region ../core/src/report.ts
|
|
1475
|
-
const REPORT_SCHEMA_VERSION =
|
|
1552
|
+
const REPORT_SCHEMA_VERSION = 3;
|
|
1553
|
+
const resolveCategory = (diagnostic) => {
|
|
1554
|
+
const category = diagnostic.category ?? findRule(diagnostic.rule)?.category;
|
|
1555
|
+
if (!category) throw new Error(`Diagnostic for unknown rule "${diagnostic.rule}" has no category`);
|
|
1556
|
+
return category;
|
|
1557
|
+
};
|
|
1476
1558
|
const toJsonReport = (diagnostics, score, label, project) => ({
|
|
1477
1559
|
diagnostics: diagnostics.map((d) => ({
|
|
1560
|
+
category: resolveCategory(d),
|
|
1478
1561
|
column: d.column,
|
|
1479
1562
|
file: d.file,
|
|
1480
1563
|
help: d.help,
|
|
@@ -1485,11 +1568,11 @@ const toJsonReport = (diagnostics, score, label, project) => ({
|
|
|
1485
1568
|
})),
|
|
1486
1569
|
label,
|
|
1487
1570
|
project,
|
|
1488
|
-
schemaVersion:
|
|
1571
|
+
schemaVersion: 3,
|
|
1489
1572
|
score,
|
|
1490
1573
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1491
1574
|
});
|
|
1492
1575
|
|
|
1493
1576
|
//#endregion
|
|
1494
1577
|
export { runDockerfileRules as a, createComposeLocator as c, discoverProject as d, version as f, runComposeRules as i, parseCompose as l, calculateScore as n, allRules as o, loadConfig as r, findRule as s, toJsonReport as t, parseDockerfile as u };
|
|
1495
|
-
//# sourceMappingURL=src-
|
|
1578
|
+
//# sourceMappingURL=src-BOGzkp17.mjs.map
|