Skip to content

Commit 0b613aa

Browse files
committed
[BUGFIX] Fix typo3:core:update command failing during update
The command detected the right packages but the actual composer update step failed due to several bugs: - Remove dev stability fallback that wrote unresolvable "9999999-dev" constraints to composer.json - Detect all packages depending on typo3/cms-*, not just those with type "typo3-cms-extension" (e.g. helhum/typo3-console) - Check all typo3/cms-* requirements for compatibility, not just typo3/cms-core - Stream composer output directly to terminal instead of buffering it - Prompt to remove unresolvable packages before running the update
1 parent 5506393 commit 0b613aa

File tree

1 file changed

+74
-51
lines changed

1 file changed

+74
-51
lines changed

src/Command/UpdateCoreCommand.php

Lines changed: 74 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
use Symfony\Component\Console\Input\ArrayInput;
2626
use Symfony\Component\Console\Input\InputInterface;
2727
use Symfony\Component\Console\Input\InputOption;
28-
use Symfony\Component\Console\Output\BufferedOutput;
2928
use Symfony\Component\Console\Output\OutputInterface;
3029
use Symfony\Component\Console\Question\ConfirmationQuestion;
3130
use Symfony\Component\Console\Style\SymfonyStyle;
@@ -103,6 +102,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
103102
if (!empty($extensionUpgrades)) {
104103
$extensionRows = [];
105104
$unresolvedRows = [];
105+
$unresolvedNames = [];
106106

107107
foreach ($extensionUpgrades as $upgrade) {
108108
if ($upgrade['newConstraint']) {
@@ -128,18 +128,19 @@ protected function execute(InputInterface $input, OutputInterface $output): int
128128
$upgrade['currentVersion'],
129129
$upgrade['coreConstraint'],
130130
];
131+
$unresolvedNames[] = $upgrade['name'];
131132
}
132133
}
133134

134135
if (!empty($extensionRows)) {
135-
$io->section('Extensions to upgrade');
136-
$io->table(['Extension', 'Installed version', 'Requires typo3/cms-core', 'New version', 'New constraint'], $extensionRows);
136+
$io->section('Packages to upgrade');
137+
$io->table(['Package', 'Installed version', 'TYPO3 constraint', 'New version', 'New constraint'], $extensionRows);
137138
}
138139

139140
if (!empty($unresolvedRows)) {
140-
$io->section('Extensions without a compatible version');
141-
$io->table(['Extension', 'Installed version', 'Requires typo3/cms-core'], $unresolvedRows);
142-
$io->warning('The extensions above have no published version compatible with ' . $targetVersion . '. The update may fail.');
141+
$io->section('Packages without a compatible version');
142+
$io->table(['Package', 'Installed version', 'TYPO3 constraint'], $unresolvedRows);
143+
$io->warning('The packages above have no published version compatible with ' . $targetVersion . '.');
143144
}
144145
}
145146

@@ -148,6 +149,20 @@ protected function execute(InputInterface $input, OutputInterface $output): int
148149
return Command::SUCCESS;
149150
}
150151

152+
// Offer to remove unresolvable packages before proceeding
153+
if (!empty($unresolvedNames)) {
154+
$removeQuestion = new ConfirmationQuestion(
155+
'Remove unresolvable packages (' . implode(', ', $unresolvedNames) . ') from composer.json before updating? [Y/n] ',
156+
true
157+
);
158+
if ($io->askQuestion($removeQuestion)) {
159+
foreach ($unresolvedNames as $name) {
160+
unset($updatedData['require'][$name], $updatedData['require-dev'][$name]);
161+
$packagesToUpdate[] = $name;
162+
}
163+
}
164+
}
165+
151166
$io->writeln('');
152167
$io->writeln('This will run: <info>composer update ' . implode(' ', $packagesToUpdate) . ' -W</info>');
153168
$io->writeln('');
@@ -164,28 +179,19 @@ protected function execute(InputInterface $input, OutputInterface $output): int
164179
$application = new Application();
165180
$application->setAutoExit(false);
166181

167-
$bufferedOutput = new BufferedOutput($output->getVerbosity(), $output->isDecorated());
168182
$arrayInput = new ArrayInput([
169183
'command' => 'update',
170184
'packages' => $packagesToUpdate,
171185
'-W' => true,
172186
]);
173-
$exitCode = $application->run($arrayInput, $bufferedOutput);
187+
$exitCode = $application->run($arrayInput, $output);
174188

175189
if ($exitCode) {
176190
$jsonFile->write($originalData);
177191
$io->error('Failed to update TYPO3 packages. composer.json has been reverted.');
178-
179-
if ($output->isVerbose()) {
180-
$io->section('Composer output');
181-
$output->write($bufferedOutput->fetch());
182-
} else {
183-
$io->note('Run with -v to see the full composer output.');
184-
}
185192
return Command::FAILURE;
186193
}
187194

188-
$output->write($bufferedOutput->fetch());
189195
$io->success('TYPO3 core and extensions updated successfully.');
190196
return Command::SUCCESS;
191197
}
@@ -199,18 +205,20 @@ private function isTypo3Package(string $name): bool
199205
'stable' => BasePackage::STABILITY_STABLE,
200206
];
201207

