Skip to content

Commit 4e37b44

Browse files
committed
refactor config resolver debug logging to use inline colorized JSON and improve merge step descriptions
- Added color-json dependency for inline JSON colorization in debug output - Replaced node:util inspect() with color-json for CLI args, options, and patterns logging - Refactored config chain merge logging to show step-by-step merge operations with source/target paths - Added formatConfigJson() helper method to format configs as inline colorized JSON - Simplified cache hit/miss messages to "Cached:"
1 parent 5fcdcb8 commit 4e37b44

4 files changed

Lines changed: 66 additions & 74 deletions

File tree

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@
211211
"@valibot/to-json-schema": "^1.3.0",
212212
"chalk": "5.6.2",
213213
"clone-deep": "^4.0.1",
214+
"color-json": "^3.0.5",
214215
"commander": "^14.0.1",
215216
"deepmerge": "^4.3.1",
216217
"detect-indent": "^7.0.2",

pnpm-lock.yaml

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/cli/bin.ts

Lines changed: 20 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
/* eslint-disable n/no-process-exit */
44
import path from 'node:path';
55
import { fileURLToPath } from 'node:url';
6-
import { inspect } from 'node:util';
6+
7+
import colorJson from 'color-json';
78

89
import { version } from '../../package.json';
910
import { Debug } from '../utils/debug';
@@ -72,9 +73,12 @@ const main = async () => {
7273
});
7374
const cliDebug = debug.module('cli');
7475

76+
const patterns = program.args.length > 0 ? program.args : [DEFAULT_PATTERN];
77+
7578
if (options.debug) {
76-
cliDebug.log(`CLI args: ${inspect(process.argv.slice(2), { colors: useColors })}`);
77-
cliDebug.log(`CLI options: ${inspect(options, { colors: useColors, depth: 2, breakLength: Infinity })}`);
79+
cliDebug.log(`CLI args: ${colorJson(process.argv.slice(2), undefined, undefined, 0)}`);
80+
cliDebug.log(`CLI options: ${colorJson(options, undefined, undefined, 0)}`);
81+
cliDebug.log(`Patterns: ${colorJson(patterns, undefined, undefined, 0)}`);
7882
}
7983

