@cervio/plugin-core 0.1.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.
@@ -0,0 +1,424 @@
1
+ import JSZip from 'jszip';
2
+ import { z } from 'zod';
3
+
4
+ type PluginType = 'task' | 'form' | 'task_form' | 'route';
5
+ type PluginActionResult<T> = {
6
+ ok: true;
7
+ value: T;
8
+ } | {
9
+ ok: false;
10
+ error: string;
11
+ };
12
+ interface PluginVersionRow {
13
+ id: string;
14
+ plugin_id: string;
15
+ version: string;
16
+ changelog: string | null;
17
+ artifact_path: string;
18
+ artifact_entry_path?: string | null;
19
+ artifact_sha256: string;
20
+ content_sha256?: string | null;
21
+ artifact_size_bytes: number;
22
+ created_at: string;
23
+ }
24
+ interface ParsedPluginManifest {
25
+ pluginKey: string;
26
+ pluginName: string | null;
27
+ pluginType: PluginType | null;
28
+ /** Present in ZIP but ignored for assignment; Cervio owns release versions. */
29
+ manifestVersion: string | null;
30
+ /** Catalog capability ids the plugin needs (e.g. calendar). */
31
+ requiredConnectionCapabilities: string[];
32
+ /** External Connections requirements (OAuth broker). */
33
+ connections: PluginConnectionRequirement[];
34
+ /** Declared Cervio entity deps (name-based; for Admin + future export). */
35
+ entities: PluginEntityRequirement[];
36
+ /** Declared Cervio API path prefixes (documentation / future allowlist). */
37
+ cervioApis: PluginCervioApiRequirement[];
38
+ artifactEntryPath: string;
39
+ rawManifest: Record<string, unknown>;
40
+ manifestEntryName: string;
41
+ }
42
+ type PluginEntityAccess = 'read' | 'write';
43
+ type PluginEntityRequirement = {
44
+ name: string;
45
+ fields: string[];
46
+ forms: string[];
47
+ access: PluginEntityAccess[];
48
+ };
49
+ type PluginCervioApiRequirement = {
50
+ method: string;
51
+ path_prefix: string;
52
+ };
53
+ type ExternalConnectionHttpMethod = 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE';
54
+ type AllowedApiRule = {
55
+ host: string;
56
+ methods: ExternalConnectionHttpMethod[];
57
+ path_prefix: string;
58
+ };
59
+ type PluginConnectionRequirement = {
60
+ id: string;
61
+ provider: string;
62
+ consent: 'user';
63
+ scopes: string[];
64
+ allowed_apis: AllowedApiRule[];
65
+ };
66
+ type ParseManifestOptions = {
67
+ /**
68
+ * Capability ids a plugin may declare in `required_connection_capabilities`.
69
+ * Host passes the live catalog; CLI uses {@link DEFAULT_PLUGIN_DECLARABLE_CAPABILITIES}.
70
+ */
71
+ allowedCapabilities?: readonly string[];
72
+ };
73
+ declare const PLUGIN_ARTIFACT_BUCKET = "plugin-artifacts";
74
+ declare const MAX_PLUGIN_ZIP_BYTES: number;
75
+ declare const MAX_PLUGIN_ZIP_ENTRIES = 5000;
76
+ declare const MAX_PLUGIN_UNCOMPRESSED_BYTES: number;
77
+ declare const PLUGIN_KEY_RE: RegExp;
78
+ declare const PLUGIN_VERSION_SELECT = "id, plugin_id, version, changelog, artifact_path, artifact_entry_path, artifact_sha256, content_sha256, artifact_size_bytes, created_at";
79
+ /**
80
+ * Static default for CLI validate. Generated from host `CONNECTION_CAPABILITY_CATALOG`
81
+ * where `pluginDeclarable === true`. Guarded by drift test against the host catalog.
82
+ */
83
+ declare const DEFAULT_PLUGIN_DECLARABLE_CAPABILITIES: readonly ["calendar"];
84
+
85
+ /** Parse `entities` from manifest.json (invalid entries skipped). */
86
+ declare function parsePluginEntityRequirements(raw: unknown): PluginEntityRequirement[];
87
+ /** Parse `cervio_apis` from manifest.json. */
88
+ declare function parsePluginCervioApiRequirements(raw: unknown): PluginCervioApiRequirement[];
89
+
90
+ declare const EXTERNAL_CONNECTION_ALLOWED_HOSTS: Record<string, readonly string[]>;
91
+ declare const EXTERNAL_CONNECTION_HTTP_METHODS: readonly ["GET", "POST", "PATCH", "PUT", "DELETE"];
92
+ declare const OAUTH_PROVIDER_KEY_RE: RegExp;
93
+ /** Lightweight host check (no DNS); mirrors host `normalizeExactApiHost` shape rules. */
94
+ declare function normalizeExactApiHost(raw: string): string | null;
95
+ declare function isExternalConnectionProvider(v: string): boolean;
96
+ /** Parse manifest `connections` array; drops invalid entries. */
97
+ declare function parsePluginConnections(raw: unknown): PluginConnectionRequirement[];
98
+
99
+ /**
100
+ * Validates a loaded plugin ZIP before any entry is inflated (CERVIO-SEC-014):
101
+ * caps the entry count and total declared uncompressed size to stop zip bombs,
102
+ * and rejects path-traversal / absolute entry names as defense-in-depth.
103
+ */
104
+ declare function assertZipSafe(zip: JSZip): PluginActionResult<true>;
105
+ declare function assertZipFile(file: {
106
+ name?: string;
107
+ size: number;
108
+ }): PluginActionResult<true>;
109
+
110
+ /**
111
+ * Next patch version assigned by Cervio. Ignores pre-release/build metadata on
112
+ * the previous label and bumps the patch component only.
113
+ */
114
+ declare function nextPatchVersion(latest: string | null | undefined): string;
115
+ /** Compare core x.y.z numerically (descending sort helper). */
116
+ declare function compareSemverDesc(a: string, b: string): number;
117
+
118
+ /** Zod schema documenting the author-facing manifest shape (source of truth for JSON Schema). */
119
+ declare const pluginManifestZodSchema: z.ZodObject<{
120
+ plugin_key: z.ZodOptional<z.ZodString>;
121
+ id: z.ZodOptional<z.ZodString>;
122
+ plugin_name: z.ZodOptional<z.ZodString>;
123
+ name: z.ZodOptional<z.ZodString>;
124
+ plugin_type: z.ZodOptional<z.ZodEnum<["task", "form", "task_form", "route"]>>;
125
+ kind: z.ZodOptional<z.ZodString>;
126
+ version: z.ZodOptional<z.ZodString>;
127
+ required_connection_capabilities: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
128
+ requiredConnectionCapabilities: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
129
+ connections: z.ZodOptional<z.ZodArray<z.ZodObject<{
130
+ id: z.ZodString;
131
+ provider: z.ZodString;
132
+ scopes: z.ZodArray<z.ZodString, "many">;
133
+ allowed_apis: z.ZodOptional<z.ZodArray<z.ZodObject<{
134
+ host: z.ZodString;
135
+ methods: z.ZodArray<z.ZodString, "many">;
136
+ path_prefix: z.ZodOptional<z.ZodString>;
137
+ pathPrefix: z.ZodOptional<z.ZodString>;
138
+ }, "strip", z.ZodTypeAny, {
139
+ host: string;
140
+ methods: string[];
141
+ path_prefix?: string | undefined;
142
+ pathPrefix?: string | undefined;
143
+ }, {
144
+ host: string;
145
+ methods: string[];
146
+ path_prefix?: string | undefined;
147
+ pathPrefix?: string | undefined;
148
+ }>, "many">>;
149
+ allowedApis: z.ZodOptional<z.ZodArray<z.ZodUnknown, "many">>;
150
+ }, "strip", z.ZodTypeAny, {
151
+ id: string;
152
+ provider: string;
153
+ scopes: string[];
154
+ allowed_apis?: {
155
+ host: string;
156
+ methods: string[];
157
+ path_prefix?: string | undefined;
158
+ pathPrefix?: string | undefined;
159
+ }[] | undefined;
160
+ allowedApis?: unknown[] | undefined;
161
+ }, {
162
+ id: string;
163
+ provider: string;
164
+ scopes: string[];
165
+ allowed_apis?: {
166
+ host: string;
167
+ methods: string[];
168
+ path_prefix?: string | undefined;
169
+ pathPrefix?: string | undefined;
170
+ }[] | undefined;
171
+ allowedApis?: unknown[] | undefined;
172
+ }>, "many">>;
173
+ entities: z.ZodOptional<z.ZodArray<z.ZodObject<{
174
+ name: z.ZodString;
175
+ fields: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
176
+ forms: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
177
+ access: z.ZodOptional<z.ZodArray<z.ZodEnum<["read", "write"]>, "many">>;
178
+ }, "strip", z.ZodTypeAny, {
179
+ name: string;
180
+ fields?: string[] | undefined;
181
+ forms?: string[] | undefined;
182
+ access?: ("read" | "write")[] | undefined;
183
+ }, {
184
+ name: string;
185
+ fields?: string[] | undefined;
186
+ forms?: string[] | undefined;
187
+ access?: ("read" | "write")[] | undefined;
188
+ }>, "many">>;
189
+ cervio_apis: z.ZodOptional<z.ZodArray<z.ZodObject<{
190
+ method: z.ZodOptional<z.ZodString>;
191
+ path_prefix: z.ZodOptional<z.ZodString>;
192
+ pathPrefix: z.ZodOptional<z.ZodString>;
193
+ }, "strip", z.ZodTypeAny, {
194
+ method?: string | undefined;
195
+ path_prefix?: string | undefined;
196
+ pathPrefix?: string | undefined;
197
+ }, {
198
+ method?: string | undefined;
199
+ path_prefix?: string | undefined;
200
+ pathPrefix?: string | undefined;
201
+ }>, "many">>;
202
+ cervioApis: z.ZodOptional<z.ZodArray<z.ZodUnknown, "many">>;
203
+ }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
204
+ plugin_key: z.ZodOptional<z.ZodString>;
205
+ id: z.ZodOptional<z.ZodString>;
206
+ plugin_name: z.ZodOptional<z.ZodString>;
207
+ name: z.ZodOptional<z.ZodString>;
208
+ plugin_type: z.ZodOptional<z.ZodEnum<["task", "form", "task_form", "route"]>>;
209
+ kind: z.ZodOptional<z.ZodString>;
210
+ version: z.ZodOptional<z.ZodString>;
211
+ required_connection_capabilities: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
212
+ requiredConnectionCapabilities: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
213
+ connections: z.ZodOptional<z.ZodArray<z.ZodObject<{
214
+ id: z.ZodString;
215
+ provider: z.ZodString;
216
+ scopes: z.ZodArray<z.ZodString, "many">;
217
+ allowed_apis: z.ZodOptional<z.ZodArray<z.ZodObject<{
218
+ host: z.ZodString;
219
+ methods: z.ZodArray<z.ZodString, "many">;
220
+ path_prefix: z.ZodOptional<z.ZodString>;
221
+ pathPrefix: z.ZodOptional<z.ZodString>;
222
+ }, "strip", z.ZodTypeAny, {
223
+ host: string;
224
+ methods: string[];
225
+ path_prefix?: string | undefined;
226
+ pathPrefix?: string | undefined;
227
+ }, {
228
+ host: string;
229
+ methods: string[];
230
+ path_prefix?: string | undefined;
231
+ pathPrefix?: string | undefined;
232
+ }>, "many">>;
233
+ allowedApis: z.ZodOptional<z.ZodArray<z.ZodUnknown, "many">>;
234
+ }, "strip", z.ZodTypeAny, {
235
+ id: string;
236
+ provider: string;
237
+ scopes: string[];
238
+ allowed_apis?: {
239
+ host: string;
240
+ methods: string[];
241
+ path_prefix?: string | undefined;
242
+ pathPrefix?: string | undefined;
243
+ }[] | undefined;
244
+ allowedApis?: unknown[] | undefined;
245
+ }, {
246
+ id: string;
247
+ provider: string;
248
+ scopes: string[];
249
+ allowed_apis?: {
250
+ host: string;
251
+ methods: string[];
252
+ path_prefix?: string | undefined;
253
+ pathPrefix?: string | undefined;
254
+ }[] | undefined;
255
+ allowedApis?: unknown[] | undefined;
256
+ }>, "many">>;
257
+ entities: z.ZodOptional<z.ZodArray<z.ZodObject<{
258
+ name: z.ZodString;
259
+ fields: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
260
+ forms: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
261
+ access: z.ZodOptional<z.ZodArray<z.ZodEnum<["read", "write"]>, "many">>;
262
+ }, "strip", z.ZodTypeAny, {
263
+ name: string;
264
+ fields?: string[] | undefined;
265
+ forms?: string[] | undefined;
266
+ access?: ("read" | "write")[] | undefined;
267
+ }, {
268
+ name: string;
269
+ fields?: string[] | undefined;
270
+ forms?: string[] | undefined;
271
+ access?: ("read" | "write")[] | undefined;
272
+ }>, "many">>;
273
+ cervio_apis: z.ZodOptional<z.ZodArray<z.ZodObject<{
274
+ method: z.ZodOptional<z.ZodString>;
275
+ path_prefix: z.ZodOptional<z.ZodString>;
276
+ pathPrefix: z.ZodOptional<z.ZodString>;
277
+ }, "strip", z.ZodTypeAny, {
278
+ method?: string | undefined;
279
+ path_prefix?: string | undefined;
280
+ pathPrefix?: string | undefined;
281
+ }, {
282
+ method?: string | undefined;
283
+ path_prefix?: string | undefined;
284
+ pathPrefix?: string | undefined;
285
+ }>, "many">>;
286
+ cervioApis: z.ZodOptional<z.ZodArray<z.ZodUnknown, "many">>;
287
+ }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
288
+ plugin_key: z.ZodOptional<z.ZodString>;
289
+ id: z.ZodOptional<z.ZodString>;
290
+ plugin_name: z.ZodOptional<z.ZodString>;
291
+ name: z.ZodOptional<z.ZodString>;
292
+ plugin_type: z.ZodOptional<z.ZodEnum<["task", "form", "task_form", "route"]>>;
293
+ kind: z.ZodOptional<z.ZodString>;
294
+ version: z.ZodOptional<z.ZodString>;
295
+ required_connection_capabilities: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
296
+ requiredConnectionCapabilities: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
297
+ connections: z.ZodOptional<z.ZodArray<z.ZodObject<{
298
+ id: z.ZodString;
299
+ provider: z.ZodString;
300
+ scopes: z.ZodArray<z.ZodString, "many">;
301
+ allowed_apis: z.ZodOptional<z.ZodArray<z.ZodObject<{
302
+ host: z.ZodString;
303
+ methods: z.ZodArray<z.ZodString, "many">;
304
+ path_prefix: z.ZodOptional<z.ZodString>;
305
+ pathPrefix: z.ZodOptional<z.ZodString>;
306
+ }, "strip", z.ZodTypeAny, {
307
+ host: string;
308
+ methods: string[];
309
+ path_prefix?: string | undefined;
310
+ pathPrefix?: string | undefined;
311
+ }, {
312
+ host: string;
313
+ methods: string[];
314
+ path_prefix?: string | undefined;
315
+ pathPrefix?: string | undefined;
316
+ }>, "many">>;
317
+ allowedApis: z.ZodOptional<z.ZodArray<z.ZodUnknown, "many">>;
318
+ }, "strip", z.ZodTypeAny, {
319
+ id: string;
320
+ provider: string;
321
+ scopes: string[];
322
+ allowed_apis?: {
323
+ host: string;
324
+ methods: string[];
325
+ path_prefix?: string | undefined;
326
+ pathPrefix?: string | undefined;
327
+ }[] | undefined;
328
+ allowedApis?: unknown[] | undefined;
329
+ }, {
330
+ id: string;
331
+ provider: string;
332
+ scopes: string[];
333
+ allowed_apis?: {
334
+ host: string;
335
+ methods: string[];
336
+ path_prefix?: string | undefined;
337
+ pathPrefix?: string | undefined;
338
+ }[] | undefined;
339
+ allowedApis?: unknown[] | undefined;
340
+ }>, "many">>;
341
+ entities: z.ZodOptional<z.ZodArray<z.ZodObject<{
342
+ name: z.ZodString;
343
+ fields: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
344
+ forms: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
345
+ access: z.ZodOptional<z.ZodArray<z.ZodEnum<["read", "write"]>, "many">>;
346
+ }, "strip", z.ZodTypeAny, {
347
+ name: string;
348
+ fields?: string[] | undefined;
349
+ forms?: string[] | undefined;
350
+ access?: ("read" | "write")[] | undefined;
351
+ }, {
352
+ name: string;
353
+ fields?: string[] | undefined;
354
+ forms?: string[] | undefined;
355
+ access?: ("read" | "write")[] | undefined;
356
+ }>, "many">>;
357
+ cervio_apis: z.ZodOptional<z.ZodArray<z.ZodObject<{
358
+ method: z.ZodOptional<z.ZodString>;
359
+ path_prefix: z.ZodOptional<z.ZodString>;
360
+ pathPrefix: z.ZodOptional<z.ZodString>;
361
+ }, "strip", z.ZodTypeAny, {
362
+ method?: string | undefined;
363
+ path_prefix?: string | undefined;
364
+ pathPrefix?: string | undefined;
365
+ }, {
366
+ method?: string | undefined;
367
+ path_prefix?: string | undefined;
368
+ pathPrefix?: string | undefined;
369
+ }>, "many">>;
370
+ cervioApis: z.ZodOptional<z.ZodArray<z.ZodUnknown, "many">>;
371
+ }, z.ZodTypeAny, "passthrough">>;
372
+ type PluginManifestZod = z.infer<typeof pluginManifestZodSchema>;
373
+
374
+ declare function normalizePluginKey(raw: string): string;
375
+ declare function parseManifestObject(parsed: Record<string, unknown>, options?: ParseManifestOptions): PluginActionResult<{
376
+ pluginKey: string;
377
+ pluginName: string | null;
378
+ pluginType: PluginType | null;
379
+ manifestVersion: string | null;
380
+ requiredConnectionCapabilities: string[];
381
+ connections: PluginConnectionRequirement[];
382
+ entities: PluginEntityRequirement[];
383
+ cervioApis: PluginCervioApiRequirement[];
384
+ }>;
385
+ /** Manifest JSON for hashing: drop author `version` so Cervio-owned labels do not affect dedup. */
386
+ declare function manifestJsonForContentHash(raw: Record<string, unknown>): string;
387
+ declare function loadZipFromBuffer(buffer: ArrayBuffer | Buffer): Promise<PluginActionResult<JSZip>>;
388
+ declare function parseZipManifest(file: File | Blob, options?: ParseManifestOptions): Promise<PluginActionResult<ParsedPluginManifest>>;
389
+
390
+ /**
391
+ * Deterministic SHA-256 over:
392
+ * - manifest.json (JSON without `version` field)
393
+ * - every non-directory entry under dist/** (paths sorted)
394
+ *
395
+ * README / other root files and ZIP compression metadata do not affect the hash.
396
+ */
397
+ declare function computePluginContentSha256(zip: JSZip, rawManifest: Record<string, unknown>): Promise<PluginActionResult<string>>;
398
+ declare function sha256Hex(buffer: Buffer | Uint8Array): string;
399
+
400
+ type PackPluginResult = {
401
+ buffer: Buffer;
402
+ pluginKey: string;
403
+ contentSha256: string;
404
+ sizeBytes: number;
405
+ };
406
+ /**
407
+ * Pack a built plugin directory into a ZIP buffer.
408
+ * Expects `manifest.json` at root and `dist/index.html` + `dist/assets/*`.
409
+ */
410
+ declare function packPlugin(opts: {
411
+ dir: string;
412
+ options?: ParseManifestOptions;
413
+ }): Promise<PluginActionResult<PackPluginResult>>;
414
+ /** Validate an on-disk plugin directory (or an existing ZIP) without uploading. */
415
+ declare function validatePluginDir(dir: string, options?: ParseManifestOptions): Promise<PluginActionResult<{
416
+ pluginKey: string;
417
+ contentSha256: string;
418
+ }>>;
419
+ declare function validatePluginZipBuffer(buffer: Buffer, options?: ParseManifestOptions): Promise<PluginActionResult<{
420
+ pluginKey: string;
421
+ contentSha256: string;
422
+ }>>;
423
+
424
+ export { type AllowedApiRule, DEFAULT_PLUGIN_DECLARABLE_CAPABILITIES, EXTERNAL_CONNECTION_ALLOWED_HOSTS, EXTERNAL_CONNECTION_HTTP_METHODS, type ExternalConnectionHttpMethod, MAX_PLUGIN_UNCOMPRESSED_BYTES, MAX_PLUGIN_ZIP_BYTES, MAX_PLUGIN_ZIP_ENTRIES, OAUTH_PROVIDER_KEY_RE, PLUGIN_ARTIFACT_BUCKET, PLUGIN_KEY_RE, PLUGIN_VERSION_SELECT, type PackPluginResult, type ParseManifestOptions, type ParsedPluginManifest, type PluginActionResult, type PluginCervioApiRequirement, type PluginConnectionRequirement, type PluginEntityAccess, type PluginEntityRequirement, type PluginManifestZod, type PluginType, type PluginVersionRow, assertZipFile, assertZipSafe, compareSemverDesc, computePluginContentSha256, isExternalConnectionProvider, loadZipFromBuffer, manifestJsonForContentHash, nextPatchVersion, normalizeExactApiHost, normalizePluginKey, packPlugin, parseManifestObject, parsePluginCervioApiRequirements, parsePluginConnections, parsePluginEntityRequirements, parseZipManifest, pluginManifestZodSchema, sha256Hex, validatePluginDir, validatePluginZipBuffer };