@bpmn-sdk/connector-gen 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build-template.d.ts +4 -0
- package/dist/build-template.js +513 -0
- package/dist/catalog.d.ts +13 -0
- package/dist/catalog.js +166 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.js +54 -0
- package/dist/parse-openapi.d.ts +12 -0
- package/dist/parse-openapi.js +208 -0
- package/dist/types.d.ts +245 -0
- package/dist/types.js +4 -0
- package/dist/write-templates.d.ts +3 -0
- package/dist/write-templates.js +27 -0
- package/package.json +31 -0
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ConnectorTemplate, GeneratorOptions, OperationWithMeta } from "./types.js";
|
|
2
|
+
export declare function buildTemplate(op: OperationWithMeta, opts: GeneratorOptions): ConnectorTemplate;
|
|
3
|
+
export declare function buildTemplates(ops: OperationWithMeta[], opts: GeneratorOptions): ConnectorTemplate[];
|
|
4
|
+
//# sourceMappingURL=build-template.d.ts.map
|
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
import { CONNECTOR_SCHEMA as SCHEMA_URL } from "./types.js";
|
|
2
|
+
// ─── Slugify / ID helpers ──────────────────────────────────────────────────────
|
|
3
|
+
function slugify(s) {
|
|
4
|
+
return s
|
|
5
|
+
.replace(/[^a-zA-Z0-9]+/g, "-")
|
|
6
|
+
.replace(/^-+|-+$/g, "")
|
|
7
|
+
.toLowerCase();
|
|
8
|
+
}
|
|
9
|
+
function operationName(op) {
|
|
10
|
+
if (op.operation.operationId)
|
|
11
|
+
return op.operation.operationId;
|
|
12
|
+
// Fallback: METHOD_path_segments
|
|
13
|
+
const segs = op.path
|
|
14
|
+
.split("/")
|
|
15
|
+
.filter(Boolean)
|
|
16
|
+
.map((s) => s.replace(/[{}]/g, ""));
|
|
17
|
+
return `${op.method}_${segs.join("_")}`;
|
|
18
|
+
}
|
|
19
|
+
function toDisplayName(id) {
|
|
20
|
+
// "listRepoIssues" → "List Repo Issues"; "list-repo-issues" → "List Repo Issues"
|
|
21
|
+
return id
|
|
22
|
+
.replace(/([a-z])([A-Z])/g, "$1 $2")
|
|
23
|
+
.replace(/[-_]+/g, " ")
|
|
24
|
+
.replace(/\b\w/g, (c) => c.toUpperCase())
|
|
25
|
+
.trim();
|
|
26
|
+
}
|
|
27
|
+
// ─── FEEL URL construction ────────────────────────────────────────────────────
|
|
28
|
+
/**
|
|
29
|
+
* Build the URL value and whether it needs FEEL.
|
|
30
|
+
* For paths with parameters (e.g. /repos/{owner}/{repo}), returns a FEEL expression
|
|
31
|
+
* and feel="optional" so it's visible and editable in the modeler.
|
|
32
|
+
*/
|
|
33
|
+
function buildUrlField(op) {
|
|
34
|
+
const base = op.baseUrl;
|
|
35
|
+
const hasPathParams = op.pathParams.length > 0;
|
|
36
|
+
if (!hasPathParams) {
|
|
37
|
+
return {
|
|
38
|
+
type: "Hidden",
|
|
39
|
+
value: `${base}${op.path}`,
|
|
40
|
+
binding: { type: "zeebe:input", name: "url" },
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
// Build FEEL expression: ="https://api/repos/" + owner + "/" + repo + "/issues"
|
|
44
|
+
const parts = op.path.split(/(\{[^}]+\})/);
|
|
45
|
+
const feelParts = parts.map((part) => {
|
|
46
|
+
const match = /^\{([^}]+)\}$/.exec(part);
|
|
47
|
+
if (match) {
|
|
48
|
+
const paramName = match[1] ?? part.slice(1, -1);
|
|
49
|
+
return paramName; // variable reference
|
|
50
|
+
}
|
|
51
|
+
const segment = `${base}${part}`;
|
|
52
|
+
return JSON.stringify(segment); // string literal
|
|
53
|
+
});
|
|
54
|
+
// Filter out empty string literals
|
|
55
|
+
const nonEmpty = feelParts.filter((p) => p !== '""');
|
|
56
|
+
const feelExpr = `=${nonEmpty.join(" + ")}`;
|
|
57
|
+
return {
|
|
58
|
+
label: "URL",
|
|
59
|
+
type: "String",
|
|
60
|
+
value: feelExpr,
|
|
61
|
+
group: "endpoint",
|
|
62
|
+
feel: "optional",
|
|
63
|
+
binding: { type: "zeebe:input", name: "url" },
|
|
64
|
+
constraints: { notEmpty: true },
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
// ─── Path parameter fields ────────────────────────────────────────────────────
|
|
68
|
+
function buildPathParamField(param) {
|
|
69
|
+
return {
|
|
70
|
+
id: param.name,
|
|
71
|
+
label: toDisplayName(param.name),
|
|
72
|
+
description: param.description,
|
|
73
|
+
type: "String",
|
|
74
|
+
group: "endpoint",
|
|
75
|
+
feel: "optional",
|
|
76
|
+
binding: { type: "zeebe:input", name: param.name },
|
|
77
|
+
constraints: param.required !== false ? { notEmpty: true } : undefined,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
// ─── Query parameter field ────────────────────────────────────────────────────
|
|
81
|
+
function buildQueryParamsField(queryParams) {
|
|
82
|
+
if (queryParams.length === 0) {
|
|
83
|
+
return {
|
|
84
|
+
label: "Query parameters",
|
|
85
|
+
type: "Text",
|
|
86
|
+
group: "endpoint",
|
|
87
|
+
feel: "required",
|
|
88
|
+
optional: true,
|
|
89
|
+
binding: { type: "zeebe:input", name: "queryParameters" },
|
|
90
|
+
value: "={}",
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
// Build a FEEL context with all params as example keys
|
|
94
|
+
const entries = queryParams.map((p) => `"${p.name}": ${p.name}Value`).join(", ");
|
|
95
|
+
const defaultValue = `={${entries}}`;
|
|
96
|
+
const names = queryParams.map((p) => `\`${p.name}\``).join(", ");
|
|
97
|
+
const reqNames = queryParams
|
|
98
|
+
.filter((p) => p.required)
|
|
99
|
+
.map((p) => `\`${p.name}\``)
|
|
100
|
+
.join(", ");
|
|
101
|
+
return {
|
|
102
|
+
label: "Query parameters",
|
|
103
|
+
description: `Available: ${names}${reqNames ? `. Required: ${reqNames}.` : ""} Remove unused keys from the FEEL context.`,
|
|
104
|
+
type: "Text",
|
|
105
|
+
group: "endpoint",
|
|
106
|
+
feel: "required",
|
|
107
|
+
optional: queryParams.every((p) => !p.required),
|
|
108
|
+
binding: { type: "zeebe:input", name: "queryParameters" },
|
|
109
|
+
value: defaultValue,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
// ─── Headers field ────────────────────────────────────────────────────────────
|
|
113
|
+
function buildHeadersField(headerParams) {
|
|
114
|
+
if (headerParams.length === 0) {
|
|
115
|
+
return {
|
|
116
|
+
label: "Headers",
|
|
117
|
+
type: "Text",
|
|
118
|
+
group: "endpoint",
|
|
119
|
+
feel: "required",
|
|
120
|
+
optional: true,
|
|
121
|
+
binding: { type: "zeebe:input", name: "headers" },
|
|
122
|
+
value: "={}",
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
const entries = headerParams.map((p) => `"${p.name}": ${slugify(p.name)}Value`).join(", ");
|
|
126
|
+
return {
|
|
127
|
+
label: "Headers",
|
|
128
|
+
type: "Text",
|
|
129
|
+
group: "endpoint",
|
|
130
|
+
feel: "required",
|
|
131
|
+
optional: headerParams.every((p) => !p.required),
|
|
132
|
+
binding: { type: "zeebe:input", name: "headers" },
|
|
133
|
+
value: `={${entries}}`,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
// ─── Body field ───────────────────────────────────────────────────────────────
|
|
137
|
+
const BODY_METHODS = ["post", "put", "patch"];
|
|
138
|
+
function buildBodyFields(op, expandBody) {
|
|
139
|
+
if (!BODY_METHODS.includes(op.method))
|
|
140
|
+
return [];
|
|
141
|
+
const condition = {
|
|
142
|
+
property: "method",
|
|
143
|
+
oneOf: ["POST", "PUT", "PATCH"],
|
|
144
|
+
type: "simple",
|
|
145
|
+
};
|
|
146
|
+
if (!expandBody || !op.requestBodySchema?.properties) {
|
|
147
|
+
return [
|
|
148
|
+
{
|
|
149
|
+
label: "Request body",
|
|
150
|
+
description: op.requestBodySchema?.description,
|
|
151
|
+
type: "Text",
|
|
152
|
+
group: "payload",
|
|
153
|
+
feel: "required",
|
|
154
|
+
optional: true,
|
|
155
|
+
binding: { type: "zeebe:input", name: "body" },
|
|
156
|
+
value: "={}",
|
|
157
|
+
condition,
|
|
158
|
+
},
|
|
159
|
+
];
|
|
160
|
+
}
|
|
161
|
+
// Expand each top-level property into its own field
|
|
162
|
+
const schema = op.requestBodySchema;
|
|
163
|
+
const required = new Set(schema.required ?? []);
|
|
164
|
+
const fields = [];
|
|
165
|
+
for (const [name, rawProp] of Object.entries(schema.properties ?? {})) {
|
|
166
|
+
const prop = rawProp;
|
|
167
|
+
const fieldType = prop.type === "boolean"
|
|
168
|
+
? "Boolean"
|
|
169
|
+
: prop.type === "number" || prop.type === "integer"
|
|
170
|
+
? "Number"
|
|
171
|
+
: "String";
|
|
172
|
+
fields.push({
|
|
173
|
+
id: `body.${name}`,
|
|
174
|
+
label: toDisplayName(name),
|
|
175
|
+
description: prop.description,
|
|
176
|
+
type: fieldType,
|
|
177
|
+
group: "payload",
|
|
178
|
+
feel: "optional",
|
|
179
|
+
optional: !required.has(name),
|
|
180
|
+
binding: { type: "zeebe:input", name: `body.${name}` },
|
|
181
|
+
constraints: required.has(name) ? { notEmpty: true } : undefined,
|
|
182
|
+
condition,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
return fields;
|
|
186
|
+
}
|
|
187
|
+
// ─── Authentication block ─────────────────────────────────────────────────────
|
|
188
|
+
const AUTH_CHOICES = [
|
|
189
|
+
{ name: "No auth", value: "noAuth" },
|
|
190
|
+
{ name: "API key", value: "apiKey" },
|
|
191
|
+
{ name: "Basic auth", value: "basic" },
|
|
192
|
+
{ name: "Bearer token", value: "bearer" },
|
|
193
|
+
{ name: "OAuth 2.0 (Client Credentials)", value: "oauth-client-credentials-flow" },
|
|
194
|
+
];
|
|
195
|
+
function authCond(equals) {
|
|
196
|
+
return { property: "authentication.type", equals, type: "simple" };
|
|
197
|
+
}
|
|
198
|
+
function buildAuthBlock(defaultAuthType = "noAuth") {
|
|
199
|
+
return [
|
|
200
|
+
{
|
|
201
|
+
id: "authentication.type",
|
|
202
|
+
label: "Type",
|
|
203
|
+
type: "Dropdown",
|
|
204
|
+
group: "authentication",
|
|
205
|
+
value: defaultAuthType,
|
|
206
|
+
binding: { type: "zeebe:input", name: "authentication.type" },
|
|
207
|
+
choices: AUTH_CHOICES,
|
|
208
|
+
},
|
|
209
|
+
// API key
|
|
210
|
+
{
|
|
211
|
+
id: "authentication.apiKeyLocation",
|
|
212
|
+
label: "API key location",
|
|
213
|
+
type: "Dropdown",
|
|
214
|
+
group: "authentication",
|
|
215
|
+
value: "headers",
|
|
216
|
+
binding: { type: "zeebe:input", name: "authentication.apiKeyLocation" },
|
|
217
|
+
condition: authCond("apiKey"),
|
|
218
|
+
choices: [
|
|
219
|
+
{ name: "Header", value: "headers" },
|
|
220
|
+
{ name: "Query parameter", value: "query" },
|
|
221
|
+
],
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
id: "authentication.apiKeyName",
|
|
225
|
+
label: "API key name",
|
|
226
|
+
type: "String",
|
|
227
|
+
group: "authentication",
|
|
228
|
+
feel: "optional",
|
|
229
|
+
binding: { type: "zeebe:input", name: "authentication.apiKeyName" },
|
|
230
|
+
condition: authCond("apiKey"),
|
|
231
|
+
constraints: { notEmpty: true },
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
id: "authentication.apiKeyValue",
|
|
235
|
+
label: "API key value",
|
|
236
|
+
type: "String",
|
|
237
|
+
group: "authentication",
|
|
238
|
+
feel: "optional",
|
|
239
|
+
binding: { type: "zeebe:input", name: "authentication.apiKeyValue" },
|
|
240
|
+
condition: authCond("apiKey"),
|
|
241
|
+
constraints: { notEmpty: true },
|
|
242
|
+
},
|
|
243
|
+
// Basic
|
|
244
|
+
{
|
|
245
|
+
id: "authentication.username",
|
|
246
|
+
label: "Username",
|
|
247
|
+
type: "String",
|
|
248
|
+
group: "authentication",
|
|
249
|
+
feel: "optional",
|
|
250
|
+
binding: { type: "zeebe:input", name: "authentication.username" },
|
|
251
|
+
condition: authCond("basic"),
|
|
252
|
+
constraints: { notEmpty: true },
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
id: "authentication.password",
|
|
256
|
+
label: "Password",
|
|
257
|
+
type: "String",
|
|
258
|
+
group: "authentication",
|
|
259
|
+
feel: "optional",
|
|
260
|
+
binding: { type: "zeebe:input", name: "authentication.password" },
|
|
261
|
+
condition: authCond("basic"),
|
|
262
|
+
constraints: { notEmpty: true },
|
|
263
|
+
},
|
|
264
|
+
// Bearer
|
|
265
|
+
{
|
|
266
|
+
id: "authentication.token",
|
|
267
|
+
label: "Bearer token",
|
|
268
|
+
type: "String",
|
|
269
|
+
group: "authentication",
|
|
270
|
+
feel: "optional",
|
|
271
|
+
binding: { type: "zeebe:input", name: "authentication.token" },
|
|
272
|
+
condition: authCond("bearer"),
|
|
273
|
+
constraints: { notEmpty: true },
|
|
274
|
+
},
|
|
275
|
+
// OAuth2 client credentials
|
|
276
|
+
{
|
|
277
|
+
id: "authentication.oauthTokenEndpoint",
|
|
278
|
+
label: "OAuth token endpoint",
|
|
279
|
+
type: "String",
|
|
280
|
+
group: "authentication",
|
|
281
|
+
feel: "optional",
|
|
282
|
+
binding: { type: "zeebe:input", name: "authentication.oauthTokenEndpoint" },
|
|
283
|
+
condition: authCond("oauth-client-credentials-flow"),
|
|
284
|
+
constraints: { notEmpty: true },
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
id: "authentication.clientId",
|
|
288
|
+
label: "Client ID",
|
|
289
|
+
type: "String",
|
|
290
|
+
group: "authentication",
|
|
291
|
+
feel: "optional",
|
|
292
|
+
binding: { type: "zeebe:input", name: "authentication.clientId" },
|
|
293
|
+
condition: authCond("oauth-client-credentials-flow"),
|
|
294
|
+
constraints: { notEmpty: true },
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
id: "authentication.clientSecret",
|
|
298
|
+
label: "Client secret",
|
|
299
|
+
type: "String",
|
|
300
|
+
group: "authentication",
|
|
301
|
+
feel: "optional",
|
|
302
|
+
binding: { type: "zeebe:input", name: "authentication.clientSecret" },
|
|
303
|
+
condition: authCond("oauth-client-credentials-flow"),
|
|
304
|
+
constraints: { notEmpty: true },
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
id: "authentication.clientAuthentication",
|
|
308
|
+
label: "Client authentication",
|
|
309
|
+
type: "Dropdown",
|
|
310
|
+
group: "authentication",
|
|
311
|
+
value: "basicAuthHeader",
|
|
312
|
+
binding: { type: "zeebe:input", name: "authentication.clientAuthentication" },
|
|
313
|
+
condition: authCond("oauth-client-credentials-flow"),
|
|
314
|
+
choices: [
|
|
315
|
+
{ name: "Send as Basic Auth header", value: "basicAuthHeader" },
|
|
316
|
+
{ name: "Send client credentials in body", value: "requestBody" },
|
|
317
|
+
],
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
id: "authentication.scopes",
|
|
321
|
+
label: "Scopes",
|
|
322
|
+
type: "String",
|
|
323
|
+
group: "authentication",
|
|
324
|
+
feel: "optional",
|
|
325
|
+
optional: true,
|
|
326
|
+
binding: { type: "zeebe:input", name: "authentication.scopes" },
|
|
327
|
+
condition: authCond("oauth-client-credentials-flow"),
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
id: "authentication.audience",
|
|
331
|
+
label: "Audience",
|
|
332
|
+
type: "String",
|
|
333
|
+
group: "authentication",
|
|
334
|
+
feel: "optional",
|
|
335
|
+
optional: true,
|
|
336
|
+
binding: { type: "zeebe:input", name: "authentication.audience" },
|
|
337
|
+
condition: authCond("oauth-client-credentials-flow"),
|
|
338
|
+
},
|
|
339
|
+
];
|
|
340
|
+
}
|
|
341
|
+
// ─── Timeout block ────────────────────────────────────────────────────────────
|
|
342
|
+
function buildTimeoutBlock() {
|
|
343
|
+
return [
|
|
344
|
+
{
|
|
345
|
+
label: "Connection timeout (seconds)",
|
|
346
|
+
type: "Number",
|
|
347
|
+
group: "timeout",
|
|
348
|
+
value: 20,
|
|
349
|
+
feel: "optional",
|
|
350
|
+
optional: true,
|
|
351
|
+
binding: { type: "zeebe:input", name: "connectionTimeoutInSeconds" },
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
label: "Read timeout (seconds)",
|
|
355
|
+
type: "Number",
|
|
356
|
+
group: "timeout",
|
|
357
|
+
value: 20,
|
|
358
|
+
feel: "optional",
|
|
359
|
+
optional: true,
|
|
360
|
+
binding: { type: "zeebe:input", name: "readTimeoutInSeconds" },
|
|
361
|
+
},
|
|
362
|
+
];
|
|
363
|
+
}
|
|
364
|
+
// ─── Output / error / retry blocks ───────────────────────────────────────────
|
|
365
|
+
function buildOutputBlock(responseSchema) {
|
|
366
|
+
let resultExprHint = "";
|
|
367
|
+
if (responseSchema?.properties) {
|
|
368
|
+
const keys = Object.keys(responseSchema.properties).slice(0, 3);
|
|
369
|
+
if (keys.length > 0) {
|
|
370
|
+
const pairs = keys.map((k) => `${k}: response.body.${k}`).join(", ");
|
|
371
|
+
resultExprHint = `={${pairs}}`;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
return [
|
|
375
|
+
{
|
|
376
|
+
label: "Result variable",
|
|
377
|
+
type: "String",
|
|
378
|
+
group: "output",
|
|
379
|
+
feel: "static",
|
|
380
|
+
optional: true,
|
|
381
|
+
binding: { type: "zeebe:taskHeader", key: "resultVariable" },
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
label: "Result expression",
|
|
385
|
+
description: resultExprHint
|
|
386
|
+
? `Example: \`${resultExprHint}\``
|
|
387
|
+
: "FEEL expression to extract values from the response",
|
|
388
|
+
type: "Text",
|
|
389
|
+
group: "output",
|
|
390
|
+
feel: "required",
|
|
391
|
+
optional: true,
|
|
392
|
+
binding: { type: "zeebe:taskHeader", key: "resultExpression" },
|
|
393
|
+
},
|
|
394
|
+
];
|
|
395
|
+
}
|
|
396
|
+
function buildErrorBlock() {
|
|
397
|
+
return [
|
|
398
|
+
{
|
|
399
|
+
label: "Error expression",
|
|
400
|
+
description: 'Example: `=if error.code = 404 then bpmnError("NOT_FOUND", error.message) else null`',
|
|
401
|
+
type: "Text",
|
|
402
|
+
group: "errors",
|
|
403
|
+
feel: "required",
|
|
404
|
+
optional: true,
|
|
405
|
+
binding: { type: "zeebe:taskHeader", key: "errorExpression" },
|
|
406
|
+
},
|
|
407
|
+
];
|
|
408
|
+
}
|
|
409
|
+
function buildRetryBlock() {
|
|
410
|
+
return [
|
|
411
|
+
{
|
|
412
|
+
label: "Retries",
|
|
413
|
+
type: "String",
|
|
414
|
+
group: "retries",
|
|
415
|
+
value: "3",
|
|
416
|
+
binding: { type: "zeebe:taskDefinition", property: "retries" },
|
|
417
|
+
constraints: { notEmpty: true },
|
|
418
|
+
},
|
|
419
|
+
{
|
|
420
|
+
label: "Retry backoff",
|
|
421
|
+
description: "ISO 8601 duration, e.g. PT5S for 5 seconds",
|
|
422
|
+
type: "String",
|
|
423
|
+
group: "retries",
|
|
424
|
+
value: "PT0S",
|
|
425
|
+
feel: "static",
|
|
426
|
+
optional: true,
|
|
427
|
+
binding: { type: "zeebe:taskHeader", key: "retryBackoff" },
|
|
428
|
+
},
|
|
429
|
+
];
|
|
430
|
+
}
|
|
431
|
+
// ─── Standard groups ──────────────────────────────────────────────────────────
|
|
432
|
+
const STANDARD_GROUPS = [
|
|
433
|
+
{ id: "endpoint", label: "HTTP Endpoint" },
|
|
434
|
+
{ id: "authentication", label: "Authentication" },
|
|
435
|
+
{ id: "timeout", label: "Timeout", openByDefault: false },
|
|
436
|
+
{ id: "payload", label: "Payload" },
|
|
437
|
+
{ id: "output", label: "Output Mapping" },
|
|
438
|
+
{ id: "errors", label: "Error Handling" },
|
|
439
|
+
{ id: "retries", label: "Retries", openByDefault: false },
|
|
440
|
+
];
|
|
441
|
+
// ─── Main builder ─────────────────────────────────────────────────────────────
|
|
442
|
+
export function buildTemplate(op, opts) {
|
|
443
|
+
const name = operationName(op);
|
|
444
|
+
const displayName = op.operation.summary ? op.operation.summary : toDisplayName(name);
|
|
445
|
+
const templateId = `${opts.idPrefix}.${slugify(name)}`;
|
|
446
|
+
const properties = [];
|
|
447
|
+
// 1. Hidden: job type
|
|
448
|
+
properties.push({
|
|
449
|
+
type: "Hidden",
|
|
450
|
+
value: "io.camunda:http-json:1",
|
|
451
|
+
binding: { type: "zeebe:taskDefinition", property: "type" },
|
|
452
|
+
});
|
|
453
|
+
// 2. Hidden: method
|
|
454
|
+
properties.push({
|
|
455
|
+
type: "Hidden",
|
|
456
|
+
value: op.method.toUpperCase(),
|
|
457
|
+
binding: { type: "zeebe:input", name: "method" },
|
|
458
|
+
});
|
|
459
|
+
// 3. URL (hidden if no path params, editable FEEL string if path params)
|
|
460
|
+
properties.push(buildUrlField(op));
|
|
461
|
+
// 4. Path params as individual inputs (above URL display)
|
|
462
|
+
for (const param of op.pathParams) {
|
|
463
|
+
properties.push(buildPathParamField(param));
|
|
464
|
+
}
|
|
465
|
+
// 5. Query parameters
|
|
466
|
+
properties.push(buildQueryParamsField(op.queryParams));
|
|
467
|
+
// 6. Headers
|
|
468
|
+
properties.push(buildHeadersField(op.headerParams));
|
|
469
|
+
// 7. Body (POST/PUT/PATCH only)
|
|
470
|
+
for (const f of buildBodyFields(op, opts.expandBody ?? false)) {
|
|
471
|
+
properties.push(f);
|
|
472
|
+
}
|
|
473
|
+
// 8. Auth block
|
|
474
|
+
for (const f of buildAuthBlock(opts.defaultAuthType)) {
|
|
475
|
+
properties.push(f);
|
|
476
|
+
}
|
|
477
|
+
// 9. Timeout
|
|
478
|
+
for (const f of buildTimeoutBlock()) {
|
|
479
|
+
properties.push(f);
|
|
480
|
+
}
|
|
481
|
+
// 10. Output
|
|
482
|
+
for (const f of buildOutputBlock(op.responseSchema)) {
|
|
483
|
+
properties.push(f);
|
|
484
|
+
}
|
|
485
|
+
// 11. Errors
|
|
486
|
+
for (const f of buildErrorBlock()) {
|
|
487
|
+
properties.push(f);
|
|
488
|
+
}
|
|
489
|
+
// 12. Retries
|
|
490
|
+
for (const f of buildRetryBlock()) {
|
|
491
|
+
properties.push(f);
|
|
492
|
+
}
|
|
493
|
+
// Drop payload group if no body fields
|
|
494
|
+
const hasPayload = properties.some((p) => p.group === "payload");
|
|
495
|
+
const groups = hasPayload ? STANDARD_GROUPS : STANDARD_GROUPS.filter((g) => g.id !== "payload");
|
|
496
|
+
return {
|
|
497
|
+
$schema: SCHEMA_URL,
|
|
498
|
+
name: displayName,
|
|
499
|
+
id: templateId,
|
|
500
|
+
version: 1,
|
|
501
|
+
description: op.operation.description ?? op.operation.summary,
|
|
502
|
+
documentationRef: op.operation.externalDocs?.url,
|
|
503
|
+
category: { id: "connectors", name: "Connectors" },
|
|
504
|
+
appliesTo: ["bpmn:Task"],
|
|
505
|
+
elementType: { value: "bpmn:ServiceTask" },
|
|
506
|
+
groups,
|
|
507
|
+
properties,
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
export function buildTemplates(ops, opts) {
|
|
511
|
+
return ops.map((op) => buildTemplate(op, opts));
|
|
512
|
+
}
|
|
513
|
+
//# sourceMappingURL=build-template.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface CatalogEntry {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
url: string;
|
|
6
|
+
/** Suggested id prefix for generated templates */
|
|
7
|
+
idPrefix: string;
|
|
8
|
+
/** Default auth hint */
|
|
9
|
+
defaultAuth: "noAuth" | "apiKey" | "basic" | "bearer" | "oauth-client-credentials-flow";
|
|
10
|
+
}
|
|
11
|
+
export declare const CATALOG: CatalogEntry[];
|
|
12
|
+
export declare function getCatalogEntry(id: string): CatalogEntry | undefined;
|
|
13
|
+
//# sourceMappingURL=catalog.d.ts.map
|
package/dist/catalog.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
export const CATALOG = [
|
|
2
|
+
{
|
|
3
|
+
id: "github",
|
|
4
|
+
name: "GitHub REST API",
|
|
5
|
+
description: "Manage repos, issues, PRs, actions, and more via the GitHub REST API",
|
|
6
|
+
url: "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.json",
|
|
7
|
+
idPrefix: "io.github",
|
|
8
|
+
defaultAuth: "bearer",
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
id: "cloudflare",
|
|
12
|
+
name: "Cloudflare API",
|
|
13
|
+
description: "Manage DNS, zones, Workers, and other Cloudflare resources",
|
|
14
|
+
url: "https://raw.githubusercontent.com/cloudflare/api-schemas/main/openapi.json",
|
|
15
|
+
idPrefix: "io.cloudflare",
|
|
16
|
+
defaultAuth: "bearer",
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
id: "stripe",
|
|
20
|
+
name: "Stripe API",
|
|
21
|
+
description: "Payments, subscriptions, invoices, and more via the Stripe REST API",
|
|
22
|
+
url: "https://raw.githubusercontent.com/stripe/openapi/master/openapi/spec3.json",
|
|
23
|
+
idPrefix: "io.stripe",
|
|
24
|
+
defaultAuth: "basic",
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: "notion",
|
|
28
|
+
name: "Notion API",
|
|
29
|
+
description: "Read and write Notion pages, databases, and blocks",
|
|
30
|
+
url: "https://raw.githubusercontent.com/notion-sdk-python/notion-sdk-py/main/openapi.yaml",
|
|
31
|
+
idPrefix: "io.notion",
|
|
32
|
+
defaultAuth: "bearer",
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
id: "resend",
|
|
36
|
+
name: "Resend Email API",
|
|
37
|
+
description: "Send transactional emails via the Resend API",
|
|
38
|
+
url: "https://raw.githubusercontent.com/resendlabs/resend-openapi/main/resend.yaml",
|
|
39
|
+
idPrefix: "io.resend",
|
|
40
|
+
defaultAuth: "bearer",
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: "openai",
|
|
44
|
+
name: "OpenAI API",
|
|
45
|
+
description: "Chat completions, embeddings, images, and more from the OpenAI platform",
|
|
46
|
+
url: "https://raw.githubusercontent.com/openai/openai-openapi/master/openapi.yaml",
|
|
47
|
+
idPrefix: "io.openai",
|
|
48
|
+
defaultAuth: "bearer",
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: "figma",
|
|
52
|
+
name: "Figma API",
|
|
53
|
+
description: "Read and write Figma files, components, comments, and team resources",
|
|
54
|
+
url: "https://raw.githubusercontent.com/figma/rest-api-spec/main/openapi/openapi.yaml",
|
|
55
|
+
idPrefix: "io.figma",
|
|
56
|
+
defaultAuth: "bearer",
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: "twilio",
|
|
60
|
+
name: "Twilio Messaging API",
|
|
61
|
+
description: "Send and manage SMS, MMS, and WhatsApp messages via Twilio",
|
|
62
|
+
url: "https://raw.githubusercontent.com/twilio/twilio-oai/main/spec/json/twilio_messaging_v1.json",
|
|
63
|
+
idPrefix: "io.twilio",
|
|
64
|
+
defaultAuth: "basic",
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: "slack",
|
|
68
|
+
name: "Slack Web API",
|
|
69
|
+
description: "Post messages, manage channels, users, and workflows in Slack",
|
|
70
|
+
url: "https://raw.githubusercontent.com/APIs-guru/openapi-directory/main/APIs/slack.com/1.7.0/openapi.yaml",
|
|
71
|
+
idPrefix: "io.slack",
|
|
72
|
+
defaultAuth: "bearer",
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: "jira",
|
|
76
|
+
name: "Atlassian Jira API",
|
|
77
|
+
description: "Manage Jira issues, projects, boards, and sprints",
|
|
78
|
+
url: "https://raw.githubusercontent.com/APIs-guru/openapi-directory/main/APIs/atlassian.com/jira/1001.0.0-SNAPSHOT/openapi.yaml",
|
|
79
|
+
idPrefix: "io.atlassian.jira",
|
|
80
|
+
defaultAuth: "bearer",
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
id: "hubspot",
|
|
84
|
+
name: "HubSpot CRM API",
|
|
85
|
+
description: "Manage contacts, companies, deals, and pipelines in the HubSpot CRM platform",
|
|
86
|
+
url: "https://raw.githubusercontent.com/APIs-guru/openapi-directory/main/APIs/hubapi.com/crm/v3/openapi.yaml",
|
|
87
|
+
idPrefix: "com.hubspot",
|
|
88
|
+
defaultAuth: "oauth-client-credentials-flow",
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
id: "discord",
|
|
92
|
+
name: "Discord API",
|
|
93
|
+
description: "Manage Discord bots, messages, channels, guilds, and webhooks",
|
|
94
|
+
url: "https://raw.githubusercontent.com/discord/discord-api-spec/main/specs/openapi.json",
|
|
95
|
+
idPrefix: "com.discord",
|
|
96
|
+
defaultAuth: "bearer",
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
id: "pagerduty",
|
|
100
|
+
name: "PagerDuty API",
|
|
101
|
+
description: "Create and manage incidents, escalation policies, and on-call schedules",
|
|
102
|
+
url: "https://raw.githubusercontent.com/PagerDuty/api-schema/main/reference/REST/openapiv3.json",
|
|
103
|
+
idPrefix: "com.pagerduty",
|
|
104
|
+
defaultAuth: "apiKey",
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
id: "zoom",
|
|
108
|
+
name: "Zoom API",
|
|
109
|
+
description: "Create and manage Zoom meetings, webinars, recordings, and users",
|
|
110
|
+
url: "https://raw.githubusercontent.com/APIs-guru/openapi-directory/main/APIs/zoom.us/2.0.0/openapi.yaml",
|
|
111
|
+
idPrefix: "us.zoom",
|
|
112
|
+
defaultAuth: "oauth-client-credentials-flow",
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
id: "mailchimp",
|
|
116
|
+
name: "Mailchimp API",
|
|
117
|
+
description: "Manage email campaigns, audiences, automations, and transactional messages",
|
|
118
|
+
url: "https://raw.githubusercontent.com/APIs-guru/openapi-directory/main/APIs/mailchimp.com/3.0.55/openapi.yaml",
|
|
119
|
+
idPrefix: "com.mailchimp",
|
|
120
|
+
defaultAuth: "apiKey",
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
id: "asana",
|
|
124
|
+
name: "Asana API",
|
|
125
|
+
description: "Manage tasks, projects, teams, and workspaces in Asana",
|
|
126
|
+
url: "https://raw.githubusercontent.com/Asana/openapi/master/defs/asana_oas.yaml",
|
|
127
|
+
idPrefix: "com.asana",
|
|
128
|
+
defaultAuth: "bearer",
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
id: "sendgrid",
|
|
132
|
+
name: "SendGrid Mail API",
|
|
133
|
+
description: "Send transactional and marketing emails with templates, tracking, and analytics",
|
|
134
|
+
url: "https://raw.githubusercontent.com/twilio/sendgrid-oai/main/spec/json/tsg_mail_v3.json",
|
|
135
|
+
idPrefix: "com.sendgrid",
|
|
136
|
+
defaultAuth: "bearer",
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
id: "paypal",
|
|
140
|
+
name: "PayPal Payments API",
|
|
141
|
+
description: "Authorize, capture, and refund online payment transactions via PayPal",
|
|
142
|
+
url: "https://raw.githubusercontent.com/paypal/paypal-rest-api-specifications/main/openapi/payments_payment_v2.json",
|
|
143
|
+
idPrefix: "com.paypal",
|
|
144
|
+
defaultAuth: "oauth-client-credentials-flow",
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
id: "plaid",
|
|
148
|
+
name: "Plaid API",
|
|
149
|
+
description: "Link bank accounts, read transactions, and verify identity via Plaid",
|
|
150
|
+
url: "https://raw.githubusercontent.com/plaid/plaid-openapi/master/2020-09-14.yml",
|
|
151
|
+
idPrefix: "com.plaid",
|
|
152
|
+
defaultAuth: "apiKey",
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
id: "vercel",
|
|
156
|
+
name: "Vercel API",
|
|
157
|
+
description: "Manage Vercel deployments, projects, domains, and team resources",
|
|
158
|
+
url: "https://raw.githubusercontent.com/APIs-guru/openapi-directory/main/APIs/vercel.com/0.0.1/openapi.yaml",
|
|
159
|
+
idPrefix: "com.vercel",
|
|
160
|
+
defaultAuth: "bearer",
|
|
161
|
+
},
|
|
162
|
+
];
|
|
163
|
+
export function getCatalogEntry(id) {
|
|
164
|
+
return CATALOG.find((e) => e.id === id);
|
|
165
|
+
}
|
|
166
|
+
//# sourceMappingURL=catalog.js.map
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export { buildTemplate, buildTemplates } from "./build-template.js";
|
|
2
|
+
export { CATALOG, getCatalogEntry } from "./catalog.js";
|
|
3
|
+
export type { CatalogEntry } from "./catalog.js";
|
|
4
|
+
export { detectDefaultAuth, getBaseUrl, getOperations, getServers, isRef, parseOpenApi, resolve, resolveRef, } from "./parse-openapi.js";
|
|
5
|
+
export type { DetectedAuth } from "./parse-openapi.js";
|
|
6
|
+
export type { ApiResponse, AuthHint, Binding, Components, ConnectorGroup, ConnectorTemplate, Constraints, GeneratorOptions, HttpMethod, MediaType, OpenApiDoc, OpenApiInfo, OpenApiServer, Operation, OperationWithMeta, OAuthFlows, Parameter, PathItem, PropertyDef, Ref, RequestBody, Schema, SecurityRequirement, SecurityScheme, WriteOptions, } from "./types.js";
|
|
7
|
+
export { CONNECTOR_SCHEMA } from "./types.js";
|
|
8
|
+
export { writeTemplates } from "./write-templates.js";
|
|
9
|
+
import type { CatalogEntry } from "./catalog.js";
|
|
10
|
+
import type { ConnectorTemplate, GeneratorOptions, WriteOptions } from "./types.js";
|
|
11
|
+
export interface GenerateOptions extends GeneratorOptions, Partial<WriteOptions> {
|
|
12
|
+
/** If provided, write files to disk. Otherwise return the templates. */
|
|
13
|
+
outputDir?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Parse an OpenAPI spec (string or object) and generate connector templates.
|
|
17
|
+
* Returns the generated templates; call `writeTemplates` separately if you need
|
|
18
|
+
* to write them to disk.
|
|
19
|
+
*/
|
|
20
|
+
export declare function generate(specText: string, opts: GeneratorOptions): ConnectorTemplate[];
|
|
21
|
+
/**
|
|
22
|
+
* Download an OpenAPI spec from a URL, generate connector templates, and
|
|
23
|
+
* optionally write them to disk.
|
|
24
|
+
*/
|
|
25
|
+
export declare function generateFromUrl(url: string, opts: GenerateOptions): Promise<{
|
|
26
|
+
templates: ConnectorTemplate[];
|
|
27
|
+
files: string[];
|
|
28
|
+
}>;
|
|
29
|
+
/**
|
|
30
|
+
* Generate connector templates from a catalog entry by ID.
|
|
31
|
+
* Downloads the spec from the catalog URL.
|
|
32
|
+
*/
|
|
33
|
+
export declare function generateFromCatalog(id: string, overrides?: Partial<GenerateOptions>): Promise<{
|
|
34
|
+
templates: ConnectorTemplate[];
|
|
35
|
+
files: string[];
|
|
36
|
+
entry: CatalogEntry;
|
|
37
|
+
}>;
|
|
38
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export { buildTemplate, buildTemplates } from "./build-template.js";
|
|
2
|
+
export { CATALOG, getCatalogEntry } from "./catalog.js";
|
|
3
|
+
export { detectDefaultAuth, getBaseUrl, getOperations, getServers, isRef, parseOpenApi, resolve, resolveRef, } from "./parse-openapi.js";
|
|
4
|
+
export { CONNECTOR_SCHEMA } from "./types.js";
|
|
5
|
+
export { writeTemplates } from "./write-templates.js";
|
|
6
|
+
import { buildTemplates } from "./build-template.js";
|
|
7
|
+
import { CATALOG, getCatalogEntry } from "./catalog.js";
|
|
8
|
+
import { detectDefaultAuth, getOperations, parseOpenApi } from "./parse-openapi.js";
|
|
9
|
+
import { writeTemplates } from "./write-templates.js";
|
|
10
|
+
/**
|
|
11
|
+
* Parse an OpenAPI spec (string or object) and generate connector templates.
|
|
12
|
+
* Returns the generated templates; call `writeTemplates` separately if you need
|
|
13
|
+
* to write them to disk.
|
|
14
|
+
*/
|
|
15
|
+
export function generate(specText, opts) {
|
|
16
|
+
const doc = parseOpenApi(specText);
|
|
17
|
+
const defaultAuth = opts.defaultAuthType ?? detectDefaultAuth(doc);
|
|
18
|
+
const ops = getOperations(doc, opts.filter);
|
|
19
|
+
return buildTemplates(ops, { ...opts, defaultAuthType: defaultAuth });
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Download an OpenAPI spec from a URL, generate connector templates, and
|
|
23
|
+
* optionally write them to disk.
|
|
24
|
+
*/
|
|
25
|
+
export async function generateFromUrl(url, opts) {
|
|
26
|
+
const res = await fetch(url);
|
|
27
|
+
if (!res.ok)
|
|
28
|
+
throw new Error(`Failed to fetch spec from ${url}: ${res.status} ${res.statusText}`);
|
|
29
|
+
const text = await res.text();
|
|
30
|
+
const templates = generate(text, opts);
|
|
31
|
+
const files = opts.outputDir
|
|
32
|
+
? await writeTemplates(templates, { outputDir: opts.outputDir, format: opts.format })
|
|
33
|
+
: [];
|
|
34
|
+
return { templates, files };
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Generate connector templates from a catalog entry by ID.
|
|
38
|
+
* Downloads the spec from the catalog URL.
|
|
39
|
+
*/
|
|
40
|
+
export async function generateFromCatalog(id, overrides = {}) {
|
|
41
|
+
const entry = getCatalogEntry(id);
|
|
42
|
+
if (!entry) {
|
|
43
|
+
const ids = CATALOG.map((e) => e.id).join(", ");
|
|
44
|
+
throw new Error(`Unknown catalog entry "${id}". Available: ${ids}`);
|
|
45
|
+
}
|
|
46
|
+
const opts = {
|
|
47
|
+
idPrefix: overrides.idPrefix ?? entry.idPrefix,
|
|
48
|
+
defaultAuthType: overrides.defaultAuthType ?? entry.defaultAuth,
|
|
49
|
+
...overrides,
|
|
50
|
+
};
|
|
51
|
+
const { templates, files } = await generateFromUrl(entry.url, opts);
|
|
52
|
+
return { templates, files, entry };
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { OpenApiDoc, OpenApiServer, OperationWithMeta, Ref } from "./types.js";
|
|
2
|
+
export declare function isRef(obj: unknown): obj is Ref;
|
|
3
|
+
export declare function resolveRef<T>(doc: OpenApiDoc, ref: string): T;
|
|
4
|
+
/** Resolve a value that may be a $ref or a concrete type. */
|
|
5
|
+
export declare function resolve<T>(doc: OpenApiDoc, obj: T | Ref): T;
|
|
6
|
+
export declare function parseOpenApi(text: string): OpenApiDoc;
|
|
7
|
+
export declare function getBaseUrl(doc: OpenApiDoc, override?: string): string;
|
|
8
|
+
export declare function getOperations(doc: OpenApiDoc, filter?: string): OperationWithMeta[];
|
|
9
|
+
export type DetectedAuth = "noAuth" | "apiKey" | "basic" | "bearer" | "oauth-client-credentials-flow";
|
|
10
|
+
export declare function detectDefaultAuth(doc: OpenApiDoc): DetectedAuth;
|
|
11
|
+
export declare function getServers(doc: OpenApiDoc): OpenApiServer[];
|
|
12
|
+
//# sourceMappingURL=parse-openapi.d.ts.map
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { parse as parseYaml } from "yaml";
|
|
2
|
+
// ─── $ref helpers ─────────────────────────────────────────────────────────────
|
|
3
|
+
export function isRef(obj) {
|
|
4
|
+
return (typeof obj === "object" &&
|
|
5
|
+
obj !== null &&
|
|
6
|
+
"$ref" in obj &&
|
|
7
|
+
typeof obj.$ref === "string");
|
|
8
|
+
}
|
|
9
|
+
export function resolveRef(doc, ref) {
|
|
10
|
+
if (!ref.startsWith("#/")) {
|
|
11
|
+
throw new Error(`External $ref not supported: "${ref}". Only local #/ refs are resolved.`);
|
|
12
|
+
}
|
|
13
|
+
const parts = ref.slice(2).split("/");
|
|
14
|
+
let current = doc;
|
|
15
|
+
for (const part of parts) {
|
|
16
|
+
if (typeof current !== "object" || current === null) {
|
|
17
|
+
throw new Error(`Cannot resolve $ref "${ref}": path segment "${part}" is not an object`);
|
|
18
|
+
}
|
|
19
|
+
const decoded = part.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
20
|
+
current = current[decoded];
|
|
21
|
+
if (current === undefined) {
|
|
22
|
+
throw new Error(`Cannot resolve $ref "${ref}": key "${decoded}" not found`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return current;
|
|
26
|
+
}
|
|
27
|
+
/** Resolve a value that may be a $ref or a concrete type. */
|
|
28
|
+
export function resolve(doc, obj) {
|
|
29
|
+
if (isRef(obj))
|
|
30
|
+
return resolveRef(doc, obj.$ref);
|
|
31
|
+
return obj;
|
|
32
|
+
}
|
|
33
|
+
// ─── Parse ────────────────────────────────────────────────────────────────────
|
|
34
|
+
export function parseOpenApi(text) {
|
|
35
|
+
const trimmed = text.trimStart();
|
|
36
|
+
// JSON starts with { or [; everything else is treated as YAML
|
|
37
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
38
|
+
try {
|
|
39
|
+
return JSON.parse(text);
|
|
40
|
+
}
|
|
41
|
+
catch (e) {
|
|
42
|
+
throw new Error(`Failed to parse OpenAPI JSON: ${String(e)}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
return parseYaml(text);
|
|
47
|
+
}
|
|
48
|
+
catch (e) {
|
|
49
|
+
throw new Error(`Failed to parse OpenAPI YAML: ${String(e)}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// ─── Base URL ─────────────────────────────────────────────────────────────────
|
|
53
|
+
export function getBaseUrl(doc, override) {
|
|
54
|
+
if (override)
|
|
55
|
+
return override.replace(/\/$/, "");
|
|
56
|
+
const server = doc.servers?.[0];
|
|
57
|
+
if (!server)
|
|
58
|
+
return "";
|
|
59
|
+
let url = server.url.replace(/\/$/, "");
|
|
60
|
+
// Expand server variables with their defaults
|
|
61
|
+
if (server.variables) {
|
|
62
|
+
for (const [key, variable] of Object.entries(server.variables)) {
|
|
63
|
+
url = url.replace(`{${key}}`, variable.default);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return url;
|
|
67
|
+
}
|
|
68
|
+
// ─── Parameter resolution (path-level params merged with op-level) ────────────
|
|
69
|
+
function resolveParameters(doc, pathItem, operation) {
|
|
70
|
+
const all = [
|
|
71
|
+
...(pathItem.parameters ?? []),
|
|
72
|
+
...(operation.parameters ?? []),
|
|
73
|
+
];
|
|
74
|
+
// Op-level overrides path-level by name+in (later entries win)
|
|
75
|
+
const map = new Map();
|
|
76
|
+
for (const item of all) {
|
|
77
|
+
const param = resolve(doc, item);
|
|
78
|
+
map.set(`${param.in}:${param.name}`, param);
|
|
79
|
+
}
|
|
80
|
+
return [...map.values()];
|
|
81
|
+
}
|
|
82
|
+
// ─── Body / response schema resolution ───────────────────────────────────────
|
|
83
|
+
function resolveBodySchema(doc, requestBody) {
|
|
84
|
+
if (!requestBody)
|
|
85
|
+
return null;
|
|
86
|
+
const body = resolve(doc, requestBody);
|
|
87
|
+
const json = body.content["application/json"];
|
|
88
|
+
if (!json?.schema)
|
|
89
|
+
return null;
|
|
90
|
+
try {
|
|
91
|
+
return resolve(doc, json.schema);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function resolveResponseSchema(doc, responses) {
|
|
98
|
+
if (!responses)
|
|
99
|
+
return null;
|
|
100
|
+
const ok = responses["200"] ?? responses["201"] ?? responses["2XX"] ?? responses.default;
|
|
101
|
+
if (!ok)
|
|
102
|
+
return null;
|
|
103
|
+
try {
|
|
104
|
+
const resp = resolve(doc, ok);
|
|
105
|
+
const json = resp.content?.["application/json"];
|
|
106
|
+
if (!json?.schema)
|
|
107
|
+
return null;
|
|
108
|
+
return resolve(doc, json.schema);
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// ─── Server validation ────────────────────────────────────────────────────────
|
|
115
|
+
function validateDoc(doc) {
|
|
116
|
+
if (!doc || typeof doc !== "object") {
|
|
117
|
+
throw new Error("Invalid OpenAPI document: must be an object");
|
|
118
|
+
}
|
|
119
|
+
const version = doc.openapi ?? doc.swagger;
|
|
120
|
+
if (typeof version !== "string") {
|
|
121
|
+
throw new Error("Invalid OpenAPI document: missing `openapi` version field");
|
|
122
|
+
}
|
|
123
|
+
if (version.startsWith("2.")) {
|
|
124
|
+
throw new Error(`OpenAPI 2.x (Swagger) is not supported. Found version "${version}". Please convert to OpenAPI 3.x first (e.g. using https://editor.swagger.io).`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// ─── Main: enumerate all operations ──────────────────────────────────────────
|
|
128
|
+
const HTTP_METHODS = [
|
|
129
|
+
"get",
|
|
130
|
+
"post",
|
|
131
|
+
"put",
|
|
132
|
+
"patch",
|
|
133
|
+
"delete",
|
|
134
|
+
"head",
|
|
135
|
+
"options",
|
|
136
|
+
"trace",
|
|
137
|
+
];
|
|
138
|
+
export function getOperations(doc, filter) {
|
|
139
|
+
validateDoc(doc);
|
|
140
|
+
const baseUrl = getBaseUrl(doc);
|
|
141
|
+
const filterRe = filter ? new RegExp(filter, "i") : null;
|
|
142
|
+
const results = [];
|
|
143
|
+
for (const [path, rawPathItem] of Object.entries(doc.paths ?? {})) {
|
|
144
|
+
let pathItem;
|
|
145
|
+
try {
|
|
146
|
+
pathItem = resolve(doc, rawPathItem);
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
for (const method of HTTP_METHODS) {
|
|
152
|
+
const operation = pathItem[method];
|
|
153
|
+
if (!operation)
|
|
154
|
+
continue;
|
|
155
|
+
if (filterRe) {
|
|
156
|
+
const haystack = `${operation.operationId ?? ""} ${operation.summary ?? ""} ${path}`;
|
|
157
|
+
if (!filterRe.test(haystack))
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
const params = resolveParameters(doc, pathItem, operation);
|
|
161
|
+
let responseSchema = null;
|
|
162
|
+
try {
|
|
163
|
+
responseSchema = resolveResponseSchema(doc, operation.responses);
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
// ignore resolution errors for response schemas
|
|
167
|
+
}
|
|
168
|
+
results.push({
|
|
169
|
+
path,
|
|
170
|
+
method,
|
|
171
|
+
operation,
|
|
172
|
+
baseUrl,
|
|
173
|
+
pathParams: params.filter((p) => p.in === "path"),
|
|
174
|
+
queryParams: params.filter((p) => p.in === "query"),
|
|
175
|
+
headerParams: params.filter((p) => p.in === "header"),
|
|
176
|
+
requestBodySchema: resolveBodySchema(doc, operation.requestBody),
|
|
177
|
+
responseSchema,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return results;
|
|
182
|
+
}
|
|
183
|
+
export function detectDefaultAuth(doc) {
|
|
184
|
+
const schemes = doc.components?.securitySchemes;
|
|
185
|
+
if (!schemes)
|
|
186
|
+
return "noAuth";
|
|
187
|
+
for (const raw of Object.values(schemes)) {
|
|
188
|
+
try {
|
|
189
|
+
const scheme = resolve(doc, raw);
|
|
190
|
+
if (scheme.type === "http" && scheme.scheme === "bearer")
|
|
191
|
+
return "bearer";
|
|
192
|
+
if (scheme.type === "http" && scheme.scheme === "basic")
|
|
193
|
+
return "basic";
|
|
194
|
+
if (scheme.type === "apiKey")
|
|
195
|
+
return "apiKey";
|
|
196
|
+
if (scheme.type === "oauth2" && scheme.flows?.clientCredentials) {
|
|
197
|
+
return "oauth-client-credentials-flow";
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
catch { }
|
|
201
|
+
}
|
|
202
|
+
return "noAuth";
|
|
203
|
+
}
|
|
204
|
+
// Re-export server info helper used downstream
|
|
205
|
+
export function getServers(doc) {
|
|
206
|
+
return doc.servers ?? [];
|
|
207
|
+
}
|
|
208
|
+
//# sourceMappingURL=parse-openapi.js.map
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
export type HttpMethod = "get" | "post" | "put" | "patch" | "delete" | "head" | "options" | "trace";
|
|
2
|
+
export interface OpenApiDoc {
|
|
3
|
+
openapi: string;
|
|
4
|
+
info: OpenApiInfo;
|
|
5
|
+
externalDocs?: {
|
|
6
|
+
url?: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
};
|
|
9
|
+
servers?: OpenApiServer[];
|
|
10
|
+
paths?: Record<string, PathItem | Ref>;
|
|
11
|
+
components?: Components;
|
|
12
|
+
security?: SecurityRequirement[];
|
|
13
|
+
}
|
|
14
|
+
export interface OpenApiInfo {
|
|
15
|
+
title: string;
|
|
16
|
+
version: string;
|
|
17
|
+
description?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface OpenApiServer {
|
|
20
|
+
url: string;
|
|
21
|
+
description?: string;
|
|
22
|
+
variables?: Record<string, {
|
|
23
|
+
default: string;
|
|
24
|
+
description?: string;
|
|
25
|
+
enum?: string[];
|
|
26
|
+
}>;
|
|
27
|
+
}
|
|
28
|
+
export interface PathItem {
|
|
29
|
+
summary?: string;
|
|
30
|
+
description?: string;
|
|
31
|
+
get?: Operation;
|
|
32
|
+
put?: Operation;
|
|
33
|
+
post?: Operation;
|
|
34
|
+
delete?: Operation;
|
|
35
|
+
options?: Operation;
|
|
36
|
+
head?: Operation;
|
|
37
|
+
patch?: Operation;
|
|
38
|
+
trace?: Operation;
|
|
39
|
+
parameters?: Array<Parameter | Ref>;
|
|
40
|
+
}
|
|
41
|
+
export interface Operation {
|
|
42
|
+
operationId?: string;
|
|
43
|
+
summary?: string;
|
|
44
|
+
description?: string;
|
|
45
|
+
tags?: string[];
|
|
46
|
+
parameters?: Array<Parameter | Ref>;
|
|
47
|
+
requestBody?: RequestBody | Ref;
|
|
48
|
+
responses?: Record<string, ApiResponse | Ref>;
|
|
49
|
+
security?: SecurityRequirement[];
|
|
50
|
+
deprecated?: boolean;
|
|
51
|
+
externalDocs?: {
|
|
52
|
+
url?: string;
|
|
53
|
+
description?: string;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export interface Parameter {
|
|
57
|
+
name: string;
|
|
58
|
+
in: "query" | "header" | "path" | "cookie";
|
|
59
|
+
description?: string;
|
|
60
|
+
required?: boolean;
|
|
61
|
+
deprecated?: boolean;
|
|
62
|
+
schema?: Schema | Ref;
|
|
63
|
+
}
|
|
64
|
+
export interface RequestBody {
|
|
65
|
+
description?: string;
|
|
66
|
+
required?: boolean;
|
|
67
|
+
content: Record<string, MediaType>;
|
|
68
|
+
}
|
|
69
|
+
export interface MediaType {
|
|
70
|
+
schema?: Schema | Ref;
|
|
71
|
+
}
|
|
72
|
+
export interface ApiResponse {
|
|
73
|
+
description?: string;
|
|
74
|
+
content?: Record<string, MediaType>;
|
|
75
|
+
}
|
|
76
|
+
export interface Schema {
|
|
77
|
+
type?: string;
|
|
78
|
+
format?: string;
|
|
79
|
+
title?: string;
|
|
80
|
+
description?: string;
|
|
81
|
+
properties?: Record<string, Schema | Ref>;
|
|
82
|
+
required?: string[];
|
|
83
|
+
items?: Schema | Ref;
|
|
84
|
+
enum?: unknown[];
|
|
85
|
+
default?: unknown;
|
|
86
|
+
nullable?: boolean;
|
|
87
|
+
allOf?: Array<Schema | Ref>;
|
|
88
|
+
oneOf?: Array<Schema | Ref>;
|
|
89
|
+
anyOf?: Array<Schema | Ref>;
|
|
90
|
+
$ref?: string;
|
|
91
|
+
}
|
|
92
|
+
export interface Ref {
|
|
93
|
+
$ref: string;
|
|
94
|
+
}
|
|
95
|
+
export interface Components {
|
|
96
|
+
schemas?: Record<string, Schema | Ref>;
|
|
97
|
+
parameters?: Record<string, Parameter | Ref>;
|
|
98
|
+
requestBodies?: Record<string, RequestBody | Ref>;
|
|
99
|
+
responses?: Record<string, ApiResponse | Ref>;
|
|
100
|
+
securitySchemes?: Record<string, SecurityScheme | Ref>;
|
|
101
|
+
}
|
|
102
|
+
export type SecurityScheme = {
|
|
103
|
+
type: "apiKey";
|
|
104
|
+
name: string;
|
|
105
|
+
in: "header" | "query" | "cookie";
|
|
106
|
+
description?: string;
|
|
107
|
+
} | {
|
|
108
|
+
type: "http";
|
|
109
|
+
scheme: string;
|
|
110
|
+
bearerFormat?: string;
|
|
111
|
+
description?: string;
|
|
112
|
+
} | {
|
|
113
|
+
type: "oauth2";
|
|
114
|
+
flows: OAuthFlows;
|
|
115
|
+
description?: string;
|
|
116
|
+
} | {
|
|
117
|
+
type: "openIdConnect";
|
|
118
|
+
openIdConnectUrl: string;
|
|
119
|
+
description?: string;
|
|
120
|
+
};
|
|
121
|
+
export interface OAuthFlows {
|
|
122
|
+
clientCredentials?: {
|
|
123
|
+
tokenUrl: string;
|
|
124
|
+
scopes?: Record<string, string>;
|
|
125
|
+
};
|
|
126
|
+
authorizationCode?: {
|
|
127
|
+
authorizationUrl: string;
|
|
128
|
+
tokenUrl: string;
|
|
129
|
+
scopes?: Record<string, string>;
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
export type SecurityRequirement = Record<string, string[]>;
|
|
133
|
+
export interface OperationWithMeta {
|
|
134
|
+
path: string;
|
|
135
|
+
method: HttpMethod;
|
|
136
|
+
operation: Operation;
|
|
137
|
+
baseUrl: string;
|
|
138
|
+
pathParams: Parameter[];
|
|
139
|
+
queryParams: Parameter[];
|
|
140
|
+
headerParams: Parameter[];
|
|
141
|
+
requestBodySchema: Schema | null;
|
|
142
|
+
responseSchema: Schema | null;
|
|
143
|
+
}
|
|
144
|
+
export declare const CONNECTOR_SCHEMA = "https://unpkg.com/@camunda/zeebe-element-templates-json-schema/resources/schema.json";
|
|
145
|
+
export interface ConnectorTemplate {
|
|
146
|
+
$schema: string;
|
|
147
|
+
name: string;
|
|
148
|
+
id: string;
|
|
149
|
+
version: number;
|
|
150
|
+
description?: string;
|
|
151
|
+
documentationRef?: string;
|
|
152
|
+
category?: {
|
|
153
|
+
id: string;
|
|
154
|
+
name: string;
|
|
155
|
+
};
|
|
156
|
+
appliesTo: string[];
|
|
157
|
+
elementType: {
|
|
158
|
+
value: string;
|
|
159
|
+
};
|
|
160
|
+
engines?: {
|
|
161
|
+
camunda?: string;
|
|
162
|
+
};
|
|
163
|
+
groups: ConnectorGroup[];
|
|
164
|
+
properties: PropertyDef[];
|
|
165
|
+
icon?: {
|
|
166
|
+
contents: string;
|
|
167
|
+
};
|
|
168
|
+
metadata?: {
|
|
169
|
+
keywords?: string[];
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
export interface ConnectorGroup {
|
|
173
|
+
id: string;
|
|
174
|
+
label: string;
|
|
175
|
+
tooltip?: string;
|
|
176
|
+
openByDefault?: boolean;
|
|
177
|
+
}
|
|
178
|
+
export type Binding = {
|
|
179
|
+
type: "zeebe:taskDefinition";
|
|
180
|
+
property: "type" | "retries";
|
|
181
|
+
} | {
|
|
182
|
+
type: "zeebe:input";
|
|
183
|
+
name: string;
|
|
184
|
+
} | {
|
|
185
|
+
type: "zeebe:output";
|
|
186
|
+
name: string;
|
|
187
|
+
} | {
|
|
188
|
+
type: "zeebe:taskHeader";
|
|
189
|
+
key: string;
|
|
190
|
+
} | {
|
|
191
|
+
type: "zeebe:property";
|
|
192
|
+
name: string;
|
|
193
|
+
};
|
|
194
|
+
export interface Condition {
|
|
195
|
+
property: string;
|
|
196
|
+
equals?: string;
|
|
197
|
+
oneOf?: string[];
|
|
198
|
+
type?: "simple";
|
|
199
|
+
}
|
|
200
|
+
export interface Constraints {
|
|
201
|
+
notEmpty?: boolean;
|
|
202
|
+
pattern?: {
|
|
203
|
+
value: string;
|
|
204
|
+
message?: string;
|
|
205
|
+
};
|
|
206
|
+
minLength?: number;
|
|
207
|
+
maxLength?: number;
|
|
208
|
+
}
|
|
209
|
+
export interface PropertyDef {
|
|
210
|
+
id?: string;
|
|
211
|
+
label?: string;
|
|
212
|
+
description?: string;
|
|
213
|
+
type: "String" | "Text" | "Number" | "Boolean" | "Dropdown" | "Hidden";
|
|
214
|
+
value?: string | boolean | number;
|
|
215
|
+
group?: string;
|
|
216
|
+
binding: Binding;
|
|
217
|
+
feel?: "optional" | "required" | "static";
|
|
218
|
+
optional?: boolean;
|
|
219
|
+
constraints?: Constraints;
|
|
220
|
+
condition?: Condition;
|
|
221
|
+
choices?: Array<{
|
|
222
|
+
name: string;
|
|
223
|
+
value: string;
|
|
224
|
+
}>;
|
|
225
|
+
tooltip?: string;
|
|
226
|
+
}
|
|
227
|
+
export type AuthHint = "noAuth" | "apiKey" | "basic" | "bearer" | "oauth-client-credentials-flow";
|
|
228
|
+
export interface GeneratorOptions {
|
|
229
|
+
/** Reverse-DNS prefix for template IDs, e.g. "io.mycompany" */
|
|
230
|
+
idPrefix: string;
|
|
231
|
+
/** Override the base URL from the spec */
|
|
232
|
+
baseUrl?: string;
|
|
233
|
+
/** Decompose top-level request body properties into individual fields */
|
|
234
|
+
expandBody?: boolean;
|
|
235
|
+
/** Filter operations by operationId/summary regex */
|
|
236
|
+
filter?: string;
|
|
237
|
+
/** Pre-select a specific auth type */
|
|
238
|
+
defaultAuthType?: AuthHint;
|
|
239
|
+
}
|
|
240
|
+
export interface WriteOptions {
|
|
241
|
+
outputDir: string;
|
|
242
|
+
/** "one-per-op": one file per operation; "array": all in one file */
|
|
243
|
+
format?: "one-per-op" | "array";
|
|
244
|
+
}
|
|
245
|
+
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// ─── OpenAPI 3.x types (minimal subset needed for generation) ─────────────────
|
|
2
|
+
// ─── Connector template types ─────────────────────────────────────────────────
|
|
3
|
+
export const CONNECTOR_SCHEMA = "https://unpkg.com/@camunda/zeebe-element-templates-json-schema/resources/schema.json";
|
|
4
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
function safeName(id) {
|
|
4
|
+
// Strip idPrefix (everything up to and including the first dot), lowercase
|
|
5
|
+
const base = id.includes(".") ? id.slice(id.indexOf(".") + 1) : id;
|
|
6
|
+
return base.replace(/[^a-zA-Z0-9-_]/g, "-").toLowerCase();
|
|
7
|
+
}
|
|
8
|
+
export async function writeTemplates(templates, opts) {
|
|
9
|
+
await mkdir(opts.outputDir, { recursive: true });
|
|
10
|
+
const format = opts.format ?? "one-per-op";
|
|
11
|
+
const written = [];
|
|
12
|
+
if (format === "array") {
|
|
13
|
+
const filePath = join(opts.outputDir, "connector-templates.json");
|
|
14
|
+
await writeFile(filePath, JSON.stringify(templates, null, 2), "utf8");
|
|
15
|
+
written.push(filePath);
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
for (const tpl of templates) {
|
|
19
|
+
const fileName = `${safeName(tpl.id)}.json`;
|
|
20
|
+
const filePath = join(opts.outputDir, fileName);
|
|
21
|
+
await writeFile(filePath, JSON.stringify(tpl, null, 2), "utf8");
|
|
22
|
+
written.push(filePath);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return written;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=write-templates.js.map
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bpmn-sdk/connector-gen",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"import": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"main": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"files": ["dist/**/*.js", "dist/**/*.d.ts"],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc",
|
|
17
|
+
"typecheck": "tsc --noEmit",
|
|
18
|
+
"check": "biome check .",
|
|
19
|
+
"test": "vitest run"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"yaml": "^2.7.0"
|
|
23
|
+
},
|
|
24
|
+
"description": "Generate Camunda REST connector element templates from OpenAPI/Swagger specs",
|
|
25
|
+
"keywords": ["camunda", "connector", "openapi", "swagger", "bpmn", "element-template"],
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "https://github.com/bpmn-sdk/monorepo"
|
|
30
|
+
}
|
|
31
|
+
}
|