helm-env-delta 1.1.3 → 1.2.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 CHANGED
@@ -666,20 +666,28 @@ hed --config <file> [options]
666
666
 
667
667
  ### Options
668
668
 
669
- | Option | Short | Description | Default |
670
- | ----------------- | ----- | ------------------------------------------- | ------------ |
671
- | `--config <path>` | `-c` | Path to YAML configuration file | **required** |
672
- | `--dry-run` | | Preview changes without writing files | `false` |
673
- | `--force` | | Override stop rules and proceed | `false` |
674
- | `--diff` | | Display console diff for changed files | `false` |
675
- | `--diff-html` | | Generate HTML report and open in browser | `false` |
676
- | `--diff-json` | | Output diff as JSON to stdout | `false` |
677
- | `--skip-format` | | Skip YAML formatting (outputFormat section) | `false` |
678
- | `--help` | `-h` | Display help | |
669
+ | Option | Short | Description | Default |
670
+ | ----------------- | ----- | ---------------------------------------------------------- | ------------ |
671
+ | `--config <path>` | `-c` | Path to YAML configuration file | **required** |
672
+ | `--validate` | | Validate configuration file and exit | `false` |
673
+ | `--dry-run` | | Preview changes without writing files | `false` |
674
+ | `--force` | | Override stop rules and proceed | `false` |
675
+ | `--diff` | | Display console diff for changed files | `false` |
676
+ | `--diff-html` | | Generate HTML report and open in browser | `false` |
677
+ | `--diff-json` | | Output diff as JSON to stdout | `false` |
678
+ | `--skip-format` | | Skip YAML formatting (outputFormat section) | `false` |
679
+ | `--verbose` | | Show detailed debug information (config, transforms, etc.) | `false` |
680
+ | `--quiet` | | Suppress all output except critical errors | `false` |
681
+ | `--help` | `-h` | Display help | |
682
+
683
+ **Note:** `--verbose` and `--quiet` are mutually exclusive. Machine-readable output (`--diff-json`) always outputs regardless of verbosity.
679
684
 
680
685
  ### Examples
681
686
 
