@zapier/zapier-sdk-cli 0.13.1 → 0.13.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zapier/zapier-sdk-cli",
3
- "version": "0.13.1",
3
+ "version": "0.13.3",
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.13.1",
45
+ "@zapier/zapier-sdk": "0.13.3",
46
46
  "@zapier/zapier-sdk-cli-login": "0.3.2",
47
- "@zapier/zapier-sdk-mcp": "0.3.14"
47
+ "@zapier/zapier-sdk-mcp": "0.3.16"
48
48
  },
49
49
  "devDependencies": {
50
50
  "@types/express": "^5.0.3",
@@ -6,7 +6,9 @@ import type {
6
6
  ListActionsPluginProvides,
7
7
  ListInputFieldsPluginProvides,
8
8
  ManifestPluginProvides,
9
+ AppItem,
9
10
  } from "@zapier/zapier-sdk";
11
+ import { toSnakeCase } from "@zapier/zapier-sdk";
10
12
 
11
13
  interface ActionWithInputFields extends Omit<Action, "inputFields" | "type"> {
12
14
  inputFields: InputFieldItem[];
@@ -17,7 +19,7 @@ interface ActionWithInputFields extends Omit<Action, "inputFields" | "type"> {
17
19
  }
18
20
 
19
21
  interface GenerateTypesOptions {
20
- appKey: string;
22
+ app: AppItem;
21
23
  authenticationId?: number;
22
24
  }
23
25
 
@@ -44,22 +46,15 @@ export class AstTypeGenerator {
44
46
  >;
45
47
  },
46
48
  ): Promise<string> {
47
- const { appKey, authenticationId, sdk } = options;
49
+ const { app, authenticationId, sdk } = options;
48
50
 
49
- // Parse app identifier (support app@version format)
50
- const { app, version } = this.parseAppIdentifier(appKey);
51
-
52
- // Fetch all actions for the app
51
+ // Fetch all actions for the app using implementation_id for correct versioning
53
52
  const actionsResult = await sdk.listActions({
54
- appKey: app,
53
+ appKey: app.implementation_id,
55
54
  });
56
55
 
57
56
  const actions = actionsResult.data;
58
57
 
59
- if (actions.length === 0) {
60
- return this.generateEmptyTypesFile(app, version);
61
- }
62
-
63
58
  // Fetch input fields for each action
64
59
  const actionsWithFields: ActionWithInputFields[] = [];
65
60
 
@@ -67,7 +62,7 @@ export class AstTypeGenerator {
67
62
  for (const action of actions) {
68
63
  try {
69
64
  const fieldsResult = await sdk.listInputFields({
70
- appKey: appKey,
65
+ appKey: app.implementation_id,
71
66
  actionKey: action.key,
72
67
  actionType: action.action_type,
73
68
  authenticationId: authenticationId,
@@ -102,7 +97,7 @@ export class AstTypeGenerator {
102
97
  ...action,
103
98
  inputFields: [],
104
99
  name: action.title || action.key,
105
- app_key: action.app_key || appKey,
100
+ app_key: action.app_key || app.implementation_id,
106
101
  action_type: (action.action_type as Action["type"]) || "write",
107
102
  title: action.title || action.key,
108
103
  type: "action" as const,
@@ -113,35 +108,23 @@ export class AstTypeGenerator {
113
108
  }
114
109
 
115
110
  // Generate TypeScript AST nodes
116
- const sourceFile = this.createSourceFile(app, actionsWithFields, version);
111
+ const sourceFile = this.createSourceFile(app, actionsWithFields);
117
112
 
118
113
  // Print the AST to string
119
114
  return this.printer.printFile(sourceFile);
120
115
  }
121
116
 
122
- private parseAppIdentifier(identifier: string): {
123
- app: string;
124
- version?: string;
125
- } {
126
- const parts = identifier.split("@");
127
- return {
128
- app: parts[0],
129
- version: parts[1],
130
- };
131
- }
132
-
133
117
  private createSourceFile(
134
- appKey: string,
118
+ app: AppItem,
135
119
  actions: ActionWithInputFields[],
136
- version?: string,
137
120
  ): ts.SourceFile {
138
- const appName = this.capitalize(appKey);
139
- const versionComment = version
140
- ? ` * Generated for ${appKey}@${version}`
141
- : ` * Generated for ${appKey}`;
121
+ const appName = this.getPreferredAppName(app);
122
+ const versionComment = ` * Generated for ${app.implementation_id}`;
123
+ const preferredKey = this.getPreferredProgrammaticKey(app);
124
+ const myVariableName = `my${appName}`;
142
125
 
143
126
  // Create header comment
144
- const headerComment = `Auto-generated TypeScript types for Zapier ${appKey} actions
127
+ const headerComment = `Auto-generated TypeScript types for Zapier ${app.key} actions
145
128
  ${versionComment.slice(3)}
146
129
  Generated on: ${new Date().toISOString()}
147
130
 
@@ -153,11 +136,11 @@ Usage:
153
136
 
154
137
  const zapier = createZapierSdk();
155
138
  // Types are automatically available:
156
- await zapier.apps.${appKey}.search.user_by_email({ authenticationId: 123, inputs: { email } })
139
+ await zapier.apps.${preferredKey}.search.user_by_email({ authenticationId: 123, inputs: { email } })
157
140
 
158
141
  // Factory usage (pinned auth):
159
- const my${appName} = zapier.apps.${appKey}({ authenticationId: 123 })
160
- await my${appName}.search.user_by_email({ inputs: { email } })`;
142
+ const ${myVariableName} = zapier.apps.${preferredKey}({ authenticationId: 123 })
143
+ await ${myVariableName}.search.user_by_email({ inputs: { email } })`;
161
144
 
162
145
  const statements: ts.Statement[] = [
163
146
  // Import the SDK to activate module augmentation
@@ -211,7 +194,8 @@ Usage:
211
194
  statements.push(appWithFactoryType);
212
195
 
213
196
  // Generate module augmentation for automatic type integration
214
- const moduleAugmentation = this.createModuleAugmentation(appKey, appName);
197
+ const allKeys = this.getAllKeys(app);
198
+ const moduleAugmentation = this.createModuleAugmentation(allKeys, appName);
215
199
  statements.push(moduleAugmentation);
216
200
 
217
201
  // Add empty export to make this a module
@@ -607,11 +591,21 @@ Usage:
607
591
  }
608
592
 
609
593
  private createModuleAugmentation(
610
- appKey: string,
594
+ appKeys: string[],
611
595
  appName: string,
612
596
  ): ts.ModuleDeclaration {
613
597
  // Create: declare module "@zapier/zapier-sdk" { interface ZapierSdkApps { [appKey]: AppWithFactory } }
614
598
  // This creates a new interface that we can merge with ZapierSdk
599
+ // Create properties for all keys (main key + other_keys)
600
+ const properties = appKeys.map((appKey) =>
601
+ this.factory.createPropertySignature(
602
+ undefined,
603
+ this.createPropertyName(appKey),
604
+ undefined,
605
+ this.factory.createTypeReferenceNode(`${appName}AppWithFactory`),
606
+ ),
607
+ );
608
+
615
609
  return this.factory.createModuleDeclaration(
616
610
  [this.factory.createToken(ts.SyntaxKind.DeclareKeyword)],
617
611
  this.factory.createStringLiteral("@zapier/zapier-sdk"),
@@ -621,14 +615,7 @@ Usage:
621
615
  "ZapierSdkApps",
622
616
  undefined,
623
617
  undefined,
624
- [
625
- this.factory.createPropertySignature(
626
- undefined,
627
- appKey,
628
- undefined,
629
- this.factory.createTypeReferenceNode(`${appName}AppWithFactory`),
630
- ),
631
- ],
618
+ properties,
632
619
  ),
633
620
  ]),
634
621
  );
@@ -673,42 +660,6 @@ Usage:
673
660
  }
674
661
  }
675
662
 
676
- private generateEmptyTypesFile(appKey: string, version?: string): string {
677
- const appName = this.capitalize(appKey);
678
- const versionComment = version
679
- ? ` * Generated for ${appKey}@${version}`
680
- : ` * Generated for ${appKey}`;
681
-
682
- return `/* eslint-disable @typescript-eslint/naming-convention, @typescript-eslint/no-explicit-any */
683
- /**
684
- * Auto-generated TypeScript types for Zapier ${appKey} actions
685
- ${versionComment}
686
- * Generated on: ${new Date().toISOString()}
687
- *
688
- * No actions found for this app.
689
- */
690
-
691
- import type { ActionExecutionOptions, ActionExecutionResult, ZapierFetchInitOptions } from '@zapier/zapier-sdk'
692
-
693
- interface ${appName}AppProxy {
694
- /** Make authenticated HTTP requests through Zapier's Relay service */
695
- fetch: (url: string | URL, init?: ZapierFetchInitOptions) => Promise<Response>
696
- }
697
-
698
- interface ${appName}AppFactory {
699
- (options: { authenticationId: number }): ${appName}AppProxy
700
- }
701
-
702
- type ${appName}AppWithFactory = ${appName}AppFactory & ${appName}AppProxy
703
-
704
- declare module "@zapier/zapier-sdk" {
705
- interface ZapierSdkApps {
706
- ${appKey}: ${appName}AppWithFactory
707
- }
708
- }
709
- `;
710
- }
711
-
712
663
  private capitalize(str: string): string {
713
664
  return str.charAt(0).toUpperCase() + str.slice(1).replace(/[-_]/g, "");
714
665
  }
@@ -741,4 +692,75 @@ declare module "@zapier/zapier-sdk" {
741
692
  // Escape comment text to prevent breaking the JSDoc comment
742
693
  return comment.replace(/\*\//g, "*\\/").replace(/\r?\n/g, " ");
743
694
  }
695
+
696
+ private createPropertyName(name: string): ts.PropertyName {
697
+ // Check if the name is a valid JavaScript identifier
698
+ if (this.isValidIdentifier(name)) {
699
+ return this.factory.createIdentifier(name);
700
+ } else {
701
+ // Use string literal for invalid identifiers (e.g., contains dashes)
702
+ return this.factory.createStringLiteral(name);
703
+ }
704
+ }
705
+
706
+ private isValidIdentifier(name: string): boolean {
707
+ // JavaScript identifier rules: must start with letter, $, or _,
708
+ // followed by letters, digits, $, or _
709
+ return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
710
+ }
711
+
712
+ private getPreferredProgrammaticKey(app: AppItem): string {
713
+ // If we have a slug, convert it to snake_case as the preferred programmatic key
714
+ if (app.slug) {
715
+ const snakeCaseSlug = toSnakeCase(app.slug);
716
+ if (this.isValidIdentifier(snakeCaseSlug)) {
717
+ return snakeCaseSlug;
718
+ }
719
+ }
720
+
721
+ // Otherwise, use the main key if it's a valid identifier
722
+ if (this.isValidIdentifier(app.key)) {
723
+ return app.key;
724
+ }
725
+
726
+ // Fallback: sanitize the key to make it a valid identifier
727
+ return this.sanitizeToIdentifier(app.key);
728
+ }
729
+
730
+ private getPreferredAppName(app: AppItem): string {
731
+ const preferredKey = this.getPreferredProgrammaticKey(app);
732
+
733
+ // If it's snake_case, convert to PascalCase
734
+ if (preferredKey.includes("_")) {
735
+ return preferredKey
736
+ .split("_")
737
+ .map((word) => this.capitalize(word.toLowerCase()))
738
+ .join("");
739
+ }
740
+
741
+ // Otherwise, capitalize first letter for PascalCase
742
+ return this.capitalize(preferredKey);
743
+ }
744
+
745
+ private sanitizeToIdentifier(name: string): string {
746
+ // Convert any string to a valid JavaScript identifier
747
+ let sanitized = name.replace(/[^a-zA-Z0-9_$]/g, "_");
748
+
749
+ // If it starts with a number, prepend an underscore
750
+ if (/^[0-9]/.test(sanitized)) {
751
+ sanitized = "_" + sanitized;
752
+ }
753
+
754
+ return sanitized;
755
+ }
756
+
757
+ private getAllKeys(app: AppItem): string[] {
758
+ // Return all possible keys for this app
759
+ const allKeys = new Set([app.key]);
760
+ if (app.slug) {
761
+ allKeys.add(app.slug);
762
+ allKeys.add(toSnakeCase(app.slug));
763
+ }
764
+ return Array.from(allKeys);
765
+ }
744
766
  }
@@ -7,6 +7,7 @@ import type {
7
7
  ListInputFieldsPluginProvides,
8
8
  ListAuthenticationsPluginProvides,
9
9
  ManifestPluginProvides,
10
+ AppItem,
10
11
  } from "@zapier/zapier-sdk";
11
12
  import { createFunction } from "@zapier/zapier-sdk";
12
13
  import { AddSchema, type AddOptions } from "./schemas";
@@ -72,7 +73,7 @@ export const addPlugin: Plugin<
72
73
  // Get apps using listApps (which respects existing manifest for version locking)
73
74
  console.log(`📦 Looking up ${appKeys.length} app(s)...`);
74
75
  const appsIterator = sdk.listApps({ appKeys }).items();
75
- const apps = [];
76
+ const apps: AppItem[] = [];
76
77
  for await (const app of appsIterator) {
77
78
  apps.push(app);
78
79
  }
@@ -151,7 +152,7 @@ export const addPlugin: Plugin<
151
152
  // Use AST-based generator directly
152
153
  const generator = new AstTypeGenerator();
153
154
  const typeDefinitions = await generator.generateTypes({
154
- appKey: manifestKey,
155
+ app,
155
156
  authenticationId,
156
157
  sdk,
157
158
  });
@@ -66,13 +66,22 @@ function formatSingleItem(formatted: FormattedItem, itemNumber: number): void {
66
66
  // Build the main title line with optional subtitle
67
67
  let titleLine = `${chalk.gray(`${itemNumber + 1}.`)} ${chalk.cyan(formatted.title)}`;
68
68
 
69
- // Generate subtitle from id or key
70
- if (formatted.id) {
71
- titleLine += ` ${chalk.gray(`(ID: ${formatted.id})`)}`;
72
- } else if (formatted.keys) {
73
- titleLine += ` ${chalk.gray(`(${formatted.keys.join(", ")})`)}`;
69
+ // Generate subtitle - always show key if available, then ID if available
70
+ const subtitleParts = [];
71
+ if (formatted.keys) {
72
+ subtitleParts.push(...formatted.keys);
74
73
  } else if (formatted.key) {
75
- titleLine += ` ${chalk.gray(`(${formatted.key})`)}`;
74
+ subtitleParts.push(formatted.key);
75
+ }
76
+ if (formatted.id) {
77
+ subtitleParts.push(formatted.id);
78
+ }
79
+
80
+ // Remove duplicates while preserving order
81
+ const uniqueParts = [...new Set(subtitleParts)];
82
+
83
+ if (uniqueParts.length > 0) {
84
+ titleLine += ` ${chalk.gray(`(${uniqueParts.join(", ")})`)}`;
76
85
  }
77
86
  console.log(titleLine);
78
87