as-test 1.1.6 → 1.1.8

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.
Files changed (42) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +4 -9
  3. package/assembly/index.ts +10 -15
  4. package/assembly/src/expectation.ts +11 -11
  5. package/assembly/src/fuzz.ts +11 -7
  6. package/assembly/src/log.ts +2 -2
  7. package/assembly/src/suite.ts +5 -5
  8. package/assembly/src/tests.ts +8 -8
  9. package/assembly/util/wipc.ts +5 -1
  10. package/bin/build-worker-pool.js +146 -142
  11. package/bin/build-worker.js +37 -34
  12. package/bin/commands/build-core.js +577 -465
  13. package/bin/commands/build.js +49 -29
  14. package/bin/commands/clean-core.js +120 -113
  15. package/bin/commands/clean.js +14 -8
  16. package/bin/commands/doctor-core.js +288 -289
  17. package/bin/commands/doctor.js +1 -1
  18. package/bin/commands/fuzz-core.js +467 -414
  19. package/bin/commands/fuzz.js +27 -10
  20. package/bin/commands/init-core.js +908 -794
  21. package/bin/commands/init.js +2 -2
  22. package/bin/commands/run-core.js +2675 -2344
  23. package/bin/commands/run.js +43 -25
  24. package/bin/commands/test.js +56 -32
  25. package/bin/commands/web-runner-source.js +1 -1
  26. package/bin/commands/web-session.js +516 -525
  27. package/bin/coverage-points.js +363 -341
  28. package/bin/crash-store.js +56 -66
  29. package/bin/index.js +4092 -3150
  30. package/bin/reporters/default.js +1090 -890
  31. package/bin/reporters/tap.js +319 -325
  32. package/bin/types.js +67 -67
  33. package/bin/util.js +1290 -1239
  34. package/bin/wipc.js +70 -73
  35. package/lib/build/index.d.ts +3 -1
  36. package/lib/build/index.js +1039 -1034
  37. package/lib/build/web-runner/client.js +1 -1
  38. package/lib/build/web-runner/html.js +1 -1
  39. package/lib/build/web-runner/worker.js +1 -1
  40. package/package.json +6 -3
  41. package/transform/lib/log.js +9 -5
  42. package/assembly/util/json.ts +0 -112
@@ -5,331 +5,330 @@ import * as path from "path";
5
5
  import { applyMode, getExec, loadConfig, tokenizeCommand } from "../util.js";
6
6
  import { Config } from "../types.js";
7
7
  const DEFAULT_CONFIG_PATH = path.join(process.cwd(), "./as-test.config.json");