682
687
  ```bash
688
+ # Validate configuration file
689
+ helm-env-delta --config config.yaml --validate
690
+
683
691
  # Basic sync
684
692
  helm-env-delta --config config.yaml
685
693
 
@@ -6,5 +6,8 @@ export type SyncCommand = {
6
6
  diffHtml: boolean;
7
7
  diffJson: boolean;
8
8
  skipFormat: boolean;
9
+ validate: boolean;
10
+ verbose: boolean;
11
+ quiet: boolean;
9
12
  };
10
13
  export declare const parseCommandLine: (argv?: string[]) => SyncCommand;
@@ -18,9 +18,16 @@ const parseCommandLine = (argv) => {
18
18
  .option('--diff', 'Display console diff for changed files', false)
19
19
  .option('--diff-html', 'Generate and open HTML diff report in browser', false)
20
20
  .option('--diff-json', 'Output diff as JSON to stdout', false)
21
- .option('--skip-format', 'Skip YAML formatting (outputFormat section)', false);
21
+ .option('--skip-format', 'Skip YAML formatting (outputFormat section)', false)
22
+ .option('--validate', 'Validate configuration file and exit', false)
23
+ .option('--verbose', 'Show detailed debug information', false)
24
+ .option('--quiet', 'Suppress all output except critical errors', false);
22
25
  program.parse(argv || process.argv);
23
26
  const options = program.opts();
27
+ if (options['verbose'] && options['quiet']) {
28
+ console.error('Error: --verbose and --quiet flags are mutually exclusive');
29
+ process.exit(1);
30
+ }
24
31
  return {
25
32
  config: options['config'],
26
33
  dryRun: options['dryRun'],
@@ -28,7 +35,10 @@ const parseCommandLine = (argv) => {
28
35
  diff: options['diff'],
29
36
  diffHtml: options['diffHtml'],
30
37
  diffJson: options['diffJson'],
31
- skipFormat: options['skipFormat']
38
+ skipFormat: options['skipFormat'],
39
+ validate: options['validate'],
40
+ verbose: options['verbose'],
41
+ quiet: options['quiet']
32
42
  };
33
43
  };
34
44
  exports.parseCommandLine = parseCommandLine;
@@ -1,3 +1,3 @@
1
1
  import { type FinalConfig } from './configFile';
2
2
  export type Config = FinalConfig;
3
- export declare const loadConfigFile: (configPath: string) => FinalConfig;
3
+ export declare const loadConfigFile: (configPath: string, quiet?: boolean, logger?: import("./logger").Logger) => FinalConfig;
@@ -3,10 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.loadConfigFile = void 0;
4
4
  const configFile_1 = require("./configFile");
5
5
  const configMerger_1 = require("./configMerger");
6
- const loadConfigFile = (configPath) => {
7
- const mergedConfig = (0, configMerger_1.resolveConfigWithExtends)(configPath);
6
+ const loadConfigFile = (configPath, quiet = false, logger) => {
7
+ const mergedConfig = (0, configMerger_1.resolveConfigWithExtends)(configPath, new Set(), 0, logger);
8
8
  const config = (0, configFile_1.parseFinalConfig)(mergedConfig, configPath);
9
- console.log(`\nConfiguration loaded: ${config.source} -> ${config.destination}` + (config.prune ? ' [prune!]' : ''));
9
+ if (!quiet)
10
+ console.log(`\nConfiguration loaded: ${config.source} -> ${config.destination}` + (config.prune ? ' [prune!]' : ''));
10
11
  return config;
11
12
  };
12
13
  exports.loadConfigFile = loadConfigFile;
@@ -17,5 +17,5 @@ export declare class ConfigMergerError extends ConfigMergerErrorClass {
17
17
  }
18
18
  export declare const isConfigMergerError: (error: unknown) => error is ConfigMergerError;
19
19
  export declare const mergeConfigs: (parent: BaseConfig, child: BaseConfig) => BaseConfig;
20
- export declare const resolveConfigWithExtends: (configPath: string, visited?: Set<string>, depth?: number) => BaseConfig;
20
+ export declare const resolveConfigWithExtends: (configPath: string, visited?: Set<string>, depth?: number, logger?: import("./logger").Logger) => BaseConfig;
21
21
  export {};
@@ -133,7 +133,7 @@ const mergePerFileRecords = (parent, child) => {
133
133
  merged[pattern] = merged[pattern] === undefined ? [...rules] : [...merged[pattern], ...rules];
134
134
  return merged;
135
135
  };
136
- const resolveConfigWithExtends = (configPath, visited = new Set(), depth = 0) => {
136
+ const resolveConfigWithExtends = (configPath, visited = new Set(), depth = 0, logger) => {
137
137
  if (depth > MAX_EXTENDS_DEPTH) {
138
138
  const depthError = new ConfigMergerError('Extends chain exceeds maximum depth of 5', {
139
139
  code: 'MAX_DEPTH_EXCEEDED',
@@ -202,6 +202,10 @@ const resolveConfigWithExtends = (configPath, visited = new Set(), depth = 0) =>
202
202
  });
203
203
  }
204
204
  const config = (0, configFile_1.parseBaseConfig)(rawConfig, absolutePath);
205
+ if (logger?.shouldShow('debug')) {
206
+ const filename = absolutePath.split('/').pop();
207
+ logger.debug(`Loading config: ${filename} (depth: ${depth})`);
208
+ }
205
209
  if (config.extends === undefined)
206
210
  return config;
207
211
  const configDirectory = node_path_1.default.dirname(absolutePath);
@@ -232,7 +236,9 @@ const resolveConfigWithExtends = (configPath, visited = new Set(), depth = 0) =>
232
236
  cause: error
233
237
  });
234
238
  }
235
- const parentConfig = (0, exports.resolveConfigWithExtends)(parentPath, visitedWithCurrent, depth + 1);
239
+ if (logger?.shouldShow('debug'))
240
+ logger.debug(` Extends: ${config.extends} → ${parentPath.split('/').pop()}`);
241
+ const parentConfig = (0, exports.resolveConfigWithExtends)(parentPath, visitedWithCurrent, depth + 1, logger);
236
242
  return (0, exports.mergeConfigs)(parentConfig, config);
237
243
  };
238
244
  exports.resolveConfigWithExtends = resolveConfigWithExtends;
@@ -1,10 +1,9 @@
1
1
  import { StopRuleViolation } from './stopRulesValidator';
2
- type BoxStyle = 'success' | 'warning' | 'error' | 'info';
3
- type ProgressStyle = 'loading' | 'success' | 'info';
4
- type ViolationMode = 'error' | 'warning' | 'force';
5
- type FileOperation = 'add' | 'update' | 'delete' | 'format';
2
+ export type BoxStyle = 'success' | 'warning' | 'error' | 'info';
3
+ export type ProgressStyle = 'loading' | 'success' | 'info';
4
+ export type ViolationMode = 'error' | 'warning' | 'force';
5
+ export type FileOperation = 'add' | 'update' | 'delete' | 'format';
6
6
  export declare const formatBox: (title: string, content: string[], style?: BoxStyle, width?: number) => string;
7
7
  export declare const formatStopRuleViolation: (violation: StopRuleViolation, mode: ViolationMode) => string;
8
8
  export declare const colorizeFileOperation: (operation: FileOperation, filePath: string, isDryRun: boolean, alreadyDeleted?: boolean) => string;
9
9
  export declare const formatProgressMessage: (message: string, style: ProgressStyle) => string;
10
- export {};
@@ -37,5 +37,5 @@ export declare class FileDiffError extends FileDiffErrorClass {
37
37
  }
38
38
  export declare const isFileDiffError: (error: unknown) => error is FileDiffError;
39
39
  export declare const getSkipPathsForFile: (filePath: string, skipPath?: Record<string, string[]>) => string[];
40
- export declare const computeFileDiff: (sourceFiles: FileMap, destinationFiles: FileMap, config: Config) => FileDiffResult;
40
+ export declare const computeFileDiff: (sourceFiles: FileMap, destinationFiles: FileMap, config: Config, logger?: import("./logger").Logger) => FileDiffResult;
41
41
  export {};
package/dist/fileDiff.js CHANGED
@@ -162,7 +162,18 @@ const processChangedFiles = (sourceFiles, destinationFiles, skipPath, transforms
162
162
  }
163
163
  return { changedFiles, unchangedFiles };
164
164
  };
165
- const computeFileDiff = (sourceFiles, destinationFiles, config) => {
165
+ const computeFileDiff = (sourceFiles, destinationFiles, config, logger) => {
166
+ if (logger?.shouldShow('debug')) {
167
+ logger.debug('Computing file differences:');
168
+ logger.debug(` Source files: ${sourceFiles.size}`);
169
+ logger.debug(` Destination files: ${destinationFiles.size}`);
170
+ const transformCount = Object.keys(config.transforms || {}).length;
171
+ if (transformCount > 0)
172
+ logger.debug(` Content transform patterns: ${transformCount}`);
173
+ const skipPathCount = Object.keys(config.skipPath || {}).length;
174
+ if (skipPathCount > 0)
175
+ logger.debug(` SkipPath patterns: ${skipPathCount}`);
176
+ }
166
177
  const addedFiles = detectAddedFiles(sourceFiles, destinationFiles);
167
178
  const deletedFiles = config.prune ? detectDeletedFiles(sourceFiles, destinationFiles) : [];
168
179
  const { changedFiles, unchangedFiles } = processChangedFiles(sourceFiles, destinationFiles, config.skipPath, config.transforms);
@@ -23,5 +23,5 @@ declare const FileLoaderErrorClass: {
23
23
  export declare class FileLoaderError extends FileLoaderErrorClass {
24
24
  }
25
25
  export declare const isFileLoaderError: (error: unknown) => error is FileLoaderError;
26
- export declare const loadFiles: (options: FileLoaderOptions) => Promise<FileMap>;
26
+ export declare const loadFiles: (options: FileLoaderOptions, logger?: import("./logger").Logger) => Promise<FileMap>;
27
27
  export {};
@@ -147,13 +147,33 @@ const readFilesIntoMap = async (baseDirectory, absoluteFilePaths) => {
147
147
  });
148
148
  }
149
149
  };
150
- const loadFiles = async (options) => {
150
+ const loadFiles = async (options, logger) => {
151
151
  const absoluteBaseDirectory = await validateAndResolveBaseDirectory(options.baseDirectory);
152
152
  const includePatterns = options.include ?? ['**/*'];
153
153
  const excludePatterns = options.exclude ?? [];
154
154
  const files = await findMatchingFiles(absoluteBaseDirectory, includePatterns, excludePatterns, options.transforms);
155
+ if (logger?.shouldShow('debug')) {
156
+ logger.debug('Glob matching:');
157
+ logger.debug(` Directory: ${absoluteBaseDirectory}`);
158
+ logger.debug(` Include patterns: ${includePatterns.join(', ')}`);
159
+ logger.debug(` Exclude patterns: ${excludePatterns.join(', ')}`);
160
+ logger.debug(` Matched: ${files.length} file(s)`);
161
+ }
155
162
  const fileMap = await readFilesIntoMap(absoluteBaseDirectory, files);
156
163
  const transformedMap = options.transforms ? (0, filenameTransformer_1.transformFilenameMap)(fileMap, options.transforms) : fileMap;
164
+ if (options.transforms && logger?.shouldShow('debug')) {
165
+ logger.debug(`Filename transforms applied: ${fileMap.size} → ${transformedMap.size} files`);
166
+ let exampleCount = 0;
167
+ for (const [transformed, content] of transformedMap.entries()) {
168
+ const original = [...fileMap.entries()].find(([_key, c]) => c === content)?.[0];
169
+ if (original && original !== transformed) {
170
+ logger.debug(` ${original} → ${transformed}`);
171
+ exampleCount++;
172
+ if (exampleCount >= 3)
173
+ break;
174
+ }
175
+ }
176
+ }
157
177
  return sortMapByKeys(transformedMap);
158
178
  };
159
179
  exports.loadFiles = loadFiles;
@@ -1,6 +1,7 @@
1
1
  import { Config } from './configFile';
2
2
  import { FileDiffResult } from './fileDiff';
3
3
  import { FileMap } from './fileLoader';
4
+ import { Logger } from './logger';
4
5
  export interface FileUpdateError {
5
6
  operation: 'add' | 'update' | 'delete';
6
7
  path: string;
@@ -23,5 +24,5 @@ declare const FileUpdaterErrorClass: {
23
24
  export declare class FileUpdaterError extends FileUpdaterErrorClass {
24
25
  }
25
26
  export declare const isFileUpdaterError: (error: unknown) => error is FileUpdaterError;
26
- export declare const updateFiles: (diffResult: FileDiffResult, sourceFiles: FileMap, destinationFiles: FileMap, config: Config, dryRun: boolean, skipFormat?: boolean) => Promise<string[]>;
27
+ export declare const updateFiles: (diffResult: FileDiffResult, sourceFiles: FileMap, destinationFiles: FileMap, config: Config, dryRun: boolean, skipFormat: boolean, logger: Logger) => Promise<string[]>;
27
28
  export {};
@@ -117,10 +117,10 @@ const mergeYamlContent = (destinationContent, processedSourceContent, filePath)
117
117
  });
118
118
  }
119
119
  };
120
- const addFile = async (relativePath, content, absoluteDestinationDirectory, config, dryRun, skipFormat = false) => {
120
+ const addFile = async (relativePath, content, absoluteDestinationDirectory, config, dryRun, skipFormat, logger) => {
121
121
  const absolutePath = node_path_1.default.join(absoluteDestinationDirectory, relativePath);
122
122
  if (dryRun) {
123
- console.log((0, consoleFormatter_1.colorizeFileOperation)('add', relativePath, true));
123
+ logger.fileOp('add', relativePath, true);
124
124
  return;
125
125
  }
126
126
  let contentToWrite = content;
@@ -142,7 +142,7 @@ const addFile = async (relativePath, content, absoluteDestinationDirectory, conf
142
142
  try {
143
143
  await ensureParentDirectory(absolutePath);
144
144
  await (0, promises_1.writeFile)(absolutePath, contentToWrite, 'utf8');
145
- console.log((0, consoleFormatter_1.colorizeFileOperation)('add', relativePath, false));
145
+ logger.fileOp('add', relativePath, false);
146
146
  }
147
147
  catch (error) {
148
148
  throw new FileUpdaterError('Failed to add file', {
@@ -152,10 +152,10 @@ const addFile = async (relativePath, content, absoluteDestinationDirectory, conf
152
152
  });
153
153
  }
154
154
  };
155
- const updateFile = async (changedFile, absoluteDestinationDirectory, config, dryRun, skipFormat = false) => {
155
+ const updateFile = async (changedFile, absoluteDestinationDirectory, config, dryRun, skipFormat, logger) => {
156
156
  const absolutePath = node_path_1.default.join(absoluteDestinationDirectory, changedFile.path);
157
157
  if (dryRun) {
158
- console.log((0, consoleFormatter_1.colorizeFileOperation)('update', changedFile.path, true));
158
+ logger.fileOp('update', changedFile.path, true);
159
159
  return;
160
160
  }
161
161
  let contentToWrite = (0, fileType_1.isYamlFile)(changedFile.path)
@@ -168,7 +168,7 @@ const updateFile = async (changedFile, absoluteDestinationDirectory, config, dry
168
168
  try {
169
169
  await ensureParentDirectory(absolutePath);
170
170
  await (0, promises_1.writeFile)(absolutePath, contentToWrite, 'utf8');
171
- console.log((0, consoleFormatter_1.colorizeFileOperation)('update', changedFile.path, false));
171
+ logger.fileOp('update', changedFile.path, false);
172
172
  }
173
173
  catch (error) {
174
174
  throw new FileUpdaterError('Failed to update file', {
@@ -178,19 +178,19 @@ const updateFile = async (changedFile, absoluteDestinationDirectory, config, dry
178
178
  });
179
179
  }
180
180
  };
181
- const deleteFile = async (relativePath, absoluteDestinationDirectory, dryRun) => {
181
+ const deleteFile = async (relativePath, absoluteDestinationDirectory, dryRun, logger) => {
182
182
  const absolutePath = node_path_1.default.join(absoluteDestinationDirectory, relativePath);
183
183
  if (dryRun) {
184
- console.log((0, consoleFormatter_1.colorizeFileOperation)('delete', relativePath, true));
184
+ logger.fileOp('delete', relativePath, true);
185
185
  return;
186
186
  }
187
187
  try {
188
188
  await (0, promises_1.unlink)(absolutePath);
189
- console.log((0, consoleFormatter_1.colorizeFileOperation)('delete', relativePath, false));
189
+ logger.fileOp('delete', relativePath, false);
190
190
  }
191
191
  catch (error) {
192
192
  if (error.code === 'ENOENT') {
193
- console.log((0, consoleFormatter_1.colorizeFileOperation)('delete', relativePath, false, true));
193
+ logger.fileOp('delete', relativePath, false, true);
194
194
  return;
195
195
  }
196
196
  throw new FileUpdaterError('Failed to delete file', {
@@ -200,21 +200,25 @@ const deleteFile = async (relativePath, absoluteDestinationDirectory, dryRun) =>
200
200
  });
201
201
  }
202
202
  };
203
- const updateFiles = async (diffResult, sourceFiles, destinationFiles, config, dryRun, skipFormat = false) => {
204
- console.log('\n' + (0, consoleFormatter_1.formatProgressMessage)('Updating files...', 'info'));
203
+ const updateFiles = async (diffResult, sourceFiles, destinationFiles, config, dryRun, skipFormat, logger) => {
204
+ logger.log('\n' + (0, consoleFormatter_1.formatProgressMessage)('Updating files...', 'info'));
205
205
  const absoluteDestinationDirectory = await validateDestinationDirectory(config.destination);
206
206
  const errors = [];
207
+ if (logger.shouldShow('debug'))
208
+ logger.debug(`Processing ${diffResult.addedFiles.length} new files`);
207
209
  for (const relativePath of diffResult.addedFiles)
208
210
  try {
209
211
  const content = sourceFiles.get(relativePath);
210
- await addFile(relativePath, content, absoluteDestinationDirectory, config, dryRun, skipFormat);
212
+ await addFile(relativePath, content, absoluteDestinationDirectory, config, dryRun, skipFormat, logger);
211
213
  }
212
214
  catch (error) {
213
215
  errors.push({ operation: 'add', path: relativePath, error: error });
214
216
  }
217
+ if (logger.shouldShow('debug'))
218
+ logger.debug(`Updating ${diffResult.changedFiles.length} changed files`);
215
219
  for (const changedFile of diffResult.changedFiles)
216
220
  try {
217
- await updateFile(changedFile, absoluteDestinationDirectory, config, dryRun, skipFormat);
221
+ await updateFile(changedFile, absoluteDestinationDirectory, config, dryRun, skipFormat, logger);
218
222
  }
219
223
  catch (error) {
220
224
  errors.push({ operation: 'update', path: changedFile.path, error: error });
@@ -229,11 +233,11 @@ const updateFiles = async (diffResult, sourceFiles, destinationFiles, config, dr
229
233
  if (formatted !== content) {
230
234
  const absolutePath = node_path_1.default.join(absoluteDestinationDirectory, relativePath);
231
235
  if (dryRun)
232
- console.log((0, consoleFormatter_1.colorizeFileOperation)('format', relativePath, true));
236
+ logger.fileOp('format', relativePath, true);
233
237
  else {
234
238
  await ensureParentDirectory(absolutePath);
235
239
  await (0, promises_1.writeFile)(absolutePath, formatted, 'utf8');
236
- console.log((0, consoleFormatter_1.colorizeFileOperation)('format', relativePath, false));
240
+ logger.fileOp('format', relativePath, false);
237
241
  }
238
242
  formattedFiles.push(relativePath);
239
243
  }
@@ -243,29 +247,29 @@ const updateFiles = async (diffResult, sourceFiles, destinationFiles, config, dr
243
247
  }
244
248
  for (const relativePath of diffResult.deletedFiles)
245
249
  try {
246
- await deleteFile(relativePath, absoluteDestinationDirectory, dryRun);
250
+ await deleteFile(relativePath, absoluteDestinationDirectory, dryRun, logger);
247
251
  }
248
252
  catch (error) {
249
253
  errors.push({ operation: 'delete', path: relativePath, error: error });
250
254
  }
251
255
  if (dryRun) {
252
- console.log('\n[DRY RUN] Would perform:');
253
- console.log(` ${diffResult.addedFiles.length} files would be added`);
254
- console.log(` ${diffResult.changedFiles.length} files would be updated`);
255
- console.log(` ${formattedFiles.length} files would be formatted`);
256
- console.log(` ${diffResult.deletedFiles.length} files would be deleted`);
256
+ logger.log('\n[DRY RUN] Would perform:');
257
+ logger.log(` ${diffResult.addedFiles.length} files would be added`);
258
+ logger.log(` ${diffResult.changedFiles.length} files would be updated`);
259
+ logger.log(` ${formattedFiles.length} files would be formatted`);
260
+ logger.log(` ${diffResult.deletedFiles.length} files would be deleted`);
257
261
  }
258
262
  else {
259
- console.log('\n✓ Files updated successfully:');
260
- console.log(` ${diffResult.addedFiles.length} files added`);
261
- console.log(` ${diffResult.changedFiles.length} files updated`);
262
- console.log(` ${formattedFiles.length} files formatted`);
263
- console.log(` ${diffResult.deletedFiles.length} files deleted`);
263
+ logger.log('\n✓ Files updated successfully:');
264
+ logger.log(` ${diffResult.addedFiles.length} files added`);
265
+ logger.log(` ${diffResult.changedFiles.length} files updated`);
266
+ logger.log(` ${formattedFiles.length} files formatted`);
267
+ logger.log(` ${diffResult.deletedFiles.length} files deleted`);
264
268
  }
265
269
  if (errors.length > 0) {
266
- console.error(`\n❌ Encountered ${errors.length} error(s):`);
270
+ logger.error(`\n❌ Encountered ${errors.length} error(s):`, 'critical');
267
271
  for (const { operation, path: errorPath, error } of errors)
268
- console.error(` [${operation}] ${errorPath}: ${error.message}`);
272
+ logger.error(` [${operation}] ${errorPath}: ${error.message}`, 'critical');
269
273
  throw new FileUpdaterError(`Failed to update ${errors.length} file(s)`, { code: 'UPDATE_FAILED' });
270
274
  }
271
275
  return formattedFiles;
@@ -23,5 +23,5 @@ declare const HtmlReporterErrorClass: {
23
23
  export declare class HtmlReporterError extends HtmlReporterErrorClass {
24
24
  }
25
25
  export declare const isHtmlReporterError: (error: unknown) => error is HtmlReporterError;
26
- export declare const generateHtmlReport: (diffResult: FileDiffResult, formattedFiles: string[], config: Config, dryRun: boolean) => Promise<void>;
26
+ export declare const generateHtmlReport: (diffResult: FileDiffResult, formattedFiles: string[], config: Config, dryRun: boolean, logger?: import("./logger").Logger) => Promise<void>;
27
27
  export {};
@@ -585,7 +585,7 @@ const openInBrowser = async (filePath) => {
585
585
  throw openError;
586
586
  }
587
587
  };
588
- const generateHtmlReport = async (diffResult, formattedFiles, config, dryRun) => {
588
+ const generateHtmlReport = async (diffResult, formattedFiles, config, dryRun, logger) => {
589
589
  const reportPath = generateTemporaryFilePath();
590
590
  const metadata = {
591
591
  timestamp: new Date().toISOString(),
@@ -598,14 +598,14 @@ const generateHtmlReport = async (diffResult, formattedFiles, config, dryRun) =>
598
598
  const changedSections = diffResult.changedFiles.map((file) => generateChangedFileSection(file));
599
599
  const htmlContent = generateHtmlTemplate(diffResult, formattedFiles, trulyUnchangedFiles, metadata, changedSections);
600
600
  await writeHtmlFile(htmlContent, reportPath);
601
- console.log(`✓ HTML report generated: ${reportPath}, opening in browser...`);
601
+ logger?.log(`✓ HTML report generated: ${reportPath}, opening in browser...`);
602
602
  try {
603
603
  await openInBrowser(reportPath);
604
604
  }
605
605
  catch {
606
606
  const absolutePath = node_path_1.default.resolve(reportPath);
607
- console.log('⚠ Could not open browser automatically. Please open manually:');
608
- console.log(` file://${absolutePath}`);
607
+ logger?.log('⚠ Could not open browser automatically. Please open manually:');
608
+ logger?.log(` file://${absolutePath}`);
609
609
  }
