@hyprune/schema 0.9.0 → 0.11.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/CHANGELOG.md +41 -0
- package/README.md +31 -1
- package/bin/validate.mjs +11 -5
- package/examples/registry/entry.json +106 -0
- package/examples/registry/index.json +137 -0
- package/examples/registry/invalid/registry-entry--bad-spdx.json +106 -0
- package/examples/registry/invalid/registry-entry--build-metadata.json +106 -0
- package/examples/registry/invalid/registry-entry--control-characters.json +106 -0
- package/examples/registry/invalid/registry-entry--duplicate-version.json +136 -0
- package/examples/registry/invalid/registry-entry--five-thumbnails.json +130 -0
- package/examples/registry/invalid/registry-entry--library-over-limit.json +22 -0
- package/examples/registry/invalid/registry-entry--name-too-long.json +106 -0
- package/examples/registry/invalid/registry-entry--tag-mismatch.json +106 -0
- package/examples/registry/invalid/registry-entry--uppercase-hash.json +106 -0
- package/examples/registry/invalid/registry-entry--versions-oldest-first.json +106 -0
- package/examples/registry/invalid/registry-index--duplicate-id.json +159 -0
- package/examples/registry/invalid/registry-index--entry-schema-pointer.json +138 -0
- package/examples/registry/invalid/registry-index--http-url.json +137 -0
- package/examples/registry/invalid/registry-index--newer-index-version.json +137 -0
- package/examples/registry/invalid/registry-index--zero-serial.json +137 -0
- package/examples/registry/invalid/release-record--archive-name.json +42 -0
- package/examples/registry/invalid/release-record--library-requires.json +42 -0
- package/examples/registry/invalid/release-record--short-commit.json +42 -0
- package/examples/registry/invalid/release-record--tag-mismatch.json +42 -0
- package/examples/registry/invalid/release-record--thumbnail-name.json +42 -0
- package/examples/registry/invalid/sources--archive-without-hash.json +50 -0
- package/examples/registry/invalid/sources--duplicate-id.json +59 -0
- package/examples/registry/invalid/sources--github-without-repo.json +50 -0
- package/examples/registry/invalid/sources--index-with-repo.json +52 -0
- package/examples/registry/invalid/sources--label-too-long.json +51 -0
- package/examples/registry/invalid/sources--reserved-id.json +51 -0
- package/examples/registry/invalid/sources--too-many.json +277 -0
- package/examples/registry/release-record.json +42 -0
- package/examples/registry/sources.json +51 -0
- package/lib/validate.d.mts +24 -0
- package/lib/validate.mjs +147 -0
- package/package.json +2 -1
- package/registry/v0.1/index.schema.json +309 -0
- package/registry/v0.1/release-record.schema.json +181 -0
- package/registry/v0.1/sources.schema.json +129 -0
- package/v0.10/object.schema.json +39 -0
- package/v0.25/ipc.schema.json +20085 -0
package/lib/validate.mjs
CHANGED
|
@@ -423,6 +423,8 @@ function validateMachine(value, fail) {
|
|
|
423
423
|
function traitCombinations(names, fail) {
|
|
424
424
|
for (const a of names) for (const c of names) if (a < c && !(traitPairs[a] || []).includes(c)) fail(`traits: ${a} and ${c} can't be combined`);
|
|
425
425
|
}
|
|
426
|
+
// RFC-0010 canonical character clip names (talk: the companions amendment).
|
|
427
|
+
const canonicalClips = ['idle', 'idle_fidget', 'walk', 'run', 'turn_left', 'turn_right', 'look_around', 'wave', 'nod', 'sit', 'sit_idle', 'stand', 'talk'];
|
|
426
428
|
function objectTraitRules(traits, slots, fail, inside = () => {}) {
|
|
427
429
|
const quat = (q, where) => { if (Math.abs(Math.hypot(...q) - 1) > 0.002) fail(`${where}: quaternion must be normalized`); };
|
|
428
430
|
const unique = (items, field, label) => { const ids = items.map(v => v[field]); if (new Set(ids).size !== ids.length) fail(`${label}: duplicate ${field}`); };
|
|
@@ -473,6 +475,14 @@ function validateObjectManifest(value, version, options, fail, warn) {
|
|
|
473
475
|
const traits = value.traits, slots = value.slots || {}, names = Object.keys(traits), b = value.bounds;
|
|
474
476
|
if (options.directory !== undefined && options.directory !== value.id) fail(`id: "${value.id}" differs from its directory "${options.directory}"`);
|
|
475
477
|
traitCombinations(names, fail);
|
|
478
|
+
// Companion block (object format 0.10 draft, RFC-0010 companions amendment).
|
|
479
|
+
if (value.companion) {
|
|
480
|
+
if (!traits.character) fail('companion: only characters can be companions');
|
|
481
|
+
else for (const clip of value.companion.idles || []) {
|
|
482
|
+
if (!(clip in traits.character.clips)) fail(`companion: idle variant "${clip}" is not a clip of traits.character`);
|
|
483
|
+
if (canonicalClips.includes(clip)) fail(`companion: "${clip}" is a canonical clip, not an idle variant`);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
476
486
|
for (let j = 0; j < 3; j++) if (b.min[j] >= b.max[j]) fail(`bounds: min ${'xyz'[j]} must be below max`);
|
|
477
487
|
const t = objectLimits.boundsTolerance, surfaces = new Set(traits.movable ? traits.movable.surfaces || ['floor'] : []);
|
|
478
488
|
const one = surfaces.size === 1 ? [...surfaces][0] : undefined;
|
|
@@ -893,3 +903,140 @@ export function validate(kind, value, method, version, options = {}) {
|
|
|
893
903
|
}
|
|
894
904
|
return { valid: errors.length === 0, errors };
|
|
895
905
|
}
|
|
906
|
+
|
|
907
|
+
// Registry documents (RFC-0015), versioned on their own under registry/v0.N/: the index
|
|
908
|
+
// (and one entry of it, the shape of a registry entry file), the release record and
|
|
909
|
+
// sources.json. Discovered from the directories like every other contract.
|
|
910
|
+
const registryRoot = new URL('../registry/', import.meta.url);
|
|
911
|
+
export const registryVersions = existsSync(registryRoot)
|
|
912
|
+
? readdirSync(registryRoot).filter(d => /^v0\.\d+$/.test(d)).map(d => d.slice(1)).sort(byVersion) : [];
|
|
913
|
+
const registrySchemas = { 'registry-index': 'index', 'registry-entry': 'index', 'release-record': 'release-record', sources: 'sources' };
|
|
914
|
+
for (const v of registryVersions) for (const name of new Set(Object.values(registrySchemas)))
|
|
915
|
+
if (existsSync(new URL(`v${v}/${name}.schema.json`, registryRoot))) ajv.addSchema(JSON.parse(readFileSync(new URL(`v${v}/${name}.schema.json`, registryRoot))));
|
|
916
|
+
/** RFC-0015 archive limits per package type (compressed bytes, expanded bytes, files, single file). */
|
|
917
|
+
export const registryLimits = Object.freeze({
|
|
918
|
+
world: Object.freeze({ bytes: 512 * 1048576, expandedBytes: 1024 * 1048576, files: 10000, fileBytes: 128 * 1048576 }),
|
|
919
|
+
library: Object.freeze({ bytes: 256 * 1048576, expandedBytes: 512 * 1048576, files: 4000, fileBytes: 32 * 1048576 }),
|
|
920
|
+
index: Object.freeze({ bytes: 16 * 1048576, entries: 20000, versions: 64, thumbnails: 4, custom: 32 }),
|
|
921
|
+
});
|
|
922
|
+
const SEMVER_PARTS = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$/;
|
|
923
|
+
/** SemVer 2.0 parse; undefined when invalid (including numeric pre-release identifiers with leading zeros). */
|
|
924
|
+
export function parseSemver(text) {
|
|
925
|
+
const m = typeof text === 'string' ? SEMVER_PARTS.exec(text) : null;
|
|
926
|
+
if (!m) return undefined;
|
|
927
|
+
const pre = m[4] === undefined ? [] : m[4].split('.');
|
|
928
|
+
if (pre.some(p => /^0\d+$/.test(p))) return undefined;
|
|
929
|
+
return { major: Number(m[1]), minor: Number(m[2]), patch: Number(m[3]), prerelease: pre, build: m[5] ?? null };
|
|
930
|
+
}
|
|
931
|
+
/** SemVer precedence: negative when a < b, 0 when equal (build metadata ignored), positive when a > b. Throws on invalid versions. */
|
|
932
|
+
export function compareSemver(a, b) {
|
|
933
|
+
const x = parseSemver(a), y = parseSemver(b);
|
|
934
|
+
if (!x || !y) throw new Error(`Invalid SemVer: ${!x ? a : b}`);
|
|
935
|
+
for (const k of ['major', 'minor', 'patch']) if (x[k] !== y[k]) return x[k] < y[k] ? -1 : 1;
|
|
936
|
+
if (!x.prerelease.length || !y.prerelease.length) return Math.sign(y.prerelease.length - x.prerelease.length);
|
|
937
|
+
for (let i = 0; i < Math.max(x.prerelease.length, y.prerelease.length); i++) {
|
|
938
|
+
const p = x.prerelease[i], q = y.prerelease[i];
|
|
939
|
+
if (p === undefined) return -1;
|
|
940
|
+
if (q === undefined) return 1;
|
|
941
|
+
const pn = /^\d+$/.test(p), qn = /^\d+$/.test(q);
|
|
942
|
+
if (pn && qn) { const d = BigInt(p) - BigInt(q); if (d) return d < 0n ? -1 : 1; }
|
|
943
|
+
else if (pn !== qn) return pn ? -1 : 1;
|
|
944
|
+
else if (p !== q) return p < q ? -1 : 1;
|
|
945
|
+
}
|
|
946
|
+
return 0;
|
|
947
|
+
}
|
|
948
|
+
/**
|
|
949
|
+
* The version a tag names by the RFC-0015 tag convention: `v<version>` for a repository
|
|
950
|
+
* with one package, `<name>-v<version>` (name a lowercase slug) for one of several.
|
|
951
|
+
* Returns {name|null, version} or undefined when the tag follows neither form.
|
|
952
|
+
*/
|
|
953
|
+
export function tagVersion(tag) {
|
|
954
|
+
if (typeof tag !== 'string') return undefined;
|
|
955
|
+
const m = /^(?:([a-z0-9]+(?:-[a-z0-9]+)*)-)?v(\d[0-9A-Za-z.+-]*)$/.exec(tag);
|
|
956
|
+
if (!m || !parseSemver(m[2])) return undefined;
|
|
957
|
+
return { name: m[1] ?? null, version: m[2] };
|
|
958
|
+
}
|
|
959
|
+
const spdxValid = text => { try { parseSpdx(text); return true; } catch { return false; } };
|
|
960
|
+
// Semantic rules of one index entry (shared by kinds registry-index and registry-entry).
|
|
961
|
+
function registryEntryRules(entry, label, fail) {
|
|
962
|
+
const limits = registryLimits[entry.type];
|
|
963
|
+
const tags = new Set(), versions = new Set();
|
|
964
|
+
entry.versions.forEach((v, i) => {
|
|
965
|
+
const at = `${label}versions/${i} (${v.tag})`;
|
|
966
|
+
if (tags.has(v.tag)) fail(`${at}: duplicate tag`); tags.add(v.tag);
|
|
967
|
+
if (versions.has(v.version)) fail(`${at}: duplicate version ${v.version}`); versions.add(v.version);
|
|
968
|
+
if (!parseSemver(v.version)) fail(`${at}: version ${v.version} is not SemVer (numeric pre-release identifiers can't have leading zeros)`);
|
|
969
|
+
const named = tagVersion(v.tag);
|
|
970
|
+
if (!named || named.version !== v.version) fail(`${at}: tag must name version ${v.version} (v${v.version} or <name>-v${v.version})`);
|
|
971
|
+
if (i > 0 && parseSemver(v.version) && parseSemver(entry.versions[i - 1].version) && compareSemver(entry.versions[i - 1].version, v.version) <= 0)
|
|
972
|
+
fail(`${at}: versions must be newest first (${entry.versions[i - 1].version} before ${v.version})`);
|
|
973
|
+
if (!spdxValid(v.license)) fail(`${at}: license: invalid SPDX expression`);
|
|
974
|
+
if (v.bytes > limits.bytes) fail(`${at}: bytes ${v.bytes} exceed the ${entry.type} archive limit ${limits.bytes}`);
|
|
975
|
+
if (v.expandedBytes > limits.expandedBytes) fail(`${at}: expandedBytes ${v.expandedBytes} exceed the ${entry.type} limit ${limits.expandedBytes}`);
|
|
976
|
+
if (v.files > limits.files) fail(`${at}: files ${v.files} exceed the ${entry.type} limit ${limits.files}`);
|
|
977
|
+
if (entry.type === 'library' && v.requires?.libraries?.length) fail(`${at}: a library requires no libraries`);
|
|
978
|
+
if ((v.requires?.libraries || []).some(r => r.startsWith(entry.id + '@'))) fail(`${at}: requires itself`);
|
|
979
|
+
});
|
|
980
|
+
}
|
|
981
|
+
function releaseRecordRules(r, fail) {
|
|
982
|
+
const limits = registryLimits[r.type], stem = `${r.id}-${r.version}`;
|
|
983
|
+
if (!parseSemver(r.version)) fail(`version: ${r.version} is not SemVer (numeric pre-release identifiers can't have leading zeros)`);
|
|
984
|
+
if (!spdxValid(r.license)) fail('license: invalid SPDX expression');
|
|
985
|
+
const named = tagVersion(r.source.tag);
|
|
986
|
+
if (!named || named.version !== r.version) fail(`source.tag: ${r.source.tag} must name version ${r.version} (v${r.version} or <name>-v${r.version})`);
|
|
987
|
+
if (r.archive.name !== `${stem}.tar.zst`) fail(`archive.name: must be ${stem}.tar.zst`);
|
|
988
|
+
if (r.archive.bytes > limits.bytes) fail(`archive.bytes: ${r.archive.bytes} exceed the ${r.type} archive limit ${limits.bytes}`);
|
|
989
|
+
if (r.archive.expandedBytes > limits.expandedBytes) fail(`archive.expandedBytes: ${r.archive.expandedBytes} exceed the ${r.type} limit ${limits.expandedBytes}`);
|
|
990
|
+
if (r.archive.files > limits.files) fail(`archive.files: ${r.archive.files} exceed the ${r.type} limit ${limits.files}`);
|
|
991
|
+
if (r.type === 'library' && r.requires?.libraries?.length) fail('requires.libraries: a library requires no libraries');
|
|
992
|
+
const names = (r.thumbnails || []).map(t => t.name);
|
|
993
|
+
names.forEach((name, i) => { if (!name.startsWith(`${stem}.thumb-${i + 1}.`)) fail(`thumbnails/${i}: name must be ${stem}.thumb-${i + 1}.png or .jpg`); });
|
|
994
|
+
}
|
|
995
|
+
function sourcesRules(doc, fail) {
|
|
996
|
+
const ids = new Set();
|
|
997
|
+
(doc.custom || []).forEach((s, i) => {
|
|
998
|
+
const at = `custom/${i} (${s.id})`;
|
|
999
|
+
if (ids.has(s.id)) fail(`${at}: duplicate id`); ids.add(s.id);
|
|
1000
|
+
if (['official', 'community'].includes(s.id)) fail(`${at}: id is reserved for a built-in source`);
|
|
1001
|
+
if (s.label.trim() !== s.label) fail(`${at}: label has leading or trailing spaces`);
|
|
1002
|
+
const extra = { index: ['repo', 'sha256'], github: ['url', 'sha256'], url: ['repo'] }[s.kind].filter(k => s[k] !== undefined);
|
|
1003
|
+
if (extra.length) fail(`${at}: kind ${s.kind} takes no ${extra.join(', ')}`);
|
|
1004
|
+
if (s.kind === 'url') {
|
|
1005
|
+
const path = new URL(s.url).pathname;
|
|
1006
|
+
if (path.endsWith('.tar.zst') && !s.sha256) fail(`${at}: an archive URL needs its sha256`);
|
|
1007
|
+
if (!path.endsWith('.tar.zst') && !path.endsWith('.json')) fail(`${at}: a package URL is a .tar.zst archive or a .release.json record`);
|
|
1008
|
+
if (path.endsWith('.json') && s.sha256) fail(`${at}: a release record URL supplies its own hash`);
|
|
1009
|
+
}
|
|
1010
|
+
});
|
|
1011
|
+
}
|
|
1012
|
+
/**
|
|
1013
|
+
* Validate a registry document (RFC-0015). Kinds: `registry-index` (an index document),
|
|
1014
|
+
* `registry-entry` (one entry, e.g. a registry entry file, which may carry `$schema`),
|
|
1015
|
+
* `release-record` and `sources`. Adds the rules a schema can't express: unique ids,
|
|
1016
|
+
* versions unique and newest first by SemVer, the tag naming the version, SPDX
|
|
1017
|
+
* expressions, per-type archive limits.
|
|
1018
|
+
* @param version registry format (default: the newest registry/v0.N)
|
|
1019
|
+
* @throws on an unknown kind or version
|
|
1020
|
+
*/
|
|
1021
|
+
export function validateRegistry(kind, value, version = registryVersions.at(-1)) {
|
|
1022
|
+
if (!Object.hasOwn(registrySchemas, kind)) throw new Error(`Unknown registry kind: ${kind}`);
|
|
1023
|
+
if (!registryVersions.includes(version)) throw new Error(`Unknown registry version: ${version}`);
|
|
1024
|
+
const id = `https://hyprune.com/schema/registry/v${version}/${registrySchemas[kind]}.schema.json${kind === 'registry-entry' ? '#/$defs/entry' : ''}`;
|
|
1025
|
+
const key = `registry:${version}:${kind}`;
|
|
1026
|
+
if (!validators.has(key)) validators.set(key, ajv.getSchema(id));
|
|
1027
|
+
const check = validators.get(key);
|
|
1028
|
+
if (!check(value)) return { valid: false, errors: check.errors.map(e => `${e.instancePath || '/'} ${e.message}`) };
|
|
1029
|
+
const errors = [], fail = m => errors.push(m);
|
|
1030
|
+
if (kind === 'registry-entry') registryEntryRules(value, '', fail);
|
|
1031
|
+
if (kind === 'registry-index') {
|
|
1032
|
+
const ids = new Set();
|
|
1033
|
+
value.entries.forEach((entry, i) => {
|
|
1034
|
+
if (ids.has(entry.id)) fail(`entries/${i}: duplicate id ${entry.id}`); ids.add(entry.id);
|
|
1035
|
+
if (entry.$schema !== undefined) fail(`entries/${i}: $schema belongs to entry files, not index entries`);
|
|
1036
|
+
registryEntryRules(entry, `entries/${i} ${entry.id}: `, fail);
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
1039
|
+
if (kind === 'release-record') releaseRecordRules(value, fail);
|
|
1040
|
+
if (kind === 'sources') sourcesRules(value, fail);
|
|
1041
|
+
return { valid: errors.length === 0, errors };
|
|
1042
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyprune/schema",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Versioned JSON Schemas for Hyprune world packages and the IPC protocol, with a validator and CLI for world authors",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"lib/",
|
|
40
40
|
"v0*/**",
|
|
41
41
|
"extensions/",
|
|
42
|
+
"registry/",
|
|
42
43
|
"examples/",
|
|
43
44
|
"CHANGELOG.md",
|
|
44
45
|
"README.md",
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://hyprune.com/schema/registry/v0.1/index.schema.json",
|
|
4
|
+
"title": "Hyprune registry index 0.1 (RFC-0015): the document every index source serves; $defs.entry is one entry and the shape of a registry entry file",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"indexVersion": {
|
|
8
|
+
"const": "0.1"
|
|
9
|
+
},
|
|
10
|
+
"name": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"$ref": "#/$defs/line",
|
|
13
|
+
"maxLength": 64
|
|
14
|
+
},
|
|
15
|
+
"homepage": {
|
|
16
|
+
"$ref": "#/$defs/https"
|
|
17
|
+
},
|
|
18
|
+
"serial": {
|
|
19
|
+
"type": "integer",
|
|
20
|
+
"minimum": 1,
|
|
21
|
+
"maximum": 9007199254740991
|
|
22
|
+
},
|
|
23
|
+
"generated": {
|
|
24
|
+
"type": "string",
|
|
25
|
+
"maxLength": 40,
|
|
26
|
+
"pattern": "^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)(\\.[0-9]{1,9})?Z$"
|
|
27
|
+
},
|
|
28
|
+
"successor": {
|
|
29
|
+
"anyOf": [
|
|
30
|
+
{
|
|
31
|
+
"$ref": "#/$defs/https"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"type": "null"
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
},
|
|
38
|
+
"entries": {
|
|
39
|
+
"type": "array",
|
|
40
|
+
"maxItems": 20000,
|
|
41
|
+
"items": {
|
|
42
|
+
"$ref": "#/$defs/entry"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"required": [
|
|
47
|
+
"indexVersion",
|
|
48
|
+
"serial",
|
|
49
|
+
"generated",
|
|
50
|
+
"entries"
|
|
51
|
+
],
|
|
52
|
+
"additionalProperties": false,
|
|
53
|
+
"$defs": {
|
|
54
|
+
"id": {
|
|
55
|
+
"type": "string",
|
|
56
|
+
"minLength": 3,
|
|
57
|
+
"maxLength": 128,
|
|
58
|
+
"pattern": "^[a-z][a-z0-9-]*(\\.[a-z][a-z0-9-]*)+$"
|
|
59
|
+
},
|
|
60
|
+
"version": {
|
|
61
|
+
"description": "SemVer 2.0 without build metadata (published versions).",
|
|
62
|
+
"type": "string",
|
|
63
|
+
"minLength": 5,
|
|
64
|
+
"maxLength": 64,
|
|
65
|
+
"pattern": "^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)(-[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*)?$"
|
|
66
|
+
},
|
|
67
|
+
"tag": {
|
|
68
|
+
"description": "The git tag the release was built from: v<version> or <name>-v<version>.",
|
|
69
|
+
"type": "string",
|
|
70
|
+
"minLength": 1,
|
|
71
|
+
"maxLength": 128,
|
|
72
|
+
"pattern": "^[A-Za-z0-9._-]+$"
|
|
73
|
+
},
|
|
74
|
+
"formatVersion": {
|
|
75
|
+
"type": "string",
|
|
76
|
+
"maxLength": 16,
|
|
77
|
+
"pattern": "^0\\.(0|[1-9][0-9]*)$"
|
|
78
|
+
},
|
|
79
|
+
"license": {
|
|
80
|
+
"description": "SPDX expression (parsed by the validator).",
|
|
81
|
+
"type": "string",
|
|
82
|
+
"minLength": 1,
|
|
83
|
+
"maxLength": 256
|
|
84
|
+
},
|
|
85
|
+
"sha256": {
|
|
86
|
+
"type": "string",
|
|
87
|
+
"pattern": "^[0-9a-f]{64}$"
|
|
88
|
+
},
|
|
89
|
+
"https": {
|
|
90
|
+
"type": "string",
|
|
91
|
+
"minLength": 9,
|
|
92
|
+
"maxLength": 2048,
|
|
93
|
+
"pattern": "^https://[^\\s/?#@]+(/[^\\s]*)?$"
|
|
94
|
+
},
|
|
95
|
+
"line": {
|
|
96
|
+
"description": "One line of plain text: no control characters.",
|
|
97
|
+
"type": "string",
|
|
98
|
+
"minLength": 1,
|
|
99
|
+
"pattern": "^[^\\u0000-\\u001f\\u007f-\\u009f]+$"
|
|
100
|
+
},
|
|
101
|
+
"slug": {
|
|
102
|
+
"type": "string",
|
|
103
|
+
"minLength": 1,
|
|
104
|
+
"maxLength": 32,
|
|
105
|
+
"pattern": "^[a-z0-9]+(-[a-z0-9]+)*$"
|
|
106
|
+
},
|
|
107
|
+
"libraryRef": {
|
|
108
|
+
"description": "An exact library id@version (RFC-0011).",
|
|
109
|
+
"type": "string",
|
|
110
|
+
"maxLength": 200,
|
|
111
|
+
"pattern": "^[a-z][a-z0-9-]*(\\.[a-z][a-z0-9-]*)+@(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)(-[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*)?$"
|
|
112
|
+
},
|
|
113
|
+
"requires": {
|
|
114
|
+
"type": "object",
|
|
115
|
+
"properties": {
|
|
116
|
+
"libraries": {
|
|
117
|
+
"type": "array",
|
|
118
|
+
"maxItems": 64,
|
|
119
|
+
"uniqueItems": true,
|
|
120
|
+
"items": {
|
|
121
|
+
"$ref": "#/$defs/libraryRef"
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
"additionalProperties": false
|
|
126
|
+
},
|
|
127
|
+
"thumbnail": {
|
|
128
|
+
"type": "object",
|
|
129
|
+
"properties": {
|
|
130
|
+
"url": {
|
|
131
|
+
"$ref": "#/$defs/https"
|
|
132
|
+
},
|
|
133
|
+
"sha256": {
|
|
134
|
+
"$ref": "#/$defs/sha256"
|
|
135
|
+
},
|
|
136
|
+
"width": {
|
|
137
|
+
"type": "integer",
|
|
138
|
+
"minimum": 1,
|
|
139
|
+
"maximum": 1280
|
|
140
|
+
},
|
|
141
|
+
"height": {
|
|
142
|
+
"type": "integer",
|
|
143
|
+
"minimum": 1,
|
|
144
|
+
"maximum": 720
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
"required": [
|
|
148
|
+
"url",
|
|
149
|
+
"sha256",
|
|
150
|
+
"width",
|
|
151
|
+
"height"
|
|
152
|
+
],
|
|
153
|
+
"additionalProperties": false
|
|
154
|
+
},
|
|
155
|
+
"version-entry": {
|
|
156
|
+
"description": "One published version of an entry (versions[]).",
|
|
157
|
+
"type": "object",
|
|
158
|
+
"properties": {
|
|
159
|
+
"tag": {
|
|
160
|
+
"$ref": "#/$defs/tag"
|
|
161
|
+
},
|
|
162
|
+
"version": {
|
|
163
|
+
"$ref": "#/$defs/version"
|
|
164
|
+
},
|
|
165
|
+
"formatVersion": {
|
|
166
|
+
"$ref": "#/$defs/formatVersion"
|
|
167
|
+
},
|
|
168
|
+
"published": {
|
|
169
|
+
"type": "string",
|
|
170
|
+
"pattern": "^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$"
|
|
171
|
+
},
|
|
172
|
+
"license": {
|
|
173
|
+
"$ref": "#/$defs/license"
|
|
174
|
+
},
|
|
175
|
+
"url": {
|
|
176
|
+
"$ref": "#/$defs/https"
|
|
177
|
+
},
|
|
178
|
+
"sha256": {
|
|
179
|
+
"$ref": "#/$defs/sha256"
|
|
180
|
+
},
|
|
181
|
+
"bytes": {
|
|
182
|
+
"type": "integer",
|
|
183
|
+
"minimum": 1,
|
|
184
|
+
"maximum": 536870912
|
|
185
|
+
},
|
|
186
|
+
"expandedBytes": {
|
|
187
|
+
"type": "integer",
|
|
188
|
+
"minimum": 0,
|
|
189
|
+
"maximum": 1073741824
|
|
190
|
+
},
|
|
191
|
+
"files": {
|
|
192
|
+
"type": "integer",
|
|
193
|
+
"minimum": 1,
|
|
194
|
+
"maximum": 10000
|
|
195
|
+
},
|
|
196
|
+
"record": {
|
|
197
|
+
"type": "object",
|
|
198
|
+
"properties": {
|
|
199
|
+
"url": {
|
|
200
|
+
"$ref": "#/$defs/https"
|
|
201
|
+
},
|
|
202
|
+
"sha256": {
|
|
203
|
+
"$ref": "#/$defs/sha256"
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
"required": [
|
|
207
|
+
"url",
|
|
208
|
+
"sha256"
|
|
209
|
+
],
|
|
210
|
+
"additionalProperties": false
|
|
211
|
+
},
|
|
212
|
+
"requires": {
|
|
213
|
+
"$ref": "#/$defs/requires"
|
|
214
|
+
},
|
|
215
|
+
"thumbnails": {
|
|
216
|
+
"type": "array",
|
|
217
|
+
"maxItems": 4,
|
|
218
|
+
"items": {
|
|
219
|
+
"$ref": "#/$defs/thumbnail"
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
"status": {
|
|
223
|
+
"enum": [
|
|
224
|
+
"available",
|
|
225
|
+
"unavailable"
|
|
226
|
+
]
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
"required": [
|
|
230
|
+
"tag",
|
|
231
|
+
"version",
|
|
232
|
+
"formatVersion",
|
|
233
|
+
"published",
|
|
234
|
+
"license",
|
|
235
|
+
"url",
|
|
236
|
+
"sha256",
|
|
237
|
+
"bytes",
|
|
238
|
+
"expandedBytes",
|
|
239
|
+
"files"
|
|
240
|
+
],
|
|
241
|
+
"additionalProperties": false
|
|
242
|
+
},
|
|
243
|
+
"entry": {
|
|
244
|
+
"description": "One package of an index. A registry entry file is exactly one entry and may add a $schema pointer; entries inside an index carry none.",
|
|
245
|
+
"type": "object",
|
|
246
|
+
"properties": {
|
|
247
|
+
"$schema": {
|
|
248
|
+
"$ref": "#/$defs/https"
|
|
249
|
+
},
|
|
250
|
+
"id": {
|
|
251
|
+
"$ref": "#/$defs/id"
|
|
252
|
+
},
|
|
253
|
+
"type": {
|
|
254
|
+
"enum": [
|
|
255
|
+
"world",
|
|
256
|
+
"library"
|
|
257
|
+
]
|
|
258
|
+
},
|
|
259
|
+
"name": {
|
|
260
|
+
"type": "string",
|
|
261
|
+
"$ref": "#/$defs/line",
|
|
262
|
+
"maxLength": 64
|
|
263
|
+
},
|
|
264
|
+
"summary": {
|
|
265
|
+
"type": "string",
|
|
266
|
+
"maxLength": 140,
|
|
267
|
+
"pattern": "^[^\\u0000-\\u001f\\u007f-\\u009f]*$"
|
|
268
|
+
},
|
|
269
|
+
"description": {
|
|
270
|
+
"type": "string",
|
|
271
|
+
"maxLength": 2000,
|
|
272
|
+
"pattern": "^[^\\u0000-\\u0008\\u000b-\\u001f\\u007f-\\u009f]*$"
|
|
273
|
+
},
|
|
274
|
+
"creator": {
|
|
275
|
+
"type": "string",
|
|
276
|
+
"$ref": "#/$defs/line",
|
|
277
|
+
"maxLength": 64
|
|
278
|
+
},
|
|
279
|
+
"repo": {
|
|
280
|
+
"$ref": "#/$defs/https"
|
|
281
|
+
},
|
|
282
|
+
"tags": {
|
|
283
|
+
"type": "array",
|
|
284
|
+
"maxItems": 8,
|
|
285
|
+
"uniqueItems": true,
|
|
286
|
+
"items": {
|
|
287
|
+
"$ref": "#/$defs/slug"
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
"versions": {
|
|
291
|
+
"type": "array",
|
|
292
|
+
"minItems": 1,
|
|
293
|
+
"maxItems": 64,
|
|
294
|
+
"items": {
|
|
295
|
+
"$ref": "#/$defs/version-entry"
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
"required": [
|
|
300
|
+
"id",
|
|
301
|
+
"type",
|
|
302
|
+
"name",
|
|
303
|
+
"creator",
|
|
304
|
+
"versions"
|
|
305
|
+
],
|
|
306
|
+
"additionalProperties": false
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://hyprune.com/schema/registry/v0.1/release-record.schema.json",
|
|
4
|
+
"title": "Hyprune release record 0.1 (RFC-0015): <id>-<version>.release.json, published with the archive of one tag",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"$schema": {
|
|
8
|
+
"$ref": "index.schema.json#/$defs/https"
|
|
9
|
+
},
|
|
10
|
+
"recordVersion": {
|
|
11
|
+
"const": "0.1"
|
|
12
|
+
},
|
|
13
|
+
"type": {
|
|
14
|
+
"enum": [
|
|
15
|
+
"world",
|
|
16
|
+
"library"
|
|
17
|
+
]
|
|
18
|
+
},
|
|
19
|
+
"id": {
|
|
20
|
+
"$ref": "index.schema.json#/$defs/id"
|
|
21
|
+
},
|
|
22
|
+
"version": {
|
|
23
|
+
"$ref": "index.schema.json#/$defs/version"
|
|
24
|
+
},
|
|
25
|
+
"formatVersion": {
|
|
26
|
+
"$ref": "index.schema.json#/$defs/formatVersion"
|
|
27
|
+
},
|
|
28
|
+
"name": {
|
|
29
|
+
"type": "string",
|
|
30
|
+
"$ref": "index.schema.json#/$defs/line",
|
|
31
|
+
"maxLength": 64
|
|
32
|
+
},
|
|
33
|
+
"license": {
|
|
34
|
+
"$ref": "index.schema.json#/$defs/license"
|
|
35
|
+
},
|
|
36
|
+
"archive": {
|
|
37
|
+
"type": "object",
|
|
38
|
+
"properties": {
|
|
39
|
+
"name": {
|
|
40
|
+
"type": "string",
|
|
41
|
+
"maxLength": 220,
|
|
42
|
+
"pattern": "^[a-z][a-z0-9.-]*-[0-9A-Za-z.-]+\\.tar\\.zst$"
|
|
43
|
+
},
|
|
44
|
+
"sha256": {
|
|
45
|
+
"$ref": "index.schema.json#/$defs/sha256"
|
|
46
|
+
},
|
|
47
|
+
"bytes": {
|
|
48
|
+
"type": "integer",
|
|
49
|
+
"minimum": 1,
|
|
50
|
+
"maximum": 536870912
|
|
51
|
+
},
|
|
52
|
+
"expandedBytes": {
|
|
53
|
+
"type": "integer",
|
|
54
|
+
"minimum": 0,
|
|
55
|
+
"maximum": 1073741824
|
|
56
|
+
},
|
|
57
|
+
"files": {
|
|
58
|
+
"type": "integer",
|
|
59
|
+
"minimum": 1,
|
|
60
|
+
"maximum": 10000
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
"required": [
|
|
64
|
+
"name",
|
|
65
|
+
"sha256",
|
|
66
|
+
"bytes",
|
|
67
|
+
"expandedBytes",
|
|
68
|
+
"files"
|
|
69
|
+
],
|
|
70
|
+
"additionalProperties": false
|
|
71
|
+
},
|
|
72
|
+
"requires": {
|
|
73
|
+
"$ref": "index.schema.json#/$defs/requires"
|
|
74
|
+
},
|
|
75
|
+
"thumbnails": {
|
|
76
|
+
"type": "array",
|
|
77
|
+
"maxItems": 4,
|
|
78
|
+
"items": {
|
|
79
|
+
"type": "object",
|
|
80
|
+
"properties": {
|
|
81
|
+
"name": {
|
|
82
|
+
"type": "string",
|
|
83
|
+
"maxLength": 220,
|
|
84
|
+
"pattern": "^[a-z][a-z0-9.-]*-[0-9A-Za-z.-]+\\.thumb-[1-4]\\.(png|jpg)$"
|
|
85
|
+
},
|
|
86
|
+
"sha256": {
|
|
87
|
+
"$ref": "index.schema.json#/$defs/sha256"
|
|
88
|
+
},
|
|
89
|
+
"width": {
|
|
90
|
+
"type": "integer",
|
|
91
|
+
"minimum": 1,
|
|
92
|
+
"maximum": 1280
|
|
93
|
+
},
|
|
94
|
+
"height": {
|
|
95
|
+
"type": "integer",
|
|
96
|
+
"minimum": 1,
|
|
97
|
+
"maximum": 720
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
"required": [
|
|
101
|
+
"name",
|
|
102
|
+
"sha256",
|
|
103
|
+
"width",
|
|
104
|
+
"height"
|
|
105
|
+
],
|
|
106
|
+
"additionalProperties": false
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
"source": {
|
|
110
|
+
"type": "object",
|
|
111
|
+
"properties": {
|
|
112
|
+
"repo": {
|
|
113
|
+
"$ref": "index.schema.json#/$defs/https"
|
|
114
|
+
},
|
|
115
|
+
"tag": {
|
|
116
|
+
"$ref": "index.schema.json#/$defs/tag"
|
|
117
|
+
},
|
|
118
|
+
"commit": {
|
|
119
|
+
"type": "string",
|
|
120
|
+
"pattern": "^([0-9a-f]{40}|[0-9a-f]{64})$"
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
"required": [
|
|
124
|
+
"repo",
|
|
125
|
+
"tag",
|
|
126
|
+
"commit"
|
|
127
|
+
],
|
|
128
|
+
"additionalProperties": false
|
|
129
|
+
},
|
|
130
|
+
"tool": {
|
|
131
|
+
"type": "object",
|
|
132
|
+
"properties": {
|
|
133
|
+
"name": {
|
|
134
|
+
"type": "string",
|
|
135
|
+
"$ref": "index.schema.json#/$defs/line",
|
|
136
|
+
"maxLength": 64
|
|
137
|
+
},
|
|
138
|
+
"version": {
|
|
139
|
+
"type": "string",
|
|
140
|
+
"minLength": 1,
|
|
141
|
+
"maxLength": 64,
|
|
142
|
+
"pattern": "^[0-9A-Za-z.+-]+$"
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
"required": [
|
|
146
|
+
"name",
|
|
147
|
+
"version"
|
|
148
|
+
],
|
|
149
|
+
"additionalProperties": false
|
|
150
|
+
},
|
|
151
|
+
"checks": {
|
|
152
|
+
"description": "Informative: which local checks passed when the release was built. Clients never trust it.",
|
|
153
|
+
"type": "object",
|
|
154
|
+
"maxProperties": 16,
|
|
155
|
+
"propertyNames": {
|
|
156
|
+
"type": "string",
|
|
157
|
+
"maxLength": 32,
|
|
158
|
+
"pattern": "^[a-z][A-Za-z0-9]*$"
|
|
159
|
+
},
|
|
160
|
+
"additionalProperties": {
|
|
161
|
+
"enum": [
|
|
162
|
+
"pass",
|
|
163
|
+
"fail",
|
|
164
|
+
"skipped"
|
|
165
|
+
]
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
"required": [
|
|
170
|
+
"recordVersion",
|
|
171
|
+
"type",
|
|
172
|
+
"id",
|
|
173
|
+
"version",
|
|
174
|
+
"formatVersion",
|
|
175
|
+
"name",
|
|
176
|
+
"license",
|
|
177
|
+
"archive",
|
|
178
|
+
"source"
|
|
179
|
+
],
|
|
180
|
+
"additionalProperties": false
|
|
181
|
+
}
|