@nmakarov/cli-toolkit 0.72.0 → 0.75.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
@@ -5449,6 +5449,7 @@ function setup(opts = {}) {
5449
5449
  const partialContext = {
5450
5450
  emitter: new EventEmitter(),
5451
5451
  isStop: () => false,
5452
+ isKill: () => false,
5452
5453
  cleanupFunctions: [],
5453
5454
  registerCleanup: (fn) => {
5454
5455
  partialContext.cleanupFunctions.push(fn);
@@ -5470,6 +5471,7 @@ function setup(opts = {}) {
5470
5471
  logger,
5471
5472
  emitter: partialContext.emitter,
5472
5473
  isStop: partialContext.isStop,
5474
+ isKill: partialContext.isKill,
5473
5475
  cleanupFunctions: partialContext.cleanupFunctions,
5474
5476
  registerCleanup: partialContext.registerCleanup,
5475
5477
  // For long-running scripts (servers): with --showUsedParams=top, print
@@ -7683,6 +7685,8 @@ var AbstractTask = class _AbstractTask {
7683
7685
  constructor(context, task) {
7684
7686
  this.context = context;
7685
7687
  this.task = task;
7688
+ this._stopRequested = false;
7689
+ this._pauseRequested = false;
7686
7690
  }
7687
7691
  /**
7688
7692
  * Return a short reason string when the task should be deferred (e.g. "locked
@@ -7700,6 +7704,26 @@ var AbstractTask = class _AbstractTask {
7700
7704
  * @param {number} [_allowanceMs] Grace period the runner promises before hard exit.
7701
7705
  */
7702
7706
  requestStop(_allowanceMs) {
7707
+ this._stopRequested = true;
7708
+ }
7709
+ /**
7710
+ * Cooperative pause signal (from `pauseTask` control-lane task). Long-running
7711
+ * tasks should finish the current unit of work, persist a checkpoint, and
7712
+ * return `{ success: true, results: { taskPaused: true, … } }` so the runner
7713
+ * keeps the row as `status=paused` instead of deleting it.
7714
+ *
7715
+ * @param {number} [_allowanceMs]
7716
+ */
7717
+ requestPause(_allowanceMs) {
7718
+ this._pauseRequested = true;
7719
+ }
7720
+ /** @returns {boolean} */
7721
+ isStopRequested() {
7722
+ return this._stopRequested === true;
7723
+ }
7724
+ /** @returns {boolean} */
7725
+ isPauseRequested() {
7726
+ return this._pauseRequested === true;
7703
7727
  }
7704
7728
  /**
7705
7729
  * Perform the task. Must be implemented by subclasses.
@@ -8406,6 +8430,8 @@ var CONTROL_LANE_TASK_NAMES = [
8406
8430
  "pause",
8407
8431
  "unpauseRunner",
8408
8432
  "unpause",
8433
+ "pauseTask",
8434
+ "resumeTask",
8409
8435
  "setRuntimeParam",
8410
8436
  "setRunnerParam"
8411
8437
  ];
@@ -8610,6 +8636,190 @@ var TaskUnpauseRunner = class extends AbstractTask {
8610
8636
  }
8611
8637
  };
8612
8638
 
8639
+ // src/tasks/coreTasks/TaskPauseTask.js
8640
+ function parseProgressObject(progress) {
8641
+ if (progress == null || progress === "") return {};
8642
+ if (typeof progress === "object" && !Array.isArray(progress)) {
8643
+ return { ...progress };
8644
+ }
8645
+ if (typeof progress === "string") {
8646
+ const t = progress.trim();
8647
+ if (t.startsWith("{") || t.startsWith("[")) {
8648
+ try {
8649
+ const parsed = JSON.parse(t);
8650
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return { ...parsed };
8651
+ } catch {
8652
+ }
8653
+ }
8654
+ return { message: progress };
8655
+ }
8656
+ return { message: String(progress) };
8657
+ }
8658
+ async function applyPauseTask(context, taskId) {
8659
+ const db = context.db;
8660
+ if (!db) throw new Error("pauseTask: context.db is required");
8661
+ const id = String(taskId ?? "").trim();
8662
+ if (!id) throw new ParamError('pauseTask: param "taskId" is required');
8663
+ const { tasksTable } = queueToTableNames(context.tasksQueueName ?? "tasks");
8664
+ const row = await db(tasksTable).where({ id }).first();
8665
+ if (!row) {
8666
+ return { success: false, results: { error: `Task ${id} not found`, taskId: id } };
8667
+ }
8668
+ if (row.status === "paused") {
8669
+ return {
8670
+ success: true,
8671
+ results: { taskId: id, status: "paused", message: "Task already paused" }
8672
+ };
8673
+ }
8674
+ if (row.status === "idle") {
8675
+ const progress = parseProgressObject(row.progress);
8676
+ progress.pauseRequested = true;
8677
+ progress.pausedAt = (/* @__PURE__ */ new Date()).toISOString();
8678
+ await db(tasksTable).where({ id }).update({
8679
+ status: "paused",
8680
+ status_changed_at: db.fn.now(),
8681
+ progress: toJsonColumn(progress)
8682
+ });
8683
+ context.logger?.warn?.(`[pauseTask] idle task ${id} (${row.name}) \u2192 paused`);
8684
+ return {
8685
+ success: true,
8686
+ results: { taskId: id, status: "paused", message: "Idle task paused" }
8687
+ };
8688
+ }
8689
+ if (row.status === "running") {
8690
+ const progress = parseProgressObject(row.progress);
8691
+ progress.pauseRequested = true;
8692
+ progress.pauseRequestedAt = (/* @__PURE__ */ new Date()).toISOString();
8693
+ await db(tasksTable).where({ id }).update({
8694
+ progress: toJsonColumn(progress)
8695
+ });
8696
+ const inst = context.runningTaskInstances?.get?.(id);
8697
+ if (inst && typeof inst.requestPause === "function") {
8698
+ await inst.requestPause();
8699
+ context.logger?.warn?.(
8700
+ `[pauseTask] signaled running task ${id} (${row.name}) to pause after current unit`
8701
+ );
8702
+ return {
8703
+ success: true,
8704
+ results: {
8705
+ taskId: id,
8706
+ status: "running",
8707
+ signaled: true,
8708
+ message: "Pause requested; task will finish current unit then park as paused"
8709
+ }
8710
+ };
8711
+ }
8712
+ context.logger?.warn?.(
8713
+ `[pauseTask] stamped pauseRequested on ${id} (${row.name}) \u2014 not running in this process`
8714
+ );
8715
+ return {
8716
+ success: true,
8717
+ results: {
8718
+ taskId: id,
8719
+ status: "running",
8720
+ signaled: false,
8721
+ message: "pauseRequested stamped on task row; target runner must observe it (enqueue pauseTask on that service)"
8722
+ }
8723
+ };
8724
+ }
8725
+ return {
8726
+ success: false,
8727
+ results: {
8728
+ error: `Cannot pause task in status "${row.status}"`,
8729
+ taskId: id,
8730
+ status: row.status
8731
+ }
8732
+ };
8733
+ }
8734
+ async function applyResumeTask(context, taskId) {
8735
+ const db = context.db;
8736
+ if (!db) throw new Error("resumeTask: context.db is required");
8737
+ const id = String(taskId ?? "").trim();
8738
+ if (!id) throw new ParamError('resumeTask: param "taskId" is required');
8739
+ const { tasksTable } = queueToTableNames(context.tasksQueueName ?? "tasks");
8740
+ const row = await db(tasksTable).where({ id }).first();
8741
+ if (!row) {
8742
+ return { success: false, results: { error: `Task ${id} not found`, taskId: id } };
8743
+ }
8744
+ if (row.status === "idle") {
8745
+ return {
8746
+ success: true,
8747
+ results: { taskId: id, status: "idle", message: "Task already idle (runnable)" }
8748
+ };
8749
+ }
8750
+ if (row.status !== "paused") {
8751
+ return {
8752
+ success: false,
8753
+ results: {
8754
+ error: `Cannot resume task in status "${row.status}" (expected paused)`,
8755
+ taskId: id,
8756
+ status: row.status
8757
+ }
8758
+ };
8759
+ }
8760
+ const progress = parseProgressObject(row.progress);
8761
+ delete progress.pauseRequested;
8762
+ delete progress.pauseRequestedAt;
8763
+ delete progress.pausedAt;
8764
+ progress.resumedAt = (/* @__PURE__ */ new Date()).toISOString();
8765
+ await db(tasksTable).where({ id }).update({
8766
+ status: "idle",
8767
+ status_changed_at: db.fn.now(),
8768
+ started_at: null,
8769
+ completed_at: null,
8770
+ success: null,
8771
+ progress: toJsonColumn(progress),
8772
+ // Clear claim binding so any matching worker can pick it up.
8773
+ service_name: null,
8774
+ server_name: null,
8775
+ instance_number: null,
8776
+ next_run_at: null
8777
+ });
8778
+ context.logger?.warn?.(`[resumeTask] paused task ${id} (${row.name}) \u2192 idle`);
8779
+ return {
8780
+ success: true,
8781
+ results: { taskId: id, status: "idle", message: "Task resumed (idle; will be claimed)" }
8782
+ };
8783
+ }
8784
+ var TaskPauseTask = class extends AbstractTask {
8785
+ static taskName = "pauseTask";
8786
+ static description = "Pause a specific task: finish current unit, persist status=paused";
8787
+ static defaultWaitForResult = true;
8788
+ static async resolveCustomParams(context, overrides = {}) {
8789
+ const merged = AbstractTask._mergeTypedParams(
8790
+ context,
8791
+ "task-pause-task",
8792
+ { taskId: "string" },
8793
+ overrides
8794
+ );
8795
+ const taskId = typeof merged.taskId === "string" ? merged.taskId.trim() : "";
8796
+ if (!taskId) throw new ParamError('pauseTask: param "taskId" is required');
8797
+ return { taskId };
8798
+ }
8799
+ async run() {
8800
+ return applyPauseTask(this.context, this.task?.params?.taskId);
8801
+ }
8802
+ };
8803
+ var TaskResumeTask = class extends AbstractTask {
8804
+ static taskName = "resumeTask";
8805
+ static description = "Resume a paused task (status paused \u2192 idle)";
8806
+ static defaultWaitForResult = true;
8807
+ static async resolveCustomParams(context, overrides = {}) {
8808
+ const merged = AbstractTask._mergeTypedParams(
8809
+ context,
8810
+ "task-resume-task",
8811
+ { taskId: "string" },
8812
+ overrides
8813
+ );
8814
+ const taskId = typeof merged.taskId === "string" ? merged.taskId.trim() : "";
8815
+ if (!taskId) throw new ParamError('resumeTask: param "taskId" is required');
8816
+ return { taskId };
8817
+ }
8818
+ async run() {
8819
+ return applyResumeTask(this.context, this.task?.params?.taskId);
8820
+ }
8821
+ };
8822
+
8613
8823
  // src/tasks/coreTasks/TaskGetLogs.js
8614
8824
  var TaskGetLogs = class extends AbstractTask {
8615
8825
  /**
@@ -8828,7 +9038,7 @@ var TasksRegistry = class _TasksRegistry {
8828
9038
  * @returns {TasksRegistry}
8829
9039
  */
8830
9040
  static withCoreTasks() {
8831
- 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);
9041
+ 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);
8832
9042
  }
8833
9043
  /**
8834
9044
  * Register a single task class under a name. Overwrites any previous entry.
@@ -8935,6 +9145,8 @@ var SERVICE_TASK_NAMES = [
8935
9145
  "pauseRunner",
8936
9146
  "unpause",
8937
9147
  "unpauseRunner",
9148
+ "pauseTask",
9149
+ "resumeTask",
8938
9150
  "shellCommand",
8939
9151
  "systemInfo",
8940
9152
  "info",
@@ -9230,11 +9442,12 @@ async function executeClaimedTask(context, tasksTable, historyTable, row, regist
9230
9442
  } finally {
9231
9443
  runningTaskInstances.delete(row.id);
9232
9444
  }
9445
+ const taskPaused = success && results && typeof results === "object" && results.taskPaused === true;
9233
9446
  await db(historyTable).insert(
9234
9447
  taskHistoryInsertFromQueueRow(row, {
9235
9448
  completed_at: /* @__PURE__ */ new Date(),
9236
9449
  success,
9237
- status: success ? "completed" : "failed",
9450
+ status: taskPaused ? "paused" : success ? "completed" : "failed",
9238
9451
  status_changed_at: db.fn.now(),
9239
9452
  params: toJsonColumn(row.params),
9240
9453
  results: toJsonColumn(results)
@@ -9257,7 +9470,24 @@ async function executeClaimedTask(context, tasksTable, historyTable, row, regist
9257
9470
  details: results
9258
9471
  });
