@guanghechen/commander 4.7.8 → 4.8.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/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Change Log
2
2
 
3
+ ## 4.8.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Expose preset directives in command help output under a dedicated `Preset Directives:` section.
8
+
9
+ ## 4.7.9
10
+
11
+ ### Patch Changes
12
+
13
+ - Support preset profile fallback by command-path suffix using dot notation, and expose resolved
14
+ preset envFile path metadata in input sources.
15
+
3
16
  ## 4.7.8
4
17
 
5
18
  ### Patch Changes
@@ -1333,6 +1333,7 @@ class CommandHelpRenderer {
1333
1333
  usage,
1334
1334
  arguments: argumentsLines,
1335
1335
  options,
1336
+ presetDirectives: params.presetDirectives ?? [],
1336
1337
  commands,
1337
1338
  examples,
1338
1339
  };
@@ -1403,6 +1404,14 @@ class CommandHelpRenderer {
1403
1404
  }
1404
1405
  lines.push('');
1405
1406
  }
1407
+ const presetDirectives = helpData.presetDirectives ?? [];
1408
+ if (presetDirectives.length > 0) {
1409
+ lines.push('Preset Directives:');
1410
+ for (const { sig, desc } of presetDirectives) {
1411
+ lines.push(this.#renderAlignedHelpLine(sig, desc, labelWidth));
1412
+ }
1413
+ lines.push('');
1414
+ }
1406
1415
  if (helpData.commands.length > 0) {
1407
1416
  lines.push('Commands:');
1408
1417
  for (const { name, desc } of helpData.commands) {
@@ -1442,6 +1451,14 @@ class CommandHelpRenderer {
1442
1451
  }
1443
1452
  lines.push('');
1444
1453
  }
1454
+ const presetDirectives = helpData.presetDirectives ?? [];
1455
+ if (presetDirectives.length > 0) {
1456
+ lines.push(styleText('Preset Directives:', TERMINAL_STYLE.bold, TERMINAL_STYLE.underline));
1457
+ for (const { sig, desc } of presetDirectives) {
1458
+ lines.push(this.#renderAlignedHelpLine(sig, desc, labelWidth, value => styleText(value, TERMINAL_STYLE.cyan)));
1459
+ }
1460
+ lines.push('');
1461
+ }
1445
1462
  if (helpData.commands.length > 0) {
1446
1463
  lines.push(styleText('Commands:', TERMINAL_STYLE.bold, TERMINAL_STYLE.underline));
1447
1464
  for (const { name, desc } of helpData.commands) {
@@ -1464,6 +1481,7 @@ class CommandHelpRenderer {
1464
1481
  const labels = [
1465
1482
  ...helpData.arguments.map(line => line.sig),
1466
1483
  ...helpData.options.map(line => line.sig),
1484
+ ...(helpData.presetDirectives ?? []).map(line => line.sig),
1467
1485
  ...helpData.commands.map(line => line.name),
1468
1486
  ];
1469
1487
  if (labels.length === 0) {
@@ -1774,11 +1792,14 @@ class CommandPresetProfileParser {
1774
1792
  return undefined;
1775
1793
  }
1776
1794
  const manifest = this.#parsePresetProfileManifest(content, profileFile.displayPath, commandPath);
1777
- const resolvedProfileSelector = presetProfile ?? manifest.defaults?.profile;
1778
- if (resolvedProfileSelector === undefined) {
1779
- throw new CommanderError('ConfigurationError', `missing profile for preset file "${profileFile.displayPath}": provide "${PRESET_PROFILE_FLAG}" or defaults.profile`, commandPath);
1780
- }
1781
- const { profileName: resolvedProfileName, variantName: explicitVariantName } = this.#parsePresetProfileSelector(resolvedProfileSelector, presetProfileSourceName ?? 'defaults.profile', commandPath);
1795
+ const resolvedProfileSelector = this.#resolvePresetProfileSelector({
1796
+ presetProfile,
1797
+ presetProfileSourceName,
1798
+ manifest,
1799
+ commandPath,
1800
+ presetFileDisplayPath: profileFile.displayPath,
1801
+ });
1802
+ const { profileName: resolvedProfileName, variantName: explicitVariantName } = this.#parsePresetProfileSelector(resolvedProfileSelector.selector, resolvedProfileSelector.sourceName, commandPath);
1782
1803
  const profile = manifest.profiles[resolvedProfileName];
1783
1804
  if (profile === undefined) {
1784
1805
  throw new CommanderError('ConfigurationError', `unknown preset profile "${resolvedProfileName}" in "${profileFile.displayPath}"`, commandPath);
@@ -1859,6 +1880,52 @@ class CommandPresetProfileParser {
1859
1880
  }
1860
1881
  }
1861
1882
  }
1883
+ #resolvePresetProfileSelector(params) {
1884
+ const { presetProfile, presetProfileSourceName, manifest, commandPath, presetFileDisplayPath } = params;
1885
+ if (presetProfile !== undefined) {
1886
+ return {
1887
+ selector: presetProfile,
1888
+ sourceName: presetProfileSourceName ?? PRESET_PROFILE_FLAG,
1889
+ };
1890
+ }
1891
+ const commandPathSuffixProfile = this.#resolveProfileNameByCommandPathSuffix({
1892
+ commandPath,
1893
+ profiles: manifest.profiles,
1894
+ });
1895
+ if (commandPathSuffixProfile !== undefined) {
1896
+ return {
1897
+ selector: commandPathSuffixProfile,
1898
+ sourceName: 'commandPath.suffix',
1899
+ };
1900
+ }
1901
+ if (manifest.defaults?.profile !== undefined) {
1902
+ return {
1903
+ selector: manifest.defaults.profile,
1904
+ sourceName: 'defaults.profile',
1905
+ };
1906
+ }
1907
+ if (manifest.profiles.default !== undefined) {
1908
+ return {
1909
+ selector: 'default',
1910
+ sourceName: 'profiles["default"]',
1911
+ };
1912
+ }
1913
+ throw new CommanderError('ConfigurationError', `missing profile for preset file "${presetFileDisplayPath}": provide "${PRESET_PROFILE_FLAG}", define command-path suffix profile, defaults.profile, or profile "default"`, commandPath);
1914
+ }
1915
+ #resolveProfileNameByCommandPathSuffix(params) {
1916
+ const { commandPath, profiles } = params;
1917
+ const commandNames = commandPath
1918
+ .split(' ')
1919
+ .map(value => value.trim())
1920
+ .filter(Boolean);
1921
+ for (let index = 0; index < commandNames.length; index += 1) {
1922
+ const profileName = commandNames.slice(index).join('.');
1923
+ if (profiles[profileName] !== undefined) {
1924
+ return profileName;
1925
+ }
1926
+ }
1927
+ return undefined;
1928
+ }
1862
1929
  #parsePresetProfileManifest(content, filepath, commandPath) {
