@nmakarov/cli-toolkit 0.25.0 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1742,46 +1742,83 @@ var Params = class _Params {
1742
1742
  paramGetters = [];
1743
1743
  trackedParams = [];
1744
1744
  _currentModule = "script";
1745
- /** Resolved early in constructor so cleanup does not read params lazily */
1746
- _showUsedParams = false;
1745
+ /**
1746
+ * Resolved early in constructor so cleanup does not read params lazily.
1747
+ * One of: false (off) | "end" (print at exit) | "top" (print after init,
1748
+ * via context.showUsedParamsIfNeeded()).
1749
+ */
1750
+ _showUsedParamsMode = false;
1751
+ /** Guard so the dump prints at most once (top OR end, never both). */
1752
+ _usedParamsPrinted = false;
1747
1753
  constructor(context, options = {}) {
1748
1754
  this.context = context;
1749
1755
  this.args = context.args;
1750
1756
  if (Object.keys(options).length > 0) {
1751
1757
  this.configure(options);
1752
1758
  }
1753
- this._showUsedParams = this.get("showUsedParams", "boolean default false");
1759
+ this._resolveShowUsedParams();
1754
1760
  if (context && typeof context.registerCleanup === "function") {
1755
1761
  context.registerCleanup((ctx) => {
1756
- if (!ctx.params.getShowUsedParams()) return;
1757
- const byModule = ctx.params.getFiguredByModule();
1758
- const modules = Object.keys(byModule).sort();
1759
- if (modules.length === 0) return;
1760
- const logger = ctx.logger;
1761
- logger.debug("[Params]: list of used params:");
1762
- if (typeof logger.highlight !== "function") {
1763
- for (const mod of modules) {
1764
- logger.debug(` [${mod}]`);
1765
- for (const [key, entry] of Object.entries(byModule[mod])) {
1766
- logger.debug(` ${key}: ${JSON.stringify(entry.value)} (${entry.source})`);
1767
- }
1768
- }
1769
- return;
1770
- }
1771
- for (const mod of modules) {
1772
- logger.debug(` [${mod}]`);
1773
- for (const [key, entry] of Object.entries(byModule[mod])) {
1774
- const valueStr = JSON.stringify(entry.value);
1775
- const display = entry.source === "default" ? valueStr : logger.highlight(valueStr);
1776
- logger.debug(` ${key}: ${display} (${entry.source})`);
1777
- }
1778
- }
1762
+ if (!ctx.params.getShowUsedParamsMode()) return;
1763
+ ctx.params.printUsedParams(ctx.logger);
1779
1764
  });
1780
1765
  }
1781
1766
  }
1782
- /** Whether --showUsedParams was requested (resolved in constructor). */
1767
+ /**
1768
+ * Resolve the --showUsedParams mode. The flag is intentionally dual-typed:
1769
+ * (absent) / --no-showUsedParams / =false -> false (off)
1770
+ * --showUsedParams / =true -> "end" (print at exit)
1771
+ * --showUsedParams=top -> "top" (print after init)
1772
+ * Read raw (uncoerced) from args so the string "top" isn't forced to a
1773
+ * boolean, then track it under the "script" module for the dump itself.
1774
+ */
1775
+ _resolveShowUsedParams() {
1776
+ const raw = this.args.get("showUsedParams");
1777
+ const source = this.args.getSource?.("showUsedParams") ?? "default";
1778
+ let mode = false;
1779
+ if (raw === void 0 || raw === null) {
1780
+ mode = false;
1781
+ } else if (typeof raw === "string" && raw.trim().toLowerCase() === "top") {
1782
+ mode = "top";
1783
+ } else {
1784
+ const s = typeof raw === "string" ? raw.trim().toLowerCase() : raw;
1785
+ const falsey = s === false || s === "false" || s === "0" || s === "no" || s === "off";
1786
+ mode = falsey ? false : "end";
1787
+ }
1788
+ this._showUsedParamsMode = mode;
1789
+ this.trackParam("showUsedParams", "string", mode, raw === void 0 ? "default" : source, "script");
1790
+ return mode;
1791
+ }
1792
+ /** Whether --showUsedParams was requested in any mode (truthy = on). */
1783
1793
  getShowUsedParams() {
1784
- return this._showUsedParams;
1794
+ return this._showUsedParamsMode !== false;
1795
+ }
1796
+ /** Resolved mode: false | "end" | "top". */
1797
+ getShowUsedParamsMode() {
1798
+ return this._showUsedParamsMode;
1799
+ }
1800
+ /**
1801
+ * Print the module-grouped list of figured params (the --showUsedParams
1802
+ * dump). Idempotent: only the first call prints, so callers can invoke it
1803
+ * at the top (long-running services) without double-printing at exit.
1804
+ */
1805
+ printUsedParams(logger) {
1806
+ if (this._usedParamsPrinted) return;
1807
+ const byModule = this.getFiguredByModule();
1808
+ const modules = Object.keys(byModule).sort();
1809
+ if (modules.length === 0) return;
1810
+ this._usedParamsPrinted = true;
1811
+ logger = logger ?? this.context?.logger ?? console;
1812
+ const hasHighlight = typeof logger.highlight === "function";
1813
+ logger.debug("[Params]: list of used params:");
1814
+ for (const mod of modules) {
1815
+ logger.debug(` [${mod}]`);
1816
+ for (const [key, entry] of Object.entries(byModule[mod])) {
1817
+ const valueStr = JSON.stringify(entry.value);
1818
+ const display = hasHighlight && entry.source !== "default" ? logger.highlight(valueStr) : valueStr;
1819
+ logger.debug(` ${key}: ${display} (${entry.source})`);
1820
+ }
1821
+ }
1785
1822
  }
1786
1823
  /**
1787
1824
  * Configure parameters
@@ -2466,7 +2503,16 @@ function setup(opts = {}) {
2466
2503
  emitter: partialContext.emitter,
2467
2504
  isStop: partialContext.isStop,
2468
2505
  cleanupFunctions: partialContext.cleanupFunctions,
2469
- registerCleanup: partialContext.registerCleanup
2506
+ registerCleanup: partialContext.registerCleanup,
2507
+ // For long-running scripts (servers): with --showUsedParams=top, print
2508
+ // the used-params list now (after the script has initialized all its
2509
+ // own components), instead of at exit. No-op for the default mode,
2510
+ // which prints at exit via the cleanup registered by Params.
2511
+ showUsedParamsIfNeeded: () => {
2512
+ if (params.getShowUsedParamsMode?.() === "top") {
2513
+ params.printUsedParams(logger);
2514
+ }
2515
+ }
2470
2516
  };
2471
2517
  logger.debug("[setup] completed successfully");
2472
2518
  return context;
@@ -2533,15 +2579,19 @@ async function init(flow2, opts = {}) {
2533
2579
  process.exit(0);
2534
2580
  }
2535
2581
  let sigintCount = 0;
2582
+ let firstSigintAt = 0;
2536
2583
  process.on("SIGINT", async () => {
2537
2584
  if (!context) return;
2538
- sigintCount += 1;
2539
- if (sigintCount === 1) {
2585
+ const now = Date.now();
2586
+ if (sigintCount === 0) {
2587
+ sigintCount = 1;
2588
+ firstSigintAt = now;
2540
2589
  stop = true;
2541
2590
  context.logger.info(`>> emitting stop with allowance ${stopAllowance}`);
2542
2591
  context.emitter.emit("stop", stopAllowance);
2543
2592
  return;
2544
2593
  }
2594
+ if (now - firstSigintAt < 250) return;
2545
2595
  context.logger.warn("[process] second SIGINT: running cleanup then exit");
2546
2596
  await runRegisteredCleanups(context);
2547
2597
  process.exit(2);