8
- export async function doctor(configPath = DEFAULT_CONFIG_PATH, selectedModes = []) {
9
- const checks = [];
10
- const resolvedConfigPath = configPath ?? DEFAULT_CONFIG_PATH;
11
- const configExists = existsSync(resolvedConfigPath);
12
- const loadedConfig = tryLoadConfig(resolvedConfigPath);
13
- const config = loadedConfig.config;
14
- if (!configExists) {
8
+ export async function doctor(
9
+ configPath = DEFAULT_CONFIG_PATH,
10
+ selectedModes = [],
11
+ ) {
12
+ const checks = [];
13
+ const resolvedConfigPath = configPath ?? DEFAULT_CONFIG_PATH;
14
+ const configExists = existsSync(resolvedConfigPath);
15
+ const loadedConfig = tryLoadConfig(resolvedConfigPath);
16
+ const config = loadedConfig.config;
17
+ if (!configExists) {
18
+ checks.push({
19
+ status: "warn",
20
+ scope: "config",
21
+ label: "Config file not found",
22
+ details: `No config at ${resolvedConfigPath}; default settings will be used.`,
23
+ fix: `Create a config with "ast init" or add ${path.basename(resolvedConfigPath)}.`,
24
+ });
25
+ } else if (!loadedConfig.loaded) {
26
+ checks.push({
27
+ status: "error",
28
+ scope: "config",
29
+ label: "Config parse failed",
30
+ details: `Could not parse ${resolvedConfigPath}.`,
31
+ fix: "Fix JSON syntax errors, then rerun `ast doctor`.",
32
+ });
33
+ } else {
34
+ checks.push({
35
+ status: "ok",
36
+ scope: "config",
37
+ label: "Config loaded",
38
+ details: resolvedConfigPath,
39
+ });
40
+ }
41
+ checks.push(checkNodeVersion());
42
+ checks.push(checkDependency("assemblyscript", true));
43
+ const selected = selectedModes.length
44
+ ? selectedModes
45
+ : Object.keys(config.modes).length
46
+ ? Object.keys(config.modes)
47
+ : [undefined];
48
+ if (selectedModes.length) {
49
+ for (const modeName of selectedModes) {
50
+ if (!config.modes[modeName]) {
15
51
  checks.push({
16
- status: "warn",
17
- scope: "config",
18
- label: "Config file not found",
19
- details: `No config at ${resolvedConfigPath}; default settings will be used.`,
20
- fix: `Create a config with "ast init" or add ${path.basename(resolvedConfigPath)}.`,
52
+ status: "error",
53
+ scope: "modes",
54
+ label: `Unknown mode "${modeName}"`,
55
+ details: `Available modes: ${Object.keys(config.modes).join(", ") || "(none)"}`,
56
+ fix: `Use "--mode <name>" with one of the configured modes, or remove "--mode ${modeName}".`,
21
57
  });
58
+ }
22
59
  }
23
- else if (!loadedConfig.loaded) {
24
- checks.push({
25
- status: "error",
26
- scope: "config",
27
- label: "Config parse failed",
28
- details: `Could not parse ${resolvedConfigPath}.`,
29
- fix: "Fix JSON syntax errors, then rerun `ast doctor`.",
30
- });
31
- }
32
- else {
33
- checks.push({
34
- status: "ok",
35
- scope: "config",
36
- label: "Config loaded",
37
- details: resolvedConfigPath,
38
- });
39
- }
40
- checks.push(checkNodeVersion());
41
- checks.push(checkDependency("assemblyscript", true));
42
- const selected = selectedModes.length
43
- ? selectedModes
44
- : Object.keys(config.modes).length
45
- ? Object.keys(config.modes)
46
- : [undefined];
47
- if (selectedModes.length) {
48
- for (const modeName of selectedModes) {
49
- if (!config.modes[modeName]) {
50
- checks.push({
51
- status: "error",
52
- scope: "modes",
53
- label: `Unknown mode "${modeName}"`,
54
- details: `Available modes: ${Object.keys(config.modes).join(", ") || "(none)"}`,
55
- fix: `Use "--mode <name>" with one of the configured modes, or remove "--mode ${modeName}".`,
56
- });
57
- }
58
- }
59
- }
60
- for (const modeName of selected) {
61
- const scope = modeName ?? "default";
62
- const modeCheck = await checkMode(config, modeName, scope);
63
- checks.push(...modeCheck);
64
- }
65
- renderChecks(checks, resolvedConfigPath, selected);
66
- const hasErrors = checks.some((check) => check.status == "error");
67
- if (hasErrors) {
68
- throw new Error("doctor checks failed");
69
- }
60
+ }
61
+ for (const modeName of selected) {
62
+ const scope = modeName ?? "default";
63
+ const modeCheck = await checkMode(config, modeName, scope);
64
+ checks.push(...modeCheck);
65
+ }
66
+ renderChecks(checks, resolvedConfigPath, selected);
67
+ const hasErrors = checks.some((check) => check.status == "error");
68
+ if (hasErrors) {
69
+ throw new Error("doctor checks failed");
70
+ }
70
71
  }
71
72
  function tryLoadConfig(configPath) {
72
- try {
73
- return { loaded: true, config: loadConfig(configPath, false) };
74
- }
75
- catch {
76
- return { loaded: false, config: new Config() };
77
- }
73
+ try {
74
+ return { loaded: true, config: loadConfig(configPath, false) };
75
+ } catch {
76
+ return { loaded: false, config: new Config() };
77
+ }
78
78
  }
79
79
  async function checkMode(config, modeName, scope) {
80
- const checks = [];
81
- let applied;
82
- try {
83
- applied = applyMode(config, modeName);
84
- }
85
- catch (error) {
86
- checks.push({
87
- status: "error",
88
- scope,
89
- label: "Mode merge failed",
90
- details: error instanceof Error ? error.message : String(error),
91
- fix: `Fix mode "${scope}" in as-test.config.json.`,
92
- });
93
- return checks;
94
- }
95
- const active = applied.config;
96
- const runtimeCommand = active.runOptions.runtime.cmd;
97
- const target = active.buildOptions.target;
98
- if (target == "wasi") {
99
- checks.push(checkDependency("@assemblyscript/wasi-shim", true, scope));
100
- }
101
- checks.push(...checkRuntimeCommand(runtimeCommand, target, scope), ...(await checkInputPatterns(active.input, scope)));
80
+ const checks = [];
81
+ let applied;
82
+ try {
83
+ applied = applyMode(config, modeName);
84
+ } catch (error) {
85
+ checks.push({
86
+ status: "error",
87
+ scope,
88
+ label: "Mode merge failed",
89
+ details: error instanceof Error ? error.message : String(error),
90
+ fix: `Fix mode "${scope}" in as-test.config.json.`,
91
+ });
102
92
  return checks;
93
+ }
94
+ const active = applied.config;
95
+ const runtimeCommand = active.runOptions.runtime.cmd;
96
+ const target = active.buildOptions.target;
97
+ if (target == "wasi") {
98
+ checks.push(checkDependency("@assemblyscript/wasi-shim", true, scope));
99
+ }
100
+ checks.push(
101
+ ...checkRuntimeCommand(runtimeCommand, target, scope),
102
+ ...(await checkInputPatterns(active.input, scope)),
103
+ );
104
+ return checks;
103
105
  }
104
106
  function checkRuntimeCommand(runtimeCommand, target, scope) {
105
- const checks = [];
106
- if (!runtimeCommand.trim().length) {
107
- checks.push({
108
- status: "error",
109
- scope,
110
- label: "Runtime command missing",
111
- details: "runOptions.runtime.cmd is empty.",
112
- fix: 'Set "runOptions.runtime.cmd" in as-test.config.json (for example: node ./.as-test/runners/default.wasi.js <file>).',
113
- });
114
- return checks;
115
- }
116
- let tokens;
117
- try {
118
- tokens = tokenizeCommand(runtimeCommand);
119
- }
120
- catch (error) {
121
- checks.push({
122
- status: "error",
123
- scope,
124
- label: "Runtime command parsing failed",
125
- details: error instanceof Error ? error.message : String(error),
126
- fix: "Fix quotes/escaping in runOptions.runtime.cmd.",
127
- });
128
- return checks;
129
- }
130
- if (!tokens.length) {
131
- checks.push({
132
- status: "error",
133
- scope,
134
- label: "Runtime command empty",
135
- details: "Command parsed to zero tokens.",
136
- fix: "Provide a runtime command executable and args.",
137
- });
138
- return checks;
139
- }
140
- const execToken = tokens[0];
141
- const execPath = getExec(execToken);
142
- if (!execPath) {
143
- checks.push({
144
- status: "error",
145
- scope,
146
- label: `Runtime executable not found: ${execToken}`,
147
- details: "Executable is not available in PATH.",
148
- fix: `Install "${execToken}" or update runOptions.runtime.cmd.`,
149
- });
150
- }
151
- else {
107
+ const checks = [];
108
+ if (!runtimeCommand.trim().length) {
109
+ checks.push({
110
+ status: "error",
111
+ scope,
112
+ label: "Runtime command missing",
113
+ details: "runOptions.runtime.cmd is empty.",
114
+ fix: 'Set "runOptions.runtime.cmd" in as-test.config.json (for example: node ./.as-test/runners/default.wasi.js <file>).',
115
+ });
116
+ return checks;
117
+ }
118
+ let tokens;
119
+ try {
120
+ tokens = tokenizeCommand(runtimeCommand);
121
+ } catch (error) {
122
+ checks.push({
123
+ status: "error",
124
+ scope,
125
+ label: "Runtime command parsing failed",
126
+ details: error instanceof Error ? error.message : String(error),
127
+ fix: "Fix quotes/escaping in runOptions.runtime.cmd.",
128
+ });
129
+ return checks;
130
+ }
131
+ if (!tokens.length) {
132
+ checks.push({
133
+ status: "error",
134
+ scope,
135
+ label: "Runtime command empty",
136
+ details: "Command parsed to zero tokens.",
137
+ fix: "Provide a runtime command executable and args.",
138
+ });
139
+ return checks;
140
+ }
141
+ const execToken = tokens[0];
142
+ const execPath = getExec(execToken);
143
+ if (!execPath) {
144
+ checks.push({
145
+ status: "error",
146
+ scope,
147
+ label: `Runtime executable not found: ${execToken}`,
148
+ details: "Executable is not available in PATH.",
149
+ fix: `Install "${execToken}" or update runOptions.runtime.cmd.`,
150
+ });
151
+ } else {
152
+ checks.push({
153
+ status: "ok",
154
+ scope,
155
+ label: "Runtime executable",
156
+ details: `${execToken} -> ${execPath}`,
157
+ });
158
+ }
159
+ if (!tokens.some((token) => token.includes("<file>"))) {
160
+ checks.push({
161
+ status: "error",
162
+ scope,
163
+ label: "Runtime command missing <file> placeholder",
164
+ details: `Runtime command for target "${target}" cannot receive the wasm artifact path.`,
165
+ fix: 'Add "<file>" to runOptions.runtime.cmd.',
166
+ });
167
+ }
168
+ if (isScriptHostRuntime(execToken)) {
169
+ const scriptPath = extractRuntimeScriptPath(tokens.slice(1));
170
+ if (scriptPath) {
171
+ const resolved = path.isAbsolute(scriptPath)
172
+ ? scriptPath
173
+ : path.join(process.cwd(), scriptPath);
174
+ if (!existsSync(resolved)) {
152
175
  checks.push({
153
- status: "ok",
154
- scope,
155
- label: "Runtime executable",
156
- details: `${execToken} -> ${execPath}`,
176
+ status: "warn",
177
+ scope,
178
+ label: "Runtime script path not found",
179
+ details: `${scriptPath} does not exist.`,
180
+ fix: "Create the runner script, or use `ast init` to scaffold default runners.",
157
181
  });
158
- }
159
- if (!tokens.some((token) => token.includes("<file>"))) {
182
+ } else {
160
183
  checks.push({
161
- status: "error",
162
- scope,
163
- label: "Runtime command missing <file> placeholder",
164
- details: `Runtime command for target "${target}" cannot receive the wasm artifact path.`,
165
- fix: 'Add "<file>" to runOptions.runtime.cmd.',
184
+ status: "ok",
185
+ scope,
186
+ label: "Runtime script path",
187
+ details: scriptPath,
166
188
  });
189
+ }
167
190
  }
168
- if (isScriptHostRuntime(execToken)) {
169
- const scriptPath = extractRuntimeScriptPath(tokens.slice(1));
170
- if (scriptPath) {
171
- const resolved = path.isAbsolute(scriptPath)
172
- ? scriptPath
173
- : path.join(process.cwd(), scriptPath);
174
- if (!existsSync(resolved)) {
175
- checks.push({
176
- status: "warn",
177
- scope,
178
- label: "Runtime script path not found",
179
- details: `${scriptPath} does not exist.`,
180
- fix: "Create the runner script, or use `ast init` to scaffold default runners.",
181
- });
182
- }
183
- else {
184
- checks.push({
185
- status: "ok",
186
- scope,
187
- label: "Runtime script path",
188
- details: scriptPath,
189
- });
190
- }
191
- }
192
- }
193
- return checks;
191
+ }
192
+ return checks;
194
193
  }
195
194
  async function checkInputPatterns(input, scope) {
196
- const patterns = Array.isArray(input) ? input : [input];
197
- const files = await glob(patterns);
198
- const specs = files.filter((file) => file.endsWith(".spec.ts"));
199
- if (!specs.length) {
200
- return [
201
- {
202
- status: "warn",
203
- scope,
204
- label: "No spec files matched input patterns",
205
- details: patterns.join(", "),
206
- fix: 'Update "input" patterns or add `*.spec.ts` files.',
207
- },
208
- ];
209
- }
195
+ const patterns = Array.isArray(input) ? input : [input];
196
+ const files = await glob(patterns);
197
+ const specs = files.filter((file) => file.endsWith(".spec.ts"));
198
+ if (!specs.length) {
210
199
  return [
211
- {
212
- status: "ok",
213
- scope,
214
- label: "Spec file discovery",
215
- details: `${specs.length} spec file(s) matched input patterns.`,
216
- },
200
+ {
201
+ status: "warn",
202
+ scope,
203
+ label: "No spec files matched input patterns",
204
+ details: patterns.join(", "),
205
+ fix: 'Update "input" patterns or add `*.spec.ts` files.',
206
+ },
217
207
  ];
208
+ }
209
+ return [
210
+ {
211
+ status: "ok",
212
+ scope,
213
+ label: "Spec file discovery",
214
+ details: `${specs.length} spec file(s) matched input patterns.`,
215
+ },
216
+ ];
218
217
  }
219
218
  function checkNodeVersion() {
220
- const version = process.versions.node;
221
- const major = Number(version.split(".")[0] ?? "0");
222
- if (!Number.isFinite(major) || major < 18) {
223
- return {
224
- status: "warn",
225
- scope: "env",
226
- label: "Node.js version is old",
227
- details: `Detected v${version}.`,
228
- fix: "Use Node.js 18+ for the best compatibility.",
229
- };
230
- }
219
+ const version = process.versions.node;
220
+ const major = Number(version.split(".")[0] ?? "0");
221
+ if (!Number.isFinite(major) || major < 18) {
231
222
  return {
232
- status: "ok",
233
- scope: "env",
234
- label: "Node.js version",
235
- details: `v${version}`,
223
+ status: "warn",
224
+ scope: "env",
225
+ label: "Node.js version is old",
226
+ details: `Detected v${version}.`,
227
+ fix: "Use Node.js 18+ for the best compatibility.",
236
228
  };
229
+ }
230
+ return {
231
+ status: "ok",
232
+ scope: "env",
233
+ label: "Node.js version",
234
+ details: `v${version}`,
235
+ };
237
236
  }
238
237
  function checkDependency(pkg, required, scope = "deps") {
239
- const pkgJson = path.join(process.cwd(), "node_modules", pkg, "package.json");
240
- if (!existsSync(pkgJson)) {
241
- return {
242
- status: required ? "error" : "warn",
243
- scope,
244
- label: `Dependency missing: ${pkg}`,
245
- details: `${pkg} is not installed in node_modules.`,
246
- fix: `Install with: npm i -D ${pkg}`,
247
- };
248
- }
238
+ const pkgJson = path.join(process.cwd(), "node_modules", pkg, "package.json");
239
+ if (!existsSync(pkgJson)) {
249
240
  return {
250
- status: "ok",
251
- scope,
252
- label: `Dependency present: ${pkg}`,
253
- details: pkgJson,
241
+ status: required ? "error" : "warn",
242
+ scope,
243
+ label: `Dependency missing: ${pkg}`,
244
+ details: `${pkg} is not installed in node_modules.`,
245
+ fix: `Install with: npm i -D ${pkg}`,
254
246
  };
247
+ }
248
+ return {
249
+ status: "ok",
250
+ scope,
251
+ label: `Dependency present: ${pkg}`,
252
+ details: pkgJson,
253
+ };
255
254
  }
256
255
  function isScriptHostRuntime(execToken) {
257
- const token = path.basename(execToken).toLowerCase();
258
- return (token == "node" ||
259
- token == "node.exe" ||
260
- token == "node.cmd" ||
261
- token == "bun" ||
262
- token == "bun.exe" ||
263
- token == "bun.cmd" ||
264
- token == "deno" ||
265
- token == "deno.exe" ||
266
- token == "deno.cmd" ||
267
- token == "tsx" ||
268
- token == "tsx.cmd" ||
269
- token == "ts-node" ||
270
- token == "ts-node.cmd");
256
+ const token = path.basename(execToken).toLowerCase();
257
+ return (
258
+ token == "node" ||
259
+ token == "node.exe" ||
260
+ token == "node.cmd" ||
261
+ token == "bun" ||
262
+ token == "bun.exe" ||
263
+ token == "bun.cmd" ||
264
+ token == "deno" ||
265
+ token == "deno.exe" ||
266
+ token == "deno.cmd" ||
267
+ token == "tsx" ||
268
+ token == "tsx.cmd" ||
269
+ token == "ts-node" ||
270
+ token == "ts-node.cmd"
271
+ );
271
272
  }
272
273
  function extractRuntimeScriptPath(args) {
273
- for (let i = 0; i < args.length; i++) {
274
- const token = args[i];
275
- if (token == "--") {
276
- const next = args[i + 1];
277
- if (next && isLikelyScriptPath(next))
278
- return next;
279
- return null;
280
- }
281
- if (token.startsWith("-"))
282
- continue;
283
- if (isLikelyScriptPath(token))
284
- return token;
285
- return null;
274
+ for (let i = 0; i < args.length; i++) {
275
+ const token = args[i];
276
+ if (token == "--") {
277
+ const next = args[i + 1];
278
+ if (next && isLikelyScriptPath(next)) return next;
279
+ return null;
286
280
  }
281
+ if (token.startsWith("-")) continue;
282
+ if (isLikelyScriptPath(token)) return token;
287
283
  return null;
284
+ }
285
+ return null;
288
286
  }
289
287
  function isLikelyScriptPath(token) {
290
- if (!token.length)
291
- return false;
292
- if (token == "<file>" || token == "<name>")
293
- return false;
294
- if (token.includes("://"))
295
- return false;
296
- if (token.startsWith("-"))
297
- return false;
298
- if (token.startsWith("./"))
299
- return true;
300
- if (token.startsWith("../"))
301
- return true;
302
- if (token.startsWith("/"))
303
- return true;
304
- if (token.startsWith(".\\"))
305
- return true;
306
- if (token.startsWith("..\\"))
307
- return true;
308
- if (/^[A-Za-z]:[\\/]/.test(token))
309
- return true;
310
- return /\.(mjs|cjs|js|ts)$/.test(token);
288
+ if (!token.length) return false;
289
+ if (token == "<file>" || token == "<name>") return false;
290
+ if (token.includes("://")) return false;
291
+ if (token.startsWith("-")) return false;
292
+ if (token.startsWith("./")) return true;
293
+ if (token.startsWith("../")) return true;
294
+ if (token.startsWith("/")) return true;
295
+ if (token.startsWith(".\\")) return true;
296
+ if (token.startsWith("..\\")) return true;
297
+ if (/^[A-Za-z]:[\\/]/.test(token)) return true;
298
+ return /\.(mjs|cjs|js|ts)$/.test(token);
311
299
  }
312
300
  function renderChecks(checks, configPath, selectedModes) {
313
- const errors = checks.filter((check) => check.status == "error").length;
314
- const warnings = checks.filter((check) => check.status == "warn").length;
315
- const oks = checks.filter((check) => check.status == "ok").length;
316
- process.stdout.write(chalk.bold.blueBright("as-test doctor") + "\n");
317
- process.stdout.write(chalk.dim(`config: ${configPath}`) + "\n");
318
- process.stdout.write(chalk.dim(`modes: ${selectedModes.length
319
- ? selectedModes.map((mode) => mode ?? "default").join(", ")
320
- : "default"}`) + "\n\n");
321
- for (const check of checks) {
322
- const badge = check.status == "ok"
323
- ? chalk.bgGreenBright.black(" OK ")
324
- : check.status == "warn"
325
- ? chalk.bgYellow.black(" WARN ")
326
- : chalk.bgRed.white(" ERROR ");
327
- process.stdout.write(`${badge} ${chalk.bold(`[${check.scope}]`)} ${check.label}\n`);
328
- process.stdout.write(` ${check.details}\n`);
329
- if (check.fix?.length) {
330
- process.stdout.write(chalk.dim(` fix: ${check.fix}\n`));
331
- }
301
+ const errors = checks.filter((check) => check.status == "error").length;
302
+ const warnings = checks.filter((check) => check.status == "warn").length;
303
+ const oks = checks.filter((check) => check.status == "ok").length;
304
+ process.stdout.write(chalk.bold.blueBright("as-test doctor") + "\n");
305
+ process.stdout.write(chalk.dim(`config: ${configPath}`) + "\n");
306
+ process.stdout.write(
307
+ chalk.dim(
308
+ `modes: ${
309
+ selectedModes.length
310
+ ? selectedModes.map((mode) => mode ?? "default").join(", ")
311
+ : "default"
312
+ }`,
313
+ ) + "\n\n",
314
+ );
315
+ for (const check of checks) {
316
+ const badge =
317
+ check.status == "ok"
318
+ ? chalk.bgGreenBright.black(" OK ")
319
+ : check.status == "warn"
320
+ ? chalk.bgYellow.black(" WARN ")
321
+ : chalk.bgRed.white(" ERROR ");
322
+ process.stdout.write(
323
+ `${badge} ${chalk.bold(`[${check.scope}]`)} ${check.label}\n`,
324
+ );
325
+ process.stdout.write(` ${check.details}\n`);
326
+ if (check.fix?.length) {
327
+ process.stdout.write(chalk.dim(` fix: ${check.fix}\n`));
332
328
  }
333
- process.stdout.write("\n");
334
- process.stdout.write(`${chalk.bold("Summary:")} ${chalk.greenBright(`${oks} ok`)}, ${warnings ? chalk.yellowBright(`${warnings} warn`) : chalk.gray("0 warn")}, ${errors ? chalk.redBright(`${errors} error`) : chalk.greenBright("0 error")}\n`);
329
+ }
330
+ process.stdout.write("\n");
331
+ process.stdout.write(
332
+ `${chalk.bold("Summary:")} ${chalk.greenBright(`${oks} ok`)}, ${warnings ? chalk.yellowBright(`${warnings} warn`) : chalk.gray("0 warn")}, ${errors ? chalk.redBright(`${errors} error`) : chalk.greenBright("0 error")}\n`,
333
+ );
335
334
  }
@@ -1,5 +1,5 @@
1
1
  import { doctor } from "./doctor-core.js";
2
2
  export { doctor } from "./doctor-core.js";
3
3
  export async function executeDoctorCommand(configPath, selectedModes) {
4
- await doctor(configPath, selectedModes);
4
+ await doctor(configPath, selectedModes);
5
5
  }