@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/CHANGELOG.md +30 -0
- package/README.md +61 -36
- package/dist/cli.cjs +37 -24
- package/dist/cli.mjs +37 -24
- package/dist/package.json +1 -1
- package/dist/src/utils/cli-generator.js +37 -17
- package/dist/src/utils/schema-formatter.d.ts +1 -1
- package/dist/src/utils/schema-formatter.js +8 -8
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/src/utils/cli-generator.test.ts +93 -0
- package/src/utils/cli-generator.ts +51 -19
- package/src/utils/schema-formatter.ts +13 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zapier/zapier-sdk-cli",
|
|
3
|
-
"version": "0.
|
|
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.
|
|
45
|
+
"@zapier/zapier-sdk": "0.8.0",
|
|
46
46
|
"@zapier/zapier-sdk-cli-login": "0.3.2",
|
|
47
|
-
"@zapier/zapier-sdk-mcp": "0.
|
|
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
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
|
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 =
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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(`${
|
|
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(
|
|
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(
|
|
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
|
}
|