@guanghechen/commander 4.7.9 → 4.8.1

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.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Fix negative-number preset option serialization, reject empty coerce input, and avoid long-name
8
+ conflicts with no- negation options.
9
+
10
+ ## 4.8.0
11
+
12
+ ### Minor Changes
13
+
14
+ - Expose preset directives in command help output under a dedicated `Preset Directives:` section.
15
+
3
16
  ## 4.7.9
4
17
 
5
18
  ### Patch Changes
@@ -489,7 +489,7 @@ function validateOptionConfig(params) {
489
489
  if (opt.type === 'number' && opt.args === 'optional') {
490
490
  throw new CommanderError('ConfigurationError', `number option "--${opt.long}" does not support args: 'optional'`, commandPath);
491
491
  }
492
- if (opt.long.startsWith('no')) {
492
+ if (/^no[A-Z]/.test(opt.long)) {
493
493
  throw new CommanderError('ConfigurationError', `option long name cannot start with "no": "${opt.long}"`, commandPath);
494
494
  }
495
495
  if (!/^[a-z][a-zA-Z0-9]*$/.test(opt.long)) {
@@ -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) {
@@ -2130,13 +2148,23 @@ class CommandPresetProfileParser {
2130
2148
  continue;
2131
2149
  }
2132
2150
  if (typeof rawValue === 'number') {
2133
- argv.push(positiveFlag, String(rawValue));
2151
+ const value = String(rawValue);
2152
+ if (value.startsWith('-'))
2153
+ argv.push(`${positiveFlag}=${value}`);
2154
+ else
2155
+ argv.push(positiveFlag, value);
2134
2156
  continue;
2135
2157
  }
2136
2158
  if (rawValue.length === 0) {
2137
2159
  continue;
2138
2160
  }
2139
- argv.push(positiveFlag, ...rawValue.map(value => String(value)));
2161
+ if (rawValue.some(value => typeof value === 'number' && String(value).startsWith('-'))) {
2162
+ for (const value of rawValue)
2163
+ argv.push(`${positiveFlag}=${String(value)}`);
2164
+ }
2165
+ else {
2166
+ argv.push(positiveFlag, ...rawValue.map(value => String(value)));
2167
+ }
2140
2168
  }
2141
2169
  return argv;
2142
2170
  }
@@ -3386,6 +3414,16 @@ class Command {
3386
3414
  commandPath: this.#getCommandPath(),
3387
3415
  arguments: this.#arguments,
3388
3416
  options: this.#resolveOptionPolicy().mergedOptions,
3417
+ presetDirectives: [
3418
+ {
3419
+ sig: `${PRESET_FILE_FLAG} <value>`,
3420
+ desc: 'Load preset manifest file',
3421
+ },
3422
+ {
3423
+ sig: `${PRESET_PROFILE_FLAG} <value>`,
3424
+ desc: 'Select preset profile: <profile> or <profile>:<variant>; requires --preset-file or command preset.file',
3425
+ },
3426
+ ],
3389
3427
  supportsBuiltinVersion: this.#supportsBuiltinVersion(),
3390
3428
  subcommands,
3391
3429
  examples: this.#examples,
@@ -3657,10 +3695,10 @@ function isDomain(rawValue) {
3657
3695
 
3658
3696
  class Coerce {
3659
3697
  constructor() { }
3660
- static create(name, expectedType, validator, errorMessage) {
3698
+ static createNumberCoerce(name, expectedType, validator, errorMessage) {
3661
3699
  return (rawValue) => {
3662
3700
  const value = Number(rawValue);
3663
- if (!validator(value)) {
3701
+ if (rawValue.trim() === '' || !validator(value)) {
3664
3702
  throw new Error(errorMessage ?? `${name} is expected as ${expectedType}, but got ${rawValue}`);
3665
3703
  }
3666
3704
  return value;
@@ -3691,7 +3729,7 @@ class Coerce {
3691
3729
  };
3692
3730
  }
3693
3731
  static integer(name, errorMessage) {
3694
- return this.create(name, 'an integer', value => Number.isInteger(value), errorMessage);
3732
+ return this.createNumberCoerce(name, 'an integer', value => Number.isInteger(value), errorMessage);
3695
3733
  }
3696
3734
  static ip(name, errorMessage) {
3697
3735
  return (rawValue) => {
@@ -3702,16 +3740,16 @@ class Coerce {
3702
3740
  };
3703
3741
  }
3704
3742
  static number(name, errorMessage) {
3705
- return this.create(name, 'a finite number', value => Number.isFinite(value), errorMessage);
3743
+ return this.createNumberCoerce(name, 'a finite number', value => Number.isFinite(value), errorMessage);
3706
3744
  }
3707
3745
  static port(name, errorMessage) {
3708
- return this.create(name, 'a valid port number (0-65535)', value => Number.isInteger(value) && value >= 0 && value <= 65535, errorMessage);
3746
+ return this.createNumberCoerce(name, 'a valid port number (0-65535)', value => Number.isInteger(value) && value >= 0 && value <= 65535, errorMessage);
3709
3747
  }
3710
3748
  static positiveInteger(name, errorMessage) {
3711
- return this.create(name, 'a positive integer', value => Number.isInteger(value) && value > 0, errorMessage);
3749
+ return this.createNumberCoerce(name, 'a positive integer', value => Number.isInteger(value) && value > 0, errorMessage);
3712
3750
  }
3713
3751
  static positiveNumber(name, errorMessage) {
3714
- return this.create(name, 'a positive number', value => Number.isFinite(value) && value > 0, errorMessage);
3752
+ return this.createNumberCoerce(name, 'a positive number', value => Number.isFinite(value) && value > 0, errorMessage);
3715
3753
  }
3716
3754
  }
3717
3755
 
package/lib/cjs/node.cjs CHANGED
@@ -502,7 +502,7 @@ function validateOptionConfig(params) {
502
502
  if (opt.type === 'number' && opt.args === 'optional') {
503
503
  throw new CommanderError('ConfigurationError', `number option "--${opt.long}" does not support args: 'optional'`, commandPath);
504
504
  }
505
- if (opt.long.startsWith('no')) {
505
+ if (/^no[A-Z]/.test(opt.long)) {
506
506
  throw new CommanderError('ConfigurationError', `option long name cannot start with "no": "${opt.long}"`, commandPath);
507
507
  }
508
508
  if (!/^[a-z][a-zA-Z0-9]*$/.test(opt.long)) {
@@ -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) {
@@ -2143,13 +2161,23 @@ class CommandPresetProfileParser {
2143
2161
  continue;
2144
2162
  }
2145
2163
  if (typeof rawValue === 'number') {
2146
- argv.push(positiveFlag, String(rawValue));
2164
+ const value = String(rawValue);
2165
+ if (value.startsWith('-'))
2166
+ argv.push(`${positiveFlag}=${value}`);
2167
+ else
2168
+ argv.push(positiveFlag, value);
2147
2169
  continue;
2148
2170
  }
2149
2171
  if (rawValue.length === 0) {
2150
2172
  continue;
2151
2173
  }
2152
- argv.push(positiveFlag, ...rawValue.map(value => String(value)));
2174
+ if (rawValue.some(value => typeof value === 'number' && String(value).startsWith('-'))) {
2175
+ for (const value of rawValue)
2176
+ argv.push(`${positiveFlag}=${String(value)}`);
2177
+ }
2178
+ else {
2179
+ argv.push(positiveFlag, ...rawValue.map(value => String(value)));
2180
+ }
2153
2181
  }
2154
2182
  return argv;
2155
2183
  }
@@ -3399,6 +3427,16 @@ class Command {
3399
3427
  commandPath: this.#getCommandPath(),
3400
3428
  arguments: this.#arguments,
3401
3429
  options: this.#resolveOptionPolicy().mergedOptions,
3430
+ presetDirectives: [
3431
+ {
3432
+ sig: `${PRESET_FILE_FLAG} <value>`,
3433
+ desc: 'Load preset manifest file',
3434
+ },
3435
+ {
3436
+ sig: `${PRESET_PROFILE_FLAG} <value>`,
3437
+ desc: 'Select preset profile: <profile> or <profile>:<variant>; requires --preset-file or command preset.file',
3438
+ },
3439
+ ],
3402
3440
  supportsBuiltinVersion: this.#supportsBuiltinVersion(),
3403
3441
  subcommands,
3404
3442
  examples: this.#examples,
@@ -3670,10 +3708,10 @@ function isDomain(rawValue) {
3670
3708
 
3671
3709
  class Coerce {
3672
3710
  constructor() { }
3673
- static create(name, expectedType, validator, errorMessage) {
3711
+ static createNumberCoerce(name, expectedType, validator, errorMessage) {
3674
3712
  return (rawValue) => {
3675
3713
  const value = Number(rawValue);
3676
- if (!validator(value)) {
3714
+ if (rawValue.trim() === '' || !validator(value)) {
3677
3715
  throw new Error(errorMessage ?? `${name} is expected as ${expectedType}, but got ${rawValue}`);
3678
3716
  }
3679
3717
  return value;
@@ -3704,7 +3742,7 @@ class Coerce {
3704
3742
  };
3705
3743
  }
3706
3744
  static integer(name, errorMessage) {
3707
- return this.create(name, 'an integer', value => Number.isInteger(value), errorMessage);
3745
+ return this.createNumberCoerce(name, 'an integer', value => Number.isInteger(value), errorMessage);
3708
3746
  }
3709
3747
  static ip(name, errorMessage) {
3710
3748
  return (rawValue) => {
@@ -3715,16 +3753,16 @@ class Coerce {
3715
3753
  };
3716
3754
  }
3717
3755
  static number(name, errorMessage) {
3718
- return this.create(name, 'a finite number', value => Number.isFinite(value), errorMessage);
3756
+ return this.createNumberCoerce(name, 'a finite number', value => Number.isFinite(value), errorMessage);
3719
3757
  }
3720
3758
  static port(name, errorMessage) {
3721
- return this.create(name, 'a valid port number (0-65535)', value => Number.isInteger(value) && value >= 0 && value <= 65535, errorMessage);
3759
+ return this.createNumberCoerce(name, 'a valid port number (0-65535)', value => Number.isInteger(value) && value >= 0 && value <= 65535, errorMessage);
3722
3760
  }
3723
3761
  static positiveInteger(name, errorMessage) {
3724
- return this.create(name, 'a positive integer', value => Number.isInteger(value) && value > 0, errorMessage);
3762
+ return this.createNumberCoerce(name, 'a positive integer', value => Number.isInteger(value) && value > 0, errorMessage);
3725
3763
  }
3726
3764
  static positiveNumber(name, errorMessage) {
3727
- return this.create(name, 'a positive number', value => Number.isFinite(value) && value > 0, errorMessage);
3765
+ return this.createNumberCoerce(name, 'a positive number', value => Number.isFinite(value) && value > 0, errorMessage);
3728
3766
  }
3729
3767
  }
3730
3768
 
@@ -487,7 +487,7 @@ function validateOptionConfig(params) {
487
487
  if (opt.type === 'number' && opt.args === 'optional') {
488
488
  throw new CommanderError('ConfigurationError', `number option "--${opt.long}" does not support args: 'optional'`, commandPath);
489
489
  }
490
- if (opt.long.startsWith('no')) {
490
+ if (/^no[A-Z]/.test(opt.long)) {
491
491
  throw new CommanderError('ConfigurationError', `option long name cannot start with "no": "${opt.long}"`, commandPath);
492
492
  }
493
493
  if (!/^[a-z][a-zA-Z0-9]*$/.test(opt.long)) {
@@ -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) {
@@ -2128,13 +2146,23 @@ class CommandPresetProfileParser {
2128
2146
  continue;
2129
2147
  }
2130
2148
  if (typeof rawValue === 'number') {
2131
- argv.push(positiveFlag, String(rawValue));
2149
+ const value = String(rawValue);
2150
+ if (value.startsWith('-'))
2151
+ argv.push(`${positiveFlag}=${value}`);
2152
+ else
2153
+ argv.push(positiveFlag, value);
2132
2154
  continue;
2133
2155
  }
2134
2156
  if (rawValue.length === 0) {
2135
2157
  continue;
2136
2158
  }
2137
- argv.push(positiveFlag, ...rawValue.map(value => String(value)));
2159
+ if (rawValue.some(value => typeof value === 'number' && String(value).startsWith('-'))) {
2160
+ for (const value of rawValue)
2161
+ argv.push(`${positiveFlag}=${String(value)}`);
2162
+ }
2163
+ else {
2164
+ argv.push(positiveFlag, ...rawValue.map(value => String(value)));
2165
+ }
2138
2166
  }
2139
2167
  return argv;
2140
2168
  }
@@ -3384,6 +3412,16 @@ class Command {
3384
3412
  commandPath: this.#getCommandPath(),
3385
3413
  arguments: this.#arguments,
3386
3414
  options: this.#resolveOptionPolicy().mergedOptions,
3415
+ presetDirectives: [
3416
+ {
3417
+ sig: `${PRESET_FILE_FLAG} <value>`,
3418
+ desc: 'Load preset manifest file',
3419
+ },
3420
+ {
3421
+ sig: `${PRESET_PROFILE_FLAG} <value>`,
3422
+ desc: 'Select preset profile: <profile> or <profile>:<variant>; requires --preset-file or command preset.file',
3423
+ },
3424
+ ],
3387
3425
  supportsBuiltinVersion: this.#supportsBuiltinVersion(),
3388
3426
  subcommands,
3389
3427
  examples: this.#examples,
@@ -3655,10 +3693,10 @@ function isDomain(rawValue) {
3655
3693
 
3656
3694
  class Coerce {
3657
3695
  constructor() { }
3658
- static create(name, expectedType, validator, errorMessage) {
3696
+ static createNumberCoerce(name, expectedType, validator, errorMessage) {
3659
3697
  return (rawValue) => {
3660
3698
  const value = Number(rawValue);
3661
- if (!validator(value)) {
3699
+ if (rawValue.trim() === '' || !validator(value)) {
3662
3700
  throw new Error(errorMessage ?? `${name} is expected as ${expectedType}, but got ${rawValue}`);
3663
3701
  }
3664
3702
  return value;
@@ -3689,7 +3727,7 @@ class Coerce {
3689
3727
  };
3690
3728
  }
3691
3729
  static integer(name, errorMessage) {
3692
- return this.create(name, 'an integer', value => Number.isInteger(value), errorMessage);
3730
+ return this.createNumberCoerce(name, 'an integer', value => Number.isInteger(value), errorMessage);
3693
3731
  }
3694
3732
  static ip(name, errorMessage) {
3695
3733
  return (rawValue) => {
@@ -3700,16 +3738,16 @@ class Coerce {
3700
3738
  };
3701
3739
  }
3702
3740
  static number(name, errorMessage) {
3703
- return this.create(name, 'a finite number', value => Number.isFinite(value), errorMessage);
3741
+ return this.createNumberCoerce(name, 'a finite number', value => Number.isFinite(value), errorMessage);
3704
3742
  }
3705
3743
  static port(name, errorMessage) {
3706
- return this.create(name, 'a valid port number (0-65535)', value => Number.isInteger(value) && value >= 0 && value <= 65535, errorMessage);
3744
+ return this.createNumberCoerce(name, 'a valid port number (0-65535)', value => Number.isInteger(value) && value >= 0 && value <= 65535, errorMessage);
3707
3745
  }
3708
3746
  static positiveInteger(name, errorMessage) {
3709
- return this.create(name, 'a positive integer', value => Number.isInteger(value) && value > 0, errorMessage);
3747
+ return this.createNumberCoerce(name, 'a positive integer', value => Number.isInteger(value) && value > 0, errorMessage);
3710
3748
  }
3711
3749
  static positiveNumber(name, errorMessage) {
3712
- return this.create(name, 'a positive number', value => Number.isFinite(value) && value > 0, errorMessage);
3750
+ return this.createNumberCoerce(name, 'a positive number', value => Number.isFinite(value) && value > 0, errorMessage);
3713
3751
  }
3714
3752
  }
3715
3753
 
package/lib/esm/node.mjs CHANGED
@@ -500,7 +500,7 @@ function validateOptionConfig(params) {
500
500
  if (opt.type === 'number' && opt.args === 'optional') {
501
501
  throw new CommanderError('ConfigurationError', `number option "--${opt.long}" does not support args: 'optional'`, commandPath);
502
502
  }
503
- if (opt.long.startsWith('no')) {
503
+ if (/^no[A-Z]/.test(opt.long)) {
504
504
  throw new CommanderError('ConfigurationError', `option long name cannot start with "no": "${opt.long}"`, commandPath);
505
505
  }
506
506
  if (!/^[a-z][a-zA-Z0-9]*$/.test(opt.long)) {
@@ -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) {
@@ -2141,13 +2159,23 @@ class CommandPresetProfileParser {
2141
2159
  continue;
2142
2160
  }
2143
2161
  if (typeof rawValue === 'number') {
2144
- argv.push(positiveFlag, String(rawValue));
2162
+ const value = String(rawValue);
2163
+ if (value.startsWith('-'))
2164
+ argv.push(`${positiveFlag}=${value}`);
2165
+ else
2166
+ argv.push(positiveFlag, value);
2145
2167
  continue;
2146
2168
  }
2147
2169
  if (rawValue.length === 0) {
2148
2170
  continue;
2149
2171
  }
2150
- argv.push(positiveFlag, ...rawValue.map(value => String(value)));
2172
+ if (rawValue.some(value => typeof value === 'number' && String(value).startsWith('-'))) {
2173
+ for (const value of rawValue)
2174
+ argv.push(`${positiveFlag}=${String(value)}`);
2175
+ }
2176
+ else {
2177
+ argv.push(positiveFlag, ...rawValue.map(value => String(value)));
2178
+ }
2151
2179
  }
2152
2180
  return argv;
2153
2181
  }
@@ -3397,6 +3425,16 @@ class Command {
3397
3425
  commandPath: this.#getCommandPath(),
3398
3426
  arguments: this.#arguments,
3399
3427
  options: this.#resolveOptionPolicy().mergedOptions,
3428
+ presetDirectives: [
3429
+ {
3430
+ sig: `${PRESET_FILE_FLAG} <value>`,
3431
+ desc: 'Load preset manifest file',
3432
+ },
3433
+ {
3434
+ sig: `${PRESET_PROFILE_FLAG} <value>`,
3435
+ desc: 'Select preset profile: <profile> or <profile>:<variant>; requires --preset-file or command preset.file',
3436
+ },
3437
+ ],
3400
3438
  supportsBuiltinVersion: this.#supportsBuiltinVersion(),
3401
3439
  subcommands,
3402
3440
  examples: this.#examples,
@@ -3668,10 +3706,10 @@ function isDomain(rawValue) {
3668
3706
 
3669
3707
  class Coerce {
3670
3708
  constructor() { }
3671
- static create(name, expectedType, validator, errorMessage) {
3709
+ static createNumberCoerce(name, expectedType, validator, errorMessage) {
3672
3710
  return (rawValue) => {
3673
3711
  const value = Number(rawValue);
3674
- if (!validator(value)) {
3712
+ if (rawValue.trim() === '' || !validator(value)) {
3675
3713
  throw new Error(errorMessage ?? `${name} is expected as ${expectedType}, but got ${rawValue}`);
3676
3714
  }
3677
3715
  return value;
@@ -3702,7 +3740,7 @@ class Coerce {
3702
3740
  };
3703
3741
  }
3704
3742
  static integer(name, errorMessage) {
3705
- return this.create(name, 'an integer', value => Number.isInteger(value), errorMessage);
3743
+ return this.createNumberCoerce(name, 'an integer', value => Number.isInteger(value), errorMessage);
3706
3744
  }
3707
3745
  static ip(name, errorMessage) {
3708
3746
  return (rawValue) => {
@@ -3713,16 +3751,16 @@ class Coerce {
3713
3751
  };
3714
3752
  }
3715
3753
  static number(name, errorMessage) {
3716
- return this.create(name, 'a finite number', value => Number.isFinite(value), errorMessage);
3754
+ return this.createNumberCoerce(name, 'a finite number', value => Number.isFinite(value), errorMessage);
3717
3755
  }
3718
3756
  static port(name, errorMessage) {
3719
- return this.create(name, 'a valid port number (0-65535)', value => Number.isInteger(value) && value >= 0 && value <= 65535, errorMessage);
3757
+ return this.createNumberCoerce(name, 'a valid port number (0-65535)', value => Number.isInteger(value) && value >= 0 && value <= 65535, errorMessage);
3720
3758
  }
3721
3759
  static positiveInteger(name, errorMessage) {
3722
- return this.create(name, 'a positive integer', value => Number.isInteger(value) && value > 0, errorMessage);
3760
+ return this.createNumberCoerce(name, 'a positive integer', value => Number.isInteger(value) && value > 0, errorMessage);
3723
3761
  }
3724
3762
  static positiveNumber(name, errorMessage) {
3725
- return this.create(name, 'a positive number', value => Number.isFinite(value) && value > 0, errorMessage);
3763
+ return this.createNumberCoerce(name, 'a positive number', value => Number.isFinite(value) && value > 0, errorMessage);
3726
3764
  }
3727
3765
  }
3728
3766
 
@@ -391,6 +391,11 @@ interface IHelpOptionLine {
391
391
  sig: string;
392
392
  desc: string;
393
393
  }
394
+ /** Help preset directive line (internal) */
395
+ interface IHelpPresetDirectiveLine {
396
+ sig: string;
397
+ desc: string;
398
+ }
394
399
  /** Help argument line (internal) */
395
400
  interface IHelpArgumentLine {
396
401
  sig: string;
@@ -413,6 +418,7 @@ interface IHelpData {
413
418
  usage: string;
414
419
  arguments: IHelpArgumentLine[];
415
420
  options: IHelpOptionLine[];
421
+ presetDirectives?: IHelpPresetDirectiveLine[];
416
422
  commands: IHelpCommandLine[];
417
423
  examples: IHelpExampleLine[];
418
424
  }
@@ -586,7 +592,7 @@ declare class Command implements ICommand {
586
592
  */
587
593
  declare class Coerce {
588
594
  private constructor();
589
- private static create;
595
+ private static createNumberCoerce;
590
596
  static choice<TValue extends string>(name: string, values: ReadonlyArray<TValue>, errorMessage?: string): (rawValue: string) => TValue;
591
597
  static domain(name: string, errorMessage?: string): (rawValue: string) => string;
592
598
  static host(name: string, errorMessage?: string): (rawValue: string) => string;
@@ -674,4 +680,4 @@ declare function getDefaultCommandRuntime(): ICommandRuntime;
674
680
  declare function setDefaultCommandRuntime(runtime: ICommandRuntime): void;
675
681
 
676
682
  export { Coerce, Command, CommanderError, createBrowserCommandRuntime, getDefaultCommandRuntime, isDomain, isIp, isIpv4, isIpv6, logColorfulOption, logDateOption, setDefaultCommandRuntime, silentOption };
677
- 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 };
@@ -391,6 +391,11 @@ interface IHelpOptionLine {
391
391
  sig: string;
392
392
  desc: string;
393
393
  }
394
+ /** Help preset directive line (internal) */
395
+ interface IHelpPresetDirectiveLine {
396
+ sig: string;
397
+ desc: string;
398
+ }
394
399
  /** Help argument line (internal) */
395
400
  interface IHelpArgumentLine {
396
401
  sig: string;
@@ -413,6 +418,7 @@ interface IHelpData {
413
418
  usage: string;
414
419
  arguments: IHelpArgumentLine[];
415
420
  options: IHelpOptionLine[];
421
+ presetDirectives?: IHelpPresetDirectiveLine[];
416
422
  commands: IHelpCommandLine[];
417
423
  examples: IHelpExampleLine[];
418
424
  }
@@ -594,7 +600,7 @@ declare class Command implements ICommand {
594
600
  */
595
601
  declare class Coerce {
596
602
  private constructor();
597
- private static create;
603
+ private static createNumberCoerce;
598
604
  static choice<TValue extends string>(name: string, values: ReadonlyArray<TValue>, errorMessage?: string): (rawValue: string) => TValue;
599
605
  static domain(name: string, errorMessage?: string): (rawValue: string) => string;
600
606
  static host(name: string, errorMessage?: string): (rawValue: string) => string;
@@ -727,4 +733,4 @@ declare class PwshCompletion {
727
733
  }
728
734
 
729
735
  export { BashCompletion, Coerce, Command, CommanderError, CompletionCommand, FishCompletion, PwshCompletion, createBrowserCommandRuntime, createNodeCommandRuntime, getDefaultCommandRuntime, isDomain, isIp, isIpv4, isIpv6, logColorfulOption, logDateOption, setDefaultCommandRuntime, silentOption };
730
- 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.9",
3
+ "version": "4.8.1",
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
- }