politty 0.4.4 → 0.4.7

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-DR47Ccac.cjs → completion-Df0eZ70u.cjs} +230 -18
  9. package/dist/completion-Df0eZ70u.cjs.map +1 -0
  10. package/dist/{zsh-XbRzR8FW.js → completion-_AnQsWh9.js} +202 -8
  11. package/dist/completion-_AnQsWh9.js.map +1 -0
  12. package/dist/docs/index.cjs +274 -28
  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 +269 -29
  19. package/dist/docs/index.js.map +1 -1
  20. package/dist/{value-completion-resolver-C9LTGr0O.d.ts → index-BA0GkZQx.d.cts} +83 -4
  21. package/dist/index-BA0GkZQx.d.cts.map +1 -0
  22. package/dist/{value-completion-resolver-BQgHsX7b.d.cts → index-rMDe9hp1.d.ts} +83 -4
  23. package/dist/index-rMDe9hp1.d.ts.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-Cn6Oq4ZZ.cjs} +453 -26
  35. package/dist/runner-Cn6Oq4ZZ.cjs.map +1 -0
  36. package/dist/{runner-D6k4BgB4.js → runner-D4ByDT5I.js} +453 -26
  37. package/dist/runner-D4ByDT5I.js.map +1 -0
  38. package/dist/{schema-extractor-DFaAZzaY.d.cts → schema-extractor-BoWkcP9a.d.cts} +48 -2
  39. package/dist/schema-extractor-BoWkcP9a.d.cts.map +1 -0
  40. package/dist/{schema-extractor-n9288WJ6.d.ts → schema-extractor-DoDO4M_i.d.ts} +49 -3
  41. package/dist/schema-extractor-DoDO4M_i.d.ts.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-DR47Ccac.cjs.map +0 -1
  60. package/dist/zsh-XbRzR8FW.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");
@@ -35,12 +35,30 @@ async function executeLifecycle(command, args, _options = {}) {
35
35
  process.off("SIGINT", signalHandler);
36
36
  process.off("SIGTERM", signalHandler);
37
37
  }
38
+ const signalError = /* @__PURE__ */ new Error("Process interrupted");
39
+ cleanupContext.error = signalError;
38
40
  if (command.cleanup) try {
39
41
  await command.cleanup(cleanupContext);
40
42
  } catch (e) {
41
43
  console.error("Error during signal cleanup:", e);
42
44
  }
45
+ if (_options.globalCleanup) try {
46
+ await _options.globalCleanup({ error: signalError });
47
+ } catch (e) {
48
+ console.error("Error during global signal cleanup:", e);
49
+ }
43
50
  collector?.stop();
51
+ if (process.stdout.writableNeedDrain) await new Promise((resolve) => {
52
+ const timeout = setTimeout(() => {
53
+ process.stdout.off("drain", onDrain);
54
+ resolve();
55
+ }, 200);
56
+ const onDrain = () => {
57
+ clearTimeout(timeout);
58
+ resolve();
59
+ };
60
+ process.stdout.once("drain", onDrain);
61
+ });
44
62
  process.exit(1);
45
63
  };
46
64
  process.on("SIGINT", signalHandler);
