mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-14 11:06:39 +00:00
LEDMatrixWidgets.loadPluginWidget has always fetched /static/plugin-widgets/<plugin>/<widget>.js, and docs/widget-guide.md has always documented that path, but nothing served it. soccer-scoreboard has shipped a 17KB widgets/custom-leagues.js since August that could never load. Both halves were missing, not just the route: - serve_plugin_widget serves the script from the plugin's widgets/ directory as text/javascript. The manifest is the allowlist -- only a widget the plugin declares is reachable -- so installing a plugin does not publish everything it ships. Path handling mirrors the sibling serve_plugin_web_ui: allowlist regexes, os.path.basename, resolve() + relative_to() containment, and the ledmatrix- prefix fallback. The declared script name is guarded too, since it comes from the plugin rather than the request. - The config form never requested one. Its x-widget dispatch is a hardcoded list of core widget names, so a plugin's own widget fell through to a plain text input. An unrecognised x-widget on a string field now asks ensureWidget() for it. The text input stays as the fallback and is removed only once the widget has actually rendered, so a missing or broken widget costs the user an editor rather than their configured value on the next save. - manifest_schema.json gains "widgets", so the declaration is validated rather than merely tolerated by additionalProperties. Verified in a browser against the real partial: a declared widget loads, registers and renders, and its field posts exactly one value; a field whose widget 404s keeps its text input and still posts its value. Not addressed: loadPluginWidgetsFromManifest still has no caller. The per-field ensureWidget path is lazier and is what the form now uses, so that bulk helper is dead weight -- worth removing, but left alone here rather than inventing a call site for it. Known limitation, documented: only string-typed fields take this path. object/array/boolean/number fields and enums are dispatched by the template's own branches, which still only know core widgets. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
292 lines
8.4 KiB
JSON
292 lines
8.4 KiB
JSON
{
|
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
"title": "Plugin Manifest Schema",
|
|
"description": "Schema for validating plugin manifest.json files",
|
|
"type": "object",
|
|
"properties": {
|
|
"id": {
|
|
"type": "string",
|
|
"description": "Unique plugin identifier"
|
|
},
|
|
"name": {
|
|
"type": "string",
|
|
"description": "Human-readable plugin name"
|
|
},
|
|
"version": {
|
|
"type": "string",
|
|
"pattern": "^\\d+\\.\\d+\\.\\d+(-[\\w\\.-]+)?(\\+[\\w\\.-]+)?$",
|
|
"description": "Plugin version (semver format)"
|
|
},
|
|
"author": {
|
|
"type": "string",
|
|
"description": "Plugin author name"
|
|
},
|
|
"description": {
|
|
"type": "string",
|
|
"description": "Brief description of what the plugin does"
|
|
},
|
|
"homepage": {
|
|
"type": "string",
|
|
"format": "uri",
|
|
"description": "Plugin homepage URL"
|
|
},
|
|
"entry_point": {
|
|
"type": "string",
|
|
"default": "manager.py",
|
|
"description": "Python file containing the plugin class"
|
|
},
|
|
"class_name": {
|
|
"type": "string",
|
|
"description": "Name of the plugin class"
|
|
},
|
|
"category": {
|
|
"type": "string",
|
|
"description": "Plugin category (e.g., 'sports', 'weather', 'custom')"
|
|
},
|
|
"tags": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "string"
|
|
},
|
|
"description": "Array of tags for categorization"
|
|
},
|
|
"icon": {
|
|
"type": "string",
|
|
"description": "Icon identifier (e.g., Font Awesome class)"
|
|
},
|
|
"compatible_versions": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "string",
|
|
"pattern": "^(>=|<=|>|<|~|\\^)?\\d+\\.\\d+\\.\\d+(-[\\w\\.-]+)?(\\+[\\w\\.-]+)?$|^\\d+\\.\\d+\\.\\d+(-[\\w\\.-]+)?(\\+[\\w\\.-]+)?\\s+-\\s+\\d+\\.\\d+\\.\\d+(-[\\w\\.-]+)?(\\+[\\w\\.-]+)?$"
|
|
},
|
|
"minItems": 1,
|
|
"description": "Array of semver ranges specifying compatible LEDMatrix versions (required)"
|
|
},
|
|
"min_ledmatrix_version": {
|
|
"type": "string",
|
|
"pattern": "^\\d+\\.\\d+\\.\\d+(-[\\w\\.-]+)?(\\+[\\w\\.-]+)?$",
|
|
"description": "Optional: Minimum LEDMatrix version required (string)"
|
|
},
|
|
"max_ledmatrix_version": {
|
|
"type": "string",
|
|
"pattern": "^\\d+\\.\\d+\\.\\d+(-[\\w\\.-]+)?(\\+[\\w\\.-]+)?$",
|
|
"description": "Optional: Maximum LEDMatrix version supported (string)"
|
|
},
|
|
"requires": {
|
|
"type": "object",
|
|
"properties": {
|
|
"python": {
|
|
"type": "string",
|
|
"description": "Python version requirement (e.g., '>=3.9')"
|
|
},
|
|
"display_size": {
|
|
"type": "object",
|
|
"properties": {
|
|
"min_width": {
|
|
"type": "integer",
|
|
"minimum": 1
|
|
},
|
|
"min_height": {
|
|
"type": "integer",
|
|
"minimum": 1
|
|
},
|
|
"max_width": {
|
|
"type": "integer",
|
|
"minimum": 1
|
|
},
|
|
"max_height": {
|
|
"type": "integer",
|
|
"minimum": 1
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"display": {
|
|
"type": "object",
|
|
"properties": {
|
|
"design_size": {
|
|
"type": "object",
|
|
"properties": {
|
|
"width": {
|
|
"type": "integer",
|
|
"minimum": 8
|
|
},
|
|
"height": {
|
|
"type": "integer",
|
|
"minimum": 8
|
|
}
|
|
},
|
|
"required": ["width", "height"],
|
|
"description": "Panel size the plugin's layout was authored against; core derives the adaptive-layout scale factor from it. Defaults to 128x32 when omitted."
|
|
}
|
|
},
|
|
"description": "Display/layout hints for the adaptive layout system"
|
|
},
|
|
"config_schema": {
|
|
"type": "string",
|
|
"description": "Path to configuration schema file"
|
|
},
|
|
"assets": {
|
|
"type": "object",
|
|
"description": "Asset requirements description"
|
|
},
|
|
"update_interval": {
|
|
"type": "integer",
|
|
"minimum": 1,
|
|
"description": "Update interval in seconds"
|
|
},
|
|
"default_duration": {
|
|
"type": "number",
|
|
"minimum": 1,
|
|
"description": "Default display duration in seconds"
|
|
},
|
|
"display_modes": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "string"
|
|
},
|
|
"description": "Array of display mode names this plugin provides"
|
|
},
|
|
"api_requirements": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "object",
|
|
"properties": {
|
|
"name": {
|
|
"type": "string"
|
|
},
|
|
"required": {
|
|
"type": "boolean"
|
|
},
|
|
"description": {
|
|
"type": "string"
|
|
},
|
|
"url": {
|
|
"type": "string",
|
|
"format": "uri"
|
|
},
|
|
"rate_limit": {
|
|
"type": "string"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"download_url_template": {
|
|
"type": "string",
|
|
"format": "uri",
|
|
"description": "URL template for downloading plugin releases (use {version} placeholder)"
|
|
},
|
|
"versions": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "object",
|
|
"properties": {
|
|
"released": {
|
|
"type": "string",
|
|
"pattern": "^\\d{4}-\\d{2}-\\d{2}$",
|
|
"description": "Release date (YYYY-MM-DD format)"
|
|
},
|
|
"version": {
|
|
"type": "string",
|
|
"pattern": "^\\d+\\.\\d+\\.\\d+(-[\\w\\.-]+)?(\\+[\\w\\.-]+)?$",
|
|
"description": "Plugin version for this entry"
|
|
},
|
|
"ledmatrix_min_version": {
|
|
"type": "string",
|
|
"pattern": "^\\d+\\.\\d+\\.\\d+(-[\\w\\.-]+)?(\\+[\\w\\.-]+)?$",
|
|
"description": "Optional: Minimum LEDMatrix version required for this plugin version (string)"
|
|
}
|
|
},
|
|
"required": ["released", "version"]
|
|
},
|
|
"description": "Array of version history entries"
|
|
},
|
|
"last_updated": {
|
|
"type": "string",
|
|
"pattern": "^\\d{4}-\\d{2}-\\d{2}$",
|
|
"description": "Last update date (YYYY-MM-DD format)"
|
|
},
|
|
"stars": {
|
|
"type": "integer",
|
|
"minimum": 0,
|
|
"description": "Number of stars (for registry)"
|
|
},
|
|
"downloads": {
|
|
"type": "integer",
|
|
"minimum": 0,
|
|
"description": "Number of downloads (for registry)"
|
|
},
|
|
"verified": {
|
|
"type": "boolean",
|
|
"description": "Whether plugin is verified (for registry)"
|
|
},
|
|
"screenshot": {
|
|
"type": "string",
|
|
"description": "Screenshot URL (for registry)"
|
|
},
|
|
"api_version": {
|
|
"type": "string",
|
|
"description": "API version (legacy field)"
|
|
},
|
|
"dependencies": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "string"
|
|
},
|
|
"description": "Python dependencies (legacy - use requirements.txt instead)"
|
|
},
|
|
"requirements_file": {
|
|
"type": "string",
|
|
"description": "Path to requirements.txt file"
|
|
},
|
|
"website": {
|
|
"type": "string",
|
|
"format": "uri",
|
|
"description": "Plugin website URL (alias for homepage)"
|
|
},
|
|
"license": {
|
|
"type": "string",
|
|
"description": "License type (e.g., 'MIT', 'Apache-2.0')"
|
|
},
|
|
"web_ui_actions": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "object"
|
|
},
|
|
"description": "Web UI action definitions"
|
|
},
|
|
"widgets": {
|
|
"type": "array",
|
|
"description": "Custom web-UI widgets this plugin provides. Each entry is served at /static/plugin-widgets/<plugin-id>/<name>.js from the plugin's widgets/ directory; only declared widgets are served. Reference one from config_schema.json with \"x-widget\": \"<name>\".",
|
|
"items": {
|
|
"type": "object",
|
|
"required": ["name"],
|
|
"properties": {
|
|
"name": {
|
|
"type": "string",
|
|
"pattern": "^[a-zA-Z0-9_-]{1,64}$",
|
|
"description": "Widget name, as used in x-widget and in the URL."
|
|
},
|
|
"script": {
|
|
"type": "string",
|
|
"pattern": "^[a-zA-Z0-9_-]{1,64}\\.js$",
|
|
"description": "Filename inside widgets/. Defaults to <name>.js."
|
|
},
|
|
"description": {
|
|
"type": "string",
|
|
"description": "Human-readable summary shown to plugin authors."
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"ledmatrix_version": {
|
|
"type": "string",
|
|
"description": "Deprecated: Use compatible_versions instead. LEDMatrix version this plugin targets"
|
|
}
|
|
},
|
|
"required": ["id", "name", "version", "author", "entry_point", "class_name", "compatible_versions"]
|
|
}
|
|
|