610
610
  };
611
611
  exports.generateHtmlReport = generateHtmlReport;
package/dist/index.js CHANGED
@@ -14,59 +14,79 @@ const fileLoader_1 = require("./fileLoader");
14
14
  const fileUpdater_1 = require("./fileUpdater");
15
15
  const htmlReporter_1 = require("./htmlReporter");
16
16
  const jsonReporter_1 = require("./jsonReporter");
17
+ const logger_1 = require("./logger");
17
18
  const stopRulesValidator_1 = require("./stopRulesValidator");
18
19
  const collisionDetector_1 = require("./utils/collisionDetector");
19
20
  const filenameTransformer_1 = require("./utils/filenameTransformer");
20
21
  const versionChecker_1 = require("./utils/versionChecker");
21
22
  const ZodError_1 = require("./ZodError");
22
23
  const main = async () => {
23
- console.log(`Now you run ${package_json_1.default.name} v${package_json_1.default.version}...`);
24
24
  const command = (0, commandLine_1.parseCommandLine)();
25
- const config = (0, configLoader_1.loadConfigFile)(command.config);
26
- console.log('\n' + (0, consoleFormatter_1.formatProgressMessage)('Loading files...', 'loading'));
25
+ const verbosityLevel = command.verbose ? 'verbose' : command.quiet ? 'quiet' : 'normal';
26
+ const logger = new logger_1.Logger({ level: verbosityLevel, isDiffJson: command.diffJson });
27
+ logger.log(`Now you run ${package_json_1.default.name} v${package_json_1.default.version}...`);
28
+ const config = (0, configLoader_1.loadConfigFile)(command.config, command.quiet, logger);
29
+ if (command.validate) {
30
+ logger.log('\n' + (0, consoleFormatter_1.formatProgressMessage)('Configuration is valid', 'success'));
31
+ return;
32
+ }
33
+ if (logger.shouldShow('debug')) {
34
+ logger.debug('\nConfig details:');
35
+ logger.debug(` Source: ${config.source}`);
36
+ logger.debug(` Destination: ${config.destination}`);
37
+ logger.debug(` Include patterns: ${config.include.join(', ')}`);
38
+ logger.debug(` Exclude patterns: ${config.exclude.join(', ')}`);
39
+ logger.debug(` Transforms: ${Object.keys(config.transforms || {}).length} pattern(s)`);
40
+ logger.debug(` Prune enabled: ${config.prune}`);
41
+ }
42
+ logger.log('\n' + (0, consoleFormatter_1.formatProgressMessage)('Loading files...', 'loading'));
27
43
  const sourceFiles = await (0, fileLoader_1.loadFiles)({
28
44
  baseDirectory: config.source,
29
45
  include: config.include,
30
46
  exclude: config.exclude,
31
47
  transforms: config.transforms
32
- });
33
- console.log((0, consoleFormatter_1.formatProgressMessage)(`Loaded ${sourceFiles.size} source file(s)`, 'success'));
48
+ }, logger);
49
+ logger.progress(`Loaded ${sourceFiles.size} source file(s)`, 'success');
34
50
  const collisions = (0, collisionDetector_1.detectCollisions)(sourceFiles, config.transforms);
