primitive-admin 1.0.53 → 1.0.55

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.
Files changed (173) hide show
  1. package/README.md +38 -10
  2. package/dist/bin/primitive.js +8 -0
  3. package/dist/bin/primitive.js.map +1 -1
  4. package/dist/src/commands/apps.js +54 -2
  5. package/dist/src/commands/apps.js.map +1 -1
  6. package/dist/src/commands/auth.js +16 -6
  7. package/dist/src/commands/auth.js.map +1 -1
  8. package/dist/src/commands/blob-buckets.js +84 -7
  9. package/dist/src/commands/blob-buckets.js.map +1 -1
  10. package/dist/src/commands/catalog.js +3 -3
  11. package/dist/src/commands/catalog.js.map +1 -1
  12. package/dist/src/commands/collections.js +12 -9
  13. package/dist/src/commands/collections.js.map +1 -1
  14. package/dist/src/commands/databases.js +220 -82
  15. package/dist/src/commands/databases.js.map +1 -1
  16. package/dist/src/commands/documents.js +77 -0
  17. package/dist/src/commands/documents.js.map +1 -1
  18. package/dist/src/commands/group-type-configs.js +1 -9
  19. package/dist/src/commands/group-type-configs.js.map +1 -1
  20. package/dist/src/commands/groups.js +12 -6
  21. package/dist/src/commands/groups.js.map +1 -1
  22. package/dist/src/commands/guides.d.ts +161 -0
  23. package/dist/src/commands/guides.js +502 -84
  24. package/dist/src/commands/guides.js.map +1 -1
  25. package/dist/src/commands/integrations.js +2 -2
  26. package/dist/src/commands/integrations.js.map +1 -1
  27. package/dist/src/commands/metadata.d.ts +2 -0
  28. package/dist/src/commands/metadata.js +344 -0
  29. package/dist/src/commands/metadata.js.map +1 -0
  30. package/dist/src/commands/prompts.js +2 -2
  31. package/dist/src/commands/prompts.js.map +1 -1
  32. package/dist/src/commands/rule-sets.d.ts +1 -0
  33. package/dist/src/commands/rule-sets.js +25 -3
  34. package/dist/src/commands/rule-sets.js.map +1 -1
  35. package/dist/src/commands/scripts.d.ts +2 -0
  36. package/dist/src/commands/scripts.js +667 -0
  37. package/dist/src/commands/scripts.js.map +1 -0
  38. package/dist/src/commands/settings.d.ts +15 -0
  39. package/dist/src/commands/settings.js +102 -0
  40. package/dist/src/commands/settings.js.map +1 -0
  41. package/dist/src/commands/sync-app-settings.d.ts +105 -0
  42. package/dist/src/commands/sync-app-settings.js +339 -0
  43. package/dist/src/commands/sync-app-settings.js.map +1 -0
  44. package/dist/src/commands/sync.d.ts +204 -9
  45. package/dist/src/commands/sync.js +1706 -371
  46. package/dist/src/commands/sync.js.map +1 -1
  47. package/dist/src/commands/vars.d.ts +8 -0
  48. package/dist/src/commands/vars.js +110 -0
  49. package/dist/src/commands/vars.js.map +1 -0
  50. package/dist/src/commands/webhooks.js +17 -3
  51. package/dist/src/commands/webhooks.js.map +1 -1
  52. package/dist/src/commands/workflows.d.ts +30 -0
  53. package/dist/src/commands/workflows.js +667 -59
  54. package/dist/src/commands/workflows.js.map +1 -1
  55. package/dist/src/lib/api-client.d.ts +122 -18
  56. package/dist/src/lib/api-client.js +131 -2
  57. package/dist/src/lib/api-client.js.map +1 -1
  58. package/dist/src/lib/app-settings-descriptor.d.ts +108 -0
  59. package/dist/src/lib/app-settings-descriptor.js +250 -0
  60. package/dist/src/lib/app-settings-descriptor.js.map +1 -0
  61. package/dist/src/lib/batch.d.ts +26 -0
  62. package/dist/src/lib/batch.js +32 -0
  63. package/dist/src/lib/batch.js.map +1 -0
  64. package/dist/src/lib/block-layout.d.ts +160 -0
  65. package/dist/src/lib/block-layout.js +451 -0
  66. package/dist/src/lib/block-layout.js.map +1 -0
  67. package/dist/src/lib/codegen-shared/generatedFiles.d.ts +76 -0
  68. package/dist/src/lib/codegen-shared/generatedFiles.js +144 -0
  69. package/dist/src/lib/codegen-shared/generatedFiles.js.map +1 -0
  70. package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.d.ts +68 -0
  71. package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.js +168 -0
  72. package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.js.map +1 -0
  73. package/dist/src/lib/config-toml.d.ts +10 -0
  74. package/dist/src/lib/config-toml.js +42 -0
  75. package/dist/src/lib/config-toml.js.map +1 -0
  76. package/dist/src/lib/config.d.ts +16 -0
  77. package/dist/src/lib/config.js +41 -0
  78. package/dist/src/lib/config.js.map +1 -1
  79. package/dist/src/lib/constants.d.ts +9 -0
  80. package/dist/src/lib/constants.js +9 -0
  81. package/dist/src/lib/constants.js.map +1 -1
  82. package/dist/src/lib/csv.d.ts +1 -2
  83. package/dist/src/lib/csv.js +3 -8
  84. package/dist/src/lib/csv.js.map +1 -1
  85. package/dist/src/lib/db-codegen/dbGenerator.d.ts +21 -65
  86. package/dist/src/lib/db-codegen/dbGenerator.js +117 -321
  87. package/dist/src/lib/db-codegen/dbGenerator.js.map +1 -1
  88. package/dist/src/lib/db-codegen/dbNaming.d.ts +37 -0
  89. package/dist/src/lib/db-codegen/dbNaming.js +70 -0
  90. package/dist/src/lib/db-codegen/dbNaming.js.map +1 -1
  91. package/dist/src/lib/db-codegen/dbTemplates.d.ts +165 -5
  92. package/dist/src/lib/db-codegen/dbTemplates.js +292 -16
  93. package/dist/src/lib/db-codegen/dbTemplates.js.map +1 -1
  94. package/dist/src/lib/db-codegen/dbTsTypes.d.ts +7 -3
  95. package/dist/src/lib/db-codegen/dbTsTypes.js +23 -1
  96. package/dist/src/lib/db-codegen/dbTsTypes.js.map +1 -1
  97. package/dist/src/lib/db-codegen/dbTypeIR.d.ts +146 -0
  98. package/dist/src/lib/db-codegen/dbTypeIR.js +517 -0
  99. package/dist/src/lib/db-codegen/dbTypeIR.js.map +1 -0
  100. package/dist/src/lib/db-codegen/generated-operation-def-descriptor.d.ts +101 -0
  101. package/dist/src/lib/db-codegen/generated-operation-def-descriptor.js +200 -0
  102. package/dist/src/lib/db-codegen/generated-operation-def-descriptor.js.map +1 -0
  103. package/dist/src/lib/generated-allowlist.js +43 -0
  104. package/dist/src/lib/generated-allowlist.js.map +1 -1
  105. package/dist/src/lib/init-config.js +2 -2
  106. package/dist/src/lib/init-config.js.map +1 -1
  107. package/dist/src/lib/output.d.ts +25 -5
  108. package/dist/src/lib/output.js +32 -4
  109. package/dist/src/lib/output.js.map +1 -1
  110. package/dist/src/lib/query-operators.d.ts +43 -0
  111. package/dist/src/lib/query-operators.js +80 -0
  112. package/dist/src/lib/query-operators.js.map +1 -0
  113. package/dist/src/lib/swift-codegen/dbGenerator.d.ts +68 -0
  114. package/dist/src/lib/swift-codegen/dbGenerator.js +380 -0
  115. package/dist/src/lib/swift-codegen/dbGenerator.js.map +1 -0
  116. package/dist/src/lib/swift-codegen/dbSwiftTypes.d.ts +42 -0
  117. package/dist/src/lib/swift-codegen/dbSwiftTypes.js +100 -0
  118. package/dist/src/lib/swift-codegen/dbSwiftTypes.js.map +1 -0
  119. package/dist/src/lib/swift-codegen/generator.d.ts +84 -0
  120. package/dist/src/lib/swift-codegen/generator.js +178 -0
  121. package/dist/src/lib/swift-codegen/generator.js.map +1 -0
  122. package/dist/src/lib/swift-codegen/schemaToSwift.d.ts +72 -0
  123. package/dist/src/lib/swift-codegen/schemaToSwift.js +644 -0
  124. package/dist/src/lib/swift-codegen/schemaToSwift.js.map +1 -0
  125. package/dist/src/lib/swift-codegen/swiftNaming.d.ts +85 -0
  126. package/dist/src/lib/swift-codegen/swiftNaming.js +198 -0
  127. package/dist/src/lib/swift-codegen/swiftNaming.js.map +1 -0
  128. package/dist/src/lib/sync-resource-types.d.ts +225 -0
  129. package/dist/src/lib/sync-resource-types.js +394 -0
  130. package/dist/src/lib/sync-resource-types.js.map +1 -0
  131. package/dist/src/lib/toml-database-config.d.ts +10 -19
  132. package/dist/src/lib/toml-database-config.js +28 -59
  133. package/dist/src/lib/toml-database-config.js.map +1 -1
  134. package/dist/src/lib/toml-metadata-config.d.ts +147 -0
  135. package/dist/src/lib/toml-metadata-config.js +463 -0
  136. package/dist/src/lib/toml-metadata-config.js.map +1 -0
  137. package/dist/src/lib/toml-native-form.d.ts +46 -0
  138. package/dist/src/lib/toml-native-form.js +78 -0
  139. package/dist/src/lib/toml-native-form.js.map +1 -0
  140. package/dist/src/lib/toml-params-validator.d.ts +34 -0
  141. package/dist/src/lib/toml-params-validator.js +118 -3
  142. package/dist/src/lib/toml-params-validator.js.map +1 -1
  143. package/dist/src/lib/workflow-apply.d.ts +86 -0
  144. package/dist/src/lib/workflow-apply.js +127 -0
  145. package/dist/src/lib/workflow-apply.js.map +1 -0
  146. package/dist/src/lib/workflow-codegen/generated-schema-descriptor.d.ts +129 -0
  147. package/dist/src/lib/workflow-codegen/generated-schema-descriptor.js +269 -0
  148. package/dist/src/lib/workflow-codegen/generated-schema-descriptor.js.map +1 -0
  149. package/dist/src/lib/workflow-codegen/generator.d.ts +87 -0
  150. package/dist/src/lib/workflow-codegen/generator.js +290 -0
  151. package/dist/src/lib/workflow-codegen/generator.js.map +1 -0
  152. package/dist/src/lib/workflow-codegen/invokerIR.d.ts +85 -0
  153. package/dist/src/lib/workflow-codegen/invokerIR.js +71 -0
  154. package/dist/src/lib/workflow-codegen/invokerIR.js.map +1 -0
  155. package/dist/src/lib/workflow-codegen/naming.d.ts +33 -0
  156. package/dist/src/lib/workflow-codegen/naming.js +81 -0
  157. package/dist/src/lib/workflow-codegen/naming.js.map +1 -0
  158. package/dist/src/lib/workflow-codegen/schemaToTs.d.ts +64 -0
  159. package/dist/src/lib/workflow-codegen/schemaToTs.js +282 -0
  160. package/dist/src/lib/workflow-codegen/schemaToTs.js.map +1 -0
  161. package/dist/src/lib/workflow-fragments.js +3 -3
  162. package/dist/src/lib/workflow-fragments.js.map +1 -1
  163. package/dist/src/lib/workflow-payload.d.ts +68 -0
  164. package/dist/src/lib/workflow-payload.js +150 -0
  165. package/dist/src/lib/workflow-payload.js.map +1 -0
  166. package/dist/src/lib/workflow-toml-validator.d.ts +1 -1
  167. package/dist/src/lib/workflow-toml-validator.js +37 -1
  168. package/dist/src/lib/workflow-toml-validator.js.map +1 -1
  169. package/dist/src/types/index.d.ts +11 -0
  170. package/dist/src/validators.d.ts +4 -3
  171. package/dist/src/validators.js +6 -5
  172. package/dist/src/validators.js.map +1 -1
  173. package/package.json +7 -3
