@zapier/zapier-sdk-cli 0.5.0 → 0.6.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 +25 -0
- package/README.md +92 -72
- package/dist/cli.js +116 -21
- package/dist/src/cli.js +1 -1
- package/dist/src/commands/generate-types/cli.js +4 -2
- package/dist/src/commands/generate-types/index.js +26 -5
- package/dist/src/commands/generate-types/schemas.d.ts +3 -0
- package/dist/src/commands/generate-types/schemas.js +4 -0
- package/dist/src/utils/cli-generator.js +55 -3
- package/dist/src/utils/parameter-resolver.js +12 -8
- package/dist/src/utils/schema-formatter.d.ts +1 -1
- package/dist/src/utils/schema-formatter.js +6 -4
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/src/cli.test.ts +1 -2
- package/src/cli.ts +1 -1
- package/src/commands/generate-types/cli.ts +4 -3
- package/src/commands/generate-types/index.ts +32 -6
- package/src/commands/generate-types/schemas.ts +4 -0
- package/src/utils/auth/login.ts +2 -1
- package/src/utils/cli-generator.ts +74 -3
- package/src/utils/parameter-resolver.ts +31 -17
- package/src/utils/schema-formatter.ts +20 -11
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zapier/zapier-sdk-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "Command line interface for Zapier SDK",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -31,9 +31,9 @@
|
|
|
31
31
|
"ora": "^8.2.0",
|
|
32
32
|
"pkce-challenge": "^5.0.0",
|
|
33
33
|
"zod": "^3.25.67",
|
|
34
|
-
"@zapier/zapier-sdk": "0.
|
|
34
|
+
"@zapier/zapier-sdk": "0.6.1",
|
|
35
35
|
"@zapier/zapier-sdk-cli-login": "0.3.2",
|
|
36
|
-
"@zapier/zapier-sdk-mcp": "0.1
|
|
36
|
+
"@zapier/zapier-sdk-mcp": "0.2.1"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@types/express": "^5.0.3",
|
package/src/cli.test.ts
CHANGED
package/src/cli.ts
CHANGED
|
@@ -75,9 +75,10 @@ export function createGenerateTypesCommand(): Command {
|
|
|
75
75
|
options,
|
|
76
76
|
);
|
|
77
77
|
|
|
78
|
-
// Create SDK instance
|
|
79
|
-
const sdk = createZapierSdk(
|
|
80
|
-
|
|
78
|
+
// Create SDK instance with manifest path if provided
|
|
79
|
+
const sdk = createZapierSdk({
|
|
80
|
+
manifestPath: rawParams.lockFilePath as string | undefined,
|
|
81
|
+
});
|
|
81
82
|
// Resolve missing parameters interactively using schema metadata
|
|
82
83
|
const resolver = new SchemaParameterResolver();
|
|
83
84
|
const resolvedParams = await resolver.resolveParameters(
|
|
@@ -64,24 +64,50 @@ export async function generateTypes(
|
|
|
64
64
|
if (authenticationId) {
|
|
65
65
|
for (const action of actions) {
|
|
66
66
|
try {
|
|
67
|
+
// Check to see if the appKey is in the manifest
|
|
68
|
+
const manifestEntry = sdk.getContext().getManifestEntry(appKey);
|
|
69
|
+
|
|
67
70
|
const fieldsResult = await sdk.listInputFields({
|
|
68
|
-
appKey
|
|
71
|
+
// If the appKey is in the manifest, use the appKey so that the types are consistent with the manifest's version, otherwise use the action.app_key
|
|
72
|
+
appKey: manifestEntry ? appKey : action.app_key,
|
|
69
73
|
actionKey: action.key,
|
|
70
|
-
actionType: action.action_type
|
|
74
|
+
actionType: action.action_type,
|
|
71
75
|
authenticationId: authenticationId,
|
|
72
76
|
});
|
|
73
77
|
|
|
74
|
-
const fields = fieldsResult.data
|
|
75
|
-
|
|
78
|
+
const fields = fieldsResult.data.map((field: unknown): ActionField => {
|
|
79
|
+
const fieldObj = field as {
|
|
80
|
+
is_required?: boolean;
|
|
81
|
+
required?: boolean;
|
|
82
|
+
[key: string]: unknown;
|
|
83
|
+
};
|
|
84
|
+
return {
|
|
85
|
+
...fieldObj,
|
|
86
|
+
required: fieldObj.is_required || fieldObj.required || false,
|
|
87
|
+
} as ActionField;
|
|
88
|
+
});
|
|
89
|
+
actionsWithFields.push({
|
|
90
|
+
...action,
|
|
91
|
+
inputFields: fields,
|
|
92
|
+
name: action.title || action.key,
|
|
93
|
+
});
|
|
76
94
|
} catch {
|
|
77
95
|
// If we can't get fields for an action, include it without fields
|
|
78
|
-
actionsWithFields.push({
|
|
96
|
+
actionsWithFields.push({
|
|
97
|
+
...action,
|
|
98
|
+
inputFields: [],
|
|
99
|
+
name: action.title || action.key,
|
|
100
|
+
});
|
|
79
101
|
}
|
|
80
102
|
}
|
|
81
103
|
} else {
|
|
82
104
|
// Convert actions to have empty input fields (will generate generic types)
|
|
83
105
|
actions.forEach((action: ActionItem) => {
|
|
84
|
-
actionsWithFields.push({
|
|
106
|
+
actionsWithFields.push({
|
|
107
|
+
...action,
|
|
108
|
+
inputFields: [],
|
|
109
|
+
name: action.title || action.key,
|
|
110
|
+
});
|
|
85
111
|
});
|
|
86
112
|
}
|
|
87
113
|
|
|
@@ -17,6 +17,10 @@ export const GenerateTypesSchema = z
|
|
|
17
17
|
debug: DebugPropertySchema.describe(
|
|
18
18
|
"Enable debug logging during generation",
|
|
19
19
|
),
|
|
20
|
+
lockFilePath: z
|
|
21
|
+
.string()
|
|
22
|
+
.optional()
|
|
23
|
+
.describe("Path to the .zapierrc lock file (defaults to .zapierrc)"),
|
|
20
24
|
})
|
|
21
25
|
.describe("Generate TypeScript SDK code for a specific app");
|
|
22
26
|
|
package/src/utils/auth/login.ts
CHANGED
|
@@ -2,6 +2,7 @@ import open from "open";
|
|
|
2
2
|
import crypto from "node:crypto";
|
|
3
3
|
import express from "express";
|
|
4
4
|
import pkceChallenge from "pkce-challenge";
|
|
5
|
+
import type { Socket } from "net";
|
|
5
6
|
|
|
6
7
|
import {
|
|
7
8
|
AUTH_MODE_HEADER,
|
|
@@ -86,7 +87,7 @@ const login = async (timeoutMs: number = LOGIN_TIMEOUT_MS): Promise<string> => {
|
|
|
86
87
|
const server = app.listen(availablePort);
|
|
87
88
|
|
|
88
89
|
// Track connections to force close them if needed
|
|
89
|
-
const connections = new Set<
|
|
90
|
+
const connections = new Set<Socket>();
|
|
90
91
|
server.on("connection", (conn) => {
|
|
91
92
|
connections.add(conn);
|
|
92
93
|
conn.on("close", () => connections.delete(conn));
|
|
@@ -157,13 +157,15 @@ function methodNameToCliCommand(methodName: string): string {
|
|
|
157
157
|
|
|
158
158
|
export function generateCliCommands(program: Command, sdk: ZapierSdk): void {
|
|
159
159
|
// Check if SDK has registry
|
|
160
|
-
if (
|
|
160
|
+
if (typeof sdk.getRegistry !== "function") {
|
|
161
161
|
console.error("SDK registry not available");
|
|
162
162
|
return;
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
-
|
|
166
|
-
|
|
165
|
+
const registry = sdk.getRegistry();
|
|
166
|
+
|
|
167
|
+
// Create all commands first
|
|
168
|
+
registry.functions.forEach((fnInfo) => {
|
|
167
169
|
if (!fnInfo.inputSchema) {
|
|
168
170
|
console.warn(`Schema not found for ${fnInfo.name}`);
|
|
169
171
|
return;
|
|
@@ -181,6 +183,75 @@ export function generateCliCommands(program: Command, sdk: ZapierSdk): void {
|
|
|
181
183
|
|
|
182
184
|
addCommand(program, cliCommandName, config);
|
|
183
185
|
});
|
|
186
|
+
|
|
187
|
+
// Override the help display to show commands grouped by category
|
|
188
|
+
program.configureHelp({
|
|
189
|
+
formatHelp: (cmd, helper) => {
|
|
190
|
+
const helpWidth = helper.helpWidth || 80;
|
|
191
|
+
|
|
192
|
+
let output = helper.commandUsage(cmd) + "\n";
|
|
193
|
+
|
|
194
|
+
if (cmd.description()) {
|
|
195
|
+
output += helper.wrap(cmd.description(), helpWidth, 0) + "\n";
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Add options section
|
|
199
|
+
const options = helper.visibleOptions(cmd);
|
|
200
|
+
if (options.length > 0) {
|
|
201
|
+
output += "\nOptions:\n";
|
|
202
|
+
const longestOptionLength = Math.max(
|
|
203
|
+
...options.map((opt) => helper.optionTerm(opt).length),
|
|
204
|
+
);
|
|
205
|
+
options.forEach((option) => {
|
|
206
|
+
const term = helper.optionTerm(option);
|
|
207
|
+
const padding = " ".repeat(
|
|
208
|
+
Math.max(2, longestOptionLength - term.length + 4),
|
|
209
|
+
);
|
|
210
|
+
output += ` ${term}${padding}${helper.optionDescription(option)}\n`;
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Add categorized commands section
|
|
215
|
+
const commands = helper.visibleCommands(cmd);
|
|
216
|
+
if (commands.length > 0) {
|
|
217
|
+
output += "\nCommands:\n";
|
|
218
|
+
|
|
219
|
+
// Collect all SDK commands that belong to categories
|
|
220
|
+
const categorizedCommands = new Set<string>();
|
|
221
|
+
|
|
222
|
+
// Group SDK commands by categories
|
|
223
|
+
registry.categories.forEach((category) => {
|
|
224
|
+
const categoryCommands = commands.filter((command) =>
|
|
225
|
+
category.functions.some((functionName) => {
|
|
226
|
+
const cliCommandName = methodNameToCliCommand(functionName);
|
|
227
|
+
return command.name() === cliCommandName;
|
|
228
|
+
}),
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
if (categoryCommands.length > 0) {
|
|
232
|
+
output += `\n ${category.titlePlural}:\n`;
|
|
233
|
+
categoryCommands.forEach((command) => {
|
|
234
|
+
output += ` ${helper.subcommandTerm(command)}\n`;
|
|
235
|
+
categorizedCommands.add(command.name());
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// Add any remaining commands that aren't part of SDK categories
|
|
241
|
+
const otherCommands = commands.filter(
|
|
242
|
+
(command) => !categorizedCommands.has(command.name()),
|
|
243
|
+
);
|
|
244
|
+
if (otherCommands.length > 0) {
|
|
245
|
+
output += `\n Other:\n`;
|
|
246
|
+
otherCommands.forEach((command) => {
|
|
247
|
+
output += ` ${helper.subcommandTerm(command)}\n`;
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return output;
|
|
253
|
+
},
|
|
254
|
+
});
|
|
184
255
|
}
|
|
185
256
|
|
|
186
257
|
function createCommandConfig(
|
|
@@ -306,7 +306,7 @@ export class SchemaParameterResolver {
|
|
|
306
306
|
private async resolveParameter(
|
|
307
307
|
param: ResolvableParameter,
|
|
308
308
|
context: ResolverContext,
|
|
309
|
-
): Promise<
|
|
309
|
+
): Promise<unknown> {
|
|
310
310
|
const resolver = getResolver(param.name);
|
|
311
311
|
if (!resolver) {
|
|
312
312
|
throw new Error(`No resolver found for parameter: ${param.name}`);
|
|
@@ -314,39 +314,55 @@ export class SchemaParameterResolver {
|
|
|
314
314
|
|
|
315
315
|
console.log(chalk.blue(`\n🔍 Resolving ${param.name}...`));
|
|
316
316
|
|
|
317
|
-
|
|
317
|
+
const typedResolver = resolver as {
|
|
318
|
+
type: string;
|
|
319
|
+
inputType?: string;
|
|
320
|
+
placeholder?: string;
|
|
321
|
+
fetch?: Function;
|
|
322
|
+
prompt?: Function;
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
if (typedResolver.type === "static") {
|
|
318
326
|
// Static resolver - just prompt for input
|
|
319
327
|
const promptConfig = {
|
|
320
|
-
type:
|
|
328
|
+
type: typedResolver.inputType === "password" ? "password" : "input",
|
|
321
329
|
name: param.name,
|
|
322
330
|
message: `Enter ${param.name}:`,
|
|
323
|
-
...(
|
|
331
|
+
...(typedResolver.placeholder && {
|
|
332
|
+
default: typedResolver.placeholder,
|
|
333
|
+
}),
|
|
324
334
|
};
|
|
325
335
|
|
|
326
336
|
const answers = await inquirer.prompt([promptConfig as any]);
|
|
327
337
|
return answers[param.name];
|
|
328
|
-
} else if (
|
|
338
|
+
} else if (typedResolver.type === "dynamic") {
|
|
329
339
|
// Dynamic resolver - fetch options and prompt for selection
|
|
330
340
|
try {
|
|
331
341
|
// Only show "Fetching..." for required parameters that typically have many options
|
|
332
342
|
if (param.isRequired && param.name !== "authenticationId") {
|
|
333
343
|
console.log(chalk.gray(`Fetching options for ${param.name}...`));
|
|
334
344
|
}
|
|
335
|
-
const items = await
|
|
345
|
+
const items = await typedResolver.fetch!(
|
|
346
|
+
context.sdk,
|
|
347
|
+
context.resolvedParams,
|
|
348
|
+
);
|
|
336
349
|
|
|
337
350
|
// Let the resolver's prompt handle empty lists (e.g., authenticationId can show "skip authentication")
|
|
338
351
|
const safeItems = items || [];
|
|
339
|
-
const promptConfig =
|
|
352
|
+
const promptConfig = typedResolver.prompt!(
|
|
353
|
+
safeItems,
|
|
354
|
+
context.resolvedParams,
|
|
355
|
+
);
|
|
340
356
|
const answers = await inquirer.prompt([promptConfig as any]);
|
|
341
357
|
return answers[param.name];
|
|
342
358
|
} catch (error) {
|
|
343
359
|
// Let the main CLI error handler display user-friendly errors
|
|
344
360
|
throw error;
|
|
345
361
|
}
|
|
346
|
-
} else if (
|
|
362
|
+
} else if (typedResolver.type === "fields") {
|
|
347
363
|
// Fields resolver - fetch field definitions and prompt for each input with recursive field resolution
|
|
348
364
|
return await this.resolveFieldsRecursively(
|
|
349
|
-
resolver as
|
|
365
|
+
resolver as unknown,
|
|
350
366
|
context,
|
|
351
367
|
param,
|
|
352
368
|
);
|
|
@@ -356,14 +372,12 @@ export class SchemaParameterResolver {
|
|
|
356
372
|
}
|
|
357
373
|
|
|
358
374
|
private async resolveFieldsRecursively(
|
|
359
|
-
resolver:
|
|
360
|
-
fetch: (sdk: unknown, params: unknown) => Promise<unknown[]>;
|
|
361
|
-
prompt: (items: unknown[], params: unknown) => unknown;
|
|
362
|
-
},
|
|
375
|
+
resolver: unknown,
|
|
363
376
|
context: ResolverContext,
|
|
364
377
|
param: ResolvableParameter,
|
|
365
|
-
): Promise<Record<string,
|
|
366
|
-
const
|
|
378
|
+
): Promise<Record<string, unknown>> {
|
|
379
|
+
const typedResolver = resolver as { fetch: Function };
|
|
380
|
+
const inputs: Record<string, unknown> = {};
|
|
367
381
|
let processedFieldKeys = new Set<string>();
|
|
368
382
|
let iteration = 0;
|
|
369
383
|
const maxIterations = 5; // Prevent infinite loops
|
|
@@ -386,7 +400,7 @@ export class SchemaParameterResolver {
|
|
|
386
400
|
),
|
|
387
401
|
);
|
|
388
402
|
|
|
389
|
-
const fields = await
|
|
403
|
+
const fields = await typedResolver.fetch(
|
|
390
404
|
updatedContext.sdk,
|
|
391
405
|
updatedContext.resolvedParams,
|
|
392
406
|
);
|
|
@@ -516,7 +530,7 @@ export class SchemaParameterResolver {
|
|
|
516
530
|
|
|
517
531
|
private async promptForField(
|
|
518
532
|
field: unknown,
|
|
519
|
-
inputs: Record<string,
|
|
533
|
+
inputs: Record<string, unknown>,
|
|
520
534
|
): Promise<void> {
|
|
521
535
|
const fieldObj = field as {
|
|
522
536
|
type?: string;
|
|
@@ -13,15 +13,16 @@ interface FormattedItem {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
interface FormatMetadata {
|
|
16
|
-
format: (item:
|
|
16
|
+
format: (item: unknown) => FormattedItem;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
function getFormatMetadata(schema:
|
|
20
|
-
return schema?._def
|
|
19
|
+
function getFormatMetadata(schema: unknown): FormatMetadata | undefined {
|
|
20
|
+
return (schema as { _def?: { formatMeta?: FormatMetadata } })?._def
|
|
21
|
+
?.formatMeta;
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
function getOutputSchema(schema:
|
|
24
|
-
return schema?._def?.outputSchema;
|
|
24
|
+
function getOutputSchema(schema: unknown): unknown {
|
|
25
|
+
return (schema as { _def?: { outputSchema?: unknown } })?._def?.outputSchema;
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
// ============================================================================
|
|
@@ -30,7 +31,7 @@ function getOutputSchema(schema: any): any {
|
|
|
30
31
|
|
|
31
32
|
export function formatItemsFromSchema(
|
|
32
33
|
inputSchema: z.ZodType,
|
|
33
|
-
items:
|
|
34
|
+
items: unknown[],
|
|
34
35
|
): void {
|
|
35
36
|
// Get the output schema and its format metadata
|
|
36
37
|
const outputSchema = getOutputSchema(inputSchema);
|
|
@@ -54,7 +55,7 @@ export function formatItemsFromSchema(
|
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
function formatSingleItem(
|
|
57
|
-
item:
|
|
58
|
+
item: unknown,
|
|
58
59
|
index: number,
|
|
59
60
|
formatMeta: FormatMetadata,
|
|
60
61
|
): void {
|
|
@@ -93,13 +94,21 @@ function applyStyle(value: string, style: string): string {
|
|
|
93
94
|
}
|
|
94
95
|
}
|
|
95
96
|
|
|
96
|
-
function formatItemsGeneric(items:
|
|
97
|
+
function formatItemsGeneric(items: unknown[]): void {
|
|
97
98
|
// Fallback formatting for items without schema metadata
|
|
98
99
|
items.forEach((item, index) => {
|
|
99
|
-
const
|
|
100
|
+
const itemObj = item as {
|
|
101
|
+
title?: string;
|
|
102
|
+
name?: string;
|
|
103
|
+
key?: string;
|
|
104
|
+
id?: string;
|
|
105
|
+
description?: string;
|
|
106
|
+
};
|
|
107
|
+
const name =
|
|
108
|
+
itemObj.title || itemObj.name || itemObj.key || itemObj.id || "Item";
|
|
100
109
|
console.log(`${chalk.gray(`${index + 1}.`)} ${chalk.cyan(name)}`);
|
|
101
|
-
if (
|
|
102
|
-
console.log(` ${chalk.dim(
|
|
110
|
+
if (itemObj.description) {
|
|
111
|
+
console.log(` ${chalk.dim(itemObj.description)}`);
|
|
103
112
|
}
|
|
104
113
|
console.log();
|
|
105
114
|
});
|