@nmakarov/cli-toolkit 0.71.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/cli-runner.cjs +233 -3
- package/dist/cli-runner.cjs.map +1 -1
- package/dist/cli-runner.js +233 -3
- package/dist/cli-runner.js.map +1 -1
- package/dist/filedatabase.cjs.map +1 -1
- package/dist/filedatabase.js.map +1 -1
- package/dist/index.cjs +243 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +239 -3
- package/dist/index.js.map +1 -1
- package/dist/tasks.cjs +243 -3
- package/dist/tasks.cjs.map +1 -1
- package/dist/tasks.js +239 -3
- package/dist/tasks.js.map +1 -1
- package/package.json +1 -1
package/dist/tasks.js
CHANGED
|
@@ -519,6 +519,9 @@ var DEFAULT_GROUP_MAX_INSTANCES = {
|
|
|
519
519
|
harvest: 1,
|
|
520
520
|
harvester: 0,
|
|
521
521
|
loader: 0,
|
|
522
|
+
toolkit: 0,
|
|
523
|
+
photocare: 0,
|
|
524
|
+
/** @deprecated use photocare */
|
|
522
525
|
photos: 0,
|
|
523
526
|
photosprocessor: 0,
|
|
524
527
|
ingest: 0
|
|
@@ -2045,6 +2048,8 @@ var AbstractTask = class _AbstractTask {
|
|
|
2045
2048
|
constructor(context, task) {
|
|
2046
2049
|
this.context = context;
|
|
2047
2050
|
this.task = task;
|
|
2051
|
+
this._stopRequested = false;
|
|
2052
|
+
this._pauseRequested = false;
|
|
2048
2053
|
}
|
|
2049
2054
|
/**
|
|
2050
2055
|
* Return a short reason string when the task should be deferred (e.g. "locked
|
|
@@ -2062,6 +2067,26 @@ var AbstractTask = class _AbstractTask {
|
|
|
2062
2067
|
* @param {number} [_allowanceMs] Grace period the runner promises before hard exit.
|
|
2063
2068
|
*/
|
|
2064
2069
|
requestStop(_allowanceMs) {
|
|
2070
|
+
this._stopRequested = true;
|
|
2071
|
+
}
|
|
2072
|
+
/**
|
|
2073
|
+
* Cooperative pause signal (from `pauseTask` control-lane task). Long-running
|
|
2074
|
+
* tasks should finish the current unit of work, persist a checkpoint, and
|
|
2075
|
+
* return `{ success: true, results: { taskPaused: true, … } }` so the runner
|
|
2076
|
+
* keeps the row as `status=paused` instead of deleting it.
|
|
2077
|
+
*
|
|
2078
|
+
* @param {number} [_allowanceMs]
|
|
2079
|
+
*/
|
|
2080
|
+
requestPause(_allowanceMs) {
|
|
2081
|
+
this._pauseRequested = true;
|
|
2082
|
+
}
|
|
2083
|
+
/** @returns {boolean} */
|
|
2084
|
+
isStopRequested() {
|
|
2085
|
+
return this._stopRequested === true;
|
|
2086
|
+
}
|
|
2087
|
+
/** @returns {boolean} */
|
|
2088
|
+
isPauseRequested() {
|
|
2089
|
+
return this._pauseRequested === true;
|
|
2065
2090
|
}
|
|
2066
2091
|
/**
|
|
2067
2092
|
* Perform the task. Must be implemented by subclasses.
|
|
@@ -2768,6 +2793,8 @@ var CONTROL_LANE_TASK_NAMES = [
|
|
|
2768
2793
|
"pause",
|
|
2769
2794
|
"unpauseRunner",
|
|
2770
2795
|
"unpause",
|
|
2796
|
+
"pauseTask",
|
|
2797
|
+
"resumeTask",
|
|
2771
2798
|
"setRuntimeParam",
|
|
2772
2799
|
"setRunnerParam"
|
|
2773
2800
|
];
|
|
@@ -2972,6 +2999,190 @@ var TaskUnpauseRunner = class extends AbstractTask {
|
|
|
2972
2999
|
}
|
|
2973
3000
|
};
|
|
2974
3001
|
|
|
3002
|
+
// src/tasks/coreTasks/TaskPauseTask.js
|
|
3003
|
+
function parseProgressObject(progress) {
|
|
3004
|
+
if (progress == null || progress === "") return {};
|
|
3005
|
+
if (typeof progress === "object" && !Array.isArray(progress)) {
|
|
3006
|
+
return { ...progress };
|
|
3007
|
+
}
|
|
3008
|
+
if (typeof progress === "string") {
|
|
3009
|
+
const t = progress.trim();
|
|
3010
|
+
if (t.startsWith("{") || t.startsWith("[")) {
|
|
3011
|
+
try {
|
|
3012
|
+
const parsed = JSON.parse(t);
|
|
3013
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return { ...parsed };
|
|
3014
|
+
} catch {
|
|
3015
|
+
}
|
|
3016
|
+
}
|
|
3017
|
+
return { message: progress };
|
|
3018
|
+
}
|
|
3019
|
+
return { message: String(progress) };
|
|
3020
|
+
}
|
|
3021
|
+
async function applyPauseTask(context, taskId) {
|
|
3022
|
+
const db = context.db;
|
|
3023
|
+
if (!db) throw new Error("pauseTask: context.db is required");
|
|
3024
|
+
const id = String(taskId ?? "").trim();
|
|
3025
|
+
if (!id) throw new ParamError('pauseTask: param "taskId" is required');
|
|
3026
|
+
const { tasksTable } = queueToTableNames(context.tasksQueueName ?? "tasks");
|
|
3027
|
+
const row = await db(tasksTable).where({ id }).first();
|
|
3028
|
+
if (!row) {
|
|
3029
|
+
return { success: false, results: { error: `Task ${id} not found`, taskId: id } };
|
|
3030
|
+
}
|
|
3031
|
+
if (row.status === "paused") {
|
|
3032
|
+
return {
|
|
3033
|
+
success: true,
|
|
3034
|
+
results: { taskId: id, status: "paused", message: "Task already paused" }
|
|
3035
|
+
};
|
|
3036
|
+
}
|
|
3037
|
+
if (row.status === "idle") {
|
|
3038
|
+
const progress = parseProgressObject(row.progress);
|
|
3039
|
+
progress.pauseRequested = true;
|
|
3040
|
+
progress.pausedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3041
|
+
await db(tasksTable).where({ id }).update({
|
|
3042
|
+
status: "paused",
|
|
3043
|
+
status_changed_at: db.fn.now(),
|
|
3044
|
+
progress: toJsonColumn(progress)
|
|
3045
|
+
});
|
|
3046
|
+
context.logger?.warn?.(`[pauseTask] idle task ${id} (${row.name}) \u2192 paused`);
|
|
3047
|
+
return {
|
|
3048
|
+
success: true,
|
|
3049
|
+
results: { taskId: id, status: "paused", message: "Idle task paused" }
|
|
3050
|
+
};
|
|
3051
|
+
}
|
|
3052
|
+
if (row.status === "running") {
|
|
3053
|
+
const progress = parseProgressObject(row.progress);
|
|
3054
|
+
progress.pauseRequested = true;
|
|
3055
|
+
progress.pauseRequestedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3056
|
+
await db(tasksTable).where({ id }).update({
|
|
3057
|
+
progress: toJsonColumn(progress)
|
|
3058
|
+
});
|
|
3059
|
+
const inst = context.runningTaskInstances?.get?.(id);
|
|
3060
|
+
if (inst && typeof inst.requestPause === "function") {
|
|
3061
|
+
await inst.requestPause();
|
|
3062
|
+
context.logger?.warn?.(
|
|
3063
|
+
`[pauseTask] signaled running task ${id} (${row.name}) to pause after current unit`
|
|
3064
|
+
);
|
|
3065
|
+
return {
|
|
3066
|
+
success: true,
|
|
3067
|
+
results: {
|
|
3068
|
+
taskId: id,
|
|
3069
|
+
status: "running",
|
|
3070
|
+
signaled: true,
|
|
3071
|
+
message: "Pause requested; task will finish current unit then park as paused"
|
|
3072
|
+
}
|
|
3073
|
+
};
|
|
3074
|
+
}
|
|
3075
|
+
context.logger?.warn?.(
|
|
3076
|
+
`[pauseTask] stamped pauseRequested on ${id} (${row.name}) \u2014 not running in this process`
|
|
3077
|
+
);
|
|
3078
|
+
return {
|
|
3079
|
+
success: true,
|
|
3080
|
+
results: {
|
|
3081
|
+
taskId: id,
|
|
3082
|
+
status: "running",
|
|
3083
|
+
signaled: false,
|
|
3084
|
+
message: "pauseRequested stamped on task row; target runner must observe it (enqueue pauseTask on that service)"
|
|
3085
|
+
}
|
|
3086
|
+
};
|
|
3087
|
+
}
|
|
3088
|
+
return {
|
|
3089
|
+
success: false,
|
|
3090
|
+
results: {
|
|
3091
|
+
error: `Cannot pause task in status "${row.status}"`,
|
|
3092
|
+
taskId: id,
|
|
3093
|
+
status: row.status
|
|
3094
|
+
}
|
|
3095
|
+
};
|
|
3096
|
+
}
|
|
3097
|
+
async function applyResumeTask(context, taskId) {
|
|
3098
|
+
const db = context.db;
|
|
3099
|
+
if (!db) throw new Error("resumeTask: context.db is required");
|
|
3100
|
+
const id = String(taskId ?? "").trim();
|
|
3101
|
+
if (!id) throw new ParamError('resumeTask: param "taskId" is required');
|
|
3102
|
+
const { tasksTable } = queueToTableNames(context.tasksQueueName ?? "tasks");
|
|
3103
|
+
const row = await db(tasksTable).where({ id }).first();
|
|
3104
|
+
if (!row) {
|
|
3105
|
+
return { success: false, results: { error: `Task ${id} not found`, taskId: id } };
|
|
3106
|
+
}
|
|
3107
|
+
if (row.status === "idle") {
|
|
3108
|
+
return {
|
|
3109
|
+
success: true,
|
|
3110
|
+
results: { taskId: id, status: "idle", message: "Task already idle (runnable)" }
|
|
3111
|
+
};
|
|
3112
|
+
}
|
|
3113
|
+
if (row.status !== "paused") {
|
|
3114
|
+
return {
|
|
3115
|
+
success: false,
|
|
3116
|
+
results: {
|
|
3117
|
+
error: `Cannot resume task in status "${row.status}" (expected paused)`,
|
|
3118
|
+
taskId: id,
|
|
3119
|
+
status: row.status
|
|
3120
|
+
}
|
|
3121
|
+
};
|
|
3122
|
+
}
|
|
3123
|
+
const progress = parseProgressObject(row.progress);
|
|
3124
|
+
delete progress.pauseRequested;
|
|
3125
|
+
delete progress.pauseRequestedAt;
|
|
3126
|
+
delete progress.pausedAt;
|
|
3127
|
+
progress.resumedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3128
|
+
await db(tasksTable).where({ id }).update({
|
|
3129
|
+
status: "idle",
|
|
3130
|
+
status_changed_at: db.fn.now(),
|
|
3131
|
+
started_at: null,
|
|
3132
|
+
completed_at: null,
|
|
3133
|
+
success: null,
|
|
3134
|
+
progress: toJsonColumn(progress),
|
|
3135
|
+
// Clear claim binding so any matching worker can pick it up.
|
|
3136
|
+
service_name: null,
|
|
3137
|
+
server_name: null,
|
|
3138
|
+
instance_number: null,
|
|
3139
|
+
next_run_at: null
|
|
3140
|
+
});
|
|
3141
|
+
context.logger?.warn?.(`[resumeTask] paused task ${id} (${row.name}) \u2192 idle`);
|
|
3142
|
+
return {
|
|
3143
|
+
success: true,
|
|
3144
|
+
results: { taskId: id, status: "idle", message: "Task resumed (idle; will be claimed)" }
|
|
3145
|
+
};
|
|
3146
|
+
}
|
|
3147
|
+
var TaskPauseTask = class extends AbstractTask {
|
|
3148
|
+
static taskName = "pauseTask";
|
|
3149
|
+
static description = "Pause a specific task: finish current unit, persist status=paused";
|
|
3150
|
+
static defaultWaitForResult = true;
|
|
3151
|
+
static async resolveCustomParams(context, overrides = {}) {
|
|
3152
|
+
const merged = AbstractTask._mergeTypedParams(
|
|
3153
|
+
context,
|
|
3154
|
+
"task-pause-task",
|
|
3155
|
+
{ taskId: "string" },
|
|
3156
|
+
overrides
|
|
3157
|
+
);
|
|
3158
|
+
const taskId = typeof merged.taskId === "string" ? merged.taskId.trim() : "";
|
|
3159
|
+
if (!taskId) throw new ParamError('pauseTask: param "taskId" is required');
|
|
3160
|
+
return { taskId };
|
|
3161
|
+
}
|
|
3162
|
+
async run() {
|
|
3163
|
+
return applyPauseTask(this.context, this.task?.params?.taskId);
|
|
3164
|
+
}
|
|
3165
|
+
};
|
|
3166
|
+
var TaskResumeTask = class extends AbstractTask {
|
|
3167
|
+
static taskName = "resumeTask";
|
|
3168
|
+
static description = "Resume a paused task (status paused \u2192 idle)";
|
|
3169
|
+
static defaultWaitForResult = true;
|
|
3170
|
+
static async resolveCustomParams(context, overrides = {}) {
|
|
3171
|
+
const merged = AbstractTask._mergeTypedParams(
|
|
3172
|
+
context,
|
|
3173
|
+
"task-resume-task",
|
|
3174
|
+
{ taskId: "string" },
|
|
3175
|
+
overrides
|
|
3176
|
+
);
|
|
3177
|
+
const taskId = typeof merged.taskId === "string" ? merged.taskId.trim() : "";
|
|
3178
|
+
if (!taskId) throw new ParamError('resumeTask: param "taskId" is required');
|
|
3179
|
+
return { taskId };
|
|
3180
|
+
}
|
|
3181
|
+
async run() {
|
|
3182
|
+
return applyResumeTask(this.context, this.task?.params?.taskId);
|
|
3183
|
+
}
|
|
3184
|
+
};
|
|
3185
|
+
|
|
2975
3186
|
// src/tasks/coreTasks/TaskGetLogs.js
|
|
2976
3187
|
var TaskGetLogs = class extends AbstractTask {
|
|
2977
3188
|
/**
|
|
@@ -3190,7 +3401,7 @@ var TasksRegistry = class _TasksRegistry {
|
|
|
3190
3401
|
* @returns {TasksRegistry}
|
|
3191
3402
|
*/
|
|
3192
3403
|
static withCoreTasks() {
|
|
3193
|
-
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);
|
|
3404
|
+
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);
|
|
3194
3405
|
}
|
|
3195
3406
|
/**
|
|
3196
3407
|
* Register a single task class under a name. Overwrites any previous entry.
|
|
@@ -3297,6 +3508,8 @@ var SERVICE_TASK_NAMES = [
|
|
|
3297
3508
|
"pauseRunner",
|
|
3298
3509
|
"unpause",
|
|
3299
3510
|
"unpauseRunner",
|
|
3511
|
+
"pauseTask",
|
|
3512
|
+
"resumeTask",
|
|
3300
3513
|
"shellCommand",
|
|
3301
3514
|
"systemInfo",
|
|
3302
3515
|
"info",
|
|
@@ -3592,11 +3805,12 @@ async function executeClaimedTask(context, tasksTable, historyTable, row, regist
|
|
|
3592
3805
|
} finally {
|
|
3593
3806
|
runningTaskInstances.delete(row.id);
|
|
3594
3807
|
}
|
|
3808
|
+
const taskPaused = success && results && typeof results === "object" && results.taskPaused === true;
|
|
3595
3809
|
await db(historyTable).insert(
|
|
3596
3810
|
taskHistoryInsertFromQueueRow(row, {
|
|
3597
3811
|
completed_at: /* @__PURE__ */ new Date(),
|
|
3598
3812
|
success,
|
|
3599
|
-
status: success ? "completed" : "failed",
|
|
3813
|
+
status: taskPaused ? "paused" : success ? "completed" : "failed",
|
|
3600
3814
|
status_changed_at: db.fn.now(),
|
|
3601
3815
|
params: toJsonColumn(row.params),
|
|
3602
3816
|
results: toJsonColumn(results)
|
|
@@ -3619,7 +3833,24 @@ async function executeClaimedTask(context, tasksTable, historyTable, row, regist
|
|
|
3619
3833
|
details: results
|
|
3620
3834
|
});
|
|
3621
3835
|
}
|
|
3622
|
-
if (
|
|
3836
|
+
if (taskPaused) {
|
|
3837
|
+
const checkpointParams = results.checkpointParams && typeof results.checkpointParams === "object" ? results.checkpointParams : row.params;
|
|
3838
|
+
const progressPayload = results.progress != null ? results.progress : { paused: true, pausedAt: (/* @__PURE__ */ new Date()).toISOString(), message: "paused" };
|
|
3839
|
+
await db(tasksTable).where({ id: row.id }).update({
|
|
3840
|
+
started_at: null,
|
|
3841
|
+
completed_at: /* @__PURE__ */ new Date(),
|
|
3842
|
+
success: true,
|
|
3843
|
+
results: toJsonColumn(results),
|
|
3844
|
+
params: toJsonColumn(checkpointParams),
|
|
3845
|
+
progress: toJsonColumn(progressPayload),
|
|
3846
|
+
status: "paused",
|
|
3847
|
+
status_changed_at: db.fn.now(),
|
|
3848
|
+
past_due: null,
|
|
3849
|
+
service_name: null,
|
|
3850
|
+
server_name: null,
|
|
3851
|
+
instance_number: null
|
|
3852
|
+
});
|
|
3853
|
+
} else if (row.schedule) {
|
|
3623
3854
|
if (success) {
|
|
3624
3855
|
let nextRunAt = null;
|
|
3625
3856
|
try {
|
|
@@ -3747,6 +3978,7 @@ async function runTasksLoop(context, options) {
|
|
|
3747
3978
|
}
|
|
3748
3979
|
const runningPromises = /* @__PURE__ */ new Set();
|
|
3749
3980
|
const runningTaskInstances = /* @__PURE__ */ new Map();
|
|
3981
|
+
context.runningTaskInstances = runningTaskInstances;
|
|
3750
3982
|
let runningControlPromise = null;
|
|
3751
3983
|
let stopRequested = false;
|
|
3752
3984
|
let stopAllowanceMs = Number(context.stopAllowanceMs) > 0 ? Number(context.stopAllowanceMs) : 6e4;
|
|
@@ -4106,7 +4338,9 @@ export {
|
|
|
4106
4338
|
SERVICE_TASK_NAMES,
|
|
4107
4339
|
TaskGetLogs,
|
|
4108
4340
|
TaskPauseRunner,
|
|
4341
|
+
TaskPauseTask,
|
|
4109
4342
|
TaskPing,
|
|
4343
|
+
TaskResumeTask,
|
|
4110
4344
|
TaskSampleProcess,
|
|
4111
4345
|
TaskSetRuntimeParam,
|
|
4112
4346
|
TaskShellCommand,
|
|
@@ -4117,6 +4351,8 @@ export {
|
|
|
4117
4351
|
TasksManager,
|
|
4118
4352
|
TasksRegistry,
|
|
4119
4353
|
appendTaskIpcLog,
|
|
4354
|
+
applyPauseTask,
|
|
4355
|
+
applyResumeTask,
|
|
4120
4356
|
applyRunnerPaused,
|
|
4121
4357
|
applyRuntimeParam,
|
|
4122
4358
|
applyRuntimePatch,
|