politty 0.4.3 → 0.4.6

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 (60) hide show
  1. package/dist/{arg-registry-BUUhZ7JR.d.ts → arg-registry-2m40k1Et.d.ts} +1 -1
  2. package/dist/{arg-registry-BUUhZ7JR.d.ts.map → arg-registry-2m40k1Et.d.ts.map} +1 -1
  3. package/dist/augment.d.ts +1 -1
  4. package/dist/completion/index.cjs +16 -168
  5. package/dist/completion/index.d.cts +2 -77
  6. package/dist/completion/index.d.ts +2 -77
  7. package/dist/completion/index.js +3 -154
  8. package/dist/{zsh-CASZWn0o.cjs → completion-Df0eZ70u.cjs} +326 -37
  9. package/dist/completion-Df0eZ70u.cjs.map +1 -0
  10. package/dist/{zsh-hjvdI8uZ.js → completion-_AnQsWh9.js} +298 -27
  11. package/dist/completion-_AnQsWh9.js.map +1 -0
  12. package/dist/docs/index.cjs +276 -29
  13. package/dist/docs/index.cjs.map +1 -1
  14. package/dist/docs/index.d.cts +62 -7
  15. package/dist/docs/index.d.cts.map +1 -1
  16. package/dist/docs/index.d.ts +62 -7
  17. package/dist/docs/index.d.ts.map +1 -1
  18. package/dist/docs/index.js +271 -30
  19. package/dist/docs/index.js.map +1 -1
  20. package/dist/{value-completion-resolver-BQgHsX7b.d.cts → index-BZalbMeu.d.ts} +83 -4
  21. package/dist/index-BZalbMeu.d.ts.map +1 -0
  22. package/dist/{value-completion-resolver-C9LTGr0O.d.ts → index-C5-0RXiH.d.cts} +83 -4
  23. package/dist/index-C5-0RXiH.d.cts.map +1 -0
  24. package/dist/index.cjs +8 -7
  25. package/dist/index.d.cts +67 -13
  26. package/dist/index.d.cts.map +1 -1
  27. package/dist/index.d.ts +68 -14
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +4 -5
  30. package/dist/{lazy-BEDnSR0m.cjs → lazy-DHlvJiQQ.cjs} +44 -8
  31. package/dist/lazy-DHlvJiQQ.cjs.map +1 -0
  32. package/dist/{lazy-BrEg8SgI.js → lazy-DSyfzR-F.js} +38 -8
  33. package/dist/lazy-DSyfzR-F.js.map +1 -0
  34. package/dist/{runner-C4fSHJMe.cjs → runner-C1Aah5c5.cjs} +365 -23
  35. package/dist/runner-C1Aah5c5.cjs.map +1 -0
  36. package/dist/{runner-D6k4BgB4.js → runner-DjG0uBxQ.js} +365 -23
  37. package/dist/runner-DjG0uBxQ.js.map +1 -0
  38. package/dist/{schema-extractor-n9288WJ6.d.ts → schema-extractor-C9APqeSL.d.ts} +30 -3
  39. package/dist/schema-extractor-C9APqeSL.d.ts.map +1 -0
  40. package/dist/{schema-extractor-DFaAZzaY.d.cts → schema-extractor-CVf0J4An.d.cts} +29 -2
  41. package/dist/schema-extractor-CVf0J4An.d.cts.map +1 -0
  42. package/dist/{subcommand-router-CAzBsLSI.js → subcommand-router-CKuy6D2b.js} +2 -2
  43. package/dist/{subcommand-router-CAzBsLSI.js.map → subcommand-router-CKuy6D2b.js.map} +1 -1
  44. package/dist/{subcommand-router-ZjNjFaUL.cjs → subcommand-router-sZHhUP7b.cjs} +2 -2
  45. package/dist/{subcommand-router-ZjNjFaUL.cjs.map → subcommand-router-sZHhUP7b.cjs.map} +1 -1
  46. package/package.json +9 -9
  47. package/dist/completion/index.cjs.map +0 -1
  48. package/dist/completion/index.d.cts.map +0 -1
  49. package/dist/completion/index.d.ts.map +0 -1
  50. package/dist/completion/index.js.map +0 -1
  51. package/dist/lazy-BEDnSR0m.cjs.map +0 -1
  52. package/dist/lazy-BrEg8SgI.js.map +0 -1
  53. package/dist/runner-C4fSHJMe.cjs.map +0 -1
  54. package/dist/runner-D6k4BgB4.js.map +0 -1
  55. package/dist/schema-extractor-DFaAZzaY.d.cts.map +0 -1
  56. package/dist/schema-extractor-n9288WJ6.d.ts.map +0 -1
  57. package/dist/value-completion-resolver-BQgHsX7b.d.cts.map +0 -1
  58. package/dist/value-completion-resolver-C9LTGr0O.d.ts.map +0 -1
  59. package/dist/zsh-CASZWn0o.cjs.map +0 -1
  60. package/dist/zsh-hjvdI8uZ.js.map +0 -1
