helm-env-delta 1.14.1 β 1.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -23
- package/dist/commandLine.d.ts +2 -0
- package/dist/commandLine.js +19 -1
- package/dist/index.js +41 -5
- package/dist/pipeline/fileDiff.js +6 -5
- package/dist/pipeline/patternUsageValidator.d.ts +1 -0
- package/dist/pipeline/patternUsageValidator.js +6 -3
- package/dist/reporters/consoleDiffReporter.js +10 -1
- package/dist/reporters/htmlReporter.js +12 -8
- package/dist/reporters/htmlStyles.d.ts +1 -1
- package/dist/reporters/htmlStyles.js +66 -2
- package/dist/reporters/htmlTemplate.js +4 -0
- package/dist/utils/gitFilter.d.ts +17 -0
- package/dist/utils/gitFilter.js +85 -0
- package/dist/utils/regexValidator.js +36 -6
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -56,9 +56,9 @@ HelmEnvDelta (`hed`) automates environment synchronization for GitOps workflows
|
|
|
56
56
|
|
|
57
57
|
π¦ **Config Inheritance** - Reuse base configurations with environment-specific overrides.
|
|
58
58
|
|
|
59
|
-
π **Multiple Reports** - Console, HTML (visual, self-contained), and JSON (CI/CD) output formats. HTML reports include collapsible diff stats dashboard, stop rule violations table (dry-run only), synchronized side-by-side scrolling, copy diff buttons, file search,
|
|
59
|
+
π **Multiple Reports** - Console, HTML (visual, self-contained), and JSON (CI/CD) output formats. HTML reports include collapsible diff stats dashboard, stop rule violations table (dry-run only), synchronized side-by-side scrolling, copy diff buttons, file search, collapse/expand controls, jump-to-sidebar navigation, and auto-collapse for large file sets. Empty categories are automatically hidden.
|
|
60
60
|
|
|
61
|
-
π **Discovery Tools** - Preview files (`-l`), inspect config (`--show-config`), filter by filename/content (`-f`), filter by change type (`-m`), validate with comprehensive warnings including unused pattern detection.
|
|
61
|
+
π **Discovery Tools** - Preview files (`-l`), inspect config (`--show-config`), filter by filename/content (`-f`), filter by change type (`-m`), filter to your own git changes (`--my`), validate with comprehensive warnings including unused pattern detection.
|
|
62
62
|
|
|
63
63
|
π‘ **Smart Suggestions** - Heuristic analysis (`--suggest`) detects patterns and recommends transforms and stop rules automatically. Control sensitivity with `--suggest-threshold`.
|
|
64
64
|
|
|
@@ -121,7 +121,7 @@ hed -c config.yaml
|
|
|
121
121
|
hed -c config.yaml -H
|
|
122
122
|
```
|
|
123
123
|
|
|
124
|
-
Self-contained HTML report β works offline, no CDN required. Includes collapsible diff stats dashboard, stop rule violations table (shown in dry-run mode), synchronized side-by-side scrolling, copy buttons, sidebar search,
|
|
124
|
+
Self-contained HTML report β works offline, no CDN required. Includes collapsible diff stats dashboard, stop rule violations table (shown in dry-run mode), synchronized side-by-side scrolling, copy buttons, sidebar search, collapse/expand controls, and jump-to-sidebar navigation. File blocks auto-collapse when there are more than 10 files. Empty categories are automatically hidden.
|
|
125
125
|
|
|
126
126
|
### 5οΈβ£ Get Smart Suggestions (Optional)
|
|
127
127
|
|
|
@@ -753,6 +753,29 @@ hed -c config.yaml -f "foo\,bar" --list-files
|
|
|
753
753
|
|
|
754
754
|
---
|
|
755
755
|
|
|
756
|
+
### π€ Git Author Filter (`--my`)
|
|
757
|
+
|
|
758
|
+
Filter the sync to only source files **you** modified in git. Your identity is read automatically from `git config user.name` (falls back to `user.email`).
|
|
759
|
+
|
|
760
|
+
```bash
|
|
761
|
+
# Sync only files you modified in the last 30 days (default)
|
|
762
|
+
hed -c config.yaml --my
|
|
763
|
+
|
|
764
|
+
# Sync only files you modified in the last 7 days
|
|
765
|
+
hed -c config.yaml --my 7
|
|
766
|
+
|
|
767
|
+
# Preview first
|
|
768
|
+
hed -c config.yaml --my --dry-run --diff
|
|
769
|
+
```
|
|
770
|
+
|
|
771
|
+
**How it works:** Queries `git log` for commits by your git identity within the time window, collects modified file paths, and filters source files to that set. The matching destination files are filtered in tandem.
|
|
772
|
+
|
|
773
|
+
**Requirements:** Must be run inside a git repository with a configured `user.name` or `user.email`.
|
|
774
|
+
|
|
775
|
+
**Use case:** In a shared monorepo with many services, use `--my` to focus syncs on just the files you touched during your current sprintβwithout needing to know their exact paths.
|
|
776
|
+
|
|
777
|
+
---
|
|
778
|
+
|
|
756
779
|
### π Config Inheritance
|
|
757
780
|
|
|
758
781
|
Reuse base configurations across environment pairs.
|
|
@@ -813,26 +836,27 @@ hed --config <file> [options] # Short alias
|
|
|
813
836
|
|
|
814
837
|
### Options
|
|
815
838
|
|
|
816
|
-
| Flag | Short | Description
|
|
817
|
-
| --------------------------- | ----- |
|
|
818
|
-
| `--config <path>` | `-c` | **Required** - Configuration file
|
|
819
|
-
| `--validate` | | Validate config and pattern usage (shows warnings)
|
|
820
|
-
| `--suggest` | | Analyze differences and suggest config updates
|
|
821
|
-
| `--suggest-threshold <0-1>` | | Minimum confidence for suggestions (default: 0.3)
|
|
822
|
-
| `--dry-run` | `-D` | Preview changes without writing files
|
|
823
|
-
| `--force` | | Override stop rules
|
|
824
|
-
| `--diff` | `-d` | Show console diff
|
|
825
|
-
| `--diff-html` | `-H` | Generate HTML report (opens in browser)
|
|
826
|
-
| `--diff-json` | `-J` | Output JSON to stdout (pipe to jq)
|
|
827
|
-
| `--list-files` | `-l` | List files without processing (takes precedence over --format-only)
|
|
828
|
-
| `--show-config` | | Display resolved config after inheritance
|
|
829
|
-
| `--format-only` | | Format destination files only (source not required)
|
|
830
|
-
| `--skip-format` | `-S` | Skip YAML formatting during sync
|
|
831
|
-
| `--filter <string>` | `-f` | Filter files by filename/content (supports `,` OR, `+` AND)
|
|
832
|
-
| `--mode <type>` | `-m` | Filter by change type: new, modified, deleted, all (default: all)
|
|
833
|
-
| `--
|
|
834
|
-
| `--
|
|
835
|
-
| `--
|
|
839
|
+
| Flag | Short | Description |
|
|
840
|
+
| --------------------------- | ----- | --------------------------------------------------------------------------- |
|
|
841
|
+
| `--config <path>` | `-c` | **Required** - Configuration file |
|
|
842
|
+
| `--validate` | | Validate config and pattern usage (shows warnings) |
|
|
843
|
+
| `--suggest` | | Analyze differences and suggest config updates |
|
|
844
|
+
| `--suggest-threshold <0-1>` | | Minimum confidence for suggestions (default: 0.3) |
|
|
845
|
+
| `--dry-run` | `-D` | Preview changes without writing files |
|
|
846
|
+
| `--force` | | Override stop rules |
|
|
847
|
+
| `--diff` | `-d` | Show console diff |
|
|
848
|
+
| `--diff-html` | `-H` | Generate HTML report (opens in browser) |
|
|
849
|
+
| `--diff-json` | `-J` | Output JSON to stdout (pipe to jq) |
|
|
850
|
+
| `--list-files` | `-l` | List files without processing (takes precedence over --format-only) |
|
|
851
|
+
| `--show-config` | | Display resolved config after inheritance |
|
|
852
|
+
| `--format-only` | | Format destination files only (source not required) |
|
|
853
|
+
| `--skip-format` | `-S` | Skip YAML formatting during sync |
|
|
854
|
+
| `--filter <string>` | `-f` | Filter files by filename/content (supports `,` OR, `+` AND) |
|
|
855
|
+
| `--mode <type>` | `-m` | Filter by change type: new, modified, deleted, all (default: all) |
|
|
856
|
+
| `--my [days]` | | Filter to source files you modified in git in the last N days (default: 30) |
|
|
857
|
+
| `--no-color` | | Disable colored output (CI/accessibility) |
|
|
858
|
+
| `--verbose` | | Show detailed debug info |
|
|
859
|
+
| `--quiet` | | Suppress output except errors |
|
|
836
860
|
|
|
837
861
|
### Examples
|
|
838
862
|
|
|
@@ -885,6 +909,12 @@ hed -c config.yaml -m modified -D -d
|
|
|
885
909
|
# Combine filter and mode
|
|
886
910
|
hed -c config.yaml -f deployment -m modified -D -d
|
|
887
911
|
|
|
912
|
+
# Sync only files you modified in the last 30 days (auto-detects your git identity)
|
|
913
|
+
hed -c config.yaml --my -D -d
|
|
914
|
+
|
|
915
|
+
# Sync only files you modified in the last 7 days
|
|
916
|
+
hed -c config.yaml --my 7 -D -d
|
|
917
|
+
|
|
888
918
|
# Format destination files only (no sync, source not required in config)
|
|
889
919
|
hed -c config.yaml --format-only
|
|
890
920
|
|
package/dist/commandLine.d.ts
CHANGED
package/dist/commandLine.js
CHANGED
|
@@ -28,6 +28,7 @@ const parseCommandLine = (argv) => {
|
|
|
28
28
|
.option('--no-color', 'Disable colored output')
|
|
29
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
|
+
.option('--my [days]', 'Filter source files to those you modified in the last N days (default: 30)')
|
|
31
32
|
.option('--verbose', 'Show detailed debug information', false)
|
|
32
33
|
.option('--quiet', 'Suppress all output except critical errors', false)
|
|
33
34
|
.addHelpText('after', `
|
|
@@ -62,6 +63,12 @@ Examples:
|
|
|
62
63
|
# Preview modified files only
|
|
63
64
|
$ helm-env-delta --config config.yaml --mode modified --dry-run --diff
|
|
64
65
|
|
|
66
|
+
# Sync only files you modified in the last 30 days
|
|
67
|
+
$ helm-env-delta --config config.yaml --my --dry-run --diff
|
|
68
|
+
|
|
69
|
+
# Sync only files you modified in the last 7 days
|
|
70
|
+
$ helm-env-delta --config config.yaml --my 7 --diff
|
|
71
|
+
|
|
65
72
|
Documentation: https://github.com/balazscsaba2006/helm-env-delta
|
|
66
73
|
`);
|
|
67
74
|
program.showSuggestionAfterError(true);
|
|
@@ -85,6 +92,15 @@ Documentation: https://github.com/balazscsaba2006/helm-env-delta
|
|
|
85
92
|
console.error('Error: --mode must be one of: ' + validModes.join(', '));
|
|
86
93
|
process.exit(1);
|
|
87
94
|
}
|
|
95
|
+
let myDays = 30;
|
|
96
|
+
if (options['my'] !== undefined && options['my'] !== true) {
|
|
97
|
+
const parsed = Number.parseInt(options['my'], 10);
|
|
98
|
+
if (Number.isNaN(parsed) || parsed < 1) {
|
|
99
|
+
console.error('Error: --my days must be a positive integer');
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
myDays = parsed;
|
|
103
|
+
}
|
|
88
104
|
return {
|
|
89
105
|
config: options['config'],
|
|
90
106
|
dryRun: options['dryRun'],
|
|
@@ -103,7 +119,9 @@ Documentation: https://github.com/balazscsaba2006/helm-env-delta
|
|
|
103
119
|
suggest: options['suggest'],
|
|
104
120
|
suggestThreshold: threshold,
|
|
105
121
|
filter: options['filter'],
|
|
106
|
-
mode: options['mode']
|
|
122
|
+
mode: options['mode'],
|
|
123
|
+
my: options['my'] !== undefined && options['my'] !== false,
|
|
124
|
+
myDays
|
|
107
125
|
};
|
|
108
126
|
};
|
|
109
127
|
exports.parseCommandLine = parseCommandLine;
|
package/dist/index.js
CHANGED
|
@@ -55,6 +55,7 @@ const commentOnlyDetector_1 = require("./utils/commentOnlyDetector");
|
|
|
55
55
|
const fileFilter_1 = require("./utils/fileFilter");
|
|
56
56
|
const filenameTransformer_1 = require("./utils/filenameTransformer");
|
|
57
57
|
const fileType_1 = require("./utils/fileType");
|
|
58
|
+
const gitFilter_1 = require("./utils/gitFilter");
|
|
58
59
|
const versionChecker_1 = require("./utils/versionChecker");
|
|
59
60
|
const main = async () => {
|
|
60
61
|
const command = (0, commandLine_1.parseCommandLine)();
|
|
@@ -117,6 +118,16 @@ const main = async () => {
|
|
|
117
118
|
sourceFiles = filtered.sourceFiles;
|
|
118
119
|
destinationFiles = filtered.destinationFiles;
|
|
119
120
|
}
|
|
121
|
+
if (command.my) {
|
|
122
|
+
const absoluteSourceDirectory = node_path_1.default.isAbsolute(validationConfig.source)
|
|
123
|
+
? validationConfig.source
|
|
124
|
+
: node_path_1.default.resolve(process.cwd(), validationConfig.source);
|
|
125
|
+
const author = await (0, gitFilter_1.getGitUser)();
|
|
126
|
+
const filtered = await (0, gitFilter_1.filterFileMapsByGitAuthor)(sourceFiles, destinationFiles, absoluteSourceDirectory, author, command.myDays);
|
|
127
|
+
sourceFiles = filtered.sourceFiles;
|
|
128
|
+
destinationFiles = filtered.destinationFiles;
|
|
129
|
+
logger.progress(`--my filter (${command.myDays} days, author: "${author}") matched ${sourceFiles.size} source, ${destinationFiles.size} destination file(s)`, 'info');
|
|
130
|
+
}
|
|
120
131
|
logger.progress(`Loaded ${sourceFiles.size} source, ${destinationFiles.size} destination file(s)`, 'success');
|
|
121
132
|
logger.log('\n' + (0, consoleFormatter_1.formatProgressMessage)('Validating pattern usage...', 'info'));
|
|
122
133
|
const usageResult = (0, pipeline_1.validatePatternUsage)(validationConfig, sourceFiles, destinationFiles);
|
|
@@ -126,6 +137,8 @@ const main = async () => {
|
|
|
126
137
|
for (const warning of usageResult.warnings) {
|
|
127
138
|
const contextString = warning.context ? chalk_1.default.dim(` (${warning.context})`) : '';
|
|
128
139
|
console.warn(chalk_1.default.yellow(` β’ ${warning.message}${contextString}`));
|
|
140
|
+
if (warning.hint)
|
|
141
|
+
console.warn(chalk_1.default.dim(` Hint: ${warning.hint}`));
|
|
129
142
|
}
|
|
130
143
|
}
|
|
131
144
|
if (hasAnyWarnings)
|
|
@@ -237,6 +250,16 @@ const main = async () => {
|
|
|
237
250
|
destinationFiles = filtered.destinationFiles;
|
|
238
251
|
logger.progress(`Filter '${command.filter}' matched ${sourceFiles.size} source, ${destinationFiles.size} destination file(s)`, 'info');
|
|
239
252
|
}
|
|
253
|
+
if (command.my) {
|
|
254
|
+
const absoluteSourceDirectory = node_path_1.default.isAbsolute(syncConfig.source)
|
|
255
|
+
? syncConfig.source
|
|
256
|
+
: node_path_1.default.resolve(process.cwd(), syncConfig.source);
|
|
257
|
+
const author = await (0, gitFilter_1.getGitUser)();
|
|
258
|
+
const filtered = await (0, gitFilter_1.filterFileMapsByGitAuthor)(sourceFiles, destinationFiles, absoluteSourceDirectory, author, command.myDays);
|
|
259
|
+
sourceFiles = filtered.sourceFiles;
|
|
260
|
+
destinationFiles = filtered.destinationFiles;
|
|
261
|
+
logger.progress(`--my filter (${command.myDays} days, author: "${author}") matched ${sourceFiles.size} source, ${destinationFiles.size} destination file(s)`, 'info');
|
|
262
|
+
}
|
|
240
263
|
if (command.listFiles) {
|
|
241
264
|
const sourceFilesList = [...sourceFiles.keys()].toSorted();
|
|
242
265
|
const destinationFilesList = [...destinationFiles.keys()].toSorted();
|
|
@@ -308,11 +331,22 @@ const main = async () => {
|
|
|
308
331
|
console.log(` ${chalk_1.default.red('Deleted:')} ${diffResult.deletedFiles.length} files (${syncConfig.prune ? 'prune enabled' : 'prune disabled'})`);
|
|
309
332
|
console.log(` ${chalk_1.default.blue('Unchanged:')} ${diffResult.unchangedFiles.length} files`);
|
|
310
333
|
console.log(chalk_1.default.dim('β'.repeat(60)));
|
|
311
|
-
if (diffResult.deletedFiles.length > 0 && syncConfig.prune)
|
|
312
|
-
console.warn(chalk_1.default.red('β οΈ Warning: Prune is enabled.
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
334
|
+
if (diffResult.deletedFiles.length > 0 && syncConfig.prune) {
|
|
335
|
+
console.warn(chalk_1.default.red('β οΈ Warning: Prune is enabled. The following files will be permanently deleted:'));
|
|
336
|
+
for (const f of diffResult.deletedFiles)
|
|
337
|
+
console.warn(chalk_1.default.red(` - ${f}`));
|
|
338
|
+
}
|
|
339
|
+
if (syncConfig.confirmationDelay > 0) {
|
|
340
|
+
const totalSeconds = Math.ceil(syncConfig.confirmationDelay / 1000);
|
|
341
|
+
console.log(chalk_1.default.dim('\nPress Ctrl+C to cancel.\n'));
|
|
342
|
+
for (let remaining = totalSeconds; remaining > 0; remaining--) {
|
|
343
|
+
process.stdout.write(chalk_1.default.dim(` Proceeding in ${remaining}s...\r`));
|
|
344
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
345
|
+
}
|
|
346
|
+
process.stdout.write(' '.repeat(40) + '\r');
|
|
347
|
+
}
|
|
348
|
+
else
|
|
349
|
+
console.log(chalk_1.default.dim('\nPress Ctrl+C to cancel, or use --dry-run to preview changes first.\n'));
|
|
316
350
|
}
|
|
317
351
|
const formattedFiles = await (0, pipeline_1.updateFiles)(diffResult, sourceFiles, destinationFiles, syncConfig, command.dryRun, command.skipFormat, logger);
|
|
318
352
|
if (command.diffHtml && !command.quiet)
|
|
@@ -349,6 +383,8 @@ const main = async () => {
|
|
|
349
383
|
console.error(error.message);
|
|
350
384
|
else if ((0, fileFilter_1.isFilterParseError)(error))
|
|
351
385
|
console.error(error.message);
|
|
386
|
+
else if ((0, gitFilter_1.isGitFilterError)(error))
|
|
387
|
+
console.error(error.message);
|
|
352
388
|
else if (error instanceof Error)
|
|
353
389
|
console.error('Unexpected error:', error.message);
|
|
354
390
|
else
|
|
@@ -22,7 +22,7 @@ class FileDiffError extends FileDiffErrorClass {
|
|
|
22
22
|
}
|
|
23
23
|
exports.FileDiffError = FileDiffError;
|
|
24
24
|
exports.isFileDiffError = (0, errors_1.createErrorTypeGuard)(FileDiffError);
|
|
25
|
-
const processAddedFileContent = (filePath, content, transforms, fixedValues, outputFormat) => {
|
|
25
|
+
const processAddedFileContent = (filePath, content, transforms, fixedValues, outputFormat, logger) => {
|
|
26
26
|
if (!(0, fileType_1.isYamlFile)(filePath))
|
|
27
27
|
return content;
|
|
28
28
|
try {
|
|
@@ -35,16 +35,17 @@ const processAddedFileContent = (filePath, content, transforms, fixedValues, out
|
|
|
35
35
|
processed = (0, yamlFormatter_1.formatYaml)(processed, filePath, outputFormat);
|
|
36
36
|
return processed;
|
|
37
37
|
}
|
|
38
|
-
catch {
|
|
38
|
+
catch (error) {
|
|
39
|
+
logger?.warn(`Warning: Could not process added file '${filePath}' (${error instanceof Error ? error.message : String(error)}). Using raw content.`, 'normal');
|
|
39
40
|
return content;
|
|
40
41
|
}
|
|
41
42
|
};
|
|
42
|
-
const detectAddedFiles = (sourceFiles, destinationFiles, config, originalPaths) => {
|
|
43
|
+
const detectAddedFiles = (sourceFiles, destinationFiles, config, originalPaths, logger) => {
|
|
43
44
|
const addedFiles = [];
|
|
44
45
|
for (const [path, content] of sourceFiles.entries())
|
|
45
46
|
if (!destinationFiles.has(path)) {
|
|
46
47
|
const originalPath = originalPaths?.get(path);
|
|
47
|
-
const processedContent = processAddedFileContent(path, content, config.transforms, config.fixedValues, config.outputFormat);
|
|
48
|
+
const processedContent = processAddedFileContent(path, content, config.transforms, config.fixedValues, config.outputFormat, logger);
|
|
48
49
|
addedFiles.push({
|
|
49
50
|
path,
|
|
50
51
|
originalPath,
|
|
@@ -279,7 +280,7 @@ const computeFileDiff = (sourceFiles, destinationFiles, config, logger, original
|
|
|
279
280
|
if (skipPathCount > 0)
|
|
280
281
|
logger.debug(` SkipPath patterns: ${skipPathCount}`);
|
|
281
282
|
}
|
|
282
|
-
const addedFiles = detectAddedFiles(sourceFiles, destinationFiles, config, originalPaths);
|
|
283
|
+
const addedFiles = detectAddedFiles(sourceFiles, destinationFiles, config, originalPaths, logger);
|
|
283
284
|
const deletedFiles = config.prune ? detectDeletedFiles(sourceFiles, destinationFiles) : [];
|
|
284
285
|
const { changedFiles, unchangedFiles } = processChangedFiles(sourceFiles, destinationFiles, config.skipPath, config.transforms, config.fixedValues, originalPaths);
|
|
285
286
|
return { addedFiles, deletedFiles, changedFiles, unchangedFiles };
|
|
@@ -59,7 +59,8 @@ const validateSkipPathPatterns = (config, sourceFiles, destinationFiles) => {
|
|
|
59
59
|
type: 'unused-skipPath-jsonpath',
|
|
60
60
|
pattern,
|
|
61
61
|
message: `skipPath JSONPath '${jsonPath}' not found in any matched files`,
|
|
62
|
-
context: `Pattern: ${pattern}, matches ${yamlFiles.length} file(s)
|
|
62
|
+
context: `Pattern: ${pattern}, matches ${yamlFiles.length} file(s)`,
|
|
63
|
+
hint: 'Run with --list-files to see which files matched, and --validate for full pattern analysis'
|
|
63
64
|
});
|
|
64
65
|
}
|
|
65
66
|
}
|
|
@@ -93,7 +94,8 @@ const validateStopRulePatterns = (config, sourceFiles, destinationFiles) => {
|
|
|
93
94
|
type: 'unused-stopRule-path',
|
|
94
95
|
pattern: globPattern,
|
|
95
96
|
message: `stopRules JSONPath '${rule.path}' not found in any matched files`,
|
|
96
|
-
context: `Rule type: ${rule.type}, matches ${yamlFiles.length} file(s)
|
|
97
|
+
context: `Rule type: ${rule.type}, matches ${yamlFiles.length} file(s)`,
|
|
98
|
+
hint: 'Run with --list-files to see which files are loaded'
|
|
97
99
|
});
|
|
98
100
|
}
|
|
99
101
|
}
|
|
@@ -126,7 +128,8 @@ const validateFixedValuesPatterns = (config, sourceFiles, destinationFiles) => {
|
|
|
126
128
|
type: 'unused-fixedValues-jsonpath',
|
|
127
129
|
pattern,
|
|
128
130
|
message: `fixedValues JSONPath '${rule.path}' not found in any matched files`,
|
|
129
|
-
context: `Pattern: ${pattern}, matches ${yamlFiles.length} file(s)
|
|
131
|
+
context: `Pattern: ${pattern}, matches ${yamlFiles.length} file(s)`,
|
|
132
|
+
hint: 'Run with --list-files to see which files matched'
|
|
130
133
|
});
|
|
131
134
|
}
|
|
132
135
|
}
|
|
@@ -88,7 +88,16 @@ const showConsoleDiff = (diffResult, config) => {
|
|
|
88
88
|
if (diffResult.addedFiles.length === 0 &&
|
|
89
89
|
diffResult.changedFiles.length === 0 &&
|
|
90
90
|
diffResult.deletedFiles.length === 0) {
|
|
91
|
-
|
|
91
|
+
const totalCompared = diffResult.unchangedFiles.length;
|
|
92
|
+
const hasSkipPath = config.skipPath && Object.keys(config.skipPath).length > 0;
|
|
93
|
+
const skipNote = hasSkipPath ? chalk_1.default.dim(' (some paths may be excluded via skipPath)') : '';
|
|
94
|
+
if (totalCompared === 0)
|
|
95
|
+
console.log(chalk_1.default.yellow.bold('\nβ No files matched the include/exclude patterns\n'));
|
|
96
|
+
else
|
|
97
|
+
console.log(chalk_1.default.green.bold(`\nβ No differences found`) +
|
|
98
|
+
chalk_1.default.dim(` β ${totalCompared} file(s) compared, all identical`) +
|
|
99
|
+
skipNote +
|
|
100
|
+
'\n');
|
|
92
101
|
return;
|
|
93
102
|
}
|
|
94
103
|
console.log(formatAddedFiles(diffResult.addedFiles));
|
|
@@ -59,13 +59,14 @@ const generateAddedFileSummary = (file) => {
|
|
|
59
59
|
return file.path;
|
|
60
60
|
return `<span class="filename-transform">${file.originalPath} β ${file.path}</span>`;
|
|
61
61
|
};
|
|
62
|
-
const
|
|
62
|
+
const JUMP_TO_SIDEBAR_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><path d="M2 2h3v12H2V2zm0-1a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1H2zm5 4h7v1H7V5zm0 3h7v1H7V8zm0 3h5v1H7v-1z"/></svg>`;
|
|
63
|
+
const generateAddedFileSection = (file, fileId, open) => {
|
|
63
64
|
const summary = generateAddedFileSummary(file);
|
|
64
65
|
const escapedContent = (0, treeRenderer_1.escapeHtml)(file.processedContent);
|
|
65
66
|
const filename = file.path.split('/').pop() || file.path;
|
|
66
67
|
return `
|
|
67
|
-
<details class="file-section" id="${fileId}" data-file-id="${fileId}" open>
|
|
68
|
-
<summary>${summary}</summary>
|
|
68
|
+
<details class="file-section" id="${fileId}" data-file-id="${fileId}"${open ? ' open' : ''}>
|
|
69
|
+
<summary><a class="jump-to-sidebar-link" data-file-id="${fileId}" href="#" title="Show in file browser">${JUMP_TO_SIDEBAR_ICON}</a><span class="summary-expand-icon"></span>${summary}</summary>
|
|
69
70
|
<div class="content-container">
|
|
70
71
|
<div class="content-actions">
|
|
71
72
|
<button class="copy-btn" data-file-id="${fileId}" title="Copy to clipboard">π Copy</button>
|
|
@@ -76,7 +77,7 @@ const generateAddedFileSection = (file, fileId) => {
|
|
|
76
77
|
</details>
|
|
77
78
|
`;
|
|
78
79
|
};
|
|
79
|
-
const generateChangedFileSection = (file, fileId) => {
|
|
80
|
+
const generateChangedFileSection = (file, fileId, open) => {
|
|
80
81
|
const isYaml = (0, fileType_1.isYamlFile)(file.path);
|
|
81
82
|
const summary = generateFileSummary(file);
|
|
82
83
|
const destinationContent = (0, serialization_1.serializeForDiff)(file.processedDestContent, isYaml);
|
|
@@ -86,8 +87,8 @@ const generateChangedFileSection = (file, fileId) => {
|
|
|
86
87
|
const { added, removed } = countDiffLines(unifiedDiff);
|
|
87
88
|
const escapedDiff = (0, treeRenderer_1.escapeHtml)(unifiedDiff);
|
|
88
89
|
const html = `
|
|
89
|
-
<details class="file-section" id="${fileId}" data-file-id="${fileId}" open>
|
|
90
|
-
<summary>${summary}<span class="summary-badges"><span class="line-badge line-added">+${added}</span><span class="line-badge line-removed">-${removed}</span></span></summary>
|
|
90
|
+
<details class="file-section" id="${fileId}" data-file-id="${fileId}"${open ? ' open' : ''}>
|
|
91
|
+
<summary><a class="jump-to-sidebar-link" data-file-id="${fileId}" href="#" title="Show in file browser">${JUMP_TO_SIDEBAR_ICON}</a><span class="summary-expand-icon"></span>${summary}<span class="summary-badges"><span class="line-badge line-added">+${added}</span><span class="line-badge line-removed">-${removed}</span></span></summary>
|
|
91
92
|
<div class="diff-toolbar">
|
|
92
93
|
<button class="copy-diff-btn" data-file-id="${fileId}">Copy Diff</button>
|
|
93
94
|
</div>
|
|
@@ -131,16 +132,19 @@ const generateHtmlReport = async (diffResult, formattedFiles, config, dryRun, lo
|
|
|
131
132
|
const changedFileIds = new Map();
|
|
132
133
|
for (const [index, file] of diffResult.changedFiles.entries())
|
|
133
134
|
changedFileIds.set(file.path, `file-${index}`);
|
|
135
|
+
const COLLAPSE_THRESHOLD = 10;
|
|
136
|
+
const changedOpen = diffResult.changedFiles.length <= COLLAPSE_THRESHOLD;
|
|
137
|
+
const addedOpen = diffResult.addedFiles.length <= COLLAPSE_THRESHOLD;
|
|
134
138
|
const fileStats = new Map();
|
|
135
139
|
const changedSections = diffResult.changedFiles.map((file, index) => {
|
|
136
|
-
const result = generateChangedFileSection(file, `file-${index}
|
|
140
|
+
const result = generateChangedFileSection(file, `file-${index}`, changedOpen);
|
|
137
141
|
fileStats.set(file.path, { added: result.added, removed: result.removed });
|
|
138
142
|
return result.html;
|
|
139
143
|
});
|
|
140
144
|
const addedFileIds = new Map();
|
|
141
145
|
for (const [index, file] of diffResult.addedFiles.entries())
|
|
142
146
|
addedFileIds.set(file.path, `added-file-${index}`);
|
|
143
|
-
const addedSections = diffResult.addedFiles.map((file, index) => generateAddedFileSection(file, `added-file-${index}
|
|
147
|
+
const addedSections = diffResult.addedFiles.map((file, index) => generateAddedFileSection(file, `added-file-${index}`, addedOpen));
|
|
144
148
|
let totalAdded = 0;
|
|
145
149
|
let totalRemoved = 0;
|
|
146
150
|
const statsArray = [];
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export declare const DIFF2HTML_STYLES: string;
|
|
2
|
-
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 .file-section[open] > summary {\n position: sticky;\n top: 0;\n z-index: 10;\n border-bottom: 1px solid #d0d7de;\n box-shadow: 0 2px 4px rgba(0,0,0,0.1);\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 /* Treeview styles */\n .tree-root {\n list-style: none;\n padding: 0;\n margin: 10px 0;\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace;\n font-size: 13px;\n }\n\n .tree-root ul {\n list-style: none;\n padding-left: 20px;\n margin: 0;\n }\n\n .tree-folder,\n .tree-file {\n padding: 4px 8px;\n border-radius: 4px;\n cursor: default;\n }\n\n .tree-folder:hover,\n .tree-file:hover {\n background: #f6f8fa;\n }\n\n .tree-toggle {\n display: inline-block;\n width: 16px;\n cursor: pointer;\n color: #586069;\n font-size: 10px;\n user-select: none;\n }\n\n .tree-folder.collapsed > .tree-toggle {\n transform: rotate(-90deg);\n }\n\n .tree-folder.collapsed > .tree-children {\n display: none;\n }\n\n .tree-folder-name {\n color: #0969da;\n font-weight: 500;\n }\n\n .tree-file-name {\n color: #586069;\n padding-left: 16px;\n }\n\n /* Sidebar styles */\n .sidebar-container {\n display: flex;\n gap: 0;\n }\n\n .sidebar {\n width: 280px;\n min-width: 280px;\n border-right: 1px solid #d0d7de;\n background: #f6f8fa;\n transition: width 0.2s, min-width 0.2s, padding 0.2s, opacity 0.2s;\n }\n\n .sidebar.collapsed {\n width: 0;\n min-width: 0;\n padding: 0;\n overflow: hidden;\n border-right: none;\n }\n\n .sidebar-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 12px 16px;\n border-bottom: 1px solid #d0d7de;\n background: #fff;\n font-weight: 600;\n font-size: 14px;\n color: #24292e;\n position: sticky;\n top: 0;\n z-index: 1;\n }\n\n .sidebar-toggle {\n background: none;\n border: 1px solid #d0d7de;\n border-radius: 4px;\n cursor: pointer;\n padding: 4px 8px;\n color: #586069;\n font-size: 12px;\n }\n\n .sidebar-toggle:hover {\n background: #f6f8fa;\n color: #24292e;\n }\n\n .sidebar-content {\n padding: 8px;\n }\n\n .sidebar-tree .tree-file-link {\n color: #586069;\n text-decoration: none;\n padding-left: 16px;\n display: block;\n }\n\n .sidebar-tree .tree-file-link:hover {\n color: #0969da;\n }\n\n .sidebar-tree .tree-file.active .tree-file-link {\n color: #0969da;\n font-weight: 600;\n }\n\n .changed-content {\n flex: 1;\n min-width: 0;\n padding-left: 20px;\n }\n\n .sidebar-expand-btn {\n display: none;\n position: fixed;\n left: 0;\n top: 50%;\n transform: translateY(-50%);\n background: #f6f8fa;\n border: 1px solid #d0d7de;\n border-left: none;\n border-radius: 0 4px 4px 0;\n padding: 8px 4px;\n cursor: pointer;\n color: #586069;\n z-index: 100;\n }\n\n .sidebar-expand-btn:hover {\n background: #eaeef2;\n color: #24292e;\n }\n\n .sidebar.collapsed ~ .sidebar-expand-btn {\n display: block;\n }\n\n /* Added content area (same as changed-content) */\n .added-content {\n flex: 1;\n min-width: 0;\n padding-left: 20px;\n }\n\n /* Content container for added files */\n .content-container {\n padding: 16px;\n background: #f6f8fa;\n border-top: 1px solid #d0d7de;\n }\n\n .content-actions {\n display: flex;\n gap: 8px;\n margin-bottom: 12px;\n }\n\n .copy-btn,\n .download-btn {\n padding: 6px 12px;\n border: 1px solid #d0d7de;\n border-radius: 6px;\n background: white;\n cursor: pointer;\n font-size: 13px;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif;\n color: #24292e;\n transition: all 0.2s;\n }\n\n .copy-btn:hover,\n .download-btn:hover {\n background: #f3f4f6;\n border-color: #b0b7be;\n }\n\n .copy-btn.copied {\n background: #d4edda;\n border-color: #28a745;\n color: #155724;\n }\n\n .file-content {\n margin: 0;\n padding: 16px;\n background: white;\n border: 1px solid #d0d7de;\n border-radius: 6px;\n overflow-x: auto;\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace;\n font-size: 12px;\n line-height: 1.5;\n white-space: pre;\n }\n\n .file-content code {\n font-family: inherit;\n }\n\n /* Scroll-to-top button */\n .scroll-to-top {\n display: none;\n position: fixed;\n bottom: 30px;\n right: 30px;\n width: 40px;\n height: 40px;\n border: none;\n border-radius: 50%;\n background: #0969da;\n color: white;\n font-size: 18px;\n cursor: pointer;\n box-shadow: 0 2px 8px rgba(0,0,0,0.2);\n transition: opacity 0.2s, background 0.2s;\n z-index: 200;\n line-height: 40px;\n text-align: center;\n padding: 0;\n }\n\n .scroll-to-top:hover {\n background: #0550ae;\n }\n\n .scroll-to-top.visible {\n display: block;\n }\n\n /* Content toolbar (collapse/expand buttons) */\n .content-toolbar {\n display: flex;\n gap: 8px;\n justify-content: flex-end;\n margin-bottom: 12px;\n }\n\n .collapse-all-btn,\n .expand-all-btn {\n padding: 4px 12px;\n border: 1px solid #d0d7de;\n border-radius: 4px;\n background: none;\n cursor: pointer;\n font-size: 12px;\n color: #586069;\n transition: all 0.2s;\n }\n\n .collapse-all-btn:hover,\n .expand-all-btn:hover {\n background: #f6f8fa;\n color: #24292e;\n }\n\n /* Line change count badges */\n .summary-badges {\n float: right;\n display: inline-flex;\n gap: 6px;\n margin-left: 12px;\n }\n\n .line-badge {\n display: inline-block;\n padding: 1px 8px;\n border-radius: 10px;\n font-size: 11px;\n font-weight: 600;\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace;\n line-height: 18px;\n }\n\n .line-added {\n background: #d4edda;\n color: #155724;\n }\n\n .line-removed {\n background: #f8d7da;\n color: #721c24;\n }\n\n /* Diff toolbar */\n .diff-toolbar {\n display: flex;\n justify-content: flex-end;\n padding: 8px 16px;\n border-bottom: 1px solid #d0d7de;\n background: #f6f8fa;\n }\n\n .copy-diff-btn {\n padding: 4px 12px;\n border: 1px solid #d0d7de;\n border-radius: 6px;\n background: white;\n cursor: pointer;\n font-size: 12px;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif;\n color: #24292e;\n transition: all 0.2s;\n }\n\n .copy-diff-btn:hover {\n background: #f3f4f6;\n border-color: #b0b7be;\n }\n\n .copy-diff-btn.copied {\n background: #d4edda;\n border-color: #28a745;\n color: #155724;\n }\n\n /* Sidebar search */\n .sidebar-search {\n width: 100%;\n padding: 6px 8px;\n border: 1px solid #d0d7de;\n border-radius: 4px;\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace;\n font-size: 12px;\n margin-bottom: 8px;\n box-sizing: border-box;\n position: sticky;\n top: 0;\n z-index: 1;\n background: #fff;\n }\n\n .sidebar-search:focus {\n outline: none;\n border-color: #0969da;\n box-shadow: 0 0 0 3px rgba(9,105,218,0.15);\n }\n\n /* Stats toggle button */\n .stats-toggle-btn {\n padding: 4px 12px;\n border: 1px solid #d0d7de;\n border-radius: 4px;\n background: none;\n cursor: pointer;\n font-size: 12px;\n color: #586069;\n transition: all 0.2s;\n }\n\n .stats-toggle-btn:hover {\n background: #f6f8fa;\n color: #24292e;\n }\n\n /* Statistics dashboard */\n .stats-dashboard {\n margin: 15px 0 0;\n padding: 12px 0 0;\n border-top: 1px solid #e1e4e8;\n }\n\n .stats-summary {\n display: flex;\n gap: 16px;\n align-items: center;\n margin-bottom: 10px;\n }\n\n .stats-summary .total-added {\n font-weight: 700;\n color: #155724;\n font-size: 16px;\n }\n\n .stats-summary .total-removed {\n font-weight: 700;\n color: #721c24;\n font-size: 16px;\n }\n\n .stats-bar {\n display: flex;\n height: 8px;\n border-radius: 4px;\n overflow: hidden;\n background: #e1e4e8;\n margin-bottom: 10px;\n }\n\n .stats-segment {\n height: 100%;\n min-width: 2px;\n }\n\n .stats-segment.added-segment {\n background: #28a745;\n }\n\n .stats-segment.removed-segment {\n background: #d73a49;\n }\n\n .top-changed-files {\n list-style: none;\n padding: 0;\n margin: 0;\n font-size: 13px;\n }\n\n .top-changed-files li {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 3px 0;\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace;\n color: #586069;\n }\n\n .top-changed-files .file-path {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n margin-right: 8px;\n }\n\n .top-changed-files .file-stats {\n white-space: nowrap;\n flex-shrink: 0;\n }\n\n /* Stop rules violations badge */\n .stat.violations { background: #f8d7da; color: #721c24; }\n\n /* Violations section */\n .violations-section {\n margin: 15px 0 0;\n padding: 12px 0 0;\n border-top: 1px solid #e1e4e8;\n }\n\n .violations-toggle-btn {\n padding: 4px 12px;\n border: 1px solid #d0d7de;\n border-radius: 4px;\n background: none;\n cursor: pointer;\n font-size: 12px;\n color: #586069;\n transition: all 0.2s;\n }\n\n .violations-toggle-btn:hover {\n background: #f6f8fa;\n color: #24292e;\n }\n\n .violations-table {\n width: 100%;\n border-collapse: collapse;\n font-size: 13px;\n margin-top: 10px;\n }\n\n .violations-table th {\n background: #f6f8fa;\n text-align: left;\n padding: 8px 12px;\n border-bottom: 2px solid #d0d7de;\n font-weight: 600;\n color: #24292e;\n }\n\n .violations-table td {\n padding: 8px 12px;\n border-bottom: 1px solid #e1e4e8;\n color: #24292e;\n vertical-align: top;\n }\n\n .violations-table tr:hover td {\n background: #f6f8fa;\n }\n\n .violation-rule-badge {\n display: inline-block;\n padding: 2px 8px;\n border-radius: 10px;\n font-size: 11px;\n font-weight: 600;\n background: #fff3cd;\n color: #856404;\n white-space: nowrap;\n }\n\n .violation-value {\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace;\n font-size: 12px;\n background: #f6f8fa;\n padding: 2px 6px;\n border-radius: 3px;\n border: 1px solid #e1e4e8;\n }\n";
|
|
2
|
+
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 display: flex;\n align-items: center;\n gap: 6px;\n list-style: none;\n }\n\n .file-section summary::-webkit-details-marker {\n display: none;\n }\n\n .summary-expand-icon::before {\n content: '\u25B6';\n font-size: 10px;\n color: #6a737d;\n }\n\n .file-section[open] > summary .summary-expand-icon::before {\n content: '\u25BC';\n }\n\n .jump-to-sidebar-link {\n display: inline-flex;\n align-items: center;\n color: #6a737d;\n text-decoration: none;\n padding: 2px 3px;\n border-radius: 3px;\n flex-shrink: 0;\n line-height: 1;\n }\n\n .jump-to-sidebar-link:hover {\n color: #0969da;\n background: rgba(9, 105, 218, 0.08);\n }\n\n .file-section summary:hover {\n background: #eaeef2;\n }\n\n .file-section[open] > summary {\n position: sticky;\n top: 0;\n z-index: 10;\n border-bottom: 1px solid #d0d7de;\n box-shadow: 0 2px 4px rgba(0,0,0,0.1);\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 /* Treeview styles */\n .tree-root {\n list-style: none;\n padding: 0;\n margin: 10px 0;\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace;\n font-size: 13px;\n }\n\n .tree-root ul {\n list-style: none;\n padding-left: 20px;\n margin: 0;\n }\n\n .tree-folder,\n .tree-file {\n padding: 4px 8px;\n border-radius: 4px;\n cursor: default;\n }\n\n .tree-folder:hover,\n .tree-file:hover {\n background: #f6f8fa;\n }\n\n .tree-toggle {\n display: inline-block;\n width: 16px;\n cursor: pointer;\n color: #586069;\n font-size: 10px;\n user-select: none;\n }\n\n .tree-folder.collapsed > .tree-toggle {\n transform: rotate(-90deg);\n }\n\n .tree-folder.collapsed > .tree-children {\n display: none;\n }\n\n .tree-folder-name {\n color: #0969da;\n font-weight: 500;\n }\n\n .tree-file-name {\n color: #586069;\n padding-left: 16px;\n }\n\n /* Sidebar styles */\n .sidebar-container {\n display: flex;\n gap: 0;\n }\n\n .sidebar {\n width: 280px;\n min-width: 280px;\n border-right: 1px solid #d0d7de;\n background: #f6f8fa;\n transition: width 0.2s, min-width 0.2s, padding 0.2s, opacity 0.2s;\n }\n\n .sidebar.collapsed {\n width: 0;\n min-width: 0;\n padding: 0;\n overflow: hidden;\n border-right: none;\n }\n\n .sidebar-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 12px 16px;\n border-bottom: 1px solid #d0d7de;\n background: #fff;\n font-weight: 600;\n font-size: 14px;\n color: #24292e;\n position: sticky;\n top: 0;\n z-index: 1;\n }\n\n .sidebar-toggle {\n background: none;\n border: 1px solid #d0d7de;\n border-radius: 4px;\n cursor: pointer;\n padding: 4px 8px;\n color: #586069;\n font-size: 12px;\n }\n\n .sidebar-toggle:hover {\n background: #f6f8fa;\n color: #24292e;\n }\n\n .sidebar-content {\n padding: 8px;\n }\n\n .sidebar-tree .tree-file-link {\n color: #586069;\n text-decoration: none;\n padding-left: 16px;\n display: block;\n }\n\n .sidebar-tree .tree-file-link:hover {\n color: #0969da;\n }\n\n .sidebar-tree .tree-file.active .tree-file-link {\n color: #0969da;\n font-weight: 600;\n }\n\n .changed-content {\n flex: 1;\n min-width: 0;\n padding-left: 20px;\n }\n\n .sidebar-expand-btn {\n display: none;\n position: fixed;\n left: 0;\n top: 50%;\n transform: translateY(-50%);\n background: #f6f8fa;\n border: 1px solid #d0d7de;\n border-left: none;\n border-radius: 0 4px 4px 0;\n padding: 8px 4px;\n cursor: pointer;\n color: #586069;\n z-index: 100;\n }\n\n .sidebar-expand-btn:hover {\n background: #eaeef2;\n color: #24292e;\n }\n\n .sidebar.collapsed ~ .sidebar-expand-btn {\n display: block;\n }\n\n /* Added content area (same as changed-content) */\n .added-content {\n flex: 1;\n min-width: 0;\n padding-left: 20px;\n }\n\n /* Content container for added files */\n .content-container {\n padding: 16px;\n background: #f6f8fa;\n border-top: 1px solid #d0d7de;\n }\n\n .content-actions {\n display: flex;\n gap: 8px;\n margin-bottom: 12px;\n }\n\n .copy-btn,\n .download-btn {\n padding: 6px 12px;\n border: 1px solid #d0d7de;\n border-radius: 6px;\n background: white;\n cursor: pointer;\n font-size: 13px;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif;\n color: #24292e;\n transition: all 0.2s;\n }\n\n .copy-btn:hover,\n .download-btn:hover {\n background: #f3f4f6;\n border-color: #b0b7be;\n }\n\n .copy-btn.copied {\n background: #d4edda;\n border-color: #28a745;\n color: #155724;\n }\n\n .file-content {\n margin: 0;\n padding: 16px;\n background: white;\n border: 1px solid #d0d7de;\n border-radius: 6px;\n overflow-x: auto;\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace;\n font-size: 12px;\n line-height: 1.5;\n white-space: pre;\n }\n\n .file-content code {\n font-family: inherit;\n }\n\n /* Scroll-to-top button */\n .scroll-to-top {\n display: none;\n position: fixed;\n bottom: 30px;\n right: 30px;\n width: 40px;\n height: 40px;\n border: none;\n border-radius: 50%;\n background: #0969da;\n color: white;\n font-size: 18px;\n cursor: pointer;\n box-shadow: 0 2px 8px rgba(0,0,0,0.2);\n transition: opacity 0.2s, background 0.2s;\n z-index: 200;\n line-height: 40px;\n text-align: center;\n padding: 0;\n }\n\n .scroll-to-top:hover {\n background: #0550ae;\n }\n\n .scroll-to-top.visible {\n display: block;\n }\n\n /* Content toolbar (collapse/expand buttons) */\n .content-toolbar {\n display: flex;\n gap: 8px;\n justify-content: flex-end;\n margin-bottom: 12px;\n }\n\n .collapse-all-btn,\n .expand-all-btn {\n padding: 4px 12px;\n border: 1px solid #d0d7de;\n border-radius: 4px;\n background: none;\n cursor: pointer;\n font-size: 12px;\n color: #586069;\n transition: all 0.2s;\n }\n\n .collapse-all-btn:hover,\n .expand-all-btn:hover {\n background: #f6f8fa;\n color: #24292e;\n }\n\n /* Line change count badges */\n .summary-badges {\n display: inline-flex;\n gap: 6px;\n margin-left: auto;\n }\n\n .line-badge {\n display: inline-block;\n padding: 1px 8px;\n border-radius: 10px;\n font-size: 11px;\n font-weight: 600;\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace;\n line-height: 18px;\n }\n\n .line-added {\n background: #d4edda;\n color: #155724;\n }\n\n .line-removed {\n background: #f8d7da;\n color: #721c24;\n }\n\n /* Diff toolbar */\n .diff-toolbar {\n display: flex;\n justify-content: flex-end;\n padding: 8px 16px;\n border-bottom: 1px solid #d0d7de;\n background: #f6f8fa;\n }\n\n .copy-diff-btn {\n padding: 4px 12px;\n border: 1px solid #d0d7de;\n border-radius: 6px;\n background: white;\n cursor: pointer;\n font-size: 12px;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif;\n color: #24292e;\n transition: all 0.2s;\n }\n\n .copy-diff-btn:hover {\n background: #f3f4f6;\n border-color: #b0b7be;\n }\n\n .copy-diff-btn.copied {\n background: #d4edda;\n border-color: #28a745;\n color: #155724;\n }\n\n /* Sidebar search */\n .sidebar-search {\n width: 100%;\n padding: 6px 8px;\n border: 1px solid #d0d7de;\n border-radius: 4px;\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace;\n font-size: 12px;\n margin-bottom: 8px;\n box-sizing: border-box;\n position: sticky;\n top: 0;\n z-index: 1;\n background: #fff;\n }\n\n .sidebar-search:focus {\n outline: none;\n border-color: #0969da;\n box-shadow: 0 0 0 3px rgba(9,105,218,0.15);\n }\n\n /* Stats toggle button */\n .stats-toggle-btn {\n padding: 4px 12px;\n border: 1px solid #d0d7de;\n border-radius: 4px;\n background: none;\n cursor: pointer;\n font-size: 12px;\n color: #586069;\n transition: all 0.2s;\n }\n\n .stats-toggle-btn:hover {\n background: #f6f8fa;\n color: #24292e;\n }\n\n /* Statistics dashboard */\n .stats-dashboard {\n margin: 15px 0 0;\n padding: 12px 0 0;\n border-top: 1px solid #e1e4e8;\n }\n\n .stats-summary {\n display: flex;\n gap: 16px;\n align-items: center;\n margin-bottom: 10px;\n }\n\n .stats-summary .total-added {\n font-weight: 700;\n color: #155724;\n font-size: 16px;\n }\n\n .stats-summary .total-removed {\n font-weight: 700;\n color: #721c24;\n font-size: 16px;\n }\n\n .stats-bar {\n display: flex;\n height: 8px;\n border-radius: 4px;\n overflow: hidden;\n background: #e1e4e8;\n margin-bottom: 10px;\n }\n\n .stats-segment {\n height: 100%;\n min-width: 2px;\n }\n\n .stats-segment.added-segment {\n background: #28a745;\n }\n\n .stats-segment.removed-segment {\n background: #d73a49;\n }\n\n .top-changed-files {\n list-style: none;\n padding: 0;\n margin: 0;\n font-size: 13px;\n }\n\n .top-changed-files li {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 3px 0;\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace;\n color: #586069;\n }\n\n .top-changed-files .file-path {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n margin-right: 8px;\n }\n\n .top-changed-files .file-stats {\n white-space: nowrap;\n flex-shrink: 0;\n }\n\n /* Stop rules violations badge */\n .stat.violations { background: #f8d7da; color: #721c24; }\n\n /* Violations section */\n .violations-section {\n margin: 15px 0 0;\n padding: 12px 0 0;\n border-top: 1px solid #e1e4e8;\n }\n\n .violations-toggle-btn {\n padding: 4px 12px;\n border: 1px solid #d0d7de;\n border-radius: 4px;\n background: none;\n cursor: pointer;\n font-size: 12px;\n color: #586069;\n transition: all 0.2s;\n }\n\n .violations-toggle-btn:hover {\n background: #f6f8fa;\n color: #24292e;\n }\n\n .violations-table {\n width: 100%;\n border-collapse: collapse;\n font-size: 13px;\n margin-top: 10px;\n }\n\n .violations-table th {\n background: #f6f8fa;\n text-align: left;\n padding: 8px 12px;\n border-bottom: 2px solid #d0d7de;\n font-weight: 600;\n color: #24292e;\n }\n\n .violations-table td {\n padding: 8px 12px;\n border-bottom: 1px solid #e1e4e8;\n color: #24292e;\n vertical-align: top;\n }\n\n .violations-table tr:hover td {\n background: #f6f8fa;\n }\n\n .violation-rule-badge {\n display: inline-block;\n padding: 2px 8px;\n border-radius: 10px;\n font-size: 11px;\n font-weight: 600;\n background: #fff3cd;\n color: #856404;\n white-space: nowrap;\n }\n\n .violation-value {\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace;\n font-size: 12px;\n background: #f6f8fa;\n padding: 2px 6px;\n border-radius: 3px;\n border: 1px solid #e1e4e8;\n }\n";
|
|
3
3
|
export declare const TAB_SCRIPT: string;
|
|
@@ -119,6 +119,40 @@ exports.HTML_STYLES = `
|
|
|
119
119
|
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
|
|
120
120
|
font-size: 13px;
|
|
121
121
|
color: #24292e;
|
|
122
|
+
display: flex;
|
|
123
|
+
align-items: center;
|
|
124
|
+
gap: 6px;
|
|
125
|
+
list-style: none;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.file-section summary::-webkit-details-marker {
|
|
129
|
+
display: none;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.summary-expand-icon::before {
|
|
133
|
+
content: '\u25B6';
|
|
134
|
+
font-size: 10px;
|
|
135
|
+
color: #6a737d;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.file-section[open] > summary .summary-expand-icon::before {
|
|
139
|
+
content: '\u25BC';
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.jump-to-sidebar-link {
|
|
143
|
+
display: inline-flex;
|
|
144
|
+
align-items: center;
|
|
145
|
+
color: #6a737d;
|
|
146
|
+
text-decoration: none;
|
|
147
|
+
padding: 2px 3px;
|
|
148
|
+
border-radius: 3px;
|
|
149
|
+
flex-shrink: 0;
|
|
150
|
+
line-height: 1;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.jump-to-sidebar-link:hover {
|
|
154
|
+
color: #0969da;
|
|
155
|
+
background: rgba(9, 105, 218, 0.08);
|
|
122
156
|
}
|
|
123
157
|
|
|
124
158
|
.file-section summary:hover {
|
|
@@ -445,10 +479,9 @@ exports.HTML_STYLES = `
|
|
|
445
479
|
|
|
446
480
|
/* Line change count badges */
|
|
447
481
|
.summary-badges {
|
|
448
|
-
float: right;
|
|
449
482
|
display: inline-flex;
|
|
450
483
|
gap: 6px;
|
|
451
|
-
margin-left:
|
|
484
|
+
margin-left: auto;
|
|
452
485
|
}
|
|
453
486
|
|
|
454
487
|
.line-badge {
|
|
@@ -770,6 +803,37 @@ exports.TAB_SCRIPT = String.raw `
|
|
|
770
803
|
});
|
|
771
804
|
});
|
|
772
805
|
|
|
806
|
+
// Jump-to-sidebar link in file block headers
|
|
807
|
+
document.querySelectorAll('.jump-to-sidebar-link').forEach(link => {
|
|
808
|
+
link.addEventListener('click', (e) => {
|
|
809
|
+
e.preventDefault();
|
|
810
|
+
e.stopPropagation(); // prevent <details> toggle
|
|
811
|
+
const fileId = link.getAttribute('data-file-id');
|
|
812
|
+
const tabContent = link.closest('.tab-content');
|
|
813
|
+
if (!tabContent || !fileId) return;
|
|
814
|
+
|
|
815
|
+
const treeFile = tabContent.querySelector('.tree-file[data-file-id="' + fileId + '"]');
|
|
816
|
+
if (!treeFile) return;
|
|
817
|
+
|
|
818
|
+
// Expand any collapsed parent folders
|
|
819
|
+
let el = treeFile.parentElement;
|
|
820
|
+
while (el) {
|
|
821
|
+
if (el.classList.contains('tree-children')) {
|
|
822
|
+
el.style.display = '';
|
|
823
|
+
if (el.parentElement) el.parentElement.classList.remove('collapsed');
|
|
824
|
+
}
|
|
825
|
+
el = el.parentElement;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
// Highlight in sidebar
|
|
829
|
+
tabContent.querySelectorAll('.sidebar-tree .tree-file').forEach(f => f.classList.remove('active'));
|
|
830
|
+
treeFile.classList.add('active');
|
|
831
|
+
|
|
832
|
+
// Scroll sidebar to the file entry
|
|
833
|
+
treeFile.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
834
|
+
});
|
|
835
|
+
});
|
|
836
|
+
|
|
773
837
|
// IntersectionObserver to highlight current file on scroll
|
|
774
838
|
const fileSections = document.querySelectorAll('.file-section[id]');
|
|
775
839
|
if (fileSections.length > 0 && 'IntersectionObserver' in window) {
|
|
@@ -144,6 +144,10 @@ const generateHtmlTemplate = (diffResult, formattedFiles, trulyUnchangedFiles, m
|
|
|
144
144
|
</aside>
|
|
145
145
|
<button class="sidebar-expand-btn" data-sidebar="added">▶</button>
|
|
146
146
|
<div class="added-content">
|
|
147
|
+
<div class="content-toolbar">
|
|
148
|
+
<button class="collapse-all-btn">Collapse All</button>
|
|
149
|
+
<button class="expand-all-btn">Expand All</button>
|
|
150
|
+
</div>
|
|
147
151
|
${addedSections.join('\n')}
|
|
148
152
|
</div>
|
|
149
153
|
</div>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type SimpleGit } from 'simple-git';
|
|
2
|
+
import type { FileMap } from '../pipeline';
|
|
3
|
+
export declare const isGitFilterError: (error: unknown) => error is {
|
|
4
|
+
[key: string]: unknown;
|
|
5
|
+
readonly code?: string;
|
|
6
|
+
readonly path?: string;
|
|
7
|
+
readonly cause?: Error;
|
|
8
|
+
readonly hints?: string[];
|
|
9
|
+
name: string;
|
|
10
|
+
message: string;
|
|
11
|
+
stack?: string;
|
|
12
|
+
};
|
|
13
|
+
export declare const getGitUser: (git?: SimpleGit) => Promise<string>;
|
|
14
|
+
export declare const filterFileMapsByGitAuthor: (sourceFiles: FileMap, destinationFiles: FileMap, absoluteSourceDirectory: string, author: string, days: number) => Promise<{
|
|
15
|
+
sourceFiles: FileMap;
|
|
16
|
+
destinationFiles: FileMap;
|
|
17
|
+
}>;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.filterFileMapsByGitAuthor = exports.getGitUser = exports.isGitFilterError = void 0;
|
|
7
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
+
const simple_git_1 = __importDefault(require("simple-git"));
|
|
9
|
+
const errors_1 = require("./errors");
|
|
10
|
+
const GitFilterError = (0, errors_1.createErrorClass)('GitFilterError', {
|
|
11
|
+
NOT_GIT_REPO: 'Current directory is not a git repository.',
|
|
12
|
+
NO_GIT_USER: 'No git user configured. Run: git config user.name "Your Name"',
|
|
13
|
+
GIT_COMMAND_FAILED: 'Git command failed'
|
|
14
|
+
});
|
|
15
|
+
exports.isGitFilterError = (0, errors_1.createErrorTypeGuard)(GitFilterError);
|
|
16
|
+
const getGitRoot = async (git) => {
|
|
17
|
+
const result = await git.revparse(['--show-toplevel']);
|
|
18
|
+
return result.trim();
|
|
19
|
+
};
|
|
20
|
+
const getGitUser = async (git) => {
|
|
21
|
+
const g = git ?? (0, simple_git_1.default)();
|
|
22
|
+
try {
|
|
23
|
+
const nameResult = await g.raw(['config', 'user.name']);
|
|
24
|
+
const name = nameResult.trim();
|
|
25
|
+
if (name)
|
|
26
|
+
return name;
|
|
27
|
+
const emailResult = await g.raw(['config', 'user.email']);
|
|
28
|
+
const email = emailResult.trim();
|
|
29
|
+
if (email)
|
|
30
|
+
return email;
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
if (error instanceof Error && error.message.toLowerCase().includes('not a git repository'))
|
|
34
|
+
throw new GitFilterError('Current directory is not a git repository.', { code: 'NOT_GIT_REPO' });
|
|
35
|
+
throw new GitFilterError('Git command failed', { code: 'GIT_COMMAND_FAILED', cause: error });
|
|
36
|
+
}
|
|
37
|
+
throw new GitFilterError('No git user configured. Run: git config user.name "Your Name"', { code: 'NO_GIT_USER' });
|
|
38
|
+
};
|
|
39
|
+
exports.getGitUser = getGitUser;
|
|
40
|
+
const getGitModifiedPaths = async (git, author, days, absoluteSourceDirectory) => {
|
|
41
|
+
try {
|
|
42
|
+
const output = await git.raw([
|
|
43
|
+
'log',
|
|
44
|
+
`--author=${author}`,
|
|
45
|
+
`--since=${days} days ago`,
|
|
46
|
+
'--name-only',
|
|
47
|
+
'--pretty=format:',
|
|
48
|
+
'--',
|
|
49
|
+
absoluteSourceDirectory
|
|
50
|
+
]);
|
|
51
|
+
const paths = new Set();
|
|
52
|
+
for (const line of output.split('\n')) {
|
|
53
|
+
const trimmed = line.trim();
|
|
54
|
+
if (trimmed)
|
|
55
|
+
paths.add(trimmed);
|
|
56
|
+
}
|
|
57
|
+
return paths;
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
if (error instanceof Error && error.message.toLowerCase().includes('not a git repository'))
|
|
61
|
+
throw new GitFilterError('Current directory is not a git repository.', { code: 'NOT_GIT_REPO' });
|
|
62
|
+
throw new GitFilterError('Git command failed', { code: 'GIT_COMMAND_FAILED', cause: error });
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
const filterFileMapsByGitAuthor = async (sourceFiles, destinationFiles, absoluteSourceDirectory, author, days) => {
|
|
66
|
+
const git = (0, simple_git_1.default)();
|
|
67
|
+
const gitRoot = await getGitRoot(git);
|
|
68
|
+
const gitModifiedPaths = await getGitModifiedPaths(git, author, days, absoluteSourceDirectory);
|
|
69
|
+
const matchingKeys = new Set();
|
|
70
|
+
for (const gitRelativePath of gitModifiedPaths) {
|
|
71
|
+
const absolutePath = node_path_1.default.join(gitRoot, gitRelativePath);
|
|
72
|
+
const fileMapKey = node_path_1.default.relative(absoluteSourceDirectory, absolutePath).replaceAll(node_path_1.default.sep, '/');
|
|
73
|
+
matchingKeys.add(fileMapKey);
|
|
74
|
+
}
|
|
75
|
+
const filteredSource = new Map();
|
|
76
|
+
const filteredDestination = new Map();
|
|
77
|
+
for (const [filePath, content] of sourceFiles)
|
|
78
|
+
if (matchingKeys.has(filePath))
|
|
79
|
+
filteredSource.set(filePath, content);
|
|
80
|
+
for (const [filePath, content] of destinationFiles)
|
|
81
|
+
if (matchingKeys.has(filePath))
|
|
82
|
+
filteredDestination.set(filePath, content);
|
|
83
|
+
return { sourceFiles: filteredSource, destinationFiles: filteredDestination };
|
|
84
|
+
};
|
|
85
|
+
exports.filterFileMapsByGitAuthor = filterFileMapsByGitAuthor;
|
|
@@ -20,6 +20,36 @@ const getAllValuesRecursive = (data) => {
|
|
|
20
20
|
return values;
|
|
21
21
|
};
|
|
22
22
|
exports.getAllValuesRecursive = getAllValuesRecursive;
|
|
23
|
+
const getAllValuesWithPaths = (data) => {
|
|
24
|
+
const results = [];
|
|
25
|
+
const traverse = (node, currentPath) => {
|
|
26
|
+
if (node === null || node === undefined)
|
|
27
|
+
return;
|
|
28
|
+
if (typeof node === 'object')
|
|
29
|
+
if (Array.isArray(node))
|
|
30
|
+
for (const [index, item] of node.entries())
|
|
31
|
+
traverse(item, currentPath ? `${currentPath}.${index}` : String(index));
|
|
32
|
+
else
|
|
33
|
+
for (const [key, value] of Object.entries(node))
|
|
34
|
+
traverse(value, currentPath ? `${currentPath}.${key}` : key);
|
|
35
|
+
else
|
|
36
|
+
results.push({ path: currentPath, value: node });
|
|
37
|
+
};
|
|
38
|
+
traverse(data, '');
|
|
39
|
+
return results;
|
|
40
|
+
};
|
|
41
|
+
const getValueAtDotPath = (data, dotPath) => {
|
|
42
|
+
if (!dotPath)
|
|
43
|
+
return data;
|
|
44
|
+
const parts = dotPath.split('.');
|
|
45
|
+
let current = data;
|
|
46
|
+
for (const part of parts) {
|
|
47
|
+
if (current === null || current === undefined || typeof current !== 'object')
|
|
48
|
+
return undefined;
|
|
49
|
+
current = current[part];
|
|
50
|
+
}
|
|
51
|
+
return current;
|
|
52
|
+
};
|
|
23
53
|
const validateTargetedRegex = (options) => {
|
|
24
54
|
const valueToCheck = options.updatedValue === undefined ? options.oldValue : options.updatedValue;
|
|
25
55
|
if (valueToCheck === undefined)
|
|
@@ -46,9 +76,9 @@ const validatePathlessRegex = (options) => {
|
|
|
46
76
|
const dataToCheck = options.updatedData === undefined ? options.oldData : options.updatedData;
|
|
47
77
|
if (dataToCheck === undefined)
|
|
48
78
|
return undefined;
|
|
49
|
-
const
|
|
50
|
-
for (const
|
|
51
|
-
const stringValue = String(value);
|
|
79
|
+
const allEntries = getAllValuesWithPaths(dataToCheck);
|
|
80
|
+
for (const entry of allEntries) {
|
|
81
|
+
const stringValue = String(entry.value);
|
|
52
82
|
for (const patternString of options.patterns) {
|
|
53
83
|
const pattern = new RegExp(patternString);
|
|
54
84
|
if (pattern.test(stringValue)) {
|
|
@@ -58,9 +88,9 @@ const validatePathlessRegex = (options) => {
|
|
|
58
88
|
return {
|
|
59
89
|
file: options.filePath,
|
|
60
90
|
rule: options.rule,
|
|
61
|
-
path:
|
|
62
|
-
oldValue: options.oldData,
|
|
63
|
-
updatedValue:
|
|
91
|
+
path: entry.path,
|
|
92
|
+
oldValue: getValueAtDotPath(options.oldData, entry.path),
|
|
93
|
+
updatedValue: entry.value,
|
|
64
94
|
message: `Value "${stringValue}" matches forbidden pattern${patternInfo} (found during global scan)`
|
|
65
95
|
};
|
|
66
96
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "helm-env-delta",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.15.0",
|
|
4
4
|
"description": "HelmEnvDelta β environment-aware YAML delta and sync for GitOps",
|
|
5
5
|
"author": "BCsabaEngine",
|
|
6
6
|
"license": "ISC",
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"@eslint/js": "^10.0.1",
|
|
68
68
|
"@types/node": "^25.5.0",
|
|
69
69
|
"@types/picomatch": "^4.0.2",
|
|
70
|
-
"@typescript-eslint/eslint-plugin": "^8.57.
|
|
70
|
+
"@typescript-eslint/eslint-plugin": "^8.57.1",
|
|
71
71
|
"@vitest/coverage-v8": "^4.1.0",
|
|
72
72
|
"eslint": "^10.0.3",
|
|
73
73
|
"eslint-config-prettier": "^10.1.8",
|
|
@@ -85,6 +85,7 @@
|
|
|
85
85
|
"diff2html": "3.4.56",
|
|
86
86
|
"open": "^11.0.0",
|
|
87
87
|
"picomatch": "^4.0.3",
|
|
88
|
+
"simple-git": "^3.33.0",
|
|
88
89
|
"tinyglobby": "^0.2.15",
|
|
89
90
|
"yaml": "^2.8.2",
|
|
90
91
|
"zod": "^4.3.6"
|