@zapier/zapier-sdk-cli 0.19.0 → 0.22.0

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 (88) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/cli.cjs +2 -2
  3. package/dist/cli.mjs +2 -2
  4. package/dist/index.cjs +1 -1
  5. package/dist/index.mjs +1 -1
  6. package/package.json +4 -4
  7. package/dist/package.json +0 -78
  8. package/dist/src/cli.d.ts +0 -2
  9. package/dist/src/cli.js +0 -105
  10. package/dist/src/generators/ast-generator.d.ts +0 -41
  11. package/dist/src/generators/ast-generator.js +0 -409
  12. package/dist/src/index.d.ts +0 -4
  13. package/dist/src/index.js +0 -4
  14. package/dist/src/plugins/add/index.d.ts +0 -15
  15. package/dist/src/plugins/add/index.js +0 -103
  16. package/dist/src/plugins/add/schemas.d.ts +0 -8
  17. package/dist/src/plugins/add/schemas.js +0 -22
  18. package/dist/src/plugins/buildManifest/index.d.ts +0 -13
  19. package/dist/src/plugins/buildManifest/index.js +0 -81
  20. package/dist/src/plugins/buildManifest/schemas.d.ts +0 -44
  21. package/dist/src/plugins/buildManifest/schemas.js +0 -17
  22. package/dist/src/plugins/bundleCode/index.d.ts +0 -15
  23. package/dist/src/plugins/bundleCode/index.js +0 -80
  24. package/dist/src/plugins/bundleCode/schemas.d.ts +0 -10
  25. package/dist/src/plugins/bundleCode/schemas.js +0 -19
  26. package/dist/src/plugins/generateAppTypes/index.d.ts +0 -13
  27. package/dist/src/plugins/generateAppTypes/index.js +0 -157
  28. package/dist/src/plugins/generateAppTypes/schemas.d.ts +0 -58
  29. package/dist/src/plugins/generateAppTypes/schemas.js +0 -21
  30. package/dist/src/plugins/getLoginConfigPath/index.d.ts +0 -15
  31. package/dist/src/plugins/getLoginConfigPath/index.js +0 -19
  32. package/dist/src/plugins/getLoginConfigPath/schemas.d.ts +0 -3
  33. package/dist/src/plugins/getLoginConfigPath/schemas.js +0 -5
  34. package/dist/src/plugins/index.d.ts +0 -8
  35. package/dist/src/plugins/index.js +0 -8
  36. package/dist/src/plugins/login/index.d.ts +0 -23
  37. package/dist/src/plugins/login/index.js +0 -95
  38. package/dist/src/plugins/login/schemas.d.ts +0 -5
  39. package/dist/src/plugins/login/schemas.js +0 -10
  40. package/dist/src/plugins/logout/index.d.ts +0 -15
  41. package/dist/src/plugins/logout/index.js +0 -18
  42. package/dist/src/plugins/logout/schemas.d.ts +0 -3
  43. package/dist/src/plugins/logout/schemas.js +0 -5
  44. package/dist/src/plugins/mcp/index.d.ts +0 -15
  45. package/dist/src/plugins/mcp/index.js +0 -24
  46. package/dist/src/plugins/mcp/schemas.d.ts +0 -5
  47. package/dist/src/plugins/mcp/schemas.js +0 -10
  48. package/dist/src/sdk.d.ts +0 -9
  49. package/dist/src/sdk.js +0 -24
  50. package/dist/src/telemetry/builders.d.ts +0 -42
  51. package/dist/src/telemetry/builders.js +0 -55
  52. package/dist/src/telemetry/events.d.ts +0 -37
  53. package/dist/src/telemetry/events.js +0 -4
  54. package/dist/src/types/sdk.d.ts +0 -5
  55. package/dist/src/types/sdk.js +0 -1
  56. package/dist/src/utils/api/client.d.ts +0 -15
  57. package/dist/src/utils/api/client.js +0 -27
  58. package/dist/src/utils/auth/login.d.ts +0 -7
  59. package/dist/src/utils/auth/login.js +0 -154
  60. package/dist/src/utils/cli-generator-utils.d.ts +0 -14
  61. package/dist/src/utils/cli-generator-utils.js +0 -122
  62. package/dist/src/utils/cli-generator.d.ts +0 -3
  63. package/dist/src/utils/cli-generator.js +0 -555
  64. package/dist/src/utils/constants.d.ts +0 -3
  65. package/dist/src/utils/constants.js +0 -5
  66. package/dist/src/utils/directory-detection.d.ts +0 -5
  67. package/dist/src/utils/directory-detection.js +0 -21
  68. package/dist/src/utils/errors.d.ts +0 -16
  69. package/dist/src/utils/errors.js +0 -19
  70. package/dist/src/utils/getCallablePromise.d.ts +0 -6
  71. package/dist/src/utils/getCallablePromise.js +0 -14
  72. package/dist/src/utils/log.d.ts +0 -8
  73. package/dist/src/utils/log.js +0 -21
  74. package/dist/src/utils/manifest-helpers.d.ts +0 -10
  75. package/dist/src/utils/manifest-helpers.js +0 -19
  76. package/dist/src/utils/package-manager-detector.d.ts +0 -16
  77. package/dist/src/utils/package-manager-detector.js +0 -77
  78. package/dist/src/utils/parameter-resolver.d.ts +0 -42
  79. package/dist/src/utils/parameter-resolver.js +0 -699
  80. package/dist/src/utils/schema-formatter.d.ts +0 -6
  81. package/dist/src/utils/schema-formatter.js +0 -115
  82. package/dist/src/utils/serializeAsync.d.ts +0 -2
  83. package/dist/src/utils/serializeAsync.js +0 -16
  84. package/dist/src/utils/spinner.d.ts +0 -1
  85. package/dist/src/utils/spinner.js +0 -21
  86. package/dist/src/utils/version-checker.d.ts +0 -17
  87. package/dist/src/utils/version-checker.js +0 -156
  88. package/dist/tsconfig.tsbuildinfo +0 -1
