@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/index.js CHANGED
@@ -1689,46 +1689,83 @@ var Params = class _Params {
1689
1689
  paramGetters = [];
1690
1690
  trackedParams = [];
1691
1691
  _currentModule = "script";
1692
- /** Resolved early in constructor so cleanup does not read params lazily */
1693
- _showUsedParams = false;
1692
+ /**
1693
+ * Resolved early in constructor so cleanup does not read params lazily.
1694
+ * One of: false (off) | "end" (print at exit) | "top" (print after init,
1695
+ * via context.showUsedParamsIfNeeded()).
1696
+ */
1697
+ _showUsedParamsMode = false;
1698
+ /** Guard so the dump prints at most once (top OR end, never both). */
1699
+ _usedParamsPrinted = false;
1694
1700
  constructor(context, options = {}) {
1695
1701
  this.context = context;
1696
1702
  this.args = context.args;
1697
1703
  if (Object.keys(options).length > 0) {
1698
1704
  this.configure(options);
1699
1705
  }
1700
- this._showUsedParams = this.get("showUsedParams", "boolean default false");
1706
+ this._resolveShowUsedParams();
1701
1707
  if (context && typeof context.registerCleanup === "function") {
1702
1708
  context.registerCleanup((ctx) => {
1703
- if (!ctx.params.getShowUsedParams()) return;
1704
- const byModule = ctx.params.getFiguredByModule();
1705
- const modules = Object.keys(byModule).sort();
1706
- if (modules.length === 0) return;
1707
- const logger = ctx.logger;
1708
- logger.debug("[Params]: list of used params:");
1709
- if (typeof logger.highlight !== "function") {
1710
- for (const mod of modules) {
1711
- logger.debug(` [${mod}]`);
1712
- for (const [key, entry] of Object.entries(byModule[mod])) {
1713
- logger.debug(` ${key}: ${JSON.stringify(entry.value)} (${entry.source})`);
1714
- }
1715
- }
1716
- return;
1717
- }
1718
- for (const mod of modules) {
1719
- logger.debug(` [${mod}]`);
1720
- for (const [key, entry] of Object.entries(byModule[mod])) {
1721
- const valueStr = JSON.stringify(entry.value);
1722
- const display = entry.source === "default" ? valueStr : logger.highlight(valueStr);
1723
- logger.debug(` ${key}: ${display} (${entry.source})`);
1724
- }
1725
- }
1709
+ if (!ctx.params.getShowUsedParamsMode()) return;
1710
+ ctx.params.printUsedParams(ctx.logger);
1726
1711
  });
1727
1712
  }
1728
1713
  }
1729
- /** Whether --showUsedParams was requested (resolved in constructor). */
1714
+ /**
1715
+ * Resolve the --showUsedParams mode. The flag is intentionally dual-typed:
1716
+ * (absent) / --no-showUsedParams / =false -> false (off)
1717
+ * --showUsedParams / =true -> "end" (print at exit)
1718
+ * --showUsedParams=top -> "top" (print after init)
1719
+ * Read raw (uncoerced) from args so the string "top" isn't forced to a
1720
+ * boolean, then track it under the "script" module for the dump itself.
1721
+ */
1722
+ _resolveShowUsedParams() {
1723
+ const raw = this.args.get("showUsedParams");
1724
+ const source = this.args.getSource?.("showUsedParams") ?? "default";
1725
+ let mode = false;
1726
+ if (raw === void 0 || raw === null) {
1727
+ mode = false;
1728
+ } else if (typeof raw === "string" && raw.trim().toLowerCase() === "top") {
1729
+ mode = "top";
1730
+ } else {
1731
+ const s = typeof raw === "string" ? raw.trim().toLowerCase() : raw;
1732
+ const falsey = s === false || s === "false" || s === "0" || s === "no" || s === "off";
1733
+ mode = falsey ? false : "end";
1734
+ }
1735
+ this._showUsedParamsMode = mode;
1736
+ this.trackParam("showUsedParams", "string", mode, raw === void 0 ? "default" : source, "script");
1737
+ return mode;
1738
+ }
1739
+ /** Whether --showUsedParams was requested in any mode (truthy = on). */
1730
1740
  getShowUsedParams() {
1731
- return this._showUsedParams;
1741
+ return this._showUsedParamsMode !== false;
1742
+ }
1743
+ /** Resolved mode: false | "end" | "top". */
1744
+ getShowUsedParamsMode() {
1745
+ return this._showUsedParamsMode;
1746
+ }
1747
+ /**
1748
+ * Print the module-grouped list of figured params (the --showUsedParams
1749
+ * dump). Idempotent: only the first call prints, so callers can invoke it
1750
+ * at the top (long-running services) without double-printing at exit.
1751
+ */
1752
+ printUsedParams(logger) {
1753
+ if (this._usedParamsPrinted) return;
1754
+ const byModule = this.getFiguredByModule();
1755
+ const modules = Object.keys(byModule).sort();
1756
+ if (modules.length === 0) return;
1757
+ this._usedParamsPrinted = true;
1758
+ logger = logger ?? this.context?.logger ?? console;
1759
+ const hasHighlight = typeof logger.highlight === "function";
1760
+ logger.debug("[Params]: list of used params:");
1761
+ for (const mod of modules) {
1762
+ logger.debug(` [${mod}]`);
1763
+ for (const [key, entry] of Object.entries(byModule[mod])) {
1764
+ const valueStr = JSON.stringify(entry.value);
1765
+ const display = hasHighlight && entry.source !== "default" ? logger.highlight(valueStr) : valueStr;
1766
+ logger.debug(` ${key}: ${display} (${entry.source})`);
1767
+ }
1768
+ }
1732
1769
  }
1733
1770
  /**
1734
1771
  * Configure parameters
@@ -1996,6 +2033,18 @@ var Params = class _Params {
1996
2033
  this._currentModule = prev;
1997
2034
  }
1998
2035
  }
2036
+ /**
2037
+ * Async variant of {@link runWithModule} for modules that await params.get().
2038
+ */
2039
+ async runWithModuleAsync(moduleName, fn) {
2040
+ const prev = this._currentModule;
2041
+ this._currentModule = moduleName;
2042
+ try {
2043
+ return await fn();
2044
+ } finally {
2045
+ this._currentModule = prev;
2046
+ }
2047
+ }
1999
2048
  /**
2000
2049
  * Infer module name from call stack: first caller outside params/index gives path like .../src/<moduleName>/...
2001
2050
  */
@@ -3259,38 +3308,71 @@ var KNEX_DEFAULTS = {
3259
3308
  };
3260
3309
  var Db = class {
3261
3310
  static async init(context, options = {}) {
3262
- const defs = {
3263
- dbName: "string",
3264
- dbConnectionString: "string",
3265
- dbProfile: "boolean default false"
3266
- };
3267
- const discovered = context?.params?.getAllForModule?.("db", defs) ?? {};
3268
- const merged = { ...discovered, ...options };
3269
- let { dbName, dbConnectionString } = merged;
3270
- const { dbProfile } = merged;
3271
- if (!dbName && !dbConnectionString) {
3272
- dbName = "local";
3273
- }
3274
- if (dbName && /^(postgresql|mysql):\/\//.test(dbName)) {
3275
- dbConnectionString = dbName;
3276
- dbName = void 0;
3277
- }
3278
- if (dbName && !dbConnectionString) {
3279
- const paramName = `dbConnectionString${capitalizeFirstLetter(dbName)}`;
3280
- dbConnectionString = await context.params.get(paramName, "string");
3311
+ const buildConfig = async () => {
3312
+ const defs = {
3313
+ dbName: "string",
3314
+ dbProfile: "boolean default false"
3315
+ };
3316
+ const discovered = context?.params?.getAllForModule?.("db", defs) ?? {};
3317
+ const merged = { ...discovered, ...options };
3318
+ let { dbName, dbProfile } = merged;
3319
+ let dbConnectionString = options.dbConnectionString ?? options.connectionString;
3320
+ let connectionParam = dbConnectionString ? "options" : null;
3281
3321
  if (!dbConnectionString) {
3282
- throw new ParamError(
3283
- `Db: cannot find dbConnectionString for dbName="${dbName}" (looked for param "${paramName}")`
3284
- );
3322
+ const src = context?.args?.getSource?.("dbConnectionString");
3323
+ if (src === "cli" || src === "overrides" || src === "config") {
3324
+ dbConnectionString = await context.params.get("dbConnectionString", "string");
3325
+ connectionParam = "dbConnectionString";
3326
+ }
3285
3327
  }
3286
- }
3287
- const config2 = {
3288
- ...KNEX_DEFAULTS,
3289
- connectionString: dbConnectionString,
3290
- name: dbName || merged.name || "default",
3291
- profile: !!dbProfile,
3292
- logger: context.logger
3328
+ if (!dbConnectionString && dbName && /^(postgresql|mysql):\/\//.test(dbName)) {
3329
+ dbConnectionString = dbName;
3330
+ dbName = void 0;
3331
+ }
3332
+ if (!dbConnectionString && dbName) {
3333
+ const paramName = `dbConnectionString${capitalizeFirstLetter(dbName)}`;
3334
+ dbConnectionString = await context.params.get(paramName, "string");
3335
+ connectionParam = paramName;
3336
+ if (!dbConnectionString) {
3337
+ throw new ParamError(
3338
+ `Db: cannot find dbConnectionString for dbName="${dbName}" (looked for param "${paramName}")`
3339
+ );
3340
+ }
3341
+ }
3342
+ if (!dbConnectionString) {
3343
+ dbConnectionString = await context.params.get("dbConnectionString", "string");
3344
+ if (dbConnectionString) {
3345
+ connectionParam = "dbConnectionString";
3346
+ }
3347
+ }
3348
+ if (!dbConnectionString) {
3349
+ if (!dbName) {
3350
+ dbName = "local";
3351
+ }
3352
+ const paramName = `dbConnectionString${capitalizeFirstLetter(dbName)}`;
3353
+ dbConnectionString = await context.params.get(paramName, "string");
3354
+ connectionParam = paramName;
3355
+ if (!dbConnectionString) {
3356
+ throw new ParamError(
3357
+ `Db: cannot find dbConnectionString for dbName="${dbName}" (looked for param "${paramName}")`
3358
+ );
3359
+ }
3360
+ }
3361
+ const displayName = resolveDbDisplayName(
3362
+ dbName,
3363
+ connectionParam,
3364
+ context?.args?.env,
3365
+ merged.name
3366
+ );
3367
+ return {
3368
+ ...KNEX_DEFAULTS,
3369
+ connectionString: dbConnectionString,
3370
+ name: displayName,
3371
+ profile: !!dbProfile,
3372
+ logger: context.logger
3373
+ };
3293
3374
  };
3375
+ const config2 = context?.params?.runWithModuleAsync ? await context.params.runWithModuleAsync("db", buildConfig) : await buildConfig();
3294
3376
  return dbConnect(context, config2);
3295
3377
  }
3296
3378
  constructor(config2) {
@@ -3307,7 +3389,6 @@ var Db = class {
3307
3389
  acquireConnectionTimeout: 1e4,
3308
3390
  ssl: { rejectUnauthorized: false },
3309
3391
  logger: console,
3310
- name: "default",
3311
3392
  ...config2
3312
3393
  };
3313
3394
  this.logger = this.config.logger;
@@ -3407,9 +3488,7 @@ var Db = class {
3407
3488
  await this.testConnection();
3408
3489
  }
3409
3490
  this.isConnected = true;
3410
- this.logger.debug?.(
3411
- `[Db] Connected to database "${this.config.name || this.config.connectionString}"`
3412
- );
3491
+ this.logger.debug?.(formatDbConnectMessage(this.config.name, this.config.connectionString));
3413
3492
  } catch (error) {
3414
3493
  if (error instanceof ParamError) {
3415
3494
  throw error;
@@ -3427,9 +3506,7 @@ var Db = class {
3427
3506
  this.knexInstance = null;
3428
3507
  this.isConnected = false;
3429
3508
  this.queriesLog = [];
3430
- this.logger.debug?.(
3431
- `[Db] Disconnected from database "${this.config.name || this.config.connectionString}"`
3432
- );
3509
+ this.logger.debug?.(formatDbDisconnectMessage(this.config.name, this.config.connectionString));
3433
3510
  } catch (error) {
3434
3511
  const errorMsg = this.getErrorMessage(error);
3435
3512
  this.logger.error?.(`[Db] Error disconnecting: ${errorMsg}`);
@@ -3558,15 +3635,74 @@ var Db = class {
3558
3635
  function capitalizeFirstLetter(str) {
3559
3636
  return str.charAt(0).toUpperCase() + str.slice(1);
3560
3637
  }
3638
+ function resolveDbDisplayName(dbName, connectionParam, argsEnv, mergedName) {
3639
+ if (dbName) {
3640
+ return dbName;
3641
+ }
3642
+ if (mergedName) {
3643
+ return mergedName;
3644
+ }
3645
+ if (connectionParam?.startsWith("dbConnectionString") && connectionParam.length > "dbConnectionString".length) {
3646
+ return connectionParam.slice("dbConnectionString".length).toLowerCase();
3647
+ }
3648
+ if (connectionParam === "dbConnectionString" && argsEnv) {
3649
+ return argsEnv;
3650
+ }
3651
+ return void 0;
3652
+ }
3653
+ function formatDbConnectMessage(name, connectionString) {
3654
+ const endpointSuffix = formatConnectionEndpointSuffix(connectionString);
3655
+ if (name) {
3656
+ return `[Db] Connected to database "${name}"${endpointSuffix}`;
3657
+ }
3658
+ return `[Db] Connected${endpointSuffix}`;
3659
+ }
3660
+ function formatDbDisconnectMessage(name, connectionString) {
3661
+ const endpointSuffix = formatConnectionEndpointSuffix(connectionString);
3662
+ if (name) {
3663
+ return `[Db] Disconnected from database "${name}"${endpointSuffix}`;
3664
+ }
3665
+ return `[Db] Disconnected${endpointSuffix}`;
3666
+ }
3667
+ function formatDbInstanceMessage(action, name) {
3668
+ if (name) {
3669
+ return `[Db] instance "${name}" ${action}`;
3670
+ }
3671
+ return `[Db] instance ${action}`;
3672
+ }
3673
+ function formatConnectionEndpointSuffix(connectionString) {
3674
+ const endpoint = formatConnectionEndpoint(connectionString);
3675
+ return endpoint ? ` (${endpoint})` : "";
3676
+ }
3677
+ function formatConnectionEndpoint(connectionString) {
3678
+ try {
3679
+ const url = new URL(connectionString);
3680
+ const host = url.hostname;
3681
+ if (!host) {
3682
+ return null;
3683
+ }
3684
+ let port = url.port;
3685
+ if (!port) {
3686
+ if (url.protocol === "postgresql:") {
3687
+ port = "5432";
3688
+ } else if (url.protocol === "mysql:") {
3689
+ port = "3306";
3690
+ }
3691
+ }
3692
+ return port ? `${host}:${port}` : host;
3693
+ } catch {
3694
+ return null;
3695
+ }
3696
+ }
3561
3697
  async function dbConnect(context, config2) {
3562
3698
  try {
3563
3699
  const db = new Db(config2);
3564
3700
  context.registerCleanup(async () => {
3565
3701
  await db.disconnect();
3566
- context.logger.debug?.(`[Db] instance "${config2.name}" disconnected`);
3702
+ context.logger.debug?.(formatDbInstanceMessage("disconnected", config2.name));
3567
3703
  });
3568
3704
  await db.connect();
3569
- context.logger.debug?.(`[Db] instance "${config2.name}" initialized`);
3705
+ context.logger.debug?.(formatDbInstanceMessage("initialized", config2.name));
3570
3706
  return db;
3571
3707
  } catch (error) {
3572
3708
  if (error instanceof ParamError) {
@@ -4176,7 +4312,16 @@ function setup(opts = {}) {
4176
4312
  emitter: partialContext.emitter,
4177
4313
  isStop: partialContext.isStop,
4178
4314
  cleanupFunctions: partialContext.cleanupFunctions,
4179
- registerCleanup: partialContext.registerCleanup
4315
+ registerCleanup: partialContext.registerCleanup,
4316
+ // For long-running scripts (servers): with --showUsedParams=top, print
4317
+ // the used-params list now (after the script has initialized all its
4318
+ // own components), instead of at exit. No-op for the default mode,
4319
+ // which prints at exit via the cleanup registered by Params.
4320
+ showUsedParamsIfNeeded: () => {
4321
+ if (params.getShowUsedParamsMode?.() === "top") {
4322
+ params.printUsedParams(logger);
4323
+ }
4324
+ }
4180
4325
  };
4181
4326
  logger.debug("[setup] completed successfully");
4182
4327
  return context;