@zapier/zapier-sdk-cli 0.16.1 ā 0.16.3
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 +18 -0
- package/dist/cli.cjs +7 -7
- package/dist/cli.mjs +7 -7
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/package.json +8 -2
- package/dist/src/cli.js +2 -1
- package/dist/src/utils/cli-generator.js +8 -7
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +11 -5
- package/src/cli.test.ts +0 -28
- package/src/cli.ts +0 -96
- package/src/generators/ast-generator.test.ts +0 -908
- package/src/generators/ast-generator.ts +0 -774
- package/src/index.ts +0 -12
- package/src/plugins/add/index.test.ts +0 -58
- package/src/plugins/add/index.ts +0 -177
- package/src/plugins/add/schemas.ts +0 -35
- package/src/plugins/buildManifest/index.test.ts +0 -679
- package/src/plugins/buildManifest/index.ts +0 -131
- package/src/plugins/buildManifest/schemas.ts +0 -55
- package/src/plugins/bundleCode/index.ts +0 -128
- package/src/plugins/bundleCode/schemas.ts +0 -24
- package/src/plugins/generateAppTypes/index.test.ts +0 -679
- package/src/plugins/generateAppTypes/index.ts +0 -227
- package/src/plugins/generateAppTypes/schemas.ts +0 -61
- package/src/plugins/getLoginConfigPath/index.ts +0 -45
- package/src/plugins/getLoginConfigPath/schemas.ts +0 -10
- package/src/plugins/index.ts +0 -8
- package/src/plugins/login/index.ts +0 -135
- package/src/plugins/login/schemas.ts +0 -13
- package/src/plugins/logout/index.ts +0 -37
- package/src/plugins/logout/schemas.ts +0 -8
- package/src/plugins/mcp/index.ts +0 -43
- package/src/plugins/mcp/schemas.ts +0 -13
- package/src/sdk.ts +0 -45
- package/src/telemetry/builders.ts +0 -113
- package/src/telemetry/events.ts +0 -39
- package/src/types/sdk.ts +0 -8
- package/src/utils/api/client.ts +0 -44
- package/src/utils/auth/login.ts +0 -214
- package/src/utils/cli-generator-utils.ts +0 -169
- package/src/utils/cli-generator.test.ts +0 -347
- package/src/utils/cli-generator.ts +0 -807
- package/src/utils/constants.ts +0 -9
- package/src/utils/directory-detection.ts +0 -23
- package/src/utils/errors.ts +0 -26
- package/src/utils/getCallablePromise.ts +0 -21
- package/src/utils/log.ts +0 -23
- package/src/utils/manifest-helpers.ts +0 -25
- package/src/utils/package-manager-detector.ts +0 -83
- package/src/utils/parameter-resolver.ts +0 -1075
- package/src/utils/schema-formatter.ts +0 -153
- package/src/utils/serializeAsync.ts +0 -26
- package/src/utils/spinner.ts +0 -23
- package/src/utils/version-checker.test.ts +0 -239
- package/src/utils/version-checker.ts +0 -237
- package/tsconfig.build.json +0 -18
- package/tsconfig.json +0 -19
- package/tsup.config.ts +0 -23
|
@@ -1,807 +0,0 @@
|
|
|
1
|
-
import type { Command } from "commander";
|
|
2
|
-
import { z } from "zod";
|
|
3
|
-
import type { ZapierSdk } from "@zapier/zapier-sdk";
|
|
4
|
-
import {
|
|
5
|
-
isPositional,
|
|
6
|
-
formatErrorMessage,
|
|
7
|
-
ZapierError,
|
|
8
|
-
type FunctionRegistryEntry,
|
|
9
|
-
} from "@zapier/zapier-sdk";
|
|
10
|
-
import { SchemaParameterResolver } from "./parameter-resolver";
|
|
11
|
-
import { formatItemsFromSchema, formatJsonOutput } from "./schema-formatter";
|
|
12
|
-
import chalk from "chalk";
|
|
13
|
-
import inquirer from "inquirer";
|
|
14
|
-
import { ZapierCliError, ZapierCliExitError } from "./errors";
|
|
15
|
-
|
|
16
|
-
// ============================================================================
|
|
17
|
-
// Types
|
|
18
|
-
// ============================================================================
|
|
19
|
-
|
|
20
|
-
interface CliCommandConfig {
|
|
21
|
-
description: string;
|
|
22
|
-
parameters: CliParameter[];
|
|
23
|
-
handler: (...args: unknown[]) => Promise<void>;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
interface CliParameter {
|
|
27
|
-
name: string;
|
|
28
|
-
type: "string" | "number" | "boolean" | "array";
|
|
29
|
-
required: boolean;
|
|
30
|
-
description?: string;
|
|
31
|
-
default?: unknown;
|
|
32
|
-
choices?: string[];
|
|
33
|
-
hasResolver?: boolean;
|
|
34
|
-
isPositional?: boolean;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// ============================================================================
|
|
38
|
-
// Schema Analysis
|
|
39
|
-
// ============================================================================
|
|
40
|
-
|
|
41
|
-
function analyzeZodSchema(
|
|
42
|
-
schema: z.ZodSchema,
|
|
43
|
-
functionInfo?: FunctionRegistryEntry,
|
|
44
|
-
): CliParameter[] {
|
|
45
|
-
const parameters: CliParameter[] = [];
|
|
46
|
-
|
|
47
|
-
// Handle ZodEffects (schemas with .refine(), .transform(), etc.)
|
|
48
|
-
// In Zod, effects have type "effect" and inner schema is accessed via innerType
|
|
49
|
-
const schemaDef = (
|
|
50
|
-
schema as unknown as {
|
|
51
|
-
_zod?: { def?: { type?: string; innerType?: z.ZodSchema } };
|
|
52
|
-
}
|
|
53
|
-
)._zod?.def;
|
|
54
|
-
if (schemaDef?.type === "effect" && schemaDef.innerType) {
|
|
55
|
-
return analyzeZodSchema(schemaDef.innerType, functionInfo);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if (schema instanceof z.ZodObject) {
|
|
59
|
-
const shape = schema.shape;
|
|
60
|
-
|
|
61
|
-
for (const [key, fieldSchema] of Object.entries(shape)) {
|
|
62
|
-
const param = analyzeZodField(
|
|
63
|
-
key,
|
|
64
|
-
fieldSchema as z.ZodSchema,
|
|
65
|
-
functionInfo,
|
|
66
|
-
);
|
|
67
|
-
if (param) {
|
|
68
|
-
parameters.push(param);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return parameters;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function analyzeZodField(
|
|
77
|
-
name: string,
|
|
78
|
-
schema: z.ZodSchema,
|
|
79
|
-
functionInfo?: FunctionRegistryEntry,
|
|
80
|
-
): CliParameter | null {
|
|
81
|
-
let baseSchema = schema;
|
|
82
|
-
let required = true;
|
|
83
|
-
let defaultValue: unknown = undefined;
|
|
84
|
-
|
|
85
|
-
// Unwrap optional, default, and nullable wrappers - keep unwrapping until we get to the base type
|
|
86
|
-
while (true) {
|
|
87
|
-
if (baseSchema instanceof z.ZodOptional) {
|
|
88
|
-
required = false;
|
|
89
|
-
baseSchema = baseSchema._zod.def.innerType as z.ZodSchema;
|
|
90
|
-
} else if (baseSchema instanceof z.ZodDefault) {
|
|
91
|
-
required = false;
|
|
92
|
-
defaultValue = (baseSchema._zod.def.defaultValue as () => unknown)();
|
|
93
|
-
baseSchema = baseSchema._zod.def.innerType as z.ZodSchema;
|
|
94
|
-
} else {
|
|
95
|
-
// Check for ZodNullable - nullable doesn't affect CLI required status, but we need to unwrap it to get the base type
|
|
96
|
-
const zodDef = (
|
|
97
|
-
baseSchema as unknown as {
|
|
98
|
-
_zod?: { def?: { typeName?: string; innerType?: z.ZodSchema } };
|
|
99
|
-
}
|
|
100
|
-
)._zod?.def;
|
|
101
|
-
|
|
102
|
-
if (zodDef?.typeName === "ZodNullable" && zodDef.innerType) {
|
|
103
|
-
baseSchema = zodDef.innerType;
|
|
104
|
-
continue;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// No more wrappers to unwrap
|
|
108
|
-
break;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Determine parameter type
|
|
113
|
-
let paramType: CliParameter["type"] = "string";
|
|
114
|
-
let choices: string[] | undefined;
|
|
115
|
-
|
|
116
|
-
if (baseSchema instanceof z.ZodString) {
|
|
117
|
-
paramType = "string";
|
|
118
|
-
} else if (baseSchema instanceof z.ZodNumber) {
|
|
119
|
-
paramType = "number";
|
|
120
|
-
} else if (baseSchema instanceof z.ZodBoolean) {
|
|
121
|
-
paramType = "boolean";
|
|
122
|
-
} else if (baseSchema instanceof z.ZodArray) {
|
|
123
|
-
paramType = "array";
|
|
124
|
-
} else if (baseSchema instanceof z.ZodEnum) {
|
|
125
|
-
paramType = "string";
|
|
126
|
-
choices = baseSchema.options as string[];
|
|
127
|
-
} else if (baseSchema instanceof z.ZodRecord) {
|
|
128
|
-
// Handle Record<string, any> as JSON string input
|
|
129
|
-
paramType = "string";
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Check if this parameter has a resolver
|
|
133
|
-
let paramHasResolver = false;
|
|
134
|
-
|
|
135
|
-
// Check function-specific resolvers first
|
|
136
|
-
if (functionInfo?.resolvers?.[name]) {
|
|
137
|
-
paramHasResolver = true;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Extract resolver metadata
|
|
141
|
-
return {
|
|
142
|
-
name,
|
|
143
|
-
type: paramType,
|
|
144
|
-
required,
|
|
145
|
-
description: schema.description,
|
|
146
|
-
default: defaultValue,
|
|
147
|
-
choices,
|
|
148
|
-
hasResolver: paramHasResolver,
|
|
149
|
-
isPositional: isPositional(schema),
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// ============================================================================
|
|
154
|
-
// CLI Structure Derivation - Purely Generic
|
|
155
|
-
// ============================================================================
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Convert camelCase to kebab-case
|
|
159
|
-
* e.g., listApps -> list-apps, generateTypes -> generate-types
|
|
160
|
-
*/
|
|
161
|
-
function toKebabCase(str: string): string {
|
|
162
|
-
return str.replace(/([A-Z])/g, "-$1").toLowerCase();
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Convert SDK method name directly to CLI command
|
|
167
|
-
* e.g., listApps -> list-apps, getApp -> get-app, generateTypes -> generate-types
|
|
168
|
-
*/
|
|
169
|
-
function methodNameToCliCommand(methodName: string): string {
|
|
170
|
-
return toKebabCase(methodName);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// ============================================================================
|
|
174
|
-
// CLI Command Generation - Completely Generic
|
|
175
|
-
// ============================================================================
|
|
176
|
-
|
|
177
|
-
export function generateCliCommands(program: Command, sdk: ZapierSdk): void {
|
|
178
|
-
// Check if SDK has registry
|
|
179
|
-
if (typeof sdk.getRegistry !== "function") {
|
|
180
|
-
console.error("SDK registry not available");
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
const registry = sdk.getRegistry({ package: "cli" });
|
|
185
|
-
|
|
186
|
-
// Create all commands first
|
|
187
|
-
registry.functions.forEach((fnInfo) => {
|
|
188
|
-
if (!fnInfo.inputSchema) {
|
|
189
|
-
console.warn(`Schema not found for ${fnInfo.name}`);
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// Convert methodName to kebab-case CLI command
|
|
194
|
-
const cliCommandName = methodNameToCliCommand(fnInfo.name);
|
|
195
|
-
|
|
196
|
-
const config = createCommandConfig(cliCommandName, fnInfo, sdk);
|
|
197
|
-
|
|
198
|
-
addCommand(program, cliCommandName, config);
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
// Override the help display to show commands grouped by category
|
|
202
|
-
program.configureHelp({
|
|
203
|
-
formatHelp: (cmd, helper) => {
|
|
204
|
-
const helpWidth = helper.helpWidth || 80;
|
|
205
|
-
|
|
206
|
-
let output = helper.commandUsage(cmd) + "\n";
|
|
207
|
-
|
|
208
|
-
if (cmd.description()) {
|
|
209
|
-
output += helper.wrap(cmd.description(), helpWidth, 0) + "\n";
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// Add options section
|
|
213
|
-
const options = helper.visibleOptions(cmd);
|
|
214
|
-
if (options.length > 0) {
|
|
215
|
-
output += "\nOptions:\n";
|
|
216
|
-
const longestOptionLength = Math.max(
|
|
217
|
-
...options.map((opt) => helper.optionTerm(opt).length),
|
|
218
|
-
);
|
|
219
|
-
options.forEach((option) => {
|
|
220
|
-
const term = helper.optionTerm(option);
|
|
221
|
-
const padding = " ".repeat(
|
|
222
|
-
Math.max(2, longestOptionLength - term.length + 4),
|
|
223
|
-
);
|
|
224
|
-
output += ` ${term}${padding}${helper.optionDescription(option)}\n`;
|
|
225
|
-
});
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// Add categorized commands section
|
|
229
|
-
const commands = helper.visibleCommands(cmd);
|
|
230
|
-
if (commands.length > 0) {
|
|
231
|
-
output += "\nCommands:\n";
|
|
232
|
-
|
|
233
|
-
// Collect all SDK commands that belong to categories
|
|
234
|
-
const categorizedCommands = new Set<string>();
|
|
235
|
-
|
|
236
|
-
// Group SDK commands by categories
|
|
237
|
-
registry.categories.forEach((category) => {
|
|
238
|
-
const categoryCommands = commands.filter((command) =>
|
|
239
|
-
category.functions.some((functionName) => {
|
|
240
|
-
const cliCommandName = methodNameToCliCommand(functionName);
|
|
241
|
-
return command.name() === cliCommandName;
|
|
242
|
-
}),
|
|
243
|
-
);
|
|
244
|
-
|
|
245
|
-
if (categoryCommands.length > 0) {
|
|
246
|
-
output += `\n ${category.titlePlural}:\n`;
|
|
247
|
-
categoryCommands.forEach((command) => {
|
|
248
|
-
output += ` ${helper.subcommandTerm(command)}\n`;
|
|
249
|
-
categorizedCommands.add(command.name());
|
|
250
|
-
});
|
|
251
|
-
}
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
// Add any remaining commands that aren't part of SDK categories
|
|
255
|
-
const otherCommands = commands.filter(
|
|
256
|
-
(command) => !categorizedCommands.has(command.name()),
|
|
257
|
-
);
|
|
258
|
-
if (otherCommands.length > 0) {
|
|
259
|
-
output += `\n Other:\n`;
|
|
260
|
-
otherCommands.forEach((command) => {
|
|
261
|
-
output += ` ${helper.subcommandTerm(command)}\n`;
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
return output;
|
|
267
|
-
},
|
|
268
|
-
});
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
function createCommandConfig(
|
|
272
|
-
cliCommandName: string,
|
|
273
|
-
functionInfo: FunctionRegistryEntry,
|
|
274
|
-
sdk: ZapierSdk,
|
|
275
|
-
): CliCommandConfig {
|
|
276
|
-
const schema = functionInfo.inputSchema as z.ZodSchema;
|
|
277
|
-
const parameters = analyzeZodSchema(schema, functionInfo);
|
|
278
|
-
const description = schema.description || `${cliCommandName} command`;
|
|
279
|
-
|
|
280
|
-
const handler = async (...args: unknown[]) => {
|
|
281
|
-
try {
|
|
282
|
-
// The last argument is always the command object with parsed options
|
|
283
|
-
const commandObj = args[args.length - 1] as {
|
|
284
|
-
opts(): Record<string, unknown>;
|
|
285
|
-
};
|
|
286
|
-
const options = commandObj.opts();
|
|
287
|
-
|
|
288
|
-
// Check if this is a list command for pagination
|
|
289
|
-
const isListCommand = functionInfo.type === "list";
|
|
290
|
-
const hasPaginationParams = parameters.some(
|
|
291
|
-
(p) => p.name === "maxItems" || p.name === "pageSize",
|
|
292
|
-
);
|
|
293
|
-
const hasUserSpecifiedMaxItems: boolean =
|
|
294
|
-
"maxItems" in options && options.maxItems !== undefined;
|
|
295
|
-
const shouldUseJson = options.json;
|
|
296
|
-
|
|
297
|
-
// Convert CLI args to SDK method parameters
|
|
298
|
-
const rawParams = convertCliArgsToSdkParams(
|
|
299
|
-
parameters,
|
|
300
|
-
args.slice(0, -1),
|
|
301
|
-
options,
|
|
302
|
-
);
|
|
303
|
-
|
|
304
|
-
// Resolve missing parameters interactively using schema metadata
|
|
305
|
-
const resolver = new SchemaParameterResolver();
|
|
306
|
-
const resolvedParams = await resolver.resolveParameters(
|
|
307
|
-
schema,
|
|
308
|
-
rawParams,
|
|
309
|
-
sdk,
|
|
310
|
-
functionInfo.name,
|
|
311
|
-
);
|
|
312
|
-
|
|
313
|
-
// Handle paginated list commands with async iteration
|
|
314
|
-
if (
|
|
315
|
-
isListCommand &&
|
|
316
|
-
hasPaginationParams &&
|
|
317
|
-
!shouldUseJson &&
|
|
318
|
-
!hasUserSpecifiedMaxItems
|
|
319
|
-
) {
|
|
320
|
-
// Get the async iterable directly from the SDK method call (don't await it! that breaks the next page behavior)
|
|
321
|
-
const sdkObj = sdk as unknown as Record<
|
|
322
|
-
string,
|
|
323
|
-
(params: unknown) => Promise<unknown> & AsyncIterable<unknown>
|
|
324
|
-
>;
|
|
325
|
-
const sdkIterator = sdkObj[functionInfo.name](resolvedParams);
|
|
326
|
-
await handlePaginatedListWithAsyncIteration(
|
|
327
|
-
functionInfo.name,
|
|
328
|
-
sdkIterator,
|
|
329
|
-
functionInfo,
|
|
330
|
-
);
|
|
331
|
-
} else {
|
|
332
|
-
// Special handling for commands that write to files
|
|
333
|
-
const hasOutputFile = (resolvedParams as { output?: unknown }).output;
|
|
334
|
-
if (hasOutputFile) {
|
|
335
|
-
// Call the SDK method for file output commands
|
|
336
|
-
const sdkObj = sdk as unknown as Record<
|
|
337
|
-
string,
|
|
338
|
-
(params: unknown) => Promise<unknown>
|
|
339
|
-
>;
|
|
340
|
-
await sdkObj[functionInfo.name](resolvedParams);
|
|
341
|
-
console.log(
|
|
342
|
-
chalk.green(`ā
${cliCommandName} completed successfully!`),
|
|
343
|
-
);
|
|
344
|
-
console.log(chalk.gray(`Output written to: ${hasOutputFile}`));
|
|
345
|
-
return;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
// Call the SDK method and handle non-paginated results
|
|
349
|
-
const sdkObj = sdk as unknown as Record<
|
|
350
|
-
string,
|
|
351
|
-
(params: unknown) => Promise<unknown>
|
|
352
|
-
>;
|
|
353
|
-
let result: unknown = await sdkObj[functionInfo.name](resolvedParams);
|
|
354
|
-
|
|
355
|
-
// Handle Response objects by wrapping in a structured envelope
|
|
356
|
-
if (result instanceof Response) {
|
|
357
|
-
const response = result;
|
|
358
|
-
let body: unknown;
|
|
359
|
-
try {
|
|
360
|
-
body = await response.json();
|
|
361
|
-
} catch {
|
|
362
|
-
// If JSON parsing fails, try to get text for error context
|
|
363
|
-
const text = response.bodyUsed
|
|
364
|
-
? "[body already consumed]"
|
|
365
|
-
: await response.text().catch(() => "[unable to read body]");
|
|
366
|
-
throw new Error(
|
|
367
|
-
`Failed to parse response as JSON (status: ${response.status}). Body: ${text.slice(0, 200)}`,
|
|
368
|
-
);
|
|
369
|
-
}
|
|
370
|
-
result = {
|
|
371
|
-
statusCode: response.status,
|
|
372
|
-
headers: Object.fromEntries(response.headers.entries()),
|
|
373
|
-
body,
|
|
374
|
-
};
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
const items = (result as { data?: unknown })?.data
|
|
378
|
-
? (result as { data: unknown }).data
|
|
379
|
-
: result;
|
|
380
|
-
|
|
381
|
-
if (shouldUseJson) {
|
|
382
|
-
console.log(JSON.stringify(items, null, 2));
|
|
383
|
-
} else if (isListCommand) {
|
|
384
|
-
formatNonPaginatedResults(
|
|
385
|
-
items as unknown[],
|
|
386
|
-
(resolvedParams as { maxItems?: number }).maxItems,
|
|
387
|
-
hasUserSpecifiedMaxItems,
|
|
388
|
-
shouldUseJson as boolean,
|
|
389
|
-
functionInfo,
|
|
390
|
-
);
|
|
391
|
-
} else {
|
|
392
|
-
formatJsonOutput(items);
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
} catch (error) {
|
|
396
|
-
// Handle Zod validation errors more gracefully
|
|
397
|
-
if (error instanceof Error && error.message.includes('"code"')) {
|
|
398
|
-
try {
|
|
399
|
-
const validationErrors = JSON.parse(error.message);
|
|
400
|
-
console.error(chalk.red("ā Validation Error:"));
|
|
401
|
-
validationErrors.forEach((err: unknown) => {
|
|
402
|
-
const errorObj = err as { path?: string[]; message?: string };
|
|
403
|
-
const field = errorObj?.path?.join(".") || "unknown";
|
|
404
|
-
console.error(
|
|
405
|
-
chalk.yellow(
|
|
406
|
-
` ⢠${field}: ${errorObj?.message || "Unknown error"}`,
|
|
407
|
-
),
|
|
408
|
-
);
|
|
409
|
-
});
|
|
410
|
-
console.error(
|
|
411
|
-
"\n" + chalk.dim(`Use --help to see available options`),
|
|
412
|
-
);
|
|
413
|
-
throw new ZapierCliExitError("Validation failed", 1);
|
|
414
|
-
} catch {
|
|
415
|
-
console.error(
|
|
416
|
-
chalk.red("Error:"),
|
|
417
|
-
error instanceof Error ? error.message : String(error),
|
|
418
|
-
);
|
|
419
|
-
throw new ZapierCliExitError(
|
|
420
|
-
error instanceof Error ? error.message : String(error),
|
|
421
|
-
1,
|
|
422
|
-
);
|
|
423
|
-
}
|
|
424
|
-
} else if (error instanceof ZapierCliError) {
|
|
425
|
-
// Re-throw all CLI errors as-is
|
|
426
|
-
throw error;
|
|
427
|
-
} else if (error instanceof ZapierError) {
|
|
428
|
-
// Handle SDK errors with clean, user-friendly messages using our formatter
|
|
429
|
-
const formattedMessage = formatErrorMessage(error);
|
|
430
|
-
console.error(chalk.red("ā Error:"), formattedMessage);
|
|
431
|
-
throw new ZapierCliExitError(formattedMessage, 1);
|
|
432
|
-
} else {
|
|
433
|
-
// Handle other errors
|
|
434
|
-
const errorMessage =
|
|
435
|
-
error instanceof Error ? error.message : "Unknown error";
|
|
436
|
-
console.error(chalk.red("ā Error:"), errorMessage);
|
|
437
|
-
throw new ZapierCliExitError(errorMessage, 1);
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
};
|
|
441
|
-
|
|
442
|
-
return {
|
|
443
|
-
description,
|
|
444
|
-
parameters,
|
|
445
|
-
handler,
|
|
446
|
-
};
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
function addCommand(
|
|
450
|
-
program: Command,
|
|
451
|
-
commandName: string,
|
|
452
|
-
config: CliCommandConfig,
|
|
453
|
-
): void {
|
|
454
|
-
const command = program.command(commandName).description(config.description);
|
|
455
|
-
|
|
456
|
-
// Track whether we've already used a positional array parameter
|
|
457
|
-
let hasPositionalArray = false;
|
|
458
|
-
|
|
459
|
-
// Add parameters to command
|
|
460
|
-
config.parameters.forEach((param) => {
|
|
461
|
-
// Convert camelCase to kebab-case for CLI display
|
|
462
|
-
const kebabName = param.name.replace(/([A-Z])/g, "-$1").toLowerCase();
|
|
463
|
-
|
|
464
|
-
if (param.hasResolver && param.required) {
|
|
465
|
-
// Required parameters with resolvers become optional positional arguments (resolver handles prompting)
|
|
466
|
-
command.argument(
|
|
467
|
-
`[${kebabName}]`,
|
|
468
|
-
param.description || `${kebabName} parameter`,
|
|
469
|
-
);
|
|
470
|
-
} else if (
|
|
471
|
-
param.required &&
|
|
472
|
-
param.type === "array" &&
|
|
473
|
-
!hasPositionalArray
|
|
474
|
-
) {
|
|
475
|
-
// First required array parameter becomes a variadic positional argument
|
|
476
|
-
hasPositionalArray = true;
|
|
477
|
-
command.argument(
|
|
478
|
-
`<${kebabName}...>`,
|
|
479
|
-
param.description || `${kebabName} parameter`,
|
|
480
|
-
);
|
|
481
|
-
} else if (param.required && param.type === "array") {
|
|
482
|
-
// Subsequent required array parameters become required flags
|
|
483
|
-
const flags = [`--${kebabName}`];
|
|
484
|
-
const flagSignature = flags.join(", ") + ` <values...>`;
|
|
485
|
-
command.requiredOption(
|
|
486
|
-
flagSignature,
|
|
487
|
-
param.description || `${kebabName} parameter (required)`,
|
|
488
|
-
);
|
|
489
|
-
} else if (param.required) {
|
|
490
|
-
// Required parameters without resolvers become required positional arguments
|
|
491
|
-
command.argument(
|
|
492
|
-
`<${kebabName}>`,
|
|
493
|
-
param.description || `${kebabName} parameter`,
|
|
494
|
-
);
|
|
495
|
-
} else if (param.isPositional) {
|
|
496
|
-
// Optional parameters marked as positional become optional positional arguments
|
|
497
|
-
command.argument(
|
|
498
|
-
`[${kebabName}]`,
|
|
499
|
-
param.description || `${kebabName} parameter`,
|
|
500
|
-
);
|
|
501
|
-
} else {
|
|
502
|
-
// Optional parameters become flags (whether they have resolvers or not)
|
|
503
|
-
const flags = [`--${kebabName}`];
|
|
504
|
-
|
|
505
|
-
if (param.type === "boolean") {
|
|
506
|
-
command.option(flags.join(", "), param.description);
|
|
507
|
-
} else if (param.type === "array") {
|
|
508
|
-
// For arrays, use variadic syntax to collect multiple values
|
|
509
|
-
const flagSignature = flags.join(", ") + ` <values...>`;
|
|
510
|
-
command.option(
|
|
511
|
-
flagSignature,
|
|
512
|
-
param.description,
|
|
513
|
-
param.default as string[] | undefined,
|
|
514
|
-
);
|
|
515
|
-
} else {
|
|
516
|
-
const flagSignature = flags.join(", ") + ` <${param.type}>`;
|
|
517
|
-
command.option(
|
|
518
|
-
flagSignature,
|
|
519
|
-
param.description || "",
|
|
520
|
-
param.default as string | boolean | string[] | undefined,
|
|
521
|
-
);
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
});
|
|
525
|
-
|
|
526
|
-
// Add formatting options for all commands
|
|
527
|
-
command.option("--json", "Output raw JSON instead of formatted results");
|
|
528
|
-
|
|
529
|
-
command.action(config.handler);
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
// ============================================================================
|
|
533
|
-
// Parameter Conversion
|
|
534
|
-
// ============================================================================
|
|
535
|
-
|
|
536
|
-
function convertCliArgsToSdkParams(
|
|
537
|
-
parameters: CliParameter[],
|
|
538
|
-
positionalArgs: unknown[],
|
|
539
|
-
options: Record<string, unknown>,
|
|
540
|
-
): Record<string, unknown> {
|
|
541
|
-
const sdkParams: Record<string, unknown> = {};
|
|
542
|
-
|
|
543
|
-
// Handle positional arguments (required parameters or optional positional parameters)
|
|
544
|
-
let argIndex = 0;
|
|
545
|
-
parameters.forEach((param) => {
|
|
546
|
-
if (
|
|
547
|
-
(param.required || param.isPositional) &&
|
|
548
|
-
argIndex < positionalArgs.length
|
|
549
|
-
) {
|
|
550
|
-
// Use the original camelCase parameter name for the SDK
|
|
551
|
-
sdkParams[param.name] = convertValue(
|
|
552
|
-
positionalArgs[argIndex],
|
|
553
|
-
param.type,
|
|
554
|
-
);
|
|
555
|
-
argIndex++;
|
|
556
|
-
}
|
|
557
|
-
});
|
|
558
|
-
|
|
559
|
-
// Handle option flags
|
|
560
|
-
Object.entries(options).forEach(([key, value]) => {
|
|
561
|
-
// Convert kebab-case back to camelCase
|
|
562
|
-
const camelKey = key.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
|
|
563
|
-
const param = parameters.find((p) => p.name === camelKey);
|
|
564
|
-
|
|
565
|
-
if (param && value !== undefined) {
|
|
566
|
-
sdkParams[camelKey] = convertValue(value, param.type);
|
|
567
|
-
}
|
|
568
|
-
});
|
|
569
|
-
|
|
570
|
-
return sdkParams;
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
function convertValue(value: unknown, type: CliParameter["type"]): unknown {
|
|
574
|
-
// Don't convert undefined values - let the resolver system handle them
|
|
575
|
-
if (value === undefined) {
|
|
576
|
-
return undefined;
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
switch (type) {
|
|
580
|
-
case "number":
|
|
581
|
-
return Number(value);
|
|
582
|
-
case "boolean":
|
|
583
|
-
return Boolean(value);
|
|
584
|
-
case "array":
|
|
585
|
-
return Array.isArray(value) ? value : [value];
|
|
586
|
-
case "string":
|
|
587
|
-
default:
|
|
588
|
-
// Handle JSON string for objects
|
|
589
|
-
if (
|
|
590
|
-
typeof value === "string" &&
|
|
591
|
-
(value.startsWith("{") || value.startsWith("["))
|
|
592
|
-
) {
|
|
593
|
-
try {
|
|
594
|
-
return JSON.parse(value);
|
|
595
|
-
} catch {
|
|
596
|
-
return value;
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
return value;
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
// ============================================================================
|
|
604
|
-
// Pagination Handlers
|
|
605
|
-
// ============================================================================
|
|
606
|
-
|
|
607
|
-
async function handlePaginatedListWithAsyncIteration(
|
|
608
|
-
sdkMethodName: string,
|
|
609
|
-
sdkResult: unknown,
|
|
610
|
-
functionInfo: FunctionRegistryEntry,
|
|
611
|
-
): Promise<void> {
|
|
612
|
-
const itemName = getItemNameFromMethod(functionInfo);
|
|
613
|
-
let totalShown = 0;
|
|
614
|
-
let pageCount = 0;
|
|
615
|
-
|
|
616
|
-
console.log(
|
|
617
|
-
chalk.blue(`š ${getListTitleFromMethod(sdkMethodName, functionInfo)}\n`),
|
|
618
|
-
);
|
|
619
|
-
|
|
620
|
-
try {
|
|
621
|
-
// Use async iteration to go through pages
|
|
622
|
-
for await (const page of sdkResult as AsyncIterable<{
|
|
623
|
-
data?: unknown[];
|
|
624
|
-
nextCursor?: string;
|
|
625
|
-
}>) {
|
|
626
|
-
const items = page.data || page;
|
|
627
|
-
pageCount++;
|
|
628
|
-
|
|
629
|
-
if (!Array.isArray(items)) {
|
|
630
|
-
console.log(chalk.yellow(`No ${itemName} found.`));
|
|
631
|
-
return;
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
if (items.length === 0 && pageCount === 1) {
|
|
635
|
-
console.log(chalk.yellow(`No ${itemName} found.`));
|
|
636
|
-
return;
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
if (items.length === 0) {
|
|
640
|
-
break; // No more items
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
// Clear screen for subsequent pages (not the first)
|
|
644
|
-
if (pageCount > 1) {
|
|
645
|
-
console.clear();
|
|
646
|
-
console.log(
|
|
647
|
-
chalk.blue(
|
|
648
|
-
`š ${getListTitleFromMethod(sdkMethodName, functionInfo)}\n`,
|
|
649
|
-
),
|
|
650
|
-
);
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
// Format and display items using function info
|
|
654
|
-
if (functionInfo && functionInfo.inputSchema) {
|
|
655
|
-
formatItemsFromSchema(
|
|
656
|
-
functionInfo as { inputSchema: z.ZodType; outputSchema?: z.ZodType },
|
|
657
|
-
items,
|
|
658
|
-
totalShown,
|
|
659
|
-
);
|
|
660
|
-
} else {
|
|
661
|
-
formatItemsGeneric(items, totalShown);
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
totalShown += items.length;
|
|
665
|
-
console.log(
|
|
666
|
-
chalk.green(
|
|
667
|
-
`\nā
Showing ${totalShown} ${itemName} (page ${pageCount})`,
|
|
668
|
-
),
|
|
669
|
-
);
|
|
670
|
-
|
|
671
|
-
// Only prompt if there's a nextCursor (more pages available)
|
|
672
|
-
if (page.nextCursor) {
|
|
673
|
-
const { continueReading } = await inquirer.prompt([
|
|
674
|
-
{
|
|
675
|
-
type: "confirm",
|
|
676
|
-
name: "continueReading",
|
|
677
|
-
message: `Load next page?`,
|
|
678
|
-
default: true,
|
|
679
|
-
},
|
|
680
|
-
]);
|
|
681
|
-
|
|
682
|
-
if (!continueReading) {
|
|
683
|
-
break;
|
|
684
|
-
}
|
|
685
|
-
} else {
|
|
686
|
-
// No more pages available, exit gracefully
|
|
687
|
-
break;
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
console.log(chalk.gray(`\nš Finished browsing ${itemName}`));
|
|
692
|
-
} catch (error) {
|
|
693
|
-
// If the result is not async iterable, fall back to showing the first page
|
|
694
|
-
const items = (sdkResult as { data?: unknown[] })?.data || sdkResult;
|
|
695
|
-
if (Array.isArray(items)) {
|
|
696
|
-
if (items.length === 0) {
|
|
697
|
-
console.log(chalk.yellow(`No ${itemName} found.`));
|
|
698
|
-
return;
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
if (functionInfo && functionInfo.inputSchema) {
|
|
702
|
-
formatItemsFromSchema(
|
|
703
|
-
functionInfo as { inputSchema: z.ZodType; outputSchema?: z.ZodType },
|
|
704
|
-
items,
|
|
705
|
-
0,
|
|
706
|
-
);
|
|
707
|
-
} else {
|
|
708
|
-
formatItemsGeneric(items, 0);
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
console.log(chalk.green(`\nā
Showing ${items.length} ${itemName}`));
|
|
712
|
-
} else {
|
|
713
|
-
throw error;
|
|
714
|
-
}
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
function formatNonPaginatedResults(
|
|
719
|
-
result: unknown[],
|
|
720
|
-
requestedMaxItems?: number,
|
|
721
|
-
userSpecifiedMaxItems?: boolean,
|
|
722
|
-
useRawJson?: boolean,
|
|
723
|
-
functionInfo?: FunctionRegistryEntry,
|
|
724
|
-
): void {
|
|
725
|
-
if (!Array.isArray(result)) {
|
|
726
|
-
if (useRawJson) {
|
|
727
|
-
console.log(JSON.stringify(result, null, 2));
|
|
728
|
-
} else {
|
|
729
|
-
formatJsonOutput(result);
|
|
730
|
-
}
|
|
731
|
-
return;
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
if (useRawJson) {
|
|
735
|
-
console.log(JSON.stringify(result, null, 2));
|
|
736
|
-
return;
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
const itemName = functionInfo ? getItemNameFromMethod(functionInfo) : "items";
|
|
740
|
-
|
|
741
|
-
if (result.length === 0) {
|
|
742
|
-
console.log(chalk.yellow(`No ${itemName} found.`));
|
|
743
|
-
return;
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
console.log(chalk.green(`\nā
Found ${result.length} ${itemName}:\n`));
|
|
747
|
-
|
|
748
|
-
// Use function info for formatting
|
|
749
|
-
if (functionInfo && functionInfo.inputSchema) {
|
|
750
|
-
formatItemsFromSchema(
|
|
751
|
-
functionInfo as { inputSchema: z.ZodType; outputSchema?: z.ZodType },
|
|
752
|
-
result,
|
|
753
|
-
);
|
|
754
|
-
} else {
|
|
755
|
-
// Fallback to generic formatting
|
|
756
|
-
formatItemsGeneric(result);
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
// Show appropriate status message
|
|
760
|
-
if (userSpecifiedMaxItems && requestedMaxItems) {
|
|
761
|
-
console.log(
|
|
762
|
-
chalk.gray(
|
|
763
|
-
`\nš Showing up to ${requestedMaxItems} ${itemName} (--max-items ${requestedMaxItems})`,
|
|
764
|
-
),
|
|
765
|
-
);
|
|
766
|
-
} else {
|
|
767
|
-
console.log(chalk.gray(`\nš All available ${itemName} shown`));
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
function formatItemsGeneric(
|
|
772
|
-
items: unknown[],
|
|
773
|
-
startingNumber: number = 0,
|
|
774
|
-
): void {
|
|
775
|
-
// Fallback formatting for items without schema metadata
|
|
776
|
-
items.forEach((item, index) => {
|
|
777
|
-
const itemObj = item as Record<string, unknown>;
|
|
778
|
-
const name = itemObj?.name || itemObj?.key || itemObj?.id || "Item";
|
|
779
|
-
console.log(
|
|
780
|
-
`${chalk.gray(`${startingNumber + index + 1}.`)} ${chalk.cyan(String(name))}`,
|
|
781
|
-
);
|
|
782
|
-
if (itemObj?.description) {
|
|
783
|
-
console.log(` ${chalk.dim(String(itemObj.description))}`);
|
|
784
|
-
}
|
|
785
|
-
console.log();
|
|
786
|
-
});
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
// Generic helper functions that infer from schema description or method name
|
|
790
|
-
function getItemNameFromMethod(functionInfo: FunctionRegistryEntry): string {
|
|
791
|
-
if (functionInfo.itemType) {
|
|
792
|
-
return `${functionInfo.itemType} items`;
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
return "items";
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
function getListTitleFromMethod(
|
|
799
|
-
methodName: string,
|
|
800
|
-
functionInfo: FunctionRegistryEntry,
|
|
801
|
-
): string {
|
|
802
|
-
if (functionInfo.itemType) {
|
|
803
|
-
return `Available ${functionInfo.itemType} items`;
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
return `${methodName} items`;
|
|
807
|
-
}
|