@@ -1,699 +0,0 @@
1
- import inquirer from "inquirer";
2
- import chalk from "chalk";
3
- import { z } from "zod";
4
- import { ZapierCliUserCancellationError } from "./errors";
5
- // ============================================================================
6
- // Local Resolution Helper Functions
7
- // ============================================================================
8
- /**
9
- * Resolve dependency chain for a parameter using local resolvers
10
- * Returns parameters in the order they need to be resolved
11
- */
12
- function getLocalResolutionOrder(paramName, resolvers, resolved = new Set()) {
13
- const resolver = resolvers[paramName];
14
- if (!resolver || resolver.type === "static") {
15
- return [paramName];
16
- }
17
- const order = [];
18
- if ("depends" in resolver && resolver.depends) {
19
- for (const dependency of resolver.depends) {
20
- if (!resolved.has(dependency)) {
21
- order.push(...getLocalResolutionOrder(dependency, resolvers, resolved));
22
- resolved.add(dependency);
23
- }
24
- }
25
- }
26
- if (!resolved.has(paramName)) {
27
- order.push(paramName);
28
- resolved.add(paramName);
29
- }
30
- return order;
31
- }
32
- /**
33
- * Get resolution order for multiple parameters using local resolvers
34
- */
35
- function getLocalResolutionOrderForParams(paramNames, resolvers) {
36
- const resolved = new Set();
37
- const order = [];
38
- for (const paramName of paramNames) {
39
- const paramOrder = getLocalResolutionOrder(paramName, resolvers, resolved);
40
- for (const param of paramOrder) {
41
- if (!order.includes(param)) {
42
- order.push(param);
43
- }
44
- }
45
- }
46
- return order;
47
- }
48
- // ============================================================================
49
- // Schema Parameter Resolver
50
- // ============================================================================
51
- export class SchemaParameterResolver {
52
- async resolveParameters(schema, providedParams, sdk, functionName) {
53
- // 1. Try to parse with current parameters
54
- const parseResult = schema.safeParse(providedParams);
55
- // Get all schema parameters to check which ones have resolvers
56
- const allParams = this.extractParametersFromSchema(schema);
57
- const resolvableParams = allParams.filter((param) => this.hasResolver(param.name, sdk, functionName));
58
- // Get all missing parameters that have resolvers
59
- const missingResolvable = resolvableParams.filter((param) => {
60
- const hasValue = this.getNestedValue(providedParams, param.path) !== undefined;
61
- return !hasValue;
62
- });
63
- // Determine parameter resolution categories:
64
- // - functionally required: must be provided (inputs)
65
- // - always prompt: should be prompted for but can be skipped (authenticationId)
66
- // - truly optional: only ask if user wants to be prompted
67
- const functionallyRequired = missingResolvable.filter((param) => {
68
- // Schema-required parameters are always functionally required
69
- if (param.isRequired)
70
- return true;
71
- // Only inputs is functionally required for run-action
72
- if (param.name === "inputs") {
73
- return true;
74
- }
75
- return false;
76
- });
77
- // Parameters that should always be prompted for directly, but can be skipped
78
- const alwaysPrompt = missingResolvable.filter((param) => {
79
- if (functionallyRequired.includes(param))
80
- return false;
81
- // authenticationId should always be prompted for (since it's usually needed)
82
- // but can be skipped with "Continue without authentication"
83
- if (param.name === "authenticationId") {
84
- return true;
85
- }
86
- return false;
87
- });
88
- const trulyOptional = missingResolvable.filter((param) => !functionallyRequired.includes(param) && !alwaysPrompt.includes(param));
89
- if (parseResult.success &&
90
- functionallyRequired.length === 0 &&
91
- alwaysPrompt.length === 0) {
92
- return parseResult.data;
93
- }
94
- if (functionallyRequired.length === 0 && alwaysPrompt.length === 0) {
95
- // No functionally required parameters missing, but check if we can parse
96
- if (!parseResult.success) {
97
- throw parseResult.error;
98
- }
99
- return parseResult.data;
100
- }
101
- // 2. Resolve functionally required parameters first
102
- const resolvedParams = { ...providedParams };
103
- const context = {
104
- sdk,
105
- currentParams: providedParams,
106
- resolvedParams,
107
- functionName,
108
- };
109
- // Get local resolvers for this function
110
- const localResolvers = this.getLocalResolvers(sdk, functionName);
111
- if (functionallyRequired.length > 0) {
112
- const requiredParamNames = functionallyRequired.map((p) => p.name);
113
- const requiredResolutionOrder = getLocalResolutionOrderForParams(requiredParamNames, localResolvers);
114
- // Find all parameters that need to be resolved (including dependencies)
115
- // from the available resolvable parameters
116
- const orderedRequiredParams = requiredResolutionOrder
117
- .map((paramName) => {
118
- // First try to find in functionally required
119
- let param = functionallyRequired.find((p) => p.name === paramName);
120
- // If not found, try always prompt (for dependencies like authenticationId)
121
- if (!param) {
122
- param = alwaysPrompt.find((p) => p.name === paramName);
123
- }
124
- // If not found, try truly optional (for other dependencies)
125
- if (!param) {
126
- param = trulyOptional.find((p) => p.name === paramName);
127
- }
128
- return param;
129
- })
130
- .filter((param) => param !== undefined);
131
- for (const param of orderedRequiredParams) {
132
- try {
133
- const value = await this.resolveParameter(param, context, functionName);
134
- this.setNestedValue(resolvedParams, param.path, value);
135
- // Update context with newly resolved value
136
- context.resolvedParams = resolvedParams;
137
- }
138
- catch (error) {
139
- if (this.isUserCancellation(error)) {
140
- console.log(chalk.yellow("\n\nOperation cancelled by user"));
141
- throw new ZapierCliUserCancellationError();
142
- }
143
- throw error;
144
- }
145
- }
146
- // Remove resolved dependencies from other categories to avoid double-prompting
147
- const resolvedParamNames = new Set(orderedRequiredParams.map((p) => p.name));
148
- alwaysPrompt.splice(0, alwaysPrompt.length, ...alwaysPrompt.filter((p) => !resolvedParamNames.has(p.name)));
149
- trulyOptional.splice(0, trulyOptional.length, ...trulyOptional.filter((p) => !resolvedParamNames.has(p.name)));
150
- }
151
- // 3. Resolve parameters that should always be prompted for (but can be skipped)
152
- if (alwaysPrompt.length > 0) {
153
- const alwaysPromptNames = alwaysPrompt.map((p) => p.name);
154
- const alwaysPromptResolutionOrder = getLocalResolutionOrderForParams(alwaysPromptNames, localResolvers);
155
- const orderedAlwaysPromptParams = alwaysPromptResolutionOrder
156
- .map((paramName) => alwaysPrompt.find((p) => p.name === paramName))
157
- .filter((param) => param !== undefined);
158
- for (const param of orderedAlwaysPromptParams) {
159
- try {
160
- const value = await this.resolveParameter(param, context, functionName);
161
- this.setNestedValue(resolvedParams, param.path, value);
162
- // Update context with newly resolved value
163
- context.resolvedParams = resolvedParams;
164
- }
165
- catch (error) {
166
- if (this.isUserCancellation(error)) {
167
- console.log(chalk.yellow("\n\nOperation cancelled by user"));
168
- throw new ZapierCliUserCancellationError();
169
- }
170
- throw error;
171
- }
172
- }
173
- }
174
- // 4. Ask user if they want to resolve truly optional parameters
175
- if (trulyOptional.length > 0) {
176
- const optionalNames = trulyOptional.map((p) => p.name).join(", ");
177
- const shouldResolveOptional = await inquirer.prompt([
178
- {
179
- type: "confirm",
180
- name: "resolveOptional",
181
- message: `Would you like to be prompted for optional parameters (${optionalNames})?`,
182
- default: false,
183
- },
184
- ]);
185
- if (shouldResolveOptional.resolveOptional) {
186
- // Resolve optional parameters using their resolvers
187
- const optionalParamNames = trulyOptional.map((p) => p.name);
188
- const optionalResolutionOrder = getLocalResolutionOrderForParams(optionalParamNames, localResolvers);
189
- const orderedOptionalParams = optionalResolutionOrder
190
- .map((paramName) => trulyOptional.find((p) => p.name === paramName))
191
- .filter((param) => param !== undefined);
192
- for (const param of orderedOptionalParams) {
193
- try {
194
- const value = await this.resolveParameter(param, context, functionName);
195
- this.setNestedValue(resolvedParams, param.path, value);
196
- // Update context with newly resolved value
197
- context.resolvedParams = resolvedParams;
198
- }
199
- catch (error) {
200
- if (this.isUserCancellation(error)) {
201
- console.log(chalk.yellow("\n\nOperation cancelled by user"));
202
- throw new ZapierCliUserCancellationError();
203
- }
204
- throw error;
205
- }
206
- }
207
- }
208
- }
209
- // 3. Validate final parameters
210
- const finalResult = schema.safeParse(resolvedParams);
211
- if (!finalResult.success) {
212
- console.error(chalk.red("❌ Parameter validation failed after resolution:"));
213
- throw finalResult.error;
214
- }
215
- return finalResult.data;
216
- }
217
- extractParametersFromSchema(schema) {
218
- const parameters = [];
219
- // Only handle ZodObject at the top level
220
- if (schema instanceof z.ZodObject) {
221
- const shape = schema.shape;
222
- for (const [key, fieldSchema] of Object.entries(shape)) {
223
- const param = this.analyzeFieldSchema(key, fieldSchema);
224
- if (param) {
225
- parameters.push(param);
226
- }
227
- }
228
- }
229
- return parameters;
230
- }
231
- analyzeFieldSchema(fieldName, fieldSchema) {
232
- let baseSchema = fieldSchema;
233
- let isRequired = true;
234
- // Check if field is optional or has default
235
- if (baseSchema instanceof z.ZodOptional) {
236
- isRequired = false;
237
- baseSchema = baseSchema._zod.def.innerType;
238
- }
239
- if (baseSchema instanceof z.ZodDefault) {
240
- isRequired = false;
241
- baseSchema = baseSchema._zod.def.innerType;
242
- }
243
- return this.createResolvableParameter([fieldName], baseSchema, isRequired);
244
- }
245
- createResolvableParameter(path, schema, isRequired) {
246
- if (path.length === 0)
247
- return null;
248
- const name = path[path.length - 1];
249
- return {
250
- name,
251
- path,
252
- schema,
253
- description: schema.description,
254
- isRequired,
255
- };
256
- }
257
- async resolveParameter(param, context, functionName) {
258
- const resolver = this.getResolver(param.name, context.sdk, functionName);
259
- if (!resolver) {
260
- throw new Error(`No resolver found for parameter: ${param.name}`);
261
- }
262
- console.log(chalk.blue(`\n🔍 Resolving ${param.name}...`));
263
- const typedResolver = resolver;
264
- if (typedResolver.type === "static") {
265
- // Static resolver - just prompt for input
266
- const promptConfig = {
267
- type: typedResolver.inputType === "password" ? "password" : "input",
268
- name: param.name,
269
- message: `Enter ${param.name}:`,
270
- ...(typedResolver.placeholder && {
271
- default: typedResolver.placeholder,
272
- }),
273
- };
274
- const answers = await inquirer.prompt([promptConfig]);
275
- return answers[param.name];
276
- }
277
- else if (typedResolver.type === "dynamic") {
278
- // Dynamic resolver - fetch options and prompt for selection
279
- try {
280
- // Only show "Fetching..." for required parameters that typically have many options
281
- if (param.isRequired && param.name !== "authenticationId") {
282
- console.log(chalk.gray(`Fetching options for ${param.name}...`));
283
- }
284
- const items = await typedResolver.fetch(context.sdk, context.resolvedParams);
285
- // Let the resolver's prompt handle empty lists (e.g., authenticationId can show "skip authentication")
286
- const safeItems = items || [];
287
- const promptConfig = typedResolver.prompt(safeItems, context.resolvedParams);
288
- const answers = await inquirer.prompt([promptConfig]);
289
- return answers[param.name];
290
- }
291
- catch (error) {
292
- // Let the main CLI error handler display user-friendly errors
293
- throw error;
294
- }
295
- }
296
- else if (typedResolver.type === "fields") {
297
- // Fields resolver - fetch field definitions and prompt for each input with recursive field resolution
298
- return await this.resolveFieldsRecursively(resolver, context, param);
299
- }
300
- throw new Error(`Unknown resolver type for ${param.name}`);
301
- }
302
- async resolveFieldsRecursively(resolver, context, param) {
303
- const typedResolver = resolver;
304
- const inputs = {};
305
- let processedFieldKeys = new Set();
306
- let iteration = 0;
307
- const maxIterations = 10; // Prevent infinite loops
308
- while (iteration < maxIterations) {
309
- iteration++;
310
- // Update context with current inputs so they're passed to listInputFields
311
- const updatedContext = {
312
- ...context,
313
- resolvedParams: {
314
- ...context.resolvedParams,
315
- inputs,
316
- },
317
- };
318
- console.log(chalk.gray(`Fetching input fields for ${param.name}${iteration > 1 ? ` (iteration ${iteration})` : ""}...`));
319
- const rootFieldItems = await typedResolver.fetch(updatedContext.sdk, updatedContext.resolvedParams);
320
- if (!rootFieldItems || rootFieldItems.length === 0) {
321
- if (iteration === 1) {
322
- console.log(chalk.yellow(`No input fields required for this action.`));
323
- }
324
- break;
325
- }
326
- // Process fields recursively, maintaining fieldset structure
327
- const fieldStats = await this.processFieldItems(rootFieldItems, inputs, processedFieldKeys, [], iteration, updatedContext);
328
- // If no new fields were processed, we're done
329
- if (fieldStats.newRequired === 0 && fieldStats.newOptional === 0) {
330
- break;
331
- }
332
- // If we only processed optional fields and skipped them, no need to re-fetch
333
- if (fieldStats.newRequired === 0 && fieldStats.optionalSkipped) {
334
- break;
335
- }
336
- }
337
- if (iteration >= maxIterations) {
338
- console.log(chalk.yellow(`\n⚠️ Maximum field resolution iterations reached. Some dynamic fields may not have been discovered.`));
339
- }
340
- return inputs;
341
- }
342
- /**
343
- * Recursively processes fieldsets and their fields, maintaining natural structure
344
- * and creating nested inputs as needed (e.g., fieldset "foo" becomes inputs.foo = [{}])
345
- */
346
- async processFieldItems(items, targetInputs, processedFieldKeys, fieldsetPath = [], iteration = 1, context) {
347
- let newRequiredCount = 0;
348
- let newOptionalCount = 0;
349
- let optionalSkipped = false;
350
- for (const item of items) {
351
- const typedItem = item;
352
- if (typedItem.type === "fieldset" && typedItem.fields && typedItem.key) {
353
- // Show fieldset context to user
354
- const fieldsetTitle = typedItem.title || typedItem.key;
355
- const pathDisplay = fieldsetPath.length > 0 ? ` (in ${fieldsetPath.join(" > ")})` : "";
356
- console.log(chalk.cyan(`\n📁 Processing fieldset: ${fieldsetTitle}${pathDisplay}`));
357
- // Create fieldset array in target inputs if it doesn't exist
358
- if (!targetInputs[typedItem.key]) {
359
- targetInputs[typedItem.key] = [{}];
360
- }
361
- // Process fields within this fieldset recursively
362
- const fieldsetTarget = targetInputs[typedItem.key][0];
363
- const nestedPath = [...fieldsetPath, fieldsetTitle];
364
- const nestedStats = await this.processFieldItems(typedItem.fields, fieldsetTarget, processedFieldKeys, nestedPath, iteration, context);
365
- newRequiredCount += nestedStats.newRequired;
366
- newOptionalCount += nestedStats.newOptional;
367
- if (nestedStats.optionalSkipped) {
368
- optionalSkipped = true;
369
- }
370
- }
371
- else if (typedItem.type === "input_field" && typedItem.key) {
372
- // Skip if already processed
373
- if (processedFieldKeys.has(typedItem.key)) {
374
- continue;
375
- }
376
- const isRequired = typedItem.is_required || false;
377
- if (isRequired) {
378
- // Process required field immediately
379
- newRequiredCount++;
380
- if (newRequiredCount === 1 && fieldsetPath.length === 0) {
381
- // Only show this message once at root level
382
- console.log(chalk.blue(`\n📝 Please provide values for the following ${iteration === 1 ? "" : "additional "}input fields:`));
383
- }
384
- await this.promptForField(typedItem, targetInputs, context);
385
- processedFieldKeys.add(typedItem.key);
386
- }
387
- else {
388
- // Collect optional fields for batch processing
389
- newOptionalCount++;
390
- }
391
- }
392
- // Skip info fields - they're for display only
393
- }
394
- // Handle optional fields after processing all required fields
395
- if (newOptionalCount > 0) {
396
- const optionalFields = items.filter((item) => {
397
- const typedItem = item;
398
- return (typedItem.type === "input_field" &&
399
- typedItem.key &&
400
- !typedItem.is_required &&
401
- !processedFieldKeys.has(typedItem.key));
402
- });
403
- if (optionalFields.length > 0) {
404
- const pathContext = fieldsetPath.length > 0 ? ` in ${fieldsetPath.join(" > ")}` : "";
405
- console.log(chalk.gray(`\nThere are ${optionalFields.length} ${iteration === 1 ? "" : "additional "}optional field(s) available${pathContext}.`));
406
- try {
407
- const shouldConfigureOptional = await inquirer.prompt([
408
- {
409
- type: "confirm",
410
- name: "configure",
411
- message: `Would you like to configure ${iteration === 1 ? "" : "these additional "}optional fields${pathContext}?`,
412
- default: false,
413
- },
414
- ]);
415
- if (shouldConfigureOptional.configure) {
416
- console.log(chalk.cyan(`\nOptional fields${pathContext}:`));
417
- for (const field of optionalFields) {
418
- await this.promptForField(field, targetInputs, context);
419
- const typedField = field;
420
- processedFieldKeys.add(typedField.key);
421
- }
422
- }
423
- else {
424
- optionalSkipped = true;
425
- // Mark these fields as processed even if skipped to avoid re-asking
426
- optionalFields.forEach((field) => {
427
- const typedField = field;
428
- processedFieldKeys.add(typedField.key);
429
- });
430
- }
431
- }
432
- catch (error) {
433
- if (this.isUserCancellation(error)) {
434
- console.log(chalk.yellow("\n\nOperation cancelled by user"));
435
- throw new ZapierCliUserCancellationError();
436
- }
437
- throw error;
438
- }
439
- }
440
- }
441
- return {
442
- newRequired: newRequiredCount,
443
- newOptional: newOptionalCount,
444
- optionalSkipped,
445
- };
446
- }
447
- getNestedValue(obj, path) {
448
- return path.reduce((current, key) => current?.[key], obj);
449
- }
450
- setNestedValue(obj, path, value) {
451
- const lastKey = path[path.length - 1];
452
- const parent = path.slice(0, -1).reduce((current, key) => {
453
- const currentObj = current;
454
- if (!(key in currentObj)) {
455
- currentObj[key] = {};
456
- }
457
- return currentObj[key];
458
- }, obj);
459
- parent[lastKey] = value;
460
- }
461
- /**
462
- * Extract and normalize field metadata from raw field object
463
- */
464
- extractFieldMetadata(field) {
465
- const fieldObj = field;
466
- const valueType = fieldObj.value_type || "string";
467
- return {
468
- key: fieldObj.key,
469
- title: fieldObj.title || fieldObj.label || fieldObj.key,
470
- description: fieldObj.description || fieldObj.helpText,
471
- isRequired: fieldObj.is_required || false,
472
- defaultValue: fieldObj.default_value ?? fieldObj.default,
473
- valueType,
474
- hasDropdown: fieldObj.format === "SELECT",
475
- isMultiSelect: Boolean(valueType === "array" ||
476
- (fieldObj.items && fieldObj.items.type !== undefined)),
477
- };
478
- }
479
- /**
480
- * Fetch a page of choices for a dropdown field
481
- */
482
- async fetchChoices(fieldMeta, inputs, context, cursor) {
483
- try {
484
- console.log(chalk.gray(cursor
485
- ? ` Fetching more choices...`
486
- : ` Fetching choices for ${fieldMeta.title}...`));
487
- const page = await context.sdk.listInputFieldChoices({
488
- appKey: context.resolvedParams.appKey,
489
- actionKey: context.resolvedParams.actionKey,
490
- actionType: context.resolvedParams.actionType,
491
- authenticationId: context.resolvedParams.authenticationId,
492
- inputFieldKey: fieldMeta.key,
493
- inputs,
494
- ...(cursor && { cursor }),
495
- });
496
- const choices = page.data.map((choice) => ({
497
- label: choice.label || choice.key || String(choice.value),
498
- value: choice.value ?? choice.key,
499
- }));
500
- if (choices.length === 0 && !cursor) {
501
- console.log(chalk.yellow(` No choices available for ${fieldMeta.title}`));
502
- }
503
- return {
504
- choices,
505
- nextCursor: page.nextCursor,
506
- };
507
- }
508
- catch (error) {
509
- console.warn(chalk.yellow(` ⚠️ Failed to fetch choices for ${fieldMeta.title}:`), error);
510
- return { choices: [] };
511
- }
512
- }
513
- /**
514
- * Prompt user with choices (handles both single and multi-select with pagination)
515
- */
516
- async promptWithChoices({ fieldMeta, choices: initialChoices, nextCursor: initialCursor, inputs, context, }) {
517
- const choices = [...initialChoices];
518
- let nextCursor = initialCursor;
519
- const LOAD_MORE_SENTINEL = Symbol("LOAD_MORE");
520
- // Progressive loading loop
521
- while (true) {
522
- const promptChoices = choices.map((choice) => ({
523
- name: choice.label,
524
- value: choice.value,
525
- }));
526
- // Add "(Load more...)" option if there are more pages
527
- if (nextCursor) {
528
- promptChoices.push({
529
- name: chalk.dim("(Load more...)"),
530
- value: LOAD_MORE_SENTINEL,
531
- });
532
- }
533
- // Add skip option for optional fields (single-select only)
534
- if (!fieldMeta.isRequired && !fieldMeta.isMultiSelect) {
535
- promptChoices.push({ name: "(Skip)", value: undefined });
536
- }
537
- const promptConfig = {
538
- type: fieldMeta.isMultiSelect ? "checkbox" : "list",
539
- name: fieldMeta.key,
540
- message: `${fieldMeta.title}${fieldMeta.isRequired ? " (required)" : " (optional)"}:`,
541
- choices: promptChoices,
542
- ...(fieldMeta.isMultiSelect && {
543
- validate: (input) => {
544
- if (fieldMeta.isRequired && (!input || input.length === 0)) {
545
- return "At least one selection is required";
546
- }
547
- return true;
548
- },
549
- }),
550
- };
551
- const answer = await inquirer.prompt([promptConfig]);
552
- let selectedValue = answer[fieldMeta.key];
553
- // Check if user selected "Load more..."
554
- const wantsMore = fieldMeta.isMultiSelect
555
- ? Array.isArray(selectedValue) &&
556
- selectedValue.includes(LOAD_MORE_SENTINEL)
557
- : selectedValue === LOAD_MORE_SENTINEL;
558
- if (wantsMore && nextCursor && context) {
559
- // Remove sentinel from multi-select
560
- if (fieldMeta.isMultiSelect && Array.isArray(selectedValue)) {
561
- selectedValue = selectedValue.filter((v) => v !== LOAD_MORE_SENTINEL);
562
- }
563
- // Fetch next page
564
- const result = await this.fetchChoices(fieldMeta, inputs, context, nextCursor);
565
- choices.push(...result.choices);
566
- nextCursor = result.nextCursor;
567
- // Re-prompt with updated choices
568
- continue;
569
- }
570
- return selectedValue;
571
- }
572
- }
573
- /**
574
- * Prompt user for free-form input (text or boolean)
575
- */
576
- async promptFreeForm(fieldMeta) {
577
- const promptConfig = {
578
- name: fieldMeta.key,
579
- message: `${fieldMeta.title}${fieldMeta.isRequired ? " (required)" : " (optional)"}:`,
580
- };
581
- if (fieldMeta.valueType === "boolean") {
582
- promptConfig.type = "confirm";
583
- promptConfig.default =
584
- fieldMeta.defaultValue !== undefined
585
- ? Boolean(fieldMeta.defaultValue)
586
- : undefined;
587
- }
588
- else {
589
- promptConfig.type = "input";
590
- promptConfig.default = fieldMeta.defaultValue;
591
- promptConfig.validate = (input) => {
592
- if (fieldMeta.isRequired && !input) {
593
- return "This field is required";
594
- }
595
- return true;
596
- };
597
- }
598
- // Add help text if available
599
- if (fieldMeta.description) {
600
- promptConfig.prefix = chalk.gray(`ℹ ${fieldMeta.description}\n`);
601
- }
602
- try {
603
- const answer = await inquirer.prompt([promptConfig]);
604
- return answer[fieldMeta.key];
605
- }
606
- catch (error) {
607
- if (this.isUserCancellation(error)) {
608
- console.log(chalk.yellow("\n\nOperation cancelled by user"));
609
- throw new ZapierCliUserCancellationError();
610
- }
611
- throw error;
612
- }
613
- }
614
- /**
615
- * Store field value in inputs object with validation
616
- */
617
- storeFieldValue(inputs, key, value, isRequired) {
618
- try {
619
- if (value !== undefined && value !== "") {
620
- inputs[key] = value;
621
- }
622
- else if (isRequired) {
623
- throw new Error(`Required field ${key} cannot be empty`);
624
- }
625
- }
626
- catch (error) {
627
- if (this.isUserCancellation(error)) {
628
- console.log(chalk.yellow("\n\nOperation cancelled by user"));
629
- throw new ZapierCliUserCancellationError();
630
- }
631
- throw error;
632
- }
633
- }
634
- async promptForField(field, inputs, context) {
635
- const fieldMeta = this.extractFieldMetadata(field);
636
- // Fetch choices if field has dropdown
637
- let choices = [];
638
- let nextCursor;
639
- if (fieldMeta.hasDropdown && context) {
640
- const result = await this.fetchChoices(fieldMeta, inputs, context);
641
- choices = result.choices;
642
- nextCursor = result.nextCursor;
643
- }
644
- // Prompt user based on field type
645
- let selectedValue;
646
- if (choices.length > 0) {
647
- selectedValue = await this.promptWithChoices({
648
- fieldMeta,
649
- choices,
650
- nextCursor,
651
- inputs,
652
- context,
653
- });
654
- }
655
- else {
656
- selectedValue = await this.promptFreeForm(fieldMeta);
657
- }
658
- // Store result
659
- this.storeFieldValue(inputs, fieldMeta.key, selectedValue, fieldMeta.isRequired);
660
- }
661
- isUserCancellation(error) {
662
- const errorObj = error;
663
- return (errorObj?.name === "ExitPromptError" ||
664
- errorObj?.message?.includes("User force closed") ||
665
- errorObj?.isTTYError === true);
666
- }
667
- hasResolver(paramName, sdk, functionName) {
668
- // Check plugin-specific resolvers first
669
- if (functionName && typeof sdk.getRegistry === "function") {
670
- const registry = sdk.getRegistry({ package: "cli" });
671
- const functionInfo = registry.functions.find((f) => f.name === functionName);
672
- if (functionInfo && functionInfo.resolvers?.[paramName]) {
673
- return true;
674
- }
675
- }
676
- // No global registry fallback
677
- return false;
678
- }
679
- getResolver(paramName, sdk, functionName) {
680
- // Check plugin-specific resolvers first
681
- if (functionName && typeof sdk.getRegistry === "function") {
682
- const registry = sdk.getRegistry({ package: "cli" });
683
- const functionInfo = registry.functions.find((f) => f.name === functionName);
684
- if (functionInfo && functionInfo.resolvers?.[paramName]) {
685
- return functionInfo.resolvers[paramName];
686
- }
687
- }
688
- // No global registry fallback
689
- return null;
690
- }
691
- getLocalResolvers(sdk, functionName) {
692
- if (!functionName || typeof sdk.getRegistry !== "function") {
693
- return {};
694
- }
695
- const registry = sdk.getRegistry();
696
- const functionInfo = registry.functions.find((f) => f.name === functionName);
697
- return functionInfo?.resolvers || {};
698
- }
699
- }