From 4b43c12f6047a02982260426859afd2f3a74fcd7 Mon Sep 17 00:00:00 2001 From: Alf Drollinger Date: Tue, 25 Feb 2025 14:25:30 +0100 Subject: [PATCH 1/2] New status wip --- composer.json-deployed | 149 ++++++++++++++++++ packages/devlink/README.md | 52 +++--- packages/devlink/ROADMAP.md | 20 ++- packages/devlink/config/devlink.json | 15 -- packages/devlink/config/devlink.php | 72 ++------- packages/devlink/deploy.sh | 6 - packages/devlink/init.php | 23 +++ .../src/Console/Commands/DeployCommand.php | 9 +- .../src/Console/Commands/LinkCommand.php | 8 +- .../src/Console/Commands/UnlinkCommand.php | 55 ------- .../devlink/src/Console/Traits/Backup.php | 21 --- packages/devlink/src/Console/Traits/Check.php | 37 +---- .../devlink/src/Console/Traits/Cleanup.php | 23 --- .../devlink/src/Console/Traits/Deploy.php | 49 +++++- packages/devlink/src/Console/Traits/Link.php | 32 ++-- .../devlink/src/Console/Traits/Prepare.php | 16 -- .../devlink/src/Console/Traits/Restore.php | 29 ---- packages/devlink/src/Console/Traits/Show.php | 4 +- .../devlink/src/Console/Traits/Unlink.php | 26 --- .../devlink/src/DevlinkServiceProvider.php | 4 +- 20 files changed, 286 insertions(+), 364 deletions(-) create mode 100644 composer.json-deployed delete mode 100644 packages/devlink/config/devlink.json create mode 100644 packages/devlink/init.php delete mode 100644 packages/devlink/src/Console/Commands/UnlinkCommand.php delete mode 100644 packages/devlink/src/Console/Traits/Backup.php delete mode 100644 packages/devlink/src/Console/Traits/Cleanup.php delete mode 100644 packages/devlink/src/Console/Traits/Prepare.php delete mode 100644 packages/devlink/src/Console/Traits/Restore.php delete mode 100644 packages/devlink/src/Console/Traits/Unlink.php diff --git a/composer.json-deployed b/composer.json-deployed new file mode 100644 index 000000000..0273a02af --- /dev/null +++ b/composer.json-deployed @@ -0,0 +1,149 @@ +{ + "name": "mooxphp/moox", + "type": "project", + "description": "The Moox Monorepo - a Laravel Demo App and our Homepage", + "keywords": [ + "framework", + "laravel", + "package", + "admin", + "dashboard", + "components", + "tall-stack", + "livewire", + "tailwindcss" + ], + "repositories": [ + { + "type": "path", + "url": "packages/*" + } + ], + "license": "MIT", + "require": { + "laravel-lang/lang": "^15.5", + "laravel/framework": "^11.0", + "laravel/tinker": "^2.8", + "moox/audit": "*", + "moox/builder": "*", + "moox/expiry": "*", + "moox/flags": "*", + "moox/frontend": "*", + "moox/jobs": "*", + "moox/locate": "*", + "moox/login-link": "*", + "moox/notifications": "*", + "moox/page": "*", + "moox/passkey": "*", + "moox/permission": "*", + "moox/press": "*", + "moox/press-trainings": "*", + "moox/press-wiki": "*", + "moox/security": "*", + "moox/skeleton": "*", + "moox/slug": "*", + "moox/trainings": "*", + "moox/user": "*", + "moox/user-device": "*", + "moox/user-session": "*", + "resend/resend-laravel": "^1.0@dev", + "wikimedia/composer-merge-plugin": "^2.1" + }, + "require-dev": { + "moox/devlink": "*", + "fakerphp/faker": "^1.23.1", + "larastan/larastan": "^3.0", + "laravel/pint": "^1.0", + "laravel/sail": "^1.26", + "nunomaduro/collision": "^8.0", + "pestphp/pest": "^3.0", + "pestphp/pest-plugin-laravel": "^3.0", + "pestphp/pest-plugin-livewire": "^3.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^2.0", + "rector/rector": "^2.0", + "spatie/laravel-ignition": "^2.3", + "spatie/laravel-ray": "^1.33" + }, + "autoload": { + "psr-4": { + "App\\": "app/", + "Database\\Factories\\": "database/factories/", + "Database\\Seeders\\": "database/seeders/" + } + }, + "autoload-dev": { + "psr-4": { + "Tests\\": "tests/" + } + }, + "scripts": { + "post-autoload-dump": [ + "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", + "@php artisan package:discover --ansi", + "@php artisan filament:upgrade" + ], + "post-update-cmd": [ + "@php artisan vendor:publish --tag=laravel-assets --ansi --force" + ], + "post-root-package-install": [ + "@php -r \"file_exists('.env') || copy('.env.example', '.env');\"" + ], + "post-create-project-cmd": [ + "@php artisan key:generate --ansi" + ], + "analyse": "./vendor/bin/phpstan analyze --memory-limit 512m", + "analyze": "@analyse", + "pint": "vendor/bin/pint", + "format": "@pint", + "lint": "@pint --test", + "rector": "vendor/bin/rector", + "refactor": "@rector", + "pest": "vendor/bin/pest", + "test:coverage": "@pest --coverage", + "test:lint": "@lint", + "test:refactor": "@rector --dry-run", + "test:types": "@analyse", + "test:arch": "@pest --filter=arch", + "test:type-coverage": "@pest --type-coverage --min=100", + "test:unit": "@pest --parallel --coverage --exactly=99.4", + "test": [ + "@test:lint", + "@test:refactor", + "@test:types", + "@test:type-coverage", + "@test:unit" + ] + }, + "extra": { + "laravel": { + "dont-discover": [] + }, + "merge-plugin": { + "include": [ + "_custom/composer.json" + ], + "recurse": true, + "replace": false, + "ignore-duplicates": false, + "merge-dev": true, + "merge-extra": false, + "merge-extra-deep": false, + "merge-replace": true, + "merge-scripts": false + } + }, + "config": { + "optimize-autoloader": true, + "preferred-install": "dist", + "sort-packages": true, + "allow-plugins": { + "pestphp/pest-plugin": true, + "phpstan/extension-installer": true, + "wikimedia/composer-merge-plugin": true, + "roots/wordpress-core-installer": true + } + }, + "minimum-stability": "dev", + "prefer-stable": false +} diff --git a/packages/devlink/README.md b/packages/devlink/README.md index 5390804e9..a9b174a0a 100644 --- a/packages/devlink/README.md +++ b/packages/devlink/README.md @@ -19,30 +19,33 @@ php artisan vendor:publish --tag="devlink-config" ```bash -# Ignore all files in packages/ (including symlinks) +# Devlink +# ignore symlinks in packages/ packages/* -# Allow tracking of real directories inside packages/ !packages/**/ -# Ensure empty directories can be committed !packages/*/.gitkeep -# for windows +# and for windows /packageslocal/* ``` 2. Configure your paths and packages in the `config/devlink.php` file and change the package path in the `.env` file, if needed (Windows users should set the `DEVLINK_PACKAGES_PATH` variable to `packageslocal`). -3. When running `devlink:status`: +3. When running `php init.php` - - Lists all packages that are currently devlinked - - Lists all packages that are configured but not devlinked - - Lists all packages that are not configured, but devlinked - - Shows the configuration and the deploy status of each package + - Creates a `.env` file from `.env.example` + - Copies `composer.json-deploy` to `composer.json` + - Runs `composer install` -4. When running `devlink:link`: +4. When running `devlink:status`: + + - Shows the configuration and status of each package + - Shows the link status (Linked, Unlinked, Deployed) + - Shows the update status (Up-to-date, Outdated) + +5. When running `devlink:link`: - Creates the packages folder, if it does not exist - - Creates backup of original composer.json → composer.json.original - Creates symlinks for all configured packages - Updates composer.json with development configuration - Creates composer.json-deploy for production use @@ -50,21 +53,10 @@ packages/* - Asks to run `php artisan optimize:clear` - Asks to run `php artisan queue:restart` -5. When running `devlink:unlink`: - - - Removes all symlinks - - Deletes the packages folder, if empty - - Creates a backup of composer.json to composer.json-backup - - Restores original composer.json from composer.json-original - - Asks to run `composer install` - - Asks to run `php artisan optimize:clear` - - Asks to run `php artisan queue:restart` - 6. When running `devlink:deploy`: - Removes all symlinks - Deletes the packages folder, if empty - - Creates a backup of composer.json to composer.json-backup - Restores production-ready composer.json from composer.json-deploy - Asks to run `composer install` - Asks to run `php artisan optimize:clear` @@ -73,17 +65,9 @@ packages/* 7. CI Safety Net - `deploy.sh`: - If composer.json-deploy exists in the repository: - - the script will restore it as composer.json - - rename composer.json-original to composer.json-backup - - Commit and push the change in GH action - - This ensures no development configuration reaches production - -## Changing branches - -If you need to change the branches for ANY of the involved repositories, you just need to run the command again, it will automatically update the symlinks for the current branch. - -> ⚠️ **Important** -> If you forget to run the command, when CHANGING BRANCHES ON ANY OF THE REPOS, you will surely run into a 500 error, that drives you nuts. + - Remove all symlinks from /packages + - rename composer.json-deploy to composer.json + - Commit and push the change in your GitHub Action ## Mac @@ -93,7 +77,7 @@ Mac works out of the box. You can have local packages mixed with the symlinked p ## Windows -On Windows there are most probably some issues with the symlinks. If you run into issues, you can either globally or project-wise disable the symlinks or do the following: +On Windows there are most probably some issues with ignoring symlinks. If you run into issues, you can either globally or project-wise disable the symlinks or do the following: ```env DEVLINK_PACKAGES_PATH=packageslocal diff --git a/packages/devlink/ROADMAP.md b/packages/devlink/ROADMAP.md index faf7d06b3..c5b07afec 100644 --- a/packages/devlink/ROADMAP.md +++ b/packages/devlink/ROADMAP.md @@ -1,8 +1,20 @@ # Roadmap +This is the roadmap for the devlink package. + ## Current tasks -- [ ] Private packages are not yet handled (copied and wired with path) -- [ ] Need to delete private packages in the link and unlink steps then -- [ ] There is probably an unclear state, because we do not yet use `-backup` yet -- [ ] For the shell script, we also need to check if `-original` and `-backup` are correct +We are working on the following tasks from top to bottom. Please don't forget to update the roadmap when a task is completed. + +- [ ] Status command needs to be improved, now has 2 statuses: + - [ ] New `Updated` status: + - [ ] devlink.php = composer.json => (Rocket) Up-to-date + - [ ] devlink.php =/= composer.json => (Construction) Outdated + - [ ] Old `Link` status: + - [ ] Linked if composer.json-devlink exists and composer.json-deploy does not exist + - [ ] Unlinked if composer.json-deploy exists and composer.json-devlink does not exist + - [ ] Unknown if composer.json-devlink and composer.json-deploy do not exist + - [ ] Error if composer.json-devlink and composer.json-deploy exist +- [ ] Private packages are not yet handled, they need to use the private repo URL +- [ ] deploy: local packages must stay +- [ ] deploy: new feature: last version for all packages instead of \* diff --git a/packages/devlink/config/devlink.json b/packages/devlink/config/devlink.json deleted file mode 100644 index dfcfb93c5..000000000 --- a/packages/devlink/config/devlink.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "status": { - "linked": true, - "deploy": false, - "error": false, - "error_message": "Devlink is active, currently not ready for deployment" - }, - "packages": { - "devlink": { - "active": true, - "linked": true, - "deployed": false - } - } -} diff --git a/packages/devlink/config/devlink.php b/packages/devlink/config/devlink.php index 2107e7bd0..19768c1f9 100644 --- a/packages/devlink/config/devlink.php +++ b/packages/devlink/config/devlink.php @@ -21,6 +21,16 @@ $public_base_path = env('DEVLINK_PUBLIC_PATH', '../moox/packages'); $private_base_path = env('DEVLINK_PRIVATE_PATH', 'disabled'); +/* +|-------------------------------------------------------------------------- +| Private Packages Repo URL +|-------------------------------------------------------------------------- +| +| The URL of the Moox package repository. Can be set in the .env file. +| +*/ +$private_repo_url = env('DEVLINK_PRIVATE_REPO_URL', 'https://pkg.moox.pro/'); + return [ /* @@ -53,201 +63,153 @@ // Moox 'audit' => [ 'active' => false, - 'linked' => true, 'path' => $public_base_path.'/audit', 'type' => 'public', - 'deploy' => true, ], 'backup-server-ui' => [ 'active' => false, - 'linked' => true, 'path' => $public_base_path.'/backup-server-ui', 'type' => 'public', - 'deploy' => true, ], 'builder' => [ 'active' => false, - 'linked' => true, 'path' => $public_base_path.'/builder', 'type' => 'public', - 'deploy' => false, ], 'category' => [ 'active' => false, - 'linked' => true, 'path' => $public_base_path.'/category', 'type' => 'public', - 'deploy' => true, ], 'core' => [ 'active' => false, - 'linked' => true, 'path' => $public_base_path.'/core', 'type' => 'public', - 'deploy' => true, ], 'devlink' => [ 'active' => false, 'linked' => false, 'path' => $public_base_path.'/devlink', 'type' => 'public', - 'deploy' => false, ], 'devops' => [ 'active' => false, - 'linked' => true, 'path' => $public_base_path.'/devops', 'type' => 'public', - 'deploy' => true, ], 'expiry' => [ 'active' => false, - 'linked' => true, 'path' => $public_base_path.'/expiry', 'type' => 'public', - 'deploy' => true, ], 'flags' => [ 'active' => false, - 'linked' => true, 'path' => $public_base_path.'/flags', 'type' => 'public', - 'deploy' => true, ], 'jobs' => [ 'active' => false, - 'linked' => true, 'path' => $public_base_path.'/jobs', 'type' => 'public', - 'deploy' => true, ], 'login-link' => [ 'active' => false, - 'linked' => true, 'path' => $public_base_path.'/login-link', 'type' => 'public', - 'deploy' => true, ], 'notifications' => [ 'active' => false, - 'linked' => true, 'path' => $public_base_path.'/notifications', 'type' => 'public', - 'deploy' => true, ], 'passkey' => [ 'active' => false, - 'linked' => true, 'path' => $public_base_path.'/passkey', 'type' => 'public', - 'deploy' => true, ], 'press' => [ 'active' => false, - 'linked' => true, 'path' => $public_base_path.'/press', 'type' => 'public', - 'deploy' => true, ], 'security' => [ 'active' => false, - 'linked' => true, 'path' => $public_base_path.'/security', 'type' => 'public', - 'deploy' => true, ], 'sync' => [ 'active' => false, - 'linked' => true, 'path' => $public_base_path.'/sync', 'type' => 'public', - 'deploy' => true, ], 'tag' => [ 'active' => false, - 'linked' => true, 'path' => $public_base_path.'/tag', 'type' => 'public', - 'deploy' => true, ], 'trainings' => [ 'active' => false, - 'linked' => true, 'path' => $public_base_path.'/trainings', 'type' => 'public', - 'deploy' => true, ], 'user' => [ 'active' => false, - 'linked' => true, 'path' => $public_base_path.'/user', 'type' => 'public', - 'deploy' => true, ], 'user-device' => [ 'active' => false, - 'linked' => true, 'path' => $public_base_path.'/user-device', 'type' => 'public', - 'deploy' => true, ], 'user-session' => [ 'active' => false, - 'linked' => true, 'path' => $public_base_path.'/user-session', 'type' => 'public', - 'deploy' => true, ], // Moox Pro 'connect' => [ 'active' => false, - 'linked' => true, 'path' => $private_base_path.'/connect', + 'repo_url' => $private_repo_url, 'type' => 'private', - 'deploy' => true, ], 'creator' => [ 'active' => false, - 'linked' => true, 'path' => $private_base_path.'/creator', + 'repo_url' => $private_repo_url, 'type' => 'private', - 'deploy' => false, ], 'data' => [ 'active' => false, - 'linked' => true, 'path' => $private_base_path.'/data', + 'repo_url' => $private_repo_url, 'type' => 'private', - 'deploy' => true, ], 'localize' => [ 'active' => false, - 'linked' => true, 'path' => $private_base_path.'/localize', + 'repo_url' => $private_repo_url, 'type' => 'private', - 'deploy' => true, ], 'media' => [ 'active' => false, - 'linked' => true, 'path' => $private_base_path.'/media', + 'repo_url' => $private_repo_url, 'type' => 'private', - 'deploy' => true, ], 'page' => [ 'active' => false, - 'linked' => true, 'path' => $private_base_path.'/page', + 'repo_url' => $private_repo_url, 'type' => 'private', - 'deploy' => true, ], 'permission' => [ 'active' => false, - 'linked' => true, 'path' => $private_base_path.'/permission', + 'repo_url' => $private_repo_url, 'type' => 'private', - 'deploy' => true, ], ], diff --git a/packages/devlink/deploy.sh b/packages/devlink/deploy.sh index 16a9ca5f9..96b73d261 100644 --- a/packages/devlink/deploy.sh +++ b/packages/devlink/deploy.sh @@ -1,12 +1,6 @@ #!/bin/bash if [ -f "composer.json-deploy" ]; then - # Remove all symlinks from /packages find packages -type l -delete - # Remove the packages folder if empty - if [ -z "$(ls -A packages)" ]; then - rm packages - fi cp composer.json-deploy composer.json - composer install fi diff --git a/packages/devlink/init.php b/packages/devlink/init.php new file mode 100644 index 000000000..91924f618 --- /dev/null +++ b/packages/devlink/init.php @@ -0,0 +1,23 @@ +basePaths = config('devlink.base_paths', []); $this->packages = config('devlink.packages', []); $this->composerJsonPath = base_path('composer.json'); $this->packagesPath = config('devlink.packages_path', base_path('packages')); @@ -48,9 +43,7 @@ public function handle(): void $this->art(); info('Hello, I will prepare your project and composer.json for deployment.'); $this->check(); - $this->unlink(); $this->deploy(); - $this->cleanup(); $this->finalize(); if ($this->errorMessage) { diff --git a/packages/devlink/src/Console/Commands/LinkCommand.php b/packages/devlink/src/Console/Commands/LinkCommand.php index 9a81b3ea3..1e852e890 100644 --- a/packages/devlink/src/Console/Commands/LinkCommand.php +++ b/packages/devlink/src/Console/Commands/LinkCommand.php @@ -4,18 +4,15 @@ use Illuminate\Console\Command; use Moox\Devlink\Console\Traits\Art; -use Moox\Devlink\Console\Traits\Backup; use Moox\Devlink\Console\Traits\Check; use Moox\Devlink\Console\Traits\Finalize; use Moox\Devlink\Console\Traits\Link; -use Moox\Devlink\Console\Traits\Prepare; -use Moox\Devlink\Console\Traits\Restore; use function Laravel\Prompts\info; class LinkCommand extends Command { - use Art, Backup, Check, Finalize, Link, Prepare, Restore; + use Art, Check, Finalize, Link; protected $signature = 'devlink:link'; @@ -35,7 +32,6 @@ public function __construct() { parent::__construct(); - $this->basePaths = config('devlink.base_paths', []); $this->packages = config('devlink.packages', []); $this->composerJsonPath = base_path('composer.json'); $this->packagesPath = config('devlink.packages_path', base_path('packages')); @@ -46,8 +42,6 @@ public function handle(): void $this->art(); info('Hello, I will link the configured packages for you.'); $this->check(); - $this->backup(); - $this->prepare(); $this->link(); $this->finalize(); info('Packages linked! Have a nice dev!'); diff --git a/packages/devlink/src/Console/Commands/UnlinkCommand.php b/packages/devlink/src/Console/Commands/UnlinkCommand.php deleted file mode 100644 index 92f265cb0..000000000 --- a/packages/devlink/src/Console/Commands/UnlinkCommand.php +++ /dev/null @@ -1,55 +0,0 @@ -basePaths = config('devlink.base_paths', []); - $this->packages = config('devlink.packages', []); - $this->composerJsonPath = base_path('composer.json'); - $this->packagesPath = config('devlink.packages_path', base_path('packages')); - } - - public function handle(): void - { - $this->art(); - info('Hello, I will unlink Moox packages from the project and restore original composer.json.'); - $this->check(); - $this->unlink(); - $this->restore(); - $this->cleanup(); - $this->finalize(); - info('Packages unlinked! Have a nice dev!'); - } -} diff --git a/packages/devlink/src/Console/Traits/Backup.php b/packages/devlink/src/Console/Traits/Backup.php deleted file mode 100644 index 7bfcb238f..000000000 --- a/packages/devlink/src/Console/Traits/Backup.php +++ /dev/null @@ -1,21 +0,0 @@ -composerJsonPath; - $backupFile = 'composer.json-original'; - - if (file_exists($source)) { - copy($source, $backupFile); - info('Backed up composer.json to composer.json-original'); - } else { - $this->errorMessage = 'composer.json not found!'; - } - } -} diff --git a/packages/devlink/src/Console/Traits/Check.php b/packages/devlink/src/Console/Traits/Check.php index 3260812f4..a10e29728 100644 --- a/packages/devlink/src/Console/Traits/Check.php +++ b/packages/devlink/src/Console/Traits/Check.php @@ -9,9 +9,6 @@ private function check(): array $status = 'unknown'; $message = 'Devlink is in unknown status'; - $composerOriginal = false; - $composerDeploy = false; - $packagesArray = []; $realPackages = []; @@ -76,40 +73,14 @@ private function check(): array $lasterror = 'composer.json does not exist'; } - if (! file_exists(base_path('composer.json'))) { - $lasterror = 'composer.json does not exist'; - } - - if (file_exists(base_path('composer.json-original'))) { - $composerOriginal = true; + if (file_exists(base_path('composer.json-linked'))) { + $status = 'linked'; + $message = 'Devlink is linked'; } if (file_exists(base_path('composer.json-deploy'))) { - $composerDeploy = true; - } - - if (file_exists(base_path('composer.json-backup'))) { - $composerBackup = true; - } - - if (! $composerOriginal && ! $composerDeploy) { - $status = 'unused'; - $message = 'Devlink is not active'; - } - - if (! $composerOriginal && $composerDeploy) { $status = 'unlinked'; - $message = 'Devlink is unlinked, not ready for deployment'; - } - - if ($composerOriginal && $composerDeploy) { - $status = 'linked'; - $message = 'Devlink is linked, not ready for deployment'; - } - - if ($composerOriginal && ! $composerDeploy) { - $status = 'deployed'; - $message = 'Devlink is ready for deployment'; + $message = 'Devlink is unlinked and ready for deployment'; } if ($lasterror !== null) { diff --git a/packages/devlink/src/Console/Traits/Cleanup.php b/packages/devlink/src/Console/Traits/Cleanup.php deleted file mode 100644 index 8b0f17956..000000000 --- a/packages/devlink/src/Console/Traits/Cleanup.php +++ /dev/null @@ -1,23 +0,0 @@ -packagesPath) && count(scandir($this->packagesPath)) === 2) { - info('Removing packages directory...'); - rmdir($this->packagesPath); - } else { - $this->errorMessage = 'Packages directory not found!'; - error($this->errorMessage); - } - } -} diff --git a/packages/devlink/src/Console/Traits/Deploy.php b/packages/devlink/src/Console/Traits/Deploy.php index c228db3fa..144e3e876 100644 --- a/packages/devlink/src/Console/Traits/Deploy.php +++ b/packages/devlink/src/Console/Traits/Deploy.php @@ -8,22 +8,59 @@ trait Deploy { private function deploy(): void + { + $this->unlink(); + $this->cleanup(); + $this->restore(); + } + + /** + * Remove all symlinks in the packages directory. + */ + private function unlink(): void + { + if (is_dir($this->packagesPath)) { + $i = 0; + foreach (scandir($this->packagesPath) as $item) { + if ($item !== '.' && $item !== '..' && is_link("$this->packagesPath/$item")) { + unlink("$this->packagesPath/$item"); + $i++; + } + } + + info("Removed $i symlinks"); + } + } + + /** + * Remove the packages directory if it is empty. + */ + private function cleanup(): void + { + if (is_dir($this->packagesPath) && count(scandir($this->packagesPath)) === 2) { + info('Removing packages directory...'); + rmdir($this->packagesPath); + } else { + $this->errorMessage = 'Packages directory not found!'; + error($this->errorMessage); + } + } + + /** + * Restore the composer.json file from the backup. + */ + private function restore(): void { $source = $this->composerJsonPath.'-deploy'; $destination = $this->composerJsonPath; - $backup = $this->composerJsonPath.'-backup'; if (file_exists($source)) { - unlink($backup); - copy($destination, $backup); unlink($destination); copy($source, $destination); - info('Deployed composer.json to composer.json-deploy'); + info('Restored composer.json from composer.json-deploy'); } else { $this->errorMessage = 'composer.json-deploy not found!'; error($this->errorMessage); } - - $devlinkStatus = 'deployed'; } } diff --git a/packages/devlink/src/Console/Traits/Link.php b/packages/devlink/src/Console/Traits/Link.php index cd9b82fe0..ea0b4cbb6 100644 --- a/packages/devlink/src/Console/Traits/Link.php +++ b/packages/devlink/src/Console/Traits/Link.php @@ -9,6 +9,7 @@ trait Link { private function link(): void { + $this->prepare(); $this->removeSymlinks(); $this->composerRemovePackages(); $this->createSymlinks(); @@ -16,9 +17,13 @@ private function link(): void $this->createDeployComposerJson(); } - /** - * Create symlinks for all configured packages. - */ + private function prepare(): void + { + if (! is_dir($this->packagesPath)) { + mkdir($this->packagesPath, 0755, true); + } + } + private function createSymlinks(): void { $linkedPackages = []; @@ -34,14 +39,9 @@ private function createSymlinks(): void continue; } - if (! ($package['linked'] ?? true)) { - continue; - } - - // Convert target path to absolute path $target = realpath($package['path']); if (! $target) { - $target = $package['path']; // Keep original for error message + $target = $package['path']; } $link = "{$this->packagesPath}/$name"; @@ -100,9 +100,6 @@ private function createSymlinks(): void } } - /** - * Remove existing symlinks that are no longer in config. - */ private function removeSymlinks(): void { $configuredPackages = array_keys(config('devlink.packages', [])); @@ -131,17 +128,15 @@ private function removeSymlinks(): void } } - /** - * Remove packages from composer.json. - */ private function composerRemovePackages(): void { $composerJson = json_decode(file_get_contents($this->composerJsonPath), true); $removedPackages = []; $configuredPackages = config('devlink.packages', []); + $packagesBaseName = basename($this->packagesPath); foreach ($composerJson['repositories'] ?? [] as $key => $repo) { - if ($repo['type'] === 'path' && str_starts_with($repo['url'], 'packages/')) { + if ($repo['type'] === 'path' && str_starts_with($repo['url'], $packagesBaseName.'/')) { $package = basename($repo['url']); if (! isset($configuredPackages[$package]) || ! ($configuredPackages[$package]['active'] ?? false)) { @@ -198,7 +193,6 @@ private function updateComposerJson(): void $addedRepos = []; $addedRequires = []; - // Debug output info("\nChecking packages for composer.json updates:"); foreach (config('devlink.packages', []) as $name => $package) { @@ -206,7 +200,7 @@ private function updateComposerJson(): void continue; } - $packagePath = "packages/{$name}"; + $packagePath = basename($this->packagesPath)."/{$name}"; $repoEntry = [ 'type' => 'path', 'url' => $packagePath, @@ -273,11 +267,9 @@ private function createDeployComposerJson(): void return; } - // Create a copy without the repositories section $deployJson = $composerJson; unset($deployJson['repositories']); - // Write to composer.json-deploy $deployPath = dirname($this->composerJsonPath).'/composer.json-deploy'; file_put_contents( $deployPath, diff --git a/packages/devlink/src/Console/Traits/Prepare.php b/packages/devlink/src/Console/Traits/Prepare.php deleted file mode 100644 index 754649487..000000000 --- a/packages/devlink/src/Console/Traits/Prepare.php +++ /dev/null @@ -1,16 +0,0 @@ -packagesPath)) { - mkdir($this->packagesPath, 0755, true); - } - } -} diff --git a/packages/devlink/src/Console/Traits/Restore.php b/packages/devlink/src/Console/Traits/Restore.php deleted file mode 100644 index 3e21c2f86..000000000 --- a/packages/devlink/src/Console/Traits/Restore.php +++ /dev/null @@ -1,29 +0,0 @@ -composerJsonPath; - $destination = $this->composerJsonPath.'-original'; - - if (file_exists($source)) { - if (file_exists($destination)) { - unlink($source); - rename($destination, $source); - info('Restored composer.json from composer.json-original'); - } else { - $this->errorMessage = 'composer.json-original not found!'; - error($this->errorMessage); - } - } else { - $this->errorMessage = 'composer.json not found!'; - error($this->errorMessage); - } - } -} diff --git a/packages/devlink/src/Console/Traits/Show.php b/packages/devlink/src/Console/Traits/Show.php index 02005e345..7c62db8c3 100644 --- a/packages/devlink/src/Console/Traits/Show.php +++ b/packages/devlink/src/Console/Traits/Show.php @@ -15,14 +15,12 @@ private function show(): void { $fullStatus = $this->check(); - $headers = ['Package', 'Type', 'Active', 'Link', 'Deploy', 'Valid', 'Linked']; + $headers = ['Package', 'Type', 'Active', 'Valid', 'Linked']; $rows = array_map(function ($row) { return [ $row['name'], $row['type'], $row['active'] ? ' '.self::CHECK_MARK.' ' : ' '.self::CROSS_MARK.' ', - $row['link'] ? ' '.self::CHECK_MARK.' ' : ' '.self::CROSS_MARK.' ', - $row['deploy'] ? ' '.self::CHECK_MARK.' ' : ' '.self::CROSS_MARK.' ', $row['valid'] ? ' '.self::CHECK_MARK.' ' : ' '.self::CROSS_MARK.' ', $row['linked'] ? ' '.self::CHECK_MARK.' ' : ' '.self::CROSS_MARK.' ', ]; diff --git a/packages/devlink/src/Console/Traits/Unlink.php b/packages/devlink/src/Console/Traits/Unlink.php deleted file mode 100644 index 4c29d127a..000000000 --- a/packages/devlink/src/Console/Traits/Unlink.php +++ /dev/null @@ -1,26 +0,0 @@ -packagesPath)) { - $i = 0; - foreach (scandir($this->packagesPath) as $item) { - if ($item !== '.' && $item !== '..' && is_link("$this->packagesPath/$item")) { - unlink("$this->packagesPath/$item"); - $i++; - } - } - - info("Removed $i symlinks"); - } - } -} diff --git a/packages/devlink/src/DevlinkServiceProvider.php b/packages/devlink/src/DevlinkServiceProvider.php index 5cc48398a..55506e064 100644 --- a/packages/devlink/src/DevlinkServiceProvider.php +++ b/packages/devlink/src/DevlinkServiceProvider.php @@ -8,7 +8,6 @@ use Moox\Devlink\Console\Commands\DeployCommand; use Moox\Devlink\Console\Commands\LinkCommand; use Moox\Devlink\Console\Commands\StatusCommand; -use Moox\Devlink\Console\Commands\UnlinkCommand; class DevlinkServiceProvider extends ServiceProvider { @@ -25,9 +24,8 @@ public function boot(): void ], 'devlink-config'); $this->commands([ - LinkCommand::class, DeployCommand::class, - UnlinkCommand::class, + LinkCommand::class, StatusCommand::class, ]); } From bc44bd840ddabf4b14cdd1e53f437bc98c8ef279 Mon Sep 17 00:00:00 2001 From: Alf Drollinger Date: Tue, 25 Feb 2025 14:59:46 +0100 Subject: [PATCH 2/2] New status --- packages/devlink/ROADMAP.md | 17 +++----- packages/devlink/src/Console/Traits/Check.php | 32 +++++++++++++- .../devlink/src/Console/Traits/Deploy.php | 43 +++++++++++++++++++ packages/devlink/src/Console/Traits/Link.php | 29 ++++++++----- packages/devlink/src/Console/Traits/Show.php | 8 ++++ 5 files changed, 106 insertions(+), 23 deletions(-) diff --git a/packages/devlink/ROADMAP.md b/packages/devlink/ROADMAP.md index c5b07afec..8164899bb 100644 --- a/packages/devlink/ROADMAP.md +++ b/packages/devlink/ROADMAP.md @@ -6,15 +6,8 @@ This is the roadmap for the devlink package. We are working on the following tasks from top to bottom. Please don't forget to update the roadmap when a task is completed. -- [ ] Status command needs to be improved, now has 2 statuses: - - [ ] New `Updated` status: - - [ ] devlink.php = composer.json => (Rocket) Up-to-date - - [ ] devlink.php =/= composer.json => (Construction) Outdated - - [ ] Old `Link` status: - - [ ] Linked if composer.json-devlink exists and composer.json-deploy does not exist - - [ ] Unlinked if composer.json-deploy exists and composer.json-devlink does not exist - - [ ] Unknown if composer.json-devlink and composer.json-deploy do not exist - - [ ] Error if composer.json-devlink and composer.json-deploy exist -- [ ] Private packages are not yet handled, they need to use the private repo URL -- [ ] deploy: local packages must stay -- [ ] deploy: new feature: last version for all packages instead of \* +- [ ] ... no open tasks at the moment ... + +## Ideas + +- [ ] Deploy: new feature: last version for all packages instead of \* diff --git a/packages/devlink/src/Console/Traits/Check.php b/packages/devlink/src/Console/Traits/Check.php index a10e29728..a508555b3 100644 --- a/packages/devlink/src/Console/Traits/Check.php +++ b/packages/devlink/src/Console/Traits/Check.php @@ -7,7 +7,17 @@ trait Check private function check(): array { $status = 'unknown'; - $message = 'Devlink is in unknown status'; + $message = 'Devlink is in unknown status, run `php artisan devlink:link` to update'; + $hasDevlink = file_exists(base_path('composer.json-devlink')); + $hasDeploy = file_exists(base_path('composer.json-deploy')); + + if ($hasDevlink && ! $hasDeploy) { + $status = 'linked'; + $message = 'Devlink is linked, happy coding!'; + } elseif (! $hasDevlink && $hasDeploy) { + $status = 'unlinked'; + $message = 'Devlink is unlinked, ready for deployment!'; + } $packagesArray = []; $realPackages = []; @@ -95,11 +105,31 @@ private function check(): array 'public_base_path' => $publicBasePath, 'private_base_path' => $privateBasePath, 'packages' => $realPackages, + 'updated' => $this->checkUpdated(), ]; return $fullStatus; } + private function checkUpdated(): bool + { + $composerJson = json_decode(file_get_contents($this->composerJsonPath), true); + $devlinkConfig = config('devlink.packages'); + + foreach ($devlinkConfig as $package => $config) { + if (! ($config['active'] ?? false)) { + continue; + } + + $packageName = 'moox/'.$package; + if (! isset($composerJson['require'][$packageName]) && ! isset($composerJson['require-dev'][$packageName])) { + return false; + } + } + + return true; + } + private function resolvePath(string $path): string { return str_starts_with($path, '~/') ? str_replace('~', getenv('HOME'), $path) : rtrim(realpath($path) ?: $path, '/'); diff --git a/packages/devlink/src/Console/Traits/Deploy.php b/packages/devlink/src/Console/Traits/Deploy.php index 144e3e876..01a696771 100644 --- a/packages/devlink/src/Console/Traits/Deploy.php +++ b/packages/devlink/src/Console/Traits/Deploy.php @@ -12,6 +12,7 @@ private function deploy(): void $this->unlink(); $this->cleanup(); $this->restore(); + $this->createDeployComposerJson(); } /** @@ -63,4 +64,46 @@ private function restore(): void error($this->errorMessage); } } + + private function createDeployComposerJson(): void + { + if (! file_exists($this->composerJsonPath)) { + $this->error('composer.json not found!'); + + return; + } + + $composerContent = file_get_contents($this->composerJsonPath); + $composerJson = json_decode($composerContent, true); + + if (json_last_error() !== JSON_ERROR_NONE) { + $this->error('Invalid composer.json format: '.json_last_error_msg()); + + return; + } + + $deployJson = $composerJson; + $repositories = $deployJson['repositories'] ?? []; + $filteredRepos = []; + + foreach ($repositories as $repo) { + if (($repo['type'] ?? '') !== 'path') { + $filteredRepos[] = $repo; + } + } + + if (empty($filteredRepos)) { + unset($deployJson['repositories']); + } else { + $deployJson['repositories'] = $filteredRepos; + } + + $deployPath = dirname($this->composerJsonPath).'/composer.json-deploy'; + file_put_contents( + $deployPath, + json_encode($deployJson, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)."\n" + ); + + info('Created composer.json-deploy with non-path repositories'); + } } diff --git a/packages/devlink/src/Console/Traits/Link.php b/packages/devlink/src/Console/Traits/Link.php index ea0b4cbb6..eeac3667d 100644 --- a/packages/devlink/src/Console/Traits/Link.php +++ b/packages/devlink/src/Console/Traits/Link.php @@ -200,19 +200,28 @@ private function updateComposerJson(): void continue; } - $packagePath = basename($this->packagesPath)."/{$name}"; - $repoEntry = [ - 'type' => 'path', - 'url' => $packagePath, - 'options' => [ - 'symlink' => true, - ], - ]; $packageName = "moox/{$name}"; + $isPrivate = ($package['type'] ?? 'public') === 'private'; + + if ($isPrivate) { + $repoEntry = [ + 'type' => 'vcs', + 'url' => $package['repo_url'] ?? config('devlink.private_repo_url'), + ]; + } else { + $packagePath = basename($this->packagesPath)."/{$name}"; + $repoEntry = [ + 'type' => 'path', + 'url' => $packagePath, + 'options' => [ + 'symlink' => true, + ], + ]; + } $repoExists = false; foreach ($repositories as $repo) { - if (($repo['type'] ?? '') === 'path' && ($repo['url'] ?? '') === $packagePath) { + if (($repo['type'] ?? '') === $repoEntry['type'] && ($repo['url'] ?? '') === $repoEntry['url']) { $repoExists = true; break; } @@ -220,7 +229,7 @@ private function updateComposerJson(): void if (! $repoExists) { $repositories[] = $repoEntry; - $addedRepos[] = $name; + $addedRepos[] = $name.($isPrivate ? ' (private)' : ''); $updated = true; } diff --git a/packages/devlink/src/Console/Traits/Show.php b/packages/devlink/src/Console/Traits/Show.php index 7c62db8c3..c6627206f 100644 --- a/packages/devlink/src/Console/Traits/Show.php +++ b/packages/devlink/src/Console/Traits/Show.php @@ -29,6 +29,7 @@ private function show(): void table($headers, $rows); $badge = ' '; + $updateBadge = ' '; if ($fullStatus['status'] === 'error') { $badge = ' '; @@ -46,7 +47,14 @@ private function show(): void $badge = ' '; } + if ($fullStatus['updated']) { + $updateBadge = ' '; + } else { + $updateBadge = ' '; + } + info(' '.$badge.strtoupper($fullStatus['status']).' '.$fullStatus['message']); + info(' '.$updateBadge.' UPDATE '.($fullStatus['updated'] ? 'All packages are in sync with composer.json' : 'You need to run `php artisan devlink:link` to update the packages')); info(' '); } }