Plugin Loader
The plugin system allows extending dde with custom shell scripts that are registered as Symfony Console commands.
Plugin Locations
Section titled “Plugin Locations”Plugins are loaded from two directories:
- Global plugins:
~/.dde/plugins/*.sh - Project plugins:
.dde/plugins/*.sh
Project plugins override global plugins that have the same @command name.
Plugin Format
Section titled “Plugin Format”A plugin is a shell script with annotation comments:
#!/usr/bin/env bash# @command deploy# @description Deploy the project to staging
set -eecho "Deploying..."# Your deployment logic hereRequired annotations:
@command— the command name (registered asproject:exec:{name})
Optional annotations:
@description— description shown indde listanddde help
Scripts without a @command annotation are ignored.
Loading Process
Section titled “Loading Process”The PluginLoader class handles discovery and parsing:
public function loadPlugins(?string $projectDir = null): array- Scans
~/.dde/plugins/for*.shfiles (via Symfony Finder, sorted by name) - Parses each file for
@commandand@descriptionannotations using regex - Creates a
PluginDefinitionfor each valid plugin - If a project directory is given, scans
.dde/plugins/similarly - Merges project plugins into global plugins (project overrides global by command name)
PluginDefinition
Section titled “PluginDefinition”final readonly class PluginDefinition{ public function __construct( public string $command, public string $description, public string $scriptPath, ) {}}Command Registration
Section titled “Command Registration”PluginProxyCommand
Section titled “PluginProxyCommand”Each plugin is wrapped in a PluginProxyCommand, which is a standard Symfony Console command:
- Registered as
project:exec:{command}(e.g.project:exec:deploy) - Accepts optional arguments via an
argsvariadic argument - Executes the shell script via
symfony/process - Supports TTY mode for interactive scripts
- Returns the script’s exit code
PluginCommandLoader
Section titled “PluginCommandLoader”The PluginCommandLoader implements Symfony’s CommandLoaderInterface for lazy loading. It:
- Calls
PluginLoader::loadPlugins()to discover all plugins - Registers each as a
PluginProxyCommand - Returns commands on demand when Symfony needs them
Override Behavior
Section titled “Override Behavior”When a global and project plugin share the same @command name:
~/.dde/plugins/deploy.sh # @command deploy.dde/plugins/deploy.sh # @command deploy (wins)The project plugin takes precedence. This uses a simple array_merge():
private function mergePlugins(array $global, array $project): array{ return array_merge($global, $project);}Since both arrays are keyed by command name, project entries overwrite global entries.
Example
Section titled “Example”Create a global plugin:
# @command hello# @description Say hello from a plugin
echo "Hello from dde plugin!"echo "Arguments: $@"Use it:
dde project:exec:hello world# Output:# Hello from dde plugin!# Arguments: world