helm-env-delta 1.6.0 → 1.7.1

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 (60) hide show
  1. package/README.md +92 -20
  2. package/dist/configMerger.d.ts +1 -0
  3. package/dist/configMerger.js +4 -4
  4. package/dist/configWarnings.d.ts +5 -1
  5. package/dist/configWarnings.js +4 -1
  6. package/dist/consoleDiffReporter.js +16 -37
  7. package/dist/constants.d.ts +5 -0
  8. package/dist/constants.js +8 -0
  9. package/dist/fileDiff.d.ts +11 -2
  10. package/dist/fileDiff.js +42 -6
  11. package/dist/fileLoader.d.ts +6 -1
  12. package/dist/fileLoader.js +11 -11
  13. package/dist/fileUpdater.d.ts +19 -1
  14. package/dist/fileUpdater.js +64 -26
  15. package/dist/htmlReporter.d.ts +2 -7
  16. package/dist/htmlReporter.js +52 -484
  17. package/dist/index.js +44 -9
  18. package/dist/jsonReporter.d.ts +1 -0
  19. package/dist/jsonReporter.js +2 -6
  20. package/dist/patternUsageValidator.d.ts +13 -0
  21. package/dist/patternUsageValidator.js +156 -0
  22. package/dist/reporters/browserLauncher.d.ts +20 -0
  23. package/dist/reporters/browserLauncher.js +64 -0
  24. package/dist/reporters/htmlStyles.d.ts +2 -0
  25. package/dist/reporters/htmlStyles.js +279 -0
  26. package/dist/reporters/htmlTemplate.d.ts +8 -0
  27. package/dist/reporters/htmlTemplate.js +105 -0
  28. package/dist/stopRulesValidator.d.ts +8 -0
  29. package/dist/stopRulesValidator.js +72 -182
  30. package/dist/suggestionEngine.d.ts +1 -0
  31. package/dist/utils/arrayDiffProcessor.d.ts +14 -0
  32. package/dist/utils/arrayDiffProcessor.js +43 -0
  33. package/dist/utils/collisionDetector.d.ts +1 -0
  34. package/dist/utils/diffGenerator.js +1 -3
  35. package/dist/utils/errors.d.ts +2 -0
  36. package/dist/utils/errors.js +8 -1
  37. package/dist/utils/fileType.js +1 -3
  38. package/dist/utils/filenameTransformer.d.ts +17 -6
  39. package/dist/utils/filenameTransformer.js +27 -31
  40. package/dist/utils/index.d.ts +6 -1
  41. package/dist/utils/index.js +22 -1
  42. package/dist/utils/jsonPath.d.ts +5 -0
  43. package/dist/utils/jsonPath.js +75 -5
  44. package/dist/utils/regexPatternFileLoader.d.ts +2 -0
  45. package/dist/utils/regexTransform.d.ts +2 -0
  46. package/dist/utils/regexTransform.js +17 -0
  47. package/dist/utils/regexValidator.d.ts +22 -0
  48. package/dist/utils/regexValidator.js +71 -0
  49. package/dist/utils/transformFileLoader.d.ts +2 -0
  50. package/dist/utils/transformer.d.ts +1 -0
  51. package/dist/utils/transformer.js +3 -8
  52. package/dist/utils/versionChecker.d.ts +1 -0
  53. package/dist/utils/versionValidator.d.ts +6 -0
  54. package/dist/utils/versionValidator.js +88 -0
  55. package/dist/utils/yamlFileLoader.d.ts +2 -0
  56. package/dist/utils/yamlTypeGuards.d.ts +7 -0
  57. package/dist/utils/yamlTypeGuards.js +35 -0
  58. package/dist/yamlFormatter.d.ts +12 -5
  59. package/dist/yamlFormatter.js +66 -98
  60. package/package.json +10 -10
package/README.md CHANGED
@@ -48,7 +48,7 @@ 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.
51
+ 🔍 **Discovery Tools** - Preview files (`--list-files`), inspect config (`--show-config`), validate with comprehensive warnings including unused pattern detection.
52
52
 
53
53
  💡 **Smart Suggestions** - Heuristic analysis (`--suggest`) detects patterns and recommends transforms and stop rules automatically. Control sensitivity with `--suggest-threshold`.
54
54
 
