Skip to content

Commit a539f7f

Browse files
committed
Merge branch 'update-command'
2 parents 2f79d43 + f961d2e commit a539f7f

17 files changed

Lines changed: 306 additions & 126 deletions

src/Command/PackagesUpdateCommand.php

Lines changed: 69 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
namespace CodedMonkey\Dirigent\Command;
44

55
use CodedMonkey\Dirigent\Doctrine\Repository\PackageRepository;
6+
use CodedMonkey\Dirigent\Entity\PackageUpdateSource;
67
use CodedMonkey\Dirigent\Message\SchedulePackageUpdate;
8+
use CodedMonkey\Dirigent\Message\UpdatePackage;
79
use Symfony\Component\Console\Attribute\AsCommand;
810
use Symfony\Component\Console\Command\Command;
911
use Symfony\Component\Console\Input\InputArgument;
@@ -16,6 +18,32 @@
1618
#[AsCommand(
1719
name: 'packages:update',
1820
description: 'Schedules packages for update',
21+
help: <<<'TXT'
22+
The <info>%command.name%</info> command schedules packages in the registry for update:
23+
24+
<info>%command.full_name%</info>
25+
26+
<fg=black;bg=yellow> </>
27+
<fg=black;bg=yellow> Make sure a worker is running, or use the --sync option. </>
28+
<fg=black;bg=yellow> </>
29+
30+
By default, only packages that have passed the periodic update interval will be scheduled for update.
31+
32+
Use the <comment>--all</comment> option to schedule all packages for update instead:
33+
34+
<info>%command.full_name% --all</info>
35+
36+
Package updates are scheduled somewhere in the next 12 minutes, except when specifying package names, then they are
37+
scheduled immediately. Check the worker's message queue if updates are not being executed.
38+
39+
It's possible to update specific packages by passing their name as arguments:
40+
41+
<info>%command.full_name% psr/cache psr/log</info>
42+
43+
Use the <comment>--sync</comment> option to skip the worker and update packages synchronously:
44+
45+
<info>%command.full_name% psr/cache psr/log --sync</info>
46+
TXT,
1947
)]
2048
class PackagesUpdateCommand extends Command
2149
{
@@ -30,48 +58,67 @@ protected function configure(): void
3058
{
3159
$this
3260
->addArgument('package', InputArgument::OPTIONAL, 'Package to update')
33-
->addOption('force', null, InputOption::VALUE_NONE, 'Forces a re-crawl of all packages');
61+
->addOption('all', null, InputOption::VALUE_NONE, 'Update all packages')
62+
->addOption('sync', null, InputOption::VALUE_NONE, 'Updates packages synchronously');
3463
}
3564

3665
protected function execute(InputInterface $input, OutputInterface $output): int
3766
{
3867
$io = new SymfonyStyle($input, $output);
3968

40-
$force = $input->getOption('force');
41-
$packageName = $input->getArgument('package');
69+
$all = $input->getOption('all');
70+
$packageNames = (array) ($input->getArgument('package') ?? []);
71+
$sync = $input->getOption('sync');
4272

43-
$randomTimes = true;
44-
$reschedule = false;
73+
if ($sync && !count($packageNames)) {
74+
$io->error('Specify a package to update when using the --sync option.');
4575

46-
if ($packageName) {
47-
if (null === $package = $this->packageRepository->findOneByName($packageName)) {
48-
$io->error("Package $packageName not found");
76+
return Command::FAILURE;
77+
}
4978

50-
return Command::FAILURE;
51-
}
79+
$randomTimes = true; // Randomize time of updates
80+
$source = PackageUpdateSource::Stale;
81+
82+
if (count($packageNames)) {
83+
$packageIds = [];
84+
foreach ($packageNames as $packageName) {
85+
if (null === $package = $this->packageRepository->findOneByName($packageName)) {
86+
$io->error("Package $packageName not found");
5287

53-
$io->writeln("Scheduling package $packageName for update...");
88+
return Command::FAILURE;
89+
}
5490

55-
$packages = [['id' => $package->getId()]];
91+
$io->writeln("Scheduling package $packageName for update...");
92+
$packageIds[] = $package->getId();
93+
}
5694

5795
$randomTimes = false;
58-
$reschedule = true;
59-
} elseif ($force) {
96+
$source = PackageUpdateSource::Manual;
97+
} elseif ($all) {
6098
$io->writeln('Scheduling all packages for update...');
61-
$packages = $this->packageRepository->getAllPackageIds();
99+
$packageIds = $this->packageRepository->getAllPackageIds();
62100

63-
$reschedule = true;
101+
$source = PackageUpdateSource::Manual;
64102
} else {
65103
$io->writeln('Scheduling stale packages for update...');
66-
$packages = $this->packageRepository->getStalePackageIds();
104+
$packageIds = $this->packageRepository->getStalePackageIds();
67105
}
68106

69-
foreach ($packages as $package) {
70-
$this->messenger->dispatch(new SchedulePackageUpdate($package['id'], randomTime: $randomTimes, reschedule: $reschedule, forceRefresh: $force));
71-
}
107+
$packageCount = count($packageIds);
108+
109+
if ($sync) {
110+
foreach ($packageIds as $packageId) {
111+
$this->messenger->dispatch(new UpdatePackage($packageId, $source));
112+
}
72113

73-
$packageCount = count($packages);
74-
$io->success("Scheduled $packageCount package(s) for update.");
114+
$io->success("Updated $packageCount package(s).");
115+
} else {
116+
foreach ($packageIds as $packageId) {
117+
$this->messenger->dispatch(new SchedulePackageUpdate($packageId, $source, $randomTimes));
118+
}
119+
120+
$io->success("Scheduled $packageCount package(s) for update.");
121+
}
75122

76123
return Command::SUCCESS;
77124
}

src/Controller/ApiController.php

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@
88
use CodedMonkey\Dirigent\Doctrine\Entity\PackageFetchStrategy;
99
use CodedMonkey\Dirigent\Doctrine\Repository\PackageRepository;
1010
use CodedMonkey\Dirigent\Doctrine\Repository\VersionRepository;
11+
use CodedMonkey\Dirigent\Entity\PackageUpdateSource;
1112
use CodedMonkey\Dirigent\Message\TrackInstallations;
1213
use CodedMonkey\Dirigent\Message\UpdatePackage;
1314
use CodedMonkey\Dirigent\Package\PackageDistributionResolver;
1415
use CodedMonkey\Dirigent\Package\PackageMetadataResolver;
1516
use CodedMonkey\Dirigent\Package\PackageProviderManager;
17+
use Doctrine\ORM\EntityManagerInterface;
1618
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
1719
use Symfony\Component\DependencyInjection\Attribute\Autowire;
1820
use Symfony\Component\HttpFoundation\BinaryFileResponse;
@@ -30,12 +32,15 @@
3032
class ApiController extends AbstractController
3133
{
3234
public function __construct(
35+
private readonly EntityManagerInterface $entityManager,
3336
private readonly PackageRepository $packageRepository,
3437
private readonly VersionRepository $versionRepository,
3538
private readonly PackageMetadataResolver $metadataResolver,
3639
private readonly PackageDistributionResolver $distributionResolver,
3740
private readonly PackageProviderManager $providerManager,
3841
private readonly MessageBusInterface $messenger,
42+
#[Autowire(param: 'dirigent.packages.dynamic_updates')]
43+
private readonly bool $dynamicUpdatesEnabled,
3944
#[Autowire(param: 'dirigent.metadata.mirror_vcs_repositories')]
4045
private readonly bool $mirrorVcsRepositories = false,
4146
) {
@@ -80,15 +85,13 @@ public function root(RouterInterface $router): JsonResponse
8085
#[IsGrantedAccess]
8186
public function packageMetadata(Request $request): Response
8287
{
83-
$packageName = $request->attributes->get('package');
88+
$packageName = $request->attributes->getString('package');
8489
$basePackageName = u($packageName)->trimSuffix('~dev')->toString();
8590

86-
if (null === $package = $this->findPackage($basePackageName)) {
91+
if (null === $this->findPackage($basePackageName)) {
8792
throw $this->createNotFoundException();
8893
}
8994

90-
$this->messenger->dispatch(new UpdatePackage($package->getId()));
91-
9295
if (!$this->providerManager->exists($packageName)) {
9396
throw $this->createNotFoundException();
9497
}
@@ -125,8 +128,6 @@ public function packageDistribution(Request $request, string $reference, string
125128
throw $this->createNotFoundException();
126129
}
127130

128-
$this->messenger->dispatch(new UpdatePackage($package->getId()));
129-
130131
if (null === $version = $this->versionRepository->findOneByNormalizedVersion($package, $versionName)) {
131132
throw $this->createNotFoundException();
132133
}
@@ -166,24 +167,41 @@ public function trackInstallations(Request $request): Response
166167
return new JsonResponse(['status' => 'success'], Response::HTTP_CREATED);
167168
}
168169

169-
private function findPackage(string $packageName): ?Package
170+
private function findPackage(string $packageName, ?bool $create = false): ?Package
170171
{
171-
// Search for the package in the database
172-
if (null !== $package = $this->packageRepository->findOneByName($packageName)) {
173-
return $package;
174-
}
172+
$this->entityManager->beginTransaction();
173+
174+
try {
175+
// Search for the package in the database
176+
if (null === $package = $this->packageRepository->findOneByName($packageName)) {
177+
if (!$create || !$this->dynamicUpdatesEnabled) {
178+
// It doesn't exist in the database and we can't create it dynamically
179+
return null;
180+
}
181+
182+
// Search for the package in external registries
183+
if (null === $registry = $this->metadataResolver->findPackageProvider($packageName)) {
184+
return null;
185+
}
186+
187+
$package = new Package();
188+
$package->setName($packageName);
189+
$package->setMirrorRegistry($registry);
190+
$package->setFetchStrategy($this->mirrorVcsRepositories ? PackageFetchStrategy::Vcs : PackageFetchStrategy::Mirror);
191+
192+
$this->packageRepository->save($package, true);
193+
}
175194

176-
// Attempt to find a package from external registries
177-
if (null === $registry = $this->metadataResolver->findPackageProvider($packageName)) {
178-
return null;
179-
}
195+
if ($this->dynamicUpdatesEnabled) {
196+
$this->messenger->dispatch(new UpdatePackage($package->getId(), PackageUpdateSource::Dynamic));
197+
}
198+
} catch (\Throwable $exception) {
199+
$this->entityManager->rollback();
180200

181-
$package = new Package();
182-
$package->setName($packageName);
183-
$package->setMirrorRegistry($registry);
184-
$package->setFetchStrategy($this->mirrorVcsRepositories ? PackageFetchStrategy::Vcs : PackageFetchStrategy::Mirror);
201+
throw $exception;
202+
}
185203

186-
$this->packageRepository->save($package, true);
204+
$this->entityManager->commit();
187205

188206
return $package;
189207
}

src/Controller/Dashboard/DashboardPackagesController.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use CodedMonkey\Dirigent\Doctrine\Entity\PackageFetchStrategy;
99
use CodedMonkey\Dirigent\Doctrine\Repository\PackageRepository;
1010
use CodedMonkey\Dirigent\EasyAdmin\PackagePaginator;
11+
use CodedMonkey\Dirigent\Entity\PackageUpdateSource;
1112
use CodedMonkey\Dirigent\Form\PackageAddMirroringFormType;
1213
use CodedMonkey\Dirigent\Form\PackageAddVcsFormType;
1314
use CodedMonkey\Dirigent\Form\PackageFormType;
@@ -112,7 +113,7 @@ public function addMirroring(Request $request): Response
112113

113114
$this->packageRepository->save($package, true);
114115

115-
$this->messenger->dispatch(new UpdatePackage($package->getId()));
116+
$this->messenger->dispatch(new UpdatePackage($package->getId(), PackageUpdateSource::Manual));
116117

117118
$results[] = [
118119
'packageName' => $packageName,
@@ -148,7 +149,7 @@ public function addVcsRepository(Request $request): Response
148149
$package = $form->getData();
149150
$this->packageRepository->save($package, true);
150151

151-
$this->messenger->dispatch(new UpdatePackage($package->getId()));
152+
$this->messenger->dispatch(new UpdatePackage($package->getId(), PackageUpdateSource::Manual));
152153

153154
return $this->redirectToRoute('dashboard_packages');
154155
}
@@ -171,7 +172,7 @@ public function edit(Request $request, #[MapPackage] Package $package): Response
171172
$package = $form->getData();
172173
$this->packageRepository->save($package, true);
173174

174-
$this->messenger->dispatch(new UpdatePackage($package->getId()));
175+
$this->messenger->dispatch(new UpdatePackage($package->getId(), PackageUpdateSource::Manual));
175176

176177
return $this->redirectToRoute('dashboard_packages_info', ['package' => $package->getName()]);
177178
}
@@ -186,7 +187,7 @@ public function edit(Request $request, #[MapPackage] Package $package): Response
186187
#[IsGranted('ROLE_ADMIN')]
187188
public function update(#[MapPackage] Package $package): Response
188189
{
189-
$this->messenger->dispatch(new UpdatePackage($package->getId(), forceRefresh: true));
190+
$this->messenger->dispatch(new UpdatePackage($package->getId(), PackageUpdateSource::Manual));
190191

191192
return $this->redirectToRoute('dashboard_packages_info', ['package' => $package->getName()]);
192193
}

src/DependencyInjection/DirigentConfiguration.php

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -69,15 +69,6 @@ public function getConfigTreeBuilder(): TreeBuilder
6969
->scalarNode('path')->defaultValue('%kernel.project_dir%/storage')->end()
7070
->end()
7171
->end()
72-
->arrayNode('packages')
73-
->addDefaultsIfNotSet()
74-
->children()
75-
->booleanNode('dynamic_updates')->defaultTrue()->end()
76-
->scalarNode('dynamic_update_delay')->defaultValue('PT4H')->end()
77-
->booleanNode('periodic_updates')->defaultTrue()->end()
78-
->scalarNode('periodic_update_interval')->defaultValue('P1W')->end()
79-
->end()
80-
->end()
8172
->arrayNode('dist_mirroring')
8273
->canBeEnabled()
8374
->children()
@@ -87,6 +78,7 @@ public function getConfigTreeBuilder(): TreeBuilder
8778
->end();
8879

8980
$this->addMetadataSection($rootNode);
81+
$this->addPackagesSection($rootNode);
9082

9183
return $treeBuilder;
9284
}
@@ -104,4 +96,32 @@ private function addMetadataSection(ArrayNodeDefinition|NodeDefinition $rootNode
10496
->end()
10597
->end();
10698
}
99+
100+
private function addPackagesSection(ArrayNodeDefinition|NodeDefinition $rootNode): void
101+
{
102+
$rootNode->children()
103+
->arrayNode('packages')
104+
->addDefaultsIfNotSet()
105+
->children()
106+
->booleanNode('dynamic_updates')
107+
->defaultTrue()
108+
->info('Whether to automatically update packages when using the (Composer) API')
109+
->end()
110+
->stringNode('dynamic_update_delay')
111+
->cannotBeEmpty()
112+
->defaultValue('PT4H')
113+
->info('The delay between package updates when using the API, in ISO 8601 duration format')
114+
->end()
115+
->booleanNode('periodic_updates')
116+
->defaultTrue()
117+
->info('Whether to automatically update packages periodically')
118+
->end()
119+
->stringNode('periodic_update_interval')
120+
->cannotBeEmpty()
121+
->defaultValue('P1W')
122+
->info('The interval between periodic package updates, in ISO 8601 duration format')
123+
->end()
124+
->end()
125+
->end();
126+
}
107127
}

0 commit comments

Comments
 (0)