@zapier/zapier-sdk-cli 0.8.4 → 0.10.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 +32 -0
- package/README.md +35 -51
- package/dist/cli.cjs +950 -433
- package/dist/cli.mjs +951 -434
- package/dist/index.cjs +729 -336
- package/dist/index.mjs +730 -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 +120 -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-utils.d.ts +2 -1
- package/dist/src/utils/cli-generator-utils.js +11 -5
- package/dist/src/utils/cli-generator.js +65 -65
- package/dist/src/utils/parameter-resolver.d.ts +4 -1
- package/dist/src/utils/parameter-resolver.js +92 -15
- package/dist/src/utils/schema-formatter.d.ts +5 -1
- package/dist/src/utils/schema-formatter.js +48 -18
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
- 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-utils.ts +17 -5
- package/src/utils/cli-generator.ts +90 -79
- package/src/utils/parameter-resolver.ts +155 -21
- package/src/utils/schema-formatter.ts +68 -33
- 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
package/dist/package.json
CHANGED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { GetSdkType, ListActionsPluginProvides, ListInputFieldsPluginProvides, ManifestPluginProvides } from "@zapier/zapier-sdk";
|
|
2
|
+
interface GenerateTypesOptions {
|
|
3
|
+
appKey: string;
|
|
4
|
+
authenticationId?: number;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* AST-based TypeScript type generator using the TypeScript Compiler API
|
|
8
|
+
*/
|
|
9
|
+
export declare class AstTypeGenerator {
|
|
10
|
+
private readonly factory;
|
|
11
|
+
private readonly printer;
|
|
12
|
+
/**
|
|
13
|
+
* Generate TypeScript types using AST for a specific app
|
|
14
|
+
*/
|
|
15
|
+
generateTypes(options: GenerateTypesOptions & {
|
|
16
|
+
sdk: GetSdkType<ListActionsPluginProvides & ListInputFieldsPluginProvides & ManifestPluginProvides>;
|
|
17
|
+
}): Promise<string>;
|
|
18
|
+
private parseAppIdentifier;
|
|
19
|
+
private createSourceFile;
|
|
20
|
+
private createImportStatement;
|
|
21
|
+
private createTypeImportStatement;
|
|
22
|
+
private groupActionsByType;
|
|
23
|
+
private createInputInterface;
|
|
24
|
+
private createActionInterface;
|
|
25
|
+
private createAppProxyInterface;
|
|
26
|
+
private createFetchMethodProperty;
|
|
27
|
+
private createAppFactoryInterface;
|
|
28
|
+
private createAppWithFactoryType;
|
|
29
|
+
private createModuleAugmentation;
|
|
30
|
+
private mapFieldTypeToTypeNode;
|
|
31
|
+
private generateEmptyTypesFile;
|
|
32
|
+
private capitalize;
|
|
33
|
+
private sanitizeActionName;
|
|
34
|
+
private sanitizeFieldName;
|
|
35
|
+
private escapeComment;
|
|
36
|
+
}
|
|
37
|
+
export {};
|
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
import * as ts from "typescript";
|
|
2
|
+
/**
|
|
3
|
+
* AST-based TypeScript type generator using the TypeScript Compiler API
|
|
4
|
+
*/
|
|
5
|
+
export class AstTypeGenerator {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.factory = ts.factory;
|
|
8
|
+
this.printer = ts.createPrinter({
|
|
9
|
+
newLine: ts.NewLineKind.LineFeed,
|
|
10
|
+
removeComments: false,
|
|
11
|
+
omitTrailingSemicolon: false,
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Generate TypeScript types using AST for a specific app
|
|
16
|
+
*/
|
|
17
|
+
async generateTypes(options) {
|
|
18
|
+
const { appKey, authenticationId, sdk } = options;
|
|
19
|
+
// Parse app identifier (support app@version format)
|
|
20
|
+
const { app, version } = this.parseAppIdentifier(appKey);
|
|
21
|
+
// Fetch all actions for the app
|
|
22
|
+
const actionsResult = await sdk.listActions({
|
|
23
|
+
appKey: app,
|
|
24
|
+
});
|
|
25
|
+
const actions = actionsResult.data;
|
|
26
|
+
if (actions.length === 0) {
|
|
27
|
+
return this.generateEmptyTypesFile(app, version);
|
|
28
|
+
}
|
|
29
|
+
// Fetch input fields for each action
|
|
30
|
+
const actionsWithFields = [];
|
|
31
|
+
if (authenticationId) {
|
|
32
|
+
for (const action of actions) {
|
|
33
|
+
try {
|
|
34
|
+
const fieldsResult = await sdk.listInputFields({
|
|
35
|
+
appKey: appKey,
|
|
36
|
+
actionKey: action.key,
|
|
37
|
+
actionType: action.action_type,
|
|
38
|
+
authenticationId: authenticationId,
|
|
39
|
+
});
|
|
40
|
+
const fields = fieldsResult.data.map((field) => {
|
|
41
|
+
const fieldObj = field;
|
|
42
|
+
return {
|
|
43
|
+
...fieldObj,
|
|
44
|
+
required: fieldObj.is_required || fieldObj.required || false,
|
|
45
|
+
};
|
|
46
|
+
});
|
|
47
|
+
actionsWithFields.push({
|
|
48
|
+
...action,
|
|
49
|
+
inputFields: fields,
|
|
50
|
+
name: action.title || action.key,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// If we can't get fields for an action, include it without fields
|
|
55
|
+
actionsWithFields.push({
|
|
56
|
+
...action,
|
|
57
|
+
inputFields: [],
|
|
58
|
+
name: action.title || action.key,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
// Convert actions to have empty input fields (will generate generic types)
|
|
65
|
+
actions.forEach((action) => {
|
|
66
|
+
actionsWithFields.push({
|
|
67
|
+
...action,
|
|
68
|
+
inputFields: [],
|
|
69
|
+
name: action.title || action.key,
|
|
70
|
+
app_key: action.app_key || appKey,
|
|
71
|
+
action_type: action.action_type || "write",
|
|
72
|
+
title: action.title || action.key,
|
|
73
|
+
type: "action",
|
|
74
|
+
description: action.description || "",
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
// Generate TypeScript AST nodes
|
|
79
|
+
const sourceFile = this.createSourceFile(app, actionsWithFields, version);
|
|
80
|
+
// Print the AST to string
|
|
81
|
+
return this.printer.printFile(sourceFile);
|
|
82
|
+
}
|
|
83
|
+
parseAppIdentifier(identifier) {
|
|
84
|
+
const parts = identifier.split("@");
|
|
85
|
+
return {
|
|
86
|
+
app: parts[0],
|
|
87
|
+
version: parts[1],
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
createSourceFile(appKey, actions, version) {
|
|
91
|
+
const appName = this.capitalize(appKey);
|
|
92
|
+
const versionComment = version
|
|
93
|
+
? ` * Generated for ${appKey}@${version}`
|
|
94
|
+
: ` * Generated for ${appKey}`;
|
|
95
|
+
// Create header comment
|
|
96
|
+
const headerComment = `Auto-generated TypeScript types for Zapier ${appKey} actions
|
|
97
|
+
${versionComment.slice(3)}
|
|
98
|
+
Generated on: ${new Date().toISOString()}
|
|
99
|
+
|
|
100
|
+
This file automatically augments the base SDK types when present.
|
|
101
|
+
No manual imports or type casting required.
|
|
102
|
+
|
|
103
|
+
Usage:
|
|
104
|
+
import { createZapierSdk } from "@zapier/zapier-sdk";
|
|
105
|
+
|
|
106
|
+
const zapier = createZapierSdk();
|
|
107
|
+
// Types are automatically available:
|
|
108
|
+
await zapier.apps.${appKey}.search.user_by_email({ authenticationId: 123, inputs: { email } })
|
|
109
|
+
|
|
110
|
+
// Factory usage (pinned auth):
|
|
111
|
+
const my${appName} = zapier.apps.${appKey}({ authenticationId: 123 })
|
|
112
|
+
await my${appName}.search.user_by_email({ inputs: { email } })`;
|
|
113
|
+
const statements = [
|
|
114
|
+
// Import the SDK to activate module augmentation
|
|
115
|
+
this.createImportStatement(["@zapier/zapier-sdk"]),
|
|
116
|
+
// Import types we'll use
|
|
117
|
+
this.createTypeImportStatement([
|
|
118
|
+
"ActionExecutionOptions",
|
|
119
|
+
"ActionExecutionResult",
|
|
120
|
+
"ZapierFetchInitOptions",
|
|
121
|
+
], "@zapier/zapier-sdk"),
|
|
122
|
+
];
|
|
123
|
+
// Group actions by type
|
|
124
|
+
const actionsByType = this.groupActionsByType(actions);
|
|
125
|
+
// Generate input interfaces for each action
|
|
126
|
+
actions.forEach((action) => {
|
|
127
|
+
if (action.inputFields.length > 0) {
|
|
128
|
+
const inputInterface = this.createInputInterface(appName, action);
|
|
129
|
+
statements.push(inputInterface);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
// Generate action type interfaces
|
|
133
|
+
Object.entries(actionsByType).forEach(([actionType, typeActions]) => {
|
|
134
|
+
const actionInterface = this.createActionInterface(appName, actionType, typeActions);
|
|
135
|
+
statements.push(actionInterface);
|
|
136
|
+
});
|
|
137
|
+
// Generate app proxy interface
|
|
138
|
+
const appProxyInterface = this.createAppProxyInterface(appName, actionsByType);
|
|
139
|
+
statements.push(appProxyInterface);
|
|
140
|
+
// Generate app factory interface
|
|
141
|
+
const appFactoryInterface = this.createAppFactoryInterface(appName);
|
|
142
|
+
statements.push(appFactoryInterface);
|
|
143
|
+
// Generate combined app type
|
|
144
|
+
const appWithFactoryType = this.createAppWithFactoryType(appName);
|
|
145
|
+
statements.push(appWithFactoryType);
|
|
146
|
+
// Generate module augmentation for automatic type integration
|
|
147
|
+
const moduleAugmentation = this.createModuleAugmentation(appKey, appName);
|
|
148
|
+
statements.push(moduleAugmentation);
|
|
149
|
+
// Add empty export to make this a module
|
|
150
|
+
statements.push(this.factory.createExportDeclaration(undefined, false, this.factory.createNamedExports([])));
|
|
151
|
+
// Create source file
|
|
152
|
+
const sourceFile = ts.createSourceFile("generated.d.ts", "", ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
|
|
153
|
+
// Add the header comment and ESLint disable as leading trivia to the first statement
|
|
154
|
+
if (statements.length > 0) {
|
|
155
|
+
// Add ESLint disable comment
|
|
156
|
+
ts.addSyntheticLeadingComment(statements[0], ts.SyntaxKind.MultiLineCommentTrivia, " eslint-disable @typescript-eslint/naming-convention, @typescript-eslint/no-explicit-any ", true);
|
|
157
|
+
// Add header comment
|
|
158
|
+
ts.addSyntheticLeadingComment(statements[0], ts.SyntaxKind.MultiLineCommentTrivia, headerComment, true);
|
|
159
|
+
}
|
|
160
|
+
return this.factory.updateSourceFile(sourceFile, statements);
|
|
161
|
+
}
|
|
162
|
+
createImportStatement(imports, from) {
|
|
163
|
+
// Handle side-effect only import (no imports, just the module path)
|
|
164
|
+
if (imports.length === 1 && !from && imports[0].startsWith("@")) {
|
|
165
|
+
return this.factory.createImportDeclaration(undefined, undefined, this.factory.createStringLiteral(imports[0]), undefined);
|
|
166
|
+
}
|
|
167
|
+
const fromModule = from || imports[0];
|
|
168
|
+
const importNames = from ? imports : [];
|
|
169
|
+
return this.factory.createImportDeclaration(undefined, importNames.length > 0
|
|
170
|
+
? this.factory.createImportClause(false, undefined, this.factory.createNamedImports(importNames.map((name) => this.factory.createImportSpecifier(false, undefined, this.factory.createIdentifier(name)))))
|
|
171
|
+
: undefined, this.factory.createStringLiteral(fromModule), undefined);
|
|
172
|
+
}
|
|
173
|
+
createTypeImportStatement(imports, from) {
|
|
174
|
+
return this.factory.createImportDeclaration(undefined, this.factory.createImportClause(true, // typeOnly: true
|
|
175
|
+
undefined, this.factory.createNamedImports(imports.map((name) => this.factory.createImportSpecifier(false, undefined, this.factory.createIdentifier(name))))), this.factory.createStringLiteral(from), undefined);
|
|
176
|
+
}
|
|
177
|
+
groupActionsByType(actions) {
|
|
178
|
+
return actions.reduce((acc, action) => {
|
|
179
|
+
if (!acc[action.action_type]) {
|
|
180
|
+
acc[action.action_type] = [];
|
|
181
|
+
}
|
|
182
|
+
acc[action.action_type].push(action);
|
|
183
|
+
return acc;
|
|
184
|
+
}, {});
|
|
185
|
+
}
|
|
186
|
+
createInputInterface(appName, action) {
|
|
187
|
+
const inputTypeName = `${appName}${this.capitalize(action.action_type)}${this.capitalize(this.sanitizeActionName(action.key))}Inputs`;
|
|
188
|
+
const properties = action.inputFields.map((field) => {
|
|
189
|
+
const fieldType = this.mapFieldTypeToTypeNode(field);
|
|
190
|
+
const isOptional = !field.required;
|
|
191
|
+
let property = this.factory.createPropertySignature(undefined, this.sanitizeFieldName(field.key), isOptional
|
|
192
|
+
? this.factory.createToken(ts.SyntaxKind.QuestionToken)
|
|
193
|
+
: undefined, fieldType);
|
|
194
|
+
// Add JSDoc comment if helpText exists
|
|
195
|
+
if (field.helpText) {
|
|
196
|
+
property = ts.addSyntheticLeadingComment(property, ts.SyntaxKind.MultiLineCommentTrivia, `* ${this.escapeComment(field.helpText)} `, true);
|
|
197
|
+
}
|
|
198
|
+
return property;
|
|
199
|
+
});
|
|
200
|
+
return this.factory.createInterfaceDeclaration(undefined, inputTypeName, undefined, undefined, properties);
|
|
201
|
+
}
|
|
202
|
+
createActionInterface(appName, actionType, typeActions) {
|
|
203
|
+
const typeName = `${appName}${this.capitalize(actionType)}Actions`;
|
|
204
|
+
const methods = typeActions.map((action) => {
|
|
205
|
+
const actionName = this.sanitizeActionName(action.key);
|
|
206
|
+
let methodSignature;
|
|
207
|
+
if (action.inputFields.length > 0) {
|
|
208
|
+
// Generate type-safe action method signature
|
|
209
|
+
const inputTypeName = `${appName}${this.capitalize(action.action_type)}${this.capitalize(this.sanitizeActionName(action.key))}Inputs`;
|
|
210
|
+
const inputsType = this.factory.createTypeLiteralNode([
|
|
211
|
+
this.factory.createPropertySignature(undefined, "inputs", undefined, this.factory.createTypeReferenceNode(inputTypeName)),
|
|
212
|
+
]);
|
|
213
|
+
const omitType = this.factory.createTypeReferenceNode("Omit", [
|
|
214
|
+
this.factory.createTypeReferenceNode("ActionExecutionOptions"),
|
|
215
|
+
this.factory.createLiteralTypeNode(this.factory.createStringLiteral("inputs")),
|
|
216
|
+
]);
|
|
217
|
+
const optionsType = this.factory.createIntersectionTypeNode([
|
|
218
|
+
inputsType,
|
|
219
|
+
omitType,
|
|
220
|
+
]);
|
|
221
|
+
methodSignature = this.factory.createMethodSignature(undefined, actionName, undefined, undefined, [
|
|
222
|
+
this.factory.createParameterDeclaration(undefined, undefined, "options", undefined, optionsType),
|
|
223
|
+
], this.factory.createTypeReferenceNode("Promise", [
|
|
224
|
+
this.factory.createTypeReferenceNode("ActionExecutionResult"),
|
|
225
|
+
]));
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
// No specific input fields available - use generic Record<string, any> for inputs
|
|
229
|
+
const genericInputsType = this.factory.createTypeLiteralNode([
|
|
230
|
+
this.factory.createPropertySignature(undefined, "inputs", this.factory.createToken(ts.SyntaxKind.QuestionToken), this.factory.createTypeReferenceNode("Record", [
|
|
231
|
+
this.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
|
|
232
|
+
this.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword),
|
|
233
|
+
])),
|
|
234
|
+
]);
|
|
235
|
+
const intersectionType = this.factory.createIntersectionTypeNode([
|
|
236
|
+
genericInputsType,
|
|
237
|
+
this.factory.createTypeReferenceNode("ActionExecutionOptions"),
|
|
238
|
+
]);
|
|
239
|
+
methodSignature = this.factory.createMethodSignature(undefined, actionName, undefined, undefined, [
|
|
240
|
+
this.factory.createParameterDeclaration(undefined, undefined, "options", this.factory.createToken(ts.SyntaxKind.QuestionToken), intersectionType),
|
|
241
|
+
], this.factory.createTypeReferenceNode("Promise", [
|
|
242
|
+
this.factory.createTypeReferenceNode("ActionExecutionResult"),
|
|
243
|
+
]));
|
|
244
|
+
}
|
|
245
|
+
// Add JSDoc comment if description exists
|
|
246
|
+
if (action.description) {
|
|
247
|
+
methodSignature = ts.addSyntheticLeadingComment(methodSignature, ts.SyntaxKind.MultiLineCommentTrivia, `* ${this.escapeComment(action.description)} `, true);
|
|
248
|
+
}
|
|
249
|
+
return methodSignature;
|
|
250
|
+
});
|
|
251
|
+
return this.factory.createInterfaceDeclaration(undefined, typeName, undefined, undefined, methods);
|
|
252
|
+
}
|
|
253
|
+
createAppProxyInterface(appName, actionsByType) {
|
|
254
|
+
const properties = [
|
|
255
|
+
...Object.keys(actionsByType).map((actionType) => this.factory.createPropertySignature(undefined, actionType, undefined, this.factory.createTypeReferenceNode(`${appName}${this.capitalize(actionType)}Actions`))),
|
|
256
|
+
// Always include fetch method for authenticated HTTP requests
|
|
257
|
+
this.createFetchMethodProperty(),
|
|
258
|
+
];
|
|
259
|
+
return this.factory.createInterfaceDeclaration(undefined, `${appName}AppProxy`, undefined, undefined, properties);
|
|
260
|
+
}
|
|
261
|
+
createFetchMethodProperty() {
|
|
262
|
+
let property = this.factory.createPropertySignature(undefined, "fetch", undefined, this.factory.createFunctionTypeNode(undefined, [
|
|
263
|
+
this.factory.createParameterDeclaration(undefined, undefined, "url", undefined, this.factory.createUnionTypeNode([
|
|
264
|
+
this.factory.createTypeReferenceNode("string"),
|
|
265
|
+
this.factory.createTypeReferenceNode("URL"),
|
|
266
|
+
])),
|
|
267
|
+
this.factory.createParameterDeclaration(undefined, undefined, "init", this.factory.createToken(ts.SyntaxKind.QuestionToken), this.factory.createTypeReferenceNode("ZapierFetchInitOptions")),
|
|
268
|
+
], this.factory.createTypeReferenceNode("Promise", [
|
|
269
|
+
this.factory.createTypeReferenceNode("Response"),
|
|
270
|
+
])));
|
|
271
|
+
// Add JSDoc comment
|
|
272
|
+
property = ts.addSyntheticLeadingComment(property, ts.SyntaxKind.MultiLineCommentTrivia, "* Make authenticated HTTP requests through Zapier's Relay service ", true);
|
|
273
|
+
return property;
|
|
274
|
+
}
|
|
275
|
+
createAppFactoryInterface(appName) {
|
|
276
|
+
const callSignature = this.factory.createCallSignature(undefined, [
|
|
277
|
+
this.factory.createParameterDeclaration(undefined, undefined, "options", undefined, this.factory.createTypeLiteralNode([
|
|
278
|
+
this.factory.createPropertySignature(undefined, "authenticationId", undefined, this.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword)),
|
|
279
|
+
])),
|
|
280
|
+
], this.factory.createTypeReferenceNode(`${appName}AppProxy`));
|
|
281
|
+
return this.factory.createInterfaceDeclaration(undefined, `${appName}AppFactory`, undefined, undefined, [callSignature]);
|
|
282
|
+
}
|
|
283
|
+
createAppWithFactoryType(appName) {
|
|
284
|
+
return this.factory.createTypeAliasDeclaration(undefined, `${appName}AppWithFactory`, undefined, this.factory.createIntersectionTypeNode([
|
|
285
|
+
this.factory.createTypeReferenceNode(`${appName}AppFactory`),
|
|
286
|
+
this.factory.createTypeReferenceNode(`${appName}AppProxy`),
|
|
287
|
+
]));
|
|
288
|
+
}
|
|
289
|
+
createModuleAugmentation(appKey, appName) {
|
|
290
|
+
// Create: declare module "@zapier/zapier-sdk" { interface ZapierSdkApps { [appKey]: AppWithFactory } }
|
|
291
|
+
// This creates a new interface that we can merge with ZapierSdk
|
|
292
|
+
return this.factory.createModuleDeclaration([this.factory.createToken(ts.SyntaxKind.DeclareKeyword)], this.factory.createStringLiteral("@zapier/zapier-sdk"), this.factory.createModuleBlock([
|
|
293
|
+
this.factory.createInterfaceDeclaration(undefined, "ZapierSdkApps", undefined, undefined, [
|
|
294
|
+
this.factory.createPropertySignature(undefined, appKey, undefined, this.factory.createTypeReferenceNode(`${appName}AppWithFactory`)),
|
|
295
|
+
]),
|
|
296
|
+
]));
|
|
297
|
+
}
|
|
298
|
+
mapFieldTypeToTypeNode(field) {
|
|
299
|
+
// Handle choices (enum-like fields)
|
|
300
|
+
if (field.choices && field.choices.length > 0) {
|
|
301
|
+
const choiceValues = field.choices
|
|
302
|
+
.filter((choice) => choice.value !== undefined &&
|
|
303
|
+
choice.value !== null &&
|
|
304
|
+
choice.value !== "")
|
|
305
|
+
.map((choice) => typeof choice.value === "string"
|
|
306
|
+
? this.factory.createLiteralTypeNode(this.factory.createStringLiteral(choice.value))
|
|
307
|
+
: this.factory.createLiteralTypeNode(this.factory.createNumericLiteral(String(choice.value))));
|
|
308
|
+
if (choiceValues.length > 0) {
|
|
309
|
+
return this.factory.createUnionTypeNode(choiceValues);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
// Map Zapier field types to TypeScript types
|
|
313
|
+
switch (field.type?.toLowerCase()) {
|
|
314
|
+
case "string":
|
|
315
|
+
case "text":
|
|
316
|
+
case "email":
|
|
317
|
+
case "url":
|
|
318
|
+
case "password":
|
|
319
|
+
case "datetime":
|
|
320
|
+
case "date":
|
|
321
|
+
case "file":
|
|
322
|
+
return this.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
|
|
323
|
+
case "integer":
|
|
324
|
+
case "number":
|
|
325
|
+
return this.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword);
|
|
326
|
+
case "boolean":
|
|
327
|
+
return this.factory.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword);
|
|
328
|
+
case "array":
|
|
329
|
+
return this.factory.createArrayTypeNode(this.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword));
|
|
330
|
+
case "object":
|
|
331
|
+
return this.factory.createTypeReferenceNode("Record", [
|
|
332
|
+
this.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
|
|
333
|
+
this.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword),
|
|
334
|
+
]);
|
|
335
|
+
default:
|
|
336
|
+
// Default to string | number | boolean for unknown types
|
|
337
|
+
return this.factory.createUnionTypeNode([
|
|
338
|
+
this.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
|
|
339
|
+
this.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword),
|
|
340
|
+
this.factory.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword),
|
|
341
|
+
]);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
generateEmptyTypesFile(appKey, version) {
|
|
345
|
+
const appName = this.capitalize(appKey);
|
|
346
|
+
const versionComment = version
|
|
347
|
+
? ` * Generated for ${appKey}@${version}`
|
|
348
|
+
: ` * Generated for ${appKey}`;
|
|
349
|
+
return `/* eslint-disable @typescript-eslint/naming-convention, @typescript-eslint/no-explicit-any */
|
|
350
|
+
/**
|
|
351
|
+
* Auto-generated TypeScript types for Zapier ${appKey} actions
|
|
352
|
+
${versionComment}
|
|
353
|
+
* Generated on: ${new Date().toISOString()}
|
|
354
|
+
*
|
|
355
|
+
* No actions found for this app.
|
|
356
|
+
*/
|
|
357
|
+
|
|
358
|
+
import type { ActionExecutionOptions, ActionExecutionResult, ZapierFetchInitOptions } from '@zapier/zapier-sdk'
|
|
359
|
+
|
|
360
|
+
interface ${appName}AppProxy {
|
|
361
|
+
/** Make authenticated HTTP requests through Zapier's Relay service */
|
|
362
|
+
fetch: (url: string | URL, init?: ZapierFetchInitOptions) => Promise<Response>
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
interface ${appName}AppFactory {
|
|
366
|
+
(options: { authenticationId: number }): ${appName}AppProxy
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
type ${appName}AppWithFactory = ${appName}AppFactory & ${appName}AppProxy
|
|
370
|
+
|
|
371
|
+
declare module "@zapier/zapier-sdk" {
|
|
372
|
+
interface ZapierSdkApps {
|
|
373
|
+
${appKey}: ${appName}AppWithFactory
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
`;
|
|
377
|
+
}
|
|
378
|
+
capitalize(str) {
|
|
379
|
+
return str.charAt(0).toUpperCase() + str.slice(1).replace(/[-_]/g, "");
|
|
380
|
+
}
|
|
381
|
+
sanitizeActionName(actionKey) {
|
|
382
|
+
// Ensure the action name is a valid TypeScript identifier
|
|
383
|
+
let sanitized = actionKey.replace(/[^a-zA-Z0-9_$]/g, "_");
|
|
384
|
+
// If it starts with a number, prepend an underscore
|
|
385
|
+
if (/^[0-9]/.test(sanitized)) {
|
|
386
|
+
sanitized = "_" + sanitized;
|
|
387
|
+
}
|
|
388
|
+
return sanitized;
|
|
389
|
+
}
|
|
390
|
+
sanitizeFieldName(fieldKey) {
|
|
391
|
+
// Ensure the field name is a valid TypeScript identifier
|
|
392
|
+
let sanitized = fieldKey.replace(/[^a-zA-Z0-9_$]/g, "_");
|
|
393
|
+
// If it starts with a number, prepend an underscore
|
|
394
|
+
if (/^[0-9]/.test(sanitized)) {
|
|
395
|
+
sanitized = "_" + sanitized;
|
|
396
|
+
}
|
|
397
|
+
return sanitized;
|
|
398
|
+
}
|
|
399
|
+
escapeComment(comment) {
|
|
400
|
+
// Escape comment text to prevent breaking the JSDoc comment
|
|
401
|
+
return comment.replace(/\*\//g, "*\\/").replace(/\r?\n/g, " ");
|
|
402
|
+
}
|
|
403
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Plugin, GetSdkType, GetContextType, ListActionsPluginProvides, ListAppsPluginProvides, ListInputFieldsPluginProvides, ListAuthenticationsPluginProvides, ManifestPluginProvides } from "@zapier/zapier-sdk";
|
|
2
|
+
import { AddSchema, type AddOptions } from "./schemas";
|
|
3
|
+
export interface AddPluginProvides {
|
|
4
|
+
add: (options: AddOptions) => Promise<void>;
|
|
5
|
+
context: {
|
|
6
|
+
meta: {
|
|
7
|
+
add: {
|
|
8
|
+
inputSchema: typeof AddSchema;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export declare const addPlugin: Plugin<GetSdkType<ListAppsPluginProvides & ListActionsPluginProvides & ListInputFieldsPluginProvides & ListAuthenticationsPluginProvides & ManifestPluginProvides>, GetContextType<ManifestPluginProvides>, AddPluginProvides>;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { createFunction } from "@zapier/zapier-sdk";
|
|
2
|
+
import { AddSchema } from "./schemas";
|
|
3
|
+
import { AstTypeGenerator } from "./ast-generator";
|
|
4
|
+
import { mkdir, access, writeFile } from "fs/promises";
|
|
5
|
+
import { resolve, join } from "path";
|
|
6
|
+
/**
|
|
7
|
+
* Detect the best default directory for TypeScript types
|
|
8
|
+
* Looks for src or lib directories, falls back to current directory
|
|
9
|
+
*/
|
|
10
|
+
async function detectTypesOutputDirectory() {
|
|
11
|
+
// Check for common source directories in priority order
|
|
12
|
+
const candidates = ["src", "lib"];
|
|
13
|
+
for (const candidate of candidates) {
|
|
14
|
+
try {
|
|
15
|
+
await access(candidate);
|
|
16
|
+
return join(candidate, "zapier", "apps");
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
// Directory doesn't exist, continue to next candidate
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
// Fall back to current directory
|
|
23
|
+
return "./zapier/apps/";
|
|
24
|
+
}
|
|
25
|
+
export const addPlugin = ({ sdk, context }) => {
|
|
26
|
+
const add = createFunction(async function add(options) {
|
|
27
|
+
const { appKeys, authenticationIds, configPath, typesOutput = await detectTypesOutputDirectory(), } = options;
|
|
28
|
+
const resolvedTypesOutput = resolve(typesOutput);
|
|
29
|
+
// Ensure types output directory exists
|
|
30
|
+
await mkdir(resolvedTypesOutput, { recursive: true });
|
|
31
|
+
// Get apps using listApps (which respects existing manifest for version locking)
|
|
32
|
+
console.log(`📦 Looking up ${appKeys.length} app(s)...`);
|
|
33
|
+
const appsIterator = sdk.listApps({ appKeys }).items();
|
|
34
|
+
const apps = [];
|
|
35
|
+
for await (const app of appsIterator) {
|
|
36
|
+
apps.push(app);
|
|
37
|
+
}
|
|
38
|
+
if (apps.length === 0) {
|
|
39
|
+
console.warn("⚠️ No apps found");
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
// Fetch authentications if provided
|
|
43
|
+
let authentications = [];
|
|
44
|
+
if (authenticationIds && authenticationIds.length > 0) {
|
|
45
|
+
console.log(`🔐 Looking up ${authenticationIds.length} authentication(s)...`);
|
|
46
|
+
const authsIterator = sdk
|
|
47
|
+
.listAuthentications({ authenticationIds })
|
|
48
|
+
.items();
|
|
49
|
+
for await (const auth of authsIterator) {
|
|
50
|
+
authentications.push(auth);
|
|
51
|
+
}
|
|
52
|
+
console.log(`🔐 Found ${authentications.length} authentication(s)`);
|
|
53
|
+
}
|
|
54
|
+
// Process each app
|
|
55
|
+
for (const app of apps) {
|
|
56
|
+
const appSlugAndKey = app.slug ? `${app.slug} (${app.key})` : app.key;
|
|
57
|
+
console.log(`📦 Adding ${appSlugAndKey}...`);
|
|
58
|
+
try {
|
|
59
|
+
if (!app.version) {
|
|
60
|
+
console.warn(`⚠️ Invalid implementation ID format for '${appSlugAndKey}': ${app.implementation_id}. Expected format: <implementationName>@<version>. Skipping...`);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
// Update manifest using manifest plugin's function
|
|
64
|
+
const [manifestKey] = await context.updateManifestEntry(app.key, {
|
|
65
|
+
implementationName: app.key,
|
|
66
|
+
version: app.version,
|
|
67
|
+
}, configPath);
|
|
68
|
+
console.log(`📝 Locked ${appSlugAndKey} to ${app.key}@${app.version} using key '${manifestKey}'`);
|
|
69
|
+
// Find matching authentication for this app if authentications were provided
|
|
70
|
+
let authenticationId;
|
|
71
|
+
if (authentications.length > 0) {
|
|
72
|
+
// Match authentication by app_key
|
|
73
|
+
const matchingAuth = authentications.find((auth) => {
|
|
74
|
+
return auth.app_key === app.key;
|
|
75
|
+
});
|
|
76
|
+
if (matchingAuth) {
|
|
77
|
+
authenticationId = matchingAuth.id;
|
|
78
|
+
console.log(`🔐 Using authentication ${authenticationId} (${matchingAuth.title}) for ${appSlugAndKey}`);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
console.warn(`⚠️ No matching authentication found for ${appSlugAndKey}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// Generate types using the manifest key for consistency
|
|
85
|
+
const typesPath = join(resolvedTypesOutput, `${manifestKey}.d.ts`);
|
|
86
|
+
try {
|
|
87
|
+
// Use AST-based generator directly
|
|
88
|
+
const generator = new AstTypeGenerator();
|
|
89
|
+
const typeDefinitions = await generator.generateTypes({
|
|
90
|
+
appKey: manifestKey,
|
|
91
|
+
authenticationId,
|
|
92
|
+
sdk,
|
|
93
|
+
});
|
|
94
|
+
// Write to file
|
|
95
|
+
await writeFile(typesPath, typeDefinitions, "utf8");
|
|
96
|
+
console.log(`🔧 Generated types for ${manifestKey} at ${typesPath}`);
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
console.warn(`⚠️ Failed to generate types for ${appSlugAndKey}: ${error}`);
|
|
100
|
+
// Continue even if type generation fails
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
console.warn(`⚠️ Failed to process ${appSlugAndKey}: ${error}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
console.log(`✅ Added ${apps.length} app(s) to manifest`);
|
|
108
|
+
}, AddSchema);
|
|
109
|
+
return {
|
|
110
|
+
add,
|
|
111
|
+
context: {
|
|
112
|
+
meta: {
|
|
113
|
+
add: {
|
|
114
|
+
categories: ["utility"],
|
|
115
|
+
inputSchema: AddSchema,
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const AddSchema: z.ZodObject<{
|
|
3
|
+
appKeys: z.ZodArray<z.ZodString, "many">;
|
|
4
|
+
authenticationIds: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
5
|
+
configPath: z.ZodOptional<z.ZodString>;
|
|
6
|
+
typesOutput: z.ZodOptional<z.ZodString>;
|
|
7
|
+
}, "strip", z.ZodTypeAny, {
|
|
8
|
+
appKeys: string[];
|
|
9
|
+
authenticationIds?: string[] | undefined;
|
|
10
|
+
configPath?: string | undefined;
|
|
11
|
+
typesOutput?: string | undefined;
|
|
12
|
+
}, {
|
|
13
|
+
appKeys: string[];
|
|
14
|
+
authenticationIds?: string[] | undefined;
|
|
15
|
+
configPath?: string | undefined;
|
|
16
|
+
typesOutput?: string | undefined;
|
|
17
|
+
}>;
|
|
18
|
+
export type AddOptions = z.infer<typeof AddSchema>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { DEFAULT_CONFIG_PATH } from "@zapier/zapier-sdk";
|
|
3
|
+
export const AddSchema = z.object({
|
|
4
|
+
appKeys: z
|
|
5
|
+
.array(z.string().min(1, "App key cannot be empty"))
|
|
6
|
+
.min(1, "At least one app key is required"),
|
|
7
|
+
authenticationIds: z
|
|
8
|
+
.array(z.string())
|
|
9
|
+
.optional()
|
|
10
|
+
.describe("Authentication IDs to use for type generation"),
|
|
11
|
+
configPath: z
|
|
12
|
+
.string()
|
|
13
|
+
.optional()
|
|
14
|
+
.describe(`Path to Zapier config file (defaults to '${DEFAULT_CONFIG_PATH}')`),
|
|
15
|
+
typesOutput: z
|
|
16
|
+
.string()
|
|
17
|
+
.optional()
|
|
18
|
+
.describe("Directory for TypeScript type files (defaults to (src|lib|.)/zapier/apps/)"),
|
|
19
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Plugin } from "@zapier/zapier-sdk";
|
|
2
|
+
import { GetLoginConfigPathSchema, type GetLoginConfigPathOptions } from "./schemas";
|
|
3
|
+
export interface GetLoginConfigPathPluginProvides {
|
|
4
|
+
getLoginConfigPath: (options?: GetLoginConfigPathOptions) => Promise<string>;
|
|
5
|
+
context: {
|
|
6
|
+
meta: {
|
|
7
|
+
getLoginConfigPath: {
|
|
8
|
+
inputSchema: typeof GetLoginConfigPathSchema;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export declare const getLoginConfigPathPlugin: Plugin<{}, // requires no existing SDK methods
|
|
14
|
+
{}, // requires no context
|
|
15
|
+
GetLoginConfigPathPluginProvides>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { GetLoginConfigPathSchema, } from "./schemas";
|
|
2
|
+
import { createFunction } from "@zapier/zapier-sdk";
|
|
3
|
+
import { getConfigPath } from "@zapier/zapier-sdk-cli-login";
|
|
4
|
+
export const getLoginConfigPathPlugin = () => {
|
|
5
|
+
const getLoginConfigPathWithSdk = createFunction(async function getLoginConfigPathWithSdk(_options) {
|
|
6
|
+
return getConfigPath();
|
|
7
|
+
}, GetLoginConfigPathSchema);
|
|
8
|
+
return {
|
|
9
|
+
getLoginConfigPath: getLoginConfigPathWithSdk,
|
|
10
|
+
context: {
|
|
11
|
+
meta: {
|
|
12
|
+
getLoginConfigPath: {
|
|
13
|
+
categories: ["utility"],
|
|
14
|
+
inputSchema: GetLoginConfigPathSchema,
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
};
|