From 983d099185788e4975264201dbcd471e4597ce34 Mon Sep 17 00:00:00 2001 From: roble Date: Wed, 18 Mar 2026 15:43:20 +0000 Subject: [PATCH 1/3] fix: use SymfonyFilesystem for renaming in merge conflict handling --- src/Installer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Installer.php b/src/Installer.php index 01d6397..9b9c2aa 100644 --- a/src/Installer.php +++ b/src/Installer.php @@ -256,7 +256,7 @@ protected function mergeStash(string $stash, string $base, string $install): voi $this->io->writeError(" Merge conflict in $rel — conflict markers inserted"); } - rename($merged, $newFile); + (new SymfonyFilesystem)->rename($merged, $newFile, true); } } From 904ca7ddcabe0f7a5b0134c409b2db9e5d93a2ff Mon Sep 17 00:00:00 2001 From: roble Date: Wed, 18 Mar 2026 20:40:31 +0000 Subject: [PATCH 2/3] feat: implement restoreStash method for handling module directory restoration --- composer.json | 5 ++- src/Installer.php | 18 ++++++++- tests/ModuleInstallerTest.php | 71 +++++++++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index d314735..effadf8 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,6 @@ { "name": "saucebase/module-installer", + "version": "1.0.1", "description": "Custom Composer installer for Sauce Base modules (type: saucebase-module). Installs to Modules/ with configurable base dir.", "type": "composer-plugin", "license": "MIT", @@ -12,8 +13,8 @@ "require": { "php": "^8.4", "composer-plugin-api": "^2.0", - "symfony/finder": "^8.0", - "symfony/process": "^8.0" + "symfony/finder": "^7.0 || ^8.0", + "symfony/process": "^7.0 || ^8.0" }, "require-dev": { "composer/composer": "^2.0", diff --git a/src/Installer.php b/src/Installer.php index 09c5d89..bbcf833 100644 --- a/src/Installer.php +++ b/src/Installer.php @@ -183,6 +183,20 @@ protected function getUpdateStrategy(): string return $strategy; } + /** + * Restore a stashed module directory back to its original install path. + * If the original path was re-created (e.g. by a partial update), the stash is discarded. + */ + protected function restoreStash(string $stashPath, PackageInterface $package): void + { + $originalPath = $this->getInstallPath($package); + if (! is_dir($originalPath)) { + (new SymfonyFilesystem)->rename($stashPath, $originalPath); + } else { + (new Filesystem)->removeDirectory($stashPath); + } + } + /** * Rename the module directory to a temp location and return the temp path. * Returns null if the directory does not exist. @@ -306,9 +320,9 @@ function () use ($target, $stashPath, $basePath) { (new Filesystem)->removeDirectory($basePath); } }, - function (\Throwable $e) use ($stashPath, $basePath) { + function (\Throwable $e) use ($stashPath, $basePath, $initial) { if ($stashPath !== null) { - (new Filesystem)->removeDirectory($stashPath); + $this->restoreStash($stashPath, $initial); } if ($basePath !== null) { (new Filesystem)->removeDirectory($basePath); diff --git a/tests/ModuleInstallerTest.php b/tests/ModuleInstallerTest.php index 30d13f6..ad74723 100644 --- a/tests/ModuleInstallerTest.php +++ b/tests/ModuleInstallerTest.php @@ -53,6 +53,11 @@ public function callMergeStash(string $stash, string $base, string $install): vo { parent::mergeStash($stash, $base, $install); } + + public function callRestoreStash(string $stashPath, PackageInterface $package): void + { + parent::restoreStash($stashPath, $package); + } } final class ModuleInstallerTest extends TestCase @@ -379,4 +384,70 @@ public function test_merge_stash_respects_upstream_deletions(): void $fs->remove($base); $fs->remove($install); } + + // ------------------------------------------------------------------------- + // restoreStash + // ------------------------------------------------------------------------- + + public function test_restore_stash_renames_stash_back_when_original_path_missing(): void + { + $io = $this->createStub(IOInterface::class); + $composer = $this->createStub(Composer::class); + + $root = new RootPackage('root/app', '1.0.0.0', '1.0.0'); + $root->setExtra(['module-dir' => sys_get_temp_dir()]); + $composer->method('getPackage')->willReturn($root); + + $installer = new TestableInstaller($io, $composer); + + $stash = sys_get_temp_dir().'/module-stash-restore-test-'.uniqid('', true); + mkdir($stash); + file_put_contents($stash.'/custom.php', 'getInstallPath($pkg); + $this->assertDirectoryDoesNotExist($originalPath); + + $installer->callRestoreStash($stash, $pkg); + + $this->assertDirectoryDoesNotExist($stash); + $this->assertDirectoryExists($originalPath); + $this->assertFileExists($originalPath.'/custom.php'); + + (new Filesystem)->remove($originalPath); + } + + public function test_restore_stash_discards_stash_when_original_path_already_exists(): void + { + $io = $this->createStub(IOInterface::class); + $composer = $this->createStub(Composer::class); + + $root = new RootPackage('root/app', '1.0.0.0', '1.0.0'); + $root->setExtra(['module-dir' => sys_get_temp_dir()]); + $composer->method('getPackage')->willReturn($root); + + $installer = new TestableInstaller($io, $composer); + + $pkg = new Package('saucebase/already-there', '1.0.0.0', '1.0.0'); + + // Pre-create the original path (partial update left it behind) + $originalPath = $installer->getInstallPath($pkg); + mkdir($originalPath, 0755, true); + file_put_contents($originalPath.'/partial.php', 'partial'); + + $stash = sys_get_temp_dir().'/module-stash-discard-test-'.uniqid('', true); + mkdir($stash); + file_put_contents($stash.'/custom.php', 'callRestoreStash($stash, $pkg); + + // Stash was discarded, original path (with partial content) remains + $this->assertDirectoryDoesNotExist($stash); + $this->assertDirectoryExists($originalPath); + $this->assertFileExists($originalPath.'/partial.php'); + + (new Filesystem)->remove($originalPath); + } } From b45743c04b52d46554ee9f22607d35abb34af660 Mon Sep 17 00:00:00 2001 From: roble Date: Wed, 18 Mar 2026 20:43:46 +0000 Subject: [PATCH 3/3] fix: update content-hash in composer.lock to reflect correct state --- composer.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.lock b/composer.lock index ffd4e74..f7f9f70 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "3b9bd3ebf86ea0e34b28472b9de4c0f3", + "content-hash": "b94f7fab44f8be2c33979a864196f3a4", "packages": [ { "name": "symfony/finder",