From 688090aa82a8f4da4cb329e602e57bb0f92be0dc Mon Sep 17 00:00:00 2001 From: Yassine Doghri Date: Sun, 5 May 2024 17:10:20 +0000 Subject: [PATCH] feat(plugins): add json schema definition for plugin manifest --- .devcontainer/devcontainer.json | 8 +- app/Resources/icons/funding/donorbox.svg | 2 +- app/Resources/icons/social/telegram.svg | 2 +- modules/Plugins/BasePlugin.php | 24 +-- modules/Plugins/manifest.schema.json | 191 +++++++++++++++++++++++ themes/cp_admin/plugins/_plugin.php | 2 +- themes/cp_admin/plugins/_settings.php | 8 +- 7 files changed, 218 insertions(+), 19 deletions(-) create mode 100644 modules/Plugins/manifest.schema.json diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 70877355..c3dfb905 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -30,7 +30,13 @@ "spark": "php", "env": "dotenv", ".rsync-filter": "diff" - } + }, + "json.schemas": [ + { + "fileMatch": ["plugins/**/manifest.json"], + "url": "/workspaces/castopod/modules/Plugins/manifest.schema.json" + } + ] }, "extensions": [ "bmewburn.vscode-intelephense-client", diff --git a/app/Resources/icons/funding/donorbox.svg b/app/Resources/icons/funding/donorbox.svg index 502860a2..a9102a6f 100644 --- a/app/Resources/icons/funding/donorbox.svg +++ b/app/Resources/icons/funding/donorbox.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/app/Resources/icons/social/telegram.svg b/app/Resources/icons/social/telegram.svg index 127b316f..8c1bb674 100644 --- a/app/Resources/icons/social/telegram.svg +++ b/app/Resources/icons/social/telegram.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/modules/Plugins/BasePlugin.php b/modules/Plugins/BasePlugin.php index 3e98dc98..cf3fd0fa 100644 --- a/modules/Plugins/BasePlugin.php +++ b/modules/Plugins/BasePlugin.php @@ -53,7 +53,7 @@ abstract class BasePlugin implements PluginInterface */ public function __set(string $name, array|string $value): void { - $this->{$name} = $name === 'releaseDate' ? Time::createFromFormat('Y-m-d', $value) : $value; + $this->{$name} = $value; } public function init(): void @@ -161,14 +161,17 @@ abstract class BasePlugin implements PluginInterface if (array_key_exists('settings', $manifest)) { $fieldRules = [ - 'key' => 'required|alpha_numeric', - 'name' => 'required|max_length[32]', - 'description' => 'permit_empty|max_length[128]', + 'key' => 'required|alpha_numeric', + 'label' => 'required|max_length[32]', + 'hint' => 'permit_empty|max_length[128]', + 'helper' => 'permit_empty|max_length[128]', ]; $defaultField = [ - 'key' => '', - 'name' => '', - 'description' => '', + 'key' => '', + 'label' => '', + 'hint' => '', + 'helper' => '', + 'optional' => false, ]; $validation->setRules($fieldRules); foreach ($manifest['settings'] as $key => $settings) { @@ -185,14 +188,12 @@ abstract class BasePlugin implements PluginInterface $rules = [ 'name' => 'required|max_length[32]', 'version' => 'required|regex_match[/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/]', - 'compatible' => 'required|in_list[1.0]', 'description' => 'max_length[128]', - 'releaseDate' => 'valid_date[Y-m-d]', 'license' => 'in_list[MIT]', 'author.name' => 'permit_empty|max_length[32]', 'author.email' => 'permit_empty|valid_email', 'author.url' => 'permit_empty|valid_url_strict', - 'website' => 'valid_url_strict', + 'homepage' => 'valid_url_strict', 'keywords.*' => 'permit_empty|in_list[seo,podcasting20,analytics]', 'hooks.*' => 'permit_empty|in_list[' . implode(',', Plugins::HOOKS) . ']', 'settings' => 'permit_empty', @@ -206,10 +207,9 @@ abstract class BasePlugin implements PluginInterface $defaultAttributes = [ 'description' => '', - 'releaseDate' => '', 'license' => '', 'author' => [], - 'website' => '', + 'homepage' => '', 'hooks' => [], 'keywords' => [], 'settings' => [ diff --git a/modules/Plugins/manifest.schema.json b/modules/Plugins/manifest.schema.json new file mode 100644 index 00000000..58528a1f --- /dev/null +++ b/modules/Plugins/manifest.schema.json @@ -0,0 +1,191 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "/schemas/manifest.json", + "title": "JSON schema for Castopod Plugins's manifest.json files", + "description": "The Castopod plugin manifest defines both metadata and behavior of a plugin", + "type": "object", + "properties": { + "name": { + "description": "The plugin name, including 'vendor-name/' prefix", + "type": "string", + "pattern": "^[a-z0-9]([_.-]?[a-z0-9]+)*/[a-z0-9]([_.-]?[a-z0-9]+)*$", + "examples": ["acme/hello-world"] + }, + "version": { + "description": "The plugin's semantic version. See https://semver.org/", + "type": "string", + "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$", + "examples": ["1.0.0"] + }, + "description": { + "description": "This helps people discover your plugin as it's listed in repositories", + "type": "string" + }, + "author": { + "$ref": "#/$defs/person" + }, + "authors": { + "type": "array", + "items": { + "$ref": "#/$defs/person" + } + }, + "homepage": { + "description": "The URL to the plugin homepage", + "type": "string", + "format": "uri" + }, + "license": { + "description": "You should specify a license for your plugin so that people know how they are permitted to use it, and any restrictions you're placing on it.", + "default": "UNLICENSED", + "anyOf": [ + { + "type": "string" + }, + { + "enum": [ + "AGPL-3.0-only", + "AGPL-3.0-or-later", + "Apache-2.0", + "BSL-1.0", + "GPL-3.0-only", + "GPL-3.0-or-later", + "LGPL-3.0-only", + "LGPL-3.0-or-later", + "MIT", + "MPL-2.0", + "Unlicense", + "UNLICENSED" + ] + } + ] + }, + "private": { + "type": "boolean", + "description": "If set to true, then repositories should refuse to publish it." + }, + "keywords": { + "description": "This helps people discover your plugin as it's listed in repositories", + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "enum": [ + "accessibility", + "analytics", + "monetization", + "podcasting2", + "privacy", + "seo" + ] + } + ] + }, + "uniqueItems": true + }, + "hooks": { + "description": "The hooks used by the plugin.", + "type": "array", + "items": { + "enum": ["channelTag", "itemTag", "siteHead"] + }, + "uniqueItems": true + }, + "settings": { + "type": "object", + "properties": { + "general": { + "type": "array", + "items": { + "$ref": "#/$defs/settings-field" + } + }, + "podcast": { + "type": "array", + "items": { + "$ref": "#/$defs/settings-field" + } + }, + "episode": { + "type": "array", + "items": { + "$ref": "#/$defs/settings-field" + } + } + } + }, + "files": { + "description": "List of files to include in your plugin package. If you include a folder in the array, all files inside it will also be included.", + "type": "array", + "items": { + "type": "string" + } + }, + "repository": { + "description": "Specify the place where your plugin code lives. This is helpful for people who want to contribute.", + "type": ["object", "string"], + "properties": { + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "directory": { + "type": "string" + } + } + } + }, + "required": ["name", "version"], + "additionalProperties": false, + "$defs": { + "person": { + "description": "A person who has been involved in creating or maintaining this plugin.", + "type": ["object", "string"], + "required": ["name"], + "properties": { + "name": { + "type": "string" + }, + "email": { + "type": "string", + "format": "email" + }, + "url": { + "type": "string", + "format": "uri" + } + } + }, + "settings-field": { + "type": "object", + "properties": { + "type": { + "enum": ["text", "email", "url", "markdown", "number", "switch"], + "default": "text" + }, + "key": { + "type": "string" + }, + "label": { + "type": "string" + }, + "hint": { + "type": "string" + }, + "helper": { + "type": "string" + }, + "optional": { + "type": "boolean" + } + }, + "required": ["key", "label"], + "additionalProperties": false + } + } +} diff --git a/themes/cp_admin/plugins/_plugin.php b/themes/cp_admin/plugins/_plugin.php index 9cb7e088..02441de5 100644 --- a/themes/cp_admin/plugins/_plugin.php +++ b/themes/cp_admin/plugins/_plugin.php @@ -12,7 +12,7 @@

getDescription() ?>