@cervio/plugin-core 0.1.1 → 1.0.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/LICENSE +21 -0
- package/README.md +1 -1
- package/dist/browser-Ddj__JFI.d.ts +141 -0
- package/dist/browser.d.ts +1 -0
- package/dist/browser.js +166 -0
- package/dist/index.d.ts +63 -368
- package/dist/index.js +390 -155
- package/manifest.schema.json +159 -4
- package/package.json +36 -21
- package/dist/index.cjs +0 -549
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -427
- package/dist/index.js.map +0 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026-present Cervio S.r.l.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -15,6 +15,6 @@ const fields = parseManifestObject(manifestJson)
|
|
|
15
15
|
const packed = await packPlugin({ dir: '.' })
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
See docs at `docs.cervio.app` (when deployed) and
|
|
18
|
+
See docs at `docs.cervio.app` (when deployed) and [Authoring](https://docs.cervio.app/docs/authoring) in the Cervio repo.
|
|
19
19
|
|
|
20
20
|
**Versioning note:** this package version is independent of plugin release versions (assigned by the host) and of `@cervio/plugin-sdk` (tracks CervioUI API).
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `boot_requests` in manifest.json: GET calls the plugin makes as soon as it
|
|
3
|
+
* starts. The host starts them itself while the plugin is still loading, and
|
|
4
|
+
* the plugin's own `fetch` of the same path + query is answered from that
|
|
5
|
+
* response (once) instead of waiting for a new round trip. Pure hint: a plugin
|
|
6
|
+
* that never asks gets nothing extra, and any other request works as before.
|
|
7
|
+
*
|
|
8
|
+
* Example: `{ "path": "/api/v1/entities/attivita/records?limit=200" }`.
|
|
9
|
+
* `/api/v1/entities/<technical name>/...` is accepted: the host also matches
|
|
10
|
+
* the same path with the entity id the plugin may use.
|
|
11
|
+
*/
|
|
12
|
+
type PluginBootRequest = {
|
|
13
|
+
/** Absolute path + optional query under `/api/v1/`, e.g. `/api/v1/entities/x/records?limit=50`. */
|
|
14
|
+
path: string;
|
|
15
|
+
};
|
|
16
|
+
/** Lenient read of the stored snapshot (`installed_plugins.declared_boot_requests`). */
|
|
17
|
+
declare function parsePluginBootRequests(raw: unknown): PluginBootRequest[];
|
|
18
|
+
|
|
19
|
+
/** Canonical Policy Plugin type is `policy`; `server` is a silent legacy alias. */
|
|
20
|
+
type PluginType = 'task' | 'form' | 'task_form' | 'route' | 'policy' | 'server';
|
|
21
|
+
type PluginActionResult<T> = {
|
|
22
|
+
ok: true;
|
|
23
|
+
value: T;
|
|
24
|
+
} | {
|
|
25
|
+
ok: false;
|
|
26
|
+
error: string;
|
|
27
|
+
};
|
|
28
|
+
interface ParsedPluginManifest {
|
|
29
|
+
pluginKey: string;
|
|
30
|
+
pluginName: string | null;
|
|
31
|
+
pluginType: PluginType | null;
|
|
32
|
+
/** Present in ZIP but ignored for assignment; Cervio owns release versions. */
|
|
33
|
+
manifestVersion: string | null;
|
|
34
|
+
/** Catalog capability ids the plugin needs (e.g. calendar). */
|
|
35
|
+
requiredConnectionCapabilities: string[];
|
|
36
|
+
/** External Connections requirements (OAuth broker). */
|
|
37
|
+
connections: PluginConnectionRequirement[];
|
|
38
|
+
/** Declared Cervio entity deps (name-based; for Admin + future export). */
|
|
39
|
+
entities: PluginEntityRequirement[];
|
|
40
|
+
/** Declared Cervio API path prefixes — fetch relay allowlist (fail-closed if empty). */
|
|
41
|
+
cervioApis: PluginCervioApiRequirement[];
|
|
42
|
+
/** Declared Admin-editable settings fields (drives Impostazioni dynamic form). */
|
|
43
|
+
settings: PluginSettingsFieldRequirement[];
|
|
44
|
+
/** GET calls the host prefetches while the plugin loads (`boot_requests`). */
|
|
45
|
+
bootRequests: PluginBootRequest[];
|
|
46
|
+
artifactEntryPath: string;
|
|
47
|
+
rawManifest: Record<string, unknown>;
|
|
48
|
+
manifestEntryName: string;
|
|
49
|
+
}
|
|
50
|
+
type PluginEntityAccess = 'read' | 'write';
|
|
51
|
+
type PluginEntityRequirement = {
|
|
52
|
+
name: string;
|
|
53
|
+
fields: string[];
|
|
54
|
+
forms: string[];
|
|
55
|
+
access: PluginEntityAccess[];
|
|
56
|
+
};
|
|
57
|
+
type PluginCervioApiRequirement = {
|
|
58
|
+
method: string;
|
|
59
|
+
path_prefix: string;
|
|
60
|
+
};
|
|
61
|
+
type PluginSettingsFieldType = 'string' | 'boolean' | 'select' | 'multiselect';
|
|
62
|
+
type PluginSettingsFieldOption = {
|
|
63
|
+
value: string;
|
|
64
|
+
label: string;
|
|
65
|
+
};
|
|
66
|
+
type PluginSettingsFieldRequirement = {
|
|
67
|
+
key: string;
|
|
68
|
+
type: PluginSettingsFieldType;
|
|
69
|
+
label: string;
|
|
70
|
+
hint?: string;
|
|
71
|
+
placeholder?: string;
|
|
72
|
+
default?: string | boolean | string[];
|
|
73
|
+
/** Required for `select` / `multiselect`. */
|
|
74
|
+
options?: PluginSettingsFieldOption[];
|
|
75
|
+
};
|
|
76
|
+
type ExternalConnectionHttpMethod = 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE';
|
|
77
|
+
type AllowedApiRule = {
|
|
78
|
+
host: string;
|
|
79
|
+
methods: ExternalConnectionHttpMethod[];
|
|
80
|
+
path_prefix: string;
|
|
81
|
+
};
|
|
82
|
+
type PluginConnectionRequirement = {
|
|
83
|
+
id: string;
|
|
84
|
+
provider: string;
|
|
85
|
+
consent: 'user';
|
|
86
|
+
scopes: string[];
|
|
87
|
+
allowed_apis: AllowedApiRule[];
|
|
88
|
+
};
|
|
89
|
+
type ParseManifestOptions = {
|
|
90
|
+
/**
|
|
91
|
+
* Capability ids a plugin may declare in `required_connection_capabilities`.
|
|
92
|
+
* Host passes the live catalog; CLI uses {@link DEFAULT_PLUGIN_DECLARABLE_CAPABILITIES}.
|
|
93
|
+
*/
|
|
94
|
+
allowedCapabilities?: readonly string[];
|
|
95
|
+
};
|
|
96
|
+
declare const MAX_PLUGIN_ZIP_BYTES: number;
|
|
97
|
+
declare const MAX_PLUGIN_ZIP_ENTRIES = 5000;
|
|
98
|
+
declare const MAX_PLUGIN_UNCOMPRESSED_BYTES: number;
|
|
99
|
+
/** Soft UX budget (warn) — not a hard reject. */
|
|
100
|
+
declare const PLUGIN_ARTIFACT_SOFT_WARN_BYTES: number;
|
|
101
|
+
/** Soft UX budget (strong warn) — not a hard reject. */
|
|
102
|
+
declare const PLUGIN_ARTIFACT_SOFT_STRONG_WARN_BYTES: number;
|
|
103
|
+
declare const PLUGIN_KEY_RE: RegExp;
|
|
104
|
+
/**
|
|
105
|
+
* Static default for CLI validate. Generated from host `CONNECTION_CAPABILITY_CATALOG`
|
|
106
|
+
* where `pluginDeclarable === true`. Guarded by drift test against the host catalog.
|
|
107
|
+
*/
|
|
108
|
+
declare const DEFAULT_PLUGIN_DECLARABLE_CAPABILITIES: readonly ["calendar"];
|
|
109
|
+
|
|
110
|
+
/** A plugin ZIP that passed the safety checks, with its files inflated in memory. */
|
|
111
|
+
interface PluginZip {
|
|
112
|
+
/** File paths (directories excluded), as stored in the archive. */
|
|
113
|
+
readonly paths: readonly string[];
|
|
114
|
+
/** Bytes of a file, or `undefined` when the archive has no such file. */
|
|
115
|
+
bytes(path: string): Uint8Array | undefined;
|
|
116
|
+
/** UTF-8 text of a file, or `undefined` when the archive has no such file. */
|
|
117
|
+
text(path: string): string | undefined;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Opens a plugin ZIP safely. Entry count, paths, nested archives, declared sizes
|
|
121
|
+
* and compression ratios are checked from the headers before any entry is
|
|
122
|
+
* inflated; the inflated total is checked again, so an archive that understates
|
|
123
|
+
* its sizes is still rejected. The only way to read a plugin ZIP: there is no
|
|
124
|
+
* unchecked variant.
|
|
125
|
+
*/
|
|
126
|
+
declare function readPluginZip(data: ArrayBuffer | Uint8Array): Promise<PluginActionResult<PluginZip>>;
|
|
127
|
+
/** Cheap pre-check on an uploaded file before reading it: extension and size. */
|
|
128
|
+
declare function assertZipFile(file: {
|
|
129
|
+
name?: string;
|
|
130
|
+
size: number;
|
|
131
|
+
}): PluginActionResult<true>;
|
|
132
|
+
|
|
133
|
+
/** Parse `entities` from manifest.json (invalid entries skipped). */
|
|
134
|
+
declare function parsePluginEntityRequirements(raw: unknown): PluginEntityRequirement[];
|
|
135
|
+
declare function isDeclarableCervioApiRequirement(row: PluginCervioApiRequirement): boolean;
|
|
136
|
+
declare function isDeclaredCervioApiRequest(method: string, pathname: string, declared: readonly PluginCervioApiRequirement[]): boolean;
|
|
137
|
+
/** Parse `cervio_apis` from manifest.json (skips malformed rows; does not enforce declarable paths). */
|
|
138
|
+
declare function parsePluginCervioApiRequirements(raw: unknown): PluginCervioApiRequirement[];
|
|
139
|
+
|
|
140
|
+
export { DEFAULT_PLUGIN_DECLARABLE_CAPABILITIES as D, MAX_PLUGIN_UNCOMPRESSED_BYTES as M, MAX_PLUGIN_ZIP_BYTES as j, MAX_PLUGIN_ZIP_ENTRIES as k, PLUGIN_ARTIFACT_SOFT_STRONG_WARN_BYTES as l, PLUGIN_ARTIFACT_SOFT_WARN_BYTES as m, PLUGIN_KEY_RE as n, assertZipFile as r, isDeclarableCervioApiRequirement as s, isDeclaredCervioApiRequest as t, parsePluginBootRequests as u, parsePluginCervioApiRequirements as v, parsePluginEntityRequirements as w, readPluginZip as x };
|
|
141
|
+
export type { AllowedApiRule as A, ExternalConnectionHttpMethod as E, ParseManifestOptions as P, PluginActionResult as a, PluginType as b, PluginConnectionRequirement as c, PluginEntityRequirement as d, PluginCervioApiRequirement as e, PluginSettingsFieldRequirement as f, PluginBootRequest as g, ParsedPluginManifest as h, PluginZip as i, PluginEntityAccess as o, PluginSettingsFieldOption as p, PluginSettingsFieldType as q };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { M as MAX_PLUGIN_UNCOMPRESSED_BYTES, j as MAX_PLUGIN_ZIP_BYTES, k as MAX_PLUGIN_ZIP_ENTRIES, a as PluginActionResult, g as PluginBootRequest, e as PluginCervioApiRequirement, i as PluginZip, r as assertZipFile, s as isDeclarableCervioApiRequirement, t as isDeclaredCervioApiRequest, u as parsePluginBootRequests, v as parsePluginCervioApiRequirements, x as readPluginZip } from './browser-Ddj__JFI.js';
|
package/dist/browser.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { unzipSync, strFromU8 } from 'fflate';
|
|
2
|
+
|
|
3
|
+
// src/zip-safety.ts
|
|
4
|
+
|
|
5
|
+
// src/types.ts
|
|
6
|
+
var MAX_PLUGIN_ZIP_BYTES = 50 * 1024 * 1024;
|
|
7
|
+
var MAX_PLUGIN_ZIP_ENTRIES = 5e3;
|
|
8
|
+
var MAX_PLUGIN_UNCOMPRESSED_BYTES = 250 * 1024 * 1024;
|
|
9
|
+
|
|
10
|
+
// src/zip-safety.ts
|
|
11
|
+
var MAX_COMPRESSION_RATIO = 100;
|
|
12
|
+
var RATIO_CHECK_FROM_BYTES = 1024 * 1024;
|
|
13
|
+
function checkEntry(entry) {
|
|
14
|
+
const { name, size, originalSize } = entry;
|
|
15
|
+
if (name.includes("..") || name.includes("\\") || name.startsWith("/")) {
|
|
16
|
+
return "zip_unsafe_entry_path";
|
|
17
|
+
}
|
|
18
|
+
if (/\.zip$/i.test(name)) return "zip_nested_archive";
|
|
19
|
+
if (originalSize > RATIO_CHECK_FROM_BYTES) {
|
|
20
|
+
if (size === 0 || originalSize / size > MAX_COMPRESSION_RATIO) {
|
|
21
|
+
return "zip_compression_ratio_too_high";
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
async function readPluginZip(data) {
|
|
27
|
+
const input = data instanceof Uint8Array ? data : new Uint8Array(data);
|
|
28
|
+
if (input.byteLength > MAX_PLUGIN_ZIP_BYTES) return { ok: false, error: "file_too_large" };
|
|
29
|
+
let error = null;
|
|
30
|
+
let entries = 0;
|
|
31
|
+
let declaredTotal = 0;
|
|
32
|
+
let files;
|
|
33
|
+
try {
|
|
34
|
+
files = unzipSync(input, {
|
|
35
|
+
filter: (entry) => {
|
|
36
|
+
if (error) return false;
|
|
37
|
+
entries += 1;
|
|
38
|
+
if (entries > MAX_PLUGIN_ZIP_ENTRIES) {
|
|
39
|
+
error = "zip_too_many_entries";
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
error = checkEntry(entry);
|
|
43
|
+
if (error) return false;
|
|
44
|
+
if (entry.name.endsWith("/")) return false;
|
|
45
|
+
declaredTotal += entry.originalSize;
|
|
46
|
+
if (declaredTotal > MAX_PLUGIN_UNCOMPRESSED_BYTES) {
|
|
47
|
+
error = "zip_uncompressed_too_large";
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
} catch {
|
|
54
|
+
files = null;
|
|
55
|
+
}
|
|
56
|
+
if (error) return { ok: false, error };
|
|
57
|
+
if (!files) return { ok: false, error: "zip_invalid" };
|
|
58
|
+
let actualTotal = 0;
|
|
59
|
+
for (const bytes of Object.values(files)) actualTotal += bytes.byteLength;
|
|
60
|
+
if (actualTotal > MAX_PLUGIN_UNCOMPRESSED_BYTES) {
|
|
61
|
+
return { ok: false, error: "zip_uncompressed_too_large" };
|
|
62
|
+
}
|
|
63
|
+
const paths = Object.keys(files);
|
|
64
|
+
return {
|
|
65
|
+
ok: true,
|
|
66
|
+
value: {
|
|
67
|
+
paths,
|
|
68
|
+
bytes: (path) => files[path],
|
|
69
|
+
text: (path) => {
|
|
70
|
+
const bytes = files[path];
|
|
71
|
+
return bytes === void 0 ? void 0 : strFromU8(bytes);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function assertZipFile(file) {
|
|
77
|
+
const name = (file.name ?? "upload.zip").toLowerCase();
|
|
78
|
+
if (!name.endsWith(".zip")) return { ok: false, error: "invalid_file_extension" };
|
|
79
|
+
if (file.size <= 0) return { ok: false, error: "empty_file" };
|
|
80
|
+
if (file.size > MAX_PLUGIN_ZIP_BYTES) return { ok: false, error: "file_too_large" };
|
|
81
|
+
return { ok: true, value: true };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// src/entity-requirements.ts
|
|
85
|
+
var HTTP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS", "*"]);
|
|
86
|
+
var DECLARABLE_HTTP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"]);
|
|
87
|
+
var CERVIO_API_PREFIX = "/api/v1/";
|
|
88
|
+
var MAX_CERVIO_API_PREFIX_LEN = 256;
|
|
89
|
+
var CERVIO_API_PREFIX_SAFE_RE = /^\/api\/v1\/[a-zA-Z0-9][a-zA-Z0-9._/-]{0,253}$/;
|
|
90
|
+
function readCervioApiPath(row) {
|
|
91
|
+
return typeof row.path_prefix === "string" ? row.path_prefix.trim() : typeof row.pathPrefix === "string" ? row.pathPrefix.trim() : "";
|
|
92
|
+
}
|
|
93
|
+
function isDeclarableCervioApiPrefix(path) {
|
|
94
|
+
if (!path.startsWith(CERVIO_API_PREFIX)) return false;
|
|
95
|
+
if (path.length > MAX_CERVIO_API_PREFIX_LEN) return false;
|
|
96
|
+
if (path.includes("..") || path.includes("//") || path.includes("\\") || path.includes("*") || path.includes("?")) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
if (!CERVIO_API_PREFIX_SAFE_RE.test(path.replace(/\/$/, "") || path)) return false;
|
|
100
|
+
const rest = path.slice(CERVIO_API_PREFIX.length);
|
|
101
|
+
const firstSeg = rest.split("/").filter(Boolean)[0]?.toLowerCase();
|
|
102
|
+
if (!firstSeg) return false;
|
|
103
|
+
if (firstSeg === "admin") return false;
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
function isDeclarableCervioApiRequirement(row) {
|
|
107
|
+
return DECLARABLE_HTTP_METHODS.has(row.method.toUpperCase()) && isDeclarableCervioApiPrefix(row.path_prefix);
|
|
108
|
+
}
|
|
109
|
+
function pathMatchesCervioApiPrefix(pathname, prefix) {
|
|
110
|
+
if (!prefix.startsWith("/") || prefix.includes("..") || prefix.includes("//")) return false;
|
|
111
|
+
if (pathname === prefix) return true;
|
|
112
|
+
if (prefix.endsWith("/")) return pathname.startsWith(prefix);
|
|
113
|
+
return pathname.startsWith(`${prefix}/`);
|
|
114
|
+
}
|
|
115
|
+
function isDeclaredCervioApiRequest(method, pathname, declared) {
|
|
116
|
+
const m = method.trim().toUpperCase();
|
|
117
|
+
if (!declared.length) return false;
|
|
118
|
+
for (const row of declared) {
|
|
119
|
+
const declaredMethod = row.method.trim().toUpperCase();
|
|
120
|
+
const methodOk = declaredMethod === m || declaredMethod === "*" || m === "HEAD" && declaredMethod === "GET";
|
|
121
|
+
if (!methodOk) continue;
|
|
122
|
+
if (pathMatchesCervioApiPrefix(pathname, row.path_prefix)) return true;
|
|
123
|
+
}
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
function parsePluginCervioApiRequirements(raw) {
|
|
127
|
+
if (!Array.isArray(raw)) return [];
|
|
128
|
+
const out = [];
|
|
129
|
+
const seen = /* @__PURE__ */ new Set();
|
|
130
|
+
for (const item of raw) {
|
|
131
|
+
if (!item || typeof item !== "object") continue;
|
|
132
|
+
const row = item;
|
|
133
|
+
const methodRaw = typeof row.method === "string" ? row.method.trim().toUpperCase() : "GET";
|
|
134
|
+
const method = HTTP_METHODS.has(methodRaw) ? methodRaw : "GET";
|
|
135
|
+
const path = readCervioApiPath(row);
|
|
136
|
+
if (!path || !path.startsWith("/")) continue;
|
|
137
|
+
const key = `${method}:${path}`;
|
|
138
|
+
if (seen.has(key)) continue;
|
|
139
|
+
seen.add(key);
|
|
140
|
+
out.push({ method, path_prefix: path });
|
|
141
|
+
}
|
|
142
|
+
return out;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// src/boot-requests.ts
|
|
146
|
+
var MAX_PLUGIN_BOOT_REQUESTS = 8;
|
|
147
|
+
var MAX_BOOT_PATH_LEN = 400;
|
|
148
|
+
function bootRequestPathname(path) {
|
|
149
|
+
if (typeof path !== "string") return null;
|
|
150
|
+
const p = path.trim();
|
|
151
|
+
if (!p.startsWith("/api/v1/") || p.length > MAX_BOOT_PATH_LEN || p.includes("#")) return null;
|
|
152
|
+
const pathname = p.split("?")[0] ?? "";
|
|
153
|
+
if (!isDeclarableCervioApiPrefix(pathname)) return null;
|
|
154
|
+
return pathname;
|
|
155
|
+
}
|
|
156
|
+
function parsePluginBootRequests(raw) {
|
|
157
|
+
if (!Array.isArray(raw)) return [];
|
|
158
|
+
const out = [];
|
|
159
|
+
for (const item of raw.slice(0, MAX_PLUGIN_BOOT_REQUESTS)) {
|
|
160
|
+
const path = item && typeof item === "object" ? item.path : void 0;
|
|
161
|
+
if (typeof path === "string" && bootRequestPathname(path)) out.push({ path: path.trim() });
|
|
162
|
+
}
|
|
163
|
+
return out;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export { MAX_PLUGIN_UNCOMPRESSED_BYTES, MAX_PLUGIN_ZIP_BYTES, MAX_PLUGIN_ZIP_ENTRIES, assertZipFile, isDeclarableCervioApiRequirement, isDeclaredCervioApiRequest, parsePluginBootRequests, parsePluginCervioApiRequirements, readPluginZip };
|