@fleetbase/registry-bridge-engine 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.php-cs-fixer.php +29 -0
- package/LICENSE.md +651 -0
- package/README.md +122 -0
- package/addon/adapters/registry-bridge.js +5 -0
- package/addon/adapters/registry-extension-bundle.js +1 -0
- package/addon/adapters/registry-extension.js +1 -0
- package/addon/components/extension-card.hbs +12 -0
- package/addon/components/extension-card.js +235 -0
- package/addon/components/extension-form.hbs +237 -0
- package/addon/components/extension-form.js +123 -0
- package/addon/components/extension-modal-title.hbs +14 -0
- package/addon/components/extension-modal-title.js +20 -0
- package/addon/components/extension-monetize-form.hbs +56 -0
- package/addon/components/extension-monetize-form.js +7 -0
- package/addon/components/extension-pending-publish-viewer.hbs +52 -0
- package/addon/components/extension-pending-publish-viewer.js +37 -0
- package/addon/components/extension-reviewer-control.hbs +68 -0
- package/addon/components/extension-reviewer-control.js +68 -0
- package/addon/components/modals/confirm-extension-purchase.hbs +5 -0
- package/addon/components/modals/confirm-extension-purchase.js +3 -0
- package/addon/components/modals/extension-details.hbs +69 -0
- package/addon/components/modals/extension-details.js +33 -0
- package/addon/components/modals/extension-purchase-form.hbs +5 -0
- package/addon/components/modals/extension-purchase-form.js +3 -0
- package/addon/components/modals/extension-uninstall.hbs +25 -0
- package/addon/components/modals/extension-uninstall.js +11 -0
- package/addon/components/modals/select-extension-bundle.hbs +43 -0
- package/addon/components/modals/select-extension-bundle.js +31 -0
- package/addon/components/progress-bar.hbs +12 -0
- package/addon/components/progress-bar.js +8 -0
- package/addon/controllers/application.js +6 -0
- package/addon/controllers/developers/analytics.js +26 -0
- package/addon/controllers/developers/extensions/edit/bundles.js +70 -0
- package/addon/controllers/developers/extensions/edit/index.js +3 -0
- package/addon/controllers/developers/extensions/edit/monetize.js +7 -0
- package/addon/controllers/developers/extensions/edit.js +107 -0
- package/addon/controllers/developers/extensions/index.js +3 -0
- package/addon/controllers/developers/extensions/new.js +32 -0
- package/addon/controllers/developers/payments/index.js +39 -0
- package/addon/controllers/developers/payments/onboard.js +67 -0
- package/addon/controllers/explore/category.js +22 -0
- package/addon/controllers/explore/index.js +15 -0
- package/addon/controllers/installed.js +86 -0
- package/addon/controllers/purchased.js +18 -0
- package/addon/engine.js +44 -0
- package/addon/models/registry-extension-bundle.js +62 -0
- package/addon/models/registry-extension.js +215 -0
- package/addon/routes/application.js +12 -0
- package/addon/routes/developers/analytics.js +10 -0
- package/addon/routes/developers/credentials.js +3 -0
- package/addon/routes/developers/extensions/edit/bundles.js +21 -0
- package/addon/routes/developers/extensions/edit/details.js +3 -0
- package/addon/routes/developers/extensions/edit/index.js +3 -0
- package/addon/routes/developers/extensions/edit/monetize.js +3 -0
- package/addon/routes/developers/extensions/edit.js +18 -0
- package/addon/routes/developers/extensions/index.js +10 -0
- package/addon/routes/developers/extensions/new.js +3 -0
- package/addon/routes/developers/extensions.js +3 -0
- package/addon/routes/developers/payments/index.js +26 -0
- package/addon/routes/developers/payments/onboard.js +21 -0
- package/addon/routes/developers/payments.js +3 -0
- package/addon/routes/developers.js +3 -0
- package/addon/routes/explore/category.js +27 -0
- package/addon/routes/explore/index.js +17 -0
- package/addon/routes/explore.js +3 -0
- package/addon/routes/installed.js +10 -0
- package/addon/routes/purchased.js +10 -0
- package/addon/routes.js +28 -0
- package/addon/serializers/registry-extension-bundle.js +15 -0
- package/addon/serializers/registry-extension.js +21 -0
- package/addon/services/stripe.js +83 -0
- package/addon/styles/registry-bridge-engine.css +142 -0
- package/addon/templates/application.hbs +26 -0
- package/addon/templates/developers/analytics.hbs +83 -0
- package/addon/templates/developers/credentials.hbs +1 -0
- package/addon/templates/developers/extensions/edit/bundles.hbs +71 -0
- package/addon/templates/developers/extensions/edit/details.hbs +16 -0
- package/addon/templates/developers/extensions/edit/index.hbs +1 -0
- package/addon/templates/developers/extensions/edit/monetize.hbs +3 -0
- package/addon/templates/developers/extensions/edit.hbs +48 -0
- package/addon/templates/developers/extensions/index.hbs +27 -0
- package/addon/templates/developers/extensions/new.hbs +39 -0
- package/addon/templates/developers/extensions.hbs +1 -0
- package/addon/templates/developers/payments/index.hbs +33 -0
- package/addon/templates/developers/payments/onboard.hbs +48 -0
- package/addon/templates/developers/payments.hbs +1 -0
- package/addon/templates/developers.hbs +1 -0
- package/addon/templates/explore/category.hbs +12 -0
- package/addon/templates/explore/index.hbs +12 -0
- package/addon/templates/explore.hbs +1 -0
- package/addon/templates/installed.hbs +32 -0
- package/addon/templates/purchased.hbs +34 -0
- package/app/adapters/registry-bridge.js +1 -0
- package/app/adapters/registry-extension-bundle.js +1 -0
- package/app/adapters/registry-extension.js +1 -0
- package/app/components/extension-card.js +1 -0
- package/app/components/extension-form.js +1 -0
- package/app/components/extension-modal-title.js +1 -0
- package/app/components/extension-monetize-form.js +1 -0
- package/app/components/extension-pending-publish-viewer.js +1 -0
- package/app/components/extension-reviewer-control.js +1 -0
- package/app/components/modals/confirm-extension-purchase.js +1 -0
- package/app/components/modals/extension-details.js +1 -0
- package/app/components/modals/extension-purchase-form.js +1 -0
- package/app/components/modals/extension-uninstall.js +1 -0
- package/app/components/modals/select-extension-bundle.js +1 -0
- package/app/components/progress-bar.js +1 -0
- package/app/controllers/application.js +1 -0
- package/app/controllers/developers/analytics.js +1 -0
- package/app/controllers/developers/extensions/edit/bundles.js +1 -0
- package/app/controllers/developers/extensions/edit/index.js +1 -0
- package/app/controllers/developers/extensions/edit/monetize.js +1 -0
- package/app/controllers/developers/extensions/edit.js +1 -0
- package/app/controllers/developers/extensions/index.js +1 -0
- package/app/controllers/developers/extensions/new.js +1 -0
- package/app/controllers/developers/payments/index.js +1 -0
- package/app/controllers/developers/payments/onboard.js +1 -0
- package/app/controllers/explore/category.js +1 -0
- package/app/controllers/explore/index.js +1 -0
- package/app/controllers/installed.js +1 -0
- package/app/controllers/purchased.js +1 -0
- package/app/models/registry-extension-bundle.js +1 -0
- package/app/models/registry-extension.js +1 -0
- package/app/routes/developers/analytics.js +1 -0
- package/app/routes/developers/credentials.js +1 -0
- package/app/routes/developers/extensions/edit/bundles.js +1 -0
- package/app/routes/developers/extensions/edit/details.js +1 -0
- package/app/routes/developers/extensions/edit/index.js +1 -0
- package/app/routes/developers/extensions/edit/monetize.js +1 -0
- package/app/routes/developers/extensions/edit.js +1 -0
- package/app/routes/developers/extensions/index.js +1 -0
- package/app/routes/developers/extensions/new.js +1 -0
- package/app/routes/developers/extensions.js +1 -0
- package/app/routes/developers/payments/index.js +1 -0
- package/app/routes/developers/payments/onboard.js +1 -0
- package/app/routes/developers/payments.js +1 -0
- package/app/routes/developers.js +1 -0
- package/app/routes/explore/category.js +1 -0
- package/app/routes/explore/index.js +1 -0
- package/app/routes/explore.js +1 -0
- package/app/routes/installed.js +1 -0
- package/app/routes/purchased.js +1 -0
- package/app/serializers/registry-extension-bundle.js +1 -0
- package/app/serializers/registry-extension.js +1 -0
- package/app/services/stripe.js +1 -0
- package/app/templates/developers/analytics.js +1 -0
- package/app/templates/developers/credentials.js +1 -0
- package/app/templates/developers/extensions/edit/bundles.js +1 -0
- package/app/templates/developers/extensions/edit/details.js +1 -0
- package/app/templates/developers/extensions/edit/index.js +1 -0
- package/app/templates/developers/extensions/edit/monetize.js +1 -0
- package/app/templates/developers/extensions/edit.js +1 -0
- package/app/templates/developers/extensions/index.js +1 -0
- package/app/templates/developers/extensions/new.js +1 -0
- package/app/templates/developers/extensions.js +1 -0
- package/app/templates/developers/payments/index.js +1 -0
- package/app/templates/developers/payments/onboard.js +1 -0
- package/app/templates/developers/payments.js +1 -0
- package/app/templates/developers.js +1 -0
- package/app/templates/explore/category.js +1 -0
- package/app/templates/explore/index.js +1 -0
- package/app/templates/explore.js +1 -0
- package/app/templates/installed.js +1 -0
- package/app/templates/purchased.js +1 -0
- package/composer.json +95 -0
- package/config/environment.js +28 -0
- package/extension.json +10 -0
- package/index.js +26 -0
- package/package.json +129 -0
- package/phpstan.neon.dist +8 -0
- package/phpunit.xml.dist +16 -0
- package/server/.gitattributes +14 -0
- package/server/config/registry-bridge.php +32 -0
- package/server/migrations/2024_03_19_060627_create_registry_users_table.php +42 -0
- package/server/migrations/2024_03_21_051614_create_registry_extensions_table.php +76 -0
- package/server/migrations/2024_03_25_044537_create_registry_extension_bundles_table.php +54 -0
- package/server/migrations/2024_03_29_072101_registry_extension_installs.php +35 -0
- package/server/migrations/2024_07_16_155000_create_registry_extension_purchases.php +41 -0
- package/server/seeders/ExtensionsCategorySeeder.php +359 -0
- package/server/src/Console/Commands/Initialize.php +35 -0
- package/server/src/Console/Commands/PostInstallExtension.php +84 -0
- package/server/src/Exceptions/InstallFailedException.php +21 -0
- package/server/src/Expansions/CategoryExpansion.php +30 -0
- package/server/src/Http/Controllers/Internal/v1/ExtensionInstallerController.php +153 -0
- package/server/src/Http/Controllers/Internal/v1/RegistryAuthController.php +230 -0
- package/server/src/Http/Controllers/Internal/v1/RegistryController.php +54 -0
- package/server/src/Http/Controllers/Internal/v1/RegistryExtensionBundleController.php +112 -0
- package/server/src/Http/Controllers/Internal/v1/RegistryExtensionController.php +257 -0
- package/server/src/Http/Controllers/Internal/v1/RegistryPaymentsController.php +227 -0
- package/server/src/Http/Controllers/RegistryBridgeController.php +13 -0
- package/server/src/Http/Filter/RegistryExtensionFilter.php +80 -0
- package/server/src/Http/Requests/AddRegistryUserRequest.php +47 -0
- package/server/src/Http/Requests/AuthenticateRegistryUserRequest.php +47 -0
- package/server/src/Http/Requests/CreateRegistryExtensionBundleRequest.php +42 -0
- package/server/src/Http/Requests/CreateRegistryExtensionRequest.php +31 -0
- package/server/src/Http/Requests/InstallExtensionRequest.php +30 -0
- package/server/src/Http/Requests/RegistryAuthRequest.php +46 -0
- package/server/src/Http/Requests/RegistryExtensionActionRequest.php +30 -0
- package/server/src/Http/Resources/RegistryUser.php +40 -0
- package/server/src/Models/RegistryExtension.php +656 -0
- package/server/src/Models/RegistryExtensionBundle.php +1015 -0
- package/server/src/Models/RegistryExtensionInstall.php +76 -0
- package/server/src/Models/RegistryExtensionPurchase.php +87 -0
- package/server/src/Models/RegistryUser.php +140 -0
- package/server/src/Providers/RegistryBridgeServiceProvider.php +117 -0
- package/server/src/Support/Bridge.php +53 -0
- package/server/src/Support/Utils.php +19 -0
- package/server/src/routes.php +58 -0
- package/server/tests/Feature.php +5 -0
- package/translations/en-us.yaml +119 -0
- package/tsconfig.declarations.json +10 -0
|
@@ -0,0 +1,1015 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\RegistryBridge\Models;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\Casts\Json;
|
|
6
|
+
use Fleetbase\Models\Company;
|
|
7
|
+
use Fleetbase\Models\File;
|
|
8
|
+
use Fleetbase\Models\Model;
|
|
9
|
+
use Fleetbase\Models\User;
|
|
10
|
+
use Fleetbase\RegistryBridge\Exceptions\InstallFailedException;
|
|
11
|
+
use Fleetbase\Support\SocketCluster\SocketClusterService;
|
|
12
|
+
use Fleetbase\Support\Utils;
|
|
13
|
+
use Fleetbase\Traits\HasApiModelBehavior;
|
|
14
|
+
use Fleetbase\Traits\HasMetaAttributes;
|
|
15
|
+
use Fleetbase\Traits\HasPublicId;
|
|
16
|
+
use Fleetbase\Traits\HasUuid;
|
|
17
|
+
use Fleetbase\Traits\Searchable;
|
|
18
|
+
use Illuminate\Support\Facades\Storage;
|
|
19
|
+
use Illuminate\Support\Str;
|
|
20
|
+
use stdClass;
|
|
21
|
+
use Symfony\Component\Process\Process;
|
|
22
|
+
|
|
23
|
+
class RegistryExtensionBundle extends Model
|
|
24
|
+
{
|
|
25
|
+
use HasUuid;
|
|
26
|
+
use HasPublicId;
|
|
27
|
+
use HasMetaAttributes;
|
|
28
|
+
use HasApiModelBehavior;
|
|
29
|
+
use Searchable;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The database table used by the model.
|
|
33
|
+
*
|
|
34
|
+
* @var string
|
|
35
|
+
*/
|
|
36
|
+
protected $table = 'registry_extension_bundles';
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The type of public Id to generate.
|
|
40
|
+
*
|
|
41
|
+
* @var string
|
|
42
|
+
*/
|
|
43
|
+
protected $publicIdType = 'bundle';
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The attributes that are mass assignable.
|
|
47
|
+
*/
|
|
48
|
+
protected $fillable = [
|
|
49
|
+
'uuid',
|
|
50
|
+
'company_uuid',
|
|
51
|
+
'created_by_uuid',
|
|
52
|
+
'extension_uuid',
|
|
53
|
+
'bundle_uuid',
|
|
54
|
+
'bundle_id',
|
|
55
|
+
'bundle_number',
|
|
56
|
+
'version',
|
|
57
|
+
'meta',
|
|
58
|
+
'status',
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The attributes that should be cast to native types.
|
|
63
|
+
*/
|
|
64
|
+
protected $casts = [
|
|
65
|
+
'meta' => Json::class,
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Dynamic attributes that are appended to object.
|
|
70
|
+
*
|
|
71
|
+
* @var array
|
|
72
|
+
*/
|
|
73
|
+
protected $appends = [
|
|
74
|
+
'bundle_filename',
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Relations that should be loaded with model.
|
|
79
|
+
*
|
|
80
|
+
* @var array
|
|
81
|
+
*/
|
|
82
|
+
protected $with = [];
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Relations that should not be loaded.
|
|
86
|
+
*
|
|
87
|
+
* @var array
|
|
88
|
+
*/
|
|
89
|
+
protected $without = ['company', 'createdBy', 'extension'];
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Searchable columns.
|
|
93
|
+
*
|
|
94
|
+
* @var array
|
|
95
|
+
*/
|
|
96
|
+
protected $searchableColumns = ['name'];
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* The "booting" method of the model.
|
|
100
|
+
*
|
|
101
|
+
* This method is called on the model boot and sets up
|
|
102
|
+
* event listeners, such as creating a unique bundle ID
|
|
103
|
+
* when a new model instance is being created.
|
|
104
|
+
*/
|
|
105
|
+
protected static function boot()
|
|
106
|
+
{
|
|
107
|
+
parent::boot();
|
|
108
|
+
|
|
109
|
+
static::creating(function ($model) {
|
|
110
|
+
$model->bundle_id = self::generateUniqueBundleId();
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
|
116
|
+
*/
|
|
117
|
+
public function company()
|
|
118
|
+
{
|
|
119
|
+
return $this->belongsTo(Company::class, 'company_uuid', 'uuid');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
|
124
|
+
*/
|
|
125
|
+
public function createdBy()
|
|
126
|
+
{
|
|
127
|
+
return $this->belongsTo(User::class, 'created_by_uuid', 'uuid');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
|
132
|
+
*/
|
|
133
|
+
public function extension()
|
|
134
|
+
{
|
|
135
|
+
return $this->belongsTo(RegistryExtension::class, 'extension_uuid', 'uuid');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
|
140
|
+
*/
|
|
141
|
+
public function bundle()
|
|
142
|
+
{
|
|
143
|
+
return $this->belongsTo(File::class, 'bundle_uuid', 'uuid');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Get the bundle original filename.
|
|
148
|
+
*
|
|
149
|
+
* @return string
|
|
150
|
+
*/
|
|
151
|
+
public function getBundleFilenameAttribute()
|
|
152
|
+
{
|
|
153
|
+
if ($this->bundle instanceof File) {
|
|
154
|
+
return $this->bundle->original_filename;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return data_get($this, 'bundle.original_filename');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Generates a unique bundle ID.
|
|
162
|
+
*
|
|
163
|
+
* This static method constructs a unique bundle identifier (ID) by appending a random string to a fixed prefix.
|
|
164
|
+
* The prefix used is 'EXTBNDL', and the total length of the generated ID is 14 characters, including the prefix.
|
|
165
|
+
* The function ensures uniqueness by checking the generated ID against existing IDs in the database and
|
|
166
|
+
* regenerating if a duplicate is found. The characters used for the random string are upper and lower case
|
|
167
|
+
* alphabets and numerals. The final bundle ID is returned in upper case.
|
|
168
|
+
*
|
|
169
|
+
* @return string a unique, uppercase bundle ID
|
|
170
|
+
*/
|
|
171
|
+
public static function generateUniqueBundleId()
|
|
172
|
+
{
|
|
173
|
+
do {
|
|
174
|
+
$prefix = 'EXTBNDL';
|
|
175
|
+
$remainingLength = 14 - strlen($prefix);
|
|
176
|
+
$characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
177
|
+
$result = '';
|
|
178
|
+
|
|
179
|
+
for ($i = 0; $i < $remainingLength; $i++) {
|
|
180
|
+
$result .= $characters[rand(0, strlen($characters) - 1)];
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
$bundleId = strtoupper($prefix . $result);
|
|
184
|
+
} while (self::where('bundle_id', $bundleId)->exists());
|
|
185
|
+
|
|
186
|
+
return $bundleId;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Extracts specified file(s) from a zipped bundle and returns their contents.
|
|
191
|
+
*
|
|
192
|
+
* This method downloads a file indicated by the File model, unzips it, and looks for
|
|
193
|
+
* specified filename(s) within the unzipped contents. The contents of each found file
|
|
194
|
+
* are returned as a stdClass object, with each property corresponding to a filename.
|
|
195
|
+
* If 'parse_json' option is true (default), file contents are decoded as JSON.
|
|
196
|
+
* Temporary files are cleaned up after extraction.
|
|
197
|
+
*
|
|
198
|
+
* @param File $bundle the File model instance representing the zipped bundle
|
|
199
|
+
* @param string|array $filenames a single filename or an array of filenames to extract from the bundle
|
|
200
|
+
* @param array $options Additional options, e.g., ['parse_json' => false] to get raw file contents.
|
|
201
|
+
*
|
|
202
|
+
* @return \stdClass|null an object containing the contents of each extracted file, or null if files are not found
|
|
203
|
+
*/
|
|
204
|
+
protected static function extractBundleFile(File $bundle, $filenames = 'extension.json', $options = []): ?\stdClass
|
|
205
|
+
{
|
|
206
|
+
$shouldParseJson = data_get($options, 'parse_json', true);
|
|
207
|
+
$tempDir = sys_get_temp_dir() . '/' . str_replace(['.', ','], '_', uniqid('fleetbase_zip_', true));
|
|
208
|
+
mkdir($tempDir);
|
|
209
|
+
|
|
210
|
+
// Download the file to a local temporary directory
|
|
211
|
+
$tempFilePath = $tempDir . '/' . basename($bundle->path);
|
|
212
|
+
$contents = Storage::disk($bundle->disk)->get($bundle->path);
|
|
213
|
+
file_put_contents($tempFilePath, $contents);
|
|
214
|
+
|
|
215
|
+
// Extract file paths
|
|
216
|
+
$extractedFilePaths = static::_extractAndFindFile($tempFilePath, $tempDir, $filenames);
|
|
217
|
+
$result = new \stdClass();
|
|
218
|
+
foreach ($extractedFilePaths as $filename => $path) {
|
|
219
|
+
if (file_exists($path)) {
|
|
220
|
+
$fileContents = file_get_contents($path);
|
|
221
|
+
if ($shouldParseJson) {
|
|
222
|
+
$result->$filename = json_decode($fileContents);
|
|
223
|
+
} else {
|
|
224
|
+
$result->$filename = $fileContents;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Cleanup: Delete the temporary directory
|
|
230
|
+
try {
|
|
231
|
+
array_map('unlink', glob("$tempDir/*.*"));
|
|
232
|
+
} catch (\Throwable $e) {
|
|
233
|
+
// Probably a directory ...
|
|
234
|
+
}
|
|
235
|
+
Utils::deleteDirectory($tempDir);
|
|
236
|
+
|
|
237
|
+
return $result;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Extracts and finds the path(s) of specified file(s) within a zipped archive.
|
|
242
|
+
*
|
|
243
|
+
* Opens the specified ZIP archive and extracts it to a temporary directory. It then
|
|
244
|
+
* searches for the given file(s) in both the root and the first valid subdirectory of
|
|
245
|
+
* the unzipped content. It returns an associative array of file paths, indexed by filenames.
|
|
246
|
+
* Invalid directories such as '__MACOSX', '.', '..', etc., are excluded from the search.
|
|
247
|
+
*
|
|
248
|
+
* @param string $zipFilePath path to the ZIP archive file
|
|
249
|
+
* @param string $tempDir temporary directory where the ZIP file is extracted
|
|
250
|
+
* @param string|array $targetFiles a single filename or an array of filenames to search for within the archive
|
|
251
|
+
*
|
|
252
|
+
* @return array associative array of paths for the requested files within the archive
|
|
253
|
+
*/
|
|
254
|
+
private static function _extractAndFindFile($zipFilePath, $tempDir, $targetFiles)
|
|
255
|
+
{
|
|
256
|
+
$paths = [];
|
|
257
|
+
$zip = new \ZipArchive();
|
|
258
|
+
|
|
259
|
+
if ($zip->open($zipFilePath) === true) {
|
|
260
|
+
$zip->extractTo($tempDir);
|
|
261
|
+
$zip->close();
|
|
262
|
+
|
|
263
|
+
foreach ((array) $targetFiles as $targetFile) {
|
|
264
|
+
// Direct check in the tempDir
|
|
265
|
+
$directPath = $tempDir . '/' . $targetFile;
|
|
266
|
+
if (file_exists($directPath)) {
|
|
267
|
+
$paths[$targetFile] = $directPath;
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Check in the first subdirectory
|
|
272
|
+
$files = scandir($tempDir);
|
|
273
|
+
foreach ($files as $file) {
|
|
274
|
+
$invalidDirectories = ['__MACOSX', '.', '..', 'DS_Store', '.DS_Store', '.idea', '.vscode'];
|
|
275
|
+
if (!Str::startsWith($file, ['.', '_']) && !in_array($file, $invalidDirectories) && is_dir($tempDir . '/' . $file)) {
|
|
276
|
+
$nestedPath = $tempDir . '/' . $file . '/' . $targetFile;
|
|
277
|
+
if (file_exists($nestedPath)) {
|
|
278
|
+
$paths[$targetFile] = $nestedPath;
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return $paths;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Extracts multiple configuration files from the bundle.
|
|
291
|
+
*
|
|
292
|
+
* This method leverages the extractBundleFile function to extract an array
|
|
293
|
+
* of specified configuration files ('extension.json', 'composer.json', 'package.json')
|
|
294
|
+
* from the bundle. It returns an object containing the contents of each file,
|
|
295
|
+
* where each property name corresponds to a filename. If a specified file is not found
|
|
296
|
+
* in the bundle, its corresponding property will be absent in the returned object.
|
|
297
|
+
* The method is useful for retrieving multiple configuration files in a single operation.
|
|
298
|
+
*
|
|
299
|
+
* @return \stdClass|null An object containing the contents of each extracted file.
|
|
300
|
+
* Properties of the object correspond to the filenames.
|
|
301
|
+
* Returns null if the bundle property is not an instance of File.
|
|
302
|
+
*/
|
|
303
|
+
public static function extractBundleData(File $bundleFile): ?\stdClass
|
|
304
|
+
{
|
|
305
|
+
return static::extractBundleFile($bundleFile, ['extension.json', 'composer.json', 'package.json']);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Extracts multiple configuration files from the bundle.
|
|
310
|
+
*
|
|
311
|
+
* This method leverages the extractBundleFile function to extract an array
|
|
312
|
+
* of specified configuration files ('extension.json', 'composer.json', 'package.json')
|
|
313
|
+
* from the bundle. It returns an object containing the contents of each file,
|
|
314
|
+
* where each property name corresponds to a filename. If a specified file is not found
|
|
315
|
+
* in the bundle, its corresponding property will be absent in the returned object.
|
|
316
|
+
* The method is useful for retrieving multiple configuration files in a single operation.
|
|
317
|
+
*
|
|
318
|
+
* @return \stdClass|null An object containing the contents of each extracted file.
|
|
319
|
+
* Properties of the object correspond to the filenames.
|
|
320
|
+
* Returns null if the bundle property is not an instance of File.
|
|
321
|
+
*/
|
|
322
|
+
public function extractExtensionData()
|
|
323
|
+
{
|
|
324
|
+
if ($this->bundle instanceof File) {
|
|
325
|
+
return static::extractBundleData($this->bundle);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Extracts 'extension.json' from the bundle file.
|
|
331
|
+
*
|
|
332
|
+
* This method is a specific implementation of extractBundleFile for extracting
|
|
333
|
+
* 'extension.json'. It checks if the 'bundle' property is an instance of File
|
|
334
|
+
* and invokes the extraction process.
|
|
335
|
+
*
|
|
336
|
+
* @return \stdClass|null The decoded JSON object from 'extension.json', or null if not found.
|
|
337
|
+
*/
|
|
338
|
+
public function extractExtensionJson(): ?\stdClass
|
|
339
|
+
{
|
|
340
|
+
$filename = 'extension.json';
|
|
341
|
+
if ($this->bundle instanceof File) {
|
|
342
|
+
$data = static::extractBundleFile($this->bundle);
|
|
343
|
+
|
|
344
|
+
return $data[$filename];
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Extracts 'composer.json' from the bundle file.
|
|
350
|
+
*
|
|
351
|
+
* This method is a specific implementation of extractBundleFile for extracting
|
|
352
|
+
* 'composer.json'. It checks if the 'bundle' property is an instance of File
|
|
353
|
+
* and invokes the extraction process.
|
|
354
|
+
*
|
|
355
|
+
* @return \stdClass|null The decoded JSON object from 'composer.json', or null if not found.
|
|
356
|
+
*/
|
|
357
|
+
public function extractComposerJson(): ?\stdClass
|
|
358
|
+
{
|
|
359
|
+
$filename = 'composer.json';
|
|
360
|
+
if ($this->bundle instanceof File) {
|
|
361
|
+
$data = static::extractBundleFile($this->bundle, $filename);
|
|
362
|
+
|
|
363
|
+
return $data[$filename];
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Extracts 'package.json' from the bundle file.
|
|
369
|
+
*
|
|
370
|
+
* This method is a specific implementation of extractBundleFile for extracting
|
|
371
|
+
* 'package.json'. It checks if the 'bundle' property is an instance of File
|
|
372
|
+
* and invokes the extraction process.
|
|
373
|
+
*
|
|
374
|
+
* @return \stdClass|null The decoded JSON object from 'package.json', or null if not found.
|
|
375
|
+
*/
|
|
376
|
+
public function extractPackageJson(): ?\stdClass
|
|
377
|
+
{
|
|
378
|
+
$filename = 'package.json';
|
|
379
|
+
if ($this->bundle instanceof File) {
|
|
380
|
+
$data = static::extractBundleFile($this->bundle, $filename);
|
|
381
|
+
|
|
382
|
+
return $data[$filename];
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Installs a specific server package using Composer based on provided metadata.
|
|
388
|
+
*
|
|
389
|
+
* This method manages the installation of a server-side package specified in the composer.json metadata.
|
|
390
|
+
* It executes a Composer command to install the package, monitors the output for progress, and broadcasts
|
|
391
|
+
* these updates in real-time to a WebSocket channel.
|
|
392
|
+
*
|
|
393
|
+
* @throws InstallFailedException if the Composer package installation fails, with a user-friendly message
|
|
394
|
+
*/
|
|
395
|
+
public function installComposerPackage(): void
|
|
396
|
+
{
|
|
397
|
+
if (!is_array($this->meta) || !isset($this->meta['composer.json'])) {
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
$composerJson = $this->meta['composer.json'];
|
|
402
|
+
if (!$composerJson) {
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Prepare for install
|
|
407
|
+
$output = '';
|
|
408
|
+
$installChannel = implode('.', ['install', $this->company_uuid, $this->extension_uuid]);
|
|
409
|
+
$packageName = data_get($composerJson, 'name');
|
|
410
|
+
$version = data_get($composerJson, 'version');
|
|
411
|
+
$composerCommand = [
|
|
412
|
+
'composer',
|
|
413
|
+
'require',
|
|
414
|
+
$packageName . ($version === 'latest' ? '' : ':' . $version),
|
|
415
|
+
];
|
|
416
|
+
|
|
417
|
+
// Create process
|
|
418
|
+
$process = new Process($composerCommand);
|
|
419
|
+
$process->setWorkingDirectory('/fleetbase/api');
|
|
420
|
+
$process->setTimeout(3600 * 2);
|
|
421
|
+
|
|
422
|
+
// Run process
|
|
423
|
+
$process->run(function ($type, $buffer) use (&$output, $installChannel) {
|
|
424
|
+
$output .= $buffer;
|
|
425
|
+
$lines = explode("\n", $buffer);
|
|
426
|
+
foreach ($lines as $line) {
|
|
427
|
+
if (trim($line) === '') {
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
$progress = static::composerInstallOutputProgress($line);
|
|
431
|
+
if ($progress > 0) {
|
|
432
|
+
SocketClusterService::publish($installChannel, [
|
|
433
|
+
'process' => 'install',
|
|
434
|
+
'step' => 'server.install',
|
|
435
|
+
'progress' => $progress,
|
|
436
|
+
]);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
if (!$process->isSuccessful()) {
|
|
442
|
+
$friendlyMessage = static::composerOutputFriendly($output);
|
|
443
|
+
throw new InstallFailedException($friendlyMessage);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Uninstalls a specific server package using Composer based on provided metadata.
|
|
449
|
+
*
|
|
450
|
+
* This function handles the uninstallation of a server-side package as defined in the composer.json metadata.
|
|
451
|
+
* It runs a Composer remove command, processes the output to track uninstallation progress, and publishes
|
|
452
|
+
* these updates through a WebSocket channel.
|
|
453
|
+
*
|
|
454
|
+
* @throws InstallFailedException if the Composer package uninstallation fails, providing a user-friendly message
|
|
455
|
+
*/
|
|
456
|
+
public function uninstallComposerPackage(): void
|
|
457
|
+
{
|
|
458
|
+
if (!is_array($this->meta) || !isset($this->meta['composer.json'])) {
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
$composerJson = $this->meta['composer.json'];
|
|
463
|
+
if (!$composerJson) {
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Prepare for uninstall
|
|
468
|
+
$output = '';
|
|
469
|
+
$uninstallChannel = implode('.', ['uninstall', $this->company_uuid, $this->extension_uuid]);
|
|
470
|
+
$packageName = data_get($composerJson, 'name');
|
|
471
|
+
$composerCommand = [
|
|
472
|
+
'composer',
|
|
473
|
+
'remove',
|
|
474
|
+
$packageName,
|
|
475
|
+
];
|
|
476
|
+
|
|
477
|
+
// Create process
|
|
478
|
+
$process = new Process($composerCommand);
|
|
479
|
+
$process->setWorkingDirectory('/fleetbase/api');
|
|
480
|
+
$process->setTimeout(3600 * 2);
|
|
481
|
+
|
|
482
|
+
// Run process
|
|
483
|
+
$process->run(function ($type, $buffer) use (&$output, $uninstallChannel) {
|
|
484
|
+
$output .= $buffer;
|
|
485
|
+
$lines = explode("\n", $buffer);
|
|
486
|
+
foreach ($lines as $line) {
|
|
487
|
+
if (trim($line) === '') {
|
|
488
|
+
continue;
|
|
489
|
+
}
|
|
490
|
+
$progress = static::composerUninstallOutputProgress($line);
|
|
491
|
+
if ($progress > 0) {
|
|
492
|
+
SocketClusterService::publish($uninstallChannel, [
|
|
493
|
+
'process' => 'uninstall',
|
|
494
|
+
'step' => 'server.uninstall',
|
|
495
|
+
'progress' => $progress,
|
|
496
|
+
]);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
if (!$process->isSuccessful()) {
|
|
502
|
+
$friendlyMessage = static::composerOutputFriendly($output);
|
|
503
|
+
throw new InstallFailedException($friendlyMessage);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* Installs a specific engine package defined in the metadata using PNPM.
|
|
509
|
+
*
|
|
510
|
+
* This method is responsible for installing an engine package based on the package metadata provided.
|
|
511
|
+
* It creates and runs a PNPM installation process, monitors its output for progress, and sends real-time
|
|
512
|
+
* updates via a WebSocket channel.
|
|
513
|
+
*
|
|
514
|
+
* @throws \Exception if the engine installation process fails
|
|
515
|
+
*/
|
|
516
|
+
public function installEnginePackage(): void
|
|
517
|
+
{
|
|
518
|
+
if (!is_array($this->meta) || !isset($this->meta['package.json'])) {
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
$packageJson = $this->meta['package.json'];
|
|
523
|
+
if (!$packageJson) {
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// Prepare for install
|
|
528
|
+
$output = '';
|
|
529
|
+
$installChannel = implode('.', ['install', $this->company_uuid, $this->extension_uuid]);
|
|
530
|
+
$packageName = data_get($packageJson, 'name');
|
|
531
|
+
$version = data_get($packageJson, 'version');
|
|
532
|
+
$installCommand = [
|
|
533
|
+
'pnpm',
|
|
534
|
+
'add',
|
|
535
|
+
$packageName . ($version === 'latest' ? '' : '@' . $version),
|
|
536
|
+
];
|
|
537
|
+
|
|
538
|
+
// Create process
|
|
539
|
+
$process = new Process($installCommand);
|
|
540
|
+
$process->setWorkingDirectory(config('fleetbase.console.path'));
|
|
541
|
+
$process->setTimeout(3600 * 2);
|
|
542
|
+
|
|
543
|
+
// Run process
|
|
544
|
+
$process->run(function ($type, $buffer) use (&$output, $installChannel) {
|
|
545
|
+
$output .= $buffer;
|
|
546
|
+
$lines = explode("\n", $buffer);
|
|
547
|
+
foreach ($lines as $line) {
|
|
548
|
+
if (trim($line) === '') {
|
|
549
|
+
continue;
|
|
550
|
+
}
|
|
551
|
+
$progress = static::pnpmInstallOutputProgress($line);
|
|
552
|
+
if ($progress > 0) {
|
|
553
|
+
SocketClusterService::publish($installChannel, [
|
|
554
|
+
'process' => 'install',
|
|
555
|
+
'step' => 'engine.install',
|
|
556
|
+
'progress' => $progress,
|
|
557
|
+
]);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
if (!$process->isSuccessful()) {
|
|
563
|
+
throw new \Exception('Engine install failed!');
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Uninstalls a specific engine package defined in the metadata using PNPM.
|
|
569
|
+
*
|
|
570
|
+
* This function initiates the uninstallation of an engine package using PNPM, based on the metadata provided.
|
|
571
|
+
* It captures and interprets the output of the uninstall command to provide real-time progress updates through
|
|
572
|
+
* a WebSocket channel.
|
|
573
|
+
*
|
|
574
|
+
* @throws \Exception if the engine uninstallation process fails
|
|
575
|
+
*/
|
|
576
|
+
public function uninstallEnginePackage(): void
|
|
577
|
+
{
|
|
578
|
+
if (!is_array($this->meta) || !isset($this->meta['package.json'])) {
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
$packageJson = $this->meta['package.json'];
|
|
583
|
+
if (!$packageJson) {
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// Prepare for uninstall
|
|
588
|
+
$output = '';
|
|
589
|
+
$uninstallChannel = implode('.', ['uninstall', $this->company_uuid, $this->extension_uuid]);
|
|
590
|
+
$packageName = data_get($packageJson, 'name');
|
|
591
|
+
$installCommand = [
|
|
592
|
+
'pnpm',
|
|
593
|
+
'remove',
|
|
594
|
+
$packageName,
|
|
595
|
+
];
|
|
596
|
+
|
|
597
|
+
// Create process
|
|
598
|
+
$process = new Process($installCommand);
|
|
599
|
+
$process->setWorkingDirectory(config('fleetbase.console.path'));
|
|
600
|
+
$process->setTimeout(3600 * 2);
|
|
601
|
+
|
|
602
|
+
// Run process
|
|
603
|
+
$process->run(function ($type, $buffer) use (&$output, $uninstallChannel) {
|
|
604
|
+
$output .= $buffer;
|
|
605
|
+
$lines = explode("\n", $buffer);
|
|
606
|
+
foreach ($lines as $line) {
|
|
607
|
+
if (trim($line) === '') {
|
|
608
|
+
continue;
|
|
609
|
+
}
|
|
610
|
+
$progress = static::pnpmUninstallOutputProgress($line);
|
|
611
|
+
if ($progress > 0) {
|
|
612
|
+
SocketClusterService::publish($uninstallChannel, [
|
|
613
|
+
'process' => 'uninstall',
|
|
614
|
+
'step' => 'engine.uninstall',
|
|
615
|
+
'progress' => $progress,
|
|
616
|
+
]);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
if (!$process->isSuccessful()) {
|
|
622
|
+
throw new \Exception('Engine uninstall failed!');
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/**
|
|
627
|
+
* Initiates and manages the build process of the Fleetbase console using PNPM.
|
|
628
|
+
*
|
|
629
|
+
* This method prepares and executes the build process for the Fleetbase console. It monitors the build
|
|
630
|
+
* output to provide real-time updates via a WebSocket channel. It captures and interprets the output to
|
|
631
|
+
* estimate the progress, which is then published to a designated channel for frontend display.
|
|
632
|
+
*
|
|
633
|
+
* @throws \Exception if the build process fails
|
|
634
|
+
*/
|
|
635
|
+
public function buildConsole()
|
|
636
|
+
{
|
|
637
|
+
// Prepare to build/rebuild console
|
|
638
|
+
$output = '';
|
|
639
|
+
$buildChannel = implode('.', ['install', $this->company_uuid, $this->extension_uuid]);
|
|
640
|
+
$buildCommand = [
|
|
641
|
+
'pnpm',
|
|
642
|
+
'build',
|
|
643
|
+
'--environment',
|
|
644
|
+
config('app.env'),
|
|
645
|
+
];
|
|
646
|
+
|
|
647
|
+
// Create process
|
|
648
|
+
$process = new Process($buildCommand);
|
|
649
|
+
$process->setWorkingDirectory(config('fleetbase.console.path'));
|
|
650
|
+
$process->setTimeout(3600 * 2);
|
|
651
|
+
|
|
652
|
+
// Run process
|
|
653
|
+
$process->run(function ($type, $buffer) use (&$output, $buildChannel) {
|
|
654
|
+
$output .= $buffer;
|
|
655
|
+
$lines = explode("\n", $buffer);
|
|
656
|
+
foreach ($lines as $line) {
|
|
657
|
+
if (trim($line) === '') {
|
|
658
|
+
continue;
|
|
659
|
+
}
|
|
660
|
+
$progress = static::pnpmBuildOutputProgress($line);
|
|
661
|
+
if ($progress > 0) {
|
|
662
|
+
SocketClusterService::publish($buildChannel, [
|
|
663
|
+
'process' => 'build',
|
|
664
|
+
'step' => 'console.build',
|
|
665
|
+
'progress' => $progress,
|
|
666
|
+
]);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
if (!$process->isSuccessful()) {
|
|
672
|
+
throw new \Exception('Console build failed!');
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
public function runInstallerProgress(): void
|
|
677
|
+
{
|
|
678
|
+
$channel = implode('.', ['install', $this->company_uuid, $this->extension_uuid]);
|
|
679
|
+
$steps = ['api.install', 'engine.install', 'console.build'];
|
|
680
|
+
|
|
681
|
+
foreach ($steps as $step) {
|
|
682
|
+
$run = range(1, 100);
|
|
683
|
+
foreach ($run as $progress) {
|
|
684
|
+
SocketClusterService::publish($channel, [
|
|
685
|
+
'process' => 'install',
|
|
686
|
+
'step' => $step,
|
|
687
|
+
'progress' => $progress,
|
|
688
|
+
]);
|
|
689
|
+
|
|
690
|
+
// minimal latency
|
|
691
|
+
usleep(500 * rand(2, 4));
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
public function runUninstallerProgress(): void
|
|
697
|
+
{
|
|
698
|
+
$channel = implode('.', ['uninstall', $this->company_uuid, $this->extension_uuid]);
|
|
699
|
+
$steps = ['api.uninstall', 'engine.uninstall', 'console.build'];
|
|
700
|
+
|
|
701
|
+
foreach ($steps as $step) {
|
|
702
|
+
$run = range(1, 100);
|
|
703
|
+
foreach ($run as $progress) {
|
|
704
|
+
SocketClusterService::publish($channel, [
|
|
705
|
+
'process' => 'uninstall',
|
|
706
|
+
'step' => $step,
|
|
707
|
+
'progress' => $progress,
|
|
708
|
+
]);
|
|
709
|
+
|
|
710
|
+
// minimal latency
|
|
711
|
+
usleep(500 * rand(2, 4));
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
/**
|
|
717
|
+
* Parses Composer output to provide a user-friendly message.
|
|
718
|
+
*
|
|
719
|
+
* This method checks the Composer output for specific keywords or phrases
|
|
720
|
+
* and returns a simplified, more understandable message suitable for end-users.
|
|
721
|
+
*
|
|
722
|
+
* @param string $output the raw output from Composer
|
|
723
|
+
*
|
|
724
|
+
* @return string a user-friendly interpretation of the output
|
|
725
|
+
*/
|
|
726
|
+
public static function composerOutputFriendly($output): string
|
|
727
|
+
{
|
|
728
|
+
// Check for successful install/update
|
|
729
|
+
if (strpos($output, 'Generating optimized autoload files') !== false) {
|
|
730
|
+
return 'Installation successful. Packages have been updated.';
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
// Check for 'Nothing to install, update or remove'
|
|
734
|
+
if (strpos($output, 'Nothing to install, update or remove') !== false) {
|
|
735
|
+
return 'No changes made. Everything is already up-to-date.';
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
// Check for 'Package is abandoned'
|
|
739
|
+
if (preg_match_all('/Package (.+) is abandoned/', $output, $matches)) {
|
|
740
|
+
$abandonedPackages = implode(', ', $matches[1]);
|
|
741
|
+
|
|
742
|
+
return "Warning: The following packages are abandoned and should be replaced: $abandonedPackages.";
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// Check for dependency issues
|
|
746
|
+
if (preg_match('/Problem (\d+)/', $output, $matches)) {
|
|
747
|
+
return 'Unable to install due to dependency compatibility issues.';
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// If no known patterns are found, return a generic message or the original output
|
|
751
|
+
return 'An error occurred during installation. Please check the log for details.';
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
/**
|
|
755
|
+
* Estimates the progress of a Composer installation process.
|
|
756
|
+
*
|
|
757
|
+
* This method interprets the Composer output to estimate the progress of the installation.
|
|
758
|
+
* It assigns a progress percentage based on identified keywords or phrases in the output.
|
|
759
|
+
*
|
|
760
|
+
* @param string $output the raw output from Composer
|
|
761
|
+
*
|
|
762
|
+
* @return int an estimated progress percentage
|
|
763
|
+
*/
|
|
764
|
+
public static function composerInstallOutputProgress($output): int
|
|
765
|
+
{
|
|
766
|
+
// Trim the output to remove unnecessary whitespace
|
|
767
|
+
$output = trim($output);
|
|
768
|
+
|
|
769
|
+
// Initial phase, updating composer.json and resolving dependencies
|
|
770
|
+
if (strpos($output, 'Running composer update') !== false) {
|
|
771
|
+
return 10;
|
|
772
|
+
}
|
|
773
|
+
// Dependencies are being updated
|
|
774
|
+
elseif (strpos($output, 'Loading composer repositories with package information') !== false) {
|
|
775
|
+
return 20;
|
|
776
|
+
}
|
|
777
|
+
// Dependencies updating step
|
|
778
|
+
elseif (strpos($output, 'Updating dependencies') !== false) {
|
|
779
|
+
return 30;
|
|
780
|
+
}
|
|
781
|
+
// Lock file is being written
|
|
782
|
+
elseif (strpos($output, 'Lock file operations') !== false) {
|
|
783
|
+
return 40;
|
|
784
|
+
}
|
|
785
|
+
// Lock file writing in progress
|
|
786
|
+
elseif (strpos($output, 'Writing lock file') !== false) {
|
|
787
|
+
return 50;
|
|
788
|
+
}
|
|
789
|
+
// Installing dependencies
|
|
790
|
+
elseif (strpos($output, 'Installing dependencies from lock file') !== false) {
|
|
791
|
+
return 60;
|
|
792
|
+
}
|
|
793
|
+
// Package operations, installation begins
|
|
794
|
+
elseif (strpos($output, 'Package operations:') !== false) {
|
|
795
|
+
return 70;
|
|
796
|
+
}
|
|
797
|
+
// Downloading a specific package
|
|
798
|
+
elseif (strpos($output, '- Downloading') !== false) {
|
|
799
|
+
return 75;
|
|
800
|
+
}
|
|
801
|
+
// Extracting archive for a package
|
|
802
|
+
elseif (strpos($output, '- Installing') !== false) {
|
|
803
|
+
return 80;
|
|
804
|
+
}
|
|
805
|
+
// Autoload files are generated, nearing completion
|
|
806
|
+
elseif (strpos($output, 'Generating optimized autoload files') !== false) {
|
|
807
|
+
return 90;
|
|
808
|
+
}
|
|
809
|
+
// Final steps, package discovery and publishing assets
|
|
810
|
+
elseif (strpos($output, 'postAutoloadDump') !== false
|
|
811
|
+
|| strpos($output, '@php artisan package:discover --ansi') !== false
|
|
812
|
+
|| strpos($output, '@php artisan vendor:publish') !== false) {
|
|
813
|
+
return 95;
|
|
814
|
+
}
|
|
815
|
+
// Completion messages
|
|
816
|
+
elseif (strpos($output, 'No security vulnerability advisories found') !== false) {
|
|
817
|
+
return 100;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
// Default progress if no known phrases are matched
|
|
821
|
+
return 0;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
/**
|
|
825
|
+
* Parses the output of the composer uninstall process to determine progress.
|
|
826
|
+
*
|
|
827
|
+
* This function analyzes the output of the `composer remove` command and returns a progress percentage
|
|
828
|
+
* based on the stage of the process. It helps in providing real-time progress updates during the
|
|
829
|
+
* uninstallation of a Composer package.
|
|
830
|
+
*
|
|
831
|
+
* @param string $output the output from the composer uninstall process
|
|
832
|
+
*
|
|
833
|
+
* @return int the progress percentage
|
|
834
|
+
*/
|
|
835
|
+
public static function composerUninstallOutputProgress($output): int
|
|
836
|
+
{
|
|
837
|
+
// Trim the output to remove unnecessary whitespace
|
|
838
|
+
$output = trim($output);
|
|
839
|
+
|
|
840
|
+
// Initial phase, updating composer.json and resolving dependencies
|
|
841
|
+
if (strpos($output, 'Running composer update') !== false) {
|
|
842
|
+
return 10;
|
|
843
|
+
}
|
|
844
|
+
// Loading repositories
|
|
845
|
+
elseif (strpos($output, 'Loading composer repositories with package information') !== false) {
|
|
846
|
+
return 20;
|
|
847
|
+
}
|
|
848
|
+
// Dependencies are being updated
|
|
849
|
+
elseif (strpos($output, 'Updating dependencies') !== false) {
|
|
850
|
+
return 30;
|
|
851
|
+
}
|
|
852
|
+
// Lock file operations are defined
|
|
853
|
+
elseif (strpos($output, 'Lock file operations') !== false) {
|
|
854
|
+
return 40;
|
|
855
|
+
}
|
|
856
|
+
// Lock file writing in progress
|
|
857
|
+
elseif (strpos($output, 'Writing lock file') !== false) {
|
|
858
|
+
return 50;
|
|
859
|
+
}
|
|
860
|
+
// Installing dependencies from lock file
|
|
861
|
+
elseif (strpos($output, 'Installing dependencies from lock file') !== false) {
|
|
862
|
+
return 60;
|
|
863
|
+
}
|
|
864
|
+
// Package operations, removal begins
|
|
865
|
+
elseif (strpos($output, 'Package operations:') !== false) {
|
|
866
|
+
return 70;
|
|
867
|
+
}
|
|
868
|
+
// Removing a specific package
|
|
869
|
+
elseif (strpos($output, '- Removing') !== false) {
|
|
870
|
+
return 80;
|
|
871
|
+
}
|
|
872
|
+
// Generating autoload files, nearing completion
|
|
873
|
+
elseif (strpos($output, 'Generating optimized autoload files') !== false) {
|
|
874
|
+
return 90;
|
|
875
|
+
}
|
|
876
|
+
// Final steps, package discovery and publishing assets
|
|
877
|
+
elseif (strpos($output, 'postAutoloadDump') !== false
|
|
878
|
+
|| strpos($output, '@php artisan package:discover --ansi') !== false
|
|
879
|
+
|| strpos($output, '@php artisan vendor:publish') !== false) {
|
|
880
|
+
return 95;
|
|
881
|
+
}
|
|
882
|
+
// Completion messages
|
|
883
|
+
elseif (strpos($output, 'No security vulnerability advisories found') !== false) {
|
|
884
|
+
return 100;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
// Default progress if no known phrases are matched
|
|
888
|
+
return 0;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
/**
|
|
892
|
+
* Estimates the progress of a PNPM installation process.
|
|
893
|
+
*
|
|
894
|
+
* This method interprets the PNPM output to estimate the progress of the installation based on specific keywords and phrases found in the output. It assigns a progress percentage based on identified phases of the install process, such as resolving dependencies and writing lock files.
|
|
895
|
+
*
|
|
896
|
+
* @param string $output the raw output from the PNPM install command
|
|
897
|
+
*
|
|
898
|
+
* @return int An estimated progress percentage. This is an integer between 0 and 100 where 0 means just started, and 100 means complete.
|
|
899
|
+
*/
|
|
900
|
+
public static function pnpmInstallOutputProgress($output): int
|
|
901
|
+
{
|
|
902
|
+
$output = trim($output);
|
|
903
|
+
|
|
904
|
+
// Check if the output indicates starting phase
|
|
905
|
+
if (strpos($output, 'Packages: +1') !== false) {
|
|
906
|
+
return 10;
|
|
907
|
+
}
|
|
908
|
+
// Check for resolved packages progress
|
|
909
|
+
elseif (preg_match('/Progress: resolved (\d+),/', $output, $matches)) {
|
|
910
|
+
$resolved = (int) $matches[1];
|
|
911
|
+
if ($resolved < 500) {
|
|
912
|
+
return 20;
|
|
913
|
+
} elseif ($resolved < 1000) {
|
|
914
|
+
return 40;
|
|
915
|
+
} elseif ($resolved < 1500) {
|
|
916
|
+
return 60;
|
|
917
|
+
} else {
|
|
918
|
+
return 80;
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
// Check for final steps
|
|
922
|
+
elseif (strpos($output, 'dependencies:') !== false) {
|
|
923
|
+
return 90;
|
|
924
|
+
}
|
|
925
|
+
// Completion message
|
|
926
|
+
elseif (strpos($output, 'Done in') !== false) {
|
|
927
|
+
return 100;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
return 0;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
/**
|
|
934
|
+
* Estimates the progress of a PNPM uninstallation process.
|
|
935
|
+
*
|
|
936
|
+
* This method analyzes the output from the PNPM uninstall command to estimate the progress of the package removal. It uses specific indicators within the output to assign a progress percentage, such as the stages of resolving dependencies, removing packages, and cleaning up.
|
|
937
|
+
*
|
|
938
|
+
* @param string $output the raw output from the PNPM uninstall command
|
|
939
|
+
*
|
|
940
|
+
* @return int An estimated progress percentage. This value is an integer between 0 and 100, where 0 indicates the beginning of the uninstall process, and 100 indicates completion.
|
|
941
|
+
*/
|
|
942
|
+
public static function pnpmUninstallOutputProgress($output): int
|
|
943
|
+
{
|
|
944
|
+
$output = trim($output);
|
|
945
|
+
|
|
946
|
+
// Check for the uninstall initiation
|
|
947
|
+
if (strpos($output, 'Packages: -1') !== false) {
|
|
948
|
+
return 10;
|
|
949
|
+
}
|
|
950
|
+
// Check for resolved packages progress
|
|
951
|
+
elseif (preg_match('/Progress: resolved (\d+),/', $output, $matches)) {
|
|
952
|
+
$resolved = (int) $matches[1];
|
|
953
|
+
if ($resolved < 500) {
|
|
954
|
+
return 20;
|
|
955
|
+
} elseif ($resolved < 1000) {
|
|
956
|
+
return 40;
|
|
957
|
+
} elseif ($resolved < 1500) {
|
|
958
|
+
return 60;
|
|
959
|
+
} else {
|
|
960
|
+
return 80;
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
// Check for final steps
|
|
964
|
+
elseif (strpos($output, 'dependencies:') !== false) {
|
|
965
|
+
return 90;
|
|
966
|
+
}
|
|
967
|
+
// Completion message
|
|
968
|
+
elseif (strpos($output, 'Done in') !== false) {
|
|
969
|
+
return 100;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
return 0;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
/**
|
|
976
|
+
* Estimates the progress of a PNPM Ember build process.
|
|
977
|
+
*
|
|
978
|
+
* This function interprets the output from the 'pnpm build' command, specifically tailored for an Ember project build,
|
|
979
|
+
* to estimate the build's progress. The function identifies specific phases of the build process and assigns a
|
|
980
|
+
* progress percentage based on these phases.
|
|
981
|
+
*
|
|
982
|
+
* @param string $output the raw output from the PNPM build command
|
|
983
|
+
*
|
|
984
|
+
* @return int An estimated progress percentage. This is an integer between 0 and 100, where 0 means just started,
|
|
985
|
+
* and 100 indicates the build is complete.
|
|
986
|
+
*/
|
|
987
|
+
public static function pnpmBuildOutputProgress($output): int
|
|
988
|
+
{
|
|
989
|
+
$output = trim($output);
|
|
990
|
+
|
|
991
|
+
// Prebuild and setup phase
|
|
992
|
+
if (strpos($output, 'node prebuild.js') !== false) {
|
|
993
|
+
return 10;
|
|
994
|
+
}
|
|
995
|
+
// Initial building phase
|
|
996
|
+
elseif (strpos($output, '- Building') !== false) {
|
|
997
|
+
return 20;
|
|
998
|
+
}
|
|
999
|
+
// Middle build process, handling various transformations and optimizations
|
|
1000
|
+
elseif (strpos($output, 'postcss-is-pseudo-class') !== false) {
|
|
1001
|
+
return 50;
|
|
1002
|
+
}
|
|
1003
|
+
// Cleanup phase before completion
|
|
1004
|
+
elseif (strpos($output, 'cleaning up...') !== false) {
|
|
1005
|
+
return 80;
|
|
1006
|
+
}
|
|
1007
|
+
// Build completion message
|
|
1008
|
+
elseif (strpos($output, 'Built project successfully') !== false) {
|
|
1009
|
+
return 100;
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
// Default progress if no known phrases are matched
|
|
1013
|
+
return 0;
|
|
1014
|
+
}
|
|
1015
|
+
}
|