35
51
  if (collisions.length > 0)
36
52
  (0, collisionDetector_1.validateNoCollisions)(collisions);
53
+ if (logger.shouldShow('debug'))
54
+ logger.debug('Filename collision check: passed');
37
55
  const destinationFiles = await (0, fileLoader_1.loadFiles)({
38
56
  baseDirectory: config.destination,
39
57
  include: config.include,
40
58
  exclude: config.exclude
41
- });
42
- console.log((0, consoleFormatter_1.formatProgressMessage)(`Loaded ${destinationFiles.size} destination file(s)`, 'success'));
43
- console.log('\n' + (0, consoleFormatter_1.formatProgressMessage)('Computing differences...', 'info'));
44
- const diffResult = (0, fileDiff_1.computeFileDiff)(sourceFiles, destinationFiles, config);
45
- if (command.diff)
59
+ }, logger);
60
+ logger.progress(`Loaded ${destinationFiles.size} destination file(s)`, 'success');
61
+ logger.log('\n' + (0, consoleFormatter_1.formatProgressMessage)('Computing differences...', 'info'));
62
+ const diffResult = (0, fileDiff_1.computeFileDiff)(sourceFiles, destinationFiles, config, logger);
63
+ if (logger.shouldShow('debug'))
64
+ logger.debug('Diff pipeline: parse → transforms → skipPath → normalize → compare');
65
+ if (command.diff && !command.quiet)
46
66
  (0, consoleDiffReporter_1.showConsoleDiff)(diffResult, config);
