@zapier/zapier-sdk-cli 0.15.14 → 0.16.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zapier/zapier-sdk-cli",
3
- "version": "0.15.14",
3
+ "version": "0.16.1",
4
4
  "description": "Command line interface for Zapier SDK",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.mjs",
@@ -46,10 +46,10 @@
46
46
  "pkce-challenge": "^5.0.0",
47
47
  "semver": "^7.7.3",
48
48
  "typescript": "^5.8.3",
49
- "zod": "^3.25.67",
50
- "@zapier/zapier-sdk-mcp": "0.3.39",
49
+ "zod": "4.2.1",
51
50
  "@zapier/zapier-sdk-cli-login": "0.3.5",
52
- "@zapier/zapier-sdk": "0.15.13"
51
+ "@zapier/zapier-sdk-mcp": "0.4.0",
52
+ "@zapier/zapier-sdk": "0.16.0"
53
53
  },
54
54
  "devDependencies": {
55
55
  "@types/express": "^5.0.3",
@@ -52,13 +52,13 @@ function analyzeZodField(
52
52
  // Unwrap optional and default wrappers
53
53
  if (baseSchema instanceof z.ZodOptional) {
54
54
  required = false;
55
- baseSchema = baseSchema._def.innerType;
55
+ baseSchema = baseSchema._zod.def.innerType as z.ZodSchema;
56
56
  }
57
57
 
58
58
  if (baseSchema instanceof z.ZodDefault) {
59
59
  required = false;
60
- defaultValue = baseSchema._def.defaultValue();
61
- baseSchema = baseSchema._def.innerType;
60
+ defaultValue = (baseSchema._zod.def.defaultValue as () => unknown)();
61
+ baseSchema = baseSchema._zod.def.innerType as z.ZodSchema;
62
62
  }
63
63
 
64
64
  // Determine parameter type
@@ -75,7 +75,7 @@ function analyzeZodField(
75
75
  paramType = "array";
76
76
  } else if (baseSchema instanceof z.ZodEnum) {
77
77
  paramType = "string";
78
- choices = baseSchema._def.values;
78
+ choices = baseSchema.options as string[];
79
79
  } else if (baseSchema instanceof z.ZodRecord) {
80
80
  // Handle Record<string, any> as JSON string input
81
81
  paramType = "string";
@@ -119,6 +119,158 @@ const mockSdk = {
119
119
  listApps: vi.fn().mockResolvedValue({ data: [] }),
120
120
  } as unknown as ZapierSdk;
121
121
 
122
+ // Schema for request command testing
123
+ const requestSchema = z
124
+ .object({
125
+ url: z.string().describe("The URL to request"),
126
+ method: z.string().optional().describe("HTTP method"),
127
+ authenticationId: z.number().optional(),
128
+ })
129
+ .describe("Make authenticated HTTP requests");
130
+
131
+ describe("CLI Response Body Handling", () => {
132
+ let consoleSpy: ReturnType<typeof vi.spyOn>;
133
+
134
+ beforeEach(() => {
135
+ vi.clearAllMocks();
136
+ consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {});
137
+ });
138
+
139
+ afterEach(() => {
140
+ consoleSpy.mockRestore();
141
+ });
142
+
143
+ it("should wrap Response objects in structured envelope with statusCode, headers, body", async () => {
144
+ const bodyData = { ok: true, users: [{ id: 1, name: "Test User" }] };
145
+
146
+ // Create a mock Response object
147
+ const mockResponse = new Response(JSON.stringify(bodyData), {
148
+ status: 200,
149
+ headers: { "Content-Type": "application/json" },
150
+ });
151
+
152
+ const mockSdkWithRequest = {
153
+ getRegistry: vi.fn(() => ({
154
+ functions: [
155
+ {
156
+ name: "request",
157
+ type: "single",
158
+ inputSchema: requestSchema,
159
+ },
160
+ ],
161
+ categories: [],
162
+ })),
163
+ request: vi.fn().mockResolvedValue(mockResponse),
164
+ } as unknown as ZapierSdk;
165
+
166
+ const program = new Command();
167
+ program.exitOverride();
168
+
169
+ generateCliCommands(program, mockSdkWithRequest);
170
+
171
+ await program.parseAsync([
172
+ "node",
173
+ "test",
174
+ "request",
175
+ "https://api.example.com/users",
176
+ "--json",
177
+ ]);
178
+
179
+ // Verify the SDK method was called
180
+ expect(mockSdkWithRequest.request).toHaveBeenCalled();
181
+
182
+ // Verify output includes the structured envelope with statusCode, headers, and body
183
+ const expectedEnvelope = {
184
+ statusCode: 200,
185
+ headers: { "content-type": "application/json" },
186
+ body: bodyData,
187
+ };
188
+ expect(consoleSpy).toHaveBeenCalledWith(
189
+ JSON.stringify(expectedEnvelope, null, 2),
190
+ );
191
+ });
192
+
193
+ it("should handle invalid JSON in Response with helpful error", async () => {
194
+ // Create a mock Response with invalid JSON
195
+ const mockResponse = new Response("not valid json {", {
196
+ status: 200,
197
+ headers: { "Content-Type": "application/json" },
198
+ });
199
+
200
+ const mockSdkWithBadResponse = {
201
+ getRegistry: vi.fn(() => ({
202
+ functions: [
203
+ {
204
+ name: "request",
205
+ type: "single",
206
+ inputSchema: requestSchema,
207
+ },
208
+ ],
209
+ categories: [],
210
+ })),
211
+ request: vi.fn().mockResolvedValue(mockResponse),
212
+ } as unknown as ZapierSdk;
213
+
214
+ const program = new Command();
215
+ program.exitOverride();
216
+ const consoleErrorSpy = vi
217
+ .spyOn(console, "error")
218
+ .mockImplementation(() => {});
219
+
220
+ generateCliCommands(program, mockSdkWithBadResponse);
221
+
222
+ try {
223
+ await program.parseAsync([
224
+ "node",
225
+ "test",
226
+ "request",
227
+ "https://api.example.com/users",
228
+ "--json",
229
+ ]);
230
+ } catch {
231
+ // Expected to throw
232
+ }
233
+
234
+ // Verify error message includes helpful context
235
+ expect(consoleErrorSpy).toHaveBeenCalledWith(
236
+ expect.stringContaining("❌"),
237
+ expect.stringContaining("Failed to parse response as JSON"),
238
+ );
239
+
240
+ consoleErrorSpy.mockRestore();
241
+ });
242
+
243
+ it("should handle non-Response objects normally", async () => {
244
+ const regularData = { apps: [{ id: 1, name: "Test App" }] };
245
+
246
+ const mockSdkRegular = {
247
+ getRegistry: vi.fn(() => ({
248
+ functions: [
249
+ {
250
+ name: "getApp",
251
+ type: "single",
252
+ inputSchema: z.object({ appKey: z.string() }).describe("Get app"),
253
+ },
254
+ ],
255
+ categories: [],
256
+ })),
257
+ getApp: vi.fn().mockResolvedValue(regularData),
258
+ } as unknown as ZapierSdk;
259
+
260
+ const program = new Command();
261
+ program.exitOverride();
262
+
263
+ generateCliCommands(program, mockSdkRegular);
264
+
265
+ await program.parseAsync(["node", "test", "get-app", "test-app", "--json"]);
266
+
267
+ expect(mockSdkRegular.getApp).toHaveBeenCalled();
268
+ expect(consoleSpy).toHaveBeenCalledWith(
269
+ JSON.stringify(regularData, null, 2),
270
+ );
271
+ });
272
+ });
273
+
122
274
  describe("CLI Command Generation", () => {
123
275
  beforeEach(() => {
124
276
  vi.clearAllMocks();
@@ -45,15 +45,14 @@ function analyzeZodSchema(
45
45
  const parameters: CliParameter[] = [];
46
46
 
47
47
  // Handle ZodEffects (schemas with .refine(), .transform(), etc.)
48
- if (
49
- (schema as unknown as { _def?: { typeName?: string } })._def &&
50
- (schema as unknown as { _def: { typeName: string } })._def.typeName ===
51
- "ZodEffects"
52
- ) {
53
- // Get the underlying schema
54
- const innerSchema = (schema as unknown as { _def: { schema: z.ZodSchema } })
55
- ._def.schema;
56
- return analyzeZodSchema(innerSchema, functionInfo);
48
+ // In Zod, effects have type "effect" and inner schema is accessed via innerType
49
+ const schemaDef = (
50
+ schema as unknown as {
51
+ _zod?: { def?: { type?: string; innerType?: z.ZodSchema } };
52
+ }
53
+ )._zod?.def;
54
+ if (schemaDef?.type === "effect" && schemaDef.innerType) {
55
+ return analyzeZodSchema(schemaDef.innerType, functionInfo);
57
56
  }
58
57
 
59
58
  if (schema instanceof z.ZodObject) {
@@ -87,26 +86,26 @@ function analyzeZodField(
87
86
  while (true) {
88
87
  if (baseSchema instanceof z.ZodOptional) {
89
88
  required = false;
90
- baseSchema = baseSchema._def.innerType;
89
+ baseSchema = baseSchema._zod.def.innerType as z.ZodSchema;
91
90
  } else if (baseSchema instanceof z.ZodDefault) {
92
91
  required = false;
93
- defaultValue = baseSchema._def.defaultValue();
94
- baseSchema = baseSchema._def.innerType;
95
- } else if (
96
- (
92
+ defaultValue = (baseSchema._zod.def.defaultValue as () => unknown)();
93
+ baseSchema = baseSchema._zod.def.innerType as z.ZodSchema;
94
+ } else {
95
+ // Check for ZodNullable - nullable doesn't affect CLI required status, but we need to unwrap it to get the base type
96
+ const zodDef = (
97
97
  baseSchema as unknown as {
98
- _def?: { typeName?: string; innerType?: z.ZodSchema };
98
+ _zod?: { def?: { typeName?: string; innerType?: z.ZodSchema } };
99
99
  }
100
- )._def &&
101
- (baseSchema as unknown as { _def: { typeName: string } })._def
102
- .typeName === "ZodNullable"
103
- ) {
104
- // nullable doesn't affect CLI required status, but we need to unwrap it to get the base type
105
- baseSchema = (
106
- baseSchema as unknown as { _def: { innerType: z.ZodSchema } }
107
- )._def.innerType;
108
- } else {
109
- break; // No more wrappers to unwrap
100
+ )._zod?.def;
101
+
102
+ if (zodDef?.typeName === "ZodNullable" && zodDef.innerType) {
103
+ baseSchema = zodDef.innerType;
104
+ continue;
105
+ }
106
+
107
+ // No more wrappers to unwrap
108
+ break;
110
109
  }
111
110
  }
112
111
 
@@ -124,7 +123,7 @@ function analyzeZodField(
124
123
  paramType = "array";
125
124
  } else if (baseSchema instanceof z.ZodEnum) {
126
125
  paramType = "string";
127
- choices = baseSchema._def.values;
126
+ choices = baseSchema.options as string[];
128
127
  } else if (baseSchema instanceof z.ZodRecord) {
129
128
  // Handle Record<string, any> as JSON string input
130
129
  paramType = "string";
@@ -351,7 +350,30 @@ function createCommandConfig(
351
350
  string,
352
351
  (params: unknown) => Promise<unknown>
353
352
  >;
354
- const result: unknown = await sdkObj[functionInfo.name](resolvedParams);
353
+ let result: unknown = await sdkObj[functionInfo.name](resolvedParams);
354
+
355
+ // Handle Response objects by wrapping in a structured envelope
356
+ if (result instanceof Response) {
357
+ const response = result;
358
+ let body: unknown;
359
+ try {
360
+ body = await response.json();
361
+ } catch {
362
+ // If JSON parsing fails, try to get text for error context
363
+ const text = response.bodyUsed
364
+ ? "[body already consumed]"
365
+ : await response.text().catch(() => "[unable to read body]");
366
+ throw new Error(
367
+ `Failed to parse response as JSON (status: ${response.status}). Body: ${text.slice(0, 200)}`,
368
+ );
369
+ }
370
+ result = {
371
+ statusCode: response.status,
372
+ headers: Object.fromEntries(response.headers.entries()),
373
+ body,
374
+ };
375
+ }
376
+
355
377
  const items = (result as { data?: unknown })?.data
356
378
  ? (result as { data: unknown }).data
357
379
  : result;
@@ -364,12 +364,12 @@ export class SchemaParameterResolver {
364
364
  // Check if field is optional or has default
365
365
  if (baseSchema instanceof z.ZodOptional) {
366
366
  isRequired = false;
367
- baseSchema = baseSchema._def.innerType;
367
+ baseSchema = baseSchema._zod.def.innerType as z.ZodSchema;
368
368
  }
369
369
 
370
370
  if (baseSchema instanceof z.ZodDefault) {
371
371
  isRequired = false;
372
- baseSchema = baseSchema._def.innerType;
372
+ baseSchema = baseSchema._zod.def.innerType as z.ZodSchema;
373
373
  }
374
374
 
375
375
  return this.createResolvableParameter([fieldName], baseSchema, isRequired);
@@ -6,12 +6,13 @@ import type { FormattedItem, FormatMetadata } from "@zapier/zapier-sdk";
6
6
  // TODO: Consider exposing these utilities or implementing proper CLI formatting
7
7
 
8
8
  function getFormatMetadata(schema: unknown): FormatMetadata | undefined {
9
- return (schema as { _def?: { formatMeta?: FormatMetadata } })?._def
10
- ?.formatMeta;
9
+ return (schema as { _zod?: { def?: { formatMeta?: FormatMetadata } } })?._zod
10
+ ?.def?.formatMeta;
11
11
  }
12
12
 
13
13
  function getOutputSchema(schema: unknown): unknown {
14
- return (schema as { _def?: { outputSchema?: unknown } })?._def?.outputSchema;
14
+ return (schema as { _zod?: { def?: { outputSchema?: unknown } } })?._zod?.def
15
+ ?.outputSchema;
15
16
  }
16
17
 
17
18
  // ============================================================================