9259
9472
  }
9260
- if (row.schedule) {
9473
+ if (taskPaused) {
9474
+ const checkpointParams = results.checkpointParams && typeof results.checkpointParams === "object" ? results.checkpointParams : row.params;
9475
+ const progressPayload = results.progress != null ? results.progress : { paused: true, pausedAt: (/* @__PURE__ */ new Date()).toISOString(), message: "paused" };
9476
+ await db(tasksTable).where({ id: row.id }).update({
9477
+ started_at: null,
9478
+ completed_at: /* @__PURE__ */ new Date(),
9479
+ success: true,
9480
+ results: toJsonColumn(results),
9481
+ params: toJsonColumn(checkpointParams),
9482
+ progress: toJsonColumn(progressPayload),
9483
+ status: "paused",
9484
+ status_changed_at: db.fn.now(),
9485
+ past_due: null,
9486
+ service_name: null,
9487
+ server_name: null,
9488
+ instance_number: null
9489
+ });
9490
+ } else if (row.schedule) {
9261
9491
  if (success) {
9262
9492
  let nextRunAt = null;
9263
9493
  try {
@@ -9385,6 +9615,7 @@ async function runTasksLoop(context, options) {
9385
9615
  }
9386
9616
  const runningPromises = /* @__PURE__ */ new Set();
9387
9617
  const runningTaskInstances = /* @__PURE__ */ new Map();
9618
+ context.runningTaskInstances = runningTaskInstances;
9388
9619
  let runningControlPromise = null;
9389
9620
  let stopRequested = false;
9390
9621
  let stopAllowanceMs = Number(context.stopAllowanceMs) > 0 ? Number(context.stopAllowanceMs) : 6e4;
@@ -9772,7 +10003,9 @@ export {
9772
10003
  ScrollableText,
9773
10004
  TaskGetLogs,
9774
10005
  TaskPauseRunner,
10006
+ TaskPauseTask,
9775
10007
  TaskPing,
10008
+ TaskResumeTask,
9776
10009
  TaskSampleProcess,
9777
10010
  TaskSetRuntimeParam,
9778
10011
  TaskShellCommand,
@@ -9787,6 +10020,8 @@ export {
9787
10020
  activateRelease,
9788
10021
  appendDeployLog,
9789
10022
  appendTaskIpcLog,
10023
+ applyPauseTask,
10024
+ applyResumeTask,
9790
10025
  applyRunnerPaused,
9791
10026
  applyRuntimeParam,
9792
10027
  applyRuntimePatch,