@udondan/avanti 0.24.0 → 0.26.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 (100) hide show
  1. package/README.md +488 -64
  2. package/dist/commands/diff.d.ts.map +1 -1
  3. package/dist/commands/diff.js +82 -18
  4. package/dist/commands/diff.js.map +1 -1
  5. package/dist/commands/lock.d.ts.map +1 -1
  6. package/dist/commands/lock.js +6 -4
  7. package/dist/commands/lock.js.map +1 -1
  8. package/dist/commands/log.d.ts.map +1 -1
  9. package/dist/commands/log.js +7 -5
  10. package/dist/commands/log.js.map +1 -1
  11. package/dist/commands/pull.d.ts.map +1 -1
  12. package/dist/commands/pull.js +682 -42
  13. package/dist/commands/pull.js.map +1 -1
  14. package/dist/commands/reset.d.ts.map +1 -1
  15. package/dist/commands/reset.js +43 -11
  16. package/dist/commands/reset.js.map +1 -1
  17. package/dist/commands/revert.d.ts.map +1 -1
  18. package/dist/commands/revert.js +68 -14
  19. package/dist/commands/revert.js.map +1 -1
  20. package/dist/condition.d.ts.map +1 -1
  21. package/dist/condition.js +9 -6
  22. package/dist/condition.js.map +1 -1
  23. package/dist/config-writeback.d.ts.map +1 -1
  24. package/dist/config-writeback.js +17 -0
  25. package/dist/config-writeback.js.map +1 -1
  26. package/dist/config.d.ts +12 -0
  27. package/dist/config.d.ts.map +1 -1
  28. package/dist/config.js +346 -3
  29. package/dist/config.js.map +1 -1
  30. package/dist/diff.d.ts +19 -0
  31. package/dist/diff.d.ts.map +1 -1
  32. package/dist/diff.js +317 -12
  33. package/dist/diff.js.map +1 -1
  34. package/dist/extract.d.ts +4 -0
  35. package/dist/extract.d.ts.map +1 -0
  36. package/dist/extract.js +142 -0
  37. package/dist/extract.js.map +1 -0
  38. package/dist/filter.d.ts +3 -0
  39. package/dist/filter.d.ts.map +1 -0
  40. package/dist/filter.js +126 -0
  41. package/dist/filter.js.map +1 -0
  42. package/dist/history.d.ts +7 -1
  43. package/dist/history.d.ts.map +1 -1
  44. package/dist/history.js +89 -9
  45. package/dist/history.js.map +1 -1
  46. package/dist/paths.d.ts +4 -0
  47. package/dist/paths.d.ts.map +1 -1
  48. package/dist/paths.js +97 -0
  49. package/dist/paths.js.map +1 -1
  50. package/dist/processors/ini.d.ts +33 -0
  51. package/dist/processors/ini.d.ts.map +1 -0
  52. package/dist/processors/ini.js +500 -0
  53. package/dist/processors/ini.js.map +1 -0
  54. package/dist/processors/insert.d.ts.map +1 -1
  55. package/dist/processors/insert.js +338 -15
  56. package/dist/processors/insert.js.map +1 -1
  57. package/dist/processors/on.d.ts +4 -0
  58. package/dist/processors/on.d.ts.map +1 -0
  59. package/dist/processors/on.js +54 -0
  60. package/dist/processors/on.js.map +1 -0
  61. package/dist/ref.d.ts +21 -0
  62. package/dist/ref.d.ts.map +1 -0
  63. package/dist/ref.js +65 -0
  64. package/dist/ref.js.map +1 -0
  65. package/dist/sources/bitbucket.d.ts.map +1 -1
  66. package/dist/sources/bitbucket.js +51 -12
  67. package/dist/sources/bitbucket.js.map +1 -1
  68. package/dist/sources/git.d.ts.map +1 -1
  69. package/dist/sources/git.js +54 -6
  70. package/dist/sources/git.js.map +1 -1
  71. package/dist/sources/github.d.ts.map +1 -1
  72. package/dist/sources/github.js +188 -51
  73. package/dist/sources/github.js.map +1 -1
  74. package/dist/sources/gitlab.d.ts.map +1 -1
  75. package/dist/sources/gitlab.js +242 -44
  76. package/dist/sources/gitlab.js.map +1 -1
  77. package/dist/sources/index.d.ts +4 -2
  78. package/dist/sources/index.d.ts.map +1 -1
  79. package/dist/sources/index.js +220 -49
  80. package/dist/sources/index.js.map +1 -1
  81. package/dist/sources/local.d.ts +3 -0
  82. package/dist/sources/local.d.ts.map +1 -1
  83. package/dist/sources/local.js +44 -0
  84. package/dist/sources/local.js.map +1 -1
  85. package/dist/types.d.ts +38 -2
  86. package/dist/types.d.ts.map +1 -1
  87. package/dist/types.js.map +1 -1
  88. package/dist/variables-remote.d.ts +1 -1
  89. package/dist/variables-remote.d.ts.map +1 -1
  90. package/dist/variables-remote.js +4 -3
  91. package/dist/variables-remote.js.map +1 -1
  92. package/dist/variables.d.ts +1 -0
  93. package/dist/variables.d.ts.map +1 -1
  94. package/dist/variables.js +37 -2
  95. package/dist/variables.js.map +1 -1
  96. package/dist/writer.d.ts +17 -0
  97. package/dist/writer.d.ts.map +1 -1
  98. package/dist/writer.js +848 -20
  99. package/dist/writer.js.map +1 -1
  100. package/package.json +14 -10
