helm-env-delta 1.3.3 → 1.4.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
@@ -48,6 +48,10 @@ HelmEnvDelta (`hed`) automates environment synchronization for GitOps workflows
48
48
 
49
49
  šŸ“Š **Multiple Reports** - Console, HTML (visual), and JSON (CI/CD) output formats.
50
50
 
51
+ šŸ” **Discovery Tools** - Preview files (`--list-files`), inspect config (`--show-config`), validate with warnings.
52
+
53
+ šŸ›”ļø **Safety First** - Pre-execution summary, first-run tips, improved error messages with helpful examples.
54
+
51
55
  ⚔ **High Performance** - 45-60% faster than alternatives with intelligent caching and parallel processing.
52
56
 
53
57
  šŸ”” **Auto Updates** - Notifies when newer versions are available (skips in CI/CD).
@@ -411,33 +415,42 @@ hed --config <file> [options] # Short alias
411
415
 
412
416
  ### Options
413
417
 
414
- | Flag | Description |
415
- | ----------------- | --------------------------------------------- |
416
- | `--config <path>` | **Required** - Configuration file |
417
- | `--validate` | Validate config and exit (no file operations) |
418
- | `--dry-run` | Preview changes without writing files |
419
- | `--force` | Override stop rules |
420
- | `--diff` | Show console diff |
421
- | `--diff-html` | Generate HTML report (opens in browser) |
422
- | `--diff-json` | Output JSON to stdout (pipe to jq) |
423
- | `--skip-format` | Skip YAML formatting |
424
- | `--verbose` | Show detailed debug info |
425
- | `--quiet` | Suppress output except errors |
418
+ | Flag | Description |
419
+ | ----------------- | ------------------------------------------------ |
420
+ | `--config <path>` | **Required** - Configuration file |
421
+ | `--validate` | Validate config and exit (shows warnings) |
422
+ | `--dry-run` | Preview changes without writing files |
423
+ | `--force` | Override stop rules |
424
+ | `--diff` | Show console diff |
425
+ | `--diff-html` | Generate HTML report (opens in browser) |
426
+ | `--diff-json` | Output JSON to stdout (pipe to jq) |
427
+ | `--list-files` | List source/destination files without processing |
428
+ | `--show-config` | Display resolved config after inheritance |
429
+ | `--skip-format` | Skip YAML formatting |
430
+ | `--no-color` | Disable colored output (CI/accessibility) |
431
+ | `--verbose` | Show detailed debug info |
432
+ | `--quiet` | Suppress output except errors |
426
433
 
427
434
  ### Examples
428
435
 
