From afe0cd49903231a343de550c73e9ce75272b12ad Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 14 Nov 2025 12:21:41 +0000 Subject: [PATCH 1/7] Initial plan From 0200ea83926d27cb1dd6341c0a9cd1334004010f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 14 Nov 2025 12:49:22 +0000 Subject: [PATCH 2/7] Add when option to one-time tasks (first/last) Co-authored-by: shyim <6224096+shyim@users.noreply.github.com> --- src/Config/ConfigFactory.php | 11 ++- src/Config/ProjectConfiguration.php | 2 +- src/Resources/config/services.xml | 2 +- src/Services/OneTimeTasks.php | 11 ++- src/Services/UpgradeManager.php | 6 +- src/Struct/OneTimeTask.php | 18 +++++ tests/Config/ConfigFactoryTest.php | 32 +++++++-- .../maintenance-mode/.shopware-project.yml | 6 ++ tests/Services/OneTimeTasksTest.php | 70 ++++++++++++++++++- tests/Services/UpgradeManagerTest.php | 4 +- 10 files changed, 147 insertions(+), 15 deletions(-) create mode 100644 src/Struct/OneTimeTask.php diff --git a/src/Config/ConfigFactory.php b/src/Config/ConfigFactory.php index d880e49..3ebe717 100644 --- a/src/Config/ConfigFactory.php +++ b/src/Config/ConfigFactory.php @@ -76,7 +76,16 @@ private static function fillConfig(ProjectConfiguration $projectConfiguration, a if (isset($deployment['one-time-tasks']) && \is_array($deployment['one-time-tasks'])) { foreach ($deployment['one-time-tasks'] as $task) { if (isset($task['id'], $task['script']) && \is_string($task['id']) && \is_string($task['script'])) { - $projectConfiguration->oneTimeTasks[$task['id']] = $task['script']; + $when = \Shopware\Deployment\Struct\OneTimeTask::WHEN_LAST; + if (isset($task['when']) && \is_string($task['when'])) { + $when = $task['when']; + } + + $projectConfiguration->oneTimeTasks[$task['id']] = new \Shopware\Deployment\Struct\OneTimeTask( + $task['id'], + $task['script'], + $when + ); } } } diff --git a/src/Config/ProjectConfiguration.php b/src/Config/ProjectConfiguration.php index 5579067..e2eea77 100644 --- a/src/Config/ProjectConfiguration.php +++ b/src/Config/ProjectConfiguration.php @@ -15,7 +15,7 @@ class ProjectConfiguration public ProjectStore $store; /** - * @var array + * @var array */ public array $oneTimeTasks = []; diff --git a/src/Resources/config/services.xml b/src/Resources/config/services.xml index c291018..81a2d0e 100644 --- a/src/Resources/config/services.xml +++ b/src/Resources/config/services.xml @@ -14,7 +14,7 @@ - + diff --git a/src/Services/OneTimeTasks.php b/src/Services/OneTimeTasks.php index 44986e5..f45757b 100644 --- a/src/Services/OneTimeTasks.php +++ b/src/Services/OneTimeTasks.php @@ -18,18 +18,23 @@ public function __construct( ) { } - public function execute(OutputInterface $output): void + public function execute(OutputInterface $output, ?string $when = null): void { $executed = $this->getExecutedTasks(); - foreach ($this->configuration->oneTimeTasks as $id => $script) { + foreach ($this->configuration->oneTimeTasks as $id => $task) { + // Filter by when if specified + if ($when !== null && $task->when !== $when) { + continue; + } + if (isset($executed[$id])) { continue; } $output->writeln('Running one-time task ' . $id); - $this->processHelper->runAndTail($script); + $this->processHelper->runAndTail($task->script); $this->markAsRun($id); } diff --git a/src/Services/UpgradeManager.php b/src/Services/UpgradeManager.php index be762a7..971a6c8 100644 --- a/src/Services/UpgradeManager.php +++ b/src/Services/UpgradeManager.php @@ -32,6 +32,9 @@ public function run(RunConfiguration $configuration, OutputInterface $output): v $this->hookExecutor->execute(HookExecutor::HOOK_PRE_UPDATE); + // Execute one-time tasks that should run first + $this->oneTimeTasks->execute($output, \Shopware\Deployment\Struct\OneTimeTask::WHEN_FIRST); + if ($this->configuration->maintenance->enabled) { $this->state->enableMaintenanceMode(); @@ -89,7 +92,8 @@ public function run(RunConfiguration $configuration, OutputInterface $output): v $this->processHelper->console(['theme:compile', '--active-only']); } - $this->oneTimeTasks->execute($output); + // Execute one-time tasks that should run last + $this->oneTimeTasks->execute($output, \Shopware\Deployment\Struct\OneTimeTask::WHEN_LAST); $this->hookExecutor->execute(HookExecutor::HOOK_POST_UPDATE); diff --git a/src/Struct/OneTimeTask.php b/src/Struct/OneTimeTask.php new file mode 100644 index 0000000..ab09ae6 --- /dev/null +++ b/src/Struct/OneTimeTask.php @@ -0,0 +1,18 @@ +createMockApplication()); static::assertTrue($config->extensionManagement->enabled); static::assertSame('ignore', $config->extensionManagement->overrides['Name']['state']); - static::assertSame(['foo' => 'test'], $config->oneTimeTasks); + static::assertArrayHasKey('foo', $config->oneTimeTasks); + static::assertInstanceOf(\Shopware\Deployment\Struct\OneTimeTask::class, $config->oneTimeTasks['foo']); + static::assertSame('foo', $config->oneTimeTasks['foo']->id); + static::assertSame('test', $config->oneTimeTasks['foo']->script); + static::assertSame('last', $config->oneTimeTasks['foo']->when); static::assertNotSame('', $config->hooks->pre); static::assertNotSame('', $config->hooks->post); static::assertNotSame('', $config->hooks->preInstall); @@ -132,7 +136,9 @@ public function testCreateWithProjectConfigOption(): void $config = ConfigFactory::create(__DIR__, $this->createMockApplication($customConfigPath)); static::assertTrue($config->extensionManagement->enabled); - static::assertSame(['foo' => 'test'], $config->oneTimeTasks); + static::assertArrayHasKey('foo', $config->oneTimeTasks); + static::assertInstanceOf(\Shopware\Deployment\Struct\OneTimeTask::class, $config->oneTimeTasks['foo']); + static::assertSame('test', $config->oneTimeTasks['foo']->script); } public function testCreateWithProjectConfigOptionRelativePath(): void @@ -141,7 +147,9 @@ public function testCreateWithProjectConfigOptionRelativePath(): void $config = ConfigFactory::create(__DIR__ . '/_fixtures', $this->createMockApplication('yml/.shopware-project.yml')); static::assertTrue($config->extensionManagement->enabled); - static::assertSame(['foo' => 'test'], $config->oneTimeTasks); + static::assertArrayHasKey('foo', $config->oneTimeTasks); + static::assertInstanceOf(\Shopware\Deployment\Struct\OneTimeTask::class, $config->oneTimeTasks['foo']); + static::assertSame('test', $config->oneTimeTasks['foo']->script); } #[Env('SHOPWARE_PROJECT_CONFIG_FILE', '_fixtures/yml/.shopware-project.yml')] @@ -151,7 +159,9 @@ public function testEnvironmentVariableOverridesProjectConfigOption(): void $config = ConfigFactory::create(__DIR__, $this->createMockApplication('some-other-config.yml')); // Should load the config from environment variable, not the CLI option - static::assertSame(['foo' => 'test'], $config->oneTimeTasks); + static::assertArrayHasKey('foo', $config->oneTimeTasks); + static::assertInstanceOf(\Shopware\Deployment\Struct\OneTimeTask::class, $config->oneTimeTasks['foo']); + static::assertSame('test', $config->oneTimeTasks['foo']->script); } public function testCreateWithNonExistentProjectConfig(): void @@ -164,4 +174,18 @@ public function testCreateWithNonExistentProjectConfig(): void static::assertSame([], $config->extensionManagement->overrides); static::assertSame([], $config->oneTimeTasks); } + + public function testOneTimeTasksWithWhenField(): void + { + $config = ConfigFactory::create(__DIR__ . '/_fixtures/maintenance-mode', $this->createMockApplication()); + + static::assertArrayHasKey('foo', $config->oneTimeTasks); + static::assertSame('last', $config->oneTimeTasks['foo']->when); + + static::assertArrayHasKey('early-task', $config->oneTimeTasks); + static::assertSame('first', $config->oneTimeTasks['early-task']->when); + + static::assertArrayHasKey('late-task', $config->oneTimeTasks); + static::assertSame('last', $config->oneTimeTasks['late-task']->when); + } } diff --git a/tests/Config/_fixtures/maintenance-mode/.shopware-project.yml b/tests/Config/_fixtures/maintenance-mode/.shopware-project.yml index 3d18666..8dedd03 100644 --- a/tests/Config/_fixtures/maintenance-mode/.shopware-project.yml +++ b/tests/Config/_fixtures/maintenance-mode/.shopware-project.yml @@ -26,3 +26,9 @@ deployment: one-time-tasks: - id: foo script: test + - id: early-task + script: echo "Running early" + when: first + - id: late-task + script: echo "Running late" + when: last diff --git a/tests/Services/OneTimeTasksTest.php b/tests/Services/OneTimeTasksTest.php index 8ed91a1..cdab5ff 100644 --- a/tests/Services/OneTimeTasksTest.php +++ b/tests/Services/OneTimeTasksTest.php @@ -68,7 +68,7 @@ public function testTask(): void $configuration = new ProjectConfiguration(); $configuration->oneTimeTasks = [ - 'test' => 'echo "test"', + 'test' => new \Shopware\Deployment\Struct\OneTimeTask('test', 'echo "test"', 'last'), ]; $tasks = new OneTimeTasks($processHelper, $connection, $configuration); @@ -88,7 +88,7 @@ public function testTaskAlreadyExecuted(): void $configuration = new ProjectConfiguration(); $configuration->oneTimeTasks = [ - 'test' => 'echo "test"', + 'test' => new \Shopware\Deployment\Struct\OneTimeTask('test', 'echo "test"', 'last'), ]; $tasks = new OneTimeTasks($processHelper, $connection, $configuration); @@ -105,4 +105,70 @@ public function testRemove(): void $tasks = new OneTimeTasks($processHelper, $connection, new ProjectConfiguration()); $tasks->remove('test'); } + + public function testTaskWithWhenFirst(): void + { + $output = $this->createMock(OutputInterface::class); + $output->expects($this->once())->method('writeln')->with('Running one-time task first-task'); + + $processHelper = $this->createMock(ProcessHelper::class); + $processHelper->expects($this->once())->method('runAndTail')->with('echo "first"'); + + $connection = $this->createMock(Connection::class); + $connection->expects($this->once())->method('fetchAllAssociativeIndexed')->willReturn([]); + $connection->expects($this->once())->method('executeStatement'); + + $configuration = new ProjectConfiguration(); + $configuration->oneTimeTasks = [ + 'first-task' => new \Shopware\Deployment\Struct\OneTimeTask('first-task', 'echo "first"', 'first'), + 'last-task' => new \Shopware\Deployment\Struct\OneTimeTask('last-task', 'echo "last"', 'last'), + ]; + + $tasks = new OneTimeTasks($processHelper, $connection, $configuration); + $tasks->execute($output, 'first'); + } + + public function testTaskWithWhenLast(): void + { + $output = $this->createMock(OutputInterface::class); + $output->expects($this->once())->method('writeln')->with('Running one-time task last-task'); + + $processHelper = $this->createMock(ProcessHelper::class); + $processHelper->expects($this->once())->method('runAndTail')->with('echo "last"'); + + $connection = $this->createMock(Connection::class); + $connection->expects($this->once())->method('fetchAllAssociativeIndexed')->willReturn([]); + $connection->expects($this->once())->method('executeStatement'); + + $configuration = new ProjectConfiguration(); + $configuration->oneTimeTasks = [ + 'first-task' => new \Shopware\Deployment\Struct\OneTimeTask('first-task', 'echo "first"', 'first'), + 'last-task' => new \Shopware\Deployment\Struct\OneTimeTask('last-task', 'echo "last"', 'last'), + ]; + + $tasks = new OneTimeTasks($processHelper, $connection, $configuration); + $tasks->execute($output, 'last'); + } + + public function testTaskWithoutWhenFilterExecutesAll(): void + { + $output = $this->createMock(OutputInterface::class); + $output->expects($this->exactly(2))->method('writeln'); + + $processHelper = $this->createMock(ProcessHelper::class); + $processHelper->expects($this->exactly(2))->method('runAndTail'); + + $connection = $this->createMock(Connection::class); + $connection->expects($this->once())->method('fetchAllAssociativeIndexed')->willReturn([]); + $connection->expects($this->exactly(2))->method('executeStatement'); + + $configuration = new ProjectConfiguration(); + $configuration->oneTimeTasks = [ + 'first-task' => new \Shopware\Deployment\Struct\OneTimeTask('first-task', 'echo "first"', 'first'), + 'last-task' => new \Shopware\Deployment\Struct\OneTimeTask('last-task', 'echo "last"', 'last'), + ]; + + $tasks = new OneTimeTasks($processHelper, $connection, $configuration); + $tasks->execute($output, null); + } } diff --git a/tests/Services/UpgradeManagerTest.php b/tests/Services/UpgradeManagerTest.php index 34a9c2d..244a3fa 100644 --- a/tests/Services/UpgradeManagerTest.php +++ b/tests/Services/UpgradeManagerTest.php @@ -27,7 +27,7 @@ public function testRun(): void { $oneTimeTasks = $this->createMock(OneTimeTasks::class); $oneTimeTasks - ->expects($this->once()) + ->expects($this->exactly(2)) ->method('execute'); $hookExecutor = $this->createMock(HookExecutor::class); @@ -219,7 +219,7 @@ public function testRunWithLicenseDomain(): void { $oneTimeTasks = $this->createMock(OneTimeTasks::class); $oneTimeTasks - ->expects($this->once()) + ->expects($this->exactly(2)) ->method('execute'); $hookExecutor = $this->createMock(HookExecutor::class); From 68a166cc1fc87e240a9241dbda040ec157bb908b Mon Sep 17 00:00:00 2001 From: Soner Sayakci Date: Wed, 19 Nov 2025 10:57:31 +0100 Subject: [PATCH 3/7] feat: use enum --- src/Config/ConfigFactory.php | 8 +++--- src/Services/OneTimeTasks.php | 4 +-- src/Services/UpgradeManager.php | 5 ++-- src/Struct/OneTimeTask.php | 5 +--- src/Struct/OneTimeTaskWhen.php | 11 +++++++++ tests/Config/ConfigFactoryTest.php | 9 ++++--- tests/Services/OneTimeTasksTest.php | 38 +++++++++++++++-------------- 7 files changed, 47 insertions(+), 33 deletions(-) create mode 100644 src/Struct/OneTimeTaskWhen.php diff --git a/src/Config/ConfigFactory.php b/src/Config/ConfigFactory.php index 3ebe717..9cbab46 100644 --- a/src/Config/ConfigFactory.php +++ b/src/Config/ConfigFactory.php @@ -6,6 +6,8 @@ use Shopware\Deployment\Application; use Shopware\Deployment\Helper\EnvironmentHelper; +use Shopware\Deployment\Struct\OneTimeTask; +use Shopware\Deployment\Struct\OneTimeTaskWhen; use Symfony\Component\Filesystem\Path; use Symfony\Component\Yaml\Yaml; @@ -76,12 +78,12 @@ private static function fillConfig(ProjectConfiguration $projectConfiguration, a if (isset($deployment['one-time-tasks']) && \is_array($deployment['one-time-tasks'])) { foreach ($deployment['one-time-tasks'] as $task) { if (isset($task['id'], $task['script']) && \is_string($task['id']) && \is_string($task['script'])) { - $when = \Shopware\Deployment\Struct\OneTimeTask::WHEN_LAST; + $when = OneTimeTaskWhen::LAST; if (isset($task['when']) && \is_string($task['when'])) { - $when = $task['when']; + $when = OneTimeTaskWhen::from($task['when']); } - $projectConfiguration->oneTimeTasks[$task['id']] = new \Shopware\Deployment\Struct\OneTimeTask( + $projectConfiguration->oneTimeTasks[$task['id']] = new OneTimeTask( $task['id'], $task['script'], $when diff --git a/src/Services/OneTimeTasks.php b/src/Services/OneTimeTasks.php index f45757b..1205f64 100644 --- a/src/Services/OneTimeTasks.php +++ b/src/Services/OneTimeTasks.php @@ -7,6 +7,7 @@ use Doctrine\DBAL\Connection; use Shopware\Deployment\Config\ProjectConfiguration; use Shopware\Deployment\Helper\ProcessHelper; +use Shopware\Deployment\Struct\OneTimeTaskWhen; use Symfony\Component\Console\Output\OutputInterface; class OneTimeTasks @@ -18,12 +19,11 @@ public function __construct( ) { } - public function execute(OutputInterface $output, ?string $when = null): void + public function execute(OutputInterface $output, OneTimeTaskWhen $when): void { $executed = $this->getExecutedTasks(); foreach ($this->configuration->oneTimeTasks as $id => $task) { - // Filter by when if specified if ($when !== null && $task->when !== $when) { continue; } diff --git a/src/Services/UpgradeManager.php b/src/Services/UpgradeManager.php index 971a6c8..85ab3df 100644 --- a/src/Services/UpgradeManager.php +++ b/src/Services/UpgradeManager.php @@ -7,6 +7,7 @@ use Shopware\Deployment\Config\ProjectConfiguration; use Shopware\Deployment\Helper\EnvironmentHelper; use Shopware\Deployment\Helper\ProcessHelper; +use Shopware\Deployment\Struct\OneTimeTaskWhen; use Shopware\Deployment\Struct\RunConfiguration; use Symfony\Component\Console\Input\ArgvInput; use Symfony\Component\Console\Output\OutputInterface; @@ -33,7 +34,7 @@ public function run(RunConfiguration $configuration, OutputInterface $output): v $this->hookExecutor->execute(HookExecutor::HOOK_PRE_UPDATE); // Execute one-time tasks that should run first - $this->oneTimeTasks->execute($output, \Shopware\Deployment\Struct\OneTimeTask::WHEN_FIRST); + $this->oneTimeTasks->execute($output, OneTimeTaskWhen::FIRST); if ($this->configuration->maintenance->enabled) { $this->state->enableMaintenanceMode(); @@ -93,7 +94,7 @@ public function run(RunConfiguration $configuration, OutputInterface $output): v } // Execute one-time tasks that should run last - $this->oneTimeTasks->execute($output, \Shopware\Deployment\Struct\OneTimeTask::WHEN_LAST); + $this->oneTimeTasks->execute($output, OneTimeTaskWhen::LAST); $this->hookExecutor->execute(HookExecutor::HOOK_POST_UPDATE); diff --git a/src/Struct/OneTimeTask.php b/src/Struct/OneTimeTask.php index ab09ae6..e6bec4a 100644 --- a/src/Struct/OneTimeTask.php +++ b/src/Struct/OneTimeTask.php @@ -6,13 +6,10 @@ readonly class OneTimeTask { - public const WHEN_FIRST = 'first'; - public const WHEN_LAST = 'last'; - public function __construct( public string $id, public string $script, - public string $when = self::WHEN_LAST, + public OneTimeTaskWhen $when = OneTimeTaskWhen::LAST, ) { } } diff --git a/src/Struct/OneTimeTaskWhen.php b/src/Struct/OneTimeTaskWhen.php new file mode 100644 index 0000000..ae2ccd1 --- /dev/null +++ b/src/Struct/OneTimeTaskWhen.php @@ -0,0 +1,11 @@ +oneTimeTasks['foo']); static::assertSame('foo', $config->oneTimeTasks['foo']->id); static::assertSame('test', $config->oneTimeTasks['foo']->script); - static::assertSame('last', $config->oneTimeTasks['foo']->when); + static::assertSame(OneTimeTaskWhen::LAST, $config->oneTimeTasks['foo']->when); static::assertNotSame('', $config->hooks->pre); static::assertNotSame('', $config->hooks->post); static::assertNotSame('', $config->hooks->preInstall); @@ -180,12 +181,12 @@ public function testOneTimeTasksWithWhenField(): void $config = ConfigFactory::create(__DIR__ . '/_fixtures/maintenance-mode', $this->createMockApplication()); static::assertArrayHasKey('foo', $config->oneTimeTasks); - static::assertSame('last', $config->oneTimeTasks['foo']->when); + static::assertSame(OneTimeTaskWhen::LAST, $config->oneTimeTasks['foo']->when); static::assertArrayHasKey('early-task', $config->oneTimeTasks); - static::assertSame('first', $config->oneTimeTasks['early-task']->when); + static::assertSame(OneTimeTaskWhen::FIRST, $config->oneTimeTasks['early-task']->when); static::assertArrayHasKey('late-task', $config->oneTimeTasks); - static::assertSame('last', $config->oneTimeTasks['late-task']->when); + static::assertSame(OneTimeTaskWhen::LAST, $config->oneTimeTasks['late-task']->when); } } diff --git a/tests/Services/OneTimeTasksTest.php b/tests/Services/OneTimeTasksTest.php index cdab5ff..271a0bf 100644 --- a/tests/Services/OneTimeTasksTest.php +++ b/tests/Services/OneTimeTasksTest.php @@ -10,6 +10,8 @@ use Shopware\Deployment\Config\ProjectConfiguration; use Shopware\Deployment\Helper\ProcessHelper; use Shopware\Deployment\Services\OneTimeTasks; +use Shopware\Deployment\Struct\OneTimeTask; +use Shopware\Deployment\Struct\OneTimeTaskWhen; use Symfony\Component\Console\Output\OutputInterface; #[CoversClass(OneTimeTasks::class)] @@ -29,7 +31,7 @@ public function testNoTasks(): void $configuration = new ProjectConfiguration(); $tasks = new OneTimeTasks($processHelper, $connection, $configuration); - $tasks->execute($output); + $tasks->execute($output, OneTimeTaskWhen::LAST); } public function testNoTasksNoTable(): void @@ -47,7 +49,7 @@ public function testNoTasksNoTable(): void $configuration = new ProjectConfiguration(); $tasks = new OneTimeTasks($processHelper, $connection, $configuration); - $tasks->execute($output); + $tasks->execute($output, OneTimeTaskWhen::FIRST); } public function testTask(): void @@ -68,11 +70,11 @@ public function testTask(): void $configuration = new ProjectConfiguration(); $configuration->oneTimeTasks = [ - 'test' => new \Shopware\Deployment\Struct\OneTimeTask('test', 'echo "test"', 'last'), + 'test' => new OneTimeTask('test', 'echo "test"', OneTimeTaskWhen::LAST), ]; $tasks = new OneTimeTasks($processHelper, $connection, $configuration); - $tasks->execute($output); + $tasks->execute($output, OneTimeTaskWhen::LAST); } public function testTaskAlreadyExecuted(): void @@ -88,11 +90,11 @@ public function testTaskAlreadyExecuted(): void $configuration = new ProjectConfiguration(); $configuration->oneTimeTasks = [ - 'test' => new \Shopware\Deployment\Struct\OneTimeTask('test', 'echo "test"', 'last'), + 'test' => new OneTimeTask('test', 'echo "test"', OneTimeTaskWhen::LAST), ]; $tasks = new OneTimeTasks($processHelper, $connection, $configuration); - $tasks->execute($output); + $tasks->execute($output, OneTimeTaskWhen::LAST); } public function testRemove(): void @@ -120,12 +122,12 @@ public function testTaskWithWhenFirst(): void $configuration = new ProjectConfiguration(); $configuration->oneTimeTasks = [ - 'first-task' => new \Shopware\Deployment\Struct\OneTimeTask('first-task', 'echo "first"', 'first'), - 'last-task' => new \Shopware\Deployment\Struct\OneTimeTask('last-task', 'echo "last"', 'last'), + 'first-task' => new OneTimeTask('first-task', 'echo "first"', OneTimeTaskWhen::FIRST), + 'last-task' => new OneTimeTask('last-task', 'echo "last"', OneTimeTaskWhen::LAST), ]; $tasks = new OneTimeTasks($processHelper, $connection, $configuration); - $tasks->execute($output, 'first'); + $tasks->execute($output, OneTimeTaskWhen::FIRST); } public function testTaskWithWhenLast(): void @@ -142,33 +144,33 @@ public function testTaskWithWhenLast(): void $configuration = new ProjectConfiguration(); $configuration->oneTimeTasks = [ - 'first-task' => new \Shopware\Deployment\Struct\OneTimeTask('first-task', 'echo "first"', 'first'), - 'last-task' => new \Shopware\Deployment\Struct\OneTimeTask('last-task', 'echo "last"', 'last'), + 'first-task' => new OneTimeTask('first-task', 'echo "first"', OneTimeTaskWhen::FIRST), + 'last-task' => new OneTimeTask('last-task', 'echo "last"', OneTimeTaskWhen::LAST), ]; $tasks = new OneTimeTasks($processHelper, $connection, $configuration); - $tasks->execute($output, 'last'); + $tasks->execute($output, OneTimeTaskWhen::LAST); } public function testTaskWithoutWhenFilterExecutesAll(): void { $output = $this->createMock(OutputInterface::class); - $output->expects($this->exactly(2))->method('writeln'); + $output->expects($this->exactly(1))->method('writeln'); $processHelper = $this->createMock(ProcessHelper::class); - $processHelper->expects($this->exactly(2))->method('runAndTail'); + $processHelper->expects($this->exactly(1))->method('runAndTail'); $connection = $this->createMock(Connection::class); $connection->expects($this->once())->method('fetchAllAssociativeIndexed')->willReturn([]); - $connection->expects($this->exactly(2))->method('executeStatement'); + $connection->expects($this->exactly(1))->method('executeStatement'); $configuration = new ProjectConfiguration(); $configuration->oneTimeTasks = [ - 'first-task' => new \Shopware\Deployment\Struct\OneTimeTask('first-task', 'echo "first"', 'first'), - 'last-task' => new \Shopware\Deployment\Struct\OneTimeTask('last-task', 'echo "last"', 'last'), + 'first-task' => new OneTimeTask('first-task', 'echo "first"', OneTimeTaskWhen::FIRST), + 'last-task' => new OneTimeTask('last-task', 'echo "last"', OneTimeTaskWhen::LAST), ]; $tasks = new OneTimeTasks($processHelper, $connection, $configuration); - $tasks->execute($output, null); + $tasks->execute($output, OneTimeTaskWhen::FIRST); } } From fac48d7f77b42ce791ae7e850cc5ef73c4a38c9d Mon Sep 17 00:00:00 2001 From: Soner Sayakci Date: Wed, 19 Nov 2025 11:00:24 +0100 Subject: [PATCH 4/7] fix: remove unnecessary null check for when option in one-time tasks --- src/Services/OneTimeTasks.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Services/OneTimeTasks.php b/src/Services/OneTimeTasks.php index 1205f64..1f13b7e 100644 --- a/src/Services/OneTimeTasks.php +++ b/src/Services/OneTimeTasks.php @@ -24,7 +24,7 @@ public function execute(OutputInterface $output, OneTimeTaskWhen $when): void $executed = $this->getExecutedTasks(); foreach ($this->configuration->oneTimeTasks as $id => $task) { - if ($when !== null && $task->when !== $when) { + if ($task->when !== $when) { continue; } From ad009dd38b0397ed2822d3b0f122966c0146a8fb Mon Sep 17 00:00:00 2001 From: Soner Sayakci Date: Wed, 19 Nov 2025 13:34:01 +0100 Subject: [PATCH 5/7] ci: use shopware-cli for setup --- .github/workflows/integration.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 1f6d082..5f978ed 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -133,8 +133,15 @@ jobs: sudo -u mysql mysqld --datadir=/var/lib/mysql --default-time-zone=SYSTEM --initialize-insecure sudo systemctl start mysql + - name: Install shopware-cli + uses: shopware/shopware-cli-action@v2 + - name: Create new Shopware Project - run: composer create-project shopware/production:6.5.8.8 . --no-interaction + run: | + shopware-cli project create shop 6.5.8.8 --no-audit + mv shop/* . + mv shop/.* . || true + rm -rf shop - name: Checkout uses: actions/checkout@v4 From b8f4ccde6f6a3f89385dadabefa075c43c2338b5 Mon Sep 17 00:00:00 2001 From: Soner Sayakci Date: Tue, 16 Dec 2025 14:53:14 +0900 Subject: [PATCH 6/7] refactor: rename OneTimeTaskWhen enum from FIRST/LAST to BEFORE/AFTER MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Improve semantic clarity by renaming enum cases to better describe when tasks execute relative to the Shopware update process. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- src/Config/ConfigFactory.php | 2 +- src/Services/UpgradeManager.php | 8 ++-- src/Struct/OneTimeTask.php | 2 +- src/Struct/OneTimeTaskWhen.php | 10 ++++- tests/Config/ConfigFactoryTest.php | 8 ++-- .../maintenance-mode/.shopware-project.yml | 4 +- tests/Services/OneTimeTasksTest.php | 44 +++++++++---------- 7 files changed, 42 insertions(+), 36 deletions(-) diff --git a/src/Config/ConfigFactory.php b/src/Config/ConfigFactory.php index 9cbab46..a76e46b 100644 --- a/src/Config/ConfigFactory.php +++ b/src/Config/ConfigFactory.php @@ -78,7 +78,7 @@ private static function fillConfig(ProjectConfiguration $projectConfiguration, a if (isset($deployment['one-time-tasks']) && \is_array($deployment['one-time-tasks'])) { foreach ($deployment['one-time-tasks'] as $task) { if (isset($task['id'], $task['script']) && \is_string($task['id']) && \is_string($task['script'])) { - $when = OneTimeTaskWhen::LAST; + $when = OneTimeTaskWhen::AFTER; if (isset($task['when']) && \is_string($task['when'])) { $when = OneTimeTaskWhen::from($task['when']); } diff --git a/src/Services/UpgradeManager.php b/src/Services/UpgradeManager.php index 85ab3df..a3fb22c 100644 --- a/src/Services/UpgradeManager.php +++ b/src/Services/UpgradeManager.php @@ -33,8 +33,8 @@ public function run(RunConfiguration $configuration, OutputInterface $output): v $this->hookExecutor->execute(HookExecutor::HOOK_PRE_UPDATE); - // Execute one-time tasks that should run first - $this->oneTimeTasks->execute($output, OneTimeTaskWhen::FIRST); + // Execute one-time tasks that should run before the update + $this->oneTimeTasks->execute($output, OneTimeTaskWhen::BEFORE); if ($this->configuration->maintenance->enabled) { $this->state->enableMaintenanceMode(); @@ -93,8 +93,8 @@ public function run(RunConfiguration $configuration, OutputInterface $output): v $this->processHelper->console(['theme:compile', '--active-only']); } - // Execute one-time tasks that should run last - $this->oneTimeTasks->execute($output, OneTimeTaskWhen::LAST); + // Execute one-time tasks that should run after the update + $this->oneTimeTasks->execute($output, OneTimeTaskWhen::AFTER); $this->hookExecutor->execute(HookExecutor::HOOK_POST_UPDATE); diff --git a/src/Struct/OneTimeTask.php b/src/Struct/OneTimeTask.php index e6bec4a..3da188a 100644 --- a/src/Struct/OneTimeTask.php +++ b/src/Struct/OneTimeTask.php @@ -9,7 +9,7 @@ public function __construct( public string $id, public string $script, - public OneTimeTaskWhen $when = OneTimeTaskWhen::LAST, + public OneTimeTaskWhen $when = OneTimeTaskWhen::AFTER, ) { } } diff --git a/src/Struct/OneTimeTaskWhen.php b/src/Struct/OneTimeTaskWhen.php index ae2ccd1..de3db10 100644 --- a/src/Struct/OneTimeTaskWhen.php +++ b/src/Struct/OneTimeTaskWhen.php @@ -4,8 +4,14 @@ namespace Shopware\Deployment\Struct; +/** + * Defines when a one-time task should be executed during deployment. + * + * - BEFORE: Execute before the Shopware update (system:update) is run + * - AFTER: Execute after the Shopware update is complete (default) + */ enum OneTimeTaskWhen: string { - case FIRST = 'first'; - case LAST = 'last'; + case BEFORE = 'before'; + case AFTER = 'after'; } diff --git a/tests/Config/ConfigFactoryTest.php b/tests/Config/ConfigFactoryTest.php index 54f24c9..af20f83 100644 --- a/tests/Config/ConfigFactoryTest.php +++ b/tests/Config/ConfigFactoryTest.php @@ -50,7 +50,7 @@ public function testExistingConfigTest(string $configDir): void static::assertInstanceOf(\Shopware\Deployment\Struct\OneTimeTask::class, $config->oneTimeTasks['foo']); static::assertSame('foo', $config->oneTimeTasks['foo']->id); static::assertSame('test', $config->oneTimeTasks['foo']->script); - static::assertSame(OneTimeTaskWhen::LAST, $config->oneTimeTasks['foo']->when); + static::assertSame(OneTimeTaskWhen::AFTER, $config->oneTimeTasks['foo']->when); static::assertNotSame('', $config->hooks->pre); static::assertNotSame('', $config->hooks->post); static::assertNotSame('', $config->hooks->preInstall); @@ -181,12 +181,12 @@ public function testOneTimeTasksWithWhenField(): void $config = ConfigFactory::create(__DIR__ . '/_fixtures/maintenance-mode', $this->createMockApplication()); static::assertArrayHasKey('foo', $config->oneTimeTasks); - static::assertSame(OneTimeTaskWhen::LAST, $config->oneTimeTasks['foo']->when); + static::assertSame(OneTimeTaskWhen::AFTER, $config->oneTimeTasks['foo']->when); static::assertArrayHasKey('early-task', $config->oneTimeTasks); - static::assertSame(OneTimeTaskWhen::FIRST, $config->oneTimeTasks['early-task']->when); + static::assertSame(OneTimeTaskWhen::BEFORE, $config->oneTimeTasks['early-task']->when); static::assertArrayHasKey('late-task', $config->oneTimeTasks); - static::assertSame(OneTimeTaskWhen::LAST, $config->oneTimeTasks['late-task']->when); + static::assertSame(OneTimeTaskWhen::AFTER, $config->oneTimeTasks['late-task']->when); } } diff --git a/tests/Config/_fixtures/maintenance-mode/.shopware-project.yml b/tests/Config/_fixtures/maintenance-mode/.shopware-project.yml index 8dedd03..42c0583 100644 --- a/tests/Config/_fixtures/maintenance-mode/.shopware-project.yml +++ b/tests/Config/_fixtures/maintenance-mode/.shopware-project.yml @@ -28,7 +28,7 @@ deployment: script: test - id: early-task script: echo "Running early" - when: first + when: before - id: late-task script: echo "Running late" - when: last + when: after diff --git a/tests/Services/OneTimeTasksTest.php b/tests/Services/OneTimeTasksTest.php index 271a0bf..fc4ec46 100644 --- a/tests/Services/OneTimeTasksTest.php +++ b/tests/Services/OneTimeTasksTest.php @@ -31,7 +31,7 @@ public function testNoTasks(): void $configuration = new ProjectConfiguration(); $tasks = new OneTimeTasks($processHelper, $connection, $configuration); - $tasks->execute($output, OneTimeTaskWhen::LAST); + $tasks->execute($output, OneTimeTaskWhen::AFTER); } public function testNoTasksNoTable(): void @@ -49,7 +49,7 @@ public function testNoTasksNoTable(): void $configuration = new ProjectConfiguration(); $tasks = new OneTimeTasks($processHelper, $connection, $configuration); - $tasks->execute($output, OneTimeTaskWhen::FIRST); + $tasks->execute($output, OneTimeTaskWhen::BEFORE); } public function testTask(): void @@ -70,11 +70,11 @@ public function testTask(): void $configuration = new ProjectConfiguration(); $configuration->oneTimeTasks = [ - 'test' => new OneTimeTask('test', 'echo "test"', OneTimeTaskWhen::LAST), + 'test' => new OneTimeTask('test', 'echo "test"', OneTimeTaskWhen::AFTER), ]; $tasks = new OneTimeTasks($processHelper, $connection, $configuration); - $tasks->execute($output, OneTimeTaskWhen::LAST); + $tasks->execute($output, OneTimeTaskWhen::AFTER); } public function testTaskAlreadyExecuted(): void @@ -90,11 +90,11 @@ public function testTaskAlreadyExecuted(): void $configuration = new ProjectConfiguration(); $configuration->oneTimeTasks = [ - 'test' => new OneTimeTask('test', 'echo "test"', OneTimeTaskWhen::LAST), + 'test' => new OneTimeTask('test', 'echo "test"', OneTimeTaskWhen::AFTER), ]; $tasks = new OneTimeTasks($processHelper, $connection, $configuration); - $tasks->execute($output, OneTimeTaskWhen::LAST); + $tasks->execute($output, OneTimeTaskWhen::AFTER); } public function testRemove(): void @@ -108,13 +108,13 @@ public function testRemove(): void $tasks->remove('test'); } - public function testTaskWithWhenFirst(): void + public function testTaskWithWhenBefore(): void { $output = $this->createMock(OutputInterface::class); - $output->expects($this->once())->method('writeln')->with('Running one-time task first-task'); + $output->expects($this->once())->method('writeln')->with('Running one-time task before-task'); $processHelper = $this->createMock(ProcessHelper::class); - $processHelper->expects($this->once())->method('runAndTail')->with('echo "first"'); + $processHelper->expects($this->once())->method('runAndTail')->with('echo "before"'); $connection = $this->createMock(Connection::class); $connection->expects($this->once())->method('fetchAllAssociativeIndexed')->willReturn([]); @@ -122,21 +122,21 @@ public function testTaskWithWhenFirst(): void $configuration = new ProjectConfiguration(); $configuration->oneTimeTasks = [ - 'first-task' => new OneTimeTask('first-task', 'echo "first"', OneTimeTaskWhen::FIRST), - 'last-task' => new OneTimeTask('last-task', 'echo "last"', OneTimeTaskWhen::LAST), + 'before-task' => new OneTimeTask('before-task', 'echo "before"', OneTimeTaskWhen::BEFORE), + 'after-task' => new OneTimeTask('after-task', 'echo "after"', OneTimeTaskWhen::AFTER), ]; $tasks = new OneTimeTasks($processHelper, $connection, $configuration); - $tasks->execute($output, OneTimeTaskWhen::FIRST); + $tasks->execute($output, OneTimeTaskWhen::BEFORE); } - public function testTaskWithWhenLast(): void + public function testTaskWithWhenAfter(): void { $output = $this->createMock(OutputInterface::class); - $output->expects($this->once())->method('writeln')->with('Running one-time task last-task'); + $output->expects($this->once())->method('writeln')->with('Running one-time task after-task'); $processHelper = $this->createMock(ProcessHelper::class); - $processHelper->expects($this->once())->method('runAndTail')->with('echo "last"'); + $processHelper->expects($this->once())->method('runAndTail')->with('echo "after"'); $connection = $this->createMock(Connection::class); $connection->expects($this->once())->method('fetchAllAssociativeIndexed')->willReturn([]); @@ -144,15 +144,15 @@ public function testTaskWithWhenLast(): void $configuration = new ProjectConfiguration(); $configuration->oneTimeTasks = [ - 'first-task' => new OneTimeTask('first-task', 'echo "first"', OneTimeTaskWhen::FIRST), - 'last-task' => new OneTimeTask('last-task', 'echo "last"', OneTimeTaskWhen::LAST), + 'before-task' => new OneTimeTask('before-task', 'echo "before"', OneTimeTaskWhen::BEFORE), + 'after-task' => new OneTimeTask('after-task', 'echo "after"', OneTimeTaskWhen::AFTER), ]; $tasks = new OneTimeTasks($processHelper, $connection, $configuration); - $tasks->execute($output, OneTimeTaskWhen::LAST); + $tasks->execute($output, OneTimeTaskWhen::AFTER); } - public function testTaskWithoutWhenFilterExecutesAll(): void + public function testTaskWithWhenFilterExecutesOnlyMatchingTasks(): void { $output = $this->createMock(OutputInterface::class); $output->expects($this->exactly(1))->method('writeln'); @@ -166,11 +166,11 @@ public function testTaskWithoutWhenFilterExecutesAll(): void $configuration = new ProjectConfiguration(); $configuration->oneTimeTasks = [ - 'first-task' => new OneTimeTask('first-task', 'echo "first"', OneTimeTaskWhen::FIRST), - 'last-task' => new OneTimeTask('last-task', 'echo "last"', OneTimeTaskWhen::LAST), + 'before-task' => new OneTimeTask('before-task', 'echo "before"', OneTimeTaskWhen::BEFORE), + 'after-task' => new OneTimeTask('after-task', 'echo "after"', OneTimeTaskWhen::AFTER), ]; $tasks = new OneTimeTasks($processHelper, $connection, $configuration); - $tasks->execute($output, OneTimeTaskWhen::FIRST); + $tasks->execute($output, OneTimeTaskWhen::BEFORE); } } From b3488a70c6685677db5022dea0aac7f1519b124d Mon Sep 17 00:00:00 2001 From: Soner Sayakci Date: Tue, 16 Dec 2025 15:03:14 +0900 Subject: [PATCH 7/7] refactor: migrate DI config from XML to PHP MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace deprecated XmlFileLoader with PhpFileLoader to resolve Symfony 7.4 deprecation warning. Service configuration moved from services.xml to services.php using the modern PHP DSL. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- src/Application.php | 6 ++--- src/Resources/config/services.php | 39 +++++++++++++++++++++++++++++++ src/Resources/config/services.xml | 27 --------------------- 3 files changed, 42 insertions(+), 30 deletions(-) create mode 100644 src/Resources/config/services.php delete mode 100644 src/Resources/config/services.xml diff --git a/src/Application.php b/src/Application.php index 79435bc..6208746 100644 --- a/src/Application.php +++ b/src/Application.php @@ -17,7 +17,7 @@ use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Dumper\XmlDumper; -use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; +use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; use Symfony\Component\EventDispatcher\Attribute\AsEventListener; use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass; use Symfony\Component\Filesystem\Filesystem; @@ -61,8 +61,8 @@ private function createContainer(): ContainerBuilder InstalledVersions::reload(include $projectDir . '/vendor/composer/installed.php'); (new DotenvLoader($projectDir))->load(); - $loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/Resources/config')); - $loader->load('services.xml'); + $loader = new PhpFileLoader($container, new FileLocator(__DIR__ . '/Resources/config')); + $loader->load('services.php'); $container->compile(); $container->set(self::class, $this); diff --git a/src/Resources/config/services.php b/src/Resources/config/services.php new file mode 100644 index 0000000..4ec36ca --- /dev/null +++ b/src/Resources/config/services.php @@ -0,0 +1,39 @@ +services() + ->defaults() + ->autowire() + ->autoconfigure() + ->public(); + + $services->set('event_dispatcher', Symfony\Component\EventDispatcher\EventDispatcher::class); + $services->alias(EventDispatcherInterface::class, 'event_dispatcher'); + + $services->set(Doctrine\DBAL\Connection::class) + ->factory([MySQLFactory::class, 'createAndRetry']); + + $services->load('Shopware\\Deployment\\', '../../') + ->exclude('../../{Application.php,ApplicationOutput.php,Struct,Resources}'); + + $services->set(Application::class) + ->synthetic(); + + $services->set(ProjectConfiguration::class) + ->factory([ConfigFactory::class, 'create']) + ->args([ + '%kernel.project_dir%', + service(Application::class), + ]); +}; diff --git a/src/Resources/config/services.xml b/src/Resources/config/services.xml deleted file mode 100644 index 81a2d0e..0000000 --- a/src/Resources/config/services.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - %kernel.project_dir% - - - -