@conform-ed/contracts 0.0.8 → 0.0.9
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/.turbo/turbo-format.log +5 -6
- package/.turbo/turbo-lint.log +3 -4
- package/.turbo/turbo-test.log +223 -197
- package/.turbo/turbo-typecheck.log +1 -2
- package/h5p-v1-zod-templates.md +124 -0
- package/package.json +3 -2
- package/src/h5p/v1/content.ts +44 -0
- package/src/h5p/v1/h5p-json.ts +67 -0
- package/src/h5p/v1/index.ts +109 -0
- package/src/h5p/v1/library-json.ts +70 -0
- package/src/h5p/v1/semantics.ts +268 -0
- package/src/h5p/v1/shared.ts +56 -0
- package/src/index.ts +2 -0
- package/test/h5p-v1.test.ts +330 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# H5P v1 Zod Templates
|
|
2
|
+
|
|
3
|
+
## Specification Sources
|
|
4
|
+
|
|
5
|
+
| Source | URL |
|
|
6
|
+
| --------------------------- | --------------------------------------------------------------- |
|
|
7
|
+
| H5P Developer Specification | https://h5p.org/documentation/developers/h5p-specification |
|
|
8
|
+
| JSON File Descriptions | https://h5p.org/documentation/developers/json-file-descriptions |
|
|
9
|
+
| Semantics Reference | https://h5p.org/documentation/developers/semantics |
|
|
10
|
+
| PHP Reference Library | https://github.com/h5p/h5p-php-library |
|
|
11
|
+
| NodeJS Reference Library | https://github.com/Lumieducation/h5p-nodejs-library |
|
|
12
|
+
|
|
13
|
+
## Scope
|
|
14
|
+
|
|
15
|
+
This bundle covers the four JSON schemas that make up the H5P package format:
|
|
16
|
+
|
|
17
|
+
| File | Schema | Description |
|
|
18
|
+
| ---------------- | -------------------------- | ------------------------------------------------------- |
|
|
19
|
+
| `h5p.json` | `H5pPackageManifestSchema` | Top-level package manifest in every `.h5p` archive |
|
|
20
|
+
| `library.json` | `H5pLibraryManifestSchema` | Library directory manifest (runnable or helper library) |
|
|
21
|
+
| `semantics.json` | `H5pSemanticsSchema` | Editor form schema — array of typed field descriptors |
|
|
22
|
+
| `content.json` | `H5pContentParamsSchema` | Permissive base (`z.record`) for per-library content |
|
|
23
|
+
|
|
24
|
+
Not in scope: ZIP extraction, file whitelist checking, content validation against semantics.
|
|
25
|
+
|
|
26
|
+
## Dependency Decision: Why No External Library
|
|
27
|
+
|
|
28
|
+
| Library | Licence | Verdict |
|
|
29
|
+
| ---------------------------------- | -------------------------------- | ------------------------------------------------------------ |
|
|
30
|
+
| `h5p/h5p-php-library` | GPL-3.0 (HTML purification code) | Cannot use — GPL is viral |
|
|
31
|
+
| `Lumieducation/h5p-nodejs-library` | MIT (JS parts) | Wrong abstraction — it's a full HTTP server, not a validator |
|
|
32
|
+
|
|
33
|
+
We implement our own Zod schemas, referencing both libraries for validation logic and regex patterns.
|
|
34
|
+
|
|
35
|
+
## Design Decisions
|
|
36
|
+
|
|
37
|
+
### Machine name validation
|
|
38
|
+
|
|
39
|
+
Pattern from `H5PValidator::isValidRequiredH5pData` in h5p-php-library:
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
/^[\w0-9\-\.]{1,255}$/i
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Used on every `machineName` field and in the library folder name schema.
|
|
46
|
+
|
|
47
|
+
### Patch version excluded from version references
|
|
48
|
+
|
|
49
|
+
H5P dependency declarations (`preloadedDependencies`, `dynamicDependencies`, `editorDependencies`) use only `{ machineName, majorVersion, minorVersion }`. Any patch release of a matching major.minor is acceptable. `H5pVersionRefSchema` reflects this: it has no `patchVersion` field.
|
|
50
|
+
|
|
51
|
+
`H5pLibraryManifestSchema` does include `patchVersion` because the library itself must declare its full three-part version.
|
|
52
|
+
|
|
53
|
+
### `runnable` and `fullscreen` are integers, not booleans
|
|
54
|
+
|
|
55
|
+
The H5P spec defines these as `0` or `1`, not `true`/`false`. `z.union([z.literal(0), z.literal(1)])` is used in `H5pLibraryManifestSchema` to match this exactly.
|
|
56
|
+
|
|
57
|
+
### Semantics: recursive type handling
|
|
58
|
+
|
|
59
|
+
`semantics.json` defines a recursive schema: `group` contains `fields: SemanticsField[]` and `list` contains `field: SemanticsField`. This is handled with the `z.lazy()` / let-then-assign pattern (same approach as cmi5 `Block`/`Au` schemas):
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
let h5pSemanticsFieldSchemaInternal: z.ZodType<H5pSemanticsField>;
|
|
63
|
+
|
|
64
|
+
const groupFieldSchema = z.object({
|
|
65
|
+
...baseFieldShape,
|
|
66
|
+
type: z.literal("group"),
|
|
67
|
+
fields: z.lazy(() => z.array(h5pSemanticsFieldSchemaInternal)),
|
|
68
|
+
...
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
h5pSemanticsFieldSchemaInternal = z.union([textFieldSchema, ..., groupFieldSchema, listFieldSchema]);
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
TypeScript-side recursive interfaces are declared manually to avoid circular inference, then the Zod schema is cast to `z.ZodType<H5pSemanticsField>`.
|
|
75
|
+
|
|
76
|
+
### Semantics: non-strict objects
|
|
77
|
+
|
|
78
|
+
Leaf field schemas (`textFieldSchema`, `numberFieldSchema`, etc.) use `z.object(...)` (not `strictObject()`) because:
|
|
79
|
+
|
|
80
|
+
1. H5P has a large ecosystem of third-party widgets that add arbitrary extra properties to field definitions.
|
|
81
|
+
2. The spec acknowledges extension properties on field objects.
|
|
82
|
+
|
|
83
|
+
Only `H5pVersionRefSchema` and `H5pMediaFileSchema` use `strictObject()` since their shapes are fully specified.
|
|
84
|
+
|
|
85
|
+
### Content params: permissive base
|
|
86
|
+
|
|
87
|
+
`content.json` structure is defined per-library by its `semantics.json`. There is no universal content schema. `H5pContentParamsSchema` is `z.record(z.string(), z.unknown())` — structurally valid JSON objects.
|
|
88
|
+
|
|
89
|
+
**Future work**: A `validateContentAgainstSemantics(content, semantics)` function could be built on top of `H5pSemanticsSchema` to perform deep structural validation. This requires a semantics traversal engine (walk the semantics field tree, validate each content value against its corresponding field schema). Out of scope for this bundle.
|
|
90
|
+
|
|
91
|
+
### License codes
|
|
92
|
+
|
|
93
|
+
Standard H5P license codes from the editor UI:
|
|
94
|
+
`CC BY`, `CC BY-SA`, `CC BY-NC`, `CC BY-NC-SA`, `CC BY-ND`, `CC BY-NC-ND`, `CC0`, `GNU GPL`, `PD`, `ODC PDDL`, `CC PDM`, `U`
|
|
95
|
+
|
|
96
|
+
`"U"` means undisclosed/unknown — the H5P default when no license is specified.
|
|
97
|
+
|
|
98
|
+
## Validation Logic Derived from PHP Validator
|
|
99
|
+
|
|
100
|
+
The following regex patterns are taken directly from `H5PValidator` in h5p-php-library:
|
|
101
|
+
|
|
102
|
+
| Field | Pattern |
|
|
103
|
+
| --------------- | -------------------------------------------------- |
|
|
104
|
+
| `machineName` | `/^[\w0-9\-\.]{1,255}$/i` |
|
|
105
|
+
| `title` | 1–255 characters |
|
|
106
|
+
| `language` | `/^[-a-zA-Z]{1,10}$/` (ISO-639-1 + region subtags) |
|
|
107
|
+
| version numbers | 1–5 digit integers |
|
|
108
|
+
| embed types | only `"iframe"` or `"div"` allowed |
|
|
109
|
+
|
|
110
|
+
Additional cross-field validations (implemented via `.superRefine()`):
|
|
111
|
+
|
|
112
|
+
- `embedTypes` must not contain duplicates
|
|
113
|
+
- `yearTo` must be ≥ `yearFrom` when both are present
|
|
114
|
+
- Runnable iframe libraries without declared `w`/`h` emit a validation issue
|
|
115
|
+
|
|
116
|
+
## File Whitelist (Not Validated by This Bundle)
|
|
117
|
+
|
|
118
|
+
Per the H5P spec, only whitelisted file types are allowed in `.h5p` archives. This is a runtime/extraction concern rather than a JSON schema concern. For reference:
|
|
119
|
+
|
|
120
|
+
**Content files**: `.png`, `.jpg`, `.jpeg`, `.gif`, `.bmp`, `.tif`, `.tiff`, `.svg`, `.mp3`, `.wav`, `.m4a`, `.mp4`, `.ogg`, `.webm`, `.pdf`, `.doc`, `.docx`, `.xls`, `.xlsx`, `.ppt`, `.pptx`, `.json`
|
|
121
|
+
|
|
122
|
+
**Library files**: All of the above, plus `.js`, `.css`, `.xml`
|
|
123
|
+
|
|
124
|
+
**Prohibited**: `.html`, `.htm`, any executable format
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@conform-ed/contracts",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"module": "src/index.ts",
|
|
6
6
|
"exports": {
|
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
"./qti/v2_1": "./src/qti/v2_1/index.ts",
|
|
28
28
|
"./qti/v2_2": "./src/qti/v2_2/index.ts",
|
|
29
29
|
"./qti/v3_0_1": "./src/qti/v3_0_1/index.ts",
|
|
30
|
-
"./vc-data-model/v2_0": "./src/vc-data-model/v2_0/index.ts"
|
|
30
|
+
"./vc-data-model/v2_0": "./src/vc-data-model/v2_0/index.ts",
|
|
31
|
+
"./h5p/v1": "./src/h5p/v1/index.ts"
|
|
31
32
|
},
|
|
32
33
|
"scripts": {
|
|
33
34
|
"build": "tsgo --noEmit",
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { strictObject } from "./shared";
|
|
3
|
+
|
|
4
|
+
// Copyright information attached to media files in content.json.
|
|
5
|
+
export const H5pCopyrightSchema = z.object({
|
|
6
|
+
license: z.string().optional(),
|
|
7
|
+
author: z.string().optional(),
|
|
8
|
+
year: z.string().optional(),
|
|
9
|
+
source: z.string().optional(),
|
|
10
|
+
title: z.string().optional(),
|
|
11
|
+
version: z.string().optional(),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
// Media file reference as stored in content.json (image, audio, video, file fields).
|
|
15
|
+
export const H5pMediaFileSchema = strictObject({
|
|
16
|
+
path: z.string().min(1),
|
|
17
|
+
mime: z.string(),
|
|
18
|
+
copyright: H5pCopyrightSchema.optional(),
|
|
19
|
+
// Original filename displayed in the editor
|
|
20
|
+
name: z.string().optional(),
|
|
21
|
+
// Width/height metadata for images
|
|
22
|
+
width: z.number().int().positive().optional(),
|
|
23
|
+
height: z.number().int().positive().optional(),
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Embedded H5P library reference as stored in content.json (library-type semantics fields).
|
|
27
|
+
// The library string uses the format "H5P.MachineName majorVersion.minorVersion".
|
|
28
|
+
export const H5pLibraryEmbedSchema = z.object({
|
|
29
|
+
library: z.string().regex(/^[\w0-9-.]+ \d+\.\d+$/u),
|
|
30
|
+
params: z.unknown(),
|
|
31
|
+
subContentId: z.string().optional(),
|
|
32
|
+
metadata: z.unknown().optional(),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Base schema for any content.json document.
|
|
36
|
+
// Content structure is defined per-library by semantics.json and therefore varies widely.
|
|
37
|
+
// Full validation against semantics requires a semantics traversal engine — see notes in
|
|
38
|
+
// h5p-v1-zod-templates.md for guidance on building one on top of H5pSemanticsSchema.
|
|
39
|
+
export const H5pContentParamsSchema = z.record(z.string(), z.unknown());
|
|
40
|
+
|
|
41
|
+
export type H5pCopyright = z.infer<typeof H5pCopyrightSchema>;
|
|
42
|
+
export type H5pMediaFile = z.infer<typeof H5pMediaFileSchema>;
|
|
43
|
+
export type H5pLibraryEmbed = z.infer<typeof H5pLibraryEmbedSchema>;
|
|
44
|
+
export type H5pContentParams = z.infer<typeof H5pContentParamsSchema>;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import {
|
|
3
|
+
strictObject,
|
|
4
|
+
H5pMachineNameSchema,
|
|
5
|
+
H5pVersionRefSchema,
|
|
6
|
+
H5pLicenseSchema,
|
|
7
|
+
H5pAuthorSchema,
|
|
8
|
+
H5pChangelogEntrySchema,
|
|
9
|
+
} from "./shared";
|
|
10
|
+
|
|
11
|
+
// h5p.json — the top-level package manifest inside every .h5p archive.
|
|
12
|
+
// Schema derived from H5PValidator::isValidRequiredH5pData in h5p-php-library.
|
|
13
|
+
export const H5pPackageManifestSchema = strictObject({
|
|
14
|
+
// Required fields
|
|
15
|
+
title: z.string().min(1).max(255),
|
|
16
|
+
// ISO-639-1 language code with optional region subtag (e.g. "en", "nb", "zh-CN")
|
|
17
|
+
language: z.string().regex(/^[-a-zA-Z]{1,10}$/u),
|
|
18
|
+
machineName: H5pMachineNameSchema,
|
|
19
|
+
mainLibrary: H5pMachineNameSchema,
|
|
20
|
+
preloadedDependencies: z.array(H5pVersionRefSchema).min(1),
|
|
21
|
+
embedTypes: z.array(z.enum(["iframe", "div"])).min(1),
|
|
22
|
+
|
|
23
|
+
// Optional metadata
|
|
24
|
+
contentType: z.string().optional(),
|
|
25
|
+
description: z.string().optional(),
|
|
26
|
+
author: z.string().optional(),
|
|
27
|
+
authors: z.array(H5pAuthorSchema).optional(),
|
|
28
|
+
source: z.string().url().optional(),
|
|
29
|
+
license: H5pLicenseSchema.optional(),
|
|
30
|
+
licenseVersion: z.string().max(10).optional(),
|
|
31
|
+
licenseExtras: z.string().max(5000).optional(),
|
|
32
|
+
authorComments: z.string().max(5000).optional(),
|
|
33
|
+
dynamicDependencies: z.array(H5pVersionRefSchema).optional(),
|
|
34
|
+
// Fixed dimensions for non-responsive content — 1–4 digits per spec
|
|
35
|
+
width: z.number().int().min(1).max(9999).optional(),
|
|
36
|
+
height: z.number().int().min(1).max(9999).optional(),
|
|
37
|
+
metaKeywords: z.string().optional(),
|
|
38
|
+
metaDescription: z.string().optional(),
|
|
39
|
+
yearFrom: z.number().int().optional(),
|
|
40
|
+
yearTo: z.number().int().optional(),
|
|
41
|
+
defaultLanguage: z.string().max(32).optional(),
|
|
42
|
+
a11yTitle: z.string().max(255).optional(),
|
|
43
|
+
changes: z.array(H5pChangelogEntrySchema).optional(),
|
|
44
|
+
}).superRefine((data, ctx) => {
|
|
45
|
+
// embedTypes must not have duplicates
|
|
46
|
+
const seen = new Set<string>();
|
|
47
|
+
for (const et of data.embedTypes) {
|
|
48
|
+
if (seen.has(et)) {
|
|
49
|
+
ctx.addIssue({
|
|
50
|
+
code: z.ZodIssueCode.custom,
|
|
51
|
+
path: ["embedTypes"],
|
|
52
|
+
message: `Duplicate embed type: "${et}"`,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
seen.add(et);
|
|
56
|
+
}
|
|
57
|
+
// yearTo must be >= yearFrom when both are present
|
|
58
|
+
if (data.yearFrom !== undefined && data.yearTo !== undefined && data.yearTo < data.yearFrom) {
|
|
59
|
+
ctx.addIssue({
|
|
60
|
+
code: z.ZodIssueCode.custom,
|
|
61
|
+
path: ["yearTo"],
|
|
62
|
+
message: "yearTo must be >= yearFrom",
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
export type H5pPackageManifest = z.infer<typeof H5pPackageManifestSchema>;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
export {
|
|
2
|
+
H5pMachineNameSchema,
|
|
3
|
+
H5pLibraryFolderNameSchema,
|
|
4
|
+
H5pVersionRefSchema,
|
|
5
|
+
H5pLicenseSchema,
|
|
6
|
+
H5pAuthorSchema,
|
|
7
|
+
H5pChangelogEntrySchema,
|
|
8
|
+
} from "./shared";
|
|
9
|
+
export type {
|
|
10
|
+
H5pMachineName,
|
|
11
|
+
H5pLibraryFolderName,
|
|
12
|
+
H5pVersionRef,
|
|
13
|
+
H5pLicense,
|
|
14
|
+
H5pAuthor,
|
|
15
|
+
H5pChangelogEntry,
|
|
16
|
+
} from "./shared";
|
|
17
|
+
|
|
18
|
+
export { H5pPackageManifestSchema } from "./h5p-json";
|
|
19
|
+
export type { H5pPackageManifest } from "./h5p-json";
|
|
20
|
+
|
|
21
|
+
export { H5pLibraryManifestSchema } from "./library-json";
|
|
22
|
+
export type { H5pLibraryManifest } from "./library-json";
|
|
23
|
+
|
|
24
|
+
export { H5pSemanticsFieldSchema, H5pSemanticsSchema } from "./semantics";
|
|
25
|
+
export type {
|
|
26
|
+
H5pFieldBase,
|
|
27
|
+
H5pTextField,
|
|
28
|
+
H5pHtmlField,
|
|
29
|
+
H5pNumberField,
|
|
30
|
+
H5pBooleanField,
|
|
31
|
+
H5pImageField,
|
|
32
|
+
H5pAudioField,
|
|
33
|
+
H5pVideoField,
|
|
34
|
+
H5pFileField,
|
|
35
|
+
H5pSelectField,
|
|
36
|
+
H5pLibraryField,
|
|
37
|
+
H5pGroupField,
|
|
38
|
+
H5pListField,
|
|
39
|
+
H5pTableField,
|
|
40
|
+
H5pSemanticsField,
|
|
41
|
+
H5pSemantics,
|
|
42
|
+
} from "./semantics";
|
|
43
|
+
|
|
44
|
+
export { H5pCopyrightSchema, H5pMediaFileSchema, H5pLibraryEmbedSchema, H5pContentParamsSchema } from "./content";
|
|
45
|
+
export type { H5pCopyright, H5pMediaFile, H5pLibraryEmbed, H5pContentParams } from "./content";
|
|
46
|
+
|
|
47
|
+
import { H5pPackageManifestSchema } from "./h5p-json";
|
|
48
|
+
import { H5pLibraryManifestSchema } from "./library-json";
|
|
49
|
+
import { H5pSemanticsFieldSchema, H5pSemanticsSchema } from "./semantics";
|
|
50
|
+
import { H5pCopyrightSchema, H5pMediaFileSchema, H5pLibraryEmbedSchema, H5pContentParamsSchema } from "./content";
|
|
51
|
+
import {
|
|
52
|
+
H5pMachineNameSchema,
|
|
53
|
+
H5pLibraryFolderNameSchema,
|
|
54
|
+
H5pVersionRefSchema,
|
|
55
|
+
H5pLicenseSchema,
|
|
56
|
+
H5pAuthorSchema,
|
|
57
|
+
H5pChangelogEntrySchema,
|
|
58
|
+
} from "./shared";
|
|
59
|
+
|
|
60
|
+
export namespace H5pV1 {
|
|
61
|
+
export namespace Schemas {
|
|
62
|
+
export const PackageManifest = H5pPackageManifestSchema;
|
|
63
|
+
export const LibraryManifest = H5pLibraryManifestSchema;
|
|
64
|
+
export const SemanticsField = H5pSemanticsFieldSchema;
|
|
65
|
+
export const Semantics = H5pSemanticsSchema;
|
|
66
|
+
export const Copyright = H5pCopyrightSchema;
|
|
67
|
+
export const MediaFile = H5pMediaFileSchema;
|
|
68
|
+
export const LibraryEmbed = H5pLibraryEmbedSchema;
|
|
69
|
+
export const ContentParams = H5pContentParamsSchema;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export namespace Shared {
|
|
73
|
+
export const MachineName = H5pMachineNameSchema;
|
|
74
|
+
export const LibraryFolderName = H5pLibraryFolderNameSchema;
|
|
75
|
+
export const VersionRef = H5pVersionRefSchema;
|
|
76
|
+
export const License = H5pLicenseSchema;
|
|
77
|
+
export const Author = H5pAuthorSchema;
|
|
78
|
+
export const ChangelogEntry = H5pChangelogEntrySchema;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export type H5pV1Schemas = typeof H5pV1.Schemas;
|
|
83
|
+
|
|
84
|
+
export const H5pV1DerivedZodTemplates = {
|
|
85
|
+
description: "H5P package and library Zod schemas for h5p.json, library.json, semantics.json, and content.json",
|
|
86
|
+
specLinks: {
|
|
87
|
+
specification: "https://h5p.org/documentation/developers/h5p-specification",
|
|
88
|
+
jsonFiles: "https://h5p.org/documentation/developers/json-file-descriptions",
|
|
89
|
+
semantics: "https://h5p.org/documentation/developers/semantics",
|
|
90
|
+
phpLibrary: "https://github.com/h5p/h5p-php-library",
|
|
91
|
+
},
|
|
92
|
+
scope:
|
|
93
|
+
"h5p.json (PackageManifest), library.json (LibraryManifest), semantics.json (SemanticsField array), content.json base types",
|
|
94
|
+
coreSchemas: ["H5pPackageManifestSchema", "H5pLibraryManifestSchema", "H5pSemanticsSchema"],
|
|
95
|
+
notes: [
|
|
96
|
+
"content.json structure varies per library — H5pContentParamsSchema is a permissive base (z.record). Full content validation requires semantics traversal.",
|
|
97
|
+
"Patch version intentionally excluded from H5pVersionRefSchema — any patch of the specified major.minor is acceptable per spec.",
|
|
98
|
+
"runnable and fullscreen are 0|1 integers, not booleans, matching the H5P spec.",
|
|
99
|
+
"PHP reference library (h5p-php-library) is GPL-3.0; NodeJS reference (Lumieducation/h5p-nodejs-library) is a full server framework. Neither is taken as a dependency.",
|
|
100
|
+
"Validation regex patterns for machineName and library folder names are derived from H5PValidator in h5p-php-library.",
|
|
101
|
+
],
|
|
102
|
+
} as const;
|
|
103
|
+
|
|
104
|
+
export const DerivedZodTemplates = H5pV1DerivedZodTemplates;
|
|
105
|
+
|
|
106
|
+
// Convenience aliases so that `H5pV1.Schemas.*` and `H5pV1.Shared.*` work
|
|
107
|
+
// when consumers import via `export * as H5pV1 from "./h5p/v1"`.
|
|
108
|
+
export const Schemas = H5pV1.Schemas;
|
|
109
|
+
export const Shared = H5pV1.Shared;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { strictObject, H5pMachineNameSchema, H5pVersionRefSchema } from "./shared";
|
|
3
|
+
|
|
4
|
+
const filePathSchema = strictObject({ path: z.string().min(1) });
|
|
5
|
+
|
|
6
|
+
const runnableFlag = z.union([z.literal(0), z.literal(1)]);
|
|
7
|
+
|
|
8
|
+
// library.json — required in every H5P library directory.
|
|
9
|
+
// Controls loading, dependencies, embedding, and editor capabilities.
|
|
10
|
+
export const H5pLibraryManifestSchema = strictObject({
|
|
11
|
+
// Required fields
|
|
12
|
+
title: z.string().min(1),
|
|
13
|
+
machineName: H5pMachineNameSchema,
|
|
14
|
+
majorVersion: z.number().int().nonnegative().max(99999),
|
|
15
|
+
minorVersion: z.number().int().nonnegative().max(99999),
|
|
16
|
+
patchVersion: z.number().int().nonnegative().max(99999),
|
|
17
|
+
// 1 = standalone runnable content type, 0 = helper library only
|
|
18
|
+
runnable: runnableFlag,
|
|
19
|
+
|
|
20
|
+
// Optional fields
|
|
21
|
+
description: z.string().optional(),
|
|
22
|
+
author: z.string().optional(),
|
|
23
|
+
// library.json accepts any SPDX license string (e.g. "MIT", "Apache-2.0").
|
|
24
|
+
// Unlike h5p.json which restricts to H5P's enumerated content licenses.
|
|
25
|
+
license: z.string().max(32).optional(),
|
|
26
|
+
|
|
27
|
+
// JavaScript and CSS assets to preload
|
|
28
|
+
preloadedJs: z.array(filePathSchema).optional(),
|
|
29
|
+
preloadedCss: z.array(filePathSchema).optional(),
|
|
30
|
+
|
|
31
|
+
// Runtime dependencies (patch version excluded per spec)
|
|
32
|
+
preloadedDependencies: z.array(H5pVersionRefSchema).optional(),
|
|
33
|
+
// Loaded on-demand during execution
|
|
34
|
+
dynamicDependencies: z.array(H5pVersionRefSchema).optional(),
|
|
35
|
+
// Required only when the H5P editor is in use
|
|
36
|
+
editorDependencies: z.array(H5pVersionRefSchema).optional(),
|
|
37
|
+
|
|
38
|
+
// Embedding dimensions — required for iframe embed type
|
|
39
|
+
w: z.number().int().positive().optional(),
|
|
40
|
+
h: z.number().int().positive().optional(),
|
|
41
|
+
embedTypes: z.array(z.enum(["iframe", "div"])).optional(),
|
|
42
|
+
// 1 = supports fullscreen mode
|
|
43
|
+
fullscreen: runnableFlag.optional(),
|
|
44
|
+
|
|
45
|
+
contentType: z.string().optional(),
|
|
46
|
+
|
|
47
|
+
// Minimum H5P core API version this library requires
|
|
48
|
+
coreApi: strictObject({
|
|
49
|
+
majorVersion: z.number().int().nonnegative(),
|
|
50
|
+
minorVersion: z.number().int().nonnegative(),
|
|
51
|
+
}).optional(),
|
|
52
|
+
|
|
53
|
+
metadataSettings: strictObject({
|
|
54
|
+
disable: runnableFlag.optional(),
|
|
55
|
+
disableExtraTitleField: runnableFlag.optional(),
|
|
56
|
+
}).optional(),
|
|
57
|
+
}).superRefine((data, ctx) => {
|
|
58
|
+
// iframe embedding requires explicit dimensions
|
|
59
|
+
if (data.runnable === 1 && data.embedTypes?.includes("iframe")) {
|
|
60
|
+
if (data.w === undefined || data.h === undefined) {
|
|
61
|
+
ctx.addIssue({
|
|
62
|
+
code: z.ZodIssueCode.custom,
|
|
63
|
+
path: ["w"],
|
|
64
|
+
message: 'Runnable libraries with embedType "iframe" should declare w and h dimensions',
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
export type H5pLibraryManifest = z.infer<typeof H5pLibraryManifestSchema>;
|