202-
private const ALL_STABILITIES = [
208+
private const PRE_RELEASE_STABILITIES = [
203209
'stable' => BasePackage::STABILITY_STABLE,
204210
'RC' => BasePackage::STABILITY_RC,
205211
'beta' => BasePackage::STABILITY_BETA,
206212
'alpha' => BasePackage::STABILITY_ALPHA,
207-
'dev' => BasePackage::STABILITY_DEV,
208213
];
209214

210215
/**
211-
* Find installed extensions incompatible with the target TYPO3 version
216+
* Find installed packages incompatible with the target TYPO3 version
212217
* and look up their latest compatible version from remote repositories.
213218
*
219+
* Checks all packages that require any typo3/cms-* package (excluding
220+
* the typo3/cms-* packages themselves, which are handled by the core update).
221+
*
214222
* @return array<int, array{name: string, currentVersion: string, coreConstraint: string, newConstraint: ?string, newDisplayVersion: ?string, stable: bool}>
215223
*/
216224
private function findExtensionUpgrades(string $targetVersion): array
@@ -223,34 +231,50 @@ private function findExtensionUpgrades(string $targetVersion): array
223231
$upgrades = [];
224232

225233
foreach ($installedRepository->getPackages() as $package) {
226-
if ($package->getType() !== 'typo3-cms-extension') {
234+
// Skip typo3/cms-* packages — they are handled by the core update
235+
if ($this->isTypo3Package($package->getName())) {
227236
continue;
228237
}
229238

239+
// Check all typo3/cms-* requirements of this package
240+
$incompatibleLinks = [];
230241
/** @var Link $link */
231242
foreach ($package->getRequires() as $link) {
232-
if ($link->getTarget() === 'typo3/cms-core' && !$targetConstraint->matches($link->getConstraint())) {
233-
// Try stable first, fall back to pre-release versions
234-
$compatibleVersion = $this->findLatestCompatibleVersion($package, $remoteRepositories, $targetVersion, self::STABLE_ONLY);
235-
$stable = true;
236-
237-
if (!$compatibleVersion) {
238-
$compatibleVersion = $this->findLatestCompatibleVersion($package, $remoteRepositories, $targetVersion, self::ALL_STABILITIES);
239-
$stable = false;
240-
}
243+
if ($this->isTypo3Package($link->getTarget()) && !$targetConstraint->matches($link->getConstraint())) {
244+
$incompatibleLinks[] = $link;
245+
}
246+
}
241247

242-
[$newConstraint, $newDisplayVersion] = $this->buildVersionStrings($compatibleVersion, $stable);
248+
if (empty($incompatibleLinks)) {
249+
continue;
250+
}
243251

244-
$upgrades[] = [
245-
'name' => $package->getName(),
246-
'currentVersion' => $package->getPrettyVersion(),
247-
'coreConstraint' => $link->getPrettyConstraint(),
248-
'newConstraint' => $newConstraint,
249-
'newDisplayVersion' => $newDisplayVersion,
250-
'stable' => $stable,
251-
];
252-
}
252+
// Build a human-readable summary of the incompatible constraints
253+
$constraintParts = [];
254+
foreach ($incompatibleLinks as $link) {
255+
$constraintParts[] = $link->getTarget() . ': ' . $link->getPrettyConstraint();
256+
}
257+
$coreConstraint = implode(', ', $constraintParts);
258+
259+
// Try stable first, fall back to pre-release versions
260+
$compatibleVersion = $this->findLatestCompatibleVersion($package, $remoteRepositories, $targetVersion, self::STABLE_ONLY);
261+
$stable = true;
262+
263+
if (!$compatibleVersion) {
264+
$compatibleVersion = $this->findLatestCompatibleVersion($package, $remoteRepositories, $targetVersion, self::PRE_RELEASE_STABILITIES);
265+
$stable = false;
253266
}
267+
268+
[$newConstraint, $newDisplayVersion] = $this->buildVersionStrings($compatibleVersion, $stable);
269+
270+
$upgrades[] = [
271+
'name' => $package->getName(),
272+
'currentVersion' => $package->getPrettyVersion(),
273+
'coreConstraint' => $coreConstraint,
274+
'newConstraint' => $newConstraint,
275+
'newDisplayVersion' => $newDisplayVersion,
276+
'stable' => $stable,
277+
];
254278
}
255279

256280
return $upgrades;
@@ -273,16 +297,6 @@ private function buildVersionStrings(?PackageInterface $package, bool $stable):
273297
return ['^' . $prettyVersion, $prettyVersion];
274298
}
275299

276-
// Dev packages: use the branch alias (dev-main, dev-master, etc.)
277-
if ($package->isDev()) {
278-
$sourceRef = $package->getSourceReference();
279-
$displayVersion = $prettyVersion;
280-
if ($sourceRef) {
281-
$displayVersion .= ' (' . substr($sourceRef, 0, 7) . ')';
282-
}
283-
return [$prettyVersion, $displayVersion];
284-
}
285-
286300
// Pre-release (RC, beta, alpha): use exact version
287301
return [$prettyVersion, $prettyVersion];
288302
}
@@ -305,12 +319,21 @@ private function findLatestCompatibleVersion(PackageInterface $package, Composit
305319
);
306320

307321
foreach ($results['packages'] as $candidate) {
322+
$compatible = true;
323+
$hasTypo3Requirement = false;
308324
/** @var Link $link */
309325
foreach ($candidate->getRequires() as $link) {
310-
if ($link->getTarget() === 'typo3/cms-core' && $targetConstraint->matches($link->getConstraint())) {
311-
return $candidate;
326+
if ($this->isTypo3Package($link->getTarget())) {
327+
$hasTypo3Requirement = true;
328+
if (!$targetConstraint->matches($link->getConstraint())) {
329+
$compatible = false;
330+
break;
331+
}
312332
}
313333
}
334+
if ($hasTypo3Requirement && $compatible) {
335+
return $candidate;
336+
}
314337
}
315338

316339
return null;

0 commit comments

Comments
 (0)