47
67
  else {
48
- console.log(` New files: ${diffResult.addedFiles.length}`);
49
- console.log(` Deleted files: ${diffResult.deletedFiles.length}`);
50
- console.log(` Changed files: ${diffResult.changedFiles.length}`);
51
- console.log(` Unchanged files: ${diffResult.unchangedFiles.length}`);
68
+ logger.log(` New files: ${diffResult.addedFiles.length}`);
69
+ logger.log(` Deleted files: ${diffResult.deletedFiles.length}`);
70
+ logger.log(` Changed files: ${diffResult.changedFiles.length}`);
71
+ logger.log(` Unchanged files: ${diffResult.unchangedFiles.length}`);
52
72
  }
53
- const validationResult = (0, stopRulesValidator_1.validateStopRules)(diffResult, config.stopRules);
73
+ const validationResult = (0, stopRulesValidator_1.validateStopRules)(diffResult, config.stopRules, logger);
54
74
  if (validationResult.violations.length > 0)
55
75
  if (command.force)
56
76
  for (const violation of validationResult.violations)
57
- console.warn('\n' + (0, consoleFormatter_1.formatStopRuleViolation)(violation, 'force'));
77
+ logger.stopRule(violation, 'force');
58
78
  else if (command.dryRun)
59
79
  for (const violation of validationResult.violations)