@@ -0,0 +1,667 @@
1
+ import { ApiClient } from "../lib/api-client.js";
2
+ import { resolveAppId } from "../lib/config.js";
3
+ import chalk from "chalk";
4
+ import { success, error, info, keyValue, result as printResult, formatTable, formatId, formatDate, formatDuration, json, divider, } from "../lib/output.js";
5
+ import { confirmPrompt } from "../lib/confirm-prompt.js";
6
+ /**
7
+ * Resolve a user-supplied script reference (either a `scriptId` or the
8
+ * script's `name`) to its `scriptId`. Scripts are named handles, so the CLI
9
+ * accepts a name for convenience the way `sync` refers to them by name; the
10
+ * block-testing API keys on `scriptId` (the block id). If a raw id matches an
11
+ * existing script it is used directly; otherwise we list the app's scripts and
12
+ * match on name (case-sensitive, then case-insensitive as a fallback).
13
+ */
14
+ async function resolveScriptId(client, appId, ref) {
15
+ const { items } = await client.listScripts(appId);
16
+ const byId = items.find((s) => s.scriptId === ref);
17
+ if (byId)
18
+ return byId.scriptId;
19
+ const byName = items.find((s) => s.name === ref);
20
+ if (byName)
21
+ return byName.scriptId;
22
+ const byNameCi = items.find((s) => String(s.name).toLowerCase() === ref.toLowerCase());
23
+ if (byNameCi)
24
+ return byNameCi.scriptId;
25
+ throw new Error(`Script not found: ${ref}`);
26
+ }
27
+ export function registerScriptsCommands(program) {
28
+ const scripts = program
29
+ .command("scripts")
30
+ .description("Inspect Rhai scripts (transforms), their versions, and tests")
31
+ .addHelpText("after", `
32
+ Examples:
33
+ $ primitive scripts list
34
+ $ primitive scripts get my-transform
35
+ $ primitive scripts configs list my-transform
36
+ $ primitive scripts tests list my-transform
37
+ $ primitive scripts tests create my-transform --name "Basic" --vars '{"x":1}'
38
+ $ primitive scripts tests run my-transform <test-case-id>
39
+ $ primitive scripts tests run-all my-transform
40
+
41
+ Scripts (transforms) are authored via 'sync' from transforms/<name>.rhai
42
+ files. Test cases live under transforms/<name>.tests/*.toml and round-trip
43
+ through 'sync pull' / 'sync push'. Both the script reference and the test
44
+ commands accept either a script id or a script name.
45
+ `);
46
+ // List scripts
47
+ scripts
48
+ .command("list")
49
+ .description("List scripts (transforms) for an app")
50
+ .argument("[app-id]", "App ID (uses current app if not specified)")
51
+ .option("--app <app-id>", "App ID (uses current app if not specified)")
52
+ .option("--json", "Output as JSON")
53
+ .action(async (appId, options) => {
54
+ const resolvedAppId = resolveAppId(appId, options);
55
+ const client = new ApiClient();
56
+ try {
57
+ const { items } = await client.listScripts(resolvedAppId);
58
+ if (options.json) {
59
+ json(items);
60
+ return;
61
+ }
62
+ if (!items || items.length === 0) {
63
+ info("No scripts found.");
64
+ return;
65
+ }
66
+ console.log(formatTable(items, [
67
+ { header: "ID", key: "scriptId", format: formatId },
68
+ { header: "NAME", key: "name" },
69
+ {
70
+ header: "ACTIVE CONFIG",
71
+ key: "activeConfigId",
72
+ format: (v) => (v ? formatId(v) : "-"),
73
+ },
74
+ { header: "MODIFIED", key: "modifiedAt", format: formatDate },
75
+ ]));
76
+ }
77
+ catch (err) {
78
+ error(err.message);
79
+ process.exit(1);
80
+ }
81
+ });
82
+ // Get a script
83
+ scripts
84
+ .command("get")
85
+ .description("Get a script's details and versions")
86
+ .argument("<script>", "Script ID or name")
87
+ .option("--app <app-id>", "App ID (uses current app if not specified)")
88
+ .option("--json", "Output as JSON")
89
+ .action(async (script, options) => {
90
+ const resolvedAppId = resolveAppId(undefined, options);
91
+ const client = new ApiClient();
92
+ try {
93
+ const scriptId = await resolveScriptId(client, resolvedAppId, script);
94
+ const result = await client.getScript(resolvedAppId, scriptId);
95
+ if (options.json) {
96
+ json(result);
97
+ return;
98
+ }
99
+ printResult("Script ID", result.scriptId);
100
+ printResult("Name", result.name);
101
+ printResult("Description", result.description || "-");
102
+ printResult("Active Config", result.activeConfigId || "-");
103
+ printResult("Created", formatDate(result.createdAt));
104
+ const configs = Array.isArray(result.configs) ? result.configs : [];
105
+ if (configs.length > 0) {
106
+ divider();
107
+ info("Versions:");
108
+ console.log(formatTable(configs, [
109
+ { header: "CONFIG ID", key: "configId", format: formatId },
110
+ { header: "NAME", key: "configName" },
111
+ { header: "STATUS", key: "status" },
112
+ {
113
+ header: "ACTIVE",
114
+ key: "configId",
115
+ format: (v) => v === result.activeConfigId ? chalk.green("Yes") : "-",
116
+ },
117
+ { header: "CONTENT HASH", key: "contentHash", format: formatId },
118
+ ]));
119
+ }
120
+ }
121
+ catch (err) {
122
+ error(err.message);
123
+ process.exit(1);
124
+ }
125
+ });
126
+ // ============================================
127
+ // CONFIGS (VERSIONS) SUBCOMMAND
128
+ // ============================================
129
+ const configs = scripts
130
+ .command("configs")
131
+ .description("Inspect a script's versioned configs");
132
+ configs
133
+ .command("list")
134
+ .description("List a script's versions (configs)")
135
+ .argument("<script>", "Script ID or name")
136
+ .option("--app <app-id>", "App ID (uses current app if not specified)")
137
+ .option("--json", "Output as JSON")
138
+ .action(async (script, options) => {
139
+ const resolvedAppId = resolveAppId(undefined, options);
140
+ const client = new ApiClient();
141
+ try {
142
+ const scriptId = await resolveScriptId(client, resolvedAppId, script);
143
+ const header = await client.getScript(resolvedAppId, scriptId);
144
+ const { items } = await client.listScriptConfigs(resolvedAppId, scriptId);
145
+ if (options.json) {
146
+ json(items);
147
+ return;
148
+ }
149
+ if (!items || items.length === 0) {
150
+ info("No versions found.");
151
+ return;
152
+ }
153
+ console.log(formatTable(items, [
154
+ { header: "CONFIG ID", key: "configId", format: formatId },
155
+ { header: "NAME", key: "configName" },
156
+ { header: "STATUS", key: "status" },
157
+ {
158
+ header: "ACTIVE",
159
+ key: "configId",
160
+ format: (v) => v === header.activeConfigId ? chalk.green("Yes") : "-",
161
+ },
162
+ { header: "CONTENT HASH", key: "contentHash", format: formatId },
163
+ { header: "CREATED", key: "createdAt", format: formatDate },
164
+ ]));
165
+ }
166
+ catch (err) {
167
+ error(err.message);
168
+ process.exit(1);
169
+ }
170
+ });
171
+ // ============================================
172
+ // TESTS SUBCOMMAND — script block testing (blockType "script")
173
+ // ============================================
174
+ const tests = scripts
175
+ .command("tests")
176
+ .description("Manage and run script test cases")
177
+ .addHelpText("after", `
178
+ Test modes:
179
+ Active version — omit --config; runs against the script's active config.
180
+ Pinned version — pass --config <config-id> to run a specific version.
181
+
182
+ Examples:
183
+ $ primitive scripts tests list my-transform
184
+ $ primitive scripts tests create my-transform --name "Basic" --vars '{"x":1}'
185
+ $ primitive scripts tests run my-transform <test-case-id>
186
+ $ primitive scripts tests run my-transform <test-case-id> --config <config-id>
187
+ $ primitive scripts tests run-all my-transform
188
+ $ primitive scripts tests runs my-transform
189
+ `);
190
+ // List test cases
191
+ tests
192
+ .command("list")
193
+ .description("List test cases for a script")
194
+ .argument("<script>", "Script ID or name")
195
+ .option("--app <app-id>", "App ID (uses current app if not specified)")
196
+ .option("--json", "Output as JSON")
197
+ .action(async (script, options) => {
198
+ const resolvedAppId = resolveAppId(undefined, options);
199
+ const client = new ApiClient();
200
+ try {
201
+ const scriptId = await resolveScriptId(client, resolvedAppId, script);
202
+ const { items } = await client.listTestCases(resolvedAppId, "script", scriptId);
203
+ if (options.json) {
204
+ json(items);
205
+ return;
206
+ }
207
+ if (!items || items.length === 0) {
208
+ info("No test cases found.");
209
+ return;
210
+ }
211
+ console.log(formatTable(items, [
212
+ { header: "ID", key: "testCaseId", format: formatId },
213
+ { header: "NAME", key: "name" },
214
+ {
215
+ header: "CONFIG",
216
+ key: "configId",
217
+ format: (v) => (v ? formatId(v) : "-"),
218
+ },
219
+ { header: "CREATED", key: "createdAt", format: formatDate },
220
+ ]));
221
+ }
222
+ catch (err) {
223
+ error(err.message);
224
+ process.exit(1);
225
+ }
226
+ });
227
+ // Create test case
228
+ tests
229
+ .command("create")
230
+ .description("Create a test case for a script")
231
+ .argument("<script>", "Script ID or name")
232
+ .option("--app <app-id>", "App ID (uses current app if not specified)")
233
+ .option("--name <name>", "Test case name (required)")
234
+ .option("--vars <json>", "Input variables as JSON (required)")
235
+ .option("--pattern <regex>", "Expected output pattern (regex)")
236
+ .option("--contains <json>", "Expected strings to contain (JSON array)")
237
+ .option("--json-subset <json>", "Expected JSON subset to match")
238
+ .option("--config <config-id>", "Config ID to pin this test to a version")
239
+ .option("--json", "Output as JSON")
240
+ .action(async (script, options) => {
241
+ const resolvedAppId = resolveAppId(undefined, options);
242
+ if (!options.name || !options.vars) {
243
+ error("Required: --name and --vars");
244
+ process.exit(1);
245
+ }
246
+ let inputVariables;
247
+ try {
248
+ inputVariables = JSON.parse(options.vars);
249
+ }
250
+ catch {
251
+ error("Invalid JSON in --vars");
252
+ process.exit(1);
253
+ }
254
+ let expectedOutputContains;
255
+ if (options.contains) {
256
+ try {
257
+ expectedOutputContains = JSON.parse(options.contains);
258
+ }
259
+ catch {
260
+ error("Invalid JSON in --contains");
261
+ process.exit(1);
262
+ }
263
+ }
264
+ let expectedJsonSubset;
265
+ if (options.jsonSubset) {
266
+ try {
267
+ expectedJsonSubset = JSON.parse(options.jsonSubset);
268
+ }
269
+ catch {
270
+ error("Invalid JSON in --json-subset");
271
+ process.exit(1);
272
+ }
273
+ }
274
+ const client = new ApiClient();
275
+ try {
276
+ const scriptId = await resolveScriptId(client, resolvedAppId, script);
277
+ const result = await client.createTestCase(resolvedAppId, "script", scriptId, {
278
+ name: options.name,
279
+ inputVariables,
280
+ expectedOutputPattern: options.pattern,
281
+ expectedOutputContains,
282
+ expectedJsonSubset,
283
+ configId: options.config,
284
+ });
285
+ if (options.json) {
286
+ json(result);
287
+ return;
288
+ }
289
+ success(`Test case created: ${result.name}`);
290
+ keyValue("Test Case ID", result.testCaseId);
291
+ }
292
+ catch (err) {
293
+ error(err.message);
294
+ process.exit(1);
295
+ }
296
+ });
297
+ // Get test case
298
+ tests
299
+ .command("get")
300
+ .description("Get test case details")
301
+ .argument("<script>", "Script ID or name")
302
+ .argument("<test-case-id>", "Test Case ID")
303
+ .option("--app <app-id>", "App ID (uses current app if not specified)")
304
+ .option("--json", "Output as JSON")
305
+ .action(async (script, testCaseId, options) => {
306
+ const resolvedAppId = resolveAppId(undefined, options);
307
+ const client = new ApiClient();
308
+ try {
309
+ const scriptId = await resolveScriptId(client, resolvedAppId, script);
310
+ const result = await client.getTestCase(resolvedAppId, "script", scriptId, testCaseId);
311
+ if (options.json) {
312
+ json(result);
313
+ return;
314
+ }
315
+ printResult("Test Case ID", result.testCaseId);
316
+ printResult("Name", result.name);
317
+ printResult("Config ID", result.configId || "-");
318
+ printResult("Created", formatDate(result.createdAt));
319
+ divider();
320
+ info("Input Variables:");
321
+ try {
322
+ console.log(JSON.stringify(JSON.parse(result.inputVariables || "{}"), null, 2));
323
+ }
324
+ catch {
325
+ console.log(result.inputVariables || "{}");
326
+ }
327
+ if (result.expectedOutputPattern) {
328
+ divider();
329
+ printResult("Expected Pattern", result.expectedOutputPattern);
330
+ }
331
+ if (result.expectedOutputContains) {
332
+ divider();
333
+ info("Expected Contains:");
334
+ try {
335
+ console.log(JSON.stringify(JSON.parse(result.expectedOutputContains), null, 2));
336
+ }
337
+ catch {
338
+ console.log(result.expectedOutputContains);
339
+ }
340
+ }
341
+ if (result.expectedJsonSubset) {
342
+ divider();
343
+ info("Expected JSON Subset:");
344
+ try {
345
+ console.log(JSON.stringify(JSON.parse(result.expectedJsonSubset), null, 2));
346
+ }
347
+ catch {
348
+ console.log(result.expectedJsonSubset);
349
+ }
350
+ }
351
+ }
352
+ catch (err) {
353
+ error(err.message);
354
+ process.exit(1);
355
+ }
356
+ });
357
+ // Update test case
358
+ tests
359
+ .command("update")
360
+ .description("Update a test case")
361
+ .argument("<script>", "Script ID or name")
362
+ .argument("<test-case-id>", "Test Case ID")
363
+ .option("--app <app-id>", "App ID (uses current app if not specified)")
364
+ .option("--name <name>", "Test case name")
365
+ .option("--vars <json>", "Input variables as JSON")
366
+ .option("--pattern <regex>", "Expected output pattern (regex)")
367
+ .option("--contains <json>", "Expected strings to contain (JSON array)")
368
+ .option("--json-subset <json>", "Expected JSON subset to match")
369
+ .option("--config <config-id>", "Config ID to pin this test to a version")
370
+ .option("--clear-pattern", "Clear expected pattern")
371
+ .option("--clear-contains", "Clear expected contains")
372
+ .option("--clear-json-subset", "Clear expected JSON subset")
373
+ .option("--json", "Output as JSON")
374
+ .action(async (script, testCaseId, options) => {
375
+ const resolvedAppId = resolveAppId(undefined, options);
376
+ const payload = {};
377
+ if (options.name)
378
+ payload.name = options.name;
379
+ if (options.vars) {
380
+ try {
381
+ payload.inputVariables = JSON.parse(options.vars);
382
+ }
383
+ catch {
384
+ error("Invalid JSON in --vars");
385
+ process.exit(1);
386
+ }
387
+ }
388
+ if (options.clearPattern) {
389
+ payload.expectedOutputPattern = null;
390
+ }
391
+ else if (options.pattern) {
392
+ payload.expectedOutputPattern = options.pattern;
393
+ }
394
+ if (options.clearContains) {
395
+ payload.expectedOutputContains = null;
396
+ }
397
+ else if (options.contains) {
398
+ try {
399
+ payload.expectedOutputContains = JSON.parse(options.contains);
400
+ }
401
+ catch {
402
+ error("Invalid JSON in --contains");
403
+ process.exit(1);
404
+ }
405
+ }
406
+ if (options.clearJsonSubset) {
407
+ payload.expectedJsonSubset = null;
408
+ }
409
+ else if (options.jsonSubset) {
410
+ try {
411
+ payload.expectedJsonSubset = JSON.parse(options.jsonSubset);
412
+ }
413
+ catch {
414
+ error("Invalid JSON in --json-subset");
415
+ process.exit(1);
416
+ }
417
+ }
418
+ if (options.config)
419
+ payload.configId = options.config;
420
+ if (Object.keys(payload).length === 0) {
421
+ error("No update options specified.");
422
+ process.exit(1);
423
+ }
424
+ const client = new ApiClient();
425
+ try {
426
+ const scriptId = await resolveScriptId(client, resolvedAppId, script);
427
+ const result = await client.updateTestCase(resolvedAppId, "script", scriptId, testCaseId, payload);
428
+ if (options.json) {
429
+ json(result);
430
+ return;
431
+ }
432
+ success("Test case updated.");
433
+ }
434
+ catch (err) {
435
+ error(err.message);
436
+ process.exit(1);
437
+ }
438
+ });
439
+ // Delete test case
440
+ tests
441
+ .command("delete")
442
+ .description("Delete a test case")
443
+ .argument("<script>", "Script ID or name")
444
+ .argument("<test-case-id>", "Test Case ID")
445
+ .option("--app <app-id>", "App ID (uses current app if not specified)")
446
+ .option("-y, --yes", "Skip confirmation prompt")
447
+ .action(async (script, testCaseId, options) => {
448
+ const resolvedAppId = resolveAppId(undefined, options);
449
+ if (!options.yes) {
450
+ let confirm;
451
+ try {
452
+ confirm = await confirmPrompt(`Are you sure you want to delete test case ${testCaseId}?`);
453
+ }
454
+ catch (err) {
455
+ error(err.message);
456
+ process.exit(1);
457
+ }
458
+ if (!confirm) {
459
+ info("Cancelled.");
460
+ return;
461
+ }
462
+ }
463
+ const client = new ApiClient();
464
+ try {
465
+ const scriptId = await resolveScriptId(client, resolvedAppId, script);
466
+ await client.deleteTestCase(resolvedAppId, "script", scriptId, testCaseId);
467
+ success("Test case deleted.");
468
+ }
469
+ catch (err) {
470
+ error(err.message);
471
+ process.exit(1);
472
+ }
473
+ });
474
+ // Run a single test case
475
+ tests
476
+ .command("run")
477
+ .description("Run a single test case")
478
+ .argument("<script>", "Script ID or name")
479
+ .argument("<test-case-id>", "Test Case ID")
480
+ .option("--app <app-id>", "App ID (uses current app if not specified)")
481
+ .option("--config <config-id>", "Pin to a specific version (defaults to the test case's config, else the active version)")
482
+ .option("--json", "Output as JSON")
483
+ .action(async (script, testCaseId, options) => {
484
+ const resolvedAppId = resolveAppId(undefined, options);
485
+ const client = new ApiClient();
486
+ try {
487
+ const scriptId = await resolveScriptId(client, resolvedAppId, script);
488
+ const testCase = await client.getTestCase(resolvedAppId, "script", scriptId, testCaseId);
489
+ let variables = {};
490
+ try {
491
+ variables = JSON.parse(testCase.inputVariables || "{}");
492
+ }
493
+ catch {
494
+ // Use empty object if parsing fails
495
+ }
496
+ const result = await client.executeBlockTest(resolvedAppId, "script", scriptId, {
497
+ variables,
498
+ testCaseId,
499
+ configId: options.config || testCase.configId,
500
+ });
501
+ if (options.json) {
502
+ json(result);
503
+ return;
504
+ }
505
+ const passed = result.verification?.passed;
506
+ if (passed) {
507
+ success("Test PASSED");
508
+ }
509
+ else {
510
+ error("Test FAILED");
511
+ }
512
+ keyValue("Duration", formatDuration(result.metrics?.durationMs));
513
+ if (result.verification) {
514
+ divider();
515
+ info("Verification:");
516
+ const v = result.verification;
517
+ keyValue(" Passed", v.passed ? chalk.green("Yes") : chalk.red("No"));
518
+ if (v.summary) {
519
+ keyValue(" Checks", `${v.summary?.passed || 0}/${v.summary?.total || 0} passed`);
520
+ }
521
+ if (v.checks && v.checks.length > 0) {
522
+ console.log();
523
+ for (const check of v.checks) {
524
+ const icon = check.passed ? chalk.green("✓") : chalk.red("✗");
525
+ console.log(` ${icon} ${check.name}`);
526
+ if (!check.passed && check.message) {
527
+ console.log(` ${chalk.dim(check.message)}`);
528
+ }
529
+ }
530
+ }
531
+ }
532
+ if (result.output) {
533
+ divider();
534
+ info("Output:");
535
+ console.log(result.output);
536
+ }
537
+ if (result.error) {
538
+ divider();
539
+ error(`Error: ${result.error}`);
540
+ }
541
+ }
542
+ catch (err) {
543
+ error(err.message);
544
+ process.exit(1);
545
+ }
546
+ });
547
+ // Run all test cases
548
+ tests
549
+ .command("run-all")
550
+ .description("Run all test cases for a script, or a specific subset")
551
+ .argument("<script>", "Script ID or name")
552
+ .option("--app <app-id>", "App ID (uses current app if not specified)")
553
+ .option("--config <config-id>", "Override config ID for all tests (pin to a version)")
554
+ .option("--test-cases <ids>", "Comma-separated list of test case IDs to run (runs only these)")
555
+ .option("--json", "Output as JSON")
556
+ .action(async (script, options) => {
557
+ const resolvedAppId = resolveAppId(undefined, options);
558
+ const client = new ApiClient();
559
+ try {
560
+ let testCaseIds;
561
+ if (options.testCases) {
562
+ testCaseIds = options.testCases
563
+ .split(",")
564
+ .map((id) => id.trim())
565
+ .filter(Boolean);
566
+ if (testCaseIds.length === 0) {
567
+ error("No valid test case IDs provided in --test-cases");
568
+ process.exit(1);
569
+ }
570
+ }
571
+ const scriptId = await resolveScriptId(client, resolvedAppId, script);
572
+ if (!options.json) {
573
+ if (testCaseIds) {
574
+ info(`Running ${testCaseIds.length} selected test case(s)...`);
575
+ }
576
+ else {
577
+ info("Running all test cases...");
578
+ }
579
+ }
580
+ const result = await client.runAllTestCases(resolvedAppId, "script", scriptId, {
581
+ overrideConfigId: options.config,
582
+ testCaseIds,
583
+ });
584
+ if (options.json) {
585
+ json(result);
586
+ return;
587
+ }
588
+ const { summary, runs, comparisonGroup } = result;
589
+ divider();
590
+ keyValue("Comparison Group", comparisonGroup);
591
+ keyValue("Total Tests", summary.total);
592
+ keyValue("Passed", chalk.green(summary.passed));
593
+ keyValue("Failed", summary.failed > 0 ? chalk.red(summary.failed) : "0");
594
+ divider();
595
+ if (runs && runs.length > 0) {
596
+ for (const r of runs) {
597
+ const passed = r.verification?.passed;
598
+ const icon = passed ? chalk.green("✓") : chalk.red("✗");
599
+ const status = passed ? "PASSED" : "FAILED";
600
+ console.log(`${icon} ${r.testCaseName} - ${status}`);
601
+ if (!passed && r.verification?.details?.reason) {
602
+ console.log(` ${chalk.dim(r.verification.details.reason)}`);
603
+ }
604
+ }
605
+ }
606
+ if (summary.failed > 0) {
607
+ process.exit(1);
608
+ }
609
+ }
610
+ catch (err) {
611
+ error(err.message);
612
+ process.exit(1);
613
+ }
614
+ });
615
+ // List test runs
616
+ tests
617
+ .command("runs")
618
+ .description("List recent test runs for a script")
619
+ .argument("<script>", "Script ID or name")
620
+ .option("--app <app-id>", "App ID (uses current app if not specified)")
621
+ .option("--limit <n>", "Maximum number of runs to show", "20")
622
+ .option("--group <group>", "Filter by comparison group")
623
+ .option("--json", "Output as JSON")
624
+ .action(async (script, options) => {
625
+ const resolvedAppId = resolveAppId(undefined, options);
626
+ const client = new ApiClient();
627
+ try {
628
+ const scriptId = await resolveScriptId(client, resolvedAppId, script);
629
+ const { items } = await client.listTestRuns(resolvedAppId, "script", scriptId, {
630
+ limit: parseInt(options.limit),
631
+ comparisonGroup: options.group,
632
+ });
633
+ if (options.json) {
634
+ json(items);
635
+ return;
636
+ }
637
+ if (!items || items.length === 0) {
638
+ info("No test runs found.");
639
+ return;
640
+ }
641
+ console.log(formatTable(items, [
642
+ { header: "RUN ID", key: "runId", format: formatId },
643
+ {
644
+ header: "TEST CASE ID",
645
+ key: "testCaseId",
646
+ format: (v) => (v ? formatId(v) : "-"),
647
+ },
648
+ {
649
+ header: "PASSED",
650
+ key: "verificationPassed",
651
+ format: (v) => v === true
652
+ ? chalk.green("Yes")
653
+ : v === false
654
+ ? chalk.red("No")
655
+ : "-",
656
+ },
657
+ { header: "DURATION", key: "durationMs", format: formatDuration },
658
+ { header: "STARTED", key: "startedAt", format: formatDate },
659
+ ]));
660
+ }
661
+ catch (err) {
662
+ error(err.message);
663
+ process.exit(1);
664
+ }
665
+ });
666
+ }
667
+ //# sourceMappingURL=scripts.js.map