helm-env-delta 1.15.2 → 2.0.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.
Files changed (38) hide show
  1. package/README.md +279 -95
  2. package/config.schema.json +496 -0
  3. package/dist/commandLine.d.ts +9 -11
  4. package/dist/commandLine.js +288 -101
  5. package/dist/config/ZodError.d.ts +2 -2
  6. package/dist/config/configFile.d.ts +1 -1
  7. package/dist/config/configFile.js +87 -41
  8. package/dist/config/configLoader.d.ts +2 -1
  9. package/dist/config/configMerger.d.ts +2 -1
  10. package/dist/consoleFormatter.d.ts +1 -1
  11. package/dist/consoleFormatter.js +12 -12
  12. package/dist/exitCodes.d.ts +5 -0
  13. package/dist/exitCodes.js +8 -0
  14. package/dist/index.js +82 -77
  15. package/dist/logger.d.ts +3 -3
  16. package/dist/pipeline/fileDiff.d.ts +6 -5
  17. package/dist/pipeline/fileLoader.d.ts +2 -1
  18. package/dist/pipeline/fileLoader.js +2 -2
  19. package/dist/pipeline/fileUpdater.d.ts +4 -4
  20. package/dist/pipeline/fileUpdater.js +1 -1
  21. package/dist/pipeline/stopRulesValidator.d.ts +2 -1
  22. package/dist/pipeline/stopRulesValidator.js +2 -4
  23. package/dist/pipeline/yamlFormatter.d.ts +1 -1
  24. package/dist/pipeline/yamlFormatter.js +9 -9
  25. package/dist/reporters/browserLauncher.js +1 -34
  26. package/dist/reporters/consoleDiffReporter.d.ts +2 -2
  27. package/dist/reporters/consoleDiffReporter.js +26 -26
  28. package/dist/reporters/htmlReporter.d.ts +4 -3
  29. package/dist/reporters/htmlReporter.js +20 -10
  30. package/dist/reporters/htmlTemplate.d.ts +1 -1
  31. package/dist/reporters/jsonReporter.d.ts +2 -2
  32. package/dist/reporters/treeRenderer.d.ts +1 -1
  33. package/dist/suggestionEngine.d.ts +2 -2
  34. package/dist/suggestionEngine.js +2 -2
  35. package/dist/utils/arrayMerger.d.ts +1 -1
  36. package/dist/utils/patternMatcher.d.ts +1 -1
  37. package/dist/utils/versionChecker.js +3 -3
  38. package/package.json +19 -17
@@ -6,122 +6,309 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.parseCommandLine = void 0;
7
7
  const commander_1 = require("commander");
8
8
  const package_json_1 = __importDefault(require("../package.json"));
