@cervio/plugin-core 1.0.0 → 1.2.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/README.md CHANGED
@@ -1,20 +1,75 @@
1
1
  # @cervio/plugin-core
2
2
 
3
- Shared Cervio plugin contracts: manifest parsing, ZIP safety, content hashing, semver assignment helpers, and `packPlugin()`.
3
+ The Cervio plugin contract: manifest parsing and validation, ZIP safety checks, the
4
+ content hash, release version helpers and packing.
4
5
 
5
- Used by the Cervio host and by `@cervio/cli` so local `cervio validate` matches server upload checks.
6
+ The Cervio host validates every plugin upload with this package, and `@cervio/cli` uses
7
+ the same code for `cervio validate` and `cervio pack`: a plugin that passes locally passes
8
+ on upload. You rarely need it directly: plugin code uses
9
+ [`@cervio/plugin-sdk`](https://www.npmjs.com/package/@cervio/plugin-sdk) and the
10
+ [`cervio` CLI](https://www.npmjs.com/package/@cervio/cli). Use it to build your own
11
+ tooling around plugin archives.
12
+
13
+ Requires Node.js 26 or later. ESM only.
6
14
 
7
15
  ```bash
8
- pnpm add @cervio/plugin-core
16
+ npm install @cervio/plugin-core
9
17
  ```
10
18
 
19
+ ## Usage
20
+
11
21
  ```ts
12
- import { parseManifestObject, packPlugin, nextPatchVersion } from '@cervio/plugin-core'
22
+ import { packPlugin, parseZipManifest, readPluginZip } from '@cervio/plugin-core'
13
23
 
14
- const fields = parseManifestObject(manifestJson)
24
+ // Pack a built plugin folder (manifest.json + dist/) exactly as the CLI does.
15
25
  const packed = await packPlugin({ dir: '.' })
26
+ if (!packed.ok) throw new Error(packed.error)
27
+ console.log(packed.value.pluginKey, packed.value.contentSha256)
28
+
29
+ // Read and validate an existing archive.
30
+ const manifest = await parseZipManifest(packed.value.buffer)
31
+
32
+ // Open a ZIP with the same safety checks as the host.
33
+ const zip = await readPluginZip(packed.value.buffer)
34
+ if (zip.ok) console.log(zip.value.paths)
16
35
  ```
17
36
 
18
- See docs at `docs.cervio.app` (when deployed) and [Authoring](https://docs.cervio.app/docs/authoring) in the Cervio repo.
37
+ Functions return `{ ok: true, value }` or `{ ok: false, error }`, where `error` is a stable
38
+ code such as `manifest_not_found_in_zip`, `build_index_missing` or `zip_nested_archive`.
39
+
40
+ ## API
41
+
42
+ | Area | Exports |
43
+ |---|---|
44
+ | Manifest | `parseManifestObject`, `parseZipManifest`, `normalizePluginKey`, `manifestJsonForContentHash`, `parsePluginEntityRequirements`, `parsePluginCervioApiRequirements`, `parsePluginBootRequests`, `isDeclarableCervioApiRequirement`, `isDeclaredCervioApiRequest` |
45
+ | ZIP | `readPluginZip` (safety checks before inflating: entry count, paths, nested archives, sizes, compression ratio), `assertZipFile`, limits `MAX_PLUGIN_ZIP_BYTES`, `MAX_PLUGIN_ZIP_ENTRIES`, `MAX_PLUGIN_UNCOMPRESSED_BYTES` |
46
+ | Packing | `packPlugin` (reproducible archives), `validatePluginDir`, `validatePluginZipBuffer` |
47
+ | Hash and versions | `computePluginContentSha256`, `sha256Hex`, `nextPatchVersion`, `compareSemverDesc` |
48
+ | Write-gate plugins | `isPolicyPluginType`, `normalizeToPolicyPluginType`, policy contract types |
49
+
50
+ `@cervio/plugin-core/browser` is the subset that runs in a browser (no `fs`): ZIP checks
51
+ and the API declaration helpers. `@cervio/plugin-core/manifest.schema.json` is the JSON
52
+ schema of `manifest.json`: point your editor at it with `"$schema"`.
53
+
54
+ The export list is the public API and follows semver; anything else is internal.
55
+
56
+ ## Versions
57
+
58
+ This package's version is independent of the release versions of your plugins, which
59
+ the Cervio host assigns on upload: never put `version` in `manifest.json`.
60
+
61
+ Documentation: [docs.cervio.app](https://docs.cervio.app).
62
+
63
+ ## License
64
+
65
+ MIT, see [LICENSE](LICENSE).
66
+
67
+ **Trademark.** "Cervio" and the Cervio logo are trademarks of Cervio S.r.l. This license
68
+ does not grant permission to use them. You may say that a plugin "works with Cervio"; you
69
+ may not imply endorsement or ship a product named Cervio.
70
+
71
+ **Platform.** Using a Cervio organization is governed by your partner or customer
72
+ agreement, not by this license.
19
73
 
20
- **Versioning note:** this package version is independent of plugin release versions (assigned by the host) and of `@cervio/plugin-sdk` (tracks CervioUI API).
74
+ **Your plugins.** Plugins you write are yours: using this package gives Cervio no rights
75
+ over your plugin, and does not require you to open-source it.
@@ -8,6 +8,13 @@
8
8
  * Example: `{ "path": "/api/v1/entities/attivita/records?limit=200" }`.
9
9
  * `/api/v1/entities/<technical name>/...` is accepted: the host also matches
10
10
  * the same path with the entity id the plugin may use.
11
+ *
12
+ * `{settings.<key>}` takes the value of one of the plugin's `settings`, filled
13
+ * by the host per organization (URL-encoded). Views are chosen per organization,
14
+ * so their id cannot be written in the manifest:
15
+ * `{ "path": "/api/v1/views/{settings.view_id}/records?limit=50" }`. When the
16
+ * setting is empty the host skips that entry and the plugin's call goes the
17
+ * normal way.
11
18
  */
12
19
  type PluginBootRequest = {
13
20
  /** Absolute path + optional query under `/api/v1/`, e.g. `/api/v1/entities/x/records?limit=50`. */
package/dist/browser.d.ts CHANGED
@@ -1 +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';
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-CWGIdJMr.js';
package/dist/browser.js CHANGED
@@ -145,10 +145,12 @@ function parsePluginCervioApiRequirements(raw) {
145
145
  // src/boot-requests.ts
146
146
  var MAX_PLUGIN_BOOT_REQUESTS = 8;
147
147
  var MAX_BOOT_PATH_LEN = 400;
148
+ var SETTING_PLACEHOLDER_RE = /\{settings\.([A-Za-z0-9_]{1,64})\}/g;
149
+ var PLACEHOLDER_PROBE = "x";
148
150
  function bootRequestPathname(path) {
149
151
  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 p = path.trim().replace(SETTING_PLACEHOLDER_RE, PLACEHOLDER_PROBE);
153
+ if (!p.startsWith("/api/v1/") || path.trim().length > MAX_BOOT_PATH_LEN || p.includes("#") || /[{}]/.test(p)) return null;
152
154
  const pathname = p.split("?")[0] ?? "";
153
155
  if (!isDeclarableCervioApiPrefix(pathname)) return null;
154
156
  return pathname;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { P as ParseManifestOptions, a as PluginActionResult, b as PluginType, c as PluginConnectionRequirement, d as PluginEntityRequirement, e as PluginCervioApiRequirement, f as PluginSettingsFieldRequirement, g as PluginBootRequest, h as ParsedPluginManifest, i as PluginZip } from './browser-Ddj__JFI.js';
2
- export { A as AllowedApiRule, D as DEFAULT_PLUGIN_DECLARABLE_CAPABILITIES, E as ExternalConnectionHttpMethod, M as MAX_PLUGIN_UNCOMPRESSED_BYTES, j as MAX_PLUGIN_ZIP_BYTES, k as MAX_PLUGIN_ZIP_ENTRIES, l as PLUGIN_ARTIFACT_SOFT_STRONG_WARN_BYTES, m as PLUGIN_ARTIFACT_SOFT_WARN_BYTES, n as PLUGIN_KEY_RE, o as PluginEntityAccess, p as PluginSettingsFieldOption, q as PluginSettingsFieldType, r as assertZipFile, s as isDeclarableCervioApiRequirement, t as isDeclaredCervioApiRequest, u as parsePluginBootRequests, v as parsePluginCervioApiRequirements, w as parsePluginEntityRequirements, x as readPluginZip } from './browser-Ddj__JFI.js';
1
+ import { P as ParseManifestOptions, a as PluginActionResult, b as PluginType, c as PluginConnectionRequirement, d as PluginEntityRequirement, e as PluginCervioApiRequirement, f as PluginSettingsFieldRequirement, g as PluginBootRequest, h as ParsedPluginManifest, i as PluginZip } from './browser-CWGIdJMr.js';
2
+ export { A as AllowedApiRule, D as DEFAULT_PLUGIN_DECLARABLE_CAPABILITIES, E as ExternalConnectionHttpMethod, M as MAX_PLUGIN_UNCOMPRESSED_BYTES, j as MAX_PLUGIN_ZIP_BYTES, k as MAX_PLUGIN_ZIP_ENTRIES, l as PLUGIN_ARTIFACT_SOFT_STRONG_WARN_BYTES, m as PLUGIN_ARTIFACT_SOFT_WARN_BYTES, n as PLUGIN_KEY_RE, o as PluginEntityAccess, p as PluginSettingsFieldOption, q as PluginSettingsFieldType, r as assertZipFile, s as isDeclarableCervioApiRequirement, t as isDeclaredCervioApiRequest, u as parsePluginBootRequests, v as parsePluginCervioApiRequirements, w as parsePluginEntityRequirements, x as readPluginZip } from './browser-CWGIdJMr.js';
3
3
 
4
4
  /**
5
5
  * Next patch version assigned by Cervio. Ignores pre-release/build metadata on
package/dist/index.js CHANGED
@@ -139,15 +139,20 @@ function parsePluginCervioApiRequirements(raw) {
139
139
  // src/boot-requests.ts
140
140
  var MAX_PLUGIN_BOOT_REQUESTS = 8;
141
141
  var MAX_BOOT_PATH_LEN = 400;
142
+ var SETTING_PLACEHOLDER_RE = /\{settings\.([A-Za-z0-9_]{1,64})\}/g;
143
+ var PLACEHOLDER_PROBE = "x";
144
+ function settingKeysOf(path2) {
145
+ return [...path2.matchAll(SETTING_PLACEHOLDER_RE)].map((m) => m[1]);
146
+ }
142
147
  function bootRequestPathname(path2) {
143
148
  if (typeof path2 !== "string") return null;
144
- const p = path2.trim();
145
- if (!p.startsWith("/api/v1/") || p.length > MAX_BOOT_PATH_LEN || p.includes("#")) return null;
149
+ const p = path2.trim().replace(SETTING_PLACEHOLDER_RE, PLACEHOLDER_PROBE);
150
+ if (!p.startsWith("/api/v1/") || path2.trim().length > MAX_BOOT_PATH_LEN || p.includes("#") || /[{}]/.test(p)) return null;
146
151
  const pathname = p.split("?")[0] ?? "";
147
152
  if (!isDeclarableCervioApiPrefix(pathname)) return null;
148
153
  return pathname;
149
154
  }
150
- function validatePluginBootRequests(raw, cervioApis) {
155
+ function validatePluginBootRequests(raw, cervioApis, settingKeys) {
151
156
  if (raw == null) return { ok: true, value: [] };
152
157
  if (!Array.isArray(raw)) return { ok: false, error: "manifest_boot_requests_invalid" };
153
158
  if (raw.length > MAX_PLUGIN_BOOT_REQUESTS) return { ok: false, error: "manifest_boot_requests_too_many" };
@@ -161,6 +166,9 @@ function validatePluginBootRequests(raw, cervioApis) {
161
166
  return { ok: false, error: "manifest_boot_requests_not_declared" };
162
167
  }
163
168
  const p = path2.trim();
169
+ if (settingKeys && settingKeysOf(p).some((k) => !settingKeys.includes(k))) {
170
+ return { ok: false, error: "manifest_boot_requests_unknown_setting" };
171
+ }
164
172
  if (seen.has(p)) continue;
165
173
  seen.add(p);
166
174
  out.push({ path: p });
@@ -523,7 +531,11 @@ function parseManifestObject(parsed, options) {
523
531
  if (!cervioApisRes.ok) return cervioApisRes;
524
532
  const cervioApis = cervioApisRes.value;
525
533
  const settings = parsePluginSettingsRequirements(parsed.settings);
526
- const bootRequestsRes = validatePluginBootRequests(parsed.boot_requests, cervioApis);
534
+ const bootRequestsRes = validatePluginBootRequests(
535
+ parsed.boot_requests,
536
+ cervioApis,
537
+ settings.map((f) => f.key)
538
+ );
527
539
  if (!bootRequestsRes.ok) return bootRequestsRes;
528
540
  const bootRequests = bootRequestsRes.value;
529
541
  return {
@@ -220,7 +220,7 @@
220
220
  "properties": {
221
221
  "path": {
222
222
  "type": "string",
223
- "description": "GET path + optional query under /api/v1/, covered by a GET `cervio_apis` prefix."
223
+ "description": "GET path + optional query under /api/v1/, covered by a GET `cervio_apis` prefix. `{settings.<key>}` takes a plugin setting (e.g. /api/v1/views/{settings.view_id}/records?limit=50)."
224
224
  }
225
225
  },
226
226
  "required": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cervio/plugin-core",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "Shared Cervio plugin manifest validation, ZIP safety, content hash, and packing.",
5
5
  "keywords": [
6
6
  "cervio",