Skip to main content

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)
ArgumentTypeDescription
manifestobjectThe 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

KeyTypeDescription
validbooleantrue when errors is empty.
errorsstring[]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, pages are required at the top level.
  • version matches the semver pattern.
  • pages[].type is 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 unified widgetEntry shape: widgetKey, slot, gridX, gridY, gridWidth, gridHeight, optional props, tabGroup, dataSource, _note.
  • slot is either one of body | sidebar | header-actions | footer | modal or matches the pattern ^(tab|section):.+.
  • Per-slot grid constraints: sidebar requires gridWidth: 1; header-actions requires gridY: 0.
  • pages[].actions[] (when present) entries have a type field in handler | open-modal | open-page | navigate. Missing type defaults to handler (via Ajv useDefaults: true, preserving v1.3.0 backward compatibility).
  • type: "custom" pages MUST include a _note field 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, settings tabs[], runtime, dependencies).

Post-schema (runtime checks):

  • pages[].id MUST 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 → calls validateManifestV2()
  • absent or matches v1 URL → existing v1 path
  • present but matches neither → falls back to v1 + console.warn once 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