@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.
@@ -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
- getResolutionOrderForParams(requiredParamNames);
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(param, context);
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
- getResolutionOrderForParams(alwaysPromptNames);
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(param, context);
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
- getResolutionOrderForParams(optionalParamNames);
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(param, context);
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 { required: boolean }).required,
512
+ (field: unknown) => (field as { is_required: boolean }).is_required,
434
513
  );
435
514
  const newOptionalFields = newFields.filter(
436
- (field: unknown) => !(field as { required: boolean }).required,
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
- required?: boolean;
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.required ? " (required)" : " (optional)"}:`,
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.required) {
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
- subtitle?: string;
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 and its format metadata
38
- const outputSchema = getOutputSchema(inputSchema);
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
- formatSingleItem(item, startingNumber + index, formatMeta);
75
+ const formatted = formatMeta.format(item);
76
+ formatSingleItem(formatted, startingNumber + index);
55
77
  });
56
78
  }
57
79
 
58
- function formatSingleItem(
59
- item: unknown,
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
- if (formatted.subtitle) {
69
- titleLine += ` ${chalk.gray(formatted.subtitle)}`;
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
- // Fallback formatting for items without schema metadata
151
+ // Convert generic items to FormattedItem and use formatSingleItem
103
152
  items.forEach((item, index) => {
104
- const itemObj = item as {
105
- title?: string;
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
  }