helm-env-delta 1.9.3 → 1.10.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 +54 -40
- package/dist/commandLine.d.ts +3 -0
- package/dist/commandLine.js +25 -7
- package/dist/consoleDiffReporter.js +10 -63
- package/dist/fileDiff.d.ts +7 -1
- package/dist/fileDiff.js +31 -5
- package/dist/fileUpdater.js +4 -4
- package/dist/htmlReporter.js +28 -1
- package/dist/index.js +21 -6
- package/dist/jsonReporter.d.ts +6 -1
- package/dist/jsonReporter.js +6 -1
- package/dist/reporters/htmlStyles.d.ts +1 -1
- package/dist/reporters/htmlStyles.js +175 -26
- package/dist/reporters/htmlTemplate.d.ts +1 -1
- package/dist/reporters/htmlTemplate.js +18 -5
- package/dist/reporters/treeRenderer.d.ts +1 -0
- package/dist/reporters/treeRenderer.js +10 -9
- package/dist/utils/fileFilter.d.ts +9 -0
- package/dist/utils/fileFilter.js +50 -0
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.js +5 -4
- package/package.json +2 -2
- package/dist/utils/arrayDiffProcessor.d.ts +0 -14
- package/dist/utils/arrayDiffProcessor.js +0 -43
package/README.md
CHANGED
|
@@ -56,7 +56,7 @@ HelmEnvDelta (`hed`) automates environment synchronization for GitOps workflows
|
|
|
56
56
|
|
|
57
57
|
📊 **Multiple Reports** - Console, HTML (visual), and JSON (CI/CD) output formats.
|
|
58
58
|
|
|
59
|
-
🔍 **Discovery Tools** - Preview files (
|
|
59
|
+
🔍 **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.
|
|
60
60
|
|
|
61
61
|
💡 **Smart Suggestions** - Heuristic analysis (`--suggest`) detects patterns and recommends transforms and stop rules automatically. Control sensitivity with `--suggest-threshold`.
|
|
62
62
|
|
|
@@ -102,28 +102,28 @@ transforms:
|
|
|
102
102
|
### 2️⃣ Preview Changes
|
|
103
103
|
|
|
104
104
|
```bash
|
|
105
|
-
|
|
105
|
+
hed -c config.yaml -D -d
|
|
106
106
|
```
|
|
107
107
|
|
|
108
108
|
### 3️⃣ Execute Sync
|
|
109
109
|
|
|
110
110
|
```bash
|
|
111
|
-
|
|
111
|
+
hed -c config.yaml
|
|
112
112
|
```
|
|
113
113
|
|
|
114
114
|
### 4️⃣ Review in Browser
|
|
115
115
|
|
|
116
116
|
```bash
|
|
117
|
-
|
|
117
|
+
hed -c config.yaml -H
|
|
118
118
|
```
|
|
119
119
|
|
|
120
120
|
### 5️⃣ Get Smart Suggestions (Optional)
|
|
121
121
|
|
|
122
122
|
```bash
|
|
123
|
-
|
|
123
|
+
hed -c config.yaml --suggest
|
|
124
124
|
|
|
125
125
|
# Control suggestion sensitivity (0-1, default: 0.3)
|
|
126
|
-
|
|
126
|
+
hed -c config.yaml --suggest --suggest-threshold 0.7
|
|
127
127
|
```
|
|
128
128
|
|
|
129
129
|
Analyzes differences and suggests transforms and stop rules automatically with configurable confidence filtering.
|
|
@@ -753,66 +753,80 @@ hed --config <file> [options] # Short alias
|
|
|
753
753
|
|
|
754
754
|
### Options
|
|
755
755
|
|
|
756
|
-
| Flag | Description |
|
|
757
|
-
| --------------------------- | ------------------------------------------------------------------- |
|
|
758
|
-
| `--config <path>` | **Required** - Configuration file |
|
|
759
|
-
| `--validate` | Validate config and pattern usage (shows warnings) |
|
|
760
|
-
| `--suggest` | Analyze differences and suggest config updates |
|
|
761
|
-
| `--suggest-threshold <0-1>` | Minimum confidence for suggestions (default: 0.3) |
|
|
762
|
-
| `--dry-run` | Preview changes without writing files |
|
|
763
|
-
| `--force` | Override stop rules |
|
|
764
|
-
| `--diff` | Show console diff |
|
|
765
|
-
| `--diff-html` | Generate HTML report (opens in browser) |
|
|
766
|
-
| `--diff-json` | Output JSON to stdout (pipe to jq) |
|
|
767
|
-
| `--list-files` | List files without processing (takes precedence over --format-only) |
|
|
768
|
-
| `--show-config` | Display resolved config after inheritance |
|
|
769
|
-
| `--format-only` | Format destination files only (source not required) |
|
|
770
|
-
| `--skip-format` | Skip YAML formatting during sync |
|
|
771
|
-
| `--
|
|
772
|
-
| `--
|
|
773
|
-
| `--
|
|
756
|
+
| Flag | Short | Description |
|
|
757
|
+
| --------------------------- | ----- | ------------------------------------------------------------------- |
|
|
758
|
+
| `--config <path>` | `-c` | **Required** - Configuration file |
|
|
759
|
+
| `--validate` | | Validate config and pattern usage (shows warnings) |
|
|
760
|
+
| `--suggest` | | Analyze differences and suggest config updates |
|
|
761
|
+
| `--suggest-threshold <0-1>` | | Minimum confidence for suggestions (default: 0.3) |
|
|
762
|
+
| `--dry-run` | `-D` | Preview changes without writing files |
|
|
763
|
+
| `--force` | | Override stop rules |
|
|
764
|
+
| `--diff` | `-d` | Show console diff |
|
|
765
|
+
| `--diff-html` | `-H` | Generate HTML report (opens in browser) |
|
|
766
|
+
| `--diff-json` | `-J` | Output JSON to stdout (pipe to jq) |
|
|
767
|
+
| `--list-files` | `-l` | List files without processing (takes precedence over --format-only) |
|
|
768
|
+
| `--show-config` | | Display resolved config after inheritance |
|
|
769
|
+
| `--format-only` | | Format destination files only (source not required) |
|
|
770
|
+
| `--skip-format` | `-S` | Skip YAML formatting during sync |
|
|
771
|
+
| `--filter <string>` | `-f` | Filter files by filename or content (case-insensitive) |
|
|
772
|
+
| `--mode <type>` | `-m` | Filter by change type: new, modified, deleted, all (default: all) |
|
|
773
|
+
| `--no-color` | | Disable colored output (CI/accessibility) |
|
|
774
|
+
| `--verbose` | | Show detailed debug info |
|
|
775
|
+
| `--quiet` | | Suppress output except errors |
|
|
774
776
|
|
|
775
777
|
### Examples
|
|
776
778
|
|
|
777
779
|
```bash
|
|
778
780
|
# Validate configuration (shows warnings)
|
|
779
|
-
hed
|
|
781
|
+
hed -c config.yaml --validate
|
|
780
782
|
|
|
781
783
|
# Get smart configuration suggestions
|
|
782
|
-
hed
|
|
784
|
+
hed -c config.yaml --suggest
|
|
783
785
|
|
|
784
786
|
# Get only high-confidence suggestions
|
|
785
|
-
hed
|
|
787
|
+
hed -c config.yaml --suggest --suggest-threshold 0.7
|
|
786
788
|
|
|
787
789
|
# Preview files that will be synced
|
|
788
|
-
hed
|
|
790
|
+
hed -c config.yaml -l
|
|
789
791
|
|
|
790
792
|
# Display resolved config (after inheritance)
|
|
791
|
-
hed
|
|
793
|
+
hed -c config.yaml --show-config
|
|
792
794
|
|
|
793
795
|
# Preview with diff
|
|
794
|
-
hed
|
|
796
|
+
hed -c config.yaml -D -d
|
|
795
797
|
|
|
796
798
|
# Visual HTML report
|
|
797
|
-
hed
|
|
799
|
+
hed -c config.yaml -H
|
|
798
800
|
|
|
799
801
|
# CI/CD integration (no colors)
|
|
800
|
-
hed
|
|
802
|
+
hed -c config.yaml -J --no-color | jq '.summary'
|
|
801
803
|
|
|
802
804
|
# Execute sync
|
|
803
|
-
hed
|
|
805
|
+
hed -c config.yaml
|
|
804
806
|
|
|
805
807
|
# Force override stop rules
|
|
806
|
-
hed
|
|
808
|
+
hed -c config.yaml --force
|
|
809
|
+
|
|
810
|
+
# Filter to only process files matching 'prod'
|
|
811
|
+
hed -c config.yaml -f prod -d
|
|
812
|
+
|
|
813
|
+
# Sync only new files
|
|
814
|
+
hed -c config.yaml -m new
|
|
815
|
+
|
|
816
|
+
# Preview modified files only
|
|
817
|
+
hed -c config.yaml -m modified -D -d
|
|
818
|
+
|
|
819
|
+
# Combine filter and mode
|
|
820
|
+
hed -c config.yaml -f deployment -m modified -D -d
|
|
807
821
|
|
|
808
822
|
# Format destination files only (no sync, source not required in config)
|
|
809
|
-
hed
|
|
823
|
+
hed -c config.yaml --format-only
|
|
810
824
|
|
|
811
825
|
# Preview format changes
|
|
812
|
-
hed
|
|
826
|
+
hed -c config.yaml --format-only -D
|
|
813
827
|
|
|
814
828
|
# List files that would be formatted (--list-files takes precedence)
|
|
815
|
-
hed
|
|
829
|
+
hed -c config.yaml --format-only -l
|
|
816
830
|
|
|
817
831
|
# Format-only config example (no source needed):
|
|
818
832
|
# destination: './prod'
|
|
@@ -841,13 +855,13 @@ flowchart LR
|
|
|
841
855
|
|
|
842
856
|
```bash
|
|
843
857
|
# 1. Preview changes
|
|
844
|
-
hed
|
|
858
|
+
hed -c config.yaml -D -d
|
|
845
859
|
|
|
846
860
|
# 2. Review in browser
|
|
847
|
-
hed
|
|
861
|
+
hed -c config.yaml -H
|
|
848
862
|
|
|
849
863
|
# 3. Execute sync
|
|
850
|
-
hed
|
|
864
|
+
hed -c config.yaml
|
|
851
865
|
|
|
852
866
|
# 4. Git workflow
|
|
853
867
|
git add prod/
|
package/dist/commandLine.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export type ChangeMode = 'new' | 'modified' | 'deleted' | 'all';
|
|
1
2
|
export type SyncCommand = {
|
|
2
3
|
config: string;
|
|
3
4
|
dryRun: boolean;
|
|
@@ -15,5 +16,7 @@ export type SyncCommand = {
|
|
|
15
16
|
noColor: boolean;
|
|
16
17
|
suggest: boolean;
|
|
17
18
|
suggestThreshold: number;
|
|
19
|
+
filter?: string;
|
|
20
|
+
mode: ChangeMode;
|
|
18
21
|
};
|
|
19
22
|
export declare const parseCommandLine: (argv?: string[]) => SyncCommand;
|
package/dist/commandLine.js
CHANGED
|
@@ -13,19 +13,21 @@ const parseCommandLine = (argv) => {
|
|
|
13
13
|
.description('Environment-aware YAML delta and sync for GitOps workflows')
|
|
14
14
|
.version(package_json_1.default.version)
|
|
15
15
|
.requiredOption('-c, --config <file>', 'Path to YAML configuration file')
|
|
16
|
-
.option('--dry-run', 'Preview changes without writing files', false)
|
|
16
|
+
.option('-D, --dry-run', 'Preview changes without writing files', false)
|
|
17
17
|
.option('--force', 'Override stop rules and proceed with changes', false)
|
|
18
|
-
.option('--diff', 'Display console diff for changed files', false)
|
|
19
|
-
.option('--diff-html', 'Generate and open HTML diff report in browser', false)
|
|
20
|
-
.option('--diff-json', 'Output diff as JSON to stdout', false)
|
|
21
|
-
.option('--skip-format', 'Skip YAML formatting (outputFormat section)', false)
|
|
18
|
+
.option('-d, --diff', 'Display console diff for changed files', false)
|
|
19
|
+
.option('-H, --diff-html', 'Generate and open HTML diff report in browser', false)
|
|
20
|
+
.option('-J, --diff-json', 'Output diff as JSON to stdout', false)
|
|
21
|
+
.option('-S, --skip-format', 'Skip YAML formatting (outputFormat section)', false)
|
|
22
22
|
.option('--format-only', 'Format YAML files in destination without syncing', false)
|
|
23
23
|
.option('--validate', 'Validate configuration file and exit', false)
|
|
24
|
-
.option('--list-files', 'List files that would be synced without processing diffs', false)
|
|
24
|
+
.option('-l, --list-files', 'List files that would be synced without processing diffs', false)
|
|
25
25
|
.option('--show-config', 'Display resolved configuration after inheritance and exit', false)
|
|
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 (case-insensitive)')
|
|
30
|
+
.option('-m, --mode <type>', 'Filter by change type: new, modified, deleted, all', 'all')
|
|
29
31
|
.option('--verbose', 'Show detailed debug information', false)
|
|
30
32
|
.option('--quiet', 'Suppress all output except critical errors', false)
|
|
31
33
|
.addHelpText('after', `
|
|
@@ -45,6 +47,15 @@ Examples:
|
|
|
45
47
|
# CI/CD usage with JSON output
|
|
46
48
|
$ helm-env-delta --config config.yaml --diff-json | jq '.summary'
|
|
47
49
|
|
|
50
|
+
# Filter to only process files matching 'prod'
|
|
51
|
+
$ helm-env-delta --config config.yaml -f prod --diff
|
|
52
|
+
|
|
53
|
+
# Sync only new files
|
|
54
|
+
$ helm-env-delta --config config.yaml --mode new
|
|
55
|
+
|
|
56
|
+
# Preview modified files only
|
|
57
|
+
$ helm-env-delta --config config.yaml --mode modified --dry-run --diff
|
|
58
|
+
|
|
48
59
|
Documentation: https://github.com/balazscsaba2006/helm-env-delta
|
|
49
60
|
`);
|
|
50
61
|
program.showSuggestionAfterError(true);
|
|
@@ -63,6 +74,11 @@ Documentation: https://github.com/balazscsaba2006/helm-env-delta
|
|
|
63
74
|
console.error('Error: --suggest-threshold must be a number between 0 and 1');
|
|
64
75
|
process.exit(1);
|
|
65
76
|
}
|
|
77
|
+
const validModes = ['new', 'modified', 'deleted', 'all'];
|
|
78
|
+
if (!validModes.includes(options['mode'])) {
|
|
79
|
+
console.error('Error: --mode must be one of: ' + validModes.join(', '));
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
66
82
|
return {
|
|
67
83
|
config: options['config'],
|
|
68
84
|
dryRun: options['dryRun'],
|
|
@@ -79,7 +95,9 @@ Documentation: https://github.com/balazscsaba2006/helm-env-delta
|
|
|
79
95
|
verbose: options['verbose'],
|
|
80
96
|
quiet: options['quiet'],
|
|
81
97
|
suggest: options['suggest'],
|
|
82
|
-
suggestThreshold: threshold
|
|
98
|
+
suggestThreshold: threshold,
|
|
99
|
+
filter: options['filter'],
|
|
100
|
+
mode: options['mode']
|
|
83
101
|
};
|
|
84
102
|
};
|
|
85
103
|
exports.parseCommandLine = parseCommandLine;
|
|
@@ -5,9 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.showConsoleDiff = void 0;
|
|
7
7
|
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
-
const yaml_1 = __importDefault(require("yaml"));
|
|
9
8
|
const fileDiff_1 = require("./fileDiff");
|
|
10
|
-
const arrayDiffProcessor_1 = require("./utils/arrayDiffProcessor");
|
|
11
9
|
const diffGenerator_1 = require("./utils/diffGenerator");
|
|
12
10
|
const fileType_1 = require("./utils/fileType");
|
|
13
11
|
const serialization_1 = require("./utils/serialization");
|
|
@@ -29,7 +27,7 @@ const formatAddedFiles = (files) => {
|
|
|
29
27
|
if (files.length === 0)
|
|
30
28
|
return '';
|
|
31
29
|
const header = chalk_1.default.green.bold(`\nAdded Files (${files.length}):`);
|
|
32
|
-
const fileList = files.map((file) => chalk_1.default.green(` + ${file}`)).join('\n');
|
|
30
|
+
const fileList = files.map((file) => chalk_1.default.green(` + ${file.path}`)).join('\n');
|
|
33
31
|
return `${header}\n${fileList}\n`;
|
|
34
32
|
};
|
|
35
33
|
const formatDeletedFiles = (files) => {
|
|
@@ -39,30 +37,6 @@ const formatDeletedFiles = (files) => {
|
|
|
39
37
|
const fileList = files.map((file) => chalk_1.default.red(` - ${file}`)).join('\n');
|
|
40
38
|
return `${header}\n${fileList}\n`;
|
|
41
39
|
};
|
|
42
|
-
const formatArrayDiff = (change) => {
|
|
43
|
-
let output = '';
|
|
44
|
-
if (change.removed.length > 0) {
|
|
45
|
-
output += chalk_1.default.red.bold(`\n Removed (${change.removed.length}):\n`);
|
|
46
|
-
for (const item of change.removed) {
|
|
47
|
-
const yaml = yaml_1.default.stringify(item, { indent: 4 });
|
|
48
|
-
const lines = yaml.split('\n').filter((l) => l.trim());
|
|
49
|
-
output += lines.map((l) => chalk_1.default.red(` - ${l}`)).join('\n');
|
|
50
|
-
output += '\n';
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
if (change.added.length > 0) {
|
|
54
|
-
output += chalk_1.default.green.bold(`\n Added (${change.added.length}):\n`);
|
|
55
|
-
for (const item of change.added) {
|
|
56
|
-
const yaml = yaml_1.default.stringify(item, { indent: 4 });
|
|
57
|
-
const lines = yaml.split('\n').filter((l) => l.trim());
|
|
58
|
-
output += lines.map((l) => chalk_1.default.green(` + ${l}`)).join('\n');
|
|
59
|
-
output += '\n';
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
if (change.unchanged.length > 0)
|
|
63
|
-
output += chalk_1.default.gray(`\n Unchanged: ${change.unchanged.length} items\n`);
|
|
64
|
-
return output;
|
|
65
|
-
};
|
|
66
40
|
const formatChangedFile = (file, config) => {
|
|
67
41
|
const isYaml = (0, fileType_1.isYamlFile)(file.path);
|
|
68
42
|
const separator = chalk_1.default.yellow('━'.repeat(60));
|
|
@@ -70,48 +44,21 @@ const formatChangedFile = (file, config) => {
|
|
|
70
44
|
const skipPathInfo = skipPaths.length > 0
|
|
71
45
|
? chalk_1.default.dim(`SkipPath patterns applied: ${skipPaths.join(', ')}`)
|
|
72
46
|
: chalk_1.default.dim('No skipPath patterns applied');
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
${colorizedDiff}
|
|
84
|
-
`;
|
|
85
|
-
}
|
|
86
|
-
const arrayInfo = (0, arrayDiffProcessor_1.detectArrayChanges)(file);
|
|
87
|
-
if (!arrayInfo.hasArrays) {
|
|
88
|
-
const destinationContent = (0, serialization_1.serializeForDiff)(file.processedDestContent, true);
|
|
89
|
-
const sourceContent = (0, serialization_1.serializeForDiff)(file.processedSourceContent, true);
|
|
90
|
-
const unifiedDiff = (0, diffGenerator_1.generateUnifiedDiff)(file.path, destinationContent, sourceContent);
|
|
91
|
-
const colorizedDiff = colorizeUnifiedDiff(unifiedDiff);
|
|
92
|
-
return `
|
|
47
|
+
const destinationContent = isYaml
|
|
48
|
+
? (0, serialization_1.serializeForDiff)(file.processedDestContent, true)
|
|
49
|
+
: String(file.processedDestContent);
|
|
50
|
+
const sourceContent = isYaml
|
|
51
|
+
? (0, serialization_1.serializeForDiff)(file.processedSourceContent, true)
|
|
52
|
+
: String(file.processedSourceContent);
|
|
53
|
+
const unifiedDiff = (0, diffGenerator_1.generateUnifiedDiff)(file.path, destinationContent, sourceContent);
|
|
54
|
+
const colorizedDiff = colorizeUnifiedDiff(unifiedDiff);
|
|
55
|
+
return `
|
|
93
56
|
${separator}
|
|
94
57
|
${chalk_1.default.yellow.bold(`File: ${file.path}`)}
|
|
95
58
|
${skipPathInfo}
|
|
96
59
|
|
|
97
60
|
${colorizedDiff}
|
|
98
61
|
`;
|
|
99
|
-
}
|
|
100
|
-
let output = `\n${separator}\n${chalk_1.default.yellow.bold(`File: ${file.path}`)}\n${skipPathInfo}\n`;
|
|
101
|
-
const destinationContent = (0, serialization_1.serializeForDiff)(file.processedDestContent, true);
|
|
102
|
-
const sourceContent = (0, serialization_1.serializeForDiff)(file.processedSourceContent, true);
|
|
103
|
-
const unifiedDiff = (0, diffGenerator_1.generateUnifiedDiff)(file.path, destinationContent, sourceContent);
|
|
104
|
-
const colorizedDiff = colorizeUnifiedDiff(unifiedDiff);
|
|
105
|
-
output += `\n${colorizedDiff}\n`;
|
|
106
|
-
if (arrayInfo.hasChanges) {
|
|
107
|
-
output += chalk_1.default.cyan.bold('\nArray-specific details:\n');
|
|
108
|
-
for (const change of arrayInfo.changes) {
|
|
109
|
-
const pathString = change.path.join('.');
|
|
110
|
-
output += chalk_1.default.cyan(`\n ${pathString}:\n`);
|
|
111
|
-
output += formatArrayDiff(change);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
return output;
|
|
115
62
|
};
|
|
116
63
|
const formatChangedFiles = (files, config) => {
|
|
117
64
|
if (files.length === 0)
|
package/dist/fileDiff.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Config, FixedValueConfig, TransformConfig } from './configFile';
|
|
2
2
|
import { FileMap } from './fileLoader';
|
|
3
3
|
export interface FileDiffResult {
|
|
4
|
-
addedFiles:
|
|
4
|
+
addedFiles: AddedFile[];
|
|
5
5
|
deletedFiles: string[];
|
|
6
6
|
changedFiles: ChangedFile[];
|
|
7
7
|
unchangedFiles: string[];
|
|
@@ -21,6 +21,12 @@ export interface ChangedFile {
|
|
|
21
21
|
parsedSource?: unknown;
|
|
22
22
|
parsedDest?: unknown;
|
|
23
23
|
}
|
|
24
|
+
export interface AddedFile {
|
|
25
|
+
path: string;
|
|
26
|
+
originalPath?: string;
|
|
27
|
+
content: string;
|
|
28
|
+
processedContent: string;
|
|
29
|
+
}
|
|
24
30
|
export interface ProcessYamlOptions {
|
|
25
31
|
filePath: string;
|
|
26
32
|
sourceContent: string;
|
package/dist/fileDiff.js
CHANGED
|
@@ -14,6 +14,7 @@ const jsonPath_1 = require("./utils/jsonPath");
|
|
|
14
14
|
const patternMatcher_1 = require("./utils/patternMatcher");
|
|
15
15
|
const serialization_1 = require("./utils/serialization");
|
|
16
16
|
const transformer_1 = require("./utils/transformer");
|
|
17
|
+
const yamlFormatter_1 = require("./yamlFormatter");
|
|
17
18
|
const FileDiffErrorClass = (0, errors_1.createErrorClass)('File Diff Error', {
|
|
18
19
|
YAML_PARSE_ERROR: 'YAML file could not be parsed'
|
|
19
20
|
});
|
|
@@ -21,11 +22,36 @@ class FileDiffError extends FileDiffErrorClass {
|
|
|
21
22
|
}
|
|
22
23
|
exports.FileDiffError = FileDiffError;
|
|
23
24
|
exports.isFileDiffError = (0, errors_1.createErrorTypeGuard)(FileDiffError);
|
|
24
|
-
const
|
|
25
|
+
const processAddedFileContent = (filePath, content, transforms, fixedValues, outputFormat) => {
|
|
26
|
+
if (!(0, fileType_1.isYamlFile)(filePath))
|
|
27
|
+
return content;
|
|
28
|
+
try {
|
|
29
|
+
const parsed = yaml_1.default.parse(content);
|
|
30
|
+
const transformed = (0, transformer_1.applyTransforms)(parsed, filePath, transforms);
|
|
31
|
+
const fixedValueRules = (0, fixedValues_1.getFixedValuesForFile)(filePath, fixedValues);
|
|
32
|
+
if (fixedValueRules.length > 0)
|
|
33
|
+
(0, fixedValues_1.applyFixedValues)(transformed, fixedValueRules);
|
|
34
|
+
let processed = yaml_1.default.stringify(transformed);
|
|
35
|
+
processed = (0, yamlFormatter_1.formatYaml)(processed, filePath, outputFormat);
|
|
36
|
+
return processed;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return content;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
const detectAddedFiles = (sourceFiles, destinationFiles, config, originalPaths) => {
|
|
25
43
|
const addedFiles = [];
|
|
26
|
-
for (const path of sourceFiles.
|
|
27
|
-
if (!destinationFiles.has(path))
|
|
28
|
-
|
|
44
|
+
for (const [path, content] of sourceFiles.entries())
|
|
45
|
+
if (!destinationFiles.has(path)) {
|
|
46
|
+
const originalPath = originalPaths?.get(path);
|
|
47
|
+
const processedContent = processAddedFileContent(path, content, config.transforms, config.fixedValues, config.outputFormat);
|
|
48
|
+
addedFiles.push({
|
|
49
|
+
path,
|
|
50
|
+
originalPath,
|
|
51
|
+
content,
|
|
52
|
+
processedContent
|
|
53
|
+
});
|
|
54
|
+
}
|
|
29
55
|
return addedFiles;
|
|
30
56
|
};
|
|
31
57
|
const detectDeletedFiles = (sourceFiles, destinationFiles) => {
|
|
@@ -228,7 +254,7 @@ const computeFileDiff = (sourceFiles, destinationFiles, config, logger, original
|
|
|
228
254
|
if (skipPathCount > 0)
|
|
229
255
|
logger.debug(` SkipPath patterns: ${skipPathCount}`);
|
|
230
256
|
}
|
|
231
|
-
const addedFiles = detectAddedFiles(sourceFiles, destinationFiles);
|
|
257
|
+
const addedFiles = detectAddedFiles(sourceFiles, destinationFiles, config, originalPaths);
|
|
232
258
|
const deletedFiles = config.prune ? detectDeletedFiles(sourceFiles, destinationFiles) : [];
|
|
233
259
|
const { changedFiles, unchangedFiles } = processChangedFiles(sourceFiles, destinationFiles, config.skipPath, config.transforms, config.fixedValues, originalPaths);
|
|
234
260
|
return { addedFiles, deletedFiles, changedFiles, unchangedFiles };
|
package/dist/fileUpdater.js
CHANGED
|
@@ -249,11 +249,11 @@ const deleteFile = async (relativePath, absoluteDestinationDirectory, dryRun, lo
|
|
|
249
249
|
const addNewFiles = async (files, sourceFiles, context) => {
|
|
250
250
|
if (context.logger.shouldShow('debug'))
|
|
251
251
|
context.logger.debug(`Processing ${files.length} new files`);
|
|
252
|
-
for (const
|
|
252
|
+
for (const addedFile of files)
|
|
253
253
|
try {
|
|
254
|
-
const content = sourceFiles.get(
|
|
254
|
+
const content = sourceFiles.get(addedFile.path);
|
|
255
255
|
await addFile({
|
|
256
|
-
relativePath,
|
|
256
|
+
relativePath: addedFile.path,
|
|
257
257
|
content,
|
|
258
258
|
absoluteDestinationDirectory: context.absoluteDestinationDirectory,
|
|
259
259
|
config: context.config,
|
|
@@ -263,7 +263,7 @@ const addNewFiles = async (files, sourceFiles, context) => {
|
|
|
263
263
|
});
|
|
264
264
|
}
|
|
265
265
|
catch (error) {
|
|
266
|
-
context.errors.push({ operation: 'add', path:
|
|
266
|
+
context.errors.push({ operation: 'add', path: addedFile.path, error: error });
|
|
267
267
|
}
|
|
268
268
|
};
|
|
269
269
|
const updateChangedFiles = async (files, context) => {
|
package/dist/htmlReporter.js
CHANGED
|
@@ -11,6 +11,7 @@ const node_path_1 = __importDefault(require("node:path"));
|
|
|
11
11
|
const diff2html_1 = require("diff2html");
|
|
12
12
|
const browserLauncher_1 = require("./reporters/browserLauncher");
|
|
13
13
|
const htmlTemplate_1 = require("./reporters/htmlTemplate");
|
|
14
|
+
const treeRenderer_1 = require("./reporters/treeRenderer");
|
|
14
15
|
const diffGenerator_1 = require("./utils/diffGenerator");
|
|
15
16
|
const errors_1 = require("./utils/errors");
|
|
16
17
|
const fileType_1 = require("./utils/fileType");
|
|
@@ -42,6 +43,28 @@ const generateFileSummary = (file) => {
|
|
|
42
43
|
return file.path;
|
|
43
44
|
return `<span class="filename-transform">${file.originalPath} → ${file.path}</span>`;
|
|
44
45
|
};
|
|
46
|
+
const generateAddedFileSummary = (file) => {
|
|
47
|
+
if (!file.originalPath)
|
|
48
|
+
return file.path;
|
|
49
|
+
return `<span class="filename-transform">${file.originalPath} → ${file.path}</span>`;
|
|
50
|
+
};
|
|
51
|
+
const generateAddedFileSection = (file, fileId) => {
|
|
52
|
+
const summary = generateAddedFileSummary(file);
|
|
53
|
+
const escapedContent = (0, treeRenderer_1.escapeHtml)(file.processedContent);
|
|
54
|
+
const filename = file.path.split('/').pop() || file.path;
|
|
55
|
+
return `
|
|
56
|
+
<details class="file-section" id="${fileId}" data-file-id="${fileId}" open>
|
|
57
|
+
<summary>${summary}</summary>
|
|
58
|
+
<div class="content-container">
|
|
59
|
+
<div class="content-actions">
|
|
60
|
+
<button class="copy-btn" data-file-id="${fileId}" title="Copy to clipboard">📋 Copy</button>
|
|
61
|
+
<button class="download-btn" data-file-id="${fileId}" data-filename="${(0, treeRenderer_1.escapeHtml)(filename)}" title="Download file">⬇ Download</button>
|
|
62
|
+
</div>
|
|
63
|
+
<pre class="file-content"><code>${escapedContent}</code></pre>
|
|
64
|
+
</div>
|
|
65
|
+
</details>
|
|
66
|
+
`;
|
|
67
|
+
};
|
|
45
68
|
const generateChangedFileSection = (file, fileId) => {
|
|
46
69
|
const isYaml = (0, fileType_1.isYamlFile)(file.path);
|
|
47
70
|
const summary = generateFileSummary(file);
|
|
@@ -91,7 +114,11 @@ const generateHtmlReport = async (diffResult, formattedFiles, config, dryRun, lo
|
|
|
91
114
|
for (const [index, file] of diffResult.changedFiles.entries())
|
|
92
115
|
changedFileIds.set(file.path, `file-${index}`);
|
|
93
116
|
const changedSections = diffResult.changedFiles.map((file, index) => generateChangedFileSection(file, `file-${index}`));
|
|
94
|
-
const
|
|
117
|
+
const addedFileIds = new Map();
|
|
118
|
+
for (const [index, file] of diffResult.addedFiles.entries())
|
|
119
|
+
addedFileIds.set(file.path, `added-file-${index}`);
|
|
120
|
+
const addedSections = diffResult.addedFiles.map((file, index) => generateAddedFileSection(file, `added-file-${index}`));
|
|
121
|
+
const htmlContent = (0, htmlTemplate_1.generateHtmlTemplate)(diffResult, formattedFiles, trulyUnchangedFiles, metadata, changedSections, changedFileIds, addedSections, addedFileIds);
|
|
95
122
|
await writeHtmlFile(htmlContent, reportPath);
|
|
96
123
|
logger?.log(`✓ HTML report generated: ${reportPath}, opening in browser...`);
|
|
97
124
|
try {
|
package/dist/index.js
CHANGED
|
@@ -60,6 +60,7 @@ const stopRulesValidator_1 = require("./stopRulesValidator");
|
|
|
60
60
|
const suggestionEngine_1 = require("./suggestionEngine");
|
|
61
61
|
const collisionDetector_1 = require("./utils/collisionDetector");
|
|
62
62
|
const commentOnlyDetector_1 = require("./utils/commentOnlyDetector");
|
|
63
|
+
const fileFilter_1 = require("./utils/fileFilter");
|
|
63
64
|
const filenameTransformer_1 = require("./utils/filenameTransformer");
|
|
64
65
|
const fileType_1 = require("./utils/fileType");
|
|
65
66
|
const versionChecker_1 = require("./utils/versionChecker");
|
|
@@ -113,14 +114,19 @@ const main = async () => {
|
|
|
113
114
|
transforms: validationConfig.transforms,
|
|
114
115
|
skipExclude: true
|
|
115
116
|
}, logger);
|
|
116
|
-
|
|
117
|
+
let sourceFiles = sourceResult.fileMap;
|
|
117
118
|
const destinationResult = await (0, fileLoader_1.loadFiles)({
|
|
118
119
|
baseDirectory: validationConfig.destination,
|
|
119
120
|
include: validationConfig.include,
|
|
120
121
|
exclude: validationConfig.exclude,
|
|
121
122
|
skipExclude: true
|
|
122
123
|
}, logger);
|
|
123
|
-
|
|
124
|
+
let destinationFiles = destinationResult.fileMap;
|
|
125
|
+
if (command.filter) {
|
|
126
|
+
const filtered = (0, fileFilter_1.filterFileMaps)(sourceFiles, destinationFiles, command.filter);
|
|
127
|
+
sourceFiles = filtered.sourceFiles;
|
|
128
|
+
destinationFiles = filtered.destinationFiles;
|
|
129
|
+
}
|
|
124
130
|
logger.progress(`Loaded ${sourceFiles.size} source, ${destinationFiles.size} destination file(s)`, 'success');
|
|
125
131
|
logger.log('\n' + (0, consoleFormatter_1.formatProgressMessage)('Validating pattern usage...', 'info'));
|
|
126
132
|
const usageResult = (0, patternUsageValidator_1.validatePatternUsage)(validationConfig, sourceFiles, destinationFiles);
|
|
@@ -159,7 +165,9 @@ const main = async () => {
|
|
|
159
165
|
include: config.include,
|
|
160
166
|
exclude: config.exclude
|
|
161
167
|
}, logger);
|
|
162
|
-
|
|
168
|
+
let destinationFiles = destinationResult.fileMap;
|
|
169
|
+
if (command.filter)
|
|
170
|
+
destinationFiles = (0, fileFilter_1.filterFileMap)(destinationFiles, command.filter);
|
|
163
171
|
logger.progress(`Loaded ${destinationFiles.size} destination file(s)`, 'success');
|
|
164
172
|
if (command.listFiles) {
|
|
165
173
|
const filesList = [...destinationFiles.keys()].toSorted();
|
|
@@ -218,7 +226,7 @@ const main = async () => {
|
|
|
218
226
|
exclude: syncConfig.exclude,
|
|
219
227
|
transforms: syncConfig.transforms
|
|
220
228
|
}, logger);
|
|
221
|
-
|
|
229
|
+
let sourceFiles = sourceResult.fileMap;
|
|
222
230
|
const originalPaths = sourceResult.originalPaths;
|
|
223
231
|
logger.progress(`Loaded ${sourceFiles.size} source file(s)`, 'success');
|
|
224
232
|
const collisions = (0, collisionDetector_1.detectCollisions)(sourceFiles, syncConfig.transforms);
|
|
@@ -231,8 +239,14 @@ const main = async () => {
|
|
|
231
239
|
include: syncConfig.include,
|
|
232
240
|
exclude: syncConfig.exclude
|
|
233
241
|
}, logger);
|
|
234
|
-
|
|
242
|
+
let destinationFiles = destinationResult.fileMap;
|
|
235
243
|
logger.progress(`Loaded ${destinationFiles.size} destination file(s)`, 'success');
|
|
244
|
+
if (command.filter) {
|
|
245
|
+
const filtered = (0, fileFilter_1.filterFileMaps)(sourceFiles, destinationFiles, command.filter);
|
|
246
|
+
sourceFiles = filtered.sourceFiles;
|
|
247
|
+
destinationFiles = filtered.destinationFiles;
|
|
248
|
+
logger.progress(`Filter '${command.filter}' matched ${sourceFiles.size} source, ${destinationFiles.size} destination file(s)`, 'info');
|
|
249
|
+
}
|
|
236
250
|
if (command.listFiles) {
|
|
237
251
|
const sourceFilesList = [...sourceFiles.keys()].toSorted();
|
|
238
252
|
const destinationFilesList = [...destinationFiles.keys()].toSorted();
|
|
@@ -246,7 +260,8 @@ const main = async () => {
|
|
|
246
260
|
return;
|
|
247
261
|
}
|
|
248
262
|
logger.log('\n' + (0, consoleFormatter_1.formatProgressMessage)('Computing differences...', 'info'));
|
|
249
|
-
const
|
|
263
|
+
const rawDiffResult = (0, fileDiff_1.computeFileDiff)(sourceFiles, destinationFiles, syncConfig, logger, originalPaths);
|
|
264
|
+
const diffResult = (0, fileFilter_1.filterDiffResultByMode)(rawDiffResult, command.mode);
|
|
250
265
|
if (logger.shouldShow('debug'))
|
|
251
266
|
logger.debug('Diff pipeline: parse → transforms → skipPath → normalize → compare');
|
|
252
267
|
if (command.diff && !command.quiet)
|
package/dist/jsonReporter.d.ts
CHANGED
|
@@ -43,8 +43,13 @@ export interface ChangedFileDetail {
|
|
|
43
43
|
diff: string;
|
|
44
44
|
changes: FieldChange[];
|
|
45
45
|
}
|
|
46
|
+
export interface AddedFileDetail {
|
|
47
|
+
path: string;
|
|
48
|
+
originalPath?: string;
|
|
49
|
+
content: string;
|
|
50
|
+
}
|
|
46
51
|
export interface JsonReportFiles {
|
|
47
|
-
added:
|
|
52
|
+
added: AddedFileDetail[];
|
|
48
53
|
deleted: string[];
|
|
49
54
|
changed: ChangedFileDetail[];
|
|
50
55
|
formatted: string[];
|
package/dist/jsonReporter.js
CHANGED
|
@@ -81,8 +81,13 @@ const generateJsonReport = (diffResult, formattedFiles, validationResult, config
|
|
|
81
81
|
unchanged: diffResult.unchangedFiles.length
|
|
82
82
|
};
|
|
83
83
|
const changedFileDetails = diffResult.changedFiles.map((file) => generateChangedFileDetail(file));
|
|
84
|
+
const addedFileDetails = diffResult.addedFiles.map((file) => ({
|
|
85
|
+
path: file.path,
|
|
86
|
+
originalPath: file.originalPath,
|
|
87
|
+
content: file.processedContent
|
|
88
|
+
}));
|
|
84
89
|
const files = {
|
|
85
|
-
added:
|
|
90
|
+
added: addedFileDetails,
|
|
86
91
|
deleted: diffResult.deletedFiles,
|
|
87
92
|
changed: changedFileDetails,
|
|
88
93
|
formatted: formattedFiles,
|
|
@@ -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 .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";
|
|
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 /* 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";
|
|
2
2
|
export declare const TAB_SCRIPT: string;
|
|
@@ -314,6 +314,68 @@ exports.HTML_STYLES = `
|
|
|
314
314
|
.sidebar.collapsed ~ .sidebar-expand-btn {
|
|
315
315
|
display: block;
|
|
316
316
|
}
|
|
317
|
+
|
|
318
|
+
/* Added content area (same as changed-content) */
|
|
319
|
+
.added-content {
|
|
320
|
+
flex: 1;
|
|
321
|
+
min-width: 0;
|
|
322
|
+
padding-left: 20px;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/* Content container for added files */
|
|
326
|
+
.content-container {
|
|
327
|
+
padding: 16px;
|
|
328
|
+
background: #f6f8fa;
|
|
329
|
+
border-top: 1px solid #d0d7de;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
.content-actions {
|
|
333
|
+
display: flex;
|
|
334
|
+
gap: 8px;
|
|
335
|
+
margin-bottom: 12px;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
.copy-btn,
|
|
339
|
+
.download-btn {
|
|
340
|
+
padding: 6px 12px;
|
|
341
|
+
border: 1px solid #d0d7de;
|
|
342
|
+
border-radius: 6px;
|
|
343
|
+
background: white;
|
|
344
|
+
cursor: pointer;
|
|
345
|
+
font-size: 13px;
|
|
346
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
347
|
+
color: #24292e;
|
|
348
|
+
transition: all 0.2s;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
.copy-btn:hover,
|
|
352
|
+
.download-btn:hover {
|
|
353
|
+
background: #f3f4f6;
|
|
354
|
+
border-color: #b0b7be;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
.copy-btn.copied {
|
|
358
|
+
background: #d4edda;
|
|
359
|
+
border-color: #28a745;
|
|
360
|
+
color: #155724;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
.file-content {
|
|
364
|
+
margin: 0;
|
|
365
|
+
padding: 16px;
|
|
366
|
+
background: white;
|
|
367
|
+
border: 1px solid #d0d7de;
|
|
368
|
+
border-radius: 6px;
|
|
369
|
+
overflow-x: auto;
|
|
370
|
+
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
|
|
371
|
+
font-size: 12px;
|
|
372
|
+
line-height: 1.5;
|
|
373
|
+
white-space: pre;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
.file-content code {
|
|
377
|
+
font-family: inherit;
|
|
378
|
+
}
|
|
317
379
|
`;
|
|
318
380
|
exports.TAB_SCRIPT = String.raw `
|
|
319
381
|
// Tab switching
|
|
@@ -343,28 +405,36 @@ exports.TAB_SCRIPT = String.raw `
|
|
|
343
405
|
});
|
|
344
406
|
});
|
|
345
407
|
|
|
346
|
-
// Sidebar toggle
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
408
|
+
// Sidebar toggle (supports multiple sidebars)
|
|
409
|
+
document.querySelectorAll('.sidebar-toggle').forEach(toggle => {
|
|
410
|
+
toggle.addEventListener('click', () => {
|
|
411
|
+
const container = toggle.closest('.sidebar-container');
|
|
412
|
+
if (!container) return;
|
|
413
|
+
const sidebar = container.querySelector('.sidebar');
|
|
414
|
+
if (sidebar) {
|
|
415
|
+
sidebar.classList.toggle('collapsed');
|
|
416
|
+
toggle.textContent = sidebar.classList.contains('collapsed') ? '\u25B6' : '\u25C0';
|
|
417
|
+
}
|
|
355
418
|
});
|
|
356
|
-
}
|
|
419
|
+
});
|
|
357
420
|
|
|
358
|
-
|
|
421
|
+
// Sidebar expand button (supports multiple sidebars)
|
|
422
|
+
document.querySelectorAll('.sidebar-expand-btn').forEach(expandBtn => {
|
|
359
423
|
expandBtn.addEventListener('click', () => {
|
|
360
|
-
|
|
361
|
-
if (
|
|
362
|
-
|
|
424
|
+
const container = expandBtn.closest('.sidebar-container');
|
|
425
|
+
if (!container) return;
|
|
426
|
+
const sidebar = container.querySelector('.sidebar');
|
|
427
|
+
const toggle = container.querySelector('.sidebar-toggle');
|
|
428
|
+
if (sidebar) {
|
|
429
|
+
sidebar.classList.remove('collapsed');
|
|
430
|
+
if (toggle) {
|
|
431
|
+
toggle.textContent = '\u25C0';
|
|
432
|
+
}
|
|
363
433
|
}
|
|
364
434
|
});
|
|
365
|
-
}
|
|
435
|
+
});
|
|
366
436
|
|
|
367
|
-
// Sidebar file click - scroll to diff
|
|
437
|
+
// Sidebar file click - scroll to diff/content
|
|
368
438
|
document.querySelectorAll('.sidebar-tree .tree-file-link').forEach(link => {
|
|
369
439
|
link.addEventListener('click', (e) => {
|
|
370
440
|
e.preventDefault();
|
|
@@ -376,8 +446,11 @@ exports.TAB_SCRIPT = String.raw `
|
|
|
376
446
|
if (target.tagName === 'DETAILS' && !target.open) {
|
|
377
447
|
target.open = true;
|
|
378
448
|
}
|
|
379
|
-
// Highlight active file in sidebar
|
|
380
|
-
|
|
449
|
+
// Highlight active file in sidebar (within same container)
|
|
450
|
+
const container = link.closest('.sidebar-container');
|
|
451
|
+
if (container) {
|
|
452
|
+
container.querySelectorAll('.sidebar-tree .tree-file').forEach(f => f.classList.remove('active'));
|
|
453
|
+
}
|
|
381
454
|
link.closest('.tree-file').classList.add('active');
|
|
382
455
|
}
|
|
383
456
|
});
|
|
@@ -390,18 +463,94 @@ exports.TAB_SCRIPT = String.raw `
|
|
|
390
463
|
entries.forEach(entry => {
|
|
391
464
|
if (entry.isIntersecting) {
|
|
392
465
|
const fileId = entry.target.id;
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
466
|
+
// Find the corresponding sidebar container
|
|
467
|
+
const tabContent = entry.target.closest('.tab-content');
|
|
468
|
+
if (tabContent) {
|
|
469
|
+
tabContent.querySelectorAll('.sidebar-tree .tree-file').forEach(f => {
|
|
470
|
+
const link = f.querySelector('.tree-file-link');
|
|
471
|
+
if (link && link.getAttribute('href') === '#' + fileId) {
|
|
472
|
+
f.classList.add('active');
|
|
473
|
+
} else {
|
|
474
|
+
f.classList.remove('active');
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
}
|
|
401
478
|
}
|
|
402
479
|
});
|
|
403
480
|
}, { threshold: 0.3, rootMargin: '-100px 0px -50% 0px' });
|
|
404
481
|
|
|
405
482
|
fileSections.forEach(section => observer.observe(section));
|
|
406
483
|
}
|
|
484
|
+
|
|
485
|
+
// Copy button functionality
|
|
486
|
+
document.querySelectorAll('.copy-btn').forEach(btn => {
|
|
487
|
+
btn.addEventListener('click', async () => {
|
|
488
|
+
const fileId = btn.getAttribute('data-file-id');
|
|
489
|
+
const section = document.getElementById(fileId);
|
|
490
|
+
if (!section) return;
|
|
491
|
+
|
|
492
|
+
const codeElement = section.querySelector('.file-content code');
|
|
493
|
+
if (!codeElement) return;
|
|
494
|
+
|
|
495
|
+
const content = codeElement.textContent || '';
|
|
496
|
+
|
|
497
|
+
try {
|
|
498
|
+
// Try modern clipboard API first
|
|
499
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
500
|
+
await navigator.clipboard.writeText(content);
|
|
501
|
+
} else {
|
|
502
|
+
// Fallback for older browsers
|
|
503
|
+
const textarea = document.createElement('textarea');
|
|
504
|
+
textarea.value = content;
|
|
505
|
+
textarea.style.position = 'fixed';
|
|
506
|
+
textarea.style.opacity = '0';
|
|
507
|
+
document.body.appendChild(textarea);
|
|
508
|
+
textarea.select();
|
|
509
|
+
document.execCommand('copy');
|
|
510
|
+
document.body.removeChild(textarea);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// Visual feedback
|
|
514
|
+
const originalText = btn.textContent;
|
|
515
|
+
btn.textContent = '\u2713 Copied!';
|
|
516
|
+
btn.classList.add('copied');
|
|
517
|
+
setTimeout(() => {
|
|
518
|
+
btn.textContent = originalText;
|
|
519
|
+
btn.classList.remove('copied');
|
|
520
|
+
}, 2000);
|
|
521
|
+
} catch (err) {
|
|
522
|
+
console.error('Failed to copy:', err);
|
|
523
|
+
btn.textContent = '\u2717 Failed';
|
|
524
|
+
setTimeout(() => {
|
|
525
|
+
btn.textContent = '\ud83d\udccb Copy';
|
|
526
|
+
}, 2000);
|
|
527
|
+
}
|
|
528
|
+
});
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
// Download button functionality
|
|
532
|
+
document.querySelectorAll('.download-btn').forEach(btn => {
|
|
533
|
+
btn.addEventListener('click', () => {
|
|
534
|
+
const fileId = btn.getAttribute('data-file-id');
|
|
535
|
+
const filename = btn.getAttribute('data-filename') || 'file.yaml';
|
|
536
|
+
const section = document.getElementById(fileId);
|
|
537
|
+
if (!section) return;
|
|
538
|
+
|
|
539
|
+
const codeElement = section.querySelector('.file-content code');
|
|
540
|
+
if (!codeElement) return;
|
|
541
|
+
|
|
542
|
+
const content = codeElement.textContent || '';
|
|
543
|
+
|
|
544
|
+
// Create blob and download
|
|
545
|
+
const blob = new Blob([content], { type: 'text/plain;charset=utf-8' });
|
|
546
|
+
const url = URL.createObjectURL(blob);
|
|
547
|
+
const link = document.createElement('a');
|
|
548
|
+
link.href = url;
|
|
549
|
+
link.download = filename;
|
|
550
|
+
document.body.appendChild(link);
|
|
551
|
+
link.click();
|
|
552
|
+
document.body.removeChild(link);
|
|
553
|
+
URL.revokeObjectURL(url);
|
|
554
|
+
});
|
|
555
|
+
});
|
|
407
556
|
`;
|
|
@@ -5,4 +5,4 @@ export interface ReportMetadata {
|
|
|
5
5
|
destination: string;
|
|
6
6
|
dryRun: boolean;
|
|
7
7
|
}
|
|
8
|
-
export declare const generateHtmlTemplate: (diffResult: FileDiffResult, formattedFiles: string[], trulyUnchangedFiles: string[], metadata: ReportMetadata, changedSections: string[], changedFileIds?: Map<string, string>) => string;
|
|
8
|
+
export declare const generateHtmlTemplate: (diffResult: FileDiffResult, formattedFiles: string[], trulyUnchangedFiles: string[], metadata: ReportMetadata, changedSections: string[], changedFileIds?: Map<string, string>, addedSections?: string[], addedFileIds?: Map<string, string>) => string;
|
|
@@ -4,10 +4,11 @@ exports.generateHtmlTemplate = void 0;
|
|
|
4
4
|
const htmlStyles_1 = require("./htmlStyles");
|
|
5
5
|
const treeBuilder_1 = require("./treeBuilder");
|
|
6
6
|
const treeRenderer_1 = require("./treeRenderer");
|
|
7
|
-
const generateHtmlTemplate = (diffResult, formattedFiles, trulyUnchangedFiles, metadata, changedSections, changedFileIds = new Map()) => {
|
|
7
|
+
const generateHtmlTemplate = (diffResult, formattedFiles, trulyUnchangedFiles, metadata, changedSections, changedFileIds = new Map(), addedSections = [], addedFileIds = new Map()) => {
|
|
8
8
|
const changedFilePaths = diffResult.changedFiles.map((f) => f.path);
|
|
9
9
|
const changedTree = (0, treeBuilder_1.buildFileTree)(changedFilePaths);
|
|
10
|
-
const
|
|
10
|
+
const addedFilePaths = diffResult.addedFiles.map((f) => f.path);
|
|
11
|
+
const addedTree = (0, treeBuilder_1.buildFileTree)(addedFilePaths);
|
|
11
12
|
const deletedTree = (0, treeBuilder_1.buildFileTree)(diffResult.deletedFiles);
|
|
12
13
|
const formattedTree = (0, treeBuilder_1.buildFileTree)(formattedFiles);
|
|
13
14
|
const unchangedTree = (0, treeBuilder_1.buildFileTree)(trulyUnchangedFiles);
|
|
@@ -72,10 +73,22 @@ ${htmlStyles_1.HTML_STYLES}
|
|
|
72
73
|
</section>
|
|
73
74
|
|
|
74
75
|
<section id="added" class="tab-content">
|
|
75
|
-
${
|
|
76
|
+
${addedSections.length > 0
|
|
76
77
|
? `
|
|
77
|
-
<div class="
|
|
78
|
-
|
|
78
|
+
<div class="sidebar-container">
|
|
79
|
+
<aside class="sidebar" id="added-sidebar">
|
|
80
|
+
<div class="sidebar-header">
|
|
81
|
+
<span>Added Files</span>
|
|
82
|
+
<button class="sidebar-toggle" data-sidebar="added">◀</button>
|
|
83
|
+
</div>
|
|
84
|
+
<div class="sidebar-content">
|
|
85
|
+
${(0, treeRenderer_1.renderSidebarTree)(addedTree, addedFileIds)}
|
|
86
|
+
</div>
|
|
87
|
+
</aside>
|
|
88
|
+
<button class="sidebar-expand-btn" data-sidebar="added">▶</button>
|
|
89
|
+
<div class="added-content">
|
|
90
|
+
${addedSections.join('\n')}
|
|
91
|
+
</div>
|
|
79
92
|
</div>
|
|
80
93
|
`
|
|
81
94
|
: '<p style="color: #586069; text-align: center; padding: 40px;">No added files</p>'}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import { TreeNode } from './treeBuilder';
|
|
2
2
|
export declare const renderTreeview: (nodes: TreeNode[]) => string;
|
|
3
3
|
export declare const renderSidebarTree: (nodes: TreeNode[], fileIds: Map<string, string>) => string;
|
|
4
|
+
export declare const escapeHtml: (text: string) => string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.renderSidebarTree = exports.renderTreeview = void 0;
|
|
3
|
+
exports.escapeHtml = exports.renderSidebarTree = exports.renderTreeview = void 0;
|
|
4
4
|
const renderTreeview = (nodes) => {
|
|
5
5
|
if (nodes.length === 0)
|
|
6
6
|
return '';
|
|
@@ -11,15 +11,15 @@ const renderTreeNode = (node) => {
|
|
|
11
11
|
if (node.isFolder) {
|
|
12
12
|
const children = node.children ? node.children.map((child) => renderTreeNode(child)).join('') : '';
|
|
13
13
|
return `
|
|
14
|
-
<li class="tree-folder" data-path="${escapeHtml(node.path)}">
|
|
14
|
+
<li class="tree-folder" data-path="${(0, exports.escapeHtml)(node.path)}">
|
|
15
15
|
<span class="tree-toggle">▼</span>
|
|
16
|
-
<span class="tree-folder-name">${escapeHtml(node.name)}</span>
|
|
16
|
+
<span class="tree-folder-name">${(0, exports.escapeHtml)(node.name)}</span>
|
|
17
17
|
<ul class="tree-children">${children}</ul>
|
|
18
18
|
</li>`;
|
|
19
19
|
}
|
|
20
20
|
return `
|
|
21
|
-
<li class="tree-file" data-path="${escapeHtml(node.path)}">
|
|
22
|
-
<span class="tree-file-name">${escapeHtml(node.name)}</span>
|
|
21
|
+
<li class="tree-file" data-path="${(0, exports.escapeHtml)(node.path)}">
|
|
22
|
+
<span class="tree-file-name">${(0, exports.escapeHtml)(node.name)}</span>
|
|
23
23
|
</li>`;
|
|
24
24
|
};
|
|
25
25
|
const renderSidebarTree = (nodes, fileIds) => {
|
|
@@ -32,16 +32,16 @@ const renderSidebarNode = (node, fileIds) => {
|
|
|
32
32
|
if (node.isFolder) {
|
|
33
33
|
const children = node.children ? node.children.map((child) => renderSidebarNode(child, fileIds)).join('') : '';
|
|
34
34
|
return `
|
|
35
|
-
<li class="tree-folder" data-path="${escapeHtml(node.path)}">
|
|
35
|
+
<li class="tree-folder" data-path="${(0, exports.escapeHtml)(node.path)}">
|
|
36
36
|
<span class="tree-toggle">▼</span>
|
|
37
|
-
<span class="tree-folder-name">${escapeHtml(node.name)}</span>
|
|
37
|
+
<span class="tree-folder-name">${(0, exports.escapeHtml)(node.name)}</span>
|
|
38
38
|
<ul class="tree-children">${children}</ul>
|
|
39
39
|
</li>`;
|
|
40
40
|
}
|
|
41
41
|
const fileId = fileIds.get(node.path) || '';
|
|
42
42
|
return `
|
|
43
|
-
<li class="tree-file" data-path="${escapeHtml(node.path)}" data-file-id="${escapeHtml(fileId)}">
|
|
44
|
-
<a href="#${escapeHtml(fileId)}" class="tree-file-link">${escapeHtml(node.name)}</a>
|
|
43
|
+
<li class="tree-file" data-path="${(0, exports.escapeHtml)(node.path)}" data-file-id="${(0, exports.escapeHtml)(fileId)}">
|
|
44
|
+
<a href="#${(0, exports.escapeHtml)(fileId)}" class="tree-file-link">${(0, exports.escapeHtml)(node.name)}</a>
|
|
45
45
|
</li>`;
|
|
46
46
|
};
|
|
47
47
|
const escapeHtml = (text) => text
|
|
@@ -50,3 +50,4 @@ const escapeHtml = (text) => text
|
|
|
50
50
|
.replaceAll('>', '>')
|
|
51
51
|
.replaceAll('"', '"')
|
|
52
52
|
.replaceAll("'", ''');
|
|
53
|
+
exports.escapeHtml = escapeHtml;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { FileDiffResult } from '../fileDiff';
|
|
2
|
+
import type { FileMap } from '../fileLoader';
|
|
3
|
+
export type ChangeMode = 'new' | 'modified' | 'deleted' | 'all';
|
|
4
|
+
export declare const filterDiffResultByMode: (diffResult: FileDiffResult, mode: ChangeMode) => FileDiffResult;
|
|
5
|
+
export declare const filterFileMap: (fileMap: FileMap, filter: string | undefined) => FileMap;
|
|
6
|
+
export declare const filterFileMaps: (sourceFiles: FileMap, destinationFiles: FileMap, filter: string | undefined) => {
|
|
7
|
+
sourceFiles: FileMap;
|
|
8
|
+
destinationFiles: FileMap;
|
|
9
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.filterFileMaps = exports.filterFileMap = exports.filterDiffResultByMode = void 0;
|
|
4
|
+
const filterDiffResultByMode = (diffResult, mode) => {
|
|
5
|
+
if (mode === 'all')
|
|
6
|
+
return diffResult;
|
|
7
|
+
return {
|
|
8
|
+
addedFiles: mode === 'new' ? diffResult.addedFiles : [],
|
|
9
|
+
deletedFiles: mode === 'deleted' ? diffResult.deletedFiles : [],
|
|
10
|
+
changedFiles: mode === 'modified' ? diffResult.changedFiles : [],
|
|
11
|
+
unchangedFiles: diffResult.unchangedFiles
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
exports.filterDiffResultByMode = filterDiffResultByMode;
|
|
15
|
+
const filterFileMap = (fileMap, filter) => {
|
|
16
|
+
if (!filter)
|
|
17
|
+
return fileMap;
|
|
18
|
+
const lowerFilter = filter.toLowerCase();
|
|
19
|
+
const filteredMap = new Map();
|
|
20
|
+
for (const [filePath, content] of fileMap) {
|
|
21
|
+
const filenameMatches = filePath.toLowerCase().includes(lowerFilter);
|
|
22
|
+
const contentMatches = content.toLowerCase().includes(lowerFilter);
|
|
23
|
+
if (filenameMatches || contentMatches)
|
|
24
|
+
filteredMap.set(filePath, content);
|
|
25
|
+
}
|
|
26
|
+
return filteredMap;
|
|
27
|
+
};
|
|
28
|
+
exports.filterFileMap = filterFileMap;
|
|
29
|
+
const filterFileMaps = (sourceFiles, destinationFiles, filter) => {
|
|
30
|
+
if (!filter)
|
|
31
|
+
return { sourceFiles, destinationFiles };
|
|
32
|
+
const lowerFilter = filter.toLowerCase();
|
|
33
|
+
const matchingPaths = new Set();
|
|
34
|
+
for (const [filePath, content] of sourceFiles)
|
|
35
|
+
if (filePath.toLowerCase().includes(lowerFilter) || content.toLowerCase().includes(lowerFilter))
|
|
36
|
+
matchingPaths.add(filePath);
|
|
37
|
+
for (const [filePath, content] of destinationFiles)
|
|
38
|
+
if (filePath.toLowerCase().includes(lowerFilter) || content.toLowerCase().includes(lowerFilter))
|
|
39
|
+
matchingPaths.add(filePath);
|
|
40
|
+
const filteredSource = new Map();
|
|
41
|
+
const filteredDestination = new Map();
|
|
42
|
+
for (const [filePath, content] of sourceFiles)
|
|
43
|
+
if (matchingPaths.has(filePath))
|
|
44
|
+
filteredSource.set(filePath, content);
|
|
45
|
+
for (const [filePath, content] of destinationFiles)
|
|
46
|
+
if (matchingPaths.has(filePath))
|
|
47
|
+
filteredDestination.set(filePath, content);
|
|
48
|
+
return { sourceFiles: filteredSource, destinationFiles: filteredDestination };
|
|
49
|
+
};
|
|
50
|
+
exports.filterFileMaps = filterFileMaps;
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -7,7 +7,6 @@ export { clearJsonPathCache, getValueAtPath, isFilterSegment, matchesFilter, par
|
|
|
7
7
|
export { isYamlFile } from './fileType';
|
|
8
8
|
export { globalMatcher, PatternMatcher } from './patternMatcher';
|
|
9
9
|
export { generateUnifiedDiff } from './diffGenerator';
|
|
10
|
-
export { type ArrayChange, type ArrayChangeInfo, detectArrayChanges } from './arrayDiffProcessor';
|
|
11
10
|
export { checkForUpdates, isVersionCheckerError, VersionCheckerError } from './versionChecker';
|
|
12
11
|
export { escapeRegex, isYamlFileLoaderError, loadYamlFile, YamlFileLoaderError } from './yamlFileLoader';
|
|
13
12
|
export { isTransformFileLoaderError, loadTransformFile, loadTransformFiles, TransformFileLoaderError } from './transformFileLoader';
|
|
@@ -21,3 +20,4 @@ export { applyFixedValues, getFixedValuesForFile, setValueAtPath } from './fixed
|
|
|
21
20
|
export type { ApplicableFilter } from './arrayMerger';
|
|
22
21
|
export { findMatchingTargetItem, getApplicableArrayFilters, itemMatchesAnyFilter, shouldPreserveItem } from './arrayMerger';
|
|
23
22
|
export { isCommentOnlyContent } from './commentOnlyDetector';
|
|
23
|
+
export { filterFileMap, filterFileMaps } 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
|
-
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.
|
|
4
|
-
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 =
|
|
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;
|
|
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; } });
|
|
@@ -24,8 +24,6 @@ Object.defineProperty(exports, "globalMatcher", { enumerable: true, get: functio
|
|
|
24
24
|
Object.defineProperty(exports, "PatternMatcher", { enumerable: true, get: function () { return patternMatcher_1.PatternMatcher; } });
|
|
25
25
|
var diffGenerator_1 = require("./diffGenerator");
|
|
26
26
|
Object.defineProperty(exports, "generateUnifiedDiff", { enumerable: true, get: function () { return diffGenerator_1.generateUnifiedDiff; } });
|
|
27
|
-
var arrayDiffProcessor_1 = require("./arrayDiffProcessor");
|
|
28
|
-
Object.defineProperty(exports, "detectArrayChanges", { enumerable: true, get: function () { return arrayDiffProcessor_1.detectArrayChanges; } });
|
|
29
27
|
var versionChecker_1 = require("./versionChecker");
|
|
30
28
|
Object.defineProperty(exports, "checkForUpdates", { enumerable: true, get: function () { return versionChecker_1.checkForUpdates; } });
|
|
31
29
|
Object.defineProperty(exports, "isVersionCheckerError", { enumerable: true, get: function () { return versionChecker_1.isVersionCheckerError; } });
|
|
@@ -86,3 +84,6 @@ Object.defineProperty(exports, "itemMatchesAnyFilter", { enumerable: true, get:
|
|
|
86
84
|
Object.defineProperty(exports, "shouldPreserveItem", { enumerable: true, get: function () { return arrayMerger_1.shouldPreserveItem; } });
|
|
87
85
|
var commentOnlyDetector_1 = require("./commentOnlyDetector");
|
|
88
86
|
Object.defineProperty(exports, "isCommentOnlyContent", { enumerable: true, get: function () { return commentOnlyDetector_1.isCommentOnlyContent; } });
|
|
87
|
+
var fileFilter_1 = require("./fileFilter");
|
|
88
|
+
Object.defineProperty(exports, "filterFileMap", { enumerable: true, get: function () { return fileFilter_1.filterFileMap; } });
|
|
89
|
+
Object.defineProperty(exports, "filterFileMaps", { enumerable: true, get: function () { return fileFilter_1.filterFileMaps; } });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "helm-env-delta",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.0",
|
|
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.0
|
|
71
|
+
"@types/node": "^25.1.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",
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { ChangedFile } from '../fileDiff';
|
|
2
|
-
export interface ArrayChange {
|
|
3
|
-
path: string[];
|
|
4
|
-
added: unknown[];
|
|
5
|
-
removed: unknown[];
|
|
6
|
-
unchanged: unknown[];
|
|
7
|
-
}
|
|
8
|
-
export interface ArrayChangeInfo {
|
|
9
|
-
hasArrays: boolean;
|
|
10
|
-
hasChanges: boolean;
|
|
11
|
-
arrayPaths: string[][];
|
|
12
|
-
changes: ArrayChange[];
|
|
13
|
-
}
|
|
14
|
-
export declare const detectArrayChanges: (file: ChangedFile) => ArrayChangeInfo;
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.detectArrayChanges = void 0;
|
|
4
|
-
const arrayDiffer_1 = require("../arrayDiffer");
|
|
5
|
-
const deepEqual_1 = require("./deepEqual");
|
|
6
|
-
const jsonPath_1 = require("./jsonPath");
|
|
7
|
-
const serialization_1 = require("./serialization");
|
|
8
|
-
const detectArrayChanges = (file) => {
|
|
9
|
-
const hasArraysInFile = (0, arrayDiffer_1.hasArrays)(file.rawParsedSource) || (0, arrayDiffer_1.hasArrays)(file.rawParsedDest);
|
|
10
|
-
if (!hasArraysInFile)
|
|
11
|
-
return {
|
|
12
|
-
hasArrays: false,
|
|
13
|
-
hasChanges: false,
|
|
14
|
-
arrayPaths: [],
|
|
15
|
-
changes: []
|
|
16
|
-
};
|
|
17
|
-
const arrayPaths = (0, arrayDiffer_1.findArrayPaths)(file.rawParsedSource);
|
|
18
|
-
const changes = [];
|
|
19
|
-
for (const path of arrayPaths) {
|
|
20
|
-
const sourceArray = (0, jsonPath_1.getValueAtPath)(file.rawParsedSource, path);
|
|
21
|
-
const destinationArray = (0, jsonPath_1.getValueAtPath)(file.rawParsedDest, path);
|
|
22
|
-
if (!Array.isArray(sourceArray) || !Array.isArray(destinationArray))
|
|
23
|
-
continue;
|
|
24
|
-
const normalizedSource = (0, serialization_1.normalizeForComparison)(sourceArray);
|
|
25
|
-
const normalizedDestination = (0, serialization_1.normalizeForComparison)(destinationArray);
|
|
26
|
-
if ((0, deepEqual_1.deepEqual)(normalizedSource, normalizedDestination))
|
|
27
|
-
continue;
|
|
28
|
-
const diff = (0, arrayDiffer_1.diffArrays)(normalizedSource, normalizedDestination);
|
|
29
|
-
changes.push({
|
|
30
|
-
path,
|
|
31
|
-
added: diff.added,
|
|
32
|
-
removed: diff.removed,
|
|
33
|
-
unchanged: diff.unchanged
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
return {
|
|
37
|
-
hasArrays: true,
|
|
38
|
-
hasChanges: changes.length > 0,
|
|
39
|
-
arrayPaths,
|
|
40
|
-
changes
|
|
41
|
-
};
|
|
42
|
-
};
|
|
43
|
-
exports.detectArrayChanges = detectArrayChanges;
|