60
- console.warn('\n' + (0, consoleFormatter_1.formatStopRuleViolation)(violation, 'warning'));
80
+ logger.stopRule(violation, 'warning');
61
81
  else {
62
82
  for (const violation of validationResult.violations)
63
- console.error('\n' + (0, consoleFormatter_1.formatStopRuleViolation)(violation, 'error'));
64
- console.error('\nUse --force to override stop rules or --dry-run to preview changes.');
83
+ logger.stopRule(violation, 'error');
84
+ logger.error('\nUse --force to override stop rules or --dry-run to preview changes.', 'critical');
65
85
  process.exit(1);
66
86
  }
67
- const formattedFiles = await (0, fileUpdater_1.updateFiles)(diffResult, sourceFiles, destinationFiles, config, command.dryRun, command.skipFormat);
68
- if (command.diffHtml)
69
- await (0, htmlReporter_1.generateHtmlReport)(diffResult, formattedFiles, config, command.dryRun);
87
+ const formattedFiles = await (0, fileUpdater_1.updateFiles)(diffResult, sourceFiles, destinationFiles, config, command.dryRun, command.skipFormat, logger);
88
+ if (command.diffHtml && !command.quiet)
89
+ await (0, htmlReporter_1.generateHtmlReport)(diffResult, formattedFiles, config, command.dryRun, logger);
70
90
  if (command.diffJson)
