@vormio/plugin-sdk 0.0.9-rc.1 → 0.0.10-rc.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/README.md +56 -6
- package/dist/bridge.d.ts +11 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/manifest.d.ts +18 -2
- package/dist/manifest.js +0 -0
- package/dist/node/index.d.ts +2 -2
- package/dist/node/index.js +1 -1
- package/dist/node/package.d.ts +8 -0
- package/dist/node/package.js +22 -1
- package/dist/report.d.ts +2 -0
- package/dist/report.js +18 -7
- package/package.json +6 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The manifest schema, runtime helpers and packaging command for writing Vormio plugins, plus the commands that publish a signed plugin catalog. The package is versioned with Vormio-Core (currently 0.0.9) and published to npm under the MIT license: `npm install @vormio/plugin-sdk@0.0.9`.
|
|
4
4
|
|
|
5
|
-
Plugin contract version: `apiVersion:
|
|
5
|
+
Plugin contract version: `apiVersion: 5`. During development (0.0.x) there is no cross-version compatibility promise: the host loads only plugins with the same `apiVersion` as its own.
|
|
6
6
|
|
|
7
7
|
## What a plugin can do
|
|
8
8
|
|
|
@@ -28,7 +28,7 @@ A plugin is a folder: a `manifest.json` at the root plus the built static files.
|
|
|
28
28
|
|
|
29
29
|
```json
|
|
30
30
|
{
|
|
31
|
-
"apiVersion":
|
|
31
|
+
"apiVersion": 5,
|
|
32
32
|
"id": "acme.model-viewer",
|
|
33
33
|
"version": "0.1.0",
|
|
34
34
|
"name": "Model Viewer",
|
|
@@ -59,6 +59,7 @@ A plugin is a folder: a `manifest.json` at the root plus the built static files.
|
|
|
59
59
|
- `id` is `publisher.name`, using only lowercase letters, digits and hyphens. The `vormio` publisher is reserved for the host.
|
|
60
60
|
- `formats` and `companionFormats` are lowercase extensions without the dot. Wildcards are not allowed.
|
|
61
61
|
- `entry` is an HTML file in the package. Pages run under a strict Content Security Policy: scripts must be separate files, not inline.
|
|
62
|
+
- `agreement` is optional; see [Agreements users accept before enabling](#agreements-users-accept-before-enabling).
|
|
62
63
|
- Unknown fields are rejected.
|
|
63
64
|
|
|
64
65
|
**Allowed files**: `html js mjs css json wasm png jpg jpeg webp svg woff2 txt md`. Links, `..` paths, Windows reserved names and names that differ only by case are not allowed.
|
|
@@ -73,6 +74,24 @@ A plugin is a folder: a `manifest.json` at the root plus the built static files.
|
|
|
73
74
|
|
|
74
75
|
Reading more formats or companion formats, or adding an extension point, requires the user to enable the plugin again, and that approval is added to what was approved before.
|
|
75
76
|
|
|
77
|
+
### Agreements users accept before enabling
|
|
78
|
+
|
|
79
|
+
Some third-party terms require every user of the plugin to accept them, such as a runtime license that each user must hold. Name the terms in the manifest:
|
|
80
|
+
|
|
81
|
+
```json
|
|
82
|
+
{
|
|
83
|
+
"agreement": {
|
|
84
|
+
"title": "Spine Runtimes License Agreement",
|
|
85
|
+
"file": "licenses/SPINE-RUNTIMES.txt"
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
- `title` is 1–80 characters without control characters. `file` is a `.txt` or `.md` file in the package, at most 256 KiB of UTF-8; Markdown is shown as plain text. A package whose agreement file is missing, too large or not UTF-8 is invalid. One agreement per plugin.
|
|
91
|
+
- When the plugin is enabled in a library, the host's confirmation names the agreement and offers its full text; the plugin is enabled only when the user agrees. Declining leaves the plugin installed and not enabled. Development folders ask the same way.
|
|
92
|
+
- The library records the SHA-256 of the file's bytes with the grant. A version whose agreement file differs needs enabling again, like reading more formats; a version with a file the user already accepted, such as one rolled back to, does not.
|
|
93
|
+
- Use it only for terms that require acceptance by each user. The host shows and records the user's choice; neither the host nor your plugin can verify that the user meets the terms, so do not suggest otherwise.
|
|
94
|
+
|
|
76
95
|
## Runtime API
|
|
77
96
|
|
|
78
97
|
```ts
|
|
@@ -162,8 +181,9 @@ interface CompositionReport {
|
|
|
162
181
|
references: {
|
|
163
182
|
from?: string // Key of the referencing subresource; omitted means the file itself
|
|
164
183
|
uri?: string // Points to another file: the raw address written in the file
|
|
165
|
-
|
|
166
|
-
|
|
184
|
+
uris?: string[] // Several candidate files, the preferred first; only with uriKey
|
|
185
|
+
key?: string // Points to a subresource in this file; give exactly one of uri, uris and key
|
|
186
|
+
uriKey?: string // With uri or uris: a subresource of that file, by the key its report gives it
|
|
167
187
|
role: string // Display role, e.g. "Base color texture"
|
|
168
188
|
evidence?: string // Location in the file, e.g. "/images/2/uri"
|
|
169
189
|
inferred?: string // Not written in the file: how you inferred it, e.g. "Same name as the skeleton"
|
|
@@ -175,8 +195,8 @@ interface CompositionReport {
|
|
|
175
195
|
}
|
|
176
196
|
```
|
|
177
197
|
|
|
178
|
-
- The host validates reports with `compositionReportSchema` (`parseReport` also checks the size); plugins can check themselves against the same schema before returning. The report serialized as JSON is at most 4 MiB (counted in UTF-8 bytes). Names and keys are at most 200 characters, kinds and roles 80, bases of inference 120, evidence 300, diagnostics 500 and addresses 2,048. Names, kinds, roles, bases of inference and evidence must not contain control characters.
|
|
179
|
-
- The report is invalid if it has duplicate keys, a `parent`, `from` or `key` that points to a missing subresource, a cyclic hierarchy, a reference with
|
|
198
|
+
- The host validates reports with `compositionReportSchema` (`parseReport` also checks the size); plugins can check themselves against the same schema before returning. The report serialized as JSON is at most 4 MiB (counted in UTF-8 bytes). Names and keys are at most 200 characters, kinds and roles 80, bases of inference 120, evidence 300, diagnostics 500 and addresses 2,048; `uris` has 2–16 addresses. Names, kinds, roles, bases of inference and evidence must not contain control characters.
|
|
199
|
+
- The report is invalid if it has duplicate keys, a `parent`, `from` or `key` that points to a missing subresource, a cyclic hierarchy, a reference with other than exactly one of `uri`, `uris` and `key`, a `uriKey` with a `key`, `uris` without a `uriKey` or with a repeated address, or an `inferred` reference with a `key`. A `uriKey` is not checked against this report: it names a subresource of the other file.
|
|
180
200
|
- Write `uri` exactly as it appears in the file; do not resolve it yourself. The host decodes percent-encoding once, ignores everything after `?` and `#`, treats `\` as `/`, resolves it relative to the analyzed file and classifies it: a file in the linked folder, out of bounds (`..` leaving the linked folder), embedded (`data:`), external (absolute paths, drive letters, other schemes) or invalid (undecodable, containing control characters, or pointing to a folder). Whether the target exists is decided by the host at query time.
|
|
181
201
|
- For embedded data (`data:`), report only the header up to the comma, e.g. `data:image/png;base64`. The host classifies it as embedded by prefix and does not store the content; a full `data:` address often exceeds 2,048 characters and would invalidate the whole report.
|
|
182
202
|
- Reporting a reference does not require reading the target file, so you do not need `companionFormats` just to report references. Declare them only when the analysis actually reads companion files.
|
|
@@ -222,6 +242,7 @@ When a format looks an object up by name in another file, report the object itse
|
|
|
222
242
|
|
|
223
243
|
- A key that other files refer to is a contract within your plugin: derive it from the format's own lookup rule (for Spine, the region name), so that it stays the same when the target file is exported again. Never use a position in a list.
|
|
224
244
|
- The host connects the two reports only when the same build of your plugin analyzed both files (same plugin ID and package). Your analyzers for both formats must be in one plugin.
|
|
245
|
+
- A build is identified by the package digest: a SHA-256 over every file's path and content, the `digest` that `vormio-plugin pack` prints. A development folder gets the digest of its files, the same as the package made from them, so any change to any file there, a viewer or thumbnail file included, is a new build. After a new build the host analyzes all your files again; while one end of a reference has been analyzed by the new build and the other not yet, the reference is unknown, with that reason, and it resolves again once both are done.
|
|
225
246
|
- Do not read the target file to check that the subresource exists. The host decides at query time, from the target's current report, and the status follows when the target is analyzed again:
|
|
226
247
|
|
|
227
248
|
| The target file | Written reference | Inferred reference |
|
|
@@ -234,6 +255,34 @@ When a format looks an object up by name in another file, report the object itse
|
|
|
234
255
|
|
|
235
256
|
- Keys follow the limits of `key`: at most 200 characters. Do not put the key in the address: the host ignores everything after `?` and `#`.
|
|
236
257
|
|
|
258
|
+
### References that look in one of several files
|
|
259
|
+
|
|
260
|
+
Some formats look an object up by name in several files in turn. An OBJ file can name several material libraries, and `usemtl Body` uses the `Body` of the last library that defines it. Report one reference with every candidate file in `uris`, the one that wins first:
|
|
261
|
+
|
|
262
|
+
```ts
|
|
263
|
+
// mtllib base.mtl
|
|
264
|
+
// mtllib ../materials/paint.mtl
|
|
265
|
+
// usemtl Body
|
|
266
|
+
{
|
|
267
|
+
from: 'groups/0',
|
|
268
|
+
uris: ['../materials/paint.mtl', 'base.mtl'], // The last mtllib wins, so it comes first
|
|
269
|
+
uriKey: 'material:Body',
|
|
270
|
+
role: 'Material',
|
|
271
|
+
}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
- Use `uris` only when there is more than one candidate; with one, give `uri` as before. The order is yours to get right: the host cannot tell which file the format would use.
|
|
275
|
+
- The host goes through the candidates in order, at query time, and resolves to the first whose report has the key. A candidate that does not exist is skipped. A candidate the host cannot settle stops it, and the reference is unknown with that candidate and the reason: never resolved to a later candidate while an earlier one might have the key.
|
|
276
|
+
|
|
277
|
+
| The candidates | Written reference | Inferred reference |
|
|
278
|
+
| -------------------------------------------------------------------------------------------------------- | ------------------------ | ------------------------ |
|
|
279
|
+
| One has the key, and every earlier one does not exist or has a complete report without it | Resolved to that one | Resolved to that one |
|
|
280
|
+
| None exists, or every existing one has a complete report without the key | Missing | Not found |
|
|
281
|
+
| An earlier one is ignored, offline, outside the linked folder, or has no complete report from your build | Unknown, with the reason | Unknown, with the reason |
|
|
282
|
+
|
|
283
|
+
- A missing reference counts once, however many candidates it has. The reference is listed as a referrer only of the file it resolves to; a candidate whose object is shadowed by an earlier one does not list it. Report the files themselves (each `mtllib`) as references of their own.
|
|
284
|
+
- As with `uri` and `uriKey`, every candidate must be analyzed by the same build of your plugin, and you do not read the candidates to check them.
|
|
285
|
+
|
|
237
286
|
## Focusing a subresource
|
|
238
287
|
|
|
239
288
|
When the user picks a subresource in the inspector, or jumps to a subresource of another file, the host asks the viewer to show it:
|
|
@@ -246,6 +295,7 @@ viewer(showAtlas, {
|
|
|
246
295
|
|
|
247
296
|
- The key is from your own analyzer's report: the host asks only when the viewer and the shown file's analysis come from the same build of your plugin.
|
|
248
297
|
- `focus` is called only after the viewer reported the file ready. A request about a file the viewer has moved away from is dropped. The same key can come again when the user asks again; show it again.
|
|
298
|
+
- Every input the viewer is given has a new `url`, also when it is a new version of the same file, and the old address stops working. `viewer()` drops a request whose `url` is not the input currently shown, so a request about an older version never lands on new content.
|
|
249
299
|
- Find it if you can, for example by selecting, framing or highlighting it; ignore keys you cannot find. The host does not wait for an answer.
|
|
250
300
|
- Pages that do not use `viewer()` subscribe with `window.vormio.onFocus(({ url, key }) => …)` and compare `url` with the input they show.
|
|
251
301
|
|
package/dist/bridge.d.ts
CHANGED
|
@@ -33,10 +33,18 @@ export interface ReportReference {
|
|
|
33
33
|
from?: string;
|
|
34
34
|
/** The address as written in the file, relative to the analyzed file. */
|
|
35
35
|
uri?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Files the target is looked up in, in priority order with the preferred first: 2–16 addresses as
|
|
38
|
+
* written in the file, relative to the analyzed file. Only with `uriKey`, instead of `uri`. The
|
|
39
|
+
* host resolves to the first candidate whose report has `uriKey`; while an earlier candidate is
|
|
40
|
+
* not settled, such as not yet analyzed, the reference is unknown.
|
|
41
|
+
*/
|
|
42
|
+
uris?: string[];
|
|
36
43
|
/** A subresource of the analyzed file. */
|
|
37
44
|
key?: string;
|
|
38
45
|
/**
|
|
39
|
-
* A subresource of the file at `uri`, by the key that file's
|
|
46
|
+
* A subresource of the file at `uri`, or of one of the files in `uris`, by the key that file's
|
|
47
|
+
* report gives it; required with `uris`.
|
|
40
48
|
* The host connects it when the same build of your plugin analyzed that file.
|
|
41
49
|
*/
|
|
42
50
|
uriKey?: string;
|
|
@@ -46,7 +54,8 @@ export interface ReportReference {
|
|
|
46
54
|
evidence?: string;
|
|
47
55
|
/**
|
|
48
56
|
* The reference is not written in the file: how the analyzer inferred it, shown to the user, such
|
|
49
|
-
* as "Same name as the skeleton". Only with `uri`. A missing target is "not found", not
|
|
57
|
+
* as "Same name as the skeleton". Only with `uri` or `uris`. A missing target is "not found", not
|
|
58
|
+
* missing.
|
|
50
59
|
*/
|
|
51
60
|
inferred?: string;
|
|
52
61
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { contributionsOf, manifestSchema, missingEntries, parseManifest, permissionSummary, pluginApiVersion, type Contribution, type ContributionKind, type ManifestResult, type PluginManifest, } from './manifest.ts';
|
|
1
|
+
export { agreementMaxBytes, contributionsOf, manifestSchema, missingEntries, parseManifest, permissionSummary, pluginApiVersion, type Contribution, type ContributionKind, type ManifestResult, type PluginAgreement, type PluginManifest, } from './manifest.ts';
|
|
2
2
|
export { checkPackageFiles, checkPackagePath, packageExtensions, packageLimits, type PackageFile, } from './package-rules.ts';
|
|
3
3
|
export { compositionReportSchema, parseReport, reportLimits, type ReportResult } from './report.ts';
|
|
4
4
|
export type { ReportDiagnostic, ReportReference, ReportSubresource } from './bridge.ts';
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { contributionsOf, manifestSchema, missingEntries, parseManifest, permissionSummary, pluginApiVersion, } from "./manifest.js";
|
|
1
|
+
export { agreementMaxBytes, contributionsOf, manifestSchema, missingEntries, parseManifest, permissionSummary, pluginApiVersion, } from "./manifest.js";
|
|
2
2
|
export { checkPackageFiles, checkPackagePath, packageExtensions, packageLimits, } from "./package-rules.js";
|
|
3
3
|
export { compositionReportSchema, parseReport, reportLimits } from "./report.js";
|
|
4
4
|
export { analyzer, bridge, isNotApplicable, notApplicable, readCompanion, readInput, thumbnail, viewer, } from "./runtime.js";
|
package/dist/manifest.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
/** The plugin contract version this SDK describes. Hosts load only plugins with this version. */
|
|
3
|
-
export declare const pluginApiVersion =
|
|
3
|
+
export declare const pluginApiVersion = 5;
|
|
4
4
|
declare const contributionSchema: z.ZodObject<{
|
|
5
5
|
id: z.ZodString;
|
|
6
6
|
entry: z.ZodString;
|
|
@@ -8,16 +8,32 @@ declare const contributionSchema: z.ZodObject<{
|
|
|
8
8
|
companionFormats: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
9
9
|
}, z.core.$strict>;
|
|
10
10
|
export type Contribution = z.output<typeof contributionSchema>;
|
|
11
|
+
/** The largest agreement file, in bytes. */
|
|
12
|
+
export declare const agreementMaxBytes: number;
|
|
13
|
+
/**
|
|
14
|
+
* Terms a third party requires every user to accept before using the plugin, such as a runtime
|
|
15
|
+
* license. The host shows the file when the plugin is enabled and runs the plugin only once the user
|
|
16
|
+
* agrees, per library; a changed file asks again. Nothing is verified.
|
|
17
|
+
*/
|
|
18
|
+
declare const agreementSchema: z.ZodObject<{
|
|
19
|
+
title: z.ZodString;
|
|
20
|
+
file: z.ZodString;
|
|
21
|
+
}, z.core.$strict>;
|
|
22
|
+
export type PluginAgreement = z.output<typeof agreementSchema>;
|
|
11
23
|
/** `publisher.name`. The `vormio` publisher is reserved for the host. */
|
|
12
24
|
export declare const pluginIdSchema: z.ZodString;
|
|
13
25
|
/** A semantic version without build metadata. */
|
|
14
26
|
export declare const versionSchema: z.ZodString;
|
|
15
27
|
export declare const manifestSchema: z.ZodObject<{
|
|
16
|
-
apiVersion: z.ZodLiteral<
|
|
28
|
+
apiVersion: z.ZodLiteral<5>;
|
|
17
29
|
id: z.ZodString;
|
|
18
30
|
version: z.ZodString;
|
|
19
31
|
name: z.ZodString;
|
|
20
32
|
description: z.ZodDefault<z.ZodString>;
|
|
33
|
+
agreement: z.ZodOptional<z.ZodObject<{
|
|
34
|
+
title: z.ZodString;
|
|
35
|
+
file: z.ZodString;
|
|
36
|
+
}, z.core.$strict>>;
|
|
21
37
|
contributions: z.ZodObject<{
|
|
22
38
|
thumbnails: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
23
39
|
id: z.ZodString;
|
package/dist/manifest.js
CHANGED
|
Binary file
|
package/dist/node/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export { checkPackage, PackageError, readPackageArchive, readPackageDirectory, type CheckedPackage, } from './package.ts';
|
|
1
|
+
export { checkPackage, PackageError, type CheckedAgreement, readPackageArchive, readPackageDirectory, type CheckedPackage, } from './package.ts';
|
|
2
2
|
export { readZip, writeZip, ZipError, type ZipEntry } from './zip.ts';
|
|
3
3
|
export { CatalogError, catalogKeyId, catalogLimits, catalogPublicKey, compareVersions, generateCatalogKeys, releaseManifest, signCatalog, verifyCatalog, type Catalog, type CatalogErrorCode, type CatalogKeys, type CatalogPayload, type CatalogPlugin, type CatalogRelease, } from './catalog.ts';
|
|
4
4
|
export { PublishError, writeCatalog, writeCatalogKeys, type WrittenCatalog, type WrittenKeys, } from './publish.ts';
|
|
5
|
-
export { contributionsOf, manifestSchema, missingEntries, parseManifest, permissionSummary, pluginApiVersion, type Contribution, type ContributionKind, type PluginManifest, } from '../manifest.ts';
|
|
5
|
+
export { agreementMaxBytes, contributionsOf, manifestSchema, missingEntries, parseManifest, permissionSummary, pluginApiVersion, type Contribution, type ContributionKind, type PluginAgreement, type PluginManifest, } from '../manifest.ts';
|
|
6
6
|
export { checkPackageFiles, checkPackagePath, packageLimits } from '../package-rules.ts';
|
|
7
7
|
export { compositionReportSchema, parseReport, reportLimits } from '../report.ts';
|
|
8
8
|
export type { CompositionReport } from '../bridge.ts';
|
package/dist/node/index.js
CHANGED
|
@@ -3,6 +3,6 @@ export { readZip, writeZip, ZipError } from "./zip.js";
|
|
|
3
3
|
export { CatalogError, catalogKeyId, catalogLimits, catalogPublicKey, compareVersions, generateCatalogKeys, releaseManifest, signCatalog, verifyCatalog, } from "./catalog.js";
|
|
4
4
|
export { PublishError, writeCatalog, writeCatalogKeys, } from "./publish.js";
|
|
5
5
|
// The manifest and package rules again, so Node code needs no DOM types from the runtime entry.
|
|
6
|
-
export { contributionsOf, manifestSchema, missingEntries, parseManifest, permissionSummary, pluginApiVersion, } from "../manifest.js";
|
|
6
|
+
export { agreementMaxBytes, contributionsOf, manifestSchema, missingEntries, parseManifest, permissionSummary, pluginApiVersion, } from "../manifest.js";
|
|
7
7
|
export { checkPackageFiles, checkPackagePath, packageLimits } from "../package-rules.js";
|
|
8
8
|
export { compositionReportSchema, parseReport, reportLimits } from "../report.js";
|
package/dist/node/package.d.ts
CHANGED
|
@@ -10,6 +10,14 @@ export interface CheckedPackage {
|
|
|
10
10
|
files: PackageFile[];
|
|
11
11
|
/** SHA-256 over every file's path and content, in path order. */
|
|
12
12
|
digest: string;
|
|
13
|
+
/** The agreement the manifest names, with its text and the SHA-256 of its bytes. */
|
|
14
|
+
agreement: CheckedAgreement | null;
|
|
15
|
+
}
|
|
16
|
+
export interface CheckedAgreement {
|
|
17
|
+
title: string;
|
|
18
|
+
file: string;
|
|
19
|
+
text: string;
|
|
20
|
+
sha256: string;
|
|
13
21
|
}
|
|
14
22
|
/** Validates file contents given as path → bytes (from a zip or a directory). */
|
|
15
23
|
export declare function checkPackage(contents: ReadonlyMap<string, Buffer>): CheckedPackage;
|
package/dist/node/package.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { lstat, readdir, readFile } from 'node:fs/promises';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
-
import { missingEntries, parseManifest } from "../manifest.js";
|
|
4
|
+
import { agreementMaxBytes, missingEntries, parseManifest, } from "../manifest.js";
|
|
5
5
|
import { checkPackageFiles, packageLimits } from "../package-rules.js";
|
|
6
6
|
import { readZip } from "./zip.js";
|
|
7
7
|
export class PackageError extends Error {
|
|
@@ -12,6 +12,25 @@ export class PackageError extends Error {
|
|
|
12
12
|
this.name = 'PackageError';
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
|
+
/** Reads the agreement file the manifest names: present, at most 256 KiB, and valid UTF-8. */
|
|
16
|
+
function checkAgreement(manifest, contents) {
|
|
17
|
+
if (!manifest.agreement)
|
|
18
|
+
return null;
|
|
19
|
+
const { title, file } = manifest.agreement;
|
|
20
|
+
const data = contents.get(file);
|
|
21
|
+
if (!data)
|
|
22
|
+
throw new PackageError([`${file}: agreement not found`]);
|
|
23
|
+
if (data.length > agreementMaxBytes)
|
|
24
|
+
throw new PackageError([`${file}: agreement larger than 256 KiB`]);
|
|
25
|
+
let text;
|
|
26
|
+
try {
|
|
27
|
+
text = new TextDecoder('utf-8', { fatal: true }).decode(data);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
throw new PackageError([`${file}: agreement is not UTF-8`]);
|
|
31
|
+
}
|
|
32
|
+
return { title, file, text, sha256: createHash('sha256').update(data).digest('hex') };
|
|
33
|
+
}
|
|
15
34
|
/** Validates file contents given as path → bytes (from a zip or a directory). */
|
|
16
35
|
export function checkPackage(contents) {
|
|
17
36
|
const files = [...contents].map(([path, data]) => ({ path, size: data.length }));
|
|
@@ -32,6 +51,7 @@ export function checkPackage(contents) {
|
|
|
32
51
|
const missing = missingEntries(parsed.manifest, new Set(contents.keys()));
|
|
33
52
|
if (missing.length)
|
|
34
53
|
throw new PackageError(missing.map((entry) => `${entry}: entry not found`));
|
|
54
|
+
const agreement = checkAgreement(parsed.manifest, contents);
|
|
35
55
|
const digest = createHash('sha256');
|
|
36
56
|
for (const path of [...contents.keys()].sort()) {
|
|
37
57
|
digest.update(path).update('\0');
|
|
@@ -41,6 +61,7 @@ export function checkPackage(contents) {
|
|
|
41
61
|
manifest: parsed.manifest,
|
|
42
62
|
files: files.sort((a, b) => (a.path < b.path ? -1 : 1)),
|
|
43
63
|
digest: digest.digest('hex'),
|
|
64
|
+
agreement,
|
|
44
65
|
};
|
|
45
66
|
}
|
|
46
67
|
/**
|
package/dist/report.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ import type { CompositionReport } from './bridge.ts';
|
|
|
4
4
|
export declare const reportLimits: {
|
|
5
5
|
readonly subresources: 10000;
|
|
6
6
|
readonly references: 50000;
|
|
7
|
+
/** Candidate files of one reference (`uris`). */
|
|
8
|
+
readonly candidates: 16;
|
|
7
9
|
readonly diagnostics: 500;
|
|
8
10
|
/** Serialized size: UTF-8 bytes of the report as JSON. */
|
|
9
11
|
readonly bytes: number;
|
package/dist/report.js
CHANGED
|
@@ -3,6 +3,8 @@ import { z } from 'zod';
|
|
|
3
3
|
export const reportLimits = {
|
|
4
4
|
subresources: 10_000,
|
|
5
5
|
references: 50_000,
|
|
6
|
+
/** Candidate files of one reference (`uris`). */
|
|
7
|
+
candidates: 16,
|
|
6
8
|
diagnostics: 500,
|
|
7
9
|
/** Serialized size: UTF-8 bytes of the report as JSON. */
|
|
8
10
|
bytes: 4 * 1024 * 1024,
|
|
@@ -14,6 +16,7 @@ const plain = (max) => z
|
|
|
14
16
|
.max(max)
|
|
15
17
|
.refine((text) => !controlCharacters.test(text), 'Control characters are not allowed');
|
|
16
18
|
const key = z.string().min(1).max(200);
|
|
19
|
+
const uri = z.string().min(1).max(2048);
|
|
17
20
|
const subresourceSchema = z.strictObject({
|
|
18
21
|
key,
|
|
19
22
|
kind: plain(80).min(1),
|
|
@@ -23,21 +26,29 @@ const subresourceSchema = z.strictObject({
|
|
|
23
26
|
const referenceSchema = z
|
|
24
27
|
.strictObject({
|
|
25
28
|
from: key.optional(),
|
|
26
|
-
uri:
|
|
29
|
+
uri: uri.optional(),
|
|
30
|
+
uris: z
|
|
31
|
+
.array(uri)
|
|
32
|
+
.min(2)
|
|
33
|
+
.max(reportLimits.candidates)
|
|
34
|
+
.refine((uris) => new Set(uris).size === uris.length, 'Candidates must not repeat')
|
|
35
|
+
.optional(),
|
|
27
36
|
key: key.optional(),
|
|
28
37
|
uriKey: key.optional(),
|
|
29
38
|
role: plain(80).min(1),
|
|
30
39
|
evidence: plain(300).optional(),
|
|
31
40
|
inferred: plain(120).min(1).optional(),
|
|
32
41
|
})
|
|
33
|
-
.refine((reference) =>
|
|
34
|
-
message: 'Give exactly one of uri and key'
|
|
42
|
+
.refine((reference) => [reference.uri, reference.uris, reference.key].filter((each) => each !== undefined).length ===
|
|
43
|
+
1, { message: 'Give exactly one of uri, uris and key' })
|
|
44
|
+
.refine((reference) => reference.inferred === undefined || reference.key === undefined, {
|
|
45
|
+
message: 'Only a reference with a uri or uris can be inferred',
|
|
35
46
|
})
|
|
36
|
-
.refine((reference) => reference.
|
|
37
|
-
message: '
|
|
47
|
+
.refine((reference) => reference.uriKey === undefined || reference.key === undefined, {
|
|
48
|
+
message: 'Give uriKey only with uri or uris',
|
|
38
49
|
})
|
|
39
|
-
.refine((reference) => reference.
|
|
40
|
-
message: 'Give uriKey
|
|
50
|
+
.refine((reference) => reference.uris === undefined || reference.uriKey !== undefined, {
|
|
51
|
+
message: 'Give uriKey with uris',
|
|
41
52
|
});
|
|
42
53
|
const diagnosticSchema = z.strictObject({
|
|
43
54
|
severity: z.enum(['error', 'warning', 'info']),
|
package/package.json
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vormio/plugin-sdk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.10-rc.1",
|
|
4
4
|
"description": "Manifest schema, runtime helpers and packaging for Vormio plugins.",
|
|
5
5
|
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/Vormio/Vormio-Core.git",
|
|
9
|
+
"directory": "packages/plugin-sdk"
|
|
10
|
+
},
|
|
6
11
|
"keywords": [
|
|
7
12
|
"vormio",
|
|
8
13
|
"plugin",
|