@nmakarov/cli-toolkit 0.23.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.
package/dist/init.js CHANGED
@@ -1731,46 +1731,83 @@ var Params = class _Params {
1731
1731
  paramGetters = [];
1732
1732
  trackedParams = [];
1733
1733
  _currentModule = "script";
1734
- /** Resolved early in constructor so cleanup does not read params lazily */
1735
- _showUsedParams = false;
1734
+ /**
1735
+ * Resolved early in constructor so cleanup does not read params lazily.
1736
+ * One of: false (off) | "end" (print at exit) | "top" (print after init,
1737
+ * via context.showUsedParamsIfNeeded()).
1738
+ */
1739
+ _showUsedParamsMode = false;
1740
+ /** Guard so the dump prints at most once (top OR end, never both). */
1741
+ _usedParamsPrinted = false;
1736
1742
  constructor(context, options = {}) {
1737
1743
  this.context = context;
1738
1744
  this.args = context.args;
1739
1745
  if (Object.keys(options).length > 0) {
1740
1746
  this.configure(options);
1741
1747
  }
1742
- this._showUsedParams = this.get("showUsedParams", "boolean default false");
1748
+ this._resolveShowUsedParams();
1743
1749
  if (context && typeof context.registerCleanup === "function") {
1744
1750
  context.registerCleanup((ctx) => {
1745
- if (!ctx.params.getShowUsedParams()) return;
1746
- const byModule = ctx.params.getFiguredByModule();
1747
- const modules = Object.keys(byModule).sort();
1748
- if (modules.length === 0) return;
1749
- const logger = ctx.logger;
1750
- logger.debug("[Params]: list of used params:");
1751
- if (typeof logger.highlight !== "function") {
1752
- for (const mod of modules) {
1753
- logger.debug(` [${mod}]`);
1754
- for (const [key, entry] of Object.entries(byModule[mod])) {
1755
- logger.debug(` ${key}: ${JSON.stringify(entry.value)} (${entry.source})`);
1756
- }
1757
- }
1758
- return;
1759
- }
1760
- for (const mod of modules) {
1761
- logger.debug(` [${mod}]`);
1762
- for (const [key, entry] of Object.entries(byModule[mod])) {
1763
- const valueStr = JSON.stringify(entry.value);
1764
- const display = entry.source === "default" ? valueStr : logger.highlight(valueStr);
1765
- logger.debug(` ${key}: ${display} (${entry.source})`);
1766
- }
1767
- }
1751
+ if (!ctx.params.getShowUsedParamsMode()) return;
1752
+ ctx.params.printUsedParams(ctx.logger);
1768
1753
  });
1769
1754
  }
1770
1755
  }
1771
- /** Whether --showUsedParams was requested (resolved in constructor). */
1756
+ /**
1757
+ * Resolve the --showUsedParams mode. The flag is intentionally dual-typed:
1758
+ * (absent) / --no-showUsedParams / =false -> false (off)
1759
+ * --showUsedParams / =true -> "end" (print at exit)
1760
+ * --showUsedParams=top -> "top" (print after init)
1761
+ * Read raw (uncoerced) from args so the string "top" isn't forced to a
1762
+ * boolean, then track it under the "script" module for the dump itself.
1763
+ */
1764
+ _resolveShowUsedParams() {
1765
+ const raw = this.args.get("showUsedParams");
1766
+ const source = this.args.getSource?.("showUsedParams") ?? "default";
1767
+ let mode = false;
1768
+ if (raw === void 0 || raw === null) {
1769
+ mode = false;
1770
+ } else if (typeof raw === "string" && raw.trim().toLowerCase() === "top") {
1771
+ mode = "top";
1772
+ } else {
1773
+ const s = typeof raw === "string" ? raw.trim().toLowerCase() : raw;
1774
+ const falsey = s === false || s === "false" || s === "0" || s === "no" || s === "off";
1775
+ mode = falsey ? false : "end";
1776
+ }
1777
+ this._showUsedParamsMode = mode;
1778
+ this.trackParam("showUsedParams", "string", mode, raw === void 0 ? "default" : source, "script");
1779
+ return mode;
1780
+ }
1781
+ /** Whether --showUsedParams was requested in any mode (truthy = on). */
1772
1782
  getShowUsedParams() {
1773
- return this._showUsedParams;
1783
+ return this._showUsedParamsMode !== false;
1784
+ }
1785
+ /** Resolved mode: false | "end" | "top". */
1786
+ getShowUsedParamsMode() {
1787
+ return this._showUsedParamsMode;
1788
+ }
1789
+ /**
1790
+ * Print the module-grouped list of figured params (the --showUsedParams
1791
+ * dump). Idempotent: only the first call prints, so callers can invoke it
1792
+ * at the top (long-running services) without double-printing at exit.
1793
+ */
1794
+ printUsedParams(logger) {
1795
+ if (this._usedParamsPrinted) return;
1796
+ const byModule = this.getFiguredByModule();
1797
+ const modules = Object.keys(byModule).sort();
1798
+ if (modules.length === 0) return;
1799
+ this._usedParamsPrinted = true;
1800
+ logger = logger ?? this.context?.logger ?? console;
1801
+ const hasHighlight = typeof logger.highlight === "function";
1802
+ logger.debug("[Params]: list of used params:");
1803
+ for (const mod of modules) {
1804
+ logger.debug(` [${mod}]`);
1805
+ for (const [key, entry] of Object.entries(byModule[mod])) {
1806
+ const valueStr = JSON.stringify(entry.value);
1807
+ const display = hasHighlight && entry.source !== "default" ? logger.highlight(valueStr) : valueStr;
1808
+ logger.debug(` ${key}: ${display} (${entry.source})`);
1809
+ }
1810
+ }
1774
1811
  }
1775
1812
  /**
1776
1813
  * Configure parameters
@@ -2038,6 +2075,18 @@ var Params = class _Params {
2038
2075
  this._currentModule = prev;
2039
2076
  }
2040
2077
  }
2078
+ /**
2079
+ * Async variant of {@link runWithModule} for modules that await params.get().
2080
+ */
2081
+ async runWithModuleAsync(moduleName, fn) {
2082
+ const prev = this._currentModule;
2083
+ this._currentModule = moduleName;
2084
+ try {
2085
+ return await fn();
2086
+ } finally {
2087
+ this._currentModule = prev;
2088
+ }
2089
+ }
2041
2090
  /**
2042
2091
  * Infer module name from call stack: first caller outside params/index gives path like .../src/<moduleName>/...
2043
2092
  */
@@ -2443,7 +2492,16 @@ function setup(opts = {}) {
2443
2492
  emitter: partialContext.emitter,
2444
2493
  isStop: partialContext.isStop,
2445
2494
  cleanupFunctions: partialContext.cleanupFunctions,
2446
- registerCleanup: partialContext.registerCleanup
2495
+ registerCleanup: partialContext.registerCleanup,
2496
+ // For long-running scripts (servers): with --showUsedParams=top, print
2497
+ // the used-params list now (after the script has initialized all its
2498
+ // own components), instead of at exit. No-op for the default mode,
2499
+ // which prints at exit via the cleanup registered by Params.
2500
+ showUsedParamsIfNeeded: () => {
2501
+ if (params.getShowUsedParamsMode?.() === "top") {
2502
+ params.printUsedParams(logger);
2503
+ }
2504
+ }
2447
2505
  };
2448
2506
  logger.debug("[setup] completed successfully");
2449
2507
  return context;
@@ -2510,15 +2568,19 @@ async function init(flow, opts = {}) {
2510
2568
  process.exit(0);
2511
2569
  }
2512
2570
  let sigintCount = 0;
2571
+ let firstSigintAt = 0;
2513
2572
  process.on("SIGINT", async () => {
2514
2573
  if (!context) return;
2515
- sigintCount += 1;
2516
- if (sigintCount === 1) {
2574
+ const now = Date.now();
2575
+ if (sigintCount === 0) {
2576
+ sigintCount = 1;
2577
+ firstSigintAt = now;
2517
2578
  stop = true;
2518
2579
  context.logger.info(`>> emitting stop with allowance ${stopAllowance}`);
2519
2580
  context.emitter.emit("stop", stopAllowance);
2520
2581
  return;
2521
2582
  }
2583
+ if (now - firstSigintAt < 250) return;
2522
2584
  context.logger.warn("[process] second SIGINT: running cleanup then exit");
2523
2585
  await runRegisteredCleanups(context);
2524
2586
  process.exit(2);