Vendor-customization restore kit
A self-contained PHP script that re-copies your customized vendor files back into vendor/ after Composer overwrites them with the vanilla CodeCanyon versions. Drop it next to your other utility scripts, run it after every composer install or vendor update, and your edits come back automatically.
What it solves
Section titled “What it solves”When you must edit a file inside vendor/, that edit is fragile — Composer owns vendor/, so the next composer install, composer update, or CodeCanyon release replaces the file with the vanilla version and your change is gone. The update-safe pattern (see the customizations playbook) is to keep the edited copy in an overlay folder that mirrors vendor paths, then re-apply it. This script is the re-apply step.
| Overlay source | resources/vendor-customizations/ (one subfolder per customized package) |
| Also accepts | resources/vendor-customizations.zip (auto-extracted if the folder is missing) |
| Target | vendor/ |
| What it does | Recursively copies each customized package’s files over the matching vendor package |
| When to run | After every composer install, composer update, or vendor/CodeCanyon update |
| Safe to re-run | Yes — it only copies files; nothing is deleted |
The overlay mirrors the real vendor layout. For example, a customized installer package lives at resources/vendor-customizations/vendor-name/package-name/ and is copied onto vendor/vendor-name/package-name/. Mark the lines you changed inside each file with ZAJ:BEGIN / ZAJ:END comments so the next person (or the next you) can see exactly what was patched and why.
How it works, step by step
Section titled “How it works, step by step”- Find the overlay. Looks for
resources/vendor-customizations/. If that folder is missing butresources/vendor-customizations.zipexists, it extracts the zip intoresources/first. - Sanity-check. If there is no overlay, it prints an informational message and exits cleanly (exit
0). Ifvendor/itself is missing, it warns you to runcomposer installfirst and exits1. - Scan packages. Each top-level subfolder of the overlay is treated as one customized package.
- Restore each package. For every package that also exists under
vendor/, it recursively copies the overlay files into the matching vendor folder, creating directories as needed and listing each file it copies. Packages not present invendor/are skipped with a warning. - Report. Prints a summary of how many packages were restored and how many were skipped, then exits
0.
The script never deletes anything in vendor/ — it only overwrites the specific files you customized. Running it twice is harmless.
How to run it
Section titled “How to run it”The script assumes it lives one level below the project root (it computes the project root as the parent of its own folder). Place it in your scripts/utility folder, then run it from the command line.
# From anywhere — pass the script pathphp path/to/restore-vendor-customizations.phpcomposer installphp path/to/restore-vendor-customizations.php// composer.json — re-apply automatically on every install/update{ "scripts": { "post-install-cmd": [ "@php path/to/restore-vendor-customizations.php" ], "post-update-cmd": [ "@php path/to/restore-vendor-customizations.php" ] }}Wiring it into the Composer post-install-cmd / post-update-cmd hooks (the third tab) is the hands-off option: every composer install then re-applies your overlay automatically, so you can never forget the step.
Exit codes
Section titled “Exit codes”| Code | Meaning |
|---|---|
0 | Done — overlay restored, or nothing to restore (no overlay found). |
1 | vendor/ is missing — run composer install first, then re-run. |
The script
Section titled “The script”Reproduced faithfully — copy it into your utility scripts folder as restore-vendor-customizations.php.
#!/usr/bin/env php<?php/** * Universal Vendor Customization Restoration Script * * Automatically restores CodeCanyon author customizations to vendor packages * after composer install/update overwrites them with vanilla versions. * * Works for ANY customized package, not just laravel-installer. * * @author Custojo Development Team * @date 2025-11-24 */
echo "\n";echo "════════════════════════════════════════════════════════════════\n";echo " VENDOR CUSTOMIZATION RESTORATION\n";echo "════════════════════════════════════════════════════════════════\n";echo "\n";
// Paths$projectRoot = dirname(__DIR__);$vendorDir = $projectRoot . '/vendor';$customizationsDir = $projectRoot . '/resources/vendor-customizations';
// Also check for zip file$zipFile = $projectRoot . '/resources/vendor-customizations.zip';if (file_exists($zipFile) && !is_dir($customizationsDir)) { echo "📦 Found vendor-customizations.zip, extracting...\n"; $zip = new ZipArchive; if ($zip->open($zipFile) === TRUE) { $zip->extractTo($projectRoot . '/resources/'); $zip->close(); echo " ✅ Extracted vendor customizations\n\n"; }}
// Check if customizations directory existsif (!is_dir($customizationsDir)) { echo "ℹ️ No vendor customizations to restore.\n"; echo " (Directory not found: $customizationsDir)\n"; echo "\n"; exit(0);}
// Check if vendor directory existsif (!is_dir($vendorDir)) { echo "⚠️ Vendor directory not found: $vendorDir\n"; echo " Run 'composer install' first.\n"; echo "\n"; exit(1);}
// Scan for customized packages$customizedPackages = array_filter( scandir($customizationsDir), function($item) use ($customizationsDir) { return $item !== '.' && $item !== '..' && is_dir($customizationsDir . '/' . $item); });
if (empty($customizedPackages)) { echo "ℹ️ No vendor customizations to restore.\n"; echo " (No packages found in $customizationsDir)\n"; echo "\n"; exit(0);}
echo "Found " . count($customizedPackages) . " customized package(s):\n";foreach ($customizedPackages as $package) { echo " 📦 $package\n";}echo "\n";
// Restore each package$restored = 0;$failed = 0;
foreach ($customizedPackages as $package) { echo "Restoring $package...\n";
$sourceDir = $customizationsDir . '/' . $package; $targetDir = $vendorDir . '/' . $package;
// Check if package exists in vendor if (!is_dir($targetDir)) { echo " ⚠️ Package not found in vendor/: $package\n"; echo " Skipping (package may have been removed or renamed)\n"; $failed++; continue; }
// Copy customized files recursively $filesCopied = copyDirectory($sourceDir, $targetDir);
if ($filesCopied > 0) { echo " ✅ Restored $filesCopied file(s)\n"; $restored++; } else { echo " ⚠️ No files copied\n"; $failed++; }}
echo "\n";echo "════════════════════════════════════════════════════════════════\n";echo " RESTORATION COMPLETE\n";echo "════════════════════════════════════════════════════════════════\n";echo "\n";echo "Summary:\n";echo " ✅ Restored: $restored package(s)\n";if ($failed > 0) { echo " ⚠️ Failed: $failed package(s)\n";}echo "\n";
exit(0);
/** * Recursively copy directory * * @param string $source Source directory * @param string $target Target directory * @return int Number of files copied */function copyDirectory($source, $target) { $filesCopied = 0;
if (!is_dir($source)) { return 0; }
$iterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($source, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::SELF_FIRST );
foreach ($iterator as $item) { $targetPath = $target . DIRECTORY_SEPARATOR . $iterator->getSubPathName();
if ($item->isDir()) { if (!is_dir($targetPath)) { mkdir($targetPath, 0755, true); } } else { // Create parent directory if needed $targetParent = dirname($targetPath); if (!is_dir($targetParent)) { mkdir($targetParent, 0755, true); }
// Copy file if (copy($item, $targetPath)) { $filesCopied++; echo " 📄 " . $iterator->getSubPathName() . "\n"; } } }
return $filesCopied;}Where it fits in the workflow
Section titled “Where it fits in the workflow”This script is one moving part of the larger customization system. Build the overlay in the customizations workflow; run this script in the vendor-updates workflow.