@@ -374,7 +374,26 @@ skipPath:
374
374
  - 'resources.limits'
375
375
  ```
376
376
 
377
- **Use cases:** Namespaces, replicas, resource limits, secrets, URLs.
377
+ #### Filter Expressions (Skip by Name)
378
+
379
+ Skip specific array items by property value using filter syntax:
380
+
381
+ ```yaml
382
+ skipPath:
383
+ '**/*.yaml':
384
+ - 'env[name=SECRET_KEY]' # Skip item where name=SECRET_KEY
385
+ - 'env[name=INTERNAL_TOKEN].value' # Skip nested field in matching item
386
+ - 'containers[name=sidecar]' # Skip entire sidecar container
387
+ - 'spec.containers[name=app].env[name=DEBUG]' # Nested filters
388
+ ```
389
+
390
+ **Syntax:**
391
+
392
+ - `array[prop=value]` - Match items where property equals value
393
+ - `array[prop="value with spaces"]` - Quoted values for special characters
394
+ - Combine with wildcards: `containers[name=app].env[*].value`
395
+
396
+ **Use cases:** Namespaces, replicas, resource limits, secrets, URLs, environment-specific array items.
378
397
 
379
398
  ---
380
399
 
@@ -610,23 +629,23 @@ hed --config <file> [options] # Short alias
610
629
 
611
630
  ### Options
612
631
 
613
- | Flag | Description |
614
- | --------------------------- | ------------------------------------------------- |
615
- | `--config <path>` | **Required** - Configuration file |
616
- | `--validate` | Validate config and exit (shows warnings) |
617
- | `--suggest` | Analyze differences and suggest config updates |
618
- | `--suggest-threshold <0-1>` | Minimum confidence for suggestions (default: 0.3) |
619
- | `--dry-run` | Preview changes without writing files |
620
- | `--force` | Override stop rules |
621
- | `--diff` | Show console diff |
622
- | `--diff-html` | Generate HTML report (opens in browser) |
623
- | `--diff-json` | Output JSON to stdout (pipe to jq) |
624
- | `--list-files` | List source/destination files without processing |
625
- | `--show-config` | Display resolved config after inheritance |
626
- | `--skip-format` | Skip YAML formatting |
627
- | `--no-color` | Disable colored output (CI/accessibility) |
628
- | `--verbose` | Show detailed debug info |
629
- | `--quiet` | Suppress output except errors |
632
+ | Flag | Description |
633
+ | --------------------------- | -------------------------------------------------- |
634
+ | `--config <path>` | **Required** - Configuration file |
635
+ | `--validate` | Validate config and pattern usage (shows warnings) |
636
+ | `--suggest` | Analyze differences and suggest config updates |
637
+ | `--suggest-threshold <0-1>` | Minimum confidence for suggestions (default: 0.3) |
638
+ | `--dry-run` | Preview changes without writing files |
639
+ | `--force` | Override stop rules |
640
+ | `--diff` | Show console diff |
641
+ | `--diff-html` | Generate HTML report (opens in browser) |
642
+ | `--diff-json` | Output JSON to stdout (pipe to jq) |
643
+ | `--list-files` | List source/destination files without processing |
644
+ | `--show-config` | Display resolved config after inheritance |
645
+ | `--skip-format` | Skip YAML formatting |
646
+ | `--no-color` | Disable colored output (CI/accessibility) |
647
+ | `--verbose` | Show detailed debug info |
648
+ | `--quiet` | Suppress output except errors |
630
649
 
631
650
  ### Examples
632
651
 
@@ -730,7 +749,7 @@ git push origin main
730
749
 
731
750
  ✅ **Flexibility** - Per-file patterns. Config inheritance. Regex transforms.
732
751
 
733
- ✅ **Reliability** - 787 tests, 84% coverage. Battle-tested.
752
+ ✅ **Reliability** - 917 tests, 84% coverage. Battle-tested.
734
753
 
735
754
  ---
736
755
 
@@ -824,6 +843,57 @@ env:
824
843
 
825
844
  ---
826
845
 
846
+ ### 🔍 Pattern Usage Validation
847
+
848
+ HelmEnvDelta validates that your configuration patterns actually match files and exist in your YAML structure. This helps catch typos, outdated patterns, and misconfigured rules early.
849
+
850
+ **Run validation:**
851
+
852
+ ```bash
853
+ helm-env-delta --config config.yaml --validate
854
+ ```
855
+
856
+ **What gets validated:**
857
+
858
+ 1. **exclude patterns** - Warns if pattern matches no files
859
+ 2. **skipPath patterns** - Two-level validation:
860
+ - Glob pattern must match at least one file
861
+ - JSONPath must exist in at least one matched file
862
+ - Filter expressions `[prop=value]` validated against actual array items
863
+ 3. **stopRules patterns** - Two-level validation:
864
+ - Glob pattern must match at least one file
865
+ - JSONPath (if specified) must exist in at least one matched file
866
+
867
+ **Example output:**
868
+
869
+ ```
870
+ ✓ Configuration is valid
871
+
872
+ ⚠️ Pattern Usage Warnings (non-fatal):
873
+
874
+ • Exclude pattern 'test/**/*.yaml' matches no files
875
+ • skipPath pattern 'legacy/*.yaml' matches no files
876
+ • skipPath JSONPath 'microservice.replicaCountX' not found in any matched files (Pattern: svc/**/values.yaml, matches 50 file(s))
877
+ • stopRules glob pattern 'helm-charts/**/*.yaml' matches no files (3 rule(s) defined)
878
+ • stopRules JSONPath 'spec.replicas' not found in any matched files (Rule type: numeric, matches 5 file(s))
879
+ ```
880
+
881
+ **Validation phases:**
882
+
883
+ - **Phase 1 (Static)**: Validates config syntax, checks for inefficient patterns, duplicates, conflicts
884
+ - **Phase 2 (File-Based)**: Loads files and validates all patterns actually match and paths exist
885
+
886
+ **When to use:**
887
+
888
+ - After updating configuration
889
+ - When patterns stop working as expected
890
+ - To verify config file patterns after file reorganization
891
+ - As part of CI/CD pre-flight checks
892
+
893
+ **Important:** Warnings are non-fatal and don't block execution. They help you catch potential issues but won't stop your workflow.
894
+
895
+ ---
896
+
827
897
  ### 🔀 Deep Merge
828
898
 
829
899
  Preserves destination values for skipped paths.
@@ -880,6 +950,8 @@ spec:
880
950
  # ✅ Correct
881
951
  - 'spec.replicas' # No prefix
882
952
  - 'env[*].name' # Array wildcard
953
+ - 'env[name=DEBUG]' # Filter by property value
954
+ - 'containers[name=app].env[name=SECRET]' # Nested filters
883
955
  ```
