Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/Installer.php
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,20 @@ function () use ($repo, $initial, $target, $installPath, $stashPath, $basePath)
(new Filesystem)->removeDirectory($stashPath);
}

// When upgrading FROM a path package, parent::update() invokes
// PathDownloader against the old path source which may not exist.
// Track the repo manually — we have already placed the files.
if ($initial->getDistType() === 'path') {
if ($repo->hasPackage($initial)) {
$repo->removePackage($initial);
}
if (! $repo->hasPackage($target)) {
$repo->addPackage(clone $target);
}

return \React\Promise\resolve(null);
}

// Delegate repo tracking (installed.json) to parent — skip the download step
// since we have already placed the files ourselves.
return $this->delegateRepoTracking($repo, $initial, $target);
Expand Down
85 changes: 85 additions & 0 deletions tests/ModuleInstallerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,30 @@ protected function parentInstall(InstalledRepositoryInterface $repo, PackageInte
{
return \React\Promise\resolve(null);
}

public bool $downloadBaseInvoked = false;

protected function downloadBase(PackageInterface $initial, string $basePath): PromiseInterface
{
$this->downloadBaseInvoked = true;
mkdir($basePath, 0755, true);

return \React\Promise\resolve(null);
}

protected function downloadFresh(PackageInterface $package, string $path): PromiseInterface
{
return \React\Promise\resolve(null);
}

public bool $delegateRepoTrackingInvoked = false;

protected function delegateRepoTracking(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target): PromiseInterface
{
$this->delegateRepoTrackingInvoked = true;

return \React\Promise\resolve(null);
}
}

final class ModuleInstallerTest extends TestCase
Expand Down Expand Up @@ -867,6 +891,67 @@ public function test_update_skips_when_install_path_is_locally_tracked(): void
(new Filesystem)->remove($baseDir);
}

// -------------------------------------------------------------------------
// update() merge-strategy guard for path-type initial packages
// -------------------------------------------------------------------------

public function test_update_skips_base_download_and_delegates_repo_manually_when_initial_is_path_type(): void
{
$baseDir = sys_get_temp_dir().'/update-path-initial-'.uniqid('', true);
mkdir($baseDir.'/test-module', 0755, true);

$composer = $this->createStub(Composer::class);
$root = new RootPackage('root/app', '1.0.0.0', '1.0.0');
$root->setExtra(['module-dir' => $baseDir]);
$composer->method('getPackage')->willReturn($root);

$initial = new Package('saucebase/test-module', '1.0.0.0', '1.0.0');
$initial->setDistType('path');

$target = new Package('saucebase/test-module', '2.0.0.0', '2.0.0');
$target->setDistType('zip');

$repo = $this->createMock(InstalledRepositoryInterface::class);
$repo->method('hasPackage')->willReturnCallback(fn ($p) => $p === $initial);
$repo->expects($this->once())->method('removePackage')->with($initial);
$repo->expects($this->once())->method('addPackage');

$installer = new TestableInstaller($this->createStub(IOInterface::class), $composer);
$installer->update($repo, $initial, $target);

$this->assertFalse($installer->downloadBaseInvoked);
$this->assertFalse($installer->delegateRepoTrackingInvoked);

(new Filesystem)->remove($baseDir);
}

public function test_update_downloads_base_and_delegates_repo_to_parent_when_initial_is_not_path_type(): void
{
$baseDir = sys_get_temp_dir().'/update-zip-initial-'.uniqid('', true);
mkdir($baseDir.'/test-module', 0755, true);

$composer = $this->createStub(Composer::class);
$root = new RootPackage('root/app', '1.0.0.0', '1.0.0');
$root->setExtra(['module-dir' => $baseDir]);
$composer->method('getPackage')->willReturn($root);

$initial = new Package('saucebase/test-module', '1.0.0.0', '1.0.0');
$initial->setDistType('zip');

$target = new Package('saucebase/test-module', '2.0.0.0', '2.0.0');
$target->setDistType('zip');

$repo = $this->createStub(InstalledRepositoryInterface::class);

$installer = new TestableInstaller($this->createStub(IOInterface::class), $composer);
$installer->update($repo, $initial, $target);

$this->assertTrue($installer->downloadBaseInvoked);
$this->assertTrue($installer->delegateRepoTrackingInvoked);

(new Filesystem)->remove($baseDir);
}

// -------------------------------------------------------------------------
// updateCode() skip flag
// -------------------------------------------------------------------------
Expand Down