@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/tasks.cjs CHANGED
@@ -30,10 +30,13 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  var tasks_exports = {};
31
31
  __export(tasks_exports, {
32
32
  AbstractTask: () => AbstractTask,
33
+ LOGGER_RUNTIME_KEYS: () => LOGGER_RUNTIME_KEYS,
34
+ LOOP_RUNTIME_KEYS: () => LOOP_RUNTIME_KEYS,
33
35
  SERVICE_TASK_NAMES: () => SERVICE_TASK_NAMES,
34
36
  TaskGetLogs: () => TaskGetLogs,
35
37
  TaskPing: () => TaskPing,
36
38
  TaskSampleProcess: () => TaskSampleProcess,
39
+ TaskSetRuntimeParam: () => TaskSetRuntimeParam,
37
40
  TaskShellCommand: () => TaskShellCommand,
38
41
  TaskStopRunner: () => TaskStopRunner,
39
42
  TaskSumAB: () => TaskSumAB,
@@ -41,11 +44,16 @@ __export(tasks_exports, {
41
44
  TasksManager: () => TasksManager,
42
45
  TasksRegistry: () => TasksRegistry,
43
46
  appendTaskIpcLog: () => appendTaskIpcLog,
47
+ applyRuntimeParam: () => applyRuntimeParam,
48
+ applyRuntimePatch: () => applyRuntimePatch,
49
+ coerceRuntimeValue: () => coerceRuntimeValue,
50
+ controlLaneTaskNames: () => controlLaneTaskNames,
44
51
  convertPattern: () => convertPattern,
45
52
  defaultTasksRegistry: () => defaultTasksRegistry,
46
53
  enqueueStopTask: () => enqueueStopTask,
47
54
  enqueueTask: () => enqueueTask,
48
55
  ensureTaskTables: () => ensureTaskTables,
56
+ ensureTasksRuntime: () => ensureTasksRuntime,
49
57
  flushTaskIpcLogs: () => flushTaskIpcLogs,
50
58
  ipcFileLogsTableNameForSourceResource: () => ipcFileLogsTableNameForSourceResource,
51
59
  listAliveRunnerHeartbeats: () => listServicesRegistry,
@@ -55,6 +63,7 @@ __export(tasks_exports, {
55
63
  nextTimeMatch: () => nextTimeMatch,
56
64
  normalizeAllowedTasks: () => normalizeAllowedTasks,
57
65
  queueToTableNames: () => queueToTableNames,
66
+ readLoopRuntime: () => readLoopRuntime,
58
67
  readTaskIpcLogsSnapshot: () => readTaskIpcLogsSnapshot,
59
68
  registerInServicesRegistry: () => registerInServicesRegistry,
60
69
  registerRunnerHeartbeat: () => registerInServicesRegistry,
@@ -2783,6 +2792,273 @@ var TaskGetLogs = class extends AbstractTask {
2783
2792
  }
2784
2793
  };
2785
2794
 
2795
+ // src/tasks/runtimeParams.js
2796
+ var LOOP_RUNTIME_KEYS = ["maxParallel", "pollMs", "claimJitterMs", "scanLimit"];
2797
+ var LOGGER_RUNTIME_KEYS = [
2798
+ "levels",
2799
+ "silent",
2800
+ "showLevel",
2801
+ "timestamp",
2802
+ "mode",
2803
+ "route",
2804
+ "prefix",
2805
+ "progressWithTimes",
2806
+ "progressThrottleMs"
2807
+ ];
2808
+ var CONTROL_LANE_TASK_NAMES = ["stopRunner", "stop", "setRuntimeParam", "setRunnerParam"];
2809
+ function controlLaneTaskNames() {
2810
+ return [...CONTROL_LANE_TASK_NAMES];
2811
+ }
2812
+ function asPositiveInt(value, key, { min = 1 } = {}) {
2813
+ const n = Number(value);
2814
+ if (!Number.isFinite(n) || n < min) {
2815
+ throw new ParamError(
2816
+ `setRuntimeParam: ${key} must be a number >= ${min} (got ${JSON.stringify(value)})`
2817
+ );
2818
+ }
2819
+ return Math.floor(n);
2820
+ }
2821
+ function coerceRuntimeValue(key, value) {
2822
+ switch (key) {
2823
+ case "maxParallel":
2824
+ return asPositiveInt(value, key, { min: 1 });
2825
+ case "pollMs":
2826
+ return asPositiveInt(value, key, { min: 50 });
2827
+ case "claimJitterMs":
2828
+ return asPositiveInt(value, key, { min: 0 });
2829
+ case "scanLimit":
2830
+ return asPositiveInt(value, key, { min: 1 });
2831
+ case "silent":
2832
+ case "showLevel":
2833
+ case "timestamp":
2834
+ case "progressWithTimes":
2835
+ if (typeof value === "boolean") return value;
2836
+ if (value === "true" || value === "1") return true;
2837
+ if (value === "false" || value === "0") return false;
2838
+ throw new ParamError(
2839
+ `setRuntimeParam: ${key} must be boolean (got ${JSON.stringify(value)})`
2840
+ );
2841
+ case "progressThrottleMs":
2842
+ return asPositiveInt(value, key, { min: 0 });
2843
+ case "levels":
2844
+ case "mode":
2845
+ case "route":
2846
+ case "prefix":
2847
+ return value;
2848
+ default:
2849
+ return value;
2850
+ }
2851
+ }
2852
+ function ensureTasksRuntime(context, seed = {}) {
2853
+ if (!context.tasksRuntime || typeof context.tasksRuntime !== "object") {
2854
+ context.tasksRuntime = {};
2855
+ }
2856
+ const rt = context.tasksRuntime;
2857
+ if (rt.maxParallel === void 0) rt.maxParallel = seed.maxParallel ?? 32;
2858
+ if (rt.pollMs === void 0) rt.pollMs = seed.pollMs ?? 1e3;
2859
+ if (rt.claimJitterMs === void 0) rt.claimJitterMs = seed.claimJitterMs ?? 0;
2860
+ if (rt.scanLimit === void 0) rt.scanLimit = seed.scanLimit ?? 100;
2861
+ return rt;
2862
+ }
2863
+ async function applyRuntimeParam(context, key, value) {
2864
+ const k = String(key ?? "").trim();
2865
+ if (!k) throw new ParamError("setRuntimeParam: key is required");
2866
+ const runtime = ensureTasksRuntime(context);
2867
+ const next = coerceRuntimeValue(k, value);
2868
+ const previous = runtime[k];
2869
+ const applied = [];
2870
+ runtime[k] = next;
2871
+ applied.push("tasksRuntime");
2872
+ if (LOGGER_RUNTIME_KEYS.includes(k) && context.logger?.configure) {
2873
+ context.logger.configure({ [k]: next });
2874
+ applied.push("logger");
2875
+ }
2876
+ const hook = context.tasksRuntimeOnParam;
2877
+ if (typeof hook === "function") {
2878
+ await hook(k, next, runtime, context);
2879
+ applied.push("onRuntimeParam");
2880
+ }
2881
+ const reg = context.servicesRegistry;
2882
+ if (reg?.rowId && reg?.registryTable) {
2883
+ try {
2884
+ const loopSnapshot = {};
2885
+ for (const lk of LOOP_RUNTIME_KEYS) {
2886
+ if (runtime[lk] !== void 0) loopSnapshot[lk] = runtime[lk];
2887
+ }
2888
+ await updateServicesRegistryMetadata(context, reg, {
2889
+ runtime: loopSnapshot,
2890
+ runtimeUpdatedAt: (/* @__PURE__ */ new Date()).toISOString()
2891
+ });
2892
+ applied.push("servicesRegistry");
2893
+ } catch (err) {
2894
+ context.logger?.warn?.(
2895
+ `[setRuntimeParam] registry metadata update failed: ${err?.message ?? String(err)}`
2896
+ );
2897
+ }
2898
+ }
2899
+ return { key: k, previous, next, applied };
2900
+ }
2901
+ async function applyRuntimePatch(context, patch) {
2902
+ if (!patch || typeof patch !== "object" || Array.isArray(patch)) {
2903
+ throw new ParamError("setRuntimeParam: patch must be a plain object");
2904
+ }
2905
+ const entries = Object.entries(patch);
2906
+ if (entries.length === 0) {
2907
+ throw new ParamError("setRuntimeParam: patch is empty");
2908
+ }
2909
+ const out = [];
2910
+ for (const [key, value] of entries) {
2911
+ out.push(await applyRuntimeParam(context, key, value));
2912
+ }
2913
+ return out;
2914
+ }
2915
+ function readLoopRuntime(context) {
2916
+ const rt = ensureTasksRuntime(context);
2917
+ return {
2918
+ maxParallel: Math.max(1, Number(rt.maxParallel) || 1),
2919
+ pollMs: Math.max(50, Number(rt.pollMs) || 1e3),
2920
+ claimJitterMs: Math.max(0, Number(rt.claimJitterMs) || 0),
2921
+ scanLimit: Math.max(1, Number(rt.scanLimit) || 100)
2922
+ };
2923
+ }
2924
+
2925
+ // src/tasks/coreTasks/TaskSetRuntimeParam.js
2926
+ var TaskSetRuntimeParam = class extends AbstractTask {
2927
+ static defaultWaitForResult = true;
2928
+ /**
2929
+ * @param {object} context
2930
+ * @param {Record<string, unknown>} [overrides]
2931
+ * @returns {Promise<object>}
2932
+ */
2933
+ static async resolveParams(context, overrides = {}) {
2934
+ const main = await super.resolveParams(context, overrides);
2935
+ if (!main.serviceName && !main.serviceGroup) {
2936
+ throw new ParamError(
2937
+ "setRuntimeParam requires --serviceName (one instance) or --serviceGroup (broadcast to alive instances)"
2938
+ );
2939
+ }
2940
+ return main;
2941
+ }
2942
+ /**
2943
+ * @param {object} context
2944
+ * @param {Record<string, unknown>} [overrides]
2945
+ * @returns {Promise<{ key?: string, value?: unknown, patch?: Record<string, unknown> }>}
2946
+ */
2947
+ static async resolveCustomParams(context, overrides = {}) {
2948
+ const merged = AbstractTask._mergeTypedParams(
2949
+ context,
2950
+ "task-set-runtime-param",
2951
+ {
2952
+ paramKey: "string",
2953
+ paramValue: "string",
2954
+ key: "string",
2955
+ value: "string"
2956
+ },
2957
+ overrides
2958
+ );
2959
+ if (merged.patch && typeof merged.patch === "object" && !Array.isArray(merged.patch)) {
2960
+ if (Object.keys(merged.patch).length === 0) {
2961
+ throw new ParamError("setRuntimeParam: patch is empty");
2962
+ }
2963
+ return { patch: { ...merged.patch } };
2964
+ }
2965
+ const key = merged.paramKey || merged.key;
2966
+ const value = merged.paramValue !== void 0 ? merged.paramValue : merged.value;
2967
+ if (!key) {
2968
+ throw new ParamError(
2969
+ `setRuntimeParam requires --paramKey/--paramValue, or --paramsJson '{"key":"maxParallel","value":16}' / '{"patch":{...}}'`
2970
+ );
2971
+ }
2972
+ let parsed = value;
2973
+ if (typeof value === "string") {
2974
+ const t = value.trim();
2975
+ if (t === "true") parsed = true;
2976
+ else if (t === "false") parsed = false;
2977
+ else if (t !== "" && !Number.isNaN(Number(t)) && /^-?\d+(\.\d+)?$/.test(t)) {
2978
+ parsed = Number(t);
2979
+ } else if (t.startsWith("{") && t.endsWith("}") || t.startsWith("[") && t.endsWith("]") || t.startsWith('"') && t.endsWith('"')) {
2980
+ try {
2981
+ parsed = JSON.parse(t);
2982
+ } catch {
2983
+ parsed = value;
2984
+ }
2985
+ }
2986
+ }
2987
+ return { key: String(key), value: parsed };
2988
+ }
2989
+ /**
2990
+ * Enqueue one or many setRuntimeParam tasks. Prefer this over a bare
2991
+ * `enqueueTask` when broadcasting to a service group.
2992
+ *
2993
+ * @param {object} context
2994
+ * @param {Record<string, unknown>} [overrides]
2995
+ * @returns {Promise<{ ids: string[], targets: string[] }>}
2996
+ */
2997
+ static async enqueue(context, overrides = {}) {
2998
+ const payload = await this.resolveParams(context, { ...overrides, name: "setRuntimeParam" });
2999
+ const queueName = payload.queueName ?? "tasks";
3000
+ if (payload.serviceName) {
3001
+ const id = await enqueueTask(context, payload);
3002
+ return { ids: [id], targets: [payload.serviceName] };
3003
+ }
3004
+ const group = String(payload.serviceGroup || "").trim();
3005
+ if (!group) {
3006
+ throw new ParamError("setRuntimeParam.enqueue: serviceGroup required for broadcast");
3007
+ }
3008
+ const alive = await listServicesRegistry(context, {
3009
+ queueName,
3010
+ serviceGroup: group,
3011
+ staleMs: overrides.staleMs ?? 45e3
3012
+ });
3013
+ if (!alive.length) {
3014
+ throw new ParamError(
3015
+ `setRuntimeParam: no alive services in group="${group}" queue="${queueName}"`
3016
+ );
3017
+ }
3018
+ const ids = [];
3019
+ const targets = [];
3020
+ for (const reg of alive) {
3021
+ const id = await enqueueTask(context, {
3022
+ ...payload,
3023
+ serviceGroup: group,
3024
+ serviceName: reg.service_name,
3025
+ serverName: reg.server_name ?? null,
3026
+ instanceNumber: reg.instance_number ?? null
3027
+ });
3028
+ ids.push(id);
3029
+ targets.push(reg.service_name);
3030
+ }
3031
+ context.logger?.info?.(
3032
+ `[setRuntimeParam] broadcast to ${targets.length} instance(s) in group=${group}: ${targets.join(", ")}`
3033
+ );
3034
+ return { ids, targets };
3035
+ }
3036
+ /**
3037
+ * @returns {Promise<{ success: true, results: object }>}
3038
+ */
3039
+ async run() {
3040
+ const params = this.task?.params ?? {};
3041
+ let changes;
3042
+ if (params.patch && typeof params.patch === "object") {
3043
+ changes = await applyRuntimePatch(this.context, params.patch);
3044
+ } else {
3045
+ changes = [await applyRuntimeParam(this.context, params.key, params.value)];
3046
+ }
3047
+ const summary = changes.map((c) => `${c.key}: ${JSON.stringify(c.previous)} \u2192 ${JSON.stringify(c.next)}`);
3048
+ this.context.logger.warn?.(
3049
+ `[TaskSetRuntimeParam] applied on ${this.context.servicesRegistry?.serviceName ?? "runner"}: ${summary.join("; ")}`
3050
+ );
3051
+ return {
3052
+ success: true,
3053
+ results: {
3054
+ runtimeParamApplied: true,
3055
+ changes,
3056
+ runtime: { ...this.context.tasksRuntime ?? {} }
3057
+ }
3058
+ };
3059
+ }
3060
+ };
3061
+
2786
3062
  // src/tasks/TasksRegistry.js
2787
3063
  var TasksRegistry = class _TasksRegistry {
2788
3064
  /**
@@ -2801,7 +3077,7 @@ var TasksRegistry = class _TasksRegistry {
2801
3077
  * @returns {TasksRegistry}
2802
3078
  */
2803
3079
  static withCoreTasks() {
2804
- 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);
3080
+ 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);
2805
3081
  }
2806
3082
  /**
2807
3083
  * Register a single task class under a name. Overwrites any previous entry.
@@ -2907,7 +3183,9 @@ var SERVICE_TASK_NAMES = [
2907
3183
  "shellCommand",
2908
3184
  "systemInfo",
2909
3185
  "info",
2910
- "getLogs"
3186
+ "getLogs",
3187
+ "setRuntimeParam",
3188
+ "setRunnerParam"
2911
3189
  ];
2912
3190
  function normalizeAllowedTasks(value) {
2913
3191
  if (!value) return void 0;
@@ -3334,18 +3612,23 @@ async function claimNextRunnableTask(context, tasksTable, serviceGroup, registry
3334
3612
  async function runTasksLoop(context, options) {
3335
3613
  const queueName = options.queueName ?? "tasks";
3336
3614
  const target = options.target;
3337
- const pollMs = options.pollMs ?? 1e3;
3338
- const claimJitterMs = options.claimJitterMs ?? 0;
3339
- const maxParallel = options.maxParallel ?? 32;
3340
- const scanLimit = options.scanLimit ?? 100;
3341
3615
  const allowedTasks = normalizeAllowedTasks(options.allowedTasks);
3342
3616
  const registry = normalizeRegistry(options.registry);
3343
3617
  const { tasksTable, historyTable } = queueToTableNames(queueName);
3344
3618
  if (!target) throw new Error("runTasksLoop: target is required");
3345
3619
  context.tasksQueueName = queueName;
3620
+ ensureTasksRuntime(context, {
3621
+ maxParallel: options.maxParallel ?? 32,
3622
+ pollMs: options.pollMs ?? 1e3,
3623
+ claimJitterMs: options.claimJitterMs ?? 0,
3624
+ scanLimit: options.scanLimit ?? 100
3625
+ });
3626
+ if (typeof options.onRuntimeParam === "function") {
3627
+ context.tasksRuntimeOnParam = options.onRuntimeParam;
3628
+ }
3346
3629
  const runningPromises = /* @__PURE__ */ new Set();
3347
3630
  const runningTaskInstances = /* @__PURE__ */ new Map();
3348
- let runningStopControlPromise = null;
3631
+ let runningControlPromise = null;
3349
3632
  let stopRequested = false;
3350
3633
  let stopAllowanceMs = 5e3;
3351
3634
  context.tasksRunnerStop = false;
@@ -3356,9 +3639,16 @@ async function runTasksLoop(context, options) {
3356
3639
  if (hbGroup) {
3357
3640
  const hbIntervalMs = options.runnerHeartbeatIntervalMs ?? 1e4;
3358
3641
  const staleMs = options.runnerHeartbeatStaleMs ?? 45e3;
3642
+ const loop0 = readLoopRuntime(context);
3359
3643
  const defaultMeta = {
3360
3644
  component: "tasks-runner",
3361
- allowedTasks: allowedTasks?.length ? allowedTasks.join(",") : "all"
3645
+ allowedTasks: allowedTasks?.length ? allowedTasks.join(",") : "all",
3646
+ runtime: {
3647
+ maxParallel: loop0.maxParallel,
3648
+ pollMs: loop0.pollMs,
3649
+ claimJitterMs: loop0.claimJitterMs,
3650
+ scanLimit: loop0.scanLimit
3651
+ }
3362
3652
  };
3363
3653
  registryReg = await registerInServicesRegistry(context, {
3364
3654
  queueName,
@@ -3371,6 +3661,7 @@ async function runTasksLoop(context, options) {
3371
3661
  enforceMaxInstances: options.runnerEnforceMaxInstances ?? true,
3372
3662
  metadata: options.runnerMetadata ?? defaultMeta
3373
3663
  });
3664
+ context.servicesRegistry = registryReg;
3374
3665
  runnerIdentity = {
3375
3666
  service_name: registryReg.serviceName,
3376
3667
  server_name: import_node_os3.default.hostname(),
@@ -3384,22 +3675,23 @@ async function runTasksLoop(context, options) {
3384
3675
  }
3385
3676
  try {
3386
3677
  while (!context.isStop() && !stopRequested && context.tasksRunnerStop !== true) {
3387
- if (!runningStopControlPromise) {
3388
- const claimedStopTask = await claimNextRunnableTask(
3678
+ const { maxParallel, pollMs, claimJitterMs, scanLimit } = readLoopRuntime(context);
3679
+ if (!runningControlPromise) {
3680
+ const claimedControlTask = await claimNextRunnableTask(
3389
3681
  context,
3390
3682
  tasksTable,
3391
3683
  target,
3392
3684
  registry,
3393
3685
  10,
3394
- ["stopRunner", "stop"],
3686
+ controlLaneTaskNames(),
3395
3687
  runnerIdentity
3396
3688
  );
3397
- if (claimedStopTask) {
3398
- runningStopControlPromise = executeClaimedTask(
3689
+ if (claimedControlTask) {
3690
+ runningControlPromise = executeClaimedTask(
3399
3691
  context,
3400
3692
  tasksTable,
3401
3693
  historyTable,
3402
- claimedStopTask,
3694
+ claimedControlTask,
3403
3695
  registry,
3404
3696
  runningTaskInstances
3405
3697
  ).then(async (outcome) => {
@@ -3410,7 +3702,7 @@ async function runTasksLoop(context, options) {
3410
3702
  await signalRunningTasksStop(context, runningTaskInstances, stopAllowanceMs);
3411
3703
  }
3412
3704
  }).finally(() => {
3413
- runningStopControlPromise = null;
3705
+ runningControlPromise = null;
3414
3706
  });
3415
3707
  }
3416
3708
  }
@@ -3441,8 +3733,8 @@ async function runTasksLoop(context, options) {
3441
3733
  runningPromises.add(p);
3442
3734
  }
3443
3735
  const wakePromises = [...runningPromises];
3444
- if (runningStopControlPromise) {
3445
- wakePromises.push(runningStopControlPromise);
3736
+ if (runningControlPromise) {
3737
+ wakePromises.push(runningControlPromise);
3446
3738
  }
3447
3739
  if (wakePromises.length === 0) {
3448
3740
  await sleepMs(pollMs);
@@ -3661,10 +3953,13 @@ var TasksManager = class _TasksManager {
3661
3953
  // Annotate the CommonJS export names for ESM import in node:
3662
3954
  0 && (module.exports = {
3663
3955
  AbstractTask,
3956
+ LOGGER_RUNTIME_KEYS,
3957
+ LOOP_RUNTIME_KEYS,
3664
3958
  SERVICE_TASK_NAMES,
3665
3959
  TaskGetLogs,
3666
3960
  TaskPing,
3667
3961
  TaskSampleProcess,
3962
+ TaskSetRuntimeParam,
3668
3963
  TaskShellCommand,
3669
3964
  TaskStopRunner,
3670
3965
  TaskSumAB,
@@ -3672,11 +3967,16 @@ var TasksManager = class _TasksManager {
3672
3967
  TasksManager,
3673
3968
  TasksRegistry,
3674
3969
  appendTaskIpcLog,
3970
+ applyRuntimeParam,
3971
+ applyRuntimePatch,
3972
+ coerceRuntimeValue,
3973
+ controlLaneTaskNames,
3675
3974
  convertPattern,
3676
3975
  defaultTasksRegistry,
3677
3976
  enqueueStopTask,
3678
3977
  enqueueTask,
3679
3978
  ensureTaskTables,
3979
+ ensureTasksRuntime,
3680
3980
  flushTaskIpcLogs,
3681
3981
  ipcFileLogsTableNameForSourceResource,
3682
3982
  listAliveRunnerHeartbeats,
@@ -3686,6 +3986,7 @@ var TasksManager = class _TasksManager {
3686
3986
  nextTimeMatch,
3687
3987
  normalizeAllowedTasks,
3688
3988
  queueToTableNames,
3989
+ readLoopRuntime,
3689
3990
  readTaskIpcLogsSnapshot,
3690
3991
  registerInServicesRegistry,
3691
3992
  registerRunnerHeartbeat,