Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
adrolli committed Feb 13, 2025
1 parent 152a85c commit 2027389
Show file tree
Hide file tree
Showing 6 changed files with 282 additions and 73 deletions.
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@

# Composer
/vendor
/vendor-dev
/vendor-def
auth.json
composer.lock

Expand Down
78 changes: 35 additions & 43 deletions packages/devlink/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,79 +7,60 @@ It is used to link the packages from the `moox` monorepo into a project. It runs
## Installation

```bash
cp composer.json.example composer.json
cp .env.example .env
composer require moox/devlink
php artisan vendor:publish --tag="devlink-config"
```

## Usage

```bash
php artisan moox:devlink
```

## Screenshot

![Moox Devlink](./devlink.jpg)

## Preparation
## How It Works

Before you can use this package, you need to prepare your project's `.gitignore` file.
1. Prepare your project's `.gitignore` file:

```bash

# Ignore all files in packages/ (including symlinks)
packages/*
# Allow tracking of real directories inside packages/
!packages/**/
# Ensure empty directories can be committed
!packages/*/.gitkeep
# Ignore all files in packages-linked/ (for Windows)
packages-linked/*
```

## Configuration

The configuration is done in the `config/devlink.php` file.

```php

'packages_path' => 'packages',

'base_paths' => [
base_path('../moox/packages'),
],

'packages' => [
'moox/tag',
],
# for windows
/packageslocal/*

```

## Command

The devlink command will create a `packages` directory in the root of the project and symlink the packages from the configured base paths.
2. Configure your paths and packages in the `config/devlink.php` file and the `.env` file, if needed (Windows users for example).

```bash
3. When running `devlink:link`:

php artisan moox:devlink
- 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
- Asks to run `composer install`
- Asks to run `php artisan optimize:clear`
- Asks to run `php artisan queue:restart`

```
4. When running `devlink:deploy`:

It will also update the `composer.json` file to include the packages in the `require` section and the `repositories` section.
- Removes all symlinks
- Deletes the packages folder, if empty
- Restores production-ready composer.json from composer.json-deploy

Finally, it will run `composer update`.
5. CI Safety Net - `deploy.sh`:
- If composer.json-deploy exists in the repository:
- The script will restore it as composer.json
- Commit and push the change in GH action
- This ensures no development configuration reaches production

### Changing branches
## 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.

```bash

php artisan moox:devlink

```

> ⚠️ **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.
Expand All @@ -101,6 +82,17 @@ On Windows there are most probably some issues with the symlinks. If you run int

Devlink will then link the packages into the `packages-linked` folder.

## Roadmap

- [ ] Test on Mac
- [ ] Test on Windows
- [ ] Test Deployment on Mac
- [ ] Test Deployment on Windows
- [ ] Implement automatic Deployment
- [ ] Implement all 3 types of packages
- [ ] If package is a symlink itself, ...?
- [ ] If package is in multiple base paths...?

## Security Vulnerabilities

