@zapier/zapier-sdk-cli 0.8.3 → 0.9.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 +20 -0
- package/README.md +35 -51
- package/dist/cli.cjs +750 -346
- package/dist/cli.mjs +751 -347
- package/dist/index.cjs +726 -336
- package/dist/index.mjs +727 -337
- package/dist/package.json +1 -1
- package/dist/src/plugins/add/ast-generator.d.ts +37 -0
- package/dist/src/plugins/add/ast-generator.js +403 -0
- package/dist/src/plugins/add/index.d.ts +13 -0
- package/dist/src/plugins/add/index.js +122 -0
- package/dist/src/plugins/add/schemas.d.ts +18 -0
- package/dist/src/plugins/add/schemas.js +19 -0
- package/dist/src/plugins/getLoginConfigPath/index.d.ts +15 -0
- package/dist/src/plugins/getLoginConfigPath/index.js +19 -0
- package/dist/src/plugins/getLoginConfigPath/schemas.d.ts +3 -0
- package/dist/src/plugins/getLoginConfigPath/schemas.js +5 -0
- package/dist/src/plugins/index.d.ts +2 -2
- package/dist/src/plugins/index.js +2 -2
- package/dist/src/sdk.js +3 -3
- package/dist/src/utils/cli-generator.js +15 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/src/plugins/add/ast-generator.ts +777 -0
- package/src/plugins/add/index.test.ts +58 -0
- package/src/plugins/add/index.ts +187 -0
- package/src/plugins/add/schemas.ts +26 -0
- package/src/plugins/getLoginConfigPath/index.ts +45 -0
- package/src/plugins/getLoginConfigPath/schemas.ts +10 -0
- package/src/plugins/index.ts +2 -2
- package/src/sdk.ts +4 -4
- package/src/utils/cli-generator.ts +22 -0
- package/tsup.config.ts +1 -1
- package/dist/src/plugins/generateTypes/index.d.ts +0 -21
- package/dist/src/plugins/generateTypes/index.js +0 -312
- package/dist/src/plugins/generateTypes/schemas.d.ts +0 -18
- package/dist/src/plugins/generateTypes/schemas.js +0 -14
- package/dist/src/plugins/getConfigPath/index.d.ts +0 -15
- package/dist/src/plugins/getConfigPath/index.js +0 -19
- package/dist/src/plugins/getConfigPath/schemas.d.ts +0 -3
- package/dist/src/plugins/getConfigPath/schemas.js +0 -5
- package/src/plugins/generateTypes/index.ts +0 -444
- package/src/plugins/generateTypes/schemas.ts +0 -23
- package/src/plugins/getConfigPath/index.ts +0 -42
- package/src/plugins/getConfigPath/schemas.ts +0 -8
|
@@ -0,0 +1,777 @@
|
|
|
1
|
+
import * as ts from "typescript";
|
|
2
|
+
import type {
|
|
3
|
+
Action,
|
|
4
|
+
ActionField,
|
|
5
|
+
GetSdkType,
|
|
6
|
+
ListActionsPluginProvides,
|
|
7
|
+
ListInputFieldsPluginProvides,
|
|
8
|
+
ManifestPluginProvides,
|
|
9
|
+
} from "@zapier/zapier-sdk";
|
|
10
|
+
|
|
11
|
+
interface ActionWithActionFields extends Omit<Action, "inputFields" | "type"> {
|
|
12
|
+
inputFields: ActionField[];
|
|
13
|
+
app_key: string;
|
|
14
|
+
action_type: Action["type"];
|
|
15
|
+
title: string;
|
|
16
|
+
type: "action";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface GenerateTypesOptions {
|
|
20
|
+
appKey: string;
|
|
21
|
+
authenticationId?: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* AST-based TypeScript type generator using the TypeScript Compiler API
|
|
26
|
+
*/
|
|
27
|
+
export class AstTypeGenerator {
|
|
28
|
+
private readonly factory = ts.factory;
|
|
29
|
+
private readonly printer = ts.createPrinter({
|
|
30
|
+
newLine: ts.NewLineKind.LineFeed,
|
|
31
|
+
removeComments: false,
|
|
32
|
+
omitTrailingSemicolon: false,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Generate TypeScript types using AST for a specific app
|
|
37
|
+
*/
|
|
38
|
+
async generateTypes(
|
|
39
|
+
options: GenerateTypesOptions & {
|
|
40
|
+
sdk: GetSdkType<
|
|
41
|
+
ListActionsPluginProvides &
|
|
42
|
+
ListInputFieldsPluginProvides &
|
|
43
|
+
ManifestPluginProvides
|
|
44
|
+
>;
|
|
45
|
+
},
|
|
46
|
+
): Promise<string> {
|
|
47
|
+
const { appKey, authenticationId, sdk } = options;
|
|
48
|
+
|
|
49
|
+
// Parse app identifier (support app@version format)
|
|
50
|
+
const { app, version } = this.parseAppIdentifier(appKey);
|
|
51
|
+
|
|
52
|
+
// Fetch all actions for the app
|
|
53
|
+
const actionsResult = await sdk.listActions({
|
|
54
|
+
appKey: app,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const actions = actionsResult.data;
|
|
58
|
+
|
|
59
|
+
if (actions.length === 0) {
|
|
60
|
+
return this.generateEmptyTypesFile(app, version);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Fetch input fields for each action
|
|
64
|
+
const actionsWithFields: ActionWithActionFields[] = [];
|
|
65
|
+
|
|
66
|
+
if (authenticationId) {
|
|
67
|
+
for (const action of actions) {
|
|
68
|
+
try {
|
|
69
|
+
const fieldsResult = await sdk.listInputFields({
|
|
70
|
+
appKey: appKey,
|
|
71
|
+
actionKey: action.key,
|
|
72
|
+
actionType: action.action_type,
|
|
73
|
+
authenticationId: authenticationId,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const fields = fieldsResult.data.map(
|
|
77
|
+
(field: unknown): ActionField => {
|
|
78
|
+
const fieldObj = field as {
|
|
79
|
+
is_required?: boolean;
|
|
80
|
+
required?: boolean;
|
|
81
|
+
[key: string]: unknown;
|
|
82
|
+
};
|
|
83
|
+
return {
|
|
84
|
+
...fieldObj,
|
|
85
|
+
required: fieldObj.is_required || fieldObj.required || false,
|
|
86
|
+
} as ActionField;
|
|
87
|
+
},
|
|
88
|
+
);
|
|
89
|
+
actionsWithFields.push({
|
|
90
|
+
...action,
|
|
91
|
+
inputFields: fields,
|
|
92
|
+
name: action.title || action.key,
|
|
93
|
+
});
|
|
94
|
+
} catch {
|
|
95
|
+
// If we can't get fields for an action, include it without fields
|
|
96
|
+
actionsWithFields.push({
|
|
97
|
+
...action,
|
|
98
|
+
inputFields: [],
|
|
99
|
+
name: action.title || action.key,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
// Convert actions to have empty input fields (will generate generic types)
|
|
105
|
+
actions.forEach(
|
|
106
|
+
(action: {
|
|
107
|
+
key: string;
|
|
108
|
+
title?: string;
|
|
109
|
+
app_key?: string;
|
|
110
|
+
action_type?: string;
|
|
111
|
+
description?: string;
|
|
112
|
+
}) => {
|
|
113
|
+
actionsWithFields.push({
|
|
114
|
+
...action,
|
|
115
|
+
inputFields: [],
|
|
116
|
+
name: action.title || action.key,
|
|
117
|
+
app_key: action.app_key || appKey,
|
|
118
|
+
action_type: (action.action_type as Action["type"]) || "write",
|
|
119
|
+
title: action.title || action.key,
|
|
120
|
+
type: "action" as const,
|
|
121
|
+
description: action.description || "",
|
|
122
|
+
});
|
|
123
|
+
},
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Generate TypeScript AST nodes
|
|
128
|
+
const sourceFile = this.createSourceFile(app, actionsWithFields, version);
|
|
129
|
+
|
|
130
|
+
// Print the AST to string
|
|
131
|
+
return this.printer.printFile(sourceFile);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private parseAppIdentifier(identifier: string): {
|
|
135
|
+
app: string;
|
|
136
|
+
version?: string;
|
|
137
|
+
} {
|
|
138
|
+
const parts = identifier.split("@");
|
|
139
|
+
return {
|
|
140
|
+
app: parts[0],
|
|
141
|
+
version: parts[1],
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
private createSourceFile(
|
|
146
|
+
appKey: string,
|
|
147
|
+
actions: ActionWithActionFields[],
|
|
148
|
+
version?: string,
|
|
149
|
+
): ts.SourceFile {
|
|
150
|
+
const appName = this.capitalize(appKey);
|
|
151
|
+
const versionComment = version
|
|
152
|
+
? ` * Generated for ${appKey}@${version}`
|
|
153
|
+
: ` * Generated for ${appKey}`;
|
|
154
|
+
|
|
155
|
+
// Create header comment
|
|
156
|
+
const headerComment = `Auto-generated TypeScript types for Zapier ${appKey} actions
|
|
157
|
+
${versionComment.slice(3)}
|
|
158
|
+
Generated on: ${new Date().toISOString()}
|
|
159
|
+
|
|
160
|
+
This file automatically augments the base SDK types when present.
|
|
161
|
+
No manual imports or type casting required.
|
|
162
|
+
|
|
163
|
+
Usage:
|
|
164
|
+
import { createZapierSdk } from "@zapier/zapier-sdk";
|
|
165
|
+
|
|
166
|
+
const zapier = createZapierSdk();
|
|
167
|
+
// Types are automatically available:
|
|
168
|
+
await zapier.apps.${appKey}.search.user_by_email({ authenticationId: 123, inputs: { email } })
|
|
169
|
+
|
|
170
|
+
// Factory usage (pinned auth):
|
|
171
|
+
const my${appName} = zapier.apps.${appKey}({ authenticationId: 123 })
|
|
172
|
+
await my${appName}.search.user_by_email({ inputs: { email } })`;
|
|
173
|
+
|
|
174
|
+
const statements: ts.Statement[] = [
|
|
175
|
+
// Import the SDK to activate module augmentation
|
|
176
|
+
this.createImportStatement(["@zapier/zapier-sdk"]),
|
|
177
|
+
|
|
178
|
+
// Import types we'll use
|
|
179
|
+
this.createTypeImportStatement(
|
|
180
|
+
[
|
|
181
|
+
"ActionExecutionOptions",
|
|
182
|
+
"ActionExecutionResult",
|
|
183
|
+
"ZapierFetchInitOptions",
|
|
184
|
+
],
|
|
185
|
+
"@zapier/zapier-sdk",
|
|
186
|
+
),
|
|
187
|
+
];
|
|
188
|
+
|
|
189
|
+
// Group actions by type
|
|
190
|
+
const actionsByType = this.groupActionsByType(actions);
|
|
191
|
+
|
|
192
|
+
// Generate input interfaces for each action
|
|
193
|
+
actions.forEach((action) => {
|
|
194
|
+
if (action.inputFields.length > 0) {
|
|
195
|
+
const inputInterface = this.createInputInterface(appName, action);
|
|
196
|
+
statements.push(inputInterface);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// Generate action type interfaces
|
|
201
|
+
Object.entries(actionsByType).forEach(([actionType, typeActions]) => {
|
|
202
|
+
const actionInterface = this.createActionInterface(
|
|
203
|
+
appName,
|
|
204
|
+
actionType,
|
|
205
|
+
typeActions,
|
|
206
|
+
);
|
|
207
|
+
statements.push(actionInterface);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// Generate app proxy interface
|
|
211
|
+
const appProxyInterface = this.createAppProxyInterface(
|
|
212
|
+
appName,
|
|
213
|
+
actionsByType,
|
|
214
|
+
);
|
|
215
|
+
statements.push(appProxyInterface);
|
|
216
|
+
|
|
217
|
+
// Generate app factory interface
|
|
218
|
+
const appFactoryInterface = this.createAppFactoryInterface(appName);
|
|
219
|
+
statements.push(appFactoryInterface);
|
|
220
|
+
|
|
221
|
+
// Generate combined app type
|
|
222
|
+
const appWithFactoryType = this.createAppWithFactoryType(appName);
|
|
223
|
+
statements.push(appWithFactoryType);
|
|
224
|
+
|
|
225
|
+
// Generate module augmentation for automatic type integration
|
|
226
|
+
const moduleAugmentation = this.createModuleAugmentation(appKey, appName);
|
|
227
|
+
statements.push(moduleAugmentation);
|
|
228
|
+
|
|
229
|
+
// Add empty export to make this a module
|
|
230
|
+
statements.push(
|
|
231
|
+
this.factory.createExportDeclaration(
|
|
232
|
+
undefined,
|
|
233
|
+
false,
|
|
234
|
+
this.factory.createNamedExports([]),
|
|
235
|
+
),
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
// Create source file
|
|
239
|
+
const sourceFile = ts.createSourceFile(
|
|
240
|
+
"generated.d.ts",
|
|
241
|
+
"",
|
|
242
|
+
ts.ScriptTarget.Latest,
|
|
243
|
+
false,
|
|
244
|
+
ts.ScriptKind.TS,
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
// Add the header comment and ESLint disable as leading trivia to the first statement
|
|
248
|
+
if (statements.length > 0) {
|
|
249
|
+
// Add ESLint disable comment
|
|
250
|
+
ts.addSyntheticLeadingComment(
|
|
251
|
+
statements[0],
|
|
252
|
+
ts.SyntaxKind.MultiLineCommentTrivia,
|
|
253
|
+
" eslint-disable @typescript-eslint/naming-convention, @typescript-eslint/no-explicit-any ",
|
|
254
|
+
true,
|
|
255
|
+
);
|
|
256
|
+
// Add header comment
|
|
257
|
+
ts.addSyntheticLeadingComment(
|
|
258
|
+
statements[0],
|
|
259
|
+
ts.SyntaxKind.MultiLineCommentTrivia,
|
|
260
|
+
headerComment,
|
|
261
|
+
true,
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return this.factory.updateSourceFile(sourceFile, statements);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
private createImportStatement(
|
|
269
|
+
imports: string[],
|
|
270
|
+
from?: string,
|
|
271
|
+
): ts.ImportDeclaration {
|
|
272
|
+
// Handle side-effect only import (no imports, just the module path)
|
|
273
|
+
if (imports.length === 1 && !from && imports[0].startsWith("@")) {
|
|
274
|
+
return this.factory.createImportDeclaration(
|
|
275
|
+
undefined,
|
|
276
|
+
undefined,
|
|
277
|
+
this.factory.createStringLiteral(imports[0]),
|
|
278
|
+
undefined,
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const fromModule = from || imports[0];
|
|
283
|
+
const importNames = from ? imports : [];
|
|
284
|
+
|
|
285
|
+
return this.factory.createImportDeclaration(
|
|
286
|
+
undefined,
|
|
287
|
+
importNames.length > 0
|
|
288
|
+
? this.factory.createImportClause(
|
|
289
|
+
false,
|
|
290
|
+
undefined,
|
|
291
|
+
this.factory.createNamedImports(
|
|
292
|
+
importNames.map((name) =>
|
|
293
|
+
this.factory.createImportSpecifier(
|
|
294
|
+
false,
|
|
295
|
+
undefined,
|
|
296
|
+
this.factory.createIdentifier(name),
|
|
297
|
+
),
|
|
298
|
+
),
|
|
299
|
+
),
|
|
300
|
+
)
|
|
301
|
+
: undefined,
|
|
302
|
+
this.factory.createStringLiteral(fromModule),
|
|
303
|
+
undefined,
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
private createTypeImportStatement(
|
|
308
|
+
imports: string[],
|
|
309
|
+
from: string,
|
|
310
|
+
): ts.ImportDeclaration {
|
|
311
|
+
return this.factory.createImportDeclaration(
|
|
312
|
+
undefined,
|
|
313
|
+
this.factory.createImportClause(
|
|
314
|
+
true, // typeOnly: true
|
|
315
|
+
undefined,
|
|
316
|
+
this.factory.createNamedImports(
|
|
317
|
+
imports.map((name) =>
|
|
318
|
+
this.factory.createImportSpecifier(
|
|
319
|
+
false,
|
|
320
|
+
undefined,
|
|
321
|
+
this.factory.createIdentifier(name),
|
|
322
|
+
),
|
|
323
|
+
),
|
|
324
|
+
),
|
|
325
|
+
),
|
|
326
|
+
this.factory.createStringLiteral(from),
|
|
327
|
+
undefined,
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
private groupActionsByType(
|
|
332
|
+
actions: ActionWithActionFields[],
|
|
333
|
+
): Record<string, ActionWithActionFields[]> {
|
|
334
|
+
return actions.reduce(
|
|
335
|
+
(acc, action) => {
|
|
336
|
+
if (!acc[action.action_type]) {
|
|
337
|
+
acc[action.action_type] = [];
|
|
338
|
+
}
|
|
339
|
+
acc[action.action_type].push(action);
|
|
340
|
+
return acc;
|
|
341
|
+
},
|
|
342
|
+
{} as Record<string, ActionWithActionFields[]>,
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
private createInputInterface(
|
|
347
|
+
appName: string,
|
|
348
|
+
action: ActionWithActionFields,
|
|
349
|
+
): ts.InterfaceDeclaration {
|
|
350
|
+
const inputTypeName = `${appName}${this.capitalize(action.action_type)}${this.capitalize(
|
|
351
|
+
this.sanitizeActionName(action.key),
|
|
352
|
+
)}Inputs`;
|
|
353
|
+
|
|
354
|
+
const properties = action.inputFields.map((field) => {
|
|
355
|
+
const fieldType = this.mapFieldTypeToTypeNode(field);
|
|
356
|
+
const isOptional = !field.required;
|
|
357
|
+
|
|
358
|
+
let property = this.factory.createPropertySignature(
|
|
359
|
+
undefined,
|
|
360
|
+
this.sanitizeFieldName(field.key),
|
|
361
|
+
isOptional
|
|
362
|
+
? this.factory.createToken(ts.SyntaxKind.QuestionToken)
|
|
363
|
+
: undefined,
|
|
364
|
+
fieldType,
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
// Add JSDoc comment if helpText exists
|
|
368
|
+
if (field.helpText) {
|
|
369
|
+
property = ts.addSyntheticLeadingComment(
|
|
370
|
+
property,
|
|
371
|
+
ts.SyntaxKind.MultiLineCommentTrivia,
|
|
372
|
+
`* ${this.escapeComment(field.helpText)} `,
|
|
373
|
+
true,
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
return property;
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
return this.factory.createInterfaceDeclaration(
|
|
381
|
+
undefined,
|
|
382
|
+
inputTypeName,
|
|
383
|
+
undefined,
|
|
384
|
+
undefined,
|
|
385
|
+
properties,
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
private createActionInterface(
|
|
390
|
+
appName: string,
|
|
391
|
+
actionType: string,
|
|
392
|
+
typeActions: ActionWithActionFields[],
|
|
393
|
+
): ts.InterfaceDeclaration {
|
|
394
|
+
const typeName = `${appName}${this.capitalize(actionType)}Actions`;
|
|
395
|
+
|
|
396
|
+
const methods = typeActions.map((action) => {
|
|
397
|
+
const actionName = this.sanitizeActionName(action.key);
|
|
398
|
+
|
|
399
|
+
let methodSignature: ts.MethodSignature;
|
|
400
|
+
|
|
401
|
+
if (action.inputFields.length > 0) {
|
|
402
|
+
// Generate type-safe action method signature
|
|
403
|
+
const inputTypeName = `${appName}${this.capitalize(action.action_type)}${this.capitalize(
|
|
404
|
+
this.sanitizeActionName(action.key),
|
|
405
|
+
)}Inputs`;
|
|
406
|
+
|
|
407
|
+
const inputsType = this.factory.createTypeLiteralNode([
|
|
408
|
+
this.factory.createPropertySignature(
|
|
409
|
+
undefined,
|
|
410
|
+
"inputs",
|
|
411
|
+
undefined,
|
|
412
|
+
this.factory.createTypeReferenceNode(inputTypeName),
|
|
413
|
+
),
|
|
414
|
+
]);
|
|
415
|
+
|
|
416
|
+
const omitType = this.factory.createTypeReferenceNode("Omit", [
|
|
417
|
+
this.factory.createTypeReferenceNode("ActionExecutionOptions"),
|
|
418
|
+
this.factory.createLiteralTypeNode(
|
|
419
|
+
this.factory.createStringLiteral("inputs"),
|
|
420
|
+
),
|
|
421
|
+
]);
|
|
422
|
+
|
|
423
|
+
const optionsType = this.factory.createIntersectionTypeNode([
|
|
424
|
+
inputsType,
|
|
425
|
+
omitType,
|
|
426
|
+
]);
|
|
427
|
+
|
|
428
|
+
methodSignature = this.factory.createMethodSignature(
|
|
429
|
+
undefined,
|
|
430
|
+
actionName,
|
|
431
|
+
undefined,
|
|
432
|
+
undefined,
|
|
433
|
+
[
|
|
434
|
+
this.factory.createParameterDeclaration(
|
|
435
|
+
undefined,
|
|
436
|
+
undefined,
|
|
437
|
+
"options",
|
|
438
|
+
undefined,
|
|
439
|
+
optionsType,
|
|
440
|
+
),
|
|
441
|
+
],
|
|
442
|
+
this.factory.createTypeReferenceNode("Promise", [
|
|
443
|
+
this.factory.createTypeReferenceNode("ActionExecutionResult"),
|
|
444
|
+
]),
|
|
445
|
+
);
|
|
446
|
+
} else {
|
|
447
|
+
// No specific input fields available - use generic Record<string, any> for inputs
|
|
448
|
+
const genericInputsType = this.factory.createTypeLiteralNode([
|
|
449
|
+
this.factory.createPropertySignature(
|
|
450
|
+
undefined,
|
|
451
|
+
"inputs",
|
|
452
|
+
this.factory.createToken(ts.SyntaxKind.QuestionToken),
|
|
453
|
+
this.factory.createTypeReferenceNode("Record", [
|
|
454
|
+
this.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
|
|
455
|
+
this.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword),
|
|
456
|
+
]),
|
|
457
|
+
),
|
|
458
|
+
]);
|
|
459
|
+
|
|
460
|
+
const intersectionType = this.factory.createIntersectionTypeNode([
|
|
461
|
+
genericInputsType,
|
|
462
|
+
this.factory.createTypeReferenceNode("ActionExecutionOptions"),
|
|
463
|
+
]);
|
|
464
|
+
|
|
465
|
+
methodSignature = this.factory.createMethodSignature(
|
|
466
|
+
undefined,
|
|
467
|
+
actionName,
|
|
468
|
+
undefined,
|
|
469
|
+
undefined,
|
|
470
|
+
[
|
|
471
|
+
this.factory.createParameterDeclaration(
|
|
472
|
+
undefined,
|
|
473
|
+
undefined,
|
|
474
|
+
"options",
|
|
475
|
+
this.factory.createToken(ts.SyntaxKind.QuestionToken),
|
|
476
|
+
intersectionType,
|
|
477
|
+
),
|
|
478
|
+
],
|
|
479
|
+
this.factory.createTypeReferenceNode("Promise", [
|
|
480
|
+
this.factory.createTypeReferenceNode("ActionExecutionResult"),
|
|
481
|
+
]),
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Add JSDoc comment if description exists
|
|
486
|
+
if (action.description) {
|
|
487
|
+
methodSignature = ts.addSyntheticLeadingComment(
|
|
488
|
+
methodSignature,
|
|
489
|
+
ts.SyntaxKind.MultiLineCommentTrivia,
|
|
490
|
+
`* ${this.escapeComment(action.description)} `,
|
|
491
|
+
true,
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
return methodSignature;
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
return this.factory.createInterfaceDeclaration(
|
|
499
|
+
undefined,
|
|
500
|
+
typeName,
|
|
501
|
+
undefined,
|
|
502
|
+
undefined,
|
|
503
|
+
methods,
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
private createAppProxyInterface(
|
|
508
|
+
appName: string,
|
|
509
|
+
actionsByType: Record<string, ActionWithActionFields[]>,
|
|
510
|
+
): ts.InterfaceDeclaration {
|
|
511
|
+
const properties = [
|
|
512
|
+
...Object.keys(actionsByType).map((actionType) =>
|
|
513
|
+
this.factory.createPropertySignature(
|
|
514
|
+
undefined,
|
|
515
|
+
actionType,
|
|
516
|
+
undefined,
|
|
517
|
+
this.factory.createTypeReferenceNode(
|
|
518
|
+
`${appName}${this.capitalize(actionType)}Actions`,
|
|
519
|
+
),
|
|
520
|
+
),
|
|
521
|
+
),
|
|
522
|
+
// Always include fetch method for authenticated HTTP requests
|
|
523
|
+
this.createFetchMethodProperty(),
|
|
524
|
+
];
|
|
525
|
+
|
|
526
|
+
return this.factory.createInterfaceDeclaration(
|
|
527
|
+
undefined,
|
|
528
|
+
`${appName}AppProxy`,
|
|
529
|
+
undefined,
|
|
530
|
+
undefined,
|
|
531
|
+
properties,
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
private createFetchMethodProperty(): ts.PropertySignature {
|
|
536
|
+
let property = this.factory.createPropertySignature(
|
|
537
|
+
undefined,
|
|
538
|
+
"fetch",
|
|
539
|
+
undefined,
|
|
540
|
+
this.factory.createFunctionTypeNode(
|
|
541
|
+
undefined,
|
|
542
|
+
[
|
|
543
|
+
this.factory.createParameterDeclaration(
|
|
544
|
+
undefined,
|
|
545
|
+
undefined,
|
|
546
|
+
"url",
|
|
547
|
+
undefined,
|
|
548
|
+
this.factory.createUnionTypeNode([
|
|
549
|
+
this.factory.createTypeReferenceNode("string"),
|
|
550
|
+
this.factory.createTypeReferenceNode("URL"),
|
|
551
|
+
]),
|
|
552
|
+
),
|
|
553
|
+
this.factory.createParameterDeclaration(
|
|
554
|
+
undefined,
|
|
555
|
+
undefined,
|
|
556
|
+
"init",
|
|
557
|
+
this.factory.createToken(ts.SyntaxKind.QuestionToken),
|
|
558
|
+
this.factory.createTypeReferenceNode("ZapierFetchInitOptions"),
|
|
559
|
+
),
|
|
560
|
+
],
|
|
561
|
+
this.factory.createTypeReferenceNode("Promise", [
|
|
562
|
+
this.factory.createTypeReferenceNode("Response"),
|
|
563
|
+
]),
|
|
564
|
+
),
|
|
565
|
+
);
|
|
566
|
+
|
|
567
|
+
// Add JSDoc comment
|
|
568
|
+
property = ts.addSyntheticLeadingComment(
|
|
569
|
+
property,
|
|
570
|
+
ts.SyntaxKind.MultiLineCommentTrivia,
|
|
571
|
+
"* Make authenticated HTTP requests through Zapier's Relay service ",
|
|
572
|
+
true,
|
|
573
|
+
);
|
|
574
|
+
|
|
575
|
+
return property;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
private createAppFactoryInterface(appName: string): ts.InterfaceDeclaration {
|
|
579
|
+
const callSignature = this.factory.createCallSignature(
|
|
580
|
+
undefined,
|
|
581
|
+
[
|
|
582
|
+
this.factory.createParameterDeclaration(
|
|
583
|
+
undefined,
|
|
584
|
+
undefined,
|
|
585
|
+
"options",
|
|
586
|
+
undefined,
|
|
587
|
+
this.factory.createTypeLiteralNode([
|
|
588
|
+
this.factory.createPropertySignature(
|
|
589
|
+
undefined,
|
|
590
|
+
"authenticationId",
|
|
591
|
+
undefined,
|
|
592
|
+
this.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword),
|
|
593
|
+
),
|
|
594
|
+
]),
|
|
595
|
+
),
|
|
596
|
+
],
|
|
597
|
+
this.factory.createTypeReferenceNode(`${appName}AppProxy`),
|
|
598
|
+
);
|
|
599
|
+
|
|
600
|
+
return this.factory.createInterfaceDeclaration(
|
|
601
|
+
undefined,
|
|
602
|
+
`${appName}AppFactory`,
|
|
603
|
+
undefined,
|
|
604
|
+
undefined,
|
|
605
|
+
[callSignature],
|
|
606
|
+
);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
private createAppWithFactoryType(appName: string): ts.TypeAliasDeclaration {
|
|
610
|
+
return this.factory.createTypeAliasDeclaration(
|
|
611
|
+
undefined,
|
|
612
|
+
`${appName}AppWithFactory`,
|
|
613
|
+
undefined,
|
|
614
|
+
this.factory.createIntersectionTypeNode([
|
|
615
|
+
this.factory.createTypeReferenceNode(`${appName}AppFactory`),
|
|
616
|
+
this.factory.createTypeReferenceNode(`${appName}AppProxy`),
|
|
617
|
+
]),
|
|
618
|
+
);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
private createModuleAugmentation(
|
|
622
|
+
appKey: string,
|
|
623
|
+
appName: string,
|
|
624
|
+
): ts.ModuleDeclaration {
|
|
625
|
+
// Create: declare module "@zapier/zapier-sdk" { interface ZapierSdkApps { [appKey]: AppWithFactory } }
|
|
626
|
+
// This creates a new interface that we can merge with ZapierSdk
|
|
627
|
+
return this.factory.createModuleDeclaration(
|
|
628
|
+
[this.factory.createToken(ts.SyntaxKind.DeclareKeyword)],
|
|
629
|
+
this.factory.createStringLiteral("@zapier/zapier-sdk"),
|
|
630
|
+
this.factory.createModuleBlock([
|
|
631
|
+
this.factory.createInterfaceDeclaration(
|
|
632
|
+
undefined,
|
|
633
|
+
"ZapierSdkApps",
|
|
634
|
+
undefined,
|
|
635
|
+
undefined,
|
|
636
|
+
[
|
|
637
|
+
this.factory.createPropertySignature(
|
|
638
|
+
undefined,
|
|
639
|
+
appKey,
|
|
640
|
+
undefined,
|
|
641
|
+
this.factory.createTypeReferenceNode(`${appName}AppWithFactory`),
|
|
642
|
+
),
|
|
643
|
+
],
|
|
644
|
+
),
|
|
645
|
+
]),
|
|
646
|
+
);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
private mapFieldTypeToTypeNode(field: ActionField): ts.TypeNode {
|
|
650
|
+
// Handle choices (enum-like fields)
|
|
651
|
+
if (field.choices && field.choices.length > 0) {
|
|
652
|
+
const choiceValues = field.choices
|
|
653
|
+
.filter(
|
|
654
|
+
(choice) =>
|
|
655
|
+
choice.value !== undefined &&
|
|
656
|
+
choice.value !== null &&
|
|
657
|
+
choice.value !== "",
|
|
658
|
+
)
|
|
659
|
+
.map((choice) =>
|
|
660
|
+
typeof choice.value === "string"
|
|
661
|
+
? this.factory.createLiteralTypeNode(
|
|
662
|
+
this.factory.createStringLiteral(choice.value),
|
|
663
|
+
)
|
|
664
|
+
: this.factory.createLiteralTypeNode(
|
|
665
|
+
this.factory.createNumericLiteral(String(choice.value)),
|
|
666
|
+
),
|
|
667
|
+
);
|
|
668
|
+
|
|
669
|
+
if (choiceValues.length > 0) {
|
|
670
|
+
return this.factory.createUnionTypeNode(choiceValues);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// Map Zapier field types to TypeScript types
|
|
675
|
+
switch (field.type?.toLowerCase()) {
|
|
676
|
+
case "string":
|
|
677
|
+
case "text":
|
|
678
|
+
case "email":
|
|
679
|
+
case "url":
|
|
680
|
+
case "password":
|
|
681
|
+
case "datetime":
|
|
682
|
+
case "date":
|
|
683
|
+
case "file":
|
|
684
|
+
return this.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
|
|
685
|
+
case "integer":
|
|
686
|
+
case "number":
|
|
687
|
+
return this.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword);
|
|
688
|
+
case "boolean":
|
|
689
|
+
return this.factory.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword);
|
|
690
|
+
case "array":
|
|
691
|
+
return this.factory.createArrayTypeNode(
|
|
692
|
+
this.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword),
|
|
693
|
+
);
|
|
694
|
+
case "object":
|
|
695
|
+
return this.factory.createTypeReferenceNode("Record", [
|
|
696
|
+
this.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
|
|
697
|
+
this.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword),
|
|
698
|
+
]);
|
|
699
|
+
default:
|
|
700
|
+
// Default to string | number | boolean for unknown types
|
|
701
|
+
return this.factory.createUnionTypeNode([
|
|
702
|
+
this.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
|
|
703
|
+
this.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword),
|
|
704
|
+
this.factory.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword),
|
|
705
|
+
]);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
private generateEmptyTypesFile(appKey: string, version?: string): string {
|
|
710
|
+
const appName = this.capitalize(appKey);
|
|
711
|
+
const versionComment = version
|
|
712
|
+
? ` * Generated for ${appKey}@${version}`
|
|
713
|
+
: ` * Generated for ${appKey}`;
|
|
714
|
+
|
|
715
|
+
return `/* eslint-disable @typescript-eslint/naming-convention, @typescript-eslint/no-explicit-any */
|
|
716
|
+
/**
|
|
717
|
+
* Auto-generated TypeScript types for Zapier ${appKey} actions
|
|
718
|
+
${versionComment}
|
|
719
|
+
* Generated on: ${new Date().toISOString()}
|
|
720
|
+
*
|
|
721
|
+
* No actions found for this app.
|
|
722
|
+
*/
|
|
723
|
+
|
|
724
|
+
import type { ActionExecutionOptions, ActionExecutionResult, ZapierFetchInitOptions } from '@zapier/zapier-sdk'
|
|
725
|
+
|
|
726
|
+
interface ${appName}AppProxy {
|
|
727
|
+
/** Make authenticated HTTP requests through Zapier's Relay service */
|
|
728
|
+
fetch: (url: string | URL, init?: ZapierFetchInitOptions) => Promise<Response>
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
interface ${appName}AppFactory {
|
|
732
|
+
(options: { authenticationId: number }): ${appName}AppProxy
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
type ${appName}AppWithFactory = ${appName}AppFactory & ${appName}AppProxy
|
|
736
|
+
|
|
737
|
+
declare module "@zapier/zapier-sdk" {
|
|
738
|
+
interface ZapierSdkApps {
|
|
739
|
+
${appKey}: ${appName}AppWithFactory
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
`;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
private capitalize(str: string): string {
|
|
746
|
+
return str.charAt(0).toUpperCase() + str.slice(1).replace(/[-_]/g, "");
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
private sanitizeActionName(actionKey: string): string {
|
|
750
|
+
// Ensure the action name is a valid TypeScript identifier
|
|
751
|
+
let sanitized = actionKey.replace(/[^a-zA-Z0-9_$]/g, "_");
|
|
752
|
+
|
|
753
|
+
// If it starts with a number, prepend an underscore
|
|
754
|
+
if (/^[0-9]/.test(sanitized)) {
|
|
755
|
+
sanitized = "_" + sanitized;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
return sanitized;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
private sanitizeFieldName(fieldKey: string): string {
|
|
762
|
+
// Ensure the field name is a valid TypeScript identifier
|
|
763
|
+
let sanitized = fieldKey.replace(/[^a-zA-Z0-9_$]/g, "_");
|
|
764
|
+
|
|
765
|
+
// If it starts with a number, prepend an underscore
|
|
766
|
+
if (/^[0-9]/.test(sanitized)) {
|
|
767
|
+
sanitized = "_" + sanitized;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
return sanitized;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
private escapeComment(comment: string): string {
|
|
774
|
+
// Escape comment text to prevent breaking the JSDoc comment
|
|
775
|
+
return comment.replace(/\*\//g, "*\\/").replace(/\r?\n/g, " ");
|
|
776
|
+
}
|
|
777
|
+
}
|