55use Illuminate \Support \Facades \File ;
66use Illuminate \Support \Facades \Process ;
77use Illuminate \Support \Str ;
8+ use Native \Mobile \Support \BundleExclusions ;
9+ use Native \Mobile \Support \BundleFileManager ;
810use Symfony \Component \Process \Process as SymfonyProcess ;
911
1012trait PreparesBuild
@@ -225,21 +227,12 @@ protected function prepareLaravelBundle(bool $excludeDevDependencies = true): vo
225227 unlink ($ destinationZip );
226228 }
227229
228- $ excludedDirs = $ this ->getExcludedPaths ();
229-
230- // Respect export-ignore from vendor package .gitattributes files
231- foreach ($ this ->loadVendorExportIgnorePatterns ($ source ) as $ prefix => $ patterns ) {
232- foreach ($ patterns as $ pattern ) {
233- $ excludedDirs [] = $ prefix .ltrim ($ pattern , '/ ' );
234- }
235- }
236-
237- $ this ->logToFile (' Excluded directories: ' .implode (', ' , $ excludedDirs ));
230+ $ configExcludes = config ('nativephp.cleanup_exclude_files ' , []);
238231
239232 $ srcDir = base_path ('vendor/nativephp/mobile/bootstrap/android ' );
240233
241234 $ this ->logToFile (' Copying Laravel source... ' );
242- $ this ->components ->task ('Copying Laravel source ' , fn () => $ this -> platformOptimizedCopy ($ source , $ tempDir , $ excludedDirs ));
235+ $ this ->components ->task ('Copying Laravel source ' , fn () => BundleFileManager:: copy ($ source , $ tempDir , $ configExcludes ));
243236
244237 $ composerArgs = $ excludeDevDependencies ? '--no-dev --no-interaction ' : '--no-interaction ' ;
245238
@@ -271,6 +264,9 @@ protected function prepareLaravelBundle(bool $excludeDevDependencies = true): vo
271264 return $ result ->successful ();
272265 });
273266
267+ $ this ->logToFile (' Removing unnecessary files... ' );
268+ $ this ->components ->task ('Removing unnecessary files ' , fn () => BundleFileManager::removeUnnecessaryFiles ($ tempDir , $ configExcludes ));
269+
274270 $ version = config ('nativephp.version ' , now ()->format ('Ymd-His ' ));
275271 $ this ->logToFile (" Writing version file: $ version " );
276272 file_put_contents ($ tempDir .DIRECTORY_SEPARATOR .'.version ' , $ version .PHP_EOL );
@@ -289,7 +285,7 @@ protected function prepareLaravelBundle(bool $excludeDevDependencies = true): vo
289285 }
290286
291287 $ this ->logToFile (' Creating bundle archive... ' );
292- $ this ->components ->task ('Creating bundle archive ' , fn () => $ this ->createZipBundle ($ tempDir , $ destinationZip, $ excludedDirs ));
288+ $ this ->components ->task ('Creating bundle archive ' , fn () => $ this ->createZipBundle ($ tempDir , $ destinationZip ));
293289
294290 if (! file_exists ($ destinationZip ) || filesize ($ destinationZip ) <= 1000 ) {
295291 $ this ->logToFile ('ERROR: Failed to create valid zip file ' );
@@ -306,133 +302,10 @@ protected function prepareLaravelBundle(bool $excludeDevDependencies = true): vo
306302 }
307303 }
308304
309- /**
310- * Paths to exclude from the app bundle.
311- *
312- * Patterns without a leading / match at any depth (e.g. inside vendor packages).
313- * Patterns with a leading / are anchored to the project root.
314- * Used by rsync during the initial copy step.
315- */
316- protected function getExcludedPaths (): array
317- {
318- // Any depth (project root + inside vendor packages)
319- $ excludes = [
320- '.git ' ,
321- '.github ' ,
322- 'node_modules ' ,
323- 'tests ' ,
324- '.DS_Store ' ,
325- '.gitignore ' ,
326- '.gitattributes ' ,
327- '.gitkeep ' ,
328- '.editorconfig ' ,
329-
330- // Non-runtime files inside vendor packages
331- '*.md ' ,
332- 'LICENSE* ' ,
333- 'docs ' ,
334- '*.yml ' ,
335- '*.yaml ' ,
336- '*.neon ' ,
337- '*.neon.dist ' ,
338-
339- // Vendor-specific
340- 'vendor/nativephp/mobile/resources ' ,
341- 'vendor/*/*/vendor ' ,
342- 'vendor/livewire/livewire/src/Features/SupportFileUploads/browser_test_image_big.jpg ' ,
343- ];
344-
345- // Platform-specific project-level excludes
346- if (PHP_OS_FAMILY === 'Windows ' ) {
347- $ excludes [] = '/nativephp ' ;
348- } else {
349- $ excludes [] = '/nativephp/ios ' ;
350- $ excludes [] = '/nativephp/android ' ;
351- }
352-
353- // Project-level directories
354- $ excludes = array_merge ($ excludes , [
355- '/output ' ,
356- '/build ' ,
357- '/dist ' ,
358- '/artifacts ' ,
359- '/storage/logs ' ,
360- '/storage/framework ' ,
361- '/public/storage ' ,
362- ]);
363-
364- // Project-level files
365- $ excludes = array_merge ($ excludes , [
366- '/*.js ' ,
367- '/*.md ' ,
368- '/*.lock ' ,
369- '/*.xml ' ,
370- '/.env.example ' ,
371- '/artisan ' ,
372- ]);
373-
374- // User-configured exclusions
375- $ excludes = array_merge ($ excludes , config ('nativephp.cleanup_exclude_files ' , []));
376-
377- return $ excludes ;
378- }
379-
380- /**
381- * Load export-ignore patterns from .gitattributes in vendor packages.
382- *
383- * @return array<string, string[]>
384- */
385- protected function loadVendorExportIgnorePatterns (string $ source ): array
386- {
387- $ patterns = [];
388- $ vendorPath = $ source .'/vendor/ ' ;
389-
390- if (! is_dir ($ vendorPath )) {
391- return $ patterns ;
392- }
393-
394- foreach (new \DirectoryIterator ($ vendorPath ) as $ namespace ) {
395- if ($ namespace ->isDot () || ! $ namespace ->isDir ()) {
396- continue ;
397- }
398-
399- foreach (new \DirectoryIterator ($ namespace ->getPathname ()) as $ package ) {
400- if ($ package ->isDot () || ! $ package ->isDir ()) {
401- continue ;
402- }
403-
404- $ gitattributes = $ package ->getPathname ().'/.gitattributes ' ;
405- if (! file_exists ($ gitattributes )) {
406- continue ;
407- }
408-
409- $ ignores = [];
410- foreach (file ($ gitattributes , FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES ) as $ line ) {
411- $ line = trim ($ line );
412- if ($ line === '' || $ line [0 ] === '# ' || ! str_contains ($ line , 'export-ignore ' )) {
413- continue ;
414- }
415-
416- $ path = trim (preg_split ('/\s+/ ' , $ line , 2 )[0 ] ?? '' );
417- if ($ path !== '' ) {
418- $ ignores [] = ltrim ($ path , '/ ' );
419- }
420- }
421-
422- if (! empty ($ ignores )) {
423- $ prefix = 'vendor/ ' .$ namespace ->getFilename ().'/ ' .$ package ->getFilename ().'/ ' ;
424- $ patterns [$ prefix ] = $ ignores ;
425- }
426- }
427- }
428-
429- return $ patterns ;
430- }
431-
432305 /**
433306 * Create ZIP bundle with cross-platform support
434307 */
435- protected function createZipBundle (string $ source , string $ destination, array $ excludedDirs = [] ): void
308+ protected function createZipBundle (string $ source , string $ destination ): void
436309 {
437310 if (PHP_OS_FAMILY === 'Windows ' ) {
438311 $ sevenZip = config ('nativephp.android.7zip-location ' );
@@ -459,16 +332,9 @@ protected function createZipBundle(string $source, string $destination, array $e
459332 exit (1 );
460333 }
461334
462- $ this ->addDirectoryToZip ($ zip , $ source , '' , $ excludedDirs );
463-
464- $ requiredDirs = [
465- 'bootstrap/cache ' ,
466- 'storage/framework/cache ' ,
467- 'storage/framework/sessions ' ,
468- 'storage/framework/views ' ,
469- ];
335+ $ this ->addDirectoryToZip ($ zip , $ source );
470336
471- foreach ($ requiredDirs as $ dir ) {
337+ foreach (BundleExclusions:: ANDROID_REQUIRED_DIRS as $ dir ) {
472338 if (! $ zip ->statName ($ dir )) {
473339 $ zip ->addEmptyDir ($ dir );
474340 }
@@ -484,7 +350,7 @@ protected function createZipBundle(string $source, string $destination, array $e
484350 /**
485351 * Add directory contents to ZIP archive
486352 */
487- protected function addDirectoryToZip (\ZipArchive $ zip , string $ source , string $ prefix = '' , array $ excludedDirs = [] ): void
353+ protected function addDirectoryToZip (\ZipArchive $ zip , string $ source , string $ prefix = '' ): void
488354 {
489355 $ source = rtrim (str_replace ('\\' , '/ ' , $ source ), '/ ' ).'/ ' ;
490356
@@ -497,32 +363,8 @@ protected function addDirectoryToZip(\ZipArchive $zip, string $source, string $p
497363 $ filePath = str_replace ('\\' , '/ ' , $ file ->getRealPath ());
498364 $ relativePath = ltrim (str_replace ('\\' , '/ ' , substr ($ filePath , strlen ($ source ))), '/ ' );
499365
500- // Check against configured exclusions first
501- $ shouldExclude = false ;
502- foreach ($ excludedDirs as $ excludedDir ) {
503- // Handle wildcard patterns (e.g., "public/fonts/*")
504- if (str_contains ($ excludedDir , '* ' )) {
505- $ pattern = str_replace ('* ' , '.* ' , preg_quote ($ excludedDir , '/ ' ));
506- if (preg_match ('/^ ' .$ pattern .'/ ' , $ relativePath )) {
507- $ shouldExclude = true ;
508- break ;
509- }
510- } else {
511- // Exact directory matching
512- if (Str::startsWith ($ relativePath , rtrim ($ excludedDir , '/ ' ).'/ ' ) || $ relativePath === rtrim ($ excludedDir , '/ ' )) {
513- $ shouldExclude = true ;
514- break ;
515- }
516- }
517- }
518-
519- // Safety net for files that may be created between rsync and zip
520- if ($ shouldExclude ||
521- Str::startsWith ($ relativePath , 'storage/framework/views/ ' ) ||
522- Str::startsWith ($ relativePath , 'storage/framework/cache/ ' ) ||
523- Str::startsWith ($ relativePath , 'storage/framework/sessions/ ' ) ||
524- Str::startsWith ($ relativePath , 'storage/app/native-build ' ) ||
525- Str::startsWith ($ relativePath , 'bootstrap/cache/ ' ) ||
366+ // Safety net for files that may be created between copy/cleanup and zip
367+ if (Str::startsWith ($ relativePath , 'bootstrap/cache/ ' ) ||
526368 Str::startsWith ($ relativePath , '.idea ' ) ||
527369 Str::endsWith ($ relativePath , '.jks ' ) ||
528370 Str::endsWith ($ relativePath , '.zip ' )) {
@@ -990,6 +832,4 @@ abstract protected function updateIcuConfiguration(): void;
990832 abstract protected function updateFirebaseConfiguration (): void ;
991833
992834 abstract protected function removeDirectory (string $ path ): void ;
993-
994- abstract protected function platformOptimizedCopy (string $ source , string $ destination , array $ excludedDirs ): void ;
995835}
0 commit comments