71
91
  (0, jsonReporter_1.generateJsonReport)(diffResult, formattedFiles, validationResult, config, command.dryRun, package_json_1.default.version);
72
92
  };
@@ -100,6 +120,8 @@ const main = async () => {
100
120
  process.exit(1);
101
121
  }
102
122
  finally {
103
- void (0, versionChecker_1.checkForUpdates)(package_json_1.default.version);
123
+ const command = (0, commandLine_1.parseCommandLine)();
124
+ if (!command.quiet)
125
+ void (0, versionChecker_1.checkForUpdates)(package_json_1.default.version);
104
126
  }
105
127
  })();
@@ -0,0 +1,20 @@
1
+ import { FileOperation, ProgressStyle, ViolationMode } from './consoleFormatter';
2
+ import { StopRuleViolation } from './stopRulesValidator';
3
+ export type VerbosityLevel = 'quiet' | 'normal' | 'verbose';
4
+ export type OutputCategory = 'critical' | 'normal' | 'debug' | 'special';
5
+ export type LoggerOptions = {
6
+ level: VerbosityLevel;
7
+ isDiffJson: boolean;
8
+ };
9
+ export declare class Logger {
10
+ private level;
11
+ constructor(options: LoggerOptions);
12
+ shouldShow(category: OutputCategory): boolean;
13
+ log(message: string, category?: OutputCategory): void;
14
+ warn(message: string, category?: OutputCategory): void;
15
+ error(message: string, category?: OutputCategory): void;
16
+ debug(message: string): void;
17
+ progress(message: string, style: ProgressStyle): void;
18
+ fileOp(operation: FileOperation, path: string, isDryRun: boolean, alreadyDeleted?: boolean): void;
19
+ stopRule(violation: StopRuleViolation, mode: ViolationMode): void;
20
+ }
package/dist/logger.js ADDED
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Logger = void 0;
4
+ const consoleFormatter_1 = require("./consoleFormatter");
5
+ class Logger {
6
+ level;
7
+ constructor(options) {
8
+ this.level = options.level;
9
+ }
10
+ shouldShow(category) {
11
+ if (category === 'special')
12
+ return true;
13
+ if (category === 'critical')
14
+ return true;
15
+ if (this.level === 'quiet')
16
+ return false;
17
+ if (this.level === 'verbose')
18
+ return true;
19
+ return category === 'normal';
20
+ }
21
+ log(message, category = 'normal') {
22
+ if (this.shouldShow(category))
23
+ console.log(message);
24
+ }
25
+ warn(message, category = 'normal') {
26
+ if (this.shouldShow(category))
27
+ console.warn(message);
28
+ }
29
+ error(message, category = 'critical') {
30
+ if (this.shouldShow(category))
31
+ console.error(message);
32
+ }
33
+ debug(message) {
34
+ if (this.shouldShow('debug'))
35
+ console.log(message);
36
+ }
37
+ progress(message, style) {
38
+ if (this.shouldShow('normal'))
39
+ console.log((0, consoleFormatter_1.formatProgressMessage)(message, style));
40
+ }
41
+ fileOp(operation, path, isDryRun, alreadyDeleted = false) {
42
+ if (this.shouldShow('normal'))
43
+ console.log((0, consoleFormatter_1.colorizeFileOperation)(operation, path, isDryRun, alreadyDeleted));
44
+ }
45
+ stopRule(violation, mode) {
46
+ const message = '\n' + (0, consoleFormatter_1.formatStopRuleViolation)(violation, mode);
47
+ if (mode === 'error')
48
+ this.error(message, 'critical');
49
+ else
50
+ this.warn(message, 'critical');
51
+ }
52
+ }
53
+ exports.Logger = Logger;
@@ -29,5 +29,5 @@ export interface ValidationResult {
29
29
  violations: StopRuleViolation[];
30
30
  isValid: boolean;
31
31
  }
