@@ -1107,6 +1107,8 @@ protected function runSeeders(string $projectPath, array $options): void
11071107 {
11081108 $ this ->tui ->addLog ('Running database seeders... ' );
11091109
1110+ $ this ->ensureArtisanDependencies ($ projectPath );
1111+
11101112 $ artisanScript = null ;
11111113 foreach (['core/artisan ' , 'artisan ' ] as $ candidate ) {
11121114 if (file_exists ($ projectPath . '/ ' . $ candidate )) {
@@ -1296,11 +1298,11 @@ protected function setupComposer(string $projectPath): void
12961298 $ composerCommand = $ this ->resolveComposerCommand ($ composerWorkDir );
12971299
12981300 try {
1299- $ installArgs = ['install ' , '--no-dev ' , '--prefer-dist ' , '--no-scripts ' ];
1301+ $ installArgs = ['install ' , '--no-dev ' , '--prefer-dist ' , '--no-scripts ' , ' --no-cache ' ];
13001302 $ process = $ this ->runComposer ($ composerCommand , $ installArgs , $ composerWorkDir );
13011303 if ($ process ->isSuccessful () && !$ this ->isComposerVendorHealthy ($ composerWorkDir )) {
13021304 $ this ->tui ->addLog ('Composer install finished but vendor is incomplete. Retrying with --prefer-source (this can happen due to GitHub rate limits)... ' , 'warning ' );
1303- $ process = $ this ->runComposer ($ composerCommand , ['install ' , '--no-dev ' , '--prefer-source ' , '--no-scripts ' ], $ composerWorkDir );
1305+ $ process = $ this ->runComposer ($ composerCommand , ['install ' , '--no-dev ' , '--prefer-source ' , '--no-scripts ' , ' --no-cache ' ], $ composerWorkDir );
13041306 }
13051307 if ($ process ->isSuccessful () && $ this ->isComposerVendorHealthy ($ composerWorkDir )) {
13061308 $ this ->tui ->addLog ('Dependencies installed successfully. ' , 'success ' );
@@ -1401,15 +1403,77 @@ protected function isComposerVendorHealthy(string $composerWorkDir): bool
14011403 return false ;
14021404 }
14031405
1404- // Migrations/seeders use Artisan which requires Symfony Console.
1405- $ symfonyConsole = $ composerWorkDir . '/vendor/symfony/console/Application.php ' ;
1406- if (!is_file ($ symfonyConsole )) {
1407- return false ;
1406+ // Migrations/seeders use Artisan (Illuminate Console) which requires Symfony Console.
1407+ $ requiredFiles = [
1408+ $ composerWorkDir . '/vendor/symfony/console/Application.php ' ,
1409+ $ composerWorkDir . '/vendor/symfony/http-kernel/Kernel.php ' ,
1410+ $ composerWorkDir . '/vendor/symfony/routing/RouteCollection.php ' ,
1411+ ];
1412+ foreach ($ requiredFiles as $ file ) {
1413+ if (!is_file ($ file )) {
1414+ return false ;
1415+ }
14081416 }
14091417
14101418 return true ;
14111419 }
14121420
1421+ protected function ensureArtisanDependencies (string $ projectPath ): void
1422+ {
1423+ $ composerWorkDir = is_file ($ projectPath . '/core/composer.json ' ) ? ($ projectPath . '/core ' ) : $ projectPath ;
1424+
1425+ if ($ this ->isComposerVendorHealthy ($ composerWorkDir )) {
1426+ return ;
1427+ }
1428+
1429+ $ this ->tui ->addLog ('Composer vendor is incomplete (missing Symfony Console). Reinstalling dependencies... ' , 'warning ' );
1430+
1431+ $ composerCommand = $ this ->resolveComposerCommand ($ composerWorkDir );
1432+ $ vendorDir = $ composerWorkDir . '/vendor ' ;
1433+
1434+ if (is_dir ($ vendorDir )) {
1435+ $ this ->removeDirectory ($ vendorDir );
1436+ }
1437+
1438+ $ installArgs = ['install ' , '--no-dev ' , '--prefer-dist ' , '--no-scripts ' , '--no-cache ' ];
1439+ $ process = $ this ->runComposer ($ composerCommand , $ installArgs , $ composerWorkDir );
1440+ if ($ process ->isSuccessful () && $ this ->isComposerVendorHealthy ($ composerWorkDir )) {
1441+ $ this ->tui ->addLog ('Dependencies reinstalled successfully. ' , 'success ' );
1442+ return ;
1443+ }
1444+
1445+ // Best-effort fallback: use source installs when dist downloads are blocked (GitHub rate limit).
1446+ if ($ this ->hasGitExecutable ()) {
1447+ $ this ->tui ->addLog ('Retrying with --prefer-source... ' , 'warning ' );
1448+ if (is_dir ($ vendorDir )) {
1449+ $ this ->removeDirectory ($ vendorDir );
1450+ }
1451+ $ process = $ this ->runComposer ($ composerCommand , ['install ' , '--no-dev ' , '--prefer-source ' , '--no-scripts ' , '--no-cache ' ], $ composerWorkDir );
1452+ if ($ process ->isSuccessful () && $ this ->isComposerVendorHealthy ($ composerWorkDir )) {
1453+ $ this ->tui ->addLog ('Dependencies installed successfully (prefer-source). ' , 'success ' );
1454+ return ;
1455+ }
1456+ }
1457+
1458+ $ fullOutput = $ this ->sanitizeComposerOutput ($ process ->getOutput () . "\n" . $ process ->getErrorOutput ());
1459+ throw new \RuntimeException (
1460+ "Composer completed but vendor is incomplete (Symfony components missing). Please run 'composer install' manually in {$ composerWorkDir }. \n" .
1461+ trim ($ fullOutput )
1462+ );
1463+ }
1464+
1465+ protected function hasGitExecutable (): bool
1466+ {
1467+ try {
1468+ $ p = new Process (['git ' , '--version ' ], null , $ this ->buildProcessEnv ());
1469+ $ p ->setTimeout (5 );
1470+ $ p ->run ();
1471+ return $ p ->isSuccessful ();
1472+ } catch (\Throwable $ e ) {
1473+ return false ;
1474+ }
1475+ }
1476+
14131477 protected function runComposer (array $ composerCommand , array $ args , string $ workingDir ): Process
14141478 {
14151479 $ process = new Process ([
@@ -1796,6 +1860,8 @@ protected function runMigrations(string $projectPath, array $options): void
17961860 {
17971861 $ this ->tui ->addLog ('Running database migrations... ' );
17981862
1863+ $ this ->ensureArtisanDependencies ($ projectPath );
1864+
17991865 $ artisanScript = null ;
18001866 foreach (['core/artisan ' , 'artisan ' ] as $ candidate ) {
18011867 if (file_exists ($ projectPath . '/ ' . $ candidate )) {
0 commit comments