8084
enforceSoloOptions(program, ['init']);
@@ -89,7 +93,6 @@ const main = async () => {
8993
const fsAdapter = new NodeFileSystemAdapter();
9094
const pathAdapter = new NodePathAdapter();
9195

92-
cliDebug.log('Initializing config resolver');
9396
const configResolver = new ConfigResolver(
9497
fsAdapter,
9598
pathAdapter,
@@ -100,7 +103,6 @@ const main = async () => {
100103
debug.module('config-resolver'),
101104
);
102105

103-
cliDebug.log('Building linter tree');
104106
const tree = new LinterTree(
105107
fsAdapter,
106108
pathAdapter,
@@ -116,11 +118,8 @@ const main = async () => {
116118
debug.module('linter-tree'),
117119
);
118120

119-
if (options.debug) {
120-
cliDebug.log(`Matching patterns: ${program.args.length > 0 ? program.args.join(', ') : DEFAULT_PATTERN}`);
121-
}
122121
const matchedPatterns = await matchPatterns(
123-
program.args.length > 0 ? program.args : [DEFAULT_PATTERN],
122+
patterns,
124123
fsAdapter,
125124
pathAdapter,
126125
{
@@ -139,15 +138,13 @@ const main = async () => {
139138
await tree.addFile(pattern);
140139
}
141140

142-
cliDebug.log('Initializing file scanner');
143141
const scanner = new LinterFileScanner(
144142
tree,
145143
configResolver,
146144
fsAdapter,
147145
debug.module('file-scanner'),
148146
);
149147

150-
cliDebug.log('Scanning files');
151148
const files = await scanner.scanAll(matchedPatterns.files);
152149

153150
if (options.printConfig) {
@@ -158,10 +155,11 @@ const main = async () => {
158155
const file = matchedPatterns.files[0]!;
159156
const config = await tree.getResolvedConfig(file);
160157

161-
console.log(inspect(config, {
162-
colors: useColors,
163-
depth: Infinity,
164-
}));
158+
console.log(
159+
useColors
160+
? colorJson(config)
161+
: JSON.stringify(config, null, 2),
162+
);
165163
return;
166164
}
167165

@@ -179,13 +177,6 @@ const main = async () => {
179177
? await LintResultCache.create(cwd, options.cacheLocation)
180178
: undefined;
181179

182-
if (cache) {
183-
cliDebug.log(`Cache enabled: ${options.cacheLocation}`);
184-
cliDebug.log(`Cache strategy: ${options.cacheStrategy}`);
185-
} else {
186-
cliDebug.log('Cache disabled');
187-
}
188-
189180
let reporter: LinterCliReporter;
190181
if (options.reporter === 'json' || options.reporter === 'json-with-metadata') {
191182
reporter = new LinterJsonReporter();
@@ -206,15 +197,15 @@ const main = async () => {
206197
cliDebug.log(`Thread configuration: ${maxThreads} max threads (auto: ${isAutoMode})`);
207198
cliDebug.log(`Project size: ${files.length} files, ${totalSize} bytes (small: ${isSmall})`);
208199

209-
let hasErrors = false;
200+
let foundErrors = false;
210201

211202
// Run sequentially if single-threaded or auto mode with small project
212203
if (maxThreads === 1 || (isAutoMode && isSmall)) {
213204
cliDebug.log('Running in sequential mode');
214205
if (cache) {
215-
hasErrors = await runSequentialWithCache(files, options, reporter, cwd, cache);
206+
foundErrors = await runSequentialWithCache(files, options, reporter, cwd, cache);
216207
} else {
217-
hasErrors = await runSequential(files, options, reporter, cwd);
208+
foundErrors = await runSequential(files, options, reporter, cwd);
218209
}
219210
} else {
220211
cliDebug.log(`Running in parallel mode with ${maxThreads} threads`);
@@ -229,9 +220,9 @@ const main = async () => {
229220
);
230221
}
231222
if (cache) {
232-
hasErrors = await runParallelWithCache(buckets, options, reporter, cwd, maxThreads, cache);
223+
foundErrors = await runParallelWithCache(buckets, options, reporter, cwd, maxThreads, cache);
233224
} else {
234-
hasErrors = await runParallel(buckets, options, reporter, cwd, maxThreads);
225+
foundErrors = await runParallel(buckets, options, reporter, cwd, maxThreads);
235226
}
236227
}
237228

@@ -241,9 +232,9 @@ const main = async () => {
241232
await cache.save();
242233
}
243234

244-
cliDebug.log(`Linting completed. Errors: ${hasErrors}`);
235+
cliDebug.log(`Linting completed. Errors: ${foundErrors}`);
245236

246-
if (hasErrors) {
237+
if (foundErrors) {
247238
cliDebug.log('Exiting with error code 1');
248239
process.exit(1);
249240
}

src/cli/utils/config-resolver/config-resolver.ts

Lines changed: 36 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import cj from 'color-json';
12
import * as v from 'valibot';
23
import { parse as parseYaml } from 'yaml';
34

@@ -75,18 +76,15 @@ export class ConfigResolver {
7576
// Check cache
7677
if (this.cache.has(absPath)) {
7778
if (this.debug) {
78-
this.debug.log(`Using cached config: ${absPath}`);
79+
this.debug.log(`Cached: ${absPath}`);
7980
}
8081
return this.cache.get(absPath)!.config;
8182
}
8283

8384
if (this.debug) {
84-
this.debug.log(`Resolving config: ${absPath}`);
85+
this.debug.log(`Resolving: ${absPath}`);
8586
}
8687
const result = await this.resolveRecursive(absPath, new Set());
87-
if (this.debug) {
88-
this.debug.log(`Resolved config from ${absPath}: ${JSON.stringify(result.config)}`);
89-
}
9088
return result.config;
9189
}
9290

@@ -122,41 +120,42 @@ export class ConfigResolver {
122120
*/
123121
public async resolveChain(configChain: ConfigChainEntry[]): Promise<LinterConfig> {
124122
if (configChain.length === 0) {
125-
if (this.debug) {
126-
this.debug.log('No config chain, using base config only');
127-
}
128-
const baseConfig = deepMerge({}, this.options.baseConfig || {}) as LinterConfig;
129-
if (this.debug) {
130-
this.debug.log(`Base config: ${JSON.stringify(baseConfig)}`);
131-
}
132-
return baseConfig;
123+
return deepMerge({}, this.options.baseConfig || {}) as LinterConfig;
133124
}
134125

135-
// Log chain paths
126+
// Log config chain hierarchy
136127
if (this.debug) {
128+
const targetPath = configChain[0]!.path;
137129
const chainPaths = configChain.map((entry) => entry.path).join(' <- ');
138-
this.debug.log(`Config chain (${configChain.length} file(s)): ${chainPaths}`);
130+
this.debug.log(`Config chain for ${targetPath} is: ${chainPaths}`);
139131
}
140132

141133
// Start with base config
142134
let merged = deepMerge({}, this.options.baseConfig || {}) as LinterConfig;
143-
if (this.debug && Object.keys(merged).length > 0) {
144-
this.debug.log(`Starting with base config: ${JSON.stringify(merged)}`);
145-
}
146135

147136
// Merge chain from farthest (root) to closest
137+
const totalSteps = configChain.length;
148138
for (let i = configChain.length - 1; i >= 0; i -= 1) {
149139
const entry = configChain[i]!;
140+
const stepNum = totalSteps - i;
141+
const sourcePath = entry.path;
142+
150143
if (this.debug) {
151-
const configJson = JSON.stringify(entry.config);
152-
this.debug.log(`Merging config [${i + 1}/${configChain.length}] from ${entry.path}: ${configJson}`);
144+
if (i === configChain.length - 1) {
145+
this.debug.log(`[${stepNum}/${totalSteps}] Merge base config to ${sourcePath}`);
146+
} else {
147+
const previousPath = configChain[i + 1]!.path;
148+
this.debug.log(`[${stepNum}/${totalSteps}] Merge ${previousPath} to ${sourcePath}`);
149+
}
153150
}
151+
154152
merged = deepMerge(merged, entry.config) as LinterConfig;
155-
}
156153

157-
if (this.debug) {
158-
this.debug.log(`Final merged config: ${JSON.stringify(merged)}`);
154+
if (this.debug) {
155+
this.debug.log(`Result for ${sourcePath}: ${ConfigResolver.formatConfigJson(merged)}`);
156+
}
159157
}
158+
160159
return ConfigResolver.normalizeConfig(merged);
161160
}
162161

@@ -210,31 +209,24 @@ export class ConfigResolver {
210209
const parsed = this.parseConfig(content, absPath);
211210
const configDir = this.pathAdapter.dirname(absPath);
212211

213-
if (this.debug) {
214-
this.debug.log(`Parsed config from ${absPath}: ${JSON.stringify(parsed)}`);
215-
}
216-
217212
// Resolve extends
218213
let mergedFromExtends: LinterConfig = {} as LinterConfig;
219214

220215
if (parsed.extends?.length) {
221216
if (this.debug) {
222217
const extendsRefs = parsed.extends.join(', ');
223-
this.debug.log(`Config extends: [${extendsRefs}]`);
218+
this.debug.log(`Config ${absPath} extends: [${extendsRefs}]`);
224219
}
225220
for (const ref of parsed.extends) {
226221
let refPath: string;
227222

228223
if (ref.startsWith(AGLINT_PREFIX)) {
229224
// Preset reference
230225
const presetName = ref.slice(AGLINT_PREFIX.length);
231-
if (this.debug) {
232-
this.debug.log(`Resolving preset: "${presetName}"`);
233-
}
234226
// eslint-disable-next-line no-await-in-loop
235227
refPath = await this.presetResolver.resolve(presetName);
236228
if (this.debug) {
237-
this.debug.log(`Preset "${presetName}" resolved to: ${refPath}`);
229+
this.debug.log(`Config ${absPath} extends preset "${presetName}" -> ${refPath}`);
238230
}
239231
} else {
240232
// Relative path reference
@@ -246,30 +238,17 @@ export class ConfigResolver {
246238

247239
// eslint-disable-next-line no-await-in-loop
248240
const extendedEntry = await this.resolveRecursive(refPath, new Set(seen));
249-
if (this.debug) {
250-
const extendedJson = JSON.stringify(extendedEntry.config);
251-
this.debug.log(`Extended config from "${ref}": ${extendedJson}`);
252-
}
253241
mergedFromExtends = deepMerge(
254242
mergedFromExtends,
255243
extendedEntry.config,
256244
) as LinterConfig;
257245
}
258-
if (this.debug) {
259-
this.debug.log(`Merged all extends: ${JSON.stringify(mergedFromExtends)}`);
260-
}
261246
}
262247

263248
// Merge local config (drop extends and root)
264249
// eslint-disable-next-line @typescript-eslint/naming-convention
265250
const { extends: _extends, root: _root, ...localRest } = parsed;
266-
if (this.debug) {
267-
this.debug.log(`Local config (without extends/root): ${JSON.stringify(localRest)}`);
268-
}
269251
const flattened = deepMerge(mergedFromExtends, localRest) as LinterConfig;
270-
if (this.debug) {
271-
this.debug.log(`Flattened config for ${absPath}: ${JSON.stringify(flattened)}`);
272-
}
273252

274253
// Normalize severity values before caching
275254
const normalized = ConfigResolver.normalizeConfig(flattened);
@@ -436,6 +415,18 @@ export class ConfigResolver {
436415
}
437416
}
438417

418+
/**
419+
* Formats a config as colorized JSON for debug logging.
420+
*
421+
* @param config The config to format.
422+
*
423+
* @returns Colorized JSON string (inline, not pretty-printed).
424+
*/
425+
private static formatConfigJson(config: LinterConfig): string {
426+
// Stringify without formatting (inline), then colorize
427+
return cj(config, undefined, undefined, 0);
428+
}
429+
439430
/**
440431
* Formats a valibot error path into a dot-separated property path.
441432
*

0 commit comments

Comments
 (0)