@cervio/plugin-core 0.1.4 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +62 -7
- package/dist/browser-Ddj__JFI.d.ts +141 -0
- package/dist/browser.d.ts +1 -2
- package/dist/browser.js +148 -31
- package/dist/index.d.ts +8 -467
- package/dist/index.js +302 -249
- package/manifest.schema.json +41 -0
- package/package.json +27 -17
- package/dist/browser-CbWn_0Kk.d.cts +0 -118
- package/dist/browser-CbWn_0Kk.d.ts +0 -118
- package/dist/browser.cjs +0 -56
- package/dist/browser.cjs.map +0 -1
- package/dist/browser.d.cts +0 -2
- package/dist/browser.js.map +0 -1
- package/dist/index.cjs +0 -736
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -581
- package/dist/index.js.map +0 -1
package/dist/index.cjs
DELETED
|
@@ -1,736 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var zod = require('zod');
|
|
4
|
-
var JSZip = require('jszip');
|
|
5
|
-
var crypto = require('crypto');
|
|
6
|
-
var fs = require('fs');
|
|
7
|
-
var path = require('path');
|
|
8
|
-
|
|
9
|
-
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
10
|
-
|
|
11
|
-
var JSZip__default = /*#__PURE__*/_interopDefault(JSZip);
|
|
12
|
-
var path__default = /*#__PURE__*/_interopDefault(path);
|
|
13
|
-
|
|
14
|
-
// src/types.ts
|
|
15
|
-
var PLUGIN_ARTIFACT_BUCKET = "plugin-artifacts";
|
|
16
|
-
var MAX_PLUGIN_ZIP_BYTES = 50 * 1024 * 1024;
|
|
17
|
-
var MAX_PLUGIN_ZIP_ENTRIES = 5e3;
|
|
18
|
-
var MAX_PLUGIN_UNCOMPRESSED_BYTES = 250 * 1024 * 1024;
|
|
19
|
-
var PLUGIN_KEY_RE = /^[a-z0-9][a-z0-9-_]{1,62}$/;
|
|
20
|
-
var PLUGIN_VERSION_SELECT = "id, plugin_id, version, changelog, artifact_path, artifact_entry_path, artifact_sha256, content_sha256, artifact_size_bytes, created_at, deleted_at, deleted_by";
|
|
21
|
-
var DEFAULT_PLUGIN_DECLARABLE_CAPABILITIES = ["calendar"];
|
|
22
|
-
|
|
23
|
-
// src/entity-requirements.ts
|
|
24
|
-
var ENTITY_NAME_RE = /^[a-z][a-z0-9_-]{0,62}$/i;
|
|
25
|
-
var FIELD_NAME_RE = /^[a-z][a-z0-9_]{0,62}$/i;
|
|
26
|
-
var HTTP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS", "*"]);
|
|
27
|
-
function asStringArray(raw, nameRe) {
|
|
28
|
-
if (!Array.isArray(raw)) return [];
|
|
29
|
-
const out = [];
|
|
30
|
-
const seen = /* @__PURE__ */ new Set();
|
|
31
|
-
for (const item of raw) {
|
|
32
|
-
if (typeof item !== "string") continue;
|
|
33
|
-
const v = item.trim();
|
|
34
|
-
if (!v || !nameRe.test(v) || seen.has(v)) continue;
|
|
35
|
-
seen.add(v);
|
|
36
|
-
out.push(v);
|
|
37
|
-
}
|
|
38
|
-
return out;
|
|
39
|
-
}
|
|
40
|
-
function parseAccess(raw) {
|
|
41
|
-
if (!Array.isArray(raw)) return ["read"];
|
|
42
|
-
const out = [];
|
|
43
|
-
for (const item of raw) {
|
|
44
|
-
if (item === "read" || item === "write") {
|
|
45
|
-
if (!out.includes(item)) out.push(item);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
return out.length ? out : ["read"];
|
|
49
|
-
}
|
|
50
|
-
function parsePluginEntityRequirements(raw) {
|
|
51
|
-
if (!Array.isArray(raw)) return [];
|
|
52
|
-
const out = [];
|
|
53
|
-
const seen = /* @__PURE__ */ new Set();
|
|
54
|
-
for (const item of raw) {
|
|
55
|
-
if (!item || typeof item !== "object") continue;
|
|
56
|
-
const row = item;
|
|
57
|
-
const name = typeof row.name === "string" ? row.name.trim() : "";
|
|
58
|
-
if (!name || !ENTITY_NAME_RE.test(name) || seen.has(name.toLowerCase())) continue;
|
|
59
|
-
seen.add(name.toLowerCase());
|
|
60
|
-
out.push({
|
|
61
|
-
name,
|
|
62
|
-
fields: asStringArray(row.fields, FIELD_NAME_RE),
|
|
63
|
-
forms: asStringArray(row.forms, FIELD_NAME_RE),
|
|
64
|
-
access: parseAccess(row.access)
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
return out;
|
|
68
|
-
}
|
|
69
|
-
function parsePluginCervioApiRequirements(raw) {
|
|
70
|
-
if (!Array.isArray(raw)) return [];
|
|
71
|
-
const out = [];
|
|
72
|
-
const seen = /* @__PURE__ */ new Set();
|
|
73
|
-
for (const item of raw) {
|
|
74
|
-
if (!item || typeof item !== "object") continue;
|
|
75
|
-
const row = item;
|
|
76
|
-
const methodRaw = typeof row.method === "string" ? row.method.trim().toUpperCase() : "GET";
|
|
77
|
-
const method = HTTP_METHODS.has(methodRaw) ? methodRaw : "GET";
|
|
78
|
-
const path2 = typeof row.path_prefix === "string" ? row.path_prefix.trim() : typeof row.pathPrefix === "string" ? row.pathPrefix.trim() : "";
|
|
79
|
-
if (!path2 || !path2.startsWith("/")) continue;
|
|
80
|
-
const key = `${method}:${path2}`;
|
|
81
|
-
if (seen.has(key)) continue;
|
|
82
|
-
seen.add(key);
|
|
83
|
-
out.push({ method, path_prefix: path2 });
|
|
84
|
-
}
|
|
85
|
-
return out;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// src/settings-requirements.ts
|
|
89
|
-
var SETTINGS_KEY_RE = /^[a-z][a-z0-9_]{0,62}$/i;
|
|
90
|
-
var SETTINGS_TYPES = /* @__PURE__ */ new Set(["string", "boolean", "select", "multiselect"]);
|
|
91
|
-
var RESERVED_SETTINGS_KEYS = /* @__PURE__ */ new Set(["debug"]);
|
|
92
|
-
function parseOptions(raw) {
|
|
93
|
-
if (!Array.isArray(raw)) return [];
|
|
94
|
-
const out = [];
|
|
95
|
-
const seen = /* @__PURE__ */ new Set();
|
|
96
|
-
for (const item of raw) {
|
|
97
|
-
if (!item || typeof item !== "object") continue;
|
|
98
|
-
const row = item;
|
|
99
|
-
const value = typeof row.value === "string" ? row.value.trim() : "";
|
|
100
|
-
const label = typeof row.label === "string" ? row.label.trim() : "";
|
|
101
|
-
if (!value || !label || seen.has(value)) continue;
|
|
102
|
-
seen.add(value);
|
|
103
|
-
out.push({ value, label });
|
|
104
|
-
}
|
|
105
|
-
return out;
|
|
106
|
-
}
|
|
107
|
-
function parseDefault(raw, type, options) {
|
|
108
|
-
const allowedValues = new Set(options.map((o) => o.value));
|
|
109
|
-
if (type === "boolean") return typeof raw === "boolean" ? raw : void 0;
|
|
110
|
-
if (type === "string") return typeof raw === "string" ? raw : void 0;
|
|
111
|
-
if (type === "select") {
|
|
112
|
-
return typeof raw === "string" && allowedValues.has(raw) ? raw : void 0;
|
|
113
|
-
}
|
|
114
|
-
if (type === "multiselect") {
|
|
115
|
-
if (!Array.isArray(raw)) return void 0;
|
|
116
|
-
const values = raw.filter((v) => typeof v === "string" && allowedValues.has(v));
|
|
117
|
-
return values.length ? values : void 0;
|
|
118
|
-
}
|
|
119
|
-
return void 0;
|
|
120
|
-
}
|
|
121
|
-
function parsePluginSettingsRequirements(raw) {
|
|
122
|
-
if (!Array.isArray(raw)) return [];
|
|
123
|
-
const out = [];
|
|
124
|
-
const seen = /* @__PURE__ */ new Set();
|
|
125
|
-
for (const item of raw) {
|
|
126
|
-
if (!item || typeof item !== "object") continue;
|
|
127
|
-
const row = item;
|
|
128
|
-
const key = typeof row.key === "string" ? row.key.trim() : "";
|
|
129
|
-
if (!key || !SETTINGS_KEY_RE.test(key) || seen.has(key) || RESERVED_SETTINGS_KEYS.has(key)) continue;
|
|
130
|
-
const type = typeof row.type === "string" ? row.type.trim() : null;
|
|
131
|
-
if (!type || !SETTINGS_TYPES.has(type)) continue;
|
|
132
|
-
const label = typeof row.label === "string" ? row.label.trim() : "";
|
|
133
|
-
if (!label) continue;
|
|
134
|
-
const options = parseOptions(row.options);
|
|
135
|
-
if ((type === "select" || type === "multiselect") && options.length === 0) continue;
|
|
136
|
-
const hint = typeof row.hint === "string" && row.hint.trim() ? row.hint.trim() : void 0;
|
|
137
|
-
const placeholder = typeof row.placeholder === "string" && row.placeholder.trim() ? row.placeholder.trim() : void 0;
|
|
138
|
-
const defaultValue = parseDefault(row.default, type, options);
|
|
139
|
-
seen.add(key);
|
|
140
|
-
const field = { key, type, label };
|
|
141
|
-
if (hint) field.hint = hint;
|
|
142
|
-
if (placeholder) field.placeholder = placeholder;
|
|
143
|
-
if (defaultValue !== void 0) field.default = defaultValue;
|
|
144
|
-
if (options.length) field.options = options;
|
|
145
|
-
out.push(field);
|
|
146
|
-
}
|
|
147
|
-
return out;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// src/connections.ts
|
|
151
|
-
var EXTERNAL_CONNECTION_ALLOWED_HOSTS = {
|
|
152
|
-
microsoft: ["graph.microsoft.com"],
|
|
153
|
-
google: ["googleapis.com", "www.googleapis.com"]
|
|
154
|
-
};
|
|
155
|
-
var EXTERNAL_CONNECTION_HTTP_METHODS = [
|
|
156
|
-
"GET",
|
|
157
|
-
"POST",
|
|
158
|
-
"PATCH",
|
|
159
|
-
"PUT",
|
|
160
|
-
"DELETE"
|
|
161
|
-
];
|
|
162
|
-
var CONNECTION_ID_RE = /^[a-z0-9][a-z0-9-_]{0,62}$/;
|
|
163
|
-
var OAUTH_PROVIDER_KEY_RE = /^[a-z0-9][a-z0-9_-]{0,62}$/;
|
|
164
|
-
var MAX_SCOPES = 32;
|
|
165
|
-
var MAX_ALLOWED_APIS = 24;
|
|
166
|
-
var MAX_SCOPE_LEN = 200;
|
|
167
|
-
var BLOCKED_HOST_SUFFIXES = [".localhost", ".local", ".internal", ".home", ".lan"];
|
|
168
|
-
function normalizeExactApiHost(raw) {
|
|
169
|
-
const host = raw.trim().toLowerCase().replace(/\.$/, "");
|
|
170
|
-
if (!host || host.length > 253 || host.includes("/") || host.includes(":") || host.includes("*") || host === "localhost" || BLOCKED_HOST_SUFFIXES.some((suffix) => host.endsWith(suffix))) {
|
|
171
|
-
return null;
|
|
172
|
-
}
|
|
173
|
-
if (!/^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)*[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(
|
|
174
|
-
host
|
|
175
|
-
)) {
|
|
176
|
-
return null;
|
|
177
|
-
}
|
|
178
|
-
if (/^\d{1,3}(\.\d{1,3}){3}$/.test(host)) return null;
|
|
179
|
-
return host;
|
|
180
|
-
}
|
|
181
|
-
function isExternalConnectionProvider(v) {
|
|
182
|
-
return OAUTH_PROVIDER_KEY_RE.test(v);
|
|
183
|
-
}
|
|
184
|
-
function parseAllowedApi(raw, provider) {
|
|
185
|
-
if (!raw || typeof raw !== "object") return null;
|
|
186
|
-
const o = raw;
|
|
187
|
-
const host = typeof o.host === "string" ? o.host.trim().toLowerCase() : "";
|
|
188
|
-
const pathPrefix = typeof o.path_prefix === "string" ? o.path_prefix.trim() : typeof o.pathPrefix === "string" ? o.pathPrefix.trim() : "";
|
|
189
|
-
const methodsRaw = o.methods;
|
|
190
|
-
if (!host || !pathPrefix || !normalizeExactApiHost(host)) return null;
|
|
191
|
-
const builtinHosts = EXTERNAL_CONNECTION_ALLOWED_HOSTS[provider];
|
|
192
|
-
if (builtinHosts && !builtinHosts.includes(host)) return null;
|
|
193
|
-
if (!pathPrefix.startsWith("/") || pathPrefix.includes("..") || pathPrefix.includes("\\")) {
|
|
194
|
-
return null;
|
|
195
|
-
}
|
|
196
|
-
if (!Array.isArray(methodsRaw) || methodsRaw.length === 0) return null;
|
|
197
|
-
const methods = [];
|
|
198
|
-
for (const m of methodsRaw) {
|
|
199
|
-
if (typeof m !== "string") return null;
|
|
200
|
-
const up = m.trim().toUpperCase();
|
|
201
|
-
if (!EXTERNAL_CONNECTION_HTTP_METHODS.includes(up)) return null;
|
|
202
|
-
if (!methods.includes(up)) {
|
|
203
|
-
methods.push(up);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
if (!methods.length) return null;
|
|
207
|
-
return { host, methods, path_prefix: pathPrefix };
|
|
208
|
-
}
|
|
209
|
-
function parsePluginConnections(raw) {
|
|
210
|
-
if (!Array.isArray(raw)) return [];
|
|
211
|
-
const out = [];
|
|
212
|
-
const seen = /* @__PURE__ */ new Set();
|
|
213
|
-
for (const item of raw) {
|
|
214
|
-
if (!item || typeof item !== "object") continue;
|
|
215
|
-
const o = item;
|
|
216
|
-
const id = typeof o.id === "string" ? o.id.trim().toLowerCase() : "";
|
|
217
|
-
if (!id || !CONNECTION_ID_RE.test(id) || seen.has(id)) continue;
|
|
218
|
-
const providerRaw = typeof o.provider === "string" ? o.provider.trim().toLowerCase() : "";
|
|
219
|
-
if (!isExternalConnectionProvider(providerRaw)) continue;
|
|
220
|
-
const scopesRaw = o.scopes;
|
|
221
|
-
if (!Array.isArray(scopesRaw) || scopesRaw.length === 0 || scopesRaw.length > MAX_SCOPES) continue;
|
|
222
|
-
const scopes = [];
|
|
223
|
-
for (const s of scopesRaw) {
|
|
224
|
-
if (typeof s !== "string") continue;
|
|
225
|
-
const t = s.trim();
|
|
226
|
-
if (!t || t.length > MAX_SCOPE_LEN) continue;
|
|
227
|
-
if (!scopes.includes(t)) scopes.push(t);
|
|
228
|
-
}
|
|
229
|
-
if (!scopes.length) continue;
|
|
230
|
-
const apisRaw = o.allowed_apis ?? o.allowedApis;
|
|
231
|
-
if (!Array.isArray(apisRaw) || apisRaw.length === 0 || apisRaw.length > MAX_ALLOWED_APIS) continue;
|
|
232
|
-
const allowed_apis = [];
|
|
233
|
-
for (const a of apisRaw) {
|
|
234
|
-
const rule = parseAllowedApi(a, providerRaw);
|
|
235
|
-
if (rule) allowed_apis.push(rule);
|
|
236
|
-
}
|
|
237
|
-
if (!allowed_apis.length) continue;
|
|
238
|
-
seen.add(id);
|
|
239
|
-
out.push({
|
|
240
|
-
id,
|
|
241
|
-
provider: providerRaw,
|
|
242
|
-
consent: "user",
|
|
243
|
-
scopes,
|
|
244
|
-
allowed_apis
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
|
-
return out;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// src/zip-safety.ts
|
|
251
|
-
var MAX_PLUGIN_COMPRESSION_RATIO = 100;
|
|
252
|
-
function assertZipSafe(zip) {
|
|
253
|
-
const names = Object.keys(zip.files);
|
|
254
|
-
if (names.length > MAX_PLUGIN_ZIP_ENTRIES) {
|
|
255
|
-
return { ok: false, error: "zip_too_many_entries" };
|
|
256
|
-
}
|
|
257
|
-
let totalUncompressed = 0;
|
|
258
|
-
for (const name of names) {
|
|
259
|
-
if (name.includes("..") || name.includes("\\") || name.startsWith("/")) {
|
|
260
|
-
return { ok: false, error: "zip_unsafe_entry_path" };
|
|
261
|
-
}
|
|
262
|
-
if (/\.zip$/i.test(name) && !zip.files[name]?.dir) {
|
|
263
|
-
return { ok: false, error: "zip_nested_archive" };
|
|
264
|
-
}
|
|
265
|
-
const entry = zip.files[name];
|
|
266
|
-
const uncompressed = entry._data?.uncompressedSize ?? 0;
|
|
267
|
-
const compressed = entry._data?.compressedSize ?? 0;
|
|
268
|
-
totalUncompressed += uncompressed;
|
|
269
|
-
if (totalUncompressed > MAX_PLUGIN_UNCOMPRESSED_BYTES) {
|
|
270
|
-
return { ok: false, error: "zip_uncompressed_too_large" };
|
|
271
|
-
}
|
|
272
|
-
if (compressed > 0 && uncompressed > 1024 * 1024) {
|
|
273
|
-
const ratio = uncompressed / compressed;
|
|
274
|
-
if (ratio > MAX_PLUGIN_COMPRESSION_RATIO) {
|
|
275
|
-
return { ok: false, error: "zip_compression_ratio_too_high" };
|
|
276
|
-
}
|
|
277
|
-
} else if (compressed === 0 && uncompressed > 1024 * 1024) {
|
|
278
|
-
return { ok: false, error: "zip_compression_ratio_too_high" };
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
return { ok: true, value: true };
|
|
282
|
-
}
|
|
283
|
-
function assertZipFile(file) {
|
|
284
|
-
const name = (file.name ?? "upload.zip").toLowerCase();
|
|
285
|
-
if (!name.endsWith(".zip")) return { ok: false, error: "invalid_file_extension" };
|
|
286
|
-
if (file.size <= 0) return { ok: false, error: "empty_file" };
|
|
287
|
-
if (file.size > MAX_PLUGIN_ZIP_BYTES) return { ok: false, error: "file_too_large" };
|
|
288
|
-
return { ok: true, value: true };
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// src/semver.ts
|
|
292
|
-
var CORE_SEMVER_RE = /^(\d+)\.(\d+)\.(\d+)/;
|
|
293
|
-
function nextPatchVersion(latest) {
|
|
294
|
-
if (!latest || typeof latest !== "string") return "0.1.0";
|
|
295
|
-
const m = latest.trim().match(CORE_SEMVER_RE);
|
|
296
|
-
if (!m) return "0.1.0";
|
|
297
|
-
const major = Number(m[1]);
|
|
298
|
-
const minor = Number(m[2]);
|
|
299
|
-
const patch = Number(m[3]);
|
|
300
|
-
if (![major, minor, patch].every((n) => Number.isFinite(n) && n >= 0)) return "0.1.0";
|
|
301
|
-
return `${major}.${minor}.${patch + 1}`;
|
|
302
|
-
}
|
|
303
|
-
function compareSemverDesc(a, b) {
|
|
304
|
-
const pa = a.match(CORE_SEMVER_RE);
|
|
305
|
-
const pb = b.match(CORE_SEMVER_RE);
|
|
306
|
-
if (!pa && !pb) return 0;
|
|
307
|
-
if (!pa) return 1;
|
|
308
|
-
if (!pb) return -1;
|
|
309
|
-
for (let i = 1; i <= 3; i++) {
|
|
310
|
-
const da = Number(pa[i]) - Number(pb[i]);
|
|
311
|
-
if (da !== 0) return da > 0 ? -1 : 1;
|
|
312
|
-
}
|
|
313
|
-
return 0;
|
|
314
|
-
}
|
|
315
|
-
var pluginManifestZodSchema = zod.z.object({
|
|
316
|
-
plugin_key: zod.z.string().regex(/^[a-z0-9][a-z0-9-_]{1,62}$/).optional().describe("Stable plugin id. Prefer this over legacy `id`."),
|
|
317
|
-
id: zod.z.string().optional().describe("Legacy alias for plugin_key (slashes become dashes)."),
|
|
318
|
-
plugin_name: zod.z.string().optional(),
|
|
319
|
-
name: zod.z.string().optional().describe("Legacy alias for plugin_name."),
|
|
320
|
-
plugin_type: zod.z.enum(["task", "form", "task_form", "route", "policy", "server"]).optional().describe("Use `policy` for Policy Plugins. `server` is a legacy alias for `policy`."),
|
|
321
|
-
kind: zod.z.string().optional().describe("Legacy alias for plugin_type (task_widget\u2192task, record_panel\u2192form)."),
|
|
322
|
-
version: zod.z.string().optional().describe("Ignored by Cervio - the host assigns release versions."),
|
|
323
|
-
hooks: zod.z.array(
|
|
324
|
-
zod.z.object({
|
|
325
|
-
event: zod.z.enum(["before_create", "before_update"]),
|
|
326
|
-
entities: zod.z.array(zod.z.string()).optional(),
|
|
327
|
-
blocking: zod.z.boolean().optional(),
|
|
328
|
-
identity: zod.z.enum(["caller", "plugin_principal"]).optional(),
|
|
329
|
-
entry: zod.z.string().optional()
|
|
330
|
-
})
|
|
331
|
-
).optional().describe("Policy Plugin hook declarations (plugin_type=policy; legacy server)."),
|
|
332
|
-
required_connection_capabilities: zod.z.array(zod.z.string()).optional(),
|
|
333
|
-
requiredConnectionCapabilities: zod.z.array(zod.z.string()).optional(),
|
|
334
|
-
connections: zod.z.array(
|
|
335
|
-
zod.z.object({
|
|
336
|
-
id: zod.z.string(),
|
|
337
|
-
provider: zod.z.string(),
|
|
338
|
-
scopes: zod.z.array(zod.z.string()),
|
|
339
|
-
allowed_apis: zod.z.array(
|
|
340
|
-
zod.z.object({
|
|
341
|
-
host: zod.z.string(),
|
|
342
|
-
methods: zod.z.array(zod.z.string()),
|
|
343
|
-
path_prefix: zod.z.string().optional(),
|
|
344
|
-
pathPrefix: zod.z.string().optional()
|
|
345
|
-
})
|
|
346
|
-
).optional(),
|
|
347
|
-
allowedApis: zod.z.array(zod.z.unknown()).optional()
|
|
348
|
-
})
|
|
349
|
-
).optional(),
|
|
350
|
-
entities: zod.z.array(
|
|
351
|
-
zod.z.object({
|
|
352
|
-
name: zod.z.string(),
|
|
353
|
-
fields: zod.z.array(zod.z.string()).optional(),
|
|
354
|
-
forms: zod.z.array(zod.z.string()).optional(),
|
|
355
|
-
access: zod.z.array(zod.z.enum(["read", "write"])).optional()
|
|
356
|
-
})
|
|
357
|
-
).optional(),
|
|
358
|
-
cervio_apis: zod.z.array(
|
|
359
|
-
zod.z.object({
|
|
360
|
-
method: zod.z.string().optional(),
|
|
361
|
-
path_prefix: zod.z.string().optional(),
|
|
362
|
-
pathPrefix: zod.z.string().optional()
|
|
363
|
-
})
|
|
364
|
-
).optional(),
|
|
365
|
-
cervioApis: zod.z.array(zod.z.unknown()).optional(),
|
|
366
|
-
settings: zod.z.array(
|
|
367
|
-
zod.z.object({
|
|
368
|
-
key: zod.z.string(),
|
|
369
|
-
type: zod.z.enum(["string", "boolean", "select", "multiselect"]),
|
|
370
|
-
label: zod.z.string(),
|
|
371
|
-
hint: zod.z.string().optional(),
|
|
372
|
-
placeholder: zod.z.string().optional(),
|
|
373
|
-
default: zod.z.union([zod.z.string(), zod.z.boolean(), zod.z.array(zod.z.string())]).optional(),
|
|
374
|
-
options: zod.z.array(
|
|
375
|
-
zod.z.object({
|
|
376
|
-
value: zod.z.string(),
|
|
377
|
-
label: zod.z.string()
|
|
378
|
-
})
|
|
379
|
-
).optional().describe("Required for `select` / `multiselect`.")
|
|
380
|
-
})
|
|
381
|
-
).optional().describe("Admin-editable settings fields (drives Impostazioni dynamic form).")
|
|
382
|
-
}).passthrough();
|
|
383
|
-
|
|
384
|
-
// src/policy-types.ts
|
|
385
|
-
function isPolicyPluginType(t) {
|
|
386
|
-
return t === "policy" || t === "server";
|
|
387
|
-
}
|
|
388
|
-
function normalizeToPolicyPluginType(t) {
|
|
389
|
-
return isPolicyPluginType(t) ? "policy" : null;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
// src/policy-entry.ts
|
|
393
|
-
var BARE_IMPORT_RE = /(?:from|import)\s*\(\s*['"](?![./]|data:)([^'"]+)['"]\s*\)|(?:from|import)\s+['"](?![./]|data:)([^'"]+)['"]/g;
|
|
394
|
-
function findBareModuleSpecifiers(source) {
|
|
395
|
-
const found = /* @__PURE__ */ new Set();
|
|
396
|
-
for (const match of source.matchAll(BARE_IMPORT_RE)) {
|
|
397
|
-
const spec = (match[1] || match[2] || "").trim();
|
|
398
|
-
if (spec) found.add(spec);
|
|
399
|
-
}
|
|
400
|
-
return [...found];
|
|
401
|
-
}
|
|
402
|
-
function assertPolicyEntrySelfContained(source) {
|
|
403
|
-
const specifiers = findBareModuleSpecifiers(source);
|
|
404
|
-
if (specifiers.length === 0) return { ok: true };
|
|
405
|
-
return {
|
|
406
|
-
ok: false,
|
|
407
|
-
error: "policy_entry_not_self_contained",
|
|
408
|
-
specifiers
|
|
409
|
-
};
|
|
410
|
-
}
|
|
411
|
-
function normalizePluginKey(raw) {
|
|
412
|
-
return raw.trim().toLowerCase().replace(/[\/\s]+/g, "-").replace(/[^a-z0-9-_]/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
|
|
413
|
-
}
|
|
414
|
-
function resolvePluginType(raw) {
|
|
415
|
-
const kindMap = {
|
|
416
|
-
route: "route",
|
|
417
|
-
task: "task",
|
|
418
|
-
form: "form",
|
|
419
|
-
task_form: "task_form",
|
|
420
|
-
// Legacy wire → canonical Policy Plugin type
|
|
421
|
-
server: "policy",
|
|
422
|
-
policy: "policy",
|
|
423
|
-
task_widget: "task",
|
|
424
|
-
record_panel: "form"
|
|
425
|
-
};
|
|
426
|
-
const candidate = kindMap[raw] ?? raw;
|
|
427
|
-
return ["task", "form", "task_form", "route", "policy", "server"].includes(
|
|
428
|
-
candidate
|
|
429
|
-
) ? candidate : null;
|
|
430
|
-
}
|
|
431
|
-
function parseRequiredCapabilities(raw, allowed) {
|
|
432
|
-
if (!Array.isArray(raw)) return [];
|
|
433
|
-
const allowedSet = new Set(allowed);
|
|
434
|
-
const out = [];
|
|
435
|
-
const seen = /* @__PURE__ */ new Set();
|
|
436
|
-
for (const item of raw) {
|
|
437
|
-
if (typeof item !== "string") continue;
|
|
438
|
-
const id = item.trim().toLowerCase();
|
|
439
|
-
if (!id || !allowedSet.has(id) || seen.has(id)) continue;
|
|
440
|
-
seen.add(id);
|
|
441
|
-
out.push(id);
|
|
442
|
-
}
|
|
443
|
-
return out;
|
|
444
|
-
}
|
|
445
|
-
function parseManifestObject(parsed, options) {
|
|
446
|
-
const allowedCapabilities = options?.allowedCapabilities ?? DEFAULT_PLUGIN_DECLARABLE_CAPABILITIES;
|
|
447
|
-
const pluginKeyRaw = typeof parsed.plugin_key === "string" ? parsed.plugin_key : typeof parsed.id === "string" ? parsed.id.split("/").filter(Boolean).join("-") : "";
|
|
448
|
-
const pluginKey = normalizePluginKey(pluginKeyRaw);
|
|
449
|
-
if (!pluginKey || !PLUGIN_KEY_RE.test(pluginKey)) {
|
|
450
|
-
return { ok: false, error: "manifest_plugin_key_invalid" };
|
|
451
|
-
}
|
|
452
|
-
const pluginName = typeof parsed.plugin_name === "string" && parsed.plugin_name.trim() ? parsed.plugin_name.trim() : typeof parsed.name === "string" && parsed.name.trim() ? parsed.name.trim() : null;
|
|
453
|
-
const pluginTypeRaw = typeof parsed.plugin_type === "string" ? parsed.plugin_type.trim() : typeof parsed.kind === "string" ? parsed.kind.trim() : "";
|
|
454
|
-
const pluginType = resolvePluginType(pluginTypeRaw);
|
|
455
|
-
const manifestVersion = typeof parsed.version === "string" && parsed.version.trim() ? parsed.version.trim() : null;
|
|
456
|
-
const requiredRaw = parsed.required_connection_capabilities ?? parsed.requiredConnectionCapabilities ?? [];
|
|
457
|
-
const requiredConnectionCapabilities = parseRequiredCapabilities(
|
|
458
|
-
requiredRaw,
|
|
459
|
-
allowedCapabilities
|
|
460
|
-
);
|
|
461
|
-
const connections = parsePluginConnections(parsed.connections);
|
|
462
|
-
const entities = parsePluginEntityRequirements(parsed.entities);
|
|
463
|
-
const cervioApis = parsePluginCervioApiRequirements(
|
|
464
|
-
parsed.cervio_apis ?? parsed.cervioApis ?? []
|
|
465
|
-
);
|
|
466
|
-
const settings = parsePluginSettingsRequirements(parsed.settings);
|
|
467
|
-
return {
|
|
468
|
-
ok: true,
|
|
469
|
-
value: {
|
|
470
|
-
pluginKey,
|
|
471
|
-
pluginName,
|
|
472
|
-
pluginType,
|
|
473
|
-
manifestVersion,
|
|
474
|
-
requiredConnectionCapabilities,
|
|
475
|
-
connections,
|
|
476
|
-
entities,
|
|
477
|
-
cervioApis,
|
|
478
|
-
settings
|
|
479
|
-
}
|
|
480
|
-
};
|
|
481
|
-
}
|
|
482
|
-
function manifestJsonForContentHash(raw) {
|
|
483
|
-
const clone = { ...raw };
|
|
484
|
-
delete clone.version;
|
|
485
|
-
return JSON.stringify(clone);
|
|
486
|
-
}
|
|
487
|
-
async function loadZipFromBuffer(buffer) {
|
|
488
|
-
try {
|
|
489
|
-
const zip = await JSZip__default.default.loadAsync(buffer);
|
|
490
|
-
const safe = assertZipSafe(zip);
|
|
491
|
-
if (!safe.ok) return safe;
|
|
492
|
-
return { ok: true, value: zip };
|
|
493
|
-
} catch {
|
|
494
|
-
return { ok: false, error: "zip_invalid" };
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
async function parseZipManifest(file, options) {
|
|
498
|
-
const buffer = await file.arrayBuffer();
|
|
499
|
-
const zipRes = await loadZipFromBuffer(buffer);
|
|
500
|
-
if (!zipRes.ok) return zipRes;
|
|
501
|
-
const zip = zipRes.value;
|
|
502
|
-
const manifestEntry = zip.file("manifest.json") ?? zip.file("manifest.example.json") ?? Object.values(zip.files).find(
|
|
503
|
-
(f) => f.name.endsWith("/manifest.json") || f.name.endsWith("/manifest.example.json")
|
|
504
|
-
);
|
|
505
|
-
if (!manifestEntry) return { ok: false, error: "manifest_not_found_in_zip" };
|
|
506
|
-
const jsonText = await manifestEntry.async("text");
|
|
507
|
-
let parsed;
|
|
508
|
-
try {
|
|
509
|
-
parsed = JSON.parse(jsonText);
|
|
510
|
-
} catch {
|
|
511
|
-
return { ok: false, error: "manifest_invalid_json" };
|
|
512
|
-
}
|
|
513
|
-
const fields = parseManifestObject(parsed, options);
|
|
514
|
-
if (!fields.ok) return fields;
|
|
515
|
-
const isPolicy = isPolicyPluginType(fields.value.pluginType);
|
|
516
|
-
if (!isPolicy) {
|
|
517
|
-
const hasDistIndex = !!zip.file("dist/index.html");
|
|
518
|
-
if (!hasDistIndex) return { ok: false, error: "build_index_missing" };
|
|
519
|
-
const hasDistAssets = Object.keys(zip.files).some((name) => /^dist\/assets\/.+/.test(name));
|
|
520
|
-
if (!hasDistAssets) return { ok: false, error: "build_assets_missing" };
|
|
521
|
-
}
|
|
522
|
-
const entryFromHooks = isPolicy && Array.isArray(parsed.hooks) ? (() => {
|
|
523
|
-
for (const h of parsed.hooks) {
|
|
524
|
-
if (typeof h?.entry === "string" && h.entry.trim()) return h.entry.trim();
|
|
525
|
-
}
|
|
526
|
-
return null;
|
|
527
|
-
})() : null;
|
|
528
|
-
const artifactEntryPath = isPolicy ? entryFromHooks || "server/index.js" : "dist/index.html";
|
|
529
|
-
if (isPolicy) {
|
|
530
|
-
const entryFile = zip.file(artifactEntryPath) || zip.file(artifactEntryPath.replace(/^\//, "")) || Object.values(zip.files).find((f) => f.name.endsWith(`/${artifactEntryPath}`));
|
|
531
|
-
if (!entryFile) return { ok: false, error: "manifest_not_found_in_zip" };
|
|
532
|
-
const entrySource = await entryFile.async("text");
|
|
533
|
-
const selfContained = assertPolicyEntrySelfContained(entrySource);
|
|
534
|
-
if (!selfContained.ok) {
|
|
535
|
-
return {
|
|
536
|
-
ok: false,
|
|
537
|
-
error: `${selfContained.error}:${selfContained.specifiers.join(",")}`
|
|
538
|
-
};
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
return {
|
|
542
|
-
ok: true,
|
|
543
|
-
value: {
|
|
544
|
-
...fields.value,
|
|
545
|
-
artifactEntryPath,
|
|
546
|
-
rawManifest: parsed,
|
|
547
|
-
manifestEntryName: manifestEntry.name
|
|
548
|
-
}
|
|
549
|
-
};
|
|
550
|
-
}
|
|
551
|
-
async function computePluginContentSha256(zip, rawManifest) {
|
|
552
|
-
try {
|
|
553
|
-
const hash = crypto.createHash("sha256");
|
|
554
|
-
hash.update("manifest\0");
|
|
555
|
-
hash.update(manifestJsonForContentHash(rawManifest));
|
|
556
|
-
hash.update("\0");
|
|
557
|
-
const distPaths = Object.keys(zip.files).filter((name) => {
|
|
558
|
-
const entry = zip.files[name];
|
|
559
|
-
if (!entry || entry.dir) return false;
|
|
560
|
-
const normalized = name.replace(/^\.\//, "");
|
|
561
|
-
return normalized === "dist" || normalized.startsWith("dist/");
|
|
562
|
-
}).map((name) => name.replace(/^\.\//, "")).sort((a, b) => a.localeCompare(b));
|
|
563
|
-
for (const path2 of distPaths) {
|
|
564
|
-
const entry = zip.file(path2);
|
|
565
|
-
if (!entry || entry.dir) continue;
|
|
566
|
-
const bytes = await entry.async("uint8array");
|
|
567
|
-
hash.update("file\0");
|
|
568
|
-
hash.update(path2);
|
|
569
|
-
hash.update("\0");
|
|
570
|
-
hash.update(bytes);
|
|
571
|
-
hash.update("\0");
|
|
572
|
-
}
|
|
573
|
-
return { ok: true, value: hash.digest("hex") };
|
|
574
|
-
} catch {
|
|
575
|
-
return { ok: false, error: "content_hash_failed" };
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
function sha256Hex(buffer) {
|
|
579
|
-
return crypto.createHash("sha256").update(buffer).digest("hex");
|
|
580
|
-
}
|
|
581
|
-
async function collectFiles(dir, base = "") {
|
|
582
|
-
const entries = await fs.promises.readdir(dir, { withFileTypes: true });
|
|
583
|
-
const out = [];
|
|
584
|
-
for (const entry of entries) {
|
|
585
|
-
const abs = path__default.default.join(dir, entry.name);
|
|
586
|
-
const rel = base ? `${base}/${entry.name}` : entry.name;
|
|
587
|
-
if (entry.isDirectory()) {
|
|
588
|
-
out.push(...await collectFiles(abs, rel));
|
|
589
|
-
} else if (entry.isFile()) {
|
|
590
|
-
out.push({ rel: rel.replace(/\\/g, "/"), abs });
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
return out;
|
|
594
|
-
}
|
|
595
|
-
async function packPlugin(opts) {
|
|
596
|
-
const root = path__default.default.resolve(opts.dir);
|
|
597
|
-
const manifestPath = path__default.default.join(root, "manifest.json");
|
|
598
|
-
let manifestRaw;
|
|
599
|
-
try {
|
|
600
|
-
const text = await fs.promises.readFile(manifestPath, "utf8");
|
|
601
|
-
manifestRaw = JSON.parse(text);
|
|
602
|
-
} catch {
|
|
603
|
-
return { ok: false, error: "manifest_not_found_in_zip" };
|
|
604
|
-
}
|
|
605
|
-
const fields = parseManifestObject(manifestRaw, opts.options);
|
|
606
|
-
if (!fields.ok) return fields;
|
|
607
|
-
const isPolicy = isPolicyPluginType(fields.value.pluginType);
|
|
608
|
-
if (!isPolicy) {
|
|
609
|
-
const distIndex = path__default.default.join(root, "dist", "index.html");
|
|
610
|
-
try {
|
|
611
|
-
await fs.promises.access(distIndex);
|
|
612
|
-
} catch {
|
|
613
|
-
return { ok: false, error: "build_index_missing" };
|
|
614
|
-
}
|
|
615
|
-
} else {
|
|
616
|
-
const entry = (Array.isArray(manifestRaw.hooks) ? manifestRaw.hooks.find((h) => h?.entry)?.entry : null) || "server/index.js";
|
|
617
|
-
const entryAbs = path__default.default.join(root, entry);
|
|
618
|
-
try {
|
|
619
|
-
await fs.promises.access(entryAbs);
|
|
620
|
-
} catch {
|
|
621
|
-
return { ok: false, error: "manifest_not_found_in_zip" };
|
|
622
|
-
}
|
|
623
|
-
let entrySource;
|
|
624
|
-
try {
|
|
625
|
-
entrySource = await fs.promises.readFile(entryAbs, "utf8");
|
|
626
|
-
} catch {
|
|
627
|
-
return { ok: false, error: "manifest_not_found_in_zip" };
|
|
628
|
-
}
|
|
629
|
-
const selfContained = assertPolicyEntrySelfContained(entrySource);
|
|
630
|
-
if (!selfContained.ok) {
|
|
631
|
-
return {
|
|
632
|
-
ok: false,
|
|
633
|
-
error: `${selfContained.error}:${selfContained.specifiers.join(",")}`
|
|
634
|
-
};
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
const zip = new JSZip__default.default();
|
|
638
|
-
const files = await collectFiles(root);
|
|
639
|
-
let hasAssets = false;
|
|
640
|
-
for (const file of files) {
|
|
641
|
-
if (file.rel.startsWith(".cervio/") || file.rel === "node_modules" || file.rel.startsWith("node_modules/")) {
|
|
642
|
-
continue;
|
|
643
|
-
}
|
|
644
|
-
if (file.rel.startsWith("dist/assets/") && file.rel !== "dist/assets/") {
|
|
645
|
-
hasAssets = true;
|
|
646
|
-
}
|
|
647
|
-
const data = await fs.promises.readFile(file.abs);
|
|
648
|
-
zip.file(file.rel, data);
|
|
649
|
-
}
|
|
650
|
-
if (!isPolicy && !hasAssets) return { ok: false, error: "build_assets_missing" };
|
|
651
|
-
const safe = assertZipSafe(zip);
|
|
652
|
-
if (!safe.ok) return safe;
|
|
653
|
-
const buffer = Buffer.from(await zip.generateAsync({ type: "uint8array", compression: "DEFLATE" }));
|
|
654
|
-
const fileCheck = assertZipFile({ name: `${fields.value.pluginKey}.zip`, size: buffer.length });
|
|
655
|
-
if (!fileCheck.ok) return fileCheck;
|
|
656
|
-
const loaded = await loadZipFromBuffer(buffer);
|
|
657
|
-
if (!loaded.ok) return loaded;
|
|
658
|
-
const hash = await computePluginContentSha256(loaded.value, manifestRaw);
|
|
659
|
-
if (!hash.ok) return hash;
|
|
660
|
-
return {
|
|
661
|
-
ok: true,
|
|
662
|
-
value: {
|
|
663
|
-
buffer,
|
|
664
|
-
pluginKey: fields.value.pluginKey,
|
|
665
|
-
contentSha256: hash.value,
|
|
666
|
-
sizeBytes: buffer.length
|
|
667
|
-
}
|
|
668
|
-
};
|
|
669
|
-
}
|
|
670
|
-
async function validatePluginDir(dir, options) {
|
|
671
|
-
const packed = await packPlugin({ dir, options });
|
|
672
|
-
if (!packed.ok) return packed;
|
|
673
|
-
const blob = new Blob([packed.value.buffer], { type: "application/zip" });
|
|
674
|
-
const parsed = await parseZipManifest(blob, options);
|
|
675
|
-
if (!parsed.ok) return parsed;
|
|
676
|
-
return {
|
|
677
|
-
ok: true,
|
|
678
|
-
value: {
|
|
679
|
-
pluginKey: packed.value.pluginKey,
|
|
680
|
-
contentSha256: packed.value.contentSha256
|
|
681
|
-
}
|
|
682
|
-
};
|
|
683
|
-
}
|
|
684
|
-
async function validatePluginZipBuffer(buffer, options) {
|
|
685
|
-
const fileCheck = assertZipFile({ name: "plugin.zip", size: buffer.length });
|
|
686
|
-
if (!fileCheck.ok) return fileCheck;
|
|
687
|
-
const blob = new Blob([buffer], { type: "application/zip" });
|
|
688
|
-
const parsed = await parseZipManifest(blob, options);
|
|
689
|
-
if (!parsed.ok) return parsed;
|
|
690
|
-
const zipRes = await loadZipFromBuffer(buffer);
|
|
691
|
-
if (!zipRes.ok) return zipRes;
|
|
692
|
-
const hash = await computePluginContentSha256(zipRes.value, parsed.value.rawManifest);
|
|
693
|
-
if (!hash.ok) return hash;
|
|
694
|
-
return {
|
|
695
|
-
ok: true,
|
|
696
|
-
value: { pluginKey: parsed.value.pluginKey, contentSha256: hash.value }
|
|
697
|
-
};
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
exports.DEFAULT_PLUGIN_DECLARABLE_CAPABILITIES = DEFAULT_PLUGIN_DECLARABLE_CAPABILITIES;
|
|
701
|
-
exports.EXTERNAL_CONNECTION_ALLOWED_HOSTS = EXTERNAL_CONNECTION_ALLOWED_HOSTS;
|
|
702
|
-
exports.EXTERNAL_CONNECTION_HTTP_METHODS = EXTERNAL_CONNECTION_HTTP_METHODS;
|
|
703
|
-
exports.MAX_PLUGIN_UNCOMPRESSED_BYTES = MAX_PLUGIN_UNCOMPRESSED_BYTES;
|
|
704
|
-
exports.MAX_PLUGIN_ZIP_BYTES = MAX_PLUGIN_ZIP_BYTES;
|
|
705
|
-
exports.MAX_PLUGIN_ZIP_ENTRIES = MAX_PLUGIN_ZIP_ENTRIES;
|
|
706
|
-
exports.OAUTH_PROVIDER_KEY_RE = OAUTH_PROVIDER_KEY_RE;
|
|
707
|
-
exports.PLUGIN_ARTIFACT_BUCKET = PLUGIN_ARTIFACT_BUCKET;
|
|
708
|
-
exports.PLUGIN_KEY_RE = PLUGIN_KEY_RE;
|
|
709
|
-
exports.PLUGIN_VERSION_SELECT = PLUGIN_VERSION_SELECT;
|
|
710
|
-
exports.assertPolicyEntrySelfContained = assertPolicyEntrySelfContained;
|
|
711
|
-
exports.assertZipFile = assertZipFile;
|
|
712
|
-
exports.assertZipSafe = assertZipSafe;
|
|
713
|
-
exports.compareSemverDesc = compareSemverDesc;
|
|
714
|
-
exports.computePluginContentSha256 = computePluginContentSha256;
|
|
715
|
-
exports.findBareModuleSpecifiers = findBareModuleSpecifiers;
|
|
716
|
-
exports.isExternalConnectionProvider = isExternalConnectionProvider;
|
|
717
|
-
exports.isPolicyPluginType = isPolicyPluginType;
|
|
718
|
-
exports.loadZipFromBuffer = loadZipFromBuffer;
|
|
719
|
-
exports.manifestJsonForContentHash = manifestJsonForContentHash;
|
|
720
|
-
exports.nextPatchVersion = nextPatchVersion;
|
|
721
|
-
exports.normalizeExactApiHost = normalizeExactApiHost;
|
|
722
|
-
exports.normalizePluginKey = normalizePluginKey;
|
|
723
|
-
exports.normalizeToPolicyPluginType = normalizeToPolicyPluginType;
|
|
724
|
-
exports.packPlugin = packPlugin;
|
|
725
|
-
exports.parseManifestObject = parseManifestObject;
|
|
726
|
-
exports.parsePluginCervioApiRequirements = parsePluginCervioApiRequirements;
|
|
727
|
-
exports.parsePluginConnections = parsePluginConnections;
|
|
728
|
-
exports.parsePluginEntityRequirements = parsePluginEntityRequirements;
|
|
729
|
-
exports.parsePluginSettingsRequirements = parsePluginSettingsRequirements;
|
|
730
|
-
exports.parseZipManifest = parseZipManifest;
|
|
731
|
-
exports.pluginManifestZodSchema = pluginManifestZodSchema;
|
|
732
|
-
exports.sha256Hex = sha256Hex;
|
|
733
|
-
exports.validatePluginDir = validatePluginDir;
|
|
734
|
-
exports.validatePluginZipBuffer = validatePluginZipBuffer;
|
|
735
|
-
//# sourceMappingURL=index.cjs.map
|
|
736
|
-
//# sourceMappingURL=index.cjs.map
|