@zapier/zapier-sdk-cli 0.22.0 ā 0.22.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.
- package/CHANGELOG.md +8 -0
- package/dist/cli.cjs +2 -2
- package/dist/cli.mjs +2 -2
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/package.json +78 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +105 -0
- package/dist/src/generators/ast-generator.d.ts +41 -0
- package/dist/src/generators/ast-generator.js +409 -0
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.js +4 -0
- package/dist/src/plugins/add/index.d.ts +15 -0
- package/dist/src/plugins/add/index.js +103 -0
- package/dist/src/plugins/add/schemas.d.ts +8 -0
- package/dist/src/plugins/add/schemas.js +22 -0
- package/dist/src/plugins/buildManifest/index.d.ts +13 -0
- package/dist/src/plugins/buildManifest/index.js +81 -0
- package/dist/src/plugins/buildManifest/schemas.d.ts +44 -0
- package/dist/src/plugins/buildManifest/schemas.js +17 -0
- package/dist/src/plugins/bundleCode/index.d.ts +15 -0
- package/dist/src/plugins/bundleCode/index.js +80 -0
- package/dist/src/plugins/bundleCode/schemas.d.ts +10 -0
- package/dist/src/plugins/bundleCode/schemas.js +19 -0
- package/dist/src/plugins/generateAppTypes/index.d.ts +13 -0
- package/dist/src/plugins/generateAppTypes/index.js +157 -0
- package/dist/src/plugins/generateAppTypes/schemas.d.ts +58 -0
- package/dist/src/plugins/generateAppTypes/schemas.js +21 -0
- package/dist/src/plugins/getLoginConfigPath/index.d.ts +15 -0
- package/dist/src/plugins/getLoginConfigPath/index.js +19 -0
- package/dist/src/plugins/getLoginConfigPath/schemas.d.ts +3 -0
- package/dist/src/plugins/getLoginConfigPath/schemas.js +5 -0
- package/dist/src/plugins/index.d.ts +8 -0
- package/dist/src/plugins/index.js +8 -0
- package/dist/src/plugins/login/index.d.ts +23 -0
- package/dist/src/plugins/login/index.js +95 -0
- package/dist/src/plugins/login/schemas.d.ts +5 -0
- package/dist/src/plugins/login/schemas.js +10 -0
- package/dist/src/plugins/logout/index.d.ts +15 -0
- package/dist/src/plugins/logout/index.js +18 -0
- package/dist/src/plugins/logout/schemas.d.ts +3 -0
- package/dist/src/plugins/logout/schemas.js +5 -0
- package/dist/src/plugins/mcp/index.d.ts +15 -0
- package/dist/src/plugins/mcp/index.js +24 -0
- package/dist/src/plugins/mcp/schemas.d.ts +5 -0
- package/dist/src/plugins/mcp/schemas.js +10 -0
- package/dist/src/sdk.d.ts +9 -0
- package/dist/src/sdk.js +24 -0
- package/dist/src/telemetry/builders.d.ts +42 -0
- package/dist/src/telemetry/builders.js +55 -0
- package/dist/src/telemetry/events.d.ts +37 -0
- package/dist/src/telemetry/events.js +4 -0
- package/dist/src/types/sdk.d.ts +5 -0
- package/dist/src/types/sdk.js +1 -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 +7 -0
- package/dist/src/utils/auth/login.js +154 -0
- package/dist/src/utils/cli-generator-utils.d.ts +14 -0
- package/dist/src/utils/cli-generator-utils.js +122 -0
- package/dist/src/utils/cli-generator.d.ts +3 -0
- package/dist/src/utils/cli-generator.js +555 -0
- package/dist/src/utils/constants.d.ts +3 -0
- package/dist/src/utils/constants.js +5 -0
- package/dist/src/utils/directory-detection.d.ts +5 -0
- package/dist/src/utils/directory-detection.js +21 -0
- package/dist/src/utils/errors.d.ts +16 -0
- package/dist/src/utils/errors.js +19 -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 +8 -0
- package/dist/src/utils/log.js +21 -0
- package/dist/src/utils/manifest-helpers.d.ts +10 -0
- package/dist/src/utils/manifest-helpers.js +19 -0
- package/dist/src/utils/package-manager-detector.d.ts +16 -0
- package/dist/src/utils/package-manager-detector.js +77 -0
- package/dist/src/utils/parameter-resolver.d.ts +42 -0
- package/dist/src/utils/parameter-resolver.js +699 -0
- package/dist/src/utils/schema-formatter.d.ts +6 -0
- package/dist/src/utils/schema-formatter.js +115 -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 +21 -0
- package/dist/src/utils/version-checker.d.ts +17 -0
- package/dist/src/utils/version-checker.js +156 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +4 -4
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { isPositional, formatErrorMessage, ZapierError, } from "@zapier/zapier-sdk";
|
|
3
|
+
import { SchemaParameterResolver } from "./parameter-resolver";
|
|
4
|
+
import { formatItemsFromSchema, formatJsonOutput } from "./schema-formatter";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
import inquirer from "inquirer";
|
|
7
|
+
import { ZapierCliError, ZapierCliExitError } from "./errors";
|
|
8
|
+
// ============================================================================
|
|
9
|
+
// Schema Analysis
|
|
10
|
+
// ============================================================================
|
|
11
|
+
function analyzeZodSchema(schema, functionInfo) {
|
|
12
|
+
const parameters = [];
|
|
13
|
+
// Handle ZodEffects (schemas with .refine(), .transform(), etc.)
|
|
14
|
+
// In Zod, effects have type "effect" and inner schema is accessed via innerType
|
|
15
|
+
const schemaDef = schema._zod?.def;
|
|
16
|
+
if (schemaDef?.type === "effect" && schemaDef.innerType) {
|
|
17
|
+
return analyzeZodSchema(schemaDef.innerType, functionInfo);
|
|
18
|
+
}
|
|
19
|
+
if (schema instanceof z.ZodObject) {
|
|
20
|
+
const shape = schema.shape;
|
|
21
|
+
for (const [key, fieldSchema] of Object.entries(shape)) {
|
|
22
|
+
const param = analyzeZodField(key, fieldSchema, functionInfo);
|
|
23
|
+
if (param) {
|
|
24
|
+
parameters.push(param);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return parameters;
|
|
29
|
+
}
|
|
30
|
+
function analyzeZodField(name, schema, functionInfo) {
|
|
31
|
+
let baseSchema = schema;
|
|
32
|
+
let required = true;
|
|
33
|
+
let defaultValue = undefined;
|
|
34
|
+
// Unwrap optional, default, and nullable wrappers - keep unwrapping until we get to the base type
|
|
35
|
+
while (true) {
|
|
36
|
+
if (baseSchema instanceof z.ZodOptional) {
|
|
37
|
+
required = false;
|
|
38
|
+
baseSchema = baseSchema._zod.def.innerType;
|
|
39
|
+
}
|
|
40
|
+
else if (baseSchema instanceof z.ZodDefault) {
|
|
41
|
+
required = false;
|
|
42
|
+
defaultValue = baseSchema._zod.def.defaultValue();
|
|
43
|
+
baseSchema = baseSchema._zod.def.innerType;
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
// Check for ZodNullable - nullable doesn't affect CLI required status, but we need to unwrap it to get the base type
|
|
47
|
+
// Use _zod.def.type for Zod 4 compatibility (matches patterns elsewhere in codebase)
|
|
48
|
+
const zodDef = baseSchema._zod?.def;
|
|
49
|
+
if (zodDef?.type === "nullable" && zodDef.innerType) {
|
|
50
|
+
baseSchema = zodDef.innerType;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
// No more wrappers to unwrap
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Determine parameter type
|
|
58
|
+
let paramType = "string";
|
|
59
|
+
let choices;
|
|
60
|
+
if (baseSchema instanceof z.ZodString) {
|
|
61
|
+
paramType = "string";
|
|
62
|
+
}
|
|
63
|
+
else if (baseSchema instanceof z.ZodNumber) {
|
|
64
|
+
paramType = "number";
|
|
65
|
+
}
|
|
66
|
+
else if (baseSchema instanceof z.ZodBoolean) {
|
|
67
|
+
paramType = "boolean";
|
|
68
|
+
}
|
|
69
|
+
else if (baseSchema instanceof z.ZodArray) {
|
|
70
|
+
paramType = "array";
|
|
71
|
+
}
|
|
72
|
+
else if (baseSchema instanceof z.ZodEnum) {
|
|
73
|
+
paramType = "string";
|
|
74
|
+
choices = baseSchema.options;
|
|
75
|
+
}
|
|
76
|
+
else if (baseSchema instanceof z.ZodRecord) {
|
|
77
|
+
// Handle Record<string, any> as JSON string input
|
|
78
|
+
paramType = "string";
|
|
79
|
+
}
|
|
80
|
+
// Check if this parameter has a resolver
|
|
81
|
+
let paramHasResolver = false;
|
|
82
|
+
// Check function-specific resolvers first
|
|
83
|
+
if (functionInfo?.resolvers?.[name]) {
|
|
84
|
+
paramHasResolver = true;
|
|
85
|
+
}
|
|
86
|
+
// Extract resolver metadata
|
|
87
|
+
return {
|
|
88
|
+
name,
|
|
89
|
+
type: paramType,
|
|
90
|
+
required,
|
|
91
|
+
description: schema.description,
|
|
92
|
+
default: defaultValue,
|
|
93
|
+
choices,
|
|
94
|
+
hasResolver: paramHasResolver,
|
|
95
|
+
isPositional: isPositional(schema),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
// ============================================================================
|
|
99
|
+
// CLI Structure Derivation - Purely Generic
|
|
100
|
+
// ============================================================================
|
|
101
|
+
/**
|
|
102
|
+
* Convert camelCase to kebab-case
|
|
103
|
+
* e.g., listApps -> list-apps, generateTypes -> generate-types
|
|
104
|
+
*/
|
|
105
|
+
function toKebabCase(str) {
|
|
106
|
+
return str.replace(/([A-Z])/g, "-$1").toLowerCase();
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Convert SDK method name directly to CLI command
|
|
110
|
+
* e.g., listApps -> list-apps, getApp -> get-app, generateTypes -> generate-types
|
|
111
|
+
*/
|
|
112
|
+
function methodNameToCliCommand(methodName) {
|
|
113
|
+
return toKebabCase(methodName);
|
|
114
|
+
}
|
|
115
|
+
// ============================================================================
|
|
116
|
+
// CLI Command Generation - Completely Generic
|
|
117
|
+
// ============================================================================
|
|
118
|
+
export function generateCliCommands(program, sdk) {
|
|
119
|
+
// Check if SDK has registry
|
|
120
|
+
if (typeof sdk.getRegistry !== "function") {
|
|
121
|
+
console.error("SDK registry not available");
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const registry = sdk.getRegistry({ package: "cli" });
|
|
125
|
+
// Create all commands first
|
|
126
|
+
registry.functions.forEach((fnInfo) => {
|
|
127
|
+
if (!fnInfo.inputSchema) {
|
|
128
|
+
console.warn(`Schema not found for ${fnInfo.name}`);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
// Convert methodName to kebab-case CLI command
|
|
132
|
+
const cliCommandName = methodNameToCliCommand(fnInfo.name);
|
|
133
|
+
const config = createCommandConfig(cliCommandName, fnInfo, sdk);
|
|
134
|
+
addCommand(program, cliCommandName, config);
|
|
135
|
+
});
|
|
136
|
+
// Override the help display to show commands grouped by category
|
|
137
|
+
program.configureHelp({
|
|
138
|
+
formatHelp: (cmd, helper) => {
|
|
139
|
+
const helpWidth = helper.helpWidth || 80;
|
|
140
|
+
let output = helper.commandUsage(cmd) + "\n";
|
|
141
|
+
if (cmd.description()) {
|
|
142
|
+
output += helper.wrap(cmd.description(), helpWidth, 0) + "\n";
|
|
143
|
+
}
|
|
144
|
+
// Add options section
|
|
145
|
+
const options = helper.visibleOptions(cmd);
|
|
146
|
+
if (options.length > 0) {
|
|
147
|
+
output += "\nOptions:\n";
|
|
148
|
+
const longestOptionLength = Math.max(...options.map((opt) => helper.optionTerm(opt).length));
|
|
149
|
+
options.forEach((option) => {
|
|
150
|
+
const term = helper.optionTerm(option);
|
|
151
|
+
const padding = " ".repeat(Math.max(2, longestOptionLength - term.length + 4));
|
|
152
|
+
output += ` ${term}${padding}${helper.optionDescription(option)}\n`;
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
// Add categorized commands section
|
|
156
|
+
const commands = helper.visibleCommands(cmd);
|
|
157
|
+
if (commands.length > 0) {
|
|
158
|
+
output += "\nCommands:\n";
|
|
159
|
+
// Collect all SDK commands that belong to categories
|
|
160
|
+
const categorizedCommands = new Set();
|
|
161
|
+
// Group SDK commands by categories
|
|
162
|
+
registry.categories.forEach((category) => {
|
|
163
|
+
const categoryCommands = commands.filter((command) => category.functions.some((functionName) => {
|
|
164
|
+
const cliCommandName = methodNameToCliCommand(functionName);
|
|
165
|
+
return command.name() === cliCommandName;
|
|
166
|
+
}));
|
|
167
|
+
if (categoryCommands.length > 0) {
|
|
168
|
+
output += `\n ${category.titlePlural}:\n`;
|
|
169
|
+
categoryCommands.forEach((command) => {
|
|
170
|
+
output += ` ${helper.subcommandTerm(command)}\n`;
|
|
171
|
+
categorizedCommands.add(command.name());
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
// Add any remaining commands that aren't part of SDK categories
|
|
176
|
+
const otherCommands = commands.filter((command) => !categorizedCommands.has(command.name()));
|
|
177
|
+
if (otherCommands.length > 0) {
|
|
178
|
+
output += `\n Other:\n`;
|
|
179
|
+
otherCommands.forEach((command) => {
|
|
180
|
+
output += ` ${helper.subcommandTerm(command)}\n`;
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return output;
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
function createCommandConfig(cliCommandName, functionInfo, sdk) {
|
|
189
|
+
const schema = functionInfo.inputSchema;
|
|
190
|
+
const parameters = analyzeZodSchema(schema, functionInfo);
|
|
191
|
+
const description = schema.description || `${cliCommandName} command`;
|
|
192
|
+
const handler = async (...args) => {
|
|
193
|
+
try {
|
|
194
|
+
// The last argument is always the command object with parsed options
|
|
195
|
+
const commandObj = args[args.length - 1];
|
|
196
|
+
const options = commandObj.opts();
|
|
197
|
+
// Check if this is a list command for pagination
|
|
198
|
+
const isListCommand = functionInfo.type === "list";
|
|
199
|
+
const hasPaginationParams = parameters.some((p) => p.name === "maxItems" || p.name === "pageSize");
|
|
200
|
+
const hasUserSpecifiedMaxItems = "maxItems" in options && options.maxItems !== undefined;
|
|
201
|
+
const shouldUseJson = options.json;
|
|
202
|
+
// Convert CLI args to SDK method parameters
|
|
203
|
+
const rawParams = convertCliArgsToSdkParams(parameters, args.slice(0, -1), options);
|
|
204
|
+
// Resolve missing parameters interactively using schema metadata
|
|
205
|
+
const resolver = new SchemaParameterResolver();
|
|
206
|
+
const resolvedParams = await resolver.resolveParameters(schema, rawParams, sdk, functionInfo.name);
|
|
207
|
+
// Handle paginated list commands with async iteration
|
|
208
|
+
if (isListCommand &&
|
|
209
|
+
hasPaginationParams &&
|
|
210
|
+
!shouldUseJson &&
|
|
211
|
+
!hasUserSpecifiedMaxItems) {
|
|
212
|
+
// Get the async iterable directly from the SDK method call (don't await it! that breaks the next page behavior)
|
|
213
|
+
const sdkObj = sdk;
|
|
214
|
+
const sdkIterator = sdkObj[functionInfo.name](resolvedParams);
|
|
215
|
+
await handlePaginatedListWithAsyncIteration(functionInfo.name, sdkIterator, functionInfo);
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
// Special handling for commands that write to files
|
|
219
|
+
const hasOutputFile = resolvedParams.output;
|
|
220
|
+
if (hasOutputFile) {
|
|
221
|
+
// Call the SDK method for file output commands
|
|
222
|
+
const sdkObj = sdk;
|
|
223
|
+
await sdkObj[functionInfo.name](resolvedParams);
|
|
224
|
+
console.log(chalk.green(`ā
${cliCommandName} completed successfully!`));
|
|
225
|
+
console.log(chalk.gray(`Output written to: ${hasOutputFile}`));
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
// Call the SDK method and handle non-paginated results
|
|
229
|
+
const sdkObj = sdk;
|
|
230
|
+
let result = await sdkObj[functionInfo.name](resolvedParams);
|
|
231
|
+
// Handle Response objects by wrapping in a structured envelope
|
|
232
|
+
if (result instanceof Response) {
|
|
233
|
+
const response = result;
|
|
234
|
+
let body;
|
|
235
|
+
// Read as text first to preserve body for error messages
|
|
236
|
+
const text = await response
|
|
237
|
+
.text()
|
|
238
|
+
.catch(() => "[unable to read body]");
|
|
239
|
+
try {
|
|
240
|
+
body = JSON.parse(text);
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
throw new Error(`Failed to parse response as JSON (status: ${response.status}). Body: ${text.slice(0, 500)}`);
|
|
244
|
+
}
|
|
245
|
+
result = {
|
|
246
|
+
statusCode: response.status,
|
|
247
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
248
|
+
body,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
const items = result?.data
|
|
252
|
+
? result.data
|
|
253
|
+
: result;
|
|
254
|
+
if (shouldUseJson) {
|
|
255
|
+
console.log(JSON.stringify(items, null, 2));
|
|
256
|
+
}
|
|
257
|
+
else if (isListCommand) {
|
|
258
|
+
formatNonPaginatedResults(items, resolvedParams.maxItems, hasUserSpecifiedMaxItems, shouldUseJson, functionInfo);
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
formatJsonOutput(items);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
catch (error) {
|
|
266
|
+
// Handle Zod validation errors more gracefully
|
|
267
|
+
if (error instanceof Error && error.message.includes('"code"')) {
|
|
268
|
+
try {
|
|
269
|
+
const validationErrors = JSON.parse(error.message);
|
|
270
|
+
console.error(chalk.red("ā Validation Error:"));
|
|
271
|
+
validationErrors.forEach((err) => {
|
|
272
|
+
const errorObj = err;
|
|
273
|
+
const field = errorObj?.path?.join(".") || "unknown";
|
|
274
|
+
console.error(chalk.yellow(` ⢠${field}: ${errorObj?.message || "Unknown error"}`));
|
|
275
|
+
});
|
|
276
|
+
console.error("\n" + chalk.dim(`Use --help to see available options`));
|
|
277
|
+
throw new ZapierCliExitError("Validation failed", 1);
|
|
278
|
+
}
|
|
279
|
+
catch {
|
|
280
|
+
console.error(chalk.red("Error:"), error instanceof Error ? error.message : String(error));
|
|
281
|
+
throw new ZapierCliExitError(error instanceof Error ? error.message : String(error), 1);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
else if (error instanceof ZapierCliError) {
|
|
285
|
+
// Re-throw all CLI errors as-is
|
|
286
|
+
throw error;
|
|
287
|
+
}
|
|
288
|
+
else if (error instanceof ZapierError) {
|
|
289
|
+
// Handle SDK errors with clean, user-friendly messages using our formatter
|
|
290
|
+
const formattedMessage = formatErrorMessage(error);
|
|
291
|
+
console.error(chalk.red("ā Error:"), formattedMessage);
|
|
292
|
+
throw new ZapierCliExitError(formattedMessage, 1);
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
// Handle other errors
|
|
296
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
297
|
+
console.error(chalk.red("ā Error:"), errorMessage);
|
|
298
|
+
throw new ZapierCliExitError(errorMessage, 1);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
return {
|
|
303
|
+
description,
|
|
304
|
+
parameters,
|
|
305
|
+
handler,
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
function addCommand(program, commandName, config) {
|
|
309
|
+
const command = program.command(commandName).description(config.description);
|
|
310
|
+
// Track whether we've already used a positional array parameter
|
|
311
|
+
let hasPositionalArray = false;
|
|
312
|
+
// Add parameters to command
|
|
313
|
+
config.parameters.forEach((param) => {
|
|
314
|
+
// Convert camelCase to kebab-case for CLI display
|
|
315
|
+
const kebabName = param.name.replace(/([A-Z])/g, "-$1").toLowerCase();
|
|
316
|
+
if (param.hasResolver && param.required) {
|
|
317
|
+
// Required parameters with resolvers become optional positional arguments (resolver handles prompting)
|
|
318
|
+
command.argument(`[${kebabName}]`, param.description || `${kebabName} parameter`);
|
|
319
|
+
}
|
|
320
|
+
else if (param.required &&
|
|
321
|
+
param.type === "array" &&
|
|
322
|
+
!hasPositionalArray) {
|
|
323
|
+
// First required array parameter becomes a variadic positional argument
|
|
324
|
+
hasPositionalArray = true;
|
|
325
|
+
command.argument(`<${kebabName}...>`, param.description || `${kebabName} parameter`);
|
|
326
|
+
}
|
|
327
|
+
else if (param.required && param.type === "array") {
|
|
328
|
+
// Subsequent required array parameters become required flags
|
|
329
|
+
const flags = [`--${kebabName}`];
|
|
330
|
+
const flagSignature = flags.join(", ") + ` <values...>`;
|
|
331
|
+
command.requiredOption(flagSignature, param.description || `${kebabName} parameter (required)`);
|
|
332
|
+
}
|
|
333
|
+
else if (param.required) {
|
|
334
|
+
// Required parameters without resolvers become required positional arguments
|
|
335
|
+
command.argument(`<${kebabName}>`, param.description || `${kebabName} parameter`);
|
|
336
|
+
}
|
|
337
|
+
else if (param.isPositional) {
|
|
338
|
+
// Optional parameters marked as positional become optional positional arguments
|
|
339
|
+
command.argument(`[${kebabName}]`, param.description || `${kebabName} parameter`);
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
// Optional parameters become flags (whether they have resolvers or not)
|
|
343
|
+
const flags = [`--${kebabName}`];
|
|
344
|
+
if (param.type === "boolean") {
|
|
345
|
+
command.option(flags.join(", "), param.description);
|
|
346
|
+
}
|
|
347
|
+
else if (param.type === "array") {
|
|
348
|
+
// For arrays, use variadic syntax to collect multiple values
|
|
349
|
+
const flagSignature = flags.join(", ") + ` <values...>`;
|
|
350
|
+
command.option(flagSignature, param.description, param.default);
|
|
351
|
+
}
|
|
352
|
+
else {
|
|
353
|
+
const flagSignature = flags.join(", ") + ` <${param.type}>`;
|
|
354
|
+
command.option(flagSignature, param.description || "", param.default);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
// Add formatting options for all commands
|
|
359
|
+
command.option("--json", "Output raw JSON instead of formatted results");
|
|
360
|
+
command.action(config.handler);
|
|
361
|
+
}
|
|
362
|
+
// ============================================================================
|
|
363
|
+
// Parameter Conversion
|
|
364
|
+
// ============================================================================
|
|
365
|
+
function convertCliArgsToSdkParams(parameters, positionalArgs, options) {
|
|
366
|
+
const sdkParams = {};
|
|
367
|
+
// Handle positional arguments (required parameters or optional positional parameters)
|
|
368
|
+
let argIndex = 0;
|
|
369
|
+
parameters.forEach((param) => {
|
|
370
|
+
if ((param.required || param.isPositional) &&
|
|
371
|
+
argIndex < positionalArgs.length) {
|
|
372
|
+
// Use the original camelCase parameter name for the SDK
|
|
373
|
+
sdkParams[param.name] = convertValue(positionalArgs[argIndex], param.type);
|
|
374
|
+
argIndex++;
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
// Handle option flags
|
|
378
|
+
Object.entries(options).forEach(([key, value]) => {
|
|
379
|
+
// Convert kebab-case back to camelCase
|
|
380
|
+
const camelKey = key.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
|
|
381
|
+
const param = parameters.find((p) => p.name === camelKey);
|
|
382
|
+
if (param && value !== undefined) {
|
|
383
|
+
sdkParams[camelKey] = convertValue(value, param.type);
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
return sdkParams;
|
|
387
|
+
}
|
|
388
|
+
function convertValue(value, type) {
|
|
389
|
+
// Don't convert undefined values - let the resolver system handle them
|
|
390
|
+
if (value === undefined) {
|
|
391
|
+
return undefined;
|
|
392
|
+
}
|
|
393
|
+
switch (type) {
|
|
394
|
+
case "number":
|
|
395
|
+
return Number(value);
|
|
396
|
+
case "boolean":
|
|
397
|
+
return Boolean(value);
|
|
398
|
+
case "array":
|
|
399
|
+
return Array.isArray(value) ? value : [value];
|
|
400
|
+
case "string":
|
|
401
|
+
default:
|
|
402
|
+
// Handle JSON string for objects
|
|
403
|
+
if (typeof value === "string" &&
|
|
404
|
+
(value.startsWith("{") || value.startsWith("["))) {
|
|
405
|
+
try {
|
|
406
|
+
return JSON.parse(value);
|
|
407
|
+
}
|
|
408
|
+
catch {
|
|
409
|
+
return value;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return value;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
// ============================================================================
|
|
416
|
+
// Pagination Handlers
|
|
417
|
+
// ============================================================================
|
|
418
|
+
async function handlePaginatedListWithAsyncIteration(sdkMethodName, sdkResult, functionInfo) {
|
|
419
|
+
const itemName = getItemNameFromMethod(functionInfo);
|
|
420
|
+
let totalShown = 0;
|
|
421
|
+
let pageCount = 0;
|
|
422
|
+
console.log(chalk.blue(`š ${getListTitleFromMethod(sdkMethodName, functionInfo)}\n`));
|
|
423
|
+
try {
|
|
424
|
+
// Use async iteration to go through pages
|
|
425
|
+
for await (const page of sdkResult) {
|
|
426
|
+
const items = page.data || page;
|
|
427
|
+
pageCount++;
|
|
428
|
+
if (!Array.isArray(items)) {
|
|
429
|
+
console.log(chalk.yellow(`No ${itemName} found.`));
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
if (items.length === 0 && pageCount === 1) {
|
|
433
|
+
console.log(chalk.yellow(`No ${itemName} found.`));
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
if (items.length === 0) {
|
|
437
|
+
break; // No more items
|
|
438
|
+
}
|
|
439
|
+
// Clear screen for subsequent pages (not the first)
|
|
440
|
+
if (pageCount > 1) {
|
|
441
|
+
console.clear();
|
|
442
|
+
console.log(chalk.blue(`š ${getListTitleFromMethod(sdkMethodName, functionInfo)}\n`));
|
|
443
|
+
}
|
|
444
|
+
// Format and display items using function info
|
|
445
|
+
if (functionInfo && functionInfo.inputSchema) {
|
|
446
|
+
formatItemsFromSchema(functionInfo, items, totalShown);
|
|
447
|
+
}
|
|
448
|
+
else {
|
|
449
|
+
formatItemsGeneric(items, totalShown);
|
|
450
|
+
}
|
|
451
|
+
totalShown += items.length;
|
|
452
|
+
console.log(chalk.green(`\nā
Showing ${totalShown} ${itemName} (page ${pageCount})`));
|
|
453
|
+
// Only prompt if there's a nextCursor (more pages available)
|
|
454
|
+
if (page.nextCursor) {
|
|
455
|
+
const { continueReading } = await inquirer.prompt([
|
|
456
|
+
{
|
|
457
|
+
type: "confirm",
|
|
458
|
+
name: "continueReading",
|
|
459
|
+
message: `Load next page?`,
|
|
460
|
+
default: true,
|
|
461
|
+
},
|
|
462
|
+
]);
|
|
463
|
+
if (!continueReading) {
|
|
464
|
+
break;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
else {
|
|
468
|
+
// No more pages available, exit gracefully
|
|
469
|
+
break;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
console.log(chalk.gray(`\nš Finished browsing ${itemName}`));
|
|
473
|
+
}
|
|
474
|
+
catch (error) {
|
|
475
|
+
// If the result is not async iterable, fall back to showing the first page
|
|
476
|
+
const items = sdkResult?.data || sdkResult;
|
|
477
|
+
if (Array.isArray(items)) {
|
|
478
|
+
if (items.length === 0) {
|
|
479
|
+
console.log(chalk.yellow(`No ${itemName} found.`));
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
if (functionInfo && functionInfo.inputSchema) {
|
|
483
|
+
formatItemsFromSchema(functionInfo, items, 0);
|
|
484
|
+
}
|
|
485
|
+
else {
|
|
486
|
+
formatItemsGeneric(items, 0);
|
|
487
|
+
}
|
|
488
|
+
console.log(chalk.green(`\nā
Showing ${items.length} ${itemName}`));
|
|
489
|
+
}
|
|
490
|
+
else {
|
|
491
|
+
throw error;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
function formatNonPaginatedResults(result, requestedMaxItems, userSpecifiedMaxItems, useRawJson, functionInfo) {
|
|
496
|
+
if (!Array.isArray(result)) {
|
|
497
|
+
if (useRawJson) {
|
|
498
|
+
console.log(JSON.stringify(result, null, 2));
|
|
499
|
+
}
|
|
500
|
+
else {
|
|
501
|
+
formatJsonOutput(result);
|
|
502
|
+
}
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
if (useRawJson) {
|
|
506
|
+
console.log(JSON.stringify(result, null, 2));
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
const itemName = functionInfo ? getItemNameFromMethod(functionInfo) : "items";
|
|
510
|
+
if (result.length === 0) {
|
|
511
|
+
console.log(chalk.yellow(`No ${itemName} found.`));
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
console.log(chalk.green(`\nā
Found ${result.length} ${itemName}:\n`));
|
|
515
|
+
// Use function info for formatting
|
|
516
|
+
if (functionInfo && functionInfo.inputSchema) {
|
|
517
|
+
formatItemsFromSchema(functionInfo, result);
|
|
518
|
+
}
|
|
519
|
+
else {
|
|
520
|
+
// Fallback to generic formatting
|
|
521
|
+
formatItemsGeneric(result);
|
|
522
|
+
}
|
|
523
|
+
// Show appropriate status message
|
|
524
|
+
if (userSpecifiedMaxItems && requestedMaxItems) {
|
|
525
|
+
console.log(chalk.gray(`\nš Showing up to ${requestedMaxItems} ${itemName} (--max-items ${requestedMaxItems})`));
|
|
526
|
+
}
|
|
527
|
+
else {
|
|
528
|
+
console.log(chalk.gray(`\nš All available ${itemName} shown`));
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
function formatItemsGeneric(items, startingNumber = 0) {
|
|
532
|
+
// Fallback formatting for items without schema metadata
|
|
533
|
+
items.forEach((item, index) => {
|
|
534
|
+
const itemObj = item;
|
|
535
|
+
const name = itemObj?.name || itemObj?.key || itemObj?.id || "Item";
|
|
536
|
+
console.log(`${chalk.gray(`${startingNumber + index + 1}.`)} ${chalk.cyan(String(name))}`);
|
|
537
|
+
if (itemObj?.description) {
|
|
538
|
+
console.log(` ${chalk.dim(String(itemObj.description))}`);
|
|
539
|
+
}
|
|
540
|
+
console.log();
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
// Generic helper functions that infer from schema description or method name
|
|
544
|
+
function getItemNameFromMethod(functionInfo) {
|
|
545
|
+
if (functionInfo.itemType) {
|
|
546
|
+
return `${functionInfo.itemType} items`;
|
|
547
|
+
}
|
|
548
|
+
return "items";
|
|
549
|
+
}
|
|
550
|
+
function getListTitleFromMethod(methodName, functionInfo) {
|
|
551
|
+
if (functionInfo.itemType) {
|
|
552
|
+
return `Available ${functionInfo.itemType} items`;
|
|
553
|
+
}
|
|
554
|
+
return `${methodName} items`;
|
|
555
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Import shared OAuth constants from login package
|
|
2
|
+
export { AUTH_MODE_HEADER } from "@zapier/zapier-sdk-cli-login";
|
|
3
|
+
// CLI-specific constants
|
|
4
|
+
export const LOGIN_PORTS = [49505, 50575, 52804, 55981, 61010, 63851];
|
|
5
|
+
export const LOGIN_TIMEOUT_MS = 300000; // 5 minutes
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { access } from "fs/promises";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
/**
|
|
4
|
+
* Detect the best default directory for TypeScript types
|
|
5
|
+
* Looks for src or lib directories, falls back to current directory
|
|
6
|
+
*/
|
|
7
|
+
export async function detectTypesOutputDirectory() {
|
|
8
|
+
// Check for common source directories in priority order
|
|
9
|
+
const candidates = ["src", "lib"];
|
|
10
|
+
for (const candidate of candidates) {
|
|
11
|
+
try {
|
|
12
|
+
await access(candidate);
|
|
13
|
+
return join(candidate, "zapier", "apps");
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
// Directory doesn't exist, continue to next candidate
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
// Fall back to current directory
|
|
20
|
+
return "./zapier/apps/";
|
|
21
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ZapierError } from "@zapier/zapier-sdk";
|
|
2
|
+
export declare abstract class ZapierCliError extends ZapierError {
|
|
3
|
+
abstract readonly exitCode: number;
|
|
4
|
+
}
|
|
5
|
+
export declare class ZapierCliUserCancellationError extends ZapierCliError {
|
|
6
|
+
readonly name = "ZapierCliUserCancellationError";
|
|
7
|
+
readonly code = "ZAPIER_CLI_USER_CANCELLATION";
|
|
8
|
+
readonly exitCode = 0;
|
|
9
|
+
constructor(message?: string);
|
|
10
|
+
}
|
|
11
|
+
export declare class ZapierCliExitError extends ZapierCliError {
|
|
12
|
+
readonly name = "ZapierCliExitError";
|
|
13
|
+
readonly code = "ZAPIER_CLI_EXIT";
|
|
14
|
+
readonly exitCode: number;
|
|
15
|
+
constructor(message: string, exitCode?: number);
|
|
16
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ZapierError } from "@zapier/zapier-sdk";
|
|
2
|
+
export class ZapierCliError extends ZapierError {
|
|
3
|
+
}
|
|
4
|
+
export class ZapierCliUserCancellationError extends ZapierCliError {
|
|
5
|
+
constructor(message = "Operation cancelled by user") {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "ZapierCliUserCancellationError";
|
|
8
|
+
this.code = "ZAPIER_CLI_USER_CANCELLATION";
|
|
9
|
+
this.exitCode = 0;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export class ZapierCliExitError extends ZapierCliError {
|
|
13
|
+
constructor(message, exitCode = 1) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.name = "ZapierCliExitError";
|
|
16
|
+
this.code = "ZAPIER_CLI_EXIT";
|
|
17
|
+
this.exitCode = exitCode;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const getCallablePromise = () => {
|
|
2
|
+
let resolve = () => { };
|
|
3
|
+
let reject = () => { };
|
|
4
|
+
const promise = new Promise((_resolve, _reject) => {
|
|
5
|
+
resolve = _resolve;
|
|
6
|
+
reject = _reject;
|
|
7
|
+
});
|
|
8
|
+
return {
|
|
9
|
+
promise,
|
|
10
|
+
resolve,
|
|
11
|
+
reject,
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
export default getCallablePromise;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
declare const log: {
|
|
2
|
+
info: (message: string, ...args: unknown[]) => void;
|
|
3
|
+
error: (message: string, ...args: unknown[]) => void;
|
|
4
|
+
success: (message: string, ...args: unknown[]) => void;
|
|
5
|
+
warn: (message: string, ...args: unknown[]) => void;
|
|
6
|
+
debug: (message: string, ...args: unknown[]) => void;
|
|
7
|
+
};
|
|
8
|
+
export default log;
|