429
436
  ```bash
430
- # Validate configuration
437
+ # Validate configuration (shows warnings)
431
438
  hed --config config.yaml --validate
432
439
 
440
+ # Preview files that will be synced
441
+ hed --config config.yaml --list-files
442
+
443
+ # Display resolved config (after inheritance)
444
+ hed --config config.yaml --show-config
445
+
433
446
  # Preview with diff
434
447
  hed --config config.yaml --dry-run --diff
435
448
 
436
449
  # Visual HTML report
437
450
  hed --config config.yaml --diff-html
438
451
 
439
- # CI/CD integration
440
- hed --config config.yaml --diff-json | jq '.summary'
452
+ # CI/CD integration (no colors)
453
+ hed --config config.yaml --diff-json --no-color | jq '.summary'
441
454
 
442
455
  # Execute sync
443
456
  hed --config config.yaml
@@ -514,7 +527,7 @@ git push origin main
514
527
 
515
528
  āœ… **Flexibility** - Per-file patterns. Config inheritance. Regex transforms.
516
529
 
517
- āœ… **Reliability** - 763 tests, 84% coverage. Battle-tested.
530
+ āœ… **Reliability** - 787 tests, 84% coverage. Battle-tested.
518
531
 
519
532
  ---
520
533
 
@@ -9,5 +9,8 @@ export type SyncCommand = {
9
9
  validate: boolean;
10
10
  verbose: boolean;
11
11
  quiet: boolean;
12
+ listFiles: boolean;
13
+ showConfig: boolean;
14
+ noColor: boolean;
12
15
  };
13
16
  export declare const parseCommandLine: (argv?: string[]) => SyncCommand;
@@ -20,8 +20,28 @@ const parseCommandLine = (argv) => {
20
20
  .option('--diff-json', 'Output diff as JSON to stdout', false)
21
21
  .option('--skip-format', 'Skip YAML formatting (outputFormat section)', false)
22
22
  .option('--validate', 'Validate configuration file and exit', false)
23
+ .option('--list-files', 'List files that would be synced without processing diffs', false)
24
+ .option('--show-config', 'Display resolved configuration after inheritance and exit', false)
25
+ .option('--no-color', 'Disable colored output')
23
26
  .option('--verbose', 'Show detailed debug information', false)
24
- .option('--quiet', 'Suppress all output except critical errors', false);
27
+ .option('--quiet', 'Suppress all output except critical errors', false)
28
+ .addHelpText('after', `
29
+ Examples:
30
+ # Preview changes before syncing
31
+ $ helm-env-delta --config config.yaml --dry-run --diff
32
+
33
+ # Sync with HTML diff report
34
+ $ helm-env-delta --config config.yaml --diff-html
35
+
36
+ # Validate stop rules without syncing
37
+ $ helm-env-delta --config config.yaml --validate
38
+
39
+ # CI/CD usage with JSON output
40
+ $ helm-env-delta --config config.yaml --diff-json | jq '.summary'
41
+
42
+ Documentation: https://github.com/balazscsaba2006/helm-env-delta
43
+ `);
44
+ program.showSuggestionAfterError(true);
25
45
  program.parse(argv || process.argv);
26
46
  const options = program.opts();
27
47
  if (options['verbose'] && options['quiet']) {
@@ -37,6 +57,9 @@ const parseCommandLine = (argv) => {
37
57
  diffJson: options['diffJson'],
38
58
  skipFormat: options['skipFormat'],
39
59
  validate: options['validate'],
60
+ listFiles: options['listFiles'],
61
+ showConfig: options['showConfig'],
62
+ noColor: !options['color'],
40
63
  verbose: options['verbose'],
41
64
  quiet: options['quiet']
42
65
  };
@@ -176,8 +176,16 @@ const resolveConfigWithExtends = (configPath, visited = new Set(), depth = 0, lo
176
176
  const errorCode = error.code;
177
177
  if (errorCode === 'ENOENT') {
178
178
  readError.message += '\n\n Hint: Config file not found:';
179
- readError.message += '\n - Check the file path is correct';
180
- readError.message += '\n - Use absolute path or path relative to current directory';
179
+ readError.message += '\n - Check the file path is correct (use --config path/to/config.yaml)';
180
+ readError.message +=
181
+ '\n - See examples at: https://github.com/balazscsaba2006/helm-env-delta/tree/main/example';
182
+ readError.message += '\n - Start with the basic example: example/0-basic/config.yaml';
183
+ readError.message += '\n - Or create a minimal config:';
184
+ readError.message += '\n';
185
+ readError.message += '\n source: ./source-dir';
186
+ readError.message += '\n destination: ./dest-dir';
187
+ readError.message += '\n skipPath:';
188
+ readError.message += '\n "**/*": ["$.metadata.labels"]';
181
189
  }
182
190
  else if (errorCode === 'EACCES') {
183
191
  readError.message += '\n\n Hint: Permission denied:';
@@ -0,0 +1,2 @@
1
+ import type { FinalConfig } from './configFile';
2
+ export declare const validateConfigWarnings: (config: FinalConfig) => string[];
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateConfigWarnings = void 0;
4
+ const validateConfigWarnings = (config) => {
5
+ const warnings = [];
6
+ const allGlobs = [...config.include, ...config.exclude];
7
+ for (const glob of allGlobs)
8
+ if (glob.includes('**/**'))
9
+ warnings.push(`Inefficient glob pattern '${glob}' detected (use '**/*' instead)`);
10
+ const includeSet = new Set(config.include);
11
+ if (includeSet.size < config.include.length)
12
+ warnings.push('Duplicate patterns found in include array');
13
+ const excludeSet = new Set(config.exclude);
14
+ if (excludeSet.size < config.exclude.length)
15
+ warnings.push('Duplicate patterns found in exclude array');
16
+ for (const pattern of config.include)
17
+ if (config.exclude.includes(pattern))
18
+ warnings.push(`Pattern '${pattern}' appears in both include and exclude (exclude takes precedence)`);
19
+ if (config.skipPath)
20
+ for (const [pattern, paths] of Object.entries(config.skipPath))
21
+ if (paths.length === 0)
22
+ warnings.push(`skipPath pattern '${pattern}' has empty array (will have no effect)`);
23
+ if (config.transforms)
24
+ for (const [pattern, rules] of Object.entries(config.transforms))
25
+ if ((rules.content?.length ?? 0) === 0 && (rules.filename?.length ?? 0) === 0)
26
+ warnings.push(`Transform pattern '${pattern}' has empty content and filename arrays (will have no effect)`);
27
+ return warnings;
28
+ };
29
+ exports.validateConfigWarnings = validateConfigWarnings;
package/dist/index.js CHANGED
@@ -1,12 +1,51 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
37
  };
5
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
+ const node_fs_1 = require("node:fs");
40
+ const node_os_1 = require("node:os");
41
+ const node_path_1 = __importDefault(require("node:path"));
42
+ const chalk_1 = __importDefault(require("chalk"));
43
+ const YAML = __importStar(require("yaml"));
6
44
  const package_json_1 = __importDefault(require("../package.json"));
7
45
  const commandLine_1 = require("./commandLine");
8
46
  const configLoader_1 = require("./configLoader");
9
47
  const configMerger_1 = require("./configMerger");
48
+ const configWarnings_1 = require("./configWarnings");
10
49
  const consoleDiffReporter_1 = require("./consoleDiffReporter");
11
50
  const consoleFormatter_1 = require("./consoleFormatter");
12
51
  const fileDiff_1 = require("./fileDiff");
@@ -22,12 +61,38 @@ const versionChecker_1 = require("./utils/versionChecker");
22
61
  const ZodError_1 = require("./ZodError");
23
62
  const main = async () => {
24
63
  const command = (0, commandLine_1.parseCommandLine)();
64
+ if (command.noColor)
65
+ chalk_1.default.level = 0;
25
66
  const verbosityLevel = command.verbose ? 'verbose' : command.quiet ? 'quiet' : 'normal';
26
67
  const logger = new logger_1.Logger({ level: verbosityLevel, isDiffJson: command.diffJson });
27
68
  logger.log(`Now you run ${package_json_1.default.name} v${package_json_1.default.version}...`);
69
+ const configDirectory = node_path_1.default.join((0, node_os_1.homedir)(), '.helm-env-delta');
70
+ const firstRunMarker = node_path_1.default.join(configDirectory, 'first-run');
71
+ const isFirstRun = !(0, node_fs_1.existsSync)(firstRunMarker);
72
+ if (isFirstRun && !command.quiet) {
73
+ console.log(chalk_1.default.cyan('\nšŸ‘‹ First time using helm-env-delta?\n'));
74
+ console.log(chalk_1.default.dim(' Tips:'));
75
+ console.log(chalk_1.default.dim(' • Always use --dry-run first to preview changes'));
76
+ console.log(chalk_1.default.dim(' • Use --diff-html to review diffs in your browser'));
77
+ console.log(chalk_1.default.dim(' • See examples: https://github.com/balazscsaba2006/helm-env-delta/tree/main/example'));
78
+ console.log(chalk_1.default.dim(' • Run with --help to see all options\n'));
79
+ (0, node_fs_1.mkdirSync)(configDirectory, { recursive: true });
80
+ (0, node_fs_1.writeFileSync)(firstRunMarker, new Date().toISOString());
81
+ }
28
82
  const config = (0, configLoader_1.loadConfigFile)(command.config, command.quiet, logger);
83
+ if (command.showConfig) {
84
+ console.log(chalk_1.default.cyan('\nāš™ļø Resolved Configuration:\n'));
85
+ console.log(YAML.stringify(config, { indent: 2 }));
86
+ return;
87
+ }
29
88
  if (command.validate) {
30
89
  logger.log('\n' + (0, consoleFormatter_1.formatProgressMessage)('Configuration is valid', 'success'));
90
+ const warnings = (0, configWarnings_1.validateConfigWarnings)(config);
91
+ if (warnings.length > 0) {
92
+ console.warn(chalk_1.default.yellow('\nāš ļø Validation Warnings (non-fatal):\n'));
93
+ for (const warning of warnings)
94
+ console.warn(chalk_1.default.yellow(` • ${warning}`));
95
+ }
31
96
  return;
32
97
  }
33
98
  if (logger.shouldShow('debug')) {
@@ -58,6 +123,18 @@ const main = async () => {
58
123
  exclude: config.exclude
59
124
  }, logger);
60
125
  logger.progress(`Loaded ${destinationFiles.size} destination file(s)`, 'success');
126
+ if (command.listFiles) {
127
+ const sourceFilesList = [...sourceFiles.keys()].toSorted();
128
+ const destinationFilesList = [...destinationFiles.keys()].toSorted();
129
+ console.log(chalk_1.default.cyan('\nšŸ“‹ Files to be synced:\n'));
130
+ console.log(chalk_1.default.green(`Source files: ${sourceFilesList.length}`));
131
+ for (const file of sourceFilesList)
132
+ console.log(` ${chalk_1.default.dim(file)}`);
133
+ console.log(chalk_1.default.yellow(`\nDestination files: ${destinationFilesList.length}`));
134
+ for (const file of destinationFilesList)
135
+ console.log(` ${chalk_1.default.dim(file)}`);
136
+ return;
137
+ }
61
138
  logger.log('\n' + (0, consoleFormatter_1.formatProgressMessage)('Computing differences...', 'info'));
62
139
  const diffResult = (0, fileDiff_1.computeFileDiff)(sourceFiles, destinationFiles, config, logger);
63
140
  if (logger.shouldShow('debug'))
@@ -84,6 +161,19 @@ const main = async () => {
84
161
  logger.error('\nUse --force to override stop rules or --dry-run to preview changes.', 'critical');
85
162
  process.exit(1);
86
163
  }
164
+ if (!command.dryRun && !command.quiet) {
165
+ console.log(chalk_1.default.cyan('\nšŸ“Š Sync Summary:'));
166
+ console.log(chalk_1.default.dim('─'.repeat(60)));
167
+ console.log(` ${chalk_1.default.green('Added:')} ${diffResult.addedFiles.length} files`);
168
+ console.log(` ${chalk_1.default.yellow('Changed:')} ${diffResult.changedFiles.length} files`);
169
+ console.log(` ${chalk_1.default.red('Deleted:')} ${diffResult.deletedFiles.length} files (${config.prune ? 'prune enabled' : 'prune disabled'})`);
170
+ console.log(` ${chalk_1.default.blue('Unchanged:')} ${diffResult.unchangedFiles.length} files`);
171
+ console.log(chalk_1.default.dim('─'.repeat(60)));
172
+ if (diffResult.deletedFiles.length > 0 && config.prune)
173
+ console.warn(chalk_1.default.red('āš ļø Warning: Prune is enabled. Files will be permanently deleted!'));
174
+ console.log(chalk_1.default.dim('\nPress Ctrl+C to cancel, or use --dry-run to preview changes first.\n'));
175
+ await new Promise((resolve) => setTimeout(resolve, 2000));
176
+ }
87
177
  const formattedFiles = await (0, fileUpdater_1.updateFiles)(diffResult, sourceFiles, destinationFiles, config, command.dryRun, command.skipFormat, logger);
88
178
  if (command.diffHtml && !command.quiet)
89
179
  await (0, htmlReporter_1.generateHtmlReport)(diffResult, formattedFiles, config, command.dryRun, logger);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "helm-env-delta",
3
- "version": "1.3.3",
3
+ "version": "1.4.0",
4
4
  "description": "HelmEnvDelta – environment-aware YAML delta and sync for GitOps",
5
5
  "author": "BCsabaEngine",
6
6
  "license": "ISC",