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
// -------------------------------------------------------------------------