@@ -225,12 +225,14 @@ protected function prepareLaravelBundle(bool $excludeDevDependencies = true): vo
225225 unlink ($ destinationZip );
226226 }
227227
228- $ excludedDirs = match (PHP_OS_FAMILY ) {
229- 'Windows ' => array_merge (config ('nativephp.cleanup_exclude_files ' ), ['.git ' , 'node_modules ' , 'nativephp ' , 'vendor/nativephp/mobile/resources ' ]),
230- 'Linux ' => array_merge (config ('nativephp.cleanup_exclude_files ' ), ['.git ' , 'node_modules ' , 'nativephp/ios ' , 'nativephp/android ' ]),
231- 'Darwin ' => array_merge (config ('nativephp.cleanup_exclude_files ' ), ['.git ' , 'node_modules ' , 'nativephp/ios ' , 'nativephp/android ' ]),
232- default => config ('nativephp.cleanup_exclude_files ' ),
233- };
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+ }
234236
235237 $ this ->logToFile (' Excluded directories: ' .implode (', ' , $ excludedDirs ));
236238
@@ -304,6 +306,129 @@ protected function prepareLaravelBundle(bool $excludeDevDependencies = true): vo
304306 }
305307 }
306308
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+
307432 /**
308433 * Create ZIP bundle with cross-platform support
309434 */
@@ -363,12 +488,12 @@ protected function addDirectoryToZip(\ZipArchive $zip, string $source, string $p
363488 {
364489 $ source = rtrim (str_replace ('\\' , '/ ' , $ source ), '/ ' ).'/ ' ;
365490
366- $ files = iterator_to_array ( new \RecursiveIteratorIterator (
491+ $ iterator = new \RecursiveIteratorIterator (
367492 new \RecursiveDirectoryIterator ($ source , \RecursiveDirectoryIterator::SKIP_DOTS ),
368493 \RecursiveIteratorIterator::LEAVES_ONLY
369- )) ;
494+ );
370495
371- foreach ($ files as $ file ) {
496+ foreach ($ iterator as $ file ) {
372497 $ filePath = str_replace ('\\' , '/ ' , $ file ->getRealPath ());
373498 $ relativePath = ltrim (str_replace ('\\' , '/ ' , substr ($ filePath , strlen ($ source ))), '/ ' );
374499
@@ -391,19 +516,14 @@ protected function addDirectoryToZip(\ZipArchive $zip, string $source, string $p
391516 }
392517 }
393518
394- // Always exclude these directories
519+ // Safety net for files that may be created between rsync and zip
395520 if ($ shouldExclude ||
396- Str::startsWith ($ relativePath , 'vendor/nativephp/mobile/resources ' ) ||
397- Str::startsWith ($ relativePath , 'vendor/nativephp/mobile/vendor ' ) ||
398- Str::startsWith ($ relativePath , '.idea ' ) ||
399- Str::startsWith ($ relativePath , 'output ' ) ||
400521 Str::startsWith ($ relativePath , 'storage/framework/views/ ' ) ||
401522 Str::startsWith ($ relativePath , 'storage/framework/cache/ ' ) ||
402523 Str::startsWith ($ relativePath , 'storage/framework/sessions/ ' ) ||
403524 Str::startsWith ($ relativePath , 'storage/app/native-build ' ) ||
404525 Str::startsWith ($ relativePath , 'bootstrap/cache/ ' ) ||
405- Str::startsWith ($ relativePath , 'nativephp ' ) ||
406- Str::startsWith ($ relativePath , 'public/storage ' ) ||
526+ Str::startsWith ($ relativePath , '.idea ' ) ||
407527 Str::endsWith ($ relativePath , '.jks ' ) ||
408528 Str::endsWith ($ relativePath , '.zip ' )) {
409529 continue ;
0 commit comments