@@ -1,5 +1,5 @@
1
- const require_subcommand_router = require('./subcommand-router-ZjNjFaUL.cjs');
2
- const require_lazy = require('./lazy-BEDnSR0m.cjs');
1
+ const require_subcommand_router = require('./subcommand-router-sZHhUP7b.cjs');
2
+ const require_lazy = require('./lazy-DHlvJiQQ.cjs');
3
3
  let zod = require("zod");
4
4
  let node_util = require("node:util");
5
5
  let string_width = require("string-width");
@@ -539,6 +539,7 @@ function renderUsageLine(command, context) {
539
539
  const parts = [];
540
540
  const name = buildUsageCommandName(command, context);
541
541
  parts.push(styles.commandName(name));
542
+ if (context?.globalExtracted?.fields.length) parts.push(styles.placeholder("[global options]"));
542
543
  const extracted = require_lazy.getExtractedFields(command);
543
544
  if (extracted) {
544
545
  const positionals = extracted.fields.filter((a) => a.positional);
@@ -724,6 +725,24 @@ function formatOption(flags, description, indent = 0, extraDescPadding = 0) {
724
725
  return `${indentStr} ${padEndVisual(flags, effectiveFlagWidth)}${description}`;
725
726
  }
726
727
  /**
728
+ * Format a single option field as a help line
729
+ */
730
+ function formatFieldLine(opt, indent = 0, extraDescPadding = 0) {
731
+ const flags = formatFlags(opt);
732
+ let desc = opt.description ?? "";
733
+ if (opt.defaultValue !== void 0) desc += ` ${styles.defaultValue(`(default: ${JSON.stringify(opt.defaultValue)})`)}`;
734
+ if (opt.required) desc += ` ${styles.required("(required)")}`;
735
+ const envInfo = formatEnvInfo(opt.env);
736
+ if (envInfo) desc += ` ${envInfo}`;
737
+ return formatOption(flags, desc, indent, extraDescPadding);
738
+ }
739
+ /**
740
+ * Render global options section
741
+ */
742
+ function renderGlobalOptions(globalExtracted) {
743
+ return globalExtracted.fields.filter((a) => !a.positional).map((opt) => formatFieldLine(opt)).join("\n");
744
+ }
745
+ /**
727
746
  * Render options for a subcommand (used by showSubcommandOptions)
728
747
  */
729
748
  function renderSubcommandOptionsCompact(command, indent) {
@@ -786,6 +805,7 @@ function generateHelp(command, options) {
786
805
  sections.push(`${styles.sectionHeader("Usage:")} ${renderUsageLine(command, context)}`);
787
806
  const optionsText = renderOptions(command, options.descriptions, context);
788
807
  if (optionsText) sections.push(`${styles.sectionHeader("Options:")}\n${optionsText}`);
808
+ if (context?.globalExtracted?.fields.length) sections.push(`${styles.sectionHeader("Global Options:")}\n${renderGlobalOptions(context.globalExtracted)}`);
789
809
  if (options.showSubcommands !== false && command.subCommands && getVisibleSubcommandEntries(command.subCommands).length > 0) {
790
810
  const currentPath = context?.commandPath?.join(" ") ?? "";
791
811
  const visibleSubCommands = Object.fromEntries(getVisibleSubcommandEntries(command.subCommands));
@@ -1098,13 +1118,20 @@ function formatCommandValidationErrors(errors) {
1098
1118
  * - Combined short options: -abc (treated as -a -b -c if all are boolean)
1099
1119
  * - Positional arguments
1100
1120
  * - -- to stop parsing options
1121
+ * - Boolean negation: --no-flag, --noFlag (requires `booleanFlags`)
1122
+ *
1123
+ * **Note:** When using negation detection (`--noFlag` / `--no-flag`),
1124
+ * supply `definedNames` so that options whose names happen to start with
1125
+ * "no" (e.g. `noDryRun`) are not mistaken for negation of another flag.
1126
+ * Without `definedNames`, all `--noX` forms matching a boolean flag will
1127
+ * be treated as negation.
1101
1128
  *
1102
1129
  * @param argv - Command line arguments
1103
1130
  * @param options - Parser options
1104
1131
  * @returns Parsed arguments
1105
1132
  */
1106
1133
  function parseArgv(argv, options = {}) {
1107
- const { aliasMap = /* @__PURE__ */ new Map(), booleanFlags = /* @__PURE__ */ new Set(), arrayFlags = /* @__PURE__ */ new Set() } = options;
1134
+ const { aliasMap = /* @__PURE__ */ new Map(), booleanFlags = /* @__PURE__ */ new Set(), arrayFlags = /* @__PURE__ */ new Set(), definedNames = /* @__PURE__ */ new Set() } = options;
1108
1135
  const result = {
1109
1136
  options: {},
1110
1137
  positionals: [],
@@ -1137,11 +1164,28 @@ function parseArgv(argv, options = {}) {
1137
1164
  const withoutDashes = arg.slice(2);
1138
1165
  if (withoutDashes.startsWith("no-")) {
1139
1166
  const flagName = withoutDashes.slice(3);
1140
- const resolvedName = aliasMap.get(flagName) ?? flagName;
1167
+ if (flagName === flagName.toLowerCase()) {
1168
+ const resolvedName = aliasMap.get(flagName) ?? flagName;
1169
+ if (booleanFlags.has(resolvedName)) {
1170
+ const asIsResolved = aliasMap.get(withoutDashes) ?? withoutDashes;
1171
+ if (!definedNames.has(asIsResolved)) {
1172
+ setOption(flagName, false);
1173
+ i++;
1174
+ continue;
1175
+ }
1176
+ }
1177
+ }
1178
+ }
1179
+ if (withoutDashes.length > 2 && withoutDashes.startsWith("no") && /[A-Z]/.test(withoutDashes[2])) {
1180
+ const camelFlagName = withoutDashes[2].toLowerCase() + withoutDashes.slice(3);
1181
+ const resolvedName = aliasMap.get(camelFlagName) ?? camelFlagName;
1141
1182
  if (booleanFlags.has(resolvedName)) {
1142
- setOption(flagName, false);
1143
- i++;
1144
- continue;
1183
+ const asIsResolved = aliasMap.get(withoutDashes) ?? withoutDashes;
1184
+ if (!definedNames.has(asIsResolved)) {
1185
+ setOption(camelFlagName, false);
1186
+ i++;
1187
+ continue;
1188
+ }
1145
1189
  }
1146
1190
  }
1147
1191
  const eqIndex = withoutDashes.indexOf("=");
@@ -1207,16 +1251,21 @@ function buildParserOptions(extracted) {
1207
1251
  const aliasMap = /* @__PURE__ */ new Map();
1208
1252
  const booleanFlags = /* @__PURE__ */ new Set();
1209
1253
  const arrayFlags = /* @__PURE__ */ new Set();
1254
+ const definedNames = /* @__PURE__ */ new Set();
1255
+ for (const field of extracted.fields) definedNames.add(field.name);
1210
1256
  for (const field of extracted.fields) {
1211
1257
  if (field.cliName !== field.name) aliasMap.set(field.cliName, field.name);
1212
1258
  if (field.alias) aliasMap.set(field.alias, field.name);
1259
+ const camelVariant = require_lazy.toCamelCase(field.name);
1260
+ if (camelVariant !== field.name && !definedNames.has(camelVariant) && !aliasMap.has(camelVariant)) aliasMap.set(camelVariant, field.name);
1213
1261
  if (field.type === "boolean") booleanFlags.add(field.name);
1214
1262
  if (field.type === "array") arrayFlags.add(field.name);
1215
1263
  }
1216
1264
  return {
1217
1265
  aliasMap,
1218
1266
  booleanFlags,
1219
- arrayFlags
1267
+ arrayFlags,
1268
+ definedNames
1220
1269
  };
1221
1270
  }
1222
1271
  /**
@@ -1240,6 +1289,151 @@ function mergeWithPositionals(parsed, extracted) {
1240
1289
  return result;
1241
1290
  }
1242
1291
 
1292
+ //#endregion
1293
+ //#region src/parser/subcommand-scanner.ts
1294
+ /**
1295
+ * Build lookup tables from extracted global schema fields.
1296
+ * Shared by scanForSubcommand, separateGlobalArgs, and findFirstPositional.
1297
+ */
1298
+ function buildGlobalFlagLookup(globalExtracted) {
1299
+ const { aliasMap = /* @__PURE__ */ new Map(), booleanFlags = /* @__PURE__ */ new Set() } = buildParserOptions(globalExtracted);
1300
+ return {
1301
+ aliasMap,
1302
+ booleanFlags,
1303
+ flagNames: new Set(globalExtracted.fields.map((f) => f.name)),
1304
+ cliNames: new Set(globalExtracted.fields.map((f) => f.cliName)),
1305
+ aliases: new Set(globalExtracted.fields.filter((f) => f.alias).map((f) => f.alias))
1306
+ };
1307
+ }
1308
+ /**
1309
+ * Resolve a long option (--flag, --flag=value, --no-flag) against global flag lookup.
1310
+ * Returns the resolved camelCase name and whether it is a known global flag.
1311
+ */
1312
+ function resolveGlobalLongOption(arg, lookup) {
1313
+ const withoutDashes = arg.includes("=") ? arg.slice(2, arg.indexOf("=")) : arg.slice(2);
1314
+ const isNegated = withoutDashes.startsWith("no-");
1315
+ const flagName = isNegated ? withoutDashes.slice(3) : withoutDashes;
1316
+ const resolvedName = lookup.aliasMap.get(flagName) ?? flagName;
1317
+ return {
1318
+ resolvedName,
1319
+ withoutDashes,
1320
+ isNegated,
1321
+ isGlobal: lookup.flagNames.has(resolvedName) || lookup.cliNames.has(withoutDashes) || lookup.cliNames.has(flagName)
1322
+ };
1323
+ }
1324
+ /**
1325
+ * Check whether a non-boolean flag should consume the next argv token as its value.
1326
+ * Returns true when the next token exists, is not a flag, and the current flag
1327
+ * is not boolean / negated / using = syntax.
1328
+ */
1329
+ function shouldConsumeValue(arg, resolvedName, isNegated, nextArg, booleanFlags) {
1330
+ return !arg.includes("=") && !booleanFlags.has(resolvedName) && !isNegated && nextArg !== void 0 && !nextArg.startsWith("-");
1331
+ }
1332
+ /**
1333
+ * Collect a recognized global flag (and its value if applicable) into `dest`,
1334
+ * returning how many argv positions were consumed (1 or 2).
1335
+ */
1336
+ function collectGlobalFlag(argv, i, resolvedName, isNegated, booleanFlags, dest) {
1337
+ const arg = argv[i];
1338
+ dest.push(arg);
1339
+ if (shouldConsumeValue(arg, resolvedName, isNegated, argv[i + 1], booleanFlags)) {
1340
+ dest.push(argv[i + 1]);
1341
+ return 2;
1342
+ }
1343
+ return 1;
1344
+ }
1345
+ /**
1346
+ * Scan argv to find the subcommand position, skipping over global flags.
1347
+ *
1348
+ * Walks argv and recognizes global flags (long, short, --no-*) so that
1349
+ * `my-cli --verbose build --output dist` correctly identifies `build` as
1350
+ * the subcommand (index 1) rather than treating `--verbose` as the subcommand.
1351
+ *
1352
+ * Limitation: flags appearing before the subcommand name are matched only
1353
+ * against the global schema. If a flag is defined in both global and a
1354
+ * subcommand's local schema, the pre-subcommand occurrence is always treated
1355
+ * as global because the local schema is not available until the subcommand is
1356
+ * identified (lazy-loaded commands make eager checking infeasible). Place
1357
+ * colliding flags after the subcommand name so that `separateGlobalArgs` can
1358
+ * apply local-precedence logic.
1359
+ *
1360
+ * @param argv - Command line arguments
1361
+ * @param subCommandNames - Valid subcommand names
1362
+ * @param globalExtracted - Extracted fields from global args schema
1363
+ * @returns Scan result with subcommand position and token separation
1364
+ */
1365
+ function scanForSubcommand(argv, subCommandNames, globalExtracted) {
1366
+ const lookup = buildGlobalFlagLookup(globalExtracted);
1367
+ const subCommandNameSet = new Set(subCommandNames);
1368
+ const globalTokensBefore = [];
1369
+ let i = 0;
1370
+ while (i < argv.length) {
1371
+ const arg = argv[i];
1372
+ if (arg === "--" || BUILTIN_FLAGS.has(arg)) break;
1373
+ if (!arg.startsWith("-") && subCommandNameSet.has(arg)) return {
1374
+ subCommandIndex: i,
1375
+ globalTokensBefore,
1376
+ tokensAfterSubcommand: argv.slice(i + 1)
1377
+ };
1378
+ if (arg.startsWith("--")) {
1379
+ const { resolvedName, isNegated, isGlobal } = resolveGlobalLongOption(arg, lookup);
1380
+ if (isGlobal) {
1381
+ i += collectGlobalFlag(argv, i, resolvedName, isNegated, lookup.booleanFlags, globalTokensBefore);
1382
+ continue;
1383
+ }
1384
+ break;
1385
+ }
1386
+ if (arg.startsWith("-") && arg.length > 1) {
1387
+ const withoutDash = arg.includes("=") ? arg.slice(1, arg.indexOf("=")) : arg.slice(1);
1388
+ if (withoutDash.length === 1) {
1389
+ const resolvedName = lookup.aliasMap.get(withoutDash) ?? withoutDash;
1390
+ if (lookup.aliases.has(withoutDash) || lookup.flagNames.has(resolvedName)) {
1391
+ i += collectGlobalFlag(argv, i, resolvedName, false, lookup.booleanFlags, globalTokensBefore);
1392
+ continue;
1393
+ }
1394
+ }
1395
+ break;
1396
+ }
1397
+ break;
1398
+ }
1399
+ return {
1400
+ subCommandIndex: -1,
1401
+ globalTokensBefore,
1402
+ tokensAfterSubcommand: []
1403
+ };
1404
+ }
1405
+ const BUILTIN_FLAGS = new Set([
1406
+ "--help",
1407
+ "-h",
1408
+ "--help-all",
1409
+ "-H",
1410
+ "--version"
1411
+ ]);
1412
+ /**
1413
+ * Find the first positional argument in argv, properly skipping global flag values.
1414
+ * Without globalExtracted, falls back to the first non-flag token.
1415
+ */
1416
+ function findFirstPositional(argv, globalExtracted) {
1417
+ if (!globalExtracted) return argv.find((arg) => !arg.startsWith("-"));
1418
+ const lookup = buildGlobalFlagLookup(globalExtracted);
1419
+ for (let i = 0; i < argv.length; i++) {
1420
+ const arg = argv[i];
1421
+ if (!arg.startsWith("-")) return arg;
1422
+ if (arg === "--") return void 0;
1423
+ if (arg.startsWith("--")) {
1424
+ const { resolvedName, isNegated, isGlobal } = resolveGlobalLongOption(arg, lookup);
1425
+ if (isGlobal && shouldConsumeValue(arg, resolvedName, isNegated, argv[i + 1], lookup.booleanFlags)) i++;
1426
+ continue;
1427
+ }
1428
+ if (arg.length === 2) {
1429
+ const ch = arg[1];
1430
+ if (lookup.aliases.has(ch)) {
1431
+ if (shouldConsumeValue(arg, lookup.aliasMap.get(ch) ?? ch, false, argv[i + 1], lookup.booleanFlags)) i++;
1432
+ }
1433
+ }
1434
+ }
1435
+ }
1436
+
1243
1437
  //#endregion
1244
1438
  //#region src/parser/arg-parser.ts
1245
1439
  /**
@@ -1253,7 +1447,23 @@ function mergeWithPositionals(parsed, extracted) {
1253
1447
  function parseArgs(argv, command, options = {}) {
1254
1448
  const subCommandNames = command.subCommands ? Object.keys(command.subCommands) : [];
1255
1449
  const hasSubCommands = subCommandNames.length > 0;
1256
- if (hasSubCommands && argv.length > 0) {
1450
+ if (hasSubCommands && argv.length > 0) if (options.globalExtracted) {
1451
+ const scanResult = scanForSubcommand(argv, subCommandNames, options.globalExtracted);
1452
+ if (scanResult.subCommandIndex >= 0) {
1453
+ const rawGlobalArgs = parseGlobalArgs(scanResult.globalTokensBefore, options.globalExtracted);
1454
+ return {
1455
+ helpRequested: false,
1456
+ helpAllRequested: false,
1457
+ versionRequested: false,
1458
+ subCommand: argv[scanResult.subCommandIndex],
1459
+ remainingArgs: scanResult.tokensAfterSubcommand,
1460
+ rawArgs: {},
1461
+ positionals: [],
1462
+ unknownFlags: [],
1463
+ rawGlobalArgs
1464
+ };
1465
+ }
1466
+ } else {
1257
1467
  const firstArg = argv[0];
1258
1468
  if (firstArg && !firstArg.startsWith("-") && subCommandNames.includes(firstArg)) return {
1259
1469
  helpRequested: false,
@@ -1291,6 +1501,13 @@ function parseArgs(argv, command, options = {}) {
1291
1501
  positionals: [],
1292
1502
  unknownFlags: []
1293
1503
  };
1504
+ let commandArgv = argv;
1505
+ let rawGlobalArgs;
1506
+ if (options.globalExtracted) {
1507
+ const { separated, globalParsed } = separateGlobalArgs(argv, options.globalExtracted, extracted);
1508
+ commandArgv = separated;
1509
+ rawGlobalArgs = globalParsed;
1510
+ }
1294
1511
  if (!extracted) return {
1295
1512
  helpRequested: false,
1296
1513
  helpAllRequested: false,
@@ -1299,9 +1516,11 @@ function parseArgs(argv, command, options = {}) {
1299
1516
  remainingArgs: [],
1300
1517
  rawArgs: {},
1301
1518
  positionals: [],
1302
- unknownFlags: []
1519
+ unknownFlags: [],
1520
+ rawGlobalArgs
1303
1521
  };
1304
- const parsed = parseArgv(argv, buildParserOptions(extracted));
1522
+ const parserOptions = buildParserOptions(extracted);
1523
+ const parsed = parseArgv(commandArgv, parserOptions);
1305
1524
  const rawArgs = mergeWithPositionals(parsed, extracted);
1306
1525
  for (const field of extracted.fields) if (field.env && rawArgs[field.name] === void 0) {
1307
1526
  const envNames = Array.isArray(field.env) ? field.env : [field.env];
@@ -1316,6 +1535,11 @@ function parseArgs(argv, command, options = {}) {
1316
1535
  const knownFlags = new Set(extracted.fields.map((f) => f.name));
1317
1536
  const knownCliNames = new Set(extracted.fields.map((f) => f.cliName));
1318
1537
  const knownAliases = new Set(extracted.fields.filter((f) => f.alias).map((f) => f.alias));
1538
+ if (options.globalExtracted) for (const f of options.globalExtracted.fields) {
1539
+ knownFlags.add(f.name);
1540
+ knownCliNames.add(f.cliName);
1541
+ if (f.alias) knownAliases.add(f.alias);
1542
+ }
1319
1543
  const unknownFlags = [];
1320
1544
  for (const key of Object.keys(parsed.options)) if (!knownFlags.has(key) && !knownCliNames.has(key) && !knownAliases.has(key)) unknownFlags.push(key);
1321
1545
  return {
@@ -1327,7 +1551,64 @@ function parseArgs(argv, command, options = {}) {
1327
1551
  rawArgs,
1328
1552
  positionals: parsed.positionals,
1329
1553
  unknownFlags,
1330
- extractedFields: extracted
1554
+ extractedFields: extracted,
1555
+ rawGlobalArgs
1556
+ };
1557
+ }
1558
+ /**
1559
+ * Parse global args from a list of tokens (e.g., tokens before the subcommand).
1560
+ * Env fallbacks are applied later in the runner on the accumulated global args.
1561
+ */
1562
+ function parseGlobalArgs(tokens, globalExtracted) {
1563
+ if (tokens.length === 0) return {};
1564
+ return mergeWithPositionals(parseArgv(tokens, buildParserOptions(globalExtracted)), globalExtracted);
1565
+ }
1566
+ /**
1567
+ * Separate global flags from command-local args in argv.
1568
+ * Global flags mixed with command args (e.g., `build --verbose --output dist`)
1569
+ * are extracted and returned separately.
1570
+ * When a flag is defined in both global and local schemas, the local definition
1571
+ * takes precedence (the flag stays in the command tokens).
1572
+ *
1573
+ * Note: Combined short flags (e.g., `-vq`) are not decomposed here; only
1574
+ * single-character short options are recognized as global. The underlying
1575
+ * `parseArgv` handles combined shorts for command-local parsing.
1576
+ */
1577
+ function separateGlobalArgs(argv, globalExtracted, localExtracted) {
1578
+ const lookup = buildGlobalFlagLookup(globalExtracted);
1579
+ const localCliNames = new Set(localExtracted?.fields.map((f) => f.cliName) ?? []);
1580
+ const localAliases = new Set(localExtracted?.fields.filter((f) => f.alias).map((f) => f.alias) ?? []);
1581
+ const globalTokens = [];
1582
+ const commandTokens = [];
1583
+ for (let i = 0; i < argv.length; i++) {
1584
+ const arg = argv[i];
1585
+ if (arg === "--") {
1586
+ commandTokens.push(...argv.slice(i));
1587
+ break;
1588
+ }
1589
+ if (arg.startsWith("--")) {
1590
+ const { resolvedName, withoutDashes, isNegated, isGlobal } = resolveGlobalLongOption(arg, lookup);
1591
+ const flagName = isNegated ? withoutDashes.slice(3) : withoutDashes;
1592
+ const isLocalCollision = localCliNames.has(withoutDashes) || localCliNames.has(flagName);
1593
+ if (isGlobal && !isLocalCollision) {
1594
+ i += collectGlobalFlag(argv, i, resolvedName, isNegated, lookup.booleanFlags, globalTokens) - 1;
1595
+ continue;
1596
+ }
1597
+ } else if (arg.startsWith("-") && arg.length > 1) {
1598
+ const withoutDash = arg.includes("=") ? arg.slice(1, arg.indexOf("=")) : arg.slice(1);
1599
+ if (withoutDash.length === 1) {
1600
+ const resolvedName = lookup.aliasMap.get(withoutDash) ?? withoutDash;
1601
+ if ((lookup.aliases.has(withoutDash) || lookup.flagNames.has(resolvedName)) && !localAliases.has(withoutDash)) {
1602
+ i += collectGlobalFlag(argv, i, resolvedName, false, lookup.booleanFlags, globalTokens) - 1;
1603
+ continue;
1604
+ }
1605
+ }
1606
+ }
1607
+ commandTokens.push(arg);
1608
+ }
1609
+ return {
1610
+ separated: commandTokens,
1611
+ globalParsed: parseGlobalArgs(globalTokens, globalExtracted)
1331
1612
  };
1332
1613
  }
1333
1614
 
@@ -1510,11 +1791,11 @@ const defaultLogger = {
1510
1791
  * ```
1511
1792
  */
1512
1793
  async function runCommand(command, argv, options = {}) {
1794
+ const globalExtracted = extractAndValidateGlobal(options);
1513
1795
  return runCommandInternal(command, argv, {
1514
1796
  ...options,
1515
1797
  handleSignals: false,
1516
- skipValidation: options.skipValidation,
1517
- logger: options.logger
1798
+ _globalExtracted: globalExtracted
1518
1799
  });
1519
1800
  }
1520
1801
  /**
@@ -1541,16 +1822,20 @@ async function runCommand(command, argv, options = {}) {
1541
1822
  * ```
1542
1823
  */
1543
1824
  async function runMain(command, options = {}) {
1825
+ const globalExtracted = extractAndValidateGlobal(options);
1544
1826
  const result = await runCommandInternal(command, process.argv.slice(2), {
1545
1827
  debug: options.debug,
1546
1828
  captureLogs: options.captureLogs,
1547
1829
  skipValidation: options.skipValidation,
1548
1830
  handleSignals: true,
1549
1831
  logger: options.logger,
1832
+ globalArgs: options.globalArgs,
1833
+ _globalExtracted: globalExtracted,
1550
1834
  _context: {
1551
1835
  commandPath: [],
1552
1836
  rootName: command.name,
1553
- rootVersion: options.version
1837
+ rootVersion: options.version,
1838
+ globalExtracted
1554
1839
  }
1555
1840
  });
1556
1841
  if (process.stdout.writableLength > 0) await new Promise((resolve) => process.stdout.once("drain", resolve));
@@ -1563,7 +1848,8 @@ async function runCommandInternal(command, argv, options = {}) {
1563
1848
  const logger = options.logger ?? defaultLogger;
1564
1849
  const context = options._context ?? {
1565
1850
  commandPath: [],
1566
- rootName: command.name
1851
+ rootName: command.name,
1852
+ globalExtracted: options._globalExtracted
1567
1853
  };
1568
1854
  const collector = options.captureLogs ?? false ? require_subcommand_router.createLogCollector() : null;
1569
1855
  collector?.start();
@@ -1571,12 +1857,19 @@ async function runCommandInternal(command, argv, options = {}) {
1571
1857
  return require_subcommand_router.mergeLogs(options._existingLogs ?? require_subcommand_router.emptyLogs(), collector?.getLogs() ?? require_subcommand_router.emptyLogs());
1572
1858
  };
1573
1859
  try {
1574
- const parseResult = parseArgs(argv, command, { skipValidation: options.skipValidation });
1860
+ const parseResult = parseArgs(argv, command, {
1861
+ skipValidation: options.skipValidation,
1862
+ globalExtracted: options._globalExtracted
1863
+ });
1864
+ const accumulatedGlobalArgs = {
1865
+ ...options._parsedGlobalArgs,
1866
+ ...parseResult.rawGlobalArgs
1867
+ };
1575
1868
  if (parseResult.helpRequested || parseResult.helpAllRequested) {
1576
1869
  let hasUnknownSubcommand = false;
1577
1870
  const subCmdNames = require_subcommand_router.listSubCommands(command);
1578
1871
  if (subCmdNames.length > 0) {
1579
- const potentialSubCmd = argv.find((arg) => !arg.startsWith("-"));
1872
+ const potentialSubCmd = findFirstPositional(argv, context.globalExtracted);
1580
1873
  if (potentialSubCmd && !subCmdNames.includes(potentialSubCmd)) {
1581
1874
  logger.error(formatUnknownSubcommand(potentialSubCmd, subCmdNames));
1582
1875
  logger.error("");
@@ -1620,13 +1913,15 @@ async function runCommandInternal(command, argv, options = {}) {
1620
1913
  const subContext = {
1621
1914
  commandPath: [...context.commandPath ?? [], parseResult.subCommand],
1622
1915
  rootName: context.rootName,
1623
- rootVersion: context.rootVersion
1916
+ rootVersion: context.rootVersion,
1917
+ globalExtracted: context.globalExtracted
1624
1918
  };
1625
1919
  collector?.stop();
1626
1920
  return runCommandInternal(subCmd, parseResult.remainingArgs, {
1627
1921
  ...options,
1628
1922
  _context: subContext,
1629
- _existingLogs: getCurrentLogs()
1923
+ _existingLogs: getCurrentLogs(),
1924
+ _parsedGlobalArgs: accumulatedGlobalArgs
1630
1925
  });
1631
1926
  }
1632
1927
  }
@@ -1658,9 +1953,35 @@ async function runCommandInternal(command, argv, options = {}) {
1658
1953
  };
1659
1954
  } else if (unknownKeysMode === "strip") for (const flag of parseResult.unknownFlags) logger.error(formatUnknownFlagWarning(flag, knownFlags));
1660
1955
  }
1956
+ let validatedGlobalArgs = {};
1957
+ if (options.globalArgs && options._globalExtracted) {
1958
+ for (const field of options._globalExtracted.fields) if (field.env && accumulatedGlobalArgs[field.name] === void 0) {
1959
+ const envNames = Array.isArray(field.env) ? field.env : [field.env];
1960
+ for (const envName of envNames) {
1961
+ const envValue = process.env[envName];
1962
+ if (envValue !== void 0) {
1963
+ accumulatedGlobalArgs[field.name] = envValue;
1964
+ break;
1965
+ }
1966
+ }
1967
+ }
1968
+ const globalValidation = validateArgs(accumulatedGlobalArgs, options.globalArgs);
1969
+ if (!globalValidation.success) {
1970
+ const errorMessage = formatValidationErrors$1(globalValidation.errors);
1971
+ logger.error(errorMessage);
1972
+ collector?.stop();
1973
+ return {
1974
+ success: false,
1975
+ error: new Error(errorMessage),
1976
+ exitCode: 1,
1977
+ logs: getCurrentLogs()
1978
+ };
1979
+ }
1980
+ validatedGlobalArgs = globalValidation.data;
1981
+ }
1661
1982
  if (!command.args) {
1662
1983
  collector?.stop();
1663
- return await executeLifecycle(command, {}, {
1984
+ return await executeLifecycle(command, validatedGlobalArgs, {
1664
1985
  handleSignals: options.handleSignals,
1665
1986
  captureLogs: options.captureLogs,
1666
1987
  existingLogs: getCurrentLogs()
@@ -1677,8 +1998,12 @@ async function runCommandInternal(command, argv, options = {}) {
1677
1998
  logs: getCurrentLogs()
1678
1999
  };
1679
2000
  }
2001
+ const mergedArgs = {
2002
+ ...validatedGlobalArgs,
2003
+ ...validationResult.data
2004
+ };
1680
2005
  collector?.stop();
1681
- return await executeLifecycle(command, validationResult.data, {
2006
+ return await executeLifecycle(command, mergedArgs, {
1682
2007
  handleSignals: options.handleSignals,
1683
2008
  captureLogs: options.captureLogs,
1684
2009
  existingLogs: getCurrentLogs()
@@ -1695,6 +2020,23 @@ async function runCommandInternal(command, argv, options = {}) {
1695
2020
  };
1696
2021
  }
1697
2022
  }
2023
+ /**
2024
+ * Extract global fields from options.globalArgs and validate the schema upfront.
2025
+ * Rejects positional fields since global options must be flags.
2026
+ * Returns undefined when no globalArgs is provided.
2027
+ */
2028
+ function extractAndValidateGlobal(options) {
2029
+ if (!options.globalArgs) return void 0;
2030
+ const extracted = require_lazy.extractFields(options.globalArgs);
2031
+ if (!options.skipValidation) {
2032
+ validateDuplicateFields(extracted);
2033
+ validateDuplicateAliases(extracted);
2034
+ validateReservedAliases(extracted, true);
2035
+ const positionalNames = extracted.fields.filter((f) => f.positional).map((f) => f.name);
2036
+ if (positionalNames.length > 0) throw new Error(`Global options schema must not contain positional arguments. Found: ${positionalNames.join(", ")}`);
2037
+ }
2038
+ return extracted;
2039
+ }
1698
2040
 
1699
2041
  //#endregion
1700
2042
  Object.defineProperty(exports, 'DuplicateAliasError', {
@@ -1835,4 +2177,4 @@ Object.defineProperty(exports, 'validateReservedAliases', {
1835
2177
  return validateReservedAliases;
1836
2178
  }
1837
2179
  });
1838
- //# sourceMappingURL=runner-C4fSHJMe.cjs.map
2180
+ //# sourceMappingURL=runner-C1Aah5c5.cjs.map