@nmakarov/cli-toolkit 0.49.0 → 0.51.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
@@ -7796,6 +7796,273 @@ var TaskGetLogs = class extends AbstractTask {
7796
7796
  }
7797
7797
  };
7798
7798
 
7799
+ // src/tasks/runtimeParams.js
7800
+ var LOOP_RUNTIME_KEYS = ["maxParallel", "pollMs", "claimJitterMs", "scanLimit"];
7801
+ var LOGGER_RUNTIME_KEYS = [
7802
+ "levels",
7803
+ "silent",
7804
+ "showLevel",
7805
+ "timestamp",
7806
+ "mode",
7807
+ "route",
7808
+ "prefix",
7809
+ "progressWithTimes",
7810
+ "progressThrottleMs"
7811
+ ];
7812
+ var CONTROL_LANE_TASK_NAMES = ["stopRunner", "stop", "setRuntimeParam", "setRunnerParam"];
7813
+ function controlLaneTaskNames() {
7814
+ return [...CONTROL_LANE_TASK_NAMES];
7815
+ }
7816
+ function asPositiveInt(value, key, { min = 1 } = {}) {
7817
+ const n = Number(value);
7818
+ if (!Number.isFinite(n) || n < min) {
7819
+ throw new ParamError(
7820
+ `setRuntimeParam: ${key} must be a number >= ${min} (got ${JSON.stringify(value)})`
7821
+ );
7822
+ }
7823
+ return Math.floor(n);
7824
+ }
7825
+ function coerceRuntimeValue(key, value) {
7826
+ switch (key) {
7827
+ case "maxParallel":
7828
+ return asPositiveInt(value, key, { min: 1 });
7829
+ case "pollMs":
7830
+ return asPositiveInt(value, key, { min: 50 });
7831
+ case "claimJitterMs":
7832
+ return asPositiveInt(value, key, { min: 0 });
7833
+ case "scanLimit":
7834
+ return asPositiveInt(value, key, { min: 1 });
7835
+ case "silent":
7836
+ case "showLevel":
7837
+ case "timestamp":
7838
+ case "progressWithTimes":
7839
+ if (typeof value === "boolean") return value;
7840
+ if (value === "true" || value === "1") return true;
7841
+ if (value === "false" || value === "0") return false;
7842
+ throw new ParamError(
7843
+ `setRuntimeParam: ${key} must be boolean (got ${JSON.stringify(value)})`
7844
+ );
7845
+ case "progressThrottleMs":
7846
+ return asPositiveInt(value, key, { min: 0 });
7847
+ case "levels":
7848
+ case "mode":
7849
+ case "route":
7850
+ case "prefix":
7851
+ return value;
7852
+ default:
7853
+ return value;
7854
+ }
7855
+ }
7856
+ function ensureTasksRuntime(context, seed = {}) {
7857
+ if (!context.tasksRuntime || typeof context.tasksRuntime !== "object") {
7858
+ context.tasksRuntime = {};
7859
+ }
7860
+ const rt = context.tasksRuntime;
7861
+ if (rt.maxParallel === void 0) rt.maxParallel = seed.maxParallel ?? 32;
7862
+ if (rt.pollMs === void 0) rt.pollMs = seed.pollMs ?? 1e3;
7863
+ if (rt.claimJitterMs === void 0) rt.claimJitterMs = seed.claimJitterMs ?? 0;
7864
+ if (rt.scanLimit === void 0) rt.scanLimit = seed.scanLimit ?? 100;
7865
+ return rt;
7866
+ }
7867
+ async function applyRuntimeParam(context, key, value) {
7868
+ const k = String(key ?? "").trim();
7869
+ if (!k) throw new ParamError("setRuntimeParam: key is required");
7870
+ const runtime = ensureTasksRuntime(context);
7871
+ const next = coerceRuntimeValue(k, value);
7872
+ const previous = runtime[k];
7873
+ const applied = [];
7874
+ runtime[k] = next;
7875
+ applied.push("tasksRuntime");
7876
+ if (LOGGER_RUNTIME_KEYS.includes(k) && context.logger?.configure) {
7877
+ context.logger.configure({ [k]: next });
7878
+ applied.push("logger");
7879
+ }
7880
+ const hook = context.tasksRuntimeOnParam;
7881
+ if (typeof hook === "function") {
7882
+ await hook(k, next, runtime, context);
7883
+ applied.push("onRuntimeParam");
7884
+ }
7885
+ const reg = context.servicesRegistry;
7886
+ if (reg?.rowId && reg?.registryTable) {
7887
+ try {
7888
+ const loopSnapshot = {};
7889
+ for (const lk of LOOP_RUNTIME_KEYS) {
7890
+ if (runtime[lk] !== void 0) loopSnapshot[lk] = runtime[lk];
7891
+ }
7892
+ await updateServicesRegistryMetadata(context, reg, {
7893
+ runtime: loopSnapshot,
7894
+ runtimeUpdatedAt: (/* @__PURE__ */ new Date()).toISOString()
7895
+ });
7896
+ applied.push("servicesRegistry");
7897
+ } catch (err) {
7898
+ context.logger?.warn?.(
7899
+ `[setRuntimeParam] registry metadata update failed: ${err?.message ?? String(err)}`
7900
+ );
7901
+ }
7902
+ }
7903
+ return { key: k, previous, next, applied };
7904
+ }
7905
+ async function applyRuntimePatch(context, patch) {
7906
+ if (!patch || typeof patch !== "object" || Array.isArray(patch)) {
7907
+ throw new ParamError("setRuntimeParam: patch must be a plain object");
7908
+ }
7909
+ const entries = Object.entries(patch);
7910
+ if (entries.length === 0) {
7911
+ throw new ParamError("setRuntimeParam: patch is empty");
7912
+ }
7913
+ const out = [];
7914
+ for (const [key, value] of entries) {
7915
+ out.push(await applyRuntimeParam(context, key, value));
7916
+ }
7917
+ return out;
7918
+ }
7919
+ function readLoopRuntime(context) {
7920
+ const rt = ensureTasksRuntime(context);
7921
+ return {
7922
+ maxParallel: Math.max(1, Number(rt.maxParallel) || 1),
7923
+ pollMs: Math.max(50, Number(rt.pollMs) || 1e3),
7924
+ claimJitterMs: Math.max(0, Number(rt.claimJitterMs) || 0),
7925
+ scanLimit: Math.max(1, Number(rt.scanLimit) || 100)
7926
+ };
7927
+ }
7928
+
7929
+ // src/tasks/coreTasks/TaskSetRuntimeParam.js
7930
+ var TaskSetRuntimeParam = class extends AbstractTask {
7931
+ static defaultWaitForResult = true;
7932
+ /**
7933
+ * @param {object} context
7934
+ * @param {Record<string, unknown>} [overrides]
7935
+ * @returns {Promise<object>}
7936
+ */
7937
+ static async resolveParams(context, overrides = {}) {
7938
+ const main = await super.resolveParams(context, overrides);
7939
+ if (!main.serviceName && !main.serviceGroup) {
7940
+ throw new ParamError(
7941
+ "setRuntimeParam requires --serviceName (one instance) or --serviceGroup (broadcast to alive instances)"
7942
+ );
7943
+ }
7944
+ return main;
7945
+ }
7946
+ /**
7947
+ * @param {object} context
7948
+ * @param {Record<string, unknown>} [overrides]
7949
+ * @returns {Promise<{ key?: string, value?: unknown, patch?: Record<string, unknown> }>}
7950
+ */
7951
+ static async resolveCustomParams(context, overrides = {}) {
7952
+ const merged = AbstractTask._mergeTypedParams(
7953
+ context,
7954
+ "task-set-runtime-param",
7955
+ {
7956
+ paramKey: "string",
7957
+ paramValue: "string",
7958
+ key: "string",
7959
+ value: "string"
7960
+ },
7961
+ overrides
7962
+ );
7963
+ if (merged.patch && typeof merged.patch === "object" && !Array.isArray(merged.patch)) {
7964
+ if (Object.keys(merged.patch).length === 0) {
7965
+ throw new ParamError("setRuntimeParam: patch is empty");
7966
+ }
7967
+ return { patch: { ...merged.patch } };
7968
+ }
7969
+ const key = merged.paramKey || merged.key;
7970
+ const value = merged.paramValue !== void 0 ? merged.paramValue : merged.value;
7971
+ if (!key) {
7972
+ throw new ParamError(
7973
+ `setRuntimeParam requires --paramKey/--paramValue, or --paramsJson '{"key":"maxParallel","value":16}' / '{"patch":{...}}'`
7974
+ );
7975
+ }
7976
+ let parsed = value;
7977
+ if (typeof value === "string") {
7978
+ const t = value.trim();
7979
+ if (t === "true") parsed = true;
7980
+ else if (t === "false") parsed = false;
7981
+ else if (t !== "" && !Number.isNaN(Number(t)) && /^-?\d+(\.\d+)?$/.test(t)) {
7982
+ parsed = Number(t);
7983
+ } else if (t.startsWith("{") && t.endsWith("}") || t.startsWith("[") && t.endsWith("]") || t.startsWith('"') && t.endsWith('"')) {
7984
+ try {
7985
+ parsed = JSON.parse(t);
7986
+ } catch {
7987
+ parsed = value;
7988
+ }
7989
+ }
7990
+ }
7991
+ return { key: String(key), value: parsed };
7992
+ }
7993
+ /**
7994
+ * Enqueue one or many setRuntimeParam tasks. Prefer this over a bare
7995
+ * `enqueueTask` when broadcasting to a service group.
7996
+ *
7997
+ * @param {object} context
7998
+ * @param {Record<string, unknown>} [overrides]
7999
+ * @returns {Promise<{ ids: string[], targets: string[] }>}
8000
+ */
8001
+ static async enqueue(context, overrides = {}) {
8002
+ const payload = await this.resolveParams(context, { ...overrides, name: "setRuntimeParam" });
8003
+ const queueName = payload.queueName ?? "tasks";
8004
+ if (payload.serviceName) {
8005
+ const id = await enqueueTask(context, payload);
8006
+ return { ids: [id], targets: [payload.serviceName] };
8007
+ }
8008
+ const group = String(payload.serviceGroup || "").trim();
8009
+ if (!group) {
8010
+ throw new ParamError("setRuntimeParam.enqueue: serviceGroup required for broadcast");
8011
+ }
8012
+ const alive = await listServicesRegistry(context, {
8013
+ queueName,
8014
+ serviceGroup: group,
8015
+ staleMs: overrides.staleMs ?? 45e3
8016
+ });
8017
+ if (!alive.length) {
8018
+ throw new ParamError(
8019
+ `setRuntimeParam: no alive services in group="${group}" queue="${queueName}"`
8020
+ );
8021
+ }
8022
+ const ids = [];
8023
+ const targets = [];
8024
+ for (const reg of alive) {
8025
+ const id = await enqueueTask(context, {
8026
+ ...payload,
8027
+ serviceGroup: group,
8028
+ serviceName: reg.service_name,
8029
+ serverName: reg.server_name ?? null,
8030
+ instanceNumber: reg.instance_number ?? null
8031
+ });
8032
+ ids.push(id);
8033
+ targets.push(reg.service_name);
8034
+ }
8035
+ context.logger?.info?.(
8036
+ `[setRuntimeParam] broadcast to ${targets.length} instance(s) in group=${group}: ${targets.join(", ")}`
8037
+ );
8038
+ return { ids, targets };
8039
+ }
8040
+ /**
8041
+ * @returns {Promise<{ success: true, results: object }>}
8042
+ */
8043
+ async run() {
8044
+ const params = this.task?.params ?? {};
8045
+ let changes;
8046
+ if (params.patch && typeof params.patch === "object") {
8047
+ changes = await applyRuntimePatch(this.context, params.patch);
8048
+ } else {
8049
+ changes = [await applyRuntimeParam(this.context, params.key, params.value)];
8050
+ }
8051
+ const summary = changes.map((c) => `${c.key}: ${JSON.stringify(c.previous)} \u2192 ${JSON.stringify(c.next)}`);
8052
+ this.context.logger.warn?.(
8053
+ `[TaskSetRuntimeParam] applied on ${this.context.servicesRegistry?.serviceName ?? "runner"}: ${summary.join("; ")}`
8054
+ );
8055
+ return {
8056
+ success: true,
8057
+ results: {
8058
+ runtimeParamApplied: true,
8059
+ changes,
8060
+ runtime: { ...this.context.tasksRuntime ?? {} }
8061
+ }
8062
+ };
8063
+ }
8064
+ };
8065
+
7799
8066
  // src/tasks/TasksRegistry.js
