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 +29 -16
- package/dist/commandLine.d.ts +3 -0
- package/dist/commandLine.js +24 -1
- package/dist/configMerger.js +10 -2
- package/dist/configWarnings.d.ts +2 -0
- package/dist/configWarnings.js +29 -0
- package/dist/index.js +90 -0
- package/package.json +1 -1
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 (
|
|
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
|
-
| `--
|
|
424
|
-
| `--
|
|
425
|
-
| `--
|
|
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** -
|
|
530
|
+
ā
**Reliability** - 787 tests, 84% coverage. Battle-tested.
|
|
518
531
|
|
|
519
532
|
---
|
|
520
533
|
|
package/dist/commandLine.d.ts
CHANGED
package/dist/commandLine.js
CHANGED
|
@@ -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
|
};
|
package/dist/configMerger.js
CHANGED
|
@@ -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 +=
|
|
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,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);
|