@sentio/cli 3.6.0-rc.2 → 3.6.0-rc.4
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 +177 -37
- package/package.json +1 -1
- package/src/commands/alert.ts +9 -3
- package/src/commands/dashboard.ts +85 -21
- package/src/commands/data.ts +68 -5
- package/src/commands/processor.ts +31 -13
- package/src/commands/version.ts +1 -1
- package/src/index.ts +6 -1
package/lib/index.js
CHANGED
|
@@ -141137,6 +141137,59 @@ function formatDebugBody(body) {
|
|
|
141137
141137
|
|
|
141138
141138
|
// src/commands/data.ts
|
|
141139
141139
|
var DEFAULT_RANGE_STEP = 3600;
|
|
141140
|
+
var VALID_METRIC_FUNCTIONS = /* @__PURE__ */ new Set([
|
|
141141
|
+
// Math
|
|
141142
|
+
"abs",
|
|
141143
|
+
"ceil",
|
|
141144
|
+
"floor",
|
|
141145
|
+
"round",
|
|
141146
|
+
"log2",
|
|
141147
|
+
"log10",
|
|
141148
|
+
"ln",
|
|
141149
|
+
// Rollup
|
|
141150
|
+
"rollup_avg",
|
|
141151
|
+
"rollup_count",
|
|
141152
|
+
"rollup_last",
|
|
141153
|
+
"rollup_max",
|
|
141154
|
+
"rollup_min",
|
|
141155
|
+
"rollup_sum",
|
|
141156
|
+
"rollup_delta",
|
|
141157
|
+
// Aggregate Over Time
|
|
141158
|
+
"avg_over_time",
|
|
141159
|
+
"count_over_time",
|
|
141160
|
+
"last_over_time",
|
|
141161
|
+
"max_over_time",
|
|
141162
|
+
"min_over_time",
|
|
141163
|
+
"sum_over_time",
|
|
141164
|
+
"delta_over_time",
|
|
141165
|
+
// Rate
|
|
141166
|
+
"rate",
|
|
141167
|
+
"irate",
|
|
141168
|
+
"delta",
|
|
141169
|
+
"moving_delta",
|
|
141170
|
+
// Rank
|
|
141171
|
+
"topk",
|
|
141172
|
+
"bottomk",
|
|
141173
|
+
// Time
|
|
141174
|
+
"timestamp",
|
|
141175
|
+
"day_of_year",
|
|
141176
|
+
"day_of_month",
|
|
141177
|
+
"day_of_week",
|
|
141178
|
+
"year",
|
|
141179
|
+
"month",
|
|
141180
|
+
"hour",
|
|
141181
|
+
"minute",
|
|
141182
|
+
// TimeShift
|
|
141183
|
+
"before",
|
|
141184
|
+
"after"
|
|
141185
|
+
]);
|
|
141186
|
+
var VALID_EVENT_FUNCTIONS = /* @__PURE__ */ new Set([
|
|
141187
|
+
// Rank
|
|
141188
|
+
"topk",
|
|
141189
|
+
"bottomk",
|
|
141190
|
+
// Delta
|
|
141191
|
+
"delta"
|
|
141192
|
+
]);
|
|
141140
141193
|
function createDataCommand() {
|
|
141141
141194
|
const dataCommand = new Command("data").description("Retrieve data from Sentio");
|
|
141142
141195
|
dataCommand.addCommand(createDataQueryCommand());
|
|
@@ -141173,7 +141226,7 @@ function buildEventsInsightQueryBody(search, options) {
|
|
|
141173
141226
|
aggregation: buildEventAggregation(options.aggr),
|
|
141174
141227
|
selectorExpr: buildSelectorExpr(options.filter),
|
|
141175
141228
|
groupBy: normalizeListOption(options.groupBy),
|
|
141176
|
-
functions: buildFunctions(options.func),
|
|
141229
|
+
functions: buildFunctions(options.func, VALID_EVENT_FUNCTIONS),
|
|
141177
141230
|
disabled: false
|
|
141178
141231
|
}
|
|
141179
141232
|
}
|
|
@@ -141197,7 +141250,7 @@ function buildMetricsInsightQueryBody(query, options) {
|
|
|
141197
141250
|
query,
|
|
141198
141251
|
labelSelector: buildMetricLabelSelector(options.filter),
|
|
141199
141252
|
aggregate: buildMetricAggregate(options.aggr, options.groupBy),
|
|
141200
|
-
functions: buildFunctions(options.func),
|
|
141253
|
+
functions: buildFunctions(options.func, VALID_METRIC_FUNCTIONS),
|
|
141201
141254
|
disabled: false
|
|
141202
141255
|
}
|
|
141203
141256
|
}
|
|
@@ -141504,7 +141557,7 @@ function handleDataCommandError(error, command) {
|
|
|
141504
141557
|
function shouldShowHelpForDataCommandError(error) {
|
|
141505
141558
|
return error.message.startsWith("Project is required.") || error.message.startsWith(
|
|
141506
141559
|
"Provide --file, --stdin, or exactly one of --event, --metric, or --price for data query"
|
|
141507
|
-
) || error.message.startsWith("Use exactly one of --event, --metric, or --price for data query.") || error.message.startsWith("Price queries only support --price") || error.message.startsWith("Provide --query, --result, --file, or --stdin.") || error.message.startsWith("Use only one of --query or --result.") || error.message.startsWith("--async only works with --query.") || error.message.startsWith("Execution id is required.");
|
|
141560
|
+
) || error.message.startsWith("Use exactly one of --event, --metric, or --price for data query.") || error.message.startsWith("Price queries only support --price") || error.message.startsWith("Provide --query, --result, --file, or --stdin.") || error.message.startsWith("Use only one of --query or --result.") || error.message.startsWith("--async only works with --query.") || error.message.startsWith("Execution id is required.") || error.message.startsWith('Unknown function "');
|
|
141508
141561
|
}
|
|
141509
141562
|
function buildEventAggregation(aggregationName = "total") {
|
|
141510
141563
|
switch (aggregationName.toUpperCase()) {
|
|
@@ -141633,8 +141686,14 @@ function parseAnyValue(rawValue) {
|
|
|
141633
141686
|
}
|
|
141634
141687
|
return { stringValue: unquotedValue };
|
|
141635
141688
|
}
|
|
141636
|
-
function buildFunctions(functions) {
|
|
141637
|
-
return normalizeListOption(functions).map(
|
|
141689
|
+
function buildFunctions(functions, validNames) {
|
|
141690
|
+
return normalizeListOption(functions).map((f14) => {
|
|
141691
|
+
const parsed = parseFunctionCall(f14);
|
|
141692
|
+
if (validNames && !validNames.has(parsed.name)) {
|
|
141693
|
+
throw new CliError(`Unknown function "${parsed.name}". Valid functions: ${[...validNames].sort().join(", ")}.`);
|
|
141694
|
+
}
|
|
141695
|
+
return parsed;
|
|
141696
|
+
});
|
|
141638
141697
|
}
|
|
141639
141698
|
function parseFunctionCall(value) {
|
|
141640
141699
|
const trimmed = value.trim();
|
|
@@ -142476,7 +142535,11 @@ function createProcessorCommand() {
|
|
|
142476
142535
|
function createProcessorStatusCommand() {
|
|
142477
142536
|
return withOutputOptions3(
|
|
142478
142537
|
withSharedProjectOptions3(withAuthOptions3(new Command("status").description("Get processor status")))
|
|
142479
|
-
).showHelpAfterError().option(
|
|
142538
|
+
).showHelpAfterError().option(
|
|
142539
|
+
"--version <selector>",
|
|
142540
|
+
"Version selector: active, pending, all, or a numeric version number",
|
|
142541
|
+
parseVersionSelector
|
|
142542
|
+
).action(async (options, command) => {
|
|
142480
142543
|
try {
|
|
142481
142544
|
await runProcessorStatus(options);
|
|
142482
142545
|
} catch (error) {
|
|
@@ -142556,22 +142619,19 @@ async function runProcessorStatus(options) {
|
|
|
142556
142619
|
const context = createApiContext(options);
|
|
142557
142620
|
const project = await resolveProjectRef(options, context, { ownerSlug: true });
|
|
142558
142621
|
const requestedVersion = normalizeVersionSelector(options.version);
|
|
142622
|
+
const apiVersion = typeof requestedVersion === "number" ? "ALL" : requestedVersion ?? "ALL";
|
|
142559
142623
|
const response = await import_api5.ProcessorService.getProcessorStatusV2({
|
|
142560
142624
|
path: {
|
|
142561
142625
|
owner: project.owner,
|
|
142562
142626
|
slug: project.slug
|
|
142563
142627
|
},
|
|
142564
142628
|
query: {
|
|
142565
|
-
version:
|
|
142629
|
+
version: apiVersion
|
|
142566
142630
|
},
|
|
142567
142631
|
headers: context.headers
|
|
142568
142632
|
});
|
|
142569
142633
|
const data4 = unwrapApiResult(response);
|
|
142570
|
-
|
|
142571
|
-
printOutput3(options, shapeProcessorStatusOutput(data4, requestedVersion));
|
|
142572
|
-
return;
|
|
142573
|
-
}
|
|
142574
|
-
printOutput3(options, data4);
|
|
142634
|
+
printOutput3(options, shapeProcessorStatusOutput(data4, requestedVersion));
|
|
142575
142635
|
}
|
|
142576
142636
|
async function runProcessorSource(options) {
|
|
142577
142637
|
const context = createApiContext(options);
|
|
@@ -142810,7 +142870,7 @@ function withOutputOptions3(command) {
|
|
|
142810
142870
|
return command.option("--json", "Print raw JSON response").option("--yaml", "Print raw YAML response");
|
|
142811
142871
|
}
|
|
142812
142872
|
function handleProcessorCommandError(error, command) {
|
|
142813
|
-
if (error instanceof CliError && (error.message.startsWith("Project is required.") || error.message.startsWith("Invalid project ") || error.message.startsWith("Invalid version selector
|
|
142873
|
+
if (error instanceof CliError && (error.message.startsWith("Project is required.") || error.message.startsWith("Invalid project ") || error.message.startsWith("Invalid version selector") || error.message.startsWith("Source file not found:"))) {
|
|
142814
142874
|
console.error(error.message);
|
|
142815
142875
|
if (command) {
|
|
142816
142876
|
console.error();
|
|
@@ -142904,14 +142964,24 @@ function formatOutput2(data4) {
|
|
|
142904
142964
|
return JSON.stringify(data4, null, 2);
|
|
142905
142965
|
}
|
|
142906
142966
|
function normalizeVersionSelector(value) {
|
|
142907
|
-
if (
|
|
142967
|
+
if (value === void 0) {
|
|
142908
142968
|
return void 0;
|
|
142909
142969
|
}
|
|
142970
|
+
if (typeof value === "number") {
|
|
142971
|
+
return value;
|
|
142972
|
+
}
|
|
142910
142973
|
const normalized = value.toUpperCase();
|
|
142911
142974
|
if (normalized === "ACTIVE" || normalized === "PENDING" || normalized === "ALL") {
|
|
142912
142975
|
return normalized;
|
|
142913
142976
|
}
|
|
142914
|
-
throw new CliError(`Invalid version selector "${value}". Use active, pending, or
|
|
142977
|
+
throw new CliError(`Invalid version selector "${value}". Use active, pending, all, or a version number.`);
|
|
142978
|
+
}
|
|
142979
|
+
function parseVersionSelector(value) {
|
|
142980
|
+
const num = Number.parseInt(value, 10);
|
|
142981
|
+
if (!Number.isNaN(num) && String(num) === value) {
|
|
142982
|
+
return num;
|
|
142983
|
+
}
|
|
142984
|
+
return value;
|
|
142915
142985
|
}
|
|
142916
142986
|
function parseInteger2(value) {
|
|
142917
142987
|
const parsedValue = Number.parseInt(value, 10);
|
|
@@ -142937,6 +143007,12 @@ function shapeProcessorStatusOutput(data4, versionSelector) {
|
|
|
142937
143007
|
processors: processors.filter((processor) => asString3(processor.versionState) === versionSelector)
|
|
142938
143008
|
};
|
|
142939
143009
|
}
|
|
143010
|
+
if (typeof versionSelector === "number") {
|
|
143011
|
+
return {
|
|
143012
|
+
...data4,
|
|
143013
|
+
processors: processors.filter((processor) => asNumber(processor.version) === versionSelector)
|
|
143014
|
+
};
|
|
143015
|
+
}
|
|
142940
143016
|
return {
|
|
142941
143017
|
...data4,
|
|
142942
143018
|
processors: processors.filter((processor) => {
|
|
@@ -143006,7 +143082,10 @@ function createAlertGetCommand() {
|
|
|
143006
143082
|
function createAlertCreateCommand() {
|
|
143007
143083
|
return withOutputOptions4(
|
|
143008
143084
|
withSharedProjectOptions4(withAuthOptions4(new Command("create").description("Create an alert rule")))
|
|
143009
|
-
).showHelpAfterError().option("--file <path>", "Read request JSON or YAML from file. Use --doc to show the full alert request format").option("--stdin", "Read request JSON or YAML from stdin. Use --doc to show the full alert request format").option("--doc", "Print the full alert request file format and exit").option("--type <type>", "Alert type: METRIC, LOG, or SQL").option("--subject <text>", "Alert subject/title").option("--message <text>", "Optional alert message template").option(
|
|
143085
|
+
).showHelpAfterError().option("--file <path>", "Read request JSON or YAML from file. Use --doc to show the full alert request format").option("--stdin", "Read request JSON or YAML from stdin. Use --doc to show the full alert request format").option("--doc", "Print the full alert request file format and exit").option("--type <type>", "Alert type: METRIC, LOG, or SQL").option("--subject <text>", "Alert subject/title").option("--message <text>", "Optional alert message template").option(
|
|
143086
|
+
"--query <text>",
|
|
143087
|
+
"Inline query. LOG: Elasticsearch query-string syntax (e.g. amount:>1000, status:error). SQL: full SQL statement."
|
|
143088
|
+
).option("--event <name>", "Inline event query for METRIC alerts").option("--metric <name>", "Inline metric query for METRIC alerts").option("--alias <alias>", "Alias for the inline METRIC query").option("--source-name <name>", "Optional source name for the inline METRIC query").option("--filter <selector>", "Inline METRIC query filter like amount>0 or meta.chain=1", collectOption2, []).option("--group-by <field>", "Inline METRIC query group-by field", collectOption2, []).option(
|
|
143010
143089
|
"--aggr <aggregation>",
|
|
143011
143090
|
"Inline aggregation. METRIC: avg|sum|min|max|count. EVENTS: total|unique|AAU|DAU|WAU|MAU"
|
|
143012
143091
|
).option("--func <function>", "Inline function like topk(1), bottomk(1), delta(1m)", collectOption2, []).option("--op <operator>", "Condition operator like >, >=, ==, !=, <, <=, between").option("--threshold <value>", "Condition threshold", parseNumber).option("--threshold2 <value>", "Second threshold for between", parseNumber).option("--for <duration>", "Evaluate over the last duration, for example 5m or 1h").option("--interval <duration>", "Alert evaluation interval, for example 1m or 5m").option("--time-column <column>", "SQL alert time column for column-based conditions").option("--value-column <column>", "SQL alert value column for column-based conditions").option("--sql-aggr <aggregation>", "SQL aggregation: COUNT, SUM, AVG, MAX, MIN, LAST").addHelpText(
|
|
@@ -143014,7 +143093,7 @@ function createAlertCreateCommand() {
|
|
|
143014
143093
|
`
|
|
143015
143094
|
|
|
143016
143095
|
Examples:
|
|
143017
|
-
$ sentio alert create --project sentio/coinbase --type LOG --subject "Large transfer logs" --query 'amount
|
|
143096
|
+
$ sentio alert create --project sentio/coinbase --type LOG --subject "Large transfer logs" --query 'amount:>1000' --op '>' --threshold 0
|
|
143018
143097
|
$ sentio alert create --project sentio/coinbase --type SQL --subject "Large transfer(SQL demo)" --query 'select timestamp, amount from transfer where amount > 1000' --time-column timestamp --value-column amount --sql-aggr MAX --op '>' --threshold 1000
|
|
143019
143098
|
$ sentio alert create --project sentio/coinbase --type METRIC --subject "Burn spike" --metric burn --filter meta.chain=1 --aggr avg --group-by meta.address --op '>' --threshold 100
|
|
143020
143099
|
$ sentio alert create --project sentio/coinbase --type METRIC --subject "Transfer anomaly" --event Transfer --filter amount>0 --aggr total --func 'delta(1m)' --op '>' --threshold 100
|
|
@@ -143637,11 +143716,14 @@ Metric alert example:
|
|
|
143637
143716
|
disabled: false
|
|
143638
143717
|
|
|
143639
143718
|
Log alert example:
|
|
143719
|
+
NOTE: logCondition.query uses Elasticsearch query-string syntax.
|
|
143720
|
+
Ranges MUST use field:>value form \u2014 C-style "field > value" is rejected at evaluation time.
|
|
143721
|
+
Examples: amount:>1000000 timestamp:>=2024-01-01 amount:[1000 TO 9999] status:error
|
|
143640
143722
|
rule:
|
|
143641
143723
|
alertType: LOG
|
|
143642
143724
|
subject: large transfer logs
|
|
143643
143725
|
logCondition:
|
|
143644
|
-
query: amount
|
|
143726
|
+
query: amount:>1000
|
|
143645
143727
|
comparisonOp: ">"
|
|
143646
143728
|
threshold: 0
|
|
143647
143729
|
|
|
@@ -144874,7 +144956,13 @@ function createDashboardAddPanelCommand() {
|
|
|
144874
144956
|
"Event filter or metric label selector like field:value or amount>0",
|
|
144875
144957
|
collectOption3,
|
|
144876
144958
|
[]
|
|
144877
|
-
).option("--group-by <field>", "Group by event property or metric label", collectOption3, []).option("--aggr <aggregation>", "Event: total|unique|AAU|DAU|WAU|MAU. Metric: avg|sum|min|max|count").option("--func <function>", "Function like topk(1), bottomk(1)", collectOption3, []).
|
|
144959
|
+
).option("--group-by <field>", "Group by event property or metric label", collectOption3, []).option("--aggr <aggregation>", "Event: total|unique|AAU|DAU|WAU|MAU. Metric: avg|sum|min|max|count").option("--func <function>", "Function like topk(1), bottomk(1)", collectOption3, []).option(
|
|
144960
|
+
"--time-range-start <value>",
|
|
144961
|
+
"Panel time range start: relative (e.g. -24h, -7d, -30m) or ISO date (e.g. 2024-01-01T00:00:00Z)"
|
|
144962
|
+
).option(
|
|
144963
|
+
"--time-range-end <value>",
|
|
144964
|
+
"Panel time range end: relative (e.g. now, -1h) or ISO date. Defaults to now when --time-range-start is set."
|
|
144965
|
+
).option("--time-range-step <seconds>", "Panel time range step in seconds (e.g. 3600)").addHelpText(
|
|
144878
144966
|
"after",
|
|
144879
144967
|
`
|
|
144880
144968
|
|
|
@@ -144911,7 +144999,17 @@ Metric insights panel examples:
|
|
|
144911
144999
|
--metric burn --filter meta.chain=1 --aggr avg --group-by meta.address
|
|
144912
145000
|
$ sentio dashboard add-panel abc123 --project owner/slug \\
|
|
144913
145001
|
--panel-name "Burn Rate Delta" --type LINE \\
|
|
144914
|
-
--metric burn --aggr sum
|
|
145002
|
+
--metric burn --aggr sum
|
|
145003
|
+
|
|
145004
|
+
Panel time range override examples:
|
|
145005
|
+
$ sentio dashboard add-panel abc123 --project owner/slug \\
|
|
145006
|
+
--panel-name "Last 24h Transfers" --type LINE \\
|
|
145007
|
+
--event Transfer --aggr total \\
|
|
145008
|
+
--time-range-start -24h --time-range-end now --time-range-step 3600
|
|
145009
|
+
$ sentio dashboard add-panel abc123 --project owner/slug \\
|
|
145010
|
+
--panel-name "Jan 2024 Volume" --type BAR \\
|
|
145011
|
+
--sql "SELECT date, sum(amount) FROM Transfer GROUP BY date" \\
|
|
145012
|
+
--time-range-start 2024-01-01T00:00:00Z --time-range-end 2024-02-01T00:00:00Z
|
|
144915
145013
|
`
|
|
144916
145014
|
).action(async (dashboardId, options, command) => {
|
|
144917
145015
|
try {
|
|
@@ -144979,14 +145077,18 @@ function buildDashboardCreateBody(options, project) {
|
|
|
144979
145077
|
};
|
|
144980
145078
|
}
|
|
144981
145079
|
function normalizeDashboardInit(input) {
|
|
145080
|
+
const emptyLayouts = {
|
|
145081
|
+
responsiveLayouts: {
|
|
145082
|
+
lg: { layouts: [] },
|
|
145083
|
+
md: { layouts: [] },
|
|
145084
|
+
sm: { layouts: [] },
|
|
145085
|
+
xs: { layouts: [] }
|
|
145086
|
+
}
|
|
145087
|
+
};
|
|
144982
145088
|
if (input === void 0) {
|
|
144983
145089
|
return {
|
|
144984
145090
|
panels: {},
|
|
144985
|
-
layouts:
|
|
144986
|
-
responsiveLayouts: {
|
|
144987
|
-
lg: { layouts: [] }
|
|
144988
|
-
}
|
|
144989
|
-
}
|
|
145091
|
+
layouts: emptyLayouts
|
|
144990
145092
|
};
|
|
144991
145093
|
}
|
|
144992
145094
|
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
@@ -144995,11 +145097,7 @@ function normalizeDashboardInit(input) {
|
|
|
144995
145097
|
const dashboard = input;
|
|
144996
145098
|
return {
|
|
144997
145099
|
panels: isRecord(dashboard.panels) ? dashboard.panels : {},
|
|
144998
|
-
layouts: isRecord(dashboard.layouts) ? dashboard.layouts :
|
|
144999
|
-
responsiveLayouts: {
|
|
145000
|
-
lg: { layouts: [] }
|
|
145001
|
-
}
|
|
145002
|
-
}
|
|
145100
|
+
layouts: isRecord(dashboard.layouts) ? dashboard.layouts : emptyLayouts
|
|
145003
145101
|
};
|
|
145004
145102
|
}
|
|
145005
145103
|
async function runDashboardAddPanel(dashboardId, options) {
|
|
@@ -145023,11 +145121,13 @@ async function runDashboardAddPanel(dashboardId, options) {
|
|
|
145023
145121
|
const chartType = normalizeChartType(options.type);
|
|
145024
145122
|
const panelId = generatePanelId();
|
|
145025
145123
|
const chart = buildPanelChart(chartType, options);
|
|
145124
|
+
const timeRangeOverride = buildTimeRangeOverride(options);
|
|
145026
145125
|
const newPanel = {
|
|
145027
145126
|
id: panelId,
|
|
145028
145127
|
name: options.panelName,
|
|
145029
145128
|
dashboardId,
|
|
145030
|
-
chart
|
|
145129
|
+
chart,
|
|
145130
|
+
...timeRangeOverride ? { timeRangeOverride } : {}
|
|
145031
145131
|
};
|
|
145032
145132
|
const existingLayouts = dashboard.layouts?.responsiveLayouts?.lg?.layouts ?? [];
|
|
145033
145133
|
let maxBottom = 0;
|
|
@@ -145046,15 +145146,17 @@ async function runDashboardAddPanel(dashboardId, options) {
|
|
|
145046
145146
|
};
|
|
145047
145147
|
const panels = { ...dashboard.panels ?? {} };
|
|
145048
145148
|
panels[panelId] = newPanel;
|
|
145049
|
-
const
|
|
145149
|
+
const existingResponsive = dashboard.layouts?.responsiveLayouts ?? {};
|
|
145150
|
+
const updatedResponsive = { ...existingResponsive };
|
|
145151
|
+
for (const bp2 of ["lg", "md", "sm", "xs"]) {
|
|
145152
|
+
const existing = existingResponsive[bp2]?.layouts ?? [];
|
|
145153
|
+
updatedResponsive[bp2] = { layouts: [...existing, newLayout] };
|
|
145154
|
+
}
|
|
145050
145155
|
const dashboardJson = {
|
|
145051
145156
|
...dashboard,
|
|
145052
145157
|
panels,
|
|
145053
145158
|
layouts: {
|
|
145054
|
-
responsiveLayouts:
|
|
145055
|
-
...dashboard.layouts?.responsiveLayouts ?? {},
|
|
145056
|
-
lg: { layouts: updatedLayouts }
|
|
145057
|
-
}
|
|
145159
|
+
responsiveLayouts: updatedResponsive
|
|
145058
145160
|
}
|
|
145059
145161
|
};
|
|
145060
145162
|
const importResponse = await import_api14.WebService.importDashboard({
|
|
@@ -145072,6 +145174,40 @@ async function runDashboardAddPanel(dashboardId, options) {
|
|
|
145072
145174
|
dashboard: importData.dashboard
|
|
145073
145175
|
});
|
|
145074
145176
|
}
|
|
145177
|
+
function buildTimeRangeLike(value) {
|
|
145178
|
+
const relMatch = value.match(/^(-?\d+)\s*([smhdwMy])$/);
|
|
145179
|
+
if (relMatch) {
|
|
145180
|
+
return { relativeTime: { unit: relMatch[2], value: Number(relMatch[1]) } };
|
|
145181
|
+
}
|
|
145182
|
+
if (value === "now" || value === "0") {
|
|
145183
|
+
return { relativeTime: { unit: "h", value: 0 } };
|
|
145184
|
+
}
|
|
145185
|
+
const ts2 = Date.parse(value);
|
|
145186
|
+
if (!Number.isNaN(ts2)) {
|
|
145187
|
+
return { absoluteTime: String(ts2) };
|
|
145188
|
+
}
|
|
145189
|
+
throw new CliError(
|
|
145190
|
+
`Invalid time range value "${value}". Use a relative offset (e.g. -24h, -7d, -30m, now) or an ISO date string.`
|
|
145191
|
+
);
|
|
145192
|
+
}
|
|
145193
|
+
function buildTimeRangeOverride(options) {
|
|
145194
|
+
if (!options.timeRangeStart && !options.timeRangeEnd) {
|
|
145195
|
+
return void 0;
|
|
145196
|
+
}
|
|
145197
|
+
const timeRange = {};
|
|
145198
|
+
if (options.timeRangeStart) {
|
|
145199
|
+
timeRange.start = buildTimeRangeLike(options.timeRangeStart);
|
|
145200
|
+
}
|
|
145201
|
+
if (options.timeRangeEnd) {
|
|
145202
|
+
timeRange.end = buildTimeRangeLike(options.timeRangeEnd);
|
|
145203
|
+
} else {
|
|
145204
|
+
timeRange.end = { relativeTime: { unit: "h", value: 0 } };
|
|
145205
|
+
}
|
|
145206
|
+
if (options.timeRangeStep) {
|
|
145207
|
+
timeRange.step = options.timeRangeStep;
|
|
145208
|
+
}
|
|
145209
|
+
return { enabled: true, timeRange };
|
|
145210
|
+
}
|
|
145075
145211
|
function buildPanelChart(chartType, options) {
|
|
145076
145212
|
if (options.sql) {
|
|
145077
145213
|
const sqlSize = Number.parseInt(String(options.size ?? "100"), 10) || 100;
|
|
@@ -145141,7 +145277,7 @@ function withOutputOptions8(command) {
|
|
|
145141
145277
|
return command.option("--json", "Print raw JSON response").option("--yaml", "Print raw YAML response");
|
|
145142
145278
|
}
|
|
145143
145279
|
function handleDashboardCommandError(error, command) {
|
|
145144
|
-
if (error instanceof CliError && (error.message.startsWith("Project is required.") || error.message.startsWith("Invalid project ") || error.message.startsWith("Dashboard ") || error.message.startsWith("Provide --file or --stdin") || error.message.startsWith("Use either --file or --stdin") || error.message.startsWith("Expected JSON or YAML") || error.message.startsWith("Invalid JSON or YAML") || error.message.startsWith("Dashboard initialization data") || error.message.startsWith("Provide exactly one data source") || error.message.startsWith("Use exactly one of --sql") || error.message.startsWith("Invalid chart type") || error.message.startsWith("Invalid aggregation") || error.message.startsWith("Invalid metric aggregation") || error.message.startsWith("Invalid filter") || error.message.startsWith("Invalid metric selector"))) {
|
|
145280
|
+
if (error instanceof CliError && (error.message.startsWith("Project is required.") || error.message.startsWith("Invalid project ") || error.message.startsWith("Dashboard ") || error.message.startsWith("Provide --file or --stdin") || error.message.startsWith("Use either --file or --stdin") || error.message.startsWith("Expected JSON or YAML") || error.message.startsWith("Invalid JSON or YAML") || error.message.startsWith("Dashboard initialization data") || error.message.startsWith("Provide exactly one data source") || error.message.startsWith("Use exactly one of --sql") || error.message.startsWith("Invalid chart type") || error.message.startsWith("Invalid aggregation") || error.message.startsWith("Invalid metric aggregation") || error.message.startsWith("Invalid filter") || error.message.startsWith("Invalid metric selector") || error.message.startsWith("Invalid time range value") || error.message.startsWith('Unknown function "'))) {
|
|
145145
145281
|
console.error(error.message);
|
|
145146
145282
|
if (command) {
|
|
145147
145283
|
console.error();
|
|
@@ -145239,6 +145375,10 @@ await printVersions();
|
|
|
145239
145375
|
if (process.argv.includes("--debug")) {
|
|
145240
145376
|
enableApiDebug();
|
|
145241
145377
|
}
|
|
145378
|
+
if (process.argv.length === 3 && process.argv[2] === "--version") {
|
|
145379
|
+
version();
|
|
145380
|
+
process.exit(0);
|
|
145381
|
+
}
|
|
145242
145382
|
program2.addCommand(createLoginCommand());
|
|
145243
145383
|
program2.addCommand(createCreateCommand());
|
|
145244
145384
|
program2.addCommand(createVersionCommand());
|
package/package.json
CHANGED
package/src/commands/alert.ts
CHANGED
|
@@ -136,7 +136,10 @@ function createAlertCreateCommand() {
|
|
|
136
136
|
.option('--type <type>', 'Alert type: METRIC, LOG, or SQL')
|
|
137
137
|
.option('--subject <text>', 'Alert subject/title')
|
|
138
138
|
.option('--message <text>', 'Optional alert message template')
|
|
139
|
-
.option(
|
|
139
|
+
.option(
|
|
140
|
+
'--query <text>',
|
|
141
|
+
'Inline query. LOG: Elasticsearch query-string syntax (e.g. amount:>1000, status:error). SQL: full SQL statement.'
|
|
142
|
+
)
|
|
140
143
|
.option('--event <name>', 'Inline event query for METRIC alerts')
|
|
141
144
|
.option('--metric <name>', 'Inline metric query for METRIC alerts')
|
|
142
145
|
.option('--alias <alias>', 'Alias for the inline METRIC query')
|
|
@@ -161,7 +164,7 @@ function createAlertCreateCommand() {
|
|
|
161
164
|
`
|
|
162
165
|
|
|
163
166
|
Examples:
|
|
164
|
-
$ sentio alert create --project sentio/coinbase --type LOG --subject "Large transfer logs" --query 'amount
|
|
167
|
+
$ sentio alert create --project sentio/coinbase --type LOG --subject "Large transfer logs" --query 'amount:>1000' --op '>' --threshold 0
|
|
165
168
|
$ sentio alert create --project sentio/coinbase --type SQL --subject "Large transfer(SQL demo)" --query 'select timestamp, amount from transfer where amount > 1000' --time-column timestamp --value-column amount --sql-aggr MAX --op '>' --threshold 1000
|
|
166
169
|
$ sentio alert create --project sentio/coinbase --type METRIC --subject "Burn spike" --metric burn --filter meta.chain=1 --aggr avg --group-by meta.address --op '>' --threshold 100
|
|
167
170
|
$ sentio alert create --project sentio/coinbase --type METRIC --subject "Transfer anomaly" --event Transfer --filter amount>0 --aggr total --func 'delta(1m)' --op '>' --threshold 100
|
|
@@ -916,11 +919,14 @@ Metric alert example:
|
|
|
916
919
|
disabled: false
|
|
917
920
|
|
|
918
921
|
Log alert example:
|
|
922
|
+
NOTE: logCondition.query uses Elasticsearch query-string syntax.
|
|
923
|
+
Ranges MUST use field:>value form — C-style "field > value" is rejected at evaluation time.
|
|
924
|
+
Examples: amount:>1000000 timestamp:>=2024-01-01 amount:[1000 TO 9999] status:error
|
|
919
925
|
rule:
|
|
920
926
|
alertType: LOG
|
|
921
927
|
subject: large transfer logs
|
|
922
928
|
logCondition:
|
|
923
|
-
query: amount
|
|
929
|
+
query: amount:>1000
|
|
924
930
|
comparisonOp: ">"
|
|
925
931
|
threshold: 0
|
|
926
932
|
|
|
@@ -50,6 +50,9 @@ interface AddPanelOptions extends DashboardOptions {
|
|
|
50
50
|
groupBy?: string[]
|
|
51
51
|
aggr?: string
|
|
52
52
|
func?: string[]
|
|
53
|
+
timeRangeStart?: string
|
|
54
|
+
timeRangeEnd?: string
|
|
55
|
+
timeRangeStep?: string
|
|
53
56
|
}
|
|
54
57
|
|
|
55
58
|
export function createDashboardCommand() {
|
|
@@ -162,6 +165,15 @@ function createDashboardAddPanelCommand() {
|
|
|
162
165
|
.option('--group-by <field>', 'Group by event property or metric label', collectOption, [])
|
|
163
166
|
.option('--aggr <aggregation>', 'Event: total|unique|AAU|DAU|WAU|MAU. Metric: avg|sum|min|max|count')
|
|
164
167
|
.option('--func <function>', 'Function like topk(1), bottomk(1)', collectOption, [])
|
|
168
|
+
.option(
|
|
169
|
+
'--time-range-start <value>',
|
|
170
|
+
'Panel time range start: relative (e.g. -24h, -7d, -30m) or ISO date (e.g. 2024-01-01T00:00:00Z)'
|
|
171
|
+
)
|
|
172
|
+
.option(
|
|
173
|
+
'--time-range-end <value>',
|
|
174
|
+
'Panel time range end: relative (e.g. now, -1h) or ISO date. Defaults to now when --time-range-start is set.'
|
|
175
|
+
)
|
|
176
|
+
.option('--time-range-step <seconds>', 'Panel time range step in seconds (e.g. 3600)')
|
|
165
177
|
.addHelpText(
|
|
166
178
|
'after',
|
|
167
179
|
`
|
|
@@ -199,7 +211,17 @@ Metric insights panel examples:
|
|
|
199
211
|
--metric burn --filter meta.chain=1 --aggr avg --group-by meta.address
|
|
200
212
|
$ sentio dashboard add-panel abc123 --project owner/slug \\
|
|
201
213
|
--panel-name "Burn Rate Delta" --type LINE \\
|
|
202
|
-
--metric burn --aggr sum
|
|
214
|
+
--metric burn --aggr sum
|
|
215
|
+
|
|
216
|
+
Panel time range override examples:
|
|
217
|
+
$ sentio dashboard add-panel abc123 --project owner/slug \\
|
|
218
|
+
--panel-name "Last 24h Transfers" --type LINE \\
|
|
219
|
+
--event Transfer --aggr total \\
|
|
220
|
+
--time-range-start -24h --time-range-end now --time-range-step 3600
|
|
221
|
+
$ sentio dashboard add-panel abc123 --project owner/slug \\
|
|
222
|
+
--panel-name "Jan 2024 Volume" --type BAR \\
|
|
223
|
+
--sql "SELECT date, sum(amount) FROM Transfer GROUP BY date" \\
|
|
224
|
+
--time-range-start 2024-01-01T00:00:00Z --time-range-end 2024-02-01T00:00:00Z
|
|
203
225
|
`
|
|
204
226
|
)
|
|
205
227
|
.action(async (dashboardId, options, command) => {
|
|
@@ -279,14 +301,19 @@ function buildDashboardCreateBody(options: DashboardCreateOptions, project: { ow
|
|
|
279
301
|
}
|
|
280
302
|
|
|
281
303
|
function normalizeDashboardInit(input: unknown) {
|
|
304
|
+
const emptyLayouts = {
|
|
305
|
+
responsiveLayouts: {
|
|
306
|
+
lg: { layouts: [] },
|
|
307
|
+
md: { layouts: [] },
|
|
308
|
+
sm: { layouts: [] },
|
|
309
|
+
xs: { layouts: [] }
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
282
313
|
if (input === undefined) {
|
|
283
314
|
return {
|
|
284
315
|
panels: {},
|
|
285
|
-
layouts:
|
|
286
|
-
responsiveLayouts: {
|
|
287
|
-
lg: { layouts: [] }
|
|
288
|
-
}
|
|
289
|
-
}
|
|
316
|
+
layouts: emptyLayouts
|
|
290
317
|
}
|
|
291
318
|
}
|
|
292
319
|
|
|
@@ -297,13 +324,7 @@ function normalizeDashboardInit(input: unknown) {
|
|
|
297
324
|
const dashboard = input as Record<string, unknown>
|
|
298
325
|
return {
|
|
299
326
|
panels: isRecord(dashboard.panels) ? dashboard.panels : {},
|
|
300
|
-
layouts: isRecord(dashboard.layouts)
|
|
301
|
-
? dashboard.layouts
|
|
302
|
-
: {
|
|
303
|
-
responsiveLayouts: {
|
|
304
|
-
lg: { layouts: [] }
|
|
305
|
-
}
|
|
306
|
-
}
|
|
327
|
+
layouts: isRecord(dashboard.layouts) ? dashboard.layouts : emptyLayouts
|
|
307
328
|
}
|
|
308
329
|
}
|
|
309
330
|
|
|
@@ -335,11 +356,13 @@ async function runDashboardAddPanel(dashboardId: string, options: AddPanelOption
|
|
|
335
356
|
const panelId = generatePanelId()
|
|
336
357
|
const chart = buildPanelChart(chartType, options)
|
|
337
358
|
|
|
338
|
-
const
|
|
359
|
+
const timeRangeOverride = buildTimeRangeOverride(options)
|
|
360
|
+
const newPanel: Record<string, unknown> = {
|
|
339
361
|
id: panelId,
|
|
340
362
|
name: options.panelName,
|
|
341
363
|
dashboardId,
|
|
342
|
-
chart
|
|
364
|
+
chart,
|
|
365
|
+
...(timeRangeOverride ? { timeRangeOverride } : {})
|
|
343
366
|
}
|
|
344
367
|
|
|
345
368
|
// 3. Compute layout position: place below all existing panels
|
|
@@ -364,16 +387,18 @@ async function runDashboardAddPanel(dashboardId: string, options: AddPanelOption
|
|
|
364
387
|
const panels = { ...(dashboard.panels ?? {}) }
|
|
365
388
|
panels[panelId] = newPanel as never
|
|
366
389
|
|
|
367
|
-
const
|
|
390
|
+
const existingResponsive = dashboard.layouts?.responsiveLayouts ?? {}
|
|
391
|
+
const updatedResponsive: Record<string, unknown> = { ...existingResponsive }
|
|
392
|
+
for (const bp of ['lg', 'md', 'sm', 'xs'] as const) {
|
|
393
|
+
const existing = (existingResponsive as Record<string, { layouts?: unknown[] } | undefined>)[bp]?.layouts ?? []
|
|
394
|
+
updatedResponsive[bp] = { layouts: [...existing, newLayout] }
|
|
395
|
+
}
|
|
368
396
|
|
|
369
397
|
const dashboardJson: Record<string, unknown> = {
|
|
370
398
|
...dashboard,
|
|
371
399
|
panels,
|
|
372
400
|
layouts: {
|
|
373
|
-
responsiveLayouts:
|
|
374
|
-
...(dashboard.layouts?.responsiveLayouts ?? {}),
|
|
375
|
-
lg: { layouts: updatedLayouts }
|
|
376
|
-
}
|
|
401
|
+
responsiveLayouts: updatedResponsive
|
|
377
402
|
}
|
|
378
403
|
}
|
|
379
404
|
|
|
@@ -393,6 +418,43 @@ async function runDashboardAddPanel(dashboardId: string, options: AddPanelOption
|
|
|
393
418
|
})
|
|
394
419
|
}
|
|
395
420
|
|
|
421
|
+
function buildTimeRangeLike(value: string): Record<string, unknown> {
|
|
422
|
+
const relMatch = value.match(/^(-?\d+)\s*([smhdwMy])$/)
|
|
423
|
+
if (relMatch) {
|
|
424
|
+
return { relativeTime: { unit: relMatch[2], value: Number(relMatch[1]) } }
|
|
425
|
+
}
|
|
426
|
+
if (value === 'now' || value === '0') {
|
|
427
|
+
return { relativeTime: { unit: 'h', value: 0 } }
|
|
428
|
+
}
|
|
429
|
+
const ts = Date.parse(value)
|
|
430
|
+
if (!Number.isNaN(ts)) {
|
|
431
|
+
return { absoluteTime: String(ts) }
|
|
432
|
+
}
|
|
433
|
+
throw new CliError(
|
|
434
|
+
`Invalid time range value "${value}". Use a relative offset (e.g. -24h, -7d, -30m, now) or an ISO date string.`
|
|
435
|
+
)
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function buildTimeRangeOverride(options: AddPanelOptions): Record<string, unknown> | undefined {
|
|
439
|
+
if (!options.timeRangeStart && !options.timeRangeEnd) {
|
|
440
|
+
return undefined
|
|
441
|
+
}
|
|
442
|
+
const timeRange: Record<string, unknown> = {}
|
|
443
|
+
if (options.timeRangeStart) {
|
|
444
|
+
timeRange.start = buildTimeRangeLike(options.timeRangeStart)
|
|
445
|
+
}
|
|
446
|
+
if (options.timeRangeEnd) {
|
|
447
|
+
timeRange.end = buildTimeRangeLike(options.timeRangeEnd)
|
|
448
|
+
} else {
|
|
449
|
+
// default end to "now" when start is provided
|
|
450
|
+
timeRange.end = { relativeTime: { unit: 'h', value: 0 } }
|
|
451
|
+
}
|
|
452
|
+
if (options.timeRangeStep) {
|
|
453
|
+
timeRange.step = options.timeRangeStep
|
|
454
|
+
}
|
|
455
|
+
return { enabled: true, timeRange }
|
|
456
|
+
}
|
|
457
|
+
|
|
396
458
|
function buildPanelChart(chartType: string, options: AddPanelOptions) {
|
|
397
459
|
if (options.sql) {
|
|
398
460
|
const sqlSize = Number.parseInt(String(options.size ?? '100'), 10) || 100
|
|
@@ -496,7 +558,9 @@ function handleDashboardCommandError(error: unknown, command?: Command) {
|
|
|
496
558
|
error.message.startsWith('Invalid aggregation') ||
|
|
497
559
|
error.message.startsWith('Invalid metric aggregation') ||
|
|
498
560
|
error.message.startsWith('Invalid filter') ||
|
|
499
|
-
error.message.startsWith('Invalid metric selector')
|
|
561
|
+
error.message.startsWith('Invalid metric selector') ||
|
|
562
|
+
error.message.startsWith('Invalid time range value') ||
|
|
563
|
+
error.message.startsWith('Unknown function "'))
|
|
500
564
|
) {
|
|
501
565
|
console.error(error.message)
|
|
502
566
|
if (command) {
|
package/src/commands/data.ts
CHANGED
|
@@ -16,6 +16,62 @@ import {
|
|
|
16
16
|
} from '../api.js'
|
|
17
17
|
|
|
18
18
|
const DEFAULT_RANGE_STEP = 3600
|
|
19
|
+
|
|
20
|
+
// Valid function names sourced from sentio/app/lib/functions.ts
|
|
21
|
+
const VALID_METRIC_FUNCTIONS: ReadonlySet<string> = new Set([
|
|
22
|
+
// Math
|
|
23
|
+
'abs',
|
|
24
|
+
'ceil',
|
|
25
|
+
'floor',
|
|
26
|
+
'round',
|
|
27
|
+
'log2',
|
|
28
|
+
'log10',
|
|
29
|
+
'ln',
|
|
30
|
+
// Rollup
|
|
31
|
+
'rollup_avg',
|
|
32
|
+
'rollup_count',
|
|
33
|
+
'rollup_last',
|
|
34
|
+
'rollup_max',
|
|
35
|
+
'rollup_min',
|
|
36
|
+
'rollup_sum',
|
|
37
|
+
'rollup_delta',
|
|
38
|
+
// Aggregate Over Time
|
|
39
|
+
'avg_over_time',
|
|
40
|
+
'count_over_time',
|
|
41
|
+
'last_over_time',
|
|
42
|
+
'max_over_time',
|
|
43
|
+
'min_over_time',
|
|
44
|
+
'sum_over_time',
|
|
45
|
+
'delta_over_time',
|
|
46
|
+
// Rate
|
|
47
|
+
'rate',
|
|
48
|
+
'irate',
|
|
49
|
+
'delta',
|
|
50
|
+
'moving_delta',
|
|
51
|
+
// Rank
|
|
52
|
+
'topk',
|
|
53
|
+
'bottomk',
|
|
54
|
+
// Time
|
|
55
|
+
'timestamp',
|
|
56
|
+
'day_of_year',
|
|
57
|
+
'day_of_month',
|
|
58
|
+
'day_of_week',
|
|
59
|
+
'year',
|
|
60
|
+
'month',
|
|
61
|
+
'hour',
|
|
62
|
+
'minute',
|
|
63
|
+
// TimeShift
|
|
64
|
+
'before',
|
|
65
|
+
'after'
|
|
66
|
+
])
|
|
67
|
+
|
|
68
|
+
const VALID_EVENT_FUNCTIONS: ReadonlySet<string> = new Set([
|
|
69
|
+
// Rank
|
|
70
|
+
'topk',
|
|
71
|
+
'bottomk',
|
|
72
|
+
// Delta
|
|
73
|
+
'delta'
|
|
74
|
+
])
|
|
19
75
|
interface CommonDataOptions {
|
|
20
76
|
host?: string
|
|
21
77
|
apiKey?: string
|
|
@@ -113,7 +169,7 @@ export function buildEventsInsightQueryBody(
|
|
|
113
169
|
aggregation: buildEventAggregation(options.aggr),
|
|
114
170
|
selectorExpr: buildSelectorExpr(options.filter),
|
|
115
171
|
groupBy: normalizeListOption(options.groupBy),
|
|
116
|
-
functions: buildFunctions(options.func),
|
|
172
|
+
functions: buildFunctions(options.func, VALID_EVENT_FUNCTIONS),
|
|
117
173
|
disabled: false
|
|
118
174
|
}
|
|
119
175
|
}
|
|
@@ -148,7 +204,7 @@ export function buildMetricsInsightQueryBody(
|
|
|
148
204
|
query,
|
|
149
205
|
labelSelector: buildMetricLabelSelector(options.filter),
|
|
150
206
|
aggregate: buildMetricAggregate(options.aggr, options.groupBy),
|
|
151
|
-
functions: buildFunctions(options.func),
|
|
207
|
+
functions: buildFunctions(options.func, VALID_METRIC_FUNCTIONS),
|
|
152
208
|
disabled: false
|
|
153
209
|
}
|
|
154
210
|
}
|
|
@@ -584,7 +640,8 @@ function shouldShowHelpForDataCommandError(error: CliError) {
|
|
|
584
640
|
error.message.startsWith('Provide --query, --result, --file, or --stdin.') ||
|
|
585
641
|
error.message.startsWith('Use only one of --query or --result.') ||
|
|
586
642
|
error.message.startsWith('--async only works with --query.') ||
|
|
587
|
-
error.message.startsWith('Execution id is required.')
|
|
643
|
+
error.message.startsWith('Execution id is required.') ||
|
|
644
|
+
error.message.startsWith('Unknown function "')
|
|
588
645
|
)
|
|
589
646
|
}
|
|
590
647
|
|
|
@@ -730,8 +787,14 @@ function parseAnyValue(rawValue: string) {
|
|
|
730
787
|
return { stringValue: unquotedValue }
|
|
731
788
|
}
|
|
732
789
|
|
|
733
|
-
function buildFunctions(functions?: string[]) {
|
|
734
|
-
return normalizeListOption(functions).map(
|
|
790
|
+
function buildFunctions(functions?: string[], validNames?: ReadonlySet<string>) {
|
|
791
|
+
return normalizeListOption(functions).map((f) => {
|
|
792
|
+
const parsed = parseFunctionCall(f)
|
|
793
|
+
if (validNames && !validNames.has(parsed.name)) {
|
|
794
|
+
throw new CliError(`Unknown function "${parsed.name}". Valid functions: ${[...validNames].sort().join(', ')}.`)
|
|
795
|
+
}
|
|
796
|
+
return parsed
|
|
797
|
+
})
|
|
735
798
|
}
|
|
736
799
|
|
|
737
800
|
function parseFunctionCall(value: string) {
|
|
@@ -27,7 +27,7 @@ interface ProcessorOptions {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
interface ProcessorStatusOptions extends ProcessorOptions {
|
|
30
|
-
version?: string
|
|
30
|
+
version?: string | number
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
interface ProcessorSourceOptions extends ProcessorOptions {
|
|
@@ -60,7 +60,11 @@ function createProcessorStatusCommand() {
|
|
|
60
60
|
withSharedProjectOptions(withAuthOptions(new Command('status').description('Get processor status')))
|
|
61
61
|
)
|
|
62
62
|
.showHelpAfterError()
|
|
63
|
-
.option(
|
|
63
|
+
.option(
|
|
64
|
+
'--version <selector>',
|
|
65
|
+
'Version selector: active, pending, all, or a numeric version number',
|
|
66
|
+
parseVersionSelector
|
|
67
|
+
)
|
|
64
68
|
.action(async (options, command) => {
|
|
65
69
|
try {
|
|
66
70
|
await runProcessorStatus(options)
|
|
@@ -176,22 +180,19 @@ async function runProcessorStatus(options: ProcessorStatusOptions) {
|
|
|
176
180
|
const context = createApiContext(options)
|
|
177
181
|
const project = await resolveProjectRef(options, context, { ownerSlug: true })
|
|
178
182
|
const requestedVersion = normalizeVersionSelector(options.version)
|
|
183
|
+
const apiVersion = typeof requestedVersion === 'number' ? 'ALL' : (requestedVersion ?? 'ALL')
|
|
179
184
|
const response = await ProcessorService.getProcessorStatusV2({
|
|
180
185
|
path: {
|
|
181
186
|
owner: project.owner,
|
|
182
187
|
slug: project.slug
|
|
183
188
|
},
|
|
184
189
|
query: {
|
|
185
|
-
version:
|
|
190
|
+
version: apiVersion
|
|
186
191
|
},
|
|
187
192
|
headers: context.headers
|
|
188
193
|
})
|
|
189
194
|
const data = unwrapApiResult(response)
|
|
190
|
-
|
|
191
|
-
printOutput(options, shapeProcessorStatusOutput(data, requestedVersion))
|
|
192
|
-
return
|
|
193
|
-
}
|
|
194
|
-
printOutput(options, data)
|
|
195
|
+
printOutput(options, shapeProcessorStatusOutput(data, requestedVersion))
|
|
195
196
|
}
|
|
196
197
|
|
|
197
198
|
async function runProcessorSource(options: ProcessorSourceOptions) {
|
|
@@ -501,7 +502,7 @@ function handleProcessorCommandError(error: unknown, command?: Command) {
|
|
|
501
502
|
error instanceof CliError &&
|
|
502
503
|
(error.message.startsWith('Project is required.') ||
|
|
503
504
|
error.message.startsWith('Invalid project ') ||
|
|
504
|
-
error.message.startsWith('Invalid version selector
|
|
505
|
+
error.message.startsWith('Invalid version selector') ||
|
|
505
506
|
error.message.startsWith('Source file not found:'))
|
|
506
507
|
) {
|
|
507
508
|
console.error(error.message)
|
|
@@ -610,15 +611,26 @@ function formatOutput(data: unknown) {
|
|
|
610
611
|
return JSON.stringify(data, null, 2)
|
|
611
612
|
}
|
|
612
613
|
|
|
613
|
-
function normalizeVersionSelector(value?: string): 'ACTIVE' | 'PENDING' | 'ALL' | undefined {
|
|
614
|
-
if (
|
|
614
|
+
function normalizeVersionSelector(value?: string | number): 'ACTIVE' | 'PENDING' | 'ALL' | number | undefined {
|
|
615
|
+
if (value === undefined) {
|
|
615
616
|
return undefined
|
|
616
617
|
}
|
|
618
|
+
if (typeof value === 'number') {
|
|
619
|
+
return value
|
|
620
|
+
}
|
|
617
621
|
const normalized = value.toUpperCase()
|
|
618
622
|
if (normalized === 'ACTIVE' || normalized === 'PENDING' || normalized === 'ALL') {
|
|
619
623
|
return normalized
|
|
620
624
|
}
|
|
621
|
-
throw new CliError(`Invalid version selector "${value}". Use active, pending, or
|
|
625
|
+
throw new CliError(`Invalid version selector "${value}". Use active, pending, all, or a version number.`)
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
function parseVersionSelector(value: string): string | number {
|
|
629
|
+
const num = Number.parseInt(value, 10)
|
|
630
|
+
if (!Number.isNaN(num) && String(num) === value) {
|
|
631
|
+
return num
|
|
632
|
+
}
|
|
633
|
+
return value
|
|
622
634
|
}
|
|
623
635
|
|
|
624
636
|
function parseInteger(value: string) {
|
|
@@ -639,7 +651,7 @@ function asNumber(value: unknown) {
|
|
|
639
651
|
|
|
640
652
|
function shapeProcessorStatusOutput(
|
|
641
653
|
data: { processors?: Array<Record<string, unknown>> },
|
|
642
|
-
versionSelector?: 'ACTIVE' | 'PENDING' | 'ALL'
|
|
654
|
+
versionSelector?: 'ACTIVE' | 'PENDING' | 'ALL' | number
|
|
643
655
|
) {
|
|
644
656
|
const processors = Array.isArray(data.processors) ? data.processors : []
|
|
645
657
|
if (versionSelector === 'ALL') {
|
|
@@ -651,6 +663,12 @@ function shapeProcessorStatusOutput(
|
|
|
651
663
|
processors: processors.filter((processor) => asString(processor.versionState) === versionSelector)
|
|
652
664
|
}
|
|
653
665
|
}
|
|
666
|
+
if (typeof versionSelector === 'number') {
|
|
667
|
+
return {
|
|
668
|
+
...data,
|
|
669
|
+
processors: processors.filter((processor) => asNumber(processor.version) === versionSelector)
|
|
670
|
+
}
|
|
671
|
+
}
|
|
654
672
|
return {
|
|
655
673
|
...data,
|
|
656
674
|
processors: processors.filter((processor) => {
|
package/src/commands/version.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { Command } from '@commander-js/extra-typings'
|
|
4
4
|
import { createLoginCommand } from './commands/login.js'
|
|
5
5
|
import { createCreateCommand } from './commands/create.js'
|
|
6
|
-
import { createVersionCommand } from './commands/version.js'
|
|
6
|
+
import { createVersionCommand, version } from './commands/version.js'
|
|
7
7
|
import { createTestCommand } from './commands/test.js'
|
|
8
8
|
import { createAddCommand } from './commands/add.js'
|
|
9
9
|
import { createCompileCommand } from './commands/compile.js'
|
|
@@ -34,6 +34,11 @@ if (process.argv.includes('--debug')) {
|
|
|
34
34
|
enableApiDebug()
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
if (process.argv.length === 3 && process.argv[2] === '--version') {
|
|
38
|
+
version()
|
|
39
|
+
process.exit(0)
|
|
40
|
+
}
|
|
41
|
+
|
|
37
42
|
program.addCommand(createLoginCommand())
|
|
38
43
|
program.addCommand(createCreateCommand())
|
|
39
44
|
program.addCommand(createVersionCommand())
|