helm-env-delta 1.8.0 → 1.8.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.
package/README.md CHANGED
@@ -780,7 +780,7 @@ git push origin main
780
780
 
781
781
  ✅ **Flexibility** - Per-file patterns. Config inheritance. Regex transforms.
782
782
 
783
- ✅ **Reliability** - 920 tests, 84% coverage. Battle-tested.
783
+ ✅ **Reliability** - 990+ tests, 84% coverage. Battle-tested.
784
784
 
785
785
  ---
786
786
 
@@ -10,6 +10,7 @@ const yaml_1 = __importDefault(require("yaml"));
10
10
  const consoleFormatter_1 = require("./consoleFormatter");
11
11
  const errors_1 = require("./utils/errors");
12
12
  const fileType_1 = require("./utils/fileType");
13
+ const jsonPath_1 = require("./utils/jsonPath");
13
14
  const transformer_1 = require("./utils/transformer");
14
15
  const yamlFormatter_1 = require("./yamlFormatter");
15
16
  const FileUpdaterErrorClass = (0, errors_1.createErrorClass)('File Updater Error', {
@@ -61,15 +62,112 @@ const ensureParentDirectory = async (filePath) => {
61
62
  });
62
63
  }
63
64
  };
64
- const deepMerge = (fullTarget, filteredSource, filteredTarget) => {
65
+ const getApplicableArrayFilters = (currentPath, skipPaths) => {
66
+ const filters = [];
67
+ for (const skipPath of skipPaths) {
68
+ const segments = (0, jsonPath_1.parseJsonPath)(skipPath);
69
+ if (segments.length <= currentPath.length)
70
+ continue;
71
+ let isPrefix = true;
72
+ for (let index = 0; index < currentPath.length; index++)
73
+ if (segments[index] !== currentPath[index]) {
74
+ isPrefix = false;
75
+ break;
76
+ }
77
+ if (!isPrefix)
78
+ continue;
79
+ const nextSegment = segments[currentPath.length];
80
+ if (!nextSegment || !(0, jsonPath_1.isFilterSegment)(nextSegment))
81
+ continue;
82
+ const filter = (0, jsonPath_1.parseFilterSegment)(nextSegment);
83
+ if (!filter)
84
+ continue;
85
+ const remainingPath = segments.slice(currentPath.length + 1);
86
+ filters.push({ filter, remainingPath });
87
+ }
88
+ return filters;
89
+ };
90
+ const itemMatchesAnyFilter = (item, applicableFilters) => {
91
+ if (!item || typeof item !== 'object')
92
+ return { matches: false };
93
+ const itemObject = item;
94
+ for (const applicableFilter of applicableFilters) {
95
+ const itemValue = itemObject[applicableFilter.filter.property];
96
+ if (itemValue !== undefined && (0, jsonPath_1.matchesFilter)(itemValue, applicableFilter.filter))
97
+ return { matches: true, matchedFilter: applicableFilter };
98
+ }
99
+ return { matches: false };
100
+ };
101
+ const findMatchingTargetItem = (sourceItem, fullTargetArray, applicableFilters) => {
102
+ if (!sourceItem || typeof sourceItem !== 'object')
103
+ return undefined;
104
+ const sourceObject = sourceItem;
105
+ for (const targetItem of fullTargetArray) {
106
+ if (!targetItem || typeof targetItem !== 'object')
107
+ continue;
108
+ const targetObject = targetItem;
109
+ for (const { filter } of applicableFilters)
110
+ if (sourceObject[filter.property] === targetObject[filter.property])
111
+ return targetItem;
112
+ }
113
+ return undefined;
114
+ };
115
+ const shouldPreserveItem = (item, applicableFilters, existingResult) => {
116
+ if (!item || typeof item !== 'object')
117
+ return false;
118
+ const itemObject = item;
119
+ const { matches } = itemMatchesAnyFilter(item, applicableFilters);
120
+ if (!matches)
121
+ return false;
122
+ for (const existingItem of existingResult) {
123
+ if (!existingItem || typeof existingItem !== 'object')
124
+ continue;
125
+ const existingObject = existingItem;
126
+ let isDuplicate = true;
127
+ for (const { filter } of applicableFilters)
128
+ if (existingObject[filter.property] !== itemObject[filter.property]) {
129
+ isDuplicate = false;
130
+ break;
131
+ }
132
+ if (isDuplicate)
133
+ return false;
134
+ }
135
+ return true;
136
+ };
137
+ const deepMerge = (fullTarget, filteredSource, filteredTarget, currentPath = [], skipPaths = []) => {
65
138
  if (filteredSource === null || filteredSource === undefined)
66
139
  return fullTarget;
67
140
  if (fullTarget === null || fullTarget === undefined)
68
141
  return filteredSource;
69
142
  if (typeof fullTarget !== typeof filteredSource)
70
143
  return filteredSource;
71
- if (Array.isArray(filteredSource))
72
- return filteredSource;
144
+ if (Array.isArray(filteredSource)) {
145
+ const fullTargetArray = Array.isArray(fullTarget) ? fullTarget : [];
146
+ const filteredTargetArray = Array.isArray(filteredTarget) ? filteredTarget : [];
147
+ const applicableFilters = getApplicableArrayFilters(currentPath, skipPaths);
148
+ if (applicableFilters.length === 0)
149
+ return filteredSource;
150
+ const hasNestedFilters = applicableFilters.some((f) => f.remainingPath.length > 0);
151
+ const result = [];
152
+ for (const sourceItem of filteredSource) {
153
+ if (hasNestedFilters && sourceItem && typeof sourceItem === 'object') {
154
+ const { matches, matchedFilter } = itemMatchesAnyFilter(sourceItem, applicableFilters);
155
+ if (matches && matchedFilter && matchedFilter.remainingPath.length > 0) {
156
+ const matchingTargetItem = findMatchingTargetItem(sourceItem, fullTargetArray, applicableFilters);
157
+ const matchingFilteredTargetItem = findMatchingTargetItem(sourceItem, filteredTargetArray, applicableFilters);
158
+ if (matchingTargetItem) {
159
+ result.push(deepMerge(matchingTargetItem, sourceItem, matchingFilteredTargetItem, currentPath, skipPaths));
160
+ continue;
161
+ }
162
+ }
163
+ }
164
+ result.push(sourceItem);
165
+ }
166
+ for (const item of fullTargetArray)
167
+ if (shouldPreserveItem(item, applicableFilters, result))
168
+ result.push(item);
169
+ return result;
170
+ }
73
171
  if (typeof filteredSource === 'object' && typeof fullTarget === 'object') {
74
172
  const sourceObject = filteredSource;
75
173
  const fullTargetObject = fullTarget;
@@ -80,12 +178,12 @@ const deepMerge = (fullTarget, filteredSource, filteredTarget) => {
80
178
  result[key] = value;
81
179
  for (const [key, value] of Object.entries(sourceObject))
82
180
  if (key in fullTargetObject)
83
- result[key] = deepMerge(fullTargetObject[key], value, filteredTargetObject[key]);
181
+ result[key] = deepMerge(fullTargetObject[key], value, filteredTargetObject[key], [...currentPath, key], skipPaths);
84
182
  return result;
85
183
  }
86
184
  return filteredSource;
87
185
  };
88
- const mergeYamlContent = (destinationContent, processedSourceContent, filteredDestinationContent, filePath) => {
186
+ const mergeYamlContent = (destinationContent, processedSourceContent, filteredDestinationContent, filePath, skipPaths = []) => {
89
187
  let destinationParsed;
90
188
  try {
91
189
  destinationParsed = yaml_1.default.parse(destinationContent);
@@ -104,7 +202,7 @@ const mergeYamlContent = (destinationContent, processedSourceContent, filteredDe
104
202
  }
105
203
  let merged;
106
204
  try {
107
- merged = deepMerge(destinationParsed, processedSourceContent, filteredDestinationContent);
205
+ merged = deepMerge(destinationParsed, processedSourceContent, filteredDestinationContent, [], skipPaths);
108
206
  }
109
207
  catch (error) {
110
208
  throw new FileUpdaterError('Failed to merge YAML content', {
@@ -168,7 +266,7 @@ const updateFile = async (options) => {
168
266
  return;
169
267
  }
170
268
  let contentToWrite = (0, fileType_1.isYamlFile)(changedFile.path)
171
- ? mergeYamlContent(changedFile.destinationContent, changedFile.rawParsedSource, changedFile.rawParsedDest, changedFile.path)
269
+ ? mergeYamlContent(changedFile.destinationContent, changedFile.rawParsedSource, changedFile.rawParsedDest, changedFile.path, changedFile.skipPaths)
172
270
  : changedFile.sourceContent;
173
271
  if ((0, fileType_1.isYamlFile)(changedFile.path)) {
174
272
  const effectiveOutputFormat = skipFormat ? undefined : config.outputFormat;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "helm-env-delta",
3
- "version": "1.8.0",
3
+ "version": "1.8.1",
4
4
  "description": "HelmEnvDelta – environment-aware YAML delta and sync for GitOps",
5
5
  "author": "BCsabaEngine",
6
6
  "license": "ISC",