@@ -539,6 +557,7 @@ function renderUsageLine(command, context) {
539
557
  const parts = [];
540
558
  const name = buildUsageCommandName(command, context);
541
559
  parts.push(styles.commandName(name));
560
+ if (context?.globalExtracted?.fields.length) parts.push(styles.placeholder("[global options]"));
542
561
  const extracted = require_lazy.getExtractedFields(command);
543
562
  if (extracted) {
544
563
  const positionals = extracted.fields.filter((a) => a.positional);
@@ -724,6 +743,24 @@ function formatOption(flags, description, indent = 0, extraDescPadding = 0) {
724
743
  return `${indentStr} ${padEndVisual(flags, effectiveFlagWidth)}${description}`;
725
744
  }
726
745
  /**
746
+ * Format a single option field as a help line
747
+ */
748
+ function formatFieldLine(opt, indent = 0, extraDescPadding = 0) {
749
+ const flags = formatFlags(opt);
750
+ let desc = opt.description ?? "";
751
+ if (opt.defaultValue !== void 0) desc += ` ${styles.defaultValue(`(default: ${JSON.stringify(opt.defaultValue)})`)}`;
752
+ if (opt.required) desc += ` ${styles.required("(required)")}`;
753
+ const envInfo = formatEnvInfo(opt.env);
754
+ if (envInfo) desc += ` ${envInfo}`;
755
+ return formatOption(flags, desc, indent, extraDescPadding);
756
+ }
757
+ /**
758
+ * Render global options section
759
+ */
760
+ function renderGlobalOptions(globalExtracted) {
761
+ return globalExtracted.fields.filter((a) => !a.positional).map((opt) => formatFieldLine(opt)).join("\n");
762
+ }
763
+ /**
727
764
  * Render options for a subcommand (used by showSubcommandOptions)
728
765
  */
729
766
  function renderSubcommandOptionsCompact(command, indent) {
@@ -786,6 +823,7 @@ function generateHelp(command, options) {
786
823
  sections.push(`${styles.sectionHeader("Usage:")} ${renderUsageLine(command, context)}`);
787
824
  const optionsText = renderOptions(command, options.descriptions, context);
788
825
  if (optionsText) sections.push(`${styles.sectionHeader("Options:")}\n${optionsText}`);
826
+ if (context?.globalExtracted?.fields.length) sections.push(`${styles.sectionHeader("Global Options:")}\n${renderGlobalOptions(context.globalExtracted)}`);
789
827
  if (options.showSubcommands !== false && command.subCommands && getVisibleSubcommandEntries(command.subCommands).length > 0) {
790
828
  const currentPath = context?.commandPath?.join(" ") ?? "";
791
829
  const visibleSubCommands = Object.fromEntries(getVisibleSubcommandEntries(command.subCommands));
@@ -1098,13 +1136,20 @@ function formatCommandValidationErrors(errors) {
1098
1136
  * - Combined short options: -abc (treated as -a -b -c if all are boolean)
1099
1137
  * - Positional arguments
1100
1138
  * - -- to stop parsing options
1139
+ * - Boolean negation: --no-flag, --noFlag (requires `booleanFlags`)
1140
+ *
1141
+ * **Note:** When using negation detection (`--noFlag` / `--no-flag`),
1142
+ * supply `definedNames` so that options whose names happen to start with
1143
+ * "no" (e.g. `noDryRun`) are not mistaken for negation of another flag.
1144
+ * Without `definedNames`, all `--noX` forms matching a boolean flag will
1145
+ * be treated as negation.
1101
1146
  *
1102
1147
  * @param argv - Command line arguments
1103
1148
  * @param options - Parser options
1104
1149
  * @returns Parsed arguments
1105
1150
  */
1106
1151
  function parseArgv(argv, options = {}) {
1107
- const { aliasMap = /* @__PURE__ */ new Map(), booleanFlags = /* @__PURE__ */ new Set(), arrayFlags = /* @__PURE__ */ new Set() } = options;
1152
+ const { aliasMap = /* @__PURE__ */ new Map(), booleanFlags = /* @__PURE__ */ new Set(), arrayFlags = /* @__PURE__ */ new Set(), definedNames = /* @__PURE__ */ new Set() } = options;
1108
1153
  const result = {
1109
1154
  options: {},
1110
1155
  positionals: [],
@@ -1137,11 +1182,28 @@ function parseArgv(argv, options = {}) {
1137
1182
  const withoutDashes = arg.slice(2);
1138
1183
  if (withoutDashes.startsWith("no-")) {
1139
1184
  const flagName = withoutDashes.slice(3);
1140
- const resolvedName = aliasMap.get(flagName) ?? flagName;
1185
+ if (flagName === flagName.toLowerCase()) {
1186
+ const resolvedName = aliasMap.get(flagName) ?? flagName;
1187
+ if (booleanFlags.has(resolvedName)) {
1188
+ const asIsResolved = aliasMap.get(withoutDashes) ?? withoutDashes;
1189
+ if (!definedNames.has(asIsResolved)) {
1190
+ setOption(flagName, false);
1191
+ i++;
1192
+ continue;
1193
+ }
1194
+ }
1195
+ }
1196
+ }
1197
+ if (withoutDashes.length > 2 && withoutDashes.startsWith("no") && /[A-Z]/.test(withoutDashes[2])) {
1198
+ const camelFlagName = withoutDashes[2].toLowerCase() + withoutDashes.slice(3);
1199
+ const resolvedName = aliasMap.get(camelFlagName) ?? camelFlagName;
1141
1200
  if (booleanFlags.has(resolvedName)) {
1142
- setOption(flagName, false);
1143
- i++;
1144
- continue;
1201
+ const asIsResolved = aliasMap.get(withoutDashes) ?? withoutDashes;
1202
+ if (!definedNames.has(asIsResolved)) {
1203
+ setOption(camelFlagName, false);
1204
+ i++;
1205
+ continue;
1206
+ }
1145
1207
  }
1146
1208
  }
1147
1209
  const eqIndex = withoutDashes.indexOf("=");
@@ -1207,16 +1269,21 @@ function buildParserOptions(extracted) {
1207
1269
  const aliasMap = /* @__PURE__ */ new Map();
1208
1270
  const booleanFlags = /* @__PURE__ */ new Set();
1209
1271
  const arrayFlags = /* @__PURE__ */ new Set();
1272
+ const definedNames = /* @__PURE__ */ new Set();
1273
+ for (const field of extracted.fields) definedNames.add(field.name);
1210
1274
  for (const field of extracted.fields) {
1211
1275
  if (field.cliName !== field.name) aliasMap.set(field.cliName, field.name);
1212
1276
  if (field.alias) aliasMap.set(field.alias, field.name);
1277
+ const camelVariant = require_lazy.toCamelCase(field.name);
1278
+ if (camelVariant !== field.name && !definedNames.has(camelVariant) && !aliasMap.has(camelVariant)) aliasMap.set(camelVariant, field.name);
1213
1279
  if (field.type === "boolean") booleanFlags.add(field.name);
1214
1280
  if (field.type === "array") arrayFlags.add(field.name);
1215
1281
  }
1216
1282
  return {
1217
1283
  aliasMap,
1218
1284
  booleanFlags,
1219
- arrayFlags
1285
+ arrayFlags,
1286
+ definedNames
1220
1287
  };
1221
1288
  }
1222
1289
  /**
@@ -1240,6 +1307,151 @@ function mergeWithPositionals(parsed, extracted) {
1240
1307
  return result;
1241
1308
  }
1242
1309
 
1310
+ //#endregion
1311
+ //#region src/parser/subcommand-scanner.ts
1312
+ /**
1313
+ * Build lookup tables from extracted global schema fields.
1314
+ * Shared by scanForSubcommand, separateGlobalArgs, and findFirstPositional.
1315
+ */
1316
+ function buildGlobalFlagLookup(globalExtracted) {
1317
+ const { aliasMap = /* @__PURE__ */ new Map(), booleanFlags = /* @__PURE__ */ new Set() } = buildParserOptions(globalExtracted);
1318
+ return {
1319
+ aliasMap,
1320
+ booleanFlags,
1321
+ flagNames: new Set(globalExtracted.fields.map((f) => f.name)),
1322
+ cliNames: new Set(globalExtracted.fields.map((f) => f.cliName)),
1323
+ aliases: new Set(globalExtracted.fields.filter((f) => f.alias).map((f) => f.alias))
1324
+ };
1325
+ }
1326
+ /**
1327
+ * Resolve a long option (--flag, --flag=value, --no-flag) against global flag lookup.
1328
+ * Returns the resolved camelCase name and whether it is a known global flag.
1329
+ */
1330
+ function resolveGlobalLongOption(arg, lookup) {
1331
+ const withoutDashes = arg.includes("=") ? arg.slice(2, arg.indexOf("=")) : arg.slice(2);
1332
+ const isNegated = withoutDashes.startsWith("no-");
1333
+ const flagName = isNegated ? withoutDashes.slice(3) : withoutDashes;
1334
+ const resolvedName = lookup.aliasMap.get(flagName) ?? flagName;
1335
+ return {
1336
+ resolvedName,
1337
+ withoutDashes,
1338
+ isNegated,
1339
+ isGlobal: lookup.flagNames.has(resolvedName) || lookup.cliNames.has(withoutDashes) || lookup.cliNames.has(flagName)
1340
+ };
1341
+ }
1342
+ /**
1343
+ * Check whether a non-boolean flag should consume the next argv token as its value.
1344
+ * Returns true when the next token exists, is not a flag, and the current flag
1345
+ * is not boolean / negated / using = syntax.
1346
+ */
1347
+ function shouldConsumeValue(arg, resolvedName, isNegated, nextArg, booleanFlags) {
1348
+ return !arg.includes("=") && !booleanFlags.has(resolvedName) && !isNegated && nextArg !== void 0 && !nextArg.startsWith("-");
1349
+ }
1350
+ /**
1351
+ * Collect a recognized global flag (and its value if applicable) into `dest`,
1352
+ * returning how many argv positions were consumed (1 or 2).
1353
+ */
1354
+ function collectGlobalFlag(argv, i, resolvedName, isNegated, booleanFlags, dest) {
1355
+ const arg = argv[i];
1356
+ dest.push(arg);
1357
+ if (shouldConsumeValue(arg, resolvedName, isNegated, argv[i + 1], booleanFlags)) {
1358
+ dest.push(argv[i + 1]);
1359
+ return 2;
1360
+ }
1361
+ return 1;
1362
+ }
1363
+ /**
1364
+ * Scan argv to find the subcommand position, skipping over global flags.
1365
+ *
1366
+ * Walks argv and recognizes global flags (long, short, --no-*) so that
1367
+ * `my-cli --verbose build --output dist` correctly identifies `build` as
1368
+ * the subcommand (index 1) rather than treating `--verbose` as the subcommand.
1369
+ *
1370
+ * Limitation: flags appearing before the subcommand name are matched only
1371
+ * against the global schema. If a flag is defined in both global and a
1372
+ * subcommand's local schema, the pre-subcommand occurrence is always treated
1373
+ * as global because the local schema is not available until the subcommand is
1374
+ * identified (lazy-loaded commands make eager checking infeasible). Place
1375
+ * colliding flags after the subcommand name so that `separateGlobalArgs` can
1376
+ * apply local-precedence logic.
1377
+ *
1378
+ * @param argv - Command line arguments
1379
+ * @param subCommandNames - Valid subcommand names
1380
+ * @param globalExtracted - Extracted fields from global args schema
1381
+ * @returns Scan result with subcommand position and token separation
1382
+ */
1383
+ function scanForSubcommand(argv, subCommandNames, globalExtracted) {
1384
+ const lookup = buildGlobalFlagLookup(globalExtracted);
1385
+ const subCommandNameSet = new Set(subCommandNames);
1386
+ const globalTokensBefore = [];
1387
+ let i = 0;
1388
+ while (i < argv.length) {
1389
+ const arg = argv[i];
1390
+ if (arg === "--" || BUILTIN_FLAGS.has(arg)) break;
1391
+ if (!arg.startsWith("-") && subCommandNameSet.has(arg)) return {
1392
+ subCommandIndex: i,
1393
+ globalTokensBefore,
1394
+ tokensAfterSubcommand: argv.slice(i + 1)
1395
+ };
1396
+ if (arg.startsWith("--")) {
1397
+ const { resolvedName, isNegated, isGlobal } = resolveGlobalLongOption(arg, lookup);
1398
+ if (isGlobal) {
1399
+ i += collectGlobalFlag(argv, i, resolvedName, isNegated, lookup.booleanFlags, globalTokensBefore);
1400
+ continue;
1401
+ }
1402
+ break;
1403
+ }
1404
+ if (arg.startsWith("-") && arg.length > 1) {
1405
+ const withoutDash = arg.includes("=") ? arg.slice(1, arg.indexOf("=")) : arg.slice(1);
1406
+ if (withoutDash.length === 1) {
1407
+ const resolvedName = lookup.aliasMap.get(withoutDash) ?? withoutDash;
1408
+ if (lookup.aliases.has(withoutDash) || lookup.flagNames.has(resolvedName)) {
1409
+ i += collectGlobalFlag(argv, i, resolvedName, false, lookup.booleanFlags, globalTokensBefore);
1410
+ continue;
1411
+ }
1412
+ }
1413
+ break;
1414
+ }
1415
+ break;
1416
+ }
1417
+ return {
1418
+ subCommandIndex: -1,
1419
+ globalTokensBefore,
1420
+ tokensAfterSubcommand: []
1421
+ };
1422
+ }
1423
+ const BUILTIN_FLAGS = new Set([
1424
+ "--help",
1425
+ "-h",
1426
+ "--help-all",
1427
+ "-H",
1428
+ "--version"
1429
+ ]);
1430
+ /**
1431
+ * Find the first positional argument in argv, properly skipping global flag values.
1432
+ * Without globalExtracted, falls back to the first non-flag token.
1433
+ */
1434
+ function findFirstPositional(argv, globalExtracted) {
1435
+ if (!globalExtracted) return argv.find((arg) => !arg.startsWith("-"));
1436
+ const lookup = buildGlobalFlagLookup(globalExtracted);
1437
+ for (let i = 0; i < argv.length; i++) {
1438
+ const arg = argv[i];
1439
+ if (!arg.startsWith("-")) return arg;
1440
+ if (arg === "--") return void 0;
1441
+ if (arg.startsWith("--")) {
1442
+ const { resolvedName, isNegated, isGlobal } = resolveGlobalLongOption(arg, lookup);
1443
+ if (isGlobal && shouldConsumeValue(arg, resolvedName, isNegated, argv[i + 1], lookup.booleanFlags)) i++;
1444
+ continue;
1445
+ }
1446
+ if (arg.length === 2) {
1447
+ const ch = arg[1];
1448
+ if (lookup.aliases.has(ch)) {
1449
+ if (shouldConsumeValue(arg, lookup.aliasMap.get(ch) ?? ch, false, argv[i + 1], lookup.booleanFlags)) i++;
1450
+ }
1451
+ }
1452
+ }
1453
+ }
1454
+
1243
1455
  //#endregion
1244
1456
  //#region src/parser/arg-parser.ts
1245
1457
  /**
@@ -1253,7 +1465,23 @@ function mergeWithPositionals(parsed, extracted) {
1253
1465
  function parseArgs(argv, command, options = {}) {
1254
1466
  const subCommandNames = command.subCommands ? Object.keys(command.subCommands) : [];
1255
1467
  const hasSubCommands = subCommandNames.length > 0;
1256
- if (hasSubCommands && argv.length > 0) {
1468
+ if (hasSubCommands && argv.length > 0) if (options.globalExtracted) {
1469
+ const scanResult = scanForSubcommand(argv, subCommandNames, options.globalExtracted);
1470
+ if (scanResult.subCommandIndex >= 0) {
1471
+ const rawGlobalArgs = parseGlobalArgs(scanResult.globalTokensBefore, options.globalExtracted);
1472
+ return {
1473
+ helpRequested: false,
1474
+ helpAllRequested: false,
1475
+ versionRequested: false,
1476
+ subCommand: argv[scanResult.subCommandIndex],
1477
+ remainingArgs: scanResult.tokensAfterSubcommand,
1478
+ rawArgs: {},
1479
+ positionals: [],
1480
+ unknownFlags: [],
1481
+ rawGlobalArgs
1482
+ };
1483
+ }
1484
+ } else {
1257
1485
  const firstArg = argv[0];
1258
1486
  if (firstArg && !firstArg.startsWith("-") && subCommandNames.includes(firstArg)) return {
1259
1487
  helpRequested: false,
@@ -1291,6 +1519,13 @@ function parseArgs(argv, command, options = {}) {
1291
1519
  positionals: [],
1292
1520
  unknownFlags: []
1293
1521
  };
1522
+ let commandArgv = argv;
1523
+ let rawGlobalArgs;
1524
+ if (options.globalExtracted) {
1525
+ const { separated, globalParsed } = separateGlobalArgs(argv, options.globalExtracted, extracted);
1526
+ commandArgv = separated;
1527
+ rawGlobalArgs = globalParsed;
1528
+ }
1294
1529
  if (!extracted) return {
1295
1530
  helpRequested: false,
1296
1531
  helpAllRequested: false,
@@ -1299,9 +1534,11 @@ function parseArgs(argv, command, options = {}) {
1299
1534
  remainingArgs: [],
1300
1535
  rawArgs: {},
1301
1536
  positionals: [],
1302
- unknownFlags: []
1537
+ unknownFlags: [],
1538
+ rawGlobalArgs
1303
1539
  };
1304
- const parsed = parseArgv(argv, buildParserOptions(extracted));
1540
+ const parserOptions = buildParserOptions(extracted);
1541
+ const parsed = parseArgv(commandArgv, parserOptions);
1305
1542
  const rawArgs = mergeWithPositionals(parsed, extracted);
1306
1543
  for (const field of extracted.fields) if (field.env && rawArgs[field.name] === void 0) {
1307
1544
  const envNames = Array.isArray(field.env) ? field.env : [field.env];
@@ -1316,6 +1553,11 @@ function parseArgs(argv, command, options = {}) {
1316
1553
  const knownFlags = new Set(extracted.fields.map((f) => f.name));
1317
1554
  const knownCliNames = new Set(extracted.fields.map((f) => f.cliName));
1318
1555
  const knownAliases = new Set(extracted.fields.filter((f) => f.alias).map((f) => f.alias));
1556
+ if (options.globalExtracted) for (const f of options.globalExtracted.fields) {
1557
+ knownFlags.add(f.name);
1558
+ knownCliNames.add(f.cliName);
1559
+ if (f.alias) knownAliases.add(f.alias);
1560
+ }
1319
1561
  const unknownFlags = [];
1320
1562
  for (const key of Object.keys(parsed.options)) if (!knownFlags.has(key) && !knownCliNames.has(key) && !knownAliases.has(key)) unknownFlags.push(key);
1321
1563
  return {
@@ -1327,7 +1569,64 @@ function parseArgs(argv, command, options = {}) {
1327
1569
  rawArgs,
1328
1570
  positionals: parsed.positionals,
1329
1571
  unknownFlags,
1330
- extractedFields: extracted
1572
+ extractedFields: extracted,
1573
+ rawGlobalArgs
1574
+ };
1575
+ }
1576
+ /**
1577
+ * Parse global args from a list of tokens (e.g., tokens before the subcommand).
1578
+ * Env fallbacks are applied later in the runner on the accumulated global args.
1579
+ */
1580
+ function parseGlobalArgs(tokens, globalExtracted) {
1581
+ if (tokens.length === 0) return {};
1582
+ return mergeWithPositionals(parseArgv(tokens, buildParserOptions(globalExtracted)), globalExtracted);
1583
+ }
1584
+ /**
1585
+ * Separate global flags from command-local args in argv.
1586
+ * Global flags mixed with command args (e.g., `build --verbose --output dist`)
1587
+ * are extracted and returned separately.
1588
+ * When a flag is defined in both global and local schemas, the local definition
1589
+ * takes precedence (the flag stays in the command tokens).
1590
+ *
1591
+ * Note: Combined short flags (e.g., `-vq`) are not decomposed here; only
1592
+ * single-character short options are recognized as global. The underlying
1593
+ * `parseArgv` handles combined shorts for command-local parsing.
1594
+ */
1595
+ function separateGlobalArgs(argv, globalExtracted, localExtracted) {
1596
+ const lookup = buildGlobalFlagLookup(globalExtracted);
1597
+ const localCliNames = new Set(localExtracted?.fields.map((f) => f.cliName) ?? []);
1598
+ const localAliases = new Set(localExtracted?.fields.filter((f) => f.alias).map((f) => f.alias) ?? []);
1599
+ const globalTokens = [];
1600
+ const commandTokens = [];
1601
+ for (let i = 0; i < argv.length; i++) {
1602
+ const arg = argv[i];
1603
+ if (arg === "--") {
1604
+ commandTokens.push(...argv.slice(i));
1605
+ break;
1606
+ }
1607
+ if (arg.startsWith("--")) {
1608
+ const { resolvedName, withoutDashes, isNegated, isGlobal } = resolveGlobalLongOption(arg, lookup);
1609
+ const flagName = isNegated ? withoutDashes.slice(3) : withoutDashes;
1610
+ const isLocalCollision = localCliNames.has(withoutDashes) || localCliNames.has(flagName);
1611
+ if (isGlobal && !isLocalCollision) {
1612
+ i += collectGlobalFlag(argv, i, resolvedName, isNegated, lookup.booleanFlags, globalTokens) - 1;
1613
+ continue;
1614
+ }
1615
+ } else if (arg.startsWith("-") && arg.length > 1) {
1616
+ const withoutDash = arg.includes("=") ? arg.slice(1, arg.indexOf("=")) : arg.slice(1);
1617
+ if (withoutDash.length === 1) {
1618
+ const resolvedName = lookup.aliasMap.get(withoutDash) ?? withoutDash;
1619
+ if ((lookup.aliases.has(withoutDash) || lookup.flagNames.has(resolvedName)) && !localAliases.has(withoutDash)) {
1620
+ i += collectGlobalFlag(argv, i, resolvedName, false, lookup.booleanFlags, globalTokens) - 1;
1621
+ continue;
1622
+ }
1623
+ }
1624
+ }
1625
+ commandTokens.push(arg);
1626
+ }
1627
+ return {
1628
+ separated: commandTokens,
1629
+ globalParsed: parseGlobalArgs(globalTokens, globalExtracted)
1331
1630
  };
1332
1631
  }
1333
1632
 
@@ -1510,12 +1809,61 @@ const defaultLogger = {
1510
1809
  * ```
1511
1810
  */
1512
1811
  async function runCommand(command, argv, options = {}) {
1513
- return runCommandInternal(command, argv, {
1812
+ const globalExtracted = extractAndValidateGlobal(options);
1813
+ const shouldCaptureLogs = options.captureLogs ?? false;
1814
+ const globalCollector = shouldCaptureLogs ? require_subcommand_router.createLogCollector() : null;
1815
+ if (options.setup) {
1816
+ globalCollector?.start();
1817
+ try {
1818
+ await options.setup({});
1819
+ } catch (e) {
1820
+ const error = e instanceof Error ? e : new Error(String(e));
1821
+ if (options.cleanup) try {
1822
+ await options.cleanup({ error });
1823
+ } catch {}
1824
+ globalCollector?.stop();
1825
+ return {
1826
+ success: false,
1827
+ error,
1828
+ exitCode: 1,
1829
+ logs: globalCollector?.getLogs() ?? require_subcommand_router.emptyLogs()
1830
+ };
1831
+ }
1832
+ globalCollector?.stop();
1833
+ }
1834
+ const result = await runCommandInternal(command, argv, {
1514
1835
  ...options,
1515
1836
  handleSignals: false,
1516
- skipValidation: options.skipValidation,
1517
- logger: options.logger
1837
+ _globalExtracted: globalExtracted,
1838
+ _globalCleanup: options.cleanup,
1839
+ _existingLogs: globalCollector?.getLogs()
1518
1840
  });
1841
+ if (options.cleanup) {
1842
+ const cleanupCollector = shouldCaptureLogs ? require_subcommand_router.createLogCollector() : null;
1843
+ cleanupCollector?.start();
1844
+ const cleanupCtx = { error: !result.success ? result.error : void 0 };
1845
+ try {
1846
+ await options.cleanup(cleanupCtx);
1847
+ } catch (e) {
1848
+ if (result.success) {
1849
+ const error = e instanceof Error ? e : new Error(String(e));
1850
+ cleanupCollector?.stop();
1851
+ return {
1852
+ success: false,
1853
+ error,
1854
+ exitCode: 1,
1855
+ logs: require_subcommand_router.mergeLogs(result.logs, cleanupCollector?.getLogs() ?? require_subcommand_router.emptyLogs())
1856
+ };
1857
+ }
1858
+ }
1859
+ cleanupCollector?.stop();
1860
+ const cleanupLogs = cleanupCollector?.getLogs() ?? require_subcommand_router.emptyLogs();
1861
+ if (cleanupLogs.entries.length > 0) return {
1862
+ ...result,
1863
+ logs: require_subcommand_router.mergeLogs(result.logs, cleanupLogs)
1864
+ };
1865
+ }
1866
+ return result;
1519
1867
  }
1520
1868
  /**
1521
1869
  * Run a CLI command as the main entry point
@@ -1541,18 +1889,38 @@ async function runCommand(command, argv, options = {}) {
1541
1889
  * ```
1542
1890
  */
1543
1891
  async function runMain(command, options = {}) {
1892
+ const globalExtracted = extractAndValidateGlobal(options);
1893
+ if (options.setup) try {
1894
+ await options.setup({});
1895
+ } catch (e) {
1896
+ const error = e instanceof Error ? e : new Error(String(e));
1897
+ if (options.cleanup) try {
1898
+ await options.cleanup({ error });
1899
+ } catch {}
1900
+ process.exit(1);
1901
+ }
1544
1902
  const result = await runCommandInternal(command, process.argv.slice(2), {
1545
1903
  debug: options.debug,
1546
1904
  captureLogs: options.captureLogs,
1547
1905
  skipValidation: options.skipValidation,
1548
1906
  handleSignals: true,
1549
1907
  logger: options.logger,
1908
+ globalArgs: options.globalArgs,
1909
+ _globalExtracted: globalExtracted,
1910
+ _globalCleanup: options.cleanup,
1550
1911
  _context: {
1551
1912
  commandPath: [],
1552
1913
  rootName: command.name,
1553
- rootVersion: options.version
1914
+ rootVersion: options.version,
1915
+ globalExtracted
1554
1916
  }
1555
1917
  });
1918
+ if (options.cleanup) {
1919
+ const cleanupCtx = { error: !result.success ? result.error : void 0 };
1920
+ try {
1921
+ await options.cleanup(cleanupCtx);
1922
+ } catch {}
1923
+ }
1556
1924
  if (process.stdout.writableLength > 0) await new Promise((resolve) => process.stdout.once("drain", resolve));
1557
1925
  process.exit(result.exitCode);
1558
1926
  }
@@ -1563,7 +1931,8 @@ async function runCommandInternal(command, argv, options = {}) {
1563
1931
  const logger = options.logger ?? defaultLogger;
1564
1932
  const context = options._context ?? {
1565
1933
  commandPath: [],
1566
- rootName: command.name
1934
+ rootName: command.name,
1935
+ globalExtracted: options._globalExtracted
1567
1936
  };
1568
1937
  const collector = options.captureLogs ?? false ? require_subcommand_router.createLogCollector() : null;
1569
1938
  collector?.start();
@@ -1571,12 +1940,19 @@ async function runCommandInternal(command, argv, options = {}) {
1571
1940
  return require_subcommand_router.mergeLogs(options._existingLogs ?? require_subcommand_router.emptyLogs(), collector?.getLogs() ?? require_subcommand_router.emptyLogs());
1572
1941
  };
1573
1942
  try {
1574
- const parseResult = parseArgs(argv, command, { skipValidation: options.skipValidation });
1943
+ const parseResult = parseArgs(argv, command, {
1944
+ skipValidation: options.skipValidation,
1945
+ globalExtracted: options._globalExtracted
1946
+ });
1947
+ const accumulatedGlobalArgs = {
1948
+ ...options._parsedGlobalArgs,
1949
+ ...parseResult.rawGlobalArgs
1950
+ };
1575
1951
  if (parseResult.helpRequested || parseResult.helpAllRequested) {
1576
1952
  let hasUnknownSubcommand = false;
1577
1953
  const subCmdNames = require_subcommand_router.listSubCommands(command);
1578
1954
  if (subCmdNames.length > 0) {
1579
- const potentialSubCmd = argv.find((arg) => !arg.startsWith("-"));
1955
+ const potentialSubCmd = findFirstPositional(argv, context.globalExtracted);
1580
1956
  if (potentialSubCmd && !subCmdNames.includes(potentialSubCmd)) {
1581
1957
  logger.error(formatUnknownSubcommand(potentialSubCmd, subCmdNames));
1582
1958
  logger.error("");
@@ -1620,13 +1996,15 @@ async function runCommandInternal(command, argv, options = {}) {
1620
1996
  const subContext = {
1621
1997
  commandPath: [...context.commandPath ?? [], parseResult.subCommand],
1622
1998
  rootName: context.rootName,
1623
- rootVersion: context.rootVersion
1999
+ rootVersion: context.rootVersion,
2000
+ globalExtracted: context.globalExtracted
1624
2001
  };
1625
2002
  collector?.stop();
1626
2003
  return runCommandInternal(subCmd, parseResult.remainingArgs, {
1627
2004
  ...options,
1628
2005
  _context: subContext,
1629
- _existingLogs: getCurrentLogs()
2006
+ _existingLogs: getCurrentLogs(),
2007
+ _parsedGlobalArgs: accumulatedGlobalArgs
1630
2008
  });
1631
2009
  }
1632
2010
  }
@@ -1658,12 +2036,39 @@ async function runCommandInternal(command, argv, options = {}) {
1658
2036
  };
1659
2037
  } else if (unknownKeysMode === "strip") for (const flag of parseResult.unknownFlags) logger.error(formatUnknownFlagWarning(flag, knownFlags));
1660
2038
  }
2039
+ let validatedGlobalArgs = {};
2040
+ if (options.globalArgs && options._globalExtracted) {
2041
+ for (const field of options._globalExtracted.fields) if (field.env && accumulatedGlobalArgs[field.name] === void 0) {
2042
+ const envNames = Array.isArray(field.env) ? field.env : [field.env];
2043
+ for (const envName of envNames) {
2044
+ const envValue = process.env[envName];
2045
+ if (envValue !== void 0) {
2046
+ accumulatedGlobalArgs[field.name] = envValue;
2047
+ break;
2048
+ }
2049
+ }
2050
+ }
2051
+ const globalValidation = validateArgs(accumulatedGlobalArgs, options.globalArgs);
2052
+ if (!globalValidation.success) {
2053
+ const errorMessage = formatValidationErrors$1(globalValidation.errors);
2054
+ logger.error(errorMessage);
2055
+ collector?.stop();
2056
+ return {
2057
+ success: false,
2058
+ error: new Error(errorMessage),
2059
+ exitCode: 1,
2060
+ logs: getCurrentLogs()
2061
+ };
2062
+ }
2063
+ validatedGlobalArgs = globalValidation.data;
2064
+ }
1661
2065
  if (!command.args) {
1662
2066
  collector?.stop();
1663
- return await executeLifecycle(command, {}, {
2067
+ return await executeLifecycle(command, validatedGlobalArgs, {
1664
2068
  handleSignals: options.handleSignals,
1665
2069
  captureLogs: options.captureLogs,
1666
- existingLogs: getCurrentLogs()
2070
+ existingLogs: getCurrentLogs(),
2071
+ globalCleanup: options._globalCleanup
1667
2072
  });
1668
2073
  }
1669
2074
  const validationResult = validateArgs(parseResult.rawArgs, command.args);
@@ -1677,11 +2082,16 @@ async function runCommandInternal(command, argv, options = {}) {
1677
2082
  logs: getCurrentLogs()
1678
2083
  };
1679
2084
  }
2085
+ const mergedArgs = {
2086
+ ...validatedGlobalArgs,
2087
+ ...validationResult.data
2088
+ };
1680
2089
  collector?.stop();
1681
- return await executeLifecycle(command, validationResult.data, {
2090
+ return await executeLifecycle(command, mergedArgs, {
1682
2091
  handleSignals: options.handleSignals,
1683
2092
  captureLogs: options.captureLogs,
1684
- existingLogs: getCurrentLogs()
2093
+ existingLogs: getCurrentLogs(),
2094
+ globalCleanup: options._globalCleanup
1685
2095
  });
1686
2096
  } catch (error) {
1687
2097
  const err = error instanceof Error ? error : new Error(String(error));
@@ -1695,6 +2105,23 @@ async function runCommandInternal(command, argv, options = {}) {
1695
2105
  };
1696
2106
  }
1697
2107
  }
2108
+ /**
2109
+ * Extract global fields from options.globalArgs and validate the schema upfront.
2110
+ * Rejects positional fields since global options must be flags.
2111
+ * Returns undefined when no globalArgs is provided.
2112
+ */
2113
+ function extractAndValidateGlobal(options) {
2114
+ if (!options.globalArgs) return void 0;
2115
+ const extracted = require_lazy.extractFields(options.globalArgs);
2116
+ if (!options.skipValidation) {
2117
+ validateDuplicateFields(extracted);
2118
+ validateDuplicateAliases(extracted);
2119
+ validateReservedAliases(extracted, true);
2120
+ const positionalNames = extracted.fields.filter((f) => f.positional).map((f) => f.name);
2121
+ if (positionalNames.length > 0) throw new Error(`Global options schema must not contain positional arguments. Found: ${positionalNames.join(", ")}`);
2122
+ }
2123
+ return extracted;
2124
+ }
1698
2125
 
1699
2126
  //#endregion
1700
2127
  Object.defineProperty(exports, 'DuplicateAliasError', {
@@ -1835,4 +2262,4 @@ Object.defineProperty(exports, 'validateReservedAliases', {
1835
2262
  return validateReservedAliases;
1836
2263
  }
1837
2264
  });
1838
- //# sourceMappingURL=runner-C4fSHJMe.cjs.map
2265
+ //# sourceMappingURL=runner-Cn6Oq4ZZ.cjs.map