@zapier/zapier-sdk-cli 0.0.1

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.
@@ -0,0 +1,413 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.SchemaParameterResolver = void 0;
7
+ const inquirer_1 = __importDefault(require("inquirer"));
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ const zod_1 = require("zod");
10
+ // ============================================================================
11
+ // Schema Parameter Resolver
12
+ // ============================================================================
13
+ class SchemaParameterResolver {
14
+ async resolveParameters(schema, providedParams, sdk) {
15
+ // 1. Try to parse with current parameters
16
+ const parseResult = schema.safeParse(providedParams);
17
+ // Even if parsing succeeds, check if we should resolve optional but important parameters
18
+ const allResolvable = this.findAllResolvableParameters(schema);
19
+ const shouldResolveAnyway = allResolvable.filter((param) => {
20
+ const hasValue = this.getNestedValue(providedParams, param.path) !== undefined;
21
+ // Resolve any parameters with resolvers even if optional, unless explicitly provided
22
+ return !hasValue && param.resolverMeta?.resolver;
23
+ });
24
+ if (parseResult.success && shouldResolveAnyway.length === 0) {
25
+ return parseResult.data;
26
+ }
27
+ // 2. Analyze what's missing and can be resolved
28
+ let missingResolvable;
29
+ if (!parseResult.success) {
30
+ missingResolvable = this.findResolvableParameters(schema, parseResult.error, providedParams);
31
+ }
32
+ else {
33
+ // Schema parsing succeeded, but we want to resolve optional important parameters
34
+ missingResolvable = shouldResolveAnyway;
35
+ }
36
+ if (missingResolvable.length === 0) {
37
+ // No resolvable parameters, throw original error
38
+ throw parseResult.error;
39
+ }
40
+ // 3. Resolve missing parameters interactively
41
+ const resolvedParams = { ...providedParams };
42
+ const context = {
43
+ sdk,
44
+ currentParams: providedParams,
45
+ resolvedParams,
46
+ };
47
+ // Sort by dependency order (parameters that depend on others go last)
48
+ const sortedMissing = this.sortByDependencies(missingResolvable);
49
+ for (const missing of sortedMissing) {
50
+ try {
51
+ const value = await this.resolveParameter(missing, context);
52
+ this.setNestedValue(resolvedParams, missing.path, value);
53
+ // Update context with newly resolved value
54
+ context.resolvedParams = resolvedParams;
55
+ }
56
+ catch (error) {
57
+ if (this.isUserCancellation(error)) {
58
+ console.log(chalk_1.default.yellow("\n\nOperation cancelled by user"));
59
+ process.exit(0);
60
+ }
61
+ console.error(chalk_1.default.red(`Failed to resolve ${missing.name}:`), error);
62
+ throw error;
63
+ }
64
+ }
65
+ // 4. Validate final parameters
66
+ const finalResult = schema.safeParse(resolvedParams);
67
+ if (!finalResult.success) {
68
+ console.error(chalk_1.default.red("❌ Parameter validation failed after resolution:"));
69
+ throw finalResult.error;
70
+ }
71
+ return finalResult.data;
72
+ }
73
+ findAllResolvableParameters(schema) {
74
+ const resolvable = [];
75
+ this.analyzeSchema(schema, [], resolvable);
76
+ return resolvable;
77
+ }
78
+ findResolvableParameters(schema, error, providedParams) {
79
+ const resolvable = this.findAllResolvableParameters(schema);
80
+ // Filter to only include parameters that are actually missing/invalid
81
+ const errorPaths = new Set(error.issues.map((issue) => issue.path.join(".")));
82
+ return resolvable.filter((param) => {
83
+ const paramPath = param.path.join(".");
84
+ const isErrorPath = errorPaths.has(paramPath);
85
+ const hasValue = this.getNestedValue(providedParams, param.path) !== undefined;
86
+ // Include if there's an error for this path or if it's required and missing
87
+ // Also include optional parameters that have resolvers and aren't provided
88
+ const shouldResolve = isErrorPath ||
89
+ (param.isRequired && !hasValue) ||
90
+ (!param.isRequired && !hasValue && param.resolverMeta?.resolver);
91
+ return shouldResolve;
92
+ });
93
+ }
94
+ analyzeSchema(schema, currentPath, resolvable) {
95
+ // Handle different Zod types
96
+ if (schema instanceof zod_1.z.ZodObject) {
97
+ const shape = schema.shape;
98
+ Object.entries(shape).forEach(([key, fieldSchema]) => {
99
+ const fieldPath = [...currentPath, key];
100
+ this.analyzeSchemaField(key, fieldSchema, fieldPath, resolvable);
101
+ });
102
+ }
103
+ else if (schema instanceof zod_1.z.ZodOptional) {
104
+ this.analyzeSchema(schema.unwrap(), currentPath, resolvable);
105
+ }
106
+ else if (schema instanceof zod_1.z.ZodDefault) {
107
+ this.analyzeSchema(schema.removeDefault(), currentPath, resolvable);
108
+ }
109
+ }
110
+ analyzeSchemaField(name, schema, path, resolvable) {
111
+ const resolverMeta = schema._def.resolverMeta;
112
+ if (resolverMeta?.resolver) {
113
+ resolvable.push({
114
+ name,
115
+ path,
116
+ schema,
117
+ description: schema.description,
118
+ resolverMeta,
119
+ isRequired: !schema.isOptional(),
120
+ });
121
+ }
122
+ // Recursively analyze nested objects
123
+ if (schema instanceof zod_1.z.ZodObject) {
124
+ this.analyzeSchema(schema, path, resolvable);
125
+ }
126
+ else if (schema instanceof zod_1.z.ZodOptional) {
127
+ this.analyzeSchemaField(name, schema.unwrap(), path, resolvable);
128
+ }
129
+ else if (schema instanceof zod_1.z.ZodDefault) {
130
+ this.analyzeSchemaField(name, schema.removeDefault(), path, resolvable);
131
+ }
132
+ }
133
+ sortByDependencies(parameters) {
134
+ // Simple topological sort based on 'depends' field
135
+ const sorted = [];
136
+ const remaining = [...parameters];
137
+ while (remaining.length > 0) {
138
+ const canResolve = remaining.filter((param) => {
139
+ const depends = param.resolverMeta?.resolver?.depends || [];
140
+ return depends.every((dep) => sorted.some((resolved) => resolved.name === dep));
141
+ });
142
+ if (canResolve.length === 0) {
143
+ // No more resolvable parameters, add remaining ones (circular dependency or no dependencies)
144
+ sorted.push(...remaining);
145
+ break;
146
+ }
147
+ sorted.push(...canResolve);
148
+ canResolve.forEach((param) => {
149
+ const index = remaining.indexOf(param);
150
+ remaining.splice(index, 1);
151
+ });
152
+ }
153
+ return sorted;
154
+ }
155
+ async resolveParameter(paramInfo, context) {
156
+ const resolver = paramInfo.resolverMeta?.resolver;
157
+ if (!resolver) {
158
+ return await this.promptStaticInput(paramInfo);
159
+ }
160
+ if (resolver.type === "static") {
161
+ return await this.promptStaticInput(paramInfo, resolver);
162
+ }
163
+ if (resolver.type === "dynamic") {
164
+ return await this.resolveDynamicParameter(paramInfo, resolver, context);
165
+ }
166
+ if (resolver.type === "fields") {
167
+ return await this.resolveFieldsParameter(paramInfo, resolver, context);
168
+ }
169
+ throw new Error(`Unknown resolver type: ${resolver.type}`);
170
+ }
171
+ async promptStaticInput(paramInfo, resolver) {
172
+ const promptConfig = {
173
+ type: resolver?.inputType === "editor"
174
+ ? "input"
175
+ : resolver?.inputType || "input",
176
+ name: "value",
177
+ message: `Enter ${paramInfo.description || paramInfo.name}${paramInfo.isRequired ? "" : " (optional)"}:`,
178
+ };
179
+ // Only use placeholder as default for required parameters
180
+ if (resolver?.placeholder && paramInfo.isRequired) {
181
+ promptConfig.default = resolver.placeholder;
182
+ }
183
+ // Add validation for required parameters
184
+ if (paramInfo.isRequired) {
185
+ promptConfig.validate = (input) => {
186
+ if (!input || (typeof input === "string" && input.trim() === "")) {
187
+ return "This field is required. Please enter a value.";
188
+ }
189
+ return true;
190
+ };
191
+ }
192
+ // For editor-like prompts, provide helpful instructions
193
+ if (resolver?.inputType === "editor") {
194
+ promptConfig.message += " (JSON format)";
195
+ }
196
+ try {
197
+ const result = await inquirer_1.default.prompt([promptConfig]);
198
+ // For optional parameters, treat empty input as undefined
199
+ if (!paramInfo.isRequired &&
200
+ (!result.value || result.value.trim() === "")) {
201
+ return undefined;
202
+ }
203
+ // Try to parse JSON if it looks like JSON
204
+ if (typeof result.value === "string" &&
205
+ result.value.trim().startsWith("{")) {
206
+ try {
207
+ return JSON.parse(result.value);
208
+ }
209
+ catch {
210
+ console.warn(chalk_1.default.yellow("Warning: Could not parse as JSON, using as string"));
211
+ return result.value;
212
+ }
213
+ }
214
+ return result.value;
215
+ }
216
+ catch (error) {
217
+ if (this.isUserCancellation(error)) {
218
+ console.log(chalk_1.default.yellow("\n\nOperation cancelled by user"));
219
+ process.exit(0);
220
+ }
221
+ throw error;
222
+ }
223
+ }
224
+ async resolveDynamicParameter(paramInfo, resolver, context) {
225
+ console.log(chalk_1.default.gray(`Fetching options for ${paramInfo.name}...`));
226
+ try {
227
+ // Use the new fetch function approach
228
+ const options = await resolver.fetch(context.sdk, context.resolvedParams);
229
+ if (options.length === 0) {
230
+ console.log(chalk_1.default.yellow(`No options available for ${paramInfo.name}`));
231
+ return await this.promptStaticInput(paramInfo);
232
+ }
233
+ // Generate prompt configuration
234
+ const promptConfig = resolver.prompt(options, context.resolvedParams);
235
+ // Execute the prompt
236
+ let result;
237
+ try {
238
+ result = await inquirer_1.default.prompt([promptConfig]);
239
+ }
240
+ catch (error) {
241
+ if (this.isUserCancellation(error)) {
242
+ console.log(chalk_1.default.yellow("\n\nOperation cancelled by user"));
243
+ process.exit(0);
244
+ }
245
+ throw error;
246
+ }
247
+ let value = result[promptConfig.name || "value"];
248
+ // Handle JSON parsing if the value looks like JSON
249
+ if (typeof value === "string" && value.trim().startsWith("{")) {
250
+ try {
251
+ value = JSON.parse(value);
252
+ }
253
+ catch {
254
+ console.warn(chalk_1.default.yellow("Warning: Could not parse as JSON, using as string"));
255
+ }
256
+ }
257
+ return value;
258
+ }
259
+ catch (error) {
260
+ if (this.isUserCancellation(error)) {
261
+ console.log(chalk_1.default.yellow("\n\nOperation cancelled by user"));
262
+ process.exit(0);
263
+ }
264
+ if (error instanceof Error && error.message.includes("401")) {
265
+ console.log(chalk_1.default.yellow(`⚠️ Invalid auth token, falling back to manual input for ${paramInfo.name}`));
266
+ }
267
+ else {
268
+ console.error(chalk_1.default.red(`Failed to fetch options for ${paramInfo.name}:`), error);
269
+ console.log(chalk_1.default.yellow("Falling back to manual input..."));
270
+ }
271
+ return await this.promptStaticInput(paramInfo);
272
+ }
273
+ }
274
+ async resolveFieldsParameter(paramInfo, resolver, context) {
275
+ console.log(chalk_1.default.gray(`Fetching field definitions for ${paramInfo.name}...`));
276
+ try {
277
+ // Fetch field definitions using the resolver's fetch function
278
+ const fields = await resolver.fetch(context.sdk, context.resolvedParams);
279
+ if (fields.length === 0) {
280
+ console.log(chalk_1.default.yellow(`No input fields required for this action`));
281
+ return {};
282
+ }
283
+ console.log(chalk_1.default.blue(`\nConfiguring inputs for ${context.resolvedParams.app} ${context.resolvedParams.type} ${context.resolvedParams.action}:`));
284
+ const inputs = {};
285
+ // Separate required and optional fields
286
+ const requiredFields = fields.filter((field) => field.required);
287
+ const optionalFields = fields.filter((field) => !field.required);
288
+ // First, prompt for all required fields
289
+ if (requiredFields.length > 0) {
290
+ console.log(chalk_1.default.cyan(`\nRequired fields (${requiredFields.length}):`));
291
+ for (const field of requiredFields) {
292
+ await this.promptForField(field, inputs);
293
+ }
294
+ }
295
+ // Then ask if user wants to configure optional fields
296
+ if (optionalFields.length > 0) {
297
+ console.log(chalk_1.default.gray(`\nThere are ${optionalFields.length} optional field(s) available.`));
298
+ let shouldConfigureOptional;
299
+ try {
300
+ shouldConfigureOptional = await inquirer_1.default.prompt([
301
+ {
302
+ type: "confirm",
303
+ name: "configure",
304
+ message: "Would you like to configure optional fields?",
305
+ default: false,
306
+ },
307
+ ]);
308
+ }
309
+ catch (error) {
310
+ if (this.isUserCancellation(error)) {
311
+ console.log(chalk_1.default.yellow("\n\nOperation cancelled by user"));
312
+ process.exit(0);
313
+ }
314
+ throw error;
315
+ }
316
+ if (shouldConfigureOptional.configure) {
317
+ console.log(chalk_1.default.cyan(`\nOptional fields:`));
318
+ for (const field of optionalFields) {
319
+ await this.promptForField(field, inputs);
320
+ }
321
+ }
322
+ }
323
+ return inputs;
324
+ }
325
+ catch (error) {
326
+ if (this.isUserCancellation(error)) {
327
+ console.log(chalk_1.default.yellow("\n\nOperation cancelled by user"));
328
+ process.exit(0);
329
+ }
330
+ console.error(chalk_1.default.red(`Failed to fetch field definitions for ${paramInfo.name}:`), error);
331
+ console.log(chalk_1.default.yellow("Falling back to manual JSON input..."));
332
+ return await this.promptStaticInput(paramInfo);
333
+ }
334
+ }
335
+ async promptForField(field, inputs) {
336
+ const fieldPrompt = {
337
+ type: "input",
338
+ name: field.key,
339
+ message: `${field.label || field.key}${field.required ? " (required)" : " (optional)"}:`,
340
+ };
341
+ // Add description/help text if available
342
+ if (field.help_text || field.description) {
343
+ fieldPrompt.message += `\n ${chalk_1.default.dim(field.help_text || field.description)}`;
344
+ }
345
+ // Set default value
346
+ if (field.default !== undefined) {
347
+ fieldPrompt.default = field.default;
348
+ }
349
+ // Add validation for required fields
350
+ if (field.required) {
351
+ fieldPrompt.validate = (input) => {
352
+ if (!input || (typeof input === "string" && input.trim() === "")) {
353
+ return "This field is required. Please enter a value.";
354
+ }
355
+ return true;
356
+ };
357
+ }
358
+ let result;
359
+ try {
360
+ result = await inquirer_1.default.prompt([fieldPrompt]);
361
+ }
362
+ catch (error) {
363
+ if (this.isUserCancellation(error)) {
364
+ console.log(chalk_1.default.yellow("\n\nOperation cancelled by user"));
365
+ process.exit(0);
366
+ }
367
+ throw error;
368
+ }
369
+ // Only include non-empty values
370
+ if (result[field.key] !== undefined && result[field.key] !== "") {
371
+ let value = result[field.key];
372
+ // Try to parse as appropriate type
373
+ if (field.type === "integer" && typeof value === "string") {
374
+ const parsed = parseInt(value, 10);
375
+ if (!isNaN(parsed))
376
+ value = parsed;
377
+ }
378
+ else if (field.type === "number" && typeof value === "string") {
379
+ const parsed = parseFloat(value);
380
+ if (!isNaN(parsed))
381
+ value = parsed;
382
+ }
383
+ else if (field.type === "boolean" && typeof value === "string") {
384
+ value = value.toLowerCase() === "true" || value === "1";
385
+ }
386
+ inputs[field.key] = value;
387
+ }
388
+ }
389
+ getNestedValue(obj, path) {
390
+ return path.reduce((current, key) => current?.[key], obj);
391
+ }
392
+ setNestedValue(obj, path, value) {
393
+ const lastKey = path[path.length - 1];
394
+ const parentPath = path.slice(0, -1);
395
+ const parent = parentPath.reduce((current, key) => {
396
+ if (!current[key]) {
397
+ current[key] = {};
398
+ }
399
+ return current[key];
400
+ }, obj);
401
+ parent[lastKey] = value;
402
+ }
403
+ isUserCancellation(error) {
404
+ // Check for various ways user cancellation can be detected
405
+ return (error &&
406
+ (error.name === "ExitPromptError" ||
407
+ error.message?.includes("User force closed the prompt") ||
408
+ error.message?.includes("SIGINT") ||
409
+ error.code === "SIGINT" ||
410
+ error.signal === "SIGINT"));
411
+ }
412
+ }
413
+ exports.SchemaParameterResolver = SchemaParameterResolver;
@@ -0,0 +1,2 @@
1
+ import { z } from "zod";
2
+ export declare function formatItemsFromSchema(inputSchema: z.ZodType, items: any[]): void;
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.formatItemsFromSchema = formatItemsFromSchema;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const output_schemas_1 = require("../../../actions-sdk/dist/output-schemas");
9
+ // ============================================================================
10
+ // Generic Schema-Driven Formatter
11
+ // ============================================================================
12
+ function formatItemsFromSchema(inputSchema, items) {
13
+ // Get the output schema and its format metadata
14
+ const outputSchema = (0, output_schemas_1.getOutputSchema)(inputSchema);
15
+ if (!outputSchema) {
16
+ // Fallback to generic formatting if no output schema
17
+ formatItemsGeneric(items);
18
+ return;
19
+ }
20
+ const formatMeta = (0, output_schemas_1.getFormatMetadata)(outputSchema);
21
+ if (!formatMeta) {
22
+ // Fallback to generic formatting if no format metadata
23
+ formatItemsGeneric(items);
24
+ return;
25
+ }
26
+ // Format each item using the schema metadata
27
+ items.forEach((item, index) => {
28
+ formatSingleItem(item, index, formatMeta);
29
+ });
30
+ }
31
+ function formatSingleItem(item, index, formatMeta) {
32
+ // Get the formatted item from the format function
33
+ const formatted = formatMeta.format(item);
34
+ // Build the main title line
35
+ let titleLine = `${chalk_1.default.gray(`${index + 1}.`)} ${chalk_1.default.cyan(formatted.title)}`;
36
+ if (formatted.subtitle) {
37
+ titleLine += ` ${chalk_1.default.gray(formatted.subtitle)}`;
38
+ }
39
+ console.log(titleLine);
40
+ // Format detail lines
41
+ for (const detail of formatted.details) {
42
+ const styledText = applyStyle(detail.text, detail.style);
43
+ console.log(` ${styledText}`);
44
+ }
45
+ console.log(); // Empty line between items
46
+ }
47
+ function applyStyle(value, style) {
48
+ switch (style) {
49
+ case "dim":
50
+ return chalk_1.default.dim(value);
51
+ case "accent":
52
+ return chalk_1.default.magenta(value);
53
+ case "warning":
54
+ return chalk_1.default.red(value);
55
+ case "success":
56
+ return chalk_1.default.green(value);
57
+ case "normal":
58
+ default:
59
+ return chalk_1.default.blue(value);
60
+ }
61
+ }
62
+ function formatItemsGeneric(items) {
63
+ // Fallback formatting for items without schema metadata
64
+ items.forEach((item, index) => {
65
+ const name = item.name || item.key || item.id || "Item";
66
+ console.log(`${chalk_1.default.gray(`${index + 1}.`)} ${chalk_1.default.cyan(name)}`);
67
+ if (item.description) {
68
+ console.log(` ${chalk_1.default.dim(item.description)}`);
69
+ }
70
+ console.log();
71
+ });
72
+ }
@@ -0,0 +1,4 @@
1
+ import { Command } from 'commander';
2
+ import { ActionsSDK } from '@zapier/actions-sdk';
3
+ export declare function generateCLICommands(program: Command, sdk: ActionsSDK): void;
4
+ export declare function enhanceCommandHelp(program: Command): void;