primitive-admin 1.1.0-alpha.4 → 1.1.0-alpha.41
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 +325 -31
- package/assets/skill/skills/primitive-platform/SKILL.md +281 -0
- package/dist/bin/primitive.d.ts +2 -0
- package/dist/bin/primitive.js +268 -16
- package/dist/bin/primitive.js.map +1 -1
- package/dist/src/commands/admins.d.ts +2 -0
- package/dist/src/commands/admins.js +123 -18
- package/dist/src/commands/admins.js.map +1 -1
- package/dist/src/commands/analytics.d.ts +2 -0
- package/dist/src/commands/analytics.js +464 -55
- package/dist/src/commands/analytics.js.map +1 -1
- package/dist/src/commands/apps.d.ts +2 -0
- package/dist/src/commands/apps.js +55 -13
- package/dist/src/commands/apps.js.map +1 -1
- package/dist/src/commands/auth.d.ts +2 -0
- package/dist/src/commands/auth.js +177 -7
- package/dist/src/commands/auth.js.map +1 -1
- package/dist/src/commands/blob-buckets.d.ts +2 -0
- package/dist/src/commands/blob-buckets.js +422 -0
- package/dist/src/commands/blob-buckets.js.map +1 -0
- package/dist/src/commands/catalog.d.ts +2 -0
- package/dist/src/commands/catalog.js +34 -35
- package/dist/src/commands/catalog.js.map +1 -1
- package/dist/src/commands/collection-type-configs.d.ts +2 -0
- package/dist/src/commands/collection-type-configs.js +178 -0
- package/dist/src/commands/collection-type-configs.js.map +1 -0
- package/dist/src/commands/collections.d.ts +2 -0
- package/dist/src/commands/collections.js +515 -0
- package/dist/src/commands/collections.js.map +1 -0
- package/dist/src/commands/comparisons.d.ts +2 -0
- package/dist/src/commands/comparisons.js +6 -6
- package/dist/src/commands/comparisons.js.map +1 -1
- package/dist/src/commands/cron-triggers.d.ts +2 -0
- package/dist/src/commands/cron-triggers.js +357 -0
- package/dist/src/commands/cron-triggers.js.map +1 -0
- package/dist/src/commands/database-types.d.ts +2 -0
- package/dist/src/commands/database-types.js +471 -0
- package/dist/src/commands/database-types.js.map +1 -0
- package/dist/src/commands/databases.d.ts +2 -0
- package/dist/src/commands/databases.js +1462 -83
- package/dist/src/commands/databases.js.map +1 -1
- package/dist/src/commands/documents.d.ts +2 -0
- package/dist/src/commands/documents.js +518 -11
- package/dist/src/commands/documents.js.map +1 -1
- package/dist/src/commands/email-templates.d.ts +2 -0
- package/dist/src/commands/email-templates.js +281 -0
- package/dist/src/commands/email-templates.js.map +1 -0
- package/dist/src/commands/env.d.ts +12 -0
- package/dist/src/commands/env.js +260 -0
- package/dist/src/commands/env.js.map +1 -0
- package/dist/src/commands/group-type-configs.d.ts +2 -0
- package/dist/src/commands/group-type-configs.js +181 -0
- package/dist/src/commands/group-type-configs.js.map +1 -0
- package/dist/src/commands/groups.d.ts +2 -0
- package/dist/src/commands/groups.js +48 -45
- package/dist/src/commands/groups.js.map +1 -1
- package/dist/src/commands/guides.d.ts +159 -0
- package/dist/src/commands/guides.js +449 -35
- package/dist/src/commands/guides.js.map +1 -1
- package/dist/src/commands/init.d.ts +17 -0
- package/dist/src/commands/init.js +811 -201
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/commands/integrations.d.ts +2 -0
- package/dist/src/commands/integrations.js +533 -46
- package/dist/src/commands/integrations.js.map +1 -1
- package/dist/src/commands/llm.d.ts +2 -0
- package/dist/src/commands/llm.js +4 -2
- package/dist/src/commands/llm.js.map +1 -1
- package/dist/src/commands/metadata.d.ts +2 -0
- package/dist/src/commands/metadata.js +194 -0
- package/dist/src/commands/metadata.js.map +1 -0
- package/dist/src/commands/prompts.d.ts +2 -0
- package/dist/src/commands/prompts.js +50 -52
- package/dist/src/commands/prompts.js.map +1 -1
- package/dist/src/commands/rule-sets.d.ts +3 -0
- package/dist/src/commands/rule-sets.js +388 -0
- package/dist/src/commands/rule-sets.js.map +1 -0
- package/dist/src/commands/scripts.d.ts +2 -0
- package/dist/src/commands/scripts.js +667 -0
- package/dist/src/commands/scripts.js.map +1 -0
- package/dist/src/commands/secrets.d.ts +2 -0
- package/dist/src/commands/secrets.js +108 -0
- package/dist/src/commands/secrets.js.map +1 -0
- package/dist/src/commands/skill.d.ts +2 -0
- package/dist/src/commands/skill.js +29 -0
- package/dist/src/commands/skill.js.map +1 -0
- package/dist/src/commands/sync.d.ts +175 -0
- package/dist/src/commands/sync.js +4412 -233
- package/dist/src/commands/sync.js.map +1 -1
- package/dist/src/commands/tokens.d.ts +2 -0
- package/dist/src/commands/tokens.js +122 -19
- package/dist/src/commands/tokens.js.map +1 -1
- package/dist/src/commands/users.d.ts +2 -0
- package/dist/src/commands/users.js +440 -22
- package/dist/src/commands/users.js.map +1 -1
- package/dist/src/commands/waitlist.d.ts +2 -0
- package/dist/src/commands/waitlist.js +10 -10
- package/dist/src/commands/waitlist.js.map +1 -1
- package/dist/src/commands/webhooks.d.ts +2 -0
- package/dist/src/commands/webhooks.js +391 -0
- package/dist/src/commands/webhooks.js.map +1 -0
- package/dist/src/commands/workflows.d.ts +49 -0
- package/dist/src/commands/workflows.js +1009 -123
- package/dist/src/commands/workflows.js.map +1 -1
- package/dist/src/lib/api-client.d.ts +1283 -0
- package/dist/src/lib/api-client.js +1129 -83
- package/dist/src/lib/api-client.js.map +1 -1
- package/dist/src/lib/auth-flow.d.ts +8 -0
- package/dist/src/lib/block-layout.d.ts +160 -0
- package/dist/src/lib/block-layout.js +451 -0
- package/dist/src/lib/block-layout.js.map +1 -0
- package/dist/src/lib/cli-manifest.d.ts +60 -0
- package/dist/src/lib/cli-manifest.js +70 -0
- package/dist/src/lib/cli-manifest.js.map +1 -0
- package/dist/src/lib/config.d.ts +53 -0
- package/dist/src/lib/config.js +92 -53
- package/dist/src/lib/config.js.map +1 -1
- package/dist/src/lib/confirm-prompt.d.ts +83 -0
- package/dist/src/lib/confirm-prompt.js +110 -0
- package/dist/src/lib/confirm-prompt.js.map +1 -0
- package/dist/src/lib/constants.d.ts +2 -0
- package/dist/src/lib/constants.js +3 -0
- package/dist/src/lib/constants.js.map +1 -0
- package/dist/src/lib/crash-handlers.d.ts +20 -0
- package/dist/src/lib/crash-handlers.js +49 -0
- package/dist/src/lib/crash-handlers.js.map +1 -0
- package/dist/src/lib/credentials-store.d.ts +79 -0
- package/dist/src/lib/credentials-store.js +307 -0
- package/dist/src/lib/credentials-store.js.map +1 -0
- package/dist/src/lib/csv.d.ts +48 -0
- package/dist/src/lib/csv.js +177 -0
- package/dist/src/lib/csv.js.map +1 -0
- package/dist/src/lib/db-codegen/dbFingerprint.d.ts +10 -0
- package/dist/src/lib/db-codegen/dbFingerprint.js +17 -0
- package/dist/src/lib/db-codegen/dbFingerprint.js.map +1 -0
- package/dist/src/lib/db-codegen/dbGenerator.d.ts +111 -0
- package/dist/src/lib/db-codegen/dbGenerator.js +371 -0
- package/dist/src/lib/db-codegen/dbGenerator.js.map +1 -0
- package/dist/src/lib/db-codegen/dbNaming.d.ts +45 -0
- package/dist/src/lib/db-codegen/dbNaming.js +104 -0
- package/dist/src/lib/db-codegen/dbNaming.js.map +1 -0
- package/dist/src/lib/db-codegen/dbTemplates.d.ts +97 -0
- package/dist/src/lib/db-codegen/dbTemplates.js +159 -0
- package/dist/src/lib/db-codegen/dbTemplates.js.map +1 -0
- package/dist/src/lib/db-codegen/dbTsTypes.d.ts +78 -0
- package/dist/src/lib/db-codegen/dbTsTypes.js +112 -0
- package/dist/src/lib/db-codegen/dbTsTypes.js.map +1 -0
- package/dist/src/lib/env-resolver.d.ts +62 -0
- package/dist/src/lib/env-resolver.js +121 -0
- package/dist/src/lib/env-resolver.js.map +1 -0
- package/dist/src/lib/fetch.d.ts +5 -0
- package/dist/src/lib/generated-allowlist.d.ts +28 -0
- package/dist/src/lib/generated-allowlist.js +220 -0
- package/dist/src/lib/generated-allowlist.js.map +1 -0
- package/dist/src/lib/init-config.d.ts +59 -0
- package/dist/src/lib/init-config.js +113 -0
- package/dist/src/lib/init-config.js.map +1 -0
- package/dist/src/lib/migration-nag.d.ts +49 -0
- package/dist/src/lib/migration-nag.js +163 -0
- package/dist/src/lib/migration-nag.js.map +1 -0
- package/dist/src/lib/output.d.ts +86 -0
- package/dist/src/lib/output.js +150 -8
- package/dist/src/lib/output.js.map +1 -1
- package/dist/src/lib/paginate.d.ts +33 -0
- package/dist/src/lib/paginate.js +42 -0
- package/dist/src/lib/paginate.js.map +1 -0
- package/dist/src/lib/project-config.d.ts +97 -0
- package/dist/src/lib/project-config.js +209 -0
- package/dist/src/lib/project-config.js.map +1 -0
- package/dist/src/lib/query-operators.d.ts +43 -0
- package/dist/src/lib/query-operators.js +80 -0
- package/dist/src/lib/query-operators.js.map +1 -0
- package/dist/src/lib/refresh-admin-credentials.d.ts +65 -0
- package/dist/src/lib/refresh-admin-credentials.js +103 -0
- package/dist/src/lib/refresh-admin-credentials.js.map +1 -0
- package/dist/src/lib/resolve-platform.d.ts +45 -0
- package/dist/src/lib/resolve-platform.js +43 -0
- package/dist/src/lib/resolve-platform.js.map +1 -0
- package/dist/src/lib/skill-installer.d.ts +23 -0
- package/dist/src/lib/skill-installer.js +135 -0
- package/dist/src/lib/skill-installer.js.map +1 -0
- package/dist/src/lib/snapshots.d.ts +99 -0
- package/dist/src/lib/snapshots.js +357 -0
- package/dist/src/lib/snapshots.js.map +1 -0
- package/dist/src/lib/sync-paths.d.ts +72 -0
- package/dist/src/lib/sync-paths.js +130 -0
- package/dist/src/lib/sync-paths.js.map +1 -0
- package/dist/src/lib/template.d.ts +97 -0
- package/dist/src/lib/template.js +336 -62
- package/dist/src/lib/template.js.map +1 -1
- package/dist/src/lib/token-inject.d.ts +56 -0
- package/dist/src/lib/token-inject.js +204 -0
- package/dist/src/lib/token-inject.js.map +1 -0
- package/dist/src/lib/toml-database-config.d.ts +152 -0
- package/dist/src/lib/toml-database-config.js +600 -0
- package/dist/src/lib/toml-database-config.js.map +1 -0
- package/dist/src/lib/toml-metadata-config.d.ts +139 -0
- package/dist/src/lib/toml-metadata-config.js +427 -0
- package/dist/src/lib/toml-metadata-config.js.map +1 -0
- package/dist/src/lib/toml-params-validator.d.ts +129 -0
- package/dist/src/lib/toml-params-validator.js +298 -0
- package/dist/src/lib/toml-params-validator.js.map +1 -0
- package/dist/src/lib/version-check.d.ts +10 -0
- package/dist/src/lib/version-check.js +172 -0
- package/dist/src/lib/version-check.js.map +1 -0
- package/dist/src/lib/workflow-apply.d.ts +66 -0
- package/dist/src/lib/workflow-apply.js +117 -0
- package/dist/src/lib/workflow-apply.js.map +1 -0
- package/dist/src/lib/workflow-fragments.d.ts +41 -0
- package/dist/src/lib/workflow-fragments.js +121 -0
- package/dist/src/lib/workflow-fragments.js.map +1 -0
- package/dist/src/lib/workflow-toml-validator.d.ts +95 -0
- package/dist/src/lib/workflow-toml-validator.js +323 -0
- package/dist/src/lib/workflow-toml-validator.js.map +1 -0
- package/dist/src/types/index.d.ts +520 -0
- package/dist/src/validators.d.ts +64 -0
- package/dist/src/validators.js +63 -0
- package/dist/src/validators.js.map +1 -0
- package/package.json +18 -4
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { readFileSync } from "fs";
|
|
2
2
|
import * as TOML from "@iarna/toml";
|
|
3
|
+
import chalk from "chalk";
|
|
3
4
|
import { ApiClient } from "../lib/api-client.js";
|
|
4
5
|
import { resolveAppId } from "../lib/config.js";
|
|
5
|
-
import { success, error, info, keyValue, formatTable, formatId, formatDate, formatStatus, formatDuration, json, divider, } from "../lib/output.js";
|
|
6
|
+
import { success, error, info, warn, keyValue, result as printResult, formatTable, formatId, formatDate, formatStatus, formatDuration, json, divider, } from "../lib/output.js";
|
|
7
|
+
import { confirmPrompt } from "../lib/confirm-prompt.js";
|
|
6
8
|
export function registerIntegrationsCommands(program) {
|
|
7
9
|
const integrations = program
|
|
8
10
|
.command("integrations")
|
|
@@ -12,7 +14,10 @@ Examples:
|
|
|
12
14
|
$ primitive integrations list
|
|
13
15
|
$ primitive integrations create --from-file weather-api.toml
|
|
14
16
|
$ primitive integrations test 01HXY...
|
|
15
|
-
|
|
17
|
+
|
|
18
|
+
Storing secrets for an integration (integration secrets are deprecated):
|
|
19
|
+
$ primitive secrets set MY_API_KEY --value <value>
|
|
20
|
+
# then reference it in integration config via {{secrets.MY_API_KEY}}
|
|
16
21
|
`);
|
|
17
22
|
// List integrations
|
|
18
23
|
integrations
|
|
@@ -134,24 +139,24 @@ Examples:
|
|
|
134
139
|
json(result);
|
|
135
140
|
return;
|
|
136
141
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
142
|
+
printResult("Integration ID", result.integrationId);
|
|
143
|
+
printResult("Key", result.integrationKey);
|
|
144
|
+
printResult("Name", result.displayName);
|
|
145
|
+
printResult("Description", result.description);
|
|
146
|
+
printResult("Status", formatStatus(result.status));
|
|
147
|
+
printResult("Timeout", result.timeoutMs ? `${result.timeoutMs}ms` : "-");
|
|
143
148
|
if (result.requestConfig) {
|
|
144
149
|
divider();
|
|
145
150
|
info("Request Config:");
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
151
|
+
printResult(" Base URL", result.requestConfig.baseUrl);
|
|
152
|
+
printResult(" Allowed Methods", result.requestConfig.allowedMethods?.join(", ") || "all");
|
|
153
|
+
printResult(" Allowed Paths", result.requestConfig.allowedPaths?.join(", ") || "all");
|
|
154
|
+
printResult(" Response Passthrough", result.requestConfig.responsePassthrough);
|
|
150
155
|
if (result.requestConfig.bodyMode) {
|
|
151
|
-
|
|
156
|
+
printResult(" Body Mode", result.requestConfig.bodyMode);
|
|
152
157
|
}
|
|
153
158
|
if (result.requestConfig.multipartFieldMapping?.length) {
|
|
154
|
-
|
|
159
|
+
printResult(" Multipart Fields", result.requestConfig.multipartFieldMapping
|
|
155
160
|
.map((f) => `${f.fieldName} (${f.type}${f.attachmentIndex != null ? `, attachment[${f.attachmentIndex}]` : ""})`)
|
|
156
161
|
.join(", "));
|
|
157
162
|
}
|
|
@@ -214,15 +219,14 @@ Examples:
|
|
|
214
219
|
const resolvedAppId = resolveAppId(undefined, options);
|
|
215
220
|
if (!options.yes) {
|
|
216
221
|
const action = options.hard ? "permanently delete" : "archive";
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
{
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
]);
|
|
222
|
+
let confirm;
|
|
223
|
+
try {
|
|
224
|
+
confirm = await confirmPrompt(`Are you sure you want to ${action} integration ${integrationId}?`);
|
|
225
|
+
}
|
|
226
|
+
catch (err) {
|
|
227
|
+
error(err.message);
|
|
228
|
+
process.exit(1);
|
|
229
|
+
}
|
|
226
230
|
if (!confirm) {
|
|
227
231
|
info("Cancelled.");
|
|
228
232
|
return;
|
|
@@ -304,17 +308,24 @@ Examples:
|
|
|
304
308
|
// List integration logs
|
|
305
309
|
integrations
|
|
306
310
|
.command("logs")
|
|
307
|
-
.description("View integration request logs")
|
|
311
|
+
.description("View integration request logs (includes workflow-initiated calls; filter with --source workflow|user|admin|test)")
|
|
308
312
|
.argument("<integration-id>", "Integration ID")
|
|
309
313
|
.option("--app <app-id>", "App ID (uses current app if not specified)")
|
|
310
314
|
.option("--limit <n>", "Number of logs to show", "20")
|
|
315
|
+
.option("--source <source>", "Filter by call source: user, admin, test, or workflow")
|
|
311
316
|
.option("--json", "Output as JSON")
|
|
312
317
|
.action(async (integrationId, options) => {
|
|
313
318
|
const resolvedAppId = resolveAppId(undefined, options);
|
|
314
319
|
const client = new ApiClient();
|
|
315
320
|
try {
|
|
321
|
+
const validSources = new Set(["user", "admin", "test", "workflow"]);
|
|
322
|
+
if (options.source && !validSources.has(options.source)) {
|
|
323
|
+
error(`Invalid --source value '${options.source}'. Must be one of: user, admin, test, workflow.`);
|
|
324
|
+
process.exit(1);
|
|
325
|
+
}
|
|
316
326
|
const logs = await client.listIntegrationLogs(resolvedAppId, integrationId, {
|
|
317
327
|
limit: parseInt(options.limit),
|
|
328
|
+
...(options.source && { source: options.source }),
|
|
318
329
|
});
|
|
319
330
|
if (options.json) {
|
|
320
331
|
json(logs);
|
|
@@ -326,10 +337,13 @@ Examples:
|
|
|
326
337
|
}
|
|
327
338
|
console.log(formatTable(logs, [
|
|
328
339
|
{ header: "TIME", key: "timestamp", format: formatDate },
|
|
340
|
+
{ header: "SOURCE", key: "source", format: (v) => v || "user" },
|
|
329
341
|
{ header: "METHOD", key: "method" },
|
|
330
342
|
{ header: "PATH", key: "path" },
|
|
331
343
|
{ header: "STATUS", key: "status" },
|
|
332
344
|
{ header: "DURATION", key: "durationMs", format: formatDuration },
|
|
345
|
+
{ header: "WORKFLOW", key: "workflowKey", format: (v) => v || "" },
|
|
346
|
+
{ header: "RUN", key: "runId", format: (v) => (v ? formatId(v) : "") },
|
|
333
347
|
{ header: "TRACE", key: "traceId", format: formatId },
|
|
334
348
|
]));
|
|
335
349
|
}
|
|
@@ -339,7 +353,16 @@ Examples:
|
|
|
339
353
|
}
|
|
340
354
|
});
|
|
341
355
|
// Secrets subcommand
|
|
342
|
-
|
|
356
|
+
//
|
|
357
|
+
// Integration secrets are deprecated. New secrets can no longer be created
|
|
358
|
+
// (the `add` subcommand below is hidden from help and the command manifest,
|
|
359
|
+
// but kept registered so scripted callers still get a clear deprecation
|
|
360
|
+
// error — see issue #1026). The modern path is App Secrets:
|
|
361
|
+
// primitive secrets set <key> --value <value>
|
|
362
|
+
// then reference them in integration config via {{secrets.KEY}}.
|
|
363
|
+
const secrets = integrations
|
|
364
|
+
.command("secrets")
|
|
365
|
+
.description("Manage integration secrets (deprecated — use `primitive secrets set` and reference {{secrets.KEY}} in integration config instead)");
|
|
343
366
|
// List secrets
|
|
344
367
|
secrets
|
|
345
368
|
.command("list")
|
|
@@ -351,6 +374,7 @@ Examples:
|
|
|
351
374
|
const resolvedAppId = resolveAppId(undefined, options);
|
|
352
375
|
const client = new ApiClient();
|
|
353
376
|
try {
|
|
377
|
+
warn("Integration secrets are deprecated. Use App Secrets with {{secrets.KEY}} templates instead.");
|
|
354
378
|
const secretsList = await client.listIntegrationSecrets(resolvedAppId, integrationId);
|
|
355
379
|
if (options.json) {
|
|
356
380
|
json(secretsList);
|
|
@@ -374,60 +398,523 @@ Examples:
|
|
|
374
398
|
}
|
|
375
399
|
});
|
|
376
400
|
// Add secret
|
|
401
|
+
//
|
|
402
|
+
// Deprecated and disabled: hidden from `--help` and the command manifest
|
|
403
|
+
// (issue #1026) so the CLI no longer advertises a command that can never
|
|
404
|
+
// succeed, but still registered so an explicit/scripted invocation gets the
|
|
405
|
+
// clear deprecation error below rather than an "unknown command" error.
|
|
377
406
|
secrets
|
|
378
|
-
.command("add")
|
|
379
|
-
.description("Add a secret to an integration")
|
|
407
|
+
.command("add", { hidden: true })
|
|
408
|
+
.description("Add a secret to an integration (deprecated — use `primitive secrets set`)")
|
|
380
409
|
.argument("<integration-id>", "Integration ID")
|
|
381
410
|
.option("--app <app-id>", "App ID (uses current app if not specified)")
|
|
382
411
|
.option("--data <json>", "Secret data as JSON (e.g., '{\"apiKey\":\"...\"}' )")
|
|
383
412
|
.option("--summary <text>", "Description of the secret")
|
|
384
413
|
.option("--json", "Output as JSON")
|
|
414
|
+
.action(async () => {
|
|
415
|
+
error("Integration secrets are deprecated and can no longer be created.");
|
|
416
|
+
info("Use App Secrets with {{secrets.KEY}} templates in defaultHeaders or staticQuery instead.");
|
|
417
|
+
info("See: primitive secrets set <key> --value <value>");
|
|
418
|
+
process.exit(1);
|
|
419
|
+
});
|
|
420
|
+
// Archive secret
|
|
421
|
+
secrets
|
|
422
|
+
.command("archive")
|
|
423
|
+
.description("Archive a secret")
|
|
424
|
+
.argument("<integration-id>", "Integration ID")
|
|
425
|
+
.argument("<secret-id>", "Secret ID")
|
|
426
|
+
.option("--app <app-id>", "App ID (uses current app if not specified)")
|
|
427
|
+
.action(async (integrationId, secretId, options) => {
|
|
428
|
+
const resolvedAppId = resolveAppId(undefined, options);
|
|
429
|
+
const client = new ApiClient();
|
|
430
|
+
try {
|
|
431
|
+
await client.archiveIntegrationSecret(resolvedAppId, integrationId, secretId);
|
|
432
|
+
success("Secret archived.");
|
|
433
|
+
}
|
|
434
|
+
catch (err) {
|
|
435
|
+
error(err.message);
|
|
436
|
+
process.exit(1);
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
// ============================================
|
|
440
|
+
// TESTS SUBCOMMAND
|
|
441
|
+
// ============================================
|
|
442
|
+
const tests = integrations
|
|
443
|
+
.command("tests")
|
|
444
|
+
.description("Manage and run integration test cases")
|
|
445
|
+
.addHelpText("after", `
|
|
446
|
+
Examples:
|
|
447
|
+
$ primitive integrations tests list <integration-id>
|
|
448
|
+
$ primitive integrations tests create <integration-id> --name "Basic test" --input '{"method":"GET","path":"/get"}'
|
|
449
|
+
$ primitive integrations tests run <integration-id> <test-case-id>
|
|
450
|
+
$ primitive integrations tests run-all <integration-id>
|
|
451
|
+
$ primitive integrations tests run-all <integration-id> --test-cases 01ABC,01DEF,01GHI
|
|
452
|
+
`);
|
|
453
|
+
// List test cases
|
|
454
|
+
tests
|
|
455
|
+
.command("list")
|
|
456
|
+
.description("List test cases for an integration")
|
|
457
|
+
.argument("<integration-id>", "Integration ID")
|
|
458
|
+
.option("--app <app-id>", "App ID (uses current app if not specified)")
|
|
459
|
+
.option("--json", "Output as JSON")
|
|
460
|
+
.action(async (integrationId, options) => {
|
|
461
|
+
const resolvedAppId = resolveAppId(undefined, options);
|
|
462
|
+
const client = new ApiClient();
|
|
463
|
+
try {
|
|
464
|
+
const { items } = await client.listTestCases(resolvedAppId, "integration", integrationId);
|
|
465
|
+
if (options.json) {
|
|
466
|
+
json(items);
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
if (!items || items.length === 0) {
|
|
470
|
+
info("No test cases found.");
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
console.log(formatTable(items, [
|
|
474
|
+
{ header: "ID", key: "testCaseId", format: formatId },
|
|
475
|
+
{ header: "NAME", key: "name" },
|
|
476
|
+
{ header: "CONFIG", key: "configId", format: (v) => v ? formatId(v) : "-" },
|
|
477
|
+
{ header: "CREATED", key: "createdAt", format: formatDate },
|
|
478
|
+
]));
|
|
479
|
+
}
|
|
480
|
+
catch (err) {
|
|
481
|
+
error(err.message);
|
|
482
|
+
process.exit(1);
|
|
483
|
+
}
|
|
484
|
+
});
|
|
485
|
+
// Create test case
|
|
486
|
+
tests
|
|
487
|
+
.command("create")
|
|
488
|
+
.description("Create a test case for an integration")
|
|
489
|
+
.argument("<integration-id>", "Integration ID")
|
|
490
|
+
.option("--app <app-id>", "App ID (uses current app if not specified)")
|
|
491
|
+
.option("--name <name>", "Test case name (required)")
|
|
492
|
+
.option("--input <json>", "Input variables as JSON (required)")
|
|
493
|
+
.option("--pattern <regex>", "Expected output pattern (regex)")
|
|
494
|
+
.option("--contains <json>", "Expected strings to contain (JSON array)")
|
|
495
|
+
.option("--json-subset <json>", "Expected JSON subset to match")
|
|
496
|
+
.option("--config <config-id>", "Config ID to use for this test")
|
|
497
|
+
.option("--evaluator-prompt <prompt-id>", "Evaluator prompt ID")
|
|
498
|
+
.option("--evaluator-config <config-id>", "Evaluator config ID")
|
|
499
|
+
.option("--json", "Output as JSON")
|
|
385
500
|
.action(async (integrationId, options) => {
|
|
386
501
|
const resolvedAppId = resolveAppId(undefined, options);
|
|
387
|
-
if (!options.
|
|
388
|
-
error("--
|
|
502
|
+
if (!options.name || !options.input) {
|
|
503
|
+
error("Required: --name and --input");
|
|
389
504
|
process.exit(1);
|
|
390
505
|
}
|
|
391
|
-
let
|
|
506
|
+
let inputVariables;
|
|
392
507
|
try {
|
|
393
|
-
|
|
508
|
+
inputVariables = JSON.parse(options.input);
|
|
394
509
|
}
|
|
395
510
|
catch {
|
|
396
|
-
error("Invalid JSON in --
|
|
511
|
+
error("Invalid JSON in --input");
|
|
397
512
|
process.exit(1);
|
|
398
513
|
}
|
|
514
|
+
let expectedOutputContains;
|
|
515
|
+
if (options.contains) {
|
|
516
|
+
try {
|
|
517
|
+
expectedOutputContains = JSON.parse(options.contains);
|
|
518
|
+
}
|
|
519
|
+
catch {
|
|
520
|
+
error("Invalid JSON in --contains");
|
|
521
|
+
process.exit(1);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
let expectedJsonSubset;
|
|
525
|
+
if (options.jsonSubset) {
|
|
526
|
+
try {
|
|
527
|
+
expectedJsonSubset = JSON.parse(options.jsonSubset);
|
|
528
|
+
}
|
|
529
|
+
catch {
|
|
530
|
+
error("Invalid JSON in --json-subset");
|
|
531
|
+
process.exit(1);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
399
534
|
const client = new ApiClient();
|
|
400
535
|
try {
|
|
401
|
-
const result = await client.
|
|
402
|
-
|
|
403
|
-
|
|
536
|
+
const result = await client.createTestCase(resolvedAppId, "integration", integrationId, {
|
|
537
|
+
name: options.name,
|
|
538
|
+
inputVariables,
|
|
539
|
+
expectedOutputPattern: options.pattern,
|
|
540
|
+
expectedOutputContains,
|
|
541
|
+
expectedJsonSubset,
|
|
542
|
+
configId: options.config,
|
|
543
|
+
evaluatorPromptId: options.evaluatorPrompt,
|
|
544
|
+
evaluatorConfigId: options.evaluatorConfig,
|
|
404
545
|
});
|
|
405
546
|
if (options.json) {
|
|
406
547
|
json(result);
|
|
407
548
|
return;
|
|
408
549
|
}
|
|
409
|
-
success(
|
|
410
|
-
keyValue("
|
|
411
|
-
keyValue("Version", result?.version);
|
|
550
|
+
success(`Test case created: ${result.name}`);
|
|
551
|
+
keyValue("Test Case ID", result.testCaseId);
|
|
412
552
|
}
|
|
413
553
|
catch (err) {
|
|
414
554
|
error(err.message);
|
|
415
555
|
process.exit(1);
|
|
416
556
|
}
|
|
417
557
|
});
|
|
418
|
-
//
|
|
419
|
-
|
|
420
|
-
.command("
|
|
421
|
-
.description("
|
|
558
|
+
// Get test case
|
|
559
|
+
tests
|
|
560
|
+
.command("get")
|
|
561
|
+
.description("Get test case details")
|
|
422
562
|
.argument("<integration-id>", "Integration ID")
|
|
423
|
-
.argument("<
|
|
563
|
+
.argument("<test-case-id>", "Test Case ID")
|
|
424
564
|
.option("--app <app-id>", "App ID (uses current app if not specified)")
|
|
425
|
-
.
|
|
565
|
+
.option("--json", "Output as JSON")
|
|
566
|
+
.action(async (integrationId, testCaseId, options) => {
|
|
426
567
|
const resolvedAppId = resolveAppId(undefined, options);
|
|
427
568
|
const client = new ApiClient();
|
|
428
569
|
try {
|
|
429
|
-
await client.
|
|
430
|
-
|
|
570
|
+
const result = await client.getTestCase(resolvedAppId, "integration", integrationId, testCaseId);
|
|
571
|
+
if (options.json) {
|
|
572
|
+
json(result);
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
printResult("Test Case ID", result.testCaseId);
|
|
576
|
+
printResult("Name", result.name);
|
|
577
|
+
printResult("Config ID", result.configId || "-");
|
|
578
|
+
printResult("Created", formatDate(result.createdAt));
|
|
579
|
+
divider();
|
|
580
|
+
info("Input Variables:");
|
|
581
|
+
try {
|
|
582
|
+
const vars = JSON.parse(result.inputVariables || "{}");
|
|
583
|
+
console.log(JSON.stringify(vars, null, 2));
|
|
584
|
+
}
|
|
585
|
+
catch {
|
|
586
|
+
console.log(result.inputVariables || "{}");
|
|
587
|
+
}
|
|
588
|
+
if (result.expectedOutputPattern) {
|
|
589
|
+
divider();
|
|
590
|
+
printResult("Expected Pattern", result.expectedOutputPattern);
|
|
591
|
+
}
|
|
592
|
+
if (result.expectedOutputContains) {
|
|
593
|
+
divider();
|
|
594
|
+
info("Expected Contains:");
|
|
595
|
+
try {
|
|
596
|
+
console.log(JSON.stringify(JSON.parse(result.expectedOutputContains), null, 2));
|
|
597
|
+
}
|
|
598
|
+
catch {
|
|
599
|
+
console.log(result.expectedOutputContains);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
if (result.expectedJsonSubset) {
|
|
603
|
+
divider();
|
|
604
|
+
info("Expected JSON Subset:");
|
|
605
|
+
try {
|
|
606
|
+
console.log(JSON.stringify(JSON.parse(result.expectedJsonSubset), null, 2));
|
|
607
|
+
}
|
|
608
|
+
catch {
|
|
609
|
+
console.log(result.expectedJsonSubset);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
if (result.evaluatorPromptId) {
|
|
613
|
+
divider();
|
|
614
|
+
printResult("Evaluator Prompt", result.evaluatorPromptId);
|
|
615
|
+
printResult("Evaluator Config", result.evaluatorConfigId || "-");
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
catch (err) {
|
|
619
|
+
error(err.message);
|
|
620
|
+
process.exit(1);
|
|
621
|
+
}
|
|
622
|
+
});
|
|
623
|
+
// Update test case
|
|
624
|
+
tests
|
|
625
|
+
.command("update")
|
|
626
|
+
.description("Update a test case")
|
|
627
|
+
.argument("<integration-id>", "Integration ID")
|
|
628
|
+
.argument("<test-case-id>", "Test Case ID")
|
|
629
|
+
.option("--app <app-id>", "App ID (uses current app if not specified)")
|
|
630
|
+
.option("--name <name>", "Test case name")
|
|
631
|
+
.option("--input <json>", "Input variables as JSON")
|
|
632
|
+
.option("--pattern <regex>", "Expected output pattern (regex)")
|
|
633
|
+
.option("--contains <json>", "Expected strings to contain (JSON array)")
|
|
634
|
+
.option("--json-subset <json>", "Expected JSON subset to match")
|
|
635
|
+
.option("--config <config-id>", "Config ID to use for this test")
|
|
636
|
+
.option("--clear-pattern", "Clear expected pattern")
|
|
637
|
+
.option("--clear-contains", "Clear expected contains")
|
|
638
|
+
.option("--clear-json-subset", "Clear expected JSON subset")
|
|
639
|
+
.option("--json", "Output as JSON")
|
|
640
|
+
.action(async (integrationId, testCaseId, options) => {
|
|
641
|
+
const resolvedAppId = resolveAppId(undefined, options);
|
|
642
|
+
const payload = {};
|
|
643
|
+
if (options.name)
|
|
644
|
+
payload.name = options.name;
|
|
645
|
+
if (options.input) {
|
|
646
|
+
try {
|
|
647
|
+
payload.inputVariables = JSON.parse(options.input);
|
|
648
|
+
}
|
|
649
|
+
catch {
|
|
650
|
+
error("Invalid JSON in --input");
|
|
651
|
+
process.exit(1);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
if (options.clearPattern) {
|
|
655
|
+
payload.expectedOutputPattern = null;
|
|
656
|
+
}
|
|
657
|
+
else if (options.pattern) {
|
|
658
|
+
payload.expectedOutputPattern = options.pattern;
|
|
659
|
+
}
|
|
660
|
+
if (options.clearContains) {
|
|
661
|
+
payload.expectedOutputContains = null;
|
|
662
|
+
}
|
|
663
|
+
else if (options.contains) {
|
|
664
|
+
try {
|
|
665
|
+
payload.expectedOutputContains = JSON.parse(options.contains);
|
|
666
|
+
}
|
|
667
|
+
catch {
|
|
668
|
+
error("Invalid JSON in --contains");
|
|
669
|
+
process.exit(1);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
if (options.clearJsonSubset) {
|
|
673
|
+
payload.expectedJsonSubset = null;
|
|
674
|
+
}
|
|
675
|
+
else if (options.jsonSubset) {
|
|
676
|
+
try {
|
|
677
|
+
payload.expectedJsonSubset = JSON.parse(options.jsonSubset);
|
|
678
|
+
}
|
|
679
|
+
catch {
|
|
680
|
+
error("Invalid JSON in --json-subset");
|
|
681
|
+
process.exit(1);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
if (options.config)
|
|
685
|
+
payload.configId = options.config;
|
|
686
|
+
if (Object.keys(payload).length === 0) {
|
|
687
|
+
error("No update options specified.");
|
|
688
|
+
process.exit(1);
|
|
689
|
+
}
|
|
690
|
+
const client = new ApiClient();
|
|
691
|
+
try {
|
|
692
|
+
const result = await client.updateTestCase(resolvedAppId, "integration", integrationId, testCaseId, payload);
|
|
693
|
+
if (options.json) {
|
|
694
|
+
json(result);
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
success("Test case updated.");
|
|
698
|
+
}
|
|
699
|
+
catch (err) {
|
|
700
|
+
error(err.message);
|
|
701
|
+
process.exit(1);
|
|
702
|
+
}
|
|
703
|
+
});
|
|
704
|
+
// Delete test case
|
|
705
|
+
tests
|
|
706
|
+
.command("delete")
|
|
707
|
+
.description("Delete a test case")
|
|
708
|
+
.argument("<integration-id>", "Integration ID")
|
|
709
|
+
.argument("<test-case-id>", "Test Case ID")
|
|
710
|
+
.option("--app <app-id>", "App ID (uses current app if not specified)")
|
|
711
|
+
.option("-y, --yes", "Skip confirmation prompt")
|
|
712
|
+
.action(async (integrationId, testCaseId, options) => {
|
|
713
|
+
const resolvedAppId = resolveAppId(undefined, options);
|
|
714
|
+
if (!options.yes) {
|
|
715
|
+
let confirm;
|
|
716
|
+
try {
|
|
717
|
+
confirm = await confirmPrompt(`Are you sure you want to delete test case ${testCaseId}?`);
|
|
718
|
+
}
|
|
719
|
+
catch (err) {
|
|
720
|
+
error(err.message);
|
|
721
|
+
process.exit(1);
|
|
722
|
+
}
|
|
723
|
+
if (!confirm) {
|
|
724
|
+
info("Cancelled.");
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
const client = new ApiClient();
|
|
729
|
+
try {
|
|
730
|
+
await client.deleteTestCase(resolvedAppId, "integration", integrationId, testCaseId);
|
|
731
|
+
success("Test case deleted.");
|
|
732
|
+
}
|
|
733
|
+
catch (err) {
|
|
734
|
+
error(err.message);
|
|
735
|
+
process.exit(1);
|
|
736
|
+
}
|
|
737
|
+
});
|
|
738
|
+
// Run a single test case
|
|
739
|
+
tests
|
|
740
|
+
.command("run")
|
|
741
|
+
.description("Run a single test case")
|
|
742
|
+
.argument("<integration-id>", "Integration ID")
|
|
743
|
+
.argument("<test-case-id>", "Test Case ID")
|
|
744
|
+
.option("--app <app-id>", "App ID (uses current app if not specified)")
|
|
745
|
+
.option("--config <config-id>", "Override config ID")
|
|
746
|
+
.option("--json", "Output as JSON")
|
|
747
|
+
.action(async (integrationId, testCaseId, options) => {
|
|
748
|
+
const resolvedAppId = resolveAppId(undefined, options);
|
|
749
|
+
const client = new ApiClient();
|
|
750
|
+
try {
|
|
751
|
+
// Get test case to retrieve its variables
|
|
752
|
+
const testCase = await client.getTestCase(resolvedAppId, "integration", integrationId, testCaseId);
|
|
753
|
+
let variables = {};
|
|
754
|
+
try {
|
|
755
|
+
variables = JSON.parse(testCase.inputVariables || "{}");
|
|
756
|
+
}
|
|
757
|
+
catch {
|
|
758
|
+
// Use empty object if parsing fails
|
|
759
|
+
}
|
|
760
|
+
const result = await client.executeBlockTest(resolvedAppId, "integration", integrationId, {
|
|
761
|
+
variables,
|
|
762
|
+
testCaseId,
|
|
763
|
+
configId: options.config || testCase.configId,
|
|
764
|
+
});
|
|
765
|
+
if (options.json) {
|
|
766
|
+
json(result);
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
const passed = result.verification?.passed;
|
|
770
|
+
if (passed) {
|
|
771
|
+
success("Test PASSED");
|
|
772
|
+
}
|
|
773
|
+
else {
|
|
774
|
+
error("Test FAILED");
|
|
775
|
+
}
|
|
776
|
+
keyValue("Duration", formatDuration(result.metrics?.durationMs));
|
|
777
|
+
if (result.verification) {
|
|
778
|
+
divider();
|
|
779
|
+
info("Verification:");
|
|
780
|
+
const v = result.verification;
|
|
781
|
+
keyValue(" Passed", v.passed ? chalk.green("Yes") : chalk.red("No"));
|
|
782
|
+
keyValue(" Checks", `${v.summary?.passed || 0}/${v.summary?.total || 0} passed`);
|
|
783
|
+
if (v.checks && v.checks.length > 0) {
|
|
784
|
+
console.log();
|
|
785
|
+
for (const check of v.checks) {
|
|
786
|
+
const icon = check.passed ? chalk.green("\u2713") : chalk.red("\u2717");
|
|
787
|
+
console.log(` ${icon} ${check.name}`);
|
|
788
|
+
if (!check.passed && check.message) {
|
|
789
|
+
console.log(` ${chalk.dim(check.message)}`);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
if (result.output) {
|
|
795
|
+
divider();
|
|
796
|
+
info("Output:");
|
|
797
|
+
console.log(typeof result.output === "string" ? result.output : JSON.stringify(result.output, null, 2));
|
|
798
|
+
}
|
|
799
|
+
if (result.error) {
|
|
800
|
+
divider();
|
|
801
|
+
error(`Error: ${result.error}`);
|
|
802
|
+
}
|
|
803
|
+
if (!passed) {
|
|
804
|
+
process.exit(1);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
catch (err) {
|
|
808
|
+
error(err.message);
|
|
809
|
+
process.exit(1);
|
|
810
|
+
}
|
|
811
|
+
});
|
|
812
|
+
// Run all test cases (or a specific group)
|
|
813
|
+
tests
|
|
814
|
+
.command("run-all")
|
|
815
|
+
.description("Run all test cases for an integration, or a specific subset")
|
|
816
|
+
.argument("<integration-id>", "Integration ID")
|
|
817
|
+
.option("--app <app-id>", "App ID (uses current app if not specified)")
|
|
818
|
+
.option("--config <config-id>", "Override config ID for all tests")
|
|
819
|
+
.option("--test-cases <ids>", "Comma-separated list of test case IDs to run (runs only these)")
|
|
820
|
+
.option("--json", "Output as JSON")
|
|
821
|
+
.action(async (integrationId, options) => {
|
|
822
|
+
const resolvedAppId = resolveAppId(undefined, options);
|
|
823
|
+
const client = new ApiClient();
|
|
824
|
+
try {
|
|
825
|
+
// Parse test case IDs if provided
|
|
826
|
+
let testCaseIds;
|
|
827
|
+
if (options.testCases) {
|
|
828
|
+
testCaseIds = options.testCases.split(",").map((id) => id.trim()).filter(Boolean);
|
|
829
|
+
if (testCaseIds.length === 0) {
|
|
830
|
+
error("No valid test case IDs provided in --test-cases");
|
|
831
|
+
process.exit(1);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
if (!options.json) {
|
|
835
|
+
if (testCaseIds) {
|
|
836
|
+
info(`Running ${testCaseIds.length} selected test case(s)...`);
|
|
837
|
+
}
|
|
838
|
+
else {
|
|
839
|
+
info("Running all test cases...");
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
const result = await client.runAllTestCases(resolvedAppId, "integration", integrationId, {
|
|
843
|
+
overrideConfigId: options.config,
|
|
844
|
+
testCaseIds,
|
|
845
|
+
});
|
|
846
|
+
if (options.json) {
|
|
847
|
+
json(result);
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
const { summary, runs, comparisonGroup } = result;
|
|
851
|
+
divider();
|
|
852
|
+
keyValue("Comparison Group", comparisonGroup);
|
|
853
|
+
keyValue("Total Tests", summary.total);
|
|
854
|
+
keyValue("Passed", chalk.green(summary.passed));
|
|
855
|
+
keyValue("Failed", summary.failed > 0 ? chalk.red(summary.failed) : "0");
|
|
856
|
+
divider();
|
|
857
|
+
if (runs && runs.length > 0) {
|
|
858
|
+
for (const r of runs) {
|
|
859
|
+
const passed = r.verification?.passed;
|
|
860
|
+
const icon = passed ? chalk.green("\u2713") : chalk.red("\u2717");
|
|
861
|
+
const status = passed ? "PASSED" : "FAILED";
|
|
862
|
+
console.log(`${icon} ${r.testCaseName} - ${status}`);
|
|
863
|
+
if (!passed && r.verification?.details?.reason) {
|
|
864
|
+
console.log(` ${chalk.dim(r.verification.details.reason)}`);
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
// Exit with error code if any tests failed
|
|
869
|
+
if (summary.failed > 0) {
|
|
870
|
+
process.exit(1);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
catch (err) {
|
|
874
|
+
error(err.message);
|
|
875
|
+
process.exit(1);
|
|
876
|
+
}
|
|
877
|
+
});
|
|
878
|
+
// List test runs
|
|
879
|
+
tests
|
|
880
|
+
.command("runs")
|
|
881
|
+
.description("List recent test runs for an integration")
|
|
882
|
+
.argument("<integration-id>", "Integration ID")
|
|
883
|
+
.option("--app <app-id>", "App ID (uses current app if not specified)")
|
|
884
|
+
.option("--limit <n>", "Maximum number of runs to show", "20")
|
|
885
|
+
.option("--group <group>", "Filter by comparison group")
|
|
886
|
+
.option("--json", "Output as JSON")
|
|
887
|
+
.action(async (integrationId, options) => {
|
|
888
|
+
const resolvedAppId = resolveAppId(undefined, options);
|
|
889
|
+
const client = new ApiClient();
|
|
890
|
+
try {
|
|
891
|
+
const { items } = await client.listTestRuns(resolvedAppId, "integration", integrationId, {
|
|
892
|
+
limit: parseInt(options.limit),
|
|
893
|
+
comparisonGroup: options.group,
|
|
894
|
+
});
|
|
895
|
+
if (options.json) {
|
|
896
|
+
json(items);
|
|
897
|
+
return;
|
|
898
|
+
}
|
|
899
|
+
if (!items || items.length === 0) {
|
|
900
|
+
info("No test runs found.");
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
console.log(formatTable(items, [
|
|
904
|
+
{ header: "RUN ID", key: "runId", format: formatId },
|
|
905
|
+
{ header: "TEST CASE ID", key: "testCaseId", format: (v) => v ? formatId(v) : "-" },
|
|
906
|
+
{
|
|
907
|
+
header: "PASSED",
|
|
908
|
+
key: "verificationPassed",
|
|
909
|
+
format: (v) => v === true
|
|
910
|
+
? chalk.green("Yes")
|
|
911
|
+
: v === false
|
|
912
|
+
? chalk.red("No")
|
|
913
|
+
: "-",
|
|
914
|
+
},
|
|
915
|
+
{ header: "DURATION", key: "durationMs", format: formatDuration },
|
|
916
|
+
{ header: "STARTED", key: "startedAt", format: formatDate },
|
|
917
|
+
]));
|
|
431
918
|
}
|
|
432
919
|
catch (err) {
|
|
433
920
|
error(err.message);
|