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
83 changes: 69 additions & 14 deletions src/Console/Commands/InstallCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;
use Saucebase\Installer\Environments\Contracts\Environment;
use Saucebase\Installer\Environments\DockerEnvironment;
use Saucebase\Installer\Environments\Environment;
use Saucebase\Installer\Environments\NativeEnvironment;
use Symfony\Component\Process\Process;

Expand Down Expand Up @@ -62,8 +62,6 @@ public function handle(): int
return self::FAILURE;
}

$this->promptForModules();

return $driver->run($this);
}

Expand All @@ -83,15 +81,16 @@ protected function resolveDriver(): Environment
$name = $this->option('driver') ?? select(
label: 'How would you like to run Saucebase?',
options: [
'native' => 'Native PHP - minimal setup, ideal for exploring',
'docker' => 'Docker - recommended for real projects: MySQL, Redis, Mailpit, HTTPS',
'native' => 'Native PHP - minimal setup, ideal for exploring',
],
default: 'native',
default: 'docker',
);

return match ($name) {
'docker' => new DockerEnvironment,
default => new NativeEnvironment,
'native' => new NativeEnvironment,
default => throw new \InvalidArgumentException("Unknown driver: {$name}"),
};
}

Expand Down Expand Up @@ -120,7 +119,7 @@ protected function runStack(): void
}
}

protected function promptForModules(): void
public function promptForModules(): void
{
if ($this->option('all-modules') || $this->option('modules') !== null || $this->option('dev') || $this->isCI()) {
return;
Expand Down Expand Up @@ -218,9 +217,9 @@ public function install(): int

$this->runStack();
$this->setupModules();
$this->rewriteCrossModuleImports();
$this->createStorageLink();
$this->clearCaches();
$this->displaySuccess();

return self::SUCCESS;
}
Expand Down Expand Up @@ -298,6 +297,16 @@ protected function setupDatabase(): bool

protected function setupModules(): void
{
// Fast path: skip Packagist discovery when all requested names are fully qualified
if ($opt = $this->option('modules')) {
$names = array_values(array_filter(array_map(fn ($n) => strtolower(trim($n)), explode(',', $opt))));
if ($names && ! array_filter($names, fn ($n) => ! str_contains($n, '/'))) {
$this->doInstallModules($names);

return;
}
}

$available = $this->fetchAvailableModules();

if (empty($available)) {
Expand All @@ -312,6 +321,11 @@ protected function setupModules(): void
return;
}

$this->doInstallModules($selected);
}

protected function doInstallModules(array $selected): void
{
$this->newLine();

// Phase 1: require all selected packages in one Composer run
Expand Down Expand Up @@ -353,6 +367,10 @@ protected function setupModules(): void
foreach ($selected as $package) {
$name = Str::after($package, '/');

if (! $this->moduleHasSeeder($name)) {
continue;
}

$this->components->task("Seeding {$name}", function () use ($name) {
$process = new Process([PHP_BINARY, base_path('artisan'), 'db:seed', "--module={$name}", '--force']);
$process->setTimeout(60);
Expand All @@ -363,6 +381,41 @@ protected function setupModules(): void
}
}

public function rewriteCrossModuleImports(): void
{
$frameworks = ['vue', 'react', 'svelte'];
$pattern = implode('|', array_map(fn ($f) => preg_quote($f, '#'), $frameworks));
$extensions = ['vue', 'ts', 'tsx', 'js'];
$moduleDirs = glob(base_path('modules/*/resources/js'), GLOB_ONLYDIR) ?: [];

foreach ($moduleDirs as $jsRoot) {
$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($jsRoot));
foreach ($iterator as $file) {
if (! $file->isFile() || ! in_array($file->getExtension(), $extensions, true)) {
continue;
}
$path = $file->getPathname();
$content = file_get_contents($path);
$rewritten = preg_replace(
"#(@modules/[^/]+/resources/js/)({$pattern})/#",
'$1',
$content
);
if ($rewritten !== $content) {
file_put_contents($path, $rewritten);
}
}
}
}

public function moduleHasSeeder(string $name): bool
{
$seederFile = 'database/seeders/DatabaseSeeder.php';

return file_exists(base_path('modules/'.strtolower($name).'/'.$seederFile))
|| file_exists(base_path('vendor/saucebase/'.strtolower($name).'/'.$seederFile));
}

public function applyModulePatches(array $modules): void
{
foreach ($modules as $package) {
Expand Down Expand Up @@ -525,16 +578,18 @@ protected function displayTagline(): void
$this->newLine();
}

protected function displaySuccess(): void
public function displaySuccess(array $steps = []): void
{
$this->newLine();
$this->info('Installation complete!');
$this->newLine();
$this->line('Next steps:');
$this->line(' 1. Ensure <fg=yellow>APP_URL</> is set correctly in <fg=yellow>.env</>');
$this->line(' 2. Start the dev server: <fg=yellow>'.($this->option('driver') === 'docker' ? 'npm run dev' : 'php artisan serve or composer dev').'</>');
$this->line(' 3. Open your app in the browser: <fg=yellow>'.config('app.url').'</>');
$this->newLine();
if ($steps) {
$this->line('Next steps:');
foreach (array_values($steps) as $i => $step) {
$this->line(' '.($i + 1).'. '.$step);
}
$this->newLine();
}
$this->line('Learn more: <fg=cyan>https://github.com/saucebase-dev/saucebase</>');
}
}
39 changes: 1 addition & 38 deletions src/Console/Commands/StackCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,8 @@ private function runInstallMode(string $framework): int
$this->files->deleteDirectory($this->jsRoot.'/react');
$this->files->deleteDirectory($this->basePath.'/stubs/saucebase/stack');
$this->deployModuleFiles($framework);
$this->rewriteCrossModuleImports($framework);
$this->flattenRecipeStubs($framework);
$this->info("Framework set to {$framework}. Run: npm install && composer dev");
$this->info("Framework set to {$framework}. Run: npm install to install dependencies.");

return self::SUCCESS;
}
Expand Down Expand Up @@ -289,42 +288,6 @@ private function deployModuleFiles(string $framework): void
}
}

private function rewriteCrossModuleImports(string $framework): void
{
$moduleDirs = glob($this->basePath.'/modules/*/', GLOB_ONLYDIR);

if (! $moduleDirs) {
return;
}

$extensions = ['vue', 'ts', 'tsx', 'js'];

foreach ($moduleDirs as $moduleDir) {
$jsRoot = $moduleDir.'resources/js';

if (! $this->files->isDirectory($jsRoot)) {
continue;
}

foreach ($this->files->allFiles($jsRoot) as $file) {
if (! in_array($file->getExtension(), $extensions)) {
continue;
}

$content = $this->files->get($file->getPathname());
$rewritten = preg_replace(
'#(@modules/[^/]+/resources/js/)'.preg_quote($framework, '#').'/#',
'$1',
$content
);

if ($rewritten !== $content) {
$this->files->put($file->getPathname(), $rewritten);
}
}
}
}

private function flattenRecipeStubs(string $framework): void
{
$others = array_diff(self::SUPPORTED, [$framework]);
Expand Down
17 changes: 0 additions & 17 deletions src/Environments/Contracts/Environment.php

This file was deleted.

Loading