@revenexx/integrations-node-sdk 1.9.1 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/branches.d.ts +47 -0
- package/dist/chunk-XO73F62E.js +122 -0
- package/dist/cli.cjs +38 -0
- package/dist/cli.js +5 -1
- package/dist/fetch.d.ts +22 -0
- package/dist/index.cjs +129 -7
- package/dist/index.d.ts +1 -0
- package/dist/index.js +71 -8
- package/dist/types.d.ts +8 -0
- package/package.json +1 -1
- package/dist/chunk-5BCAXRU3.js +0 -59
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { INode, INodeDescription, INodeResult, IOutputPort } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* A node's output ports are alternatives: exactly one fires. A node with a
|
|
4
|
+
* single port has nothing to choose between, so its result may leave `branch`
|
|
5
|
+
* out; a node with several must say which one it took, because the worker
|
|
6
|
+
* otherwise activates every outgoing connection (PO-751).
|
|
7
|
+
*
|
|
8
|
+
* A node counts as multi-port when EITHER holds:
|
|
9
|
+
* - more than one port is declared (an `error` port counts: `out` + `error` is two);
|
|
10
|
+
* - a port is marked `resolveOutputs`, because that set is only known while the
|
|
11
|
+
* author edits and may hold several.
|
|
12
|
+
*
|
|
13
|
+
* A single port is one way out however it is declared — of kind `branch`, or named
|
|
14
|
+
* from a setting by `sourceFromConfig` (one name, with its `fallback`).
|
|
15
|
+
*/
|
|
16
|
+
export declare function isMultiPort(outputs: readonly IOutputPort[] | undefined): boolean;
|
|
17
|
+
/** A result that names its branch — what every multi-port node returns. */
|
|
18
|
+
export interface IBranchedNodeResult extends INodeResult {
|
|
19
|
+
branch: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Declaration problems of a node's output ports that the manifest build can see.
|
|
23
|
+
* Returns one sentence per problem, empty when there is none.
|
|
24
|
+
*
|
|
25
|
+
* A node with several ports must give each fixed port a unique `name`, since `branch`
|
|
26
|
+
* names one of them, and a generated port's `fallback` name must not repeat a fixed
|
|
27
|
+
* one. (Whether `execute` really returns a `branch` cannot be read from a declaration
|
|
28
|
+
* — see {@link assertBranchNamed}.)
|
|
29
|
+
*/
|
|
30
|
+
export declare function outputProblems(description: INodeDescription): string[];
|
|
31
|
+
/**
|
|
32
|
+
* The warnings `rvnxx-nodes manifest` prints for a package's nodes: every
|
|
33
|
+
* {@link outputProblems} finding, worded for the log. In 1.x they are warnings and the
|
|
34
|
+
* build continues; the next major turns them into errors.
|
|
35
|
+
*/
|
|
36
|
+
export declare function outputWarnings(nodes: readonly INode[]): string[];
|
|
37
|
+
/**
|
|
38
|
+
* Check a result against the node's declaration. For a node's own tests:
|
|
39
|
+
* `assertBranchNamed(node, await node.execute(ctx, inputs), config)`.
|
|
40
|
+
*
|
|
41
|
+
* Throws when a multi-port node returned no `branch`, and when the branch is none of
|
|
42
|
+
* the names the node can produce: a fixed name is always accepted; beyond that, a
|
|
43
|
+
* `resolveOutputs` port accepts any name (it is unknown here), and a `sourceFromConfig`
|
|
44
|
+
* port accepts its fallback name and — when `config` is passed — the value it holds
|
|
45
|
+
* (with no `config`, any name). A single-port node may leave `branch` out.
|
|
46
|
+
*/
|
|
47
|
+
export declare function assertBranchNamed(node: INode | INodeDescription, result: INodeResult, config?: Record<string, unknown>): void;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// src/branches.ts
|
|
2
|
+
function isMultiPort(outputs) {
|
|
3
|
+
if (!outputs) return false;
|
|
4
|
+
return outputs.length > 1 || outputs.some((p) => p.resolveOutputs === true);
|
|
5
|
+
}
|
|
6
|
+
var isGenerated = (p) => p.sourceFromConfig !== void 0 || p.resolveOutputs === true;
|
|
7
|
+
function outputProblems(description) {
|
|
8
|
+
const outputs = description.outputs ?? [];
|
|
9
|
+
if (!isMultiPort(outputs)) return [];
|
|
10
|
+
const problems = [];
|
|
11
|
+
const seen = /* @__PURE__ */ new Set();
|
|
12
|
+
for (const [i, port] of outputs.entries()) {
|
|
13
|
+
if (isGenerated(port)) continue;
|
|
14
|
+
if (port.name === void 0 || port.name === "") {
|
|
15
|
+
problems.push(
|
|
16
|
+
`${description.slug}: output ${i + 1} has no name, so a branch cannot choose it \u2014 a node with several outputs must name each one.`
|
|
17
|
+
);
|
|
18
|
+
} else if (seen.has(port.name)) {
|
|
19
|
+
problems.push(`${description.slug}: output name "${port.name}" is declared twice.`);
|
|
20
|
+
} else {
|
|
21
|
+
seen.add(port.name);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
for (const port of outputs) {
|
|
25
|
+
const fb = port.fallback?.name;
|
|
26
|
+
if (isGenerated(port) && fb !== void 0 && seen.has(fb)) {
|
|
27
|
+
problems.push(`${description.slug}: the fallback name "${fb}" is also the name of a fixed output, so a branch "${fb}" is ambiguous.`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return problems;
|
|
31
|
+
}
|
|
32
|
+
function outputWarnings(nodes) {
|
|
33
|
+
return nodes.flatMap((n) => outputProblems(n.description));
|
|
34
|
+
}
|
|
35
|
+
function assertBranchNamed(node, result, config) {
|
|
36
|
+
const description = "execute" in node ? node.description : node;
|
|
37
|
+
const outputs = description.outputs ?? [];
|
|
38
|
+
if (!isMultiPort(outputs)) return;
|
|
39
|
+
const branch = result.branch;
|
|
40
|
+
if (typeof branch !== "string" || branch === "") {
|
|
41
|
+
throw new Error(
|
|
42
|
+
`${description.slug} has several outputs, so its result must name the branch it took; without it every output fires.`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
if (outputs.some((p) => !isGenerated(p) && p.name === branch)) return;
|
|
46
|
+
const producible = outputs.some((p) => {
|
|
47
|
+
if (p.resolveOutputs === true) return true;
|
|
48
|
+
if (p.sourceFromConfig === void 0) return false;
|
|
49
|
+
if (p.fallback?.name === branch) return true;
|
|
50
|
+
return config === void 0 || config[p.sourceFromConfig] === branch;
|
|
51
|
+
});
|
|
52
|
+
if (!producible) {
|
|
53
|
+
const names = outputs.map((p) => p.name ?? p.fallback?.name).filter((n) => n);
|
|
54
|
+
throw new Error(
|
|
55
|
+
`${description.slug} returned branch "${branch}", which none of its outputs can produce (${names.join(", ")}).`
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// src/extract.ts
|
|
61
|
+
function extractManifest(node) {
|
|
62
|
+
return node.description;
|
|
63
|
+
}
|
|
64
|
+
function extractManifests(nodes) {
|
|
65
|
+
return nodes.map(extractManifest);
|
|
66
|
+
}
|
|
67
|
+
function extractCredentialManifest(credential) {
|
|
68
|
+
return credential.description;
|
|
69
|
+
}
|
|
70
|
+
function extractCredentialManifests(credentials) {
|
|
71
|
+
return credentials.map(extractCredentialManifest);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// src/manifest.ts
|
|
75
|
+
var MANIFEST_VERSION = "v0-draft";
|
|
76
|
+
var PACKAGE_ICON = /^lucide:[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
77
|
+
var PHP_TRIM = /^[ \t\n\r\0\v]+|[ \t\n\r\0\v]+$/g;
|
|
78
|
+
function packageIconProblem(icon) {
|
|
79
|
+
if (icon === void 0 || PACKAGE_ICON.test(icon)) return void 0;
|
|
80
|
+
return `package.json "revenexx.icon" must be a Lucide icon name such as "lucide:bell", got "${icon}".`;
|
|
81
|
+
}
|
|
82
|
+
function parsePackageMeta(raw) {
|
|
83
|
+
const obj = raw && typeof raw === "object" ? raw : {};
|
|
84
|
+
const str = (v) => typeof v === "string" ? v.trim() : "";
|
|
85
|
+
const revenexx = obj.revenexx && typeof obj.revenexx === "object" ? obj.revenexx : {};
|
|
86
|
+
const displayName = str(revenexx.displayName);
|
|
87
|
+
const icon = typeof revenexx.icon === "string" ? revenexx.icon.replace(PHP_TRIM, "") : "";
|
|
88
|
+
return {
|
|
89
|
+
name: str(obj.name),
|
|
90
|
+
version: str(obj.version),
|
|
91
|
+
displayName: displayName !== "" ? displayName : void 0,
|
|
92
|
+
icon: icon !== "" ? icon : void 0
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function buildManifest(nodes, credentials = [], templates = []) {
|
|
96
|
+
const manifest = {
|
|
97
|
+
manifestVersion: MANIFEST_VERSION,
|
|
98
|
+
nodes: extractManifests(nodes)
|
|
99
|
+
};
|
|
100
|
+
if (credentials.length > 0) {
|
|
101
|
+
manifest.credentials = extractCredentialManifests(credentials);
|
|
102
|
+
}
|
|
103
|
+
if (templates.length > 0) {
|
|
104
|
+
manifest.templates = templates;
|
|
105
|
+
}
|
|
106
|
+
return manifest;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export {
|
|
110
|
+
isMultiPort,
|
|
111
|
+
outputProblems,
|
|
112
|
+
outputWarnings,
|
|
113
|
+
assertBranchNamed,
|
|
114
|
+
extractManifest,
|
|
115
|
+
extractManifests,
|
|
116
|
+
extractCredentialManifest,
|
|
117
|
+
extractCredentialManifests,
|
|
118
|
+
MANIFEST_VERSION,
|
|
119
|
+
packageIconProblem,
|
|
120
|
+
parsePackageMeta,
|
|
121
|
+
buildManifest
|
|
122
|
+
};
|
package/dist/cli.cjs
CHANGED
|
@@ -28,6 +28,41 @@ var fs2 = __toESM(require("fs"), 1);
|
|
|
28
28
|
var import_node_path2 = require("path");
|
|
29
29
|
var import_node_url = require("url");
|
|
30
30
|
|
|
31
|
+
// src/branches.ts
|
|
32
|
+
function isMultiPort(outputs) {
|
|
33
|
+
if (!outputs) return false;
|
|
34
|
+
return outputs.length > 1 || outputs.some((p) => p.resolveOutputs === true);
|
|
35
|
+
}
|
|
36
|
+
var isGenerated = (p) => p.sourceFromConfig !== void 0 || p.resolveOutputs === true;
|
|
37
|
+
function outputProblems(description) {
|
|
38
|
+
const outputs = description.outputs ?? [];
|
|
39
|
+
if (!isMultiPort(outputs)) return [];
|
|
40
|
+
const problems = [];
|
|
41
|
+
const seen = /* @__PURE__ */ new Set();
|
|
42
|
+
for (const [i, port] of outputs.entries()) {
|
|
43
|
+
if (isGenerated(port)) continue;
|
|
44
|
+
if (port.name === void 0 || port.name === "") {
|
|
45
|
+
problems.push(
|
|
46
|
+
`${description.slug}: output ${i + 1} has no name, so a branch cannot choose it \u2014 a node with several outputs must name each one.`
|
|
47
|
+
);
|
|
48
|
+
} else if (seen.has(port.name)) {
|
|
49
|
+
problems.push(`${description.slug}: output name "${port.name}" is declared twice.`);
|
|
50
|
+
} else {
|
|
51
|
+
seen.add(port.name);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
for (const port of outputs) {
|
|
55
|
+
const fb = port.fallback?.name;
|
|
56
|
+
if (isGenerated(port) && fb !== void 0 && seen.has(fb)) {
|
|
57
|
+
problems.push(`${description.slug}: the fallback name "${fb}" is also the name of a fixed output, so a branch "${fb}" is ambiguous.`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return problems;
|
|
61
|
+
}
|
|
62
|
+
function outputWarnings(nodes) {
|
|
63
|
+
return nodes.flatMap((n) => outputProblems(n.description));
|
|
64
|
+
}
|
|
65
|
+
|
|
31
66
|
// src/images.ts
|
|
32
67
|
var fs = __toESM(require("fs"), 1);
|
|
33
68
|
var import_node_path = require("path");
|
|
@@ -174,6 +209,9 @@ async function runManifest() {
|
|
|
174
209
|
'\u26A0 package.json has no "revenexx.displayName" \u2014 the node palette will fall back to the raw package name.'
|
|
175
210
|
);
|
|
176
211
|
}
|
|
212
|
+
for (const w of outputWarnings(mod.NODES)) {
|
|
213
|
+
console.warn(`\u26A0 ${w} This will be an error in the next major version.`);
|
|
214
|
+
}
|
|
177
215
|
const manifest = buildManifest(mod.NODES, credentials, templates);
|
|
178
216
|
const outDir = (0, import_node_path2.resolve)(projectRoot, "dist");
|
|
179
217
|
const outFile = (0, import_node_path2.resolve)(outDir, "manifest.json");
|
package/dist/cli.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
buildManifest,
|
|
4
|
+
outputWarnings,
|
|
4
5
|
packageIconProblem,
|
|
5
6
|
parsePackageMeta
|
|
6
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-XO73F62E.js";
|
|
7
8
|
|
|
8
9
|
// src/cli.ts
|
|
9
10
|
import * as fs2 from "fs";
|
|
@@ -107,6 +108,9 @@ async function runManifest() {
|
|
|
107
108
|
'\u26A0 package.json has no "revenexx.displayName" \u2014 the node palette will fall back to the raw package name.'
|
|
108
109
|
);
|
|
109
110
|
}
|
|
111
|
+
for (const w of outputWarnings(mod.NODES)) {
|
|
112
|
+
console.warn(`\u26A0 ${w} This will be an error in the next major version.`);
|
|
113
|
+
}
|
|
110
114
|
const manifest = buildManifest(mod.NODES, credentials, templates);
|
|
111
115
|
const outDir = resolve2(projectRoot, "dist");
|
|
112
116
|
const outFile = resolve2(outDir, "manifest.json");
|
package/dist/fetch.d.ts
CHANGED
|
@@ -37,11 +37,33 @@ export interface SafeFetchRetry {
|
|
|
37
37
|
attempts: number;
|
|
38
38
|
delayMs?: number;
|
|
39
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Binds a request to the one origin its credential belongs to (PO-688).
|
|
42
|
+
*
|
|
43
|
+
* Setting it declares the whole request secret-bearing — headers, query, path
|
|
44
|
+
* and body alike — so nothing has to name which part carries the secret: a
|
|
45
|
+
* request to any other origin is refused with `CREDENTIAL_ORIGIN`, on the first
|
|
46
|
+
* hop and on every redirect, before that origin is looked up or sent anything.
|
|
47
|
+
*/
|
|
48
|
+
export interface SafeFetchCredential {
|
|
49
|
+
/** The only origin this request may reach, e.g. `https://api.pipedrive.com`. */
|
|
50
|
+
origin: string | URL;
|
|
51
|
+
/**
|
|
52
|
+
* What a redirect off that origin does. `'refuse'` (the default) fails the
|
|
53
|
+
* call. `'strip'` follows it the way an unbound request would — every header
|
|
54
|
+
* not known to be harmless dropped, a body refused — for a download that is
|
|
55
|
+
* redirected to a CDN or a signed storage link. A secret in the query or the
|
|
56
|
+
* path is not protected on such a hop.
|
|
57
|
+
*/
|
|
58
|
+
crossOriginRedirect?: 'refuse' | 'strip';
|
|
59
|
+
}
|
|
40
60
|
export interface SafeFetchOptions extends RequestInit {
|
|
41
61
|
timeoutMs?: number;
|
|
42
62
|
/** Pass `ctx.signal` so the workflow engine can cancel the request. */
|
|
43
63
|
signal?: AbortSignal;
|
|
44
64
|
retry?: SafeFetchRetry;
|
|
65
|
+
/** Bind the request to the origin of the credential it carries. */
|
|
66
|
+
credential?: SafeFetchCredential;
|
|
45
67
|
}
|
|
46
68
|
export declare function safeFetch(url: string | URL, options?: SafeFetchOptions): Promise<Response>;
|
|
47
69
|
/**
|
package/dist/index.cjs
CHANGED
|
@@ -50,6 +50,7 @@ __export(index_exports, {
|
|
|
50
50
|
RetryableError: () => RetryableError,
|
|
51
51
|
SimpleValueCredential: () => SimpleValueCredential,
|
|
52
52
|
VALUELESS_OPERATORS: () => VALUELESS_OPERATORS,
|
|
53
|
+
assertBranchNamed: () => assertBranchNamed,
|
|
53
54
|
assertPublicUrl: () => assertPublicUrl,
|
|
54
55
|
backoffDelay: () => backoffDelay,
|
|
55
56
|
buildManifest: () => buildManifest,
|
|
@@ -64,12 +65,15 @@ __export(index_exports, {
|
|
|
64
65
|
headerCrossesOrigin: () => headerCrossesOrigin,
|
|
65
66
|
httpErrorResult: () => httpErrorResult,
|
|
66
67
|
isBlockedAddress: () => isBlockedAddress,
|
|
68
|
+
isMultiPort: () => isMultiPort,
|
|
67
69
|
isNodeWithIteration: () => isNodeWithIteration,
|
|
68
70
|
isOAuthAuthorizeCredential: () => isOAuthAuthorizeCredential,
|
|
69
71
|
isOperator: () => isOperator,
|
|
70
72
|
maxBytesConfigField: () => maxBytesConfigField,
|
|
71
73
|
normalizeCredentialType: () => normalizeCredentialType,
|
|
72
74
|
normalizeLocalized: () => normalizeLocalized,
|
|
75
|
+
outputProblems: () => outputProblems,
|
|
76
|
+
outputWarnings: () => outputWarnings,
|
|
73
77
|
packageIconProblem: () => packageIconProblem,
|
|
74
78
|
parsePackageMeta: () => parsePackageMeta,
|
|
75
79
|
readArrayBuffer: () => readArrayBuffer,
|
|
@@ -294,6 +298,65 @@ function extractCredentialManifests(credentials) {
|
|
|
294
298
|
return credentials.map(extractCredentialManifest);
|
|
295
299
|
}
|
|
296
300
|
|
|
301
|
+
// src/branches.ts
|
|
302
|
+
function isMultiPort(outputs) {
|
|
303
|
+
if (!outputs) return false;
|
|
304
|
+
return outputs.length > 1 || outputs.some((p) => p.resolveOutputs === true);
|
|
305
|
+
}
|
|
306
|
+
var isGenerated = (p) => p.sourceFromConfig !== void 0 || p.resolveOutputs === true;
|
|
307
|
+
function outputProblems(description) {
|
|
308
|
+
const outputs = description.outputs ?? [];
|
|
309
|
+
if (!isMultiPort(outputs)) return [];
|
|
310
|
+
const problems = [];
|
|
311
|
+
const seen = /* @__PURE__ */ new Set();
|
|
312
|
+
for (const [i, port] of outputs.entries()) {
|
|
313
|
+
if (isGenerated(port)) continue;
|
|
314
|
+
if (port.name === void 0 || port.name === "") {
|
|
315
|
+
problems.push(
|
|
316
|
+
`${description.slug}: output ${i + 1} has no name, so a branch cannot choose it \u2014 a node with several outputs must name each one.`
|
|
317
|
+
);
|
|
318
|
+
} else if (seen.has(port.name)) {
|
|
319
|
+
problems.push(`${description.slug}: output name "${port.name}" is declared twice.`);
|
|
320
|
+
} else {
|
|
321
|
+
seen.add(port.name);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
for (const port of outputs) {
|
|
325
|
+
const fb = port.fallback?.name;
|
|
326
|
+
if (isGenerated(port) && fb !== void 0 && seen.has(fb)) {
|
|
327
|
+
problems.push(`${description.slug}: the fallback name "${fb}" is also the name of a fixed output, so a branch "${fb}" is ambiguous.`);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return problems;
|
|
331
|
+
}
|
|
332
|
+
function outputWarnings(nodes) {
|
|
333
|
+
return nodes.flatMap((n) => outputProblems(n.description));
|
|
334
|
+
}
|
|
335
|
+
function assertBranchNamed(node, result, config) {
|
|
336
|
+
const description = "execute" in node ? node.description : node;
|
|
337
|
+
const outputs = description.outputs ?? [];
|
|
338
|
+
if (!isMultiPort(outputs)) return;
|
|
339
|
+
const branch = result.branch;
|
|
340
|
+
if (typeof branch !== "string" || branch === "") {
|
|
341
|
+
throw new Error(
|
|
342
|
+
`${description.slug} has several outputs, so its result must name the branch it took; without it every output fires.`
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
if (outputs.some((p) => !isGenerated(p) && p.name === branch)) return;
|
|
346
|
+
const producible = outputs.some((p) => {
|
|
347
|
+
if (p.resolveOutputs === true) return true;
|
|
348
|
+
if (p.sourceFromConfig === void 0) return false;
|
|
349
|
+
if (p.fallback?.name === branch) return true;
|
|
350
|
+
return config === void 0 || config[p.sourceFromConfig] === branch;
|
|
351
|
+
});
|
|
352
|
+
if (!producible) {
|
|
353
|
+
const names = outputs.map((p) => p.name ?? p.fallback?.name).filter((n) => n);
|
|
354
|
+
throw new Error(
|
|
355
|
+
`${description.slug} returned branch "${branch}", which none of its outputs can produce (${names.join(", ")}).`
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
297
360
|
// src/ssrf.ts
|
|
298
361
|
var import_node_diagnostics_channel = require("diagnostics_channel");
|
|
299
362
|
var import_node_net = require("net");
|
|
@@ -542,6 +605,26 @@ function clampResponseBytes(maxBytes) {
|
|
|
542
605
|
if (!Number.isFinite(maxBytes) || maxBytes < 1) return MAX_RESPONSE_BYTES;
|
|
543
606
|
return Math.min(maxBytes, MAX_RESPONSE_BYTES);
|
|
544
607
|
}
|
|
608
|
+
function boundOrigin(credential) {
|
|
609
|
+
let parsed;
|
|
610
|
+
try {
|
|
611
|
+
parsed = new URL(credential.origin);
|
|
612
|
+
} catch {
|
|
613
|
+
parsed = void 0;
|
|
614
|
+
}
|
|
615
|
+
if (!parsed || parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
616
|
+
throw new NodeError("CREDENTIAL_ORIGIN", "The origin a credential is bound to is not an http(s) address", { status: 0 });
|
|
617
|
+
}
|
|
618
|
+
return parsed.origin;
|
|
619
|
+
}
|
|
620
|
+
function credentialOriginRefusal(target, bound, hop) {
|
|
621
|
+
const what = hop === void 0 ? "a request" : `a ${hop} redirect`;
|
|
622
|
+
return new NodeError(
|
|
623
|
+
"CREDENTIAL_ORIGIN",
|
|
624
|
+
`Refused ${what} to ${target}: the credential it carries is bound to ${bound}`,
|
|
625
|
+
{ status: 0 }
|
|
626
|
+
);
|
|
627
|
+
}
|
|
545
628
|
async function timedFetch(url, fetchOptions, ctxSignal, effectiveMs) {
|
|
546
629
|
const ac = new AbortController();
|
|
547
630
|
const timer = setTimeout(() => ac.abort(), effectiveMs);
|
|
@@ -581,7 +664,16 @@ function hasContent(body) {
|
|
|
581
664
|
if (body instanceof URLSearchParams) return body.toString().length > 0;
|
|
582
665
|
return true;
|
|
583
666
|
}
|
|
584
|
-
|
|
667
|
+
function refuseUserinfo(url, status = 0) {
|
|
668
|
+
if (url.username !== "" || url.password !== "") {
|
|
669
|
+
throw new NodeError(
|
|
670
|
+
"INVALID_URL",
|
|
671
|
+
status === 0 ? "Refused a URL with a user name or password in it: pass them in a credential, not in the address" : `Refused a ${status} redirect to a URL with a user name or password in it`,
|
|
672
|
+
{ status: 0 }
|
|
673
|
+
);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
async function guardedFetch(url, fetchOptions, ctxSignal, effectiveMs, binding) {
|
|
585
677
|
let currentUrl = url;
|
|
586
678
|
let currentInit = { ...fetchOptions, redirect: "manual" };
|
|
587
679
|
await guardUrl(currentUrl, ctxSignal, effectiveMs);
|
|
@@ -610,6 +702,10 @@ async function guardedFetch(url, fetchOptions, ctxSignal, effectiveMs) {
|
|
|
610
702
|
} catch {
|
|
611
703
|
throw new NodeError("BLOCKED_ADDRESS", "Blocked redirect with an invalid Location header", { status: 0 });
|
|
612
704
|
}
|
|
705
|
+
refuseUserinfo(nextUrl, redirectStatus);
|
|
706
|
+
if (binding && binding.crossOriginRedirect === "refuse" && nextUrl.origin !== binding.origin) {
|
|
707
|
+
throw credentialOriginRefusal(nextUrl.origin, binding.origin, redirectStatus);
|
|
708
|
+
}
|
|
613
709
|
if (base.protocol === "https:" && nextUrl.protocol !== "https:") {
|
|
614
710
|
throw new NodeError("BLOCKED_ADDRESS", "Blocked https\u2192http downgrade on redirect", { status: 0 });
|
|
615
711
|
}
|
|
@@ -630,7 +726,9 @@ async function guardedFetch(url, fetchOptions, ctxSignal, effectiveMs) {
|
|
|
630
726
|
);
|
|
631
727
|
}
|
|
632
728
|
await guardUrl(nextUrl, ctxSignal, effectiveMs);
|
|
633
|
-
if (nextUrl.origin !==
|
|
729
|
+
if (binding && nextUrl.origin !== binding.origin) {
|
|
730
|
+
for (const name of [...headers.keys()]) headers.delete(name);
|
|
731
|
+
} else if (nextUrl.origin !== base.origin) {
|
|
634
732
|
for (const name of [...headers.keys()]) {
|
|
635
733
|
if (!headerCrossesOrigin(name)) headers.delete(name);
|
|
636
734
|
}
|
|
@@ -640,7 +738,7 @@ async function guardedFetch(url, fetchOptions, ctxSignal, effectiveMs) {
|
|
|
640
738
|
}
|
|
641
739
|
}
|
|
642
740
|
async function safeFetch(url, options = {}) {
|
|
643
|
-
const { timeoutMs = DEFAULT_TIMEOUT_MS, signal: ctxSignal, retry, ...fetchOptions } = options;
|
|
741
|
+
const { timeoutMs = DEFAULT_TIMEOUT_MS, signal: ctxSignal, retry, credential, ...fetchOptions } = options;
|
|
644
742
|
const effectiveMs = Number.isFinite(timeoutMs) && timeoutMs > 0 ? Math.min(timeoutMs, MAX_TIMEOUT_MS) : DEFAULT_TIMEOUT_MS;
|
|
645
743
|
const rawAttempts = retry?.attempts ?? DEFAULT_RETRY_ATTEMPTS;
|
|
646
744
|
const safeAttempts = Number.isFinite(rawAttempts) ? Math.min(Math.max(0, rawAttempts), MAX_RETRY_ATTEMPTS) : DEFAULT_RETRY_ATTEMPTS;
|
|
@@ -648,14 +746,26 @@ async function safeFetch(url, options = {}) {
|
|
|
648
746
|
const retryDelayMs = retry?.delayMs ?? DEFAULT_RETRY_DELAY_MS;
|
|
649
747
|
if (ctxSignal?.aborted) throw ctxSignal.reason;
|
|
650
748
|
if (fetchOptions.headers !== void 0) fetchOptions.headers = sendableHeaders(fetchOptions.headers);
|
|
749
|
+
let parsed;
|
|
750
|
+
try {
|
|
751
|
+
parsed = url instanceof URL ? url : new URL(url);
|
|
752
|
+
} catch {
|
|
753
|
+
throw new NodeError("INVALID_URL", "Refused an address that cannot be read as a URL", { status: 0 });
|
|
754
|
+
}
|
|
755
|
+
refuseUserinfo(parsed);
|
|
756
|
+
let binding;
|
|
757
|
+
if (credential) {
|
|
758
|
+
binding = { origin: boundOrigin(credential), crossOriginRedirect: credential.crossOriginRedirect ?? "refuse" };
|
|
759
|
+
if (parsed.origin !== binding.origin) throw credentialOriginRefusal(parsed.origin, binding.origin);
|
|
760
|
+
}
|
|
651
761
|
let lastError;
|
|
652
762
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
653
763
|
if (ctxSignal?.aborted) throw ctxSignal.reason;
|
|
654
764
|
try {
|
|
655
|
-
return await guardedFetch(url, fetchOptions, ctxSignal, effectiveMs);
|
|
765
|
+
return await guardedFetch(url, fetchOptions, ctxSignal, effectiveMs, binding);
|
|
656
766
|
} catch (err) {
|
|
657
767
|
if (ctxSignal?.aborted) throw ctxSignal.reason;
|
|
658
|
-
if (err instanceof NodeError && (err.code === "BLOCKED_ADDRESS" || err.code === "TOO_MANY_REDIRECTS")) {
|
|
768
|
+
if (err instanceof NodeError && (err.code === "BLOCKED_ADDRESS" || err.code === "TOO_MANY_REDIRECTS" || err.code === "CREDENTIAL_ORIGIN" || err.code === "INVALID_URL")) {
|
|
659
769
|
throw err;
|
|
660
770
|
}
|
|
661
771
|
lastError = err;
|
|
@@ -971,15 +1081,23 @@ var BaseCredential = class {
|
|
|
971
1081
|
});
|
|
972
1082
|
const text = await readText(res, MAX_TOKEN_RESPONSE_BYTES);
|
|
973
1083
|
if (!res.ok) {
|
|
974
|
-
throw new Error(`token endpoint ${url} -> ${res.status}${formatOAuthError(text)}`);
|
|
1084
|
+
throw new Error(`token endpoint ${endpointForMessage(url)} -> ${res.status}${formatOAuthError(text)}`);
|
|
975
1085
|
}
|
|
976
1086
|
try {
|
|
977
1087
|
return JSON.parse(text);
|
|
978
1088
|
} catch {
|
|
979
|
-
throw new Error(`token endpoint ${url} returned non-JSON body`);
|
|
1089
|
+
throw new Error(`token endpoint ${endpointForMessage(url)} returned non-JSON body`);
|
|
980
1090
|
}
|
|
981
1091
|
}
|
|
982
1092
|
};
|
|
1093
|
+
function endpointForMessage(url) {
|
|
1094
|
+
try {
|
|
1095
|
+
const u = new URL(url);
|
|
1096
|
+
return `${u.origin}${u.pathname}`;
|
|
1097
|
+
} catch {
|
|
1098
|
+
return "(unreadable address)";
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
983
1101
|
function formatOAuthError(body) {
|
|
984
1102
|
try {
|
|
985
1103
|
const parsed = JSON.parse(body);
|
|
@@ -1180,6 +1298,7 @@ function base64Url(buf) {
|
|
|
1180
1298
|
RetryableError,
|
|
1181
1299
|
SimpleValueCredential,
|
|
1182
1300
|
VALUELESS_OPERATORS,
|
|
1301
|
+
assertBranchNamed,
|
|
1183
1302
|
assertPublicUrl,
|
|
1184
1303
|
backoffDelay,
|
|
1185
1304
|
buildManifest,
|
|
@@ -1194,12 +1313,15 @@ function base64Url(buf) {
|
|
|
1194
1313
|
headerCrossesOrigin,
|
|
1195
1314
|
httpErrorResult,
|
|
1196
1315
|
isBlockedAddress,
|
|
1316
|
+
isMultiPort,
|
|
1197
1317
|
isNodeWithIteration,
|
|
1198
1318
|
isOAuthAuthorizeCredential,
|
|
1199
1319
|
isOperator,
|
|
1200
1320
|
maxBytesConfigField,
|
|
1201
1321
|
normalizeCredentialType,
|
|
1202
1322
|
normalizeLocalized,
|
|
1323
|
+
outputProblems,
|
|
1324
|
+
outputWarnings,
|
|
1203
1325
|
packageIconProblem,
|
|
1204
1326
|
parsePackageMeta,
|
|
1205
1327
|
readArrayBuffer,
|
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export * from './credentialType.js';
|
|
|
6
6
|
export * from './errors.js';
|
|
7
7
|
export * from './nodeErrors.js';
|
|
8
8
|
export * from './extract.js';
|
|
9
|
+
export * from './branches.js';
|
|
9
10
|
export * from './fetch.js';
|
|
10
11
|
export { assertPublicUrl, isBlockedAddress } from './ssrf.js';
|
|
11
12
|
export type { LookupAddress, LookupFn } from './ssrf.js';
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
2
|
MANIFEST_VERSION,
|
|
3
|
+
assertBranchNamed,
|
|
3
4
|
buildManifest,
|
|
4
5
|
extractCredentialManifest,
|
|
5
6
|
extractCredentialManifests,
|
|
6
7
|
extractManifest,
|
|
7
8
|
extractManifests,
|
|
9
|
+
isMultiPort,
|
|
10
|
+
outputProblems,
|
|
11
|
+
outputWarnings,
|
|
8
12
|
packageIconProblem,
|
|
9
13
|
parsePackageMeta
|
|
10
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-XO73F62E.js";
|
|
11
15
|
|
|
12
16
|
// src/types.ts
|
|
13
17
|
function isNodeWithIteration(node) {
|
|
@@ -450,6 +454,26 @@ function clampResponseBytes(maxBytes) {
|
|
|
450
454
|
if (!Number.isFinite(maxBytes) || maxBytes < 1) return MAX_RESPONSE_BYTES;
|
|
451
455
|
return Math.min(maxBytes, MAX_RESPONSE_BYTES);
|
|
452
456
|
}
|
|
457
|
+
function boundOrigin(credential) {
|
|
458
|
+
let parsed;
|
|
459
|
+
try {
|
|
460
|
+
parsed = new URL(credential.origin);
|
|
461
|
+
} catch {
|
|
462
|
+
parsed = void 0;
|
|
463
|
+
}
|
|
464
|
+
if (!parsed || parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
465
|
+
throw new NodeError("CREDENTIAL_ORIGIN", "The origin a credential is bound to is not an http(s) address", { status: 0 });
|
|
466
|
+
}
|
|
467
|
+
return parsed.origin;
|
|
468
|
+
}
|
|
469
|
+
function credentialOriginRefusal(target, bound, hop) {
|
|
470
|
+
const what = hop === void 0 ? "a request" : `a ${hop} redirect`;
|
|
471
|
+
return new NodeError(
|
|
472
|
+
"CREDENTIAL_ORIGIN",
|
|
473
|
+
`Refused ${what} to ${target}: the credential it carries is bound to ${bound}`,
|
|
474
|
+
{ status: 0 }
|
|
475
|
+
);
|
|
476
|
+
}
|
|
453
477
|
async function timedFetch(url, fetchOptions, ctxSignal, effectiveMs) {
|
|
454
478
|
const ac = new AbortController();
|
|
455
479
|
const timer = setTimeout(() => ac.abort(), effectiveMs);
|
|
@@ -489,7 +513,16 @@ function hasContent(body) {
|
|
|
489
513
|
if (body instanceof URLSearchParams) return body.toString().length > 0;
|
|
490
514
|
return true;
|
|
491
515
|
}
|
|
492
|
-
|
|
516
|
+
function refuseUserinfo(url, status = 0) {
|
|
517
|
+
if (url.username !== "" || url.password !== "") {
|
|
518
|
+
throw new NodeError(
|
|
519
|
+
"INVALID_URL",
|
|
520
|
+
status === 0 ? "Refused a URL with a user name or password in it: pass them in a credential, not in the address" : `Refused a ${status} redirect to a URL with a user name or password in it`,
|
|
521
|
+
{ status: 0 }
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
async function guardedFetch(url, fetchOptions, ctxSignal, effectiveMs, binding) {
|
|
493
526
|
let currentUrl = url;
|
|
494
527
|
let currentInit = { ...fetchOptions, redirect: "manual" };
|
|
495
528
|
await guardUrl(currentUrl, ctxSignal, effectiveMs);
|
|
@@ -518,6 +551,10 @@ async function guardedFetch(url, fetchOptions, ctxSignal, effectiveMs) {
|
|
|
518
551
|
} catch {
|
|
519
552
|
throw new NodeError("BLOCKED_ADDRESS", "Blocked redirect with an invalid Location header", { status: 0 });
|
|
520
553
|
}
|
|
554
|
+
refuseUserinfo(nextUrl, redirectStatus);
|
|
555
|
+
if (binding && binding.crossOriginRedirect === "refuse" && nextUrl.origin !== binding.origin) {
|
|
556
|
+
throw credentialOriginRefusal(nextUrl.origin, binding.origin, redirectStatus);
|
|
557
|
+
}
|
|
521
558
|
if (base.protocol === "https:" && nextUrl.protocol !== "https:") {
|
|
522
559
|
throw new NodeError("BLOCKED_ADDRESS", "Blocked https\u2192http downgrade on redirect", { status: 0 });
|
|
523
560
|
}
|
|
@@ -538,7 +575,9 @@ async function guardedFetch(url, fetchOptions, ctxSignal, effectiveMs) {
|
|
|
538
575
|
);
|
|
539
576
|
}
|
|
540
577
|
await guardUrl(nextUrl, ctxSignal, effectiveMs);
|
|
541
|
-
if (nextUrl.origin !==
|
|
578
|
+
if (binding && nextUrl.origin !== binding.origin) {
|
|
579
|
+
for (const name of [...headers.keys()]) headers.delete(name);
|
|
580
|
+
} else if (nextUrl.origin !== base.origin) {
|
|
542
581
|
for (const name of [...headers.keys()]) {
|
|
543
582
|
if (!headerCrossesOrigin(name)) headers.delete(name);
|
|
544
583
|
}
|
|
@@ -548,7 +587,7 @@ async function guardedFetch(url, fetchOptions, ctxSignal, effectiveMs) {
|
|
|
548
587
|
}
|
|
549
588
|
}
|
|
550
589
|
async function safeFetch(url, options = {}) {
|
|
551
|
-
const { timeoutMs = DEFAULT_TIMEOUT_MS, signal: ctxSignal, retry, ...fetchOptions } = options;
|
|
590
|
+
const { timeoutMs = DEFAULT_TIMEOUT_MS, signal: ctxSignal, retry, credential, ...fetchOptions } = options;
|
|
552
591
|
const effectiveMs = Number.isFinite(timeoutMs) && timeoutMs > 0 ? Math.min(timeoutMs, MAX_TIMEOUT_MS) : DEFAULT_TIMEOUT_MS;
|
|
553
592
|
const rawAttempts = retry?.attempts ?? DEFAULT_RETRY_ATTEMPTS;
|
|
554
593
|
const safeAttempts = Number.isFinite(rawAttempts) ? Math.min(Math.max(0, rawAttempts), MAX_RETRY_ATTEMPTS) : DEFAULT_RETRY_ATTEMPTS;
|
|
@@ -556,14 +595,26 @@ async function safeFetch(url, options = {}) {
|
|
|
556
595
|
const retryDelayMs = retry?.delayMs ?? DEFAULT_RETRY_DELAY_MS;
|
|
557
596
|
if (ctxSignal?.aborted) throw ctxSignal.reason;
|
|
558
597
|
if (fetchOptions.headers !== void 0) fetchOptions.headers = sendableHeaders(fetchOptions.headers);
|
|
598
|
+
let parsed;
|
|
599
|
+
try {
|
|
600
|
+
parsed = url instanceof URL ? url : new URL(url);
|
|
601
|
+
} catch {
|
|
602
|
+
throw new NodeError("INVALID_URL", "Refused an address that cannot be read as a URL", { status: 0 });
|
|
603
|
+
}
|
|
604
|
+
refuseUserinfo(parsed);
|
|
605
|
+
let binding;
|
|
606
|
+
if (credential) {
|
|
607
|
+
binding = { origin: boundOrigin(credential), crossOriginRedirect: credential.crossOriginRedirect ?? "refuse" };
|
|
608
|
+
if (parsed.origin !== binding.origin) throw credentialOriginRefusal(parsed.origin, binding.origin);
|
|
609
|
+
}
|
|
559
610
|
let lastError;
|
|
560
611
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
561
612
|
if (ctxSignal?.aborted) throw ctxSignal.reason;
|
|
562
613
|
try {
|
|
563
|
-
return await guardedFetch(url, fetchOptions, ctxSignal, effectiveMs);
|
|
614
|
+
return await guardedFetch(url, fetchOptions, ctxSignal, effectiveMs, binding);
|
|
564
615
|
} catch (err) {
|
|
565
616
|
if (ctxSignal?.aborted) throw ctxSignal.reason;
|
|
566
|
-
if (err instanceof NodeError && (err.code === "BLOCKED_ADDRESS" || err.code === "TOO_MANY_REDIRECTS")) {
|
|
617
|
+
if (err instanceof NodeError && (err.code === "BLOCKED_ADDRESS" || err.code === "TOO_MANY_REDIRECTS" || err.code === "CREDENTIAL_ORIGIN" || err.code === "INVALID_URL")) {
|
|
567
618
|
throw err;
|
|
568
619
|
}
|
|
569
620
|
lastError = err;
|
|
@@ -844,15 +895,23 @@ var BaseCredential = class {
|
|
|
844
895
|
});
|
|
845
896
|
const text = await readText(res, MAX_TOKEN_RESPONSE_BYTES);
|
|
846
897
|
if (!res.ok) {
|
|
847
|
-
throw new Error(`token endpoint ${url} -> ${res.status}${formatOAuthError(text)}`);
|
|
898
|
+
throw new Error(`token endpoint ${endpointForMessage(url)} -> ${res.status}${formatOAuthError(text)}`);
|
|
848
899
|
}
|
|
849
900
|
try {
|
|
850
901
|
return JSON.parse(text);
|
|
851
902
|
} catch {
|
|
852
|
-
throw new Error(`token endpoint ${url} returned non-JSON body`);
|
|
903
|
+
throw new Error(`token endpoint ${endpointForMessage(url)} returned non-JSON body`);
|
|
853
904
|
}
|
|
854
905
|
}
|
|
855
906
|
};
|
|
907
|
+
function endpointForMessage(url) {
|
|
908
|
+
try {
|
|
909
|
+
const u = new URL(url);
|
|
910
|
+
return `${u.origin}${u.pathname}`;
|
|
911
|
+
} catch {
|
|
912
|
+
return "(unreadable address)";
|
|
913
|
+
}
|
|
914
|
+
}
|
|
856
915
|
function formatOAuthError(body) {
|
|
857
916
|
try {
|
|
858
917
|
const parsed = JSON.parse(body);
|
|
@@ -1052,6 +1111,7 @@ export {
|
|
|
1052
1111
|
RetryableError,
|
|
1053
1112
|
SimpleValueCredential,
|
|
1054
1113
|
VALUELESS_OPERATORS,
|
|
1114
|
+
assertBranchNamed,
|
|
1055
1115
|
assertPublicUrl,
|
|
1056
1116
|
backoffDelay,
|
|
1057
1117
|
buildManifest,
|
|
@@ -1066,12 +1126,15 @@ export {
|
|
|
1066
1126
|
headerCrossesOrigin,
|
|
1067
1127
|
httpErrorResult,
|
|
1068
1128
|
isBlockedAddress,
|
|
1129
|
+
isMultiPort,
|
|
1069
1130
|
isNodeWithIteration,
|
|
1070
1131
|
isOAuthAuthorizeCredential,
|
|
1071
1132
|
isOperator,
|
|
1072
1133
|
maxBytesConfigField,
|
|
1073
1134
|
normalizeCredentialType,
|
|
1074
1135
|
normalizeLocalized,
|
|
1136
|
+
outputProblems,
|
|
1137
|
+
outputWarnings,
|
|
1075
1138
|
packageIconProblem,
|
|
1076
1139
|
parsePackageMeta,
|
|
1077
1140
|
readArrayBuffer,
|
package/dist/types.d.ts
CHANGED
|
@@ -368,6 +368,14 @@ export interface INodeState {
|
|
|
368
368
|
}
|
|
369
369
|
export interface INodeResult {
|
|
370
370
|
outputs: Record<string, unknown>;
|
|
371
|
+
/**
|
|
372
|
+
* The output port the node took. Output ports are alternatives — exactly one
|
|
373
|
+
* fires — so a node with more than one output (an `error` port counts) must
|
|
374
|
+
* name it; only a node with a single output may omit it. The worker fails such
|
|
375
|
+
* a step from the integrations release that carries PO-751; until then a result
|
|
376
|
+
* without `branch` fires every output. See `isMultiPort` for what counts as
|
|
377
|
+
* several, and `assertBranchNamed` to check a result in the node's own tests.
|
|
378
|
+
*/
|
|
371
379
|
branch?: string;
|
|
372
380
|
}
|
|
373
381
|
/**
|
package/package.json
CHANGED
package/dist/chunk-5BCAXRU3.js
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
// src/extract.ts
|
|
2
|
-
function extractManifest(node) {
|
|
3
|
-
return node.description;
|
|
4
|
-
}
|
|
5
|
-
function extractManifests(nodes) {
|
|
6
|
-
return nodes.map(extractManifest);
|
|
7
|
-
}
|
|
8
|
-
function extractCredentialManifest(credential) {
|
|
9
|
-
return credential.description;
|
|
10
|
-
}
|
|
11
|
-
function extractCredentialManifests(credentials) {
|
|
12
|
-
return credentials.map(extractCredentialManifest);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// src/manifest.ts
|
|
16
|
-
var MANIFEST_VERSION = "v0-draft";
|
|
17
|
-
var PACKAGE_ICON = /^lucide:[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
18
|
-
var PHP_TRIM = /^[ \t\n\r\0\v]+|[ \t\n\r\0\v]+$/g;
|
|
19
|
-
function packageIconProblem(icon) {
|
|
20
|
-
if (icon === void 0 || PACKAGE_ICON.test(icon)) return void 0;
|
|
21
|
-
return `package.json "revenexx.icon" must be a Lucide icon name such as "lucide:bell", got "${icon}".`;
|
|
22
|
-
}
|
|
23
|
-
function parsePackageMeta(raw) {
|
|
24
|
-
const obj = raw && typeof raw === "object" ? raw : {};
|
|
25
|
-
const str = (v) => typeof v === "string" ? v.trim() : "";
|
|
26
|
-
const revenexx = obj.revenexx && typeof obj.revenexx === "object" ? obj.revenexx : {};
|
|
27
|
-
const displayName = str(revenexx.displayName);
|
|
28
|
-
const icon = typeof revenexx.icon === "string" ? revenexx.icon.replace(PHP_TRIM, "") : "";
|
|
29
|
-
return {
|
|
30
|
-
name: str(obj.name),
|
|
31
|
-
version: str(obj.version),
|
|
32
|
-
displayName: displayName !== "" ? displayName : void 0,
|
|
33
|
-
icon: icon !== "" ? icon : void 0
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
function buildManifest(nodes, credentials = [], templates = []) {
|
|
37
|
-
const manifest = {
|
|
38
|
-
manifestVersion: MANIFEST_VERSION,
|
|
39
|
-
nodes: extractManifests(nodes)
|
|
40
|
-
};
|
|
41
|
-
if (credentials.length > 0) {
|
|
42
|
-
manifest.credentials = extractCredentialManifests(credentials);
|
|
43
|
-
}
|
|
44
|
-
if (templates.length > 0) {
|
|
45
|
-
manifest.templates = templates;
|
|
46
|
-
}
|
|
47
|
-
return manifest;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export {
|
|
51
|
-
extractManifest,
|
|
52
|
-
extractManifests,
|
|
53
|
-
extractCredentialManifest,
|
|
54
|
-
extractCredentialManifests,
|
|
55
|
-
MANIFEST_VERSION,
|
|
56
|
-
packageIconProblem,
|
|
57
|
-
parsePackageMeta,
|
|
58
|
-
buildManifest
|
|
59
|
-
};
|