@vornrun/connector-sdk 0.7.0-beta.13 → 0.7.0-beta.14
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/{packaging-DFTuP4fr.d.ts → check-Hw1F1a5z.d.ts} +76 -75
- package/dist/{chunk-XOP6JKBX.js → chunk-PMMEWBVK.js} +586 -352
- package/dist/cli.d.ts +3 -1
- package/dist/cli.js +3 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +3 -1
- package/package.json +1 -1
|
@@ -1,6 +1,264 @@
|
|
|
1
|
+
// src/define.ts
|
|
2
|
+
var KEY_PATTERN = /^[a-zA-Z][a-zA-Z0-9_-]*$/;
|
|
3
|
+
var PATH_DATA_PATTERN = /^[MmZzLlHhVvCcSsQqTtAa0-9\s,.\-+eE]+$/;
|
|
4
|
+
var VIEW_BOX_PATTERN = /^-?[\d.]+\s+-?[\d.]+\s+-?[\d.]+\s+-?[\d.]+$/;
|
|
5
|
+
var DEDUPE_STRATEGIES = ["timestamp", "lastItem"];
|
|
6
|
+
var AUTH_RUNGS = ["none", "cli", "key", "oauth"];
|
|
7
|
+
var ABSOLUTE_URL_PATTERN = /^https?:\/\//i;
|
|
8
|
+
var CONFIG_ROOTED_URL_PATTERN = /^\{\{\s*config\./;
|
|
9
|
+
function assertUnique(kind, keys) {
|
|
10
|
+
const seen = /* @__PURE__ */ new Set();
|
|
11
|
+
for (const key of keys) {
|
|
12
|
+
if (seen.has(key)) throw new Error(`Duplicate ${kind} "${key}"`);
|
|
13
|
+
seen.add(key);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function assertAuth(definition) {
|
|
17
|
+
const auth = definition.auth;
|
|
18
|
+
if (!auth) return;
|
|
19
|
+
const id = definition.id;
|
|
20
|
+
if (!AUTH_RUNGS.includes(auth.rung)) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
`Connector ${id} declares unknown auth rung ${JSON.stringify(auth.rung)}; expected ${AUTH_RUNGS.join(", ")}`
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
if (auth.rung === "cli" && !auth.probe?.command?.trim()) {
|
|
26
|
+
throw new Error(`Connector ${id} borrows a CLI login but declares no probe command to ask it`);
|
|
27
|
+
}
|
|
28
|
+
if (auth.rung === "key") {
|
|
29
|
+
const keys = auth.keys ?? [];
|
|
30
|
+
if (keys.length === 0) {
|
|
31
|
+
throw new Error(`Connector ${id} signs in with a key but names no config field holding it`);
|
|
32
|
+
}
|
|
33
|
+
const declared = new Set((definition.config ?? []).map((field) => field.key));
|
|
34
|
+
for (const key of keys) {
|
|
35
|
+
if (!declared.has(key)) {
|
|
36
|
+
throw new Error(`Connector ${id} names auth key "${key}", which is not a config field`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (auth.rung === "none") {
|
|
41
|
+
const secret = (definition.config ?? []).find((field) => field.secret === true);
|
|
42
|
+
if (secret) {
|
|
43
|
+
throw new Error(
|
|
44
|
+
`Connector ${id} claims it needs no sign-in but declares secret field "${secret.key}"`
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function envNameFor(key, explicit) {
|
|
50
|
+
if (explicit) return explicit;
|
|
51
|
+
return key.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[-\s]+/g, "_").toUpperCase();
|
|
52
|
+
}
|
|
53
|
+
function defineConnector(definition) {
|
|
54
|
+
if (!KEY_PATTERN.test(definition.id ?? "")) {
|
|
55
|
+
throw new Error(`Connector id "${definition.id}" must start with a letter and be url-safe`);
|
|
56
|
+
}
|
|
57
|
+
if (!definition.name?.trim()) {
|
|
58
|
+
throw new Error(`Connector ${definition.id} is missing a name`);
|
|
59
|
+
}
|
|
60
|
+
if (definition.icon) {
|
|
61
|
+
const { viewBox, paths } = definition.icon;
|
|
62
|
+
if (!Array.isArray(paths) || paths.length === 0) {
|
|
63
|
+
throw new Error(`Connector ${definition.id} has an icon with no paths`);
|
|
64
|
+
}
|
|
65
|
+
for (const path of paths) {
|
|
66
|
+
if (typeof path !== "string" || !PATH_DATA_PATTERN.test(path)) {
|
|
67
|
+
throw new Error(
|
|
68
|
+
`Connector ${definition.id} has an icon path that is not SVG path data. Only path data is accepted, not markup.`
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (viewBox !== void 0 && !VIEW_BOX_PATTERN.test(viewBox)) {
|
|
73
|
+
throw new Error(`Connector ${definition.id} has an icon viewBox that is not four numbers`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const triggers = definition.triggers ?? [];
|
|
77
|
+
const actions = definition.actions ?? [];
|
|
78
|
+
if (triggers.length === 0 && actions.length === 0) {
|
|
79
|
+
throw new Error(`Connector ${definition.id} declares no triggers and no actions`);
|
|
80
|
+
}
|
|
81
|
+
for (const trigger of triggers) {
|
|
82
|
+
if (!KEY_PATTERN.test(trigger.type ?? "")) {
|
|
83
|
+
throw new Error(`Trigger type "${trigger.type}" must start with a letter and be url-safe`);
|
|
84
|
+
}
|
|
85
|
+
const loose = trigger;
|
|
86
|
+
const declarative = typeof loose.fetch === "function";
|
|
87
|
+
const imperative = typeof loose.poll === "function";
|
|
88
|
+
if (declarative && imperative) {
|
|
89
|
+
throw new Error(`Trigger ${trigger.type} declares both fetch() and poll(); pick one`);
|
|
90
|
+
}
|
|
91
|
+
if (declarative !== (loose.dedupe !== void 0)) {
|
|
92
|
+
throw new Error(
|
|
93
|
+
`Trigger ${trigger.type} needs fetch() and a dedupe strategy together, not one alone`
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
if (loose.dedupe !== void 0 && !DEDUPE_STRATEGIES.includes(loose.dedupe)) {
|
|
97
|
+
throw new Error(
|
|
98
|
+
`Trigger ${trigger.type} has unknown dedupe strategy ${JSON.stringify(loose.dedupe)}; expected ${DEDUPE_STRATEGIES.join(" or ")}`
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
if (loose.poll !== void 0 && !imperative) {
|
|
102
|
+
throw new Error(`Trigger ${trigger.type} declares poll but it is not a function`);
|
|
103
|
+
}
|
|
104
|
+
if (!declarative && !imperative) {
|
|
105
|
+
throw new Error(`Trigger ${trigger.type} is missing a fetch() or poll() implementation`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
for (const action of actions) {
|
|
109
|
+
if (!KEY_PATTERN.test(action.type ?? "")) {
|
|
110
|
+
throw new Error(`Action type "${action.type}" must start with a letter and be url-safe`);
|
|
111
|
+
}
|
|
112
|
+
const loose = action;
|
|
113
|
+
const written = typeof loose.run === "function";
|
|
114
|
+
const declared = loose.request !== void 0;
|
|
115
|
+
if (written && declared) {
|
|
116
|
+
throw new Error(`Action ${action.type} declares both run() and a request; pick one`);
|
|
117
|
+
}
|
|
118
|
+
if (!written && !declared) {
|
|
119
|
+
throw new Error(`Action ${action.type} is missing a run() implementation or a request`);
|
|
120
|
+
}
|
|
121
|
+
if (declared) {
|
|
122
|
+
const request = loose.request;
|
|
123
|
+
if (typeof request?.url !== "string" || request.url.trim() === "") {
|
|
124
|
+
throw new Error(`Action ${action.type} declares a request with no URL`);
|
|
125
|
+
}
|
|
126
|
+
const url = request.url.trim();
|
|
127
|
+
if (!ABSOLUTE_URL_PATTERN.test(url) && !CONFIG_ROOTED_URL_PATTERN.test(url)) {
|
|
128
|
+
throw new Error(
|
|
129
|
+
`Action ${action.type} declares the request URL "${url}", which is neither absolute nor rooted in a {{config.\u2026}} value`
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (!declared && loose.postReceive !== void 0) {
|
|
134
|
+
throw new Error(`Action ${action.type} has postReceive but no request for it to reshape`);
|
|
135
|
+
}
|
|
136
|
+
for (const input of action.inputs ?? []) {
|
|
137
|
+
if (input.loadOptions !== void 0 && definition.options?.[input.loadOptions] === void 0) {
|
|
138
|
+
throw new Error(
|
|
139
|
+
`Action ${action.type} argument "${input.key}" loads options from "${input.loadOptions}", which the connector does not serve`
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
assertUnique(
|
|
145
|
+
"trigger",
|
|
146
|
+
triggers.map((trigger) => trigger.type)
|
|
147
|
+
);
|
|
148
|
+
assertUnique(
|
|
149
|
+
"action",
|
|
150
|
+
actions.map((action) => action.type)
|
|
151
|
+
);
|
|
152
|
+
assertUnique(
|
|
153
|
+
"config field",
|
|
154
|
+
(definition.config ?? []).map((field) => field.key)
|
|
155
|
+
);
|
|
156
|
+
assertAuth(definition);
|
|
157
|
+
return {
|
|
158
|
+
...definition,
|
|
159
|
+
version: definition.version ?? "0.0.0",
|
|
160
|
+
config: definition.config ?? [],
|
|
161
|
+
triggers,
|
|
162
|
+
actions
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
function resolveConfig(connector, env = process.env) {
|
|
166
|
+
const config = {};
|
|
167
|
+
const missing = [];
|
|
168
|
+
for (const field of connector.config) {
|
|
169
|
+
const name = envNameFor(field.key, field.env);
|
|
170
|
+
const value = env[name] ?? field.default;
|
|
171
|
+
if (value === void 0 || value === "") {
|
|
172
|
+
if (field.required) missing.push(`${field.key} (${name})`);
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
config[field.key] = value;
|
|
176
|
+
}
|
|
177
|
+
if (missing.length > 0) {
|
|
178
|
+
throw new Error(
|
|
179
|
+
`Connector ${connector.id} is missing required configuration: ${missing.join(", ")}`
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
return config;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// src/setup.ts
|
|
186
|
+
function pollToolName(triggerType) {
|
|
187
|
+
return `poll_${triggerType}`;
|
|
188
|
+
}
|
|
189
|
+
var MANIFEST_TOOL = "vorn_connector_manifest";
|
|
190
|
+
var PREFLIGHT_TOOL = "vorn_connector_preflight";
|
|
191
|
+
var OPTIONS_TOOL = "vorn_connector_options";
|
|
192
|
+
function connectionSetup(connector, triggerType) {
|
|
193
|
+
const trigger = connector.triggers.find((entry) => entry.type === triggerType);
|
|
194
|
+
if (!trigger) {
|
|
195
|
+
throw new Error(`Connector ${connector.id} has no trigger "${triggerType}"`);
|
|
196
|
+
}
|
|
197
|
+
return {
|
|
198
|
+
connectorId: connector.id,
|
|
199
|
+
triggerType,
|
|
200
|
+
filters: {
|
|
201
|
+
pollTool: pollToolName(triggerType),
|
|
202
|
+
itemsPath: "items",
|
|
203
|
+
idField: "externalId",
|
|
204
|
+
timestampField: "updatedAt",
|
|
205
|
+
titleField: "title",
|
|
206
|
+
urlField: "url",
|
|
207
|
+
cursorArg: "cursor",
|
|
208
|
+
cursorPath: "nextCursor"
|
|
209
|
+
},
|
|
210
|
+
env: connector.config.map((field) => ({
|
|
211
|
+
name: envNameFor(field.key, field.env),
|
|
212
|
+
required: field.required === true,
|
|
213
|
+
secret: field.secret === true,
|
|
214
|
+
...field.description !== void 0 && { description: field.description },
|
|
215
|
+
...field.builderHint !== void 0 && { builderHint: field.builderHint }
|
|
216
|
+
}))
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
function connectorManifest(connector) {
|
|
220
|
+
return {
|
|
221
|
+
id: connector.id,
|
|
222
|
+
name: connector.name,
|
|
223
|
+
version: connector.version,
|
|
224
|
+
...connector.description !== void 0 && { description: connector.description },
|
|
225
|
+
...connector.icon !== void 0 && { icon: connector.icon },
|
|
226
|
+
...connector.auth !== void 0 && { auth: connector.auth },
|
|
227
|
+
triggers: connector.triggers.map((trigger) => ({
|
|
228
|
+
type: trigger.type,
|
|
229
|
+
label: trigger.label,
|
|
230
|
+
...trigger.description !== void 0 && { description: trigger.description },
|
|
231
|
+
// Carried through so the app can seed a connection's status mapping and
|
|
232
|
+
// its polling workflow. Absent when the connector said nothing, which is
|
|
233
|
+
// different from saying there is nothing.
|
|
234
|
+
...trigger.statusMapping !== void 0 && { statusMapping: trigger.statusMapping },
|
|
235
|
+
...trigger.defaultWorkflow !== void 0 && { defaultWorkflow: trigger.defaultWorkflow },
|
|
236
|
+
setup: connectionSetup(connector, trigger.type)
|
|
237
|
+
})),
|
|
238
|
+
actions: connector.actions.map((action) => ({
|
|
239
|
+
type: action.type,
|
|
240
|
+
label: action.label,
|
|
241
|
+
...action.description !== void 0 && { description: action.description },
|
|
242
|
+
inputs: (action.inputs ?? []).map((input) => ({
|
|
243
|
+
key: input.key,
|
|
244
|
+
label: input.label,
|
|
245
|
+
type: input.type ?? "string",
|
|
246
|
+
required: input.required === true,
|
|
247
|
+
...input.options !== void 0 && { options: input.options },
|
|
248
|
+
...input.loadOptions !== void 0 && { loadOptions: input.loadOptions },
|
|
249
|
+
...input.builderHint !== void 0 && { builderHint: input.builderHint }
|
|
250
|
+
})),
|
|
251
|
+
...action.outputs !== void 0 && { outputs: action.outputs },
|
|
252
|
+
...action.sample !== void 0 && { sample: action.sample }
|
|
253
|
+
}))
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
1
257
|
// src/packaging.ts
|
|
2
258
|
import { builtinModules } from "module";
|
|
3
259
|
import { readFileSync } from "fs";
|
|
260
|
+
import { mkdtemp, writeFile } from "fs/promises";
|
|
261
|
+
import { tmpdir } from "os";
|
|
4
262
|
import { dirname, isAbsolute, join, resolve } from "path";
|
|
5
263
|
var MAX_PACK_BYTES = 8 * 1024 * 1024;
|
|
6
264
|
var LIFECYCLE_SCRIPTS = [
|
|
@@ -13,8 +271,8 @@ var LIFECYCLE_SCRIPTS = [
|
|
|
13
271
|
"postpublish"
|
|
14
272
|
];
|
|
15
273
|
var BUILTINS = new Set(builtinModules);
|
|
16
|
-
function finding(code, target, message) {
|
|
17
|
-
return { level
|
|
274
|
+
function finding(code, target, message, level = "error") {
|
|
275
|
+
return { level, code, target, message };
|
|
18
276
|
}
|
|
19
277
|
function lifecycleScriptFindings(pkg) {
|
|
20
278
|
const scripts = pkg?.scripts;
|
|
@@ -45,6 +303,120 @@ function bundleDependencyFindings(external) {
|
|
|
45
303
|
)
|
|
46
304
|
];
|
|
47
305
|
}
|
|
306
|
+
var RELATIVE_REQUIRE = /(?:__)?(?:require(?:\.resolve)?|import)\(\s*(['"])(\.\.?\/[^'"]*)\1\s*\)/y;
|
|
307
|
+
var RELATIVE_CREATE_REQUIRE = /createRequire\([^()]*\)\(\s*(['"])(\.\.?\/[^'"]*)\1\s*\)/y;
|
|
308
|
+
var CALL_WORDS = /* @__PURE__ */ new Set(["require", "__require", "createRequire", "import"]);
|
|
309
|
+
var BEFORE_REGEX = /* @__PURE__ */ new Set(["", ..."(,=:[!&|?{};+-*%~^<>"]);
|
|
310
|
+
var BEFORE_REGEX_WORDS = /* @__PURE__ */ new Set([
|
|
311
|
+
"return",
|
|
312
|
+
"typeof",
|
|
313
|
+
"instanceof",
|
|
314
|
+
"in",
|
|
315
|
+
"of",
|
|
316
|
+
"new",
|
|
317
|
+
"delete",
|
|
318
|
+
"void",
|
|
319
|
+
"case",
|
|
320
|
+
"do",
|
|
321
|
+
"else",
|
|
322
|
+
"yield",
|
|
323
|
+
"await"
|
|
324
|
+
]);
|
|
325
|
+
var WORD = /[\w$]/;
|
|
326
|
+
function endOfQuoted(code, start) {
|
|
327
|
+
const quote2 = code[start];
|
|
328
|
+
let i = start + 1;
|
|
329
|
+
while (i < code.length) {
|
|
330
|
+
if (code[i] === "\\") {
|
|
331
|
+
i += 2;
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
if (code[i] === quote2) return i + 1;
|
|
335
|
+
i += 1;
|
|
336
|
+
}
|
|
337
|
+
return code.length;
|
|
338
|
+
}
|
|
339
|
+
function endOfRegex(code, start) {
|
|
340
|
+
let i = start + 1;
|
|
341
|
+
let inClass = false;
|
|
342
|
+
while (i < code.length) {
|
|
343
|
+
const ch = code[i];
|
|
344
|
+
if (ch === "\\") {
|
|
345
|
+
i += 2;
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
if (ch === "\n") return i;
|
|
349
|
+
if (ch === "[") inClass = true;
|
|
350
|
+
else if (ch === "]") inClass = false;
|
|
351
|
+
else if (ch === "/" && !inClass) return i + 1;
|
|
352
|
+
i += 1;
|
|
353
|
+
}
|
|
354
|
+
return code.length;
|
|
355
|
+
}
|
|
356
|
+
function relativeRuntimeSpecifiers(code) {
|
|
357
|
+
const found = /* @__PURE__ */ new Set();
|
|
358
|
+
let previous = "";
|
|
359
|
+
let previousWord = "";
|
|
360
|
+
let i = 0;
|
|
361
|
+
while (i < code.length) {
|
|
362
|
+
const ch = code[i];
|
|
363
|
+
if (ch === "/" && code[i + 1] === "/") {
|
|
364
|
+
const end = code.indexOf("\n", i);
|
|
365
|
+
i = end === -1 ? code.length : end;
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
if (ch === "/" && code[i + 1] === "*") {
|
|
369
|
+
const end = code.indexOf("*/", i + 2);
|
|
370
|
+
i = end === -1 ? code.length : end + 2;
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
if (ch === '"' || ch === "'" || ch === "`") {
|
|
374
|
+
i = endOfQuoted(code, i);
|
|
375
|
+
previous = ch;
|
|
376
|
+
previousWord = "";
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
if (ch === "/" && (BEFORE_REGEX.has(previous) || BEFORE_REGEX_WORDS.has(previousWord))) {
|
|
380
|
+
i = endOfRegex(code, i);
|
|
381
|
+
previous = "/";
|
|
382
|
+
previousWord = "";
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
if (WORD.test(ch)) {
|
|
386
|
+
const start = i;
|
|
387
|
+
while (i < code.length && WORD.test(code[i])) i += 1;
|
|
388
|
+
const word = code.slice(start, i);
|
|
389
|
+
if (CALL_WORDS.has(word) && code[start - 1] !== ".") {
|
|
390
|
+
for (const pattern of [RELATIVE_REQUIRE, RELATIVE_CREATE_REQUIRE]) {
|
|
391
|
+
pattern.lastIndex = start;
|
|
392
|
+
const match = pattern.exec(code);
|
|
393
|
+
if (match) found.add(match[2]);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
previous = code[i - 1];
|
|
397
|
+
previousWord = word;
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
if (!/\s/.test(ch)) {
|
|
401
|
+
previous = ch;
|
|
402
|
+
previousWord = "";
|
|
403
|
+
}
|
|
404
|
+
i += 1;
|
|
405
|
+
}
|
|
406
|
+
return [...found];
|
|
407
|
+
}
|
|
408
|
+
function bundledRequireFindings(code) {
|
|
409
|
+
const specifiers = relativeRuntimeSpecifiers(code);
|
|
410
|
+
if (specifiers.length === 0) return [];
|
|
411
|
+
return [
|
|
412
|
+
finding(
|
|
413
|
+
"runtime-dependencies",
|
|
414
|
+
"bundle",
|
|
415
|
+
`${specifiers.sort().join(", ")} ${specifiers.length === 1 ? "is" : "are"} required at runtime; a pack is one file, so nothing beside it survives packing`,
|
|
416
|
+
"warn"
|
|
417
|
+
)
|
|
418
|
+
];
|
|
419
|
+
}
|
|
48
420
|
function packageDirFor(resolveDir, entry) {
|
|
49
421
|
const from = resolve(resolveDir);
|
|
50
422
|
if (entry === void 0) return from;
|
|
@@ -72,6 +444,100 @@ function packEntryContents(entry, sdkModule = "@vornrun/connector-sdk") {
|
|
|
72
444
|
""
|
|
73
445
|
].join("\n");
|
|
74
446
|
}
|
|
447
|
+
async function stagePack(connector, code) {
|
|
448
|
+
const dir = await mkdtemp(join(tmpdir(), "vorn-pack-"));
|
|
449
|
+
await writeFile(join(dir, "index.js"), code, "utf8");
|
|
450
|
+
await writeFile(
|
|
451
|
+
join(dir, "manifest.json"),
|
|
452
|
+
`${JSON.stringify(connectorManifest(connector), null, 2)}
|
|
453
|
+
`,
|
|
454
|
+
"utf8"
|
|
455
|
+
);
|
|
456
|
+
return dir;
|
|
457
|
+
}
|
|
458
|
+
var LAUNCH_TIMEOUT_MS = 15e3;
|
|
459
|
+
var LAUNCH_ENV_KEYS = [
|
|
460
|
+
"PATH",
|
|
461
|
+
"HOME",
|
|
462
|
+
"USERPROFILE",
|
|
463
|
+
"HOMEDRIVE",
|
|
464
|
+
"HOMEPATH",
|
|
465
|
+
"APPDATA",
|
|
466
|
+
"LOCALAPPDATA",
|
|
467
|
+
"PROGRAMDATA",
|
|
468
|
+
"PROGRAMFILES",
|
|
469
|
+
"SystemRoot",
|
|
470
|
+
"SYSTEMDRIVE",
|
|
471
|
+
"COMSPEC",
|
|
472
|
+
"PATHEXT",
|
|
473
|
+
"TMPDIR",
|
|
474
|
+
"TEMP",
|
|
475
|
+
"TMP",
|
|
476
|
+
"LANG",
|
|
477
|
+
"LC_ALL",
|
|
478
|
+
"LC_CTYPE",
|
|
479
|
+
"TZ",
|
|
480
|
+
"SHELL",
|
|
481
|
+
"TERM",
|
|
482
|
+
"USER",
|
|
483
|
+
"LOGNAME",
|
|
484
|
+
"NODE_EXTRA_CA_CERTS"
|
|
485
|
+
];
|
|
486
|
+
function launchEnv() {
|
|
487
|
+
const env = {};
|
|
488
|
+
for (const key of LAUNCH_ENV_KEYS) {
|
|
489
|
+
const value = process.env[key];
|
|
490
|
+
if (value !== void 0) env[key] = value;
|
|
491
|
+
}
|
|
492
|
+
return env;
|
|
493
|
+
}
|
|
494
|
+
function errorLine(text) {
|
|
495
|
+
const lines = text.split("\n").map((line) => line.trim()).filter((line) => line !== "");
|
|
496
|
+
return [...lines].reverse().find((line) => /Error\b/.test(line)) ?? lines[lines.length - 1];
|
|
497
|
+
}
|
|
498
|
+
function withTimeout(promise, ms, message) {
|
|
499
|
+
let timer;
|
|
500
|
+
return Promise.race([
|
|
501
|
+
promise.finally(() => clearTimeout(timer)),
|
|
502
|
+
new Promise((_, reject) => {
|
|
503
|
+
timer = setTimeout(() => reject(new Error(message)), ms);
|
|
504
|
+
})
|
|
505
|
+
]);
|
|
506
|
+
}
|
|
507
|
+
async function packLaunchFindings(dir) {
|
|
508
|
+
const { Client } = await import("@modelcontextprotocol/sdk/client/index.js");
|
|
509
|
+
const { StdioClientTransport } = await import("@modelcontextprotocol/sdk/client/stdio.js");
|
|
510
|
+
const transport = new StdioClientTransport({
|
|
511
|
+
command: process.execPath,
|
|
512
|
+
args: ["index.js"],
|
|
513
|
+
cwd: dir,
|
|
514
|
+
env: launchEnv(),
|
|
515
|
+
stderr: "pipe"
|
|
516
|
+
});
|
|
517
|
+
const client = new Client({ name: "vorn-connector-check", version: "1" }, { capabilities: {} });
|
|
518
|
+
let stderr = "";
|
|
519
|
+
transport.stderr?.on("data", (chunk) => {
|
|
520
|
+
stderr += chunk.toString();
|
|
521
|
+
});
|
|
522
|
+
try {
|
|
523
|
+
await withTimeout(
|
|
524
|
+
client.connect(transport),
|
|
525
|
+
LAUNCH_TIMEOUT_MS,
|
|
526
|
+
`did not answer within ${LAUNCH_TIMEOUT_MS / 1e3}s of starting`
|
|
527
|
+
);
|
|
528
|
+
return [];
|
|
529
|
+
} catch (error) {
|
|
530
|
+
const said = error instanceof Error ? error.message : String(error);
|
|
531
|
+
return [
|
|
532
|
+
finding("pack-launch", "bundle", `did not start as a pack: ${errorLine(stderr) ?? said}`)
|
|
533
|
+
];
|
|
534
|
+
} finally {
|
|
535
|
+
await client.close().catch(() => {
|
|
536
|
+
});
|
|
537
|
+
await transport.close().catch(() => {
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
}
|
|
75
541
|
async function esbuildBundle(request) {
|
|
76
542
|
const { build } = await import("esbuild");
|
|
77
543
|
const result = await build({
|
|
@@ -87,7 +553,15 @@ async function esbuildBundle(request) {
|
|
|
87
553
|
format: "esm",
|
|
88
554
|
write: false,
|
|
89
555
|
metafile: true,
|
|
90
|
-
legalComments: "none"
|
|
556
|
+
legalComments: "none",
|
|
557
|
+
// A bundled CommonJS dependency asks for its builtins through esbuild's shim, which throws unless a real require is in scope.
|
|
558
|
+
banner: {
|
|
559
|
+
js: [
|
|
560
|
+
"import { createRequire as __vornCreateRequire } from 'node:module'",
|
|
561
|
+
"const require = __vornCreateRequire(import.meta.url)",
|
|
562
|
+
""
|
|
563
|
+
].join("\n")
|
|
564
|
+
}
|
|
91
565
|
});
|
|
92
566
|
const output = Object.values(result.metafile.outputs)[0];
|
|
93
567
|
return {
|
|
@@ -681,336 +1155,80 @@ function quote(value) {
|
|
|
681
1155
|
}
|
|
682
1156
|
function coerceArg(value, type) {
|
|
683
1157
|
if (typeof value !== "string") return value;
|
|
684
|
-
if (type === "number") {
|
|
685
|
-
const parsed = Number(value);
|
|
686
|
-
if (Number.isNaN(parsed)) throw new Error(`Expected a number, got "${quote(value)}"`);
|
|
687
|
-
return parsed;
|
|
688
|
-
}
|
|
689
|
-
if (type === "boolean") {
|
|
690
|
-
if (value === "true") return true;
|
|
691
|
-
if (value === "false") return false;
|
|
692
|
-
throw new Error(`Expected a boolean, got "${quote(value)}"`);
|
|
693
|
-
}
|
|
694
|
-
if (type === "json") {
|
|
695
|
-
try {
|
|
696
|
-
return JSON.parse(value);
|
|
697
|
-
} catch {
|
|
698
|
-
throw new Error(`Expected JSON, got "${quote(value)}"`);
|
|
699
|
-
}
|
|
700
|
-
}
|
|
701
|
-
return value;
|
|
702
|
-
}
|
|
703
|
-
async function runAction(connector, actionType, args, options = {}) {
|
|
704
|
-
const action = connector.actions.find((entry) => entry.type === actionType);
|
|
705
|
-
if (!action) {
|
|
706
|
-
throw new Error(`Connector ${connector.id} has no action "${actionType}"`);
|
|
707
|
-
}
|
|
708
|
-
const coerced = { ...args };
|
|
709
|
-
for (const input of action.inputs ?? []) {
|
|
710
|
-
const value = coerced[input.key];
|
|
711
|
-
if (value === void 0 || value === "") {
|
|
712
|
-
if (input.required) throw new Error(`Action ${actionType} requires "${input.key}"`);
|
|
713
|
-
delete coerced[input.key];
|
|
714
|
-
continue;
|
|
715
|
-
}
|
|
716
|
-
try {
|
|
717
|
-
coerced[input.key] = coerceArg(value, input.type);
|
|
718
|
-
} catch (error) {
|
|
719
|
-
throw new Error(
|
|
720
|
-
`Action ${actionType} argument "${input.key}": ${error instanceof Error ? error.message : String(error)}`,
|
|
721
|
-
{ cause: error }
|
|
722
|
-
);
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
const config = options.config ?? {};
|
|
726
|
-
const method = (action.request?.method ?? "GET").toUpperCase();
|
|
727
|
-
const retryable = action.idempotent === true || action.request !== void 0 && SAFE_METHODS.has(method);
|
|
728
|
-
const fetchImpl = resilientFetch({
|
|
729
|
-
fetchImpl: options.fetchImpl ?? globalThis.fetch,
|
|
730
|
-
retryable,
|
|
731
|
-
...options.retry !== void 0 && { retry: options.retry },
|
|
732
|
-
...options.sleep !== void 0 && { sleep: options.sleep }
|
|
733
|
-
});
|
|
734
|
-
if (action.request !== void 0) {
|
|
735
|
-
try {
|
|
736
|
-
return await executeRequest(
|
|
737
|
-
action.request,
|
|
738
|
-
action.postReceive,
|
|
739
|
-
{ args: coerced, config },
|
|
740
|
-
{ fetchImpl }
|
|
741
|
-
);
|
|
742
|
-
} catch (error) {
|
|
743
|
-
throw new Error(
|
|
744
|
-
`Action ${actionType}: ${error instanceof Error ? error.message : String(error)}`,
|
|
745
|
-
{ cause: error }
|
|
746
|
-
);
|
|
747
|
-
}
|
|
748
|
-
}
|
|
749
|
-
if (typeof action.run !== "function") {
|
|
750
|
-
throw new Error(`Action ${actionType} has neither a run() implementation nor a request`);
|
|
751
|
-
}
|
|
752
|
-
const output = await action.run(coerced, {
|
|
753
|
-
config,
|
|
754
|
-
now: options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString()),
|
|
755
|
-
fetch: fetchImpl
|
|
756
|
-
});
|
|
757
|
-
return output ?? {};
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
// src/define.ts
|
|
761
|
-
var KEY_PATTERN = /^[a-zA-Z][a-zA-Z0-9_-]*$/;
|
|
762
|
-
var PATH_DATA_PATTERN = /^[MmZzLlHhVvCcSsQqTtAa0-9\s,.\-+eE]+$/;
|
|
763
|
-
var VIEW_BOX_PATTERN = /^-?[\d.]+\s+-?[\d.]+\s+-?[\d.]+\s+-?[\d.]+$/;
|
|
764
|
-
var DEDUPE_STRATEGIES = ["timestamp", "lastItem"];
|
|
765
|
-
var AUTH_RUNGS = ["none", "cli", "key", "oauth"];
|
|
766
|
-
var ABSOLUTE_URL_PATTERN = /^https?:\/\//i;
|
|
767
|
-
var CONFIG_ROOTED_URL_PATTERN = /^\{\{\s*config\./;
|
|
768
|
-
function assertUnique(kind, keys) {
|
|
769
|
-
const seen = /* @__PURE__ */ new Set();
|
|
770
|
-
for (const key of keys) {
|
|
771
|
-
if (seen.has(key)) throw new Error(`Duplicate ${kind} "${key}"`);
|
|
772
|
-
seen.add(key);
|
|
773
|
-
}
|
|
774
|
-
}
|
|
775
|
-
function assertAuth(definition) {
|
|
776
|
-
const auth = definition.auth;
|
|
777
|
-
if (!auth) return;
|
|
778
|
-
const id = definition.id;
|
|
779
|
-
if (!AUTH_RUNGS.includes(auth.rung)) {
|
|
780
|
-
throw new Error(
|
|
781
|
-
`Connector ${id} declares unknown auth rung ${JSON.stringify(auth.rung)}; expected ${AUTH_RUNGS.join(", ")}`
|
|
782
|
-
);
|
|
783
|
-
}
|
|
784
|
-
if (auth.rung === "cli" && !auth.probe?.command?.trim()) {
|
|
785
|
-
throw new Error(`Connector ${id} borrows a CLI login but declares no probe command to ask it`);
|
|
786
|
-
}
|
|
787
|
-
if (auth.rung === "key") {
|
|
788
|
-
const keys = auth.keys ?? [];
|
|
789
|
-
if (keys.length === 0) {
|
|
790
|
-
throw new Error(`Connector ${id} signs in with a key but names no config field holding it`);
|
|
791
|
-
}
|
|
792
|
-
const declared = new Set((definition.config ?? []).map((field) => field.key));
|
|
793
|
-
for (const key of keys) {
|
|
794
|
-
if (!declared.has(key)) {
|
|
795
|
-
throw new Error(`Connector ${id} names auth key "${key}", which is not a config field`);
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
}
|
|
799
|
-
if (auth.rung === "none") {
|
|
800
|
-
const secret = (definition.config ?? []).find((field) => field.secret === true);
|
|
801
|
-
if (secret) {
|
|
802
|
-
throw new Error(
|
|
803
|
-
`Connector ${id} claims it needs no sign-in but declares secret field "${secret.key}"`
|
|
804
|
-
);
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
function envNameFor(key, explicit) {
|
|
809
|
-
if (explicit) return explicit;
|
|
810
|
-
return key.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[-\s]+/g, "_").toUpperCase();
|
|
811
|
-
}
|
|
812
|
-
function defineConnector(definition) {
|
|
813
|
-
if (!KEY_PATTERN.test(definition.id ?? "")) {
|
|
814
|
-
throw new Error(`Connector id "${definition.id}" must start with a letter and be url-safe`);
|
|
815
|
-
}
|
|
816
|
-
if (!definition.name?.trim()) {
|
|
817
|
-
throw new Error(`Connector ${definition.id} is missing a name`);
|
|
818
|
-
}
|
|
819
|
-
if (definition.icon) {
|
|
820
|
-
const { viewBox, paths } = definition.icon;
|
|
821
|
-
if (!Array.isArray(paths) || paths.length === 0) {
|
|
822
|
-
throw new Error(`Connector ${definition.id} has an icon with no paths`);
|
|
823
|
-
}
|
|
824
|
-
for (const path of paths) {
|
|
825
|
-
if (typeof path !== "string" || !PATH_DATA_PATTERN.test(path)) {
|
|
826
|
-
throw new Error(
|
|
827
|
-
`Connector ${definition.id} has an icon path that is not SVG path data. Only path data is accepted, not markup.`
|
|
828
|
-
);
|
|
829
|
-
}
|
|
830
|
-
}
|
|
831
|
-
if (viewBox !== void 0 && !VIEW_BOX_PATTERN.test(viewBox)) {
|
|
832
|
-
throw new Error(`Connector ${definition.id} has an icon viewBox that is not four numbers`);
|
|
833
|
-
}
|
|
1158
|
+
if (type === "number") {
|
|
1159
|
+
const parsed = Number(value);
|
|
1160
|
+
if (Number.isNaN(parsed)) throw new Error(`Expected a number, got "${quote(value)}"`);
|
|
1161
|
+
return parsed;
|
|
834
1162
|
}
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
throw new Error(`
|
|
1163
|
+
if (type === "boolean") {
|
|
1164
|
+
if (value === "true") return true;
|
|
1165
|
+
if (value === "false") return false;
|
|
1166
|
+
throw new Error(`Expected a boolean, got "${quote(value)}"`);
|
|
839
1167
|
}
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
1168
|
+
if (type === "json") {
|
|
1169
|
+
try {
|
|
1170
|
+
return JSON.parse(value);
|
|
1171
|
+
} catch {
|
|
1172
|
+
throw new Error(`Expected JSON, got "${quote(value)}"`);
|
|
843
1173
|
}
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
1174
|
+
}
|
|
1175
|
+
return value;
|
|
1176
|
+
}
|
|
1177
|
+
async function runAction(connector, actionType, args, options = {}) {
|
|
1178
|
+
const action = connector.actions.find((entry) => entry.type === actionType);
|
|
1179
|
+
if (!action) {
|
|
1180
|
+
throw new Error(`Connector ${connector.id} has no action "${actionType}"`);
|
|
1181
|
+
}
|
|
1182
|
+
const coerced = { ...args };
|
|
1183
|
+
for (const input of action.inputs ?? []) {
|
|
1184
|
+
const value = coerced[input.key];
|
|
1185
|
+
if (value === void 0 || value === "") {
|
|
1186
|
+
if (input.required) throw new Error(`Action ${actionType} requires "${input.key}"`);
|
|
1187
|
+
delete coerced[input.key];
|
|
1188
|
+
continue;
|
|
849
1189
|
}
|
|
850
|
-
|
|
1190
|
+
try {
|
|
1191
|
+
coerced[input.key] = coerceArg(value, input.type);
|
|
1192
|
+
} catch (error) {
|
|
851
1193
|
throw new Error(
|
|
852
|
-
`
|
|
1194
|
+
`Action ${actionType} argument "${input.key}": ${error instanceof Error ? error.message : String(error)}`,
|
|
1195
|
+
{ cause: error }
|
|
853
1196
|
);
|
|
854
1197
|
}
|
|
855
|
-
|
|
1198
|
+
}
|
|
1199
|
+
const config = options.config ?? {};
|
|
1200
|
+
const method = (action.request?.method ?? "GET").toUpperCase();
|
|
1201
|
+
const retryable = action.idempotent === true || action.request !== void 0 && SAFE_METHODS.has(method);
|
|
1202
|
+
const fetchImpl = resilientFetch({
|
|
1203
|
+
fetchImpl: options.fetchImpl ?? globalThis.fetch,
|
|
1204
|
+
retryable,
|
|
1205
|
+
...options.retry !== void 0 && { retry: options.retry },
|
|
1206
|
+
...options.sleep !== void 0 && { sleep: options.sleep }
|
|
1207
|
+
});
|
|
1208
|
+
if (action.request !== void 0) {
|
|
1209
|
+
try {
|
|
1210
|
+
return await executeRequest(
|
|
1211
|
+
action.request,
|
|
1212
|
+
action.postReceive,
|
|
1213
|
+
{ args: coerced, config },
|
|
1214
|
+
{ fetchImpl }
|
|
1215
|
+
);
|
|
1216
|
+
} catch (error) {
|
|
856
1217
|
throw new Error(
|
|
857
|
-
`
|
|
1218
|
+
`Action ${actionType}: ${error instanceof Error ? error.message : String(error)}`,
|
|
1219
|
+
{ cause: error }
|
|
858
1220
|
);
|
|
859
1221
|
}
|
|
860
|
-
if (loose.poll !== void 0 && !imperative) {
|
|
861
|
-
throw new Error(`Trigger ${trigger.type} declares poll but it is not a function`);
|
|
862
|
-
}
|
|
863
|
-
if (!declarative && !imperative) {
|
|
864
|
-
throw new Error(`Trigger ${trigger.type} is missing a fetch() or poll() implementation`);
|
|
865
|
-
}
|
|
866
|
-
}
|
|
867
|
-
for (const action of actions) {
|
|
868
|
-
if (!KEY_PATTERN.test(action.type ?? "")) {
|
|
869
|
-
throw new Error(`Action type "${action.type}" must start with a letter and be url-safe`);
|
|
870
|
-
}
|
|
871
|
-
const loose = action;
|
|
872
|
-
const written = typeof loose.run === "function";
|
|
873
|
-
const declared = loose.request !== void 0;
|
|
874
|
-
if (written && declared) {
|
|
875
|
-
throw new Error(`Action ${action.type} declares both run() and a request; pick one`);
|
|
876
|
-
}
|
|
877
|
-
if (!written && !declared) {
|
|
878
|
-
throw new Error(`Action ${action.type} is missing a run() implementation or a request`);
|
|
879
|
-
}
|
|
880
|
-
if (declared) {
|
|
881
|
-
const request = loose.request;
|
|
882
|
-
if (typeof request?.url !== "string" || request.url.trim() === "") {
|
|
883
|
-
throw new Error(`Action ${action.type} declares a request with no URL`);
|
|
884
|
-
}
|
|
885
|
-
const url = request.url.trim();
|
|
886
|
-
if (!ABSOLUTE_URL_PATTERN.test(url) && !CONFIG_ROOTED_URL_PATTERN.test(url)) {
|
|
887
|
-
throw new Error(
|
|
888
|
-
`Action ${action.type} declares the request URL "${url}", which is neither absolute nor rooted in a {{config.\u2026}} value`
|
|
889
|
-
);
|
|
890
|
-
}
|
|
891
|
-
}
|
|
892
|
-
if (!declared && loose.postReceive !== void 0) {
|
|
893
|
-
throw new Error(`Action ${action.type} has postReceive but no request for it to reshape`);
|
|
894
|
-
}
|
|
895
|
-
for (const input of action.inputs ?? []) {
|
|
896
|
-
if (input.loadOptions !== void 0 && definition.options?.[input.loadOptions] === void 0) {
|
|
897
|
-
throw new Error(
|
|
898
|
-
`Action ${action.type} argument "${input.key}" loads options from "${input.loadOptions}", which the connector does not serve`
|
|
899
|
-
);
|
|
900
|
-
}
|
|
901
|
-
}
|
|
902
|
-
}
|
|
903
|
-
assertUnique(
|
|
904
|
-
"trigger",
|
|
905
|
-
triggers.map((trigger) => trigger.type)
|
|
906
|
-
);
|
|
907
|
-
assertUnique(
|
|
908
|
-
"action",
|
|
909
|
-
actions.map((action) => action.type)
|
|
910
|
-
);
|
|
911
|
-
assertUnique(
|
|
912
|
-
"config field",
|
|
913
|
-
(definition.config ?? []).map((field) => field.key)
|
|
914
|
-
);
|
|
915
|
-
assertAuth(definition);
|
|
916
|
-
return {
|
|
917
|
-
...definition,
|
|
918
|
-
version: definition.version ?? "0.0.0",
|
|
919
|
-
config: definition.config ?? [],
|
|
920
|
-
triggers,
|
|
921
|
-
actions
|
|
922
|
-
};
|
|
923
|
-
}
|
|
924
|
-
function resolveConfig(connector, env = process.env) {
|
|
925
|
-
const config = {};
|
|
926
|
-
const missing = [];
|
|
927
|
-
for (const field of connector.config) {
|
|
928
|
-
const name = envNameFor(field.key, field.env);
|
|
929
|
-
const value = env[name] ?? field.default;
|
|
930
|
-
if (value === void 0 || value === "") {
|
|
931
|
-
if (field.required) missing.push(`${field.key} (${name})`);
|
|
932
|
-
continue;
|
|
933
|
-
}
|
|
934
|
-
config[field.key] = value;
|
|
935
|
-
}
|
|
936
|
-
if (missing.length > 0) {
|
|
937
|
-
throw new Error(
|
|
938
|
-
`Connector ${connector.id} is missing required configuration: ${missing.join(", ")}`
|
|
939
|
-
);
|
|
940
1222
|
}
|
|
941
|
-
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
// src/setup.ts
|
|
945
|
-
function pollToolName(triggerType) {
|
|
946
|
-
return `poll_${triggerType}`;
|
|
947
|
-
}
|
|
948
|
-
var MANIFEST_TOOL = "vorn_connector_manifest";
|
|
949
|
-
var PREFLIGHT_TOOL = "vorn_connector_preflight";
|
|
950
|
-
var OPTIONS_TOOL = "vorn_connector_options";
|
|
951
|
-
function connectionSetup(connector, triggerType) {
|
|
952
|
-
const trigger = connector.triggers.find((entry) => entry.type === triggerType);
|
|
953
|
-
if (!trigger) {
|
|
954
|
-
throw new Error(`Connector ${connector.id} has no trigger "${triggerType}"`);
|
|
1223
|
+
if (typeof action.run !== "function") {
|
|
1224
|
+
throw new Error(`Action ${actionType} has neither a run() implementation nor a request`);
|
|
955
1225
|
}
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
idField: "externalId",
|
|
963
|
-
timestampField: "updatedAt",
|
|
964
|
-
titleField: "title",
|
|
965
|
-
urlField: "url",
|
|
966
|
-
cursorArg: "cursor",
|
|
967
|
-
cursorPath: "nextCursor"
|
|
968
|
-
},
|
|
969
|
-
env: connector.config.map((field) => ({
|
|
970
|
-
name: envNameFor(field.key, field.env),
|
|
971
|
-
required: field.required === true,
|
|
972
|
-
secret: field.secret === true,
|
|
973
|
-
...field.description !== void 0 && { description: field.description },
|
|
974
|
-
...field.builderHint !== void 0 && { builderHint: field.builderHint }
|
|
975
|
-
}))
|
|
976
|
-
};
|
|
977
|
-
}
|
|
978
|
-
function connectorManifest(connector) {
|
|
979
|
-
return {
|
|
980
|
-
id: connector.id,
|
|
981
|
-
name: connector.name,
|
|
982
|
-
version: connector.version,
|
|
983
|
-
...connector.description !== void 0 && { description: connector.description },
|
|
984
|
-
...connector.icon !== void 0 && { icon: connector.icon },
|
|
985
|
-
...connector.auth !== void 0 && { auth: connector.auth },
|
|
986
|
-
triggers: connector.triggers.map((trigger) => ({
|
|
987
|
-
type: trigger.type,
|
|
988
|
-
label: trigger.label,
|
|
989
|
-
...trigger.description !== void 0 && { description: trigger.description },
|
|
990
|
-
// Carried through so the app can seed a connection's status mapping and
|
|
991
|
-
// its polling workflow. Absent when the connector said nothing, which is
|
|
992
|
-
// different from saying there is nothing.
|
|
993
|
-
...trigger.statusMapping !== void 0 && { statusMapping: trigger.statusMapping },
|
|
994
|
-
...trigger.defaultWorkflow !== void 0 && { defaultWorkflow: trigger.defaultWorkflow },
|
|
995
|
-
setup: connectionSetup(connector, trigger.type)
|
|
996
|
-
})),
|
|
997
|
-
actions: connector.actions.map((action) => ({
|
|
998
|
-
type: action.type,
|
|
999
|
-
label: action.label,
|
|
1000
|
-
...action.description !== void 0 && { description: action.description },
|
|
1001
|
-
inputs: (action.inputs ?? []).map((input) => ({
|
|
1002
|
-
key: input.key,
|
|
1003
|
-
label: input.label,
|
|
1004
|
-
type: input.type ?? "string",
|
|
1005
|
-
required: input.required === true,
|
|
1006
|
-
...input.options !== void 0 && { options: input.options },
|
|
1007
|
-
...input.loadOptions !== void 0 && { loadOptions: input.loadOptions },
|
|
1008
|
-
...input.builderHint !== void 0 && { builderHint: input.builderHint }
|
|
1009
|
-
})),
|
|
1010
|
-
...action.outputs !== void 0 && { outputs: action.outputs },
|
|
1011
|
-
...action.sample !== void 0 && { sample: action.sample }
|
|
1012
|
-
}))
|
|
1013
|
-
};
|
|
1226
|
+
const output = await action.run(coerced, {
|
|
1227
|
+
config,
|
|
1228
|
+
now: options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString()),
|
|
1229
|
+
fetch: fetchImpl
|
|
1230
|
+
});
|
|
1231
|
+
return output ?? {};
|
|
1014
1232
|
}
|
|
1015
1233
|
|
|
1016
1234
|
// src/harness.ts
|
|
@@ -1111,6 +1329,7 @@ function createConnectorHarness(connector, harnessOptions = {}) {
|
|
|
1111
1329
|
}
|
|
1112
1330
|
|
|
1113
1331
|
// src/check.ts
|
|
1332
|
+
import { rm } from "fs/promises";
|
|
1114
1333
|
var EXECUTABLE_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
1115
1334
|
var CREDENTIAL_NAME = /(secret|token|password|passphrase|api[-_]?key|credential)/i;
|
|
1116
1335
|
var INPUT_TYPES = /* @__PURE__ */ new Set(["string", "number", "boolean", "select", "json"]);
|
|
@@ -1204,7 +1423,13 @@ function actionShapeFindings(action) {
|
|
|
1204
1423
|
}
|
|
1205
1424
|
return found;
|
|
1206
1425
|
}
|
|
1207
|
-
|
|
1426
|
+
function bundles(options) {
|
|
1427
|
+
return Boolean(options.bundle && options.entry !== void 0 && options.packageDir !== void 0);
|
|
1428
|
+
}
|
|
1429
|
+
function launches(options) {
|
|
1430
|
+
return bundles(options) && options.mock === true;
|
|
1431
|
+
}
|
|
1432
|
+
async function packageFindings(connector, options) {
|
|
1208
1433
|
if (options.packageDir === void 0) return [];
|
|
1209
1434
|
const pkg = readNearestPackageJson(packageDirFor(options.packageDir, options.entry));
|
|
1210
1435
|
const found = [...lifecycleScriptFindings(pkg)];
|
|
@@ -1225,7 +1450,15 @@ async function packageFindings(options) {
|
|
|
1225
1450
|
contents: packEntryContents(options.entry),
|
|
1226
1451
|
resolveDir: options.packageDir
|
|
1227
1452
|
});
|
|
1228
|
-
found.push(...bundleDependencyFindings(built.external));
|
|
1453
|
+
found.push(...bundleDependencyFindings(built.external), ...bundledRequireFindings(built.code));
|
|
1454
|
+
if (options.mock) {
|
|
1455
|
+
const dir = await stagePack(connector, built.code);
|
|
1456
|
+
try {
|
|
1457
|
+
found.push(...await packLaunchFindings(dir));
|
|
1458
|
+
} finally {
|
|
1459
|
+
await rm(dir, { recursive: true, force: true });
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1229
1462
|
}
|
|
1230
1463
|
return found;
|
|
1231
1464
|
}
|
|
@@ -1420,7 +1653,7 @@ async function checkConnector(connector, options = {}) {
|
|
|
1420
1653
|
}
|
|
1421
1654
|
found.push(...authFindings(connector));
|
|
1422
1655
|
found.push(...secretFindings(connector));
|
|
1423
|
-
found.push(...await packageFindings(options));
|
|
1656
|
+
found.push(...await packageFindings(connector, options));
|
|
1424
1657
|
found.push(...await mockFindings(connector, options));
|
|
1425
1658
|
found.push(...await liveFindings(connector, options));
|
|
1426
1659
|
const perTrigger = await Promise.all(
|
|
@@ -1516,7 +1749,8 @@ var CHECK_OWNERS = {
|
|
|
1516
1749
|
"mock-network-escape": "mock",
|
|
1517
1750
|
"mock-not-observed": "mock",
|
|
1518
1751
|
"preflight-failed": "live",
|
|
1519
|
-
"live-action-failed": "live"
|
|
1752
|
+
"live-action-failed": "live",
|
|
1753
|
+
"pack-launch": "launch"
|
|
1520
1754
|
};
|
|
1521
1755
|
function checksRun(connector, options) {
|
|
1522
1756
|
const names = ["manifest", "auth"];
|
|
@@ -1524,7 +1758,8 @@ function checksRun(connector, options) {
|
|
|
1524
1758
|
if (connector.actions.length > 0) names.push("actions");
|
|
1525
1759
|
if (connector.triggers.length > 0) names.push("dedupe");
|
|
1526
1760
|
if (options.packageDir !== void 0) names.push("no-lifecycle-scripts", "keywords");
|
|
1527
|
-
if (options
|
|
1761
|
+
if (bundles(options)) names.push("no-runtime-deps");
|
|
1762
|
+
if (launches(options)) names.push("launch");
|
|
1528
1763
|
if (options.mock && connector.actions.length > 0) names.push("mock");
|
|
1529
1764
|
if (options.live && liveExamines(connector)) names.push("live");
|
|
1530
1765
|
return names;
|
|
@@ -1555,8 +1790,7 @@ function formatFindings(findings) {
|
|
|
1555
1790
|
}
|
|
1556
1791
|
|
|
1557
1792
|
// src/pack.ts
|
|
1558
|
-
import {
|
|
1559
|
-
import { tmpdir } from "os";
|
|
1793
|
+
import { mkdir, rm as rm2, stat } from "fs/promises";
|
|
1560
1794
|
import { join as join2, resolve as resolve2 } from "path";
|
|
1561
1795
|
function finding3(code, target, message) {
|
|
1562
1796
|
return { level: "error", code, target, message };
|
|
@@ -1573,29 +1807,24 @@ async function packConnector(connector, options) {
|
|
|
1573
1807
|
const contents = packEntryContents(options.entry, options.sdkModule);
|
|
1574
1808
|
const bundle = options.bundle ?? esbuildBundle;
|
|
1575
1809
|
const built = await bundle({ contents, resolveDir });
|
|
1576
|
-
findings.push(...bundleDependencyFindings(built.external));
|
|
1810
|
+
findings.push(...bundleDependencyFindings(built.external), ...bundledRequireFindings(built.code));
|
|
1577
1811
|
if (findings.some((item) => item.level === "error")) return { findings };
|
|
1578
1812
|
const outDir = resolve2(options.outDir ?? process.cwd());
|
|
1579
1813
|
await mkdir(outDir, { recursive: true });
|
|
1580
1814
|
const file = join2(outDir, packFileName(connector));
|
|
1581
|
-
const staging = await
|
|
1815
|
+
const staging = await stagePack(connector, built.code);
|
|
1582
1816
|
try {
|
|
1583
|
-
await
|
|
1584
|
-
|
|
1585
|
-
join2(staging, "manifest.json"),
|
|
1586
|
-
`${JSON.stringify(connectorManifest(connector), null, 2)}
|
|
1587
|
-
`,
|
|
1588
|
-
"utf8"
|
|
1589
|
-
);
|
|
1817
|
+
findings.push(...await (options.launch ?? packLaunchFindings)(staging));
|
|
1818
|
+
if (findings.some((item) => item.level === "error")) return { findings };
|
|
1590
1819
|
const { create } = await import("tar");
|
|
1591
1820
|
await create({ gzip: true, file, cwd: staging }, ["manifest.json", "index.js"]);
|
|
1592
1821
|
} finally {
|
|
1593
|
-
await
|
|
1822
|
+
await rm2(staging, { recursive: true, force: true });
|
|
1594
1823
|
}
|
|
1595
1824
|
const bytes = (await stat(file)).size;
|
|
1596
1825
|
const maxBytes = options.maxBytes ?? MAX_PACK_BYTES;
|
|
1597
1826
|
if (bytes > maxBytes) {
|
|
1598
|
-
await
|
|
1827
|
+
await rm2(file, { force: true });
|
|
1599
1828
|
return {
|
|
1600
1829
|
findings: [
|
|
1601
1830
|
...findings,
|
|
@@ -1663,7 +1892,7 @@ function packageJson(id, description, inRepo) {
|
|
|
1663
1892
|
}
|
|
1664
1893
|
});
|
|
1665
1894
|
}
|
|
1666
|
-
function tsconfig() {
|
|
1895
|
+
function tsconfig(inRepo) {
|
|
1667
1896
|
return jsonFile({
|
|
1668
1897
|
compilerOptions: {
|
|
1669
1898
|
target: "ES2022",
|
|
@@ -1676,10 +1905,11 @@ function tsconfig() {
|
|
|
1676
1905
|
skipLibCheck: true,
|
|
1677
1906
|
types: ["node"],
|
|
1678
1907
|
noEmit: true,
|
|
1908
|
+
resolveJsonModule: true,
|
|
1679
1909
|
ignoreDeprecations: "6.0",
|
|
1680
1910
|
allowImportingTsExtensions: true
|
|
1681
1911
|
},
|
|
1682
|
-
include: ["src/**/*", "vitest.config.ts"]
|
|
1912
|
+
include: ["src/**/*", ...inRepo ? ["vitest.config.ts"] : []]
|
|
1683
1913
|
});
|
|
1684
1914
|
}
|
|
1685
1915
|
function tsupConfig() {
|
|
@@ -1712,12 +1942,14 @@ function changelog() {
|
|
|
1712
1942
|
}
|
|
1713
1943
|
function connectorSource(id, name, description) {
|
|
1714
1944
|
return `import { defineConnector } from '@vornrun/connector-sdk'
|
|
1945
|
+
// Bundled at build time: a pack is one file, so a version read from disk is not there to read.
|
|
1946
|
+
import pkg from '../package.json'
|
|
1715
1947
|
|
|
1716
1948
|
export const connector = defineConnector({
|
|
1717
1949
|
id: ${JSON.stringify(id)},
|
|
1718
1950
|
name: ${JSON.stringify(name)},
|
|
1719
1951
|
description: ${JSON.stringify(description)},
|
|
1720
|
-
version:
|
|
1952
|
+
version: pkg.version,
|
|
1721
1953
|
// Prefer a login the machine already has: { rung: 'cli', probe: { command: 'tool', args: ['auth', 'status'] } }
|
|
1722
1954
|
auth: { rung: 'key', keys: ['apiToken'] },
|
|
1723
1955
|
config: [
|
|
@@ -1971,9 +2203,10 @@ function scaffoldFiles(options) {
|
|
|
1971
2203
|
{ path: "src/connector.test.ts", contents: testSource(name) },
|
|
1972
2204
|
{ path: "src/entry.test.ts", contents: entryTestSource() },
|
|
1973
2205
|
{ path: "README.md", contents: readme(options.id, name, description) },
|
|
2206
|
+
// Everywhere: the generated source imports its package.json, which needs resolveJsonModule to compile.
|
|
2207
|
+
{ path: "tsconfig.json", contents: tsconfig(inRepo) },
|
|
1974
2208
|
...inRepo ? [
|
|
1975
2209
|
{ path: "CHANGELOG.md", contents: changelog() },
|
|
1976
|
-
{ path: "tsconfig.json", contents: tsconfig() },
|
|
1977
2210
|
{ path: "tsup.config.ts", contents: tsupConfig() },
|
|
1978
2211
|
{ path: "vitest.config.ts", contents: vitestConfig() }
|
|
1979
2212
|
] : []
|
|
@@ -2170,9 +2403,19 @@ async function serveConnector(connector, options = {}) {
|
|
|
2170
2403
|
}
|
|
2171
2404
|
|
|
2172
2405
|
export {
|
|
2406
|
+
envNameFor,
|
|
2407
|
+
defineConnector,
|
|
2408
|
+
resolveConfig,
|
|
2409
|
+
pollToolName,
|
|
2410
|
+
MANIFEST_TOOL,
|
|
2411
|
+
PREFLIGHT_TOOL,
|
|
2412
|
+
OPTIONS_TOOL,
|
|
2413
|
+
connectionSetup,
|
|
2414
|
+
connectorManifest,
|
|
2173
2415
|
MAX_PACK_BYTES,
|
|
2174
2416
|
lifecycleScriptFindings,
|
|
2175
2417
|
bundleDependencyFindings,
|
|
2418
|
+
bundledRequireFindings,
|
|
2176
2419
|
readNearestPackageJson,
|
|
2177
2420
|
esbuildBundle,
|
|
2178
2421
|
normalizeItem,
|
|
@@ -2194,15 +2437,6 @@ export {
|
|
|
2194
2437
|
drainPoll,
|
|
2195
2438
|
runOptions,
|
|
2196
2439
|
runAction,
|
|
2197
|
-
envNameFor,
|
|
2198
|
-
defineConnector,
|
|
2199
|
-
resolveConfig,
|
|
2200
|
-
pollToolName,
|
|
2201
|
-
MANIFEST_TOOL,
|
|
2202
|
-
PREFLIGHT_TOOL,
|
|
2203
|
-
OPTIONS_TOOL,
|
|
2204
|
-
connectionSetup,
|
|
2205
|
-
connectorManifest,
|
|
2206
2440
|
MockRouteMissError,
|
|
2207
2441
|
escapedMockHttp,
|
|
2208
2442
|
withMockHttp,
|