@nmakarov/cli-toolkit 0.72.0 → 0.73.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
@@ -1348,7 +1348,9 @@ __export(src_exports, {
1348
1348
  ScrollableText: () => ScrollableText,
1349
1349
  TaskGetLogs: () => TaskGetLogs,
1350
1350
  TaskPauseRunner: () => TaskPauseRunner,
1351
+ TaskPauseTask: () => TaskPauseTask,
1351
1352
  TaskPing: () => TaskPing,
1353
+ TaskResumeTask: () => TaskResumeTask,
1352
1354
  TaskSampleProcess: () => TaskSampleProcess,
1353
1355
  TaskSetRuntimeParam: () => TaskSetRuntimeParam,
1354
1356
  TaskShellCommand: () => TaskShellCommand,
@@ -1363,6 +1365,8 @@ __export(src_exports, {
1363
1365
  activateRelease: () => activateRelease,
1364
1366
  appendDeployLog: () => appendDeployLog,
1365
1367
  appendTaskIpcLog: () => appendTaskIpcLog,
1368
+ applyPauseTask: () => applyPauseTask,
1369
+ applyResumeTask: () => applyResumeTask,
1366
1370
  applyRunnerPaused: () => applyRunnerPaused,
1367
1371
  applyRuntimeParam: () => applyRuntimeParam,
1368
1372
  applyRuntimePatch: () => applyRuntimePatch,
@@ -7864,6 +7868,8 @@ var AbstractTask = class _AbstractTask {
7864
7868
  constructor(context, task) {
7865
7869
  this.context = context;
7866
7870
  this.task = task;
7871
+ this._stopRequested = false;
7872
+ this._pauseRequested = false;
7867
7873
  }
7868
7874
  /**
7869
7875
  * Return a short reason string when the task should be deferred (e.g. "locked
@@ -7881,6 +7887,26 @@ var AbstractTask = class _AbstractTask {
7881
7887
  * @param {number} [_allowanceMs] Grace period the runner promises before hard exit.
7882
7888
  */
7883
7889
  requestStop(_allowanceMs) {
7890
+ this._stopRequested = true;
7891
+ }
7892
+ /**
7893
+ * Cooperative pause signal (from `pauseTask` control-lane task). Long-running
7894
+ * tasks should finish the current unit of work, persist a checkpoint, and
7895
+ * return `{ success: true, results: { taskPaused: true, … } }` so the runner
7896
+ * keeps the row as `status=paused` instead of deleting it.
7897
+ *
7898
+ * @param {number} [_allowanceMs]
7899
+ */
7900
+ requestPause(_allowanceMs) {
7901
+ this._pauseRequested = true;
7902
+ }
7903
+ /** @returns {boolean} */
7904
+ isStopRequested() {
7905
+ return this._stopRequested === true;
7906
+ }
7907
+ /** @returns {boolean} */
7908
+ isPauseRequested() {
7909
+ return this._pauseRequested === true;
7884
7910
  }
7885
7911
  /**
7886
7912
  * Perform the task. Must be implemented by subclasses.
@@ -8587,6 +8613,8 @@ var CONTROL_LANE_TASK_NAMES = [
8587
8613
  "pause",
8588
8614
  "unpauseRunner",
8589
8615
  "unpause",
8616
+ "pauseTask",
8617
+ "resumeTask",
8590
8618
  "setRuntimeParam",
8591
8619
  "setRunnerParam"
8592
8620
  ];
@@ -8791,6 +8819,190 @@ var TaskUnpauseRunner = class extends AbstractTask {
8791
8819
  }
8792
8820
  };
8793
8821
 
8822
+ // src/tasks/coreTasks/TaskPauseTask.js
8823
+ function parseProgressObject(progress) {
8824
+ if (progress == null || progress === "") return {};
8825
+ if (typeof progress === "object" && !Array.isArray(progress)) {
8826
+ return { ...progress };
8827
+ }
8828
+ if (typeof progress === "string") {
8829
+ const t = progress.trim();
8830
+ if (t.startsWith("{") || t.startsWith("[")) {
8831
+ try {
8832
+ const parsed = JSON.parse(t);
8833
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return { ...parsed };
8834
+ } catch {
8835
+ }
8836
+ }
8837
+ return { message: progress };
8838
+ }
8839
+ return { message: String(progress) };
8840
+ }
8841
+ async function applyPauseTask(context, taskId) {
8842
+ const db = context.db;
8843
+ if (!db) throw new Error("pauseTask: context.db is required");
8844
+ const id = String(taskId ?? "").trim();
8845
+ if (!id) throw new ParamError('pauseTask: param "taskId" is required');
8846
+ const { tasksTable } = queueToTableNames(context.tasksQueueName ?? "tasks");
8847
+ const row = await db(tasksTable).where({ id }).first();
8848
+ if (!row) {
8849
+ return { success: false, results: { error: `Task ${id} not found`, taskId: id } };
8850
+ }
8851
+ if (row.status === "paused") {
8852
+ return {
8853
+ success: true,
8854
+ results: { taskId: id, status: "paused", message: "Task already paused" }
8855
+ };
8856
+ }
8857
+ if (row.status === "idle") {
8858
+ const progress = parseProgressObject(row.progress);
8859
+ progress.pauseRequested = true;
8860
+ progress.pausedAt = (/* @__PURE__ */ new Date()).toISOString();
8861
+ await db(tasksTable).where({ id }).update({
8862
+ status: "paused",
8863
+ status_changed_at: db.fn.now(),
8864
+ progress: toJsonColumn(progress)
8865
+ });
8866
+ context.logger?.warn?.(`[pauseTask] idle task ${id} (${row.name}) \u2192 paused`);
8867
+ return {
8868
+ success: true,
8869
+ results: { taskId: id, status: "paused", message: "Idle task paused" }
8870
+ };
8871
+ }
8872
+ if (row.status === "running") {
8873
+ const progress = parseProgressObject(row.progress);
8874
+ progress.pauseRequested = true;
8875
+ progress.pauseRequestedAt = (/* @__PURE__ */ new Date()).toISOString();
8876
+ await db(tasksTable).where({ id }).update({
8877
+ progress: toJsonColumn(progress)
8878
+ });
8879
+ const inst = context.runningTaskInstances?.get?.(id);
8880
+ if (inst && typeof inst.requestPause === "function") {
8881
+ await inst.requestPause();
8882
+ context.logger?.warn?.(
8883
+ `[pauseTask] signaled running task ${id} (${row.name}) to pause after current unit`
8884
+ );
8885
+ return {
8886
+ success: true,
8887
+ results: {
8888
+ taskId: id,
8889
+ status: "running",
8890
+ signaled: true,
8891
+ message: "Pause requested; task will finish current unit then park as paused"
8892
+ }
8893
+ };
8894
+ }
8895
+ context.logger?.warn?.(
8896
+ `[pauseTask] stamped pauseRequested on ${id} (${row.name}) \u2014 not running in this process`
8897
+ );
8898
+ return {
8899
+ success: true,
8900
+ results: {
8901
+ taskId: id,
8902
+ status: "running",
8903
+ signaled: false,
8904
+ message: "pauseRequested stamped on task row; target runner must observe it (enqueue pauseTask on that service)"
8905
+ }
8906
+ };
8907
+ }
8908
+ return {
8909
+ success: false,
8910
+ results: {
8911
+ error: `Cannot pause task in status "${row.status}"`,
8912
+ taskId: id,
8913
+ status: row.status
8914
+ }
8915
+ };
8916
+ }
8917
+ async function applyResumeTask(context, taskId) {
8918
+ const db = context.db;
8919
+ if (!db) throw new Error("resumeTask: context.db is required");
8920
+ const id = String(taskId ?? "").trim();
8921
+ if (!id) throw new ParamError('resumeTask: param "taskId" is required');
8922
+ const { tasksTable } = queueToTableNames(context.tasksQueueName ?? "tasks");
8923
+ const row = await db(tasksTable).where({ id }).first();
8924
+ if (!row) {
8925
+ return { success: false, results: { error: `Task ${id} not found`, taskId: id } };
8926
+ }
8927
+ if (row.status === "idle") {
8928
+ return {
8929
+ success: true,
8930
+ results: { taskId: id, status: "idle", message: "Task already idle (runnable)" }
8931
+ };
8932
+ }
8933
+ if (row.status !== "paused") {
8934
+ return {
8935
+ success: false,
8936
+ results: {
8937
+ error: `Cannot resume task in status "${row.status}" (expected paused)`,
8938
+ taskId: id,
8939
+ status: row.status
8940
+ }
8941
+ };
8942
+ }
8943
+ const progress = parseProgressObject(row.progress);
8944
+ delete progress.pauseRequested;
8945
+ delete progress.pauseRequestedAt;
8946
+ delete progress.pausedAt;
8947
+ progress.resumedAt = (/* @__PURE__ */ new Date()).toISOString();
8948
+ await db(tasksTable).where({ id }).update({
8949
+ status: "idle",
8950
+ status_changed_at: db.fn.now(),
8951
+ started_at: null,
8952
+ completed_at: null,
8953
+ success: null,
8954
+ progress: toJsonColumn(progress),
8955
+ // Clear claim binding so any matching worker can pick it up.
8956
+ service_name: null,
8957
+ server_name: null,
8958
+ instance_number: null,
8959
+ next_run_at: null
8960
+ });
8961
+ context.logger?.warn?.(`[resumeTask] paused task ${id} (${row.name}) \u2192 idle`);
8962
+ return {
8963
+ success: true,
8964
+ results: { taskId: id, status: "idle", message: "Task resumed (idle; will be claimed)" }
8965
+ };
8966
+ }
8967
+ var TaskPauseTask = class extends AbstractTask {
8968
+ static taskName = "pauseTask";
8969
+ static description = "Pause a specific task: finish current unit, persist status=paused";
8970
+ static defaultWaitForResult = true;
8971
+ static async resolveCustomParams(context, overrides = {}) {
8972
+ const merged = AbstractTask._mergeTypedParams(
8973
+ context,
8974
+ "task-pause-task",
8975
+ { taskId: "string" },
8976
+ overrides
8977
+ );
8978
+ const taskId = typeof merged.taskId === "string" ? merged.taskId.trim() : "";
8979
+ if (!taskId) throw new ParamError('pauseTask: param "taskId" is required');
8980
+ return { taskId };
8981
+ }
8982
+ async run() {
8983
+ return applyPauseTask(this.context, this.task?.params?.taskId);
8984
+ }
8985
+ };
8986
+ var TaskResumeTask = class extends AbstractTask {
8987
+ static taskName = "resumeTask";
8988
+ static description = "Resume a paused task (status paused \u2192 idle)";
8989
+ static defaultWaitForResult = true;
8990
+ static async resolveCustomParams(context, overrides = {}) {
8991
+ const merged = AbstractTask._mergeTypedParams(
8992
+ context,
8993
+ "task-resume-task",
8994
+ { taskId: "string" },
8995
+ overrides
8996
+ );
8997
+ const taskId = typeof merged.taskId === "string" ? merged.taskId.trim() : "";
8998
+ if (!taskId) throw new ParamError('resumeTask: param "taskId" is required');
8999
+ return { taskId };
9000
+ }
9001
+ async run() {
9002
+ return applyResumeTask(this.context, this.task?.params?.taskId);
9003
+ }
9004
+ };
9005
+
8794
9006
  // src/tasks/coreTasks/TaskGetLogs.js
8795
9007
  var TaskGetLogs = class extends AbstractTask {
8796
9008
  /**
@@ -9009,7 +9221,7 @@ var TasksRegistry = class _TasksRegistry {
9009
9221
  * @returns {TasksRegistry}
9010
9222
  */
9011
9223
  static withCoreTasks() {
9012
- 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("pauseRunner", TaskPauseRunner).add("pause", TaskPauseRunner).add("unpauseRunner", TaskUnpauseRunner).add("unpause", TaskUnpauseRunner).add("getLogs", TaskGetLogs).add("setRuntimeParam", TaskSetRuntimeParam).add("setRunnerParam", TaskSetRuntimeParam);
9224
+ 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("pauseRunner", TaskPauseRunner).add("pause", TaskPauseRunner).add("unpauseRunner", TaskUnpauseRunner).add("unpause", TaskUnpauseRunner).add("pauseTask", TaskPauseTask).add("resumeTask", TaskResumeTask).add("getLogs", TaskGetLogs).add("setRuntimeParam", TaskSetRuntimeParam).add("setRunnerParam", TaskSetRuntimeParam);
9013
9225
  }
9014
9226
  /**
9015
9227
  * Register a single task class under a name. Overwrites any previous entry.
@@ -9116,6 +9328,8 @@ var SERVICE_TASK_NAMES = [
9116
9328
  "pauseRunner",
9117
9329
  "unpause",
9118
9330
  "unpauseRunner",
9331
+ "pauseTask",
9332
+ "resumeTask",
9119
9333
  "shellCommand",
9120
9334
  "systemInfo",
9121
9335
  "info",
@@ -9411,11 +9625,12 @@ async function executeClaimedTask(context, tasksTable, historyTable, row, regist
9411
9625
  } finally {
9412
9626
  runningTaskInstances.delete(row.id);
9413
9627
  }
9628
+ const taskPaused = success && results && typeof results === "object" && results.taskPaused === true;
9414
9629
  await db(historyTable).insert(
9415
9630
  taskHistoryInsertFromQueueRow(row, {
9416
9631
  completed_at: /* @__PURE__ */ new Date(),
9417
9632
  success,
9418
- status: success ? "completed" : "failed",
9633
+ status: taskPaused ? "paused" : success ? "completed" : "failed",
9419
9634
  status_changed_at: db.fn.now(),
9420
9635
  params: toJsonColumn(row.params),
9421
9636
  results: toJsonColumn(results)
@@ -9438,7 +9653,24 @@ async function executeClaimedTask(context, tasksTable, historyTable, row, regist
9438
9653
  details: results
9439
9654
  });
9440
9655
  }
9441
- if (row.schedule) {
9656
+ if (taskPaused) {
9657
+ const checkpointParams = results.checkpointParams && typeof results.checkpointParams === "object" ? results.checkpointParams : row.params;
9658
+ const progressPayload = results.progress != null ? results.progress : { paused: true, pausedAt: (/* @__PURE__ */ new Date()).toISOString(), message: "paused" };
9659
+ await db(tasksTable).where({ id: row.id }).update({
9660
+ started_at: null,
9661
+ completed_at: /* @__PURE__ */ new Date(),
9662
+ success: true,
9663
+ results: toJsonColumn(results),
9664
+ params: toJsonColumn(checkpointParams),
9665
+ progress: toJsonColumn(progressPayload),
9666
+ status: "paused",
9667
+ status_changed_at: db.fn.now(),
9668
+ past_due: null,
9669
+ service_name: null,
9670
+ server_name: null,
9671
+ instance_number: null
9672
+ });
9673
+ } else if (row.schedule) {
9442
9674
  if (success) {
9443
9675
  let nextRunAt = null;
9444
9676
  try {
@@ -9566,6 +9798,7 @@ async function runTasksLoop(context, options) {
9566
9798
  }
9567
9799
  const runningPromises = /* @__PURE__ */ new Set();
9568
9800
  const runningTaskInstances = /* @__PURE__ */ new Map();
9801
+ context.runningTaskInstances = runningTaskInstances;
9569
9802
  let runningControlPromise = null;
9570
9803
  let stopRequested = false;
9571
9804
  let stopAllowanceMs = Number(context.stopAllowanceMs) > 0 ? Number(context.stopAllowanceMs) : 6e4;
@@ -9954,7 +10187,9 @@ var TasksManager = class _TasksManager {
9954
10187
  ScrollableText,
9955
10188
  TaskGetLogs,
9956
10189
  TaskPauseRunner,
10190
+ TaskPauseTask,
9957
10191
  TaskPing,
10192
+ TaskResumeTask,
9958
10193
  TaskSampleProcess,
9959
10194
  TaskSetRuntimeParam,
9960
10195
  TaskShellCommand,
@@ -9969,6 +10204,8 @@ var TasksManager = class _TasksManager {
9969
10204
  activateRelease,
9970
10205
  appendDeployLog,
9971
10206
  appendTaskIpcLog,
10207
+ applyPauseTask,
10208
+ applyResumeTask,
9972
10209
  applyRunnerPaused,
9973
10210
  applyRuntimeParam,
9974
10211
  applyRuntimePatch,