@zapier/zapier-sdk 0.0.2 → 0.0.3

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.
Files changed (41) hide show
  1. package/dist/actions-sdk.d.ts +0 -4
  2. package/dist/actions-sdk.js +43 -1029
  3. package/dist/api.d.ts +62 -0
  4. package/dist/api.js +227 -0
  5. package/dist/functions/bundleCode.d.ts +18 -0
  6. package/dist/functions/bundleCode.js +91 -0
  7. package/dist/functions/generateTypes.d.ts +16 -0
  8. package/dist/functions/generateTypes.js +271 -0
  9. package/dist/functions/getAction.d.ts +16 -0
  10. package/dist/functions/getAction.js +25 -0
  11. package/dist/functions/getApp.d.ts +14 -0
  12. package/dist/functions/getApp.js +41 -0
  13. package/dist/functions/listActions.d.ts +15 -0
  14. package/dist/functions/listActions.js +127 -0
  15. package/dist/functions/listApps.d.ts +16 -0
  16. package/dist/functions/listApps.js +50 -0
  17. package/dist/functions/listAuths.d.ts +18 -0
  18. package/dist/functions/listAuths.js +118 -0
  19. package/dist/functions/listFields.d.ts +18 -0
  20. package/dist/functions/listFields.js +67 -0
  21. package/dist/functions/runAction.d.ts +18 -0
  22. package/dist/functions/runAction.js +156 -0
  23. package/dist/index.d.ts +9 -0
  24. package/dist/index.js +20 -1
  25. package/dist/output-schemas.d.ts +4 -4
  26. package/dist/schemas.d.ts +24 -24
  27. package/dist/types.d.ts +12 -0
  28. package/package.json +1 -1
  29. package/src/actions-sdk.ts +47 -1376
  30. package/src/api.ts +361 -0
  31. package/src/functions/bundleCode.ts +85 -0
  32. package/src/functions/generateTypes.ts +309 -0
  33. package/src/functions/getAction.ts +34 -0
  34. package/src/functions/getApp.ts +47 -0
  35. package/src/functions/listActions.ts +151 -0
  36. package/src/functions/listApps.ts +65 -0
  37. package/src/functions/listAuths.ts +161 -0
  38. package/src/functions/listFields.ts +95 -0
  39. package/src/functions/runAction.ts +256 -0
  40. package/src/index.ts +11 -0
  41. package/src/types.ts +13 -0
@@ -4,13 +4,19 @@ import {
4
4
  ActionExecutionOptions,
5
5
  ActionExecutionResult,
6
6
  ActionField,
7
- NeedsRequest,
8
- NeedsResponse,
9
7
  Authentication,
10
- AuthenticationsResponse,
11
- AppNotFoundError,
12
8
  BaseSdkOptions,
13
9
  } from "./types";