@@ -37,6 +37,8 @@ exports._testable = void 0;
37
37
  exports.resolveJsonOptions = resolveJsonOptions;
38
38
  exports.resolveYamlOptions = resolveYamlOptions;
39
39
  exports.resolveTomlOptions = resolveTomlOptions;
40
+ exports.resolveIniOptions = resolveIniOptions;
41
+ exports.formatSourceLabel = formatSourceLabel;
40
42
  exports.fetchSource = fetchSource;
41
43
  const crypto = __importStar(require("crypto"));
42
44
  const os = __importStar(require("os"));
@@ -60,10 +62,15 @@ const vault_1 = require("./vault");
60
62
  const json_1 = require("../processors/json");
61
63
  const yaml_1 = require("../processors/yaml");
62
64
  const toml_1 = require("../processors/toml");
65
+ const ini_1 = require("../processors/ini");
63
66
  const binary_1 = require("../binary");
67
+ const filter_1 = require("../filter");
68
+ const extract_1 = require("../extract");
69
+ const config_1 = require("../config");
64
70
  const JSON_EXTENSIONS = new Set(['.json', '.jsonc']);
65
71
  const YAML_EXTENSIONS = new Set(['.yaml', '.yml']);
66
72
  const TOML_EXTENSIONS = new Set(['.toml']);
