From 0bde79b296158e4e44a935722dfc488d112935e0 Mon Sep 17 00:00:00 2001 From: roble Date: Sat, 6 Jun 2026 13:31:48 +0100 Subject: [PATCH] feat: add locally-tracked guard to install and update methods in Installer --- src/Installer.php | 24 ++++++++++++ tests/ModuleInstallerTest.php | 70 +++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+) diff --git a/src/Installer.php b/src/Installer.php index 5fd8b2c..b53129c 100644 --- a/src/Installer.php +++ b/src/Installer.php @@ -452,6 +452,16 @@ public function install(InstalledRepositoryInterface $repo, PackageInterface $pa return \React\Promise\resolve(null); } + if ($this->isLocallyTracked($this->getInstallPath($package))) { + $this->io->write(" - Skipping install for locally tracked module: {$package->getPrettyName()}"); + + if (! $repo->hasPackage($package)) { + $repo->addPackage(clone $package); + } + + return \React\Promise\resolve(null); + } + $promise = $this->parentInstall($repo, $package); return $promise->then(function () use ($package) { @@ -540,6 +550,20 @@ public function update(InstalledRepositoryInterface $repo, PackageInterface $ini } $installPath = $this->getInstallPath($target); + + if ($this->isLocallyTracked($installPath)) { + $this->io->write(" - Skipping update for locally tracked module: {$target->getPrettyName()}"); + + if ($repo->hasPackage($initial)) { + $repo->removePackage($initial); + } + + if (! $repo->hasPackage($target)) { + $repo->addPackage(clone $target); + } + + return \React\Promise\resolve(null); + } $stashPath = null; $basePath = null; diff --git a/tests/ModuleInstallerTest.php b/tests/ModuleInstallerTest.php index ae6d942..5e9a7fb 100644 --- a/tests/ModuleInstallerTest.php +++ b/tests/ModuleInstallerTest.php @@ -674,6 +674,40 @@ public function test_install_does_not_register_package_in_repo_when_already_pres $installer->install($repo, $pkg); } + // ------------------------------------------------------------------------- + // install() locally-tracked guard (distType='zip', physical .git check) + // ------------------------------------------------------------------------- + + public function test_install_skips_when_install_path_is_locally_tracked(): void + { + $baseDir = sys_get_temp_dir().'/install-tracked-'.uniqid('', true); + mkdir($baseDir.'/test-module/.git', 0755, true); + + $io = $this->createMock(IOInterface::class); + $io->expects($this->once()) + ->method('write') + ->with($this->stringContains('Skipping install for locally tracked')); + + $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); + + $pkg = new Package('saucebase/test-module', '1.0.0.0', '1.0.0'); + $pkg->setDistType('zip'); + + $repo = $this->createMock(InstalledRepositoryInterface::class); + $repo->method('hasPackage')->willReturn(false); + $repo->expects($this->once())->method('addPackage'); + + $installer = new TestableInstaller($io, $composer); + $promise = $installer->install($repo, $pkg); + + $this->assertNotNull($promise); + + (new Filesystem)->remove($baseDir); + } + // ------------------------------------------------------------------------- // update() path-repository guard // ------------------------------------------------------------------------- @@ -797,6 +831,42 @@ public function test_uninstall_skips_remove_package_when_package_not_in_repo(): $installer->uninstall($repo, $pkg); } + // ------------------------------------------------------------------------- + // update() locally-tracked guard (distType='zip', physical .git check) + // ------------------------------------------------------------------------- + + public function test_update_skips_when_install_path_is_locally_tracked(): void + { + $baseDir = sys_get_temp_dir().'/update-tracked-'.uniqid('', true); + mkdir($baseDir.'/test-module/.git', 0755, true); + + $io = $this->createMock(IOInterface::class); + $io->expects($this->once()) + ->method('write') + ->with($this->stringContains('Skipping update for locally tracked')); + + $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'); + $target = new Package('saucebase/test-module', '2.0.0.0', '2.0.0'); + $target->setDistType('zip'); + + $repo = $this->createMock(InstalledRepositoryInterface::class); + $repo->method('hasPackage')->willReturnOnConsecutiveCalls(true, false); + $repo->expects($this->once())->method('removePackage'); + $repo->expects($this->once())->method('addPackage'); + + $installer = new TestableInstaller($io, $composer); + $promise = $installer->update($repo, $initial, $target); + + $this->assertNotNull($promise); + + (new Filesystem)->remove($baseDir); + } + // ------------------------------------------------------------------------- // updateCode() skip flag // -------------------------------------------------------------------------