primitive-admin 1.0.61 → 1.0.63
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 +125 -69
- package/assets/skill/skills/primitive-platform/SKILL.md +336 -37
- package/dist/bin/primitive.js +25 -14
- package/dist/bin/primitive.js.map +1 -1
- package/dist/src/commands/analytics.js +36 -16
- package/dist/src/commands/analytics.js.map +1 -1
- package/dist/src/commands/apps.js +52 -188
- package/dist/src/commands/apps.js.map +1 -1
- package/dist/src/commands/blob-buckets.js +13 -153
- package/dist/src/commands/blob-buckets.js.map +1 -1
- package/dist/src/commands/collection-type-configs.js +14 -100
- package/dist/src/commands/collection-type-configs.js.map +1 -1
- package/dist/src/commands/collections.js +47 -8
- package/dist/src/commands/collections.js.map +1 -1
- package/dist/src/commands/config.d.ts +46 -0
- package/dist/src/commands/config.js +479 -0
- package/dist/src/commands/config.js.map +1 -0
- package/dist/src/commands/connections.js +12 -7
- package/dist/src/commands/connections.js.map +1 -1
- package/dist/src/commands/cron-triggers.js +84 -176
- package/dist/src/commands/cron-triggers.js.map +1 -1
- package/dist/src/commands/database-type-configs.d.ts +2 -0
- package/dist/src/commands/database-type-configs.js +171 -0
- package/dist/src/commands/database-type-configs.js.map +1 -0
- package/dist/src/commands/database-types.js +4 -4
- package/dist/src/commands/database-types.js.map +1 -1
- package/dist/src/commands/databases.d.ts +63 -0
- package/dist/src/commands/databases.js +649 -87
- package/dist/src/commands/databases.js.map +1 -1
- package/dist/src/commands/documents.js +869 -12
- package/dist/src/commands/documents.js.map +1 -1
- package/dist/src/commands/email-templates.js +21 -128
- package/dist/src/commands/email-templates.js.map +1 -1
- package/dist/src/commands/env.d.ts +12 -1
- package/dist/src/commands/env.js +115 -26
- package/dist/src/commands/env.js.map +1 -1
- package/dist/src/commands/feature-flags.d.ts +14 -0
- package/dist/src/commands/feature-flags.js +116 -0
- package/dist/src/commands/feature-flags.js.map +1 -0
- package/dist/src/commands/group-type-configs.js +14 -109
- package/dist/src/commands/group-type-configs.js.map +1 -1
- package/dist/src/commands/init.d.ts +24 -1
- package/dist/src/commands/init.js +1194 -279
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/commands/integrations.js +166 -435
- package/dist/src/commands/integrations.js.map +1 -1
- package/dist/src/commands/metadata-category-configs.d.ts +12 -0
- package/dist/src/commands/metadata-category-configs.js +112 -0
- package/dist/src/commands/metadata-category-configs.js.map +1 -0
- package/dist/src/commands/metadata.js +33 -141
- package/dist/src/commands/metadata.js.map +1 -1
- package/dist/src/commands/prompts.js +206 -563
- package/dist/src/commands/prompts.js.map +1 -1
- package/dist/src/commands/rule-sets.d.ts +1 -1
- package/dist/src/commands/rule-sets.js +16 -132
- package/dist/src/commands/rule-sets.js.map +1 -1
- package/dist/src/commands/scripts.d.ts +10 -0
- package/dist/src/commands/scripts.js +203 -241
- package/dist/src/commands/scripts.js.map +1 -1
- package/dist/src/commands/sessions.js +17 -5
- package/dist/src/commands/sessions.js.map +1 -1
- package/dist/src/commands/sync-app-settings.d.ts +113 -70
- package/dist/src/commands/sync-app-settings.js +193 -242
- package/dist/src/commands/sync-app-settings.js.map +1 -1
- package/dist/src/commands/sync.d.ts +1904 -51
- package/dist/src/commands/sync.js +9468 -1790
- package/dist/src/commands/sync.js.map +1 -1
- package/dist/src/commands/tokens.js +8 -2
- package/dist/src/commands/tokens.js.map +1 -1
- package/dist/src/commands/users.js +92 -1
- package/dist/src/commands/users.js.map +1 -1
- package/dist/src/commands/vars.js +44 -58
- package/dist/src/commands/vars.js.map +1 -1
- package/dist/src/commands/webhooks.js +318 -156
- package/dist/src/commands/webhooks.js.map +1 -1
- package/dist/src/commands/workflows.d.ts +49 -12
- package/dist/src/commands/workflows.js +652 -1086
- package/dist/src/commands/workflows.js.map +1 -1
- package/dist/src/lib/access-rule-display.d.ts +21 -0
- package/dist/src/lib/access-rule-display.js +34 -0
- package/dist/src/lib/access-rule-display.js.map +1 -0
- package/dist/src/lib/api-client.d.ts +541 -61
- package/dist/src/lib/api-client.js +559 -102
- package/dist/src/lib/api-client.js.map +1 -1
- package/dist/src/lib/app-settings-descriptor.d.ts +180 -25
- package/dist/src/lib/app-settings-descriptor.js +403 -82
- package/dist/src/lib/app-settings-descriptor.js.map +1 -1
- package/dist/src/lib/canonical-json.d.ts +12 -0
- package/dist/src/lib/canonical-json.js +35 -0
- package/dist/src/lib/canonical-json.js.map +1 -0
- package/dist/src/lib/cli-manifest.d.ts +8 -0
- package/dist/src/lib/cli-manifest.js +1 -0
- package/dist/src/lib/cli-manifest.js.map +1 -1
- package/dist/src/lib/codegen-shared/generatedFiles.d.ts +6 -2
- package/dist/src/lib/codegen-shared/generatedFiles.js +9 -2
- package/dist/src/lib/codegen-shared/generatedFiles.js.map +1 -1
- package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.d.ts +6 -6
- package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.js +8 -8
- package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.js.map +1 -1
- package/dist/src/lib/config-object-descriptor.d.ts +127 -0
- package/dist/src/lib/config-object-descriptor.js +658 -0
- package/dist/src/lib/config-object-descriptor.js.map +1 -0
- package/dist/src/lib/config-payload.d.ts +85 -0
- package/dist/src/lib/config-payload.js +116 -0
- package/dist/src/lib/config-payload.js.map +1 -0
- package/dist/src/lib/config-surface.d.ts +130 -0
- package/dist/src/lib/config-surface.js +300 -0
- package/dist/src/lib/config-surface.js.map +1 -0
- package/dist/src/lib/credentials-store.d.ts +10 -0
- package/dist/src/lib/credentials-store.js +23 -0
- package/dist/src/lib/credentials-store.js.map +1 -1
- package/dist/src/lib/data-input.d.ts +23 -0
- package/dist/src/lib/data-input.js +50 -0
- package/dist/src/lib/data-input.js.map +1 -0
- package/dist/src/lib/db-codegen/dbTemplates.js +1 -1
- package/dist/src/lib/db-codegen/dbTemplates.js.map +1 -1
- package/dist/src/lib/db-codegen/dbTypeIR.d.ts +1 -1
- package/dist/src/lib/env-resolver-core.d.ts +187 -0
- package/dist/src/lib/env-resolver-core.js +325 -0
- package/dist/src/lib/env-resolver-core.js.map +1 -0
- package/dist/src/lib/env-resolver.d.ts +28 -6
- package/dist/src/lib/env-resolver.js +44 -32
- package/dist/src/lib/env-resolver.js.map +1 -1
- package/dist/src/lib/generated-allowlist.js +12 -0
- package/dist/src/lib/generated-allowlist.js.map +1 -1
- package/dist/src/lib/generated-config-surfaces.d.ts +682 -0
- package/dist/src/lib/generated-config-surfaces.js +4058 -0
- package/dist/src/lib/generated-config-surfaces.js.map +1 -0
- package/dist/src/lib/generated-template-lint.d.ts +212 -0
- package/dist/src/lib/generated-template-lint.js +624 -0
- package/dist/src/lib/generated-template-lint.js.map +1 -0
- package/dist/src/lib/init-adopt.d.ts +16 -0
- package/dist/src/lib/init-adopt.js +34 -0
- package/dist/src/lib/init-adopt.js.map +1 -0
- package/dist/src/lib/init-assets.d.ts +39 -0
- package/dist/src/lib/init-assets.js +97 -0
- package/dist/src/lib/init-assets.js.map +1 -0
- package/dist/src/lib/init-client-platforms.d.ts +14 -0
- package/dist/src/lib/init-client-platforms.js +71 -0
- package/dist/src/lib/init-client-platforms.js.map +1 -0
- package/dist/src/lib/init-config.d.ts +44 -5
- package/dist/src/lib/init-config.js +80 -7
- package/dist/src/lib/init-config.js.map +1 -1
- package/dist/src/lib/init-email-redirect-uris.d.ts +37 -0
- package/dist/src/lib/init-email-redirect-uris.js +46 -0
- package/dist/src/lib/init-email-redirect-uris.js.map +1 -0
- package/dist/src/lib/init-ios-links.d.ts +90 -0
- package/dist/src/lib/init-ios-links.js +218 -0
- package/dist/src/lib/init-ios-links.js.map +1 -0
- package/dist/src/lib/init-plan.d.ts +80 -0
- package/dist/src/lib/init-plan.js +95 -0
- package/dist/src/lib/init-plan.js.map +1 -0
- package/dist/src/lib/init-production-env.d.ts +48 -0
- package/dist/src/lib/init-production-env.js +59 -0
- package/dist/src/lib/init-production-env.js.map +1 -0
- package/dist/src/lib/init-schema.d.ts +74 -0
- package/dist/src/lib/init-schema.js +358 -0
- package/dist/src/lib/init-schema.js.map +1 -0
- package/dist/src/lib/init-xcode.d.ts +33 -0
- package/dist/src/lib/init-xcode.js +114 -0
- package/dist/src/lib/init-xcode.js.map +1 -0
- package/dist/src/lib/integration-request-config.d.ts +30 -0
- package/dist/src/lib/integration-request-config.js +145 -0
- package/dist/src/lib/integration-request-config.js.map +1 -0
- package/dist/src/lib/integration-selector.d.ts +42 -0
- package/dist/src/lib/integration-selector.js +46 -0
- package/dist/src/lib/integration-selector.js.map +1 -0
- package/dist/src/lib/local-state.d.ts +55 -0
- package/dist/src/lib/local-state.js +167 -0
- package/dist/src/lib/local-state.js.map +1 -0
- package/dist/src/lib/local-test-cases.d.ts +63 -0
- package/dist/src/lib/local-test-cases.js +135 -0
- package/dist/src/lib/local-test-cases.js.map +1 -0
- package/dist/src/lib/log-inspection.d.ts +568 -0
- package/dist/src/lib/log-inspection.js +639 -0
- package/dist/src/lib/log-inspection.js.map +1 -0
- package/dist/src/lib/migration-nag.d.ts +2 -2
- package/dist/src/lib/migration-nag.js +3 -3
- package/dist/src/lib/object-status-filter.d.ts +22 -0
- package/dist/src/lib/object-status-filter.js +45 -0
- package/dist/src/lib/object-status-filter.js.map +1 -0
- package/dist/src/lib/output.d.ts +24 -1
- package/dist/src/lib/output.js +42 -1
- package/dist/src/lib/output.js.map +1 -1
- package/dist/src/lib/package-manager.d.ts +140 -0
- package/dist/src/lib/package-manager.js +305 -0
- package/dist/src/lib/package-manager.js.map +1 -0
- package/dist/src/lib/paginate.d.ts +50 -0
- package/dist/src/lib/paginate.js +53 -0
- package/dist/src/lib/paginate.js.map +1 -1
- package/dist/src/lib/platform-owned.d.ts +29 -3
- package/dist/src/lib/platform-owned.js +42 -3
- package/dist/src/lib/platform-owned.js.map +1 -1
- package/dist/src/lib/project-config.d.ts +8 -0
- package/dist/src/lib/project-config.js +30 -5
- package/dist/src/lib/project-config.js.map +1 -1
- package/dist/src/lib/query-operators.d.ts +1 -1
- package/dist/src/lib/query-operators.js +1 -1
- package/dist/src/lib/record-filter.d.ts +18 -0
- package/dist/src/lib/record-filter.js +55 -0
- package/dist/src/lib/record-filter.js.map +1 -0
- package/dist/src/lib/resolve-init-dev-port.d.ts +56 -0
- package/dist/src/lib/resolve-init-dev-port.js +55 -0
- package/dist/src/lib/resolve-init-dev-port.js.map +1 -0
- package/dist/src/lib/resolve-init-server.d.ts +64 -0
- package/dist/src/lib/resolve-init-server.js +77 -0
- package/dist/src/lib/resolve-init-server.js.map +1 -0
- package/dist/src/lib/resolve-platform.d.ts +43 -14
- package/dist/src/lib/resolve-platform.js +74 -12
- package/dist/src/lib/resolve-platform.js.map +1 -1
- package/dist/src/lib/run-status.d.ts +19 -0
- package/dist/src/lib/run-status.generated.d.ts +39 -0
- package/dist/src/lib/run-status.generated.js +66 -0
- package/dist/src/lib/run-status.generated.js.map +1 -0
- package/dist/src/lib/run-status.js +19 -0
- package/dist/src/lib/run-status.js.map +1 -0
- package/dist/src/lib/server-text-normalization.d.ts +51 -0
- package/dist/src/lib/server-text-normalization.js +90 -0
- package/dist/src/lib/server-text-normalization.js.map +1 -0
- package/dist/src/lib/server-url.d.ts +22 -0
- package/dist/src/lib/server-url.js +33 -0
- package/dist/src/lib/server-url.js.map +1 -0
- package/dist/src/lib/signing-secret-status.d.ts +81 -0
- package/dist/src/lib/signing-secret-status.js +116 -0
- package/dist/src/lib/signing-secret-status.js.map +1 -0
- package/dist/src/lib/skill-installer.js +6 -3
- package/dist/src/lib/skill-installer.js.map +1 -1
- package/dist/src/lib/snapshots.d.ts +2 -2
- package/dist/src/lib/snapshots.js +2 -2
- package/dist/src/lib/swift-codegen/dbGenerator.d.ts +15 -2
- package/dist/src/lib/swift-codegen/dbGenerator.js +250 -18
- package/dist/src/lib/swift-codegen/dbGenerator.js.map +1 -1
- package/dist/src/lib/swift-codegen/generator.d.ts +11 -2
- package/dist/src/lib/swift-codegen/generator.js +181 -7
- package/dist/src/lib/swift-codegen/generator.js.map +1 -1
- package/dist/src/lib/swift-codegen/siblingSymbols.d.ts +7 -3
- package/dist/src/lib/swift-codegen/siblingSymbols.js +4 -1
- package/dist/src/lib/swift-codegen/siblingSymbols.js.map +1 -1
- package/dist/src/lib/sync-dir-selector.d.ts +21 -0
- package/dist/src/lib/sync-dir-selector.js +30 -0
- package/dist/src/lib/sync-dir-selector.js.map +1 -0
- package/dist/src/lib/sync-paths.d.ts +39 -0
- package/dist/src/lib/sync-paths.js +70 -2
- package/dist/src/lib/sync-paths.js.map +1 -1
- package/dist/src/lib/sync-resource-types.d.ts +138 -31
- package/dist/src/lib/sync-resource-types.js +241 -47
- package/dist/src/lib/sync-resource-types.js.map +1 -1
- package/dist/src/lib/sync-selectors.d.ts +95 -0
- package/dist/src/lib/sync-selectors.js +228 -0
- package/dist/src/lib/sync-selectors.js.map +1 -0
- package/dist/src/lib/template.d.ts +62 -4
- package/dist/src/lib/template.js +147 -39
- package/dist/src/lib/template.js.map +1 -1
- package/dist/src/lib/test-case-file-names.d.ts +40 -0
- package/dist/src/lib/test-case-file-names.js +91 -0
- package/dist/src/lib/test-case-file-names.js.map +1 -0
- package/dist/src/lib/test-case-keys.d.ts +29 -0
- package/dist/src/lib/test-case-keys.js +55 -0
- package/dist/src/lib/test-case-keys.js.map +1 -0
- package/dist/src/lib/test-case-variables.d.ts +15 -0
- package/dist/src/lib/test-case-variables.js +29 -0
- package/dist/src/lib/test-case-variables.js.map +1 -0
- package/dist/src/lib/toml-database-config.d.ts +4 -4
- package/dist/src/lib/toml-database-config.js +27 -34
- package/dist/src/lib/toml-database-config.js.map +1 -1
- package/dist/src/lib/toml-metadata-config.d.ts +9 -5
- package/dist/src/lib/toml-metadata-config.js +40 -45
- package/dist/src/lib/toml-metadata-config.js.map +1 -1
- package/dist/src/lib/toml-native-form.d.ts +1 -1
- package/dist/src/lib/toml-native-form.js +1 -1
- package/dist/src/lib/toml-params-validator.d.ts +1 -1
- package/dist/src/lib/toml-params-validator.js +1 -1
- package/dist/src/lib/toml-scalar-edit.d.ts +43 -0
- package/dist/src/lib/toml-scalar-edit.js +283 -0
- package/dist/src/lib/toml-scalar-edit.js.map +1 -0
- package/dist/src/lib/user-selector.d.ts +24 -0
- package/dist/src/lib/user-selector.js +33 -0
- package/dist/src/lib/user-selector.js.map +1 -0
- package/dist/src/lib/version-check.d.ts +25 -0
- package/dist/src/lib/version-check.js +77 -8
- package/dist/src/lib/version-check.js.map +1 -1
- package/dist/src/lib/watch.d.ts +121 -0
- package/dist/src/lib/watch.js +169 -0
- package/dist/src/lib/watch.js.map +1 -0
- package/dist/src/lib/web-url.d.ts +40 -0
- package/dist/src/lib/web-url.js +76 -0
- package/dist/src/lib/web-url.js.map +1 -0
- package/dist/src/lib/workflow-apply.d.ts +69 -45
- package/dist/src/lib/workflow-apply.js +99 -72
- package/dist/src/lib/workflow-apply.js.map +1 -1
- package/dist/src/lib/workflow-codegen/generator.js +4 -4
- package/dist/src/lib/workflow-codegen/generator.js.map +1 -1
- package/dist/src/lib/workflow-codegen/invokerIR.d.ts +11 -0
- package/dist/src/lib/workflow-codegen/invokerIR.js +5 -0
- package/dist/src/lib/workflow-codegen/invokerIR.js.map +1 -1
- package/dist/src/lib/workflow-config-apply.d.ts +70 -0
- package/dist/src/lib/workflow-config-apply.js +137 -0
- package/dist/src/lib/workflow-config-apply.js.map +1 -0
- package/dist/src/lib/workflow-config-sidecar.d.ts +63 -0
- package/dist/src/lib/workflow-config-sidecar.js +96 -0
- package/dist/src/lib/workflow-config-sidecar.js.map +1 -0
- package/dist/src/lib/workflow-defaults.d.ts +29 -0
- package/dist/src/lib/workflow-defaults.js +41 -0
- package/dist/src/lib/workflow-defaults.js.map +1 -0
- package/dist/src/lib/workflow-include-preserve.d.ts +76 -0
- package/dist/src/lib/workflow-include-preserve.js +286 -0
- package/dist/src/lib/workflow-include-preserve.js.map +1 -0
- package/dist/src/lib/workflow-payload.d.ts +31 -20
- package/dist/src/lib/workflow-payload.js +100 -110
- package/dist/src/lib/workflow-payload.js.map +1 -1
- package/dist/src/lib/workflow-toml-validator.d.ts +37 -16
- package/dist/src/lib/workflow-toml-validator.js +141 -16
- package/dist/src/lib/workflow-toml-validator.js.map +1 -1
- package/dist/src/types/index.d.ts +78 -22
- package/dist/src/validators.d.ts +7 -7
- package/dist/src/validators.js +6 -6
- package/package.json +11 -6
- package/dist/src/commands/settings.d.ts +0 -15
- package/dist/src/commands/settings.js +0 -102
- package/dist/src/commands/settings.js.map +0 -1
|
@@ -1,23 +1,36 @@
|
|
|
1
|
-
import { readFileSync } from "fs";
|
|
2
|
-
import { parseConfigToml } from "../lib/config-toml.js";
|
|
3
1
|
import chalk from "chalk";
|
|
4
2
|
import { ApiClient } from "../lib/api-client.js";
|
|
5
3
|
import { resolveAppId } from "../lib/config.js";
|
|
6
|
-
import { success, error, info,
|
|
4
|
+
import { success, error, info, keyValue, result as printResult, formatTable, formatId, formatDate, formatStatus, formatDuration, json, divider, } from "../lib/output.js";
|
|
5
|
+
import { buildIntegrationEnvelope } from "../lib/log-inspection.js";
|
|
7
6
|
import { confirmPrompt } from "../lib/confirm-prompt.js";
|
|
7
|
+
import { parseStatusFilter } from "../lib/object-status-filter.js";
|
|
8
|
+
import { testCaseInputVariables } from "../lib/test-case-variables.js";
|
|
9
|
+
import { resolveIntegrationSelector } from "../lib/integration-selector.js";
|
|
8
10
|
export function registerIntegrationsCommands(program) {
|
|
9
11
|
const integrations = program
|
|
10
12
|
.command("integrations")
|
|
11
|
-
.description("Configure external HTTP API connections
|
|
13
|
+
.description("Configure external HTTP API connections")
|
|
12
14
|
.addHelpText("after", `
|
|
13
15
|
Examples:
|
|
14
16
|
$ primitive integrations list
|
|
15
|
-
$ primitive
|
|
17
|
+
$ primitive config create integration weather-api # scaffold integrations/weather-api.toml
|
|
18
|
+
$ primitive config push --only integration/weather-api # apply it
|
|
16
19
|
$ primitive integrations test 01HXY...
|
|
20
|
+
$ primitive integrations archive 01HXY... # retire it (soft delete)
|
|
17
21
|
|
|
18
|
-
Storing
|
|
22
|
+
Storing credentials for an integration:
|
|
19
23
|
$ primitive secrets set MY_API_KEY --value <value>
|
|
20
24
|
# then reference it in integration config via {{secrets.MY_API_KEY}}
|
|
25
|
+
`);
|
|
26
|
+
// Server-side configuration is authored in TOML and applied with `config push`
|
|
27
|
+
// (issue #2645) — this group no longer carries config-setting flags.
|
|
28
|
+
integrations.addHelpText("after", `
|
|
29
|
+
Configuration lives in integrations/<key>.toml:
|
|
30
|
+
$ primitive config fields integration # the file's keys, types and defaults
|
|
31
|
+
$ primitive config create integration <key> # scaffold one locally
|
|
32
|
+
$ primitive config set integration/<key> <path>=<value>
|
|
33
|
+
$ primitive config push --only integration/<key>
|
|
21
34
|
`);
|
|
22
35
|
// List integrations
|
|
23
36
|
integrations
|
|
@@ -25,14 +38,20 @@ Storing secrets for an integration (integration secrets are deprecated):
|
|
|
25
38
|
.description("List integrations")
|
|
26
39
|
.argument("[app-id]", "App ID (uses current app if not specified)")
|
|
27
40
|
.option("--app <app-id>", "App ID")
|
|
28
|
-
.option("--status <status>", "Filter by status:
|
|
41
|
+
.option("--status <status>", "Filter by status: active, inactive, archived")
|
|
29
42
|
.option("--json", "Output as JSON")
|
|
30
43
|
.action(async (appId, options) => {
|
|
31
44
|
const resolvedAppId = resolveAppId(appId, options);
|
|
32
45
|
const client = new ApiClient();
|
|
33
46
|
try {
|
|
47
|
+
// #2803 — `draft` is gone; refuse it by name rather than returning the
|
|
48
|
+
// empty list a canonical filter would produce.
|
|
34
49
|
const { items } = await client.listIntegrations(resolvedAppId, {
|
|
35
|
-
status: options.status,
|
|
50
|
+
status: parseStatusFilter("integrations", options.status, [
|
|
51
|
+
"active",
|
|
52
|
+
"inactive",
|
|
53
|
+
"archived",
|
|
54
|
+
]),
|
|
36
55
|
});
|
|
37
56
|
if (options.json) {
|
|
38
57
|
json(items);
|
|
@@ -55,75 +74,112 @@ Storing secrets for an integration (integration secrets are deprecated):
|
|
|
55
74
|
process.exit(1);
|
|
56
75
|
}
|
|
57
76
|
});
|
|
58
|
-
//
|
|
77
|
+
// #2645 criterion 9 / #2803 — the operational verbs. They are the only
|
|
78
|
+
// writers of `status`, which is server-owned and no longer a TOML key.
|
|
59
79
|
integrations
|
|
60
|
-
.command("
|
|
61
|
-
.description("
|
|
62
|
-
.argument("
|
|
63
|
-
.option("--app <app-id>", "App ID")
|
|
64
|
-
.option("--key <key>", "Integration key (unique identifier)")
|
|
65
|
-
.option("--name <name>", "Display name")
|
|
66
|
-
.option("--description <desc>", "Description")
|
|
67
|
-
.option("--base-url <url>", "Base URL for API requests")
|
|
68
|
-
.option("--allowed-methods <methods>", "Comma-separated list of allowed HTTP methods")
|
|
69
|
-
.option("--allowed-paths <paths>", "Comma-separated list of allowed paths")
|
|
70
|
-
.option("--timeout-ms <ms>", "Request timeout in milliseconds")
|
|
71
|
-
.option("--from-file <path>", "Load integration from TOML file")
|
|
80
|
+
.command("disable")
|
|
81
|
+
.description("Take integration out of service now. Operational, not configuration: `status` is server-owned and 'config push' can neither set nor clear it.")
|
|
82
|
+
.argument("<integration-id>", "Integration ID")
|
|
83
|
+
.option("--app <app-id>", "App ID (uses current app if not specified)")
|
|
72
84
|
.option("--json", "Output as JSON")
|
|
73
|
-
.action(async (
|
|
74
|
-
const resolvedAppId = resolveAppId(
|
|
85
|
+
.action(async (id, options) => {
|
|
86
|
+
const resolvedAppId = resolveAppId(undefined, options);
|
|
75
87
|
const client = new ApiClient();
|
|
76
|
-
|
|
77
|
-
|
|
88
|
+
try {
|
|
89
|
+
const result = await client.setIntegrationAvailability(resolvedAppId, id, "disable");
|
|
90
|
+
if (options.json) {
|
|
91
|
+
json(result);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
success(`Integration disabled: ${result.integrationKey || id}`);
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
error(err.message);
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
integrations
|
|
102
|
+
.command("enable")
|
|
103
|
+
.description("Put integration back in service.")
|
|
104
|
+
.argument("<integration-id>", "Integration ID")
|
|
105
|
+
.option("--app <app-id>", "App ID (uses current app if not specified)")
|
|
106
|
+
.option("--json", "Output as JSON")
|
|
107
|
+
.action(async (id, options) => {
|
|
108
|
+
const resolvedAppId = resolveAppId(undefined, options);
|
|
109
|
+
const client = new ApiClient();
|
|
110
|
+
try {
|
|
111
|
+
const result = await client.setIntegrationAvailability(resolvedAppId, id, "enable");
|
|
112
|
+
if (options.json) {
|
|
113
|
+
json(result);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
success(`Integration enabled: ${result.integrationKey || id}`);
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
error(err.message);
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
// #2907 — the delete flow's CLI spelling. Not a second writer of `status`:
|
|
124
|
+
// this is the plain `DELETE` the server has always answered by writing the
|
|
125
|
+
// tombstone, which until now only the admin API and the console could reach.
|
|
126
|
+
// Named `archive`, not `delete`, because a configuration noun gets no
|
|
127
|
+
// `delete` verb (docs/cli-design.md) and prune-by-push stays the only CLI
|
|
128
|
+
// hard-delete path.
|
|
129
|
+
integrations
|
|
130
|
+
.command("archive")
|
|
131
|
+
.description("Retire an integration (soft delete). Delete-lifecycle, not `disable`: the row is kept, goes on holding its key, and there is no un-archive.")
|
|
132
|
+
.argument("<integration-id>", "Integration ID")
|
|
133
|
+
.option("--app <app-id>", "App ID (uses current app if not specified)")
|
|
134
|
+
.option("-y, --yes", "Skip confirmation prompt")
|
|
135
|
+
.option("--json", "Output as JSON")
|
|
136
|
+
.addHelpText("after", `
|
|
137
|
+
What archiving an integration costs:
|
|
138
|
+
The proxy refuses it, and so does every workflow step that calls it.
|
|
139
|
+
The archived row still holds its integrationKey, so a push cannot re-create
|
|
140
|
+
one under the same key.
|
|
141
|
+
\`primitive integrations enable\` refuses an archived integration; there is no
|
|
142
|
+
un-archive.
|
|
143
|
+
|
|
144
|
+
Reclaiming the key means hard-deleting the row:
|
|
145
|
+
$ rm integrations/<key>.toml
|
|
146
|
+
$ primitive config push --prune # confirmed; hard-deletes the row
|
|
147
|
+
# or hard-delete it in the console, then re-add the file and push.
|
|
148
|
+
`)
|
|
149
|
+
.action(async (id, options) => {
|
|
150
|
+
const resolvedAppId = resolveAppId(undefined, options);
|
|
151
|
+
if (!options.yes) {
|
|
152
|
+
let confirmed;
|
|
78
153
|
try {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
payload = {
|
|
82
|
-
integrationKey: tomlData.integration?.key || tomlData.key,
|
|
83
|
-
displayName: tomlData.integration?.displayName || tomlData.displayName,
|
|
84
|
-
description: tomlData.integration?.description || tomlData.description,
|
|
85
|
-
requestConfig: tomlData.requestConfig || {},
|
|
86
|
-
timeoutMs: tomlData.integration?.timeoutMs || tomlData.timeoutMs,
|
|
87
|
-
};
|
|
154
|
+
confirmed = await confirmPrompt(`Archive integration ${id}? It is retired, goes on holding its key, ` +
|
|
155
|
+
`and there is no un-archive.`);
|
|
88
156
|
}
|
|
89
157
|
catch (err) {
|
|
90
|
-
error(
|
|
158
|
+
error(err.message);
|
|
91
159
|
process.exit(1);
|
|
92
160
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
error("Required: --key, --name, --base-url (or use --from-file)");
|
|
97
|
-
process.exit(1);
|
|
161
|
+
if (!confirmed) {
|
|
162
|
+
info("Cancelled.");
|
|
163
|
+
return;
|
|
98
164
|
}
|
|
99
|
-
payload = {
|
|
100
|
-
integrationKey: options.key,
|
|
101
|
-
displayName: options.name,
|
|
102
|
-
description: options.description,
|
|
103
|
-
requestConfig: {
|
|
104
|
-
baseUrl: options.baseUrl,
|
|
105
|
-
allowedMethods: options.allowedMethods?.split(",").map((s) => s.trim()),
|
|
106
|
-
allowedPaths: options.allowedPaths?.split(",").map((s) => s.trim()),
|
|
107
|
-
},
|
|
108
|
-
timeoutMs: options.timeoutMs ? parseInt(options.timeoutMs) : undefined,
|
|
109
|
-
};
|
|
110
165
|
}
|
|
166
|
+
const client = new ApiClient();
|
|
111
167
|
try {
|
|
112
|
-
|
|
168
|
+
// The soft delete: no `hard`, so the server writes the tombstone and
|
|
169
|
+
// keeps the row (and its canonical block mirror) in step.
|
|
170
|
+
const result = await client.deleteIntegration(resolvedAppId, id);
|
|
113
171
|
if (options.json) {
|
|
114
172
|
json(result);
|
|
115
173
|
return;
|
|
116
174
|
}
|
|
117
|
-
success(`Integration
|
|
118
|
-
|
|
119
|
-
keyValue("Key", result.integrationKey);
|
|
175
|
+
success(`Integration archived: ${id}`);
|
|
176
|
+
info("It is retired and still holds its key — reclaim the key by removing its file and running a confirmed `primitive config push --prune`.");
|
|
120
177
|
}
|
|
121
178
|
catch (err) {
|
|
122
179
|
error(err.message);
|
|
123
180
|
process.exit(1);
|
|
124
181
|
}
|
|
125
182
|
});
|
|
126
|
-
// Get integration
|
|
127
183
|
integrations
|
|
128
184
|
.command("get")
|
|
129
185
|
.description("Get integration details")
|
|
@@ -145,6 +201,12 @@ Storing secrets for an integration (integration secrets are deprecated):
|
|
|
145
201
|
printResult("Description", result.description);
|
|
146
202
|
printResult("Status", formatStatus(result.status));
|
|
147
203
|
printResult("Timeout", result.timeoutMs ? `${result.timeoutMs}ms` : "-");
|
|
204
|
+
// #2631: no rule means every non-admin call is denied. Print that in
|
|
205
|
+
// words — a blank or "-" would read as "unrestricted" and hide an
|
|
206
|
+
// integration that was never given a rule.
|
|
207
|
+
printResult("Access Rule", typeof result.accessRule === "string" && result.accessRule !== ""
|
|
208
|
+
? result.accessRule
|
|
209
|
+
: "(none — all non-admin calls are denied; set one to allow callers)");
|
|
148
210
|
if (result.requestConfig) {
|
|
149
211
|
divider();
|
|
150
212
|
info("Request Config:");
|
|
@@ -167,82 +229,6 @@ Storing secrets for an integration (integration secrets are deprecated):
|
|
|
167
229
|
process.exit(1);
|
|
168
230
|
}
|
|
169
231
|
});
|
|
170
|
-
// Update integration
|
|
171
|
-
integrations
|
|
172
|
-
.command("update")
|
|
173
|
-
.description("Update an integration")
|
|
174
|
-
.argument("<integration-id>", "Integration ID")
|
|
175
|
-
.option("--app <app-id>", "App ID (uses current app if not specified)")
|
|
176
|
-
.option("--name <name>", "Display name")
|
|
177
|
-
.option("--description <desc>", "Description")
|
|
178
|
-
.option("--status <status>", "Status: draft, active, archived")
|
|
179
|
-
.option("--timeout-ms <ms>", "Request timeout in milliseconds")
|
|
180
|
-
.option("--json", "Output as JSON")
|
|
181
|
-
.action(async (integrationId, options) => {
|
|
182
|
-
const resolvedAppId = resolveAppId(undefined, options);
|
|
183
|
-
const payload = {};
|
|
184
|
-
if (options.name)
|
|
185
|
-
payload.displayName = options.name;
|
|
186
|
-
if (options.description)
|
|
187
|
-
payload.description = options.description;
|
|
188
|
-
if (options.status)
|
|
189
|
-
payload.status = options.status;
|
|
190
|
-
if (options.timeoutMs)
|
|
191
|
-
payload.timeoutMs = parseInt(options.timeoutMs);
|
|
192
|
-
if (Object.keys(payload).length === 0) {
|
|
193
|
-
error("No update options specified.");
|
|
194
|
-
process.exit(1);
|
|
195
|
-
}
|
|
196
|
-
const client = new ApiClient();
|
|
197
|
-
try {
|
|
198
|
-
const result = await client.updateIntegration(resolvedAppId, integrationId, payload);
|
|
199
|
-
if (options.json) {
|
|
200
|
-
json(result);
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
success("Integration updated.");
|
|
204
|
-
}
|
|
205
|
-
catch (err) {
|
|
206
|
-
error(err.message);
|
|
207
|
-
process.exit(1);
|
|
208
|
-
}
|
|
209
|
-
});
|
|
210
|
-
// Delete integration
|
|
211
|
-
integrations
|
|
212
|
-
.command("delete")
|
|
213
|
-
.description("Delete or archive an integration")
|
|
214
|
-
.argument("<integration-id>", "Integration ID")
|
|
215
|
-
.option("--app <app-id>", "App ID (uses current app if not specified)")
|
|
216
|
-
.option("--hard", "Permanently delete instead of archive")
|
|
217
|
-
.option("-y, --yes", "Skip confirmation prompt")
|
|
218
|
-
.action(async (integrationId, options) => {
|
|
219
|
-
const resolvedAppId = resolveAppId(undefined, options);
|
|
220
|
-
if (!options.yes) {
|
|
221
|
-
const action = options.hard ? "permanently delete" : "archive";
|
|
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
|
-
}
|
|
230
|
-
if (!confirm) {
|
|
231
|
-
info("Cancelled.");
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
const client = new ApiClient();
|
|
236
|
-
try {
|
|
237
|
-
await client.deleteIntegration(resolvedAppId, integrationId, options.hard);
|
|
238
|
-
success(`Integration ${options.hard ? "deleted" : "archived"}.`);
|
|
239
|
-
}
|
|
240
|
-
catch (err) {
|
|
241
|
-
error(err.message);
|
|
242
|
-
process.exit(1);
|
|
243
|
-
}
|
|
244
|
-
});
|
|
245
|
-
// Test integration
|
|
246
232
|
integrations
|
|
247
233
|
.command("test")
|
|
248
234
|
.description("Test an integration by making a request")
|
|
@@ -313,6 +299,9 @@ Storing secrets for an integration (integration secrets are deprecated):
|
|
|
313
299
|
.option("--app <app-id>", "App ID (uses current app if not specified)")
|
|
314
300
|
.option("--limit <n>", "Number of logs to show", "20")
|
|
315
301
|
.option("--source <source>", "Filter by call source: user, admin, test, or workflow")
|
|
302
|
+
.option("--status <status>", "Filter by upstream HTTP status (e.g. 200, 500)")
|
|
303
|
+
.option("--from <iso>", "Only logs at or after this ISO timestamp")
|
|
304
|
+
.option("--to <iso>", "Only logs at or before this ISO timestamp")
|
|
316
305
|
.option("--json", "Output as JSON")
|
|
317
306
|
.action(async (integrationId, options) => {
|
|
318
307
|
const resolvedAppId = resolveAppId(undefined, options);
|
|
@@ -326,9 +315,12 @@ Storing secrets for an integration (integration secrets are deprecated):
|
|
|
326
315
|
const logs = await client.listIntegrationLogs(resolvedAppId, integrationId, {
|
|
327
316
|
limit: parseInt(options.limit),
|
|
328
317
|
...(options.source && { source: options.source }),
|
|
318
|
+
...(options.status && { status: options.status }),
|
|
319
|
+
...(options.from && { from: options.from }),
|
|
320
|
+
...(options.to && { to: options.to }),
|
|
329
321
|
});
|
|
330
322
|
if (options.json) {
|
|
331
|
-
json(logs);
|
|
323
|
+
json(buildIntegrationEnvelope(logs));
|
|
332
324
|
return;
|
|
333
325
|
}
|
|
334
326
|
if (!logs || logs.length === 0) {
|
|
@@ -352,115 +344,43 @@ Storing secrets for an integration (integration secrets are deprecated):
|
|
|
352
344
|
process.exit(1);
|
|
353
345
|
}
|
|
354
346
|
});
|
|
355
|
-
// Secrets subcommand
|
|
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)");
|
|
366
|
-
// List secrets
|
|
367
|
-
secrets
|
|
368
|
-
.command("list")
|
|
369
|
-
.description("List secrets for an integration")
|
|
370
|
-
.argument("<integration-id>", "Integration ID")
|
|
371
|
-
.option("--app <app-id>", "App ID (uses current app if not specified)")
|
|
372
|
-
.option("--json", "Output as JSON")
|
|
373
|
-
.action(async (integrationId, options) => {
|
|
374
|
-
const resolvedAppId = resolveAppId(undefined, options);
|
|
375
|
-
const client = new ApiClient();
|
|
376
|
-
try {
|
|
377
|
-
warn("Integration secrets are deprecated. Use App Secrets with {{secrets.KEY}} templates instead.");
|
|
378
|
-
const secretsList = await client.listIntegrationSecrets(resolvedAppId, integrationId);
|
|
379
|
-
if (options.json) {
|
|
380
|
-
json(secretsList);
|
|
381
|
-
return;
|
|
382
|
-
}
|
|
383
|
-
if (!secretsList || secretsList.length === 0) {
|
|
384
|
-
info("No secrets found.");
|
|
385
|
-
return;
|
|
386
|
-
}
|
|
387
|
-
console.log(formatTable(secretsList, [
|
|
388
|
-
{ header: "ID", key: "secretId", format: formatId },
|
|
389
|
-
{ header: "VERSION", key: "version" },
|
|
390
|
-
{ header: "SUMMARY", key: "secretSummary" },
|
|
391
|
-
{ header: "STATUS", key: "status", format: formatStatus },
|
|
392
|
-
{ header: "CREATED", key: "createdAt", format: formatDate },
|
|
393
|
-
]));
|
|
394
|
-
}
|
|
395
|
-
catch (err) {
|
|
396
|
-
error(err.message);
|
|
397
|
-
process.exit(1);
|
|
398
|
-
}
|
|
399
|
-
});
|
|
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.
|
|
406
|
-
secrets
|
|
407
|
-
.command("add", { hidden: true })
|
|
408
|
-
.description("Add a secret to an integration (deprecated — use `primitive secrets set`)")
|
|
409
|
-
.argument("<integration-id>", "Integration ID")
|
|
410
|
-
.option("--app <app-id>", "App ID (uses current app if not specified)")
|
|
411
|
-
.option("--data <json>", "Secret data as JSON (e.g., '{\"apiKey\":\"...\"}' )")
|
|
412
|
-
.option("--summary <text>", "Description of the secret")
|
|
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
347
|
// ============================================
|
|
440
348
|
// TESTS SUBCOMMAND
|
|
441
349
|
// ============================================
|
|
442
350
|
const tests = integrations
|
|
443
351
|
.command("tests")
|
|
444
|
-
.description("
|
|
352
|
+
.description("Run and inspect integration test cases")
|
|
445
353
|
.addHelpText("after", `
|
|
354
|
+
Test cases are authored in TOML at integrations/<key>.tests/<name>.toml and
|
|
355
|
+
applied with \`primitive config push\` (issue #2769); \`config fields integration\`
|
|
356
|
+
lists the fields. Deleting a case is removing its file and running
|
|
357
|
+
\`config push --prune\`.
|
|
358
|
+
|
|
359
|
+
Every verb here takes the integration's KEY or its ID (#3000). An identifier
|
|
360
|
+
that names no integration in the app fails; \`Total Tests: 0\` only ever means
|
|
361
|
+
the integration exists and has no cases.
|
|
362
|
+
|
|
446
363
|
Examples:
|
|
447
|
-
$ primitive integrations tests list <integration
|
|
448
|
-
$ primitive integrations tests
|
|
449
|
-
$ primitive integrations tests run <integration
|
|
450
|
-
$ primitive integrations tests run-all <integration-
|
|
451
|
-
$ primitive integrations tests run-all <integration-id> --test-cases 01ABC,01DEF,01GHI
|
|
364
|
+
$ primitive integrations tests list <integration>
|
|
365
|
+
$ primitive integrations tests run <integration> <test-case-id>
|
|
366
|
+
$ primitive integrations tests run-all <integration>
|
|
367
|
+
$ primitive integrations tests run-all <integration> --test-cases 01ABC,01DEF,01GHI
|
|
452
368
|
`);
|
|
453
369
|
// List test cases
|
|
454
370
|
tests
|
|
455
371
|
.command("list")
|
|
456
372
|
.description("List test cases for an integration")
|
|
457
|
-
.argument("<integration
|
|
373
|
+
.argument("<integration>", "Integration key or ID")
|
|
458
374
|
.option("--app <app-id>", "App ID (uses current app if not specified)")
|
|
459
375
|
.option("--json", "Output as JSON")
|
|
460
|
-
.action(async (
|
|
376
|
+
.action(async (integration, options) => {
|
|
461
377
|
const resolvedAppId = resolveAppId(undefined, options);
|
|
462
378
|
const client = new ApiClient();
|
|
463
379
|
try {
|
|
380
|
+
// #3000 — resolve BEFORE listing: the test-case endpoint answers an
|
|
381
|
+
// unknown block with an empty page, so an unresolved identifier would
|
|
382
|
+
// print "No test cases found." and exit 0.
|
|
383
|
+
const { integrationId } = await resolveIntegrationSelector(client, resolvedAppId, integration);
|
|
464
384
|
const { items } = await client.listTestCases(resolvedAppId, "integration", integrationId);
|
|
465
385
|
if (options.json) {
|
|
466
386
|
json(items);
|
|
@@ -482,91 +402,19 @@ Examples:
|
|
|
482
402
|
process.exit(1);
|
|
483
403
|
}
|
|
484
404
|
});
|
|
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")
|
|
500
|
-
.action(async (integrationId, options) => {
|
|
501
|
-
const resolvedAppId = resolveAppId(undefined, options);
|
|
502
|
-
if (!options.name || !options.input) {
|
|
503
|
-
error("Required: --name and --input");
|
|
504
|
-
process.exit(1);
|
|
505
|
-
}
|
|
506
|
-
let inputVariables;
|
|
507
|
-
try {
|
|
508
|
-
inputVariables = JSON.parse(options.input);
|
|
509
|
-
}
|
|
510
|
-
catch {
|
|
511
|
-
error("Invalid JSON in --input");
|
|
512
|
-
process.exit(1);
|
|
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
|
-
}
|
|
534
|
-
const client = new ApiClient();
|
|
535
|
-
try {
|
|
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,
|
|
545
|
-
});
|
|
546
|
-
if (options.json) {
|
|
547
|
-
json(result);
|
|
548
|
-
return;
|
|
549
|
-
}
|
|
550
|
-
success(`Test case created: ${result.name}`);
|
|
551
|
-
keyValue("Test Case ID", result.testCaseId);
|
|
552
|
-
}
|
|
553
|
-
catch (err) {
|
|
554
|
-
error(err.message);
|
|
555
|
-
process.exit(1);
|
|
556
|
-
}
|
|
557
|
-
});
|
|
558
405
|
// Get test case
|
|
559
406
|
tests
|
|
560
407
|
.command("get")
|
|
561
408
|
.description("Get test case details")
|
|
562
|
-
.argument("<integration
|
|
409
|
+
.argument("<integration>", "Integration key or ID")
|
|
563
410
|
.argument("<test-case-id>", "Test Case ID")
|
|
564
411
|
.option("--app <app-id>", "App ID (uses current app if not specified)")
|
|
565
412
|
.option("--json", "Output as JSON")
|
|
566
|
-
.action(async (
|
|
413
|
+
.action(async (integration, testCaseId, options) => {
|
|
567
414
|
const resolvedAppId = resolveAppId(undefined, options);
|
|
568
415
|
const client = new ApiClient();
|
|
569
416
|
try {
|
|
417
|
+
const { integrationId } = await resolveIntegrationSelector(client, resolvedAppId, integration);
|
|
570
418
|
const result = await client.getTestCase(resolvedAppId, "integration", integrationId, testCaseId);
|
|
571
419
|
if (options.json) {
|
|
572
420
|
json(result);
|
|
@@ -578,13 +426,7 @@ Examples:
|
|
|
578
426
|
printResult("Created", formatDate(result.createdAt));
|
|
579
427
|
divider();
|
|
580
428
|
info("Input Variables:");
|
|
581
|
-
|
|
582
|
-
const vars = JSON.parse(result.inputVariables || "{}");
|
|
583
|
-
console.log(JSON.stringify(vars, null, 2));
|
|
584
|
-
}
|
|
585
|
-
catch {
|
|
586
|
-
console.log(result.inputVariables || "{}");
|
|
587
|
-
}
|
|
429
|
+
console.log(JSON.stringify(testCaseInputVariables(result.inputVariables), null, 2));
|
|
588
430
|
if (result.expectedOutputPattern) {
|
|
589
431
|
divider();
|
|
590
432
|
printResult("Expected Pattern", result.expectedOutputPattern);
|
|
@@ -620,143 +462,27 @@ Examples:
|
|
|
620
462
|
process.exit(1);
|
|
621
463
|
}
|
|
622
464
|
});
|
|
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
465
|
// Run a single test case
|
|
739
466
|
tests
|
|
740
467
|
.command("run")
|
|
741
468
|
.description("Run a single test case")
|
|
742
|
-
.argument("<integration
|
|
469
|
+
.argument("<integration>", "Integration key or ID")
|
|
743
470
|
.argument("<test-case-id>", "Test Case ID")
|
|
744
471
|
.option("--app <app-id>", "App ID (uses current app if not specified)")
|
|
745
472
|
.option("--config <config-id>", "Override config ID")
|
|
746
473
|
.option("--json", "Output as JSON")
|
|
747
|
-
.action(async (
|
|
474
|
+
.action(async (integration, testCaseId, options) => {
|
|
748
475
|
const resolvedAppId = resolveAppId(undefined, options);
|
|
749
476
|
const client = new ApiClient();
|
|
750
477
|
try {
|
|
478
|
+
const { integrationId } = await resolveIntegrationSelector(client, resolvedAppId, integration);
|
|
751
479
|
// Get test case to retrieve its variables
|
|
752
480
|
const testCase = await client.getTestCase(resolvedAppId, "integration", integrationId, testCaseId);
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
// Use empty object if parsing fails
|
|
759
|
-
}
|
|
481
|
+
// #2968 — the case's variables ARE the request the run issues, so a
|
|
482
|
+
// dropped `inputVariables` here is the config's bare defaults instead
|
|
483
|
+
// of the case's method, path and body. The server hands them back
|
|
484
|
+
// parsed; older servers hand back JSON text.
|
|
485
|
+
const variables = testCaseInputVariables(testCase.inputVariables);
|
|
760
486
|
const result = await client.executeBlockTest(resolvedAppId, "integration", integrationId, {
|
|
761
487
|
variables,
|
|
762
488
|
testCaseId,
|
|
@@ -813,15 +539,19 @@ Examples:
|
|
|
813
539
|
tests
|
|
814
540
|
.command("run-all")
|
|
815
541
|
.description("Run all test cases for an integration, or a specific subset")
|
|
816
|
-
.argument("<integration
|
|
542
|
+
.argument("<integration>", "Integration key or ID")
|
|
817
543
|
.option("--app <app-id>", "App ID (uses current app if not specified)")
|
|
818
544
|
.option("--config <config-id>", "Override config ID for all tests")
|
|
819
545
|
.option("--test-cases <ids>", "Comma-separated list of test case IDs to run (runs only these)")
|
|
820
546
|
.option("--json", "Output as JSON")
|
|
821
|
-
.action(async (
|
|
547
|
+
.action(async (integration, options) => {
|
|
822
548
|
const resolvedAppId = resolveAppId(undefined, options);
|
|
823
549
|
const client = new ApiClient();
|
|
824
550
|
try {
|
|
551
|
+
// #3000 — resolve BEFORE announcing the run: an unresolved identifier
|
|
552
|
+
// came back as `Total Tests: 0 / Failed: 0` and exit 0, which reads as
|
|
553
|
+
// a pass in CI.
|
|
554
|
+
const { integrationId } = await resolveIntegrationSelector(client, resolvedAppId, integration);
|
|
825
555
|
// Parse test case IDs if provided
|
|
826
556
|
let testCaseIds;
|
|
827
557
|
if (options.testCases) {
|
|
@@ -879,15 +609,16 @@ Examples:
|
|
|
879
609
|
tests
|
|
880
610
|
.command("runs")
|
|
881
611
|
.description("List recent test runs for an integration")
|
|
882
|
-
.argument("<integration
|
|
612
|
+
.argument("<integration>", "Integration key or ID")
|
|
883
613
|
.option("--app <app-id>", "App ID (uses current app if not specified)")
|
|
884
614
|
.option("--limit <n>", "Maximum number of runs to show", "20")
|
|
885
615
|
.option("--group <group>", "Filter by comparison group")
|
|
886
616
|
.option("--json", "Output as JSON")
|
|
887
|
-
.action(async (
|
|
617
|
+
.action(async (integration, options) => {
|
|
888
618
|
const resolvedAppId = resolveAppId(undefined, options);
|
|
889
619
|
const client = new ApiClient();
|
|
890
620
|
try {
|
|
621
|
+
const { integrationId } = await resolveIntegrationSelector(client, resolvedAppId, integration);
|
|
891
622
|
const { items } = await client.listTestRuns(resolvedAppId, "integration", integrationId, {
|
|
892
623
|
limit: parseInt(options.limit),
|
|
893
624
|
comparisonGroup: options.group,
|