10
+ import { createZapierApi, type ApiClient } from "./api";
11
+ import { listAuths } from "./functions/listAuths";
12
+ import { listApps } from "./functions/listApps";
13
+ import { getApp } from "./functions/getApp";
14
+ import { listActions } from "./functions/listActions";
15
+ import { getAction } from "./functions/getAction";
16
+ import { runAction } from "./functions/runAction";
17
+ import { listFields } from "./functions/listFields";
18
+ import { generateTypes as generateTypesFunction } from "./functions/generateTypes";
19
+ import { bundleCode as bundleCodeFunction } from "./functions/bundleCode";
14
20
  import {
15
21
  AppsListOptions,
16
22
  AppsGetOptions,
@@ -22,7 +28,6 @@ import {
22
28
  FieldsListOptions,
23
29
  GenerateOptions,
24
30
  BundleOptions,
25
- SdkSchemas,
26
31
  } from "./schemas";
27
32
  export * from "./schemas";
28
33
  export * from "./output-schemas";
@@ -69,10 +74,6 @@ export interface BundleFunction {
69
74
  export interface ActionsService {
70
75
  list(options?: ActionsListOptions): Promise<Action[]>;
71
76
  get(options: ActionsGetOptions): Promise<Action>;
72
- /** @deprecated Use sdk.fields.list() instead */
73
- fields(options: FieldsListOptions): Promise<ActionField[]>;
74
- /** @deprecated Use sdk.fields.list() instead */
75
- getFields(options: FieldsListOptions): Promise<ActionField[]>;
76
77
  run: ActionRunner & ActionProxy;
77
78
  }
78
79
 
@@ -110,52 +111,42 @@ export function createActionsSdk(options: ActionsSdkOptions = {}): ActionsSdk {
110
111
  // If no token provided, try to get it from environment variable
111
112
  const finalToken = token || process.env.ZAPIER_TOKEN;
112
113
 
113
- const debugLog = createDebugLogger(debug);
114
-
115
- // Wrap fetch with debug logging if debug is enabled
116
- const wrappedFetch = debug
117
- ? createDebugFetch({
118
- originalFetch: customFetch,
119
- debugLog,
120
- })
121
- : customFetch;
114
+ // Create the API client
115
+ const api = createZapierApi({
116
+ baseUrl,
117
+ token: finalToken,
118
+ debug,
119
+ fetch: customFetch,
120
+ });
122
121
 
123
122
  // Create SDK object - we'll populate services after creation to avoid circular dependency
124
123
  const sdk: ActionsSdk = {} as ActionsSdk;
125
124
 
126
125
  const appsService = createAppsService({
127
- fetch: wrappedFetch,
128
- baseUrl,
126
+ api,
129
127
  token: finalToken,
130
128
  authentications,
131
- debugLog: debug ? debugLog : undefined,
132
129
  sdk,
133
130
  });
134
131
 
135
132
  const actionsService = createActionsService({
136
- fetch: wrappedFetch,
137
- baseUrl,
133
+ api,
138
134
  token: finalToken,
139
135
  authentications,
140
- debugLog,
141
136
  sdk,
142
137
  });
143
138
 
144
139
  const authsService = createAuthsService({
145
- fetch: wrappedFetch,
146
- baseUrl,
140
+ api,
147
141
  token: finalToken,
148
142
  authentications,
149
- debugLog,
150
143
  sdk,
151
144
  });
152
145
 
153
146
  const fieldsService = createFieldsService({
154
- fetch: wrappedFetch,
155
- baseUrl,
147
+ api,
156
148
  token: finalToken,
157
149
  authentications,
158
- debugLog,
159
150
  sdk,
160
151
  });
161
152
 
@@ -163,17 +154,11 @@ export function createActionsSdk(options: ActionsSdkOptions = {}): ActionsSdk {
163
154
  const generateFunction: GenerateFunction = async (
164
155
  options: GenerateOptions,
165
156
  ) => {
166
- // Validate options using schema
167
- const validatedOptions = SdkSchemas.generate.parse(options);
168
-
169
- return await generateTypes(validatedOptions, sdk);
157
+ return generateTypesFunction({ ...options, api, token: finalToken });
170
158
  };
171
159
 
172
160
  const bundleFunction: BundleFunction = async (options: BundleOptions) => {
173
- // Validate options using schema
174
- const validatedOptions = SdkSchemas.bundle.parse(options);
175
-
176
- return await bundleCode(validatedOptions);
161
+ return bundleCodeFunction(options);
177
162
  };
178
163
 
179
164
  // Populate the SDK object
@@ -184,293 +169,59 @@ export function createActionsSdk(options: ActionsSdkOptions = {}): ActionsSdk {
184
169
  sdk.generate = generateFunction;
185
170
  sdk.bundle = bundleFunction;
186
171
 
187
- if (debug) {
188
- debugLog("🚀 Zapier SDK initialized", {
189
- baseUrl,
190
- hasAuth: !!finalToken,
191
- tokenType: finalToken ? (isJwt(finalToken) ? "JWT" : "Bearer") : "none",
192
- appAuthCount: authentications ? Object.keys(authentications).length : 0,
193
- });
194
- }
172
+ // Note: Debug logging for SDK initialization is now handled by the API client
195
173
 
196
174
  return sdk;
197
175
  }
198
176
 
199
177
  interface AppsServiceOptions {
200
- fetch: typeof globalThis.fetch;
201
- baseUrl: string;
178
+ api: ApiClient;
202
179
  token?: string;
203
180
  authentications?: { [appKey: string]: { id: number }[] };
204
- debugLog?: (message: string, details?: any) => void;
205
181
  sdk: ActionsSdk;
206
182
  }
207
183
 
208
184
  interface AuthsServiceOptions {
209
- fetch: typeof globalThis.fetch;
210
- baseUrl: string;
185
+ api: ApiClient;
211
186
  token?: string;
212
187
  authentications?: { [appKey: string]: { id: number }[] };
213
- debugLog?: (message: string, details?: any) => void;
214
188
  sdk: ActionsSdk;
215
189
  }
216
190
 
217
191
  interface ActionsServiceOptions {
218
- fetch: typeof globalThis.fetch;
219
- baseUrl: string;
192
+ api: ApiClient;
220
193
  token?: string;
221
194
  authentications?: { [appKey: string]: { id: number }[] };
222
- debugLog?: (message: string, data?: any) => void;
223
195
  sdk: ActionsSdk;
224
196
  }
225
197
 
226
198
  interface FieldsServiceOptions {
227
- fetch: typeof globalThis.fetch;
228
- baseUrl: string;
199
+ api: ApiClient;
229
200
  token?: string;
230
201
  authentications?: { [appKey: string]: { id: number }[] };
231
- debugLog?: (message: string, data?: any) => void;
232
202
  sdk: ActionsSdk;
233
203
  }
234
204
 
235
205
  function createAppsService(options: AppsServiceOptions): AppsService {
236
- const { fetch, baseUrl, debugLog } = options;
206
+ const { api, token } = options;
237
207
 
238
208
  return {
239
209
  async list(options?: AppsListOptions): Promise<Integration[]> {
240
- const url = new URL("/api/v4/apps/", baseUrl);
241
-
242
- // Add query parameters if provided
243
- if (options?.category) {
244
- url.searchParams.set("category", options.category);
245
- }
246
- if (options?.limit) {
247
- url.searchParams.set("limit", options.limit.toString());
248
- }
249
- if (options?.offset) {
250
- url.searchParams.set("offset", options.offset.toString());
251
- }
252
-
253
- const response = await fetch(url.toString());
254
- if (!response.ok) {
255
- throw new Error(
256
- `Failed to fetch apps: ${response.status} ${response.statusText}`,
257
- );
258
- }
259
-
260
- const data = await response.json();
261
-
262
- // Debug: Log pagination info if debug is enabled
263
- if (debugLog) {
264
- debugLog("API Response Pagination Info", {
265
- resultsCount: data.results?.length || 0,
266
- hasNext: !!data.next,
267
- hasPrevious: !!data.previous,
268
- count: data.count,
269
- nextUrl: data.next,
270
- previousUrl: data.previous,
271
- });
272
- }
273
-
274
- // Transform API response to our Integration interface
275
- const apps =
276
- data.results?.map(
277
- (app: any): Integration => ({
278
- key: app.slug,
279
- name: app.name,
280
- description: app.description,
281
- version: "1.0.0", // API doesn't provide version
282
- category: app.category?.name,
283
- actions: [], // Will be populated separately
284
- triggers: [],
285
- current_implementation_id: app.current_implementation_id,
286
- }),
287
- ) || [];
288
-
289
- // Add pagination metadata to the response
290
- if (apps.length > 0) {
291
- (apps as any).__pagination = {
292
- count: data.count,
293
- hasNext: !!data.next,
294
- hasPrevious: !!data.previous,
295
- nextUrl: data.next,
296
- previousUrl: data.previous,
297
- };
298
- }
299
-
300
- return apps;
210
+ return listApps({ ...options, api, token });
301
211
  },
302
212
 
303
213
  async get(options: AppsGetOptions): Promise<Integration> {
304
- const { key } = options;
305
- const url = new URL(`/api/v4/apps/${key}/`, baseUrl);
306
-
307
- const response = await fetch(url.toString());
308
- if (!response.ok) {
309
- if (response.status === 404) {
310
- throw new AppNotFoundError(key);
311
- }
312
- throw new Error(
313
- `Failed to fetch app: ${response.status} ${response.statusText}`,
314
- );
315
- }
316
-
317
- const app = await response.json();
318
-
319
- return {
320
- key: app.slug,
321
- name: app.name,
322
- description: app.description,
323
- version: "1.0.0",
324
- category: app.category?.name,
325
- actions: [],
326
- triggers: [],
327
- current_implementation_id: app.current_implementation_id,
328
- };
214
+ return getApp({ ...options, api, token });
329
215
  },
330
216
  };
331
217
  }
332
218
 
333
219
  function createAuthsService(options: AuthsServiceOptions): AuthsService {
334
- const { fetch, baseUrl, token, authentications, debugLog, sdk } = options;
335
-
336
- // Local function to handle the actual API fetching
337
- const listAuths = async (
338
- options?: AuthsListOptions,
339
- ): Promise<Authentication[]> => {
340
- const url = new URL("/api/v4/authentications/", baseUrl);
341
-
342
- // Handle appKey filtering by getting the selected_api first
343
- if (options?.appKey) {
344
- try {
345
- // Use the SDK's getApp function instead of duplicating the API call
346
- const appData = await sdk.apps.get({ key: options.appKey });
347
- const selectedApi = appData.current_implementation_id;
348
- if (selectedApi) {
349
- // Use versionless_selected_api to find auths across all app versions
350
- // Extract the base name without the version (e.g., "SlackCLIAPI" from "SlackCLIAPI@1.21.1")
351
- const versionlessApi = selectedApi.split("@")[0];
352
- url.searchParams.set("versionless_selected_api", versionlessApi);
353
- }
354
- } catch (error) {
355
- // If it's an AppNotFoundError, re-throw it
356
- if (error instanceof AppNotFoundError) {
357
- throw error;
358
- }
359
- // For other errors, continue without app filtering
360
- console.warn(
361
- `Warning: Could not filter by app ${options.appKey}:`,
362
- error instanceof Error ? error.message : "Unknown error",
363
- );
364
- }
365
- }
366
-
367
- // Add other query parameters if provided
368
- if (options?.account_id) {
369
- url.searchParams.set("account_id", options.account_id);
370
- }
371
- if (options?.owner) {
372
- url.searchParams.set("owner", options.owner);
373
- }
374
- if (options?.limit) {
375
- url.searchParams.set("limit", options.limit.toString());
376
- }
377
- if (options?.offset) {
378
- url.searchParams.set("offset", options.offset.toString());
379
- }
380
-
381
- // Build headers with auth if available
382
- const headers: Record<string, string> = {};
383
-
384
- if (token) {
385
- headers["Authorization"] = getAuthorizationHeader(token);
386
- } else {
387
- throw new Error(
388
- "Authentication token is required to list authentications. Please provide token when creating the SDK.",
389
- );
390
- }
391
-
392
- const response = await fetch(url.toString(), { headers });
393
- if (!response.ok) {
394
- if (response.status === 401) {
395
- throw new Error(
396
- `Authentication failed. Your token may not have permission to access authentications or may be expired. (HTTP ${response.status})`,
397
- );
398
- }
399
- if (response.status === 403) {
400
- throw new Error(
401
- `Access forbidden. Your token may not have the required scopes to list authentications. (HTTP ${response.status})`,
402
- );
403
- }
404
- throw new Error(
405
- `Failed to fetch authentications: ${response.status} ${response.statusText}`,
406
- );
407
- }
408
-
409
- const data: AuthenticationsResponse = await response.json();
410
-
411
- // Debug: Log pagination info if debug is enabled
412
- if (debugLog) {
413
- debugLog("API Response Pagination Info", {
414
- resultsCount: data.results?.length || 0,
415
- hasNext: !!data.next,
416
- hasPrevious: !!data.previous,
417
- count: data.count,
418
- nextUrl: data.next,
419
- previousUrl: data.previous,
420
- });
421
- }
422
-
423
- // Transform API response
424
- const auths = data.results || [];
425
-
426
- // Add pagination metadata to the response
427
- if (auths.length > 0) {
428
- (auths as any).__pagination = {
429
- count: data.count,
430
- hasNext: !!data.next,
431
- hasPrevious: !!data.previous,
432
- nextUrl: data.next,
433
- previousUrl: data.previous,
434
- };
435
- }
436
-
437
- return auths;
438
- };
220
+ const { api, token, authentications } = options;
439
221
 
440
222
  return {
441
223
  async list(options?: AuthsListOptions): Promise<Authentication[]> {
442
- // If a limit is provided and no specific owner filter, prioritize owned auths
443
- if (options?.limit && options?.owner === undefined) {
444
- // First get owned auths
445
- const ownedAuths = await listAuths({
446
- ...options,
447
- owner: "me",
448
- });
449
-
450
- // If we have enough owned auths, just slice and return
451
- if (ownedAuths.length >= options.limit) {
452
- return ownedAuths.slice(0, options.limit);
453
- }
454
-
455
- // Get all auths up to the original limit to fill remaining slots
456
- const allAuths = await listAuths({
457
- ...options,
458
- owner: undefined,
459
- });
460
-
461
- // Filter out auths the user already owns to avoid duplicates
462
- const ownedAuthIds = new Set(ownedAuths.map((auth) => auth.id));
463
- const additionalAuths = allAuths.filter(
464
- (auth) => !ownedAuthIds.has(auth.id),
465
- );
466
-
467
- // Combine and slice to the requested limit
468
- const combined = [...ownedAuths, ...additionalAuths];
469
- return combined.slice(0, options.limit);
470
- }
471
-
472
- // Standard implementation for non-prioritized requests
473
- return listAuths(options);
224
+ return listAuths({ ...options, api, token });
474
225
  },
475
226
 
476
227
  find(options: { appKey: string }): number {
@@ -498,260 +249,26 @@ function createAuthsService(options: AuthsServiceOptions): AuthsService {
498
249
  }
499
250
 
500
251
  function createFieldsService(options: FieldsServiceOptions): FieldsService {
501
- const { fetch, baseUrl, token, sdk } = options;
252
+ const { api, token } = options;
502
253
 
503
254
  return {
504
255
  async list(options: FieldsListOptions): Promise<ActionField[]> {
505
- // Map consistent parameter names to internal API names
506
- const { app, action, type, authId, params } = options;
507
- const appKey = app;
508
- const actionKey = action;
509
- const actionType = type;
510
-
511
- // Build headers with auth if available
512
- const headers: Record<string, string> = {};
513
- if (token) {
514
- headers["Authorization"] = getAuthorizationHeader(token);
515
- }
516
-
517
- // Use the SDK's getApp function instead of duplicating the API call
518
- const appData = await sdk.apps.get({ key: appKey });
519
- const selectedApi = appData.current_implementation_id;
520
-
521
- if (!selectedApi) {
522
- throw new Error("No current_implementation_id found for app");
523
- }
524
-
525
- // Build needs request
526
- const needsRequest: NeedsRequest = {
527
- selected_api: selectedApi,
528
- action: actionKey,
529
- type_of: actionType,
530
- authentication_id: authId,
531
- params: params || {},
532
- };
533
-
534
- const needsUrl = new URL("/api/v4/implementations/needs/", baseUrl);
535
-
536
- // Add Content-Type to headers
537
- const needsHeaders = { ...headers, "Content-Type": "application/json" };
538
-
539
- const needsResponse = await fetch(needsUrl.toString(), {
540
- method: "POST",
541
- headers: needsHeaders,
542
- body: JSON.stringify(needsRequest),
543
- });
544
-
545
- if (!needsResponse.ok) {
546
- // Add more detailed error info for debugging
547
- let errorMessage = `Failed to fetch action fields: ${needsResponse.status} ${needsResponse.statusText}`;
548
- try {
549
- const errorBody = await needsResponse.text();
550
- errorMessage += ` - ${errorBody}`;
551
- } catch {
552
- // Ignore error reading response body
553
- }
554
- throw new Error(errorMessage);
555
- }
556
-
557
- const needsData: NeedsResponse = await needsResponse.json();
558
-
559
- if (!needsData.success) {
560
- throw new Error(
561
- `Failed to get action fields: ${
562
- needsData.errors?.join(", ") || "Unknown error"
563
- }`,
564
- );
565
- }
566
-
567
- // Transform API response to our ActionField interface
568
- return (needsData.needs || []).map((need: any) => ({
569
- key: need.key,
570
- label: need.label,
571
- required: need.required || false,
572
- type: need.type,
573
- helpText: need.help_text,
574
- helpTextHtml: need.help_text_html,
575
- choices: need.choices?.map((choice: any) => ({
576
- value: choice.value,
577
- label: choice.label,
578
- })),
579
- default: need.default,
580
- placeholder: need.placeholder,
581
- computed: need.computed,
582
- customField: need.custom_field,
583
- dependsOn: need.depends_on,
584
- format: need.format,
585
- inputFormat: need.input_format,
586
- }));
256
+ return listFields({ ...options, api, token });
587
257
  },
588
258
  };
589
259
  }
590
260
 
591
261
  function createActionsService(options: ActionsServiceOptions): ActionsService {
592
- const { fetch, baseUrl, token, authentications, debugLog, sdk } = options;
262
+ const { api, token, authentications } = options;
593
263
 
594
264
  // Create the base service with named methods
595
265
  const baseService: ActionsService = {
596
266
  async list(options?: ActionsListOptions): Promise<Action[]> {
597
- // If filtering by appKey, use optimized approach with selected_apis parameter
598
- if (options?.appKey) {
599
- try {
600
- // Use the SDK's getApp function instead of duplicating the API call
601
- const appData = await sdk.apps.get({ key: options.appKey });
602
-
603
- // Extract implementation name from current_implementation_id (e.g., "SlackCLIAPI@1.20.0" -> "SlackCLIAPI")
604
- const implementationId =
605
- appData.current_implementation_id?.split("@")[0];
606
- if (!implementationId) {
607
- throw new Error("No current_implementation_id found for app");
608
- }
609
-
610
- const url = new URL("/api/v4/implementations/", baseUrl);
611
- url.searchParams.set("global", "true");
612
- url.searchParams.set("public_only", "true");
613
- url.searchParams.set("selected_apis", implementationId);
614
-
615
- const response = await fetch(url.toString());
616
- if (!response.ok) {
617
- throw new Error(
618
- `Failed to fetch actions: ${response.status} ${response.statusText}`,
619
- );
620
- }
621
-
622
- const data = await response.json();
623
- const actions: Action[] = [];
624
-
625
- // Transform implementations to actions
626
- for (const implementation of data.results || []) {
627
- if (implementation.actions) {
628
- for (const action of implementation.actions) {
629
- const transformedAction: Action = {
630
- key: action.key,
631
- name: action.name || action.label,
632
- description: action.description || "",
633
- appKey: implementation.slug || "",
634
- type: action.type || action.type_of || "read",
635
- inputFields: [], // Would need additional API call for detailed fields
636
- outputFields: [],
637
- };
638
-
639
- // Apply type filter if provided
640
- if (options?.type && transformedAction.type !== options.type) {
641
- continue;
642
- }
643
-
644
- actions.push(transformedAction);
645
- }
646
- }
647
- }
648
-
649
- // Add pagination metadata for app-specific queries
650
- if (actions.length > 0) {
651
- (actions as any).__pagination = {
652
- count: data.count,
653
- hasNext: !!data.next,
654
- hasPrevious: !!data.previous,
655
- nextUrl: data.next,
656
- previousUrl: data.previous,
657
- };
658
- }
659
-
660
- return actions;
661
- } catch (error) {
662
- // If it's an AppNotFoundError, don't try fallback - just re-throw
663
- if (error instanceof AppNotFoundError) {
664
- throw error;
665
- }
666
- // Fallback to original approach if optimized approach fails
667
- console.warn(
668
- "Optimized app lookup failed, falling back to full scan:",
669
- error,
670
- );
671
- }
672
- }
673
-
674
- // Original approach for general queries or when optimization fails
675
- const url = new URL("/api/v4/implementations/", baseUrl);
676
-
677
- // Add query parameters
678
- url.searchParams.set("global", "true");
679
- url.searchParams.set("public_only", "true");
680
-
681
- const response = await fetch(url.toString());
682
- if (!response.ok) {
683
- throw new Error(
684
- `Failed to fetch actions: ${response.status} ${response.statusText}`,
685
- );
686
- }
687
-
688
- const data = await response.json();
689
- const actions: Action[] = [];
690
-
691
- // Transform implementations to actions
692
- for (const implementation of data.results || []) {
693
- if (implementation.actions) {
694
- for (const action of implementation.actions) {
695
- const transformedAction: Action = {
696
- key: action.key,
697
- name: action.name || action.label,
698
- description: action.description || "",
699
- appKey: implementation.slug || "",
700
- type: action.type || action.type_of || "read",
701
- inputFields: [], // Would need additional API call for detailed fields
702
- outputFields: [],
703
- };
704
-
705
- // Apply filters
706
- if (
707
- options?.appKey &&
708
- transformedAction.appKey !== options.appKey
709
- ) {
710
- continue;
711
- }
712
- if (options?.type && transformedAction.type !== options.type) {
713
- continue;
714
- }
715
-
716
- actions.push(transformedAction);
717
- }
718
- }
719
- }
720
-
721
- // Add pagination metadata for general queries
722
- if (actions.length > 0) {
723
- (actions as any).__pagination = {
724
- count: data.count,
725
- hasNext: !!data.next,
726
- hasPrevious: !!data.previous,
727
- nextUrl: data.next,
728
- previousUrl: data.previous,
729
- };
730
- }
731
-
732
- return actions;
267
+ return listActions({ ...options, api, token });
733
268
  },
734
269
 
735
270
  async get(options: ActionsGetOptions): Promise<Action> {
736
- const { app, action: actionKey, type } = options;
737
- const actions = await baseService.list({ appKey: app });
738
- const action = actions.find(
739
- (a) => a.key === actionKey && a.type === type,
740
- );
741
-
742
- if (!action) {
743
- throw new Error(`Action not found: ${actionKey} with type ${type}`);
744
- }
745
-
746
- return action;
747
- },
748
-
749
- async fields(options: FieldsListOptions): Promise<ActionField[]> {
750
- return await sdk.fields.list(options);
751
- },
752
-
753
- async getFields(options: FieldsListOptions): Promise<ActionField[]> {
754
- return await sdk.fields.list(options);
271
+ return getAction({ ...options, api, token });
755
272
  },
756
273
 
757
274
  // run will be replaced with function/proxy pattern below
@@ -763,20 +280,6 @@ function createActionsService(options: ActionsServiceOptions): ActionsService {
763
280
  ): Promise<ActionExecutionResult> => {
764
281
  const { app, type, action, inputs, authId: providedAuthId } = params;
765
282
 
766
- // Validate that the action exists
767
- const actionData = await baseService.get({
768
- app: app,
769
- action: action,
770
- type: type,
771
- });
772
-
773
- // Validate action type matches
774
- if (actionData.type !== type) {
775
- throw new Error(
776
- `Action type mismatch: expected ${type}, got ${actionData.type}`,
777
- );
778
- }
779
-
780
283
  // Resolve auth ID for this specific app
781
284
  let authId: number | undefined = providedAuthId;
782
285
 
@@ -793,29 +296,16 @@ function createActionsService(options: ActionsServiceOptions): ActionsService {
793
296
  authId = auths[0].id;
794
297
  }
795
298
 
796
- // Execute the action using the appropriate API based on action type
797
- const startTime = Date.now();
798
- const result = await executeActionWithStrategy({
799
- fetch,
800
- baseUrl,
801
- appSlug: app,
802
- actionKey: action,
803
- actionType: actionData.type,
804
- executionOptions: { inputs: inputs || {} },
805
- auth: token ? { token: token, authentication_id: authId } : undefined,
806
- debugLog,
807
- sdk,
299
+ // Delegate to standalone runAction function
300
+ return runAction({
301
+ app,
302
+ type,
303
+ action,
304
+ inputs,
305
+ authId,
306
+ api,
307
+ token,
808
308
  });
809
- const executionTime = Date.now() - startTime;
810
-
811
- return {
812
- success: true,
813
- data: result,
814
- metadata: {
815
- executionTime,
816
- requestId: generateRequestId(),
817
- },
818
- };
819
309
  };
820
310
 
821
311
  // Create proxy wrapper for the run function
@@ -864,822 +354,3 @@ function createActionsService(options: ActionsServiceOptions): ActionsService {
864
354
  // Return the service with the new run function/proxy
865
355
  return baseService;
866
356
  }
867
-
868
- interface ExecuteActionStrategyOptions {
869
- fetch: typeof globalThis.fetch;
870
- baseUrl: string;
871
- appSlug: string;
872
- actionKey: string;
873
- actionType: string;
874
- executionOptions: ActionExecutionOptions;
875
- auth?: { token: string; authentication_id?: number };
876
- debugLog?: (message: string, data?: any) => void;
877
- sdk: ActionsSdk;
878
- }
879
-
880
- async function executeActionWithStrategy(
881
- options: ExecuteActionStrategyOptions,
882
- ): Promise<any> {
883
- const {
884
- fetch,
885
- baseUrl,
886
- appSlug,
887
- actionKey,
888
- actionType,
889
- executionOptions,
890
- auth,
891
- debugLog,
892
- sdk,
893
- } = options;
894
- debugLog?.("🎯 Choosing execution strategy", { actionType });
895
-
896
- // Actions API supports: read, read_bulk, write
897
- // Invoke API supports: search, read, write, read_bulk, and more
898
-
899
- const actionsApiTypes = ["read", "read_bulk", "write"];
900
- const useActionsApi = actionsApiTypes.includes(actionType);
901
-
902
- if (useActionsApi) {
903
- debugLog?.("🔧 Using Actions API", {
904
- actionType,
905
- reason: "supported type",
906
- });
907
- return executeActionViaActionsApi({
908
- fetch,
909
- baseUrl,
910
- appSlug,
911
- actionKey,
912
- actionType,
913
- executionOptions,
914
- auth,
915
- debugLog,
916
- sdk,
917
- });
918
- } else {
919
- debugLog?.("🔧 Using Invoke API", {
920
- actionType,
921
- reason: "unsupported by Actions API",
922
- });
923
- return executeActionViaInvokeApi({
924
- fetch,
925
- baseUrl,
926
- appSlug,
927
- actionKey,
928
- actionType,
929
- executionOptions,
930
- auth,
931
- debugLog,
932
- sdk,
933
- });
934
- }
935
- }
936
-
937
- interface ExecuteActionViaActionsApiOptions {
938
- fetch: typeof globalThis.fetch;
939
- baseUrl: string;
940
- appSlug: string;
941
- actionKey: string;
942
- actionType: string;
943
- executionOptions: ActionExecutionOptions;
944
- auth?: { token: string; authentication_id?: number };
945
- debugLog?: (message: string, data?: any) => void;
946
- sdk: ActionsSdk;
947
- }
948
-
949
- async function executeActionViaActionsApi(
950
- options: ExecuteActionViaActionsApiOptions,
951
- ): Promise<any> {
952
- const {
953
- fetch,
954
- baseUrl,
955
- appSlug,
956
- actionKey,
957
- actionType,
958
- executionOptions,
959
- auth,
960
- sdk,
961
- } = options;
962
- // Note: Action validation is handled by the caller before reaching this function
963
-
964
- // Use the SDK's getApp function instead of duplicating the API call
965
- const appData = await sdk.apps.get({ key: appSlug });
966
- const selectedApi = appData.current_implementation_id;
967
-
968
- if (!selectedApi) {
969
- throw new Error("No current_implementation_id found for app");
970
- }
971
-
972
- if (!auth?.token) {
973
- throw new Error(
974
- "Authentication token is required. Please provide token when creating the SDK.",
975
- );
976
- }
977
-
978
- // Determine if token is JWT and use appropriate prefix
979
- const authHeader = getAuthorizationHeader(auth.token);
980
-
981
- // Step 1: POST to /actions/v1/runs to start execution
982
- const runRequest = {
983
- data: {
984
- authentication_id: auth.authentication_id || 1,
985
- selected_api: selectedApi,
986
- action_key: actionKey,
987
- action_type: actionType,
988
- inputs: executionOptions.inputs || {},
989
- },
990
- };
991
-
992
- const runUrl = `${baseUrl}/api/actions/v1/runs`;
993
-
994
- const headers = {
995
- "Content-Type": "application/json",
996
- Authorization: authHeader,
997
- };
998
-
999
- const runResponse = await fetch(runUrl, {
1000
- method: "POST",
1001
- headers,
1002
- body: JSON.stringify(runRequest),
1003
- });
1004
-
1005
- if (!runResponse.ok) {
1006
- throw new Error(
1007
- `Failed to start action execution: POST ${runUrl} - ${runResponse.status} ${runResponse.statusText}`,
1008
- );
1009
- }
1010
-
1011
- const runData = await runResponse.json();
1012
- const runId = runData.data.id;
1013
-
1014
- // Step 2: Poll GET /actions/v1/runs/{run_id} for results
1015
- return await pollForResults({ fetch, baseUrl, runId, authHeader });
1016
- }
1017
-
1018
- interface PollForResultsOptions {
1019
- fetch: typeof globalThis.fetch;
1020
- baseUrl: string;
1021
- runId: string;
1022
- authHeader: string;
1023
- }
1024
-
1025
- async function pollForResults(options: PollForResultsOptions): Promise<any> {
1026
- const { fetch, baseUrl, runId, authHeader } = options;
1027
- let delay = 50; // Start with 50ms
1028
- const maxDelay = 1000; // Cap at 1 second
1029
- const maxAttempts = 30; // Maximum number of polling attempts
1030
-
1031
- for (let attempt = 0; attempt < maxAttempts; attempt++) {
1032
- const pollUrl = `${baseUrl}/api/actions/v1/runs/${runId}`;
1033
-
1034
- const response = await fetch(pollUrl, {
1035
- headers: {
1036
- Authorization: authHeader,
1037
- },
1038
- });
1039
-
1040
- if (response.status === 200) {
1041
- // Action completed
1042
- const result = await response.json();
1043
- return result.data;
1044
- } else if (response.status === 202) {
1045
- // Still processing, wait and try again
1046
- await new Promise((resolve) => setTimeout(resolve, delay));
1047
- delay = Math.min(delay * 2, maxDelay); // Double delay up to max
1048
- continue;
1049
- } else {
1050
- // Error occurred
1051
- throw new Error(
1052
- `Failed to fetch action results: GET ${pollUrl} - ${response.status} ${response.statusText}`,
1053
- );
1054
- }
1055
- }
1056
-
1057
- throw new Error(`Action execution timed out after ${maxAttempts} attempts`);
1058
- }
1059
-
1060
- interface ExecuteActionViaInvokeApiOptions {
1061
- fetch: typeof globalThis.fetch;
1062
- baseUrl: string;
1063
- appSlug: string;
1064
- actionKey: string;
1065
- actionType: string;
1066
- executionOptions: ActionExecutionOptions;
1067
- auth?: { token: string; authentication_id?: number };
1068
- debugLog?: (message: string, data?: any) => void;
1069
- sdk: ActionsSdk;
1070
- }
1071
-
1072
- async function executeActionViaInvokeApi(
1073
- options: ExecuteActionViaInvokeApiOptions,
1074
- ): Promise<any> {
1075
- const {
1076
- fetch,
1077
- baseUrl,
1078
- appSlug,
1079
- actionKey,
1080
- actionType,
1081
- executionOptions,
1082
- auth,
1083
- sdk,
1084
- } = options;
1085
- // Note: Action and app validation is handled by the caller before reaching this function
1086
-
1087
- // Use the SDK's getApp function instead of duplicating the API call
1088
- const appData = await sdk.apps.get({ key: appSlug });
1089
- const selectedApi = appData.current_implementation_id;
1090
-
1091
- if (!selectedApi) {
1092
- throw new Error("No current_implementation_id found for app");
1093
- }
1094
-
1095
- if (!auth?.token) {
1096
- throw new Error(
1097
- "Authentication token is required. Please provide token when creating the SDK.",
1098
- );
1099
- }
1100
-
1101
- // Determine if token is JWT and use appropriate prefix
1102
- const authHeader = getAuthorizationHeader(auth.token);
1103
-
1104
- // Step 1: POST to /invoke/v1/invoke to start execution
1105
- const invokeRequest = {
1106
- selected_api: selectedApi,
1107
- action: actionKey,
1108
- type_of: actionType,
1109
- authentication_id: auth.authentication_id || 1,
1110
- params: executionOptions.inputs || {},
1111
- };
1112
-
1113
- const invokeUrl = `${baseUrl}/api/invoke/v1/invoke`;
1114
-
1115
- const headers = {
1116
- "Content-Type": "application/json",
1117
- Authorization: authHeader,
1118
- };
1119
-
1120
- const invokeResponse = await fetch(invokeUrl, {
1121
- method: "POST",
1122
- headers,
1123
- body: JSON.stringify(invokeRequest),
1124
- });
1125
-
1126
- if (!invokeResponse.ok) {
1127
- throw new Error(
1128
- `Failed to start action execution: POST ${invokeUrl} - ${invokeResponse.status} ${invokeResponse.statusText}`,
1129
- );
1130
- }
1131
-
1132
- const invokeData = await invokeResponse.json();
1133
- const invocationId = invokeData.invocation_id;
1134
-
1135
- // Step 2: Poll GET /invoke/v1/invoke/{invocation_id} for results
1136
- return await pollForInvokeResults({
1137
- fetch,
1138
- baseUrl,
1139
- invocationId,
1140
- authHeader,
1141
- });
1142
- }
1143
-
1144
- interface PollForInvokeResultsOptions {
1145
- fetch: typeof globalThis.fetch;
1146
- baseUrl: string;
1147
- invocationId: string;
1148
- authHeader: string;
1149
- }
1150
-
1151
- async function pollForInvokeResults(
1152
- options: PollForInvokeResultsOptions,
1153
- ): Promise<any> {
1154
- const { fetch, baseUrl, invocationId, authHeader } = options;
1155
- let delay = 50; // Start with 50ms
1156
- const maxDelay = 1000; // Cap at 1 second
1157
- const maxAttempts = 30; // Maximum number of polling attempts
1158
-
1159
- for (let attempt = 0; attempt < maxAttempts; attempt++) {
1160
- const pollUrl = `${baseUrl}/api/invoke/v1/invoke/${invocationId}`;
1161
-
1162
- const response = await fetch(pollUrl, {
1163
- headers: {
1164
- Authorization: authHeader,
1165
- },
1166
- });
1167
-
1168
- if (response.status === 200) {
1169
- // Action completed
1170
- const result = await response.json();
1171
- return result.results || result;
1172
- } else if (response.status === 202) {
1173
- // Still processing, wait and try again
1174
- await new Promise((resolve) => setTimeout(resolve, delay));
1175
- delay = Math.min(delay * 2, maxDelay); // Double delay up to max
1176
- continue;
1177
- } else {
1178
- // Error occurred
1179
- throw new Error(
1180
- `Failed to fetch action results: GET ${pollUrl} - ${response.status} ${response.statusText}`,
1181
- );
1182
- }
1183
- }
1184
-
1185
- throw new Error(`Action execution timed out after ${maxAttempts} attempts`);
1186
- }
1187
-
1188
- function generateRequestId(): string {
1189
- return Math.random().toString(36).substring(2) + Date.now().toString(36);
1190
- }
1191
-
1192
- function getAuthorizationHeader(token: string): string {
1193
- // Check if token is a JWT (has 3 parts separated by dots)
1194
- if (isJwt(token)) {
1195
- return `JWT ${token}`;
1196
- }
1197
- // Default to Bearer for other token types
1198
- return `Bearer ${token}`;
1199
- }
1200
-
1201
- function isJwt(token: string): boolean {
1202
- // JWT tokens have exactly 3 parts separated by dots
1203
- const parts = token.split(".");
1204
- if (parts.length !== 3) {
1205
- return false;
1206
- }
1207
-
1208
- // Each part should be base64url encoded (no padding)
1209
- // Basic validation - each part should be non-empty and contain valid base64url characters
1210
- const base64urlPattern = /^[A-Za-z0-9_-]+$/;
1211
- return parts.every((part) => part.length > 0 && base64urlPattern.test(part));
1212
- }
1213
-
1214
- function createDebugLogger(enabled: boolean) {
1215
- if (!enabled) {
1216
- return () => {}; // No-op function when debug is disabled
1217
- }
1218
-
1219
- return (message: string, data?: any) => {
1220
- const timestamp = new Date().toISOString();
1221
- if (data) {
1222
- console.log(`[${timestamp}] Zapier SDK: ${message}`, data);
1223
- } else {
1224
- console.log(`[${timestamp}] Zapier SDK: ${message}`);
1225
- }
1226
- };
1227
- }
1228
-
1229
- interface DebugFetchOptions {
1230
- originalFetch: typeof globalThis.fetch;
1231
- debugLog: (message: string, data?: any) => void;
1232
- }
1233
-
1234
- function createDebugFetch(options: DebugFetchOptions) {
1235
- const { originalFetch, debugLog } = options;
1236
- return async (input: RequestInfo | URL, options?: RequestInit) => {
1237
- const startTime = Date.now();
1238
-
1239
- // Convert input to URL string for logging
1240
- const urlString =
1241
- input instanceof URL
1242
- ? input.toString()
1243
- : typeof input === "string"
1244
- ? input
1245
- : input.url;
1246
-
1247
- // Log request
1248
- const requestInfo = {
1249
- method: options?.method || "GET",
1250
- url: urlString,
1251
- headers: options?.headers
1252
- ? maskSensitiveHeaders(options.headers)
1253
- : undefined,
1254
- hasBody: !!options?.body,
1255
- };
1256
-
1257
- debugLog("📤 HTTP Request", requestInfo);
1258
-
1259
- try {
1260
- const response = await originalFetch(input, options);
1261
- const duration = Date.now() - startTime;
1262
-
1263
- // Log response
1264
- debugLog("📥 HTTP Response", {
1265
- status: response.status,
1266
- statusText: response.statusText,
1267
- ok: response.ok,
1268
- duration: `${duration}ms`,
1269
- url: urlString,
1270
- });
1271
-
1272
- return response;
1273
- } catch (error) {
1274
- const duration = Date.now() - startTime;
1275
-
1276
- debugLog("❌ HTTP Error", {
1277
- error: error instanceof Error ? error.message : "Unknown error",
1278
- duration: `${duration}ms`,
1279
- url: urlString,
1280
- });
1281
-
1282
- throw error;
1283
- }
1284
- };
1285
- }
1286
-
1287
- function maskSensitiveHeaders(headers: HeadersInit): Record<string, string> {
1288
- const maskedHeaders: Record<string, string> = {};
1289
-
1290
- if (headers instanceof Headers) {
1291
- headers.forEach((value, key) => {
1292
- maskedHeaders[key] =
1293
- key.toLowerCase() === "authorization" ? maskToken(value) : value;
1294
- });
1295
- } else if (Array.isArray(headers)) {
1296
- headers.forEach(([key, value]) => {
1297
- maskedHeaders[key] =
1298
- key.toLowerCase() === "authorization" ? maskToken(value) : value;
1299
- });
1300
- } else {
1301
- Object.entries(headers).forEach(([key, value]) => {
1302
- maskedHeaders[key] =
1303
- key.toLowerCase() === "authorization" ? maskToken(value) : value;
1304
- });
1305
- }
1306
-
1307
- return maskedHeaders;
1308
- }
1309
-
1310
- function maskToken(token: string): string {
1311
- if (token.length <= 8) {
1312
- return "***";
1313
- }
1314
-
1315
- // Show first 4 and last 4 characters, mask the middle
1316
- const start = token.substring(0, 4);
1317
- const end = token.substring(token.length - 4);
1318
- const middle = "*".repeat(Math.min(token.length - 8, 20)); // Cap mask length
1319
-
1320
- return `${start}${middle}${end}`;
1321
- }
1322
-
1323
- // ============================================================================
1324
- // Generate Implementation
1325
- // ============================================================================
1326
-
1327
- interface ActionWithActionFields extends Omit<Action, "inputFields"> {
1328
- inputFields: ActionField[];
1329
- }
1330
-
1331
- async function generateTypes(
1332
- options: GenerateOptions,
1333
- sdk: ActionsSdk,
1334
- ): Promise<string> {
1335
- const { appKey, authId, output = `./types/${appKey}.d.ts` } = options;
1336
-
1337
- // Parse app identifier (support app@version format)
1338
- const { app, version } = parseAppIdentifier(appKey);
1339
-
1340
- // Fetch all actions for the app
1341
- const actions = await sdk.actions.list({ appKey: app });
1342
-
1343
- if (actions.length === 0) {
1344
- const typeDefinitions = generateEmptyTypesFile(app, version);
1345
-
1346
- if (output) {
1347
- const fs = await import("fs");
1348
- const path = await import("path");
1349
- fs.mkdirSync(path.dirname(output), { recursive: true });
1350
- fs.writeFileSync(output, typeDefinitions, "utf8");
1351
- }
1352
-
1353
- return typeDefinitions;
1354
- }
1355
-
1356
- // Fetch input fields for each action
1357
- const actionsWithFields: ActionWithActionFields[] = [];
1358
-
1359
- if (authId) {
1360
- for (const action of actions) {
1361
- try {
1362
- const fields = await sdk.fields.list({
1363
- app: action.appKey,
1364
- action: action.key,
1365
- type: action.type,
1366
- authId: authId,
1367
- });
1368
- actionsWithFields.push({ ...action, inputFields: fields });
1369
- } catch {
1370
- // If we can't get fields for an action, include it without fields
1371
- actionsWithFields.push({ ...action, inputFields: [] });
1372
- }
1373
- }
1374
- } else {
1375
- // Convert actions to have empty input fields (will generate generic types)
1376
- actions.forEach((action) => {
1377
- actionsWithFields.push({ ...action, inputFields: [] });
1378
- });
1379
- }
1380
-
1381
- // Generate TypeScript types
1382
- const typeDefinitions = generateTypeDefinitions(
1383
- app,
1384
- actionsWithFields,
1385
- version,
1386
- );
1387
-
1388
- // Write to file if output path specified
1389
- if (output) {
1390
- const fs = await import("fs");
1391
- const path = await import("path");
1392
- fs.mkdirSync(path.dirname(output), { recursive: true });
1393
- fs.writeFileSync(output, typeDefinitions, "utf8");
1394
- }
1395
-
1396
- return typeDefinitions;
1397
- }
1398
-
1399
- function parseAppIdentifier(identifier: string): {
1400
- app: string;
1401
- version?: string;
1402
- } {
1403
- const parts = identifier.split("@");
1404
- return {
1405
- app: parts[0],
1406
- version: parts[1],
1407
- };
1408
- }
1409
-
1410
- function generateTypeDefinitions(
1411
- appKey: string,
1412
- actions: ActionWithActionFields[],
1413
- version?: string,
1414
- ): string {
1415
- // Handle empty actions
1416
- if (actions.length === 0) {
1417
- return generateEmptyTypesFile(appKey, version);
1418
- }
1419
-
1420
- // Group actions by type
1421
- const actionsByType = actions.reduce(
1422
- (acc, action) => {
1423
- if (!acc[action.type]) {
1424
- acc[action.type] = [];
1425
- }
1426
- acc[action.type].push(action);
1427
- return acc;
1428
- },
1429
- {} as Record<string, ActionWithActionFields[]>,
1430
- );
1431
-
1432
- const appName = capitalize(appKey);
1433
- const versionComment = version
1434
- ? ` * Generated for ${appKey}@${version}`
1435
- : ` * Generated for ${appKey}`;
1436
-
1437
- let output = `/* eslint-disable @typescript-eslint/naming-convention */
1438
- /**
1439
- * Auto-generated TypeScript types for Zapier ${appKey} actions
1440
- ${versionComment}
1441
- * Generated on: ${new Date().toISOString()}
1442
- *
1443
- * Usage:
1444
- * import type { ${appName}Actions } from './path/to/this/file'
1445
- * const sdk: ActionsSdk & { actions: ${appName}Actions } = createActionsSdk(...)
1446
- */
1447
-
1448
- import type { ActionExecutionOptions, ActionExecutionResult } from '@zapier/zapier-sdk'
1449
-
1450
- `;
1451
-
1452
- // Generate input types for each action
1453
- actions.forEach((action) => {
1454
- if (action.inputFields.length > 0) {
1455
- const inputTypeName = `${appName}${capitalize(action.type)}${capitalize(
1456
- sanitizeActionName(action.key),
1457
- )}Inputs`;
1458
-
1459
- output += `interface ${inputTypeName} {\n`;
1460
-
1461
- action.inputFields.forEach((field) => {
1462
- const isOptional = !field.required;
1463
- const fieldType = mapFieldTypeToTypeScript(field);
1464
- const description = field.helpText
1465
- ? ` /** ${escapeComment(field.helpText)} */\n`
1466
- : "";
1467
-
1468
- output += `${description} ${sanitizeFieldName(field.key)}${
1469
- isOptional ? "?" : ""
1470
- }: ${fieldType}\n`;
1471
- });
1472
-
1473
- output += `}\n\n`;
1474
- }
1475
- });
1476
-
1477
- // Generate action type interfaces for each action type
1478
- Object.entries(actionsByType).forEach(([actionType, typeActions]) => {
1479
- const typeName = `${appName}${capitalize(actionType)}Actions`;
1480
-
1481
- output += `interface ${typeName} {\n`;
1482
-
1483
- typeActions.forEach((action: ActionWithActionFields) => {
1484
- const actionName = sanitizeActionName(action.key);
1485
- const description = action.description
1486
- ? ` /** ${escapeComment(action.description)} */\n`
1487
- : "";
1488
-
1489
- // Generate type-safe action method signature
1490
- if (action.inputFields.length > 0) {
1491
- const inputTypeName = `${appName}${capitalize(action.type)}${capitalize(
1492
- sanitizeActionName(action.key),
1493
- )}Inputs`;
1494
- output += `${description} ${actionName}: (options: { inputs: ${inputTypeName} } & Omit<ActionExecutionOptions, 'inputs'>) => Promise<ActionExecutionResult>\n`;
1495
- } else {
1496
- // No specific input fields available - use generic Record<string, any> for inputs
1497
- output += `${description} ${actionName}: (options?: { inputs?: Record<string, any> } & ActionExecutionOptions) => Promise<ActionExecutionResult>\n`;
1498
- }
1499
- });
1500
-
1501
- output += `}\n\n`;
1502
- });
1503
-
1504
- // Generate the main app actions interface
1505
- output += `export interface ${appName}Actions {\n`;
1506
- output += ` run: {\n`;
1507
- output += ` ${appKey}: {\n`;
1508
-
1509
- Object.keys(actionsByType).forEach((actionType) => {
1510
- const typeName = `${appName}${capitalize(actionType)}Actions`;
1511
- output += ` ${actionType}: ${typeName}\n`;
1512
- });
1513
-
1514
- output += ` }\n`;
1515
- output += ` }\n`;
1516
- output += `}\n`;
1517
-
1518
- return output;
1519
- }
1520
-
1521
- function generateEmptyTypesFile(appKey: string, version?: string): string {
1522
- const appName = capitalize(appKey);
1523
- const versionComment = version
1524
- ? ` * Generated for ${appKey}@${version}`
1525
- : ` * Generated for ${appKey}`;
1526
-
1527
- return `/* eslint-disable @typescript-eslint/naming-convention */
1528
- /**
1529
- * Auto-generated TypeScript types for Zapier ${appKey} actions
1530
- ${versionComment}
1531
- * Generated on: ${new Date().toISOString()}
1532
- *
1533
- * No actions found for this app.
1534
- */
1535
-
1536
- import type { ActionExecutionOptions, ActionExecutionResult } from '@zapier/zapier-sdk'
1537
-
1538
- export interface ${appName}Actions {
1539
- run: {
1540
- ${appKey}: {
1541
- // No actions available
1542
- }
1543
- }
1544
- }
1545
- `;
1546
- }
1547
-
1548
- function capitalize(str: string): string {
1549
- return str.charAt(0).toUpperCase() + str.slice(1).replace(/[-_]/g, "");
1550
- }
1551
-
1552
- function sanitizeActionName(actionKey: string): string {
1553
- // Ensure the action name is a valid TypeScript identifier
1554
- return actionKey.replace(/[^a-zA-Z0-9_$]/g, "_");
1555
- }
1556
-
1557
- function sanitizeFieldName(fieldKey: string): string {
1558
- // Ensure the field name is a valid TypeScript identifier
1559
- return fieldKey.replace(/[^a-zA-Z0-9_$]/g, "_");
1560
- }
1561
-
1562
- function escapeComment(comment: string): string {
1563
- // Escape comment text to prevent breaking the JSDoc comment
1564
- return comment.replace(/\*\//g, "*\\/").replace(/\r?\n/g, " ");
1565
- }
1566
-
1567
- function mapFieldTypeToTypeScript(field: ActionField): string {
1568
- // Handle choices (enum-like fields)
1569
- if (field.choices && field.choices.length > 0) {
1570
- const choiceValues = field.choices
1571
- .filter(
1572
- (choice) =>
1573
- choice.value !== undefined &&
1574
- choice.value !== null &&
1575
- choice.value !== "",
1576
- )
1577
- .map((choice) =>
1578
- typeof choice.value === "string" ? `"${choice.value}"` : choice.value,
1579
- );
1580
-
1581
- if (choiceValues.length > 0) {
1582
- return choiceValues.join(" | ");
1583
- }
1584
- // If all choices were filtered out, fall through to default type handling
1585
- }
1586
-
1587
- // Map Zapier field types to TypeScript types
1588
- switch (field.type?.toLowerCase()) {
1589
- case "string":
1590
- case "text":
1591
- case "email":
1592
- case "url":
1593
- case "password":
1594
- return "string";
1595
- case "integer":
1596
- case "number":
1597
- return "number";
1598
- case "boolean":
1599
- return "boolean";
1600
- case "datetime":
1601
- case "date":
1602
- return "string"; // ISO date strings
1603
- case "file":
1604
- return "string"; // File URL or content
1605
- case "array":
1606
- return "any[]";
1607
- case "object":
1608
- return "Record<string, any>";
1609
- default:
1610
- // Default to string for unknown types, with union for common cases
1611
- return "string | number | boolean";
1612
- }
1613
- }
1614
-
1615
- // ============================================================================
1616
- // Bundle Implementation
1617
- // ============================================================================
1618
-
1619
- async function bundleCode(options: BundleOptions): Promise<string> {
1620
- const {
1621
- input,
1622
- output,
1623
- target = "es2020",
1624
- cjs = false,
1625
- minify = false,
1626
- string: returnString = false,
1627
- } = options;
1628
-
1629
- // Dynamically import esbuild
1630
- const { buildSync } = await import("esbuild");
1631
- const fs = await import("fs");
1632
- const path = await import("path");
1633
-
1634
- // Resolve input path
1635
- const resolvedInput = path.resolve(process.cwd(), input);
1636
-
1637
- try {
1638
- // Bundle with esbuild
1639
- const result = buildSync({
1640
- entryPoints: [resolvedInput],
1641
- bundle: true,
1642
- platform: "node",
1643
- target: target,
1644
- format: cjs ? "cjs" : "esm",
1645
- minify: minify,
1646
- write: false,
1647
- external: [], // Bundle everything
1648
- banner: {
1649
- js: "#!/usr/bin/env node",
1650
- },
1651
- });
1652
-
1653
- if (result.errors.length > 0) {
1654
- throw new Error(
1655
- `Bundle failed: ${result.errors.map((e) => e.text).join(", ")}`,
1656
- );
1657
- }
1658
-
1659
- const bundledCode = result.outputFiles?.[0]?.text;
1660
- if (!bundledCode) {
1661
- throw new Error("No output generated");
1662
- }
1663
-
1664
- let finalOutput = bundledCode;
1665
-
1666
- if (returnString) {
1667
- // Output as quoted string for node -e using JSON.stringify
1668
- finalOutput = JSON.stringify(bundledCode);
1669
- }
1670
-
1671
- // Write to file if output path specified
1672
- if (output) {
1673
- fs.mkdirSync(path.dirname(output), { recursive: true });
1674
- fs.writeFileSync(output, finalOutput, "utf8");
1675
- }
1676
-
1677
- return finalOutput;
1678
- } catch (error) {
1679
- throw new Error(
1680
- `Bundle failed: ${
1681
- error instanceof Error ? error.message : "Unknown error"
1682
- }`,
1683
- );
1684
- }
1685
- }