@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.cjs CHANGED
@@ -1125,6 +1125,7 @@ __export(src_exports, {
1125
1125
  controlLaneTaskNames: () => controlLaneTaskNames,
1126
1126
  convertPattern: () => convertPattern,
1127
1127
  createRelease: () => createRelease,
1128
+ createTaskProgressReporter: () => createTaskProgressReporter,
1128
1129
  defaultFileSynopsisFunction: () => defaultFileSynopsisFunction,
1129
1130
  defaultTasksRegistry: () => defaultTasksRegistry,
1130
1131
  defaultVersionSynopsisFunction: () => defaultVersionSynopsisFunction,
@@ -3693,13 +3694,15 @@ var CONNECTION_ERROR_CODES = /* @__PURE__ */ new Set([
3693
3694
  "57P02",
3694
3695
  "57P03"
3695
3696
  ]);
3696
- 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;
3697
+ 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;
3697
3698
  var Db = class _Db {
3698
3699
  static async init(context, options = {}) {
3699
3700
  const buildConfig = async () => {
3700
3701
  const defs = {
3701
3702
  dbName: "string",
3702
- dbProfile: "boolean default false"
3703
+ dbProfile: "boolean default false",
3704
+ /** Cap knex pool size (default 10). Size ≈ expected concurrency + headroom. */
3705
+ dbPoolMax: "number"
3703
3706
  };
3704
3707
  const discovered = context?.params?.getAllForModule?.("db", defs) ?? {};
3705
3708
  const merged = { ...discovered, ...options };
@@ -3758,12 +3761,23 @@ var Db = class _Db {
3758
3761
  context?.args?.env,
3759
3762
  merged.name
3760
3763
  );
3764
+ const poolMaxRaw = merged.dbPoolMax ?? merged.pool?.max;
3765
+ const poolMax = Number(poolMaxRaw);
3766
+ const pool = {
3767
+ ...KNEX_DEFAULTS.pool,
3768
+ ...merged.pool && typeof merged.pool === "object" ? merged.pool : {}
3769
+ };
3770
+ if (Number.isFinite(poolMax) && poolMax >= 1) {
3771
+ pool.max = Math.floor(poolMax);
3772
+ }
3761
3773
  return {
3762
3774
  ...KNEX_DEFAULTS,
3763
3775
  connectionString: dbConnectionString,
3764
3776
  name: displayName,
3765
3777
  profile: !!dbProfile,
3766
- logger: context.logger
3778
+ logger: context.logger,
3779
+ pool,
3780
+ ...merged.acquireConnectionTimeout != null ? { acquireConnectionTimeout: merged.acquireConnectionTimeout } : {}
3767
3781
  };
3768
3782
  };
3769
3783
  const config2 = context?.params?.runWithModuleAsync ? await context.params.runWithModuleAsync("db", buildConfig) : await buildConfig();
@@ -5190,6 +5204,9 @@ var Logger = class _Logger {
5190
5204
  const message = this.inspectChunks([operation, ...chunks]);
5191
5205
  this.out({ level: "response", message });
5192
5206
  }
5207
+ /**
5208
+ * @returns {boolean} true when the progress line was emitted (not throttled)
5209
+ */
5193
5210
  progress(message, opts) {
5194
5211
  const { prefix, count, total } = opts;
5195
5212
  const paddedTotal = String(total).length;
@@ -5223,7 +5240,9 @@ var Logger = class _Logger {
5223
5240
  if (this.options.progressThrottle && prefix) {
5224
5241
  this.lastProgressTimes[prefix] = Date.now();
5225
5242
  }
5243
+ return true;
5226
5244
  }
5245
+ return false;
5227
5246
  }
5228
5247
  shouldOutputProgress(prefix, count, total) {
5229
5248
  if (!this.options.progressThrottle) {
@@ -5819,10 +5838,47 @@ function isFreshOnline(proc, oldPid) {
5819
5838
  }
5820
5839
  return true;
5821
5840
  }
5841
+ async function deletePm2App(appName, options = {}) {
5842
+ const { dryRun = false, logger = console, waitTimeoutMs = 65e3, oldPid = null } = options;
5843
+ if (dryRun) {
5844
+ logger.info(`[dryRun] would pm2 delete ${appName}`);
5845
+ return;
5846
+ }
5847
+ const before = await getPm2Process(appName, { logger });
5848
+ if (!before) {
5849
+ logger.info(`pm2 ${appName}: not present (skip delete)`);
5850
+ return;
5851
+ }
5852
+ await runShell(`pm2 delete "${appName}"`, { logger });
5853
+ await waitPm2(
5854
+ appName,
5855
+ (proc) => {
5856
+ if (proc) return false;
5857
+ if (oldPid != null && isPidAlive(oldPid)) return false;
5858
+ return true;
5859
+ },
5860
+ { timeoutMs: waitTimeoutMs, logger, label: "deleted (process gone)" }
5861
+ );
5862
+ logger.info(`pm2 ${appName} deleted`);
5863
+ }
5864
+ async function startFromEcosystem(paths, options = {}) {
5865
+ const { dryRun = false, logger = console } = options;
5866
+ if (dryRun) {
5867
+ logger.info(`[dryRun] would pm2 start ${paths.ecosystem} --update-env`);
5868
+ return;
5869
+ }
5870
+ await runShell(
5871
+ `ENV=production NODE_ENV=production pm2 start "${paths.ecosystem}" --update-env`,
5872
+ { logger }
5873
+ );
5874
+ logger.info("pm2 started from ecosystem");
5875
+ }
5822
5876
  async function reloadPm2(paths, options = {}) {
5823
5877
  const { dryRun = false, logger = console, appName = null, waitTimeoutMs = 65e3 } = options;
5824
5878
  if (dryRun) {
5825
- logger.info(`[dryRun] would pm2 startOrReload ${paths.ecosystem} --update-env`);
5879
+ logger.info(
5880
+ `[dryRun] would pm2 delete${appName ? ` ${appName}` : ""} + start ${paths.ecosystem} --update-env`
5881
+ );
5826
5882
  return;
5827
5883
  }
5828
5884
  let oldPid = null;
@@ -5831,14 +5887,15 @@ async function reloadPm2(paths, options = {}) {
5831
5887
  const n = Number(before?.pid);
5832
5888
  if (Number.isFinite(n) && n > 0 && before?.pm2_env?.status === "online") {
5833
5889
  oldPid = n;
5834
- logger.info(`pm2 ${appName}: reloading (old pid=${oldPid})`);
5890
+ logger.info(`pm2 ${appName}: recreate (old pid=${oldPid})`);
5835
5891
  }
5892
+ await deletePm2App(appName, { dryRun, logger, waitTimeoutMs, oldPid });
5893
+ } else {
5894
+ logger.warn?.(
5895
+ "reloadPm2: appName missing; starting ecosystem without delete (pass appName so script/env always recreate cleanly)"
5896
+ );
5836
5897
  }
5837
- await runShell(
5838
- `ENV=production NODE_ENV=production pm2 startOrReload "${paths.ecosystem}" --update-env`,
5839
- { logger }
5840
- );
5841
- logger.info("pm2 reloaded");
5898
+ await startFromEcosystem(paths, { dryRun, logger });
5842
5899
  if (appName) {
5843
5900
  const proc = await waitPm2(
5844
5901
  appName,
@@ -5846,7 +5903,7 @@ async function reloadPm2(paths, options = {}) {
5846
5903
  {
5847
5904
  timeoutMs: waitTimeoutMs,
5848
5905
  logger,
5849
- label: oldPid ? `online on new pid (old ${oldPid} gone)` : "online after reload"
5906
+ label: oldPid ? `online on new pid (old ${oldPid} gone)` : "online after recreate"
5850
5907
  }
5851
5908
  );
5852
5909
  logger.info(`pm2 ${appName} online pid=${proc?.pid ?? "?"}`);
@@ -5883,14 +5940,15 @@ async function stopPm2(appName, options = {}) {
5883
5940
  async function startPm2(paths, options = {}) {
5884
5941
  const { dryRun = false, logger = console, appName = null, waitTimeoutMs = 65e3 } = options;
5885
5942
  if (dryRun) {
5886
- logger.info(`[dryRun] would pm2 start ${paths.ecosystem} --update-env`);
5943
+ logger.info(
5944
+ `[dryRun] would pm2 delete${appName ? ` ${appName}` : ""} + start ${paths.ecosystem} --update-env`
5945
+ );
5887
5946
  return;
5888
5947
  }
5889
- await runShell(
5890
- `ENV=production NODE_ENV=production pm2 start "${paths.ecosystem}" --update-env`,
5891
- { logger }
5892
- );
5893
- logger.info("pm2 started");
5948
+ if (appName) {
5949
+ await deletePm2App(appName, { dryRun, logger, waitTimeoutMs });
5950
+ }
5951
+ await startFromEcosystem(paths, { dryRun, logger });
5894
5952
  if (appName) {
5895
5953
  const proc = await waitPm2(
5896
5954
  appName,
@@ -6145,7 +6203,7 @@ function buildEnsureEnvScript() {
6145
6203
  return `/**
6146
6204
  * Written by cli-toolkit deploy (init-structure). Do not hand-edit.
6147
6205
  * Preloaded via pm2 node_args --require. Always set (do not only fill when unset):
6148
- * \`pm2 startOrReload --update-env\` can inject a non-production ENV from the
6206
+ * \`pm2 start \u2026 --update-env\` can inject a non-production ENV from the
6149
6207
  * deploy shell / prior dump, and a conditional assign would leave it in place.
6150
6208
  */
6151
6209
  process.env.ENV = "production";
@@ -6511,7 +6569,9 @@ async function rollbackService(service, options = {}) {
6511
6569
  logger.info(`rollback target: v${buildInfo.version} release=${buildInfo.release ?? rollbackTarget.name}`);
6512
6570
  }
6513
6571
  await activateRelease(rollbackTarget.path, paths, { dryRun, logger });
6514
- await reloadPm2(paths, { dryRun, logger });
6572
+ const appName = service.pm2?.appName ?? null;
6573
+ const waitTimeoutMs = 65e3;
6574
+ await reloadPm2(paths, { dryRun, logger, appName, waitTimeoutMs });
6515
6575
  const summary = `rollback ${activeName} \u2192 ${rollbackTarget.name} dryRun=${dryRun}`;
6516
6576
  if (!dryRun) await appendDeployLog(paths.deployLog, summary);
6517
6577
  return { from: activeName, to: rollbackTarget.name, path: rollbackTarget.path };
@@ -7022,6 +7082,34 @@ async function updateTaskProgress(context, tasksTable, taskId, progress) {
7022
7082
  progress: typeof progress === "string" ? progress : JSON.stringify(progress)
7023
7083
  });
7024
7084
  }
7085
+ function createTaskProgressReporter(context, tasksTable, taskId) {
7086
+ let pump = null;
7087
+ let pending = void 0;
7088
+ let hasPending = false;
7089
+ return (progress) => {
7090
+ pending = progress;
7091
+ hasPending = true;
7092
+ if (pump) return pump;
7093
+ pump = (async () => {
7094
+ try {
7095
+ while (hasPending) {
7096
+ hasPending = false;
7097
+ const value = pending;
7098
+ try {
7099
+ await updateTaskProgress(context, tasksTable, taskId, value);
7100
+ } catch (err) {
7101
+ context.logger?.warn?.(
7102
+ `[tasks] progress update failed for ${taskId}: ${err?.message ?? err}`
7103
+ );
7104
+ }
7105
+ }
7106
+ } finally {
7107
+ pump = null;
7108
+ }
7109
+ })();
7110
+ return pump;
7111
+ };
7112
+ }
7025
7113
 
7026
7114
  // src/tasks/servicesRegistry.js
7027
7115
  function getDb2(context) {
@@ -8873,7 +8961,8 @@ async function executeClaimedTask(context, tasksTable, historyTable, row, regist
8873
8961
  try {
8874
8962
  taskInstance = new TaskClass(context, row);
8875
8963
  runningTaskInstances.set(row.id, taskInstance);
8876
- const runResult = await taskInstance.run((progress) => updateTaskProgress(context, tasksTable, row.id, progress));
8964
+ const reportProgress = createTaskProgressReporter(context, tasksTable, row.id);
8965
+ const runResult = await taskInstance.run(reportProgress);
8877
8966
  success = !!runResult?.success;
8878
8967
  results = runResult?.results ?? null;
8879
8968
  } catch (error) {
@@ -9421,6 +9510,7 @@ var TasksManager = class _TasksManager {
9421
9510
  controlLaneTaskNames,
9422
9511
  convertPattern,
9423
9512
  createRelease,
9513
+ createTaskProgressReporter,
9424
9514
  defaultFileSynopsisFunction,
9425
9515
  defaultTasksRegistry,
9426
9516
  defaultVersionSynopsisFunction,