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 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 (`--list-files`), inspect config (`--show-config`), validate with comprehensive warnings including unused pattern detection.
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
- helm-env-delta --config config.yaml --dry-run --diff
105
+ hed -c config.yaml -D -d
106
106
  ```
107
107
 
108
108
  ### 3️⃣ Execute Sync
109
109
 
110
110
  ```bash
111
- helm-env-delta --config config.yaml
111
+ hed -c config.yaml
112
112
  ```
113
113
 
114
114
  ### 4️⃣ Review in Browser
115
115
 
116
116
  ```bash
117
- helm-env-delta --config config.yaml --diff-html
117
+ hed -c config.yaml -H
118
118
  ```
119
119
 
120
120
  ### 5️⃣ Get Smart Suggestions (Optional)
121
121
 
122
122
  ```bash
123
- helm-env-delta --config config.yaml --suggest
123
+ hed -c config.yaml --suggest
124
124
 
125
125
  # Control suggestion sensitivity (0-1, default: 0.3)
126
- helm-env-delta --config config.yaml --suggest --suggest-threshold 0.7
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
- | `--no-color` | Disable colored output (CI/accessibility) |
772
- | `--verbose` | Show detailed debug info |
773
- | `--quiet` | Suppress output except errors |
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 --config config.yaml --validate
781
+ hed -c config.yaml --validate
780
782
 
781
783
  # Get smart configuration suggestions
782
- hed --config config.yaml --suggest
784
+ hed -c config.yaml --suggest
783
785
 
784
786
  # Get only high-confidence suggestions
785
- hed --config config.yaml --suggest --suggest-threshold 0.7
787
+ hed -c config.yaml --suggest --suggest-threshold 0.7
786
788
 
787
789
  # Preview files that will be synced
788
- hed --config config.yaml --list-files
790
+ hed -c config.yaml -l
789
791
 
790
792
  # Display resolved config (after inheritance)
791
- hed --config config.yaml --show-config
793
+ hed -c config.yaml --show-config
792
794
 
793
795
  # Preview with diff
794
- hed --config config.yaml --dry-run --diff
796
+ hed -c config.yaml -D -d
795
797
 
796
798
  # Visual HTML report
797
- hed --config config.yaml --diff-html
799
+ hed -c config.yaml -H
798
800
 
799
801
  # CI/CD integration (no colors)
800
- hed --config config.yaml --diff-json --no-color | jq '.summary'
802
+ hed -c config.yaml -J --no-color | jq '.summary'
801
803
 
802
804
  # Execute sync
803
- hed --config config.yaml
805
+ hed -c config.yaml
804
806
 
805
807
  # Force override stop rules
806
- hed --config config.yaml --force
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 --config config.yaml --format-only
823
+ hed -c config.yaml --format-only
810
824
 
811
825
  # Preview format changes
812
- hed --config config.yaml --format-only --dry-run
826
+ hed -c config.yaml --format-only -D
813
827
 
814
828
  # List files that would be formatted (--list-files takes precedence)
815
- hed --config config.yaml --format-only --list-files
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 --config config.yaml --dry-run --diff
858
+ hed -c config.yaml -D -d
845
859
 
846
860
  # 2. Review in browser
847
- hed --config config.yaml --diff-html
861
+ hed -c config.yaml -H
848
862
 
849
863
  # 3. Execute sync
850
- hed --config config.yaml
864
+ hed -c config.yaml
851
865
 
852
866
  # 4. Git workflow
853
867
  git add prod/
