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
18 changes: 8 additions & 10 deletions src/Installer.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
*/
class Installer extends LibraryInstaller
{
const DEFAULT_ROOT = 'Modules';
const DEFAULT_ROOT = 'modules';

Comment on lines +23 to 24

Copilot AI Apr 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing the default root/module directory naming (DEFAULT_ROOT and getModuleName) will change the install path for existing installations. During update(), stashModuleDir($this->getInstallPath($initial)) will now look in the new path, so previously-installed modules (e.g. Modules/StudlyCase) won’t be stashed/merged and Composer may also fail to uninstall the old path, leaving an orphaned directory. Consider adding a migration/legacy-path fallback (e.g., detect and stash/remove the legacy Modules/<StudlyCase> / <StudlyCase> location when it exists) and/or overriding uninstall() to clean up legacy paths.

Copilot uses AI. Check for mistakes.
const DEFAULT_MODULE_TYPE = 'laravel-module';

Expand Down Expand Up @@ -59,10 +59,11 @@ protected function getBaseInstallationPath()
}

/**
* Get the module name, i.e. "saucebase/something-nice" will be transformed into "SomethingNice"
* Get the module directory name from the package name.
* "saucebase/something-nice" → "something-nice" (lowercase slug, hyphens preserved).
*
* @param PackageInterface $package Compose Package Interface
* @return string Module Name
* @param PackageInterface $package Composer Package Interface
* @return string Module directory name
Comment on lines +62 to +66

Copilot AI Apr 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some documentation is now inconsistent with the new lowercase install root and slug-based directory naming. In particular, the getBaseInstallationPath() doc comment in this file still says it defaults to Modules/, and the repo README/composer.json description also reference Modules/<StudlyCase> behavior. Please update those docs to match the new modules/<lowercase-slug> behavior so consumers aren’t misled.

Copilot uses AI. Check for mistakes.
*
* @throws ModuleInstallerException
*/
Expand All @@ -74,13 +75,10 @@ protected function getModuleName(PackageInterface $package)
throw new ModuleInstallerException("Invalid package name: $name");
}

// Take only the part after the vendor (index 1)
[$vendor, $packageName] = explode('/', $name, 2);
// Take only the part after the vendor (index 1) and lowercase it
[, $packageName] = explode('/', $name, 2);

// Split by "-" and convert each segment to ucfirst
$parts = explode('-', $packageName);

return implode('', array_map('ucfirst', $parts));
return strtolower($packageName);
}

public function supports($packageType)
Expand Down
28 changes: 24 additions & 4 deletions tests/ModuleInstallerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,13 @@ public function test_supports_custom_module_type_from_extra(): void

public function test_get_install_path_uses_default_modules_dir_when_no_composer(): void
{
// Composer is null -> should fall back to DEFAULT_ROOT ("Modules")
// Composer is null -> should fall back to DEFAULT_ROOT ("modules")
$io = $this->createStub(IOInterface::class);
$installer = new TestableInstaller($io, null);

$pkg = new Package('saucebase/something-nice', '1.0.0.0', '1.0.0');

$this->assertSame('Modules/SomethingNice', $installer->getInstallPath($pkg));
$this->assertSame('modules/something-nice', $installer->getInstallPath($pkg));
}

public function test_get_install_path_uses_default_when_no_module_dir_in_extra(): void
Expand All @@ -113,7 +113,7 @@ public function test_get_install_path_uses_default_when_no_module_dir_in_extra()
$installer = new TestableInstaller($io, $composer);
$pkg = new Package('vendor/awesome-toolkit', '1.0.0.0', '1.0.0');

$this->assertSame('Modules/AwesomeToolkit', $installer->getInstallPath($pkg));
$this->assertSame('modules/awesome-toolkit', $installer->getInstallPath($pkg));
}

public function test_get_install_path_honors_extra_module_dir(): void
Expand All @@ -128,7 +128,27 @@ public function test_get_install_path_honors_extra_module_dir(): void
$installer = new TestableInstaller($io, $composer);
$pkg = new Package('vendor/awesome-toolkit', '1.0.0.0', '1.0.0');

$this->assertSame('CustomModules/AwesomeToolkit', $installer->getInstallPath($pkg));
$this->assertSame('CustomModules/awesome-toolkit', $installer->getInstallPath($pkg));
}

public function test_get_module_name_returns_lowercase_slug(): void
{
$io = $this->createStub(IOInterface::class);
$installer = new TestableInstaller($io, null);

$cases = [
'saucebase/auth' => 'auth',
'saucebase/billing' => 'billing',
'saucebase/my-module' => 'my-module',
'saucebase/some-CAPS' => 'some-caps',
'vendor/awesome-toolkit' => 'awesome-toolkit',
];

foreach ($cases as $packageName => $expected) {
$pkg = $this->createStub(PackageInterface::class);
$pkg->method('getPrettyName')->willReturn($packageName);
$this->assertSame($expected, $installer->callGetModuleName($pkg), "Failed for: $packageName");
}
}

public function test_get_module_name_throws_on_invalid_pretty_name(): void
Expand Down