helm-env-delta 1.7.0 → 1.7.2
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 +32 -3
- package/dist/commandLine.d.ts +1 -0
- package/dist/commandLine.js +6 -0
- package/dist/fileDiff.d.ts +2 -1
- package/dist/fileDiff.js +33 -4
- package/dist/fileLoader.d.ts +6 -1
- package/dist/fileLoader.js +21 -17
- package/dist/htmlReporter.js +11 -5
- package/dist/index.js +57 -7
- package/dist/patternUsageValidator.js +39 -2
- package/dist/reporters/htmlStyles.d.ts +1 -1
- package/dist/reporters/htmlStyles.js +17 -4
- package/dist/utils/filenameTransformer.d.ts +5 -1
- package/dist/utils/filenameTransformer.js +5 -2
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.js +4 -2
- package/dist/utils/jsonPath.d.ts +5 -0
- package/dist/utils/jsonPath.js +75 -5
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -374,7 +374,26 @@ skipPath:
|
|
|
374
374
|
- 'resources.limits'
|
|
375
375
|
```
|
|
376
376
|
|
|
377
|
-
|
|
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
|
|
|
@@ -623,7 +642,8 @@ hed --config <file> [options] # Short alias
|
|
|
623
642
|
| `--diff-json` | Output JSON to stdout (pipe to jq) |
|
|
624
643
|
| `--list-files` | List source/destination files without processing |
|
|
625
644
|
| `--show-config` | Display resolved config after inheritance |
|
|
626
|
-
| `--
|
|
645
|
+
| `--format-only` | Format destination files without syncing |
|
|
646
|
+
| `--skip-format` | Skip YAML formatting during sync |
|
|
627
647
|
| `--no-color` | Disable colored output (CI/accessibility) |
|
|
628
648
|
| `--verbose` | Show detailed debug info |
|
|
629
649
|
| `--quiet` | Suppress output except errors |
|
|
@@ -660,6 +680,12 @@ hed --config config.yaml
|
|
|
660
680
|
|
|
661
681
|
# Force override stop rules
|
|
662
682
|
hed --config config.yaml --force
|
|
683
|
+
|
|
684
|
+
# Format destination files only (no sync)
|
|
685
|
+
hed --config config.yaml --format-only
|
|
686
|
+
|
|
687
|
+
# Preview format changes
|
|
688
|
+
hed --config config.yaml --format-only --dry-run
|
|
663
689
|
```
|
|
664
690
|
|
|
665
691
|
---
|
|
@@ -730,7 +756,7 @@ git push origin main
|
|
|
730
756
|
|
|
731
757
|
✅ **Flexibility** - Per-file patterns. Config inheritance. Regex transforms.
|
|
732
758
|
|
|
733
|
-
✅ **Reliability** -
|
|
759
|
+
✅ **Reliability** - 920 tests, 84% coverage. Battle-tested.
|
|
734
760
|
|
|
735
761
|
---
|
|
736
762
|
|
|
@@ -840,6 +866,7 @@ helm-env-delta --config config.yaml --validate
|
|
|
840
866
|
2. **skipPath patterns** - Two-level validation:
|
|
841
867
|
- Glob pattern must match at least one file
|
|
842
868
|
- JSONPath must exist in at least one matched file
|
|
869
|
+
- Filter expressions `[prop=value]` validated against actual array items
|
|
843
870
|
3. **stopRules patterns** - Two-level validation:
|
|
844
871
|
- Glob pattern must match at least one file
|
|
845
872
|
- JSONPath (if specified) must exist in at least one matched file
|
|
@@ -930,6 +957,8 @@ spec:
|
|
|
930
957
|
# ✅ Correct
|
|
931
958
|
- 'spec.replicas' # No prefix
|
|
932
959
|
- 'env[*].name' # Array wildcard
|
|
960
|
+
- 'env[name=DEBUG]' # Filter by property value
|
|
961
|
+
- 'containers[name=app].env[name=SECRET]' # Nested filters
|
|
933
962
|
```
|
|
934
963
|
|
|
935
964
|
---
|
package/dist/commandLine.d.ts
CHANGED
package/dist/commandLine.js
CHANGED
|
@@ -19,6 +19,7 @@ const parseCommandLine = (argv) => {
|
|
|
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
21
|
.option('--skip-format', 'Skip YAML formatting (outputFormat section)', false)
|
|
22
|
+
.option('--format-only', 'Format YAML files in destination without syncing', false)
|
|
22
23
|
.option('--validate', 'Validate configuration file and exit', false)
|
|
23
24
|
.option('--list-files', 'List files that would be synced without processing diffs', false)
|
|
24
25
|
.option('--show-config', 'Display resolved configuration after inheritance and exit', false)
|
|
@@ -53,6 +54,10 @@ Documentation: https://github.com/balazscsaba2006/helm-env-delta
|
|
|
53
54
|
console.error('Error: --verbose and --quiet flags are mutually exclusive');
|
|
54
55
|
process.exit(1);
|
|
55
56
|
}
|
|
57
|
+
if (options['formatOnly'] && options['skipFormat']) {
|
|
58
|
+
console.error('Error: --format-only and --skip-format flags are mutually exclusive');
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
56
61
|
const threshold = Number.parseFloat(options['suggestThreshold']);
|
|
57
62
|
if (Number.isNaN(threshold) || threshold < 0 || threshold > 1) {
|
|
58
63
|
console.error('Error: --suggest-threshold must be a number between 0 and 1');
|
|
@@ -66,6 +71,7 @@ Documentation: https://github.com/balazscsaba2006/helm-env-delta
|
|
|
66
71
|
diffHtml: options['diffHtml'],
|
|
67
72
|
diffJson: options['diffJson'],
|
|
68
73
|
skipFormat: options['skipFormat'],
|
|
74
|
+
formatOnly: options['formatOnly'],
|
|
69
75
|
validate: options['validate'],
|
|
70
76
|
listFiles: options['listFiles'],
|
|
71
77
|
showConfig: options['showConfig'],
|
package/dist/fileDiff.d.ts
CHANGED
|
@@ -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;
|
|
@@ -46,5 +47,5 @@ export declare class FileDiffError extends FileDiffErrorClass {
|
|
|
46
47
|
}
|
|
47
48
|
export declare const isFileDiffError: (error: unknown) => error is FileDiffError;
|
|
48
49
|
export declare const getSkipPathsForFile: (filePath: string, skipPath?: Record<string, string[]>) => string[];
|
|
49
|
-
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;
|
|
50
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;
|
|
@@ -142,13 +166,14 @@ const processYamlFile = (options) => {
|
|
|
142
166
|
parsedDest: destinationParsed
|
|
143
167
|
};
|
|
144
168
|
};
|
|
145
|
-
const processChangedFiles = (sourceFiles, destinationFiles, skipPath, transforms) => {
|
|
169
|
+
const processChangedFiles = (sourceFiles, destinationFiles, skipPath, transforms, originalPaths) => {
|
|
146
170
|
const changedFiles = [];
|
|
147
171
|
const unchangedFiles = [];
|
|
148
172
|
for (const [path, sourceContent] of sourceFiles.entries()) {
|
|
149
173
|
if (!destinationFiles.has(path))
|
|
150
174
|
continue;
|
|
151
175
|
const destinationContent = destinationFiles.get(path);
|
|
176
|
+
const originalPath = originalPaths?.get(path);
|
|
152
177
|
const isYaml = (0, fileType_1.isYamlFile)(path);
|
|
153
178
|
if (isYaml) {
|
|
154
179
|
const changed = processYamlFile({
|
|
@@ -158,8 +183,11 @@ const processChangedFiles = (sourceFiles, destinationFiles, skipPath, transforms
|
|
|
158
183
|
skipPath,
|
|
159
184
|
transforms
|
|
160
185
|
});
|
|
161
|
-
if (changed)
|
|
186
|
+
if (changed) {
|
|
187
|
+
if (originalPath)
|
|
188
|
+
changed.originalPath = originalPath;
|
|
162
189
|
changedFiles.push(changed);
|
|
190
|
+
}
|
|
163
191
|
else
|
|
164
192
|
unchangedFiles.push(path);
|
|
165
193
|
}
|
|
@@ -168,6 +196,7 @@ const processChangedFiles = (sourceFiles, destinationFiles, skipPath, transforms
|
|
|
168
196
|
else
|
|
169
197
|
changedFiles.push({
|
|
170
198
|
path,
|
|
199
|
+
originalPath,
|
|
171
200
|
sourceContent,
|
|
172
201
|
destinationContent: destinationContent,
|
|
173
202
|
processedSourceContent: sourceContent,
|
|
@@ -179,7 +208,7 @@ const processChangedFiles = (sourceFiles, destinationFiles, skipPath, transforms
|
|
|
179
208
|
}
|
|
180
209
|
return { changedFiles, unchangedFiles };
|
|
181
210
|
};
|
|
182
|
-
const computeFileDiff = (sourceFiles, destinationFiles, config, logger) => {
|
|
211
|
+
const computeFileDiff = (sourceFiles, destinationFiles, config, logger, originalPaths) => {
|
|
183
212
|
if (logger?.shouldShow('debug')) {
|
|
184
213
|
logger.debug('Computing file differences:');
|
|
185
214
|
logger.debug(` Source files: ${sourceFiles.size}`);
|
|
@@ -193,7 +222,7 @@ const computeFileDiff = (sourceFiles, destinationFiles, config, logger) => {
|
|
|
193
222
|
}
|
|
194
223
|
const addedFiles = detectAddedFiles(sourceFiles, destinationFiles);
|
|
195
224
|
const deletedFiles = config.prune ? detectDeletedFiles(sourceFiles, destinationFiles) : [];
|
|
196
|
-
const { changedFiles, unchangedFiles } = processChangedFiles(sourceFiles, destinationFiles, config.skipPath, config.transforms);
|
|
225
|
+
const { changedFiles, unchangedFiles } = processChangedFiles(sourceFiles, destinationFiles, config.skipPath, config.transforms, originalPaths);
|
|
197
226
|
return { addedFiles, deletedFiles, changedFiles, unchangedFiles };
|
|
198
227
|
};
|
|
199
228
|
exports.computeFileDiff = computeFileDiff;
|
package/dist/fileLoader.d.ts
CHANGED
|
@@ -4,8 +4,13 @@ export interface FileLoaderOptions {
|
|
|
4
4
|
include: string[];
|
|
5
5
|
exclude: string[];
|
|
6
6
|
transforms?: TransformConfig;
|
|
7
|
+
skipExclude?: boolean;
|
|
7
8
|
}
|
|
8
9
|
export type FileMap = Map<string, string>;
|
|
10
|
+
export interface FileLoaderResult {
|
|
11
|
+
fileMap: FileMap;
|
|
12
|
+
originalPaths: Map<string, string>;
|
|
13
|
+
}
|
|
9
14
|
declare const FileLoaderErrorClass: {
|
|
10
15
|
new (message: string, options?: import("./utils/errors").ErrorOptions): {
|
|
11
16
|
[key: string]: unknown;
|
|
@@ -24,5 +29,5 @@ declare const FileLoaderErrorClass: {
|
|
|
24
29
|
export declare class FileLoaderError extends FileLoaderErrorClass {
|
|
25
30
|
}
|
|
26
31
|
export declare const isFileLoaderError: (error: unknown) => error is FileLoaderError;
|
|
27
|
-
export declare const loadFiles: (options: FileLoaderOptions, logger?: import("./logger").Logger) => Promise<
|
|
32
|
+
export declare const loadFiles: (options: FileLoaderOptions, logger?: import("./logger").Logger) => Promise<FileLoaderResult>;
|
|
28
33
|
export {};
|
package/dist/fileLoader.js
CHANGED
|
@@ -67,10 +67,12 @@ const validateAndResolveBaseDirectory = async (baseDirectory) => {
|
|
|
67
67
|
throw accessError;
|
|
68
68
|
}
|
|
69
69
|
};
|
|
70
|
-
const findMatchingFiles = async (baseDirectory, includePatterns, excludePatterns, transforms) => {
|
|
70
|
+
const findMatchingFiles = async (baseDirectory, includePatterns, excludePatterns, transforms, skipExclude) => {
|
|
71
71
|
try {
|
|
72
72
|
if (!transforms) {
|
|
73
|
-
const allPatterns =
|
|
73
|
+
const allPatterns = skipExclude
|
|
74
|
+
? [...includePatterns]
|
|
75
|
+
: [...includePatterns, ...excludePatterns.map((pattern) => `!${pattern}`)];
|
|
74
76
|
const matchedFiles = await (0, tinyglobby_1.glob)(allPatterns, {
|
|
75
77
|
cwd: baseDirectory,
|
|
76
78
|
absolute: true,
|
|
@@ -94,9 +96,11 @@ const findMatchingFiles = async (baseDirectory, includePatterns, excludePatterns
|
|
|
94
96
|
const included = includePatterns.some((pattern) => patternMatcher_1.globalMatcher.match(transformedPath, pattern));
|
|
95
97
|
if (!included)
|
|
96
98
|
continue;
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
99
|
+
if (!skipExclude) {
|
|
100
|
+
const excluded = excludePatterns.some((pattern) => patternMatcher_1.globalMatcher.match(transformedPath, pattern));
|
|
101
|
+
if (excluded)
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
100
104
|
filtered.push(absolutePath);
|
|
101
105
|
}
|
|
102
106
|
return filtered;
|
|
@@ -153,7 +157,7 @@ const loadFiles = async (options, logger) => {
|
|
|
153
157
|
const absoluteBaseDirectory = await validateAndResolveBaseDirectory(options.baseDirectory);
|
|
154
158
|
const includePatterns = options.include ?? ['**/*'];
|
|
155
159
|
const excludePatterns = options.exclude ?? [];
|
|
156
|
-
const files = await findMatchingFiles(absoluteBaseDirectory, includePatterns, excludePatterns, options.transforms);
|
|
160
|
+
const files = await findMatchingFiles(absoluteBaseDirectory, includePatterns, excludePatterns, options.transforms, options.skipExclude);
|
|
157
161
|
if (logger?.shouldShow('debug')) {
|
|
158
162
|
logger.debug('Glob matching:');
|
|
159
163
|
logger.debug(` Directory: ${absoluteBaseDirectory}`);
|
|
@@ -162,20 +166,20 @@ const loadFiles = async (options, logger) => {
|
|
|
162
166
|
logger.debug(` Matched: ${files.length} file(s)`);
|
|
163
167
|
}
|
|
164
168
|
const fileMap = await readFilesIntoMap(absoluteBaseDirectory, files);
|
|
165
|
-
const
|
|
169
|
+
const transformResult = (0, filenameTransformer_1.transformFilenameMap)(fileMap, options.transforms);
|
|
166
170
|
if (options.transforms && logger?.shouldShow('debug')) {
|
|
167
|
-
logger.debug(`Filename transforms applied: ${fileMap.size} → ${
|
|
171
|
+
logger.debug(`Filename transforms applied: ${fileMap.size} → ${transformResult.fileMap.size} files`);
|
|
168
172
|
let exampleCount = 0;
|
|
169
|
-
for (const [
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
if (exampleCount >= 3)
|
|
175
|
-
break;
|
|
176
|
-
}
|
|
173
|
+
for (const [transformedPath, originalPath] of transformResult.originalPaths.entries()) {
|
|
174
|
+
logger.debug(` ${originalPath} → ${transformedPath}`);
|
|
175
|
+
exampleCount++;
|
|
176
|
+
if (exampleCount >= 3)
|
|
177
|
+
break;
|
|
177
178
|
}
|
|
178
179
|
}
|
|
179
|
-
return
|
|
180
|
+
return {
|
|
181
|
+
fileMap: sortMapByKeys(transformResult.fileMap),
|
|
182
|
+
originalPaths: transformResult.originalPaths
|
|
183
|
+
};
|
|
180
184
|
};
|
|
181
185
|
exports.loadFiles = loadFiles;
|
package/dist/htmlReporter.js
CHANGED
|
@@ -72,8 +72,14 @@ const generateArrayDiffHtml = (change) => {
|
|
|
72
72
|
html += '</div>';
|
|
73
73
|
return html;
|
|
74
74
|
};
|
|
75
|
+
const generateFileSummary = (file) => {
|
|
76
|
+
if (!file.originalPath)
|
|
77
|
+
return file.path;
|
|
78
|
+
return `<span class="filename-transform">${file.originalPath} → ${file.path}</span>`;
|
|
79
|
+
};
|
|
75
80
|
const generateChangedFileSection = (file) => {
|
|
76
81
|
const isYaml = (0, fileType_1.isYamlFile)(file.path);
|
|
82
|
+
const summary = generateFileSummary(file);
|
|
77
83
|
if (!isYaml) {
|
|
78
84
|
const destinationContent = (0, serialization_1.serializeForDiff)(file.processedDestContent, false);
|
|
79
85
|
const sourceContent = (0, serialization_1.serializeForDiff)(file.processedSourceContent, false);
|
|
@@ -81,7 +87,7 @@ const generateChangedFileSection = (file) => {
|
|
|
81
87
|
const diffHtml = generateDiffHtml(unifiedDiff);
|
|
82
88
|
return `
|
|
83
89
|
<details class="file-section" open>
|
|
84
|
-
<summary>${
|
|
90
|
+
<summary>${summary}</summary>
|
|
85
91
|
<div class="diff-container">
|
|
86
92
|
${diffHtml}
|
|
87
93
|
</div>
|
|
@@ -96,7 +102,7 @@ const generateChangedFileSection = (file) => {
|
|
|
96
102
|
const diffHtml = generateDiffHtml(unifiedDiff);
|
|
97
103
|
return `
|
|
98
104
|
<details class="file-section" open>
|
|
99
|
-
<summary>${
|
|
105
|
+
<summary>${summary}</summary>
|
|
100
106
|
<div class="diff-container">
|
|
101
107
|
${diffHtml}
|
|
102
108
|
</div>
|
|
@@ -109,18 +115,18 @@ const generateChangedFileSection = (file) => {
|
|
|
109
115
|
const diffHtml = generateDiffHtml(unifiedDiff);
|
|
110
116
|
let arrayDiffsHtml = '';
|
|
111
117
|
if (arrayInfo.hasChanges) {
|
|
112
|
-
arrayDiffsHtml = '<
|
|
118
|
+
arrayDiffsHtml = '<details class="array-details"><summary>Array-specific details:</summary>';
|
|
113
119
|
for (const change of arrayInfo.changes) {
|
|
114
120
|
const pathString = change.path.join('.');
|
|
115
121
|
arrayDiffsHtml += `<div class="array-section"><h4>${pathString}:</h4>`;
|
|
116
122
|
arrayDiffsHtml += generateArrayDiffHtml(change);
|
|
117
123
|
arrayDiffsHtml += '</div>';
|
|
118
124
|
}
|
|
119
|
-
arrayDiffsHtml += '</
|
|
125
|
+
arrayDiffsHtml += '</details>';
|
|
120
126
|
}
|
|
121
127
|
return `
|
|
122
128
|
<details class="file-section" open>
|
|
123
|
-
<summary>${
|
|
129
|
+
<summary>${summary}</summary>
|
|
124
130
|
<div class="diff-container">
|
|
125
131
|
${diffHtml}
|
|
126
132
|
${arrayDiffsHtml}
|
package/dist/index.js
CHANGED
|
@@ -37,6 +37,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
39
|
const node_fs_1 = require("node:fs");
|
|
40
|
+
const promises_1 = require("node:fs/promises");
|
|
40
41
|
const node_os_1 = require("node:os");
|
|
41
42
|
const node_path_1 = __importDefault(require("node:path"));
|
|
42
43
|
const chalk_1 = __importDefault(require("chalk"));
|
|
@@ -60,7 +61,9 @@ const stopRulesValidator_1 = require("./stopRulesValidator");
|
|
|
60
61
|
const suggestionEngine_1 = require("./suggestionEngine");
|
|
61
62
|
const collisionDetector_1 = require("./utils/collisionDetector");
|
|
62
63
|
const filenameTransformer_1 = require("./utils/filenameTransformer");
|
|
64
|
+
const fileType_1 = require("./utils/fileType");
|
|
63
65
|
const versionChecker_1 = require("./utils/versionChecker");
|
|
66
|
+
const yamlFormatter_1 = require("./yamlFormatter");
|
|
64
67
|
const ZodError_1 = require("./ZodError");
|
|
65
68
|
const main = async () => {
|
|
66
69
|
const command = (0, commandLine_1.parseCommandLine)();
|
|
@@ -98,17 +101,21 @@ const main = async () => {
|
|
|
98
101
|
console.warn(chalk_1.default.yellow(` • ${warning}`));
|
|
99
102
|
}
|
|
100
103
|
logger.log('\n' + (0, consoleFormatter_1.formatProgressMessage)('Loading files for validation...', 'loading'));
|
|
101
|
-
const
|
|
104
|
+
const sourceResult = await (0, fileLoader_1.loadFiles)({
|
|
102
105
|
baseDirectory: config.source,
|
|
103
106
|
include: config.include,
|
|
104
107
|
exclude: config.exclude,
|
|
105
|
-
transforms: config.transforms
|
|
108
|
+
transforms: config.transforms,
|
|
109
|
+
skipExclude: true
|
|
106
110
|
}, logger);
|
|
107
|
-
const
|
|
111
|
+
const sourceFiles = sourceResult.fileMap;
|
|
112
|
+
const destinationResult = await (0, fileLoader_1.loadFiles)({
|
|
108
113
|
baseDirectory: config.destination,
|
|
109
114
|
include: config.include,
|
|
110
|
-
exclude: config.exclude
|
|
115
|
+
exclude: config.exclude,
|
|
116
|
+
skipExclude: true
|
|
111
117
|
}, logger);
|
|
118
|
+
const destinationFiles = destinationResult.fileMap;
|
|
112
119
|
logger.progress(`Loaded ${sourceFiles.size} source, ${destinationFiles.size} destination file(s)`, 'success');
|
|
113
120
|
logger.log('\n' + (0, consoleFormatter_1.formatProgressMessage)('Validating pattern usage...', 'info'));
|
|
114
121
|
const usageResult = (0, patternUsageValidator_1.validatePatternUsage)(config, sourceFiles, destinationFiles);
|
|
@@ -136,23 +143,26 @@ const main = async () => {
|
|
|
136
143
|
logger.debug(` Prune enabled: ${config.prune}`);
|
|
137
144
|
}
|
|
138
145
|
logger.log('\n' + (0, consoleFormatter_1.formatProgressMessage)('Loading files...', 'loading'));
|
|
139
|
-
const
|
|
146
|
+
const sourceResult = await (0, fileLoader_1.loadFiles)({
|
|
140
147
|
baseDirectory: config.source,
|
|
141
148
|
include: config.include,
|
|
142
149
|
exclude: config.exclude,
|
|
143
150
|
transforms: config.transforms
|
|
144
151
|
}, logger);
|
|
152
|
+
const sourceFiles = sourceResult.fileMap;
|
|
153
|
+
const originalPaths = sourceResult.originalPaths;
|
|
145
154
|
logger.progress(`Loaded ${sourceFiles.size} source file(s)`, 'success');
|
|
146
155
|
const collisions = (0, collisionDetector_1.detectCollisions)(sourceFiles, config.transforms);
|
|
147
156
|
if (collisions.length > 0)
|
|
148
157
|
(0, collisionDetector_1.validateNoCollisions)(collisions);
|
|
149
158
|
if (logger.shouldShow('debug'))
|
|
150
159
|
logger.debug('Filename collision check: passed');
|
|
151
|
-
const
|
|
160
|
+
const destinationResult = await (0, fileLoader_1.loadFiles)({
|
|
152
161
|
baseDirectory: config.destination,
|
|
153
162
|
include: config.include,
|
|
154
163
|
exclude: config.exclude
|
|
155
164
|
}, logger);
|
|
165
|
+
const destinationFiles = destinationResult.fileMap;
|
|
156
166
|
logger.progress(`Loaded ${destinationFiles.size} destination file(s)`, 'success');
|
|
157
167
|
if (command.listFiles) {
|
|
158
168
|
const sourceFilesList = [...sourceFiles.keys()].toSorted();
|
|
@@ -166,8 +176,48 @@ const main = async () => {
|
|
|
166
176
|
console.log(` ${chalk_1.default.dim(file)}`);
|
|
167
177
|
return;
|
|
168
178
|
}
|
|
179
|
+
if (command.formatOnly) {
|
|
180
|
+
if (!config.outputFormat) {
|
|
181
|
+
logger.log(chalk_1.default.yellow('\n⚠️ No outputFormat configured. Nothing to format.'));
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
logger.log('\n' + (0, consoleFormatter_1.formatProgressMessage)('Formatting files...', 'info'));
|
|
185
|
+
let formattedCount = 0;
|
|
186
|
+
const errors = [];
|
|
187
|
+
for (const [relativePath, content] of destinationFiles) {
|
|
188
|
+
if (!(0, fileType_1.isYamlFile)(relativePath))
|
|
189
|
+
continue;
|
|
190
|
+
try {
|
|
191
|
+
const formatted = (0, yamlFormatter_1.formatYaml)(content, relativePath, config.outputFormat);
|
|
192
|
+
if (formatted !== content) {
|
|
193
|
+
const absolutePath = node_path_1.default.join(config.destination, relativePath);
|
|
194
|
+
if (command.dryRun)
|
|
195
|
+
logger.fileOp('format', relativePath, true);
|
|
196
|
+
else {
|
|
197
|
+
await (0, promises_1.writeFile)(absolutePath, formatted, 'utf8');
|
|
198
|
+
logger.fileOp('format', relativePath, false);
|
|
199
|
+
}
|
|
200
|
+
formattedCount++;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
catch (error) {
|
|
204
|
+
errors.push({ path: relativePath, error: error });
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (command.dryRun)
|
|
208
|
+
logger.log(`\n[DRY RUN] Would format ${formattedCount} file(s)`);
|
|
209
|
+
else
|
|
210
|
+
logger.log(`\n✓ Formatted ${formattedCount} file(s)`);
|
|
211
|
+
if (errors.length > 0) {
|
|
212
|
+
logger.error(`\n❌ Encountered ${errors.length} error(s):`, 'critical');
|
|
213
|
+
for (const { path: errorPath, error } of errors)
|
|
214
|
+
logger.error(` ${errorPath}: ${error.message}`, 'critical');
|
|
215
|
+
process.exit(1);
|
|
216
|
+
}
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
169
219
|
logger.log('\n' + (0, consoleFormatter_1.formatProgressMessage)('Computing differences...', 'info'));
|
|
170
|
-
const diffResult = (0, fileDiff_1.computeFileDiff)(sourceFiles, destinationFiles, config, logger);
|
|
220
|
+
const diffResult = (0, fileDiff_1.computeFileDiff)(sourceFiles, destinationFiles, config, logger, originalPaths);
|
|
171
221
|
if (logger.shouldShow('debug'))
|
|
172
222
|
logger.debug('Diff pipeline: parse → transforms → skipPath → normalize → compare');
|
|
173
223
|
if (command.diff && !command.quiet)
|
|
@@ -99,6 +99,44 @@ const validateStopRulePatterns = (config, sourceFiles, destinationFiles) => {
|
|
|
99
99
|
return warnings;
|
|
100
100
|
};
|
|
101
101
|
const hasPathField = (rule) => 'path' in rule && typeof rule.path === 'string';
|
|
102
|
+
const pathCouldMatch = (object, pathParts) => {
|
|
103
|
+
let current = object;
|
|
104
|
+
for (const part of pathParts) {
|
|
105
|
+
if (!current || typeof current !== 'object')
|
|
106
|
+
return false;
|
|
107
|
+
if ((0, jsonPath_1.isFilterSegment)(part)) {
|
|
108
|
+
if (!Array.isArray(current))
|
|
109
|
+
return false;
|
|
110
|
+
const filter = (0, jsonPath_1.parseFilterSegment)(part);
|
|
111
|
+
if (!filter)
|
|
112
|
+
return false;
|
|
113
|
+
const matched = current.find((item) => {
|
|
114
|
+
if (!item || typeof item !== 'object')
|
|
115
|
+
return false;
|
|
116
|
+
const itemValue = item[filter.property];
|
|
117
|
+
return String(itemValue) === filter.value;
|
|
118
|
+
});
|
|
119
|
+
if (!matched)
|
|
120
|
+
return false;
|
|
121
|
+
current = matched;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (Array.isArray(current)) {
|
|
125
|
+
if (part === '*') {
|
|
126
|
+
if (pathParts.indexOf(part) === pathParts.length - 1)
|
|
127
|
+
return true;
|
|
128
|
+
return current.some((item) => item !== undefined);
|
|
129
|
+
}
|
|
130
|
+
const index = Number(part);
|
|
131
|
+
if (Number.isNaN(index))
|
|
132
|
+
return false;
|
|
133
|
+
current = current[index];
|
|
134
|
+
}
|
|
135
|
+
else
|
|
136
|
+
current = current[part];
|
|
137
|
+
}
|
|
138
|
+
return current !== undefined;
|
|
139
|
+
};
|
|
102
140
|
const validateJsonPathInFiles = (jsonPath, filePaths, sourceFiles, destinationFiles) => {
|
|
103
141
|
const pathParts = (0, jsonPath_1.parseJsonPath)(jsonPath);
|
|
104
142
|
for (const filePath of filePaths) {
|
|
@@ -107,8 +145,7 @@ const validateJsonPathInFiles = (jsonPath, filePaths, sourceFiles, destinationFi
|
|
|
107
145
|
continue;
|
|
108
146
|
try {
|
|
109
147
|
const parsed = yaml_1.default.parse(content);
|
|
110
|
-
|
|
111
|
-
if (value !== undefined)
|
|
148
|
+
if (pathCouldMatch(parsed, pathParts))
|
|
112
149
|
return true;
|
|
113
150
|
}
|
|
114
151
|
catch {
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const HTML_STYLES = "\n /* Custom styles */\n body {\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif;\n margin: 0;\n padding: 20px;\n background: #f6f8fa;\n }\n\n header {\n background: white;\n padding: 20px;\n border-radius: 6px;\n margin-bottom: 20px;\n box-shadow: 0 1px 3px rgba(0,0,0,0.12);\n }\n\n h1 {\n margin: 0 0 10px 0;\n color: #24292e;\n }\n\n .metadata {\n display: flex;\n gap: 20px;\n margin: 10px 0;\n color: #586069;\n font-size: 14px;\n }\n\n .dry-run-badge {\n display: inline-block;\n padding: 4px 8px;\n background: #cfe2ff;\n color: #084298;\n border-radius: 4px;\n font-weight: bold;\n font-size: 12px;\n }\n\n .summary {\n display: flex;\n gap: 12px;\n margin: 15px 0;\n }\n\n .stat {\n padding: 8px 16px;\n border-radius: 6px;\n font-weight: 600;\n font-size: 14px;\n }\n\n .stat.added { background: #d4edda; color: #155724; }\n .stat.changed { background: #fff3cd; color: #856404; }\n .stat.deleted { background: #f8d7da; color: #721c24; }\n .stat.formatted { background: #d1ecf1; color: #0c5460; }\n .stat.unchanged { background: #e2e3e5; color: #383d41; }\n\n .tabs {\n display: flex;\n background: white;\n border-radius: 6px 6px 0 0;\n border-bottom: 1px solid #d0d7de;\n box-shadow: 0 1px 3px rgba(0,0,0,0.12);\n }\n\n .tab {\n padding: 12px 24px;\n border: none;\n background: transparent;\n cursor: pointer;\n font-size: 14px;\n color: #586069;\n transition: all 0.2s;\n }\n\n .tab:hover {\n color: #24292e;\n }\n\n .tab.active {\n border-bottom: 2px solid #0969da;\n color: #0969da;\n font-weight: 600;\n }\n\n main {\n background: white;\n padding: 20px;\n border-radius: 0 0 6px 6px;\n box-shadow: 0 1px 3px rgba(0,0,0,0.12);\n }\n\n .tab-content {\n display: none;\n }\n\n .tab-content.active {\n display: block;\n }\n\n .file-section {\n margin: 12px 0;\n border: 1px solid #d0d7de;\n border-radius: 6px;\n }\n\n .file-section summary {\n padding: 12px 16px;\n background: #f6f8fa;\n cursor: pointer;\n font-weight: 600;\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace;\n font-size: 13px;\n color: #24292e;\n }\n\n .file-section summary:hover {\n background: #eaeef2;\n }\n\n .diff-container {\n padding: 0;\n }\n\n /* Hide diff2html file header with rename badge */\n .d2h-file-header {\n display: none;\n }\n\n .file-list {\n margin: 20px 0;\n }\n\n .file-list ul {\n list-style: none;\n padding: 0;\n margin: 10px 0;\n }\n\n .file-list li {\n padding: 8px 16px;\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace;\n font-size: 13px;\n color: #586069;\n border-bottom: 1px solid #f6f8fa;\n }\n\n .file-list li:hover {\n background: #f6f8fa;\n }\n\n .array-details {\n margin: 20px 0;\n
|
|
1
|
+
export declare const HTML_STYLES = "\n /* Custom styles */\n body {\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif;\n margin: 0;\n padding: 20px;\n background: #f6f8fa;\n }\n\n header {\n background: white;\n padding: 20px;\n border-radius: 6px;\n margin-bottom: 20px;\n box-shadow: 0 1px 3px rgba(0,0,0,0.12);\n }\n\n h1 {\n margin: 0 0 10px 0;\n color: #24292e;\n }\n\n .metadata {\n display: flex;\n gap: 20px;\n margin: 10px 0;\n color: #586069;\n font-size: 14px;\n }\n\n .dry-run-badge {\n display: inline-block;\n padding: 4px 8px;\n background: #cfe2ff;\n color: #084298;\n border-radius: 4px;\n font-weight: bold;\n font-size: 12px;\n }\n\n .summary {\n display: flex;\n gap: 12px;\n margin: 15px 0;\n }\n\n .stat {\n padding: 8px 16px;\n border-radius: 6px;\n font-weight: 600;\n font-size: 14px;\n }\n\n .stat.added { background: #d4edda; color: #155724; }\n .stat.changed { background: #fff3cd; color: #856404; }\n .stat.deleted { background: #f8d7da; color: #721c24; }\n .stat.formatted { background: #d1ecf1; color: #0c5460; }\n .stat.unchanged { background: #e2e3e5; color: #383d41; }\n\n .tabs {\n display: flex;\n background: white;\n border-radius: 6px 6px 0 0;\n border-bottom: 1px solid #d0d7de;\n box-shadow: 0 1px 3px rgba(0,0,0,0.12);\n }\n\n .tab {\n padding: 12px 24px;\n border: none;\n background: transparent;\n cursor: pointer;\n font-size: 14px;\n color: #586069;\n transition: all 0.2s;\n }\n\n .tab:hover {\n color: #24292e;\n }\n\n .tab.active {\n border-bottom: 2px solid #0969da;\n color: #0969da;\n font-weight: 600;\n }\n\n main {\n background: white;\n padding: 20px;\n border-radius: 0 0 6px 6px;\n box-shadow: 0 1px 3px rgba(0,0,0,0.12);\n }\n\n .tab-content {\n display: none;\n }\n\n .tab-content.active {\n display: block;\n }\n\n .file-section {\n margin: 12px 0;\n border: 1px solid #d0d7de;\n border-radius: 6px;\n }\n\n .file-section summary {\n padding: 12px 16px;\n background: #f6f8fa;\n cursor: pointer;\n font-weight: 600;\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace;\n font-size: 13px;\n color: #24292e;\n }\n\n .file-section summary:hover {\n background: #eaeef2;\n }\n\n .filename-transform {\n color: #0969da;\n }\n\n .diff-container {\n padding: 0;\n }\n\n /* Hide diff2html file header with rename badge */\n .d2h-file-header {\n display: none;\n }\n\n .file-list {\n margin: 20px 0;\n }\n\n .file-list ul {\n list-style: none;\n padding: 0;\n margin: 10px 0;\n }\n\n .file-list li {\n padding: 8px 16px;\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace;\n font-size: 13px;\n color: #586069;\n border-bottom: 1px solid #f6f8fa;\n }\n\n .file-list li:hover {\n background: #f6f8fa;\n }\n\n .array-details {\n margin: 20px 0;\n background: #f6f8fa;\n border-radius: 6px;\n border-left: 3px solid #0969da;\n }\n\n .array-details summary {\n padding: 12px 15px;\n cursor: pointer;\n color: #0969da;\n font-size: 13px;\n font-weight: 600;\n }\n\n .array-details summary:hover {\n background: #eaeef2;\n }\n\n .array-details > .array-section {\n margin: 0 15px 15px 15px;\n }\n\n .array-section {\n margin: 15px 0;\n padding: 10px;\n background: white;\n border-radius: 4px;\n border: 1px solid #d0d7de;\n }\n\n .array-section h4 {\n margin: 0 0 10px 0;\n color: #24292e;\n font-size: 14px;\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace;\n }\n\n .array-unchanged {\n padding: 8px;\n color: #586069;\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace;\n font-size: 12px;\n }\n\n .added-items, .removed-items {\n margin: 10px 0;\n }\n\n .added-items h4 {\n color: #1a7f37;\n margin: 0 0 8px 0;\n font-size: 13px;\n }\n\n .removed-items h4 {\n color: #cf222e;\n margin: 0 0 8px 0;\n font-size: 13px;\n }\n\n .added-items ul, .removed-items ul {\n list-style: none;\n padding: 0;\n margin: 0;\n }\n\n .added-items li {\n padding: 6px 10px;\n background: #dafbe1;\n border-left: 3px solid #1a7f37;\n margin: 4px 0;\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace;\n font-size: 12px;\n }\n\n .removed-items li {\n padding: 6px 10px;\n background: #ffebe9;\n border-left: 3px solid #cf222e;\n margin: 4px 0;\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace;\n font-size: 12px;\n }\n\n .added-items pre, .removed-items pre {\n margin: 0;\n white-space: pre-wrap;\n word-wrap: break-word;\n }\n\n .unchanged-count {\n padding: 8px 10px;\n color: #586069;\n font-size: 12px;\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace;\n background: #f6f8fa;\n border-radius: 4px;\n margin: 10px 0;\n }\n";
|
|
2
2
|
export declare const TAB_SCRIPT = "\n // Tab switching\n document.querySelectorAll('.tab').forEach(tab => {\n tab.addEventListener('click', () => {\n const tabName = tab.getAttribute('data-tab');\n\n // Update tabs\n document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));\n tab.classList.add('active');\n\n // Update content\n document.querySelectorAll('.tab-content').forEach(content => {\n content.classList.remove('active');\n });\n document.getElementById(tabName).classList.add('active');\n });\n });\n";
|
|
@@ -123,6 +123,10 @@ exports.HTML_STYLES = `
|
|
|
123
123
|
background: #eaeef2;
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
+
.filename-transform {
|
|
127
|
+
color: #0969da;
|
|
128
|
+
}
|
|
129
|
+
|
|
126
130
|
.diff-container {
|
|
127
131
|
padding: 0;
|
|
128
132
|
}
|
|
@@ -156,16 +160,25 @@ exports.HTML_STYLES = `
|
|
|
156
160
|
|
|
157
161
|
.array-details {
|
|
158
162
|
margin: 20px 0;
|
|
159
|
-
padding: 15px;
|
|
160
163
|
background: #f6f8fa;
|
|
161
164
|
border-radius: 6px;
|
|
162
165
|
border-left: 3px solid #0969da;
|
|
163
166
|
}
|
|
164
167
|
|
|
165
|
-
.array-details
|
|
166
|
-
|
|
168
|
+
.array-details summary {
|
|
169
|
+
padding: 12px 15px;
|
|
170
|
+
cursor: pointer;
|
|
167
171
|
color: #0969da;
|
|
168
|
-
font-size:
|
|
172
|
+
font-size: 13px;
|
|
173
|
+
font-weight: 600;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.array-details summary:hover {
|
|
177
|
+
background: #eaeef2;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.array-details > .array-section {
|
|
181
|
+
margin: 0 15px 15px 15px;
|
|
169
182
|
}
|
|
170
183
|
|
|
171
184
|
.array-section {
|
|
@@ -26,4 +26,8 @@ export declare const isFilenameTransformerError: (error: unknown) => error is {
|
|
|
26
26
|
};
|
|
27
27
|
export declare const getFilenameTransformsForFile: (filePath: string, transforms?: TransformConfig) => TransformRule[];
|
|
28
28
|
export declare const transformFilename: (filePath: string, transforms?: TransformConfig) => string;
|
|
29
|
-
export
|
|
29
|
+
export interface TransformMapResult {
|
|
30
|
+
fileMap: Map<string, string>;
|
|
31
|
+
originalPaths: Map<string, string>;
|
|
32
|
+
}
|
|
33
|
+
export declare const transformFilenameMap: (fileMap: Map<string, string>, transforms?: TransformConfig) => TransformMapResult;
|
|
@@ -92,13 +92,16 @@ const transformFilename = (filePath, transforms) => {
|
|
|
92
92
|
};
|
|
93
93
|
exports.transformFilename = transformFilename;
|
|
94
94
|
const transformFilenameMap = (fileMap, transforms) => {
|
|
95
|
+
const originalPaths = new Map();
|
|
95
96
|
if (!transforms)
|
|
96
|
-
return fileMap;
|
|
97
|
+
return { fileMap, originalPaths };
|
|
97
98
|
const transformed = new Map();
|
|
98
99
|
for (const [originalPath, content] of fileMap.entries()) {
|
|
99
100
|
const transformedPath = (0, exports.transformFilename)(originalPath, transforms);
|
|
100
101
|
transformed.set(transformedPath, content);
|
|
102
|
+
if (transformedPath !== originalPath)
|
|
103
|
+
originalPaths.set(transformedPath, originalPath);
|
|
101
104
|
}
|
|
102
|
-
return transformed;
|
|
105
|
+
return { fileMap: transformed, originalPaths };
|
|
103
106
|
};
|
|
104
107
|
exports.transformFilenameMap = transformFilenameMap;
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export type { ErrorOptions } from './errors';
|
|
|
2
2
|
export { createErrorClass, createErrorTypeGuard } from './errors';
|
|
3
3
|
export { deepEqual } from './deepEqual';
|
|
4
4
|
export { normalizeForComparison, serializeForDiff } from './serialization';
|
|
5
|
-
export { clearJsonPathCache, getValueAtPath, parseJsonPath } from './jsonPath';
|
|
5
|
+
export { clearJsonPathCache, getValueAtPath, isFilterSegment, parseFilterSegment, parseJsonPath } from './jsonPath';
|
|
6
6
|
export { isYamlFile } from './fileType';
|
|
7
7
|
export { globalMatcher, PatternMatcher } from './patternMatcher';
|
|
8
8
|
export { generateUnifiedDiff } from './diffGenerator';
|
package/dist/utils/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
exports.UUID_PATTERN = exports.SEMVER_PATTERN = exports.SEMANTIC_PATTERNS = void 0;
|
|
3
|
+
exports.NUMERIC_MIN_MULTIPLIER = exports.NUMERIC_MIN_FLOOR = exports.MAX_EXAMPLES_PER_SUGGESTION = exports.ISO_TIMESTAMP_PATTERN = exports.FILTER_THRESHOLDS = exports.CONSTRAINT_FIELD_NAMES = exports.CONFIDENCE_DEFAULTS = exports.ARRAY_KEY_FIELDS = exports.ANTONYM_PAIRS = exports.isYamlSeq = exports.isYamlMap = exports.isYamlCollection = exports.isScalar = exports.extractScalarValue = exports.extractKeyValue = exports.validateVersionString = exports.applyRegexRulesSequentially = exports.validateTargetedRegex = exports.validatePathlessRegex = exports.getAllValuesRecursive = exports.RegexPatternFileLoaderError = exports.loadRegexPatternsFromKeys = exports.loadRegexPatternArray = exports.isRegexPatternFileLoaderError = exports.TransformFileLoaderError = exports.loadTransformFiles = exports.loadTransformFile = exports.isTransformFileLoaderError = exports.YamlFileLoaderError = exports.loadYamlFile = exports.isYamlFileLoaderError = exports.escapeRegex = exports.VersionCheckerError = exports.isVersionCheckerError = exports.checkForUpdates = exports.detectArrayChanges = exports.generateUnifiedDiff = exports.PatternMatcher = exports.globalMatcher = exports.isYamlFile = exports.parseJsonPath = exports.parseFilterSegment = exports.isFilterSegment = exports.getValueAtPath = exports.clearJsonPathCache = exports.serializeForDiff = exports.normalizeForComparison = exports.deepEqual = exports.createErrorTypeGuard = exports.createErrorClass = void 0;
|
|
4
|
+
exports.UUID_PATTERN = exports.SEMVER_PATTERN = exports.SEMANTIC_PATTERNS = exports.SEMANTIC_KEYWORDS = exports.PROBLEMATIC_REGEX_CHARS = void 0;
|
|
5
5
|
var errors_1 = require("./errors");
|
|
6
6
|
Object.defineProperty(exports, "createErrorClass", { enumerable: true, get: function () { return errors_1.createErrorClass; } });
|
|
7
7
|
Object.defineProperty(exports, "createErrorTypeGuard", { enumerable: true, get: function () { return errors_1.createErrorTypeGuard; } });
|
|
@@ -13,6 +13,8 @@ Object.defineProperty(exports, "serializeForDiff", { enumerable: true, get: func
|
|
|
13
13
|
var jsonPath_1 = require("./jsonPath");
|
|
14
14
|
Object.defineProperty(exports, "clearJsonPathCache", { enumerable: true, get: function () { return jsonPath_1.clearJsonPathCache; } });
|
|
15
15
|
Object.defineProperty(exports, "getValueAtPath", { enumerable: true, get: function () { return jsonPath_1.getValueAtPath; } });
|
|
16
|
+
Object.defineProperty(exports, "isFilterSegment", { enumerable: true, get: function () { return jsonPath_1.isFilterSegment; } });
|
|
17
|
+
Object.defineProperty(exports, "parseFilterSegment", { enumerable: true, get: function () { return jsonPath_1.parseFilterSegment; } });
|
|
16
18
|
Object.defineProperty(exports, "parseJsonPath", { enumerable: true, get: function () { return jsonPath_1.parseJsonPath; } });
|
|
17
19
|
var fileType_1 = require("./fileType");
|
|
18
20
|
Object.defineProperty(exports, "isYamlFile", { enumerable: true, get: function () { return fileType_1.isYamlFile; } });
|
package/dist/utils/jsonPath.d.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
export declare const isFilterSegment: (segment: string) => boolean;
|
|
2
|
+
export declare const parseFilterSegment: (segment: string) => {
|
|
3
|
+
property: string;
|
|
4
|
+
value: string;
|
|
5
|
+
} | undefined;
|
|
1
6
|
export declare const parseJsonPath: (path: string) => string[];
|
|
2
7
|
export declare const clearJsonPathCache: () => void;
|
|
3
8
|
export declare const getValueAtPath: (object: unknown, path: string[]) => unknown;
|
package/dist/utils/jsonPath.js
CHANGED
|
@@ -1,15 +1,70 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getValueAtPath = exports.clearJsonPathCache = exports.parseJsonPath = void 0;
|
|
3
|
+
exports.getValueAtPath = exports.clearJsonPathCache = exports.parseJsonPath = exports.parseFilterSegment = exports.isFilterSegment = void 0;
|
|
4
4
|
const pathCache = new Map();
|
|
5
|
+
const FILTER_PREFIX = 'filter:';
|
|
6
|
+
const FILTER_REGEX = /^\[([A-Z_a-z]\w*)=("([^"]*)"|([^\]]*))]/;
|
|
7
|
+
const isFilterSegment = (segment) => segment.startsWith(FILTER_PREFIX);
|
|
8
|
+
exports.isFilterSegment = isFilterSegment;
|
|
9
|
+
const parseFilterSegment = (segment) => {
|
|
10
|
+
if (!(0, exports.isFilterSegment)(segment))
|
|
11
|
+
return undefined;
|
|
12
|
+
const content = segment.slice(FILTER_PREFIX.length);
|
|
13
|
+
const equalIndex = content.indexOf('=');
|
|
14
|
+
if (equalIndex === -1)
|
|
15
|
+
return undefined;
|
|
16
|
+
return {
|
|
17
|
+
property: content.slice(0, equalIndex),
|
|
18
|
+
value: content.slice(equalIndex + 1)
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
exports.parseFilterSegment = parseFilterSegment;
|
|
5
22
|
const parseJsonPath = (path) => {
|
|
6
23
|
const cached = pathCache.get(path);
|
|
7
24
|
if (cached !== undefined)
|
|
8
25
|
return cached;
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
26
|
+
const result = [];
|
|
27
|
+
let index = 0;
|
|
28
|
+
let currentSegment = '';
|
|
29
|
+
while (index < path.length) {
|
|
30
|
+
const char = path[index];
|
|
31
|
+
if (char === '.') {
|
|
32
|
+
if (currentSegment) {
|
|
33
|
+
result.push(currentSegment);
|
|
34
|
+
currentSegment = '';
|
|
35
|
+
}
|
|
36
|
+
index++;
|
|
37
|
+
}
|
|
38
|
+
else if (char === '[') {
|
|
39
|
+
if (currentSegment) {
|
|
40
|
+
result.push(currentSegment);
|
|
41
|
+
currentSegment = '';
|
|
42
|
+
}
|
|
43
|
+
const remaining = path.slice(index);
|
|
44
|
+
const filterMatch = FILTER_REGEX.exec(remaining);
|
|
45
|
+
if (filterMatch) {
|
|
46
|
+
const property = filterMatch[1];
|
|
47
|
+
const value = filterMatch[3] === undefined ? filterMatch[4] : filterMatch[3];
|
|
48
|
+
result.push(`${FILTER_PREFIX}${property}=${value}`);
|
|
49
|
+
index += filterMatch[0].length;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
const indexMatch = /^\[(\*|\d+)]/.exec(remaining);
|
|
53
|
+
if (indexMatch?.[1]) {
|
|
54
|
+
result.push(indexMatch[1]);
|
|
55
|
+
index += indexMatch[0].length;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
index++;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
currentSegment += char;
|
|
62
|
+
index++;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (currentSegment)
|
|
66
|
+
result.push(currentSegment);
|
|
67
|
+
const parsed = result.filter((part) => part.length > 0);
|
|
13
68
|
pathCache.set(path, parsed);
|
|
14
69
|
return parsed;
|
|
15
70
|
};
|
|
@@ -23,6 +78,21 @@ const getValueAtPath = (object, path) => {
|
|
|
23
78
|
for (const part of path) {
|
|
24
79
|
if (!current || typeof current !== 'object')
|
|
25
80
|
return undefined;
|
|
81
|
+
if ((0, exports.isFilterSegment)(part)) {
|
|
82
|
+
if (!Array.isArray(current))
|
|
83
|
+
return undefined;
|
|
84
|
+
const filter = (0, exports.parseFilterSegment)(part);
|
|
85
|
+
if (!filter)
|
|
86
|
+
return undefined;
|
|
87
|
+
const matched = current.find((item) => {
|
|
88
|
+
if (!item || typeof item !== 'object')
|
|
89
|
+
return false;
|
|
90
|
+
const itemValue = item[filter.property];
|
|
91
|
+
return String(itemValue) === filter.value;
|
|
92
|
+
});
|
|
93
|
+
current = matched;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
26
96
|
if (Array.isArray(current)) {
|
|
27
97
|
if (part === '*')
|
|
28
98
|
return undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "helm-env-delta",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.2",
|
|
4
4
|
"description": "HelmEnvDelta – environment-aware YAML delta and sync for GitOps",
|
|
5
5
|
"author": "BCsabaEngine",
|
|
6
6
|
"license": "ISC",
|
|
@@ -62,24 +62,24 @@
|
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
64
|
"@types/hogan.js": "^3.0.5",
|
|
65
|
-
"@types/node": "^25.0.
|
|
65
|
+
"@types/node": "^25.0.10",
|
|
66
66
|
"@types/picomatch": "^4.0.2",
|
|
67
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
68
|
-
"@typescript-eslint/parser": "^8.
|
|
69
|
-
"@vitest/coverage-v8": "^4.0.
|
|
67
|
+
"@typescript-eslint/eslint-plugin": "^8.53.1",
|
|
68
|
+
"@typescript-eslint/parser": "^8.53.1",
|
|
69
|
+
"@vitest/coverage-v8": "^4.0.17",
|
|
70
70
|
"eslint": "^9.39.2",
|
|
71
71
|
"eslint-config-prettier": "^10.1.8",
|
|
72
72
|
"eslint-plugin-simple-import-sort": "^12.1.1",
|
|
73
73
|
"eslint-plugin-unicorn": "^62.0.0",
|
|
74
|
-
"prettier": "^3.
|
|
74
|
+
"prettier": "^3.8.1",
|
|
75
75
|
"tsx": "^4.21.0",
|
|
76
76
|
"typescript": "^5.9.3",
|
|
77
|
-
"vitest": "^4.0.
|
|
77
|
+
"vitest": "^4.0.17"
|
|
78
78
|
},
|
|
79
79
|
"dependencies": {
|
|
80
80
|
"chalk": "^5.6.2",
|
|
81
81
|
"commander": "^14.0.2",
|
|
82
|
-
"diff": "^8.0.
|
|
82
|
+
"diff": "^8.0.3",
|
|
83
83
|
"diff2html": "3.4.52",
|
|
84
84
|
"open": "^11.0.0",
|
|
85
85
|
"picomatch": "^4.0.3",
|