@@ -1298,10 +1298,19 @@ protected function setupComposer(string $projectPath): void
12981298 $ composerCommand = $ this ->resolveComposerCommand ($ composerWorkDir );
12991299
13001300 try {
1301+ $ vendorDir = $ composerWorkDir . '/vendor ' ;
1302+ if (is_dir ($ vendorDir ) && !$ this ->isComposerVendorHealthy ($ composerWorkDir )) {
1303+ $ this ->tui ->addLog ('Detected incomplete vendor directory. Removing and reinstalling... ' , 'warning ' );
1304+ $ this ->removeDirectory ($ vendorDir );
1305+ }
1306+
13011307 $ installArgs = ['install ' , '--no-dev ' , '--prefer-dist ' , '--no-scripts ' , '--no-cache ' ];
13021308 $ process = $ this ->runComposer ($ composerCommand , $ installArgs , $ composerWorkDir );
13031309 if ($ process ->isSuccessful () && !$ this ->isComposerVendorHealthy ($ composerWorkDir )) {
1304- $ this ->tui ->addLog ('Composer install finished but vendor is incomplete. Retrying with --prefer-source (this can happen due to GitHub rate limits)... ' , 'warning ' );
1310+ $ this ->tui ->addLog ('Composer install finished but vendor is incomplete. Retrying with clean vendor and --prefer-source (this can happen due to GitHub rate limits)... ' , 'warning ' );
1311+ if (is_dir ($ vendorDir )) {
1312+ $ this ->removeDirectory ($ vendorDir );
1313+ }
13051314 $ process = $ this ->runComposer ($ composerCommand , ['install ' , '--no-dev ' , '--prefer-source ' , '--no-scripts ' , '--no-cache ' ], $ composerWorkDir );
13061315 }
13071316 if ($ process ->isSuccessful () && $ this ->isComposerVendorHealthy ($ composerWorkDir )) {
@@ -1399,25 +1408,52 @@ protected function setupComposer(string $projectPath): void
13991408 protected function isComposerVendorHealthy (string $ composerWorkDir ): bool
14001409 {
14011410 $ autoload = $ composerWorkDir . '/vendor/autoload.php ' ;
1402- if (!is_file ($ autoload )) {
1411+ if (!is_file ($ autoload ) || ! $ this -> isNonEmptyFile ( $ autoload ) ) {
14031412 return false ;
14041413 }
14051414
14061415 // Migrations/seeders use Artisan (Illuminate Console) which requires Symfony Console.
14071416 $ requiredFiles = [
1408- $ composerWorkDir . '/vendor/symfony/console/Application.php ' ,
1409- $ composerWorkDir . '/vendor/symfony/http-kernel/Kernel.php ' ,
1410- $ composerWorkDir . '/vendor/symfony/routing/RouteCollection.php ' ,
1417+ [ $ composerWorkDir . '/vendor/symfony/console/Application.php ' , ' class Application ' ] ,
1418+ [ $ composerWorkDir . '/vendor/symfony/http-kernel/Kernel.php ' , ' class Kernel ' ] ,
1419+ [ $ composerWorkDir . '/vendor/symfony/routing/RouteCollection.php ' , ' class RouteCollection ' ] ,
14111420 ];
1412- foreach ($ requiredFiles as $ file ) {
1413- if (!is_file ($ file )) {
1421+ foreach ($ requiredFiles as $ it ) {
1422+ [$ file , $ needle ] = $ it ;
1423+ if (!$ this ->fileContains ($ file , $ needle )) {
14141424 return false ;
14151425 }
14161426 }
14171427
14181428 return true ;
14191429 }
14201430
1431+ protected function isNonEmptyFile (string $ path ): bool
1432+ {
1433+ $ size = @filesize ($ path );
1434+ return is_int ($ size ) && $ size > 0 ;
1435+ }
1436+
1437+ protected function fileContains (string $ path , string $ needle ): bool
1438+ {
1439+ if (!is_file ($ path ) || !$ this ->isNonEmptyFile ($ path )) {
1440+ return false ;
1441+ }
1442+ $ fh = @fopen ($ path , 'rb ' );
1443+ if (!is_resource ($ fh )) {
1444+ return false ;
1445+ }
1446+ try {
1447+ $ buf = @fread ($ fh , 8192 );
1448+ } finally {
1449+ @fclose ($ fh );
1450+ }
1451+ if (!is_string ($ buf ) || $ buf === '' ) {
1452+ return false ;
1453+ }
1454+ return strpos ($ buf , $ needle ) !== false ;
1455+ }
1456+
14211457 protected function ensureArtisanDependencies (string $ projectPath ): void
14221458 {
14231459 $ composerWorkDir = is_file ($ projectPath . '/core/composer.json ' ) ? ($ projectPath . '/core ' ) : $ projectPath ;
0 commit comments