@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.
- package/dist/cli.js +1 -3
- package/dist/utils/cli-generator.d.ts +2 -3
- package/dist/utils/cli-generator.js +98 -124
- package/dist/utils/parameter-resolver.d.ts +6 -11
- package/dist/utils/parameter-resolver.js +223 -329
- package/dist/utils/schema-formatter.js +8 -3
- package/package.json +5 -3
- package/src/cli.ts +3 -6
- package/src/utils/cli-generator.ts +123 -161
- package/src/utils/parameter-resolver.ts +277 -432
- package/src/utils/schema-formatter.ts +23 -5
- package/test/cli.test.ts +16 -16
- package/tsconfig.json +1 -1
- package/dist/commands/action.d.ts +0 -2
- package/dist/commands/action.js +0 -295
- package/dist/commands/browse.d.ts +0 -2
- package/dist/commands/browse.js +0 -257
- package/dist/commands/bundle.d.ts +0 -2
- package/dist/commands/bundle.js +0 -101
- package/dist/commands/generate.d.ts +0 -9
- package/dist/commands/generate.js +0 -281
- package/dist/utils/auth-picker.d.ts +0 -17
- package/dist/utils/auth-picker.js +0 -121
- package/dist/utils/schema-generator.d.ts +0 -4
- package/dist/utils/schema-generator.js +0 -389
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import inquirer from "inquirer";
|
|
2
2
|
import chalk from "chalk";
|
|
3
3
|
import { z } from "zod";
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
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:
|
|
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:
|
|
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
|
-
//
|
|
41
|
-
const
|
|
42
|
-
const
|
|
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
|
-
|
|
46
|
-
return !hasValue && param.resolverMeta?.resolver;
|
|
52
|
+
return !hasValue;
|
|
47
53
|
});
|
|
48
54
|
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
54
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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 (
|
|
68
|
-
// No
|
|
69
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
81
|
-
|
|
95
|
+
if (functionallyRequired.length > 0) {
|
|
96
|
+
const requiredParamNames = functionallyRequired.map((p) => p.name);
|
|
97
|
+
const requiredResolutionOrder =
|
|
98
|
+
getResolutionOrderForParams(requiredParamNames);
|
|
82
99
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
//
|
|
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
|
|
178
|
+
private extractParametersFromSchema(
|
|
114
179
|
schema: z.ZodSchema,
|
|
115
180
|
): ResolvableParameter[] {
|
|
116
|
-
const
|
|
117
|
-
this.analyzeSchema(schema, [], resolvable);
|
|
118
|
-
return resolvable;
|
|
119
|
-
}
|
|
181
|
+
const parameters: ResolvableParameter[] = [];
|
|
120
182
|
|
|
121
|
-
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
if (
|
|
184
|
-
|
|
185
|
-
|
|
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
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
-
|
|
226
|
-
|
|
227
|
-
const index = remaining.indexOf(param);
|
|
228
|
-
remaining.splice(index, 1);
|
|
229
|
-
});
|
|
230
|
-
}
|
|
215
|
+
return this.createResolvableParameter([fieldName], baseSchema, isRequired);
|
|
216
|
+
}
|
|
231
217
|
|
|
232
|
-
|
|
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
|
-
|
|
237
|
+
param: ResolvableParameter,
|
|
237
238
|
context: ResolverContext,
|
|
238
239
|
): Promise<any> {
|
|
239
|
-
const resolver =
|
|
240
|
-
|
|
240
|
+
const resolver = getResolver(param.name);
|
|
241
241
|
if (!resolver) {
|
|
242
|
-
|
|
242
|
+
throw new Error(`No resolver found for parameter: ${param.name}`);
|
|
243
243
|
}
|
|
244
244
|
|
|
245
|
-
|
|
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
|
-
|
|
279
|
-
|
|
280
|
-
promptConfig
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
}
|
|
284
|
-
|
|
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
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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
|
-
|
|
305
|
-
|
|
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
|
-
|
|
349
|
-
|
|
350
|
-
|
|
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
|
-
|
|
354
|
-
|
|
355
|
-
|
|
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
|
-
|
|
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
|
-
|
|
362
|
-
|
|
363
|
-
|
|
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
|
-
|
|
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
|
-
|
|
396
|
-
|
|
397
|
-
|
|
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
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
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
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
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
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
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
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
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
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
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
|
|
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
|
-
|
|
495
|
-
|
|
496
|
-
fieldPrompt
|
|
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
|
-
|
|
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
|
-
(
|
|
567
|
-
|
|
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
|
}
|