@zapier/zapier-sdk-cli 0.4.1 → 0.4.2
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/CHANGELOG.md +8 -0
- package/dist/cli.js +961 -284
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +28 -0
- package/dist/src/commands/bundle-code/cli.d.ts +2 -0
- package/dist/src/commands/bundle-code/cli.js +77 -0
- package/dist/src/commands/bundle-code/index.d.ts +5 -0
- package/dist/src/commands/bundle-code/index.js +62 -0
- package/dist/src/commands/bundle-code/schemas.d.ts +24 -0
- package/dist/src/commands/bundle-code/schemas.js +19 -0
- package/dist/src/commands/configPath.d.ts +2 -0
- package/dist/src/commands/configPath.js +9 -0
- package/dist/src/commands/generate-types/cli.d.ts +2 -0
- package/dist/src/commands/generate-types/cli.js +73 -0
- package/dist/src/commands/generate-types/index.d.ts +8 -0
- package/dist/src/commands/generate-types/index.js +273 -0
- package/dist/src/commands/generate-types/schemas.d.ts +18 -0
- package/dist/src/commands/generate-types/schemas.js +11 -0
- package/dist/src/commands/index.d.ts +6 -0
- package/dist/src/commands/index.js +6 -0
- package/dist/src/commands/login.d.ts +2 -0
- package/dist/src/commands/login.js +25 -0
- package/dist/src/commands/logout.d.ts +2 -0
- package/dist/src/commands/logout.js +16 -0
- package/dist/src/commands/mcp.d.ts +2 -0
- package/dist/src/commands/mcp.js +11 -0
- package/dist/src/index.d.ts +0 -0
- package/dist/src/index.js +3 -0
- package/dist/src/utils/api/client.d.ts +15 -0
- package/dist/src/utils/api/client.js +27 -0
- package/dist/src/utils/auth/login.d.ts +2 -0
- package/dist/src/utils/auth/login.js +134 -0
- package/dist/src/utils/cli-generator-utils.d.ts +13 -0
- package/dist/src/utils/cli-generator-utils.js +116 -0
- package/dist/src/utils/cli-generator.d.ts +3 -0
- package/dist/src/utils/cli-generator.js +443 -0
- package/dist/src/utils/constants.d.ts +5 -0
- package/dist/src/utils/constants.js +6 -0
- package/dist/src/utils/getCallablePromise.d.ts +6 -0
- package/dist/src/utils/getCallablePromise.js +14 -0
- package/dist/src/utils/log.d.ts +7 -0
- package/dist/src/utils/log.js +16 -0
- package/dist/src/utils/parameter-resolver.d.ts +14 -0
- package/dist/src/utils/parameter-resolver.js +387 -0
- package/dist/src/utils/schema-formatter.d.ts +2 -0
- package/dist/src/utils/schema-formatter.js +71 -0
- package/dist/src/utils/serializeAsync.d.ts +2 -0
- package/dist/src/utils/serializeAsync.js +16 -0
- package/dist/src/utils/spinner.d.ts +1 -0
- package/dist/src/utils/spinner.js +13 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +5 -3
- package/src/cli.test.ts +15 -0
- package/src/cli.ts +9 -3
- package/src/commands/bundle-code/cli.ts +103 -0
- package/src/commands/bundle-code/index.ts +91 -0
- package/src/commands/bundle-code/schemas.ts +24 -0
- package/src/commands/generate-types/cli.ts +110 -0
- package/src/commands/generate-types/index.ts +365 -0
- package/src/commands/generate-types/schemas.ts +23 -0
- package/src/commands/index.ts +3 -1
- package/src/commands/mcp.ts +14 -0
- package/src/utils/cli-generator-utils.ts +157 -0
- package/src/utils/cli-generator.ts +148 -91
- package/src/utils/parameter-resolver.ts +217 -85
- package/src/utils/schema-formatter.ts +1 -1
- package/tsconfig.json +3 -5
- package/src/commands/whoami.ts +0 -25
- package/src/utils/pager.ts +0 -202
- package/test/cli.test.ts +0 -46
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
import inquirer from "inquirer";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { getResolver, hasResolver, getResolutionOrderForParams, } from "@zapier/zapier-sdk";
|
|
5
|
+
// ============================================================================
|
|
6
|
+
// Schema Parameter Resolver
|
|
7
|
+
// ============================================================================
|
|
8
|
+
export class SchemaParameterResolver {
|
|
9
|
+
async resolveParameters(schema, providedParams, sdk) {
|
|
10
|
+
// 1. Try to parse with current parameters
|
|
11
|
+
const parseResult = schema.safeParse(providedParams);
|
|
12
|
+
// Get all schema parameters to check which ones have resolvers
|
|
13
|
+
const allParams = this.extractParametersFromSchema(schema);
|
|
14
|
+
const resolvableParams = allParams.filter((param) => hasResolver(param.name));
|
|
15
|
+
// Get all missing parameters that have resolvers
|
|
16
|
+
const missingResolvable = resolvableParams.filter((param) => {
|
|
17
|
+
const hasValue = this.getNestedValue(providedParams, param.path) !== undefined;
|
|
18
|
+
return !hasValue;
|
|
19
|
+
});
|
|
20
|
+
// Determine parameter resolution categories:
|
|
21
|
+
// - functionally required: must be provided (inputs)
|
|
22
|
+
// - always prompt: should be prompted for but can be skipped (authenticationId)
|
|
23
|
+
// - truly optional: only ask if user wants to be prompted
|
|
24
|
+
const functionallyRequired = missingResolvable.filter((param) => {
|
|
25
|
+
// Schema-required parameters are always functionally required
|
|
26
|
+
if (param.isRequired)
|
|
27
|
+
return true;
|
|
28
|
+
// Only inputs is functionally required for run-action
|
|
29
|
+
if (param.name === "inputs") {
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
return false;
|
|
33
|
+
});
|
|
34
|
+
// Parameters that should always be prompted for directly, but can be skipped
|
|
35
|
+
const alwaysPrompt = missingResolvable.filter((param) => {
|
|
36
|
+
if (functionallyRequired.includes(param))
|
|
37
|
+
return false;
|
|
38
|
+
// authenticationId should always be prompted for (since it's usually needed)
|
|
39
|
+
// but can be skipped with "Continue without authentication"
|
|
40
|
+
if (param.name === "authenticationId") {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
});
|
|
45
|
+
const trulyOptional = missingResolvable.filter((param) => !functionallyRequired.includes(param) && !alwaysPrompt.includes(param));
|
|
46
|
+
if (parseResult.success &&
|
|
47
|
+
functionallyRequired.length === 0 &&
|
|
48
|
+
alwaysPrompt.length === 0) {
|
|
49
|
+
return parseResult.data;
|
|
50
|
+
}
|
|
51
|
+
if (functionallyRequired.length === 0 && alwaysPrompt.length === 0) {
|
|
52
|
+
// No functionally required parameters missing, but check if we can parse
|
|
53
|
+
if (!parseResult.success) {
|
|
54
|
+
throw parseResult.error;
|
|
55
|
+
}
|
|
56
|
+
return parseResult.data;
|
|
57
|
+
}
|
|
58
|
+
// 2. Resolve functionally required parameters and their dependencies first
|
|
59
|
+
const resolvedParams = { ...providedParams };
|
|
60
|
+
const context = {
|
|
61
|
+
sdk,
|
|
62
|
+
currentParams: providedParams,
|
|
63
|
+
resolvedParams,
|
|
64
|
+
};
|
|
65
|
+
if (functionallyRequired.length > 0) {
|
|
66
|
+
const requiredParamNames = functionallyRequired.map((p) => p.name);
|
|
67
|
+
const requiredResolutionOrder = getResolutionOrderForParams(requiredParamNames);
|
|
68
|
+
// Find all parameters that need to be resolved (including dependencies)
|
|
69
|
+
// from the available resolvable parameters
|
|
70
|
+
const orderedRequiredParams = requiredResolutionOrder
|
|
71
|
+
.map((paramName) => {
|
|
72
|
+
// First try to find in functionally required
|
|
73
|
+
let param = functionallyRequired.find((p) => p.name === paramName);
|
|
74
|
+
// If not found, try always prompt (for dependencies like authenticationId)
|
|
75
|
+
if (!param) {
|
|
76
|
+
param = alwaysPrompt.find((p) => p.name === paramName);
|
|
77
|
+
}
|
|
78
|
+
// If not found, try truly optional (for other dependencies)
|
|
79
|
+
if (!param) {
|
|
80
|
+
param = trulyOptional.find((p) => p.name === paramName);
|
|
81
|
+
}
|
|
82
|
+
return param;
|
|
83
|
+
})
|
|
84
|
+
.filter((param) => param !== undefined);
|
|
85
|
+
for (const param of orderedRequiredParams) {
|
|
86
|
+
try {
|
|
87
|
+
const value = await this.resolveParameter(param, context);
|
|
88
|
+
this.setNestedValue(resolvedParams, param.path, value);
|
|
89
|
+
// Update context with newly resolved value
|
|
90
|
+
context.resolvedParams = resolvedParams;
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
if (this.isUserCancellation(error)) {
|
|
94
|
+
console.log(chalk.yellow("\n\nOperation cancelled by user"));
|
|
95
|
+
process.exit(0);
|
|
96
|
+
}
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// Remove resolved dependencies from other categories to avoid double-prompting
|
|
101
|
+
const resolvedParamNames = new Set(orderedRequiredParams.map((p) => p.name));
|
|
102
|
+
alwaysPrompt.splice(0, alwaysPrompt.length, ...alwaysPrompt.filter((p) => !resolvedParamNames.has(p.name)));
|
|
103
|
+
trulyOptional.splice(0, trulyOptional.length, ...trulyOptional.filter((p) => !resolvedParamNames.has(p.name)));
|
|
104
|
+
}
|
|
105
|
+
// 3. Resolve parameters that should always be prompted for (but can be skipped)
|
|
106
|
+
if (alwaysPrompt.length > 0) {
|
|
107
|
+
const alwaysPromptNames = alwaysPrompt.map((p) => p.name);
|
|
108
|
+
const alwaysPromptResolutionOrder = getResolutionOrderForParams(alwaysPromptNames);
|
|
109
|
+
const orderedAlwaysPromptParams = alwaysPromptResolutionOrder
|
|
110
|
+
.map((paramName) => alwaysPrompt.find((p) => p.name === paramName))
|
|
111
|
+
.filter((param) => param !== undefined);
|
|
112
|
+
for (const param of orderedAlwaysPromptParams) {
|
|
113
|
+
try {
|
|
114
|
+
const value = await this.resolveParameter(param, context);
|
|
115
|
+
this.setNestedValue(resolvedParams, param.path, value);
|
|
116
|
+
// Update context with newly resolved value
|
|
117
|
+
context.resolvedParams = resolvedParams;
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
if (this.isUserCancellation(error)) {
|
|
121
|
+
console.log(chalk.yellow("\n\nOperation cancelled by user"));
|
|
122
|
+
process.exit(0);
|
|
123
|
+
}
|
|
124
|
+
throw error;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// 4. Ask user if they want to resolve truly optional parameters
|
|
129
|
+
if (trulyOptional.length > 0) {
|
|
130
|
+
const optionalNames = trulyOptional.map((p) => p.name).join(", ");
|
|
131
|
+
const shouldResolveOptional = await inquirer.prompt([
|
|
132
|
+
{
|
|
133
|
+
type: "confirm",
|
|
134
|
+
name: "resolveOptional",
|
|
135
|
+
message: `Would you like to be prompted for optional parameters (${optionalNames})?`,
|
|
136
|
+
default: false,
|
|
137
|
+
},
|
|
138
|
+
]);
|
|
139
|
+
if (shouldResolveOptional.resolveOptional) {
|
|
140
|
+
// Resolve optional parameters using their resolvers
|
|
141
|
+
const optionalParamNames = trulyOptional.map((p) => p.name);
|
|
142
|
+
const optionalResolutionOrder = getResolutionOrderForParams(optionalParamNames);
|
|
143
|
+
const orderedOptionalParams = optionalResolutionOrder
|
|
144
|
+
.map((paramName) => trulyOptional.find((p) => p.name === paramName))
|
|
145
|
+
.filter((param) => param !== undefined);
|
|
146
|
+
for (const param of orderedOptionalParams) {
|
|
147
|
+
try {
|
|
148
|
+
const value = await this.resolveParameter(param, context);
|
|
149
|
+
this.setNestedValue(resolvedParams, param.path, value);
|
|
150
|
+
// Update context with newly resolved value
|
|
151
|
+
context.resolvedParams = resolvedParams;
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
if (this.isUserCancellation(error)) {
|
|
155
|
+
console.log(chalk.yellow("\n\nOperation cancelled by user"));
|
|
156
|
+
process.exit(0);
|
|
157
|
+
}
|
|
158
|
+
throw error;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// 3. Validate final parameters
|
|
164
|
+
const finalResult = schema.safeParse(resolvedParams);
|
|
165
|
+
if (!finalResult.success) {
|
|
166
|
+
console.error(chalk.red("❌ Parameter validation failed after resolution:"));
|
|
167
|
+
throw finalResult.error;
|
|
168
|
+
}
|
|
169
|
+
return finalResult.data;
|
|
170
|
+
}
|
|
171
|
+
extractParametersFromSchema(schema) {
|
|
172
|
+
const parameters = [];
|
|
173
|
+
// Only handle ZodObject at the top level
|
|
174
|
+
if (schema instanceof z.ZodObject) {
|
|
175
|
+
const shape = schema.shape;
|
|
176
|
+
for (const [key, fieldSchema] of Object.entries(shape)) {
|
|
177
|
+
const param = this.analyzeFieldSchema(key, fieldSchema);
|
|
178
|
+
if (param) {
|
|
179
|
+
parameters.push(param);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return parameters;
|
|
184
|
+
}
|
|
185
|
+
analyzeFieldSchema(fieldName, fieldSchema) {
|
|
186
|
+
let baseSchema = fieldSchema;
|
|
187
|
+
let isRequired = true;
|
|
188
|
+
// Check if field is optional or has default
|
|
189
|
+
if (baseSchema instanceof z.ZodOptional) {
|
|
190
|
+
isRequired = false;
|
|
191
|
+
baseSchema = baseSchema._def.innerType;
|
|
192
|
+
}
|
|
193
|
+
if (baseSchema instanceof z.ZodDefault) {
|
|
194
|
+
isRequired = false;
|
|
195
|
+
baseSchema = baseSchema._def.innerType;
|
|
196
|
+
}
|
|
197
|
+
return this.createResolvableParameter([fieldName], baseSchema, isRequired);
|
|
198
|
+
}
|
|
199
|
+
createResolvableParameter(path, schema, isRequired) {
|
|
200
|
+
if (path.length === 0)
|
|
201
|
+
return null;
|
|
202
|
+
const name = path[path.length - 1];
|
|
203
|
+
return {
|
|
204
|
+
name,
|
|
205
|
+
path,
|
|
206
|
+
schema,
|
|
207
|
+
description: schema.description,
|
|
208
|
+
isRequired,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
async resolveParameter(param, context) {
|
|
212
|
+
const resolver = getResolver(param.name);
|
|
213
|
+
if (!resolver) {
|
|
214
|
+
throw new Error(`No resolver found for parameter: ${param.name}`);
|
|
215
|
+
}
|
|
216
|
+
console.log(chalk.blue(`\n🔍 Resolving ${param.name}...`));
|
|
217
|
+
if (resolver.type === "static") {
|
|
218
|
+
// Static resolver - just prompt for input
|
|
219
|
+
const promptConfig = {
|
|
220
|
+
type: resolver.inputType === "password" ? "password" : "input",
|
|
221
|
+
name: param.name,
|
|
222
|
+
message: `Enter ${param.name}:`,
|
|
223
|
+
...(resolver.placeholder && { default: resolver.placeholder }),
|
|
224
|
+
};
|
|
225
|
+
const answers = await inquirer.prompt([promptConfig]);
|
|
226
|
+
return answers[param.name];
|
|
227
|
+
}
|
|
228
|
+
else if (resolver.type === "dynamic") {
|
|
229
|
+
// Dynamic resolver - fetch options and prompt for selection
|
|
230
|
+
try {
|
|
231
|
+
// Only show "Fetching..." for required parameters that typically have many options
|
|
232
|
+
if (param.isRequired && param.name !== "authenticationId") {
|
|
233
|
+
console.log(chalk.gray(`Fetching options for ${param.name}...`));
|
|
234
|
+
}
|
|
235
|
+
const items = await resolver.fetch(context.sdk, context.resolvedParams);
|
|
236
|
+
// Let the resolver's prompt handle empty lists (e.g., authenticationId can show "skip authentication")
|
|
237
|
+
const safeItems = items || [];
|
|
238
|
+
const promptConfig = resolver.prompt(safeItems, context.resolvedParams);
|
|
239
|
+
const answers = await inquirer.prompt([promptConfig]);
|
|
240
|
+
return answers[param.name];
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
// Let the main CLI error handler display user-friendly errors
|
|
244
|
+
throw error;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
else if (resolver.type === "fields") {
|
|
248
|
+
// Fields resolver - fetch field definitions and prompt for each input with recursive field resolution
|
|
249
|
+
return await this.resolveFieldsRecursively(resolver, context, param);
|
|
250
|
+
}
|
|
251
|
+
throw new Error(`Unknown resolver type for ${param.name}`);
|
|
252
|
+
}
|
|
253
|
+
async resolveFieldsRecursively(resolver, context, param) {
|
|
254
|
+
const inputs = {};
|
|
255
|
+
let processedFieldKeys = new Set();
|
|
256
|
+
let iteration = 0;
|
|
257
|
+
const maxIterations = 5; // Prevent infinite loops
|
|
258
|
+
while (iteration < maxIterations) {
|
|
259
|
+
iteration++;
|
|
260
|
+
// Update context with current inputs so they're passed to listInputFields
|
|
261
|
+
const updatedContext = {
|
|
262
|
+
...context,
|
|
263
|
+
resolvedParams: {
|
|
264
|
+
...context.resolvedParams,
|
|
265
|
+
inputs,
|
|
266
|
+
},
|
|
267
|
+
};
|
|
268
|
+
console.log(chalk.gray(`Fetching input fields for ${param.name}${iteration > 1 ? ` (iteration ${iteration})` : ""}...`));
|
|
269
|
+
const fields = await resolver.fetch(updatedContext.sdk, updatedContext.resolvedParams);
|
|
270
|
+
if (!fields || fields.length === 0) {
|
|
271
|
+
if (iteration === 1) {
|
|
272
|
+
console.log(chalk.yellow(`No input fields required for this action.`));
|
|
273
|
+
}
|
|
274
|
+
break;
|
|
275
|
+
}
|
|
276
|
+
// Find new fields that we haven't processed yet
|
|
277
|
+
const newFields = fields.filter((field) => !processedFieldKeys.has(field.key));
|
|
278
|
+
if (newFields.length === 0) {
|
|
279
|
+
// No new fields, we're done
|
|
280
|
+
break;
|
|
281
|
+
}
|
|
282
|
+
// Separate new required and optional fields
|
|
283
|
+
const newRequiredFields = newFields.filter((field) => field.required);
|
|
284
|
+
const newOptionalFields = newFields.filter((field) => !field.required);
|
|
285
|
+
// Prompt for new required fields
|
|
286
|
+
if (newRequiredFields.length > 0) {
|
|
287
|
+
console.log(chalk.blue(`\n📝 Please provide values for the following ${iteration === 1 ? "" : "additional "}input fields:`));
|
|
288
|
+
for (const field of newRequiredFields) {
|
|
289
|
+
await this.promptForField(field, inputs);
|
|
290
|
+
processedFieldKeys.add(field.key);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
// Ask about optional fields
|
|
294
|
+
let shouldConfigureOptional = { configure: false };
|
|
295
|
+
if (newOptionalFields.length > 0) {
|
|
296
|
+
console.log(chalk.gray(`\nThere are ${newOptionalFields.length} ${iteration === 1 ? "" : "additional "}optional field(s) available.`));
|
|
297
|
+
try {
|
|
298
|
+
shouldConfigureOptional = await inquirer.prompt([
|
|
299
|
+
{
|
|
300
|
+
type: "confirm",
|
|
301
|
+
name: "configure",
|
|
302
|
+
message: `Would you like to configure ${iteration === 1 ? "" : "these additional "}optional fields?`,
|
|
303
|
+
default: false,
|
|
304
|
+
},
|
|
305
|
+
]);
|
|
306
|
+
}
|
|
307
|
+
catch (error) {
|
|
308
|
+
if (this.isUserCancellation(error)) {
|
|
309
|
+
console.log(chalk.yellow("\n\nOperation cancelled by user"));
|
|
310
|
+
process.exit(0);
|
|
311
|
+
}
|
|
312
|
+
throw error;
|
|
313
|
+
}
|
|
314
|
+
if (shouldConfigureOptional.configure) {
|
|
315
|
+
console.log(chalk.cyan(`\nOptional fields:`));
|
|
316
|
+
for (const field of newOptionalFields) {
|
|
317
|
+
await this.promptForField(field, inputs);
|
|
318
|
+
processedFieldKeys.add(field.key);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
// Mark these fields as processed even if skipped to avoid re-asking
|
|
323
|
+
newOptionalFields.forEach((field) => processedFieldKeys.add(field.key));
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
// If we only processed optional fields and skipped them, no need to re-fetch
|
|
327
|
+
if (newRequiredFields.length === 0 &&
|
|
328
|
+
(!newOptionalFields.length || !shouldConfigureOptional.configure)) {
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
if (iteration >= maxIterations) {
|
|
333
|
+
console.log(chalk.yellow(`\n⚠️ Maximum field resolution iterations reached. Some dynamic fields may not have been discovered.`));
|
|
334
|
+
}
|
|
335
|
+
return inputs;
|
|
336
|
+
}
|
|
337
|
+
getNestedValue(obj, path) {
|
|
338
|
+
return path.reduce((current, key) => current?.[key], obj);
|
|
339
|
+
}
|
|
340
|
+
setNestedValue(obj, path, value) {
|
|
341
|
+
const lastKey = path[path.length - 1];
|
|
342
|
+
const parent = path.slice(0, -1).reduce((current, key) => {
|
|
343
|
+
if (!(key in current)) {
|
|
344
|
+
current[key] = {};
|
|
345
|
+
}
|
|
346
|
+
return current[key];
|
|
347
|
+
}, obj);
|
|
348
|
+
parent[lastKey] = value;
|
|
349
|
+
}
|
|
350
|
+
async promptForField(field, inputs) {
|
|
351
|
+
const fieldPrompt = {
|
|
352
|
+
type: field.type === "boolean" ? "confirm" : "input",
|
|
353
|
+
name: field.key,
|
|
354
|
+
message: `${field.label || field.key}${field.required ? " (required)" : " (optional)"}:`,
|
|
355
|
+
...(field.helpText && { prefix: chalk.gray(`ℹ ${field.helpText}\n`) }),
|
|
356
|
+
...(field.default && { default: field.default }),
|
|
357
|
+
};
|
|
358
|
+
if (field.choices && field.choices.length > 0) {
|
|
359
|
+
fieldPrompt.type = "list";
|
|
360
|
+
fieldPrompt.choices = field.choices.map((choice) => ({
|
|
361
|
+
name: choice.label || choice.value,
|
|
362
|
+
value: choice.value,
|
|
363
|
+
}));
|
|
364
|
+
}
|
|
365
|
+
try {
|
|
366
|
+
const answer = await inquirer.prompt([fieldPrompt]);
|
|
367
|
+
if (answer[field.key] !== undefined && answer[field.key] !== "") {
|
|
368
|
+
inputs[field.key] = answer[field.key];
|
|
369
|
+
}
|
|
370
|
+
else if (field.required) {
|
|
371
|
+
throw new Error(`Required field ${field.key} cannot be empty`);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
catch (error) {
|
|
375
|
+
if (this.isUserCancellation(error)) {
|
|
376
|
+
console.log(chalk.yellow("\n\nOperation cancelled by user"));
|
|
377
|
+
process.exit(0);
|
|
378
|
+
}
|
|
379
|
+
throw error;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
isUserCancellation(error) {
|
|
383
|
+
return (error?.name === "ExitPromptError" ||
|
|
384
|
+
error?.message?.includes("User force closed") ||
|
|
385
|
+
error?.isTTYError);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
function getFormatMetadata(schema) {
|
|
3
|
+
return schema?._def?.formatMeta;
|
|
4
|
+
}
|
|
5
|
+
function getOutputSchema(schema) {
|
|
6
|
+
return schema?._def?.outputSchema;
|
|
7
|
+
}
|
|
8
|
+
// ============================================================================
|
|
9
|
+
// Generic Schema-Driven Formatter
|
|
10
|
+
// ============================================================================
|
|
11
|
+
export function formatItemsFromSchema(inputSchema, items) {
|
|
12
|
+
// Get the output schema and its format metadata
|
|
13
|
+
const outputSchema = getOutputSchema(inputSchema);
|
|
14
|
+
if (!outputSchema) {
|
|
15
|
+
// Fallback to generic formatting if no output schema
|
|
16
|
+
formatItemsGeneric(items);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const formatMeta = getFormatMetadata(outputSchema);
|
|
20
|
+
if (!formatMeta) {
|
|
21
|
+
// Fallback to generic formatting if no format metadata
|
|
22
|
+
formatItemsGeneric(items);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
// Format each item using the schema metadata
|
|
26
|
+
items.forEach((item, index) => {
|
|
27
|
+
formatSingleItem(item, index, formatMeta);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
function formatSingleItem(item, index, formatMeta) {
|
|
31
|
+
// Get the formatted item from the format function
|
|
32
|
+
const formatted = formatMeta.format(item);
|
|
33
|
+
// Build the main title line
|
|
34
|
+
let titleLine = `${chalk.gray(`${index + 1}.`)} ${chalk.cyan(formatted.title)}`;
|
|
35
|
+
if (formatted.subtitle) {
|
|
36
|
+
titleLine += ` ${chalk.gray(formatted.subtitle)}`;
|
|
37
|
+
}
|
|
38
|
+
console.log(titleLine);
|
|
39
|
+
// Format detail lines
|
|
40
|
+
for (const detail of formatted.details) {
|
|
41
|
+
const styledText = applyStyle(detail.text, detail.style);
|
|
42
|
+
console.log(` ${styledText}`);
|
|
43
|
+
}
|
|
44
|
+
console.log(); // Empty line between items
|
|
45
|
+
}
|
|
46
|
+
function applyStyle(value, style) {
|
|
47
|
+
switch (style) {
|
|
48
|
+
case "dim":
|
|
49
|
+
return chalk.dim(value);
|
|
50
|
+
case "accent":
|
|
51
|
+
return chalk.magenta(value);
|
|
52
|
+
case "warning":
|
|
53
|
+
return chalk.red(value);
|
|
54
|
+
case "success":
|
|
55
|
+
return chalk.green(value);
|
|
56
|
+
case "normal":
|
|
57
|
+
default:
|
|
58
|
+
return chalk.blue(value);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function formatItemsGeneric(items) {
|
|
62
|
+
// Fallback formatting for items without schema metadata
|
|
63
|
+
items.forEach((item, index) => {
|
|
64
|
+
const name = item.title || item.name || item.key || item.id || "Item";
|
|
65
|
+
console.log(`${chalk.gray(`${index + 1}.`)} ${chalk.cyan(name)}`);
|
|
66
|
+
if (item.description) {
|
|
67
|
+
console.log(` ${chalk.dim(item.description)}`);
|
|
68
|
+
}
|
|
69
|
+
console.log();
|
|
70
|
+
});
|
|
71
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
const promises = {};
|
|
3
|
+
const serializeAsync = (fn) => async (...args) => {
|
|
4
|
+
const hash = createHash("sha256")
|
|
5
|
+
.update(fn.toString(), "utf8")
|
|
6
|
+
.digest()
|
|
7
|
+
.toString("hex");
|
|
8
|
+
const promise = promises[hash];
|
|
9
|
+
if (!promise) {
|
|
10
|
+
promises[hash] = fn(...args).finally(() => {
|
|
11
|
+
delete promises[hash];
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
return promises[hash];
|
|
15
|
+
};
|
|
16
|
+
export default serializeAsync;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const spinPromise: <T>(promise: Promise<T>, text: string) => Promise<T>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import ora from "ora";
|
|
2
|
+
export const spinPromise = async (promise, text) => {
|
|
3
|
+
const spinner = ora(text).start();
|
|
4
|
+
try {
|
|
5
|
+
const result = await promise;
|
|
6
|
+
spinner.succeed();
|
|
7
|
+
return result;
|
|
8
|
+
}
|
|
9
|
+
catch (error) {
|
|
10
|
+
spinner.fail();
|
|
11
|
+
throw error;
|
|
12
|
+
}
|
|
13
|
+
};
|