@zapier/zapier-sdk-cli 0.0.2 → 0.1.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.
@@ -1,9 +1,12 @@
1
1
  import inquirer from "inquirer";
2
2
  import chalk from "chalk";
3
3
  import { z } from "zod";
4
- import { ActionsSdk } from "@zapier/zapier-sdk";
5
-
6
- // For editor-like prompts, we'll use a regular input prompt with multiline support
4
+ import { ZapierSdk } from "@zapier/zapier-sdk";
5
+ import {
6
+ getResolver,
7
+ hasResolver,
8
+ getResolutionOrderForParams,
9
+ } from "@zapier/zapier-sdk";
7
10
 
8
11
  // ============================================================================
9
12
  // Types
@@ -14,12 +17,11 @@ interface ResolvableParameter {
14
17
  path: string[];
15
18
  schema: z.ZodType;
16
19
  description?: string;
17
- resolverMeta?: any;
18
20
  isRequired: boolean;
19
21
  }
20
22
 
21
23
  interface ResolverContext {
22
- sdk: ActionsSdk;
24
+ sdk: ZapierSdk;
23
25
  currentParams: any;
24
26
  resolvedParams: any;
25
27
  }
@@ -32,44 +34,57 @@ export class SchemaParameterResolver {
32
34
  async resolveParameters(
33
35
  schema: z.ZodSchema,
34
36
  providedParams: any,
35
- sdk: ActionsSdk,
37
+ sdk: ZapierSdk,
36
38
  ): Promise<any> {
37
39
  // 1. Try to parse with current parameters
38
40
  const parseResult = schema.safeParse(providedParams);
39
41
 
40
- // Even if parsing succeeds, check if we should resolve optional but important parameters
41
- const allResolvable = this.findAllResolvableParameters(schema);
42
- const shouldResolveAnyway = allResolvable.filter((param) => {
42
+ // Get all schema parameters to check which ones have resolvers
43
+ const allParams = this.extractParametersFromSchema(schema);
44
+ const resolvableParams = allParams.filter((param) =>
45
+ hasResolver(param.name),
46
+ );
47
+
48
+ // Get all missing parameters that have resolvers
49
+ const missingResolvable = resolvableParams.filter((param) => {
43
50
  const hasValue =
44
51
  this.getNestedValue(providedParams, param.path) !== undefined;
45
- // Resolve any parameters with resolvers even if optional, unless explicitly provided
46
- return !hasValue && param.resolverMeta?.resolver;
52
+ return !hasValue;
47
53
  });
48
54
 
49
- if (parseResult.success && shouldResolveAnyway.length === 0) {
50
- return parseResult.data;
51
- }
55
+ // Determine which parameters are "functionally required" vs truly optional
56
+ // For run-action: appKey, actionType, actionKey are schema-required
57
+ // authenticationId and inputs are schema-optional but functionally needed
58
+ const functionallyRequired = missingResolvable.filter((param) => {
59
+ // Schema-required parameters are always functionally required
60
+ if (param.isRequired) return true;
52
61
 
53
- // 2. Analyze what's missing and can be resolved
54
- let missingResolvable: ResolvableParameter[];
62
+ // For run-action, inputs and authenticationId are functionally required
63
+ // even though schema-optional
64
+ if (param.name === "inputs" || param.name === "authenticationId") {
65
+ return true;
66
+ }
55
67
 
56
- if (!parseResult.success) {
57
- missingResolvable = this.findResolvableParameters(
58
- schema,
59
- parseResult.error,
60
- providedParams,
61
- );
62
- } else {
63
- // Schema parsing succeeded, but we want to resolve optional important parameters
64
- missingResolvable = shouldResolveAnyway;
68
+ return false;
69
+ });
70
+
71
+ const trulyOptional = missingResolvable.filter(
72
+ (param) => !functionallyRequired.includes(param),
73
+ );
74
+
75
+ if (parseResult.success && functionallyRequired.length === 0) {
76
+ return parseResult.data;
65
77
  }
66
78
 
67
- if (missingResolvable.length === 0) {
68
- // No resolvable parameters, throw original error
69
- throw parseResult.error;
79
+ if (functionallyRequired.length === 0) {
80
+ // No functionally required parameters missing, but check if we can parse
81
+ if (!parseResult.success) {
82
+ throw parseResult.error;
83
+ }
84
+ return parseResult.data;
70
85
  }
71
86
 
72
- // 3. Resolve missing parameters interactively
87
+ // 2. Resolve functionally required parameters first
73
88
  const resolvedParams = { ...providedParams };
74
89
  const context: ResolverContext = {
75
90
  sdk,
@@ -77,27 +92,77 @@ export class SchemaParameterResolver {
77
92
  resolvedParams,
78
93
  };
79
94
 
80
- // Sort by dependency order (parameters that depend on others go last)
81
- const sortedMissing = this.sortByDependencies(missingResolvable);
95
+ if (functionallyRequired.length > 0) {
96
+ const requiredParamNames = functionallyRequired.map((p) => p.name);
97
+ const requiredResolutionOrder =
98
+ getResolutionOrderForParams(requiredParamNames);
82
99
 
83
- for (const missing of sortedMissing) {
84
- try {
85
- const value = await this.resolveParameter(missing, context);
86
- this.setNestedValue(resolvedParams, missing.path, value);
100
+ const orderedRequiredParams = requiredResolutionOrder
101
+ .map((paramName) =>
102
+ functionallyRequired.find((p) => p.name === paramName),
103
+ )
104
+ .filter((param): param is ResolvableParameter => param !== undefined);
87
105
 
88
- // Update context with newly resolved value
89
- context.resolvedParams = resolvedParams;
90
- } catch (error) {
91
- if (this.isUserCancellation(error)) {
92
- console.log(chalk.yellow("\n\nOperation cancelled by user"));
93
- process.exit(0);
106
+ for (const param of orderedRequiredParams) {
107
+ try {
108
+ const value = await this.resolveParameter(param, context);
109
+ this.setNestedValue(resolvedParams, param.path, value);
110
+
111
+ // Update context with newly resolved value
112
+ context.resolvedParams = resolvedParams;
113
+ } catch (error) {
114
+ if (this.isUserCancellation(error)) {
115
+ console.log(chalk.yellow("\n\nOperation cancelled by user"));
116
+ process.exit(0);
117
+ }
118
+ console.error(chalk.red(`Failed to resolve ${param.name}:`), error);
119
+ throw error;
94
120
  }
95
- console.error(chalk.red(`Failed to resolve ${missing.name}:`), error);
96
- throw error;
97
121
  }
98
122
  }
99
123
 
100
- // 4. Validate final parameters
124
+ // 3. Ask user if they want to resolve truly optional parameters
125
+ if (trulyOptional.length > 0) {
126
+ const optionalNames = trulyOptional.map((p) => p.name).join(", ");
127
+ const shouldResolveOptional = await inquirer.prompt([
128
+ {
129
+ type: "confirm",
130
+ name: "resolveOptional",
131
+ message: `Would you like to be prompted for optional parameters (${optionalNames})?`,
132
+ default: false,
133
+ },
134
+ ]);
135
+
136
+ if (shouldResolveOptional.resolveOptional) {
137
+ // Resolve optional parameters using their resolvers
138
+ const optionalParamNames = trulyOptional.map((p) => p.name);
139
+ const optionalResolutionOrder =
140
+ getResolutionOrderForParams(optionalParamNames);
141
+
142
+ const orderedOptionalParams = optionalResolutionOrder
143
+ .map((paramName) => trulyOptional.find((p) => p.name === paramName))
144
+ .filter((param): param is ResolvableParameter => param !== undefined);
145
+
146
+ for (const param of orderedOptionalParams) {
147
+ try {
148
+ const value = await this.resolveParameter(param, context);
149
+ this.setNestedValue(resolvedParams, param.path, value);
150
+
151
+ // Update context with newly resolved value
152
+ context.resolvedParams = resolvedParams;
153
+ } catch (error) {
154
+ if (this.isUserCancellation(error)) {
155
+ console.log(chalk.yellow("\n\nOperation cancelled by user"));
156
+ process.exit(0);
157
+ }
158
+ console.error(chalk.red(`Failed to resolve ${param.name}:`), error);
159
+ throw error;
160
+ }
161
+ }
162
+ }
163
+ }
164
+
165
+ // 3. Validate final parameters
101
166
  const finalResult = schema.safeParse(resolvedParams);
102
167
 
103
168
  if (!finalResult.success) {
@@ -110,410 +175,230 @@ export class SchemaParameterResolver {
110
175
  return finalResult.data;
111
176
  }
112
177
 
113
- private findAllResolvableParameters(
178
+ private extractParametersFromSchema(
114
179
  schema: z.ZodSchema,
115
180
  ): ResolvableParameter[] {
116
- const resolvable: ResolvableParameter[] = [];
117
- this.analyzeSchema(schema, [], resolvable);
118
- return resolvable;
119
- }
181
+ const parameters: ResolvableParameter[] = [];
120
182
 
121
- private findResolvableParameters(
122
- schema: z.ZodSchema,
123
- error: z.ZodError,
124
- providedParams: any,
125
- ): ResolvableParameter[] {
126
- const resolvable = this.findAllResolvableParameters(schema);
127
-
128
- // Filter to only include parameters that are actually missing/invalid
129
- const errorPaths = new Set(
130
- error.issues.map((issue) => issue.path.join(".")),
131
- );
132
-
133
- return resolvable.filter((param) => {
134
- const paramPath = param.path.join(".");
135
- const isErrorPath = errorPaths.has(paramPath);
136
- const hasValue =
137
- this.getNestedValue(providedParams, param.path) !== undefined;
138
-
139
- // Include if there's an error for this path or if it's required and missing
140
- // Also include optional parameters that have resolvers and aren't provided
141
- const shouldResolve =
142
- isErrorPath ||
143
- (param.isRequired && !hasValue) ||
144
- (!param.isRequired && !hasValue && param.resolverMeta?.resolver);
145
-
146
- return shouldResolve;
147
- });
148
- }
149
-
150
- private analyzeSchema(
151
- schema: z.ZodType,
152
- currentPath: string[],
153
- resolvable: ResolvableParameter[],
154
- ): void {
155
- // Handle different Zod types
183
+ // Only handle ZodObject at the top level
156
184
  if (schema instanceof z.ZodObject) {
157
185
  const shape = schema.shape;
158
-
159
- Object.entries(shape).forEach(([key, fieldSchema]) => {
160
- const fieldPath = [...currentPath, key];
161
- this.analyzeSchemaField(
162
- key,
163
- fieldSchema as z.ZodType,
164
- fieldPath,
165
- resolvable,
166
- );
167
- });
168
- } else if (schema instanceof z.ZodOptional) {
169
- this.analyzeSchema(schema.unwrap(), currentPath, resolvable);
170
- } else if (schema instanceof z.ZodDefault) {
171
- this.analyzeSchema(schema.removeDefault(), currentPath, resolvable);
186
+ for (const [key, fieldSchema] of Object.entries<z.ZodSchema>(shape)) {
187
+ const param = this.analyzeFieldSchema(key, fieldSchema);
188
+ if (param) {
189
+ parameters.push(param);
190
+ }
191
+ }
172
192
  }
193
+
194
+ return parameters;
173
195
  }
174
196
 
175
- private analyzeSchemaField(
176
- name: string,
177
- schema: z.ZodType,
178
- path: string[],
179
- resolvable: ResolvableParameter[],
180
- ): void {
181
- const resolverMeta = (schema._def as any).resolverMeta;
182
-
183
- if (resolverMeta?.resolver) {
184
- resolvable.push({
185
- name,
186
- path,
187
- schema,
188
- description: schema.description,
189
- resolverMeta,
190
- isRequired: !schema.isOptional(),
191
- });
197
+ private analyzeFieldSchema(
198
+ fieldName: string,
199
+ fieldSchema: z.ZodSchema,
200
+ ): ResolvableParameter | null {
201
+ let baseSchema = fieldSchema;
202
+ let isRequired = true;
203
+
204
+ // Check if field is optional or has default
205
+ if (baseSchema instanceof z.ZodOptional) {
206
+ isRequired = false;
207
+ baseSchema = baseSchema._def.innerType;
192
208
  }
193
209
 
194
- // Recursively analyze nested objects
195
- if (schema instanceof z.ZodObject) {
196
- this.analyzeSchema(schema, path, resolvable);
197
- } else if (schema instanceof z.ZodOptional) {
198
- this.analyzeSchemaField(name, schema.unwrap(), path, resolvable);
199
- } else if (schema instanceof z.ZodDefault) {
200
- this.analyzeSchemaField(name, schema.removeDefault(), path, resolvable);
210
+ if (baseSchema instanceof z.ZodDefault) {
211
+ isRequired = false;
212
+ baseSchema = baseSchema._def.innerType;
201
213
  }
202
- }
203
-
204
- private sortByDependencies(
205
- parameters: ResolvableParameter[],
206
- ): ResolvableParameter[] {
207
- // Simple topological sort based on 'depends' field
208
- const sorted: ResolvableParameter[] = [];
209
- const remaining = [...parameters];
210
-
211
- while (remaining.length > 0) {
212
- const canResolve = remaining.filter((param) => {
213
- const depends = param.resolverMeta?.resolver?.depends || [];
214
- return depends.every((dep: string) =>
215
- sorted.some((resolved) => resolved.name === dep),
216
- );
217
- });
218
-
219
- if (canResolve.length === 0) {
220
- // No more resolvable parameters, add remaining ones (circular dependency or no dependencies)
221
- sorted.push(...remaining);
222
- break;
223
- }
224
214
 
225
- sorted.push(...canResolve);
226
- canResolve.forEach((param) => {
227
- const index = remaining.indexOf(param);
228
- remaining.splice(index, 1);
229
- });
230
- }
215
+ return this.createResolvableParameter([fieldName], baseSchema, isRequired);
216
+ }
231
217
 
232
- return sorted;
218
+ private createResolvableParameter(
219
+ path: string[],
220
+ schema: z.ZodSchema,
221
+ isRequired: boolean,
222
+ ): ResolvableParameter | null {
223
+ if (path.length === 0) return null;
224
+
225
+ const name = path[path.length - 1];
226
+
227
+ return {
228
+ name,
229
+ path,
230
+ schema,
231
+ description: schema.description,
232
+ isRequired,
233
+ };
233
234
  }
234
235
 
235
236
  private async resolveParameter(
236
- paramInfo: ResolvableParameter,
237
+ param: ResolvableParameter,
237
238
  context: ResolverContext,
238
239
  ): Promise<any> {
239
- const resolver = paramInfo.resolverMeta?.resolver;
240
-
240
+ const resolver = getResolver(param.name);
241
241
  if (!resolver) {
242
- return await this.promptStaticInput(paramInfo);
242
+ throw new Error(`No resolver found for parameter: ${param.name}`);
243
243
  }
244
244
 
245
- if (resolver.type === "static") {
246
- return await this.promptStaticInput(paramInfo, resolver);
247
- }
248
-
249
- if (resolver.type === "dynamic") {
250
- return await this.resolveDynamicParameter(paramInfo, resolver, context);
251
- }
252
-
253
- if (resolver.type === "fields") {
254
- return await this.resolveFieldsParameter(paramInfo, resolver, context);
255
- }
256
-
257
- throw new Error(`Unknown resolver type: ${resolver.type}`);
258
- }
259
-
260
- private async promptStaticInput(
261
- paramInfo: ResolvableParameter,
262
- resolver?: any,
263
- ): Promise<any> {
264
- const promptConfig: any = {
265
- type:
266
- resolver?.inputType === "editor"
267
- ? "input"
268
- : resolver?.inputType || "input",
269
- name: "value",
270
- message: `Enter ${paramInfo.description || paramInfo.name}${paramInfo.isRequired ? "" : " (optional)"}:`,
271
- };
272
-
273
- // Only use placeholder as default for required parameters
274
- if (resolver?.placeholder && paramInfo.isRequired) {
275
- promptConfig.default = resolver.placeholder;
276
- }
245
+ console.log(chalk.blue(`\n🔍 Resolving ${param.name}...`));
277
246
 
278
- // Add validation for required parameters
279
- if (paramInfo.isRequired) {
280
- promptConfig.validate = (input: any) => {
281
- if (!input || (typeof input === "string" && input.trim() === "")) {
282
- return "This field is required. Please enter a value.";
283
- }
284
- return true;
247
+ if (resolver.type === "static") {
248
+ // Static resolver - just prompt for input
249
+ const promptConfig = {
250
+ type: resolver.inputType === "password" ? "password" : "input",
251
+ name: param.name,
252
+ message: `Enter ${param.name}:`,
253
+ ...(resolver.placeholder && { default: resolver.placeholder }),
285
254
  };
286
- }
287
255
 
288
- // For editor-like prompts, provide helpful instructions
289
- if (resolver?.inputType === "editor") {
290
- promptConfig.message += " (JSON format)";
291
- }
292
-
293
- try {
294
- const result = await inquirer.prompt([promptConfig]);
295
-
296
- // For optional parameters, treat empty input as undefined
297
- if (
298
- !paramInfo.isRequired &&
299
- (!result.value || result.value.trim() === "")
300
- ) {
301
- return undefined;
302
- }
256
+ const answers = await inquirer.prompt([promptConfig as any]);
257
+ return answers[param.name];
258
+ } else if (resolver.type === "dynamic") {
259
+ // Dynamic resolver - fetch options and prompt for selection
260
+ try {
261
+ console.log(chalk.gray(`Fetching options for ${param.name}...`));
262
+ const items = await resolver.fetch(context.sdk, context.resolvedParams);
303
263
 
304
- // Try to parse JSON if it looks like JSON
305
- if (
306
- typeof result.value === "string" &&
307
- result.value.trim().startsWith("{")
308
- ) {
309
- try {
310
- return JSON.parse(result.value);
311
- } catch {
312
- console.warn(
313
- chalk.yellow("Warning: Could not parse as JSON, using as string"),
314
- );
315
- return result.value;
264
+ if (!items || items.length === 0) {
265
+ throw new Error(`No options available for ${param.name}`);
316
266
  }
317
- }
318
-
319
- return result.value;
320
- } catch (error) {
321
- if (this.isUserCancellation(error)) {
322
- console.log(chalk.yellow("\n\nOperation cancelled by user"));
323
- process.exit(0);
324
- }
325
- throw error;
326
- }
327
- }
328
-
329
- private async resolveDynamicParameter(
330
- paramInfo: ResolvableParameter,
331
- resolver: any,
332
- context: ResolverContext,
333
- ): Promise<any> {
334
- console.log(chalk.gray(`Fetching options for ${paramInfo.name}...`));
335
-
336
- try {
337
- // Use the new fetch function approach
338
- const options = await resolver.fetch(context.sdk, context.resolvedParams);
339
-
340
- if (options.length === 0) {
341
- console.log(chalk.yellow(`No options available for ${paramInfo.name}`));
342
- return await this.promptStaticInput(paramInfo);
343
- }
344
-
345
- // Generate prompt configuration
346
- const promptConfig = resolver.prompt(options, context.resolvedParams);
347
267
 
348
- // Execute the prompt
349
- let result;
350
- try {
351
- result = await inquirer.prompt([promptConfig]);
268
+ const promptConfig = resolver.prompt(items, context.resolvedParams);
269
+ const answers = await inquirer.prompt([promptConfig as any]);
270
+ return answers[param.name];
352
271
  } catch (error) {
353
- if (this.isUserCancellation(error)) {
354
- console.log(chalk.yellow("\n\nOperation cancelled by user"));
355
- process.exit(0);
356
- }
272
+ console.error(
273
+ chalk.red(`Failed to fetch options for ${param.name}:`),
274
+ error instanceof Error ? error.message : error,
275
+ );
357
276
  throw error;
358
277
  }
359
- let value = result[promptConfig.name || "value"];
278
+ } else if ((resolver as any).type === "fields") {
279
+ // Fields resolver - fetch field definitions and prompt for each input
280
+ try {
281
+ console.log(chalk.gray(`Fetching input fields for ${param.name}...`));
282
+ const fields = await (resolver as any).fetch(
283
+ context.sdk,
284
+ context.resolvedParams,
285
+ );
360
286
 
361
- // Handle JSON parsing if the value looks like JSON
362
- if (typeof value === "string" && value.trim().startsWith("{")) {
363
- try {
364
- value = JSON.parse(value);
365
- } catch {
366
- console.warn(
367
- chalk.yellow("Warning: Could not parse as JSON, using as string"),
287
+ if (!fields || fields.length === 0) {
288
+ console.log(
289
+ chalk.yellow(`No input fields required for this action.`),
368
290
  );
291
+ return {};
369
292
  }
370
- }
371
293
 
372
- return value;
373
- } catch (error) {
374
- if (this.isUserCancellation(error)) {
375
- console.log(chalk.yellow("\n\nOperation cancelled by user"));
376
- process.exit(0);
377
- }
378
- if (error instanceof Error && error.message.includes("401")) {
379
- console.log(
380
- chalk.yellow(
381
- `⚠️ Invalid auth token, falling back to manual input for ${paramInfo.name}`,
382
- ),
383
- );
384
- } else {
385
- console.error(
386
- chalk.red(`Failed to fetch options for ${paramInfo.name}:`),
387
- error,
388
- );
389
- console.log(chalk.yellow("Falling back to manual input..."));
390
- }
391
- return await this.promptStaticInput(paramInfo);
392
- }
393
- }
294
+ const inputs: Record<string, any> = {};
394
295
 
395
- private async resolveFieldsParameter(
396
- paramInfo: ResolvableParameter,
397
- resolver: any,
398
- context: ResolverContext,
399
- ): Promise<any> {
400
- console.log(
401
- chalk.gray(`Fetching field definitions for ${paramInfo.name}...`),
402
- );
296
+ // Separate required and optional fields
297
+ const requiredFields = fields.filter((field: any) => field.required);
298
+ const optionalFields = fields.filter((field: any) => !field.required);
403
299
 
404
- try {
405
- // Fetch field definitions using the resolver's fetch function
406
- const fields = await resolver.fetch(context.sdk, context.resolvedParams);
407
-
408
- if (fields.length === 0) {
409
- console.log(chalk.yellow(`No input fields required for this action`));
410
- return {};
411
- }
412
-
413
- console.log(
414
- chalk.blue(
415
- `\nConfiguring inputs for ${context.resolvedParams.app} ${context.resolvedParams.type} ${context.resolvedParams.action}:`,
416
- ),
417
- );
418
-
419
- const inputs: Record<string, any> = {};
420
-
421
- // Separate required and optional fields
422
- const requiredFields = fields.filter((field: any) => field.required);
423
- const optionalFields = fields.filter((field: any) => !field.required);
424
-
425
- // First, prompt for all required fields
426
- if (requiredFields.length > 0) {
427
- console.log(
428
- chalk.cyan(`\nRequired fields (${requiredFields.length}):`),
429
- );
430
- for (const field of requiredFields) {
431
- await this.promptForField(field, inputs);
300
+ // First, prompt for all required fields
301
+ if (requiredFields.length > 0) {
302
+ console.log(
303
+ chalk.blue(
304
+ `\n📝 Please provide values for the following input fields:`,
305
+ ),
306
+ );
307
+ for (const field of requiredFields) {
308
+ await this.promptForField(field, inputs);
309
+ }
432
310
  }
433
- }
434
311
 
435
- // Then ask if user wants to configure optional fields
436
- if (optionalFields.length > 0) {
437
- console.log(
438
- chalk.gray(
439
- `\nThere are ${optionalFields.length} optional field(s) available.`,
440
- ),
441
- );
312
+ // Then ask if user wants to configure optional fields
313
+ if (optionalFields.length > 0) {
314
+ console.log(
315
+ chalk.gray(
316
+ `\nThere are ${optionalFields.length} optional field(s) available.`,
317
+ ),
318
+ );
442
319
 
443
- let shouldConfigureOptional;
444
- try {
445
- shouldConfigureOptional = await inquirer.prompt([
446
- {
447
- type: "confirm",
448
- name: "configure",
449
- message: "Would you like to configure optional fields?",
450
- default: false,
451
- },
452
- ]);
453
- } catch (error) {
454
- if (this.isUserCancellation(error)) {
455
- console.log(chalk.yellow("\n\nOperation cancelled by user"));
456
- process.exit(0);
320
+ let shouldConfigureOptional;
321
+ try {
322
+ shouldConfigureOptional = await inquirer.prompt([
323
+ {
324
+ type: "confirm",
325
+ name: "configure",
326
+ message: "Would you like to configure optional fields?",
327
+ default: false,
328
+ },
329
+ ]);
330
+ } catch (error) {
331
+ if (this.isUserCancellation(error)) {
332
+ console.log(chalk.yellow("\n\nOperation cancelled by user"));
333
+ process.exit(0);
334
+ }
335
+ throw error;
457
336
  }
458
- throw error;
459
- }
460
337
 
461
- if (shouldConfigureOptional.configure) {
462
- console.log(chalk.cyan(`\nOptional fields:`));
463
- for (const field of optionalFields) {
464
- await this.promptForField(field, inputs);
338
+ if (shouldConfigureOptional.configure) {
339
+ console.log(chalk.cyan(`\nOptional fields:`));
340
+ for (const field of optionalFields) {
341
+ await this.promptForField(field, inputs);
342
+ }
465
343
  }
466
344
  }
467
- }
468
345
 
469
- return inputs;
470
- } catch (error) {
471
- if (this.isUserCancellation(error)) {
472
- console.log(chalk.yellow("\n\nOperation cancelled by user"));
473
- process.exit(0);
346
+ return inputs;
347
+ } catch (error) {
348
+ console.error(
349
+ chalk.red(`Failed to fetch fields for ${param.name}:`),
350
+ error instanceof Error ? error.message : error,
351
+ );
352
+ throw error;
474
353
  }
475
- console.error(
476
- chalk.red(`Failed to fetch field definitions for ${paramInfo.name}:`),
477
- error,
478
- );
479
- console.log(chalk.yellow("Falling back to manual JSON input..."));
480
- return await this.promptStaticInput(paramInfo);
481
354
  }
355
+
356
+ throw new Error(`Unknown resolver type for ${param.name}`);
357
+ }
358
+
359
+ private getNestedValue(obj: any, path: string[]): any {
360
+ return path.reduce((current, key) => current?.[key], obj);
361
+ }
362
+
363
+ private setNestedValue(obj: any, path: string[], value: any): void {
364
+ const lastKey = path[path.length - 1];
365
+ const parent = path.slice(0, -1).reduce((current, key) => {
366
+ if (!(key in current)) {
367
+ current[key] = {};
368
+ }
369
+ return current[key];
370
+ }, obj);
371
+ parent[lastKey] = value;
482
372
  }
483
373
 
484
374
  private async promptForField(
485
375
  field: any,
486
376
  inputs: Record<string, any>,
487
377
  ): Promise<void> {
488
- const fieldPrompt: any = {
489
- type: "input",
378
+ const fieldPrompt = {
379
+ type: field.type === "boolean" ? "confirm" : "input",
490
380
  name: field.key,
491
381
  message: `${field.label || field.key}${field.required ? " (required)" : " (optional)"}:`,
382
+ ...(field.helpText && { prefix: chalk.gray(`ℹ ${field.helpText}\n`) }),
383
+ ...(field.default && { default: field.default }),
492
384
  };
493
385
 
494
- // Add description/help text if available
495
- if (field.help_text || field.description) {
496
- fieldPrompt.message += `\n ${chalk.dim(field.help_text || field.description)}`;
386
+ if (field.choices && field.choices.length > 0) {
387
+ fieldPrompt.type = "list";
388
+ (fieldPrompt as any).choices = field.choices.map((choice: any) => ({
389
+ name: choice.label || choice.value,
390
+ value: choice.value,
391
+ }));
497
392
  }
498
393
 
499
- // Set default value
500
- if (field.default !== undefined) {
501
- fieldPrompt.default = field.default;
502
- }
503
-
504
- // Add validation for required fields
505
- if (field.required) {
506
- fieldPrompt.validate = (input: any) => {
507
- if (!input || (typeof input === "string" && input.trim() === "")) {
508
- return "This field is required. Please enter a value.";
509
- }
510
- return true;
511
- };
512
- }
513
-
514
- let result;
515
394
  try {
516
- result = await inquirer.prompt([fieldPrompt]);
395
+ const answer = await inquirer.prompt([fieldPrompt as any]);
396
+
397
+ if (answer[field.key] !== undefined && answer[field.key] !== "") {
398
+ inputs[field.key] = answer[field.key];
399
+ } else if (field.required) {
400
+ throw new Error(`Required field ${field.key} cannot be empty`);
401
+ }
517
402
  } catch (error) {
518
403
  if (this.isUserCancellation(error)) {
519
404
  console.log(chalk.yellow("\n\nOperation cancelled by user"));
@@ -521,53 +406,13 @@ export class SchemaParameterResolver {
521
406
  }
522
407
  throw error;
523
408
  }
524
-
525
- // Only include non-empty values
526
- if (result[field.key] !== undefined && result[field.key] !== "") {
527
- let value = result[field.key];
528
-
529
- // Try to parse as appropriate type
530
- if (field.type === "integer" && typeof value === "string") {
531
- const parsed = parseInt(value, 10);
532
- if (!isNaN(parsed)) value = parsed;
533
- } else if (field.type === "number" && typeof value === "string") {
534
- const parsed = parseFloat(value);
535
- if (!isNaN(parsed)) value = parsed;
536
- } else if (field.type === "boolean" && typeof value === "string") {
537
- value = value.toLowerCase() === "true" || value === "1";
538
- }
539
-
540
- inputs[field.key] = value;
541
- }
542
- }
543
-
544
- private getNestedValue(obj: any, path: string[]): any {
545
- return path.reduce((current, key) => current?.[key], obj);
546
- }
547
-
548
- private setNestedValue(obj: any, path: string[], value: any): void {
549
- const lastKey = path[path.length - 1];
550
- const parentPath = path.slice(0, -1);
551
-
552
- const parent = parentPath.reduce((current, key) => {
553
- if (!current[key]) {
554
- current[key] = {};
555
- }
556
- return current[key];
557
- }, obj);
558
-
559
- parent[lastKey] = value;
560
409
  }
561
410
 
562
411
  private isUserCancellation(error: any): boolean {
563
- // Check for various ways user cancellation can be detected
564
412
  return (
565
- error &&
566
- (error.name === "ExitPromptError" ||
567
- error.message?.includes("User force closed the prompt") ||
568
- error.message?.includes("SIGINT") ||
569
- error.code === "SIGINT" ||
570
- error.signal === "SIGINT")
413
+ error?.name === "ExitPromptError" ||
414
+ error?.message?.includes("User force closed") ||
415
+ error?.isTTYError
571
416
  );
572
417
  }
573
418
  }