@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/cli-runner.cjs +243 -4
- package/dist/cli-runner.cjs.map +1 -1
- package/dist/cli-runner.js +243 -4
- package/dist/cli-runner.js.map +1 -1
- package/dist/index.cjs +242 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +238 -3
- package/dist/index.js.map +1 -1
- package/dist/init.cjs +13 -1
- package/dist/init.cjs.map +1 -1
- package/dist/init.js +13 -1
- package/dist/init.js.map +1 -1
- package/dist/tasks.cjs +240 -3
- package/dist/tasks.cjs.map +1 -1
- package/dist/tasks.js +236 -3
- package/dist/tasks.js.map +1 -1
- package/package.json +1 -1
package/dist/cli-runner.js
CHANGED
|
@@ -2794,6 +2794,7 @@ function setup(opts = {}) {
|
|
|
2794
2794
|
const partialContext = {
|
|
2795
2795
|
emitter: new EventEmitter(),
|
|
2796
2796
|
isStop: () => false,
|
|
2797
|
+
isKill: () => false,
|
|
2797
2798
|
cleanupFunctions: [],
|
|
2798
2799
|
registerCleanup: (fn) => {
|
|
2799
2800
|
partialContext.cleanupFunctions.push(fn);
|
|
@@ -2815,6 +2816,7 @@ function setup(opts = {}) {
|
|
|
2815
2816
|
logger,
|
|
2816
2817
|
emitter: partialContext.emitter,
|
|
2817
2818
|
isStop: partialContext.isStop,
|
|
2819
|
+
isKill: partialContext.isKill,
|
|
2818
2820
|
cleanupFunctions: partialContext.cleanupFunctions,
|
|
2819
2821
|
registerCleanup: partialContext.registerCleanup,
|
|
2820
2822
|
// For long-running scripts (servers): with --showUsedParams=top, print
|
|
@@ -2864,6 +2866,7 @@ function printAllParameters(context) {
|
|
|
2864
2866
|
}
|
|
2865
2867
|
async function init(flow2, opts = {}) {
|
|
2866
2868
|
let stop = false;
|
|
2869
|
+
let kill = false;
|
|
2867
2870
|
let context = null;
|
|
2868
2871
|
let cleanupRan = false;
|
|
2869
2872
|
const runRegisteredCleanups = async (ctx) => {
|
|
@@ -2893,6 +2896,7 @@ async function init(flow2, opts = {}) {
|
|
|
2893
2896
|
}
|
|
2894
2897
|
context = setup(opts);
|
|
2895
2898
|
context.isStop = () => stop;
|
|
2899
|
+
context.isKill = () => kill;
|
|
2896
2900
|
context = await setupModules(context, opts);
|
|
2897
2901
|
const stopAfter = context.args.get("stopAfter");
|
|
2898
2902
|
const stopAllowanceSec = Number(context.params.get("stopAllowance", "number default 60"));
|
|
@@ -2931,6 +2935,14 @@ async function init(flow2, opts = {}) {
|
|
|
2931
2935
|
);
|
|
2932
2936
|
context.emitter.emit("stop", stopAllowanceMs);
|
|
2933
2937
|
});
|
|
2938
|
+
process.on("SIGUSR2", () => {
|
|
2939
|
+
if (!context || kill) return;
|
|
2940
|
+
kill = true;
|
|
2941
|
+
stop = true;
|
|
2942
|
+
context.logger.warn(">> SIGUSR2: emitting kill (urgent stop)");
|
|
2943
|
+
context.emitter.emit("kill");
|
|
2944
|
+
context.emitter.emit("stop", stopAllowanceMs);
|
|
2945
|
+
});
|
|
2934
2946
|
await flow2(context);
|
|
2935
2947
|
} catch (error) {
|
|
2936
2948
|
const errorLocation = error instanceof Error && error.stack ? error.stack.split("\n")[1]?.trim() || "Unknown location" : "Unknown location";
|
|
@@ -2957,7 +2969,7 @@ async function init(flow2, opts = {}) {
|
|
|
2957
2969
|
}
|
|
2958
2970
|
if (process.exitCode && process.exitCode !== 0) {
|
|
2959
2971
|
process.exit(process.exitCode);
|
|
2960
|
-
} else if (stop) {
|
|
2972
|
+
} else if (stop || kill) {
|
|
2961
2973
|
process.exit(0);
|
|
2962
2974
|
}
|
|
2963
2975
|
}
|
|
@@ -5855,6 +5867,8 @@ var AbstractTask = class _AbstractTask {
|
|
|
5855
5867
|
constructor(context, task) {
|
|
5856
5868
|
this.context = context;
|
|
5857
5869
|
this.task = task;
|
|
5870
|
+
this._stopRequested = false;
|
|
5871
|
+
this._pauseRequested = false;
|
|
5858
5872
|
}
|
|
5859
5873
|
/**
|
|
5860
5874
|
* Return a short reason string when the task should be deferred (e.g. "locked
|
|
@@ -5872,6 +5886,26 @@ var AbstractTask = class _AbstractTask {
|
|
|
5872
5886
|
* @param {number} [_allowanceMs] Grace period the runner promises before hard exit.
|
|
5873
5887
|
*/
|
|
5874
5888
|
requestStop(_allowanceMs) {
|
|
5889
|
+
this._stopRequested = true;
|
|
5890
|
+
}
|
|
5891
|
+
/**
|
|
5892
|
+
* Cooperative pause signal (from `pauseTask` control-lane task). Long-running
|
|
5893
|
+
* tasks should finish the current unit of work, persist a checkpoint, and
|
|
5894
|
+
* return `{ success: true, results: { taskPaused: true, … } }` so the runner
|
|
5895
|
+
* keeps the row as `status=paused` instead of deleting it.
|
|
5896
|
+
*
|
|
5897
|
+
* @param {number} [_allowanceMs]
|
|
5898
|
+
*/
|
|
5899
|
+
requestPause(_allowanceMs) {
|
|
5900
|
+
this._pauseRequested = true;
|
|
5901
|
+
}
|
|
5902
|
+
/** @returns {boolean} */
|
|
5903
|
+
isStopRequested() {
|
|
5904
|
+
return this._stopRequested === true;
|
|
5905
|
+
}
|
|
5906
|
+
/** @returns {boolean} */
|
|
5907
|
+
isPauseRequested() {
|
|
5908
|
+
return this._pauseRequested === true;
|
|
5875
5909
|
}
|
|
5876
5910
|
/**
|
|
5877
5911
|
* Perform the task. Must be implemented by subclasses.
|
|
@@ -6570,6 +6604,8 @@ var CONTROL_LANE_TASK_NAMES = [
|
|
|
6570
6604
|
"pause",
|
|
6571
6605
|
"unpauseRunner",
|
|
6572
6606
|
"unpause",
|
|
6607
|
+
"pauseTask",
|
|
6608
|
+
"resumeTask",
|
|
6573
6609
|
"setRuntimeParam",
|
|
6574
6610
|
"setRunnerParam"
|
|
6575
6611
|
];
|
|
@@ -6774,6 +6810,190 @@ var TaskUnpauseRunner = class extends AbstractTask {
|
|
|
6774
6810
|
}
|
|
6775
6811
|
};
|
|
6776
6812
|
|
|
6813
|
+
// src/tasks/coreTasks/TaskPauseTask.js
|
|
6814
|
+
function parseProgressObject(progress) {
|
|
6815
|
+
if (progress == null || progress === "") return {};
|
|
6816
|
+
if (typeof progress === "object" && !Array.isArray(progress)) {
|
|
6817
|
+
return { ...progress };
|
|
6818
|
+
}
|
|
6819
|
+
if (typeof progress === "string") {
|
|
6820
|
+
const t = progress.trim();
|
|
6821
|
+
if (t.startsWith("{") || t.startsWith("[")) {
|
|
6822
|
+
try {
|
|
6823
|
+
const parsed = JSON.parse(t);
|
|
6824
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return { ...parsed };
|
|
6825
|
+
} catch {
|
|
6826
|
+
}
|
|
6827
|
+
}
|
|
6828
|
+
return { message: progress };
|
|
6829
|
+
}
|
|
6830
|
+
return { message: String(progress) };
|
|
6831
|
+
}
|
|
6832
|
+
async function applyPauseTask(context, taskId) {
|
|
6833
|
+
const db = context.db;
|
|
6834
|
+
if (!db) throw new Error("pauseTask: context.db is required");
|
|
6835
|
+
const id = String(taskId ?? "").trim();
|
|
6836
|
+
if (!id) throw new ParamError('pauseTask: param "taskId" is required');
|
|
6837
|
+
const { tasksTable } = queueToTableNames(context.tasksQueueName ?? "tasks");
|
|
6838
|
+
const row = await db(tasksTable).where({ id }).first();
|
|
6839
|
+
if (!row) {
|
|
6840
|
+
return { success: false, results: { error: `Task ${id} not found`, taskId: id } };
|
|
6841
|
+
}
|
|
6842
|
+
if (row.status === "paused") {
|
|
6843
|
+
return {
|
|
6844
|
+
success: true,
|
|
6845
|
+
results: { taskId: id, status: "paused", message: "Task already paused" }
|
|
6846
|
+
};
|
|
6847
|
+
}
|
|
6848
|
+
if (row.status === "idle") {
|
|
6849
|
+
const progress = parseProgressObject(row.progress);
|
|
6850
|
+
progress.pauseRequested = true;
|
|
6851
|
+
progress.pausedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
6852
|
+
await db(tasksTable).where({ id }).update({
|
|
6853
|
+
status: "paused",
|
|
6854
|
+
status_changed_at: db.fn.now(),
|
|
6855
|
+
progress: toJsonColumn(progress)
|
|
6856
|
+
});
|
|
6857
|
+
context.logger?.warn?.(`[pauseTask] idle task ${id} (${row.name}) \u2192 paused`);
|
|
6858
|
+
return {
|
|
6859
|
+
success: true,
|
|
6860
|
+
results: { taskId: id, status: "paused", message: "Idle task paused" }
|
|
6861
|
+
};
|
|
6862
|
+
}
|
|
6863
|
+
if (row.status === "running") {
|
|
6864
|
+
const progress = parseProgressObject(row.progress);
|
|
6865
|
+
progress.pauseRequested = true;
|
|
6866
|
+
progress.pauseRequestedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
6867
|
+
await db(tasksTable).where({ id }).update({
|
|
6868
|
+
progress: toJsonColumn(progress)
|
|
6869
|
+
});
|
|
6870
|
+
const inst = context.runningTaskInstances?.get?.(id);
|
|
6871
|
+
if (inst && typeof inst.requestPause === "function") {
|
|
6872
|
+
await inst.requestPause();
|
|
6873
|
+
context.logger?.warn?.(
|
|
6874
|
+
`[pauseTask] signaled running task ${id} (${row.name}) to pause after current unit`
|
|
6875
|
+
);
|
|
6876
|
+
return {
|
|
6877
|
+
success: true,
|
|
6878
|
+
results: {
|
|
6879
|
+
taskId: id,
|
|
6880
|
+
status: "running",
|
|
6881
|
+
signaled: true,
|
|
6882
|
+
message: "Pause requested; task will finish current unit then park as paused"
|
|
6883
|
+
}
|
|
6884
|
+
};
|
|
6885
|
+
}
|
|
6886
|
+
context.logger?.warn?.(
|
|
6887
|
+
`[pauseTask] stamped pauseRequested on ${id} (${row.name}) \u2014 not running in this process`
|
|
6888
|
+
);
|
|
6889
|
+
return {
|
|
6890
|
+
success: true,
|
|
6891
|
+
results: {
|
|
6892
|
+
taskId: id,
|
|
6893
|
+
status: "running",
|
|
6894
|
+
signaled: false,
|
|
6895
|
+
message: "pauseRequested stamped on task row; target runner must observe it (enqueue pauseTask on that service)"
|
|
6896
|
+
}
|
|
6897
|
+
};
|
|
6898
|
+
}
|
|
6899
|
+
return {
|
|
6900
|
+
success: false,
|
|
6901
|
+
results: {
|
|
6902
|
+
error: `Cannot pause task in status "${row.status}"`,
|
|
6903
|
+
taskId: id,
|
|
6904
|
+
status: row.status
|
|
6905
|
+
}
|
|
6906
|
+
};
|
|
6907
|
+
}
|
|
6908
|
+
async function applyResumeTask(context, taskId) {
|
|
6909
|
+
const db = context.db;
|
|
6910
|
+
if (!db) throw new Error("resumeTask: context.db is required");
|
|
6911
|
+
const id = String(taskId ?? "").trim();
|
|
6912
|
+
if (!id) throw new ParamError('resumeTask: param "taskId" is required');
|
|
6913
|
+
const { tasksTable } = queueToTableNames(context.tasksQueueName ?? "tasks");
|
|
6914
|
+
const row = await db(tasksTable).where({ id }).first();
|
|
6915
|
+
if (!row) {
|
|
6916
|
+
return { success: false, results: { error: `Task ${id} not found`, taskId: id } };
|
|
6917
|
+
}
|
|
6918
|
+
if (row.status === "idle") {
|
|
6919
|
+
return {
|
|
6920
|
+
success: true,
|
|
6921
|
+
results: { taskId: id, status: "idle", message: "Task already idle (runnable)" }
|
|
6922
|
+
};
|
|
6923
|
+
}
|
|
6924
|
+
if (row.status !== "paused") {
|
|
6925
|
+
return {
|
|
6926
|
+
success: false,
|
|
6927
|
+
results: {
|
|
6928
|
+
error: `Cannot resume task in status "${row.status}" (expected paused)`,
|
|
6929
|
+
taskId: id,
|
|
6930
|
+
status: row.status
|
|
6931
|
+
}
|
|
6932
|
+
};
|
|
6933
|
+
}
|
|
6934
|
+
const progress = parseProgressObject(row.progress);
|
|
6935
|
+
delete progress.pauseRequested;
|
|
6936
|
+
delete progress.pauseRequestedAt;
|
|
6937
|
+
delete progress.pausedAt;
|
|
6938
|
+
progress.resumedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
6939
|
+
await db(tasksTable).where({ id }).update({
|
|
6940
|
+
status: "idle",
|
|
6941
|
+
status_changed_at: db.fn.now(),
|
|
6942
|
+
started_at: null,
|
|
6943
|
+
completed_at: null,
|
|
6944
|
+
success: null,
|
|
6945
|
+
progress: toJsonColumn(progress),
|
|
6946
|
+
// Clear claim binding so any matching worker can pick it up.
|
|
6947
|
+
service_name: null,
|
|
6948
|
+
server_name: null,
|
|
6949
|
+
instance_number: null,
|
|
6950
|
+
next_run_at: null
|
|
6951
|
+
});
|
|
6952
|
+
context.logger?.warn?.(`[resumeTask] paused task ${id} (${row.name}) \u2192 idle`);
|
|
6953
|
+
return {
|
|
6954
|
+
success: true,
|
|
6955
|
+
results: { taskId: id, status: "idle", message: "Task resumed (idle; will be claimed)" }
|
|
6956
|
+
};
|
|
6957
|
+
}
|
|
6958
|
+
var TaskPauseTask = class extends AbstractTask {
|
|
6959
|
+
static taskName = "pauseTask";
|
|
6960
|
+
static description = "Pause a specific task: finish current unit, persist status=paused";
|
|
6961
|
+
static defaultWaitForResult = true;
|
|
6962
|
+
static async resolveCustomParams(context, overrides = {}) {
|
|
6963
|
+
const merged = AbstractTask._mergeTypedParams(
|
|
6964
|
+
context,
|
|
6965
|
+
"task-pause-task",
|
|
6966
|
+
{ taskId: "string" },
|
|
6967
|
+
overrides
|
|
6968
|
+
);
|
|
6969
|
+
const taskId = typeof merged.taskId === "string" ? merged.taskId.trim() : "";
|
|
6970
|
+
if (!taskId) throw new ParamError('pauseTask: param "taskId" is required');
|
|
6971
|
+
return { taskId };
|
|
6972
|
+
}
|
|
6973
|
+
async run() {
|
|
6974
|
+
return applyPauseTask(this.context, this.task?.params?.taskId);
|
|
6975
|
+
}
|
|
6976
|
+
};
|
|
6977
|
+
var TaskResumeTask = class extends AbstractTask {
|
|
6978
|
+
static taskName = "resumeTask";
|
|
6979
|
+
static description = "Resume a paused task (status paused \u2192 idle)";
|
|
6980
|
+
static defaultWaitForResult = true;
|
|
6981
|
+
static async resolveCustomParams(context, overrides = {}) {
|
|
6982
|
+
const merged = AbstractTask._mergeTypedParams(
|
|
6983
|
+
context,
|
|
6984
|
+
"task-resume-task",
|
|
6985
|
+
{ taskId: "string" },
|
|
6986
|
+
overrides
|
|
6987
|
+
);
|
|
6988
|
+
const taskId = typeof merged.taskId === "string" ? merged.taskId.trim() : "";
|
|
6989
|
+
if (!taskId) throw new ParamError('resumeTask: param "taskId" is required');
|
|
6990
|
+
return { taskId };
|
|
6991
|
+
}
|
|
6992
|
+
async run() {
|
|
6993
|
+
return applyResumeTask(this.context, this.task?.params?.taskId);
|
|
6994
|
+
}
|
|
6995
|
+
};
|
|
6996
|
+
|
|
6777
6997
|
// src/tasks/coreTasks/TaskGetLogs.js
|
|
6778
6998
|
var TaskGetLogs = class extends AbstractTask {
|
|
6779
6999
|
/**
|
|
@@ -6992,7 +7212,7 @@ var TasksRegistry = class _TasksRegistry {
|
|
|
6992
7212
|
* @returns {TasksRegistry}
|
|
6993
7213
|
*/
|
|
6994
7214
|
static withCoreTasks() {
|
|
6995
|
-
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);
|
|
7215
|
+
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);
|
|
6996
7216
|
}
|
|
6997
7217
|
/**
|
|
6998
7218
|
* Register a single task class under a name. Overwrites any previous entry.
|
|
@@ -7184,11 +7404,12 @@ async function executeClaimedTask(context, tasksTable, historyTable, row, regist
|
|
|
7184
7404
|
} finally {
|
|
7185
7405
|
runningTaskInstances.delete(row.id);
|
|
7186
7406
|
}
|
|
7407
|
+
const taskPaused = success && results && typeof results === "object" && results.taskPaused === true;
|
|
7187
7408
|
await db(historyTable).insert(
|
|
7188
7409
|
taskHistoryInsertFromQueueRow(row, {
|
|
7189
7410
|
completed_at: /* @__PURE__ */ new Date(),
|
|
7190
7411
|
success,
|
|
7191
|
-
status: success ? "completed" : "failed",
|
|
7412
|
+
status: taskPaused ? "paused" : success ? "completed" : "failed",
|
|
7192
7413
|
status_changed_at: db.fn.now(),
|
|
7193
7414
|
params: toJsonColumn(row.params),
|
|
7194
7415
|
results: toJsonColumn(results)
|
|
@@ -7211,7 +7432,24 @@ async function executeClaimedTask(context, tasksTable, historyTable, row, regist
|
|
|
7211
7432
|
details: results
|
|
7212
7433
|
});
|
|
7213
7434
|
}
|
|
7214
|
-
if (
|
|
7435
|
+
if (taskPaused) {
|
|
7436
|
+
const checkpointParams = results.checkpointParams && typeof results.checkpointParams === "object" ? results.checkpointParams : row.params;
|
|
7437
|
+
const progressPayload = results.progress != null ? results.progress : { paused: true, pausedAt: (/* @__PURE__ */ new Date()).toISOString(), message: "paused" };
|
|
7438
|
+
await db(tasksTable).where({ id: row.id }).update({
|
|
7439
|
+
started_at: null,
|
|
7440
|
+
completed_at: /* @__PURE__ */ new Date(),
|
|
7441
|
+
success: true,
|
|
7442
|
+
results: toJsonColumn(results),
|
|
7443
|
+
params: toJsonColumn(checkpointParams),
|
|
7444
|
+
progress: toJsonColumn(progressPayload),
|
|
7445
|
+
status: "paused",
|
|
7446
|
+
status_changed_at: db.fn.now(),
|
|
7447
|
+
past_due: null,
|
|
7448
|
+
service_name: null,
|
|
7449
|
+
server_name: null,
|
|
7450
|
+
instance_number: null
|
|
7451
|
+
});
|
|
7452
|
+
} else if (row.schedule) {
|
|
7215
7453
|
if (success) {
|
|
7216
7454
|
let nextRunAt = null;
|
|
7217
7455
|
try {
|
|
@@ -7339,6 +7577,7 @@ async function runTasksLoop(context, options) {
|
|
|
7339
7577
|
}
|
|
7340
7578
|
const runningPromises = /* @__PURE__ */ new Set();
|
|
7341
7579
|
const runningTaskInstances = /* @__PURE__ */ new Map();
|
|
7580
|
+
context.runningTaskInstances = runningTaskInstances;
|
|
7342
7581
|
let runningControlPromise = null;
|
|
7343
7582
|
let stopRequested = false;
|
|
7344
7583
|
let stopAllowanceMs = Number(context.stopAllowanceMs) > 0 ? Number(context.stopAllowanceMs) : 6e4;
|