32
- export declare const validateStopRules: (diffResult: FileDiffResult, stopRulesConfig?: Record<string, StopRule[]>) => ValidationResult;
32
+ export declare const validateStopRules: (diffResult: FileDiffResult, stopRulesConfig?: Record<string, StopRule[]>, logger?: import("./logger").Logger) => ValidationResult;
33
33
  export {};
@@ -26,14 +26,22 @@ class StopRulesValidatorError extends StopRulesValidatorErrorClass {
26
26
  }
27
27
  exports.StopRulesValidatorError = StopRulesValidatorError;
28
28
  exports.isStopRulesValidatorError = (0, errors_1.createErrorTypeGuard)(StopRulesValidatorError);
29
- const validateStopRules = (diffResult, stopRulesConfig) => {
29
+ const validateStopRules = (diffResult, stopRulesConfig, logger) => {
30
30
  if (!stopRulesConfig)
31
31
  return { violations: [], isValid: true };
32
+ if (logger?.shouldShow('debug')) {
33
+ const totalRules = Object.values(stopRulesConfig).reduce((sum, rules) => sum + rules.length, 0);
34
+ logger.debug('Stop rule validation:');
35
+ logger.debug(` Total rules: ${totalRules}`);
36
+ logger.debug(` Files to check: ${diffResult.changedFiles.length}`);
37
+ }
32
38
  const violations = [];
33
39
  for (const changedFile of diffResult.changedFiles) {
34
40
  const fileViolations = validateFileAgainstRules(changedFile, stopRulesConfig);
35
41
  violations.push(...fileViolations);
36
42
  }
43
+ if (logger?.shouldShow('debug'))
44
+ logger.debug(`Stop rules: ${violations.length} violation(s) found`);
37
45
  return {
38
46
  violations,
39
47
  isValid: violations.length === 0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "helm-env-delta",
3
- "version": "1.1.3",
3
+ "version": "1.2.0",
4
4
  "description": "HelmEnvDelta – environment-aware YAML delta and sync for GitOps",
5
5
  "author": "BCsabaEngine",
6
6
  "license": "ISC",