@sentio/cli 3.4.1 → 3.5.0-rc.1
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/lib/index.js +285 -3
- package/package.json +1 -1
- package/src/api.ts +31 -0
- package/src/commands/processor.ts +366 -5
package/lib/index.js
CHANGED
|
@@ -150515,6 +150515,31 @@ async function postApiJson(apiPath, context, body, query) {
|
|
|
150515
150515
|
}
|
|
150516
150516
|
return await response.json();
|
|
150517
150517
|
}
|
|
150518
|
+
async function putApiJson(apiPath, context, body, query) {
|
|
150519
|
+
const url5 = getApiUrl(apiPath, context.host);
|
|
150520
|
+
if (query) {
|
|
150521
|
+
for (const [key, value] of Object.entries(query)) {
|
|
150522
|
+
if (value !== void 0) {
|
|
150523
|
+
url5.searchParams.set(key, String(value));
|
|
150524
|
+
}
|
|
150525
|
+
}
|
|
150526
|
+
}
|
|
150527
|
+
const response = await fetch(url5.href, {
|
|
150528
|
+
method: "PUT",
|
|
150529
|
+
headers: {
|
|
150530
|
+
...body !== void 0 ? { "Content-Type": "application/json" } : {},
|
|
150531
|
+
...context.headers
|
|
150532
|
+
},
|
|
150533
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
150534
|
+
});
|
|
150535
|
+
if (!response.ok) {
|
|
150536
|
+
const text = await response.text();
|
|
150537
|
+
throw new CliError(
|
|
150538
|
+
`Sentio API request failed: ${response.status} ${response.statusText}${text ? ` - ${text}` : ""}`
|
|
150539
|
+
);
|
|
150540
|
+
}
|
|
150541
|
+
return await response.json();
|
|
150542
|
+
}
|
|
150518
150543
|
function parseStructuredInput(content, source, filename) {
|
|
150519
150544
|
if (!content.trim()) {
|
|
150520
150545
|
throw new CliError(`Expected JSON or YAML in ${source}, but it was empty.`);
|
|
@@ -151913,6 +151938,10 @@ function createProcessorCommand() {
|
|
|
151913
151938
|
processorCommand.addCommand(createProcessorStatusCommand());
|
|
151914
151939
|
processorCommand.addCommand(createProcessorSourceCommand());
|
|
151915
151940
|
processorCommand.addCommand(createProcessorActivatePendingCommand());
|
|
151941
|
+
processorCommand.addCommand(createProcessorPauseCommand());
|
|
151942
|
+
processorCommand.addCommand(createProcessorResumeCommand());
|
|
151943
|
+
processorCommand.addCommand(createProcessorStopCommand());
|
|
151944
|
+
processorCommand.addCommand(createProcessorLogsCommand());
|
|
151916
151945
|
return processorCommand;
|
|
151917
151946
|
}
|
|
151918
151947
|
function createProcessorStatusCommand() {
|
|
@@ -151942,7 +151971,7 @@ function createProcessorActivatePendingCommand() {
|
|
|
151942
151971
|
withSharedProjectOptions3(
|
|
151943
151972
|
withAuthOptions3(new Command("activate-pending").description("Activate the pending version"))
|
|
151944
151973
|
)
|
|
151945
|
-
).showHelpAfterError().action(async (options, command) => {
|
|
151974
|
+
).showHelpAfterError().option("-y, --yes", "Bypass confirmation").action(async (options, command) => {
|
|
151946
151975
|
try {
|
|
151947
151976
|
await runActivatePending(options);
|
|
151948
151977
|
} catch (error) {
|
|
@@ -151950,6 +151979,50 @@ function createProcessorActivatePendingCommand() {
|
|
|
151950
151979
|
}
|
|
151951
151980
|
});
|
|
151952
151981
|
}
|
|
151982
|
+
function createProcessorPauseCommand() {
|
|
151983
|
+
return withOutputOptions3(
|
|
151984
|
+
withSharedProjectOptions3(withAuthOptions3(new Command("pause").description("Pause a processor")))
|
|
151985
|
+
).showHelpAfterError().argument("[processorId]", "ID of the processor").option("--reason <reason>", "Reason for pausing").option("-y, --yes", "Bypass confirmation").action(async (processorId, options, command) => {
|
|
151986
|
+
try {
|
|
151987
|
+
await runProcessorPause(processorId, options);
|
|
151988
|
+
} catch (error) {
|
|
151989
|
+
handleProcessorCommandError(error, command);
|
|
151990
|
+
}
|
|
151991
|
+
});
|
|
151992
|
+
}
|
|
151993
|
+
function createProcessorResumeCommand() {
|
|
151994
|
+
return withOutputOptions3(
|
|
151995
|
+
withSharedProjectOptions3(withAuthOptions3(new Command("resume").description("Resume a processor")))
|
|
151996
|
+
).showHelpAfterError().argument("[processorId]", "ID of the processor").option("-y, --yes", "Bypass confirmation").action(async (processorId, options, command) => {
|
|
151997
|
+
try {
|
|
151998
|
+
await runProcessorResume(processorId, options);
|
|
151999
|
+
} catch (error) {
|
|
152000
|
+
handleProcessorCommandError(error, command);
|
|
152001
|
+
}
|
|
152002
|
+
});
|
|
152003
|
+
}
|
|
152004
|
+
function createProcessorStopCommand() {
|
|
152005
|
+
return withOutputOptions3(
|
|
152006
|
+
withSharedProjectOptions3(withAuthOptions3(new Command("stop").description("Stop a processor")))
|
|
152007
|
+
).showHelpAfterError().argument("[processorId]", "ID of the processor").option("-y, --yes", "Bypass confirmation").action(async (processorId, options, command) => {
|
|
152008
|
+
try {
|
|
152009
|
+
await runProcessorStop(processorId, options);
|
|
152010
|
+
} catch (error) {
|
|
152011
|
+
handleProcessorCommandError(error, command);
|
|
152012
|
+
}
|
|
152013
|
+
});
|
|
152014
|
+
}
|
|
152015
|
+
function createProcessorLogsCommand() {
|
|
152016
|
+
return withOutputOptions3(
|
|
152017
|
+
withSharedProjectOptions3(withAuthOptions3(new Command("logs").description("View processor logs")))
|
|
152018
|
+
).showHelpAfterError().argument("[processorId]", "ID of the processor (defaults to active processor)").option("--limit <count>", "Maximum number of log entries to fetch", parseInteger2, 100).option("-f, --follow", "Poll for new log entries continuously").option("--log-type <type>", "Filter by log type (e.g. execution, system)").option("--level <level>", "Filter by log level: DEBUG, INFO, WARNING, ERROR").option("--query <query>", "Free-text filter query").action(async (processorId, options, command) => {
|
|
152019
|
+
try {
|
|
152020
|
+
await runProcessorLogs(processorId, options);
|
|
152021
|
+
} catch (error) {
|
|
152022
|
+
handleProcessorCommandError(error, command);
|
|
152023
|
+
}
|
|
152024
|
+
});
|
|
152025
|
+
}
|
|
151953
152026
|
async function runProcessorStatus(options) {
|
|
151954
152027
|
const context = createApiContext(options);
|
|
151955
152028
|
const project = await resolveProjectRef(options, context, { ownerSlug: true });
|
|
@@ -151998,6 +152071,32 @@ async function runProcessorSource(options) {
|
|
|
151998
152071
|
async function runActivatePending(options) {
|
|
151999
152072
|
const context = createApiContext(options);
|
|
152000
152073
|
const project = await resolveProjectRef(options, context, { ownerSlug: true });
|
|
152074
|
+
const statusResponse = await import_api5.ProcessorService.getProcessorStatusV2({
|
|
152075
|
+
path: {
|
|
152076
|
+
owner: project.owner,
|
|
152077
|
+
slug: project.slug
|
|
152078
|
+
},
|
|
152079
|
+
query: { version: "ALL" },
|
|
152080
|
+
headers: context.headers
|
|
152081
|
+
});
|
|
152082
|
+
const data = unwrapApiResult(statusResponse);
|
|
152083
|
+
const processors = Array.isArray(data.processors) ? data.processors : [];
|
|
152084
|
+
const activeProcessor = processors.find((p7) => asString3(p7.versionState) === "ACTIVE");
|
|
152085
|
+
const pendingProcessor = processors.find((p7) => asString3(p7.versionState) === "PENDING");
|
|
152086
|
+
if (!pendingProcessor) {
|
|
152087
|
+
throw new CliError(`No pending version found for project ${project.owner}/${project.slug}.`);
|
|
152088
|
+
}
|
|
152089
|
+
if (!options.yes) {
|
|
152090
|
+
let message = `Activate the pending version ${asNumber(pendingProcessor.version)}. Are you sure you want to proceed?`;
|
|
152091
|
+
if (activeProcessor) {
|
|
152092
|
+
message = `Activate the pending version ${asNumber(pendingProcessor.version)} may obsolete the active version ${asNumber(activeProcessor.version)}. Are you sure you want to proceed?`;
|
|
152093
|
+
}
|
|
152094
|
+
const isConfirmed = await confirm(message);
|
|
152095
|
+
if (!isConfirmed) {
|
|
152096
|
+
console.log("Activation cancelled.");
|
|
152097
|
+
return;
|
|
152098
|
+
}
|
|
152099
|
+
}
|
|
152001
152100
|
const response = await import_api5.ProcessorService.activatePendingVersion({
|
|
152002
152101
|
path: {
|
|
152003
152102
|
owner: project.owner,
|
|
@@ -152010,6 +152109,168 @@ async function runActivatePending(options) {
|
|
|
152010
152109
|
...unwrapApiResult(response)
|
|
152011
152110
|
});
|
|
152012
152111
|
}
|
|
152112
|
+
async function resolveAndConfirmProcessor(actionName, processorId, options) {
|
|
152113
|
+
const context = createApiContext(options);
|
|
152114
|
+
let resolvedProcessorId = processorId;
|
|
152115
|
+
let versionToConfirm = "";
|
|
152116
|
+
if (!resolvedProcessorId) {
|
|
152117
|
+
const project = await resolveProjectRef(options, context, { ownerSlug: true });
|
|
152118
|
+
const statusResponse = await import_api5.ProcessorService.getProcessorStatusV2({
|
|
152119
|
+
path: {
|
|
152120
|
+
owner: project.owner,
|
|
152121
|
+
slug: project.slug
|
|
152122
|
+
},
|
|
152123
|
+
query: { version: "ACTIVE" },
|
|
152124
|
+
headers: context.headers
|
|
152125
|
+
});
|
|
152126
|
+
const data = unwrapApiResult(statusResponse);
|
|
152127
|
+
const processors = Array.isArray(data.processors) ? data.processors : [];
|
|
152128
|
+
const activeProcessor = processors.find((p7) => asString3(p7.versionState) === "ACTIVE");
|
|
152129
|
+
if (!activeProcessor || !activeProcessor.processorId) {
|
|
152130
|
+
throw new CliError(
|
|
152131
|
+
`No active processor found for project ${project.owner}/${project.slug}. Please specify a processorId.`
|
|
152132
|
+
);
|
|
152133
|
+
}
|
|
152134
|
+
resolvedProcessorId = asString3(activeProcessor.processorId);
|
|
152135
|
+
versionToConfirm = `version ${asNumber(activeProcessor.version)} of project ${project.owner}/${project.slug}`;
|
|
152136
|
+
} else {
|
|
152137
|
+
versionToConfirm = `processor ${resolvedProcessorId}`;
|
|
152138
|
+
}
|
|
152139
|
+
if (!options.yes) {
|
|
152140
|
+
const isConfirmed = await confirm(`Are you sure you want to ${actionName} ${versionToConfirm}?`);
|
|
152141
|
+
if (!isConfirmed) {
|
|
152142
|
+
console.log(`${actionName.charAt(0).toUpperCase() + actionName.slice(1)} cancelled.`);
|
|
152143
|
+
return void 0;
|
|
152144
|
+
}
|
|
152145
|
+
}
|
|
152146
|
+
return resolvedProcessorId;
|
|
152147
|
+
}
|
|
152148
|
+
async function runProcessorPause(processorId, options) {
|
|
152149
|
+
const resolvedProcessorId = await resolveAndConfirmProcessor("pause", processorId, options);
|
|
152150
|
+
if (!resolvedProcessorId) return;
|
|
152151
|
+
const context = createApiContext(options);
|
|
152152
|
+
const response = await putApiJson(`/api/v1/processors/${resolvedProcessorId}/pause`, context, {
|
|
152153
|
+
reason: options.reason
|
|
152154
|
+
});
|
|
152155
|
+
printOutput3(options, { processorId: resolvedProcessorId, action: "paused", ...response });
|
|
152156
|
+
}
|
|
152157
|
+
async function runProcessorResume(processorId, options) {
|
|
152158
|
+
const resolvedProcessorId = await resolveAndConfirmProcessor("resume", processorId, options);
|
|
152159
|
+
if (!resolvedProcessorId) return;
|
|
152160
|
+
const context = createApiContext(options);
|
|
152161
|
+
const response = await putApiJson(`/api/v1/processors/${resolvedProcessorId}/resume`, context);
|
|
152162
|
+
printOutput3(options, {
|
|
152163
|
+
processorId: resolvedProcessorId,
|
|
152164
|
+
action: "resumed",
|
|
152165
|
+
...response
|
|
152166
|
+
});
|
|
152167
|
+
}
|
|
152168
|
+
async function runProcessorStop(processorId, options) {
|
|
152169
|
+
const resolvedProcessorId = await resolveAndConfirmProcessor("stop", processorId, options);
|
|
152170
|
+
if (!resolvedProcessorId) return;
|
|
152171
|
+
const context = createApiContext(options);
|
|
152172
|
+
const response = await postApiJson(`/api/v1/processors/stop`, context, { processorId: resolvedProcessorId });
|
|
152173
|
+
printOutput3(options, {
|
|
152174
|
+
processorId: resolvedProcessorId,
|
|
152175
|
+
action: "stopped",
|
|
152176
|
+
...response
|
|
152177
|
+
});
|
|
152178
|
+
}
|
|
152179
|
+
async function resolveProcessorId(processorId, options) {
|
|
152180
|
+
if (processorId) return processorId;
|
|
152181
|
+
const context = createApiContext(options);
|
|
152182
|
+
const project = await resolveProjectRef(options, context, { ownerSlug: true });
|
|
152183
|
+
const statusResponse = await import_api5.ProcessorService.getProcessorStatusV2({
|
|
152184
|
+
path: { owner: project.owner, slug: project.slug },
|
|
152185
|
+
query: { version: "ACTIVE" },
|
|
152186
|
+
headers: context.headers
|
|
152187
|
+
});
|
|
152188
|
+
const data = unwrapApiResult(statusResponse);
|
|
152189
|
+
const processors = Array.isArray(data.processors) ? data.processors : [];
|
|
152190
|
+
const activeProcessor = processors.find((p7) => asString3(p7.versionState) === "ACTIVE");
|
|
152191
|
+
if (!activeProcessor || !activeProcessor.processorId) {
|
|
152192
|
+
throw new CliError(
|
|
152193
|
+
`No active processor found for project ${project.owner}/${project.slug}. Please specify a processorId.`
|
|
152194
|
+
);
|
|
152195
|
+
}
|
|
152196
|
+
return asString3(activeProcessor.processorId);
|
|
152197
|
+
}
|
|
152198
|
+
async function runProcessorLogs(processorId, options) {
|
|
152199
|
+
const resolvedProcessorId = await resolveProcessorId(processorId, options);
|
|
152200
|
+
const context = createApiContext(options);
|
|
152201
|
+
if (options.follow && !options.json && !options.yaml) {
|
|
152202
|
+
await followProcessorLogs(resolvedProcessorId, context, options);
|
|
152203
|
+
return;
|
|
152204
|
+
}
|
|
152205
|
+
const response = await postApiJson(
|
|
152206
|
+
`/api/v1/processors/${resolvedProcessorId}/logs`,
|
|
152207
|
+
context,
|
|
152208
|
+
buildLogsRequestBody(resolvedProcessorId, options)
|
|
152209
|
+
);
|
|
152210
|
+
printOutput3(options, response);
|
|
152211
|
+
}
|
|
152212
|
+
async function followProcessorLogs(processorId, context, options) {
|
|
152213
|
+
let until;
|
|
152214
|
+
let running = true;
|
|
152215
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
152216
|
+
process19.on("SIGINT", () => {
|
|
152217
|
+
running = false;
|
|
152218
|
+
});
|
|
152219
|
+
while (running) {
|
|
152220
|
+
try {
|
|
152221
|
+
const body = { ...buildLogsRequestBody(processorId, options), until };
|
|
152222
|
+
const response = await postApiJson(`/api/v1/processors/${processorId}/logs`, context, body);
|
|
152223
|
+
const entries2 = Array.isArray(response.logs) ? response.logs : [];
|
|
152224
|
+
for (const entry of entries2) {
|
|
152225
|
+
const e10 = entry;
|
|
152226
|
+
const id = e10.id ?? "";
|
|
152227
|
+
if (id && seenIds.has(id)) continue;
|
|
152228
|
+
if (id) seenIds.add(id);
|
|
152229
|
+
process19.stdout.write(formatLogEntry(e10) + "\n");
|
|
152230
|
+
}
|
|
152231
|
+
if (response.until) {
|
|
152232
|
+
until = response.until;
|
|
152233
|
+
}
|
|
152234
|
+
} catch {
|
|
152235
|
+
}
|
|
152236
|
+
await new Promise((resolve) => setTimeout(resolve, 2e3));
|
|
152237
|
+
}
|
|
152238
|
+
}
|
|
152239
|
+
function buildLogsRequestBody(processorId, options) {
|
|
152240
|
+
const body = { processorId, limit: options.limit };
|
|
152241
|
+
if (options.logType) {
|
|
152242
|
+
body.logTypeFilters = [options.logType];
|
|
152243
|
+
}
|
|
152244
|
+
if (options.level || options.query) {
|
|
152245
|
+
const parts = [];
|
|
152246
|
+
if (options.level) parts.push(options.level.toUpperCase());
|
|
152247
|
+
if (options.query) parts.push(options.query);
|
|
152248
|
+
body.query = parts.join(" ");
|
|
152249
|
+
}
|
|
152250
|
+
return body;
|
|
152251
|
+
}
|
|
152252
|
+
function formatLogEntry(entry) {
|
|
152253
|
+
const formattedTime = entry.timestamp ? source_default.gray(entry.timestamp.replace("T", " ").replace("Z", "")) : "";
|
|
152254
|
+
const level = (entry.level ?? "INFO").toUpperCase();
|
|
152255
|
+
const logType = entry.logType ? source_default.gray(`[${entry.logType}]`) : "";
|
|
152256
|
+
const coloredLevel = colorSeverity(level);
|
|
152257
|
+
const message = entry.message ?? "";
|
|
152258
|
+
const chain4 = entry.chainId ? source_default.gray(`(chain=${entry.chainId})`) : "";
|
|
152259
|
+
return [formattedTime, coloredLevel, logType, message, chain4].filter(Boolean).join(" ");
|
|
152260
|
+
}
|
|
152261
|
+
function colorSeverity(severity) {
|
|
152262
|
+
switch (severity) {
|
|
152263
|
+
case "ERROR":
|
|
152264
|
+
return source_default.red(`[${severity}]`);
|
|
152265
|
+
case "WARNING":
|
|
152266
|
+
case "WARN":
|
|
152267
|
+
return source_default.yellow(`[${severity}]`);
|
|
152268
|
+
case "DEBUG":
|
|
152269
|
+
return source_default.gray(`[${severity}]`);
|
|
152270
|
+
default:
|
|
152271
|
+
return source_default.cyan(`[${severity}]`);
|
|
152272
|
+
}
|
|
152273
|
+
}
|
|
152013
152274
|
function withAuthOptions3(command) {
|
|
152014
152275
|
return command.option("--host <host>", "Override Sentio host").option("--api-key <key>", "Use an explicit API key instead of saved credentials").option("--token <token>", "Use an explicit bearer token instead of saved credentials");
|
|
152015
152276
|
}
|
|
@@ -152053,7 +152314,8 @@ function formatOutput2(data) {
|
|
|
152053
152314
|
lines.push(`${group.versionState} (${group.processors.length})`);
|
|
152054
152315
|
for (const processor of group.processors) {
|
|
152055
152316
|
const version2 = asNumber(processor.version);
|
|
152056
|
-
const
|
|
152317
|
+
const processorStatus = processor.processorStatus;
|
|
152318
|
+
const statusState = asString3(processorStatus?.state) ?? "UNKNOWN";
|
|
152057
152319
|
const uploadedAt = asString3(processor.uploadedAt);
|
|
152058
152320
|
lines.push(`- v${version2 ?? "?"} status=${statusState}${uploadedAt ? ` uploaded=${uploadedAt}` : ""}`);
|
|
152059
152321
|
if (asString3(processor.processorId)) {
|
|
@@ -152063,9 +152325,17 @@ function formatOutput2(data) {
|
|
|
152063
152325
|
for (const stateEntry of states.slice(0, 5)) {
|
|
152064
152326
|
const state = stateEntry;
|
|
152065
152327
|
const chainId = asString3(state.chainId) ?? "?";
|
|
152066
|
-
const
|
|
152328
|
+
const stateStatus = state.status;
|
|
152329
|
+
const chainState = asString3(stateStatus?.state) ?? "UNKNOWN";
|
|
152067
152330
|
const block = asString3(state.processedBlockNumber) ?? "?";
|
|
152068
152331
|
lines.push(` chain ${chainId}: ${chainState} block=${block}`);
|
|
152332
|
+
const errorRecord = stateStatus?.errorRecord;
|
|
152333
|
+
const chainError = asString3(errorRecord?.message);
|
|
152334
|
+
if (chainError) {
|
|
152335
|
+
const createdAt = asString3(errorRecord?.createdAt);
|
|
152336
|
+
const prefix = createdAt ? `[${createdAt}] ` : "";
|
|
152337
|
+
lines.push(` error: ${prefix}${chainError}`);
|
|
152338
|
+
}
|
|
152069
152339
|
}
|
|
152070
152340
|
if (states.length > 5) {
|
|
152071
152341
|
lines.push(` ... ${states.length - 5} more chains`);
|
|
@@ -152090,6 +152360,18 @@ function formatOutput2(data) {
|
|
|
152090
152360
|
const objectData = data;
|
|
152091
152361
|
return `Pending processor version activated for ${asString3(objectData.project) ?? "<project>"}.`;
|
|
152092
152362
|
}
|
|
152363
|
+
if (data && typeof data === "object" && "action" in data && "processorId" in data) {
|
|
152364
|
+
const objectData = data;
|
|
152365
|
+
return `Processor ${asString3(objectData.processorId)} successfully ${asString3(objectData.action)}.`;
|
|
152366
|
+
}
|
|
152367
|
+
if (data && typeof data === "object" && "logs" in data) {
|
|
152368
|
+
const logsData = data;
|
|
152369
|
+
const entries2 = Array.isArray(logsData.logs) ? logsData.logs : [];
|
|
152370
|
+
if (entries2.length === 0) {
|
|
152371
|
+
return "No logs found.";
|
|
152372
|
+
}
|
|
152373
|
+
return entries2.map((entry) => formatLogEntry(entry)).join("\n");
|
|
152374
|
+
}
|
|
152093
152375
|
return JSON.stringify(data, null, 2);
|
|
152094
152376
|
}
|
|
152095
152377
|
function normalizeVersionSelector(value) {
|
package/package.json
CHANGED
package/src/api.ts
CHANGED
|
@@ -420,6 +420,37 @@ export async function postApiJson<T>(
|
|
|
420
420
|
return (await response.json()) as T
|
|
421
421
|
}
|
|
422
422
|
|
|
423
|
+
export async function putApiJson<T>(
|
|
424
|
+
apiPath: string,
|
|
425
|
+
context: ApiContext,
|
|
426
|
+
body?: unknown,
|
|
427
|
+
query?: Record<string, string | number | boolean | undefined>
|
|
428
|
+
): Promise<T> {
|
|
429
|
+
const url = getApiUrl(apiPath, context.host)
|
|
430
|
+
if (query) {
|
|
431
|
+
for (const [key, value] of Object.entries(query)) {
|
|
432
|
+
if (value !== undefined) {
|
|
433
|
+
url.searchParams.set(key, String(value))
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
const response = await fetch(url.href, {
|
|
438
|
+
method: 'PUT',
|
|
439
|
+
headers: {
|
|
440
|
+
...(body !== undefined ? { 'Content-Type': 'application/json' } : {}),
|
|
441
|
+
...context.headers
|
|
442
|
+
},
|
|
443
|
+
body: body !== undefined ? JSON.stringify(body) : undefined
|
|
444
|
+
})
|
|
445
|
+
if (!response.ok) {
|
|
446
|
+
const text = await response.text()
|
|
447
|
+
throw new CliError(
|
|
448
|
+
`Sentio API request failed: ${response.status} ${response.statusText}${text ? ` - ${text}` : ''}`
|
|
449
|
+
)
|
|
450
|
+
}
|
|
451
|
+
return (await response.json()) as T
|
|
452
|
+
}
|
|
453
|
+
|
|
423
454
|
function parseStructuredInput(content: string, source: string, filename?: string) {
|
|
424
455
|
if (!content.trim()) {
|
|
425
456
|
throw new CliError(`Expected JSON or YAML in ${source}, but it was empty.`)
|
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
import { ProcessorExtService, ProcessorService } from '@sentio/api'
|
|
2
2
|
import { Command, InvalidArgumentError } from '@commander-js/extra-typings'
|
|
3
|
+
import chalk from 'chalk'
|
|
3
4
|
import process from 'process'
|
|
4
5
|
import yaml from 'yaml'
|
|
5
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
CliError,
|
|
8
|
+
createApiContext,
|
|
9
|
+
handleCommandError,
|
|
10
|
+
resolveProjectRef,
|
|
11
|
+
unwrapApiResult,
|
|
12
|
+
postApiJson,
|
|
13
|
+
putApiJson
|
|
14
|
+
} from '../api.js'
|
|
15
|
+
import { confirm } from './upload.js'
|
|
6
16
|
|
|
7
17
|
interface ProcessorOptions {
|
|
8
18
|
host?: string
|
|
@@ -25,11 +35,23 @@ interface ProcessorSourceOptions extends ProcessorOptions {
|
|
|
25
35
|
path?: string
|
|
26
36
|
}
|
|
27
37
|
|
|
38
|
+
interface ProcessorLogsOptions extends ProcessorOptions {
|
|
39
|
+
limit?: number
|
|
40
|
+
follow?: boolean
|
|
41
|
+
logType?: string
|
|
42
|
+
level?: string
|
|
43
|
+
query?: string
|
|
44
|
+
}
|
|
45
|
+
|
|
28
46
|
export function createProcessorCommand() {
|
|
29
47
|
const processorCommand = new Command('processor').description('Manage Sentio processor versions')
|
|
30
48
|
processorCommand.addCommand(createProcessorStatusCommand())
|
|
31
49
|
processorCommand.addCommand(createProcessorSourceCommand())
|
|
32
50
|
processorCommand.addCommand(createProcessorActivatePendingCommand())
|
|
51
|
+
processorCommand.addCommand(createProcessorPauseCommand())
|
|
52
|
+
processorCommand.addCommand(createProcessorResumeCommand())
|
|
53
|
+
processorCommand.addCommand(createProcessorStopCommand())
|
|
54
|
+
processorCommand.addCommand(createProcessorLogsCommand())
|
|
33
55
|
return processorCommand
|
|
34
56
|
}
|
|
35
57
|
|
|
@@ -71,6 +93,7 @@ function createProcessorActivatePendingCommand() {
|
|
|
71
93
|
)
|
|
72
94
|
)
|
|
73
95
|
.showHelpAfterError()
|
|
96
|
+
.option('-y, --yes', 'Bypass confirmation')
|
|
74
97
|
.action(async (options, command) => {
|
|
75
98
|
try {
|
|
76
99
|
await runActivatePending(options)
|
|
@@ -80,6 +103,75 @@ function createProcessorActivatePendingCommand() {
|
|
|
80
103
|
})
|
|
81
104
|
}
|
|
82
105
|
|
|
106
|
+
function createProcessorPauseCommand() {
|
|
107
|
+
return withOutputOptions(
|
|
108
|
+
withSharedProjectOptions(withAuthOptions(new Command('pause').description('Pause a processor')))
|
|
109
|
+
)
|
|
110
|
+
.showHelpAfterError()
|
|
111
|
+
.argument('[processorId]', 'ID of the processor')
|
|
112
|
+
.option('--reason <reason>', 'Reason for pausing')
|
|
113
|
+
.option('-y, --yes', 'Bypass confirmation')
|
|
114
|
+
.action(async (processorId, options, command) => {
|
|
115
|
+
try {
|
|
116
|
+
await runProcessorPause(processorId, options)
|
|
117
|
+
} catch (error) {
|
|
118
|
+
handleProcessorCommandError(error, command)
|
|
119
|
+
}
|
|
120
|
+
})
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function createProcessorResumeCommand() {
|
|
124
|
+
return withOutputOptions(
|
|
125
|
+
withSharedProjectOptions(withAuthOptions(new Command('resume').description('Resume a processor')))
|
|
126
|
+
)
|
|
127
|
+
.showHelpAfterError()
|
|
128
|
+
.argument('[processorId]', 'ID of the processor')
|
|
129
|
+
.option('-y, --yes', 'Bypass confirmation')
|
|
130
|
+
.action(async (processorId, options, command) => {
|
|
131
|
+
try {
|
|
132
|
+
await runProcessorResume(processorId, options)
|
|
133
|
+
} catch (error) {
|
|
134
|
+
handleProcessorCommandError(error, command)
|
|
135
|
+
}
|
|
136
|
+
})
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function createProcessorStopCommand() {
|
|
140
|
+
return withOutputOptions(
|
|
141
|
+
withSharedProjectOptions(withAuthOptions(new Command('stop').description('Stop a processor')))
|
|
142
|
+
)
|
|
143
|
+
.showHelpAfterError()
|
|
144
|
+
.argument('[processorId]', 'ID of the processor')
|
|
145
|
+
.option('-y, --yes', 'Bypass confirmation')
|
|
146
|
+
.action(async (processorId, options, command) => {
|
|
147
|
+
try {
|
|
148
|
+
await runProcessorStop(processorId, options)
|
|
149
|
+
} catch (error) {
|
|
150
|
+
handleProcessorCommandError(error, command)
|
|
151
|
+
}
|
|
152
|
+
})
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function createProcessorLogsCommand() {
|
|
156
|
+
return withOutputOptions(
|
|
157
|
+
withSharedProjectOptions(withAuthOptions(new Command('logs').description('View processor logs')))
|
|
158
|
+
)
|
|
159
|
+
.showHelpAfterError()
|
|
160
|
+
.argument('[processorId]', 'ID of the processor (defaults to active processor)')
|
|
161
|
+
.option('--limit <count>', 'Maximum number of log entries to fetch', parseInteger, 100)
|
|
162
|
+
.option('-f, --follow', 'Poll for new log entries continuously')
|
|
163
|
+
.option('--log-type <type>', 'Filter by log type (e.g. execution, system)')
|
|
164
|
+
.option('--level <level>', 'Filter by log level: DEBUG, INFO, WARNING, ERROR')
|
|
165
|
+
.option('--query <query>', 'Free-text filter query')
|
|
166
|
+
.action(async (processorId, options, command) => {
|
|
167
|
+
try {
|
|
168
|
+
await runProcessorLogs(processorId, options)
|
|
169
|
+
} catch (error) {
|
|
170
|
+
handleProcessorCommandError(error, command)
|
|
171
|
+
}
|
|
172
|
+
})
|
|
173
|
+
}
|
|
174
|
+
|
|
83
175
|
async function runProcessorStatus(options: ProcessorStatusOptions) {
|
|
84
176
|
const context = createApiContext(options)
|
|
85
177
|
const project = await resolveProjectRef(options, context, { ownerSlug: true })
|
|
@@ -127,9 +219,41 @@ async function runProcessorSource(options: ProcessorSourceOptions) {
|
|
|
127
219
|
printOutput(options, data)
|
|
128
220
|
}
|
|
129
221
|
|
|
130
|
-
async function runActivatePending(options: ProcessorOptions) {
|
|
222
|
+
async function runActivatePending(options: ProcessorOptions & { yes?: boolean }) {
|
|
131
223
|
const context = createApiContext(options)
|
|
132
224
|
const project = await resolveProjectRef(options, context, { ownerSlug: true })
|
|
225
|
+
|
|
226
|
+
const statusResponse = await ProcessorService.getProcessorStatusV2({
|
|
227
|
+
path: {
|
|
228
|
+
owner: project.owner,
|
|
229
|
+
slug: project.slug
|
|
230
|
+
},
|
|
231
|
+
query: { version: 'ALL' },
|
|
232
|
+
headers: context.headers
|
|
233
|
+
})
|
|
234
|
+
const data = unwrapApiResult(statusResponse)
|
|
235
|
+
const processors = Array.isArray(data.processors) ? data.processors : []
|
|
236
|
+
|
|
237
|
+
const activeProcessor = processors.find((p) => asString(p.versionState) === 'ACTIVE')
|
|
238
|
+
const pendingProcessor = processors.find((p) => asString(p.versionState) === 'PENDING')
|
|
239
|
+
|
|
240
|
+
if (!pendingProcessor) {
|
|
241
|
+
throw new CliError(`No pending version found for project ${project.owner}/${project.slug}.`)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (!options.yes) {
|
|
245
|
+
let message = `Activate the pending version ${asNumber(pendingProcessor.version)}. Are you sure you want to proceed?`
|
|
246
|
+
if (activeProcessor) {
|
|
247
|
+
message = `Activate the pending version ${asNumber(pendingProcessor.version)} may obsolete the active version ${asNumber(activeProcessor.version)}. Are you sure you want to proceed?`
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const isConfirmed = await confirm(message)
|
|
251
|
+
if (!isConfirmed) {
|
|
252
|
+
console.log('Activation cancelled.')
|
|
253
|
+
return
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
133
257
|
const response = await ProcessorService.activatePendingVersion({
|
|
134
258
|
path: {
|
|
135
259
|
owner: project.owner,
|
|
@@ -143,6 +267,216 @@ async function runActivatePending(options: ProcessorOptions) {
|
|
|
143
267
|
})
|
|
144
268
|
}
|
|
145
269
|
|
|
270
|
+
async function resolveAndConfirmProcessor(
|
|
271
|
+
actionName: string,
|
|
272
|
+
processorId: string | undefined,
|
|
273
|
+
options: ProcessorOptions & { yes?: boolean }
|
|
274
|
+
): Promise<string | undefined> {
|
|
275
|
+
const context = createApiContext(options)
|
|
276
|
+
let resolvedProcessorId = processorId
|
|
277
|
+
let versionToConfirm = ''
|
|
278
|
+
|
|
279
|
+
if (!resolvedProcessorId) {
|
|
280
|
+
const project = await resolveProjectRef(options, context, { ownerSlug: true })
|
|
281
|
+
const statusResponse = await ProcessorService.getProcessorStatusV2({
|
|
282
|
+
path: {
|
|
283
|
+
owner: project.owner,
|
|
284
|
+
slug: project.slug
|
|
285
|
+
},
|
|
286
|
+
query: { version: 'ACTIVE' },
|
|
287
|
+
headers: context.headers
|
|
288
|
+
})
|
|
289
|
+
const data = unwrapApiResult(statusResponse)
|
|
290
|
+
const processors = Array.isArray(data.processors) ? data.processors : []
|
|
291
|
+
const activeProcessor = processors.find((p) => asString(p.versionState) === 'ACTIVE')
|
|
292
|
+
|
|
293
|
+
if (!activeProcessor || !activeProcessor.processorId) {
|
|
294
|
+
throw new CliError(
|
|
295
|
+
`No active processor found for project ${project.owner}/${project.slug}. Please specify a processorId.`
|
|
296
|
+
)
|
|
297
|
+
}
|
|
298
|
+
resolvedProcessorId = asString(activeProcessor.processorId)!
|
|
299
|
+
versionToConfirm = `version ${asNumber(activeProcessor.version)} of project ${project.owner}/${project.slug}`
|
|
300
|
+
} else {
|
|
301
|
+
versionToConfirm = `processor ${resolvedProcessorId}`
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (!options.yes) {
|
|
305
|
+
const isConfirmed = await confirm(`Are you sure you want to ${actionName} ${versionToConfirm}?`)
|
|
306
|
+
if (!isConfirmed) {
|
|
307
|
+
console.log(`${actionName.charAt(0).toUpperCase() + actionName.slice(1)} cancelled.`)
|
|
308
|
+
return undefined
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return resolvedProcessorId
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
async function runProcessorPause(
|
|
316
|
+
processorId: string | undefined,
|
|
317
|
+
options: ProcessorOptions & { reason?: string; yes?: boolean }
|
|
318
|
+
) {
|
|
319
|
+
const resolvedProcessorId = await resolveAndConfirmProcessor('pause', processorId, options)
|
|
320
|
+
if (!resolvedProcessorId) return
|
|
321
|
+
const context = createApiContext(options)
|
|
322
|
+
const response = await putApiJson(`/api/v1/processors/${resolvedProcessorId}/pause`, context, {
|
|
323
|
+
reason: options.reason
|
|
324
|
+
})
|
|
325
|
+
printOutput(options, { processorId: resolvedProcessorId, action: 'paused', ...(response as Record<string, unknown>) })
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
async function runProcessorResume(processorId: string | undefined, options: ProcessorOptions & { yes?: boolean }) {
|
|
329
|
+
const resolvedProcessorId = await resolveAndConfirmProcessor('resume', processorId, options)
|
|
330
|
+
if (!resolvedProcessorId) return
|
|
331
|
+
const context = createApiContext(options)
|
|
332
|
+
const response = await putApiJson(`/api/v1/processors/${resolvedProcessorId}/resume`, context)
|
|
333
|
+
printOutput(options, {
|
|
334
|
+
processorId: resolvedProcessorId,
|
|
335
|
+
action: 'resumed',
|
|
336
|
+
...(response as Record<string, unknown>)
|
|
337
|
+
})
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
async function runProcessorStop(processorId: string | undefined, options: ProcessorOptions & { yes?: boolean }) {
|
|
341
|
+
const resolvedProcessorId = await resolveAndConfirmProcessor('stop', processorId, options)
|
|
342
|
+
if (!resolvedProcessorId) return
|
|
343
|
+
const context = createApiContext(options)
|
|
344
|
+
const response = await postApiJson(`/api/v1/processors/stop`, context, { processorId: resolvedProcessorId })
|
|
345
|
+
printOutput(options, {
|
|
346
|
+
processorId: resolvedProcessorId,
|
|
347
|
+
action: 'stopped',
|
|
348
|
+
...(response as Record<string, unknown>)
|
|
349
|
+
})
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
async function resolveProcessorId(processorId: string | undefined, options: ProcessorOptions): Promise<string> {
|
|
353
|
+
if (processorId) return processorId
|
|
354
|
+
const context = createApiContext(options)
|
|
355
|
+
const project = await resolveProjectRef(options, context, { ownerSlug: true })
|
|
356
|
+
const statusResponse = await ProcessorService.getProcessorStatusV2({
|
|
357
|
+
path: { owner: project.owner, slug: project.slug },
|
|
358
|
+
query: { version: 'ACTIVE' },
|
|
359
|
+
headers: context.headers
|
|
360
|
+
})
|
|
361
|
+
const data = unwrapApiResult(statusResponse)
|
|
362
|
+
const processors = Array.isArray(data.processors) ? data.processors : []
|
|
363
|
+
const activeProcessor = processors.find((p) => asString(p.versionState) === 'ACTIVE')
|
|
364
|
+
if (!activeProcessor || !activeProcessor.processorId) {
|
|
365
|
+
throw new CliError(
|
|
366
|
+
`No active processor found for project ${project.owner}/${project.slug}. Please specify a processorId.`
|
|
367
|
+
)
|
|
368
|
+
}
|
|
369
|
+
return asString(activeProcessor.processorId)!
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
async function runProcessorLogs(processorId: string | undefined, options: ProcessorLogsOptions) {
|
|
373
|
+
const resolvedProcessorId = await resolveProcessorId(processorId, options)
|
|
374
|
+
const context = createApiContext(options)
|
|
375
|
+
|
|
376
|
+
if (options.follow && !options.json && !options.yaml) {
|
|
377
|
+
await followProcessorLogs(resolvedProcessorId, context, options)
|
|
378
|
+
return
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const response = await postApiJson<ProcessorLogsResponse>(
|
|
382
|
+
`/api/v1/processors/${resolvedProcessorId}/logs`,
|
|
383
|
+
context,
|
|
384
|
+
buildLogsRequestBody(resolvedProcessorId, options)
|
|
385
|
+
)
|
|
386
|
+
printOutput(options, response)
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
async function followProcessorLogs(
|
|
390
|
+
processorId: string,
|
|
391
|
+
context: ReturnType<typeof createApiContext>,
|
|
392
|
+
options: ProcessorLogsOptions
|
|
393
|
+
) {
|
|
394
|
+
let until: unknown[] | undefined
|
|
395
|
+
let running = true
|
|
396
|
+
const seenIds = new Set<string>()
|
|
397
|
+
|
|
398
|
+
process.on('SIGINT', () => {
|
|
399
|
+
running = false
|
|
400
|
+
})
|
|
401
|
+
|
|
402
|
+
while (running) {
|
|
403
|
+
try {
|
|
404
|
+
const body = { ...buildLogsRequestBody(processorId, options), until }
|
|
405
|
+
const response = await postApiJson<ProcessorLogsResponse>(`/api/v1/processors/${processorId}/logs`, context, body)
|
|
406
|
+
const entries = Array.isArray(response.logs) ? response.logs : []
|
|
407
|
+
for (const entry of entries) {
|
|
408
|
+
const e = entry as ProcessorLog
|
|
409
|
+
const id = e.id ?? ''
|
|
410
|
+
if (id && seenIds.has(id)) continue
|
|
411
|
+
if (id) seenIds.add(id)
|
|
412
|
+
process.stdout.write(formatLogEntry(e) + '\n')
|
|
413
|
+
}
|
|
414
|
+
if (response.until) {
|
|
415
|
+
until = response.until
|
|
416
|
+
}
|
|
417
|
+
} catch {
|
|
418
|
+
// Ignore transient fetch errors during follow mode
|
|
419
|
+
}
|
|
420
|
+
await new Promise((resolve) => setTimeout(resolve, 2000))
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function buildLogsRequestBody(processorId: string, options: ProcessorLogsOptions): Record<string, unknown> {
|
|
425
|
+
const body: Record<string, unknown> = { processorId, limit: options.limit }
|
|
426
|
+
if (options.logType) {
|
|
427
|
+
body.logTypeFilters = [options.logType]
|
|
428
|
+
}
|
|
429
|
+
if (options.level || options.query) {
|
|
430
|
+
const parts: string[] = []
|
|
431
|
+
if (options.level) parts.push(options.level.toUpperCase())
|
|
432
|
+
if (options.query) parts.push(options.query)
|
|
433
|
+
body.query = parts.join(' ')
|
|
434
|
+
}
|
|
435
|
+
return body
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
interface ProcessorLog {
|
|
439
|
+
id?: string
|
|
440
|
+
message?: string
|
|
441
|
+
timestamp?: string
|
|
442
|
+
attributes?: Record<string, unknown>
|
|
443
|
+
logType?: string
|
|
444
|
+
level?: string
|
|
445
|
+
highlightedMessage?: string
|
|
446
|
+
chainId?: string
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
interface ProcessorLogsResponse {
|
|
450
|
+
logs?: ProcessorLog[]
|
|
451
|
+
until?: unknown[]
|
|
452
|
+
total?: string
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function formatLogEntry(entry: ProcessorLog): string {
|
|
456
|
+
const formattedTime = entry.timestamp ? chalk.gray(entry.timestamp.replace('T', ' ').replace('Z', '')) : ''
|
|
457
|
+
const level = (entry.level ?? 'INFO').toUpperCase()
|
|
458
|
+
const logType = entry.logType ? chalk.gray(`[${entry.logType}]`) : ''
|
|
459
|
+
const coloredLevel = colorSeverity(level)
|
|
460
|
+
const message = entry.message ?? ''
|
|
461
|
+
const chain = entry.chainId ? chalk.gray(`(chain=${entry.chainId})`) : ''
|
|
462
|
+
|
|
463
|
+
return [formattedTime, coloredLevel, logType, message, chain].filter(Boolean).join(' ')
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function colorSeverity(severity: string): string {
|
|
467
|
+
switch (severity) {
|
|
468
|
+
case 'ERROR':
|
|
469
|
+
return chalk.red(`[${severity}]`)
|
|
470
|
+
case 'WARNING':
|
|
471
|
+
case 'WARN':
|
|
472
|
+
return chalk.yellow(`[${severity}]`)
|
|
473
|
+
case 'DEBUG':
|
|
474
|
+
return chalk.gray(`[${severity}]`)
|
|
475
|
+
default:
|
|
476
|
+
return chalk.cyan(`[${severity}]`)
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
146
480
|
function withAuthOptions<T extends Command<any, any, any>>(command: T) {
|
|
147
481
|
return command
|
|
148
482
|
.option('--host <host>', 'Override Sentio host')
|
|
@@ -204,8 +538,8 @@ function formatOutput(data: unknown) {
|
|
|
204
538
|
lines.push(`${group.versionState} (${group.processors.length})`)
|
|
205
539
|
for (const processor of group.processors) {
|
|
206
540
|
const version = asNumber(processor.version)
|
|
207
|
-
const
|
|
208
|
-
|
|
541
|
+
const processorStatus = processor.processorStatus as Record<string, unknown> | undefined
|
|
542
|
+
const statusState = asString(processorStatus?.state) ?? 'UNKNOWN'
|
|
209
543
|
const uploadedAt = asString(processor.uploadedAt)
|
|
210
544
|
lines.push(`- v${version ?? '?'} status=${statusState}${uploadedAt ? ` uploaded=${uploadedAt}` : ''}`)
|
|
211
545
|
if (asString(processor.processorId)) {
|
|
@@ -215,9 +549,17 @@ function formatOutput(data: unknown) {
|
|
|
215
549
|
for (const stateEntry of states.slice(0, 5)) {
|
|
216
550
|
const state = stateEntry as Record<string, unknown>
|
|
217
551
|
const chainId = asString(state.chainId) ?? '?'
|
|
218
|
-
const
|
|
552
|
+
const stateStatus = state.status as Record<string, unknown> | undefined
|
|
553
|
+
const chainState = asString(stateStatus?.state) ?? 'UNKNOWN'
|
|
219
554
|
const block = asString(state.processedBlockNumber) ?? '?'
|
|
220
555
|
lines.push(` chain ${chainId}: ${chainState} block=${block}`)
|
|
556
|
+
const errorRecord = stateStatus?.errorRecord as Record<string, unknown> | undefined
|
|
557
|
+
const chainError = asString(errorRecord?.message)
|
|
558
|
+
if (chainError) {
|
|
559
|
+
const createdAt = asString(errorRecord?.createdAt)
|
|
560
|
+
const prefix = createdAt ? `[${createdAt}] ` : ''
|
|
561
|
+
lines.push(` error: ${prefix}${chainError}`)
|
|
562
|
+
}
|
|
221
563
|
}
|
|
222
564
|
if (states.length > 5) {
|
|
223
565
|
lines.push(` ... ${states.length - 5} more chains`)
|
|
@@ -246,6 +588,25 @@ function formatOutput(data: unknown) {
|
|
|
246
588
|
return `Pending processor version activated for ${asString(objectData.project) ?? '<project>'}.`
|
|
247
589
|
}
|
|
248
590
|
|
|
591
|
+
if (
|
|
592
|
+
data &&
|
|
593
|
+
typeof data === 'object' &&
|
|
594
|
+
'action' in (data as Record<string, unknown>) &&
|
|
595
|
+
'processorId' in (data as Record<string, unknown>)
|
|
596
|
+
) {
|
|
597
|
+
const objectData = data as Record<string, unknown>
|
|
598
|
+
return `Processor ${asString(objectData.processorId)} successfully ${asString(objectData.action)}.`
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
if (data && typeof data === 'object' && 'logs' in (data as Record<string, unknown>)) {
|
|
602
|
+
const logsData = data as ProcessorLogsResponse
|
|
603
|
+
const entries = Array.isArray(logsData.logs) ? logsData.logs : []
|
|
604
|
+
if (entries.length === 0) {
|
|
605
|
+
return 'No logs found.'
|
|
606
|
+
}
|
|
607
|
+
return entries.map((entry) => formatLogEntry(entry)).join('\n')
|
|
608
|
+
}
|
|
609
|
+
|
|
249
610
|
return JSON.stringify(data, null, 2)
|
|
250
611
|
}
|
|
251
612
|
|