Skip to content
prod e051e98
Browse

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.

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 sourceresources/vendor-customizations/ (one subfolder per customized package)
Also acceptsresources/vendor-customizations.zip (auto-extracted if the folder is missing)
Targetvendor/
What it doesRecursively copies each customized package’s files over the matching vendor package
When to runAfter every composer install, composer update, or vendor/CodeCanyon update
Safe to re-runYes — 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.

  1. Find the overlay. Looks for resources/vendor-customizations/. If that folder is missing but resources/vendor-customizations.zip exists, it extracts the zip into resources/ first.
  2. Sanity-check. If there is no overlay, it prints an informational message and exits cleanly (exit 0). If vendor/ itself is missing, it warns you to run composer install first and exits 1.
  3. Scan packages. Each top-level subfolder of the overlay is treated as one customized package.
  4. 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 in vendor/ are skipped with a warning.
  5. 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.

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.

Terminal window
# From anywhere — pass the script path
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.

CodeMeaning
0Done — overlay restored, or nothing to restore (no overlay found).
1vendor/ is missing — run composer install first, then re-run.

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 exists
if (!is_dir($customizationsDir)) {
echo "ℹ️ No vendor customizations to restore.\n";
echo " (Directory not found: $customizationsDir)\n";
echo "\n";
exit(0);
}
// Check if vendor directory exists
if (!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;
}

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.