helm-env-delta 1.10.0 → 1.10.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 +36 -1
- package/dist/commandLine.js +7 -1
- package/dist/index.js +2 -0
- package/dist/utils/fileFilter.d.ts +32 -0
- package/dist/utils/fileFilter.js +89 -12
- package/dist/utils/index.d.ts +2 -1
- package/dist/utils/index.js +5 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -693,6 +693,35 @@ outputFormat:
|
|
|
693
693
|
|
|
694
694
|
---
|
|
695
695
|
|
|
696
|
+
### 🔍 CLI Filter Operators
|
|
697
|
+
|
|
698
|
+
The `-f/--filter` flag supports logical operators for complex filtering:
|
|
699
|
+
|
|
700
|
+
| Operator | Name | Example | Matches |
|
|
701
|
+
| -------- | ------ | -------------------- | ----------------------------------------------- |
|
|
702
|
+
| (none) | Simple | `-f prod` | Files where filename or content contains "prod" |
|
|
703
|
+
| `\|` | OR | `-f "prod\|staging"` | Files matching "prod" OR "staging" |
|
|
704
|
+
| `&` | AND | `-f "values&prod"` | Files matching "values" AND "prod" |
|
|
705
|
+
|
|
706
|
+
```bash
|
|
707
|
+
# OR: match ANY term (filename or content)
|
|
708
|
+
hed -c config.yaml -f "prod|staging" --list-files
|
|
709
|
+
|
|
710
|
+
# AND: match ALL terms (can be split between filename and content)
|
|
711
|
+
hed -c config.yaml -f "values&prod" --list-files
|
|
712
|
+
|
|
713
|
+
# Escape literal | or & with backslash
|
|
714
|
+
hed -c config.yaml -f "foo\|bar" --list-files
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
**Constraints:**
|
|
718
|
+
|
|
719
|
+
- Cannot mix `&` and `|` in a single filter (throws error)
|
|
720
|
+
- Case-insensitive matching
|
|
721
|
+
- Empty terms are ignored (`a||b` becomes `a|b`)
|
|
722
|
+
|
|
723
|
+
---
|
|
724
|
+
|
|
696
725
|
### 🔗 Config Inheritance
|
|
697
726
|
|
|
698
727
|
Reuse base configurations across environment pairs.
|
|
@@ -768,7 +797,7 @@ hed --config <file> [options] # Short alias
|
|
|
768
797
|
| `--show-config` | | Display resolved config after inheritance |
|
|
769
798
|
| `--format-only` | | Format destination files only (source not required) |
|
|
770
799
|
| `--skip-format` | `-S` | Skip YAML formatting during sync |
|
|
771
|
-
| `--filter <string>` | `-f` | Filter files by filename
|
|
800
|
+
| `--filter <string>` | `-f` | Filter files by filename/content (supports `\|` OR, `&` AND) |
|
|
772
801
|
| `--mode <type>` | `-m` | Filter by change type: new, modified, deleted, all (default: all) |
|
|
773
802
|
| `--no-color` | | Disable colored output (CI/accessibility) |
|
|
774
803
|
| `--verbose` | | Show detailed debug info |
|
|
@@ -810,6 +839,12 @@ hed -c config.yaml --force
|
|
|
810
839
|
# Filter to only process files matching 'prod'
|
|
811
840
|
hed -c config.yaml -f prod -d
|
|
812
841
|
|
|
842
|
+
# Filter with OR: match files containing 'prod' OR 'staging'
|
|
843
|
+
hed -c config.yaml -f "prod|staging" -l
|
|
844
|
+
|
|
845
|
+
# Filter with AND: match files containing BOTH 'values' AND 'prod'
|
|
846
|
+
hed -c config.yaml -f "values&prod" -d
|
|
847
|
+
|
|
813
848
|
# Sync only new files
|
|
814
849
|
hed -c config.yaml -m new
|
|
815
850
|
|
package/dist/commandLine.js
CHANGED
|
@@ -26,7 +26,7 @@ const parseCommandLine = (argv) => {
|
|
|
26
26
|
.option('--suggest', 'Analyze differences and suggest transforms and stop rules', false)
|
|
27
27
|
.option('--suggest-threshold <number>', 'Minimum confidence for suggestions (0-1, default: 0.3)', '0.3')
|
|
28
28
|
.option('--no-color', 'Disable colored output')
|
|
29
|
-
.option('-f, --filter <string>', 'Filter files by filename or content (
|
|
29
|
+
.option('-f, --filter <string>', 'Filter files by filename or content (supports | for OR, & for AND)')
|
|
30
30
|
.option('-m, --mode <type>', 'Filter by change type: new, modified, deleted, all', 'all')
|
|
31
31
|
.option('--verbose', 'Show detailed debug information', false)
|
|
32
32
|
.option('--quiet', 'Suppress all output except critical errors', false)
|
|
@@ -50,6 +50,12 @@ Examples:
|
|
|
50
50
|
# Filter to only process files matching 'prod'
|
|
51
51
|
$ helm-env-delta --config config.yaml -f prod --diff
|
|
52
52
|
|
|
53
|
+
# Filter with OR: files matching 'prod' OR 'staging'
|
|
54
|
+
$ helm-env-delta --config config.yaml -f "prod|staging" --diff
|
|
55
|
+
|
|
56
|
+
# Filter with AND: files matching 'values' AND 'prod'
|
|
57
|
+
$ helm-env-delta --config config.yaml -f "values&prod" --diff
|
|
58
|
+
|
|
53
59
|
# Sync only new files
|
|
54
60
|
$ helm-env-delta --config config.yaml --mode new
|
|
55
61
|
|
package/dist/index.js
CHANGED
|
@@ -355,6 +355,8 @@ const main = async () => {
|
|
|
355
355
|
console.error(error.message);
|
|
356
356
|
else if ((0, suggestionEngine_1.isSuggestionEngineError)(error))
|
|
357
357
|
console.error(error.message);
|
|
358
|
+
else if ((0, fileFilter_1.isFilterParseError)(error))
|
|
359
|
+
console.error(error.message);
|
|
358
360
|
else if (error instanceof Error)
|
|
359
361
|
console.error('Unexpected error:', error.message);
|
|
360
362
|
else
|
|
@@ -1,6 +1,38 @@
|
|
|
1
1
|
import type { FileDiffResult } from '../fileDiff';
|
|
2
2
|
import type { FileMap } from '../fileLoader';
|
|
3
3
|
export type ChangeMode = 'new' | 'modified' | 'deleted' | 'all';
|
|
4
|
+
export type FilterLogicalOperator = 'AND' | 'OR' | 'NONE';
|
|
5
|
+
export interface ParsedFilter {
|
|
6
|
+
operator: FilterLogicalOperator;
|
|
7
|
+
terms: string[];
|
|
8
|
+
}
|
|
9
|
+
export declare const FilterParseError: {
|
|
10
|
+
new (message: string, options?: import("./errors").ErrorOptions): {
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
readonly code?: string;
|
|
13
|
+
readonly path?: string;
|
|
14
|
+
readonly cause?: Error;
|
|
15
|
+
readonly hints?: string[];
|
|
16
|
+
name: string;
|
|
17
|
+
message: string;
|
|
18
|
+
stack?: string;
|
|
19
|
+
};
|
|
20
|
+
captureStackTrace(targetObject: object, constructorOpt?: Function): void;
|
|
21
|
+
prepareStackTrace(err: Error, stackTraces: NodeJS.CallSite[]): any;
|
|
22
|
+
stackTraceLimit: number;
|
|
23
|
+
};
|
|
24
|
+
export declare const isFilterParseError: (error: unknown) => error is {
|
|
25
|
+
[key: string]: unknown;
|
|
26
|
+
readonly code?: string;
|
|
27
|
+
readonly path?: string;
|
|
28
|
+
readonly cause?: Error;
|
|
29
|
+
readonly hints?: string[];
|
|
30
|
+
name: string;
|
|
31
|
+
message: string;
|
|
32
|
+
stack?: string;
|
|
33
|
+
};
|
|
34
|
+
export declare const parseFilterExpression: (filter: string | undefined) => ParsedFilter;
|
|
35
|
+
export declare const fileMatchesFilter: (filePath: string, content: string, parsedFilter: ParsedFilter) => boolean;
|
|
4
36
|
export declare const filterDiffResultByMode: (diffResult: FileDiffResult, mode: ChangeMode) => FileDiffResult;
|
|
5
37
|
export declare const filterFileMap: (fileMap: FileMap, filter: string | undefined) => FileMap;
|
|
6
38
|
export declare const filterFileMaps: (sourceFiles: FileMap, destinationFiles: FileMap, filter: string | undefined) => {
|
package/dist/utils/fileFilter.js
CHANGED
|
@@ -1,6 +1,86 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.filterFileMaps = exports.filterFileMap = exports.filterDiffResultByMode = void 0;
|
|
3
|
+
exports.filterFileMaps = exports.filterFileMap = exports.filterDiffResultByMode = exports.fileMatchesFilter = exports.parseFilterExpression = exports.isFilterParseError = exports.FilterParseError = void 0;
|
|
4
|
+
const errors_1 = require("./errors");
|
|
5
|
+
const filterParseErrorCodes = {
|
|
6
|
+
MIXED_OPERATORS: 'Cannot combine AND (&) and OR (|) operators in a single filter expression'
|
|
7
|
+
};
|
|
8
|
+
exports.FilterParseError = (0, errors_1.createErrorClass)('FilterParseError', filterParseErrorCodes);
|
|
9
|
+
exports.isFilterParseError = (0, errors_1.createErrorTypeGuard)(exports.FilterParseError);
|
|
10
|
+
const parseFilterExpression = (filter) => {
|
|
11
|
+
if (!filter)
|
|
12
|
+
return { operator: 'NONE', terms: [] };
|
|
13
|
+
let hasUnescapedOr = false;
|
|
14
|
+
let hasUnescapedAnd = false;
|
|
15
|
+
for (let index = 0; index < filter.length; index++) {
|
|
16
|
+
const char = filter[index];
|
|
17
|
+
const isEscaped = index > 0 && filter[index - 1] === '\\';
|
|
18
|
+
if (char === '|' && !isEscaped)
|
|
19
|
+
hasUnescapedOr = true;
|
|
20
|
+
if (char === '&' && !isEscaped)
|
|
21
|
+
hasUnescapedAnd = true;
|
|
22
|
+
}
|
|
23
|
+
if (hasUnescapedOr && hasUnescapedAnd)
|
|
24
|
+
throw new exports.FilterParseError('Mixed operators detected', {
|
|
25
|
+
code: 'MIXED_OPERATORS',
|
|
26
|
+
hints: [
|
|
27
|
+
'Use only | (OR) or only & (AND) in a single filter',
|
|
28
|
+
String.raw `Escape literal characters with backslash: \| or \&`
|
|
29
|
+
]
|
|
30
|
+
});
|
|
31
|
+
const operator = hasUnescapedOr ? 'OR' : hasUnescapedAnd ? 'AND' : 'NONE';
|
|
32
|
+
let terms;
|
|
33
|
+
if (operator === 'NONE')
|
|
34
|
+
terms = [filter];
|
|
35
|
+
else {
|
|
36
|
+
const splitChar = operator === 'OR' ? '|' : '&';
|
|
37
|
+
terms = [];
|
|
38
|
+
let currentTerm = '';
|
|
39
|
+
for (let index = 0; index < filter.length; index++) {
|
|
40
|
+
const char = filter[index];
|
|
41
|
+
const isEscaped = index > 0 && filter[index - 1] === '\\';
|
|
42
|
+
if (char === splitChar && !isEscaped) {
|
|
43
|
+
terms.push(currentTerm);
|
|
44
|
+
currentTerm = '';
|
|
45
|
+
}
|
|
46
|
+
else if (char === '\\' && index + 1 < filter.length && (filter[index + 1] === '|' || filter[index + 1] === '&'))
|
|
47
|
+
continue;
|
|
48
|
+
else
|
|
49
|
+
currentTerm += char;
|
|
50
|
+
}
|
|
51
|
+
terms.push(currentTerm);
|
|
52
|
+
}
|
|
53
|
+
const processedTerms = terms
|
|
54
|
+
.map((term) => term
|
|
55
|
+
.replaceAll(/\\([&|])/g, '$1')
|
|
56
|
+
.trim()
|
|
57
|
+
.toLowerCase())
|
|
58
|
+
.filter((term) => term.length > 0);
|
|
59
|
+
const finalOperator = processedTerms.length <= 1 ? 'NONE' : operator;
|
|
60
|
+
return { operator: finalOperator, terms: processedTerms };
|
|
61
|
+
};
|
|
62
|
+
exports.parseFilterExpression = parseFilterExpression;
|
|
63
|
+
const fileMatchesFilter = (filePath, content, parsedFilter) => {
|
|
64
|
+
const { operator, terms } = parsedFilter;
|
|
65
|
+
if (terms.length === 0)
|
|
66
|
+
return true;
|
|
67
|
+
const lowerPath = filePath.toLowerCase();
|
|
68
|
+
const lowerContent = content.toLowerCase();
|
|
69
|
+
const termMatches = (term) => lowerPath.includes(term) || lowerContent.includes(term);
|
|
70
|
+
switch (operator) {
|
|
71
|
+
case 'OR': {
|
|
72
|
+
return terms.some((term) => termMatches(term));
|
|
73
|
+
}
|
|
74
|
+
case 'AND': {
|
|
75
|
+
return terms.every((term) => termMatches(term));
|
|
76
|
+
}
|
|
77
|
+
case 'NONE': {
|
|
78
|
+
const firstTerm = terms[0];
|
|
79
|
+
return terms.length === 0 || (firstTerm !== undefined && termMatches(firstTerm));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
exports.fileMatchesFilter = fileMatchesFilter;
|
|
4
84
|
const filterDiffResultByMode = (diffResult, mode) => {
|
|
5
85
|
if (mode === 'all')
|
|
6
86
|
return diffResult;
|
|
@@ -13,29 +93,26 @@ const filterDiffResultByMode = (diffResult, mode) => {
|
|
|
13
93
|
};
|
|
14
94
|
exports.filterDiffResultByMode = filterDiffResultByMode;
|
|
15
95
|
const filterFileMap = (fileMap, filter) => {
|
|
16
|
-
|
|
96
|
+
const parsedFilter = (0, exports.parseFilterExpression)(filter);
|
|
97
|
+
if (parsedFilter.terms.length === 0)
|
|
17
98
|
return fileMap;
|
|
18
|
-
const lowerFilter = filter.toLowerCase();
|
|
19
99
|
const filteredMap = new Map();
|
|
20
|
-
for (const [filePath, content] of fileMap)
|
|
21
|
-
|
|
22
|
-
const contentMatches = content.toLowerCase().includes(lowerFilter);
|
|
23
|
-
if (filenameMatches || contentMatches)
|
|
100
|
+
for (const [filePath, content] of fileMap)
|
|
101
|
+
if ((0, exports.fileMatchesFilter)(filePath, content, parsedFilter))
|
|
24
102
|
filteredMap.set(filePath, content);
|
|
25
|
-
}
|
|
26
103
|
return filteredMap;
|
|
27
104
|
};
|
|
28
105
|
exports.filterFileMap = filterFileMap;
|
|
29
106
|
const filterFileMaps = (sourceFiles, destinationFiles, filter) => {
|
|
30
|
-
|
|
107
|
+
const parsedFilter = (0, exports.parseFilterExpression)(filter);
|
|
108
|
+
if (parsedFilter.terms.length === 0)
|
|
31
109
|
return { sourceFiles, destinationFiles };
|
|
32
|
-
const lowerFilter = filter.toLowerCase();
|
|
33
110
|
const matchingPaths = new Set();
|
|
34
111
|
for (const [filePath, content] of sourceFiles)
|
|
35
|
-
if (
|
|
112
|
+
if ((0, exports.fileMatchesFilter)(filePath, content, parsedFilter))
|
|
36
113
|
matchingPaths.add(filePath);
|
|
37
114
|
for (const [filePath, content] of destinationFiles)
|
|
38
|
-
if (
|
|
115
|
+
if ((0, exports.fileMatchesFilter)(filePath, content, parsedFilter))
|
|
39
116
|
matchingPaths.add(filePath);
|
|
40
117
|
const filteredSource = new Map();
|
|
41
118
|
const filteredDestination = new Map();
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -20,4 +20,5 @@ export { applyFixedValues, getFixedValuesForFile, setValueAtPath } from './fixed
|
|
|
20
20
|
export type { ApplicableFilter } from './arrayMerger';
|
|
21
21
|
export { findMatchingTargetItem, getApplicableArrayFilters, itemMatchesAnyFilter, shouldPreserveItem } from './arrayMerger';
|
|
22
22
|
export { isCommentOnlyContent } from './commentOnlyDetector';
|
|
23
|
-
export {
|
|
23
|
+
export type { FilterLogicalOperator, ParsedFilter } from './fileFilter';
|
|
24
|
+
export { fileMatchesFilter, filterFileMap, filterFileMaps, FilterParseError, isFilterParseError, parseFilterExpression } from './fileFilter';
|
package/dist/utils/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
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.generateUnifiedDiff = exports.PatternMatcher = exports.globalMatcher = exports.isYamlFile = exports.parseJsonPath = exports.parseFilterSegment = exports.matchesFilter = exports.isFilterSegment = exports.getValueAtPath = exports.clearJsonPathCache = exports.serializeForDiff = exports.normalizeForComparison = exports.deepEqual = exports.createErrorTypeGuard = exports.createErrorClass = void 0;
|
|
4
|
-
exports.filterFileMaps = exports.filterFileMap = exports.isCommentOnlyContent = exports.shouldPreserveItem = exports.itemMatchesAnyFilter = exports.getApplicableArrayFilters = exports.findMatchingTargetItem = exports.setValueAtPath = exports.getFixedValuesForFile = exports.applyFixedValues = exports.UUID_PATTERN = exports.SEMVER_PATTERN = exports.SEMANTIC_PATTERNS = exports.SEMANTIC_KEYWORDS = exports.PROBLEMATIC_REGEX_CHARS = void 0;
|
|
4
|
+
exports.parseFilterExpression = exports.isFilterParseError = exports.FilterParseError = exports.filterFileMaps = exports.filterFileMap = exports.fileMatchesFilter = exports.isCommentOnlyContent = exports.shouldPreserveItem = exports.itemMatchesAnyFilter = exports.getApplicableArrayFilters = exports.findMatchingTargetItem = exports.setValueAtPath = exports.getFixedValuesForFile = exports.applyFixedValues = 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; } });
|
|
@@ -85,5 +85,9 @@ Object.defineProperty(exports, "shouldPreserveItem", { enumerable: true, get: fu
|
|
|
85
85
|
var commentOnlyDetector_1 = require("./commentOnlyDetector");
|
|
86
86
|
Object.defineProperty(exports, "isCommentOnlyContent", { enumerable: true, get: function () { return commentOnlyDetector_1.isCommentOnlyContent; } });
|
|
87
87
|
var fileFilter_1 = require("./fileFilter");
|
|
88
|
+
Object.defineProperty(exports, "fileMatchesFilter", { enumerable: true, get: function () { return fileFilter_1.fileMatchesFilter; } });
|
|
88
89
|
Object.defineProperty(exports, "filterFileMap", { enumerable: true, get: function () { return fileFilter_1.filterFileMap; } });
|
|
89
90
|
Object.defineProperty(exports, "filterFileMaps", { enumerable: true, get: function () { return fileFilter_1.filterFileMaps; } });
|
|
91
|
+
Object.defineProperty(exports, "FilterParseError", { enumerable: true, get: function () { return fileFilter_1.FilterParseError; } });
|
|
92
|
+
Object.defineProperty(exports, "isFilterParseError", { enumerable: true, get: function () { return fileFilter_1.isFilterParseError; } });
|
|
93
|
+
Object.defineProperty(exports, "parseFilterExpression", { enumerable: true, get: function () { return fileFilter_1.parseFilterExpression; } });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "helm-env-delta",
|
|
3
|
-
"version": "1.10.
|
|
3
|
+
"version": "1.10.2",
|
|
4
4
|
"description": "HelmEnvDelta – environment-aware YAML delta and sync for GitOps",
|
|
5
5
|
"author": "BCsabaEngine",
|
|
6
6
|
"license": "ISC",
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
},
|
|
69
69
|
"devDependencies": {
|
|
70
70
|
"@types/hogan.js": "^3.0.5",
|
|
71
|
-
"@types/node": "^25.
|
|
71
|
+
"@types/node": "^25.2.0",
|
|
72
72
|
"@types/picomatch": "^4.0.2",
|
|
73
73
|
"@typescript-eslint/eslint-plugin": "^8.54.0",
|
|
74
74
|
"@typescript-eslint/parser": "^8.54.0",
|
|
@@ -84,9 +84,9 @@
|
|
|
84
84
|
},
|
|
85
85
|
"dependencies": {
|
|
86
86
|
"chalk": "^5.6.2",
|
|
87
|
-
"commander": "^14.0.
|
|
87
|
+
"commander": "^14.0.3",
|
|
88
88
|
"diff": "^8.0.3",
|
|
89
|
-
"diff2html": "3.4.
|
|
89
|
+
"diff2html": "3.4.56",
|
|
90
90
|
"open": "^11.0.0",
|
|
91
91
|
"picomatch": "^4.0.3",
|
|
92
92
|
"tinyglobby": "^0.2.15",
|