From 3c420284e320e87d302c7b35c7333ee48832b8ed Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Fri, 26 Jun 2026 21:11:46 -0500 Subject: [PATCH] fix: harden nested package build against collisions and missing assets - Write nested sub-extension ZIPs to subdirectory to prevent filename collisions with outer package ZIPs (deferred ZipArchive::addFile reads) - Apply exclude patterns to nested top-level files (parity with outer) - Patch nested manifest with folder="packages" so Joomla finds sub-ZIPs - Include all top-level directories (not just language/) in nested packages Claude-Session: https://claude.ai/code/session_01MbEjBtsSjPuTWhqqrMS2wG --- cli/release_package.php | 38 +++++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/cli/release_package.php b/cli/release_package.php index fa0dc5d..665aaaf 100644 --- a/cli/release_package.php +++ b/cli/release_package.php @@ -285,7 +285,6 @@ class ReleasePackageCli extends CliFramework echo " Building nested package: {$subName}\n"; $nestedPkgDirs = glob("{$subSourceDir}/packages/*", GLOB_ONLYDIR) ?: []; - // Read nested manifest to filter only listed sub-extensions $nestedManifested = []; foreach ($nestedPkgManifests as $npmf) { $npmXml = @simplexml_load_file($npmf); @@ -299,12 +298,16 @@ class ReleasePackageCli extends CliFramework } } + // Use a subdirectory to avoid filename collisions with outer ZIPs + $nestedOutputDir = "{$outputDir}/nested_{$subName}"; + @mkdir($nestedOutputDir, 0755, true); + foreach ($nestedPkgDirs as $npd) { $nestedSubName = basename($npd); if (!empty($nestedManifested) && !isset($nestedManifested[$nestedSubName])) { continue; } - $nestedSubZipPath = "{$outputDir}/{$nestedSubName}.zip"; + $nestedSubZipPath = "{$nestedOutputDir}/{$nestedSubName}.zip"; $nsZip = new \ZipArchive(); if ($nsZip->open($nestedSubZipPath, \ZipArchive::CREATE | \ZipArchive::OVERWRITE) !== true) { continue; @@ -315,17 +318,38 @@ class ReleasePackageCli extends CliFramework echo " Nested sub: {$nestedSubName}.zip\n"; } - // Add top-level files (manifest, script, language) + // Patch nested manifest: ensure folder="packages" so Joomla finds sub-ZIPs + foreach ($nestedPkgManifests as $npmf) { + $npmContent = file_get_contents($npmf); + if ($npmContent !== false + && strpos($npmContent, '') !== false + && strpos($npmContent, 'folder="packages"') === false + ) { + $npmContent = str_replace('', '', $npmContent); + file_put_contents($npmf, $npmContent); + echo " Fixed: added folder=\"packages\" to " . basename($npmf) . "\n"; + } + } + + // Add top-level files (manifest, script, etc.) with exclude filter $nestedTopFiles = array_merge( glob("{$subSourceDir}/*.xml") ?: [], glob("{$subSourceDir}/*.php") ?: [] ); foreach ($nestedTopFiles as $ntf) { - $subZip->addFile($ntf, basename($ntf)); + if (!$this->isExcluded(basename($ntf), $this->excludePatterns)) { + $subZip->addFile($ntf, basename($ntf)); + } } - $nestedLangDir = "{$subSourceDir}/language"; - if (is_dir($nestedLangDir)) { - $this->addDirToZip($subZip, $nestedLangDir, 'language', $this->excludePatterns); + + // Add all top-level directories except packages/ + $nestedTopDirs = glob("{$subSourceDir}/*", GLOB_ONLYDIR) ?: []; + foreach ($nestedTopDirs as $ntd) { + $ndName = basename($ntd); + if ($ndName === 'packages') { + continue; + } + $this->addDirToZip($subZip, $ntd, $ndName, $this->excludePatterns); } } else { $this->addDirToZip($subZip, $subSourceDir, '', $this->excludePatterns);