@@ -13,10 +13,10 @@
{{/each}}
- Extensions
- Analytics
- Payments
- Credentials
+ Extensions
+ Analytics
+ Payments
+ Credentials
diff --git a/composer.json b/composer.json
index 7814eaf..ae10766 100644
--- a/composer.json
+++ b/composer.json
@@ -7,7 +7,7 @@
"fleetbase-registry-bridge",
"fleetbase"
],
- "license": "MIT",
+ "license": "AGPL-3.0-or-later",
"authors": [
{
"name": "Fleetbase Pte Ltd.",
@@ -20,7 +20,7 @@
],
"require": {
"php": "^8.0",
- "fleetbase/core-api": "^1.4.25",
+ "fleetbase/core-api": "^1.4.26",
"php-http/guzzle7-adapter": "^1.0",
"psr/http-factory-implementation": "*"
},
diff --git a/extension.json b/extension.json
index ad1af24..1d596bd 100644
--- a/extension.json
+++ b/extension.json
@@ -3,7 +3,7 @@
"version": "0.0.1",
"description": "Internal Bridge between Fleetbase API and Extensions Registry",
"repository": "https://github.com/fleetbase/registry-bridge",
- "license": "MIT",
+ "license": "AGPL-3.0-or-later",
"author": "Fleetbase Pte Ltd ",
"engine": "package.json",
"api": "composer.json"
diff --git a/package.json b/package.json
index 1691c20..cc15f88 100644
--- a/package.json
+++ b/package.json
@@ -2,6 +2,9 @@
"name": "@fleetbase/registry-bridge-engine",
"version": "0.0.1",
"description": "Internal Bridge between Fleetbase API and Extensions Registry",
+ "fleetbase": {
+ "route": "extensions"
+ },
"keywords": [
"fleetbase-extension",
"fleetbase-registry-bridge",
@@ -10,7 +13,7 @@
"ember-engine"
],
"repository": "https://github.com/fleetbase/registry-bridge",
- "license": "MIT",
+ "license": "AGPL-3.0-or-later",
"author": "Fleetbase Pte Ltd ",
"directories": {
"app": "app",
@@ -36,7 +39,7 @@
},
"dependencies": {
"@fleetbase/ember-core": "^0.2.11",
- "@fleetbase/ember-ui": "^0.2.16",
+ "@fleetbase/ember-ui": "^0.2.17",
"@babel/core": "^7.23.2",
"@fortawesome/ember-fontawesome": "^0.4.1",
"@fortawesome/fontawesome-svg-core": "^6.4.0",
diff --git a/server/src/Console/Commands/PostInstallExtension.php b/server/src/Console/Commands/PostInstallExtension.php
new file mode 100644
index 0000000..d88d576
--- /dev/null
+++ b/server/src/Console/Commands/PostInstallExtension.php
@@ -0,0 +1,87 @@
+argument('extensionId');
+ $extension = RegistryExtension::disableCache()->where('public_id', $extensionId)->first();
+
+ if ($extension) {
+ $this->postInstallExtension($extension);
+ $this->info('Post install commands executed successfully.');
+ } else {
+ $this->error('Extension not found.');
+ }
+
+ return 0;
+ }
+
+ /**
+ * Post install extension commands.
+ *
+ * @param \Fleetbase\RegistryBridge\Models\RegistryExtension $extension
+ * @return void
+ */
+ public function postInstallExtension(RegistryExtension $extension): void
+ {
+ if (isset($extension->currentBundle)) {
+ $composerJson = $extension->currentBundle->meta['composer.json'];
+ if ($composerJson) {
+ $extensionPath = base_path('vendor/' . $composerJson['name']);
+
+ $commands = [
+ 'rm -rf /fleetbase/.pnpm-store',
+ 'rm -rf node_modules',
+ 'pnpm install',
+ 'pnpm build'
+ ];
+
+ $this->info('Running post install for: ' . $extension->name);
+ $this->info('Extension install path: ' . $extensionPath);
+ foreach ($commands as $command) {
+ $this->info('Running extension post install command: `' . $command . '`');
+ $process = Process::fromShellCommandline($command, $extensionPath);
+ $process->run(function ($type, $buffer) {
+ if (Process::ERR === $type) {
+ $this->error($buffer);
+ } else {
+ $this->info($buffer);
+ }
+ });
+
+ // Check if the process was successful
+ if (!$process->isSuccessful()) {
+ throw new ProcessFailedException($process);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/server/src/Http/Controllers/Internal/v1/ExtensionInstallerController.php b/server/src/Http/Controllers/Internal/v1/ExtensionInstallerController.php
index c65f226..f87c494 100644
--- a/server/src/Http/Controllers/Internal/v1/ExtensionInstallerController.php
+++ b/server/src/Http/Controllers/Internal/v1/ExtensionInstallerController.php
@@ -85,6 +85,7 @@ public function install(InstallExtensionRequest $request)
*/
public function uninstall(InstallExtensionRequest $request)
{
+ set_time_limit(120);
$extension = RegistryExtension::where('public_id', $request->input('extension'))->first();
$uninstalled = false;
diff --git a/server/src/Http/Controllers/Internal/v1/RegistryController.php b/server/src/Http/Controllers/Internal/v1/RegistryController.php
index 3082e54..1a55a6f 100644
--- a/server/src/Http/Controllers/Internal/v1/RegistryController.php
+++ b/server/src/Http/Controllers/Internal/v1/RegistryController.php
@@ -5,6 +5,7 @@
use Fleetbase\Http\Controllers\Controller;
use Fleetbase\Http\Resources\Category as CategoryResource;
use Fleetbase\Models\Category;
+use Fleetbase\RegistryBridge\Models\RegistryExtension;
class RegistryController extends Controller
{
@@ -16,4 +17,157 @@ public function categories()
return CategoryResource::collection($categories);
}
+
+ public function loadInstalledEngines()
+ {
+ $installedExtensions = RegistryExtension::disableCache()->whereHas('installs', function ($query) {
+ $query->where('company_uuid', session('company'));
+ })->get()->mapWithKeys(function ($extension) {
+ return [$extension->public_id => $extension->currentBundle->meta['package.json'] ?? []];
+ });
+
+ return response()->json($installedExtensions);
+ }
+
+ public function loadEngineManifest(string $extensionId)
+ {
+ $extension = RegistryExtension::where('public_id', $extensionId)->first();
+ if (!$extension) {
+ return response()->json([]);
+ }
+
+ if (isset($extension->currentBundle)) {
+ $composerJson = $extension->currentBundle->meta['composer.json'];
+ $packageJson = $extension->currentBundle->meta['package.json'] ?? null;
+ if ($composerJson) {
+ $packageDistPath = base_path('vendor/' .$composerJson['name'] . '/dist');
+ $assetManifestPath = $packageDistPath . '/asset-manifest.json';
+ $assetManifest = json_decode(file_get_contents($assetManifestPath));
+
+ // Get bundles
+ $bundles = $assetManifest->bundles;
+
+ // Create consumable assets map
+ $output = [];
+ foreach ($bundles as $manifest) {
+ if (isset($manifest->assets) && is_array($manifest->assets)) {
+ foreach ($manifest->assets as $asset) {
+ $name = null;
+ $type = $asset->type;
+ $content = file_get_contents($packageDistPath . $asset->uri);
+ $syntax = $type === 'js' ? 'application/javascript' : 'text/css';
+ if ($type === 'js' && static::isES6Module($content)) {
+ $syntax = 'module';
+ }
+
+ // Check for config environments which should be injected as meta tags
+ if (static::containsConfigEnvironment($content)) {
+ // might need to make as additional $output[] element
+ $type = 'meta';
+ $name = static::extractModuleName($content);
+ $content = urlencode(static::extractConfigEnvironment($content));
+ $syntax = null;
+ }
+
+ $output[] = [
+ 'name' => $name,
+ 'type' => $type,
+ 'content' => $content,
+ 'syntax' => $syntax
+ ];
+ }
+ }
+ }
+
+ return response()->json($output);
+ }
+ }
+
+ return response()->json([]);
+ }
+
+ private static function isES6Module(string $contents)
+ {
+ // Check for module-specific keywords like import or export
+ if (preg_match('/\bimport\s|\bexport\s/', $contents)) {
+ return true; // It should be a module
+ }
+
+ return false; // It should not be a module
+ }
+
+ private static function containsConfigEnvironment(string $contents): bool
+ {
+ // This regex checks if the string starts with define and includes a module name ending with "/config/environment"
+ return preg_match('/^define\("@[^"]+\/config\/environment",/', $contents);
+ }
+
+
+ private static function extractConfigEnvironment(string $contents)
+ {
+ // This regex extracts the JSON-like object from the define function's return statement
+ if (preg_match('/define\("@[^"]+\/config\/environment",.*?\(function\(\)\{return\{default:(\{.*?\})\}\}\)\)/s', $contents, $matches)) {
+ // Capture the object after "default:"
+ $configString = $matches[1];
+ // Correctly balance the curly braces to form a valid JSON object
+ $configString = self::balanceCurlyBraces($configString);
+ $configString = self::fixJsonFormat($configString);
+ // Convert the balanced string to an array
+ return $configString;
+ }
+
+ return null;
+ }
+
+ private static function fixJsonFormat($jsObject) {
+ // Add quotes around keys where they may be missing
+ $pattern = '/(?<=[{,])(\s*)([a-zA-Z0-9_]+)(\s*):/m';
+ $replacement = '$1"$2"$3:';
+ $jsonLikeString = preg_replace($pattern, $replacement, $jsObject);
+
+ // Replace single quotes with double quotes around string values
+ $pattern = "/:(\s*)'([^']*)'/";
+ $replacement = ':$1"$2"';
+ $jsonLikeString = preg_replace($pattern, $replacement, $jsonLikeString);
+
+ // Ensure booleans and null are properly formatted
+ $jsonLikeString = preg_replace('/:(\s*)(true|false|null)/i', ':$1$2', $jsonLikeString);
+
+ // Convert the inner single quotes encapsulated by double quotes back to single quotes
+ $pattern = '/"(.*?)"/s';
+ $callback = function ($matches) {
+ return '"' . str_replace('"', "'", $matches[1]) . '"';
+ };
+ $jsonLikeString = preg_replace_callback($pattern, $callback, $jsonLikeString);
+
+ return $jsonLikeString;
+ }
+
+ private static function balanceCurlyBraces($string)
+ {
+ // This function aims to balance curly braces if they are not properly closed in the extracted string
+ $count = 0;
+ $length = strlen($string);
+ for ($i = 0; $i < $length; $i++) {
+ if ($string[$i] === '{') {
+ $count++;
+ } elseif ($string[$i] === '}') {
+ $count--;
+ if ($count === 0) {
+ // Cut the string up to the last matching closing brace
+ return substr($string, 0, $i + 1);
+ }
+ }
+ }
+ return $string; // Return as is if braces are balanced
+ }
+
+ private static function extractModuleName(string $contents)
+ {
+ // This regex matches the module name part of the define function call
+ if (preg_match('/define\("([^"]+)"/', $contents, $matches)) {
+ return $matches[1];
+ }
+ return null;
+ }
}
diff --git a/server/src/Models/RegistryExtensionBundle.php b/server/src/Models/RegistryExtensionBundle.php
index e92cfa3..6ca5095 100644
--- a/server/src/Models/RegistryExtensionBundle.php
+++ b/server/src/Models/RegistryExtensionBundle.php
@@ -708,7 +708,7 @@ public function installEnginePackage(): void
dd($output);
if (!$process->isSuccessful()) {
- throw new Exception('Engine install failed!');
+ throw new \Exception('Engine install failed!');
// $friendlyMessage = static::composerOutputFriendly($output);
// throw new InstallFailedException($friendlyMessage);
}
diff --git a/server/src/Providers/RegistryBridgeServiceProvider.php b/server/src/Providers/RegistryBridgeServiceProvider.php
index a78c8cf..4b525e8 100644
--- a/server/src/Providers/RegistryBridgeServiceProvider.php
+++ b/server/src/Providers/RegistryBridgeServiceProvider.php
@@ -20,6 +20,15 @@ class RegistryBridgeServiceProvider extends CoreServiceProvider
*/
public $observers = [];
+ /**
+ * The console commands registered with the service provider.
+ *
+ * @var array
+ */
+ public $commands = [
+ \Fleetbase\RegistryBridge\Console\Commands\PostInstallExtension::class
+ ];
+
/**
* The middleware groups registered with the service provider.
*
@@ -61,6 +70,7 @@ public function register()
*/
public function boot()
{
+ $this->registerCommands();
$this->registerMiddleware();
$this->registerExpansionsFrom(__DIR__ . '/../Expansions');
$this->loadRoutesFrom(__DIR__ . '/../routes.php');
diff --git a/server/src/routes.php b/server/src/routes.php
index a5a750f..93c9fd6 100644
--- a/server/src/routes.php
+++ b/server/src/routes.php
@@ -22,6 +22,8 @@ function ($router) {
*/
$router->group(['prefix' => config('internals.api.routing.internal_prefix', 'v1'), 'namespace' => 'Internal\v1'], function ($router) {
$router->get('categories', 'RegistryController@categories');
+ $router->get('load-installed-engines', 'RegistryController@loadInstalledEngines');
+ $router->get('load-engine-manifest/{extensionId}', 'RegistryController@loadEngineManifest');
$router->group(['prefix' => 'installer'], function ($router) {
$router->post('install', 'ExtensionInstallerController@install');
$router->post('uninstall', 'ExtensionInstallerController@uninstall');