validateManifestV2
Validate a v2 Conduction app manifest against the v2 JSON Schema. The schema validation uses a pre-compiled standalone Ajv validator (generated at build time from src/schemas/app-manifest-v2.schema.json), plus runtime post-checks the schema cannot express (cross-field arithmetic, $id uniqueness, @resolve: sentinel path rejection).
The v1 validator (validateManifest) stays hand-rolled for backward compatibility. The two coexist behind the $schema dispatch in validateManifest() — most consumers just call validateManifest(manifest) and get the correct path automatically.
CSP compliance
Nextcloud enforces a strict Content Security Policy that does not allow unsafe-eval. Ajv's default mode JIT-compiles schema validators using new Function(), which triggers an EvalError and prevents the Vue app from mounting in any v2-adopting app.
To fix this, the v2 schema validator is pre-compiled at build time using Ajv's standalone code generation (ajv/dist/standalone). The generator script (scripts/build-validators.js) is invoked automatically via npm run prebuild and npm run pretest. The output (src/utils/validateManifestV2.compiled.js) is a plain JS module containing static code — no new Function() anywhere at runtime.
The compiled file is gitignored and regenerated on every install cycle. If you see an import error for validateManifestV2.compiled.js, run npm run build:validators once.
Signature
import { validateManifestV2 } from '@conduction/nextcloud-vue'
const { valid, errors } = validateManifestV2(manifest)
| Argument | Type | Description |
|---|---|---|
manifest | object | The v2 manifest object to validate. MUST declare a $schema field pointing to the v2 schema URL (the dispatcher in validateManifest() enforces this; calling validateManifestV2 directly skips the dispatch). |
Return value
| Key | Type | Description |
|---|---|---|
valid | boolean | true when errors is empty. |
errors | string[] | Bracket-path error messages matching the v1 validator's format, e.g. '/pages/0/widgets/2: gridX + gridWidth (8 + 6 = 14) exceeds 12 for slot "body"'. |
Rules enforced
Schema-level (via Ajv, see app-manifest-v2.schema.json):
$schema,version,menu,pagesare required at the top level.versionmatches the semver pattern.pages[].typeis one of the 11 closed-enum values:index | detail | dashboard | logs | settings | chat | files | form | wiki | map | custom.pages[].widgets[](when present) entries follow the unifiedwidgetEntryshape:widgetKey,slot,gridX,gridY,gridWidth,gridHeight, optionalprops,tabGroup,dataSource,_note.slotis either one ofbody | sidebar | header-actions | footer | modalor matches the pattern^(tab|section):.+.- Per-slot grid constraints:
sidebarrequiresgridWidth: 1;header-actionsrequiresgridY: 0. pages[].actions[](when present) entries have atypefield inhandler | open-modal | open-page | navigate. Missingtypedefaults tohandler(via AjvuseDefaults: true, preserving v1.3.0 backward compatibility).type: "custom"pages MUST include a_notefield documenting why decomposition was not feasible.- All v1.3.0 features carry forward unchanged in the schema (
dataSource,@resolve:sentinels, dynamic menus, named-view sidebar,cardComponent, settingstabs[],runtime,dependencies).
Post-schema (runtime checks):
pages[].idMUST be unique across the array.- For every
widgets[]entry:gridX + gridWidth ≤ 12(only enforced on 12-column slots —body,header-actions,footer,modal,tab:*,section:*). @resolve:<key>sentinels are REJECTED on registry-key and router-invariant paths:pages[].id,pages[].route,pages[].component,pages[].headerComponent,pages[].actionsComponent,pages[].slots.*,menu[].id,menu[].route,dependencies[],version. Mirrors v1's behaviour.
Dispatch contract
validateManifest() reads manifest.$schema:
- ends with
/app-manifest-v2.schema.json→ callsvalidateManifestV2() - absent or matches v1 URL → existing v1 path
- present but matches neither → falls back to v1 +
console.warnonce per module load
Consumers should call validateManifest() in most cases. validateManifestV2() is exported for explicit v2-only paths (e.g., the codemod CLI, the hydra gate-manifest-validates gate).
See also
- validateManifest — v1 hand-rolled validator
- useAppManifest — wraps
validateManifest()for reactive use in components - ADR-036 in hydra — v2 design rationale
- docs/architecture/manifest.md — manifest authoring overview (v1 + v2)