1863
1930
  let parsed;
1864
1931
  try {
@@ -2786,7 +2853,7 @@ function scanPresetDirectives(params) {
2786
2853
  return { cleanArgv, presetFile, presetProfile };
2787
2854
  }
2788
2855
  function buildPresetSources(params) {
2789
- const { userCmds, userArgv, userEnvs, presetArgv, presetEnvs, presetMeta } = params;
2856
+ const { userCmds, userArgv, userEnvs, presetArgv, presetEnvs, presetMeta, presetResolvedEnvFile, } = params;
2790
2857
  const presetSourceMeta = presetMeta === undefined
2791
2858
  ? undefined
2792
2859
  : {
@@ -2794,6 +2861,11 @@ function buildPresetSources(params) {
2794
2861
  file: presetMeta.file,
2795
2862
  profile: presetMeta.profile,
2796
2863
  variant: presetMeta.variant,
2864
+ ...(presetResolvedEnvFile === undefined
2865
+ ? {}
2866
+ : {
2867
+ resolvedEnvFile: presetResolvedEnvFile,
2868
+ }),
2797
2869
  };
2798
2870
  const presetState = presetSourceMeta === undefined ? 'none' : 'applied';
2799
2871
  const sources = {
@@ -2900,9 +2972,17 @@ async function runPresetStage(params) {
2900
2972
  presetArgv,
2901
2973
  presetEnvs,
2902
2974
  presetMeta: resolvedProfile?.issueMeta,
2975
+ presetResolvedEnvFile: resolvePresetEnvFilePath(resolvedProfile),
2903
2976
  });
2904
2977
  return { tailArgv, envs, segments, sources };
2905
2978
  }
2979
+ function resolvePresetEnvFilePath(resolvedProfile) {
2980
+ if (!resolvedProfile) {
2981
+ return undefined;
2982
+ }
2983
+ return (resolvedProfile.variantEnvFileSource?.absolutePath ??
2984
+ resolvedProfile.profileEnvFileSource?.absolutePath);
2985
+ }
2906
2986
 
2907
2987
  function findSubcommandEntry(entries, token) {
2908
2988
  return entries.find(entry => entry.name === token || entry.aliases.includes(token));
@@ -3324,6 +3404,16 @@ class Command {
3324
3404
  commandPath: this.#getCommandPath(),
3325
3405
  arguments: this.#arguments,
3326
3406
  options: this.#resolveOptionPolicy().mergedOptions,
3407
+ presetDirectives: [
3408
+ {
3409
+ sig: `${PRESET_FILE_FLAG} <value>`,
3410
+ desc: 'Load preset manifest file',
3411
+ },
3412
+ {
3413
+ sig: `${PRESET_PROFILE_FLAG} <value>`,
3414
+ desc: 'Select preset profile: <profile> or <profile>:<variant>; requires --preset-file or command preset.file',
3415
+ },
3416
+ ],
3327
3417
  supportsBuiltinVersion: this.#supportsBuiltinVersion(),
3328
3418
  subcommands,
3329
3419
  examples: this.#examples,
package/lib/cjs/node.cjs CHANGED
@@ -1346,6 +1346,7 @@ class CommandHelpRenderer {
1346
1346
  usage,
1347
1347
  arguments: argumentsLines,
1348
1348
  options,
1349
+ presetDirectives: params.presetDirectives ?? [],
1349
1350
  commands,
1350
1351
  examples,
1351
1352
  };
@@ -1416,6 +1417,14 @@ class CommandHelpRenderer {
1416
1417
  }
1417
1418
  lines.push('');
1418
1419
  }
1420
+ const presetDirectives = helpData.presetDirectives ?? [];
1421
+ if (presetDirectives.length > 0) {
1422
+ lines.push('Preset Directives:');
1423
+ for (const { sig, desc } of presetDirectives) {
1424
+ lines.push(this.#renderAlignedHelpLine(sig, desc, labelWidth));
1425
+ }
1426
+ lines.push('');
1427
+ }
1419
1428
  if (helpData.commands.length > 0) {
1420
1429
  lines.push('Commands:');
1421
1430
  for (const { name, desc } of helpData.commands) {
@@ -1455,6 +1464,14 @@ class CommandHelpRenderer {
1455
1464
  }
1456
1465
  lines.push('');
1457
1466
  }
1467
+ const presetDirectives = helpData.presetDirectives ?? [];
1468
+ if (presetDirectives.length > 0) {
1469
+ lines.push(styleText('Preset Directives:', TERMINAL_STYLE.bold, TERMINAL_STYLE.underline));
1470
+ for (const { sig, desc } of presetDirectives) {
1471
+ lines.push(this.#renderAlignedHelpLine(sig, desc, labelWidth, value => styleText(value, TERMINAL_STYLE.cyan)));
1472
+ }
1473
+ lines.push('');
1474
+ }
1458
1475
  if (helpData.commands.length > 0) {
1459
1476
  lines.push(styleText('Commands:', TERMINAL_STYLE.bold, TERMINAL_STYLE.underline));
1460
1477
  for (const { name, desc } of helpData.commands) {
@@ -1477,6 +1494,7 @@ class CommandHelpRenderer {
1477
1494
  const labels = [
1478
1495
  ...helpData.arguments.map(line => line.sig),
1479
1496
  ...helpData.options.map(line => line.sig),
1497
+ ...(helpData.presetDirectives ?? []).map(line => line.sig),
1480
1498
  ...helpData.commands.map(line => line.name),
1481
1499
  ];
1482
1500
  if (labels.length === 0) {
@@ -1787,11 +1805,14 @@ class CommandPresetProfileParser {
1787
1805
  return undefined;
1788
1806
  }
1789
1807
  const manifest = this.#parsePresetProfileManifest(content, profileFile.displayPath, commandPath);
1790
- const resolvedProfileSelector = presetProfile ?? manifest.defaults?.profile;
1791
- if (resolvedProfileSelector === undefined) {
1792
- throw new CommanderError('ConfigurationError', `missing profile for preset file "${profileFile.displayPath}": provide "${PRESET_PROFILE_FLAG}" or defaults.profile`, commandPath);
1793
- }
1794
- const { profileName: resolvedProfileName, variantName: explicitVariantName } = this.#parsePresetProfileSelector(resolvedProfileSelector, presetProfileSourceName ?? 'defaults.profile', commandPath);
1808
+ const resolvedProfileSelector = this.#resolvePresetProfileSelector({
1809
+ presetProfile,
1810
+ presetProfileSourceName,
1811
+ manifest,
1812
+ commandPath,
1813
+ presetFileDisplayPath: profileFile.displayPath,
1814
+ });
1815
+ const { profileName: resolvedProfileName, variantName: explicitVariantName } = this.#parsePresetProfileSelector(resolvedProfileSelector.selector, resolvedProfileSelector.sourceName, commandPath);
1795
1816
  const profile = manifest.profiles[resolvedProfileName];
1796
1817
  if (profile === undefined) {
1797
1818
  throw new CommanderError('ConfigurationError', `unknown preset profile "${resolvedProfileName}" in "${profileFile.displayPath}"`, commandPath);
@@ -1872,6 +1893,52 @@ class CommandPresetProfileParser {
1872
1893
  }
1873
1894
  }
1874
1895
  }
1896
+ #resolvePresetProfileSelector(params) {
1897
+ const { presetProfile, presetProfileSourceName, manifest, commandPath, presetFileDisplayPath } = params;
1898
+ if (presetProfile !== undefined) {
1899
+ return {
1900
+ selector: presetProfile,
1901
+ sourceName: presetProfileSourceName ?? PRESET_PROFILE_FLAG,
1902
+ };
1903
+ }
1904
+ const commandPathSuffixProfile = this.#resolveProfileNameByCommandPathSuffix({
1905
+ commandPath,
1906
+ profiles: manifest.profiles,
1907
+ });
1908
+ if (commandPathSuffixProfile !== undefined) {
1909
+ return {
1910
+ selector: commandPathSuffixProfile,
1911
+ sourceName: 'commandPath.suffix',
1912
+ };
1913
+ }
1914
+ if (manifest.defaults?.profile !== undefined) {
1915
+ return {
1916
+ selector: manifest.defaults.profile,
1917
+ sourceName: 'defaults.profile',
1918
+ };
1919
+ }
1920
+ if (manifest.profiles.default !== undefined) {
1921
+ return {
1922
+ selector: 'default',
1923
+ sourceName: 'profiles["default"]',
1924
+ };
1925
+ }
1926
+ throw new CommanderError('ConfigurationError', `missing profile for preset file "${presetFileDisplayPath}": provide "${PRESET_PROFILE_FLAG}", define command-path suffix profile, defaults.profile, or profile "default"`, commandPath);
1927
+ }
1928
+ #resolveProfileNameByCommandPathSuffix(params) {
1929
+ const { commandPath, profiles } = params;
1930
+ const commandNames = commandPath
1931
+ .split(' ')
1932
+ .map(value => value.trim())
1933
+ .filter(Boolean);
1934
+ for (let index = 0; index < commandNames.length; index += 1) {
1935
+ const profileName = commandNames.slice(index).join('.');
1936
+ if (profiles[profileName] !== undefined) {
1937
+ return profileName;
1938
+ }
1939
+ }
1940
+ return undefined;
1941
+ }
1875
1942
  #parsePresetProfileManifest(content, filepath, commandPath) {
1876
1943
  let parsed;
1877
1944
  try {
@@ -2799,7 +2866,7 @@ function scanPresetDirectives(params) {
2799
2866
  return { cleanArgv, presetFile, presetProfile };
2800
2867
  }
2801
2868
  function buildPresetSources(params) {
2802
- const { userCmds, userArgv, userEnvs, presetArgv, presetEnvs, presetMeta } = params;
2869
+ const { userCmds, userArgv, userEnvs, presetArgv, presetEnvs, presetMeta, presetResolvedEnvFile, } = params;
2803
2870
  const presetSourceMeta = presetMeta === undefined
2804
2871
  ? undefined
2805
2872
  : {
@@ -2807,6 +2874,11 @@ function buildPresetSources(params) {
2807
2874
  file: presetMeta.file,
2808
2875
  profile: presetMeta.profile,
2809
2876
  variant: presetMeta.variant,
2877
+ ...(presetResolvedEnvFile === undefined
2878
+ ? {}
2879
+ : {
2880
+ resolvedEnvFile: presetResolvedEnvFile,
2881
+ }),
2810
2882
  };
2811
2883
  const presetState = presetSourceMeta === undefined ? 'none' : 'applied';
2812
2884
  const sources = {
@@ -2913,9 +2985,17 @@ async function runPresetStage(params) {
2913
2985
  presetArgv,
2914
2986
  presetEnvs,
2915
2987
  presetMeta: resolvedProfile?.issueMeta,
2988
+ presetResolvedEnvFile: resolvePresetEnvFilePath(resolvedProfile),
2916
2989
  });
2917
2990
  return { tailArgv, envs, segments, sources };
2918
2991
  }
2992
+ function resolvePresetEnvFilePath(resolvedProfile) {
2993
+ if (!resolvedProfile) {
2994
+ return undefined;
2995
+ }
2996
+ return (resolvedProfile.variantEnvFileSource?.absolutePath ??
2997
+ resolvedProfile.profileEnvFileSource?.absolutePath);
2998
+ }
2919
2999
 
2920
3000
  function findSubcommandEntry(entries, token) {
2921
3001
  return entries.find(entry => entry.name === token || entry.aliases.includes(token));
@@ -3337,6 +3417,16 @@ class Command {
3337
3417
  commandPath: this.#getCommandPath(),
3338
3418
  arguments: this.#arguments,
3339
3419
  options: this.#resolveOptionPolicy().mergedOptions,
3420
+ presetDirectives: [
3421
+ {
3422
+ sig: `${PRESET_FILE_FLAG} <value>`,
3423
+ desc: 'Load preset manifest file',
3424
+ },
3425
+ {
3426
+ sig: `${PRESET_PROFILE_FLAG} <value>`,
3427
+ desc: 'Select preset profile: <profile> or <profile>:<variant>; requires --preset-file or command preset.file',
3428
+ },
3429
+ ],
3340
3430
  supportsBuiltinVersion: this.#supportsBuiltinVersion(),
3341
3431
  subcommands,
3342
3432
  examples: this.#examples,
@@ -1331,6 +1331,7 @@ class CommandHelpRenderer {
1331
1331
  usage,
1332
1332
  arguments: argumentsLines,
1333
1333
  options,
1334
+ presetDirectives: params.presetDirectives ?? [],
1334
1335
  commands,
1335
1336
  examples,
1336
1337
  };
@@ -1401,6 +1402,14 @@ class CommandHelpRenderer {
1401
1402
  }
1402
1403
  lines.push('');
1403
1404
  }
1405
+ const presetDirectives = helpData.presetDirectives ?? [];
1406
+ if (presetDirectives.length > 0) {
1407
+ lines.push('Preset Directives:');
1408
+ for (const { sig, desc } of presetDirectives) {
1409
+ lines.push(this.#renderAlignedHelpLine(sig, desc, labelWidth));
1410
+ }
1411
+ lines.push('');
1412
+ }
1404
1413
  if (helpData.commands.length > 0) {
1405
1414
  lines.push('Commands:');
1406
1415
  for (const { name, desc } of helpData.commands) {
@@ -1440,6 +1449,14 @@ class CommandHelpRenderer {
1440
1449
  }
1441
1450
  lines.push('');
1442
1451
  }
1452
+ const presetDirectives = helpData.presetDirectives ?? [];
1453
+ if (presetDirectives.length > 0) {
1454
+ lines.push(styleText('Preset Directives:', TERMINAL_STYLE.bold, TERMINAL_STYLE.underline));
1455
+ for (const { sig, desc } of presetDirectives) {
1456
+ lines.push(this.#renderAlignedHelpLine(sig, desc, labelWidth, value => styleText(value, TERMINAL_STYLE.cyan)));
1457
+ }
1458
+ lines.push('');
1459
+ }
1443
1460
  if (helpData.commands.length > 0) {
1444
1461
  lines.push(styleText('Commands:', TERMINAL_STYLE.bold, TERMINAL_STYLE.underline));
1445
1462
  for (const { name, desc } of helpData.commands) {
@@ -1462,6 +1479,7 @@ class CommandHelpRenderer {
1462
1479
  const labels = [
1463
1480
  ...helpData.arguments.map(line => line.sig),
1464
1481
  ...helpData.options.map(line => line.sig),
1482
+ ...(helpData.presetDirectives ?? []).map(line => line.sig),
1465
1483
  ...helpData.commands.map(line => line.name),
1466
1484
  ];
1467
1485
  if (labels.length === 0) {
@@ -1772,11 +1790,14 @@ class CommandPresetProfileParser {
1772
1790
  return undefined;
1773
1791
  }
1774
1792
  const manifest = this.#parsePresetProfileManifest(content, profileFile.displayPath, commandPath);
1775
- const resolvedProfileSelector = presetProfile ?? manifest.defaults?.profile;
1776
- if (resolvedProfileSelector === undefined) {
1777
- throw new CommanderError('ConfigurationError', `missing profile for preset file "${profileFile.displayPath}": provide "${PRESET_PROFILE_FLAG}" or defaults.profile`, commandPath);
1778
- }
1779
- const { profileName: resolvedProfileName, variantName: explicitVariantName } = this.#parsePresetProfileSelector(resolvedProfileSelector, presetProfileSourceName ?? 'defaults.profile', commandPath);
1793
+ const resolvedProfileSelector = this.#resolvePresetProfileSelector({
1794
+ presetProfile,
1795
+ presetProfileSourceName,
1796
+ manifest,
1797
+ commandPath,
1798
+ presetFileDisplayPath: profileFile.displayPath,
1799
+ });
1800
+ const { profileName: resolvedProfileName, variantName: explicitVariantName } = this.#parsePresetProfileSelector(resolvedProfileSelector.selector, resolvedProfileSelector.sourceName, commandPath);
1780
1801
  const profile = manifest.profiles[resolvedProfileName];
1781
1802
  if (profile === undefined) {
1782
1803
  throw new CommanderError('ConfigurationError', `unknown preset profile "${resolvedProfileName}" in "${profileFile.displayPath}"`, commandPath);
@@ -1857,6 +1878,52 @@ class CommandPresetProfileParser {
1857
1878
  }
1858
1879
  }
1859
1880
  }
1881
+ #resolvePresetProfileSelector(params) {
1882
+ const { presetProfile, presetProfileSourceName, manifest, commandPath, presetFileDisplayPath } = params;
1883
+ if (presetProfile !== undefined) {
1884
+ return {
1885
+ selector: presetProfile,
1886
+ sourceName: presetProfileSourceName ?? PRESET_PROFILE_FLAG,
1887
+ };
1888
+ }
1889
+ const commandPathSuffixProfile = this.#resolveProfileNameByCommandPathSuffix({
1890
+ commandPath,
1891
+ profiles: manifest.profiles,
1892
+ });
1893
+ if (commandPathSuffixProfile !== undefined) {
1894
+ return {
1895
+ selector: commandPathSuffixProfile,
1896
+ sourceName: 'commandPath.suffix',
1897
+ };
1898
+ }
1899
+ if (manifest.defaults?.profile !== undefined) {
1900
+ return {
1901
+ selector: manifest.defaults.profile,
1902
+ sourceName: 'defaults.profile',
1903
+ };
1904
+ }
1905
+ if (manifest.profiles.default !== undefined) {
1906
+ return {
1907
+ selector: 'default',
1908
+ sourceName: 'profiles["default"]',
1909
+ };
1910
+ }
1911
+ throw new CommanderError('ConfigurationError', `missing profile for preset file "${presetFileDisplayPath}": provide "${PRESET_PROFILE_FLAG}", define command-path suffix profile, defaults.profile, or profile "default"`, commandPath);
1912
+ }
1913
+ #resolveProfileNameByCommandPathSuffix(params) {
1914
+ const { commandPath, profiles } = params;
1915
+ const commandNames = commandPath
1916
+ .split(' ')
1917
+ .map(value => value.trim())
1918
+ .filter(Boolean);
1919
+ for (let index = 0; index < commandNames.length; index += 1) {
1920
+ const profileName = commandNames.slice(index).join('.');
1921
+ if (profiles[profileName] !== undefined) {
1922
+ return profileName;
1923
+ }
1924
+ }
1925
+ return undefined;
1926
+ }
1860
1927
  #parsePresetProfileManifest(content, filepath, commandPath) {
1861
1928
  let parsed;
1862
1929
  try {
@@ -2784,7 +2851,7 @@ function scanPresetDirectives(params) {
2784
2851
  return { cleanArgv, presetFile, presetProfile };
2785
2852
  }
2786
2853
  function buildPresetSources(params) {
2787
- const { userCmds, userArgv, userEnvs, presetArgv, presetEnvs, presetMeta } = params;
2854
+ const { userCmds, userArgv, userEnvs, presetArgv, presetEnvs, presetMeta, presetResolvedEnvFile, } = params;
2788
2855
  const presetSourceMeta = presetMeta === undefined
2789
2856
  ? undefined
2790
2857
  : {
@@ -2792,6 +2859,11 @@ function buildPresetSources(params) {
2792
2859
  file: presetMeta.file,
2793
2860
  profile: presetMeta.profile,
2794
2861
  variant: presetMeta.variant,
2862
+ ...(presetResolvedEnvFile === undefined
2863
+ ? {}
2864
+ : {
2865
+ resolvedEnvFile: presetResolvedEnvFile,
2866
+ }),
2795
2867
  };
2796
2868
  const presetState = presetSourceMeta === undefined ? 'none' : 'applied';
2797
2869
  const sources = {
@@ -2898,9 +2970,17 @@ async function runPresetStage(params) {
2898
2970
  presetArgv,
2899
2971
  presetEnvs,
2900
2972
  presetMeta: resolvedProfile?.issueMeta,
2973
+ presetResolvedEnvFile: resolvePresetEnvFilePath(resolvedProfile),
2901
2974
  });
2902
2975
  return { tailArgv, envs, segments, sources };
2903
2976
  }
2977
+ function resolvePresetEnvFilePath(resolvedProfile) {
2978
+ if (!resolvedProfile) {
2979
+ return undefined;
2980
+ }
2981
+ return (resolvedProfile.variantEnvFileSource?.absolutePath ??
2982
+ resolvedProfile.profileEnvFileSource?.absolutePath);
2983
+ }
2904
2984
 
2905
2985
  function findSubcommandEntry(entries, token) {
2906
2986
  return entries.find(entry => entry.name === token || entry.aliases.includes(token));
@@ -3322,6 +3402,16 @@ class Command {
3322
3402
  commandPath: this.#getCommandPath(),
3323
3403
  arguments: this.#arguments,
3324
3404
  options: this.#resolveOptionPolicy().mergedOptions,
3405
+ presetDirectives: [
3406
+ {
3407
+ sig: `${PRESET_FILE_FLAG} <value>`,
3408
+ desc: 'Load preset manifest file',
3409
+ },
3410
+ {
3411
+ sig: `${PRESET_PROFILE_FLAG} <value>`,
3412
+ desc: 'Select preset profile: <profile> or <profile>:<variant>; requires --preset-file or command preset.file',
3413
+ },
3414
+ ],
3325
3415
  supportsBuiltinVersion: this.#supportsBuiltinVersion(),
3326
3416
  subcommands,
3327
3417
  examples: this.#examples,
package/lib/esm/node.mjs CHANGED
@@ -1344,6 +1344,7 @@ class CommandHelpRenderer {
1344
1344
  usage,
1345
1345
  arguments: argumentsLines,
1346
1346
  options,
1347
+ presetDirectives: params.presetDirectives ?? [],
1347
1348
  commands,
1348
1349
  examples,
1349
1350
  };
@@ -1414,6 +1415,14 @@ class CommandHelpRenderer {
1414
1415
  }
1415
1416
  lines.push('');
1416
1417
  }
1418
+ const presetDirectives = helpData.presetDirectives ?? [];
1419
+ if (presetDirectives.length > 0) {
1420
+ lines.push('Preset Directives:');
1421
+ for (const { sig, desc } of presetDirectives) {
1422
+ lines.push(this.#renderAlignedHelpLine(sig, desc, labelWidth));
1423
+ }
1424
+ lines.push('');
1425
+ }
1417
1426
  if (helpData.commands.length > 0) {
1418
1427
  lines.push('Commands:');
1419
1428
  for (const { name, desc } of helpData.commands) {
@@ -1453,6 +1462,14 @@ class CommandHelpRenderer {
1453
1462
  }
1454
1463
  lines.push('');
1455
1464
  }
1465
+ const presetDirectives = helpData.presetDirectives ?? [];
1466
+ if (presetDirectives.length > 0) {
1467
+ lines.push(styleText('Preset Directives:', TERMINAL_STYLE.bold, TERMINAL_STYLE.underline));
1468
+ for (const { sig, desc } of presetDirectives) {
1469
+ lines.push(this.#renderAlignedHelpLine(sig, desc, labelWidth, value => styleText(value, TERMINAL_STYLE.cyan)));
1470
+ }
1471
+ lines.push('');
1472
+ }
1456
1473
  if (helpData.commands.length > 0) {
1457
1474
  lines.push(styleText('Commands:', TERMINAL_STYLE.bold, TERMINAL_STYLE.underline));
1458
1475
  for (const { name, desc } of helpData.commands) {
@@ -1475,6 +1492,7 @@ class CommandHelpRenderer {
1475
1492
  const labels = [
1476
1493
  ...helpData.arguments.map(line => line.sig),
1477
1494
  ...helpData.options.map(line => line.sig),
1495
+ ...(helpData.presetDirectives ?? []).map(line => line.sig),
1478
1496
  ...helpData.commands.map(line => line.name),
1479
1497
  ];
1480
1498
  if (labels.length === 0) {
@@ -1785,11 +1803,14 @@ class CommandPresetProfileParser {
1785
1803
  return undefined;
1786
1804
  }
1787
1805
  const manifest = this.#parsePresetProfileManifest(content, profileFile.displayPath, commandPath);
1788
- const resolvedProfileSelector = presetProfile ?? manifest.defaults?.profile;
1789
- if (resolvedProfileSelector === undefined) {
1790
- throw new CommanderError('ConfigurationError', `missing profile for preset file "${profileFile.displayPath}": provide "${PRESET_PROFILE_FLAG}" or defaults.profile`, commandPath);
1791
- }
1792
- const { profileName: resolvedProfileName, variantName: explicitVariantName } = this.#parsePresetProfileSelector(resolvedProfileSelector, presetProfileSourceName ?? 'defaults.profile', commandPath);
1806
+ const resolvedProfileSelector = this.#resolvePresetProfileSelector({
1807
+ presetProfile,
1808
+ presetProfileSourceName,
1809
+ manifest,
1810
+ commandPath,
1811
+ presetFileDisplayPath: profileFile.displayPath,
1812
+ });
1813
+ const { profileName: resolvedProfileName, variantName: explicitVariantName } = this.#parsePresetProfileSelector(resolvedProfileSelector.selector, resolvedProfileSelector.sourceName, commandPath);
1793
1814
  const profile = manifest.profiles[resolvedProfileName];
1794
1815
  if (profile === undefined) {
1795
1816
  throw new CommanderError('ConfigurationError', `unknown preset profile "${resolvedProfileName}" in "${profileFile.displayPath}"`, commandPath);
@@ -1870,6 +1891,52 @@ class CommandPresetProfileParser {
1870
1891
  }
1871
1892
  }
1872
1893
  }
1894
+ #resolvePresetProfileSelector(params) {
1895
+ const { presetProfile, presetProfileSourceName, manifest, commandPath, presetFileDisplayPath } = params;
1896
+ if (presetProfile !== undefined) {
1897
+ return {
1898
+ selector: presetProfile,
1899
+ sourceName: presetProfileSourceName ?? PRESET_PROFILE_FLAG,
1900
+ };
1901
+ }
1902
+ const commandPathSuffixProfile = this.#resolveProfileNameByCommandPathSuffix({
1903
+ commandPath,
1904
+ profiles: manifest.profiles,
1905
+ });
1906
+ if (commandPathSuffixProfile !== undefined) {
1907
+ return {
1908
+ selector: commandPathSuffixProfile,
1909
+ sourceName: 'commandPath.suffix',
1910
+ };
1911
+ }
1912
+ if (manifest.defaults?.profile !== undefined) {
1913
+ return {
1914
+ selector: manifest.defaults.profile,
1915
+ sourceName: 'defaults.profile',
1916
+ };
1917
+ }
1918
+ if (manifest.profiles.default !== undefined) {
1919
+ return {
1920
+ selector: 'default',
1921
+ sourceName: 'profiles["default"]',
1922
+ };
1923
+ }
1924
+ throw new CommanderError('ConfigurationError', `missing profile for preset file "${presetFileDisplayPath}": provide "${PRESET_PROFILE_FLAG}", define command-path suffix profile, defaults.profile, or profile "default"`, commandPath);
1925
+ }
1926
+ #resolveProfileNameByCommandPathSuffix(params) {
1927
+ const { commandPath, profiles } = params;
1928
+ const commandNames = commandPath
1929
+ .split(' ')
1930
+ .map(value => value.trim())
1931
+ .filter(Boolean);
1932
+ for (let index = 0; index < commandNames.length; index += 1) {
1933
+ const profileName = commandNames.slice(index).join('.');
1934
+ if (profiles[profileName] !== undefined) {
1935
+ return profileName;
1936
+ }
1937
+ }
1938
+ return undefined;
1939
+ }
1873
1940
  #parsePresetProfileManifest(content, filepath, commandPath) {
1874
1941
  let parsed;
1875
1942
  try {
@@ -2797,7 +2864,7 @@ function scanPresetDirectives(params) {
2797
2864
  return { cleanArgv, presetFile, presetProfile };
2798
2865
  }
2799
2866
  function buildPresetSources(params) {
2800
- const { userCmds, userArgv, userEnvs, presetArgv, presetEnvs, presetMeta } = params;
2867
+ const { userCmds, userArgv, userEnvs, presetArgv, presetEnvs, presetMeta, presetResolvedEnvFile, } = params;
2801
2868
  const presetSourceMeta = presetMeta === undefined
2802
2869
  ? undefined
2803
2870
  : {
@@ -2805,6 +2872,11 @@ function buildPresetSources(params) {
2805
2872
  file: presetMeta.file,
2806
2873
  profile: presetMeta.profile,
2807
2874
  variant: presetMeta.variant,
2875
+ ...(presetResolvedEnvFile === undefined
2876
+ ? {}
2877
+ : {
2878
+ resolvedEnvFile: presetResolvedEnvFile,
2879
+ }),
2808
2880
  };
2809
2881
  const presetState = presetSourceMeta === undefined ? 'none' : 'applied';
2810
2882
  const sources = {
@@ -2911,9 +2983,17 @@ async function runPresetStage(params) {
2911
2983
  presetArgv,
2912
2984
  presetEnvs,
2913
2985
  presetMeta: resolvedProfile?.issueMeta,
2986
+ presetResolvedEnvFile: resolvePresetEnvFilePath(resolvedProfile),
2914
2987
  });
2915
2988
  return { tailArgv, envs, segments, sources };
2916
2989
  }
2990
+ function resolvePresetEnvFilePath(resolvedProfile) {
2991
+ if (!resolvedProfile) {
2992
+ return undefined;
2993
+ }
2994
+ return (resolvedProfile.variantEnvFileSource?.absolutePath ??
2995
+ resolvedProfile.profileEnvFileSource?.absolutePath);
2996
+ }
2917
2997
 
2918
2998
  function findSubcommandEntry(entries, token) {
2919
2999
  return entries.find(entry => entry.name === token || entry.aliases.includes(token));
@@ -3335,6 +3415,16 @@ class Command {
3335
3415
  commandPath: this.#getCommandPath(),
3336
3416
  arguments: this.#arguments,
3337
3417
  options: this.#resolveOptionPolicy().mergedOptions,
3418
+ presetDirectives: [
3419
+ {
3420
+ sig: `${PRESET_FILE_FLAG} <value>`,
3421
+ desc: 'Load preset manifest file',
3422
+ },
3423
+ {
3424
+ sig: `${PRESET_PROFILE_FLAG} <value>`,
3425
+ desc: 'Select preset profile: <profile> or <profile>:<variant>; requires --preset-file or command preset.file',
3426
+ },
3427
+ ],
3338
3428
  supportsBuiltinVersion: this.#supportsBuiltinVersion(),
3339
3429
  subcommands,
3340
3430
  examples: this.#examples,
@@ -17,6 +17,7 @@ interface ICommandPresetSourceMeta {
17
17
  file?: string;
18
18
  profile?: string;
19
19
  variant?: string;
20
+ resolvedEnvFile?: string;
20
21
  }
21
22
  /** Preset execution state in input source snapshot */
22
23
  type ICommandPresetSourceState = 'skipped' | 'none' | 'applied';
@@ -390,6 +391,11 @@ interface IHelpOptionLine {
390
391
  sig: string;
391
392
  desc: string;
392
393
  }
394
+ /** Help preset directive line (internal) */
395
+ interface IHelpPresetDirectiveLine {
396
+ sig: string;
397
+ desc: string;
398
+ }
393
399
  /** Help argument line (internal) */
394
400
  interface IHelpArgumentLine {
395
401
  sig: string;
@@ -412,6 +418,7 @@ interface IHelpData {
412
418
  usage: string;
413
419
  arguments: IHelpArgumentLine[];
414
420
  options: IHelpOptionLine[];
421
+ presetDirectives?: IHelpPresetDirectiveLine[];
415
422
  commands: IHelpCommandLine[];
416
423
  examples: IHelpExampleLine[];
417
424
  }
@@ -673,4 +680,4 @@ declare function getDefaultCommandRuntime(): ICommandRuntime;
673
680
  declare function setDefaultCommandRuntime(runtime: ICommandRuntime): void;
674
681
 
675
682
  export { Coerce, Command, CommanderError, createBrowserCommandRuntime, getDefaultCommandRuntime, isDomain, isIp, isIpv4, isIpv6, logColorfulOption, logDateOption, setDefaultCommandRuntime, silentOption };
676
- export type { ICommand, ICommandAction, ICommandActionParams, ICommandArgumentConfig, ICommandArgumentKind, ICommandArgumentType, ICommandArgvSegment, ICommandBuiltinConfig, ICommandBuiltinOptionConfig, ICommandBuiltinOptionResolved, ICommandBuiltinParsedOptions, ICommandBuiltinResolved, ICommandConfig, ICommandContext, ICommandControlScanResult, ICommandControls, ICommandErrorIssue, ICommandErrorIssueCode, ICommandErrorMeta, ICommandExample, ICommandHintIssue, ICommandHintIssueCode, ICommandInputSources, ICommandIssue, ICommandIssueBase, ICommandIssueCode, ICommandIssueKind, ICommandIssueReason, ICommandIssueScope, ICommandIssueSourceAttribution, ICommandOptionArgs, ICommandOptionConfig, ICommandOptionType, ICommandParseResult, ICommandParsedArgs, ICommandParsedOpts, ICommandPresetConfig, ICommandPresetIssueMeta, ICommandPresetProfileDefaults, ICommandPresetProfileItem, ICommandPresetProfileManifest, ICommandPresetProfileOptionValue, ICommandPresetProfileVariantItem, ICommandPresetResult, ICommandPresetSourceMeta, ICommandPresetSourceState, ICommandResolveResult, ICommandRouteResult, ICommandRunParams, ICommandRuntime, ICommandRuntimeStats, ICommandShiftResult, ICommandStage, ICommandToken, ICommandTokenSource, ICommandTokenType, ICommandTokenizeResult, ICommanderErrorKind, ICompletionArgumentMeta, ICompletionCommandConfig, ICompletionMeta, ICompletionOptionMeta, ICompletionPaths, ICompletionShellType, IHelpArgumentLine, IHelpCommandLine, IHelpData, IHelpExampleLine, IHelpOptionLine, ISubcommandEntry };
683
+ export type { ICommand, ICommandAction, ICommandActionParams, ICommandArgumentConfig, ICommandArgumentKind, ICommandArgumentType, ICommandArgvSegment, ICommandBuiltinConfig, ICommandBuiltinOptionConfig, ICommandBuiltinOptionResolved, ICommandBuiltinParsedOptions, ICommandBuiltinResolved, ICommandConfig, ICommandContext, ICommandControlScanResult, ICommandControls, ICommandErrorIssue, ICommandErrorIssueCode, ICommandErrorMeta, ICommandExample, ICommandHintIssue, ICommandHintIssueCode, ICommandInputSources, ICommandIssue, ICommandIssueBase, ICommandIssueCode, ICommandIssueKind, ICommandIssueReason, ICommandIssueScope, ICommandIssueSourceAttribution, ICommandOptionArgs, ICommandOptionConfig, ICommandOptionType, ICommandParseResult, ICommandParsedArgs, ICommandParsedOpts, ICommandPresetConfig, ICommandPresetIssueMeta, ICommandPresetProfileDefaults, ICommandPresetProfileItem, ICommandPresetProfileManifest, ICommandPresetProfileOptionValue, ICommandPresetProfileVariantItem, ICommandPresetResult, ICommandPresetSourceMeta, ICommandPresetSourceState, ICommandResolveResult, ICommandRouteResult, ICommandRunParams, ICommandRuntime, ICommandRuntimeStats, ICommandShiftResult, ICommandStage, ICommandToken, ICommandTokenSource, ICommandTokenType, ICommandTokenizeResult, ICommanderErrorKind, ICompletionArgumentMeta, ICompletionCommandConfig, ICompletionMeta, ICompletionOptionMeta, ICompletionPaths, ICompletionShellType, IHelpArgumentLine, IHelpCommandLine, IHelpData, IHelpExampleLine, IHelpOptionLine, IHelpPresetDirectiveLine, ISubcommandEntry };
@@ -17,6 +17,7 @@ interface ICommandPresetSourceMeta {
17
17
  file?: string;
18
18
  profile?: string;
19
19
  variant?: string;
20
+ resolvedEnvFile?: string;
20
21
  }
21
22
  /** Preset execution state in input source snapshot */
22
23
  type ICommandPresetSourceState = 'skipped' | 'none' | 'applied';
@@ -390,6 +391,11 @@ interface IHelpOptionLine {
390
391
  sig: string;
391
392
  desc: string;
392
393
  }
394
+ /** Help preset directive line (internal) */
395
+ interface IHelpPresetDirectiveLine {
396
+ sig: string;
397
+ desc: string;
398
+ }
393
399
  /** Help argument line (internal) */
394
400
  interface IHelpArgumentLine {
395
401
  sig: string;
@@ -412,6 +418,7 @@ interface IHelpData {
412
418
  usage: string;
413
419
  arguments: IHelpArgumentLine[];
414
420
  options: IHelpOptionLine[];
421
+ presetDirectives?: IHelpPresetDirectiveLine[];
415
422
  commands: IHelpCommandLine[];
416
423
  examples: IHelpExampleLine[];
417
424
  }
@@ -726,4 +733,4 @@ declare class PwshCompletion {
726
733
  }
727
734
 
728
735
  export { BashCompletion, Coerce, Command, CommanderError, CompletionCommand, FishCompletion, PwshCompletion, createBrowserCommandRuntime, createNodeCommandRuntime, getDefaultCommandRuntime, isDomain, isIp, isIpv4, isIpv6, logColorfulOption, logDateOption, setDefaultCommandRuntime, silentOption };
729
- export type { ICommand, ICommandAction, ICommandActionParams, ICommandArgumentConfig, ICommandArgumentKind, ICommandArgumentType, ICommandArgvSegment, ICommandBuiltinConfig, ICommandBuiltinOptionConfig, ICommandBuiltinOptionResolved, ICommandBuiltinParsedOptions, ICommandBuiltinResolved, ICommandConfig, ICommandContext, ICommandControlScanResult, ICommandControls, ICommandErrorIssue, ICommandErrorIssueCode, ICommandErrorMeta, ICommandExample, ICommandHintIssue, ICommandHintIssueCode, ICommandInputSources, ICommandIssue, ICommandIssueBase, ICommandIssueCode, ICommandIssueKind, ICommandIssueReason, ICommandIssueScope, ICommandIssueSourceAttribution, ICommandOptionArgs, ICommandOptionConfig, ICommandOptionType, ICommandParseResult, ICommandParsedArgs, ICommandParsedOpts, ICommandPresetConfig, ICommandPresetIssueMeta, ICommandPresetProfileDefaults, ICommandPresetProfileItem, ICommandPresetProfileManifest, ICommandPresetProfileOptionValue, ICommandPresetProfileVariantItem, ICommandPresetResult, ICommandPresetSourceMeta, ICommandPresetSourceState, ICommandResolveResult, ICommandRouteResult, ICommandRunParams, ICommandRuntime, ICommandRuntimeStats, ICommandShiftResult, ICommandStage, ICommandToken, ICommandTokenSource, ICommandTokenType, ICommandTokenizeResult, ICommanderErrorKind, ICompletionArgumentMeta, ICompletionCommandConfig, ICompletionMeta, ICompletionOptionMeta, ICompletionPaths, ICompletionShellType, IHelpArgumentLine, IHelpCommandLine, IHelpData, IHelpExampleLine, IHelpOptionLine, ISubcommandEntry };
736
+ export type { ICommand, ICommandAction, ICommandActionParams, ICommandArgumentConfig, ICommandArgumentKind, ICommandArgumentType, ICommandArgvSegment, ICommandBuiltinConfig, ICommandBuiltinOptionConfig, ICommandBuiltinOptionResolved, ICommandBuiltinParsedOptions, ICommandBuiltinResolved, ICommandConfig, ICommandContext, ICommandControlScanResult, ICommandControls, ICommandErrorIssue, ICommandErrorIssueCode, ICommandErrorMeta, ICommandExample, ICommandHintIssue, ICommandHintIssueCode, ICommandInputSources, ICommandIssue, ICommandIssueBase, ICommandIssueCode, ICommandIssueKind, ICommandIssueReason, ICommandIssueScope, ICommandIssueSourceAttribution, ICommandOptionArgs, ICommandOptionConfig, ICommandOptionType, ICommandParseResult, ICommandParsedArgs, ICommandParsedOpts, ICommandPresetConfig, ICommandPresetIssueMeta, ICommandPresetProfileDefaults, ICommandPresetProfileItem, ICommandPresetProfileManifest, ICommandPresetProfileOptionValue, ICommandPresetProfileVariantItem, ICommandPresetResult, ICommandPresetSourceMeta, ICommandPresetSourceState, ICommandResolveResult, ICommandRouteResult, ICommandRunParams, ICommandRuntime, ICommandRuntimeStats, ICommandShiftResult, ICommandStage, ICommandToken, ICommandTokenSource, ICommandTokenType, ICommandTokenizeResult, ICommanderErrorKind, ICompletionArgumentMeta, ICompletionCommandConfig, ICompletionMeta, ICompletionOptionMeta, ICompletionPaths, ICompletionShellType, IHelpArgumentLine, IHelpCommandLine, IHelpData, IHelpExampleLine, IHelpOptionLine, IHelpPresetDirectiveLine, ISubcommandEntry };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guanghechen/commander",
3
- "version": "4.7.8",
3
+ "version": "4.8.0",
4
4
  "description": "A minimal, type-safe command-line interface builder with fluent API",
5
5
  "author": {
6
6
  "name": "guanghechen",
@@ -1,152 +0,0 @@
1
- {
2
- "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://guanghechen.github.io/sora/schemas/commander/preset.config.schema.json",
4
- "title": "Commander Preset Config",
5
- "description": "Preset profile manifest for @guanghechen/commander --preset-file.",
6
- "type": "object",
7
- "additionalProperties": false,
8
- "required": ["version", "profiles"],
9
- "examples": [
10
- {
11
- "version": 1,
12
- "defaults": { "profile": "dev:local" },
13
- "profiles": {
14
- "dev": {
15
- "envFile": "dev.env",
16
- "envs": { "NODE_ENV": "development" },
17
- "opts": { "logLevel": "debug", "retry": 2 },
18
- "defaultVariant": "local",
19
- "variants": {
20
- "local": { "opts": { "retry": 1 } },
21
- "ci": { "envFile": "ci.env", "envs": { "CI": "1" } }
22
- }
23
- }
24
- }
25
- }
26
- ],
27
- "properties": {
28
- "$schema": {
29
- "type": "string",
30
- "description": "Optional schema reference for editors (e.g. VSCode)."
31
- },
32
- "version": {
33
- "type": "integer",
34
- "const": 1,
35
- "description": "Schema version."
36
- },
37
- "defaults": {
38
- "type": "object",
39
- "additionalProperties": false,
40
- "properties": {
41
- "profile": {
42
- "$ref": "#/$defs/profileSelector"
43
- }
44
- },
45
- "description": "Default selector when --preset-profile is not provided."
46
- },
47
- "profiles": {
48
- "type": "object",
49
- "minProperties": 1,
50
- "propertyNames": {
51
- "$ref": "#/$defs/profileName"
52
- },
53
- "additionalProperties": {
54
- "$ref": "#/$defs/profileItem"
55
- },
56
- "description": "Profiles keyed by profile name."
57
- }
58
- },
59
- "$defs": {
60
- "profileName": {
61
- "type": "string",
62
- "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]*$",
63
- "description": "Profile/variant name."
64
- },
65
- "profileSelector": {
66
- "type": "string",
67
- "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]*(?::[A-Za-z0-9][A-Za-z0-9._-]*)?$",
68
- "description": "Selector format: <profile> or <profile>:<variant>."
69
- },
70
- "optionName": {
71
- "type": "string",
72
- "pattern": "^(?:--)?(?:[a-z][a-zA-Z0-9]*|[A-Za-z][A-Za-z0-9]*(?:-[A-Za-z0-9]+)+)$",
73
- "description": "Option key in opts map; aligned with runtime normalization."
74
- },
75
- "optionValue": {
76
- "oneOf": [
77
- { "type": "boolean" },
78
- { "type": "string" },
79
- { "type": "number" },
80
- {
81
- "type": "array",
82
- "items": {
83
- "oneOf": [{ "type": "string" }, { "type": "number" }]
84
- }
85
- }
86
- ]
87
- },
88
- "envMap": {
89
- "type": "object",
90
- "additionalProperties": {
91
- "type": "string"
92
- }
93
- },
94
- "optsMap": {
95
- "type": "object",
96
- "propertyNames": {
97
- "$ref": "#/$defs/optionName"
98
- },
99
- "additionalProperties": {
100
- "$ref": "#/$defs/optionValue"
101
- }
102
- },
103
- "variantItem": {
104
- "type": "object",
105
- "additionalProperties": false,
106
- "properties": {
107
- "envFile": {
108
- "type": "string",
109
- "minLength": 1
110
- },
111
- "envs": {
112
- "$ref": "#/$defs/envMap"
113
- },
114
- "opts": {
115
- "$ref": "#/$defs/optsMap"
116
- }
117
- }
118
- },
119
- "variantsMap": {
120
- "type": "object",
121
- "propertyNames": {
122
- "$ref": "#/$defs/profileName"
123
- },
124
- "additionalProperties": {
125
- "$ref": "#/$defs/variantItem"
126
- }
127
- },
128
- "profileItem": {
129
- "type": "object",
130
- "additionalProperties": false,
131
- "properties": {
132
- "envFile": {
133
- "type": "string",
134
- "minLength": 1
135
- },
136
- "envs": {
137
- "$ref": "#/$defs/envMap"
138
- },
139
- "opts": {
140
- "$ref": "#/$defs/optsMap"
141
- },
142
- "defaultVariant": {
143
- "$ref": "#/$defs/profileName"
144
- },
145
- "variants": {
146
- "$ref": "#/$defs/variantsMap"
147
- }
148
- },
149
- "description": "defaultVariant existence in variants must still be validated by runtime."
150
- }
151
- }
152
- }