@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.
package/dist/init.cjs CHANGED
@@ -1756,46 +1756,83 @@ var Params = class _Params {
1756
1756
  paramGetters = [];
1757
1757
  trackedParams = [];
1758
1758
  _currentModule = "script";
1759
- /** Resolved early in constructor so cleanup does not read params lazily */
1760
- _showUsedParams = false;
1759
+ /**
1760
+ * Resolved early in constructor so cleanup does not read params lazily.
1761
+ * One of: false (off) | "end" (print at exit) | "top" (print after init,
1762
+ * via context.showUsedParamsIfNeeded()).
1763
+ */
1764
+ _showUsedParamsMode = false;
1765
+ /** Guard so the dump prints at most once (top OR end, never both). */
1766
+ _usedParamsPrinted = false;
1761
1767
  constructor(context, options = {}) {
1762
1768
  this.context = context;
1763
1769
  this.args = context.args;
1764
1770
  if (Object.keys(options).length > 0) {
1765
1771
  this.configure(options);
1766
1772
  }
1767
- this._showUsedParams = this.get("showUsedParams", "boolean default false");
1773
+ this._resolveShowUsedParams();
1768
1774
  if (context && typeof context.registerCleanup === "function") {
1769
1775
  context.registerCleanup((ctx) => {
1770
- if (!ctx.params.getShowUsedParams()) return;
1771
- const byModule = ctx.params.getFiguredByModule();
1772
- const modules = Object.keys(byModule).sort();
1773
- if (modules.length === 0) return;
1774
- const logger = ctx.logger;
1775
- logger.debug("[Params]: list of used params:");
1776
- if (typeof logger.highlight !== "function") {
1777
- for (const mod of modules) {
1778
- logger.debug(` [${mod}]`);
1779
- for (const [key, entry] of Object.entries(byModule[mod])) {
1780
- logger.debug(` ${key}: ${JSON.stringify(entry.value)} (${entry.source})`);
1781
- }
1782
- }
1783
- return;
1784
- }
1785
- for (const mod of modules) {
1786
- logger.debug(` [${mod}]`);
1787
- for (const [key, entry] of Object.entries(byModule[mod])) {
1788
- const valueStr = JSON.stringify(entry.value);
1789
- const display = entry.source === "default" ? valueStr : logger.highlight(valueStr);
1790
- logger.debug(` ${key}: ${display} (${entry.source})`);
1791
- }
1792
- }
1776
+ if (!ctx.params.getShowUsedParamsMode()) return;
1777
+ ctx.params.printUsedParams(ctx.logger);
1793
1778
  });
1794
1779
  }
1795
1780
  }
1796
- /** Whether --showUsedParams was requested (resolved in constructor). */
1781
+ /**
1782
+ * Resolve the --showUsedParams mode. The flag is intentionally dual-typed:
1783
+ * (absent) / --no-showUsedParams / =false -> false (off)
1784
+ * --showUsedParams / =true -> "end" (print at exit)
1785
+ * --showUsedParams=top -> "top" (print after init)
1786
+ * Read raw (uncoerced) from args so the string "top" isn't forced to a
1787
+ * boolean, then track it under the "script" module for the dump itself.
1788
+ */
1789
+ _resolveShowUsedParams() {
1790
+ const raw = this.args.get("showUsedParams");
1791
+ const source = this.args.getSource?.("showUsedParams") ?? "default";
1792
+ let mode = false;
1793
+ if (raw === void 0 || raw === null) {
1794
+ mode = false;
1795
+ } else if (typeof raw === "string" && raw.trim().toLowerCase() === "top") {
1796
+ mode = "top";
1797
+ } else {
1798
+ const s = typeof raw === "string" ? raw.trim().toLowerCase() : raw;
1799
+ const falsey = s === false || s === "false" || s === "0" || s === "no" || s === "off";
1800
+ mode = falsey ? false : "end";
1801
+ }
1802
+ this._showUsedParamsMode = mode;
1803
+ this.trackParam("showUsedParams", "string", mode, raw === void 0 ? "default" : source, "script");
1804
+ return mode;
1805
+ }
1806
+ /** Whether --showUsedParams was requested in any mode (truthy = on). */
1797
1807
  getShowUsedParams() {
1798
- return this._showUsedParams;
1808
+ return this._showUsedParamsMode !== false;
1809
+ }
1810
+ /** Resolved mode: false | "end" | "top". */
1811
+ getShowUsedParamsMode() {
1812
+ return this._showUsedParamsMode;
1813
+ }
1814
+ /**
1815
+ * Print the module-grouped list of figured params (the --showUsedParams
1816
+ * dump). Idempotent: only the first call prints, so callers can invoke it
1817
+ * at the top (long-running services) without double-printing at exit.
1818
+ */
1819
+ printUsedParams(logger) {
1820
+ if (this._usedParamsPrinted) return;
1821
+ const byModule = this.getFiguredByModule();
1822
+ const modules = Object.keys(byModule).sort();
1823
+ if (modules.length === 0) return;
1824
+ this._usedParamsPrinted = true;
1825
+ logger = logger ?? this.context?.logger ?? console;
1826
+ const hasHighlight = typeof logger.highlight === "function";
1827
+ logger.debug("[Params]: list of used params:");
1828
+ for (const mod of modules) {
1829
+ logger.debug(` [${mod}]`);
1830
+ for (const [key, entry] of Object.entries(byModule[mod])) {
1831
+ const valueStr = JSON.stringify(entry.value);
1832
+ const display = hasHighlight && entry.source !== "default" ? logger.highlight(valueStr) : valueStr;
1833
+ logger.debug(` ${key}: ${display} (${entry.source})`);
1834
+ }
1835
+ }
1799
1836
  }
1800
1837
  /**
1801
1838
  * Configure parameters
@@ -2480,7 +2517,16 @@ function setup(opts = {}) {
2480
2517
  emitter: partialContext.emitter,
2481
2518
  isStop: partialContext.isStop,
2482
2519
  cleanupFunctions: partialContext.cleanupFunctions,
2483
- registerCleanup: partialContext.registerCleanup
2520
+ registerCleanup: partialContext.registerCleanup,
2521
+ // For long-running scripts (servers): with --showUsedParams=top, print
2522
+ // the used-params list now (after the script has initialized all its
2523
+ // own components), instead of at exit. No-op for the default mode,
2524
+ // which prints at exit via the cleanup registered by Params.
2525
+ showUsedParamsIfNeeded: () => {
2526
+ if (params.getShowUsedParamsMode?.() === "top") {
2527
+ params.printUsedParams(logger);
2528
+ }
2529
+ }
2484
2530
  };
2485
2531
  logger.debug("[setup] completed successfully");
2486
2532
  return context;
@@ -2547,15 +2593,19 @@ async function init(flow, opts = {}) {
2547
2593
  process.exit(0);
2548
2594
  }
2549
2595
  let sigintCount = 0;
2596
+ let firstSigintAt = 0;
2550
2597
  process.on("SIGINT", async () => {
2551
2598
  if (!context) return;
2552
- sigintCount += 1;
2553
- if (sigintCount === 1) {
2599
+ const now = Date.now();
2600
+ if (sigintCount === 0) {
2601
+ sigintCount = 1;
2602
+ firstSigintAt = now;
2554
2603
  stop = true;
2555
2604
  context.logger.info(`>> emitting stop with allowance ${stopAllowance}`);
2556
2605
  context.emitter.emit("stop", stopAllowance);
2557
2606
  return;
2558
2607
  }
2608
+ if (now - firstSigintAt < 250) return;
2559
2609
  context.logger.warn("[process] second SIGINT: running cleanup then exit");
2560
2610
  await runRegisteredCleanups(context);
2561
2611
  process.exit(2);