@cervio/plugin-core 1.0.1 → 1.3.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/{browser-Ddj__JFI.d.ts → browser-CWGIdJMr.d.ts} +7 -0
- package/dist/browser.d.ts +1 -1
- package/dist/browser.js +4 -2
- package/dist/index.d.ts +17 -4
- package/dist/index.js +82 -5
- package/manifest.schema.json +1 -1
- package/package.json +1 -1
|
@@ -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-
|
|
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/") ||
|
|
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-
|
|
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-
|
|
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
|
|
@@ -94,6 +94,15 @@ declare function parseZipManifest(file: Blob | ArrayBuffer | Uint8Array, options
|
|
|
94
94
|
declare function computePluginContentSha256(zip: PluginZip, rawManifest: Record<string, unknown>): PluginActionResult<string>;
|
|
95
95
|
declare function sha256Hex(buffer: Buffer | Uint8Array): string;
|
|
96
96
|
|
|
97
|
+
/**
|
|
98
|
+
* Non-blocking advice from `validatePluginDir` on how fast the plugin opens.
|
|
99
|
+
* A plugin with hints is still valid and can be pushed.
|
|
100
|
+
*/
|
|
101
|
+
type PluginStartupHint = {
|
|
102
|
+
code: 'boot_requests_missing' | 'mark_ready_missing' | 'boot_request_hardcoded_view';
|
|
103
|
+
message: string;
|
|
104
|
+
};
|
|
105
|
+
|
|
97
106
|
type PackPluginResult = {
|
|
98
107
|
buffer: Buffer;
|
|
99
108
|
pluginKey: string;
|
|
@@ -108,10 +117,14 @@ declare function packPlugin(opts: {
|
|
|
108
117
|
dir: string;
|
|
109
118
|
options?: ParseManifestOptions;
|
|
110
119
|
}): Promise<PluginActionResult<PackPluginResult>>;
|
|
111
|
-
/**
|
|
120
|
+
/**
|
|
121
|
+
* Validate an on-disk plugin directory without uploading. `hints` is
|
|
122
|
+
* non-blocking advice on start-up speed (boot_requests, markReady).
|
|
123
|
+
*/
|
|
112
124
|
declare function validatePluginDir(dir: string, options?: ParseManifestOptions): Promise<PluginActionResult<{
|
|
113
125
|
pluginKey: string;
|
|
114
126
|
contentSha256: string;
|
|
127
|
+
hints: PluginStartupHint[];
|
|
115
128
|
}>>;
|
|
116
129
|
declare function validatePluginZipBuffer(buffer: Buffer, options?: ParseManifestOptions): Promise<PluginActionResult<{
|
|
117
130
|
pluginKey: string;
|
|
@@ -119,4 +132,4 @@ declare function validatePluginZipBuffer(buffer: Buffer, options?: ParseManifest
|
|
|
119
132
|
}>>;
|
|
120
133
|
|
|
121
134
|
export { ParseManifestOptions, ParsedPluginManifest, PluginActionResult, PluginBootRequest, PluginCervioApiRequirement, PluginConnectionRequirement, PluginEntityRequirement, PluginSettingsFieldRequirement, PluginType, PluginZip, compareSemverDesc, computePluginContentSha256, isPolicyPluginType, manifestJsonForContentHash, nextPatchVersion, normalizePluginKey, normalizeToPolicyPluginType, packPlugin, parseManifestObject, parseZipManifest, sha256Hex, validatePluginDir, validatePluginZipBuffer };
|
|
122
|
-
export type { PackPluginResult, PolicyHookCaller, PolicyHookEvent, PolicyHookInput, PolicyHookResult, PolicyHost };
|
|
135
|
+
export type { PackPluginResult, PluginStartupHint, PolicyHookCaller, PolicyHookEvent, PolicyHookInput, PolicyHookResult, PolicyHost };
|
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/") ||
|
|
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(
|
|
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 {
|
|
@@ -622,6 +634,44 @@ function computePluginContentSha256(zip, rawManifest) {
|
|
|
622
634
|
function sha256Hex(buffer) {
|
|
623
635
|
return createHash("sha256").update(buffer).digest("hex");
|
|
624
636
|
}
|
|
637
|
+
|
|
638
|
+
// src/startup-hints.ts
|
|
639
|
+
var ANSWERED_AT_BOOT = /* @__PURE__ */ new Set(["/api/v1/me", "/api/v1/entities"]);
|
|
640
|
+
var MARK_READY_MARKER = "__cervioPluginReady";
|
|
641
|
+
var VIEW_ID_IN_PATH = /\/api\/v1\/views\/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\//i;
|
|
642
|
+
var isIframePlugin = (t) => t === "route" || t === "task" || t === "task_form" || t === "form";
|
|
643
|
+
function pluginStartupHints(input) {
|
|
644
|
+
if (!isIframePlugin(input.pluginType)) return [];
|
|
645
|
+
const hints = [];
|
|
646
|
+
const readsData = input.cervioApis.some(
|
|
647
|
+
(a) => a.method.toUpperCase() === "GET" && !ANSWERED_AT_BOOT.has(a.path_prefix)
|
|
648
|
+
);
|
|
649
|
+
if (input.pluginType === "route" && readsData && input.bootRequests.length === 0) {
|
|
650
|
+
hints.push({
|
|
651
|
+
code: "boot_requests_missing",
|
|
652
|
+
message: "No boot_requests: list the GET calls of the first screen in manifest.json (same path and query as the code). Cervio starts them while the plugin loads (measured: data on screen 350-500 ms sooner on the first open, 75-220 ms on later ones)."
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
if (!input.bundleText.includes(MARK_READY_MARKER)) {
|
|
656
|
+
hints.push({
|
|
657
|
+
code: "mark_ready_missing",
|
|
658
|
+
message: "markReady() is never called: call it from '@cervio/plugin-sdk' once the first data is on screen (also on error). Cervio keeps its loading skeleton until then and uses it to time the plugin open."
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
const viewKeys = input.settings.filter((s) => s.type === "string" && /view/i.test(s.key)).map((s) => s.key);
|
|
662
|
+
for (const b of input.bootRequests) {
|
|
663
|
+
const m = VIEW_ID_IN_PATH.exec(b.path);
|
|
664
|
+
if (!m) continue;
|
|
665
|
+
const placeholder = viewKeys.length ? `{settings.${viewKeys[0]}}` : "{settings.<key>}";
|
|
666
|
+
hints.push({
|
|
667
|
+
code: "boot_request_hardcoded_view",
|
|
668
|
+
message: `boot_requests path "${b.path}" has a fixed view id: write ${placeholder} instead` + (viewKeys.length ? "" : " and add a string setting for the view") + ", so the prefetch follows the view chosen in each organization."
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
return hints;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// src/pack.ts
|
|
625
675
|
var ZIP_MTIME = /* @__PURE__ */ new Date("2000-01-01T00:00:00Z");
|
|
626
676
|
async function collectFiles(dir, base = "") {
|
|
627
677
|
const entries = await promises.readdir(dir, { withFileTypes: true });
|
|
@@ -717,16 +767,43 @@ async function packPlugin(opts) {
|
|
|
717
767
|
}
|
|
718
768
|
};
|
|
719
769
|
}
|
|
770
|
+
async function readBundleText(root) {
|
|
771
|
+
const dist = path.join(
|
|
772
|
+
/*turbopackIgnore: true*/
|
|
773
|
+
root,
|
|
774
|
+
"dist"
|
|
775
|
+
);
|
|
776
|
+
let files;
|
|
777
|
+
try {
|
|
778
|
+
files = await collectFiles(dist);
|
|
779
|
+
} catch {
|
|
780
|
+
return "";
|
|
781
|
+
}
|
|
782
|
+
const parts = [];
|
|
783
|
+
for (const f of files) {
|
|
784
|
+
if (/\.(m?js|html)$/i.test(f.rel)) parts.push(await promises.readFile(f.abs, "utf8"));
|
|
785
|
+
}
|
|
786
|
+
return parts.join("\n");
|
|
787
|
+
}
|
|
720
788
|
async function validatePluginDir(dir, options) {
|
|
721
789
|
const packed = await packPlugin({ dir, options });
|
|
722
790
|
if (!packed.ok) return packed;
|
|
723
791
|
const parsed = await parseZipManifest(packed.value.buffer, options);
|
|
724
792
|
if (!parsed.ok) return parsed;
|
|
793
|
+
const fields = parsed.value;
|
|
794
|
+
const hints = pluginStartupHints({
|
|
795
|
+
pluginType: fields.pluginType,
|
|
796
|
+
cervioApis: fields.cervioApis,
|
|
797
|
+
bootRequests: fields.bootRequests,
|
|
798
|
+
settings: fields.settings,
|
|
799
|
+
bundleText: isPolicyPluginType(fields.pluginType) ? "" : await readBundleText(path.resolve(dir))
|
|
800
|
+
});
|
|
725
801
|
return {
|
|
726
802
|
ok: true,
|
|
727
803
|
value: {
|
|
728
804
|
pluginKey: packed.value.pluginKey,
|
|
729
|
-
contentSha256: packed.value.contentSha256
|
|
805
|
+
contentSha256: packed.value.contentSha256,
|
|
806
|
+
hints
|
|
730
807
|
}
|
|
731
808
|
};
|
|
732
809
|
}
|
package/manifest.schema.json
CHANGED
|
@@ -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": [
|