Please review [our security policy](https://github.com/mooxphp/moox/security/policy) on how to report security vulnerabilities.
Expand Down
27 changes: 21 additions & 6 deletions packages/devlink/config/devlink.php
Original file line number Diff line number Diff line change
@@ -1,23 +1,38 @@
<?php

return [

// User-specific path from .env
'packages_path' => env('DEVLINK_PACKAGES_PATH', 'packages'),

// The base paths to the packages, project or branch-wise
'base_paths' => [
base_path('../moox/packages'),
],

// The internal packages need to be copied on deploy, project or branch-wise,
'copy_packages' => [
// 'builder-pro',
],

// The packages that need to be removed on deploy,project or branch-wise
'skip_packages' => [
'devlink',
],

// The packages that are installed from Packagist on deploy, project or branch-wise
'packages' => [
'audit',
// 'audit',
// 'backup-server-ui',
'builder',
// 'builder-pro',
// 'builder',
// 'category',
// 'core',
// 'connect',
// 'data',
// 'devops',
// 'expiry',
'flags',
'jobs',
// 'flags',
// 'jobs',
// 'login-link',
// 'localization',
// 'media',
Expand All @@ -28,7 +43,7 @@
// 'press',
// 'security',
// 'sync',
// 'tag',
'tag',
// 'trainings',
// 'user',
// 'user-device',
Expand Down
12 changes: 12 additions & 0 deletions packages/devlink/deploy.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/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
185 changes: 185 additions & 0 deletions packages/devlink/src/Commands/DeployPackages.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
<?php

namespace Moox\Devlink\Commands;

use Illuminate\Console\Command;

class DeployPackages extends Command
{
protected $signature = 'devlink:deploy';

protected $description = 'Symlink Moox packages into the project from multiple base paths and ensure composer.json is updated';

protected array $basePaths;

protected array $packages;

protected string $composerJsonPath;

protected string $packagesPath;

public function __construct()
{
parent::__construct();

$this->basePaths = config('devlink.base_paths', []);
$this->packages = config('devlink.packages', []);

if (empty($this->basePaths)) {
$this->warn('No base paths configured in config/devlink.php');
}

if (empty($this->packages)) {
$this->warn('No packages configured in config/devlink.php');
}

$this->composerJsonPath = base_path('composer.json');
$this->packagesPath = config('devlink.packages_path', base_path('packages'));
}

public function handle(): void
{
$this->art();
$this->hello();
$this->checks();
$this->removeAllSymlinks();
$this->removePackagesDirectoryIfEmpty();
$this->restoreComposerJson();
$this->runComposerUpdate();
$this->optimizeClear();
$this->queueRestart();
$this->goodbye();
}

public function art(): void
{
$this->info('
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓
▓▓▒░░▒▓▓▒▒░░░░░░▒▒▓▓▓▒░░░░░░░▒▓▓ ▓▓▓▓▒░░░░░░░▒▓▓▓▓ ▓▓▓▓▓▒░░░░░░░▒▒▓▓▓▓▓▒▒▒▒▓▓ ▓▓▓▒▒▒▒▓▓
▓▒░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▓▓▓▓▓▒░░░░░░░░░░░░░▒▓▓▓ ▓▓▓▓▒░░░░░░░░░░░░░▒▓▓▓░░░░░▒▓▓ ▓▓▒░░░░░▓▓
▓▒░░░░░░▒▓▓▓▓▒░░░░░░░▒▓▓▓▓░░░░░▒▓▓▓░░░░░▒▓▓▓▓▒░░░░░░░▓▓▓▓░░░░░░▒▓▓▓▓▓░░░░░░▒▓▓░░░░░▒▓▓▓▓▓░░░░░▒▓▓
▓▒░░░░▓▓▓▓ ▓▓░░░░░▓▓▓ ▓▓▓░░░░▒▓▓░░░░▒▓▓▓ ▓▓▓▓░░░░░▓░░░░░░▓▓▓▓ ▓▓▓▒░░░░▓▓▓▒░░░░░▓▓▓░░░░░▓▓▓
▓▒░░░░▒▓ ▓▓░░░░░▓▓ ▓▓░░░░▒▓░░░░▒▓▓ ▓▓▓░░▒░░░░░▓▓▓ ▓▓░░░░▒▓▓▓▓░░░░░░░░░░░▓▓
▓▒░░░░▒▓ ▓▓░░░░░▓▓ ▓▓░░░░▒▓░░░░▒▓ ▓▓▓░░░░░▒▓▓ ▓▓▒░░░░▓ ▓▓▓░░░░░░░░░▓▓
▓▒░░░░▒▓ ▓▓░░░░░▓▓ ▓▓░░░░▒▓░░░░▒▓▓ ▓▓▒░░░░░▒░░▒▓▓ ▓▓░░░░▒▓▓▓▒░░░░░▒░░░░░▒▓
▓▒░░░░▒▓ ▓▓░░░░░▓▓ ▓▓░░░░▒▓▓░░░░▒▓▓▓ ▓▓▓▒░░░░░▒▒░░░░░▒▓▓▓ ▓▓▓░░░░░▓▓▓░░░░░▒▓▓▓░░░░░▒▓▓
▓▒░░░░▒▓ ▓▓░░░░░▓▓ ▓▓░░░░▒▓▓▓░░░░░░▒▒▓▓▒░░░░░░▒▓▓▓▓░░░░░░░▒▒▓▓▒░░░░░░▓▓▓░░░░░▒▓▓▓▓▓▒░░░░░▓▓
▓▒░░░░▒▓ ▓▓░░░░░▓▓ ▓▓░░░░▒▓▓▓▓▒░░░░░░░░░░░░░▒▓▓▓ ▓▓▓▓▒░░░░░░░░░░░░░▒▓▓▒░░░░░▓▓▓ ▓▓▒░░░░░▒▓
▓▓░░░▒▓▓ ▓▓▒░░░▒▓▓ ▓▓░░░░▓▓ ▓▓▓▓▒░░░░░░▒▒▓▓▓▓ ▓▓▓▓▓▒▒░░░░░▒▒▓▓▓▓▓░░░░▒▓▓ ▓▓▓░░░░▒▓
▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓
');
}

private function hello(): void
{
$this->info('Hello, I will prepare your project for deployment.');
}

private function checks(): void
{
$this->line("\nConfiguration:");
$this->line('Base paths:');
if (empty($this->basePaths)) {
$this->warn('- No base paths configured');
} else {
foreach ($this->basePaths as $path) {
$resolvedPath = $this->resolvePath($path);
$this->line("- $path");
$this->line("$resolvedPath".(is_dir($resolvedPath) ? ' (exists)' : ' (not found)'));
}
}

$this->line("\nConfigured packages:");
if (empty($this->packages)) {
$this->warn('No packages configured in config/devlink.php');
} else {
foreach ($this->packages as $package) {
$this->line("- $package");
}
}

$this->line('');
}

private function removeAllSymlinks(): void
{
if (is_dir($this->packagesPath)) {
foreach (scandir($this->packagesPath) as $item) {
if ($item !== '.' && $item !== '..' && is_link("$this->packagesPath/$item")) {
unlink("$this->packagesPath/$item");
}
}
}
}

private function removePackagesDirectoryIfEmpty(): void
{
if (is_dir($this->packagesPath) && count(scandir($this->packagesPath)) === 2) {
rmdir($this->packagesPath);
}
}

private function restoreComposerJson(): void
{
$deployFile = $this->composerJsonPath.'-deploy';
if (! file_exists($deployFile)) {
$this->error('composer.json-deploy not found!');

return;
}

unlink($this->composerJsonPath);
rename($deployFile, $this->composerJsonPath);
$this->info('Restored composer.json from composer.json-deploy');
}

private function runComposerUpdate(): void
{
if ($this->confirm('Run composer update now?', true)) {
$output = [];
$returnVar = 0;
exec('composer update 2>&1', $output, $returnVar);

if ($returnVar !== 0) {
$this->error('Composer update failed: '.implode("\n", $output));

return;
}

$this->info('Composer update completed successfully');
} else {
$this->info("Please run 'composer update' manually");
}
}

private function optimizeClear(): void
{
if ($this->confirm('Run artisan optimize:clear now?', true)) {
$this->info('Clearing cache...');
$this->call('optimize:clear');
$this->info('Cache cleared successfully');
} else {
$this->info("Please run 'artisan optimize:clear' manually");
}
}

private function queueRestart(): void
{
if ($this->confirm('Run queue:restart now?', false)) {
$this->info('Restarting queue...');
$this->call('queue:restart');
}
}

private function goodbye(): void
{
$this->info('Have a nice dev!');
}

private function resolvePath(string $path): string
{
return str_starts_with($path, '~/') ? str_replace('~', getenv('HOME'), $path) : rtrim(realpath($path) ?: $path, '/');
}
}
Loading

0 comments on commit 2027389

Please sign in to comment.