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
5 changes: 3 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
@@ -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/<Name> with configurable base dir.",
"type": "composer-plugin",
"license": "MIT",
Expand All @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 17 additions & 3 deletions src/Installer.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -257,7 +271,7 @@ protected function mergeStash(string $stash, string $base, string $install): voi
$this->io->writeError(" <warning>Merge conflict in $rel — conflict markers inserted</warning>");
}

rename($merged, $newFile);
(new SymfonyFilesystem)->rename($merged, $newFile, true);
}
}

Expand Down Expand Up @@ -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);
Expand Down
71 changes: 71 additions & 0 deletions tests/ModuleInstallerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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', '<?php // user file');

$pkg = new Package('saucebase/test-module', '1.0.0.0', '1.0.0');

// The original install path does not exist (was cleared during update)
$originalPath = $installer->getInstallPath($pkg);
$this->assertDirectoryDoesNotExist($originalPath);

Comment on lines +397 to +412
$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', '<?php // user file');

$installer->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);
}
}