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