@nmakarov/cli-toolkit 0.18.0 → 0.21.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/README.md +9 -0
- package/dist/cli-runner.cjs +426 -97
- package/dist/cli-runner.cjs.map +1 -1
- package/dist/cli-runner.js +457 -128
- package/dist/cli-runner.js.map +1 -1
- package/dist/db.cjs +1 -1
- package/dist/db.cjs.map +1 -1
- package/dist/db.js +1 -1
- package/dist/db.js.map +1 -1
- package/dist/filedatabase.cjs.map +1 -1
- package/dist/filedatabase.js.map +1 -1
- package/dist/http-client2.cjs +6 -2
- package/dist/http-client2.cjs.map +1 -1
- package/dist/http-client2.js +6 -2
- package/dist/http-client2.js.map +1 -1
- package/dist/index.cjs +482 -96
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +469 -94
- package/dist/index.js.map +1 -1
- package/dist/init.cjs +16 -1
- package/dist/init.cjs.map +1 -1
- package/dist/init.js +16 -1
- package/dist/init.js.map +1 -1
- package/dist/mock-server.cjs.map +1 -1
- package/dist/mock-server.js.map +1 -1
- package/dist/params.cjs +16 -1
- package/dist/params.cjs.map +1 -1
- package/dist/params.js +16 -1
- package/dist/params.js.map +1 -1
- package/dist/tasks.cjs +458 -87
- package/dist/tasks.cjs.map +1 -1
- package/dist/tasks.js +478 -118
- package/dist/tasks.js.map +1 -1
- package/package.json +10 -3
- package/scripts/ssm/parse-cli.ts +35 -0
- package/scripts/ssm/ssm-admin.ts +151 -0
- package/scripts/ssm/ssm-pull.ts +150 -0
package/dist/cli-runner.js
CHANGED
|
@@ -907,11 +907,11 @@ function buildBreadcrumb(parts) {
|
|
|
907
907
|
if (parts.length === 1) return parts[0];
|
|
908
908
|
return parts.slice(1).map((part) => `\u2190 ${part}`).join(" ");
|
|
909
909
|
}
|
|
910
|
-
function buildDetailBreadcrumb(
|
|
911
|
-
if (
|
|
912
|
-
return suffix ? `\u2190 ${suffix}` :
|
|
910
|
+
function buildDetailBreadcrumb(path6, suffix = "") {
|
|
911
|
+
if (path6.length <= 1) {
|
|
912
|
+
return suffix ? `\u2190 ${suffix}` : path6[0] || "";
|
|
913
913
|
}
|
|
914
|
-
const breadcrumb = buildBreadcrumb(
|
|
914
|
+
const breadcrumb = buildBreadcrumb(path6);
|
|
915
915
|
return suffix ? `${breadcrumb} ${suffix}` : breadcrumb;
|
|
916
916
|
}
|
|
917
917
|
var init_utils = __esm({
|
|
@@ -1101,7 +1101,7 @@ var init_screen = __esm({
|
|
|
1101
1101
|
});
|
|
1102
1102
|
|
|
1103
1103
|
// src/scripts/cli-runner.ts
|
|
1104
|
-
import
|
|
1104
|
+
import path5 from "path";
|
|
1105
1105
|
import { pathToFileURL } from "url";
|
|
1106
1106
|
|
|
1107
1107
|
// src/args/index.ts
|
|
@@ -1927,7 +1927,7 @@ var Params = class _Params {
|
|
|
1927
1927
|
throw new ParamError(`default value "${defValObj.value}" type mismatch`);
|
|
1928
1928
|
}
|
|
1929
1929
|
type = type.default(defValObj.value);
|
|
1930
|
-
} else if (str.match(
|
|
1930
|
+
} else if (str.match(/\s*required\s*/)) {
|
|
1931
1931
|
type = type.required();
|
|
1932
1932
|
} else {
|
|
1933
1933
|
type = type.optional();
|
|
@@ -2003,6 +2003,8 @@ var Params = class _Params {
|
|
|
2003
2003
|
/**
|
|
2004
2004
|
* Get all parameters from definitions (main script).
|
|
2005
2005
|
* Same as getAllForModule("script", defs). Processes left-to-right for cross-parameter references.
|
|
2006
|
+
* Libraries should use {@link getAllForModule} with an explicit module name (or {@link runWithModule}
|
|
2007
|
+
* around {@link get}) so --showUsedParams groups usage correctly.
|
|
2006
2008
|
*/
|
|
2007
2009
|
getAll(defs2) {
|
|
2008
2010
|
return this.getAllForModule("script", defs2);
|
|
@@ -2039,6 +2041,19 @@ var Params = class _Params {
|
|
|
2039
2041
|
this._currentModule = prev;
|
|
2040
2042
|
}
|
|
2041
2043
|
}
|
|
2044
|
+
/**
|
|
2045
|
+
* Run a callback with {@link _currentModule} set so single {@link get} calls are tracked
|
|
2046
|
+
* under the same module (for --showUsedParams / getFiguredByModule).
|
|
2047
|
+
*/
|
|
2048
|
+
runWithModule(moduleName, fn) {
|
|
2049
|
+
const prev = this._currentModule;
|
|
2050
|
+
this._currentModule = moduleName;
|
|
2051
|
+
try {
|
|
2052
|
+
return fn();
|
|
2053
|
+
} finally {
|
|
2054
|
+
this._currentModule = prev;
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2042
2057
|
/**
|
|
2043
2058
|
* Infer module name from call stack: first caller outside params/index gives path like .../src/<moduleName>/...
|
|
2044
2059
|
*/
|
|
@@ -2052,9 +2067,9 @@ var Params = class _Params {
|
|
|
2052
2067
|
if (!parenMatch) continue;
|
|
2053
2068
|
const parts = parenMatch[1].split(":");
|
|
2054
2069
|
if (parts.length < 3) continue;
|
|
2055
|
-
const
|
|
2056
|
-
if (!
|
|
2057
|
-
const srcMatch =
|
|
2070
|
+
const path6 = parts.slice(0, -2).join(":").replace(/^file:\/\//, "");
|
|
2071
|
+
if (!path6 || path6.includes(paramsIndexPath)) continue;
|
|
2072
|
+
const srcMatch = path6.match(/[/\\]src[/\\]([^/\\]+)(?:[/\\]|$)/);
|
|
2058
2073
|
if (srcMatch) return srcMatch[1];
|
|
2059
2074
|
}
|
|
2060
2075
|
return "script";
|
|
@@ -2905,7 +2920,7 @@ async function dbFindAndConnect(context, dbNameOrConnectionString) {
|
|
|
2905
2920
|
dbConnectionString: "string",
|
|
2906
2921
|
dbProfile: "boolean default false"
|
|
2907
2922
|
};
|
|
2908
|
-
const paramsConfig = context.params.
|
|
2923
|
+
const paramsConfig = context.params.getAll(defs2);
|
|
2909
2924
|
dbName = paramsConfig.dbName;
|
|
2910
2925
|
dbConnectionString = paramsConfig.dbConnectionString;
|
|
2911
2926
|
dbProfile = paramsConfig.dbProfile;
|
|
@@ -3010,6 +3025,12 @@ function toJsonColumn(value) {
|
|
|
3010
3025
|
return JSON.stringify(value);
|
|
3011
3026
|
}
|
|
3012
3027
|
|
|
3028
|
+
// src/tasks/servicesRegistry.ts
|
|
3029
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
3030
|
+
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
3031
|
+
import os from "os";
|
|
3032
|
+
import path3 from "path";
|
|
3033
|
+
|
|
3013
3034
|
// src/tasks/taskUtils.ts
|
|
3014
3035
|
import { randomUUID } from "crypto";
|
|
3015
3036
|
function getDb(context) {
|
|
@@ -3028,6 +3049,12 @@ function queueToTableNames(queue) {
|
|
|
3028
3049
|
historyTable: `${queue}_history`
|
|
3029
3050
|
};
|
|
3030
3051
|
}
|
|
3052
|
+
function servicesRegistryTable(queue) {
|
|
3053
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(queue)) {
|
|
3054
|
+
throw new Error(`Invalid queue name "${queue}". Use letters, numbers, underscore only.`);
|
|
3055
|
+
}
|
|
3056
|
+
return `${queue}_services_registry`;
|
|
3057
|
+
}
|
|
3031
3058
|
async function ensureTaskTables(context, options = {}) {
|
|
3032
3059
|
const queue = options.queue ?? "tasks";
|
|
3033
3060
|
const recreate = options.recreate ?? false;
|
|
@@ -3064,18 +3091,6 @@ async function ensureTaskTables(context, options = {}) {
|
|
|
3064
3091
|
t.index(["target", "task"], `${tasksTable}_target_task_idx`);
|
|
3065
3092
|
});
|
|
3066
3093
|
}
|
|
3067
|
-
const tasksHasOpid = await db.schema.hasColumn(tasksTable, "opid");
|
|
3068
|
-
if (!tasksHasOpid) {
|
|
3069
|
-
await db.schema.alterTable(tasksTable, (t) => {
|
|
3070
|
-
t.text("opid");
|
|
3071
|
-
});
|
|
3072
|
-
}
|
|
3073
|
-
const tasksHasPausedAt = await db.schema.hasColumn(tasksTable, "paused_at");
|
|
3074
|
-
if (!tasksHasPausedAt) {
|
|
3075
|
-
await db.schema.alterTable(tasksTable, (t) => {
|
|
3076
|
-
t.timestamp("paused_at").defaultTo(null);
|
|
3077
|
-
});
|
|
3078
|
-
}
|
|
3079
3094
|
if (needsHistory) {
|
|
3080
3095
|
await db.schema.createTable(historyTable, (t) => {
|
|
3081
3096
|
t.uuid("id").notNullable();
|
|
@@ -3098,10 +3113,24 @@ async function ensureTaskTables(context, options = {}) {
|
|
|
3098
3113
|
t.index(["task", "created_at"], `${historyTable}_task_created_idx`);
|
|
3099
3114
|
});
|
|
3100
3115
|
}
|
|
3101
|
-
const
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3116
|
+
const registryTable = servicesRegistryTable(queue);
|
|
3117
|
+
const needsRegistry = !await db.tableExists(registryTable);
|
|
3118
|
+
if (needsRegistry) {
|
|
3119
|
+
await db.schema.createTable(registryTable, (t) => {
|
|
3120
|
+
t.uuid("id").primary().defaultTo(db.raw("uuid_generate_v4()"));
|
|
3121
|
+
t.uuid("instance_id").notNullable().unique();
|
|
3122
|
+
t.text("queue").notNullable();
|
|
3123
|
+
t.text("service_group").notNullable();
|
|
3124
|
+
t.text("service_name").notNullable();
|
|
3125
|
+
t.text("target").notNullable();
|
|
3126
|
+
t.text("hostname");
|
|
3127
|
+
t.integer("pid");
|
|
3128
|
+
t.json("metadata");
|
|
3129
|
+
t.timestamp("created_at").notNullable().defaultTo(db.fn.now());
|
|
3130
|
+
t.timestamp("last_seen_at").notNullable().defaultTo(db.fn.now());
|
|
3131
|
+
t.unique(["queue", "service_name"], `${registryTable}_queue_service_name_uniq`);
|
|
3132
|
+
t.index(["queue", "service_group", "last_seen_at"], `${registryTable}_queue_group_seen_idx`);
|
|
3133
|
+
t.index(["queue", "last_seen_at"], `${registryTable}_queue_seen_idx`);
|
|
3105
3134
|
});
|
|
3106
3135
|
}
|
|
3107
3136
|
}
|
|
@@ -3112,9 +3141,228 @@ async function updateTaskProgress(context, tasksTable, taskId, progress) {
|
|
|
3112
3141
|
});
|
|
3113
3142
|
}
|
|
3114
3143
|
|
|
3144
|
+
// src/tasks/servicesRegistry.ts
|
|
3145
|
+
function getDb2(context) {
|
|
3146
|
+
const db = context.db;
|
|
3147
|
+
if (!db) {
|
|
3148
|
+
throw new Error("Services registry requires context.db");
|
|
3149
|
+
}
|
|
3150
|
+
return db;
|
|
3151
|
+
}
|
|
3152
|
+
var DEFAULT_GROUP_MAX_INSTANCES = {
|
|
3153
|
+
intake: 1,
|
|
3154
|
+
harvest: 1,
|
|
3155
|
+
loader: 0,
|
|
3156
|
+
photos: 0,
|
|
3157
|
+
photosprocessor: 0,
|
|
3158
|
+
ingest: 0
|
|
3159
|
+
};
|
|
3160
|
+
function sanitizeNamePart(raw) {
|
|
3161
|
+
const s = String(raw || "").trim().replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
3162
|
+
return s.slice(0, 80) || "runner";
|
|
3163
|
+
}
|
|
3164
|
+
function identityFilePath(identityDir, queue, serviceGroup) {
|
|
3165
|
+
const safeQ = sanitizeNamePart(queue);
|
|
3166
|
+
const safeG = sanitizeNamePart(serviceGroup);
|
|
3167
|
+
return path3.join(identityDir, `${safeQ}_${safeG}.json`);
|
|
3168
|
+
}
|
|
3169
|
+
async function readIdentityFile(filePath) {
|
|
3170
|
+
try {
|
|
3171
|
+
const text = await readFile(filePath, "utf8");
|
|
3172
|
+
const parsed = JSON.parse(text);
|
|
3173
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
3174
|
+
} catch {
|
|
3175
|
+
return {};
|
|
3176
|
+
}
|
|
3177
|
+
}
|
|
3178
|
+
async function writeIdentityFile(filePath, data) {
|
|
3179
|
+
await mkdir(path3.dirname(filePath), { recursive: true });
|
|
3180
|
+
await writeFile(filePath, `${JSON.stringify(data, null, 2)}
|
|
3181
|
+
`, "utf8");
|
|
3182
|
+
}
|
|
3183
|
+
function resolveMaxInstances(serviceGroup, override) {
|
|
3184
|
+
if (override !== void 0 && Number.isFinite(override)) {
|
|
3185
|
+
return Math.max(0, Math.floor(Number(override)));
|
|
3186
|
+
}
|
|
3187
|
+
const g = serviceGroup.trim().toLowerCase();
|
|
3188
|
+
return DEFAULT_GROUP_MAX_INSTANCES[g] ?? 0;
|
|
3189
|
+
}
|
|
3190
|
+
async function countAliveInGroup(db, registryTable, queue, serviceGroup, staleMs, excludeInstanceId) {
|
|
3191
|
+
const cutoff = new Date(Date.now() - staleMs);
|
|
3192
|
+
let q = db(registryTable).where({ queue, service_group: serviceGroup }).where("last_seen_at", ">", cutoff);
|
|
3193
|
+
if (excludeInstanceId) {
|
|
3194
|
+
q = q.whereNot("instance_id", excludeInstanceId);
|
|
3195
|
+
}
|
|
3196
|
+
const row = await q.count("id as count").first();
|
|
3197
|
+
return Number(row?.count ?? 0);
|
|
3198
|
+
}
|
|
3199
|
+
function isUniqueViolation(error) {
|
|
3200
|
+
const code = error?.code ?? error?.errno;
|
|
3201
|
+
return code === "23505" || String(error?.message || "").includes("duplicate key");
|
|
3202
|
+
}
|
|
3203
|
+
async function registerInServicesRegistry(context, options) {
|
|
3204
|
+
const db = getDb2(context);
|
|
3205
|
+
const registryTable = servicesRegistryTable(options.queue);
|
|
3206
|
+
const serviceGroup = options.serviceGroup.trim();
|
|
3207
|
+
if (!serviceGroup) {
|
|
3208
|
+
throw new Error("registerInServicesRegistry: serviceGroup is required");
|
|
3209
|
+
}
|
|
3210
|
+
const identityPath = identityFilePath(options.identityDir, options.queue, serviceGroup);
|
|
3211
|
+
let identity = await readIdentityFile(identityPath);
|
|
3212
|
+
let instanceId = typeof identity.instanceId === "string" && identity.instanceId.trim() ? identity.instanceId.trim() : randomUUID2();
|
|
3213
|
+
identity.instanceId = instanceId;
|
|
3214
|
+
await writeIdentityFile(identityPath, identity);
|
|
3215
|
+
const hostname = os.hostname();
|
|
3216
|
+
const pid = typeof process.pid === "number" ? process.pid : null;
|
|
3217
|
+
const meta = toJsonColumn(options.metadata ?? null);
|
|
3218
|
+
const existing = await db(registryTable).where({ instance_id: instanceId }).first();
|
|
3219
|
+
if (existing) {
|
|
3220
|
+
await db(registryTable).where({ instance_id: instanceId }).update({
|
|
3221
|
+
target: options.target,
|
|
3222
|
+
hostname,
|
|
3223
|
+
pid,
|
|
3224
|
+
metadata: meta,
|
|
3225
|
+
last_seen_at: db.fn.now()
|
|
3226
|
+
});
|
|
3227
|
+
const serviceName = String(existing.service_name);
|
|
3228
|
+
identity.serviceName = serviceName;
|
|
3229
|
+
await writeIdentityFile(identityPath, identity);
|
|
3230
|
+
const reg = {
|
|
3231
|
+
instanceId,
|
|
3232
|
+
serviceName,
|
|
3233
|
+
serviceGroup,
|
|
3234
|
+
queue: options.queue,
|
|
3235
|
+
target: options.target,
|
|
3236
|
+
rowId: String(existing.id)
|
|
3237
|
+
};
|
|
3238
|
+
context.servicesRegistry = reg;
|
|
3239
|
+
context.runnerHeartbeat = reg;
|
|
3240
|
+
context.logger.info?.(
|
|
3241
|
+
`[services-registry] resumed instance_id=${instanceId} name=${serviceName} group=${serviceGroup} queue=${options.queue}`
|
|
3242
|
+
);
|
|
3243
|
+
return {
|
|
3244
|
+
instanceId,
|
|
3245
|
+
serviceName,
|
|
3246
|
+
serviceGroup,
|
|
3247
|
+
queue: options.queue,
|
|
3248
|
+
target: options.target,
|
|
3249
|
+
rowId: String(existing.id),
|
|
3250
|
+
registryTable
|
|
3251
|
+
};
|
|
3252
|
+
}
|
|
3253
|
+
const maxAllowed = resolveMaxInstances(serviceGroup, options.groupMaxInstances);
|
|
3254
|
+
const aliveOthers = await countAliveInGroup(
|
|
3255
|
+
db,
|
|
3256
|
+
registryTable,
|
|
3257
|
+
options.queue,
|
|
3258
|
+
serviceGroup,
|
|
3259
|
+
options.staleMs,
|
|
3260
|
+
instanceId
|
|
3261
|
+
);
|
|
3262
|
+
if (maxAllowed > 0 && aliveOthers >= maxAllowed) {
|
|
3263
|
+
const msg = `[services-registry] group limit reached for "${serviceGroup}": ${aliveOthers} alive (max ${maxAllowed}, queue=${options.queue}).`;
|
|
3264
|
+
if (options.enforceMaxInstances) {
|
|
3265
|
+
throw new Error(msg);
|
|
3266
|
+
}
|
|
3267
|
+
context.logger.warn?.(`${msg} Starting anyway (runnerEnforceMaxInstances=false).`);
|
|
3268
|
+
}
|
|
3269
|
+
const explicitName = options.serviceName?.trim();
|
|
3270
|
+
const fromFile = typeof identity.serviceName === "string" ? identity.serviceName.trim() : "";
|
|
3271
|
+
const hostBase = sanitizeNamePart(hostname);
|
|
3272
|
+
const groupBase = sanitizeNamePart(serviceGroup);
|
|
3273
|
+
const baseCandidates = [];
|
|
3274
|
+
if (explicitName) baseCandidates.push(sanitizeNamePart(explicitName));
|
|
3275
|
+
if (fromFile) baseCandidates.push(sanitizeNamePart(fromFile));
|
|
3276
|
+
baseCandidates.push(`${groupBase}-${hostBase}`);
|
|
3277
|
+
baseCandidates.push(groupBase);
|
|
3278
|
+
function* eachServiceNameCandidate(bases) {
|
|
3279
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3280
|
+
for (const rawBase of bases) {
|
|
3281
|
+
const base = sanitizeNamePart(rawBase);
|
|
3282
|
+
if (!base) continue;
|
|
3283
|
+
const seq = [base];
|
|
3284
|
+
for (let n = 2; n <= 500; n++) seq.push(`${base}-${n}`);
|
|
3285
|
+
for (const c of seq) {
|
|
3286
|
+
if (seen.has(c)) continue;
|
|
3287
|
+
seen.add(c);
|
|
3288
|
+
yield c;
|
|
3289
|
+
}
|
|
3290
|
+
}
|
|
3291
|
+
}
|
|
3292
|
+
let inserted;
|
|
3293
|
+
for (const candidate of eachServiceNameCandidate(baseCandidates)) {
|
|
3294
|
+
try {
|
|
3295
|
+
const rows = await db(registryTable).insert({
|
|
3296
|
+
instance_id: instanceId,
|
|
3297
|
+
queue: options.queue,
|
|
3298
|
+
service_group: serviceGroup,
|
|
3299
|
+
service_name: candidate,
|
|
3300
|
+
target: options.target,
|
|
3301
|
+
hostname,
|
|
3302
|
+
pid,
|
|
3303
|
+
metadata: meta,
|
|
3304
|
+
last_seen_at: db.fn.now()
|
|
3305
|
+
}).returning(["id", "service_name"]);
|
|
3306
|
+
const row = Array.isArray(rows) ? rows[0] : rows;
|
|
3307
|
+
if (row) {
|
|
3308
|
+
inserted = { id: String(row.id), service_name: String(row.service_name) };
|
|
3309
|
+
break;
|
|
3310
|
+
}
|
|
3311
|
+
} catch (error) {
|
|
3312
|
+
if (!isUniqueViolation(error)) {
|
|
3313
|
+
throw error;
|
|
3314
|
+
}
|
|
3315
|
+
}
|
|
3316
|
+
}
|
|
3317
|
+
if (!inserted) {
|
|
3318
|
+
throw new Error(
|
|
3319
|
+
`[services-registry] could not allocate a unique service_name for group=${serviceGroup} queue=${options.queue} (too many collisions).`
|
|
3320
|
+
);
|
|
3321
|
+
}
|
|
3322
|
+
identity.serviceName = inserted.service_name;
|
|
3323
|
+
await writeIdentityFile(identityPath, identity);
|
|
3324
|
+
const regNew = {
|
|
3325
|
+
instanceId,
|
|
3326
|
+
serviceName: inserted.service_name,
|
|
3327
|
+
serviceGroup,
|
|
3328
|
+
queue: options.queue,
|
|
3329
|
+
target: options.target,
|
|
3330
|
+
rowId: inserted.id
|
|
3331
|
+
};
|
|
3332
|
+
context.servicesRegistry = regNew;
|
|
3333
|
+
context.runnerHeartbeat = regNew;
|
|
3334
|
+
context.logger.info?.(
|
|
3335
|
+
`[services-registry] registered instance_id=${instanceId} name=${inserted.service_name} group=${serviceGroup} queue=${options.queue} target=${options.target}`
|
|
3336
|
+
);
|
|
3337
|
+
return {
|
|
3338
|
+
instanceId,
|
|
3339
|
+
serviceName: inserted.service_name,
|
|
3340
|
+
serviceGroup,
|
|
3341
|
+
queue: options.queue,
|
|
3342
|
+
target: options.target,
|
|
3343
|
+
rowId: inserted.id,
|
|
3344
|
+
registryTable
|
|
3345
|
+
};
|
|
3346
|
+
}
|
|
3347
|
+
async function touchServicesRegistry(context, registration) {
|
|
3348
|
+
const db = getDb2(context);
|
|
3349
|
+
const hostname = os.hostname();
|
|
3350
|
+
const pid = typeof process.pid === "number" ? process.pid : null;
|
|
3351
|
+
await db(registration.registryTable).where({ instance_id: registration.instanceId }).update({
|
|
3352
|
+
last_seen_at: db.fn.now(),
|
|
3353
|
+
hostname,
|
|
3354
|
+
pid
|
|
3355
|
+
});
|
|
3356
|
+
}
|
|
3357
|
+
async function unregisterServicesRegistry(context, registration) {
|
|
3358
|
+
const db = getDb2(context);
|
|
3359
|
+
await db(registration.registryTable).where({ instance_id: registration.instanceId }).delete();
|
|
3360
|
+
context.logger.info?.(`[services-registry] unregistered name=${registration.serviceName} instance_id=${registration.instanceId}`);
|
|
3361
|
+
}
|
|
3362
|
+
|
|
3115
3363
|
// src/filedatabase/index.ts
|
|
3116
3364
|
import fs3 from "fs";
|
|
3117
|
-
import
|
|
3365
|
+
import path4 from "path";
|
|
3118
3366
|
|
|
3119
3367
|
// src/filedatabase/serializers.ts
|
|
3120
3368
|
function detectDataType(data) {
|
|
@@ -3241,7 +3489,7 @@ var FileDatabase = class _FileDatabase {
|
|
|
3241
3489
|
if (this.versioned && version) {
|
|
3242
3490
|
parts.push(version);
|
|
3243
3491
|
}
|
|
3244
|
-
return
|
|
3492
|
+
return path4.resolve(...parts);
|
|
3245
3493
|
}
|
|
3246
3494
|
/**
|
|
3247
3495
|
* Set current version and version folder
|
|
@@ -3278,7 +3526,7 @@ var FileDatabase = class _FileDatabase {
|
|
|
3278
3526
|
this.currentFileNumber = 0;
|
|
3279
3527
|
const versions = await this.getVersions();
|
|
3280
3528
|
while (versions.length > this.maxVersions) {
|
|
3281
|
-
const versionToDelete =
|
|
3529
|
+
const versionToDelete = path4.resolve(this.getDestinationPath(), versions.shift());
|
|
3282
3530
|
this.logger.silly?.(`[FileDatabase] Deleting old version: ${versionToDelete}`);
|
|
3283
3531
|
await fs3.promises.rm(versionToDelete, { recursive: true, force: true });
|
|
3284
3532
|
}
|
|
@@ -3297,7 +3545,7 @@ var FileDatabase = class _FileDatabase {
|
|
|
3297
3545
|
await ensurePath(destPath);
|
|
3298
3546
|
const items = await fs3.promises.readdir(destPath);
|
|
3299
3547
|
const versions = items.filter((item) => {
|
|
3300
|
-
const itemPath =
|
|
3548
|
+
const itemPath = path4.join(destPath, item);
|
|
3301
3549
|
const stat = fs3.statSync(itemPath);
|
|
3302
3550
|
return stat.isDirectory() && isTimestampFolder(item);
|
|
3303
3551
|
});
|
|
@@ -3354,7 +3602,7 @@ var FileDatabase = class _FileDatabase {
|
|
|
3354
3602
|
const items = await fs3.promises.readdir(tablePath);
|
|
3355
3603
|
if (items.includes("metadata.json")) {
|
|
3356
3604
|
const metadata = JSON.parse(
|
|
3357
|
-
await fs3.promises.readFile(
|
|
3605
|
+
await fs3.promises.readFile(path4.join(tablePath, "metadata.json"), "utf8")
|
|
3358
3606
|
);
|
|
3359
3607
|
return {
|
|
3360
3608
|
versioned: false,
|
|
@@ -3363,13 +3611,13 @@ var FileDatabase = class _FileDatabase {
|
|
|
3363
3611
|
};
|
|
3364
3612
|
}
|
|
3365
3613
|
const versionFolders = items.filter((item) => {
|
|
3366
|
-
const itemPath =
|
|
3614
|
+
const itemPath = path4.join(tablePath, item);
|
|
3367
3615
|
const stat = fs3.statSync(itemPath);
|
|
3368
3616
|
return stat.isDirectory() && isTimestampFolder(item);
|
|
3369
3617
|
});
|
|
3370
3618
|
if (versionFolders.length > 0) {
|
|
3371
3619
|
const latestVersion = versionFolders.sort().pop();
|
|
3372
|
-
const versionMetadataPath =
|
|
3620
|
+
const versionMetadataPath = path4.join(tablePath, latestVersion, "metadata.json");
|
|
3373
3621
|
return {
|
|
3374
3622
|
versioned: true,
|
|
3375
3623
|
hasMetadata: fs3.existsSync(versionMetadataPath),
|
|
@@ -3390,7 +3638,7 @@ var FileDatabase = class _FileDatabase {
|
|
|
3390
3638
|
* Load metadata from JSON file
|
|
3391
3639
|
*/
|
|
3392
3640
|
async loadMetadataJson(version) {
|
|
3393
|
-
const metadataFile =
|
|
3641
|
+
const metadataFile = path4.join(this.getDestinationPath(), version, "metadata.json");
|
|
3394
3642
|
if (fs3.existsSync(metadataFile)) {
|
|
3395
3643
|
try {
|
|
3396
3644
|
const rawData = await fs3.promises.readFile(metadataFile, "utf8");
|
|
@@ -3406,7 +3654,7 @@ var FileDatabase = class _FileDatabase {
|
|
|
3406
3654
|
* Reads all files to get accurate counts - used when synopsis calculation is needed
|
|
3407
3655
|
*/
|
|
3408
3656
|
async figureMetadataFromVersionFiles(version) {
|
|
3409
|
-
const versionPath =
|
|
3657
|
+
const versionPath = path4.join(this.getDestinationPath(), version);
|
|
3410
3658
|
if (!fs3.existsSync(versionPath)) {
|
|
3411
3659
|
return this.getDefaultMetadata();
|
|
3412
3660
|
}
|
|
@@ -3418,10 +3666,10 @@ var FileDatabase = class _FileDatabase {
|
|
|
3418
3666
|
let detectedDataType = null;
|
|
3419
3667
|
for (let i = 0; i < files.length; i++) {
|
|
3420
3668
|
const fileName = files[i];
|
|
3421
|
-
const filePath =
|
|
3669
|
+
const filePath = path4.join(versionPath, fileName);
|
|
3422
3670
|
try {
|
|
3423
3671
|
const rawData = await fs3.promises.readFile(filePath, "utf8");
|
|
3424
|
-
const extension =
|
|
3672
|
+
const extension = path4.extname(fileName).toLowerCase();
|
|
3425
3673
|
let dataType = "text";
|
|
3426
3674
|
if (extension === ".json") {
|
|
3427
3675
|
dataType = "json-array";
|
|
@@ -3454,7 +3702,7 @@ var FileDatabase = class _FileDatabase {
|
|
|
3454
3702
|
* Much faster for large datasets with many files
|
|
3455
3703
|
*/
|
|
3456
3704
|
async buildMetadataOptimized(version) {
|
|
3457
|
-
const versionPath =
|
|
3705
|
+
const versionPath = path4.join(this.getDestinationPath(), version);
|
|
3458
3706
|
if (!fs3.existsSync(versionPath)) {
|
|
3459
3707
|
return this.getDefaultMetadata();
|
|
3460
3708
|
}
|
|
@@ -3470,7 +3718,7 @@ var FileDatabase = class _FileDatabase {
|
|
|
3470
3718
|
fileName
|
|
3471
3719
|
}));
|
|
3472
3720
|
const firstFile = metadata.files[0];
|
|
3473
|
-
const firstFilePath =
|
|
3721
|
+
const firstFilePath = path4.join(versionPath, firstFile.fileName);
|
|
3474
3722
|
const firstFileRaw = await fs3.promises.readFile(firstFilePath, "utf8");
|
|
3475
3723
|
let firstFileData;
|
|
3476
3724
|
try {
|
|
@@ -3487,7 +3735,7 @@ var FileDatabase = class _FileDatabase {
|
|
|
3487
3735
|
}
|
|
3488
3736
|
if (files.length > 1) {
|
|
3489
3737
|
const lastFile = metadata.files[metadata.files.length - 1];
|
|
3490
|
-
const lastFilePath =
|
|
3738
|
+
const lastFilePath = path4.join(versionPath, lastFile.fileName);
|
|
3491
3739
|
const lastFileRaw = await fs3.promises.readFile(lastFilePath, "utf8");
|
|
3492
3740
|
const lastFileData = deserializeData(lastFileRaw, metadata.dataType);
|
|
3493
3741
|
lastFile.recordsCount = Array.isArray(lastFileData) ? lastFileData.length : 1;
|
|
@@ -3538,9 +3786,9 @@ var FileDatabase = class _FileDatabase {
|
|
|
3538
3786
|
if (!this.currentVersion) {
|
|
3539
3787
|
return;
|
|
3540
3788
|
}
|
|
3541
|
-
metadataFile =
|
|
3789
|
+
metadataFile = path4.join(this.getDestinationPath(), this.currentVersion, "metadata.json");
|
|
3542
3790
|
} else {
|
|
3543
|
-
metadataFile =
|
|
3791
|
+
metadataFile = path4.join(this.getDestinationPath(), "metadata.json");
|
|
3544
3792
|
}
|
|
3545
3793
|
await fs3.promises.writeFile(metadataFile, JSON.stringify(metadataToSave, null, 4), "utf8");
|
|
3546
3794
|
}
|
|
@@ -3603,7 +3851,7 @@ var FileDatabase = class _FileDatabase {
|
|
|
3603
3851
|
}
|
|
3604
3852
|
}
|
|
3605
3853
|
if (!Array.isArray(data) && !forceNewFile) {
|
|
3606
|
-
const lastFileExtension =
|
|
3854
|
+
const lastFileExtension = path4.extname(lastFile.fileName);
|
|
3607
3855
|
const expectedExtension = `.${getFileExtension(incomingDataType)}`;
|
|
3608
3856
|
if (lastFileExtension !== expectedExtension) {
|
|
3609
3857
|
if (lastFileRecordsCount > 0) {
|
|
@@ -3613,7 +3861,7 @@ var FileDatabase = class _FileDatabase {
|
|
|
3613
3861
|
}
|
|
3614
3862
|
}
|
|
3615
3863
|
} else if (!Array.isArray(data) && forceNewFile) {
|
|
3616
|
-
const lastFileExtension =
|
|
3864
|
+
const lastFileExtension = path4.extname(lastFile.fileName);
|
|
3617
3865
|
const expectedExtension = `.${getFileExtension(incomingDataType)}`;
|
|
3618
3866
|
if (lastFileExtension !== expectedExtension) {
|
|
3619
3867
|
lastFile.fileName = `${this.currentFileNumber.toString().padStart(6, "0")}.${getFileExtension(incomingDataType)}`;
|
|
@@ -3703,7 +3951,7 @@ var FileDatabase = class _FileDatabase {
|
|
|
3703
3951
|
*/
|
|
3704
3952
|
async safeWrite(filePath, data) {
|
|
3705
3953
|
const serializedData = serializeData(data);
|
|
3706
|
-
const dir =
|
|
3954
|
+
const dir = path4.dirname(filePath);
|
|
3707
3955
|
const requiredBytes = Buffer.byteLength(serializedData, "utf8");
|
|
3708
3956
|
const freeBytes = getFreeDiskSpace(dir);
|
|
3709
3957
|
if (freeBytes !== null) {
|
|
@@ -3748,7 +3996,7 @@ var FileDatabase = class _FileDatabase {
|
|
|
3748
3996
|
} else {
|
|
3749
3997
|
await ensurePath(this.getDestinationPath());
|
|
3750
3998
|
if (this.useMetadata === true) {
|
|
3751
|
-
const metadataPath =
|
|
3999
|
+
const metadataPath = path4.join(this.getDestinationPath(), "metadata.json");
|
|
3752
4000
|
if (fs3.existsSync(metadataPath)) {
|
|
3753
4001
|
try {
|
|
3754
4002
|
const rawData = await fs3.promises.readFile(metadataPath, "utf8");
|
|
@@ -3801,7 +4049,7 @@ var FileDatabase = class _FileDatabase {
|
|
|
3801
4049
|
}
|
|
3802
4050
|
if (this.useMetadata) {
|
|
3803
4051
|
const destPath = this.getDestinationPath();
|
|
3804
|
-
const metadataPath =
|
|
4052
|
+
const metadataPath = path4.join(destPath, "metadata.json");
|
|
3805
4053
|
if (fs3.existsSync(metadataPath)) {
|
|
3806
4054
|
try {
|
|
3807
4055
|
const rawData = await fs3.promises.readFile(metadataPath, "utf8");
|
|
@@ -3837,7 +4085,7 @@ var FileDatabase = class _FileDatabase {
|
|
|
3837
4085
|
if (options.filename) {
|
|
3838
4086
|
const destPath2 = this.getDestinationPath();
|
|
3839
4087
|
await ensurePath(destPath2);
|
|
3840
|
-
const filePath =
|
|
4088
|
+
const filePath = path4.join(destPath2, options.filename);
|
|
3841
4089
|
await this.safeWrite(filePath, data);
|
|
3842
4090
|
return;
|
|
3843
4091
|
}
|
|
@@ -3886,11 +4134,11 @@ var FileDatabase = class _FileDatabase {
|
|
|
3886
4134
|
const forceNewFile = hasCustomMetadata && targetFileIndex === null;
|
|
3887
4135
|
let { dataToWrite, dataLeftOver, fileName } = this.figureOutDataAndFileToWrite(data, targetFileIndex, forceNewFile);
|
|
3888
4136
|
const destPath = this.getDestinationPath(this.currentVersion || void 0);
|
|
3889
|
-
await this.safeWrite(
|
|
4137
|
+
await this.safeWrite(path4.join(destPath, fileName), dataToWrite);
|
|
3890
4138
|
this.updateMetadata(dataToWrite, fileName, options.customMetadata);
|
|
3891
4139
|
while (dataLeftOver && dataLeftOver.length > 0 && targetFileIndex === null) {
|
|
3892
4140
|
const writeContext = this.figureOutDataAndFileToWrite(dataLeftOver);
|
|
3893
|
-
await this.safeWrite(
|
|
4141
|
+
await this.safeWrite(path4.join(destPath, writeContext.fileName), writeContext.dataToWrite);
|
|
3894
4142
|
this.updateMetadata(writeContext.dataToWrite, writeContext.fileName, options.customMetadata);
|
|
3895
4143
|
dataLeftOver = writeContext.dataLeftOver;
|
|
3896
4144
|
}
|
|
@@ -3906,7 +4154,7 @@ var FileDatabase = class _FileDatabase {
|
|
|
3906
4154
|
const { version, nextPage = false, pageSize, filename } = options;
|
|
3907
4155
|
if (filename) {
|
|
3908
4156
|
const destPath = this.getDestinationPath(version);
|
|
3909
|
-
const filePath =
|
|
4157
|
+
const filePath = path4.join(destPath, filename);
|
|
3910
4158
|
try {
|
|
3911
4159
|
const rawData = await fs3.promises.readFile(filePath, "utf8");
|
|
3912
4160
|
return JSON.parse(rawData);
|
|
@@ -3918,7 +4166,7 @@ var FileDatabase = class _FileDatabase {
|
|
|
3918
4166
|
const isNonPaginatedData = this.metadata.dataType === "text" || this.metadata.dataType === "xml" || this.metadata.dataType === "json-object";
|
|
3919
4167
|
if (isNonPaginatedData) {
|
|
3920
4168
|
const file = this.metadata.files[0];
|
|
3921
|
-
const filePath =
|
|
4169
|
+
const filePath = path4.join(this.getDestinationPath(this.currentVersion || void 0), file.fileName);
|
|
3922
4170
|
try {
|
|
3923
4171
|
const rawData = await fs3.promises.readFile(filePath, "utf8");
|
|
3924
4172
|
return deserializeData(rawData, this.metadata.dataType);
|
|
@@ -3956,7 +4204,7 @@ var FileDatabase = class _FileDatabase {
|
|
|
3956
4204
|
let cumulativeRecords = currentFileOffset;
|
|
3957
4205
|
for (let i = currentFileIndex; i < this.metadata.files.length && recordsRead < effectivePageSize; i++) {
|
|
3958
4206
|
const file = this.metadata.files[i];
|
|
3959
|
-
const filePath =
|
|
4207
|
+
const filePath = path4.join(this.getDestinationPath(this.currentVersion || void 0), file.fileName);
|
|
3960
4208
|
try {
|
|
3961
4209
|
const rawData = await fs3.promises.readFile(filePath, "utf8");
|
|
3962
4210
|
const fileData = deserializeData(rawData, this.metadata.dataType);
|
|
@@ -4000,7 +4248,7 @@ var FileDatabase = class _FileDatabase {
|
|
|
4000
4248
|
* Returns data file names (.json, .txt, .xml) excluding metadata.json.
|
|
4001
4249
|
*/
|
|
4002
4250
|
async listFilenames() {
|
|
4003
|
-
const destPath = this.versioned && this.currentVersion ?
|
|
4251
|
+
const destPath = this.versioned && this.currentVersion ? path4.join(this.getDestinationPath(), this.currentVersion) : this.getDestinationPath();
|
|
4004
4252
|
try {
|
|
4005
4253
|
const entries = await fs3.promises.readdir(destPath, { withFileTypes: true });
|
|
4006
4254
|
return entries.filter((e) => e.isFile() && e.name !== "metadata.json" && /\.(json|txt|xml)$/i.test(e.name)).map((e) => e.name);
|
|
@@ -4014,8 +4262,8 @@ var FileDatabase = class _FileDatabase {
|
|
|
4014
4262
|
* Use with listFilenames() to manage individual files.
|
|
4015
4263
|
*/
|
|
4016
4264
|
async removeFile(filename) {
|
|
4017
|
-
const destPath = this.versioned && this.currentVersion ?
|
|
4018
|
-
const filePath =
|
|
4265
|
+
const destPath = this.versioned && this.currentVersion ? path4.join(this.getDestinationPath(), this.currentVersion) : this.getDestinationPath();
|
|
4266
|
+
const filePath = path4.join(destPath, filename);
|
|
4019
4267
|
try {
|
|
4020
4268
|
await fs3.promises.unlink(filePath);
|
|
4021
4269
|
} catch (err) {
|
|
@@ -4041,7 +4289,7 @@ var FileDatabase = class _FileDatabase {
|
|
|
4041
4289
|
this.metadata.files.splice(idx, 1);
|
|
4042
4290
|
this.metadata.totalRecords = Math.max(0, (this.metadata.totalRecords || 0) - recordsCount);
|
|
4043
4291
|
const destPath = this.getDestinationPath();
|
|
4044
|
-
const filePath =
|
|
4292
|
+
const filePath = path4.join(destPath, filename);
|
|
4045
4293
|
try {
|
|
4046
4294
|
await fs3.promises.unlink(filePath);
|
|
4047
4295
|
} catch (err) {
|
|
@@ -4097,7 +4345,7 @@ var FileDatabase = class _FileDatabase {
|
|
|
4097
4345
|
});
|
|
4098
4346
|
if (matches) {
|
|
4099
4347
|
const destPath = this.getDestinationPath();
|
|
4100
|
-
const filePath =
|
|
4348
|
+
const filePath = path4.join(destPath, fileEntry.fileName);
|
|
4101
4349
|
const fileData = await fs3.promises.readFile(filePath, "utf8");
|
|
4102
4350
|
const data = deserializeData(fileData, metadata.dataType || "json-object");
|
|
4103
4351
|
results.push({
|
|
@@ -4120,7 +4368,7 @@ var FileDatabase = class _FileDatabase {
|
|
|
4120
4368
|
});
|
|
4121
4369
|
if (matches) {
|
|
4122
4370
|
const destPath = this.getDestinationPath(version);
|
|
4123
|
-
const filePath =
|
|
4371
|
+
const filePath = path4.join(destPath, fileEntry.fileName);
|
|
4124
4372
|
const fileData = await fs3.promises.readFile(filePath, "utf8");
|
|
4125
4373
|
const data = deserializeData(fileData, metadata.dataType || "json-object");
|
|
4126
4374
|
results.push({
|
|
@@ -4480,7 +4728,7 @@ var TaskShellCommand = class extends TaskMaster {
|
|
|
4480
4728
|
};
|
|
4481
4729
|
|
|
4482
4730
|
// src/tasks/coreTasks/TaskSystemInfo.ts
|
|
4483
|
-
import
|
|
4731
|
+
import os2 from "os";
|
|
4484
4732
|
import fs4 from "fs/promises";
|
|
4485
4733
|
function toGb(valueBytes) {
|
|
4486
4734
|
return `${(valueBytes / 1024 ** 3).toFixed(2)} GB`;
|
|
@@ -4502,10 +4750,10 @@ async function getDiskStats() {
|
|
|
4502
4750
|
var TaskSystemInfo = class extends TaskMaster {
|
|
4503
4751
|
async run() {
|
|
4504
4752
|
try {
|
|
4505
|
-
const totalMemory =
|
|
4506
|
-
const freeMemory =
|
|
4753
|
+
const totalMemory = os2.totalmem();
|
|
4754
|
+
const freeMemory = os2.freemem();
|
|
4507
4755
|
const usedMemory = totalMemory - freeMemory;
|
|
4508
|
-
const cpus =
|
|
4756
|
+
const cpus = os2.cpus();
|
|
4509
4757
|
const cpuUtilization = cpus.map((cpu) => {
|
|
4510
4758
|
const total = Object.values(cpu.times).reduce((acc, time) => acc + time, 0);
|
|
4511
4759
|
const usage = (total - cpu.times.idle) / total * 100;
|
|
@@ -4531,10 +4779,10 @@ var TaskSystemInfo = class extends TaskMaster {
|
|
|
4531
4779
|
utilization: cpuUtilization
|
|
4532
4780
|
},
|
|
4533
4781
|
runtime: {
|
|
4534
|
-
platform:
|
|
4535
|
-
arch:
|
|
4536
|
-
uptimeSec:
|
|
4537
|
-
hostname:
|
|
4782
|
+
platform: os2.platform(),
|
|
4783
|
+
arch: os2.arch(),
|
|
4784
|
+
uptimeSec: os2.uptime(),
|
|
4785
|
+
hostname: os2.hostname()
|
|
4538
4786
|
}
|
|
4539
4787
|
};
|
|
4540
4788
|
this.context.logger.info?.(`[TaskSystemInfo] collected system metrics (${this.task.id})`);
|
|
@@ -4637,7 +4885,7 @@ import { spawn as spawn2 } from "child_process";
|
|
|
4637
4885
|
// src/tasks/index.ts
|
|
4638
4886
|
var LOCKED_BY_ERROR_MESSAGE = "locked by error";
|
|
4639
4887
|
var defaultTasksRegistry = TasksRegistry.withCoreTasks();
|
|
4640
|
-
function
|
|
4888
|
+
function getDb3(context) {
|
|
4641
4889
|
const db = context.db;
|
|
4642
4890
|
if (!db) {
|
|
4643
4891
|
throw new Error("Tasks component requires context.db. Initialize DB first and attach to context.");
|
|
@@ -4672,7 +4920,7 @@ async function signalRunningTasksStop(context, runningTaskInstances, allowanceMs
|
|
|
4672
4920
|
context.emitter.emit("stop", allowanceMs);
|
|
4673
4921
|
}
|
|
4674
4922
|
async function executeClaimedTask(context, tasksTable, historyTable, row, registry, runningTaskInstances) {
|
|
4675
|
-
const db =
|
|
4923
|
+
const db = getDb3(context);
|
|
4676
4924
|
const taskName = row.task;
|
|
4677
4925
|
const TaskClass = registry.get(taskName);
|
|
4678
4926
|
const { paused_at: _pausedAt, ...rowForHistory } = row;
|
|
@@ -4773,7 +5021,7 @@ async function executeClaimedTask(context, tasksTable, historyTable, row, regist
|
|
|
4773
5021
|
return { stopRunnerRequested, stopAllowanceMs };
|
|
4774
5022
|
}
|
|
4775
5023
|
async function claimNextRunnableTask(context, tasksTable, target, registry, scanLimit, taskNames) {
|
|
4776
|
-
const db =
|
|
5024
|
+
const db = getDb3(context);
|
|
4777
5025
|
let query = db(tasksTable).whereNull("started_at").whereNull("paused_at").where({ target }).orderByRaw("CASE WHEN past_due IS NULL THEN 1 ELSE 0 END ASC").orderBy([{ column: "priority", order: "desc" }]).orderByRaw("CASE WHEN completed_at IS NULL THEN 0 ELSE 1 END ASC").orderBy([{ column: "completed_at", order: "asc" }, { column: "created_at", order: "asc" }]).limit(scanLimit);
|
|
4778
5026
|
if (taskNames && taskNames.length > 0) {
|
|
4779
5027
|
query = query.whereIn("task", taskNames);
|
|
@@ -4819,25 +5067,76 @@ async function runTasksLoop(context, options) {
|
|
|
4819
5067
|
let stopRequested = false;
|
|
4820
5068
|
let stopAllowanceMs = 5e3;
|
|
4821
5069
|
context.__tasksRunnerStop = false;
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
)
|
|
4832
|
-
|
|
4833
|
-
|
|
5070
|
+
let registryReg = null;
|
|
5071
|
+
let registryInterval = null;
|
|
5072
|
+
const hbGroup = options.runnerServiceGroup?.trim();
|
|
5073
|
+
if (hbGroup) {
|
|
5074
|
+
const identityDir = options.runnerIdentityDir ?? "./data/runner-identities";
|
|
5075
|
+
const hbIntervalMs = options.runnerHeartbeatIntervalMs ?? 1e4;
|
|
5076
|
+
const staleMs = options.runnerHeartbeatStaleMs ?? 45e3;
|
|
5077
|
+
const defaultMeta = {
|
|
5078
|
+
component: "tasks-runner",
|
|
5079
|
+
allowedTasks: allowedTasks?.length ? allowedTasks.join(",") : "all"
|
|
5080
|
+
};
|
|
5081
|
+
registryReg = await registerInServicesRegistry(context, {
|
|
5082
|
+
queue,
|
|
5083
|
+
target,
|
|
5084
|
+
serviceGroup: hbGroup,
|
|
5085
|
+
serviceName: options.runnerServiceName,
|
|
5086
|
+
identityDir,
|
|
5087
|
+
staleMs,
|
|
5088
|
+
groupMaxInstances: options.runnerGroupMaxInstances,
|
|
5089
|
+
enforceMaxInstances: options.runnerEnforceMaxInstances ?? true,
|
|
5090
|
+
metadata: options.runnerMetadata ?? defaultMeta
|
|
5091
|
+
});
|
|
5092
|
+
registryInterval = setInterval(() => {
|
|
5093
|
+
void touchServicesRegistry(context, registryReg).catch((err) => {
|
|
5094
|
+
context.logger.warn?.(`[services-registry] touch failed: ${err?.message ?? String(err)}`);
|
|
5095
|
+
});
|
|
5096
|
+
}, hbIntervalMs);
|
|
5097
|
+
}
|
|
5098
|
+
try {
|
|
5099
|
+
while (!context.isStop() && !stopRequested && !context.__tasksRunnerStop) {
|
|
5100
|
+
if (!runningStopControlPromise) {
|
|
5101
|
+
const claimedStopTask = await claimNextRunnableTask(
|
|
5102
|
+
context,
|
|
5103
|
+
tasksTable,
|
|
5104
|
+
target,
|
|
5105
|
+
registry,
|
|
5106
|
+
10,
|
|
5107
|
+
["stopRunner", "stop"]
|
|
5108
|
+
);
|
|
5109
|
+
if (claimedStopTask) {
|
|
5110
|
+
runningStopControlPromise = executeClaimedTask(
|
|
5111
|
+
context,
|
|
5112
|
+
tasksTable,
|
|
5113
|
+
historyTable,
|
|
5114
|
+
claimedStopTask,
|
|
5115
|
+
registry,
|
|
5116
|
+
runningTaskInstances
|
|
5117
|
+
).then(async (outcome) => {
|
|
5118
|
+
if (outcome.stopRunnerRequested && !stopRequested) {
|
|
5119
|
+
stopRequested = true;
|
|
5120
|
+
stopAllowanceMs = outcome.stopAllowanceMs || 5e3;
|
|
5121
|
+
context.__tasksRunnerStop = true;
|
|
5122
|
+
await signalRunningTasksStop(context, runningTaskInstances, stopAllowanceMs);
|
|
5123
|
+
}
|
|
5124
|
+
}).finally(() => {
|
|
5125
|
+
runningStopControlPromise = null;
|
|
5126
|
+
});
|
|
5127
|
+
}
|
|
5128
|
+
}
|
|
5129
|
+
while (runningPromises.size < maxParallel) {
|
|
5130
|
+
const claimed = await claimNextRunnableTask(
|
|
4834
5131
|
context,
|
|
4835
5132
|
tasksTable,
|
|
4836
|
-
|
|
4837
|
-
claimedStopTask,
|
|
5133
|
+
target,
|
|
4838
5134
|
registry,
|
|
4839
|
-
|
|
4840
|
-
|
|
5135
|
+
scanLimit,
|
|
5136
|
+
allowedTasks
|
|
5137
|
+
);
|
|
5138
|
+
if (!claimed) break;
|
|
5139
|
+
const p = executeClaimedTask(context, tasksTable, historyTable, claimed, registry, runningTaskInstances).then(async (outcome) => {
|
|
4841
5140
|
if (outcome.stopRunnerRequested && !stopRequested) {
|
|
4842
5141
|
stopRequested = true;
|
|
4843
5142
|
stopAllowanceMs = outcome.stopAllowanceMs || 5e3;
|
|
@@ -4845,49 +5144,41 @@ async function runTasksLoop(context, options) {
|
|
|
4845
5144
|
await signalRunningTasksStop(context, runningTaskInstances, stopAllowanceMs);
|
|
4846
5145
|
}
|
|
4847
5146
|
}).finally(() => {
|
|
4848
|
-
|
|
5147
|
+
runningPromises.delete(p);
|
|
4849
5148
|
});
|
|
5149
|
+
runningPromises.add(p);
|
|
5150
|
+
}
|
|
5151
|
+
await sleepMs(pollMs);
|
|
5152
|
+
}
|
|
5153
|
+
if (context.isStop() && !stopRequested) {
|
|
5154
|
+
await signalRunningTasksStop(context, runningTaskInstances, 5e3);
|
|
5155
|
+
}
|
|
5156
|
+
if (runningPromises.size > 0) {
|
|
5157
|
+
if (stopRequested) {
|
|
5158
|
+
await Promise.race([
|
|
5159
|
+
Promise.allSettled(Array.from(runningPromises)),
|
|
5160
|
+
sleepMs(stopAllowanceMs).then(() => {
|
|
5161
|
+
context.logger.warn?.(
|
|
5162
|
+
`[tasks] stop allowance (${stopAllowanceMs}ms) reached; ${runningPromises.size} task(s) still running`
|
|
5163
|
+
);
|
|
5164
|
+
})
|
|
5165
|
+
]);
|
|
5166
|
+
} else {
|
|
5167
|
+
await Promise.allSettled(Array.from(runningPromises));
|
|
4850
5168
|
}
|
|
4851
5169
|
}
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
target,
|
|
4857
|
-
registry,
|
|
4858
|
-
scanLimit,
|
|
4859
|
-
allowedTasks
|
|
4860
|
-
);
|
|
4861
|
-
if (!claimed) break;
|
|
4862
|
-
const p = executeClaimedTask(context, tasksTable, historyTable, claimed, registry, runningTaskInstances).then(async (outcome) => {
|
|
4863
|
-
if (outcome.stopRunnerRequested && !stopRequested) {
|
|
4864
|
-
stopRequested = true;
|
|
4865
|
-
stopAllowanceMs = outcome.stopAllowanceMs || 5e3;
|
|
4866
|
-
context.__tasksRunnerStop = true;
|
|
4867
|
-
await signalRunningTasksStop(context, runningTaskInstances, stopAllowanceMs);
|
|
4868
|
-
}
|
|
4869
|
-
}).finally(() => {
|
|
4870
|
-
runningPromises.delete(p);
|
|
4871
|
-
});
|
|
4872
|
-
runningPromises.add(p);
|
|
5170
|
+
} finally {
|
|
5171
|
+
if (registryInterval) {
|
|
5172
|
+
clearInterval(registryInterval);
|
|
5173
|
+
registryInterval = null;
|
|
4873
5174
|
}
|
|
4874
|
-
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
await Promise.race([
|
|
4882
|
-
Promise.allSettled(Array.from(runningPromises)),
|
|
4883
|
-
sleepMs(stopAllowanceMs).then(() => {
|
|
4884
|
-
context.logger.warn?.(
|
|
4885
|
-
`[tasks] stop allowance (${stopAllowanceMs}ms) reached; ${runningPromises.size} task(s) still running`
|
|
4886
|
-
);
|
|
4887
|
-
})
|
|
4888
|
-
]);
|
|
4889
|
-
} else {
|
|
4890
|
-
await Promise.allSettled(Array.from(runningPromises));
|
|
5175
|
+
if (registryReg) {
|
|
5176
|
+
await unregisterServicesRegistry(context, registryReg).catch((err) => {
|
|
5177
|
+
context.logger.warn?.(`[services-registry] unregister failed: ${err?.message ?? String(err)}`);
|
|
5178
|
+
});
|
|
5179
|
+
registryReg = null;
|
|
5180
|
+
delete context.servicesRegistry;
|
|
5181
|
+
delete context.runnerHeartbeat;
|
|
4891
5182
|
}
|
|
4892
5183
|
}
|
|
4893
5184
|
}
|
|
@@ -4901,6 +5192,14 @@ var TasksManager = class _TasksManager {
|
|
|
4901
5192
|
scanLimit;
|
|
4902
5193
|
allowedTasks;
|
|
4903
5194
|
registry;
|
|
5195
|
+
runnerServiceGroup;
|
|
5196
|
+
runnerServiceName;
|
|
5197
|
+
runnerIdentityDir;
|
|
5198
|
+
runnerHeartbeatIntervalMs;
|
|
5199
|
+
runnerHeartbeatStaleMs;
|
|
5200
|
+
runnerGroupMaxInstances;
|
|
5201
|
+
runnerEnforceMaxInstances;
|
|
5202
|
+
runnerMetadata;
|
|
4904
5203
|
constructor(context, options = {}) {
|
|
4905
5204
|
this.context = context;
|
|
4906
5205
|
this.queue = options.queue ?? "tasks";
|
|
@@ -4911,6 +5210,14 @@ var TasksManager = class _TasksManager {
|
|
|
4911
5210
|
this.scanLimit = options.scanLimit ?? 100;
|
|
4912
5211
|
this.allowedTasks = normalizeAllowedTasks(options.allowedTasks);
|
|
4913
5212
|
this.registry = normalizeRegistry(options.registry);
|
|
5213
|
+
this.runnerServiceGroup = options.runnerServiceGroup;
|
|
5214
|
+
this.runnerServiceName = options.runnerServiceName;
|
|
5215
|
+
this.runnerIdentityDir = options.runnerIdentityDir;
|
|
5216
|
+
this.runnerHeartbeatIntervalMs = options.runnerHeartbeatIntervalMs;
|
|
5217
|
+
this.runnerHeartbeatStaleMs = options.runnerHeartbeatStaleMs;
|
|
5218
|
+
this.runnerGroupMaxInstances = options.runnerGroupMaxInstances;
|
|
5219
|
+
this.runnerEnforceMaxInstances = options.runnerEnforceMaxInstances;
|
|
5220
|
+
this.runnerMetadata = options.runnerMetadata;
|
|
4914
5221
|
}
|
|
4915
5222
|
static init(context, options = {}) {
|
|
4916
5223
|
const defs2 = {
|
|
@@ -4920,7 +5227,14 @@ var TasksManager = class _TasksManager {
|
|
|
4920
5227
|
pollMs: "number default 1000",
|
|
4921
5228
|
maxParallel: "number default 1",
|
|
4922
5229
|
scanLimit: "number default 100",
|
|
4923
|
-
allowedTasks: "string"
|
|
5230
|
+
allowedTasks: "string",
|
|
5231
|
+
runnerServiceGroup: "string",
|
|
5232
|
+
runnerServiceName: "string",
|
|
5233
|
+
runnerIdentityDir: "string default ./data/runner-identities",
|
|
5234
|
+
runnerHeartbeatIntervalMs: "number default 10000",
|
|
5235
|
+
runnerHeartbeatStaleMs: "number default 45000",
|
|
5236
|
+
runnerGroupMaxInstances: "number",
|
|
5237
|
+
runnerEnforceMaxInstances: "boolean default true"
|
|
4924
5238
|
};
|
|
4925
5239
|
const discovered = context.params.getAllForModule(defs2);
|
|
4926
5240
|
const resolved = {
|
|
@@ -4931,6 +5245,13 @@ var TasksManager = class _TasksManager {
|
|
|
4931
5245
|
maxParallel: discovered.maxParallel,
|
|
4932
5246
|
scanLimit: discovered.scanLimit,
|
|
4933
5247
|
allowedTasks: discovered.allowedTasks,
|
|
5248
|
+
runnerServiceGroup: discovered.runnerServiceGroup,
|
|
5249
|
+
runnerServiceName: discovered.runnerServiceName,
|
|
5250
|
+
runnerIdentityDir: discovered.runnerIdentityDir,
|
|
5251
|
+
runnerHeartbeatIntervalMs: discovered.runnerHeartbeatIntervalMs,
|
|
5252
|
+
runnerHeartbeatStaleMs: discovered.runnerHeartbeatStaleMs,
|
|
5253
|
+
runnerGroupMaxInstances: discovered.runnerGroupMaxInstances,
|
|
5254
|
+
runnerEnforceMaxInstances: discovered.runnerEnforceMaxInstances,
|
|
4934
5255
|
...options
|
|
4935
5256
|
};
|
|
4936
5257
|
return new _TasksManager(context, resolved);
|
|
@@ -4949,7 +5270,15 @@ var TasksManager = class _TasksManager {
|
|
|
4949
5270
|
maxParallel: options.maxParallel ?? this.maxParallel,
|
|
4950
5271
|
scanLimit: options.scanLimit ?? this.scanLimit,
|
|
4951
5272
|
allowedTasks: options.allowedTasks ?? this.allowedTasks,
|
|
4952
|
-
registry: options.registry ?? this.registry
|
|
5273
|
+
registry: options.registry ?? this.registry,
|
|
5274
|
+
runnerServiceGroup: options.runnerServiceGroup ?? this.runnerServiceGroup,
|
|
5275
|
+
runnerServiceName: options.runnerServiceName ?? this.runnerServiceName,
|
|
5276
|
+
runnerIdentityDir: options.runnerIdentityDir ?? this.runnerIdentityDir,
|
|
5277
|
+
runnerHeartbeatIntervalMs: options.runnerHeartbeatIntervalMs ?? this.runnerHeartbeatIntervalMs,
|
|
5278
|
+
runnerHeartbeatStaleMs: options.runnerHeartbeatStaleMs ?? this.runnerHeartbeatStaleMs,
|
|
5279
|
+
runnerGroupMaxInstances: options.runnerGroupMaxInstances ?? this.runnerGroupMaxInstances,
|
|
5280
|
+
runnerEnforceMaxInstances: options.runnerEnforceMaxInstances ?? this.runnerEnforceMaxInstances,
|
|
5281
|
+
runnerMetadata: options.runnerMetadata ?? this.runnerMetadata
|
|
4953
5282
|
});
|
|
4954
5283
|
}
|
|
4955
5284
|
};
|
|
@@ -4960,7 +5289,7 @@ var defs = {
|
|
|
4960
5289
|
tasksModule: "string"
|
|
4961
5290
|
};
|
|
4962
5291
|
async function loadTasksModule(modulePath) {
|
|
4963
|
-
const absolute =
|
|
5292
|
+
const absolute = path5.isAbsolute(modulePath) ? modulePath : path5.resolve(process.cwd(), modulePath);
|
|
4964
5293
|
const imported = await import(pathToFileURL(absolute).href);
|
|
4965
5294
|
if (!imported.tasksRegistry || typeof imported.tasksRegistry !== "object") {
|
|
4966
5295
|
throw new Error(`tasksModule "${modulePath}" must export "tasksRegistry" object`);
|