7800
8067
  var TasksRegistry = class _TasksRegistry {
7801
8068
  /**
@@ -7814,7 +8081,7 @@ var TasksRegistry = class _TasksRegistry {
7814
8081
  * @returns {TasksRegistry}
7815
8082
  */
7816
8083
  static withCoreTasks() {
7817
- return new _TasksRegistry().add("ping", TaskPing).add("sampleProcess", TaskSampleProcess).add("shellCommand", TaskShellCommand).add("systemInfo", TaskSystemInfo).add("info", TaskSystemInfo).add("taskSumAB", TaskSumAB).add("stopRunner", TaskStopRunner).add("stop", TaskStopRunner).add("getLogs", TaskGetLogs);
8084
+ return new _TasksRegistry().add("ping", TaskPing).add("sampleProcess", TaskSampleProcess).add("shellCommand", TaskShellCommand).add("systemInfo", TaskSystemInfo).add("info", TaskSystemInfo).add("taskSumAB", TaskSumAB).add("stopRunner", TaskStopRunner).add("stop", TaskStopRunner).add("getLogs", TaskGetLogs).add("setRuntimeParam", TaskSetRuntimeParam).add("setRunnerParam", TaskSetRuntimeParam);
7818
8085
  }
7819
8086
  /**
7820
8087
  * Register a single task class under a name. Overwrites any previous entry.
@@ -7920,7 +8187,9 @@ var SERVICE_TASK_NAMES = [
7920
8187
  "shellCommand",
7921
8188
  "systemInfo",
7922
8189
  "info",
7923
- "getLogs"
8190
+ "getLogs",
8191
+ "setRuntimeParam",
8192
+ "setRunnerParam"
7924
8193
  ];
7925
8194
  function normalizeAllowedTasks(value) {
7926
8195
  if (!value) return void 0;
@@ -8347,18 +8616,23 @@ async function claimNextRunnableTask(context, tasksTable, serviceGroup, registry
8347
8616
  async function runTasksLoop(context, options) {
8348
8617
  const queueName = options.queueName ?? "tasks";
8349
8618
  const target = options.target;
8350
- const pollMs = options.pollMs ?? 1e3;
8351
- const claimJitterMs = options.claimJitterMs ?? 0;
8352
- const maxParallel = options.maxParallel ?? 32;
8353
- const scanLimit = options.scanLimit ?? 100;
8354
8619
  const allowedTasks = normalizeAllowedTasks(options.allowedTasks);
8355
8620
  const registry = normalizeRegistry(options.registry);
8356
8621
  const { tasksTable, historyTable } = queueToTableNames(queueName);
8357
8622
  if (!target) throw new Error("runTasksLoop: target is required");
8358
8623
  context.tasksQueueName = queueName;
8624
+ ensureTasksRuntime(context, {
8625
+ maxParallel: options.maxParallel ?? 32,
8626
+ pollMs: options.pollMs ?? 1e3,
8627
+ claimJitterMs: options.claimJitterMs ?? 0,
8628
+ scanLimit: options.scanLimit ?? 100
8629
+ });
8630
+ if (typeof options.onRuntimeParam === "function") {
8631
+ context.tasksRuntimeOnParam = options.onRuntimeParam;
8632
+ }
8359
8633
  const runningPromises = /* @__PURE__ */ new Set();
8360
8634
  const runningTaskInstances = /* @__PURE__ */ new Map();
8361
- let runningStopControlPromise = null;
8635
+ let runningControlPromise = null;
8362
8636
  let stopRequested = false;
8363
8637
  let stopAllowanceMs = 5e3;
8364
8638
  context.tasksRunnerStop = false;
@@ -8369,9 +8643,16 @@ async function runTasksLoop(context, options) {
8369
8643
  if (hbGroup) {
8370
8644
  const hbIntervalMs = options.runnerHeartbeatIntervalMs ?? 1e4;
8371
8645
  const staleMs = options.runnerHeartbeatStaleMs ?? 45e3;
8646
+ const loop0 = readLoopRuntime(context);
8372
8647
  const defaultMeta = {
8373
8648
  component: "tasks-runner",
8374
- allowedTasks: allowedTasks?.length ? allowedTasks.join(",") : "all"
8649
+ allowedTasks: allowedTasks?.length ? allowedTasks.join(",") : "all",
8650
+ runtime: {
8651
+ maxParallel: loop0.maxParallel,
8652
+ pollMs: loop0.pollMs,
8653
+ claimJitterMs: loop0.claimJitterMs,
8654
+ scanLimit: loop0.scanLimit
8655
+ }
8375
8656
  };
8376
8657
  registryReg = await registerInServicesRegistry(context, {
8377
8658
  queueName,
@@ -8384,6 +8665,7 @@ async function runTasksLoop(context, options) {
8384
8665
  enforceMaxInstances: options.runnerEnforceMaxInstances ?? true,
8385
8666
  metadata: options.runnerMetadata ?? defaultMeta
8386
8667
  });
8668
+ context.servicesRegistry = registryReg;
8387
8669
  runnerIdentity = {
8388
8670
  service_name: registryReg.serviceName,
8389
8671
  server_name: os3.hostname(),
@@ -8397,22 +8679,23 @@ async function runTasksLoop(context, options) {
8397
8679
  }
8398
8680
  try {
8399
8681
  while (!context.isStop() && !stopRequested && context.tasksRunnerStop !== true) {
8400
- if (!runningStopControlPromise) {
8401
- const claimedStopTask = await claimNextRunnableTask(
8682
+ const { maxParallel, pollMs, claimJitterMs, scanLimit } = readLoopRuntime(context);
8683
+ if (!runningControlPromise) {
8684
+ const claimedControlTask = await claimNextRunnableTask(
8402
8685
  context,
8403
8686
  tasksTable,
8404
8687
  target,
8405
8688
  registry,
8406
8689
  10,
8407
- ["stopRunner", "stop"],
8690
+ controlLaneTaskNames(),
8408
8691
  runnerIdentity
8409
8692
  );
8410
- if (claimedStopTask) {
8411
- runningStopControlPromise = executeClaimedTask(
8693
+ if (claimedControlTask) {
8694
+ runningControlPromise = executeClaimedTask(
8412
8695
  context,
8413
8696
  tasksTable,
8414
8697
  historyTable,
8415
- claimedStopTask,
8698
+ claimedControlTask,
8416
8699
  registry,
8417
8700
  runningTaskInstances
8418
8701
  ).then(async (outcome) => {
@@ -8423,7 +8706,7 @@ async function runTasksLoop(context, options) {
8423
8706
  await signalRunningTasksStop(context, runningTaskInstances, stopAllowanceMs);
8424
8707
  }
8425
8708
  }).finally(() => {
8426
- runningStopControlPromise = null;
8709
+ runningControlPromise = null;
8427
8710
  });
8428
8711
  }
8429
8712
  }
@@ -8454,8 +8737,8 @@ async function runTasksLoop(context, options) {
8454
8737
  runningPromises.add(p);
8455
8738
  }
8456
8739
  const wakePromises = [...runningPromises];
8457
- if (runningStopControlPromise) {
8458
- wakePromises.push(runningStopControlPromise);
8740
+ if (runningControlPromise) {
8741
+ wakePromises.push(runningControlPromise);
8459
8742
  }
8460
8743
  if (wakePromises.length === 0) {
8461
8744
  await sleepMs(pollMs);
@@ -8683,6 +8966,8 @@ export {
8683
8966
  FooterPresets,
8684
8967
  GridCell,
8685
8968
  InputField,
8969
+ LOGGER_RUNTIME_KEYS,
8970
+ LOOP_RUNTIME_KEYS,
8686
8971
  ListComponent,
8687
8972
  ListItem,
8688
8973
  MultiColumnListComponent,
@@ -8701,6 +8986,7 @@ export {
8701
8986
  TaskGetLogs,
8702
8987
  TaskPing,
8703
8988
  TaskSampleProcess,
8989
+ TaskSetRuntimeParam,
8704
8990
  TaskShellCommand,
8705
8991
  TaskStopRunner,
8706
8992
  TaskSumAB,
@@ -8712,12 +8998,16 @@ export {
8712
8998
  activateRelease,
8713
8999
  appendDeployLog,
8714
9000
  appendTaskIpcLog,
9001
+ applyRuntimeParam,
9002
+ applyRuntimePatch,
8715
9003
  bootstrapHost,
8716
9004
  buildBreadcrumb,
8717
9005
  buildDetailBreadcrumb,
8718
9006
  buildFooter,
8719
9007
  bumpPatchVersion,
8720
9008
  cloneRepo,
9009
+ coerceRuntimeValue,
9010
+ controlLaneTaskNames,
8721
9011
  convertPattern,
8722
9012
  createRelease,
8723
9013
  defaultFileSynopsisFunction,
@@ -8739,6 +9029,7 @@ export {
8739
9029
  ensureSchemaEverywhere,
8740
9030
  ensureTable,
8741
9031
  ensureTaskTables,
9032
+ ensureTasksRuntime,
8742
9033
  flushTaskIpcLogs,
8743
9034
  getArgsInstance,
8744
9035
  createElement2 as h,
@@ -8770,6 +9061,7 @@ export {
8770
9061
  pullRepo,
8771
9062
  queueToTableNames,
8772
9063
  readCurrentRelease,
9064
+ readLoopRuntime,
8773
9065
  readReleaseBuildInfo,
8774
9066
  readTaskIpcLogsSnapshot,
8775
9067
  registerInServicesRegistry,