884
956
 
885
957
  ---
@@ -5,6 +5,7 @@ declare const ConfigMergerErrorClass: {
5
5
  readonly code?: string;
6
6
  readonly path?: string;
7
7
  readonly cause?: Error;
8
+ readonly hints?: string[];
8
9
  name: string;
9
10
  message: string;
10
11
  stack?: string;
@@ -41,6 +41,7 @@ const node_fs_1 = require("node:fs");
41
41
  const node_path_1 = __importDefault(require("node:path"));
42
42
  const YAML = __importStar(require("yaml"));
43
43
  const configFile_1 = require("./configFile");
44
+ const constants_1 = require("./constants");
44
45
  const errors_1 = require("./utils/errors");
45
46
  const ConfigMergerErrorClass = (0, errors_1.createErrorClass)('Config Merger Error', {
46
47
  CIRCULAR_DEPENDENCY: 'Circular dependency detected in extends chain',
@@ -69,7 +70,6 @@ class ConfigMergerError extends ConfigMergerErrorClass {
69
70
  }
70
71
  exports.ConfigMergerError = ConfigMergerError;
71
72
  exports.isConfigMergerError = (0, errors_1.createErrorTypeGuard)(ConfigMergerError);
72
- const MAX_EXTENDS_DEPTH = 5;
73
73
  const mergeConfigs = (parent, child) => {
74
74
  const merged = {};
75
75
  if (child.source !== undefined)
@@ -143,14 +143,14 @@ const mergePerFileRecords = (parent, child) => {
143
143
  return merged;
144
144
  };
145
145
  const resolveConfigWithExtends = (configPath, visited = new Set(), depth = 0, logger) => {
146
- if (depth > MAX_EXTENDS_DEPTH) {
147
- const depthError = new ConfigMergerError('Extends chain exceeds maximum depth of 5', {
146
+ if (depth > constants_1.MAX_CONFIG_EXTENDS_DEPTH) {
147
+ const depthError = new ConfigMergerError(`Extends chain exceeds maximum depth of ${constants_1.MAX_CONFIG_EXTENDS_DEPTH}`, {
148
148
  code: 'MAX_DEPTH_EXCEEDED',
149
149
  path: configPath,
150
150
  depth
151
151
  });
152
152
  depthError.message += '\n\n Hint: Simplify your config inheritance:';
153
- depthError.message += '\n - Maximum depth is 5 levels';
153
+ depthError.message += `\n - Maximum depth is ${constants_1.MAX_CONFIG_EXTENDS_DEPTH} levels`;
154
154
  depthError.message += `\n - Current depth: ${depth}`;
155
155
  depthError.message += '\n - Consider consolidating base configs';
156
156
  throw depthError;
@@ -1,2 +1,6 @@
1
1
  import type { FinalConfig } from './configFile';
2
- export declare const validateConfigWarnings: (config: FinalConfig) => string[];
2
+ export interface WarningResult {
3
+ warnings: string[];
4
+ hasWarnings: boolean;
5
+ }
6
+ export declare const validateConfigWarnings: (config: FinalConfig) => WarningResult;
@@ -24,6 +24,9 @@ const validateConfigWarnings = (config) => {
24
24
  for (const [pattern, rules] of Object.entries(config.transforms))
25
25
  if ((rules.content?.length ?? 0) === 0 && (rules.filename?.length ?? 0) === 0)
26
26
  warnings.push(`Transform pattern '${pattern}' has empty content and filename arrays (will have no effect)`);
27
- return warnings;
27
+ return {
28
+ warnings,
29
+ hasWarnings: warnings.length > 0
30
+ };
28
31
  };
29
32
  exports.validateConfigWarnings = validateConfigWarnings;
@@ -6,12 +6,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.showConsoleDiff = void 0;
7
7
  const chalk_1 = __importDefault(require("chalk"));
8
8
  const yaml_1 = __importDefault(require("yaml"));
9
- const arrayDiffer_1 = require("./arrayDiffer");
10
9
  const fileDiff_1 = require("./fileDiff");
11
- const deepEqual_1 = require("./utils/deepEqual");
10
+ const arrayDiffProcessor_1 = require("./utils/arrayDiffProcessor");
12
11
  const diffGenerator_1 = require("./utils/diffGenerator");
13
12
  const fileType_1 = require("./utils/fileType");
14
- const jsonPath_1 = require("./utils/jsonPath");
15
13
  const serialization_1 = require("./utils/serialization");
16
14
  const colorizeUnifiedDiff = (diff) => {
17
15
  return diff
@@ -41,29 +39,28 @@ const formatDeletedFiles = (files) => {
41
39
  const fileList = files.map((file) => chalk_1.default.red(` - ${file}`)).join('\n');
42
40
  return `${header}\n${fileList}\n`;
43
41
  };
44
- const formatArrayDiff = (sourceArray, destinationArray) => {
45
- const diff = (0, arrayDiffer_1.diffArrays)(sourceArray, destinationArray);
42
+ const formatArrayDiff = (change) => {
46
43
  let output = '';
47
- if (diff.removed.length > 0) {
48
- output += chalk_1.default.red.bold(`\n Removed (${diff.removed.length}):\n`);
49
- for (const item of diff.removed) {
44
+ if (change.removed.length > 0) {
45
+ output += chalk_1.default.red.bold(`\n Removed (${change.removed.length}):\n`);
46
+ for (const item of change.removed) {
50
47
  const yaml = yaml_1.default.stringify(item, { indent: 4 });
51
48
  const lines = yaml.split('\n').filter((l) => l.trim());
52
49
  output += lines.map((l) => chalk_1.default.red(` - ${l}`)).join('\n');
53
50
  output += '\n';
54
51
  }
55
52
  }
56
- if (diff.added.length > 0) {
57
- output += chalk_1.default.green.bold(`\n Added (${diff.added.length}):\n`);
58
- for (const item of diff.added) {
53
+ if (change.added.length > 0) {
54
+ output += chalk_1.default.green.bold(`\n Added (${change.added.length}):\n`);
55
+ for (const item of change.added) {
59
56
  const yaml = yaml_1.default.stringify(item, { indent: 4 });
60
57
  const lines = yaml.split('\n').filter((l) => l.trim());
61
58
  output += lines.map((l) => chalk_1.default.green(` + ${l}`)).join('\n');
62
59
  output += '\n';
63
60
  }
64
61
  }
65
- if (diff.unchanged.length > 0)
66
- output += chalk_1.default.gray(`\n Unchanged: ${diff.unchanged.length} items\n`);
62
+ if (change.unchanged.length > 0)
63
+ output += chalk_1.default.gray(`\n Unchanged: ${change.unchanged.length} items\n`);
67
64
  return output;
68
65
  };
69
66
  const formatChangedFile = (file, config) => {
@@ -86,8 +83,8 @@ ${skipPathInfo}
86
83
  ${colorizedDiff}
87
84
  `;
88
85
  }
89
- const hasArraysInFile = (0, arrayDiffer_1.hasArrays)(file.rawParsedSource) || (0, arrayDiffer_1.hasArrays)(file.rawParsedDest);
90
- if (!hasArraysInFile) {
86
+ const arrayInfo = (0, arrayDiffProcessor_1.detectArrayChanges)(file);
87
+ if (!arrayInfo.hasArrays) {
91
88
  const destinationContent = (0, serialization_1.serializeForDiff)(file.processedDestContent, true);
92
89
  const sourceContent = (0, serialization_1.serializeForDiff)(file.processedSourceContent, true);
93
90
  const unifiedDiff = (0, diffGenerator_1.generateUnifiedDiff)(file.path, destinationContent, sourceContent);
@@ -106,30 +103,12 @@ ${colorizedDiff}
106
103
  const unifiedDiff = (0, diffGenerator_1.generateUnifiedDiff)(file.path, destinationContent, sourceContent);
107
104
  const colorizedDiff = colorizeUnifiedDiff(unifiedDiff);
108
105
  output += `\n${colorizedDiff}\n`;
109
- const arrayPaths = (0, arrayDiffer_1.findArrayPaths)(file.rawParsedSource);
110
- const hasArrayChanges = arrayPaths.some((path) => {
111
- const sourceArray = (0, jsonPath_1.getValueAtPath)(file.rawParsedSource, path);
112
- const destinationArray = (0, jsonPath_1.getValueAtPath)(file.rawParsedDest, path);
113
- if (!Array.isArray(sourceArray) || !Array.isArray(destinationArray))
114
- return false;
115
- return !(0, deepEqual_1.deepEqual)((0, serialization_1.normalizeForComparison)(sourceArray), (0, serialization_1.normalizeForComparison)(destinationArray));
116
- });
117
- if (hasArrayChanges) {
106
+ if (arrayInfo.hasChanges) {
118
107
  output += chalk_1.default.cyan.bold('\nArray-specific details:\n');
119
- for (const path of arrayPaths) {
120
- const pathString = path.join('.');
121
- const sourceArray = (0, jsonPath_1.getValueAtPath)(file.rawParsedSource, path);
122
- const destinationArray = (0, jsonPath_1.getValueAtPath)(file.rawParsedDest, path);
123
- if (!Array.isArray(sourceArray))
124
- continue;
125
- if (!Array.isArray(destinationArray))
126
- continue;
127
- const normalizedSource = (0, serialization_1.normalizeForComparison)(sourceArray);
128
- const normalizedDestination = (0, serialization_1.normalizeForComparison)(destinationArray);
129
- if ((0, deepEqual_1.deepEqual)(normalizedSource, normalizedDestination))
130
- continue;
108
+ for (const change of arrayInfo.changes) {
109
+ const pathString = change.path.join('.');
131
110
  output += chalk_1.default.cyan(`\n ${pathString}:\n`);
132
- output += formatArrayDiff(normalizedSource, normalizedDestination);
111
+ output += formatArrayDiff(change);
133
112
  }
134
113
  }
135
114
  return output;
@@ -0,0 +1,5 @@
1
+ export declare const SYNC_CONFIRMATION_DELAY_MS = 2000;
2
+ export declare const YAML_LINE_WIDTH_UNLIMITED = 0;
3
+ export declare const YAML_DEFAULT_INDENT = 2;
4
+ export declare const MAX_CONFIG_EXTENDS_DEPTH = 5;
5
+ export declare const SEMVER_STRICT_PATTERN: RegExp;
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SEMVER_STRICT_PATTERN = exports.MAX_CONFIG_EXTENDS_DEPTH = exports.YAML_DEFAULT_INDENT = exports.YAML_LINE_WIDTH_UNLIMITED = exports.SYNC_CONFIRMATION_DELAY_MS = void 0;
4
+ exports.SYNC_CONFIRMATION_DELAY_MS = 2000;
5
+ exports.YAML_LINE_WIDTH_UNLIMITED = 0;
6
+ exports.YAML_DEFAULT_INDENT = 2;
7
+ exports.MAX_CONFIG_EXTENDS_DEPTH = 5;
8
+ exports.SEMVER_STRICT_PATTERN = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
@@ -1,4 +1,4 @@
1
- import { Config } from './configFile';
1
+ import { Config, TransformConfig } from './configFile';
2
2
  import { FileMap } from './fileLoader';
3
3
  export interface FileDiffResult {
4
4
  addedFiles: string[];
@@ -8,6 +8,7 @@ export interface FileDiffResult {
8
8
  }
9
9
  export interface ChangedFile {
10
10
  path: string;
11
+ originalPath?: string;
11
12
  sourceContent: string;
12
13
  destinationContent: string;
13
14
  processedSourceContent: unknown;
@@ -20,12 +21,20 @@ export interface ChangedFile {
20
21
  parsedSource?: unknown;
21
22
  parsedDest?: unknown;
22
23
  }
24
+ export interface ProcessYamlOptions {
25
+ filePath: string;
26
+ sourceContent: string;
27
+ destinationContent: string;
28
+ skipPath?: Record<string, string[]>;
29
+ transforms?: TransformConfig;
30
+ }
23
31
  declare const FileDiffErrorClass: {
24
32
  new (message: string, options?: import("./utils/errors").ErrorOptions): {
25
33
  [key: string]: unknown;
26
34
  readonly code?: string;
27
35
  readonly path?: string;
28
36
  readonly cause?: Error;
37
+ readonly hints?: string[];
29
38
  name: string;
30
39
  message: string;
31
40
  stack?: string;
@@ -38,5 +47,5 @@ export declare class FileDiffError extends FileDiffErrorClass {
38
47
  }
39
48
  export declare const isFileDiffError: (error: unknown) => error is FileDiffError;
40
49
  export declare const getSkipPathsForFile: (filePath: string, skipPath?: Record<string, string[]>) => string[];
41
- export declare const computeFileDiff: (sourceFiles: FileMap, destinationFiles: FileMap, config: Config, logger?: import("./logger").Logger) => FileDiffResult;
50
+ export declare const computeFileDiff: (sourceFiles: FileMap, destinationFiles: FileMap, config: Config, logger?: import("./logger").Logger, originalPaths?: Map<string, string>) => FileDiffResult;
42
51
  export {};
package/dist/fileDiff.js CHANGED
@@ -41,6 +41,30 @@ const deleteJsonPathRecursive = (object, parts, index) => {
41
41
  const currentPart = parts[index];
42
42
  if (!currentPart)
43
43
  return;
44
+ if ((0, jsonPath_1.isFilterSegment)(currentPart)) {
45
+ if (!Array.isArray(object))
46
+ return;
47
+ const filter = (0, jsonPath_1.parseFilterSegment)(currentPart);
48
+ if (!filter)
49
+ return;
50
+ if (index === parts.length - 1)
51
+ for (let index_ = object.length - 1; index_ >= 0; index_--) {
52
+ const item = object[index_];
53
+ if (item && typeof item === 'object') {
54
+ const itemValue = item[filter.property];
55
+ if (String(itemValue) === filter.value)
56
+ object.splice(index_, 1);
57
+ }
58
+ }
59
+ else
60
+ for (const item of object)
61
+ if (item && typeof item === 'object') {
62
+ const itemValue = item[filter.property];
63
+ if (String(itemValue) === filter.value)
64
+ deleteJsonPathRecursive(item, parts, index + 1);
65
+ }
66
+ return;
67
+ }
44
68
  if (index === parts.length - 1) {
45
69
  if (currentPart === '*' && Array.isArray(object))
46
70
  object.length = 0;
@@ -80,7 +104,8 @@ const getSkipPathsForFile = (filePath, skipPath) => {
80
104
  return pathsToSkip;
81
105
  };
82
106
  exports.getSkipPathsForFile = getSkipPathsForFile;
83
- const processYamlFile = (filePath, sourceContent, destinationContent, skipPath, transforms) => {
107
+ const processYamlFile = (options) => {
108
+ const { filePath, sourceContent, destinationContent, skipPath, transforms } = options;
84
109
  let sourceParsed;
85
110
  let destinationParsed;
86
111
  try {
@@ -141,18 +166,28 @@ const processYamlFile = (filePath, sourceContent, destinationContent, skipPath,
141
166
  parsedDest: destinationParsed
142
167
  };
143
168
  };
144
- const processChangedFiles = (sourceFiles, destinationFiles, skipPath, transforms) => {
169
+ const processChangedFiles = (sourceFiles, destinationFiles, skipPath, transforms, originalPaths) => {
145
170
  const changedFiles = [];
146
171
  const unchangedFiles = [];
147
172
  for (const [path, sourceContent] of sourceFiles.entries()) {
148
173
  if (!destinationFiles.has(path))
149
174
  continue;
150
175
  const destinationContent = destinationFiles.get(path);
176
+ const originalPath = originalPaths?.get(path);
151
177
  const isYaml = (0, fileType_1.isYamlFile)(path);
152
178
  if (isYaml) {
153
- const changed = processYamlFile(path, sourceContent, destinationContent, skipPath, transforms);
154
- if (changed)
179
+ const changed = processYamlFile({
180
+ filePath: path,
181
+ sourceContent,
182
+ destinationContent,
183
+ skipPath,
184
+ transforms
185
+ });
186
+ if (changed) {
187
+ if (originalPath)
188
+ changed.originalPath = originalPath;
155
189
  changedFiles.push(changed);
190
+ }
156
191
  else
157
192
  unchangedFiles.push(path);
158
193
  }
@@ -161,6 +196,7 @@ const processChangedFiles = (sourceFiles, destinationFiles, skipPath, transforms
161
196
  else
162
197
  changedFiles.push({
163
198
  path,
199
+ originalPath,
164
200
  sourceContent,
165
201
  destinationContent: destinationContent,
166
202
  processedSourceContent: sourceContent,
@@ -172,7 +208,7 @@ const processChangedFiles = (sourceFiles, destinationFiles, skipPath, transforms
172
208
  }
173
209
  return { changedFiles, unchangedFiles };
174
210
  };
175
- const computeFileDiff = (sourceFiles, destinationFiles, config, logger) => {
211
+ const computeFileDiff = (sourceFiles, destinationFiles, config, logger, originalPaths) => {
176
212
  if (logger?.shouldShow('debug')) {
177
213
  logger.debug('Computing file differences:');
178
214
  logger.debug(` Source files: ${sourceFiles.size}`);
@@ -186,7 +222,7 @@ const computeFileDiff = (sourceFiles, destinationFiles, config, logger) => {
186
222
  }
187
223
  const addedFiles = detectAddedFiles(sourceFiles, destinationFiles);
188
224
  const deletedFiles = config.prune ? detectDeletedFiles(sourceFiles, destinationFiles) : [];
189
- const { changedFiles, unchangedFiles } = processChangedFiles(sourceFiles, destinationFiles, config.skipPath, config.transforms);
225
+ const { changedFiles, unchangedFiles } = processChangedFiles(sourceFiles, destinationFiles, config.skipPath, config.transforms, originalPaths);
190
226
  return { addedFiles, deletedFiles, changedFiles, unchangedFiles };
191
227
  };
192
228
  exports.computeFileDiff = computeFileDiff;
@@ -6,12 +6,17 @@ export interface FileLoaderOptions {
6
6
  transforms?: TransformConfig;
7
7
  }
8
8
  export type FileMap = Map<string, string>;
9
+ export interface FileLoaderResult {
10
+ fileMap: FileMap;
11
+ originalPaths: Map<string, string>;
12
+ }
9
13
  declare const FileLoaderErrorClass: {
10
14
  new (message: string, options?: import("./utils/errors").ErrorOptions): {
11
15
  [key: string]: unknown;
12
16
  readonly code?: string;
13
17
  readonly path?: string;
14
18
  readonly cause?: Error;
19
+ readonly hints?: string[];
15
20
  name: string;
16
21
  message: string;
17
22
  stack?: string;
@@ -23,5 +28,5 @@ declare const FileLoaderErrorClass: {
23
28
  export declare class FileLoaderError extends FileLoaderErrorClass {
24
29
  }
25
30
  export declare const isFileLoaderError: (error: unknown) => error is FileLoaderError;
26
- export declare const loadFiles: (options: FileLoaderOptions, logger?: import("./logger").Logger) => Promise<FileMap>;
31
+ export declare const loadFiles: (options: FileLoaderOptions, logger?: import("./logger").Logger) => Promise<FileLoaderResult>;
27
32
  export {};
@@ -162,20 +162,20 @@ const loadFiles = async (options, logger) => {
162
162
  logger.debug(` Matched: ${files.length} file(s)`);
163
163
  }
164
164
  const fileMap = await readFilesIntoMap(absoluteBaseDirectory, files);
165
- const transformedMap = options.transforms ? (0, filenameTransformer_1.transformFilenameMap)(fileMap, options.transforms) : fileMap;
165
+ const transformResult = (0, filenameTransformer_1.transformFilenameMap)(fileMap, options.transforms);
166
166
  if (options.transforms && logger?.shouldShow('debug')) {
167
- logger.debug(`Filename transforms applied: ${fileMap.size} → ${transformedMap.size} files`);
167
+ logger.debug(`Filename transforms applied: ${fileMap.size} → ${transformResult.fileMap.size} files`);
168
168
  let exampleCount = 0;
169
- for (const [transformed, content] of transformedMap.entries()) {
170
- const original = [...fileMap.entries()].find(([_key, c]) => c === content)?.[0];
171
- if (original && original !== transformed) {
172
- logger.debug(` ${original} ${transformed}`);
173
- exampleCount++;
174
- if (exampleCount >= 3)
175
- break;
176
- }
169
+ for (const [transformedPath, originalPath] of transformResult.originalPaths.entries()) {
170
+ logger.debug(` ${originalPath} ${transformedPath}`);
171
+ exampleCount++;
172
+ if (exampleCount >= 3)
173
+ break;
177
174
  }
178
175
  }
179
- return sortMapByKeys(transformedMap);
176
+ return {
177
+ fileMap: sortMapByKeys(transformResult.fileMap),
178
+ originalPaths: transformResult.originalPaths
179
+ };
180
180
  };
181
181
  exports.loadFiles = loadFiles;
@@ -1,5 +1,5 @@
1
1
  import { Config } from './configFile';
2
- import { FileDiffResult } from './fileDiff';
2
+ import { ChangedFile, FileDiffResult } from './fileDiff';
3
3
  import { FileMap } from './fileLoader';
4
4
  import { Logger } from './logger';
5
5
  export interface FileUpdateError {
@@ -7,12 +7,30 @@ export interface FileUpdateError {
7
7
  path: string;
8
8
  error: Error;
9
9
  }
10
+ export interface FileOperationOptions {
11
+ relativePath: string;
12
+ content: string;
13
+ absoluteDestinationDirectory: string;
14
+ config: Config;
15
+ dryRun: boolean;
16
+ skipFormat: boolean;
17
+ logger: Logger;
18
+ }
19
+ export interface UpdateFileOptions {
20
+ changedFile: ChangedFile;
21
+ absoluteDestinationDirectory: string;
22
+ config: Config;
23
+ dryRun: boolean;
24
+ skipFormat: boolean;
25
+ logger: Logger;
26
+ }
10
27
  declare const FileUpdaterErrorClass: {
11
28
  new (message: string, options?: import("./utils/errors").ErrorOptions): {
12
29
  [key: string]: unknown;
13
30
  readonly code?: string;
14
31
  readonly path?: string;
15
32
  readonly cause?: Error;
33
+ readonly hints?: string[];
16
34
  name: string;
17
35
  message: string;
18
36
  stack?: string;