@zapier/zapier-sdk-cli 0.9.0 → 0.10.0
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 +20 -0
- package/dist/cli.cjs +213 -100
- package/dist/cli.mjs +214 -101
- package/dist/index.cjs +15 -12
- package/dist/index.mjs +15 -12
- package/dist/package.json +1 -1
- package/dist/src/plugins/add/index.js +11 -13
- package/dist/src/utils/cli-generator-utils.d.ts +2 -1
- package/dist/src/utils/cli-generator-utils.js +11 -5
- package/dist/src/utils/cli-generator.js +50 -65
- package/dist/src/utils/parameter-resolver.d.ts +4 -1
- package/dist/src/utils/parameter-resolver.js +92 -15
- package/dist/src/utils/schema-formatter.d.ts +5 -1
- package/dist/src/utils/schema-formatter.js +48 -18
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/src/plugins/add/index.ts +15 -15
- package/src/utils/cli-generator-utils.ts +17 -5
- package/src/utils/cli-generator.ts +68 -79
- package/src/utils/parameter-resolver.ts +155 -21
- package/src/utils/schema-formatter.ts +68 -33
|
@@ -2,11 +2,6 @@ import inquirer from "inquirer";
|
|
|
2
2
|
import chalk from "chalk";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import type { ZapierSdk } from "@zapier/zapier-sdk";
|
|
5
|
-
import {
|
|
6
|
-
getResolver,
|
|
7
|
-
hasResolver,
|
|
8
|
-
getResolutionOrderForParams,
|
|
9
|
-
} from "@zapier/zapier-sdk";
|
|
10
5
|
|
|
11
6
|
// ============================================================================
|
|
12
7
|
// Types
|
|
@@ -24,6 +19,66 @@ interface ResolverContext {
|
|
|
24
19
|
sdk: ZapierSdk;
|
|
25
20
|
currentParams: Record<string, unknown>;
|
|
26
21
|
resolvedParams: Record<string, unknown>;
|
|
22
|
+
functionName?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ============================================================================
|
|
26
|
+
// Local Resolution Helper Functions
|
|
27
|
+
// ============================================================================
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Resolve dependency chain for a parameter using local resolvers
|
|
31
|
+
* Returns parameters in the order they need to be resolved
|
|
32
|
+
*/
|
|
33
|
+
function getLocalResolutionOrder(
|
|
34
|
+
paramName: string,
|
|
35
|
+
resolvers: Record<string, any>,
|
|
36
|
+
resolved: Set<string> = new Set(),
|
|
37
|
+
): string[] {
|
|
38
|
+
const resolver = resolvers[paramName];
|
|
39
|
+
if (!resolver || resolver.type === "static") {
|
|
40
|
+
return [paramName];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const order: string[] = [];
|
|
44
|
+
|
|
45
|
+
if ("depends" in resolver && resolver.depends) {
|
|
46
|
+
for (const dependency of resolver.depends) {
|
|
47
|
+
if (!resolved.has(dependency)) {
|
|
48
|
+
order.push(...getLocalResolutionOrder(dependency, resolvers, resolved));
|
|
49
|
+
resolved.add(dependency);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!resolved.has(paramName)) {
|
|
55
|
+
order.push(paramName);
|
|
56
|
+
resolved.add(paramName);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return order;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get resolution order for multiple parameters using local resolvers
|
|
64
|
+
*/
|
|
65
|
+
function getLocalResolutionOrderForParams(
|
|
66
|
+
paramNames: string[],
|
|
67
|
+
resolvers: Record<string, any>,
|
|
68
|
+
): string[] {
|
|
69
|
+
const resolved = new Set<string>();
|
|
70
|
+
const order: string[] = [];
|
|
71
|
+
|
|
72
|
+
for (const paramName of paramNames) {
|
|
73
|
+
const paramOrder = getLocalResolutionOrder(paramName, resolvers, resolved);
|
|
74
|
+
for (const param of paramOrder) {
|
|
75
|
+
if (!order.includes(param)) {
|
|
76
|
+
order.push(param);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return order;
|
|
27
82
|
}
|
|
28
83
|
|
|
29
84
|
// ============================================================================
|
|
@@ -35,6 +90,7 @@ export class SchemaParameterResolver {
|
|
|
35
90
|
schema: z.ZodSchema,
|
|
36
91
|
providedParams: unknown,
|
|
37
92
|
sdk: ZapierSdk,
|
|
93
|
+
functionName?: string,
|
|
38
94
|
): Promise<unknown> {
|
|
39
95
|
// 1. Try to parse with current parameters
|
|
40
96
|
const parseResult = schema.safeParse(providedParams);
|
|
@@ -42,7 +98,7 @@ export class SchemaParameterResolver {
|
|
|
42
98
|
// Get all schema parameters to check which ones have resolvers
|
|
43
99
|
const allParams = this.extractParametersFromSchema(schema);
|
|
44
100
|
const resolvableParams = allParams.filter((param) =>
|
|
45
|
-
hasResolver(param.name),
|
|
101
|
+
this.hasResolver(param.name, sdk, functionName),
|
|
46
102
|
);
|
|
47
103
|
|
|
48
104
|
// Get all missing parameters that have resolvers
|
|
@@ -108,12 +164,18 @@ export class SchemaParameterResolver {
|
|
|
108
164
|
sdk,
|
|
109
165
|
currentParams: providedParams as Record<string, unknown>,
|
|
110
166
|
resolvedParams,
|
|
167
|
+
functionName,
|
|
111
168
|
};
|
|
112
169
|
|
|
170
|
+
// Get local resolvers for this function
|
|
171
|
+
const localResolvers = this.getLocalResolvers(sdk, functionName);
|
|
172
|
+
|
|
113
173
|
if (functionallyRequired.length > 0) {
|
|
114
174
|
const requiredParamNames = functionallyRequired.map((p) => p.name);
|
|
115
|
-
const requiredResolutionOrder =
|
|
116
|
-
|
|
175
|
+
const requiredResolutionOrder = getLocalResolutionOrderForParams(
|
|
176
|
+
requiredParamNames,
|
|
177
|
+
localResolvers,
|
|
178
|
+
);
|
|
117
179
|
|
|
118
180
|
// Find all parameters that need to be resolved (including dependencies)
|
|
119
181
|
// from the available resolvable parameters
|
|
@@ -135,7 +197,11 @@ export class SchemaParameterResolver {
|
|
|
135
197
|
|
|
136
198
|
for (const param of orderedRequiredParams) {
|
|
137
199
|
try {
|
|
138
|
-
const value = await this.resolveParameter(
|
|
200
|
+
const value = await this.resolveParameter(
|
|
201
|
+
param,
|
|
202
|
+
context,
|
|
203
|
+
functionName,
|
|
204
|
+
);
|
|
139
205
|
this.setNestedValue(resolvedParams, param.path, value);
|
|
140
206
|
|
|
141
207
|
// Update context with newly resolved value
|
|
@@ -168,8 +234,10 @@ export class SchemaParameterResolver {
|
|
|
168
234
|
// 3. Resolve parameters that should always be prompted for (but can be skipped)
|
|
169
235
|
if (alwaysPrompt.length > 0) {
|
|
170
236
|
const alwaysPromptNames = alwaysPrompt.map((p) => p.name);
|
|
171
|
-
const alwaysPromptResolutionOrder =
|
|
172
|
-
|
|
237
|
+
const alwaysPromptResolutionOrder = getLocalResolutionOrderForParams(
|
|
238
|
+
alwaysPromptNames,
|
|
239
|
+
localResolvers,
|
|
240
|
+
);
|
|
173
241
|
|
|
174
242
|
const orderedAlwaysPromptParams = alwaysPromptResolutionOrder
|
|
175
243
|
.map((paramName) => alwaysPrompt.find((p) => p.name === paramName))
|
|
@@ -177,7 +245,11 @@ export class SchemaParameterResolver {
|
|
|
177
245
|
|
|
178
246
|
for (const param of orderedAlwaysPromptParams) {
|
|
179
247
|
try {
|
|
180
|
-
const value = await this.resolveParameter(
|
|
248
|
+
const value = await this.resolveParameter(
|
|
249
|
+
param,
|
|
250
|
+
context,
|
|
251
|
+
functionName,
|
|
252
|
+
);
|
|
181
253
|
this.setNestedValue(resolvedParams, param.path, value);
|
|
182
254
|
|
|
183
255
|
// Update context with newly resolved value
|
|
@@ -207,8 +279,10 @@ export class SchemaParameterResolver {
|
|
|
207
279
|
if (shouldResolveOptional.resolveOptional) {
|
|
208
280
|
// Resolve optional parameters using their resolvers
|
|
209
281
|
const optionalParamNames = trulyOptional.map((p) => p.name);
|
|
210
|
-
const optionalResolutionOrder =
|
|
211
|
-
|
|
282
|
+
const optionalResolutionOrder = getLocalResolutionOrderForParams(
|
|
283
|
+
optionalParamNames,
|
|
284
|
+
localResolvers,
|
|
285
|
+
);
|
|
212
286
|
|
|
213
287
|
const orderedOptionalParams = optionalResolutionOrder
|
|
214
288
|
.map((paramName) => trulyOptional.find((p) => p.name === paramName))
|
|
@@ -216,7 +290,11 @@ export class SchemaParameterResolver {
|
|
|
216
290
|
|
|
217
291
|
for (const param of orderedOptionalParams) {
|
|
218
292
|
try {
|
|
219
|
-
const value = await this.resolveParameter(
|
|
293
|
+
const value = await this.resolveParameter(
|
|
294
|
+
param,
|
|
295
|
+
context,
|
|
296
|
+
functionName,
|
|
297
|
+
);
|
|
220
298
|
this.setNestedValue(resolvedParams, param.path, value);
|
|
221
299
|
|
|
222
300
|
// Update context with newly resolved value
|
|
@@ -306,8 +384,9 @@ export class SchemaParameterResolver {
|
|
|
306
384
|
private async resolveParameter(
|
|
307
385
|
param: ResolvableParameter,
|
|
308
386
|
context: ResolverContext,
|
|
387
|
+
functionName?: string,
|
|
309
388
|
): Promise<unknown> {
|
|
310
|
-
const resolver = getResolver(param.name);
|
|
389
|
+
const resolver = this.getResolver(param.name, context.sdk, functionName);
|
|
311
390
|
if (!resolver) {
|
|
312
391
|
throw new Error(`No resolver found for parameter: ${param.name}`);
|
|
313
392
|
}
|
|
@@ -430,10 +509,10 @@ export class SchemaParameterResolver {
|
|
|
430
509
|
|
|
431
510
|
// Separate new required and optional fields
|
|
432
511
|
const newRequiredFields = newFields.filter(
|
|
433
|
-
(field: unknown) => (field as {
|
|
512
|
+
(field: unknown) => (field as { is_required: boolean }).is_required,
|
|
434
513
|
);
|
|
435
514
|
const newOptionalFields = newFields.filter(
|
|
436
|
-
(field: unknown) => !(field as {
|
|
515
|
+
(field: unknown) => !(field as { is_required: boolean }).is_required,
|
|
437
516
|
);
|
|
438
517
|
|
|
439
518
|
// Prompt for new required fields
|
|
@@ -536,7 +615,7 @@ export class SchemaParameterResolver {
|
|
|
536
615
|
type?: string;
|
|
537
616
|
key: string;
|
|
538
617
|
label?: string;
|
|
539
|
-
|
|
618
|
+
is_required?: boolean;
|
|
540
619
|
helpText?: string;
|
|
541
620
|
default?: unknown;
|
|
542
621
|
choices?: Array<{ label?: string; value: unknown }>;
|
|
@@ -545,7 +624,7 @@ export class SchemaParameterResolver {
|
|
|
545
624
|
const fieldPrompt: Record<string, unknown> = {
|
|
546
625
|
type: fieldObj.type === "boolean" ? "confirm" : "input",
|
|
547
626
|
name: fieldObj.key,
|
|
548
|
-
message: `${fieldObj.label || fieldObj.key}${fieldObj.
|
|
627
|
+
message: `${fieldObj.label || fieldObj.key}${fieldObj.is_required ? " (required)" : " (optional)"}:`,
|
|
549
628
|
};
|
|
550
629
|
|
|
551
630
|
if (fieldObj.helpText) {
|
|
@@ -574,7 +653,7 @@ export class SchemaParameterResolver {
|
|
|
574
653
|
|
|
575
654
|
if (answer[fieldObj.key] !== undefined && answer[fieldObj.key] !== "") {
|
|
576
655
|
inputs[fieldObj.key] = answer[fieldObj.key];
|
|
577
|
-
} else if (fieldObj.
|
|
656
|
+
} else if (fieldObj.is_required) {
|
|
578
657
|
throw new Error(`Required field ${fieldObj.key} cannot be empty`);
|
|
579
658
|
}
|
|
580
659
|
} catch (error) {
|
|
@@ -598,4 +677,59 @@ export class SchemaParameterResolver {
|
|
|
598
677
|
errorObj?.isTTYError === true
|
|
599
678
|
);
|
|
600
679
|
}
|
|
680
|
+
|
|
681
|
+
private hasResolver(
|
|
682
|
+
paramName: string,
|
|
683
|
+
sdk: ZapierSdk,
|
|
684
|
+
functionName?: string,
|
|
685
|
+
): boolean {
|
|
686
|
+
// Check plugin-specific resolvers first
|
|
687
|
+
if (functionName && typeof sdk.getRegistry === "function") {
|
|
688
|
+
const registry = sdk.getRegistry();
|
|
689
|
+
const functionInfo = registry.functions.find(
|
|
690
|
+
(f) => f.name === functionName,
|
|
691
|
+
);
|
|
692
|
+
if (functionInfo && functionInfo.resolvers?.[paramName]) {
|
|
693
|
+
return true;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// No global registry fallback
|
|
698
|
+
return false;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
private getResolver(
|
|
702
|
+
paramName: string,
|
|
703
|
+
sdk: ZapierSdk,
|
|
704
|
+
functionName?: string,
|
|
705
|
+
): any {
|
|
706
|
+
// Check plugin-specific resolvers first
|
|
707
|
+
if (functionName && typeof sdk.getRegistry === "function") {
|
|
708
|
+
const registry = sdk.getRegistry();
|
|
709
|
+
const functionInfo = registry.functions.find(
|
|
710
|
+
(f) => f.name === functionName,
|
|
711
|
+
);
|
|
712
|
+
if (functionInfo && functionInfo.resolvers?.[paramName]) {
|
|
713
|
+
return functionInfo.resolvers[paramName];
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// No global registry fallback
|
|
718
|
+
return null;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
private getLocalResolvers(
|
|
722
|
+
sdk: ZapierSdk,
|
|
723
|
+
functionName?: string,
|
|
724
|
+
): Record<string, any> {
|
|
725
|
+
if (!functionName || typeof sdk.getRegistry !== "function") {
|
|
726
|
+
return {};
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
const registry = sdk.getRegistry();
|
|
730
|
+
const functionInfo = registry.functions.find(
|
|
731
|
+
(f) => f.name === functionName,
|
|
732
|
+
);
|
|
733
|
+
return functionInfo?.resolvers || {};
|
|
734
|
+
}
|
|
601
735
|
}
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import type { z } from "zod";
|
|
3
|
+
import util from "util";
|
|
3
4
|
// These functions are internal to SDK, implementing basic formatting fallback
|
|
4
5
|
// TODO: Consider exposing these utilities or implementing proper CLI formatting
|
|
5
6
|
|
|
6
7
|
interface FormattedItem {
|
|
7
8
|
title: string;
|
|
8
|
-
|
|
9
|
+
id?: string;
|
|
10
|
+
key?: string;
|
|
11
|
+
description?: string;
|
|
12
|
+
data?: unknown; // Optional: if provided, use formatJsonOutput instead of details
|
|
9
13
|
details: Array<{
|
|
10
14
|
text: string;
|
|
11
15
|
style: "normal" | "dim" | "accent" | "warning" | "success";
|
|
@@ -25,17 +29,34 @@ function getOutputSchema(schema: unknown): unknown {
|
|
|
25
29
|
return (schema as { _def?: { outputSchema?: unknown } })?._def?.outputSchema;
|
|
26
30
|
}
|
|
27
31
|
|
|
32
|
+
// ============================================================================
|
|
33
|
+
// JSON Formatting
|
|
34
|
+
// ============================================================================
|
|
35
|
+
|
|
36
|
+
export function formatJsonOutput(data: unknown): void {
|
|
37
|
+
// Don't print anything for undefined results (commands that just perform actions)
|
|
38
|
+
if (data === undefined) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Use util.inspect for colored output
|
|
43
|
+
console.log(
|
|
44
|
+
util.inspect(data, { colors: true, depth: null, breakLength: 80 }),
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
28
48
|
// ============================================================================
|
|
29
49
|
// Generic Schema-Driven Formatter
|
|
30
50
|
// ============================================================================
|
|
31
51
|
|
|
32
52
|
export function formatItemsFromSchema(
|
|
33
|
-
inputSchema: z.ZodType,
|
|
53
|
+
functionInfo: { inputSchema: z.ZodType; outputSchema?: z.ZodType },
|
|
34
54
|
items: unknown[],
|
|
35
55
|
startingNumber: number = 0,
|
|
36
56
|
): void {
|
|
37
|
-
// Get the output schema
|
|
38
|
-
const outputSchema =
|
|
57
|
+
// Get the output schema from function info or fall back to input schema output schema
|
|
58
|
+
const outputSchema =
|
|
59
|
+
functionInfo.outputSchema || getOutputSchema(functionInfo.inputSchema);
|
|
39
60
|
if (!outputSchema) {
|
|
40
61
|
// Fallback to generic formatting if no output schema
|
|
41
62
|
formatItemsGeneric(items, startingNumber);
|
|
@@ -51,25 +72,35 @@ export function formatItemsFromSchema(
|
|
|
51
72
|
|
|
52
73
|
// Format each item using the schema metadata
|
|
53
74
|
items.forEach((item, index) => {
|
|
54
|
-
|
|
75
|
+
const formatted = formatMeta.format(item);
|
|
76
|
+
formatSingleItem(formatted, startingNumber + index);
|
|
55
77
|
});
|
|
56
78
|
}
|
|
57
79
|
|
|
58
|
-
function formatSingleItem(
|
|
59
|
-
|
|
60
|
-
itemNumber: number,
|
|
61
|
-
formatMeta: FormatMetadata,
|
|
62
|
-
): void {
|
|
63
|
-
// Get the formatted item from the format function
|
|
64
|
-
const formatted = formatMeta.format(item);
|
|
65
|
-
|
|
66
|
-
// Build the main title line
|
|
80
|
+
function formatSingleItem(formatted: FormattedItem, itemNumber: number): void {
|
|
81
|
+
// Build the main title line with optional subtitle
|
|
67
82
|
let titleLine = `${chalk.gray(`${itemNumber + 1}.`)} ${chalk.cyan(formatted.title)}`;
|
|
68
|
-
|
|
69
|
-
|
|
83
|
+
|
|
84
|
+
// Generate subtitle from id or key
|
|
85
|
+
if (formatted.id) {
|
|
86
|
+
titleLine += ` ${chalk.gray(`(ID: ${formatted.id})`)}`;
|
|
87
|
+
} else if (formatted.key) {
|
|
88
|
+
titleLine += ` ${chalk.gray(`(${formatted.key})`)}`;
|
|
70
89
|
}
|
|
71
90
|
console.log(titleLine);
|
|
72
91
|
|
|
92
|
+
// Show description if available
|
|
93
|
+
if (formatted.description) {
|
|
94
|
+
console.log(` ${chalk.dim(formatted.description)}`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// If data is provided, use JSON formatting instead of details
|
|
98
|
+
if (formatted.data !== undefined) {
|
|
99
|
+
formatJsonOutput(formatted.data);
|
|
100
|
+
console.log(); // Empty line between items
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
73
104
|
// Format detail lines
|
|
74
105
|
for (const detail of formatted.details) {
|
|
75
106
|
const styledText = applyStyle(detail.text, detail.style);
|
|
@@ -95,27 +126,31 @@ function applyStyle(value: string, style: string): string {
|
|
|
95
126
|
}
|
|
96
127
|
}
|
|
97
128
|
|
|
129
|
+
function convertGenericItemToFormattedItem(item: unknown): FormattedItem {
|
|
130
|
+
const itemObj = item as {
|
|
131
|
+
title?: string;
|
|
132
|
+
name?: string;
|
|
133
|
+
key?: string;
|
|
134
|
+
id?: string;
|
|
135
|
+
description?: string;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
title: itemObj.title || itemObj.name || itemObj.key || itemObj.id || "Item",
|
|
140
|
+
id: itemObj.id,
|
|
141
|
+
key: itemObj.key,
|
|
142
|
+
description: itemObj.description,
|
|
143
|
+
details: [],
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
98
147
|
function formatItemsGeneric(
|
|
99
148
|
items: unknown[],
|
|
100
149
|
startingNumber: number = 0,
|
|
101
150
|
): void {
|
|
102
|
-
//
|
|
151
|
+
// Convert generic items to FormattedItem and use formatSingleItem
|
|
103
152
|
items.forEach((item, index) => {
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
name?: string;
|
|
107
|
-
key?: string;
|
|
108
|
-
id?: string;
|
|
109
|
-
description?: string;
|
|
110
|
-
};
|
|
111
|
-
const name =
|
|
112
|
-
itemObj.title || itemObj.name || itemObj.key || itemObj.id || "Item";
|
|
113
|
-
console.log(
|
|
114
|
-
`${chalk.gray(`${startingNumber + index + 1}.`)} ${chalk.cyan(name)}`,
|
|
115
|
-
);
|
|
116
|
-
if (itemObj.description) {
|
|
117
|
-
console.log(` ${chalk.dim(itemObj.description)}`);
|
|
118
|
-
}
|
|
119
|
-
console.log();
|
|
153
|
+
const formatted = convertGenericItemToFormattedItem(item);
|
|
154
|
+
formatSingleItem(formatted, startingNumber + index);
|
|
120
155
|
});
|
|
121
156
|
}
|