9
+ const exitCodes_1 = require("./exitCodes");
10
+ const exitOverrideFunction = (error) => {
11
+ if (error.exitCode === 0)
12
+ process.exit(0);
13
+ process.exit(exitCodes_1.EXIT_CONFIG_ERROR);
14
+ };
15
+ const validModes = ['new', 'modified', 'deleted', 'all'];
16
+ const parseMode = (mode) => {
17
+ if (!validModes.includes(mode)) {
18
+ console.error('Error: --mode must be one of: ' + validModes.join(', '));
19
+ process.exit(exitCodes_1.EXIT_CONFIG_ERROR);
20
+ }
21
+ return mode;
22
+ };
23
+ const parseMyDays = (myRaw) => {
24
+ if (myRaw === undefined || myRaw === false)
25
+ return { my: false, myDays: 30 };
26
+ if (myRaw === true)
27
+ return { my: true, myDays: 30 };
28
+ const parsed = Number.parseInt(myRaw, 10);
29
+ if (Number.isNaN(parsed) || parsed < 1) {
30
+ console.error('Error: --my days must be a positive integer');
31
+ process.exit(exitCodes_1.EXIT_CONFIG_ERROR);
32
+ }
33
+ return { my: true, myDays: parsed };
34
+ };
35
+ const checkVerboseQuiet = (verbose, quiet) => {
36
+ if (verbose && quiet) {
37
+ console.error('Error: --verbose and --quiet flags are mutually exclusive');
38
+ process.exit(exitCodes_1.EXIT_CONFIG_ERROR);
39
+ }
40
+ };
41
+ const addGlobalOptions = (cmd) => cmd
42
+ .option('--verbose', 'Show detailed debug information', false)
43
+ .option('--quiet', 'Suppress all output except critical errors', false)
44
+ .option('--no-color', 'Disable colored output');
9
45
  const parseCommandLine = (argv) => {
46
+ let result;
10
47
  const program = new commander_1.Command();
11
48
  program
12
49
  .name('helm-env-delta')
13
50
  .description('Environment-aware YAML delta and sync for GitOps workflows')
14
51
  .version(package_json_1.default.version)
52
+ .showSuggestionAfterError(true)
53
+ .exitOverride(exitOverrideFunction);
54
+ addGlobalOptions(program
55
+ .command('run')
56
+ .description('Sync source YAML changes to destination')
15
57
  .requiredOption('-c, --config <file>', 'Path to YAML configuration file')
16
58
  .option('-D, --dry-run', 'Preview changes without writing files', false)
17
59
  .option('--force', 'Override stop rules and proceed with changes', 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
60
  .option('-S, --skip-format', 'Skip YAML formatting (outputFormat section)', false)
22
- .option('--format-only', 'Format YAML files in destination without syncing', false)
23
- .option('--validate', 'Validate configuration file and exit', false)
24
- .option('-l, --list-files', 'List files that would be synced without processing diffs', false)
25
- .option('--show-config', 'Display resolved configuration after inheritance and exit', false)
26
- .option('--suggest', 'Analyze differences and suggest transforms and stop rules', false)
61
+ .option('-f, --filter <string>', 'Filter files by name or content (supports , for OR, + for AND)')
62
+ .option('-m, --mode <type>', 'Filter by change type: new, modified, deleted, all', 'all')
63
+ .option('--my [days]', 'Limit to files you modified in the last N days (default: 30)')
64
+ .addHelpText('after', `
65
+ Examples:
66
+ $ helm-env-delta run -c config.yaml
67
+ $ helm-env-delta run -c config.yaml --dry-run
68
+ $ helm-env-delta run -c config.yaml --force
69
+ $ helm-env-delta run -c config.yaml --mode new
70
+ $ helm-env-delta run -c config.yaml -f prod --my 7
71
+ `)
72
+ .exitOverride(exitOverrideFunction)).action(function (options) {
73
+ checkVerboseQuiet(options['verbose'], options['quiet']);
74
+ const { my, myDays } = parseMyDays(options['my']);
75
+ result = {
76
+ commandName: 'run',
77
+ config: options['config'],
78
+ dryRun: options['dryRun'],
79
+ force: options['force'],
80
+ strict: false,
81
+ html: false,
82
+ json: false,
83
+ skipFormat: options['skipFormat'],
84
+ suggestThreshold: 0.3,
85
+ filter: options['filter'],
86
+ mode: parseMode(options['mode']),
87
+ my,
88
+ myDays,
89
+ verbose: options['verbose'],
90
+ quiet: options['quiet'],
91
+ noColor: !options['color']
92
+ };
93
+ });
94
+ addGlobalOptions(program
95
+ .command('validate')
96
+ .description('Validate configuration and patterns against source files')
97
+ .requiredOption('-c, --config <file>', 'Path to YAML configuration file')
98
+ .option('-f, --filter <string>', 'Filter files by name or content')
99
+ .option('--my [days]', 'Limit to files you modified in the last N days (default: 30)')
100
+ .option('--strict', 'Exit non-zero if any warnings are found', false)
101
+ .addHelpText('after', `
102
+ Examples:
103
+ $ helm-env-delta validate -c config.yaml
104
+ $ helm-env-delta validate -c config.yaml -f prod
105
+ $ helm-env-delta validate -c config.yaml --strict
106
+ `)
107
+ .exitOverride(exitOverrideFunction)).action(function (options) {
108
+ checkVerboseQuiet(options['verbose'], options['quiet']);
109
+ const { my, myDays } = parseMyDays(options['my']);
110
+ result = {
111
+ commandName: 'validate',
112
+ config: options['config'],
113
+ dryRun: false,
114
+ force: false,
115
+ strict: options['strict'],
116
+ html: false,
117
+ json: false,
118
+ skipFormat: false,
119
+ suggestThreshold: 0.3,
120
+ filter: options['filter'],
121
+ mode: 'all',
122
+ my,
123
+ myDays,
124
+ verbose: options['verbose'],
125
+ quiet: options['quiet'],
126
+ noColor: !options['color']
127
+ };
128
+ });
129
+ addGlobalOptions(program
130
+ .command('format')
131
+ .description('Format YAML files in destination without syncing')
132
+ .requiredOption('-c, --config <file>', 'Path to YAML configuration file')
133
+ .option('-D, --dry-run', 'Preview formatting changes without writing files', false)
134
+ .option('-f, --filter <string>', 'Filter files by name or content')
135
+ .addHelpText('after', `
136
+ Examples:
137
+ $ helm-env-delta format -c config.yaml
138
+ $ helm-env-delta format -c config.yaml --dry-run
139
+ $ helm-env-delta format -c config.yaml -f prod
140
+ `)
141
+ .exitOverride(exitOverrideFunction)).action(function (options) {
142
+ checkVerboseQuiet(options['verbose'], options['quiet']);
143
+ result = {
144
+ commandName: 'format',
145
+ config: options['config'],
146
+ dryRun: options['dryRun'],
147
+ force: false,
148
+ strict: false,
149
+ html: false,
150
+ json: false,
151
+ skipFormat: false,
152
+ suggestThreshold: 0.3,
153
+ filter: options['filter'],
154
+ mode: 'all',
155
+ my: false,
156
+ myDays: 30,
157
+ verbose: options['verbose'],
158
+ quiet: options['quiet'],
159
+ noColor: !options['color']
160
+ };
161
+ });
162
+ addGlobalOptions(program
163
+ .command('suggest')
164
+ .description('Analyze differences and suggest transforms and stop rules')
165
+ .requiredOption('-c, --config <file>', 'Path to YAML configuration file')
27
166
  .option('--suggest-threshold <number>', 'Minimum confidence for suggestions (0-1, default: 0.3)', '0.3')
28
- .option('--no-color', 'Disable colored output')
29
- .option('-f, --filter <string>', 'Filter files by filename or content (supports , for OR, + for AND)')
167
+ .option('-f, --filter <string>', 'Filter files by name or content')
30
168
  .option('-m, --mode <type>', 'Filter by change type: new, modified, deleted, all', 'all')
31
- .option('--my [days]', 'Filter source files to those you modified in the last N days (default: 30)')
32
- .option('--verbose', 'Show detailed debug information', false)
33
- .option('--quiet', 'Suppress all output except critical errors', false)
169
+ .option('--my [days]', 'Limit to files you modified in the last N days (default: 30)')
34
170
  .addHelpText('after', `
35
171
  Examples:
36
- # Preview changes before syncing
37
- $ helm-env-delta --config config.yaml --dry-run --diff
38
-
39
- # Get configuration suggestions
40
- $ helm-env-delta --config config.yaml --suggest
41
-
42
- # Sync with HTML diff report
43
- $ helm-env-delta --config config.yaml --diff-html
44
-
45
- # Validate stop rules without syncing
46
- $ helm-env-delta --config config.yaml --validate
47
-
48
- # CI/CD usage with JSON output
49
- $ helm-env-delta --config config.yaml --diff-json | jq '.summary'
50
-
51
- # Filter to only process files matching 'prod'
52
- $ helm-env-delta --config config.yaml -f prod --diff
53
-
54
- # Filter with OR: files matching 'prod' OR 'staging'
55
- $ helm-env-delta --config config.yaml -f prod,staging --diff
56
-
57
- # Filter with AND: files matching 'values' AND 'prod'
58
- $ helm-env-delta --config config.yaml -f values+prod --diff
59
-
60
- # Sync only new files
61
- $ helm-env-delta --config config.yaml --mode new
62
-
63
- # Preview modified files only
64
- $ helm-env-delta --config config.yaml --mode modified --dry-run --diff
65
-
66
- # Sync only files you modified in the last 30 days
67
- $ helm-env-delta --config config.yaml --my --dry-run --diff
68
-
69
- # Sync only files you modified in the last 7 days
70
- $ helm-env-delta --config config.yaml --my 7 --diff
71
-
72
- Documentation: https://github.com/balazscsaba2006/helm-env-delta
73
- `);
74
- program.showSuggestionAfterError(true);
75
- program.parse(argv || process.argv);
76
- const options = program.opts();
77
- if (options['verbose'] && options['quiet']) {
78
- console.error('Error: --verbose and --quiet flags are mutually exclusive');
79
- process.exit(1);
80
- }
81
- if (options['formatOnly'] && options['skipFormat']) {
82
- console.error('Error: --format-only and --skip-format flags are mutually exclusive');
83
- process.exit(1);
84
- }
85
- const threshold = Number.parseFloat(options['suggestThreshold']);
86
- if (Number.isNaN(threshold) || threshold < 0 || threshold > 1) {
87
- console.error('Error: --suggest-threshold must be a number between 0 and 1');
88
- process.exit(1);
89
- }
90
- const validModes = ['new', 'modified', 'deleted', 'all'];
91
- if (!validModes.includes(options['mode'])) {
92
- console.error('Error: --mode must be one of: ' + validModes.join(', '));
93
- process.exit(1);
94
- }
95
- let myDays = 30;
96
- if (options['my'] !== undefined && options['my'] !== true) {
97
- const parsed = Number.parseInt(options['my'], 10);
98
- if (Number.isNaN(parsed) || parsed < 1) {
99
- console.error('Error: --my days must be a positive integer');
100
- process.exit(1);
172
+ $ helm-env-delta suggest -c config.yaml
173
+ $ helm-env-delta suggest -c config.yaml --suggest-threshold 0.5
174
+ $ helm-env-delta suggest -c config.yaml -f prod
175
+ `)
176
+ .exitOverride(exitOverrideFunction)).action(function (options) {
177
+ checkVerboseQuiet(options['verbose'], options['quiet']);
178
+ const threshold = Number.parseFloat(options['suggestThreshold']);
179
+ if (Number.isNaN(threshold) || threshold < 0 || threshold > 1) {
180
+ console.error('Error: --suggest-threshold must be a number between 0 and 1');
181
+ process.exit(exitCodes_1.EXIT_CONFIG_ERROR);
101
182
  }
102
- myDays = parsed;
183
+ const { my, myDays } = parseMyDays(options['my']);
184
+ result = {
185
+ commandName: 'suggest',
186
+ config: options['config'],
187
+ dryRun: false,
188
+ force: false,
189
+ strict: false,
190
+ html: false,
191
+ json: false,
192
+ skipFormat: false,
193
+ suggestThreshold: threshold,
194
+ filter: options['filter'],
195
+ mode: parseMode(options['mode']),
196
+ my,
197
+ myDays,
198
+ verbose: options['verbose'],
199
+ quiet: options['quiet'],
200
+ noColor: !options['color']
201
+ };
202
+ });
203
+ addGlobalOptions(program
204
+ .command('diff')
205
+ .description('Show changes between source and destination (read-only)')
206
+ .requiredOption('-c, --config <file>', 'Path to YAML configuration file')
207
+ .option('-H, --html', 'Generate and open HTML diff report in browser', false)
208
+ .option('-J, --json', 'Output diff as JSON to stdout', false)
209
+ .option('--report-output <path>', 'Save HTML report to a file or directory (suppresses browser auto-open)')
210
+ .option('-f, --filter <string>', 'Filter files by name or content')
211
+ .option('-m, --mode <type>', 'Filter by change type: new, modified, deleted, all', 'all')
212
+ .option('--my [days]', 'Limit to files you modified in the last N days (default: 30)')
213
+ .addHelpText('after', `
214
+ Examples:
215
+ $ helm-env-delta diff -c config.yaml
216
+ $ helm-env-delta diff -c config.yaml --html
217
+ $ helm-env-delta diff -c config.yaml --json | jq '.summary'
218
+ $ helm-env-delta diff -c config.yaml --report-output ./reports/
219
+ $ helm-env-delta diff -c config.yaml -f prod --mode modified
220
+ `)
221
+ .exitOverride(exitOverrideFunction)).action(function (options) {
222
+ checkVerboseQuiet(options['verbose'], options['quiet']);
223
+ const { my, myDays } = parseMyDays(options['my']);
224
+ result = {
225
+ commandName: 'diff',
226
+ config: options['config'],
227
+ dryRun: false,
228
+ force: false,
229
+ strict: false,
230
+ html: options['html'],
231
+ json: options['json'],
232
+ reportOutput: options['reportOutput'],
233
+ skipFormat: false,
234
+ suggestThreshold: 0.3,
235
+ filter: options['filter'],
236
+ mode: parseMode(options['mode']),
237
+ my,
238
+ myDays,
239
+ verbose: options['verbose'],
240
+ quiet: options['quiet'],
241
+ noColor: !options['color']
242
+ };
243
+ });
244
+ addGlobalOptions(program
245
+ .command('list-files')
246
+ .description('List files that would be loaded without processing diffs')
247
+ .requiredOption('-c, --config <file>', 'Path to YAML configuration file')
248
+ .option('-f, --filter <string>', 'Filter files by name or content')
249
+ .option('--my [days]', 'Limit to files you modified in the last N days (default: 30)')
250
+ .addHelpText('after', `
251
+ Examples:
252
+ $ helm-env-delta list-files -c config.yaml
253
+ $ helm-env-delta list-files -c config.yaml -f prod
254
+ $ helm-env-delta list-files -c config.yaml --my 7
255
+ `)
256
+ .exitOverride(exitOverrideFunction)).action(function (options) {
257
+ checkVerboseQuiet(options['verbose'], options['quiet']);
258
+ const { my, myDays } = parseMyDays(options['my']);
259
+ result = {
260
+ commandName: 'list-files',
261
+ config: options['config'],
262
+ dryRun: false,
263
+ force: false,
264
+ strict: false,
265
+ html: false,
266
+ json: false,
267
+ skipFormat: false,
268
+ suggestThreshold: 0.3,
269
+ filter: options['filter'],
270
+ mode: 'all',
271
+ my,
272
+ myDays,
273
+ verbose: options['verbose'],
274
+ quiet: options['quiet'],
275
+ noColor: !options['color']
276
+ };
277
+ });
278
+ addGlobalOptions(program
279
+ .command('show-config')
280
+ .description('Display resolved configuration after inheritance')
281
+ .requiredOption('-c, --config <file>', 'Path to YAML configuration file')
282
+ .addHelpText('after', `
283
+ Examples:
284
+ $ helm-env-delta show-config -c config.yaml
285
+ `)
286
+ .exitOverride(exitOverrideFunction)).action(function (options) {
287
+ checkVerboseQuiet(options['verbose'], options['quiet']);
288
+ result = {
289
+ commandName: 'show-config',
290
+ config: options['config'],
291
+ dryRun: false,
292
+ force: false,
293
+ strict: false,
294
+ html: false,
295
+ json: false,
296
+ skipFormat: false,
297
+ suggestThreshold: 0.3,
298
+ filter: undefined,
299
+ mode: 'all',
300
+ my: false,
301
+ myDays: 30,
302
+ verbose: options['verbose'],
303
+ quiet: options['quiet'],
304
+ noColor: !options['color']
305
+ };
306
+ });
307
+ program.parse(argv ?? process.argv);
308
+ if (!result) {
309
+ program.help();
310
+ process.exit(exitCodes_1.EXIT_CONFIG_ERROR);
103
311
  }
104
- return {
105
- config: options['config'],
106
- dryRun: options['dryRun'],
107
- force: options['force'],
108
- diff: options['diff'],
109
- diffHtml: options['diffHtml'],
110
- diffJson: options['diffJson'],
111
- skipFormat: options['skipFormat'],
112
- formatOnly: options['formatOnly'],
113
- validate: options['validate'],
114
- listFiles: options['listFiles'],
115
- showConfig: options['showConfig'],
116
- noColor: !options['color'],
117
- verbose: options['verbose'],
118
- quiet: options['quiet'],
119
- suggest: options['suggest'],
120
- suggestThreshold: threshold,
121
- filter: options['filter'],
122
- mode: options['mode'],
123
- my: options['my'] !== undefined && options['my'] !== false,
124
- myDays
125
- };
312
+ return result;
126
313
  };
127
314
  exports.parseCommandLine = parseCommandLine;
@@ -1,8 +1,8 @@
1
- import { z } from 'zod';
1
+ import { type z } from 'zod';
2
2
  export declare class ZodValidationError extends Error {
3
3
  readonly zodError: z.ZodError;
4
4
  readonly sourcePath?: string | undefined;
5
5
  constructor(zodError: z.ZodError, sourcePath?: string | undefined);
6
- private static formatError;
6
+ private static readonly formatError;
7
7
  }
8
8
  export declare const isZodValidationError: (error: unknown) => error is ZodValidationError;
@@ -100,7 +100,7 @@ declare const transformRulesSchema: z.ZodObject<{
100
100
  contentFile: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
101
101
  filenameFile: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
102
102
  }, z.core.$strip>;
103
- declare const baseConfigSchema: z.ZodObject<{
103
+ export declare const baseConfigSchema: z.ZodObject<{
104
104
  extends: z.ZodOptional<z.ZodString>;
105
105
  requiredVersion: z.ZodOptional<z.ZodString>;
106
106
  source: z.ZodOptional<z.ZodString>;
@@ -3,25 +3,25 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.isConfigValidationError = exports.ConfigValidationError = exports.parseConfig = exports.parseFormatOnlyConfig = exports.parseFinalConfig = exports.parseBaseConfig = void 0;
6
+ exports.isConfigValidationError = exports.ConfigValidationError = exports.parseConfig = exports.parseFormatOnlyConfig = exports.parseFinalConfig = exports.parseBaseConfig = exports.baseConfigSchema = void 0;
7
7
  const node_path_1 = __importDefault(require("node:path"));
8
8
  const zod_1 = require("zod");
9
9
  const regexSafety_1 = require("../utils/regexSafety");
10
10
  const ZodError_1 = require("./ZodError");
11
11
  const semverMajorUpgradeRuleSchema = zod_1.z.object({
12
12
  type: zod_1.z.literal('semverMajorUpgrade'),
13
- path: zod_1.z.string().min(1)
13
+ path: zod_1.z.string().min(1).describe('JSONPath to the version field to check for major upgrades')
14
14
  });
15
15
  const semverDowngradeRuleSchema = zod_1.z.object({
16
16
  type: zod_1.z.literal('semverDowngrade'),
17
- path: zod_1.z.string().min(1)
17
+ path: zod_1.z.string().min(1).describe('JSONPath to the version field to check for any downgrade')
18
18
  });
19
19
  const numericRuleSchema = zod_1.z
20
20
  .object({
21
21
  type: zod_1.z.literal('numeric'),
22
- path: zod_1.z.string().min(1),
23
- min: zod_1.z.number().optional(),
24
- max: zod_1.z.number().optional()
22
+ path: zod_1.z.string().min(1).describe('JSONPath to the numeric field to validate'),
23
+ min: zod_1.z.number().optional().describe('Minimum allowed value (inclusive)'),
24
+ max: zod_1.z.number().optional().describe('Maximum allowed value (inclusive)')
25
25
  })
26
26
  .refine((data) => {
27
27
  if (data.min !== undefined && data.max !== undefined)
@@ -34,8 +34,8 @@ const numericRuleSchema = zod_1.z
34
34
  const regexRuleSchema = zod_1.z
35
35
  .object({
36
36
  type: zod_1.z.literal('regex'),
37
- path: zod_1.z.string().min(1).optional(),
38
- regex: zod_1.z.string().min(1)
37
+ path: zod_1.z.string().min(1).optional().describe('JSONPath to check (omit to scan ALL values recursively)'),
38
+ regex: zod_1.z.string().min(1).describe('Regex pattern that must NOT match (blocks sync if matched)')
39
39
  })
40
40
  .refine((data) => {
41
41
  try {
@@ -55,19 +55,22 @@ const regexRuleSchema = zod_1.z
55
55
  });
56
56
  const regexFileRuleSchema = zod_1.z.object({
57
57
  type: zod_1.z.literal('regexFile'),
58
- path: zod_1.z.string().min(1).optional(),
59
- file: zod_1.z.string().min(1)
58
+ path: zod_1.z.string().min(1).optional().describe('JSONPath to check (omit to scan ALL values recursively)'),
59
+ file: zod_1.z.string().min(1).describe('Path to a YAML file containing an array of regex patterns')
60
60
  });
61
61
  const regexFileKeyRuleSchema = zod_1.z.object({
62
62
  type: zod_1.z.literal('regexFileKey'),
63
- path: zod_1.z.string().min(1).optional(),
64
- file: zod_1.z.string().min(1)
63
+ path: zod_1.z.string().min(1).optional().describe('JSONPath to check (omit to scan ALL values recursively)'),
64
+ file: zod_1.z.string().min(1).describe('Path to a YAML file whose keys are used as regex patterns')
65
65
  });
66
66
  const versionFormatRuleSchema = zod_1.z
67
67
  .object({
68
68
  type: zod_1.z.literal('versionFormat'),
69
- path: zod_1.z.string().min(1),
70
- vPrefix: zod_1.z.enum(['required', 'allowed', 'forbidden']).default('allowed')
69
+ path: zod_1.z.string().min(1).describe('JSONPath to the version field to validate format'),
70
+ vPrefix: zod_1.z
71
+ .enum(['required', 'allowed', 'forbidden'])
72
+ .default('allowed')
73
+ .describe('"required" = must start with v, "allowed" = either, "forbidden" = no v (default: "allowed")')
71
74
  })
72
75
  .strict();
73
76
  const stopRuleSchema = zod_1.z.discriminatedUnion('type', [
@@ -80,11 +83,17 @@ const stopRuleSchema = zod_1.z.discriminatedUnion('type', [
80
83
  versionFormatRuleSchema
81
84
  ]);
82
85
  const arraySortRuleSchema = zod_1.z.object({
83
- path: zod_1.z.string().min(1),
84
- sortBy: zod_1.z.string().min(1).optional(),
85
- order: zod_1.z.enum(['asc', 'desc']).default('asc')
86
+ path: zod_1.z.string().min(1).describe('JSONPath to the array to sort'),
87
+ sortBy: zod_1.z
88
+ .string()
89
+ .min(1)
90
+ .optional()
91
+ .describe('Object property to sort by (required for arrays of objects; omit for scalar arrays)'),
92
+ order: zod_1.z.enum(['asc', 'desc']).default('asc').describe('Sort direction: "asc" or "desc" (default: "asc")')
93
+ });
94
+ const keySortRuleSchema = zod_1.z.object({
95
+ path: zod_1.z.string().min(1).describe('JSONPath to the object whose keys should be sorted alphabetically')
86
96
  });
87
- const keySortRuleSchema = zod_1.z.object({ path: zod_1.z.string().min(1) });
88
97
  const DANGEROUS_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
89
98
  const hasDangerousKeys = (value) => {
90
99
  if (value === null || typeof value !== 'object')
@@ -139,37 +148,74 @@ const transformRulesSchema = zod_1.z
139
148
  data.filenameFile !== undefined, {
140
149
  message: 'At least one of content, filename, contentFile, or filenameFile must be specified'
141
150
  });
142
- const baseConfigSchema = zod_1.z.object({
143
- extends: zod_1.z.string().min(1).optional(),
151
+ exports.baseConfigSchema = zod_1.z.object({
152
+ extends: zod_1.z.string().min(1).optional().describe('Path to a parent config file to inherit from (up to 5 levels deep)'),
144
153
  requiredVersion: zod_1.z
145
154
  .string()
146
155
  .min(1)
147
156
  .regex(/^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/, {
148
157
  message: 'Must be a valid semver version (e.g., "1.2.3" or "v1.2.3")'
149
158
  })
150
- .optional(),
151
- source: zod_1.z.string().min(1).optional(),
152
- destination: zod_1.z.string().min(1).optional(),
153
- include: zod_1.z.array(zod_1.z.string().min(1)).optional(),
154
- exclude: zod_1.z.array(zod_1.z.string().min(1)).optional(),
155
- prune: zod_1.z.boolean().optional(),
156
- confirmationDelay: zod_1.z.number().int().min(0).optional(),
157
- skipPath: zod_1.z.record(zod_1.z.string(), zod_1.z.array(zod_1.z.string())).optional(),
159
+ .optional()
160
+ .describe('Minimum required hed CLI version (e.g. "1.2.3"); also suppresses auto-update notifications'),
161
+ source: zod_1.z.string().min(1).optional().describe('Path to the source directory (environment to copy FROM)'),
162
+ destination: zod_1.z.string().min(1).optional().describe('Path to the destination directory (environment to sync INTO)'),
163
+ include: zod_1.z.array(zod_1.z.string().min(1)).optional().describe('Glob patterns for files to include (default: ["**/*"])'),
164
+ exclude: zod_1.z.array(zod_1.z.string().min(1)).optional().describe('Glob patterns for files to exclude from processing'),
165
+ prune: zod_1.z.boolean().optional().describe('Delete destination files that no longer exist in source (default: false)'),
166
+ confirmationDelay: zod_1.z
167
+ .number()
168
+ .int()
169
+ .min(0)
170
+ .optional()
171
+ .describe('Milliseconds to pause before applying changes (default: 3000; 0 = no delay)'),
172
+ skipPath: zod_1.z
173
+ .record(zod_1.z.string(), zod_1.z.array(zod_1.z.string()))
174
+ .optional()
175
+ .describe('Per-file-glob map of JSONPath patterns whose destination values are preserved'),
158
176
  outputFormat: zod_1.z
159
177
  .object({
160
- indent: zod_1.z.number().int().min(1).max(10).optional(),
161
- keySeparator: zod_1.z.boolean().optional(),
162
- quoteValues: zod_1.z.record(zod_1.z.string(), zod_1.z.array(zod_1.z.string())).optional(),
163
- keyOrders: zod_1.z.record(zod_1.z.string(), zod_1.z.array(zod_1.z.string())).optional(),
164
- keySort: zod_1.z.record(zod_1.z.string(), zod_1.z.array(keySortRuleSchema)).optional(),
165
- arraySort: zod_1.z.record(zod_1.z.string(), zod_1.z.array(arraySortRuleSchema)).optional()
178
+ indent: zod_1.z
179
+ .number()
180
+ .int()
181
+ .min(1)
182
+ .max(10)
183
+ .optional()
184
+ .describe('Number of spaces for YAML indentation (1–10, default: 2)'),
185
+ keySeparator: zod_1.z.boolean().optional().describe('Insert a blank line between top-level keys (default: false)'),
186
+ quoteValues: zod_1.z
187
+ .record(zod_1.z.string(), zod_1.z.array(zod_1.z.string()))
188
+ .optional()
189
+ .describe('Per-file-glob map of JSONPath patterns whose values should be quoted'),
190
+ keyOrders: zod_1.z
191
+ .record(zod_1.z.string(), zod_1.z.array(zod_1.z.string()))
192
+ .optional()
193
+ .describe('Per-file-glob explicit key ordering arrays'),
194
+ keySort: zod_1.z
195
+ .record(zod_1.z.string(), zod_1.z.array(keySortRuleSchema))
196
+ .optional()
197
+ .describe('Per-file-glob rules for alphabetically sorting object keys at a path'),
198
+ arraySort: zod_1.z
199
+ .record(zod_1.z.string(), zod_1.z.array(arraySortRuleSchema))
200
+ .optional()
201
+ .describe('Per-file-glob rules for sorting array elements at a path')
166
202
  })
167
- .optional(),
168
- transforms: zod_1.z.record(zod_1.z.string(), transformRulesSchema).optional(),
169
- stopRules: zod_1.z.record(zod_1.z.string(), zod_1.z.array(stopRuleSchema)).optional(),
170
- fixedValues: zod_1.z.record(zod_1.z.string(), zod_1.z.array(fixedValueRuleSchema)).optional()
203
+ .optional()
204
+ .describe('YAML output formatting rules applied after merge'),
205
+ transforms: zod_1.z
206
+ .record(zod_1.z.string(), transformRulesSchema)
207
+ .optional()
208
+ .describe('Per-file-glob regex and file-based content/filename transform rules'),
209
+ stopRules: zod_1.z
210
+ .record(zod_1.z.string(), zod_1.z.array(stopRuleSchema))
211
+ .optional()
212
+ .describe('Per-file-glob validation rules that block sync if triggered (use --force to override)'),
213
+ fixedValues: zod_1.z
214
+ .record(zod_1.z.string(), zod_1.z.array(fixedValueRuleSchema))
215
+ .optional()
216
+ .describe('Per-file-glob rules that pin specific JSONPath locations to constant values after merge')
171
217
  });
172
- const finalConfigSchema = baseConfigSchema
218
+ const finalConfigSchema = exports.baseConfigSchema
173
219
  .omit({ extends: true })
174
220
  .required({ source: true, destination: true })
175
221
  .extend({
@@ -197,7 +243,7 @@ const finalConfigSchema = baseConfigSchema
197
243
  message: 'Source and destination folders cannot be the same',
198
244
  path: ['destination']
199
245
  });
200
- const formatOnlyConfigSchema = baseConfigSchema
246
+ const formatOnlyConfigSchema = exports.baseConfigSchema
201
247
  .omit({ extends: true })
202
248
  .required({ destination: true })
203
249
  .extend({
@@ -218,7 +264,7 @@ const formatOnlyConfigSchema = baseConfigSchema
218
264
  .default({ indent: 2, keySeparator: false })
219
265
  });
220
266
  const parseBaseConfig = (data, configPath) => {
221
- const result = baseConfigSchema.safeParse(data);
267
+ const result = exports.baseConfigSchema.safeParse(data);
222
268
  if (!result.success)
223
269
  throw new ZodError_1.ZodValidationError(result.error, configPath);
224
270
  return result.data;
@@ -1,3 +1,4 @@
1
+ import type { Logger } from '../logger';
1
2
  import { type FinalConfig, type FormatOnlyConfig } from './configFile';
2
3
  declare const ConfigLoaderErrorClass: {
3
4
  new (message: string, options?: import("../utils").ErrorOptions): {
@@ -21,5 +22,5 @@ export type Config = FinalConfig;
21
22
  export type LoadConfigOptions = {
22
23
  formatOnly?: boolean;
23
24
  };
24
- export declare const loadConfigFile: (configPath: string, quiet?: boolean, logger?: import("../logger").Logger, options?: LoadConfigOptions) => FinalConfig | FormatOnlyConfig;
25
+ export declare const loadConfigFile: (configPath: string, quiet?: boolean, logger?: Logger, options?: LoadConfigOptions) => FinalConfig | FormatOnlyConfig;
25
26
  export {};
@@ -1,3 +1,4 @@
1
+ import type { Logger } from '../logger';
1
2
  import { type BaseConfig } from './configFile';
2
3
  declare const ConfigMergerErrorClass: {
3
4
  new (message: string, options?: import("../utils/errors").ErrorOptions): {
@@ -18,5 +19,5 @@ export declare class ConfigMergerError extends ConfigMergerErrorClass {
18
19
  }
19
20
  export declare const isConfigMergerError: (error: unknown) => error is ConfigMergerError;
20
21
  export declare const mergeConfigs: (parent: BaseConfig, child: BaseConfig) => BaseConfig;
21
- export declare const resolveConfigWithExtends: (configPath: string, visited?: Set<string>, depth?: number, logger?: import("../logger").Logger) => BaseConfig;
22
+ export declare const resolveConfigWithExtends: (configPath: string, visited?: Set<string>, depth?: number, logger?: Logger) => BaseConfig;
22
23
  export {};