### Install host runner with deploytasks:host:install Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/README.md Console command to install the host runner script. Installs the runner (executable), creates the host-task directory (default deploy/host-tasks/ with .gitkeep), and adds a .gitignore block. Idempotent; use --force to refresh after a bundle update. ```bash bin/console deploytasks:host:install ``` -------------------------------- ### deploytasks:host:install command usage Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/docs/commands.md Run the install command to set up host-scope tasks. Use --force to overwrite existing files and rewrite the .gitignore block. Without --force, existing files and the .gitignore block are left untouched. ```bash bin/console deploytasks:host:install bin/console deploytasks:host:install --force ``` -------------------------------- ### Clone and install dependencies Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/CONTRIBUTING.md Clones the repository and installs Composer dependencies. ```bash git clone https://github.com/Soviann/deploy-tasks-bundle.git cd deploy-tasks-bundle composer install ``` -------------------------------- ### Install bundle with Composer Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/README.md Run this command to install the soviann/deploy-tasks-bundle package. Requires PHP >= 8.2 (>= 8.4 for Symfony 8) and Symfony 6.4 LTS, 7.x or 8.x. ```bash composer require soviann/deploy-tasks-bundle ``` -------------------------------- ### Publish configuration file Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/docs/installation.md Example configuration for the bundle. Customizes storage backend, slow task threshold, events, locking, generation, and host settings. See storage.md for full reference. ```yaml # config/packages/soviann_deploy_tasks.yaml soviann_deploy_tasks: slow_task_threshold: 300 # seconds (>= 0); warn when a task runs longer — nothing is killed. 0 disables the check. storage: type: filesystem # or: database, custom filesystem: path: '%kernel.project_dir%/var/deploy-tasks' events: enabled: true lock: enabled: true ttl: 3600 # lock lifetime in seconds; the runner refreshes it between tasks generate: directory: src/DeployTasks/Task/ template: ~ # path to a custom PHP template root_namespace: App # root namespace for src/-rooted --dir (mirrors symfony/maker-bundle) host: directory: '%kernel.project_dir%/deploy/host-tasks' # host-scope *.sh task directory — must match DEPLOY_TASKS_HOST_DIR log_path: '%kernel.project_dir%/.deploy-tasks-host.log' # host runner completion log — must match DEPLOY_TASKS_HOST_STORAGE lock_path: '%kernel.project_dir%/.deploy-tasks-host.lock' # host runner flock file — must match DEPLOY_TASKS_HOST_LOCK ``` -------------------------------- ### Install symfony/process with Composer Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/docs/creating-tasks.md Install the soft dependency required for ProcessRunnerTrait. ```bash composer require symfony/process ``` -------------------------------- ### deploytasks:status command examples Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/docs/commands.md Shows example invocations of the deploytasks:status command. The --no-state option omits execution state, --group filters by group slot and is repeatable, and --filter-status filters by statuses. Without flags, all slots are shown. ```bash bin/console deploytasks:status bin/console deploytasks:status --no-state bin/console deploytasks:status --group=predeploy bin/console deploytasks:status --group=predeploy --group=postdeploy ``` -------------------------------- ### Install doctrine/dbal via Composer Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/docs/storage.md Required dependency for database storage. Run this command to install the Doctrine DBAL package. ```bash composer require doctrine/dbal ``` -------------------------------- ### deploytasks:run command usage examples Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/docs/commands.md Shows various invocations of the deploytasks:run command with different options to control task execution, including dry-run, rerun-all, targeting specific tasks by ID, filtering by group, and requiring at least one matching task. ```bash bin/console deploytasks:run bin/console deploytasks:run --dry-run bin/console deploytasks:run --rerun-all bin/console deploytasks:run --id=task_20260412143000_seed_categories bin/console deploytasks:run --rerun-all --id=task_20260412143000_seed_categories bin/console deploytasks:run --group=predeploy bin/console deploytasks:run --group=predeploy --group=postdeploy bin/console deploytasks:run --id=task_20260412143000_seed_categories --group=predeploy bin/console deploytasks:run --require-some --group=predeploy ``` -------------------------------- ### Example task using ProcessRunnerTrait Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/docs/creating-tasks.md Full example of a deploy task that uses ProcessRunnerTrait to run npm build and postbuild commands. The AsDeployTask attribute sets a timeout of 120 seconds, which is applied as the Process hard timeout when greater than zero. ```php use Soviann\DeployTasksBundle\Attribute\AsDeployTask; use Soviann\DeployTasksBundle\DeployTaskInterface; use Soviann\DeployTasksBundle\Helper\ProcessRunnerTrait; use Soviann\DeployTasksBundle\TaskResult; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Process\Process; #[AsDeployTask(id: 'build_assets', timeout: 120)] final class BuildAssetsTask implements DeployTaskInterface { use ProcessRunnerTrait; public function getDescription(): string { return 'Build frontend assets'; } public function run(OutputInterface $output): TaskResult { // Option A: Execute a command directly (string or array of arguments): $result = $this->runCommand(['npm', 'run', 'build'], $output, cwd: __DIR__.'/../../assets'); if (TaskResult::SUCCESS !== $result) { return $result; } // Option B: Pass a custom Process instance when specialized configuration is needed: $process = new Process(['npm', 'run', 'postbuild'], cwd: __DIR__.'/../../assets'); return $this->runProcess($process, $output); } } ``` -------------------------------- ### Full Configuration Example for soviann_deploy_tasks Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/CLAUDE.md Complete YAML configuration under the `soviann_deploy_tasks:` root key. Includes storage type options, event and lock toggles, generation settings, and host task paths. Note that `type: database` requires `doctrine/dbal` at compile time, and `type: custom` requires `storage.custom.service`. ```yaml soviann_deploy_tasks: sorter: ~ # service ID or null (default: DefaultTaskSorter) logger: ~ # PSR-3 service ID; null = autodetect app logger, NullLogger fallback slow_task_threshold: 300 # seconds (>= 0); warn past it, kill nothing; 0 disables the check storage: type: filesystem # filesystem | database | custom filesystem: path: '%kernel.project_dir%/var/deploy-tasks' database: connection: default # DBAL connection name table: deploy_task_executions auto_create_table: true # auto-init schema on first use id_column: id id_column_length: 255 # >= 1 status_column: status executed_at_column: executed_at error_column: error group_column: task_group # column for the group slot key group_column_length: 128 # >= 1 transaction_mode: all_or_nothing # none | per_task | all_or_nothing (default: all_or_nothing) custom: service: ~ # service ID, required when type=custom transaction_mode: none # none | per_task | all_or_nothing (default: none); per_task/all_or_nothing require TransactionalStorageInterface events: enabled: true lock: enabled: true generate: directory: src/DeployTasks/Task/ # default output directory for `deploytasks:generate` template: ~ # path to a custom PHP template root_namespace: App # root namespace for `src/`-rooted `--dir` (mirrors symfony/maker-bundle) host: directory: '%kernel.project_dir%/deploy/host-tasks' # host-scope `*.sh` task directory — must match `DEPLOY_TASKS_HOST_DIR` log_path: '%kernel.project_dir%/.deploy-tasks-host.log' # host runner completion log — must match `DEPLOY_TASKS_HOST_STORAGE` lock_path: '%kernel.project_dir%/.deploy-tasks-host.lock' # host runner flock file — must match `DEPLOY_TASKS_HOST_LOCK` ``` -------------------------------- ### deploytasks:prune command usage examples Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/docs/commands.md Shows basic usage, dry-run preview, forced non-interactive execution, and group-filtered pruning for the deploytasks:prune command. The --no-interaction option requires --force unless combined with --dry-run. The --group option is repeatable. ```bash bin/console deploytasks:prune ``` ```bash bin/console deploytasks:prune --dry-run ``` ```bash bin/console deploytasks:prune --no-interaction --force ``` ```bash bin/console deploytasks:prune --group=predeploy ``` ```bash bin/console deploytasks:prune --group=predeploy --group=postdeploy ``` -------------------------------- ### Create a deploy task service Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/README.md Example of a Symfony service implementing DeployTaskInterface. Use the AsDeployTask attribute with a unique id and priority. Inject dependencies in the constructor and return TaskResult from run(). ```php use App\Repository\CategoryRepository; use Soviann\DeployTasksBundle\Attribute\AsDeployTask; use Soviann\DeployTasksBundle\DeployTaskInterface; use Soviann\DeployTasksBundle\TaskResult; use Symfony\Component\Console\Output\OutputInterface; #[AsDeployTask(id: 'task_20260412143000_seed_categories', priority: 10)] final class SeedCategoriesTask implements DeployTaskInterface { public function __construct( private readonly CategoryRepository $categories, ) { } public function getDescription(): string { return 'Seeds the categories table with initial data.'; } public function run(OutputInterface $output): TaskResult { $this->categories->seedDefaults(); $output->writeln('Categories seeded.'); return TaskResult::SUCCESS; } } ``` -------------------------------- ### AsDeployTask env filtering examples Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/docs/creating-tasks.md Shows how to restrict a deploy task to specific environments using the env parameter. Use 'prod' for production only, an array for multiple environments, and null for all environments (default). Tasks whose env does not match the current environment are silently skipped at runtime. ```php #[AsDeployTask(id: 'task_...', env: 'prod')] // prod only #[AsDeployTask(id: 'task_...', env: ['dev', 'test'])] // dev and test only #[AsDeployTask(id: 'task_...', env: null)] // all environments (default) ``` -------------------------------- ### Implement DeployTaskInterface with AsDeployTask attribute Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/llms.txt Example task class that implements DeployTaskInterface, uses the AsDeployTask attribute, and injects a repository via constructor. The run method returns TaskResult::SUCCESS, FAILURE, or SKIPPED (stays pending). ```php categories->seedDefaults(); $output->writeln('Categories seeded.'); return TaskResult::SUCCESS; // or TaskResult::FAILURE, TaskResult::SKIPPED (stays pending) } } ``` -------------------------------- ### Listener example for AfterTaskEvent (PHP) Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/docs/events.md Example listener class using the AsEventListener attribute. It logs task execution using the event's taskId and duration properties. ```php use Soviann\DeployTasksBundle\Event\AfterTaskEvent; use Symfony\Component\EventDispatcher\Attribute\AsEventListener; #[AsEventListener] final class LogTaskExecutionListener { public function __invoke(AfterTaskEvent $event): void { // Log task execution: $event->taskId, $event->duration } } ``` -------------------------------- ### deploytasks:host:rollup command usage examples Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/docs/commands.md Shows basic usage and forced non-interactive execution for the host-scope rollup. The --force flag is required with --no-interaction, otherwise the command refuses to run. ```bash bin/console deploytasks:host:rollup bin/console deploytasks:host:rollup --no-interaction --force ``` -------------------------------- ### deploytasks:reset command usage examples Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/docs/commands.md Shows basic usage, forced non-interactive execution, and group-specific reset for the deploytasks:reset command. The --no-interaction option requires --force, otherwise the command refuses to run. Without --group, all recorded slots for the task are cleared. ```bash bin/console deploytasks:reset task_20260412143000_seed_categories ``` ```bash bin/console deploytasks:reset task_20260412143000_seed_categories --no-interaction --force ``` ```bash bin/console deploytasks:reset task_20260412143000_seed_categories --group=predeploy ``` -------------------------------- ### AsDeployTask groups filtering examples Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/docs/creating-tasks.md Shows how to assign deploy tasks to named groups using the groups parameter. Use a string for a single group, an array for multiple groups, and null for the default slot. Execution is scoped per (task, group) slot; without --group every slot runs, while --group= narrows to tasks declaring the listed groups. ```php #[AsDeployTask(id: 'task_...', groups: 'predeploy')] // single group #[AsDeployTask(id: 'task_...', groups: ['predeploy', 'postdeploy'])] // multiple groups #[AsDeployTask(id: 'task_...', groups: null)] // default slot (omit --group to run) ``` -------------------------------- ### Configure transaction_mode in YAML Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/docs/storage.md Example YAML configuration setting transaction_mode to all_or_nothing for the database storage backend. This is the default for database; custom backends default to none. ```yaml soviann_deploy_tasks: storage: type: database database: transaction_mode: all_or_nothing # default for database; custom defaults to none ``` -------------------------------- ### Unit Test Setup with InMemoryStorage Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/docs/testing.md Configures a TaskRunner with InMemoryStorage for unit testing task logic without filesystem or database. The TaskRunner constructor requires eight arguments; optional arguments include dispatcher, lockFactory, environment, clock, and logger. A transactionMode other than 'none' throws IncompatibleStorageException unless storage implements TransactionalStorageInterface. ```php use Soviann\DeployTasksBundle\Identifier\TaskDescriptionResolver; use Soviann\DeployTasksBundle\Identifier\TaskIdResolver; use Soviann\DeployTasksBundle\Runner\TaskRegistry; use Soviann\DeployTasksBundle\Runner\TaskRunner; use Soviann\DeployTasksBundle\Runner\TransactionMode; use Soviann\DeployTasksBundle\Sorting\DefaultTaskSorter; use Soviann\DeployTasksBundle\Storage\InMemory\InMemoryStorage; $storage = new InMemoryStorage(); $idResolver = new TaskIdResolver(); $registry = new TaskRegistry([$yourTask], $idResolver); $runner = new TaskRunner( $registry, $storage, new DefaultTaskSorter($idResolver), $idResolver, new TaskDescriptionResolver(), slowTaskThreshold: 300, transactionMode: TransactionMode::None, lockTtl: 3600, ); ``` -------------------------------- ### deploytasks:rollup command usage examples Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/docs/commands.md Shows basic usage, forced non-interactive execution, and group-specific rollups. The --force flag is required with --no-interaction, otherwise the command refuses to run. --group can be repeated to target multiple slots. ```bash bin/console deploytasks:rollup bin/console deploytasks:rollup --no-interaction --force bin/console deploytasks:rollup --group=predeploy bin/console deploytasks:rollup --group=predeploy --group=postdeploy ``` -------------------------------- ### Implement TaskSorterInterface for custom task ordering Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/docs/advanced.md Define a custom task execution order by implementing TaskSorterInterface. The sort method receives an array of DeployTaskInterface objects and must return a list of them. The example returns the tasks unchanged, but you can apply your own ordering logic. ```php use Soviann\DeployTasksBundle\Sorting\TaskSorterInterface; use Soviann\DeployTasksBundle\DeployTaskInterface; final class MySorter implements TaskSorterInterface { /** * @param array $tasks * * @return list */ public function sort(array $tasks): array { // Your custom ordering logic return \array_values($tasks); } } ``` -------------------------------- ### Create Process with null timeout for unlimited execution Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/docs/creating-tasks.md Example of constructing a Process with timeout: null to avoid symfony/process's default 60-second hard timeout. This is necessary when the AsDeployTask attribute does not set a positive timeout. ```php $process = new Process(['rsync', '-a', $src, $dest], timeout: null); ``` -------------------------------- ### AsDeployTask with transactional: false Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/docs/advanced.md Example of opting a task out of per-task transaction wrapping by setting transactional to false in the AsDeployTask attribute. ```APIDOC ## AsDeployTask Attribute ### Description Opt a task out of per-task transaction wrapping by setting `transactional: false` in the `AsDeployTask` attribute. This only applies when the active storage is configured with `storage..transaction_mode: per_task`. ### Usage ```php #[AsDeployTask(id: 'task_data_migration', transactional: false)] ``` ### Notes - Requires a storage backend implementing `TransactionalStorageInterface` (e.g., `DbalStorage`). - Under `transaction_mode: none` or `all_or_nothing`, declaring `transactional: false` fails the container build. - Under `transaction_mode: none`, `transactional: true` fails the container build. - Under `transaction_mode: all_or_nothing`, `transactional: false` fails the container build. - `transactional: true` on a storage that does not implement `TransactionalStorageInterface` always fails the container build with `IncompatibleStorageException`. ``` -------------------------------- ### Assign tasks to groups with AsDeployTask attribute Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/README.md PHP attribute examples for assigning tasks to groups. The first assigns to a single group 'predeploy'; the second assigns to multiple groups 'predeploy' and 'postdeploy'. Without --group, commands operate on all slots; with --group, only matching groups are processed. ```php #[AsDeployTask(id: 'task_...', groups: 'predeploy')] #[AsDeployTask(id: 'task_...', groups: ['predeploy', 'postdeploy'])] ``` -------------------------------- ### Enable lock configuration Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/docs/advanced.md When symfony/lock is installed, the runner acquires a lock before execution to prevent concurrent runs. The lock is enabled by default. If the lock cannot be acquired, the command exits with code 75 (EX_TEMPFAIL), which CI pipelines should treat as 'retry recommended' rather than a genuine failure. ```yaml soviann_deploy_tasks: lock: enabled: true # default ``` -------------------------------- ### Run deploytasks:create-schema Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/docs/commands.md Executes the command to provision the schema. Re-running is safe because implementations are idempotent. ```bash bin/console deploytasks:create-schema ``` -------------------------------- ### Define a task with AsDeployTask attribute Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/docs/creating-tasks.md Recommended way to create a task: implement DeployTaskInterface and add #[AsDeployTask] with metadata like id, priority, and description. The run() method returns TaskResult::SUCCESS after executing logic. ```php use Soviann\DeployTasksBundle\Attribute\AsDeployTask; use Soviann\DeployTasksBundle\DeployTaskInterface; use Soviann\DeployTasksBundle\TaskResult; use Symfony\Component\Console\Output\OutputInterface; #[AsDeployTask(id: 'task_20260412143000_seed_categories', priority: 10, description: 'Seed categories')] final class SeedCategoriesTask implements DeployTaskInterface { public function getDescription(): string { return 'Seed categories'; } public function run(OutputInterface $output): TaskResult { // Your logic here $output->writeln('Seeding categories...'); return TaskResult::SUCCESS; } } ``` -------------------------------- ### Filesystem storage configuration (YAML) Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/docs/storage.md Default storage backend configuration. Requires no additional packages. The path must be dedicated to deploy-task state; a path containing composer.json is refused with a StorageException. The directory is created automatically on first write. ```yaml soviann_deploy_tasks: storage: type: filesystem filesystem: path: '%kernel.project_dir%/var/deploy-tasks' ``` -------------------------------- ### Generate runner config from bundle: bin/console deploytasks:host:config --write Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/docs/host-tasks.md Run this command to write deploy-tasks-host.local.sh at the project root, sourced by bin/deploy-tasks-host.sh on every run. It uses project-relative paths so it stays correct even when the PHP container mounts the project at a different absolute path. Re-run after changing any host.* value; deploytasks:status warns when the generated file drifts. ```bash bin/console deploytasks:host:config --write ``` -------------------------------- ### Generate a deploy task with default and custom directory Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/docs/commands.md Run the command with no arguments to generate a task in the default directory src/DeployTasks/Task/. Use --dir to specify a custom target directory. ```bash bin/console deploytasks:generate bin/console deploytasks:generate --dir=src/Task/ ``` -------------------------------- ### Configure soviann_deploy_tasks with slow_task_threshold, storage, and lock Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/README.md YAML configuration for the bundle. Sets slow_task_threshold to 300 seconds (warns but does not kill), selects filesystem storage with a path under var/deploy-tasks, and enables locking. Storage type can be filesystem, database, or custom. ```yaml # config/packages/soviann_deploy_tasks.yaml soviann_deploy_tasks: slow_task_threshold: 300 # seconds; warn when a task runs longer (nothing is killed) storage: type: filesystem # filesystem | database | custom filesystem: path: '%kernel.project_dir%/var/deploy-tasks' lock: enabled: true ``` -------------------------------- ### Run all tests with PHPUnit Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/CONTRIBUTING.md Runs the full test suite. The DBAL storage tests use an in-memory SQLite database, so no external database server is needed. ```bash # All tests vendor/bin/phpunit ``` -------------------------------- ### Functional Test Kernel with Bundle Registration Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/docs/testing.md Boots a minimal kernel with the bundle registered to test DI wiring, commands, and configuration. The kernel registers FrameworkBundle and SoviannDeployTasksBundle, configures the framework for tests, sets filesystem storage to a temp directory, and registers a test task with the 'soviann_deploy_tasks.task' tag. ```php use Symfony\Component\HttpKernel\Kernel; use Soviann\DeployTasksBundle\SoviannDeployTasksBundle; final class TestKernel extends Kernel { public function registerBundles(): iterable { yield new \Symfony\Bundle\FrameworkBundle\FrameworkBundle(); yield new SoviannDeployTasksBundle(); } protected function configureContainer(ContainerConfigurator $container): void { $container->extension('framework', ['test' => true]); $container->extension('soviann_deploy_tasks', [ 'storage' => [ 'type' => 'filesystem', 'filesystem' => ['path' => sys_get_temp_dir().'/deploy-tasks-test'], ], ]); // Register test tasks with the bundle tag $container->services() ->set(YourTestTask::class) ->tag('soviann_deploy_tasks.task'); } } ``` -------------------------------- ### deploytasks:host:config command usage Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/docs/commands.md Run without --write to print the DEPLOY_TASKS_HOST_* export lines to stdout. Use --write to write them to deploy-tasks-host.local.sh at the project root, which is sourced by bin/deploy-tasks-host.sh on every run. Without --write, paths under the project directory are emitted project-relative; an absolute path outside the project directory triggers a warning. With --write, the command refuses to overwrite a file not generated by this command (no '# Generated by deploytasks:host:config' marker) and reports an error, printing the exports for manual merge. Exit codes: 0 on success; 1 on failure (value cannot be represented as a single-quoted shell literal, --write with missing project directory, or target file exists and was not generated by this command). ```bash bin/console deploytasks:host:config bin/console deploytasks:host:config --write ``` -------------------------------- ### Run all pending deploy tasks Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/README.md Executes all pending deploy tasks via the console command. ```bash bin/console deploytasks:run ``` -------------------------------- ### Generate a deploy task with deploytasks:generate Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/docs/creating-tasks.md Use the generator command to create a new task file. The default output is src/DeployTasks/Task/DeployTask.php, and the --dir option sets a custom target directory. The command takes no positional argument. ```bash bin/console deploytasks:generate # DeployTask20260412143000.php bin/console deploytasks:generate --dir=src/Task/ # custom target directory ``` -------------------------------- ### Run deploytasks:create-schema with --dump-sql Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/docs/commands.md Outputs the SQL statement instead of executing it, useful for Doctrine migrations. ```bash bin/console deploytasks:create-schema --dump-sql ``` -------------------------------- ### Instantiate InMemoryStorage directly Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/docs/storage.md InMemoryStorage is for tests only and is not registered as a bundle service, so it must be instantiated directly. It is not transactional and does not implement TransactionalStorageInterface, so using it with transaction_mode per_task or all_or_nothing fails at construction with IncompatibleStorageException. ```php use Soviann\DeployTasksBundle\Storage\InMemory\InMemoryStorage; $storage = new InMemoryStorage(); ``` -------------------------------- ### Development Commands for Testing and Code Quality Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/CLAUDE.md Run all tests, specific test suites, static analysis with PHPStan level 9, and code style fixes with PHP-CS-Fixer. Use --dry-run to check style without modifying files. ```bash vendor/bin/phpunit # all tests vendor/bin/phpunit --testsuite Unit # unit tests only vendor/bin/phpunit --testsuite Functional # functional tests only vendor/bin/phpstan analyse # static analysis (level 9) vendor/bin/php-cs-fixer fix # auto-fix code style vendor/bin/php-cs-fixer fix --dry-run # check code style ``` -------------------------------- ### DeployTaskInterface Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/llms.txt Interface that deploy tasks must implement. Provides a description and the main run method returning a TaskResult. ```APIDOC ## DeployTaskInterface ### Description Interface that deploy tasks must implement. Provides a description and the main run method returning a TaskResult. ### Methods #### getDescription(): string - Returns a string description of the task. #### run(OutputInterface $output): TaskResult - Executes the task and returns a TaskResult indicating success, failure, or skipped. ``` -------------------------------- ### CLI Commands Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/llms.txt List of CLI commands provided by the bundle, with their options. ```APIDOC ## CLI Commands ### deploytasks:run Options: --dry-run, --rerun-all, --id=, --group=, --require-some ### deploytasks:status Options: --no-state, --group=, --filter-status=, --show-orphaned ### deploytasks:show No options listed. ### deploytasks:skip Options: --group= ### deploytasks:reset Options: --group=, --no-interaction, --force ### deploytasks:prune Options: --dry-run, --group=, --no-interaction, --force ### deploytasks:rollup Options: --group=, --no-interaction, --force ### deploytasks:generate Options: --dir, --namespace ### deploytasks:create-schema Options: --dump-sql (when using storage.type: database) ### Host-scope commands - deploytasks:host:install (--force) - deploytasks:host:generate (--dir) - deploytasks:host:skip - deploytasks:host:reset (--no-interaction, --force) - deploytasks:host:rollup (--no-interaction, --force) - deploytasks:host:config (--write) ``` -------------------------------- ### Add storage directory to .gitignore Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/docs/storage.md Recommended to add the storage directory to .gitignore since it is created automatically and contains runtime state. ```gitignore /var/deploy-tasks/ ``` -------------------------------- ### Generate a host task with bin/console deploytasks:host:generate Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/docs/host-tasks.md Creates a new host task script file named deploy_task_YYYYMMDD_HHMMSS.sh in the deploy/host-tasks/ directory. Edit the generated file to implement the task. ```bash bin/console deploytasks:host:generate ``` -------------------------------- ### Enable Contrib recipes permanently Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/README.md Run this Composer command to allow Contrib recipes without prompting. This is needed to use the Flex recipe for the bundle. ```bash composer config extra.symfony.allow-contrib true ``` -------------------------------- ### Run static analysis with PHPStan Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/CONTRIBUTING.md Runs PHPStan static analysis at level 9. No suppressions are allowed. ```bash # Static analysis (level 9) vendor/bin/phpstan analyse ``` -------------------------------- ### deploytasks:host:generate command usage Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/docs/commands.md Generate a new host-scope deploy task script. Use --dir to specify the target directory, defaulting to the host.directory config (default deploy/host-tasks/). The generated file is executable (0750) and contains a bash stub with set -euo pipefail and a TODO marker. ```bash bin/console deploytasks:host:generate bin/console deploytasks:host:generate --dir=deploy/host-tasks/ ``` -------------------------------- ### Register bundle manually in config/bundles.php Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/docs/installation.md Manual registration of the bundle when not using Flex. Add this line to config/bundles.php. ```php Soviann\DeployTasksBundle\SoviannDeployTasksBundle::class => ['all' => true], ``` -------------------------------- ### Configure dedicated recipe endpoint Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/README.md Alternative to enabling Contrib recipes globally. Sets a custom Flex endpoint to serve the bundle's recipe from a dedicated repository. ```bash composer config extra.symfony.endpoint --json '["https://api.github.com/repos/Soviann/flex-recipes/contents/index.json", "flex://defaults"]' ``` -------------------------------- ### Create schema via console command Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/docs/storage.md Commands to create the database table directly or dump the SQL for a Doctrine migration. The SQL output uses platform-aware identifier quoting (backticks for MySQL, double quotes for PostgreSQL/SQLite). ```bash # Create the table directly bin/console deploytasks:create-schema # Output SQL only (e.g. for a Doctrine migration) bin/console deploytasks:create-schema --dump-sql ``` -------------------------------- ### Bundle configuration for custom Redis storage Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/docs/storage.md Configures soviann_deploy_tasks to use the custom RedisStorage service. transaction_mode defaults to none; per_task and all_or_nothing require implementing TransactionalStorageInterface. ```yaml # config/packages/soviann_deploy_tasks.yaml soviann_deploy_tasks: storage: type: custom custom: service: App\Storage\RedisStorage # service ID of your TaskStorageInterface implementation transaction_mode: none # default; per_task/all_or_nothing require TransactionalStorageInterface ``` -------------------------------- ### Show deploy task details Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/docs/commands.md Use this command to inspect a single deploy task by ID. It displays metadata, declared groups, and every stored execution record with full error text. Exit code 0 on success, 2 if the task ID is not registered. ```bash bin/console deploytasks:show task_20260412143000_seed_categories ``` -------------------------------- ### RedisStorage PHP implementation using Predis Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/docs/storage.md Implements TaskStorageInterface with Redis via Predis. Uses hash keys per task execution and a Redis set as an index. Methods cover has/get/save/remove/removeAll/findByTaskId/all/reset. The hydrate() method reconstructs TaskExecution objects from stored fields. Requires a Predis ClientInterface service. ```php namespace App\Storage; use Predis\ClientInterface; use Soviann\DeployTasksBundle\Storage\TaskExecution; use Soviann\DeployTasksBundle\Storage\TaskStatus; use Soviann\DeployTasksBundle\Storage\TaskStorageInterface; final class RedisStorage implements TaskStorageInterface { private const KEY_PREFIX = 'soviann_deploy_tasks:'; private const INDEX_KEY = 'soviann_deploy_tasks:index'; public function __construct(private readonly ClientInterface $redis) { } public function has(string $taskId, ?string $group = null): bool { return 1 === $this->redis->exists($this->key($taskId, $group)); } public function get(string $taskId, ?string $group = null): ?TaskExecution { $payload = $this->redis->hgetall($this->key($taskId, $group)); return [] === $payload ? null : $this->hydrate($payload); } public function save(TaskExecution $execution): void { $key = $this->key($execution->id, $execution->group); $this->redis->hmset($key, [ 'id' => $execution->id, 'task_group' => $execution->group ?? '', 'status' => $execution->status->value, 'executed_at' => $execution->executedAt->format(\DATE_ATOM), 'error' => $execution->error ?? '', 'duration_ms' => $execution->durationMs ?? '', ]); $this->redis->sadd(self::INDEX_KEY, [$key]); } public function remove(string $taskId, ?string $group = null): void { $key = $this->key($taskId, $group); $this->redis->del([$key]); $this->redis->srem(self::INDEX_KEY, $key); } public function removeAll(string $taskId): void { foreach ($this->redis->keys(self::KEY_PREFIX.$taskId.':*') as $key) { $this->redis->del([$key]); $this->redis->srem(self::INDEX_KEY, $key); } } public function findByTaskId(string $taskId): array { $executions = []; foreach ($this->redis->keys(self::KEY_PREFIX.$taskId.':*') as $key) { $payload = $this->redis->hgetall($key); if ([] !== $payload) { $executions[] = $this->hydrate($payload); } } return $executions; } public function all(): array { $executions = []; foreach ($this->redis->smembers(self::INDEX_KEY) as $key) { $payload = $this->redis->hgetall($key); if ([] !== $payload) { $executions[] = $this->hydrate($payload); } } return $executions; } public function reset(): void { $keys = $this->redis->smembers(self::INDEX_KEY); if ([] !== $keys) { $this->redis->del($keys); } $this->redis->del([self::INDEX_KEY]); } private function key(string $taskId, ?string $group): string { return self::KEY_PREFIX.$taskId.':'.($group ?? ''); } /** * @param array $payload */ private function hydrate(array $payload): TaskExecution { return new TaskExecution( id: $payload['id'], status: TaskStatus::from($payload['status']), executedAt: new \DateTimeImmutable($payload['executed_at']), error: '' === $payload['error'] ? null : $payload['error'], group: '' === $payload['task_group'] ? null : $payload['task_group'], durationMs: '' === $payload['duration_ms'] ? null : (int) $payload['duration_ms'], ); } } ``` -------------------------------- ### Run deploy tasks commands Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/llms.txt Commands to run pending tasks, optionally filtered by group, and check status. The --group=predeploy option filters to tasks declaring the 'predeploy' group. ```bash bin/console deploytasks:run bin/console deploytasks:run --group=predeploy # optional: filter to tasks declaring groups: 'predeploy' bin/console deploytasks:status ``` -------------------------------- ### Monolog routing configuration for soviann_deploy_tasks channel Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/docs/logging.md Configure a dedicated stream handler for the soviann_deploy_tasks channel in config/packages/monolog.yaml. The handler writes to a dedicated log file at info level. ```yaml monolog: channels: ['soviann_deploy_tasks'] handlers: soviann_deploy_tasks: type: stream path: '%kernel.logs_dir%/soviann_deploy_tasks.log' level: info channels: ['soviann_deploy_tasks'] ``` -------------------------------- ### Raw SQL to create execution table Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/docs/storage.md Manual SQL to create the deploy_task_executions table. The task_group column stores empty string for the default slot because NULL is not allowed in a primary key. The composite primary key (id, task_group) allows one row per (task, group) slot. ```sql CREATE TABLE IF NOT EXISTS deploy_task_executions ( id VARCHAR(255) NOT NULL, task_group VARCHAR(128) NOT NULL DEFAULT '', status VARCHAR(16) NOT NULL, executed_at VARCHAR(32) NOT NULL, error TEXT DEFAULT NULL, duration_ms INTEGER DEFAULT NULL, PRIMARY KEY (id, task_group) ); ``` -------------------------------- ### deploytasks:host:rollup Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/docs/host-tasks.md Appends every pending script ID in one pass and reports the count. An empty host directory (or one where every script is already done) warns and exits successfully without prompting. ```APIDOC ## deploytasks:host:rollup ### Description Appends every pending script id in one pass and reports the count. An empty host directory (or one where every script is already done) warns and exits successfully without prompting. Destructive: same confirmation/`--force` convention as `deploytasks:rollup` — a bulk-operation guard: each individual append is reversible via `host:reset`, but appending everything in one pass deserves a stop. ### Method CLI ### Endpoint `bin/console deploytasks:host:rollup` ### Parameters #### Options - **--no-interaction** (flag) - Optional - Skips confirmation prompts. - **--force** (flag) - Optional - Proceeds without confirmation when combined with `--no-interaction`. ### Response #### Success Response - **count** (integer) - The number of pending script IDs appended. ### Notes - Operates on the completion log only (`host.log_path` config, default `.deploy-tasks-host.log`). - Uses exact-line semantics (`grep -Fxq`). - Takes the runner's own `flock` (`host.lock_path` config, default `.deploy-tasks-host.lock`) around the log mutation. - If the host tasks directory doesn't exist, exits with `Command::INVALID` and a pointer to the documentation. ``` -------------------------------- ### Run pending host tasks with bash bin/deploy-tasks-host.sh Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/docs/host-tasks.md Executes pending host tasks. Defaults to APP_ENV=dev when no argument is given. Passing 'prod' loads .env.prod and .env.prod.local. The --dry-run flag performs a dry run without executing tasks. ```bash bash bin/deploy-tasks-host.sh # defaults to APP_ENV=dev ``` ```bash bash bin/deploy-tasks-host.sh prod # loads .env.prod + .env.prod.local ``` ```bash bash bin/deploy-tasks-host.sh prod --dry-run ``` -------------------------------- ### Run unit tests only Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/CONTRIBUTING.md Runs only the unit test suite. ```bash # Unit tests only vendor/bin/phpunit --testsuite Unit ``` -------------------------------- ### Check deploy task status Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/README.md Displays the status of all deploy tasks. ```bash bin/console deploytasks:status ``` -------------------------------- ### Replace runProcessWithTimeout with runProcess timeout parameter (0.5.0) Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/UPGRADE.md In version 0.5.0, ProcessRunnerTrait::runProcessWithTimeout() was removed. Use runProcess() with the optional $timeout parameter instead. The diff shows the before and after calls. ```diff -$this->runProcessWithTimeout($process, 30, $output); +$this->runProcess($process, $output, timeout: 30); ``` -------------------------------- ### Cleanup Temp Storage in tearDown Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/docs/testing.md Placeholder for removing the temp storage directory recursively in tearDown() to avoid test pollution between runs. The actual removal logic is not shown. ```php protected function tearDown(): void { // Remove temp storage directory recursively } ``` -------------------------------- ### Command Testing with CommandTester Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/docs/testing.md Uses CommandTester to execute the 'deploytasks:status' command in a booted TestKernel. This snippet assumes the TestKernel class is defined as shown in the functional testing section. ```php use Symfony\Bundle\FrameworkBundle\Console\Application; use Symfony\Component\Console\Tester\CommandTester; $kernel = new TestKernel('test', true); $kernel->boot(); $application = new Application($kernel); $command = $application->find('deploytasks:status'); $tester = new CommandTester($command); $tester->execute([]); ``` -------------------------------- ### Enable events in configuration (YAML) Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/docs/events.md Configuration to enable events. Events are enabled by default when the dispatcher is available. The 'enabled' key defaults to true. ```yaml soviann_deploy_tasks: events: enabled: true # default ``` -------------------------------- ### Configure database storage type Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/docs/storage.md YAML configuration enabling database storage with default settings. The table is auto-created on first use by default. transaction_mode defaults to 'all_or_nothing'. ```yaml soviann_deploy_tasks: storage: type: database database: connection: default # Doctrine DBAL connection name table: deploy_task_executions # Table name auto_create_table: true # Create table on first use group_column: task_group # Column for the group slot key group_column_length: 128 transaction_mode: all_or_nothing # default — none | per_task | all_or_nothing ``` -------------------------------- ### deploytasks:status Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/docs/host-tasks.md Appends a 'Host tasks' section to the status output, listing each deploy/host-tasks/*.sh script as done or pending. It is a read-only bridge that reads the host directory and the completion log. ```APIDOC ## deploytasks:status ### Description Appends a "Host tasks" section listing each `deploy/host-tasks/*.sh` script as `done` (its ID is a full line in `.deploy-tasks-host.log`) or `pending`. This is a read-only bridge: PHP only reads the host directory and the log, it never writes to them. ### Method CLI ### Endpoint `bin/console deploytasks:status` ### Parameters None documented. ### Response #### Success Response - **Host tasks section** (list) - Each script ID with status `done` or `pending`. #### Response Example ``` Host tasks: deploy_task_20260418_143022: done deploy_task_20260418_143023: pending ``` ### Notes - Reads from `host.directory` and `host.log_path` bundle config (defaults `deploy/host-tasks` and `.deploy-tasks-host.log` under the project dir). - Does not see `DEPLOY_TASKS_HOST_DIR` or `DEPLOY_TASKS_HOST_STORAGE` env var overrides. ``` -------------------------------- ### Reset a host task with deploytasks:host:reset Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/docs/commands.md Removes a host task's completion-log entry so it runs again on the next bin/deploy-tasks-host.sh. Requires the host task ID. Under --no-interaction, --force is required. Exit codes: 0 on success, 2 for invalid input or missing --force, 1 if confirmation declined, 75 if the host lock is held. ```bash bin/console deploytasks:host:reset deploy_task_20260418_143022 ``` ```bash bin/console deploytasks:host:reset deploy_task_20260418_143022 --no-interaction --force ``` -------------------------------- ### Skip a host task with deploytasks:host:skip Source: https://github.com/soviann/deploy-tasks-bundle/blob/main/docs/commands.md Marks a host task as done in the completion log without running its script. Requires the host task ID (script basename without .sh). Prompts for confirmation; proceeds under --no-interaction without --force. Exit codes: 0 on success, 2 if the directory or script is missing, 1 if confirmation declined, 75 if the host lock is held. ```bash bin/console deploytasks:host:skip deploy_task_20260418_143022 ```