#!/usr/bin/env php =')) { exit(0); } function removeTree(string $path): bool { if (is_link($path) || is_file($path)) { return unlink($path); } $entries = scandir($path); if ($entries === false) { return false; } $ok = true; foreach ($entries as $entry) { if ($entry === '.' || $entry === '..') { continue; } $removed = removeTree($path . '/' . $entry); $ok = $ok && $removed; } $removed = rmdir($path); return $ok && $removed; } function copyTree(string $from, string $to): bool { if (!is_dir($to) && !mkdir($to, 0o755, true)) { return false; } $entries = scandir($from); if ($entries === false) { return false; } $ok = true; foreach ($entries as $entry) { if ($entry === '.' || $entry === '..') { continue; } $source = $from . '/' . $entry; $target = $to . '/' . $entry; if (is_link($source)) { $copied = symlink((string) readlink($source), $target); } elseif (is_dir($source)) { $copied = copyTree($source, $target); } else { $copied = copy($source, $target); } $ok = $ok && $copied; } return $ok; } // Stage the copy beside the nested copy so a failed copy never leaves the working // tree half-replaced: the existing directory is only swapped out once the staged // copy is complete. Never fail the install. $stagingDir = $nestedPackageDir . '.staging-' . getmypid(); $backupDir = $nestedPackageDir . '.backup-' . getmypid(); if (!copyTree($rootPackageDir, $stagingDir) || !is_file($stagingDir . '/src/Ast/Type/CallableTypeNode.php')) { removeTree($stagingDir); out('warning: sync failed, keeping the existing nested copy for a later run to retry'); exit(0); } if (!rename($nestedPackageDir, $backupDir)) { removeTree($stagingDir); out('warning: sync failed, keeping the existing nested copy for a later run to retry'); exit(0); } if (!rename($stagingDir, $nestedPackageDir)) { if (!rename($backupDir, $nestedPackageDir)) { out('warning: sync failed and the nested copy could not be restored from ' . $backupDir); exit(0); } removeTree($stagingDir); out('warning: sync failed, keeping the existing nested copy for a later run to retry'); exit(0); } removeTree($backupDir); // Keep the nested metadata truthful so repeat runs are a no-op. Composer // never reads this file (the nested copy is not composer-managed); it only // feeds the version guard above. $nestedInstalledFile = $vendorDir . '/rector/rector/vendor/composer/installed.json'; $nestedInstalled = is_file($nestedInstalledFile) ? json_decode((string) file_get_contents($nestedInstalledFile), true) : null; if (is_array($nestedInstalled)) { foreach (($nestedInstalled['packages'] ?? []) as $index => $entry) { if (($entry['name'] ?? null) === 'phpstan/phpdoc-parser') { $nestedInstalled['packages'][$index]['version'] = $rootVersion; file_put_contents($nestedInstalledFile, json_encode($nestedInstalled, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n"); break; } } } out("synced nested copy {$nestedVersion} -> project copy {$rootVersion}");