@zapier/zapier-sdk-cli 0.6.4 → 0.8.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zapier/zapier-sdk-cli",
3
- "version": "0.6.4",
3
+ "version": "0.8.0",
4
4
  "description": "Command line interface for Zapier SDK",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.mjs",
@@ -42,9 +42,9 @@
42
42
  "ora": "^8.2.0",
43
43
  "pkce-challenge": "^5.0.0",
44
44
  "zod": "^3.25.67",
45
- "@zapier/zapier-sdk": "0.6.4",
45
+ "@zapier/zapier-sdk": "0.8.0",
46
46
  "@zapier/zapier-sdk-cli-login": "0.3.2",
47
- "@zapier/zapier-sdk-mcp": "0.2.4"
47
+ "@zapier/zapier-sdk-mcp": "0.3.0"
48
48
  },
49
49
  "devDependencies": {
50
50
  "@types/express": "^5.0.3",
@@ -0,0 +1,93 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+ import chalk from "chalk";
3
+
4
+ // We need to create a test for the formatItemsGeneric function that's defined in cli-generator.ts
5
+ // Since it's not exported, we'll create a simple test that verifies the numbering logic
6
+
7
+ interface TestItem {
8
+ name?: string;
9
+ key?: string;
10
+ id?: string;
11
+ }
12
+
13
+ describe("CLI Generator Pagination Numbering", () => {
14
+ let mockConsoleLog: ReturnType<typeof vi.spyOn>;
15
+
16
+ beforeEach(() => {
17
+ mockConsoleLog = vi.spyOn(console, "log").mockImplementation(() => {});
18
+ });
19
+
20
+ afterEach(() => {
21
+ mockConsoleLog.mockRestore();
22
+ });
23
+
24
+ // Since formatItemsGeneric is not exported, let's create a regression test
25
+ // that tests the key functionality we care about: continuous numbering
26
+ it("should prevent pagination numbering regression by testing chalk formatting", () => {
27
+ // Simulate what formatItemsGeneric does for pagination numbering
28
+ const simulateFormatting = (items: TestItem[], startingNumber: number) => {
29
+ const calls: string[] = [];
30
+ items.forEach((item, index) => {
31
+ const name = item.name || item.key || item.id || "Item";
32
+ const formatted = `${chalk.gray(`${startingNumber + index + 1}.`)} ${chalk.cyan(String(name))}`;
33
+ calls.push(formatted);
34
+ });
35
+ return calls;
36
+ };
37
+
38
+ // Test first page (items 1-3)
39
+ const page1Items = [
40
+ { name: "App 1", key: "app1" },
41
+ { name: "App 2", key: "app2" },
42
+ { name: "App 3", key: "app3" },
43
+ ];
44
+
45
+ const page2Items = [
46
+ { name: "App 4", key: "app4" },
47
+ { name: "App 5", key: "app5" },
48
+ ];
49
+
50
+ // Format first page starting from 0
51
+ const page1Output = simulateFormatting(page1Items, 0);
52
+
53
+ // Format second page starting from 3 (3 items shown already)
54
+ const page2Output = simulateFormatting(page2Items, 3);
55
+
56
+ // Check that page 1 contains 1., 2., 3.
57
+ expect(page1Output.some((line) => line.includes("1."))).toBe(true);
58
+ expect(page1Output.some((line) => line.includes("2."))).toBe(true);
59
+ expect(page1Output.some((line) => line.includes("3."))).toBe(true);
60
+
61
+ // Check that page 2 contains 4., 5. (continuing from page 1)
62
+ expect(page2Output.some((line) => line.includes("4."))).toBe(true);
63
+ expect(page2Output.some((line) => line.includes("5."))).toBe(true);
64
+
65
+ // Most importantly: page 2 should NOT restart at 1.
66
+ expect(page2Output.some((line) => line.includes("1."))).toBe(false);
67
+ expect(page2Output.some((line) => line.includes("2."))).toBe(false);
68
+ expect(page2Output.some((line) => line.includes("3."))).toBe(false);
69
+ });
70
+
71
+ it("should handle edge case of starting number 0", () => {
72
+ const simulateFormatting = (
73
+ items: TestItem[],
74
+ startingNumber: number = 0,
75
+ ) => {
76
+ const calls: string[] = [];
77
+ items.forEach((item, index) => {
78
+ const name = item.name || item.key || item.id || "Item";
79
+ const formatted = `${chalk.gray(`${startingNumber + index + 1}.`)} ${chalk.cyan(String(name))}`;
80
+ calls.push(formatted);
81
+ });
82
+ return calls;
83
+ };
84
+
85
+ const items = [{ name: "First Item" }, { name: "Second Item" }];
86
+
87
+ // Default startingNumber should be 0, resulting in 1. and 2.
88
+ const output = simulateFormatting(items, 0);
89
+
90
+ expect(output.some((line) => line.includes("1."))).toBe(true);
91
+ expect(output.some((line) => line.includes("2."))).toBe(true);
92
+ });
93
+ });
@@ -69,6 +69,18 @@ interface CliParameter {
69
69
  function analyzeZodSchema(schema: z.ZodSchema): CliParameter[] {
70
70
  const parameters: CliParameter[] = [];
71
71
 
72
+ // Handle ZodEffects (schemas with .refine(), .transform(), etc.)
73
+ if (
74
+ (schema as unknown as { _def?: { typeName?: string } })._def &&
75
+ (schema as unknown as { _def: { typeName: string } })._def.typeName ===
76
+ "ZodEffects"
77
+ ) {
78
+ // Get the underlying schema
79
+ const innerSchema = (schema as unknown as { _def: { schema: z.ZodSchema } })
80
+ ._def.schema;
81
+ return analyzeZodSchema(innerSchema);
82
+ }
83
+
72
84
  if (schema instanceof z.ZodObject) {
73
85
  const shape = schema.shape;
74
86
 
@@ -91,16 +103,31 @@ function analyzeZodField(
91
103
  let required = true;
92
104
  let defaultValue: unknown = undefined;
93
105
 
94
- // Unwrap optional and default wrappers
95
- if (baseSchema instanceof z.ZodOptional) {
96
- required = false;
97
- baseSchema = baseSchema._def.innerType;
98
- }
99
-
100
- if (baseSchema instanceof z.ZodDefault) {
101
- required = false;
102
- defaultValue = baseSchema._def.defaultValue();
103
- baseSchema = baseSchema._def.innerType;
106
+ // Unwrap optional, default, and nullable wrappers - keep unwrapping until we get to the base type
107
+ while (true) {
108
+ if (baseSchema instanceof z.ZodOptional) {
109
+ required = false;
110
+ baseSchema = baseSchema._def.innerType;
111
+ } else if (baseSchema instanceof z.ZodDefault) {
112
+ required = false;
113
+ defaultValue = baseSchema._def.defaultValue();
114
+ baseSchema = baseSchema._def.innerType;
115
+ } else if (
116
+ (
117
+ baseSchema as unknown as {
118
+ _def?: { typeName?: string; innerType?: z.ZodSchema };
119
+ }
120
+ )._def &&
121
+ (baseSchema as unknown as { _def: { typeName: string } })._def
122
+ .typeName === "ZodNullable"
123
+ ) {
124
+ // nullable doesn't affect CLI required status, but we need to unwrap it to get the base type
125
+ baseSchema = (
126
+ baseSchema as unknown as { _def: { innerType: z.ZodSchema } }
127
+ )._def.innerType;
128
+ } else {
129
+ break; // No more wrappers to unwrap
130
+ }
104
131
  }
105
132
 
106
133
  // Determine parameter type
@@ -307,12 +334,12 @@ function createCommandConfig(
307
334
  !shouldUseJson &&
308
335
  !hasUserSpecifiedMaxItems
309
336
  ) {
310
- // Get the async iterator directly from the SDK method call
337
+ // Get the async iterable directly from the SDK method call (don't await it! that breaks the next page behavior)
311
338
  const sdkObj = sdk as unknown as Record<
312
339
  string,
313
- (params: unknown) => Promise<unknown>
340
+ (params: unknown) => Promise<unknown> & AsyncIterable<unknown>
314
341
  >;
315
- const sdkIterator = await sdkObj[sdkMethodName](resolvedParams);
342
+ const sdkIterator = sdkObj[sdkMethodName](resolvedParams);
316
343
  await handlePaginatedListWithAsyncIteration(
317
344
  sdkMethodName,
318
345
  sdkIterator,
@@ -577,9 +604,9 @@ async function handlePaginatedListWithAsyncIteration(
577
604
 
578
605
  // Format and display items using schema
579
606
  if (schema) {
580
- formatItemsFromSchema(schema as z.ZodType, items);
607
+ formatItemsFromSchema(schema as z.ZodType, items, totalShown);
581
608
  } else {
582
- formatItemsGeneric(items);
609
+ formatItemsGeneric(items, totalShown);
583
610
  }
584
611
 
585
612
  totalShown += items.length;
@@ -620,9 +647,9 @@ async function handlePaginatedListWithAsyncIteration(
620
647
  }
621
648
 
622
649
  if (schema) {
623
- formatItemsFromSchema(schema as z.ZodType, items);
650
+ formatItemsFromSchema(schema as z.ZodType, items, 0);
624
651
  } else {
625
- formatItemsGeneric(items);
652
+ formatItemsGeneric(items, 0);
626
653
  }
627
654
 
628
655
  console.log(chalk.green(`\n✅ Showing ${items.length} ${itemName}`));
@@ -683,12 +710,17 @@ function formatNonPaginatedResults(
683
710
  }
684
711
  }
685
712
 
686
- function formatItemsGeneric(items: unknown[]): void {
713
+ function formatItemsGeneric(
714
+ items: unknown[],
715
+ startingNumber: number = 0,
716
+ ): void {
687
717
  // Fallback formatting for items without schema metadata
688
718
  items.forEach((item, index) => {
689
719
  const itemObj = item as Record<string, unknown>;
690
720
  const name = itemObj?.name || itemObj?.key || itemObj?.id || "Item";
691
- console.log(`${chalk.gray(`${index + 1}.`)} ${chalk.cyan(String(name))}`);
721
+ console.log(
722
+ `${chalk.gray(`${startingNumber + index + 1}.`)} ${chalk.cyan(String(name))}`,
723
+ );
692
724
  if (itemObj?.description) {
693
725
  console.log(` ${chalk.dim(String(itemObj.description))}`);
694
726
  }
@@ -32,38 +32,39 @@ function getOutputSchema(schema: unknown): unknown {
32
32
  export function formatItemsFromSchema(
33
33
  inputSchema: z.ZodType,
34
34
  items: unknown[],
35
+ startingNumber: number = 0,
35
36
  ): void {
36
37
  // Get the output schema and its format metadata
37
38
  const outputSchema = getOutputSchema(inputSchema);
38
39
  if (!outputSchema) {
39
40
  // Fallback to generic formatting if no output schema
40
- formatItemsGeneric(items);
41
+ formatItemsGeneric(items, startingNumber);
41
42
  return;
42
43
  }
43
44
 
44
45
  const formatMeta = getFormatMetadata(outputSchema);
45
46
  if (!formatMeta) {
46
47
  // Fallback to generic formatting if no format metadata
47
- formatItemsGeneric(items);
48
+ formatItemsGeneric(items, startingNumber);
48
49
  return;
49
50
  }
50
51
 
51
52
  // Format each item using the schema metadata
52
53
  items.forEach((item, index) => {
53
- formatSingleItem(item, index, formatMeta);
54
+ formatSingleItem(item, startingNumber + index, formatMeta);
54
55
  });
55
56
  }
56
57
 
57
58
  function formatSingleItem(
58
59
  item: unknown,
59
- index: number,
60
+ itemNumber: number,
60
61
  formatMeta: FormatMetadata,
61
62
  ): void {
62
63
  // Get the formatted item from the format function
63
64
  const formatted = formatMeta.format(item);
64
65
 
65
66
  // Build the main title line
66
- let titleLine = `${chalk.gray(`${index + 1}.`)} ${chalk.cyan(formatted.title)}`;
67
+ let titleLine = `${chalk.gray(`${itemNumber + 1}.`)} ${chalk.cyan(formatted.title)}`;
67
68
  if (formatted.subtitle) {
68
69
  titleLine += ` ${chalk.gray(formatted.subtitle)}`;
69
70
  }
@@ -94,7 +95,10 @@ function applyStyle(value: string, style: string): string {
94
95
  }
95
96
  }
96
97
 
97
- function formatItemsGeneric(items: unknown[]): void {
98
+ function formatItemsGeneric(
99
+ items: unknown[],
100
+ startingNumber: number = 0,
101
+ ): void {
98
102
  // Fallback formatting for items without schema metadata
99
103
  items.forEach((item, index) => {
100
104
  const itemObj = item as {
@@ -106,7 +110,9 @@ function formatItemsGeneric(items: unknown[]): void {
106
110
  };
107
111
  const name =
108
112
  itemObj.title || itemObj.name || itemObj.key || itemObj.id || "Item";
109
- console.log(`${chalk.gray(`${index + 1}.`)} ${chalk.cyan(name)}`);
113
+ console.log(
114
+ `${chalk.gray(`${startingNumber + index + 1}.`)} ${chalk.cyan(name)}`,
115
+ );
110
116
  if (itemObj.description) {
111
117
  console.log(` ${chalk.dim(itemObj.description)}`);
112
118
  }