Skip to content

Instantly share code, notes, and snippets.

@egyjs
Last active October 27, 2025 21:38
Show Gist options
  • Save egyjs/728de2deb6f750936c096587ec5ddf0b to your computer and use it in GitHub Desktop.
Save egyjs/728de2deb6f750936c096587ec5ddf0b to your computer and use it in GitHub Desktop.
NinjaPortal Local Development Setup Script
#!/usr/bin/env php
<?php
/**
* Rollback Local Development Setup
*
* This script reverts changes made by setup-local-dev.php:
* - Restores composer.json from backup
* - Optionally removes local package clones
* - Runs composer update to use remote packages again
*
* Usage:
* Local: php rollback-local-dev.php
* Remote: curl -fsSL https://gist.githubusercontent.com/egyjs/728de2deb6f750936c096587ec5ddf0b/raw/1aede9f68585646a653465504d1695d5a5f3822e/rollback-local-dev.php | php
*
* @version 1.0.0
* @author NinjaPortal Team
*/
// Colors for terminal output
const COLOR_GREEN = "\033[0;32m";
const COLOR_BLUE = "\033[0;34m";
const COLOR_YELLOW = "\033[1;33m";
const COLOR_RED = "\033[0;31m";
const COLOR_NC = "\033[0m";
const COMPOSER_FILE = './composer.json';
const COMPOSER_BACKUP = './composer.json.backup';
const PACKAGES_DIR = './packages/ninjaportal';
function println($message, $color = COLOR_NC) {
echo $color . $message . COLOR_NC . PHP_EOL;
}
function printHeader($message) {
println("\n=========================================", COLOR_BLUE);
println($message, COLOR_BLUE);
println("=========================================\n", COLOR_BLUE);
}
function deleteDirectory($dir) {
if (!is_dir($dir)) {
return true;
}
$items = array_diff(scandir($dir), ['.', '..']);
foreach ($items as $item) {
$path = $dir . DIRECTORY_SEPARATOR . $item;
is_dir($path) ? deleteDirectory($path) : unlink($path);
}
return rmdir($dir);
}
try {
printHeader("Rollback Local Development Setup");
// Step 1: Check for backup
println("Step 1: Checking for composer.json backup...", COLOR_GREEN);
if (!file_exists(COMPOSER_BACKUP)) {
println("Warning: No backup file found at " . COMPOSER_BACKUP, COLOR_YELLOW);
println("Cannot restore composer.json automatically.", COLOR_YELLOW);
echo "\nDo you want to manually remove 'repositories' from composer.json? (y/n): ";
$handle = fopen("php://stdin", "r");
$line = fgets($handle);
fclose($handle);
if (trim(strtolower($line)) === 'y') {
$composerData = json_decode(file_get_contents(COMPOSER_FILE), true);
if ($composerData && isset($composerData['repositories'])) {
unset($composerData['repositories']);
// Also remove local PSR-4 mappings
if (isset($composerData['autoload']['psr-4'])) {
foreach ($composerData['autoload']['psr-4'] as $namespace => $path) {
if (strpos($path, './packages/ninjaportal/') === 0) {
unset($composerData['autoload']['psr-4'][$namespace]);
}
}
}
$json = json_encode($composerData, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
file_put_contents(COMPOSER_FILE, $json . "\n");
println(" βœ“ Removed local repositories from composer.json", COLOR_GREEN);
}
} else {
println("Skipped composer.json modification.", COLOR_YELLOW);
}
} else {
println("Found backup: " . COMPOSER_BACKUP, COLOR_YELLOW);
// Step 2: Restore backup
println("\nStep 2: Restoring composer.json from backup...", COLOR_GREEN);
// Create a backup of current state before restoring
$currentBackup = COMPOSER_FILE . '.before-rollback';
copy(COMPOSER_FILE, $currentBackup);
println(" Created backup of current state: $currentBackup", COLOR_YELLOW);
// Restore from backup
copy(COMPOSER_BACKUP, COMPOSER_FILE);
println(" βœ“ composer.json restored from backup", COLOR_GREEN);
}
// Step 3: Ask about removing local packages
println("\nStep 3: Remove local package clones...", COLOR_GREEN);
if (is_dir(PACKAGES_DIR)) {
$packages = array_filter(scandir(PACKAGES_DIR), function($item) {
return $item !== '.' && $item !== '..' && is_dir(PACKAGES_DIR . '/' . $item);
});
if (!empty($packages)) {
println("Found " . count($packages) . " local packages:", COLOR_YELLOW);
foreach ($packages as $pkg) {
println(" - $pkg");
}
echo "\nDo you want to DELETE these local packages? (y/n): ";
$handle = fopen("php://stdin", "r");
$line = fgets($handle);
fclose($handle);
if (trim(strtolower($line)) === 'y') {
println(" Removing local packages...", COLOR_BLUE);
foreach ($packages as $pkg) {
$pkgPath = PACKAGES_DIR . '/' . $pkg;
if (deleteDirectory($pkgPath)) {
println(" βœ“ Removed $pkg", COLOR_GREEN);
} else {
println(" βœ— Failed to remove $pkg", COLOR_RED);
}
}
// Remove packages directory if empty
$remaining = array_diff(scandir(PACKAGES_DIR), ['.', '..']);
if (empty($remaining)) {
rmdir(PACKAGES_DIR);
println(" βœ“ Removed empty packages directory", COLOR_GREEN);
}
} else {
println(" Kept local packages (not deleted)", COLOR_YELLOW);
println(" Note: You can delete them manually later if needed", COLOR_YELLOW);
}
} else {
println("No local packages found.", COLOR_YELLOW);
}
} else {
println("Packages directory doesn't exist.", COLOR_YELLOW);
}
// Step 4: Run composer update
println("\nStep 4: Running composer update...", COLOR_GREEN);
println(" This will reinstall packages from remote repositories.", COLOR_YELLOW);
echo " Do you want to run 'composer update' now? (y/n): ";
$handle = fopen("php://stdin", "r");
$line = fgets($handle);
fclose($handle);
if (trim(strtolower($line)) === 'y') {
println(" Running composer update...", COLOR_BLUE);
passthru('composer update');
println(" βœ“ Composer update completed", COLOR_GREEN);
} else {
println(" Skipped. Run 'composer update' manually to complete rollback.", COLOR_YELLOW);
}
// Summary
printHeader("Rollback Complete!");
println("You are now back to using remote packages.\n", COLOR_GREEN);
println("What was done:", COLOR_YELLOW);
println(" βœ“ composer.json restored (backup saved)");
println(" βœ“ Local packages removed (if selected)");
println(" βœ“ Composer updated (if selected)");
println("\nNext steps:", COLOR_YELLOW);
println(" 1. Run 'composer update' if you haven't already");
println(" 2. Continue working with remote packages");
println(" 3. Run setup-local-dev.php again if you need local development");
println("\nDone! πŸ‘", COLOR_BLUE);
} catch (Exception $e) {
println("\nError: " . $e->getMessage(), COLOR_RED);
exit(1);
}
#!/usr/bin/env php
<?php
/**
* NinjaPortal Local Development Setup Script
*
* This script automates the process of setting up local development for ninjaportal packages:
* 1. Lists all ninjaportal packages in vendor/
* 2. Clones them from GitHub to packages/ninjaportal/
* 3. Updates composer.json to use local repositories with symlinks
* 4. Configures composer to use local path repositories with locked versions
* 5. Updates PSR-4 autoload mappings
*
* IMPORTANT: Your original composer.json is backed up as composer.json.backup and
* can be restored using rollback-local-dev.php
*
* Usage:
* Local: php setup-local-dev.php
* Remote: url -fsSL https://gist.githubusercontent.com/egyjs/728de2deb6f750936c096587ec5ddf0b/raw/96302b943ae6320ac1f383125a92397f0c7a0351/setup-local-dev.php | php
*
* @version 1.1.0
* @author NinjaPortal Team
*/
// Colors for terminal output
const COLOR_GREEN = "\033[0;32m";
const COLOR_BLUE = "\033[0;34m";
const COLOR_YELLOW = "\033[1;33m";
const COLOR_RED = "\033[0;31m";
const COLOR_NC = "\033[0m";
// Configuration
const VENDOR_DIR = './vendor/ninjaportal';
const PACKAGES_DIR = './packages/ninjaportal';
const GITHUB_ORG = 'ninjaportal';
const COMPOSER_FILE = './composer.json';
const COMPOSER_LOCK_FILE = './composer.lock';
function println($message, $color = COLOR_NC) {
echo $color . $message . COLOR_NC . PHP_EOL;
}
function printHeader($message) {
println("\n=========================================", COLOR_BLUE);
println($message, COLOR_BLUE);
println("=========================================\n", COLOR_BLUE);
}
function execCommand($command, &$output = null, &$returnCode = null) {
exec($command . ' 2>&1', $output, $returnCode);
return $returnCode === 0;
}
function getInstalledPackageVersions(): array {
if (!file_exists(COMPOSER_LOCK_FILE)) {
return [];
}
$lockData = json_decode(file_get_contents(COMPOSER_LOCK_FILE), true);
if (!is_array($lockData)) {
return [];
}
$versions = [];
foreach (['packages', 'packages-dev'] as $section) {
if (empty($lockData[$section]) || !is_array($lockData[$section])) {
continue;
}
foreach ($lockData[$section] as $package) {
if (isset($package['name'], $package['version'])) {
$versions[$package['name']] = $package['version'];
}
}
}
return $versions;
}
function checkoutPackageVersion(string $packagePath, string $packageName, ?string $version): bool {
if (!$version) {
return false;
}
$originalDir = getcwd();
chdir($packagePath);
$statusOutput = [];
execCommand('git status --porcelain', $statusOutput, $statusCode);
if ($statusCode === 0 && !empty(array_filter($statusOutput))) {
println(" ⚠ Local changes detected in $packageName. Skipping checkout of $version.", COLOR_YELLOW);
chdir($originalDir);
return false;
}
execCommand('git fetch --tags --quiet', $fetchOutput, $fetchCode);
if ($fetchCode !== 0) {
println(" ⚠ Failed to fetch tags for $packageName.", COLOR_YELLOW);
chdir($originalDir);
return false;
}
$tagReference = escapeshellarg("refs/tags/$version");
$tagCheckOutput = [];
execCommand("git rev-parse -q --verify $tagReference", $tagCheckOutput, $tagExists);
if ($tagExists !== 0) {
println(" ⚠ Tag $version not found for $packageName.", COLOR_YELLOW);
chdir($originalDir);
return false;
}
$escapedVersion = escapeshellarg($version);
execCommand("git checkout --force --detach $escapedVersion", $checkoutOutput, $checkoutCode);
chdir($originalDir);
if ($checkoutCode !== 0) {
println(" ⚠ Failed to check out $version for $packageName.", COLOR_YELLOW);
return false;
}
println(" βœ“ Checked out $version", COLOR_GREEN);
return true;
}
// Main execution
try {
printHeader("NinjaPortal Local Development Setup");
// Step 1: List packages
println("Step 1: Listing ninjaportal packages in vendor...", COLOR_GREEN);
if (!is_dir(VENDOR_DIR)) {
println("Error: " . VENDOR_DIR . " directory not found!", COLOR_RED);
println("Please run 'composer install' first.", COLOR_YELLOW);
exit(1);
}
$packages = array_filter(scandir(VENDOR_DIR), function($item) {
return $item !== '.' && $item !== '..' && is_dir(VENDOR_DIR . '/' . $item);
});
println("Found " . count($packages) . " packages:", COLOR_YELLOW);
foreach ($packages as $pkg) {
println(" - $pkg");
}
$installedVersions = getInstalledPackageVersions();
if (empty($installedVersions)) {
println("Warning: composer.lock not found or unreadable. Falling back to default branches.", COLOR_YELLOW);
}
// Step 2: Create packages directory
println("\nStep 2: Creating packages directory...", COLOR_GREEN);
if (!is_dir(PACKAGES_DIR)) {
mkdir(PACKAGES_DIR, 0755, true);
println("Created: " . PACKAGES_DIR, COLOR_YELLOW);
} else {
println("Directory already exists: " . PACKAGES_DIR, COLOR_YELLOW);
}
// Step 3: Clone repositories
println("\nStep 3: Cloning repositories...", COLOR_GREEN);
$clonedPackages = [];
$packageVersions = [];
foreach ($packages as $pkg) {
$packageName = GITHUB_ORG . '/' . $pkg;
$targetVersion = $installedVersions[$packageName] ?? null;
$pkgPath = PACKAGES_DIR . '/' . $pkg;
$packageVersions[$pkg] = $targetVersion;
if (is_dir($pkgPath)) {
if ($targetVersion) {
println(" ⚠ Package '$pkg' already exists, aligning to $targetVersion...", COLOR_YELLOW);
if (!checkoutPackageVersion($pkgPath, $packageName, $targetVersion)) {
println(" Using existing checkout for $pkg", COLOR_YELLOW);
}
} else {
println(" ⚠ Package '$pkg' already exists, pulling latest changes...", COLOR_YELLOW);
chdir($pkgPath);
$output = [];
execCommand("git pull origin main", $output, $returnCode);
if ($returnCode !== 0) {
execCommand("git pull origin master", $output, $returnCode);
}
if ($returnCode === 0) {
println(" βœ“ Updated $pkg", COLOR_GREEN);
} else {
println(" Could not pull, using existing version", COLOR_YELLOW);
}
chdir(__DIR__);
}
$clonedPackages[] = $pkg;
} else {
println(" πŸ“¦ Cloning $pkg...", COLOR_BLUE);
$repoUrl = "https://github.com/" . GITHUB_ORG . "/$pkg.git";
$output = [];
if (execCommand("git clone $repoUrl $pkgPath", $output, $returnCode)) {
println(" βœ“ Successfully cloned $pkg", COLOR_GREEN);
if ($targetVersion) {
println(" Aligning $pkg to $targetVersion", COLOR_BLUE);
checkoutPackageVersion($pkgPath, $packageName, $targetVersion);
} else {
println(" ⚠ No locked version found; using repository default branch", COLOR_YELLOW);
}
$clonedPackages[] = $pkg;
} else {
println(" βœ— Failed to clone $pkg from $repoUrl", COLOR_RED);
println(" Please check if the repository exists and you have access", COLOR_YELLOW);
}
}
}
if (empty($clonedPackages)) {
println("\nNo packages were cloned. Exiting...", COLOR_YELLOW);
exit(0);
}
// Step 4: Update composer.json
println("\nStep 4: Updating composer.json...", COLOR_GREEN);
// Create backup
$backupFile = COMPOSER_FILE . '.backup';
copy(COMPOSER_FILE, $backupFile);
println(" Backup created: $backupFile", COLOR_YELLOW);
// Load composer.json
$composerData = json_decode(file_get_contents(COMPOSER_FILE), true);
if (!$composerData) {
throw new Exception("Failed to parse composer.json");
}
// Add repositories
$repositories = [];
foreach ($clonedPackages as $pkg) {
$packageName = GITHUB_ORG . '/' . $pkg;
$repositoryOptions = [
'symlink' => true
];
if (!empty($packageVersions[$pkg])) {
$repositoryOptions['versions'] = [
$packageName => $packageVersions[$pkg]
];
}
$repositories[] = [
'type' => 'path',
'url' => './packages/ninjaportal/' . $pkg,
'options' => $repositoryOptions
];
}
$composerData['repositories'] = $repositories;
// Step 5: Preserve existing version constraints
println("\nStep 5: Preserving version constraints (no changes needed)", COLOR_GREEN);
// Step 6: Update PSR-4 autoload
println("\nStep 6: Updating PSR-4 autoload mappings...", COLOR_GREEN);
if (!isset($composerData['autoload'])) {
$composerData['autoload'] = [];
}
if (!isset($composerData['autoload']['psr-4'])) {
$composerData['autoload']['psr-4'] = [];
}
// Read PSR-4 mappings from each package
foreach ($clonedPackages as $pkg) {
$pkgComposerFile = PACKAGES_DIR . '/' . $pkg . '/composer.json';
if (file_exists($pkgComposerFile)) {
$pkgComposer = json_decode(file_get_contents($pkgComposerFile), true);
if ($pkgComposer && isset($pkgComposer['autoload']['psr-4'])) {
foreach ($pkgComposer['autoload']['psr-4'] as $namespace => $path) {
// Map to local package path
$localPath = './packages/ninjaportal/' . $pkg . '/' . ltrim($path, './');
$composerData['autoload']['psr-4'][$namespace] = $localPath;
println(" Added: $namespace => $localPath", COLOR_YELLOW);
}
}
}
}
// Save updated composer.json
$json = json_encode($composerData, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
file_put_contents(COMPOSER_FILE, $json . "\n");
println(" βœ“ composer.json updated", COLOR_GREEN);
// Step 7: Prompt for composer update
println("\nStep 7: Running composer update...", COLOR_GREEN);
println(" This will install packages from local paths instead of remote repositories.", COLOR_YELLOW);
echo " Do you want to run 'composer update' now? (y/n): ";
$handle = fopen("php://stdin", "r");
$line = fgets($handle);
fclose($handle);
if (trim(strtolower($line)) === 'y') {
println(" Running composer update...", COLOR_BLUE);
passthru('composer update');
println(" βœ“ Composer update completed", COLOR_GREEN);
} else {
println(" Skipped. Run 'composer update' manually when ready.", COLOR_YELLOW);
}
// Summary
printHeader("Setup Complete!");
println("Local development environment is ready!\n", COLOR_GREEN);
println("What was changed:", COLOR_YELLOW);
println(" βœ“ Added path repositories for local packages with locked versions");
println(" βœ“ Preserved root version constraints");
println(" βœ“ Updated PSR-4 autoload mappings");
println(" βœ“ Created backup: composer.json.backup");
println("\nNext steps:", COLOR_YELLOW);
println(" 1. Run 'composer update' if you haven't already");
println(" 2. Start developing in ./packages/ninjaportal/<package-name>");
println(" 3. Changes will be reflected immediately via symlinks");
println(" 4. Commit and push changes from the package directories");
println("\nAvailable packages:", COLOR_YELLOW);
foreach ($clonedPackages as $pkg) {
println(" βœ“ $pkg -> " . PACKAGES_DIR . "/$pkg");
}
println("\nTo revert to remote packages:", COLOR_YELLOW);
println(" php rollback-local-dev.php");
println("\nHappy coding! πŸš€", COLOR_BLUE);
} catch (Exception $e) {
println("\nError: " . $e->getMessage(), COLOR_RED);
exit(1);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment