@nmakarov/cli-toolkit 0.59.0 → 0.63.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
@@ -3503,13 +3503,15 @@ var CONNECTION_ERROR_CODES = /* @__PURE__ */ new Set([
3503
3503
  "57P02",
3504
3504
  "57P03"
3505
3505
  ]);
3506
- var CONNECTION_ERROR_MESSAGE_RE = /connection (terminated|ended|closed|destroyed|reset|refused|not open)|Connection terminated unexpectedly|Client has encountered a connection error|server closed the connection|Cannot use a pool after calling end|This socket has been ended|connect ECONNRESET|Timeout acquiring a connection/i;
3506
+ var CONNECTION_ERROR_MESSAGE_RE = /connection (terminated|ended|closed|destroyed|reset|refused|not open)|Connection terminated unexpectedly|Client has encountered a connection error|server closed the connection|Cannot use a pool after calling end|This socket has been ended|connect ECONNRESET/i;
3507
3507
  var Db = class _Db {
3508
3508
  static async init(context, options = {}) {
3509
3509
  const buildConfig = async () => {
3510
3510
  const defs = {
3511
3511
  dbName: "string",
3512
- dbProfile: "boolean default false"
3512
+ dbProfile: "boolean default false",
3513
+ /** Cap knex pool size (default 10). Size ≈ expected concurrency + headroom. */
3514
+ dbPoolMax: "number"
3513
3515
  };
3514
3516
  const discovered = context?.params?.getAllForModule?.("db", defs) ?? {};
3515
3517
  const merged = { ...discovered, ...options };
@@ -3568,12 +3570,23 @@ var Db = class _Db {
3568
3570
  context?.args?.env,
3569
3571
  merged.name
3570
3572
  );
3573
+ const poolMaxRaw = merged.dbPoolMax ?? merged.pool?.max;
3574
+ const poolMax = Number(poolMaxRaw);
3575
+ const pool = {
3576
+ ...KNEX_DEFAULTS.pool,
3577
+ ...merged.pool && typeof merged.pool === "object" ? merged.pool : {}
3578
+ };
3579
+ if (Number.isFinite(poolMax) && poolMax >= 1) {
3580
+ pool.max = Math.floor(poolMax);
3581
+ }
3571
3582
  return {
3572
3583
  ...KNEX_DEFAULTS,
3573
3584
  connectionString: dbConnectionString,
3574
3585
  name: displayName,
3575
3586
  profile: !!dbProfile,
3576
- logger: context.logger
3587
+ logger: context.logger,
3588
+ pool,
3589
+ ...merged.acquireConnectionTimeout != null ? { acquireConnectionTimeout: merged.acquireConnectionTimeout } : {}
3577
3590
  };
3578
3591
  };
3579
3592
  const config2 = context?.params?.runWithModuleAsync ? await context.params.runWithModuleAsync("db", buildConfig) : await buildConfig();
@@ -5026,6 +5039,9 @@ var Logger = class _Logger {
5026
5039
  const message = this.inspectChunks([operation, ...chunks]);
5027
5040
  this.out({ level: "response", message });
5028
5041
  }
5042
+ /**
5043
+ * @returns {boolean} true when the progress line was emitted (not throttled)
5044
+ */
5029
5045
  progress(message, opts) {
5030
5046
  const { prefix, count, total } = opts;
5031
5047
  const paddedTotal = String(total).length;
@@ -5059,7 +5075,9 @@ var Logger = class _Logger {
5059
5075
  if (this.options.progressThrottle && prefix) {
5060
5076
  this.lastProgressTimes[prefix] = Date.now();
5061
5077
  }
5078
+ return true;
5062
5079
  }
5080
+ return false;
5063
5081
  }
5064
5082
  shouldOutputProgress(prefix, count, total) {
5065
5083
  if (!this.options.progressThrottle) {
@@ -5655,10 +5673,47 @@ function isFreshOnline(proc, oldPid) {
5655
5673
  }
5656
5674
  return true;
5657
5675
  }
5676
+ async function deletePm2App(appName, options = {}) {
5677
+ const { dryRun = false, logger = console, waitTimeoutMs = 65e3, oldPid = null } = options;
5678
+ if (dryRun) {
5679
+ logger.info(`[dryRun] would pm2 delete ${appName}`);
5680
+ return;
5681
+ }
5682
+ const before = await getPm2Process(appName, { logger });
5683
+ if (!before) {
5684
+ logger.info(`pm2 ${appName}: not present (skip delete)`);
5685
+ return;
5686
+ }
5687
+ await runShell(`pm2 delete "${appName}"`, { logger });
5688
+ await waitPm2(
5689
+ appName,
5690
+ (proc) => {
5691
+ if (proc) return false;
5692
+ if (oldPid != null && isPidAlive(oldPid)) return false;
5693
+ return true;
5694
+ },
5695
+ { timeoutMs: waitTimeoutMs, logger, label: "deleted (process gone)" }
5696
+ );
5697
+ logger.info(`pm2 ${appName} deleted`);
5698
+ }
5699
+ async function startFromEcosystem(paths, options = {}) {
5700
+ const { dryRun = false, logger = console } = options;
5701
+ if (dryRun) {
5702
+ logger.info(`[dryRun] would pm2 start ${paths.ecosystem} --update-env`);
5703
+ return;
5704
+ }
5705
+ await runShell(
5706
+ `ENV=production NODE_ENV=production pm2 start "${paths.ecosystem}" --update-env`,
5707
+ { logger }
5708
+ );
5709
+ logger.info("pm2 started from ecosystem");
5710
+ }
5658
5711
  async function reloadPm2(paths, options = {}) {
5659
5712
  const { dryRun = false, logger = console, appName = null, waitTimeoutMs = 65e3 } = options;
5660
5713
  if (dryRun) {
5661
- logger.info(`[dryRun] would pm2 startOrReload ${paths.ecosystem} --update-env`);
5714
+ logger.info(
5715
+ `[dryRun] would pm2 delete${appName ? ` ${appName}` : ""} + start ${paths.ecosystem} --update-env`
5716
+ );
5662
5717
  return;
5663
5718
  }
5664
5719
  let oldPid = null;
@@ -5667,14 +5722,15 @@ async function reloadPm2(paths, options = {}) {
5667
5722
  const n = Number(before?.pid);
5668
5723
  if (Number.isFinite(n) && n > 0 && before?.pm2_env?.status === "online") {
5669
5724
  oldPid = n;
5670
- logger.info(`pm2 ${appName}: reloading (old pid=${oldPid})`);
5725
+ logger.info(`pm2 ${appName}: recreate (old pid=${oldPid})`);
5671
5726
  }
5727
+ await deletePm2App(appName, { dryRun, logger, waitTimeoutMs, oldPid });
5728
+ } else {
5729
+ logger.warn?.(
5730
+ "reloadPm2: appName missing; starting ecosystem without delete (pass appName so script/env always recreate cleanly)"
5731
+ );
5672
5732
  }
5673
- await runShell(
5674
- `ENV=production NODE_ENV=production pm2 startOrReload "${paths.ecosystem}" --update-env`,
5675
- { logger }
5676
- );
5677
- logger.info("pm2 reloaded");
5733
+ await startFromEcosystem(paths, { dryRun, logger });
5678
5734
  if (appName) {
5679
5735
  const proc = await waitPm2(
5680
5736
  appName,
@@ -5682,7 +5738,7 @@ async function reloadPm2(paths, options = {}) {
5682
5738
  {
5683
5739
  timeoutMs: waitTimeoutMs,
5684
5740
  logger,
5685
- label: oldPid ? `online on new pid (old ${oldPid} gone)` : "online after reload"
5741
+ label: oldPid ? `online on new pid (old ${oldPid} gone)` : "online after recreate"
5686
5742
  }
5687
5743
  );
5688
5744
  logger.info(`pm2 ${appName} online pid=${proc?.pid ?? "?"}`);
@@ -5719,14 +5775,15 @@ async function stopPm2(appName, options = {}) {
5719
5775
  async function startPm2(paths, options = {}) {
5720
5776
  const { dryRun = false, logger = console, appName = null, waitTimeoutMs = 65e3 } = options;
5721
5777
  if (dryRun) {
5722
- logger.info(`[dryRun] would pm2 start ${paths.ecosystem} --update-env`);
5778
+ logger.info(
5779
+ `[dryRun] would pm2 delete${appName ? ` ${appName}` : ""} + start ${paths.ecosystem} --update-env`
5780
+ );
5723
5781
  return;
5724
5782
  }
5725
- await runShell(
5726
- `ENV=production NODE_ENV=production pm2 start "${paths.ecosystem}" --update-env`,
5727
- { logger }
5728
- );
5729
- logger.info("pm2 started");
5783
+ if (appName) {
5784
+ await deletePm2App(appName, { dryRun, logger, waitTimeoutMs });
5785
+ }
5786
+ await startFromEcosystem(paths, { dryRun, logger });
5730
5787
  if (appName) {
5731
5788
  const proc = await waitPm2(
5732
5789
  appName,
@@ -5981,7 +6038,7 @@ function buildEnsureEnvScript() {
5981
6038
  return `/**
5982
6039
  * Written by cli-toolkit deploy (init-structure). Do not hand-edit.
5983
6040
  * Preloaded via pm2 node_args --require. Always set (do not only fill when unset):
5984
- * \`pm2 startOrReload --update-env\` can inject a non-production ENV from the
6041
+ * \`pm2 start \u2026 --update-env\` can inject a non-production ENV from the
5985
6042
  * deploy shell / prior dump, and a conditional assign would leave it in place.
5986
6043
  */
5987
6044
  process.env.ENV = "production";
@@ -6347,7 +6404,9 @@ async function rollbackService(service, options = {}) {
6347
6404
  logger.info(`rollback target: v${buildInfo.version} release=${buildInfo.release ?? rollbackTarget.name}`);
6348
6405
  }
6349
6406
  await activateRelease(rollbackTarget.path, paths, { dryRun, logger });
6350
- await reloadPm2(paths, { dryRun, logger });
6407
+ const appName = service.pm2?.appName ?? null;
6408
+ const waitTimeoutMs = 65e3;
6409
+ await reloadPm2(paths, { dryRun, logger, appName, waitTimeoutMs });
6351
6410
  const summary = `rollback ${activeName} \u2192 ${rollbackTarget.name} dryRun=${dryRun}`;
6352
6411
  if (!dryRun) await appendDeployLog(paths.deployLog, summary);
6353
6412
  return { from: activeName, to: rollbackTarget.name, path: rollbackTarget.path };
@@ -6858,6 +6917,34 @@ async function updateTaskProgress(context, tasksTable, taskId, progress) {
6858
6917
  progress: typeof progress === "string" ? progress : JSON.stringify(progress)
6859
6918
  });
6860
6919
  }
6920
+ function createTaskProgressReporter(context, tasksTable, taskId) {
6921
+ let pump = null;
6922
+ let pending = void 0;
6923
+ let hasPending = false;
6924
+ return (progress) => {
6925
+ pending = progress;
6926
+ hasPending = true;
6927
+ if (pump) return pump;
6928
+ pump = (async () => {
6929
+ try {
6930
+ while (hasPending) {
6931
+ hasPending = false;
6932
+ const value = pending;
6933
+ try {
6934
+ await updateTaskProgress(context, tasksTable, taskId, value);
6935
+ } catch (err) {
6936
+ context.logger?.warn?.(
6937
+ `[tasks] progress update failed for ${taskId}: ${err?.message ?? err}`
6938
+ );
6939
+ }
6940
+ }
6941
+ } finally {
6942
+ pump = null;
6943
+ }
6944
+ })();
6945
+ return pump;
6946
+ };
6947
+ }
6861
6948
 
6862
6949
  // src/tasks/servicesRegistry.js
6863
6950
  function getDb2(context) {
@@ -8709,7 +8796,8 @@ async function executeClaimedTask(context, tasksTable, historyTable, row, regist
8709
8796
  try {
8710
8797
  taskInstance = new TaskClass(context, row);
8711
8798
  runningTaskInstances.set(row.id, taskInstance);
8712
- const runResult = await taskInstance.run((progress) => updateTaskProgress(context, tasksTable, row.id, progress));
8799
+ const reportProgress = createTaskProgressReporter(context, tasksTable, row.id);
8800
+ const runResult = await taskInstance.run(reportProgress);
8713
8801
  success = !!runResult?.success;
8714
8802
  results = runResult?.results ?? null;
8715
8803
  } catch (error) {
@@ -9256,6 +9344,7 @@ export {
9256
9344
  controlLaneTaskNames,
9257
9345
  convertPattern,
9258
9346
  createRelease,
9347
+ createTaskProgressReporter,
9259
9348
  defaultFileSynopsisFunction,
9260
9349
  defaultTasksRegistry,
9261
9350
  defaultVersionSynopsisFunction,