73
+ const INI_EXTENSIONS = new Set(['.ini', '.cfg']);
67
74
  function srcFilename(src) {
68
75
  if (typeof src === 'string') {
69
76
  if (src.startsWith('http://') || src.startsWith('https://')) {
@@ -144,6 +151,12 @@ function hasTomlExtension(src) {
144
151
  return false;
145
152
  return TOML_EXTENSIONS.has(path.extname(name).toLowerCase());
146
153
  }
154
+ function hasIniExtension(src) {
155
+ const name = srcFilename(src);
156
+ if (!name)
157
+ return false;
158
+ return INI_EXTENSIONS.has(path.extname(name).toLowerCase());
159
+ }
147
160
  function resolveJsonOptions(entry, srcs) {
148
161
  const { json } = entry;
149
162
  if (json === false)
@@ -183,12 +196,40 @@ function resolveTomlOptions(entry, srcs) {
183
196
  return {};
184
197
  return null;
185
198
  }
199
+ function resolveIniOptions(entry, srcs) {
200
+ const { ini } = entry;
201
+ if (ini === false)
202
+ return null;
203
+ if (ini === true)
204
+ return {};
205
+ if (ini !== undefined && typeof ini === 'object')
206
+ return ini;
207
+ // Auto-detect: all sources have an INI file extension
208
+ if (srcs.length > 0 && srcs.every(hasIniExtension))
209
+ return {};
210
+ return null;
211
+ }
186
212
  // labelForSrc returns the source label used in SourceFetchRecord. Structured
187
213
  // sources (github:, gitlab:, etc.) keep variable references unresolved so the
188
214
  // label matches the literal YAML values that applyUpdatedShas reads for SHA
189
215
  // writeback. Plain-string sources resolve variables since they don't support
190
- // SHA pinning.
216
+ // SHA pinning. When a filter is present a NUL-byte separator (\x00) is used
217
+ // before "filter:<json>" so the label is unambiguous even if the base contains
218
+ // the display string " | filter:" (e.g. a local path with that literal text).
219
+ // Use formatSourceLabel() to convert to a human-readable form for display.
191
220
  function labelForSrc(src, vars) {
221
+ const base = baseLabelForSrc(src, vars);
222
+ const filter = filterForSrc(src);
223
+ if (filter && filter.length > 0)
224
+ return `${base}\x00filter:${JSON.stringify(filter)}`;
225
+ return base;
226
+ }
227
+ // formatSourceLabel converts an internal label (which may contain a NUL-byte
228
+ // filter separator) into a human-readable string for log/error output.
229
+ function formatSourceLabel(label) {
230
+ return label.replace('\x00filter:', ' | filter:');
231
+ }
232
+ function baseLabelForSrc(src, vars) {
192
233
  if (typeof src === 'string')
193
234
  return (0, variables_1.resolveVars)(src, vars);
194
235
  if ('github' in src) {
@@ -253,6 +294,7 @@ function labelForSrc(src, vars) {
253
294
  // using variables are correctly distinguished when vars change between
254
295
  // stabilization iterations, and raw: sources are keyed by their content.
255
296
  function cacheKeyForSrc(src, vars) {
297
+ let base;
256
298
  if (typeof src === 'string')
257
299
  return (0, variables_1.resolveVars)(src, vars);
258
300
  if ('github' in src) {
@@ -265,12 +307,14 @@ function cacheKeyForSrc(src, vars) {
265
307
  : (src.github.via ?? 'api,cli');
266
308
  const via = viaStr === 'api,cli' ? '' : `(${viaStr})`;
267
309
  if ('release' in src.github) {
268
- return `github${host}:${(0, variables_1.resolveVars)(src.github.repo, vars)}:release:${(0, variables_1.resolveVars)(src.github.release, vars)}${via}`;
310
+ base = `github${host}:${(0, variables_1.resolveVars)(src.github.repo, vars)}:release:${(0, variables_1.resolveVars)(src.github.release, vars)}${via}`;
311
+ }
312
+ else {
313
+ const ref = src.github.ref ? `@${(0, variables_1.resolveVars)(src.github.ref, vars)}` : '';
314
+ base = `github${host}:${(0, variables_1.resolveVars)(src.github.repo, vars)}:${(0, variables_1.resolveVars)(src.github.file, vars)}${ref}${via}`;
269
315
  }
270
- const ref = src.github.ref ? `@${(0, variables_1.resolveVars)(src.github.ref, vars)}` : '';
271
- return `github${host}:${(0, variables_1.resolveVars)(src.github.repo, vars)}:${(0, variables_1.resolveVars)(src.github.file, vars)}${ref}${via}`;
272
316
  }
273
- if ('gitlab' in src) {
317
+ else if ('gitlab' in src) {
274
318
  const resolvedHost = src.gitlab.host
275
319
  ? (0, variables_1.resolveVars)(src.gitlab.host, vars).trim()
276
320
  : '';
@@ -280,12 +324,14 @@ function cacheKeyForSrc(src, vars) {
280
324
  : (src.gitlab.via ?? 'api,cli');
281
325
  const via = viaStr === 'api,cli' ? '' : `(${viaStr})`;
282
326
  if ('release' in src.gitlab) {
283
- return `gitlab${host}:${(0, variables_1.resolveVars)(src.gitlab.project, vars)}:release:${(0, variables_1.resolveVars)(src.gitlab.release, vars)}${via}`;
327
+ base = `gitlab${host}:${(0, variables_1.resolveVars)(src.gitlab.project, vars)}:release:${(0, variables_1.resolveVars)(src.gitlab.release, vars)}${via}`;
328
+ }
329
+ else {
330
+ const ref = src.gitlab.ref ? `@${(0, variables_1.resolveVars)(src.gitlab.ref, vars)}` : '';
331
+ base = `gitlab${host}:${(0, variables_1.resolveVars)(src.gitlab.project, vars)}:${(0, variables_1.resolveVars)(src.gitlab.file, vars)}${ref}${via}`;
284
332
  }
285
- const ref = src.gitlab.ref ? `@${(0, variables_1.resolveVars)(src.gitlab.ref, vars)}` : '';
286
- return `gitlab${host}:${(0, variables_1.resolveVars)(src.gitlab.project, vars)}:${(0, variables_1.resolveVars)(src.gitlab.file, vars)}${ref}${via}`;
287
333
  }
288
- if ('bitbucket' in src) {
334
+ else if ('bitbucket' in src) {
289
335
  const ref = src.bitbucket.ref
290
336
  ? `@${(0, variables_1.resolveVars)(src.bitbucket.ref, vars)}`
291
337
  : '';
@@ -293,17 +339,19 @@ function cacheKeyForSrc(src, vars) {
293
339
  ? (0, variables_1.resolveVars)(src.bitbucket.host, vars).trim()
294
340
  : '';
295
341
  const host = resolvedHost ? `[${resolvedHost}]` : '';
296
- return `bitbucket${host}:${(0, variables_1.resolveVars)(src.bitbucket.workspace, vars)}/${(0, variables_1.resolveVars)(src.bitbucket.repo, vars)}:${(0, variables_1.resolveVars)(src.bitbucket.file, vars)}${ref}`;
342
+ base = `bitbucket${host}:${(0, variables_1.resolveVars)(src.bitbucket.workspace, vars)}/${(0, variables_1.resolveVars)(src.bitbucket.repo, vars)}:${(0, variables_1.resolveVars)(src.bitbucket.file, vars)}${ref}`;
297
343
  }
298
- if ('git' in src) {
344
+ else if ('git' in src) {
299
345
  const ref = src.git.ref ? `@${(0, variables_1.resolveVars)(src.git.ref, vars)}` : '';
300
- return `git:${(0, variables_1.resolveVars)(src.git.repo, vars)}:${(0, variables_1.resolveVars)(src.git.file, vars)}${ref}`;
346
+ base = `git:${(0, variables_1.resolveVars)(src.git.repo, vars)}:${(0, variables_1.resolveVars)(src.git.file, vars)}${ref}`;
301
347
  }
302
- if ('exec' in src)
348
+ else if ('exec' in src) {
303
349
  return `exec:${(0, variables_1.resolveVars)(src.exec, vars)}`;
304
- if ('aws_s3' in src)
305
- return `aws_s3:${(0, variables_1.resolveVars)(src.aws_s3, vars)}`;
306
- if ('aws_secrets_manager' in src) {
350
+ }
351
+ else if ('aws_s3' in src) {
352
+ base = `aws_s3:${(0, variables_1.resolveVars)(src.aws_s3, vars)}`;
353
+ }
354
+ else if ('aws_secrets_manager' in src) {
307
355
  const k = src.aws_secrets_manager.key !== undefined
308
356
  ? `#${(0, variables_1.resolveVars)(src.aws_secrets_manager.key, vars)}`
309
357
  : '';
@@ -312,28 +360,35 @@ function cacheKeyForSrc(src, vars) {
312
360
  : '';
313
361
  return `aws_secrets_manager:${(0, variables_1.resolveVars)(src.aws_secrets_manager.name, vars)}${k}${r}`;
314
362
  }
315
- if ('aws_systems_manager_parameter' in src) {
363
+ else if ('aws_systems_manager_parameter' in src) {
316
364
  const r = src.aws_systems_manager_parameter.region !== undefined
317
365
  ? `@${(0, variables_1.resolveVars)(src.aws_systems_manager_parameter.region, vars)}`
318
366
  : '';
319
367
  return `aws_systems_manager_parameter:${(0, variables_1.resolveVars)(src.aws_systems_manager_parameter.name, vars)}${r}`;
320
368
  }
321
- if ('vault' in src) {
369
+ else if ('vault' in src) {
322
370
  const field = src.vault.field
323
371
  ? `#${(0, variables_1.resolveVars)(src.vault.field, vars)}`
324
372
  : '';
325
373
  return `vault:${(0, variables_1.resolveVars)(src.vault.path, vars)}${field}`;
326
374
  }
327
- if ('http' in src)
375
+ else if ('http' in src) {
328
376
  return `http:${(0, variables_1.resolveVars)(src.http, vars)}`;
329
- if ('path' in src)
330
- return `path:${(0, variables_1.resolveVars)(src.path, vars)}`;
331
- if ('url' in src)
377
+ }
378
+ else if ('path' in src) {
379
+ base = `path:${(0, variables_1.resolveVars)(src.path, vars)}`;
380
+ }
381
+ else if ('url' in src) {
332
382
  return `url:${(0, variables_1.resolveVars)(src.url, vars)}`;
333
- // raw: key includes the resolved content so distinct raw values don't collide
334
- if ('raw' in src)
383
+ }
384
+ else if ('raw' in src) {
385
+ // raw: key includes the resolved content so distinct raw values don't collide
335
386
  return `raw:${(0, variables_1.resolveVars)(src.raw, vars)}`;
336
- return JSON.stringify(src);
387
+ }
388
+ else {
389
+ return JSON.stringify(src);
390
+ }
391
+ return base;
337
392
  }
338
393
  function expectedShaForSrc(src) {
339
394
  if (typeof src === 'string')
@@ -383,6 +438,23 @@ function buildRecord(src, files, vars) {
383
438
  matched: expectedSha === undefined || expectedSha === observedSha,
384
439
  };
385
440
  }
441
+ function filterForSrc(src) {
442
+ if (typeof src === 'string')
443
+ return undefined;
444
+ if ('github' in src)
445
+ return src.filter;
446
+ if ('gitlab' in src)
447
+ return src.filter;
448
+ if ('bitbucket' in src)
449
+ return src.filter;
450
+ if ('git' in src)
451
+ return src.filter;
452
+ if ('aws_s3' in src)
453
+ return src.filter;
454
+ if ('path' in src)
455
+ return src.filter;
456
+ return undefined;
457
+ }
386
458
  function computeFilesSha(files) {
387
459
  // Always include filename in the hash so a rename/path change affects the SHA
388
460
  // consistently, whether the source resolves to one file or many.
@@ -396,21 +468,46 @@ function computeFilesSha(files) {
396
468
  }
397
469
  return hash.digest('hex');
398
470
  }
399
- async function _fetchOneSrcRaw(src, workingDir, vars) {
471
+ async function _fetchOneSrcRaw(src, workingDir, vars, configBase) {
400
472
  if ((0, logger_1.isVerbose)())
401
- (0, logger_1.verbose)(`fetching source: ${(0, fetch_1.redactUrl)(labelForSrc(src, vars))}`);
473
+ (0, logger_1.verbose)(`fetching source: ${(0, fetch_1.redactUrl)(formatSourceLabel(labelForSrc(src, vars)))}`);
402
474
  if (typeof src === 'string') {
403
475
  const resolved = (0, variables_1.resolveVars)(src, vars);
404
- if (resolved.startsWith('http://') || resolved.startsWith('https://')) {
405
- const content = await (0, http_1.fetchHttp)(resolved);
406
- const filename = (0, http_1.inferFilenameFromUrl)(resolved) ?? 'download';
476
+ const effective = configBase !== undefined
477
+ ? (0, config_1.resolveRelativeSrc)(resolved, configBase)
478
+ : resolved;
479
+ if (effective.startsWith('http://') || effective.startsWith('https://')) {
480
+ const content = await (0, http_1.fetchHttp)(effective);
481
+ const filename = (0, http_1.inferFilenameFromUrl)(effective) ?? 'download';
407
482
  return { files: new Map([[filename, content]]) };
408
483
  }
409
- if ((0, git_1.isGitRemoteUrl)(resolved)) {
410
- const { repo, file, ref } = (0, git_1.parseGitRemoteSpec)(resolved);
484
+ if ((0, git_1.isGitRemoteUrl)(effective)) {
485
+ const { repo, file, ref } = (0, git_1.parseGitRemoteSpec)(effective);
411
486
  return { files: (0, git_1.fetchGit)(repo, file, ref).files };
412
487
  }
413
- return { files: (0, local_1.fetchLocal)(resolved, workingDir).files };
488
+ if (effective.startsWith('github:')) {
489
+ let parsedGh;
490
+ try {
491
+ parsedGh = (0, config_1.parseGitHubSpec)(effective);
492
+ }
493
+ catch {
494
+ throw new Error(`Invalid github source spec "${effective}". Expected: github:owner/repo:path/to/file[@ref]`);
495
+ }
496
+ const result = await (0, github_1.fetchGitHub)(parsedGh.repo, parsedGh.file, parsedGh.ref);
497
+ return { files: result.files };
498
+ }
499
+ if (effective.startsWith('gitlab:')) {
500
+ let parsedGl;
501
+ try {
502
+ parsedGl = (0, config_1.parseGitLabSpec)(effective);
503
+ }
504
+ catch {
505
+ throw new Error(`Invalid gitlab source spec "${effective}". Expected: gitlab:group/project:path/to/file[@ref]`);
506
+ }
507
+ const result = await (0, gitlab_1.fetchGitLab)(parsedGl.project, parsedGl.file, parsedGl.ref);
508
+ return { files: result.files };
509
+ }
510
+ return { files: (0, local_1.fetchLocal)(effective, workingDir).files };
414
511
  }
415
512
  if ('raw' in src) {
416
513
  return {
@@ -531,7 +628,7 @@ async function _fetchOneSrcRaw(src, workingDir, vars) {
531
628
  }
532
629
  return { files };
533
630
  }
534
- async function fetchOneSrc(src, workingDir, vars, cache, getTargetPath = () => '', pendingWrites) {
631
+ async function fetchOneSrc(src, workingDir, vars, cache, getTargetPath = () => '', pendingWrites, configBase) {
535
632
  // Evaluate source-level conditions before the cache so a cached result for
536
633
  // the same URL is not returned when conditions gate this source out.
537
634
  if (typeof src !== 'string') {
@@ -547,7 +644,7 @@ async function fetchOneSrc(src, workingDir, vars, cache, getTargetPath = () => '
547
644
  // write target in this run should resolve to the future content, not whatever
548
645
  // is on disk (or in the cache from a previous iteration).
549
646
  if (pendingWrites !== undefined) {
550
- const pending = pendingLocalFiles(src, workingDir, vars, pendingWrites);
647
+ const pending = pendingLocalFiles(src, workingDir, vars, pendingWrites, configBase);
551
648
  if (pending !== null) {
552
649
  return { files: pending, record: buildRecord(src, pending, vars) };
553
650
  }
@@ -558,11 +655,11 @@ async function fetchOneSrc(src, workingDir, vars, cache, getTargetPath = () => '
558
655
  let skipped;
559
656
  if (cached !== undefined) {
560
657
  if ((0, logger_1.isVerbose)())
561
- (0, logger_1.verbose)(`cache hit: ${(0, fetch_1.redactUrl)(labelForSrc(src, vars))}`);
658
+ (0, logger_1.verbose)(`cache hit: ${(0, fetch_1.redactUrl)(formatSourceLabel(labelForSrc(src, vars)))}`);
562
659
  files = cached.files;
563
660
  }
564
661
  else {
565
- const raw = await _fetchOneSrcRaw(src, workingDir, vars);
662
+ const raw = await _fetchOneSrcRaw(src, workingDir, vars, configBase);
566
663
  files = raw.files;
567
664
  skipped = raw.skipped;
568
665
  // Don't cache skipped results: if optional changes to required between
@@ -572,11 +669,16 @@ async function fetchOneSrc(src, workingDir, vars, cache, getTargetPath = () => '
572
669
  }
573
670
  if (skipped)
574
671
  return { files: new Map(), record: null, skipped: true };
672
+ const rawFilter = filterForSrc(src);
673
+ const filterPatterns = rawFilter?.map((p) => (0, variables_1.resolveVars)(p, vars));
674
+ if (filterPatterns && filterPatterns.length > 0) {
675
+ files = (0, filter_1.applyFilter)(files, filterPatterns);
676
+ }
575
677
  // Recompute record from the current source spec so expectedSha/matched
576
678
  // always reflect the caller's config iteration, not the first fetch.
577
679
  return { files, record: buildRecord(src, files, vars) };
578
680
  }
579
- function pendingLocalFiles(src, workingDir, vars, pendingWrites) {
681
+ function pendingLocalFiles(src, workingDir, vars, pendingWrites, configBase) {
580
682
  let rawPath = null;
581
683
  try {
582
684
  if (typeof src === 'string') {
@@ -584,7 +686,16 @@ function pendingLocalFiles(src, workingDir, vars, pendingWrites) {
584
686
  if (!resolved.startsWith('http://') &&
585
687
  !resolved.startsWith('https://') &&
586
688
  !(0, git_1.isGitRemoteUrl)(resolved)) {
587
- rawPath = resolved;
689
+ const effective = configBase !== undefined
690
+ ? (0, config_1.resolveRelativeSrc)(resolved, configBase)
691
+ : resolved;
692
+ if (!effective.startsWith('http://') &&
693
+ !effective.startsWith('https://') &&
694
+ !(0, git_1.isGitRemoteUrl)(effective) &&
695
+ !effective.startsWith('github:') &&
696
+ !effective.startsWith('gitlab:')) {
697
+ rawPath = effective;
698
+ }
588
699
  }
589
700
  }
590
701
  else if ('path' in src) {
@@ -634,7 +745,7 @@ function assertTextFiles(files, context) {
634
745
  }
635
746
  }
636
747
  }
637
- async function fetchSource(entry, workingDir, vars = {}, cache, getTargetPathOverride, pendingWrites) {
748
+ async function fetchSource(entry, workingDir, vars = {}, cache, getTargetPathOverride, pendingWrites, configBase) {
638
749
  const { src } = entry;
639
750
  const getTargetPath = getTargetPathOverride ??
640
751
  (() => (0, paths_1.resolveTargetPath)(entry, '', workingDir, vars));
@@ -644,7 +755,7 @@ async function fetchSource(entry, workingDir, vars = {}, cache, getTargetPathOve
644
755
  const sourceRecords = [];
645
756
  for (let i = 0; i < src.length; i++) {
646
757
  try {
647
- const { files, record, skipped } = await fetchOneSrc(src[i], workingDir, vars, cache, getTargetPath, pendingWrites);
758
+ const { files, record, skipped } = await fetchOneSrc(src[i], workingDir, vars, cache, getTargetPath, pendingWrites, configBase);
648
759
  if (skipped)
649
760
  continue;
650
761
  assertTextFiles(files, `source ${i}`);
@@ -667,6 +778,9 @@ async function fetchSource(entry, workingDir, vars = {}, cache, getTargetPathOve
667
778
  const tomlOpts = jsonOpts === null && yamlOpts === null
668
779
  ? resolveTomlOptions(entry, src)
669
780
  : null;
781
+ const iniOpts = jsonOpts === null && yamlOpts === null && tomlOpts === null
782
+ ? resolveIniOptions(entry, src)
783
+ : null;
670
784
  let content;
671
785
  if (jsonOpts !== null) {
672
786
  content = (0, json_1.mergeJson)(parts, jsonOpts);
@@ -677,6 +791,9 @@ async function fetchSource(entry, workingDir, vars = {}, cache, getTargetPathOve
677
791
  else if (tomlOpts !== null) {
678
792
  content = (0, toml_1.mergeToml)(parts, tomlOpts);
679
793
  }
794
+ else if (iniOpts !== null) {
795
+ content = (0, ini_1.mergeIni)(parts, iniOpts);
796
+ }
680
797
  else {
681
798
  content = parts.join('\n');
682
799
  }
@@ -686,10 +803,37 @@ async function fetchSource(entry, workingDir, vars = {}, cache, getTargetPathOve
686
803
  };
687
804
  }
688
805
  // Single src — delegate dispatch to fetchOneSrc, then apply post-processing
689
- const { files: singleFiles, record: singleRecord, skipped, } = await fetchOneSrc(src, workingDir, vars, cache, getTargetPath, pendingWrites);
806
+ const { files: singleFiles, record: singleRecord, skipped, } = await fetchOneSrc(src, workingDir, vars, cache, getTargetPath, pendingWrites, configBase);
690
807
  if (skipped)
691
808
  return { files: new Map(), sourceRecords: [], allSkipped: true };
692
- const singleResult = { files: singleFiles };
809
+ let resolvedFiles = singleFiles;
810
+ if (entry.extract !== undefined) {
811
+ if (singleFiles.size !== 1) {
812
+ throw new Error(`"extract" requires a single-file source, but source returned ${singleFiles.size} file(s)`);
813
+ }
814
+ const [[archiveFilename, archiveBuffer]] = [...singleFiles.entries()];
815
+ if ((0, extract_1.detectArchiveFormat)(archiveFilename) === null) {
816
+ throw new Error(`"extract" was specified but "${archiveFilename}" is not a recognised archive format`);
817
+ }
818
+ const extracted = await (0, extract_1.extractArchive)(archiveBuffer, archiveFilename);
819
+ if (entry.extract === true) {
820
+ resolvedFiles = extracted;
821
+ }
822
+ else {
823
+ const resolvedPatterns = entry.extract.map((p) => (0, variables_1.resolveVars)(p, vars));
824
+ try {
825
+ resolvedFiles = (0, filter_1.applyFilter)(extracted, resolvedPatterns);
826
+ }
827
+ catch (err) {
828
+ if (err instanceof Error &&
829
+ err.message.startsWith('filter matched no files')) {
830
+ throw new Error(`extract matched no entries (${resolvedPatterns.length} pattern${resolvedPatterns.length === 1 ? '' : 's'}: ${resolvedPatterns.map((p) => JSON.stringify(p)).join(', ')})`, { cause: err });
831
+ }
832
+ throw err;
833
+ }
834
+ }
835
+ }
836
+ const singleResult = { files: resolvedFiles };
693
837
  const sourceRecords = singleRecord !== null ? [singleRecord] : [];
694
838
  // When a directory source resolves to multiple files but the target is a
695
839
  // single file (no trailing slash), merge all files sorted by name instead
@@ -704,7 +848,13 @@ async function fetchSource(entry, workingDir, vars = {}, cache, getTargetPathOve
704
848
  let dirTomlOpts = dirJsonOpts === null && dirYamlOpts === null
705
849
  ? resolveTomlOptions(entry, [src])
706
850
  : null;
707
- if (dirJsonOpts === null && dirYamlOpts === null && dirTomlOpts === null) {
851
+ let dirIniOpts = dirJsonOpts === null && dirYamlOpts === null && dirTomlOpts === null
852
+ ? resolveIniOptions(entry, [src])
853
+ : null;
854
+ if (dirJsonOpts === null &&
855
+ dirYamlOpts === null &&
856
+ dirTomlOpts === null &&
857
+ dirIniOpts === null) {
708
858
  const keys = Array.from(singleResult.files.keys());
709
859
  if (entry.json !== false &&
710
860
  keys.every((k) => JSON_EXTENSIONS.has(path.extname(k).toLowerCase()))) {
@@ -718,8 +868,15 @@ async function fetchSource(entry, workingDir, vars = {}, cache, getTargetPathOve
718
868
  keys.every((k) => TOML_EXTENSIONS.has(path.extname(k).toLowerCase()))) {
719
869
  dirTomlOpts = {};
720
870
  }
871
+ else if (entry.ini !== false &&
872
+ keys.every((k) => INI_EXTENSIONS.has(path.extname(k).toLowerCase()))) {
873
+ dirIniOpts = {};
874
+ }
721
875
  }
722
- if (dirJsonOpts !== null || dirYamlOpts !== null || dirTomlOpts !== null) {
876
+ if (dirJsonOpts !== null ||
877
+ dirYamlOpts !== null ||
878
+ dirTomlOpts !== null ||
879
+ dirIniOpts !== null) {
723
880
  assertTextFiles(singleResult.files, 'directory merge');
724
881
  const sortedValues = Array.from(singleResult.files.entries())
725
882
  .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
@@ -732,9 +889,12 @@ async function fetchSource(entry, workingDir, vars = {}, cache, getTargetPathOve
732
889
  else if (dirYamlOpts !== null) {
733
890
  merged = (0, yaml_1.mergeYaml)(sortedValues, dirYamlOpts);
734
891
  }
735
- else {
892
+ else if (dirTomlOpts !== null) {
736
893
  merged = (0, toml_1.mergeToml)(sortedValues, dirTomlOpts);
737
894
  }
895
+ else {
896
+ merged = (0, ini_1.mergeIni)(sortedValues, dirIniOpts);
897
+ }
738
898
  return {
739
899
  files: new Map([[filename, Buffer.from(merged, 'utf8')]]),
740
900
  sourceRecords,
@@ -747,9 +907,15 @@ async function fetchSource(entry, workingDir, vars = {}, cache, getTargetPathOve
747
907
  const singleTomlOpts = singleJsonOpts === null && singleYamlOpts === null
748
908
  ? resolveTomlOptions(entry, [src])
749
909
  : null;
910
+ const singleIniOpts = singleJsonOpts === null &&
911
+ singleYamlOpts === null &&
912
+ singleTomlOpts === null
913
+ ? resolveIniOptions(entry, [src])
914
+ : null;
750
915
  if (singleJsonOpts === null &&
751
916
  singleYamlOpts === null &&
752
- singleTomlOpts === null)
917
+ singleTomlOpts === null &&
918
+ singleIniOpts === null)
753
919
  return { ...singleResult, sourceRecords };
754
920
  const formatted = new Map();
755
921
  for (const [k, v] of singleResult.files) {
@@ -758,7 +924,9 @@ async function fetchSource(entry, workingDir, vars = {}, cache, getTargetPathOve
758
924
  ? 'json'
759
925
  : singleYamlOpts !== null
760
926
  ? 'yaml'
761
- : 'toml';
927
+ : singleTomlOpts !== null
928
+ ? 'toml'
929
+ : 'ini';
762
930
  throw new Error(`Binary file "${k}" cannot be formatted as ${fmtName}. Remove the format option or use a text source.`);
763
931
  }
764
932
  const text = v.toString('utf8');
@@ -768,9 +936,12 @@ async function fetchSource(entry, workingDir, vars = {}, cache, getTargetPathOve
768
936
  else if (singleYamlOpts !== null) {
769
937
  formatted.set(k, Buffer.from((0, yaml_1.formatYaml)(text), 'utf8'));
770
938
  }
771
- else {
939
+ else if (singleTomlOpts !== null) {
772
940
  formatted.set(k, Buffer.from((0, toml_1.formatToml)(text), 'utf8'));
773
941
  }
942
+ else {
943
+ formatted.set(k, Buffer.from((0, ini_1.formatIni)(text), 'utf8'));
944
+ }
774
945
  }
775
946
  return { files: formatted, sourceRecords };
776
947
  }