style updates
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'enabled' => (bool) env('MARKETPLACE_ENABLED', true),
|
||||
'catalogue_url' => env('MARKETPLACE_CATALOGUE_URL'),
|
||||
|
||||
@@ -1,14 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Marketplace\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Modules\Marketplace\Models\InstalledPackage;
|
||||
|
||||
final class MarketplaceAuditCommand extends Command
|
||||
{
|
||||
protected $signature='marketplace:audit'; protected $description='Audit installed marketplace package state.';
|
||||
protected $signature = 'marketplace:audit';
|
||||
|
||||
protected $description = 'Audit installed marketplace package state.';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$packages=InstalledPackage::query()->with('package')->get();
|
||||
foreach ($packages as $package) $this->line(sprintf('%s %s [%s]%s',$package->package_name,$package->version,$package->status,$package->hasUpdate()?' update available':''));
|
||||
$this->info($packages->count().' installed package(s) audited.'); return self::SUCCESS;
|
||||
$packages = InstalledPackage::query()->with('package')->get();
|
||||
foreach ($packages as $package) {
|
||||
$this->line(sprintf('%s %s [%s]%s', $package->package_name, $package->version, $package->status, $package->hasUpdate() ? ' update available' : ''));
|
||||
}
|
||||
$this->info($packages->count().' installed package(s) audited.');
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Marketplace\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
final class MarketplaceSyncCommand extends Command
|
||||
{
|
||||
protected $signature='marketplace:sync'; protected $description='Synchronise the configured marketplace catalogue.';
|
||||
protected $signature = 'marketplace:sync';
|
||||
|
||||
protected $description = 'Synchronise the configured marketplace catalogue.';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
if (blank(config('marketplace.catalogue_url'))) { $this->warn('MARKETPLACE_CATALOGUE_URL is not configured.'); return self::SUCCESS; }
|
||||
$this->info('Catalogue synchronisation is configured. Remote installs remain disabled unless explicitly enabled.'); return self::SUCCESS;
|
||||
if (blank(config('marketplace.catalogue_url'))) {
|
||||
$this->warn('MARKETPLACE_CATALOGUE_URL is not configured.');
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
$this->info('Catalogue synchronisation is configured. Remote installs remain disabled unless explicitly enabled.');
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
+75
-27
@@ -1,48 +1,96 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
return new class extends Migration {
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('marketplace_publishers', function (Blueprint $table) {
|
||||
$table->id(); $table->string('name'); $table->string('slug')->unique(); $table->string('email')->nullable();
|
||||
$table->string('website')->nullable(); $table->text('public_key')->nullable(); $table->boolean('is_verified')->default(false)->index();
|
||||
$table->json('metadata')->nullable(); $table->timestamps();
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->string('slug')->unique();
|
||||
$table->string('email')->nullable();
|
||||
$table->string('website')->nullable();
|
||||
$table->text('public_key')->nullable();
|
||||
$table->boolean('is_verified')->default(false)->index();
|
||||
$table->json('metadata')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
Schema::create('marketplace_packages', function (Blueprint $table) {
|
||||
$table->id(); $table->foreignId('publisher_id')->nullable()->constrained('marketplace_publishers')->nullOnDelete();
|
||||
$table->string('type',30)->index(); $table->string('name'); $table->string('slug')->unique(); $table->string('package_name')->unique();
|
||||
$table->text('description')->nullable(); $table->string('latest_version')->nullable(); $table->string('homepage')->nullable();
|
||||
$table->string('repository_url')->nullable(); $table->string('download_url')->nullable(); $table->string('checksum',128)->nullable();
|
||||
$table->text('signature')->nullable(); $table->json('manifest')->nullable(); $table->boolean('is_featured')->default(false)->index();
|
||||
$table->boolean('is_active')->default(true)->index(); $table->timestamp('catalogue_updated_at')->nullable(); $table->timestamps();
|
||||
$table->id();
|
||||
$table->foreignId('publisher_id')->nullable()->constrained('marketplace_publishers')->nullOnDelete();
|
||||
$table->string('type', 30)->index();
|
||||
$table->string('name');
|
||||
$table->string('slug')->unique();
|
||||
$table->string('package_name')->unique();
|
||||
$table->text('description')->nullable();
|
||||
$table->string('latest_version')->nullable();
|
||||
$table->string('homepage')->nullable();
|
||||
$table->string('repository_url')->nullable();
|
||||
$table->string('download_url')->nullable();
|
||||
$table->string('checksum', 128)->nullable();
|
||||
$table->text('signature')->nullable();
|
||||
$table->json('manifest')->nullable();
|
||||
$table->boolean('is_featured')->default(false)->index();
|
||||
$table->boolean('is_active')->default(true)->index();
|
||||
$table->timestamp('catalogue_updated_at')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
Schema::create('marketplace_installed_packages', function (Blueprint $table) {
|
||||
$table->id(); $table->foreignId('marketplace_package_id')->nullable()->constrained('marketplace_packages')->nullOnDelete();
|
||||
$table->string('package_name')->unique(); $table->string('type',30)->index(); $table->string('version');
|
||||
$table->string('status',30)->default('installed')->index(); $table->json('manifest')->nullable(); $table->string('checksum',128)->nullable();
|
||||
$table->string('install_path')->nullable(); $table->boolean('is_enabled')->default(true)->index();
|
||||
$table->foreignId('installed_by')->nullable()->constrained('users')->nullOnDelete(); $table->timestamp('installed_at')->nullable();
|
||||
$table->timestamp('updated_at_marketplace')->nullable(); $table->timestamps();
|
||||
$table->id();
|
||||
$table->foreignId('marketplace_package_id')->nullable()->constrained('marketplace_packages')->nullOnDelete();
|
||||
$table->string('package_name')->unique();
|
||||
$table->string('type', 30)->index();
|
||||
$table->string('version');
|
||||
$table->string('status', 30)->default('installed')->index();
|
||||
$table->json('manifest')->nullable();
|
||||
$table->string('checksum', 128)->nullable();
|
||||
$table->string('install_path')->nullable();
|
||||
$table->boolean('is_enabled')->default(true)->index();
|
||||
$table->foreignId('installed_by')->nullable()->constrained('users')->nullOnDelete();
|
||||
$table->timestamp('installed_at')->nullable();
|
||||
$table->timestamp('updated_at_marketplace')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
Schema::create('marketplace_installations', function (Blueprint $table) {
|
||||
$table->id(); $table->foreignId('installed_package_id')->nullable()->constrained('marketplace_installed_packages')->nullOnDelete();
|
||||
$table->string('package_name')->index(); $table->string('operation',30)->index(); $table->string('from_version')->nullable();
|
||||
$table->string('to_version')->nullable(); $table->string('status',30)->default('pending')->index(); $table->string('snapshot_path')->nullable();
|
||||
$table->text('message')->nullable(); $table->json('context')->nullable(); $table->foreignId('performed_by')->nullable()->constrained('users')->nullOnDelete();
|
||||
$table->timestamp('started_at')->nullable(); $table->timestamp('completed_at')->nullable(); $table->timestamps();
|
||||
$table->id();
|
||||
$table->foreignId('installed_package_id')->nullable()->constrained('marketplace_installed_packages')->nullOnDelete();
|
||||
$table->string('package_name')->index();
|
||||
$table->string('operation', 30)->index();
|
||||
$table->string('from_version')->nullable();
|
||||
$table->string('to_version')->nullable();
|
||||
$table->string('status', 30)->default('pending')->index();
|
||||
$table->string('snapshot_path')->nullable();
|
||||
$table->text('message')->nullable();
|
||||
$table->json('context')->nullable();
|
||||
$table->foreignId('performed_by')->nullable()->constrained('users')->nullOnDelete();
|
||||
$table->timestamp('started_at')->nullable();
|
||||
$table->timestamp('completed_at')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
Schema::create('marketplace_licences', function (Blueprint $table) {
|
||||
$table->id(); $table->foreignId('marketplace_package_id')->nullable()->constrained('marketplace_packages')->nullOnDelete();
|
||||
$table->string('licence_key')->nullable(); $table->string('status',30)->default('inactive')->index(); $table->string('plan')->nullable();
|
||||
$table->string('licensed_to')->nullable(); $table->timestamp('activated_at')->nullable(); $table->timestamp('expires_at')->nullable();
|
||||
$table->json('metadata')->nullable(); $table->timestamps();
|
||||
$table->id();
|
||||
$table->foreignId('marketplace_package_id')->nullable()->constrained('marketplace_packages')->nullOnDelete();
|
||||
$table->string('licence_key')->nullable();
|
||||
$table->string('status', 30)->default('inactive')->index();
|
||||
$table->string('plan')->nullable();
|
||||
$table->string('licensed_to')->nullable();
|
||||
$table->timestamp('activated_at')->nullable();
|
||||
$table->timestamp('expires_at')->nullable();
|
||||
$table->json('metadata')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('marketplace_licences'); Schema::dropIfExists('marketplace_installations');
|
||||
Schema::dropIfExists('marketplace_installed_packages'); Schema::dropIfExists('marketplace_packages'); Schema::dropIfExists('marketplace_publishers');
|
||||
Schema::dropIfExists('marketplace_licences');
|
||||
Schema::dropIfExists('marketplace_installations');
|
||||
Schema::dropIfExists('marketplace_installed_packages');
|
||||
Schema::dropIfExists('marketplace_packages');
|
||||
Schema::dropIfExists('marketplace_publishers');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,16 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Marketplace\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Controller;
|
||||
use Modules\Marketplace\Models\InstalledPackage;
|
||||
use Modules\Marketplace\Models\MarketplacePackage;
|
||||
use Modules\Marketplace\Services\MarketplaceManager;
|
||||
|
||||
final class MarketplaceApiController extends Controller
|
||||
{
|
||||
public function packages(Request $request): JsonResponse { return response()->json(MarketplacePackage::query()->with('publisher')->where('is_active',true)->when($request->filled('type'),fn($q)=>$q->where('type',$request->string('type')))->paginate(min(100,max(1,$request->integer('per_page',25))))); }
|
||||
public function installed(): JsonResponse { return response()->json(InstalledPackage::query()->with('package')->paginate(50)); }
|
||||
public function install(MarketplacePackage $package,MarketplaceManager $manager,Request $request): JsonResponse { return response()->json($manager->install($package,$request->user()?->id)->load('package'),201); }
|
||||
public function toggle(InstalledPackage $package,MarketplaceManager $manager,Request $request): JsonResponse { return response()->json($manager->setEnabled($package,!$package->is_enabled,$request->user()?->id)); }
|
||||
public function remove(InstalledPackage $package,MarketplaceManager $manager,Request $request): JsonResponse { $manager->remove($package,$request->user()?->id); return response()->json(['status'=>'removed']); }
|
||||
public function packages(Request $request): JsonResponse
|
||||
{
|
||||
return response()->json(MarketplacePackage::query()->with('publisher')->where('is_active', true)->when($request->filled('type'), fn ($q) => $q->where('type', $request->string('type')))->paginate(min(100, max(1, $request->integer('per_page', 25)))));
|
||||
}
|
||||
|
||||
public function installed(): JsonResponse
|
||||
{
|
||||
return response()->json(InstalledPackage::query()->with('package')->paginate(50));
|
||||
}
|
||||
|
||||
public function install(MarketplacePackage $package, MarketplaceManager $manager, Request $request): JsonResponse
|
||||
{
|
||||
return response()->json($manager->install($package, $request->user()?->id)->load('package'), 201);
|
||||
}
|
||||
|
||||
public function toggle(InstalledPackage $package, MarketplaceManager $manager, Request $request): JsonResponse
|
||||
{
|
||||
return response()->json($manager->setEnabled($package, ! $package->is_enabled, $request->user()?->id));
|
||||
}
|
||||
|
||||
public function remove(InstalledPackage $package, MarketplaceManager $manager, Request $request): JsonResponse
|
||||
{
|
||||
$manager->remove($package, $request->user()?->id);
|
||||
|
||||
return response()->json(['status' => 'removed']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Marketplace\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Controller;
|
||||
@@ -8,17 +10,55 @@ use Modules\Marketplace\Models\Installation;
|
||||
use Modules\Marketplace\Models\InstalledPackage;
|
||||
use Modules\Marketplace\Models\MarketplacePackage;
|
||||
use Modules\Marketplace\Services\MarketplaceManager;
|
||||
|
||||
final class MarketplaceController extends Controller
|
||||
{
|
||||
public function index(Request $request): View
|
||||
{
|
||||
$packages=MarketplacePackage::query()->with('publisher')->where('is_active',true)->when($request->filled('type'),fn($q)=>$q->where('type',$request->string('type')))->when($request->filled('search'),fn($q)=>$q->where(fn($q)=>$q->where('name','like','%'.$request->string('search').'%')->orWhere('description','like','%'.$request->string('search').'%')))->latest('is_featured')->paginate(20)->withQueryString();
|
||||
return view('marketplace::index',compact('packages'));
|
||||
$packages = MarketplacePackage::query()->with('publisher')->where('is_active', true)->when($request->filled('type'), fn ($q) => $q->where('type', $request->string('type')))->when($request->filled('search'), fn ($q) => $q->where(fn ($q) => $q->where('name', 'like', '%'.$request->string('search').'%')->orWhere('description', 'like', '%'.$request->string('search').'%')))->latest('is_featured')->paginate(20)->withQueryString();
|
||||
|
||||
return view('marketplace::index', compact('packages'));
|
||||
}
|
||||
|
||||
public function installed(): View
|
||||
{
|
||||
$packages = InstalledPackage::query()->with('package')->latest()->paginate(25);
|
||||
|
||||
return view('marketplace::installed', compact('packages'));
|
||||
}
|
||||
|
||||
public function install(MarketplacePackage $package, MarketplaceManager $manager, Request $request): RedirectResponse
|
||||
{
|
||||
$manager->install($package, $request->user()?->id);
|
||||
|
||||
return back()->with('status', 'Package installed successfully.');
|
||||
}
|
||||
|
||||
public function update(MarketplacePackage $package, MarketplaceManager $manager, Request $request): RedirectResponse
|
||||
{
|
||||
$manager->install($package, $request->user()?->id);
|
||||
|
||||
return back()->with('status', 'Package updated successfully.');
|
||||
}
|
||||
|
||||
public function toggle(InstalledPackage $package, MarketplaceManager $manager, Request $request): RedirectResponse
|
||||
{
|
||||
$manager->setEnabled($package, ! $package->is_enabled, $request->user()?->id);
|
||||
|
||||
return back()->with('status', 'Package state updated.');
|
||||
}
|
||||
|
||||
public function remove(InstalledPackage $package, MarketplaceManager $manager, Request $request): RedirectResponse
|
||||
{
|
||||
$manager->remove($package, $request->user()?->id);
|
||||
|
||||
return back()->with('status', 'Package removed.');
|
||||
}
|
||||
|
||||
public function rollback(InstalledPackage $package, Installation $installation, MarketplaceManager $manager, Request $request): RedirectResponse
|
||||
{
|
||||
$manager->rollback($package, $installation, $request->user()?->id);
|
||||
|
||||
return back()->with('status','Package rolled back.');
|
||||
}
|
||||
public function installed(): View { $packages=InstalledPackage::query()->with('package')->latest()->paginate(25); return view('marketplace::installed',compact('packages')); }
|
||||
public function install(MarketplacePackage $package,MarketplaceManager $manager,Request $request): RedirectResponse { $manager->install($package,$request->user()?->id); return back()->with('status','Package installed successfully.'); }
|
||||
public function update(MarketplacePackage $package,MarketplaceManager $manager,Request $request): RedirectResponse { $manager->install($package,$request->user()?->id); return back()->with('status','Package updated successfully.'); }
|
||||
public function toggle(InstalledPackage $package,MarketplaceManager $manager,Request $request): RedirectResponse { $manager->setEnabled($package,!$package->is_enabled,$request->user()?->id); return back()->with('status','Package state updated.'); }
|
||||
public function remove(InstalledPackage $package,MarketplaceManager $manager,Request $request): RedirectResponse { $manager->remove($package,$request->user()?->id); return back()->with('status','Package removed.'); }
|
||||
public function rollback(InstalledPackage $package,Installation $installation,MarketplaceManager $manager,Request $request): RedirectResponse { $manager->rollback($package,$installation,$request->user()?->id); return back()->with('status','Package rolled back.'); }
|
||||
}
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Marketplace\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
final class Installation extends Model
|
||||
{
|
||||
protected $table='marketplace_installations';
|
||||
protected $fillable=['installed_package_id','package_name','operation','from_version','to_version','status','snapshot_path','message','context','performed_by','started_at','completed_at'];
|
||||
protected $casts=['context'=>'array','started_at'=>'datetime','completed_at'=>'datetime'];
|
||||
protected $table = 'marketplace_installations';
|
||||
|
||||
protected $fillable = ['installed_package_id', 'package_name', 'operation', 'from_version', 'to_version', 'status', 'snapshot_path', 'message', 'context', 'performed_by', 'started_at', 'completed_at'];
|
||||
|
||||
protected $casts = ['context' => 'array', 'started_at' => 'datetime', 'completed_at' => 'datetime'];
|
||||
}
|
||||
|
||||
@@ -1,14 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Marketplace\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
final class InstalledPackage extends Model
|
||||
{
|
||||
protected $table='marketplace_installed_packages';
|
||||
protected $fillable=['marketplace_package_id','package_name','type','version','status','manifest','checksum','install_path','is_enabled','installed_by','installed_at','updated_at_marketplace'];
|
||||
protected $casts=['manifest'=>'array','is_enabled'=>'boolean','installed_at'=>'datetime','updated_at_marketplace'=>'datetime'];
|
||||
public function package(): BelongsTo { return $this->belongsTo(MarketplacePackage::class,'marketplace_package_id'); }
|
||||
public function installations(): HasMany { return $this->hasMany(Installation::class,'installed_package_id'); }
|
||||
public function hasUpdate(): bool { return $this->package?->latest_version && version_compare($this->package->latest_version,$this->version,'>'); }
|
||||
protected $table = 'marketplace_installed_packages';
|
||||
|
||||
protected $fillable = ['marketplace_package_id', 'package_name', 'type', 'version', 'status', 'manifest', 'checksum', 'install_path', 'is_enabled', 'installed_by', 'installed_at', 'updated_at_marketplace'];
|
||||
|
||||
protected $casts = ['manifest' => 'array', 'is_enabled' => 'boolean', 'installed_at' => 'datetime', 'updated_at_marketplace' => 'datetime'];
|
||||
|
||||
public function package(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(MarketplacePackage::class, 'marketplace_package_id');
|
||||
}
|
||||
|
||||
public function installations(): HasMany
|
||||
{
|
||||
return $this->hasMany(Installation::class, 'installed_package_id');
|
||||
}
|
||||
|
||||
public function hasUpdate(): bool
|
||||
{
|
||||
return $this->package?->latest_version && version_compare($this->package->latest_version, $this->version, '>');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Marketplace\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
final class MarketplaceLicence extends Model
|
||||
{
|
||||
protected $table='marketplace_licences';
|
||||
protected $fillable=['marketplace_package_id','licence_key','status','plan','licensed_to','activated_at','expires_at','metadata'];
|
||||
protected $hidden=['licence_key'];
|
||||
protected $casts=['activated_at'=>'datetime','expires_at'=>'datetime','metadata'=>'array'];
|
||||
protected $table = 'marketplace_licences';
|
||||
|
||||
protected $fillable = ['marketplace_package_id', 'licence_key', 'status', 'plan', 'licensed_to', 'activated_at', 'expires_at', 'metadata'];
|
||||
|
||||
protected $hidden = ['licence_key'];
|
||||
|
||||
protected $casts = ['activated_at' => 'datetime', 'expires_at' => 'datetime', 'metadata' => 'array'];
|
||||
}
|
||||
|
||||
@@ -1,13 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Marketplace\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
final class MarketplacePackage extends Model
|
||||
{
|
||||
protected $table='marketplace_packages';
|
||||
protected $fillable=['publisher_id','type','name','slug','package_name','description','latest_version','homepage','repository_url','download_url','checksum','signature','manifest','is_featured','is_active','catalogue_updated_at'];
|
||||
protected $casts=['manifest'=>'array','is_featured'=>'boolean','is_active'=>'boolean','catalogue_updated_at'=>'datetime'];
|
||||
public function publisher(): BelongsTo { return $this->belongsTo(Publisher::class); }
|
||||
public function licences(): HasMany { return $this->hasMany(MarketplaceLicence::class,'marketplace_package_id'); }
|
||||
protected $table = 'marketplace_packages';
|
||||
|
||||
protected $fillable = ['publisher_id', 'type', 'name', 'slug', 'package_name', 'description', 'latest_version', 'homepage', 'repository_url', 'download_url', 'checksum', 'signature', 'manifest', 'is_featured', 'is_active', 'catalogue_updated_at'];
|
||||
|
||||
protected $casts = ['manifest' => 'array', 'is_featured' => 'boolean', 'is_active' => 'boolean', 'catalogue_updated_at' => 'datetime'];
|
||||
|
||||
public function publisher(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Publisher::class);
|
||||
}
|
||||
|
||||
public function licences(): HasMany
|
||||
{
|
||||
return $this->hasMany(MarketplaceLicence::class, 'marketplace_package_id');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Marketplace\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
final class Publisher extends Model
|
||||
{
|
||||
protected $table='marketplace_publishers';
|
||||
protected $fillable=['name','slug','email','website','public_key','is_verified','metadata'];
|
||||
protected $casts=['is_verified'=>'boolean','metadata'=>'array'];
|
||||
public function packages(): HasMany { return $this->hasMany(MarketplacePackage::class,'publisher_id'); }
|
||||
protected $table = 'marketplace_publishers';
|
||||
|
||||
protected $fillable = ['name', 'slug', 'email', 'website', 'public_key', 'is_verified', 'metadata'];
|
||||
|
||||
protected $casts = ['is_verified' => 'boolean', 'metadata' => 'array'];
|
||||
|
||||
public function packages(): HasMany
|
||||
{
|
||||
return $this->hasMany(MarketplacePackage::class, 'publisher_id');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Marketplace\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Modules\Marketplace\Console\Commands\MarketplaceAuditCommand;
|
||||
use Modules\Marketplace\Console\Commands\MarketplaceSyncCommand;
|
||||
use Modules\Marketplace\Services\CompatibilityChecker;
|
||||
use Modules\Marketplace\Services\DependencyResolver;
|
||||
use Modules\Marketplace\Services\MarketplaceManager;
|
||||
use Modules\Marketplace\Services\PackageManifest;
|
||||
use Modules\Marketplace\Services\PackageVerifier;
|
||||
|
||||
final class MarketplaceServiceProvider extends ServiceProvider
|
||||
{
|
||||
public function register(): void
|
||||
@@ -18,13 +23,14 @@ final class MarketplaceServiceProvider extends ServiceProvider
|
||||
$this->app->singleton(MarketplaceManager::class);
|
||||
$this->app->register(RouteServiceProvider::class);
|
||||
}
|
||||
|
||||
public function boot(): void
|
||||
{
|
||||
$this->loadMigrationsFrom(module_path('Marketplace', 'Database/Migrations'));
|
||||
$this->loadViewsFrom(module_path('Marketplace', 'Resources/views'), 'marketplace');
|
||||
$this->loadTranslationsFrom(module_path('Marketplace', 'Resources/lang'), 'marketplace');
|
||||
if ($this->app->runningInConsole()) {
|
||||
$this->commands([\Modules\Marketplace\Console\Commands\MarketplaceSyncCommand::class, \Modules\Marketplace\Console\Commands\MarketplaceAuditCommand::class]);
|
||||
$this->commands([MarketplaceSyncCommand::class, MarketplaceAuditCommand::class]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Marketplace\Providers;
|
||||
|
||||
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
final class RouteServiceProvider extends ServiceProvider
|
||||
{
|
||||
public function boot(): void
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Modules\Marketplace\Http\Controllers\MarketplaceApiController;
|
||||
|
||||
Route::middleware('auth:sanctum')->prefix('marketplace')->name('api.marketplace.')->group(function () {
|
||||
Route::get('/packages',[MarketplaceApiController::class,'packages'])->middleware('permission:marketplace.view')->name('packages');
|
||||
Route::get('/installed',[MarketplaceApiController::class,'installed'])->middleware('permission:marketplace.view')->name('installed');
|
||||
Route::post('/packages/{package}/install',[MarketplaceApiController::class,'install'])->middleware('permission:marketplace.install')->name('install');
|
||||
Route::post('/installed/{package}/toggle',[MarketplaceApiController::class,'toggle'])->middleware('permission:marketplace.update')->name('toggle');
|
||||
Route::delete('/installed/{package}',[MarketplaceApiController::class,'remove'])->middleware('permission:marketplace.remove')->name('remove');
|
||||
Route::get('/packages', [MarketplaceApiController::class, 'packages'])->middleware('permission:marketplace.view')->name('packages');
|
||||
Route::get('/installed', [MarketplaceApiController::class, 'installed'])->middleware('permission:marketplace.view')->name('installed');
|
||||
Route::post('/packages/{package}/install', [MarketplaceApiController::class, 'install'])->middleware('permission:marketplace.install')->name('install');
|
||||
Route::post('/installed/{package}/toggle', [MarketplaceApiController::class, 'toggle'])->middleware('permission:marketplace.update')->name('toggle');
|
||||
Route::delete('/installed/{package}', [MarketplaceApiController::class, 'remove'])->middleware('permission:marketplace.remove')->name('remove');
|
||||
});
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Modules\Marketplace\Http\Controllers\MarketplaceController;
|
||||
Route::middleware(['auth','verified'])->prefix('administration/marketplace')->name('administration.marketplace.')->group(function () {
|
||||
Route::get('/',[MarketplaceController::class,'index'])->middleware('permission:marketplace.view')->name('index');
|
||||
Route::get('/installed',[MarketplaceController::class,'installed'])->middleware('permission:marketplace.view')->name('installed');
|
||||
Route::post('/packages/{package}/install',[MarketplaceController::class,'install'])->middleware('permission:marketplace.install')->name('install');
|
||||
Route::post('/packages/{package}/update',[MarketplaceController::class,'update'])->middleware('permission:marketplace.update')->name('update');
|
||||
Route::post('/installed/{package}/toggle',[MarketplaceController::class,'toggle'])->middleware('permission:marketplace.update')->name('toggle');
|
||||
Route::delete('/installed/{package}',[MarketplaceController::class,'remove'])->middleware('permission:marketplace.remove')->name('remove');
|
||||
Route::post('/installed/{package}/rollback/{installation}',[MarketplaceController::class,'rollback'])->middleware('permission:marketplace.update')->name('rollback');
|
||||
|
||||
Route::middleware(['auth', 'verified'])->prefix('administration/marketplace')->name('administration.marketplace.')->group(function () {
|
||||
Route::get('/', [MarketplaceController::class, 'index'])->middleware('permission:marketplace.view')->name('index');
|
||||
Route::get('/installed', [MarketplaceController::class, 'installed'])->middleware('permission:marketplace.view')->name('installed');
|
||||
Route::post('/packages/{package}/install', [MarketplaceController::class, 'install'])->middleware('permission:marketplace.install')->name('install');
|
||||
Route::post('/packages/{package}/update', [MarketplaceController::class, 'update'])->middleware('permission:marketplace.update')->name('update');
|
||||
Route::post('/installed/{package}/toggle', [MarketplaceController::class, 'toggle'])->middleware('permission:marketplace.update')->name('toggle');
|
||||
Route::delete('/installed/{package}', [MarketplaceController::class, 'remove'])->middleware('permission:marketplace.remove')->name('remove');
|
||||
Route::post('/installed/{package}/rollback/{installation}', [MarketplaceController::class, 'rollback'])->middleware('permission:marketplace.update')->name('rollback');
|
||||
});
|
||||
|
||||
@@ -1,17 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Marketplace\Services;
|
||||
|
||||
use Illuminate\Support\Facades\App;
|
||||
|
||||
final class CompatibilityChecker
|
||||
{
|
||||
public function __construct(private VersionConstraint $constraints=new VersionConstraint()) {}
|
||||
public function __construct(private VersionConstraint $constraints = new VersionConstraint) {}
|
||||
|
||||
public function check(array $manifest): array
|
||||
{
|
||||
$errors=[];
|
||||
$php=(string)($manifest['php'] ?? '*'); $laravel=(string)($manifest['laravel'] ?? '*');
|
||||
$platform=(string)($manifest['content_manager'] ?? '*'); $platformVersion=(string) config('app.version','1.0.0');
|
||||
if (!$this->constraints->matches(PHP_VERSION,$php)) $errors[]="Requires PHP $php; running ".PHP_VERSION.'.';
|
||||
if (!$this->constraints->matches(App::version(),$laravel)) $errors[]="Requires Laravel $laravel; running ".App::version().'.';
|
||||
if (!$this->constraints->matches($platformVersion,$platform)) $errors[]="Requires Content Manager $platform; running $platformVersion.";
|
||||
return ['compatible'=>$errors===[],'errors'=>$errors];
|
||||
$errors = [];
|
||||
$php = (string) ($manifest['php'] ?? '*');
|
||||
$laravel = (string) ($manifest['laravel'] ?? '*');
|
||||
$platform = (string) ($manifest['content_manager'] ?? '*');
|
||||
$platformVersion = (string) config('app.version', '1.0.0');
|
||||
if (! $this->constraints->matches(PHP_VERSION, $php)) {
|
||||
$errors[] = "Requires PHP $php; running ".PHP_VERSION.'.';
|
||||
}
|
||||
if (! $this->constraints->matches(App::version(), $laravel)) {
|
||||
$errors[] = "Requires Laravel $laravel; running ".App::version().'.';
|
||||
}
|
||||
if (! $this->constraints->matches($platformVersion, $platform)) {
|
||||
$errors[] = "Requires Content Manager $platform; running $platformVersion.";
|
||||
}
|
||||
|
||||
return ['compatible' => $errors === [], 'errors' => $errors];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Marketplace\Services;
|
||||
|
||||
use Modules\Marketplace\Models\InstalledPackage;
|
||||
|
||||
final class DependencyResolver
|
||||
{
|
||||
public function __construct(private VersionConstraint $constraints=new VersionConstraint()) {}
|
||||
public function __construct(private VersionConstraint $constraints = new VersionConstraint) {}
|
||||
|
||||
public function resolve(array $manifest): array
|
||||
{
|
||||
$missing=[]; $incompatible=[];
|
||||
foreach (($manifest['requires'] ?? []) as $package=>$constraint) {
|
||||
$installed=InstalledPackage::query()->where('package_name',$package)->where('status','installed')->first();
|
||||
if (!$installed) { $missing[$package]=$constraint; continue; }
|
||||
if (!$this->constraints->matches($installed->version,(string)$constraint)) $incompatible[$package]=['required'=>$constraint,'installed'=>$installed->version];
|
||||
$missing = [];
|
||||
$incompatible = [];
|
||||
foreach (($manifest['requires'] ?? []) as $package => $constraint) {
|
||||
$installed = InstalledPackage::query()->where('package_name', $package)->where('status', 'installed')->first();
|
||||
if (! $installed) {
|
||||
$missing[$package] = $constraint;
|
||||
|
||||
continue;
|
||||
}
|
||||
if (! $this->constraints->matches($installed->version, (string) $constraint)) {
|
||||
$incompatible[$package] = ['required' => $constraint, 'installed' => $installed->version];
|
||||
}
|
||||
}
|
||||
return ['satisfied'=>$missing===[] && $incompatible===[],'missing'=>$missing,'incompatible'=>$incompatible];
|
||||
|
||||
return ['satisfied' => $missing === [] && $incompatible === [], 'missing' => $missing, 'incompatible' => $incompatible];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +1,72 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Marketplace\Services;
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use RuntimeException;
|
||||
use Modules\Marketplace\Models\Installation;
|
||||
use Modules\Marketplace\Models\InstalledPackage;
|
||||
use Modules\Marketplace\Models\MarketplacePackage;
|
||||
use RuntimeException;
|
||||
|
||||
final class MarketplaceManager
|
||||
{
|
||||
public function __construct(private PackageManifest $manifests,private CompatibilityChecker $compatibility,private DependencyResolver $dependencies) {}
|
||||
public function install(MarketplacePackage $package,?int $userId=null): InstalledPackage
|
||||
public function __construct(private PackageManifest $manifests, private CompatibilityChecker $compatibility, private DependencyResolver $dependencies) {}
|
||||
|
||||
public function install(MarketplacePackage $package, ?int $userId = null): InstalledPackage
|
||||
{
|
||||
if (!config('marketplace.enabled')) throw new RuntimeException('The marketplace is disabled.');
|
||||
$manifest=$this->manifests->parse($package->manifest ?: ['name'=>$package->package_name,'type'=>$package->type,'version'=>$package->latest_version]);
|
||||
$compatibility=$this->compatibility->check($manifest); if (!$compatibility['compatible']) throw new RuntimeException(implode(' ',$compatibility['errors']));
|
||||
$dependencies=$this->dependencies->resolve($manifest); if (!$dependencies['satisfied']) throw new RuntimeException('Package dependencies are not satisfied.');
|
||||
return DB::transaction(function() use ($package,$manifest,$userId) {
|
||||
$installed=InstalledPackage::query()->firstOrNew(['package_name'=>$package->package_name]); $from=$installed->exists?$installed->version:null;
|
||||
$installed->fill(['marketplace_package_id'=>$package->id,'type'=>$package->type,'version'=>$manifest['version'],'status'=>'installed','manifest'=>$manifest,'checksum'=>$package->checksum,'is_enabled'=>true,'installed_by'=>$userId,'installed_at'=>$installed->installed_at ?: now(),'updated_at_marketplace'=>now()])->save();
|
||||
$this->history($installed,$from?'update':'install',$from,$manifest['version'],$userId,'completed'); return $installed->refresh();
|
||||
if (! config('marketplace.enabled')) {
|
||||
throw new RuntimeException('The marketplace is disabled.');
|
||||
}
|
||||
$manifest = $this->manifests->parse($package->manifest ?: ['name' => $package->package_name, 'type' => $package->type, 'version' => $package->latest_version]);
|
||||
$compatibility = $this->compatibility->check($manifest);
|
||||
if (! $compatibility['compatible']) {
|
||||
throw new RuntimeException(implode(' ', $compatibility['errors']));
|
||||
}
|
||||
$dependencies = $this->dependencies->resolve($manifest);
|
||||
if (! $dependencies['satisfied']) {
|
||||
throw new RuntimeException('Package dependencies are not satisfied.');
|
||||
}
|
||||
|
||||
return DB::transaction(function () use ($package, $manifest, $userId) {
|
||||
$installed = InstalledPackage::query()->firstOrNew(['package_name' => $package->package_name]);
|
||||
$from = $installed->exists ? $installed->version : null;
|
||||
$installed->fill(['marketplace_package_id' => $package->id, 'type' => $package->type, 'version' => $manifest['version'], 'status' => 'installed', 'manifest' => $manifest, 'checksum' => $package->checksum, 'is_enabled' => true, 'installed_by' => $userId, 'installed_at' => $installed->installed_at ?: now(), 'updated_at_marketplace' => now()])->save();
|
||||
$this->history($installed, $from ? 'update' : 'install', $from, $manifest['version'], $userId, 'completed');
|
||||
|
||||
return $installed->refresh();
|
||||
});
|
||||
}
|
||||
public function setEnabled(InstalledPackage $package,bool $enabled,?int $userId=null): InstalledPackage
|
||||
|
||||
public function setEnabled(InstalledPackage $package, bool $enabled, ?int $userId = null): InstalledPackage
|
||||
{
|
||||
$package->update(['is_enabled'=>$enabled,'status'=>$enabled?'installed':'disabled']); $this->history($package,$enabled?'enable':'disable',$package->version,$package->version,$userId,'completed'); return $package->refresh();
|
||||
$package->update(['is_enabled' => $enabled, 'status' => $enabled ? 'installed' : 'disabled']);
|
||||
$this->history($package, $enabled ? 'enable' : 'disable', $package->version, $package->version, $userId, 'completed');
|
||||
|
||||
return $package->refresh();
|
||||
}
|
||||
public function remove(InstalledPackage $package,?int $userId=null): void
|
||||
|
||||
public function remove(InstalledPackage $package, ?int $userId = null): void
|
||||
{
|
||||
DB::transaction(function() use ($package,$userId) { $this->history($package,'remove',$package->version,null,$userId,'completed'); $package->update(['status'=>'removed','is_enabled'=>false]); });
|
||||
DB::transaction(function () use ($package, $userId) {
|
||||
$this->history($package, 'remove', $package->version, null, $userId, 'completed');
|
||||
$package->update(['status' => 'removed', 'is_enabled' => false]);
|
||||
});
|
||||
}
|
||||
public function rollback(InstalledPackage $package,Installation $installation,?int $userId=null): InstalledPackage
|
||||
|
||||
public function rollback(InstalledPackage $package, Installation $installation, ?int $userId = null): InstalledPackage
|
||||
{
|
||||
if ($installation->package_name!==$package->package_name || blank($installation->from_version)) throw new RuntimeException('This installation cannot be rolled back.');
|
||||
$current=$package->version; $package->update(['version'=>$installation->from_version,'status'=>'installed','is_enabled'=>true]);
|
||||
$this->history($package,'rollback',$current,$installation->from_version,$userId,'completed'); return $package->refresh();
|
||||
if ($installation->package_name !== $package->package_name || blank($installation->from_version)) {
|
||||
throw new RuntimeException('This installation cannot be rolled back.');
|
||||
}
|
||||
$current = $package->version;
|
||||
$package->update(['version' => $installation->from_version, 'status' => 'installed', 'is_enabled' => true]);
|
||||
$this->history($package, 'rollback', $current, $installation->from_version, $userId, 'completed');
|
||||
|
||||
return $package->refresh();
|
||||
}
|
||||
|
||||
private function history(InstalledPackage $package, string $operation, ?string $from, ?string $to, ?int $userId, string $status): Installation
|
||||
{
|
||||
return Installation::query()->create(['installed_package_id' => $package->id, 'package_name' => $package->package_name, 'operation' => $operation, 'from_version' => $from, 'to_version' => $to, 'status' => $status, 'performed_by' => $userId, 'started_at' => now(), 'completed_at' => now()]);
|
||||
}
|
||||
private function history(InstalledPackage $package,string $operation,?string $from,?string $to,?int $userId,string $status): Installation
|
||||
{ return Installation::query()->create(['installed_package_id'=>$package->id,'package_name'=>$package->package_name,'operation'=>$operation,'from_version'=>$from,'to_version'=>$to,'status'=>$status,'performed_by'=>$userId,'started_at'=>now(),'completed_at'=>now()]); }
|
||||
}
|
||||
|
||||
@@ -1,22 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Marketplace\Services;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
final class PackageManifest
|
||||
{
|
||||
public function parse(array|string $manifest): array
|
||||
{
|
||||
if (is_string($manifest)) {
|
||||
$decoded=json_decode($manifest,true);
|
||||
if (!is_array($decoded)) throw ValidationException::withMessages(['manifest'=>'The package manifest is not valid JSON.']);
|
||||
$manifest=$decoded;
|
||||
$decoded = json_decode($manifest, true);
|
||||
if (! is_array($decoded)) {
|
||||
throw ValidationException::withMessages(['manifest' => 'The package manifest is not valid JSON.']);
|
||||
}
|
||||
$manifest = $decoded;
|
||||
}
|
||||
foreach (['name','type','version'] as $field) if (!filled(Arr::get($manifest,$field))) throw ValidationException::withMessages(['manifest'=>"Manifest field [$field] is required."]);
|
||||
if (!in_array($manifest['type'],config('marketplace.supported_types',[]),true)) throw ValidationException::withMessages(['manifest'=>'Unsupported marketplace package type.']);
|
||||
$manifest['requires']=$manifest['requires'] ?? [];
|
||||
$manifest['content_manager']=$manifest['content_manager'] ?? '*';
|
||||
$manifest['laravel']=$manifest['laravel'] ?? '*';
|
||||
foreach (['name', 'type', 'version'] as $field) {
|
||||
if (! filled(Arr::get($manifest, $field))) {
|
||||
throw ValidationException::withMessages(['manifest' => "Manifest field [$field] is required."]);
|
||||
}
|
||||
}
|
||||
if (! in_array($manifest['type'], config('marketplace.supported_types', []), true)) {
|
||||
throw ValidationException::withMessages(['manifest' => 'Unsupported marketplace package type.']);
|
||||
}
|
||||
$manifest['requires'] = $manifest['requires'] ?? [];
|
||||
$manifest['content_manager'] = $manifest['content_manager'] ?? '*';
|
||||
$manifest['laravel'] = $manifest['laravel'] ?? '*';
|
||||
|
||||
return $manifest;
|
||||
}
|
||||
public function fromFile(string $path): array { return $this->parse((string) file_get_contents($path)); }
|
||||
|
||||
public function fromFile(string $path): array
|
||||
{
|
||||
return $this->parse((string) file_get_contents($path));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Marketplace\Services;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
final class PackageVerifier
|
||||
{
|
||||
public function verify(string $path,array $manifest,?string $expectedChecksum=null): array
|
||||
public function verify(string $path, array $manifest, ?string $expectedChecksum = null): array
|
||||
{
|
||||
if (!is_file($path)) throw new RuntimeException("Package archive [$path] does not exist.");
|
||||
$actual=hash_file('sha256',$path);
|
||||
$expected=$expectedChecksum ?: ($manifest['checksum'] ?? null);
|
||||
if (config('marketplace.require_checksum',true) && blank($expected)) throw new RuntimeException('A SHA-256 checksum is required.');
|
||||
if (filled($expected) && !hash_equals(strtolower((string)$expected),strtolower($actual))) throw new RuntimeException('Package checksum verification failed.');
|
||||
if (config('marketplace.require_signature',false) && blank($manifest['signature'] ?? null)) throw new RuntimeException('A package signature is required.');
|
||||
return ['verified'=>true,'checksum'=>$actual,'signature_present'=>filled($manifest['signature'] ?? null)];
|
||||
if (! is_file($path)) {
|
||||
throw new RuntimeException("Package archive [$path] does not exist.");
|
||||
}
|
||||
$actual = hash_file('sha256', $path);
|
||||
$expected = $expectedChecksum ?: ($manifest['checksum'] ?? null);
|
||||
if (config('marketplace.require_checksum', true) && blank($expected)) {
|
||||
throw new RuntimeException('A SHA-256 checksum is required.');
|
||||
}
|
||||
if (filled($expected) && ! hash_equals(strtolower((string) $expected), strtolower($actual))) {
|
||||
throw new RuntimeException('Package checksum verification failed.');
|
||||
}
|
||||
if (config('marketplace.require_signature', false) && blank($manifest['signature'] ?? null)) {
|
||||
throw new RuntimeException('A package signature is required.');
|
||||
}
|
||||
|
||||
return ['verified' => true, 'checksum' => $actual, 'signature_present' => filled($manifest['signature'] ?? null)];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Marketplace\Services;
|
||||
|
||||
final class VersionConstraint
|
||||
{
|
||||
public function matches(string $version,string $constraint): bool
|
||||
public function matches(string $version, string $constraint): bool
|
||||
{
|
||||
$constraint=trim($constraint);
|
||||
if ($constraint==='' || $constraint==='*') return true;
|
||||
foreach (preg_split('/\s*\|\|\s*/',$constraint) ?: [] as $alternative) {
|
||||
$ok=true;
|
||||
foreach (preg_split('/\s*,?\s+/',trim($alternative)) ?: [] as $part) {
|
||||
if ($part==='') continue;
|
||||
if (str_starts_with($part,'^')) { $base=substr($part,1); $major=(int) explode('.',$base)[0]; $ok=$ok && version_compare($version,$base,'>=') && version_compare($version,($major+1).'.0.0','<'); continue; }
|
||||
if (str_starts_with($part,'~')) { $base=substr($part,1); $bits=array_pad(explode('.',$base),3,'0'); $upper=$bits[0].'.'.((int)$bits[1]+1).'.0'; $ok=$ok && version_compare($version,$base,'>=') && version_compare($version,$upper,'<'); continue; }
|
||||
if (preg_match('/^(>=|<=|>|<|=)?\s*([0-9][0-9A-Za-z.\-+]*)$/',$part,$m)) $ok=$ok && version_compare($version,$m[2],$m[1] ?: '=');
|
||||
else $ok=$ok && $version===$part;
|
||||
}
|
||||
if ($ok) return true;
|
||||
$constraint = trim($constraint);
|
||||
if ($constraint === '' || $constraint === '*') {
|
||||
return true;
|
||||
}
|
||||
foreach (preg_split('/\s*\|\|\s*/', $constraint) ?: [] as $alternative) {
|
||||
$ok = true;
|
||||
foreach (preg_split('/\s*,?\s+/', trim($alternative)) ?: [] as $part) {
|
||||
if ($part === '') {
|
||||
continue;
|
||||
}
|
||||
if (str_starts_with($part, '^')) {
|
||||
$base = substr($part, 1);
|
||||
$major = (int) explode('.', $base)[0];
|
||||
$ok = $ok && version_compare($version, $base, '>=') && version_compare($version, ($major + 1).'.0.0', '<');
|
||||
|
||||
continue;
|
||||
}
|
||||
if (str_starts_with($part, '~')) {
|
||||
$base = substr($part, 1);
|
||||
$bits = array_pad(explode('.', $base), 3, '0');
|
||||
$upper = $bits[0].'.'.((int) $bits[1] + 1).'.0';
|
||||
$ok = $ok && version_compare($version, $base, '>=') && version_compare($version, $upper, '<');
|
||||
|
||||
continue;
|
||||
}
|
||||
if (preg_match('/^(>=|<=|>|<|=)?\s*([0-9][0-9A-Za-z.\-+]*)$/', $part, $m)) {
|
||||
$ok = $ok && version_compare($version, $m[2], $m[1] ?: '=');
|
||||
} else {
|
||||
$ok = $ok && $version === $part;
|
||||
}
|
||||
}
|
||||
if ($ok) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Marketplace\Tests\Feature;
|
||||
|
||||
use Modules\Marketplace\Services\CompatibilityChecker;
|
||||
use Modules\Marketplace\Services\PackageManifest;
|
||||
use Tests\TestCase;
|
||||
|
||||
final class MarketplaceModuleTest extends TestCase
|
||||
{
|
||||
public function test_manifest_parser_accepts_supported_package(): void
|
||||
{
|
||||
$manifest=app(PackageManifest::class)->parse(['name'=>'vendor/example','type'=>'module','version'=>'1.0.0']);
|
||||
$this->assertSame('module',$manifest['type']); $this->assertSame([], $manifest['requires']);
|
||||
$manifest = app(PackageManifest::class)->parse(['name' => 'vendor/example', 'type' => 'module', 'version' => '1.0.0']);
|
||||
$this->assertSame('module', $manifest['type']);
|
||||
$this->assertSame([], $manifest['requires']);
|
||||
}
|
||||
|
||||
public function test_current_platform_is_compatible_with_wildcard_manifest(): void
|
||||
{
|
||||
$result=app(CompatibilityChecker::class)->check(['php'=>'*','laravel'=>'*','content_manager'=>'*']);
|
||||
$result = app(CompatibilityChecker::class)->check(['php' => '*', 'laravel' => '*', 'content_manager' => '*']);
|
||||
$this->assertTrue($result['compatible']);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user