@@ -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;
@@ -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
- if (!isYaml) {
74
- const destinationContent = String(file.processedDestContent);
75
- const sourceContent = String(file.processedSourceContent);
76
- const unifiedDiff = (0, diffGenerator_1.generateUnifiedDiff)(file.path, destinationContent, sourceContent);
77
- const colorizedDiff = colorizeUnifiedDiff(unifiedDiff);
78
- return `
79
- ${separator}
80
- ${chalk_1.default.yellow.bold(`File: ${file.path}`)}
81
- ${skipPathInfo}
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)
@@ -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: string[];
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 detectAddedFiles = (sourceFiles, destinationFiles) => {
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.keys())
27
- if (!destinationFiles.has(path))
28
- addedFiles.push(path);
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 };
@@ -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 relativePath of files)
252
+ for (const addedFile of files)
253
253
  try {
254
- const content = sourceFiles.get(relativePath);
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: relativePath, error: error });
266
+ context.errors.push({ operation: 'add', path: addedFile.path, error: error });
267
267
  }
268
268
  };
269
269
  const updateChangedFiles = async (files, context) => {
@@ -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 htmlContent = (0, htmlTemplate_1.generateHtmlTemplate)(diffResult, formattedFiles, trulyUnchangedFiles, metadata, changedSections, changedFileIds);
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
- const sourceFiles = sourceResult.fileMap;
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
- const destinationFiles = destinationResult.fileMap;
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
- const destinationFiles = destinationResult.fileMap;
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
- const sourceFiles = sourceResult.fileMap;
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
- const destinationFiles = destinationResult.fileMap;
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 diffResult = (0, fileDiff_1.computeFileDiff)(sourceFiles, destinationFiles, syncConfig, logger, originalPaths);
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)
@@ -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: string[];
52
+ added: AddedFileDetail[];
48
53
  deleted: string[];
49
54
  changed: ChangedFileDetail[];
50
55
  formatted: string[];
@@ -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: diffResult.addedFiles,
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
- const sidebarToggle = document.querySelector('.sidebar-toggle');
348
- const sidebar = document.querySelector('.sidebar');
349
- const expandBtn = document.querySelector('.sidebar-expand-btn');
350
-
351
- if (sidebarToggle && sidebar) {
352
- sidebarToggle.addEventListener('click', () => {
353
- sidebar.classList.toggle('collapsed');
354
- sidebarToggle.textContent = sidebar.classList.contains('collapsed') ? '\u25B6' : '\u25C0';
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
- if (expandBtn && sidebar) {
421
+ // Sidebar expand button (supports multiple sidebars)
422
+ document.querySelectorAll('.sidebar-expand-btn').forEach(expandBtn => {
359
423
  expandBtn.addEventListener('click', () => {
360
- sidebar.classList.remove('collapsed');
361
- if (sidebarToggle) {
362
- sidebarToggle.textContent = '\u25C0';
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
- document.querySelectorAll('.sidebar-tree .tree-file').forEach(f => f.classList.remove('active'));
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
- document.querySelectorAll('.sidebar-tree .tree-file').forEach(f => {
394
- const link = f.querySelector('.tree-file-link');
395
- if (link && link.getAttribute('href') === '#' + fileId) {
396
- f.classList.add('active');
397
- } else {
398
- f.classList.remove('active');
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 addedTree = (0, treeBuilder_1.buildFileTree)(diffResult.addedFiles);
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
- ${diffResult.addedFiles.length > 0
76
+ ${addedSections.length > 0
76
77
  ? `
77
- <div class="file-list">
78
- ${(0, treeRenderer_1.renderTreeview)(addedTree)}
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">&#9664;</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">&#9654;</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">&#9660;</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">&#9660;</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('>', '&gt;')
51
51
  .replaceAll('"', '&quot;')
52
52
  .replaceAll("'", '&#39;');
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;
@@ -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';
@@ -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.detectArrayChanges = 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.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 = exports.NUMERIC_MIN_MULTIPLIER = void 0;
3
+ exports.NUMERIC_MIN_MULTIPLIER = exports.NUMERIC_MIN_FLOOR = exports.MAX_EXAMPLES_PER_SUGGESTION = exports.ISO_TIMESTAMP_PATTERN = exports.FILTER_THRESHOLDS = exports.CONSTRAINT_FIELD_NAMES = exports.CONFIDENCE_DEFAULTS = exports.ARRAY_KEY_FIELDS = exports.ANTONYM_PAIRS = exports.isYamlSeq = exports.isYamlMap = exports.isYamlCollection = exports.isScalar = exports.extractScalarValue = exports.extractKeyValue = exports.validateVersionString = exports.applyRegexRulesSequentially = exports.validateTargetedRegex = exports.validatePathlessRegex = exports.getAllValuesRecursive = exports.RegexPatternFileLoaderError = exports.loadRegexPatternsFromKeys = exports.loadRegexPatternArray = exports.isRegexPatternFileLoaderError = exports.TransformFileLoaderError = exports.loadTransformFiles = exports.loadTransformFile = exports.isTransformFileLoaderError = exports.YamlFileLoaderError = exports.loadYamlFile = exports.isYamlFileLoaderError = exports.escapeRegex = exports.VersionCheckerError = exports.isVersionCheckerError = exports.checkForUpdates = exports.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.9.3",
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.10",
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;