From b4dd9fc81e9aef69913d9afac678bf3511e8e621 Mon Sep 17 00:00:00 2001 From: Eser DENIZ Date: Wed, 1 Oct 2025 09:58:26 +0200 Subject: [PATCH 1/4] feat: migrate to Bifrost --- config/nativephp-internal.php | 10 +- config/nativephp.php | 2 +- .../Commands/Bifrost/ClearBundleCommand.php | 68 +++ .../Bifrost/DownloadBundleCommand.php | 240 ++++++++++ src/Electron/Commands/Bifrost/InitCommand.php | 153 +++++++ .../Commands/Bifrost/LoginCommand.php | 121 +++++ .../Commands/Bifrost/LogoutCommand.php | 47 ++ src/Electron/Commands/BuildCommand.php | 4 +- src/Electron/Commands/BundleCommand.php | 412 ------------------ src/Electron/ElectronServiceProvider.php | 12 +- src/Electron/Traits/CleansEnvFile.php | 47 -- src/Electron/Traits/HandlesBifrost.php | 123 ++++++ src/Electron/Traits/HandlesZephpyr.php | 64 --- src/Electron/Traits/ManagesEnvFile.php | 120 +++++ 14 files changed, 890 insertions(+), 533 deletions(-) create mode 100644 src/Electron/Commands/Bifrost/ClearBundleCommand.php create mode 100644 src/Electron/Commands/Bifrost/DownloadBundleCommand.php create mode 100644 src/Electron/Commands/Bifrost/InitCommand.php create mode 100644 src/Electron/Commands/Bifrost/LoginCommand.php create mode 100644 src/Electron/Commands/Bifrost/LogoutCommand.php delete mode 100644 src/Electron/Commands/BundleCommand.php delete mode 100644 src/Electron/Traits/CleansEnvFile.php create mode 100644 src/Electron/Traits/HandlesBifrost.php delete mode 100644 src/Electron/Traits/HandlesZephpyr.php create mode 100644 src/Electron/Traits/ManagesEnvFile.php diff --git a/config/nativephp-internal.php b/config/nativephp-internal.php index 35a53fd1..cb23ca0f 100644 --- a/config/nativephp-internal.php +++ b/config/nativephp-internal.php @@ -31,12 +31,12 @@ 'api_url' => env('NATIVEPHP_API_URL', 'http://localhost:4000/api/'), /** - * Configuration for the Zephpyr API. + * Configuration for the Bifrost API. */ - 'zephpyr' => [ - 'host' => env('ZEPHPYR_HOST', 'https://zephpyr.com'), - 'token' => env('ZEPHPYR_TOKEN'), - 'key' => env('ZEPHPYR_KEY'), + 'bifrost' => [ + 'host' => env('BIFROST_HOST', 'https://bifrost.nativephp.com'), + 'token' => env('BIFROST_TOKEN'), + 'project' => env('BIFROST_PROJECT'), ], /** diff --git a/config/nativephp.php b/config/nativephp.php index 91938bd9..430f5a60 100644 --- a/config/nativephp.php +++ b/config/nativephp.php @@ -64,7 +64,7 @@ 'GITHUB_*', 'DO_SPACES_*', '*_SECRET', - 'ZEPHPYR_*', + 'BIFROST_*', 'NATIVEPHP_UPDATER_PATH', 'NATIVEPHP_APPLE_ID', 'NATIVEPHP_APPLE_ID_PASS', diff --git a/src/Electron/Commands/Bifrost/ClearBundleCommand.php b/src/Electron/Commands/Bifrost/ClearBundleCommand.php new file mode 100644 index 00000000..00652419 --- /dev/null +++ b/src/Electron/Commands/Bifrost/ClearBundleCommand.php @@ -0,0 +1,68 @@ +warn('No bundle or signature files found to clear.'); + + return static::SUCCESS; + } + + $cleared = []; + $failed = []; + + if ($bundleExists) { + if (unlink($bundlePath)) { + $cleared[] = 'bundle'; + } else { + $failed[] = 'bundle'; + } + } + + if ($signatureExists) { + if (unlink($signaturePath)) { + $cleared[] = 'GPG signature'; + } else { + $failed[] = 'GPG signature'; + } + } + + if (! empty($cleared)) { + $clearedText = implode(' and ', $cleared); + $this->info("Cleared {$clearedText} successfully!"); + $this->line('Note: Building in this state would be unsecure without a valid bundle.'); + } + + if (! empty($failed)) { + $failedText = implode(' and ', $failed); + $this->error("Failed to remove {$failedText}."); + + return static::FAILURE; + } + + return static::SUCCESS; + } +} diff --git a/src/Electron/Commands/Bifrost/DownloadBundleCommand.php b/src/Electron/Commands/Bifrost/DownloadBundleCommand.php new file mode 100644 index 00000000..f13b7aab --- /dev/null +++ b/src/Electron/Commands/Bifrost/DownloadBundleCommand.php @@ -0,0 +1,240 @@ +validateAuthAndGetUser(); + } catch (Exception $e) { + $this->error($e->getMessage()); + $this->line('Run: php artisan bifrost:login'); + + return static::FAILURE; + } + + if (! $this->checkForBifrostProject()) { + return static::FAILURE; + } + + intro('Fetching latest desktop bundle...'); + + try { + $projectId = config('nativephp-internal.bifrost.project'); + $response = $this->makeApiRequest('GET', "api/v1/projects/{$projectId}/builds/latest-desktop-bundle"); + + if ($response->failed()) { + $this->handleApiError($response); + + return static::FAILURE; + } + + $buildData = $response->json(); + + if (! isset($buildData['data']['download_url'])) { + $this->error('Bundle download URL not found in response.'); + + return static::FAILURE; + } + + $this->displayBundleInfo($buildData['data']); + + $bundlePath = $this->prepareBundlePath(); + + if (! $this->downloadBundle($buildData['data']['download_url'], $bundlePath)) { + return static::FAILURE; + } + + // Download GPG signature if available + $signaturePath = null; + if (isset($buildData['data']['signature_url'])) { + $signaturePath = $bundlePath.'.asc'; + if (! $this->downloadSignature($buildData['data']['signature_url'], $signaturePath)) { + $this->warn('Failed to download GPG signature file.'); + } + } + + $this->displaySuccessInfo($bundlePath, $signaturePath); + + return static::SUCCESS; + } catch (Exception $e) { + $this->error('Failed to download bundle: '.$e->getMessage()); + + return static::FAILURE; + } + } + + private function displayBundleInfo(array $buildData): void + { + $this->line(''); + $this->info('Bundle Details:'); + $this->line('Version: '.($buildData['version'] ?? 'Unknown')); + $this->line('Git Commit: '.substr($buildData['git_commit'] ?? '', 0, 8)); + $this->line('Git Branch: '.($buildData['git_branch'] ?? 'Unknown')); + $this->line('Created: '.($buildData['created_at'] ?? 'Unknown')); + } + + private function prepareBundlePath(): string + { + $buildDir = base_path('build'); + if (! is_dir($buildDir)) { + mkdir($buildDir, 0755, true); + } + + return base_path('build/__nativephp_app_bundle'); + } + + private function downloadBundle(string $downloadUrl, string $bundlePath): bool + { + $this->line(''); + $this->info('Downloading bundle...'); + + $progressBar = $this->output->createProgressBar(); + $progressBar->setFormat(' %current%/%max% [%bar%] %percent:3s%% %message%'); + + try { + $downloadResponse = Http::withOptions([ + 'sink' => $bundlePath, + 'progress' => function ($downloadTotal, $downloadedBytes) use ($progressBar) { + if ($downloadTotal > 0) { + $progressBar->setMaxSteps($downloadTotal); + $progressBar->setProgress($downloadedBytes); + $progressBar->setMessage(sprintf('%.1f MB', $downloadedBytes / 1024 / 1024)); + } + }, + ])->get($downloadUrl); + + $progressBar->finish(); + $this->line(''); + + if ($downloadResponse->failed()) { + $this->error('Failed to download bundle.'); + $this->cleanupFailedDownload($bundlePath); + + return false; + } + + return true; + } catch (Exception $e) { + $progressBar->finish(); + $this->line(''); + $this->error('Download failed: '.$e->getMessage()); + $this->cleanupFailedDownload($bundlePath); + + return false; + } + } + + private function cleanupFailedDownload(string $bundlePath): void + { + if (file_exists($bundlePath)) { + unlink($bundlePath); + $this->line('Cleaned up partial download.'); + } + } + + private function downloadSignature(string $signatureUrl, string $signaturePath): bool + { + $this->line(''); + $this->info('Downloading GPG signature...'); + + try { + $downloadResponse = Http::get($signatureUrl); + + if ($downloadResponse->failed()) { + return false; + } + + file_put_contents($signaturePath, $downloadResponse->body()); + + return true; + } catch (Exception $e) { + return false; + } + } + + private function displaySuccessInfo(string $bundlePath, ?string $signaturePath = null): void + { + $this->line(''); + $this->info('Bundle downloaded successfully!'); + $this->line('Location: '.$bundlePath); + + if (file_exists($bundlePath)) { + $sizeInMB = number_format(filesize($bundlePath) / 1024 / 1024, 2); + $this->line("Size: {$sizeInMB} MB"); + } + + if ($signaturePath && file_exists($signaturePath)) { + $this->line('GPG Signature: '.$signaturePath); + $this->line(''); + $this->info('To verify the bundle integrity:'); + $this->line('gpg --verify '.basename($signaturePath).' '.basename($bundlePath)); + } + } + + private function handleApiError($response): void + { + $status = $response->status(); + $data = $response->json(); + + switch ($status) { + case 404: + $this->line(''); + $this->error('No desktop builds found for this project.'); + $this->line(''); + $teamSlug = $this->getCurrentTeamSlug(); + $projectId = config('nativephp-internal.bifrost.project'); + $baseUrl = rtrim($this->baseUrl(), '/'); + + if ($teamSlug && $projectId) { + $this->info("Create a build at: {$baseUrl}/{$teamSlug}/desktop/projects/{$projectId}"); + } else { + $this->info("Visit the dashboard: {$baseUrl}/dashboard"); + } + break; + + case 503: + $retryAfter = intval($response->header('Retry-After')); + $diff = now()->addSeconds($retryAfter); + $diffMessage = $retryAfter <= 60 ? 'a minute' : $diff->diffForHumans(syntax: CarbonInterface::DIFF_ABSOLUTE); + $this->line(''); + $this->warn('Build is still in progress.'); + $this->line("Please try again in {$diffMessage}."); + break; + + case 500: + $this->line(''); + $this->error('Latest build has failed or was cancelled.'); + if (isset($data['build_id'])) { + $this->line('Build ID: '.$data['build_id']); + } + if (isset($data['status'])) { + $this->line('Status: '.$data['status']); + } + break; + + default: + $this->line(''); + $this->error('Failed to fetch bundle: '.($data['message'] ?? 'Unknown error')); + } + } +} diff --git a/src/Electron/Commands/Bifrost/InitCommand.php b/src/Electron/Commands/Bifrost/InitCommand.php new file mode 100644 index 00000000..db68111f --- /dev/null +++ b/src/Electron/Commands/Bifrost/InitCommand.php @@ -0,0 +1,153 @@ +validateAuthAndGetUser(); + } catch (Exception $e) { + $this->error($e->getMessage()); + $this->line('Run: php artisan bifrost:login'); + + return static::FAILURE; + } + + intro('Fetching your desktop projects...'); + + try { + $response = $this->makeApiRequest('GET', 'api/v1/projects'); + + if ($response->failed()) { + $this->handleApiError($response); + + return static::FAILURE; + } + + $responseData = $response->json(); + + if (! isset($responseData['data']) || ! is_array($responseData['data'])) { + $this->error('Invalid API response format.'); + + return static::FAILURE; + } + + $projects = collect($responseData['data']) + ->filter(fn ($project) => isset($project['type']) && $project['type'] === 'desktop') + ->values() + ->toArray(); + + if (empty($projects)) { + $this->displayNoProjectsMessage($user); + + return static::FAILURE; + } + + $choices = collect($projects)->mapWithKeys(function ($project) { + $name = $project['name'] ?? 'Unknown'; + $repo = $project['repo'] ?? 'No repository'; + + return [$project['uuid'] => "{$name} - {$repo}"]; + })->toArray(); + + $selectedProjectUuid = select( + label: 'Select a desktop project', + options: $choices, + required: true + ); + + $selectedProject = collect($projects)->firstWhere('uuid', $selectedProjectUuid); + + if (! $selectedProject) { + $this->error('Selected project not found.'); + + return static::FAILURE; + } + + // Store project UUID in .env file + $this->updateEnvFile('BIFROST_PROJECT', $selectedProjectUuid); + + $this->displaySuccessMessage($selectedProject); + + return static::SUCCESS; + } catch (Exception $e) { + $this->error('Failed to fetch projects: '.$e->getMessage()); + + return static::FAILURE; + } + } + + private function displayNoProjectsMessage(array $user): void + { + $this->line(''); + $this->warn('No desktop projects found.'); + $this->line(''); + + $teamSlug = $user['current_team']['slug'] ?? null; + $baseUrl = rtrim($this->baseUrl(), '/'); + + if ($teamSlug) { + $this->info("Create a desktop project at: {$baseUrl}/{$teamSlug}/onboarding/project/desktop"); + } else { + $this->info("Create a desktop project at: {$baseUrl}/onboarding/project/desktop"); + } + } + + private function displaySuccessMessage(array $project): void + { + $this->line(''); + $this->info('Project selected successfully!'); + $this->line('Project: '.($project['name'] ?? 'Unknown')); + $this->line('Repository: '.($project['repo'] ?? 'Unknown')); + $this->line(''); + $this->line('You can now run "php artisan bifrost:download-bundle" to download the latest bundle.'); + } + + private function handleApiError($response): void + { + $status = $response->status(); + $baseUrl = rtrim($this->baseUrl(), '/'); + + switch ($status) { + case 403: + $this->line(''); + $this->error('No teams found. Please create a team first.'); + $this->line(''); + $this->info("Create a team at: {$baseUrl}/onboarding/team"); + break; + + case 422: + $this->line(''); + $this->error('Team setup incomplete or subscription required.'); + $this->line(''); + $this->info("Complete setup at: {$baseUrl}/dashboard"); + break; + + default: + $this->line(''); + $this->error('Failed to fetch projects: '.$response->json('message', 'Unknown error')); + $this->line(''); + $this->info("Visit the dashboard: {$baseUrl}/dashboard"); + } + } +} diff --git a/src/Electron/Commands/Bifrost/LoginCommand.php b/src/Electron/Commands/Bifrost/LoginCommand.php new file mode 100644 index 00000000..dabacefa --- /dev/null +++ b/src/Electron/Commands/Bifrost/LoginCommand.php @@ -0,0 +1,121 @@ + match (true) { + ! filter_var($value, FILTER_VALIDATE_EMAIL) => 'Please enter a valid email address.', + default => null + } + ); + + $password = password( + label: 'Password', + required: true + ); + + $this->line(''); + $this->info('Logging in...'); + + try { + $response = Http::acceptJson() + ->post($this->baseUrl().'api/v1/auth/login', [ + 'email' => $email, + 'password' => $password, + ]); + + if ($response->failed()) { + $this->line(''); + $this->error('Login failed: '.$response->json('message', 'Invalid credentials')); + + return static::FAILURE; + } + + $responseData = $response->json(); + + if (! isset($responseData['data']['token'])) { + $this->line(''); + $this->error('Login response missing token. Please try again.'); + + return static::FAILURE; + } + + $token = $responseData['data']['token']; + + // Store token in .env file + $this->updateEnvFile('BIFROST_TOKEN', $token); + + // Fetch and display user info + $this->displayUserInfo($token); + + $this->line(''); + $this->line('Next step: Run "php artisan bifrost:init" to select a project.'); + + return static::SUCCESS; + } catch (Exception $e) { + $this->line(''); + $this->error('Network error: '.$e->getMessage()); + + return static::FAILURE; + } + } + + private function displayUserInfo(string $token): void + { + try { + $userResponse = Http::acceptJson() + ->withToken($token) + ->get($this->baseUrl().'api/v1/auth/user'); + + if ($userResponse->successful()) { + $userData = $userResponse->json(); + + if (isset($userData['data'])) { + $user = $userData['data']; + $this->line(''); + $this->info('Successfully logged in!'); + $this->line('User: '.($user['name'] ?? 'Unknown').' ('.($user['email'] ?? 'Unknown').')'); + + if (isset($user['current_team']['name'])) { + $this->line('Team: '.$user['current_team']['name']); + } + + return; + } + } + } catch (Exception $e) { + // Silently fail user info display - login was successful + } + + $this->line(''); + $this->info('Successfully logged in!'); + } +} diff --git a/src/Electron/Commands/Bifrost/LogoutCommand.php b/src/Electron/Commands/Bifrost/LogoutCommand.php new file mode 100644 index 00000000..696e636b --- /dev/null +++ b/src/Electron/Commands/Bifrost/LogoutCommand.php @@ -0,0 +1,47 @@ +checkForBifrostToken()) { + $this->warn('You are not logged in.'); + + return static::SUCCESS; + } + + intro('Logging out from Bifrost...'); + + // Attempt to logout on the server + Http::acceptJson() + ->withToken(config('nativephp-internal.bifrost.token')) + ->post($this->baseUrl().'api/v1/auth/logout'); + + // Remove token and project from .env file regardless of server response + $this->removeFromEnvFile(['BIFROST_TOKEN', 'BIFROST_PROJECT']); + + $this->info('Successfully logged out!'); + $this->line('Your API token and project selection have been removed.'); + + return static::SUCCESS; + } +} diff --git a/src/Electron/Commands/BuildCommand.php b/src/Electron/Commands/BuildCommand.php index 1a4e6edf..af01b978 100644 --- a/src/Electron/Commands/BuildCommand.php +++ b/src/Electron/Commands/BuildCommand.php @@ -7,12 +7,12 @@ use Illuminate\Support\Str; use Native\Electron\ElectronServiceProvider; use Native\Electron\Facades\Updater; -use Native\Electron\Traits\CleansEnvFile; use Native\Electron\Traits\CopiesBundleToBuildDirectory; use Native\Electron\Traits\CopiesCertificateAuthority; use Native\Electron\Traits\HasPreAndPostProcessing; use Native\Electron\Traits\InstallsAppIcon; use Native\Electron\Traits\LocatesPhpBinary; +use Native\Electron\Traits\ManagesEnvFile; use Native\Electron\Traits\OsAndArch; use Native\Electron\Traits\PatchesPackagesJson; use Native\Electron\Traits\PrunesVendorDirectory; @@ -27,12 +27,12 @@ )] class BuildCommand extends Command { - use CleansEnvFile; use CopiesBundleToBuildDirectory; use CopiesCertificateAuthority; use HasPreAndPostProcessing; use InstallsAppIcon; use LocatesPhpBinary; + use ManagesEnvFile; use OsAndArch; use PatchesPackagesJson; use PrunesVendorDirectory; diff --git a/src/Electron/Commands/BundleCommand.php b/src/Electron/Commands/BundleCommand.php deleted file mode 100644 index 898563c2..00000000 --- a/src/Electron/Commands/BundleCommand.php +++ /dev/null @@ -1,412 +0,0 @@ -option('clear')) { - if (file_exists(base_path('build/__nativephp_app_bundle'))) { - unlink(base_path('build/__nativephp_app_bundle')); - } - - $this->info('Bundle removed. Building in this state would be unsecure.'); - - return static::SUCCESS; - } - - // Check for ZEPHPYR_KEY - if (! $this->checkForZephpyrKey()) { - return static::FAILURE; - } - - // Check for ZEPHPYR_TOKEN - if (! $this->checkForZephpyrToken()) { - return static::FAILURE; - } - - // Check if the token is valid - if (! $this->checkAuthenticated()) { - $this->error('Invalid API token: check your ZEPHPYR_TOKEN on '.$this->baseUrl().'user/api-tokens'); - - return static::FAILURE; - } - - // Download the latest bundle if requested - if ($this->option('fetch')) { - if (! $this->fetchLatestBundle()) { - - return static::FAILURE; - } - - $this->info('Latest bundle downloaded.'); - - return static::SUCCESS; - } - - $this->preProcess(); - - $this->setAppNameAndVersion(); - intro('Copying App to build directory...'); - - // We update composer.json later, - $this->copyToBuildDirectory(); - - $this->newLine(); - intro('Cleaning .env file...'); - $this->cleanEnvFile(); - - $this->newLine(); - intro('Copying app icons...'); - $this->installIcon(); - - $this->newLine(); - intro('Pruning vendor directory'); - $this->pruneVendorDirectory(); - - $this->cleanEnvFile(); - - // Check composer.json for symlinked or private packages - if (! $this->checkComposerJson()) { - return static::FAILURE; - } - - // Package the app up into a zip - if (! $this->zipApplication()) { - $this->error("Failed to create zip archive at {$this->zipPath}."); - - return static::FAILURE; - } - - // Send the zip file - $result = $this->sendToZephpyr(); - $this->handleApiErrors($result); - - // Success - $this->info('Successfully uploaded to Zephpyr.'); - $this->line('Use native:bundle --fetch to retrieve the latest bundle.'); - - // Clean up temp files - $this->cleanUp(); - - return static::SUCCESS; - } - - private function zipApplication(): bool - { - $this->zipName = 'app_'.str()->random(8).'.zip'; - $this->zipPath = $this->zipPath($this->zipName); - - // Create zip path - if (! @mkdir(dirname($this->zipPath), recursive: true) && ! is_dir(dirname($this->zipPath))) { - return false; - } - - $zip = new ZipArchive; - - if ($zip->open($this->zipPath, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) { - return false; - } - - $this->addFilesToZip($zip); - - $zip->close(); - - return true; - } - - private function checkComposerJson(): bool - { - $composerJson = json_decode(file_get_contents($this->buildPath('composer.json')), true); - - // // Fail if there is symlinked packages - // foreach ($composerJson['repositories'] ?? [] as $repository) { - // - // $symlinked = $repository['options']['symlink'] ?? true; - // if ($repository['type'] === 'path' && $symlinked) { - // $this->error('Symlinked packages are not supported. Please remove them from your composer.json.'); - // - // return false; - // } - // // Work with private packages but will not in the future - // // elseif ($repository['type'] === 'composer') { - // // if (! $this->checkComposerPackageAuth($repository['url'])) { - // // $this->error('Cannot authenticate with '.$repository['url'].'.'); - // // $this->error('Go to '.$this->baseUrl().' and add your composer package credentials.'); - // // - // // return false; - // // } - // // } - // } - - // Remove repositories with type path, we include symlinked packages - if (! empty($composerJson['repositories'])) { - - $this->newLine(); - intro('Patching composer.json in development mode…'); - - $filteredRepo = array_filter($composerJson['repositories'], - fn ($repository) => $repository['type'] !== 'path'); - - if (count($filteredRepo) !== count($composerJson['repositories'])) { - $composerJson['repositories'] = $filteredRepo; - file_put_contents($this->buildPath('composer.json'), - json_encode($composerJson, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); - - // Process::path($this->buildPath()) - // ->run('composer install --no-dev', function (string $type, string $output) { - // echo $output; - // }); - } - - } - - return true; - } - - // private function checkComposerPackageAuth(string $repositoryUrl): bool - // { - // // Check if the user has authenticated the package on Zephpyr - // $host = parse_url($repositoryUrl, PHP_URL_HOST); - // $this->line('Checking '.$host.' authentication…'); - // - // return Http::acceptJson() - // ->withToken(config('nativephp-internal.zephpyr.token')) - // ->get($this->baseUrl().'api/v1/project/'.$this->key.'/composer/auth/'.$host) - // ->successful(); - // } - - private function addFilesToZip(ZipArchive $zip): void - { - $this->newLine(); - intro('Creating zip archive…'); - - $finder = (new Finder)->files() - ->followLinks() - // ->ignoreVCSIgnored(true) // TODO: Make our own list of ignored files - ->in($this->buildPath()) - ->exclude([ - // We add those a few lines below and they are ignored by most .gitignore anyway - 'vendor', - 'node_modules', - - // Exclude the following directories - 'dist', // Compiled nativephp assets - 'build', // Compiled box assets - 'temp', // Temp files - 'tests', // Tests - 'auth.json', // Composer auth file - ]) - ->exclude(config('nativephp.cleanup_exclude_files', [])); - - $this->finderToZip($finder, $zip); - - // Why do I have to force this? please someone explain. - if (file_exists($this->buildPath('public/build'))) { - $this->finderToZip( - (new Finder)->files() - ->followLinks() - ->in($this->buildPath('public/build')), $zip, 'public/build'); - } - - // Add .env file manually because Finder ignores VCS and dot files - $zip->addFile($this->buildPath('.env'), '.env'); - - // Add auth.json file to support private packages - // WARNING: Only for testing purposes, don't uncomment this - // $zip->addFile($this->buildPath('auth.json'), 'auth.json'); - - // Custom binaries - $binaryPath = Str::replaceStart($this->buildPath('vendor'), '', config('nativephp.binary_path')); - - // Add composer dependencies without unnecessary files - $vendor = (new Finder)->files() - ->exclude(array_filter([ - 'nativephp/php-bin', - 'nativephp/electron/resources/electron', - '*/*/vendor', // Exclude sub-vendor directories - $binaryPath, - ])) - ->in($this->buildPath('vendor')); - - $this->finderToZip($vendor, $zip, 'vendor'); - - // Add javascript dependencies - if (file_exists($this->buildPath('node_modules'))) { - $nodeModules = (new Finder)->files() - ->in($this->buildPath('node_modules')); - - $this->finderToZip($nodeModules, $zip, 'node_modules'); - } - } - - private function finderToZip(Finder $finder, ZipArchive $zip, ?string $path = null): void - { - foreach ($finder as $file) { - if ($file->getRealPath() === false) { - continue; - } - - $zipPath = str($path)->finish('/').$file->getRelativePathname(); - $zipPath = str_replace('\\', '/', $zipPath); - - $zip->addFile($file->getRealPath(), $zipPath); - } - } - - private function sendToZephpyr() - { - intro('Uploading zip to Zephpyr…'); - - return Http::acceptJson() - ->timeout(300) // 5 minutes - ->withoutRedirecting() // Upload won't work if we follow redirects (it transform POST to GET) - ->withToken(config('nativephp-internal.zephpyr.token')) - ->attach('archive', fopen($this->zipPath, 'r'), $this->zipName) - ->post($this->baseUrl().'api/v1/project/'.$this->key.'/build/'); - } - - private function fetchLatestBundle(): bool - { - intro('Fetching latest bundle…'); - - $response = Http::acceptJson() - ->withToken(config('nativephp-internal.zephpyr.token')) - ->get($this->baseUrl().'api/v1/project/'.$this->key.'/build/download'); - - if ($response->failed()) { - - if ($response->status() === 404) { - $this->error('Project or bundle not found.'); - } elseif ($response->status() === 500) { - $url = $response->json('url'); - - if ($url) { - $this->error('Build failed. Inspect the build here: '.$url); - } else { - $this->error('Build failed. Please try again later.'); - } - } elseif ($response->status() === 503) { - $retryAfter = intval($response->header('Retry-After')); - $diff = now()->addSeconds($retryAfter); - $diffMessage = $retryAfter <= 60 ? 'a minute' : $diff->diffForHumans(syntax: CarbonInterface::DIFF_ABSOLUTE); - $this->warn('Bundle not ready. Please try again in '.$diffMessage.'.'); - } else { - $this->handleApiErrors($response); - } - - return false; - } - - // Save the bundle - @mkdir(base_path('build'), recursive: true); - file_put_contents(base_path('build/__nativephp_app_bundle'), $response->body()); - - return true; - } - - protected function exitWithMessage(string $message): void - { - $this->error($message); - $this->cleanUp(); - - exit(static::FAILURE); - } - - private function handleApiErrors(Response $result): void - { - if ($result->status() === 413) { - $fileSize = Number::fileSize(filesize($this->zipPath)); - $this->exitWithMessage('File is too large to upload ('.$fileSize.'). Please contact support.'); - } elseif ($result->status() === 422) { - $this->error('Request refused:'.$result->json('message')); - } elseif ($result->status() === 429) { - $this->exitWithMessage('Too many requests. Please try again in '.now()->addSeconds(intval($result->header('Retry-After')))->diffForHumans(syntax: CarbonInterface::DIFF_ABSOLUTE).'.'); - } elseif ($result->failed()) { - $this->exitWithMessage("Request failed. Error code: {$result->status()}"); - } - } - - protected function cleanUp(): void - { - $this->postProcess(); - - if ($this->option('without-cleanup')) { - return; - } - - $previousBuilds = glob($this->zipPath().'/app_*.zip'); - $failedZips = glob($this->zipPath().'/app_*.part'); - - $deleteFiles = array_merge($previousBuilds, $failedZips); - - if (empty($deleteFiles)) { - return; - } - - $this->line('Cleaning up…'); - - foreach ($deleteFiles as $file) { - @unlink($file); - } - } - - protected function buildPath(string $path = ''): string - { - return base_path('build/app/'.$path); - } - - protected function zipPath(string $path = ''): string - { - return base_path('build/zip/'.$path); - } - - protected function sourcePath(string $path = ''): string - { - return base_path($path); - } -} diff --git a/src/Electron/ElectronServiceProvider.php b/src/Electron/ElectronServiceProvider.php index 2b3fee8a..aaa73099 100644 --- a/src/Electron/ElectronServiceProvider.php +++ b/src/Electron/ElectronServiceProvider.php @@ -3,8 +3,12 @@ namespace Native\Electron; use Illuminate\Foundation\Application; +use Native\Electron\Commands\Bifrost\ClearBundleCommand; +use Native\Electron\Commands\Bifrost\DownloadBundleCommand; +use Native\Electron\Commands\Bifrost\InitCommand; +use Native\Electron\Commands\Bifrost\LoginCommand; +use Native\Electron\Commands\Bifrost\LogoutCommand; use Native\Electron\Commands\BuildCommand; -use Native\Electron\Commands\BundleCommand; use Native\Electron\Commands\DevelopCommand; use Native\Electron\Commands\InstallCommand; use Native\Electron\Commands\PublishCommand; @@ -26,8 +30,12 @@ public function configurePackage(Package $package): void DevelopCommand::class, BuildCommand::class, PublishCommand::class, - BundleCommand::class, ResetCommand::class, + LoginCommand::class, + LogoutCommand::class, + InitCommand::class, + DownloadBundleCommand::class, + ClearBundleCommand::class, ]); } diff --git a/src/Electron/Traits/CleansEnvFile.php b/src/Electron/Traits/CleansEnvFile.php deleted file mode 100644 index 9eb493d6..00000000 --- a/src/Electron/Traits/CleansEnvFile.php +++ /dev/null @@ -1,47 +0,0 @@ -overrideKeys, config('nativephp.cleanup_env_keys', [])); - - $envFile = $this->buildPath(app()->environmentFile()); - - $contents = collect(file($envFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES)) - // Remove cleanup keys - ->filter(function (string $line) use ($cleanUpKeys) { - $key = str($line)->before('='); - - return ! $key->is($cleanUpKeys) - && ! $key->startsWith('#'); - }) - // Set defaults (other config overrides are handled in the NativeServiceProvider) - // The Log channel needs to be configured before anything else. - ->push('LOG_CHANNEL=stack') - ->push('LOG_STACK=daily') - ->push('LOG_DAILY_DAYS=3') - ->push('LOG_LEVEL=warning') - ->join("\n"); - - file_put_contents($envFile, $contents); - } -} diff --git a/src/Electron/Traits/HandlesBifrost.php b/src/Electron/Traits/HandlesBifrost.php new file mode 100644 index 00000000..04507b9e --- /dev/null +++ b/src/Electron/Traits/HandlesBifrost.php @@ -0,0 +1,123 @@ +finish('/'); + } + + private function checkAuthenticated(): bool + { + intro('Checking authentication…'); + + try { + return Http::acceptJson() + ->withToken(config('nativephp-internal.bifrost.token')) + ->get($this->baseUrl().'api/v1/auth/user')->successful(); + } catch (Exception $e) { + $this->error('Network error: '.$e->getMessage()); + + return false; + } + } + + private function checkForBifrostToken(): bool + { + if (! config('nativephp-internal.bifrost.token')) { + $this->line(''); + $this->warn('No BIFROST_TOKEN found. Please login first.'); + $this->line(''); + $this->line('Run: php artisan bifrost:login'); + $this->line(''); + + return false; + } + + return true; + } + + private function checkForBifrostProject(): bool + { + if (! config('nativephp-internal.bifrost.project')) { + $this->line(''); + $this->warn('No BIFROST_PROJECT found. Please select a project first.'); + $this->line(''); + $this->line('Run: php artisan bifrost:init'); + $this->line(''); + + return false; + } + + return true; + } + + /** + * Validates authentication and returns user data + * + * @throws Exception + */ + private function validateAuthAndGetUser(): array + { + if (! $this->checkForBifrostToken()) { + throw new Exception('No BIFROST_TOKEN found. Please login first.'); + } + + try { + $response = Http::acceptJson() + ->withToken(config('nativephp-internal.bifrost.token')) + ->get($this->baseUrl().'api/v1/auth/user'); + + if ($response->failed()) { + throw new Exception('Invalid API token. Please login again.'); + } + + $data = $response->json(); + + if (! isset($data['data'])) { + throw new Exception('Invalid API response format.'); + } + + return $data['data']; + } catch (Exception $e) { + throw new Exception('Authentication failed: '.$e->getMessage()); + } + } + + private function getCurrentTeamSlug(): ?string + { + try { + $user = $this->validateAuthAndGetUser(); + + return $user['current_team']['slug'] ?? null; + } catch (Exception $e) { + return null; + } + } + + protected function makeApiRequest(string $method, string $endpoint, array $data = []): Response + { + try { + $request = Http::acceptJson() + ->withToken(config('nativephp-internal.bifrost.token')); + + return match (strtoupper($method)) { + 'GET' => $request->get($this->baseUrl().$endpoint), + 'POST' => $request->post($this->baseUrl().$endpoint, $data), + 'PUT' => $request->put($this->baseUrl().$endpoint, $data), + 'DELETE' => $request->delete($this->baseUrl().$endpoint), + default => throw new Exception("Unsupported HTTP method: {$method}") + }; + } catch (Exception $e) { + throw new Exception("API request failed: {$e->getMessage()}"); + } + } +} diff --git a/src/Electron/Traits/HandlesZephpyr.php b/src/Electron/Traits/HandlesZephpyr.php deleted file mode 100644 index 26749968..00000000 --- a/src/Electron/Traits/HandlesZephpyr.php +++ /dev/null @@ -1,64 +0,0 @@ -finish('/'); - } - - private function checkAuthenticated() - { - intro('Checking authentication…'); - - return Http::acceptJson() - ->withToken(config('nativephp-internal.zephpyr.token')) - ->get($this->baseUrl().'api/v1/user')->successful(); - } - - private function checkForZephpyrKey() - { - $this->key = config('nativephp-internal.zephpyr.key'); - - if (! $this->key) { - $this->line(''); - $this->warn('No ZEPHPYR_KEY found. Cannot bundle!'); - $this->line(''); - $this->line('Add this app\'s ZEPHPYR_KEY to its .env file:'); - $this->line(base_path('.env')); - $this->line(''); - $this->info('Not set up with Zephpyr yet? Secure your NativePHP app builds and more!'); - $this->info('Check out '.$this->baseUrl().''); - $this->line(''); - - return false; - } - - return true; - } - - private function checkForZephpyrToken() - { - if (! config('nativephp-internal.zephpyr.token')) { - $this->line(''); - $this->warn('No ZEPHPYR_TOKEN found. Cannot bundle!'); - $this->line(''); - $this->line('Add your Zephpyr API token to your .env file (ZEPHPYR_TOKEN):'); - $this->line(base_path('.env')); - $this->line(''); - $this->info('Not set up with Zephpyr yet? Secure your NativePHP app builds and more!'); - $this->info('Check out '.$this->baseUrl().''); - $this->line(''); - - return false; - } - - return true; - } -} diff --git a/src/Electron/Traits/ManagesEnvFile.php b/src/Electron/Traits/ManagesEnvFile.php new file mode 100644 index 00000000..38be45d2 --- /dev/null +++ b/src/Electron/Traits/ManagesEnvFile.php @@ -0,0 +1,120 @@ +overrideKeys, config('nativephp.cleanup_env_keys', [])); + + $envFile = $this->getEnvPath(); + + $contents = collect(file($envFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES)) + // Remove cleanup keys + ->filter(function (string $line) use ($cleanUpKeys) { + $key = str($line)->before('='); + + return ! $key->is($cleanUpKeys) + && ! $key->startsWith('#'); + }) + // Set defaults (other config overrides are handled in the NativeServiceProvider) + // The Log channel needs to be configured before anything else. + ->push('LOG_CHANNEL=stack') + ->push('LOG_STACK=daily') + ->push('LOG_DAILY_DAYS=3') + ->push('LOG_LEVEL=warning') + ->join("\n"); + + file_put_contents($envFile, $contents); + } + + /** + * Update or add an environment variable + */ + protected function updateEnvFile(string $key, string $value, ?string $envPath = null): void + { + $envPath = $envPath ?? $this->getEnvPath(); + $envContent = file_get_contents($envPath); + + $pattern = "/^{$key}=.*$/m"; + + if (preg_match($pattern, $envContent)) { + $envContent = preg_replace($pattern, "{$key}={$value}", $envContent); + } else { + $envContent = rtrim($envContent, "\n")."\n{$key}={$value}\n"; + } + + file_put_contents($envPath, $envContent); + } + + /** + * Remove environment variables + */ + protected function removeFromEnvFile(array $keys, ?string $envPath = null): void + { + $envPath = $envPath ?? $this->getEnvPath(); + $envContent = file_get_contents($envPath); + + foreach ($keys as $key) { + $envContent = preg_replace("/^{$key}=.*$/m", '', $envContent); + } + + // Clean up extra newlines + $envContent = preg_replace('/\n\n+/', "\n\n", $envContent); + $envContent = trim($envContent)."\n"; + + file_put_contents($envPath, $envContent); + } + + /** + * Get an environment variable value + */ + protected function getEnvValue(string $key, ?string $envPath = null): ?string + { + $envPath = $envPath ?? $this->getEnvPath(); + + if (! file_exists($envPath)) { + return null; + } + + $envContent = file_get_contents($envPath); + + if (preg_match("/^{$key}=(.*)$/m", $envContent, $matches)) { + return trim($matches[1]); + } + + return null; + } + + /** + * Get the appropriate .env file path based on context + */ + private function getEnvPath(): string + { + // If buildPath method exists (BuildCommand context), use build directory + if (method_exists($this, 'buildPath')) { + return $this->buildPath(app()->environmentFile()); + } + + // Default to base path (Bifrost commands context) + return base_path('.env'); + } +} From 74380aa7a21f8b25fcb5047f7f3186c7ae6483fc Mon Sep 17 00:00:00 2001 From: gwleuverink Date: Thu, 9 Oct 2025 22:01:30 +0200 Subject: [PATCH 2/4] fix - remove bundle command --- src/Drivers/Electron/ElectronServiceProvider.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Drivers/Electron/ElectronServiceProvider.php b/src/Drivers/Electron/ElectronServiceProvider.php index 0f309bbb..be958672 100644 --- a/src/Drivers/Electron/ElectronServiceProvider.php +++ b/src/Drivers/Electron/ElectronServiceProvider.php @@ -5,7 +5,6 @@ use Illuminate\Foundation\Application; use Native\Desktop\Builder\Builder; use Native\Desktop\Drivers\Electron\Commands\BuildCommand; -use Native\Desktop\Drivers\Electron\Commands\BundleCommand; use Native\Desktop\Drivers\Electron\Commands\InstallCommand; use Native\Desktop\Drivers\Electron\Commands\PublishCommand; use Native\Desktop\Drivers\Electron\Commands\ResetCommand; @@ -41,7 +40,6 @@ public function configurePackage(Package $package): void RunCommand::class, BuildCommand::class, PublishCommand::class, - BundleCommand::class, ResetCommand::class, ]); } From c436565363fa2155efa0b2c231a277a4fc532415 Mon Sep 17 00:00:00 2001 From: gwleuverink Date: Thu, 9 Oct 2025 22:26:15 +0200 Subject: [PATCH 3/4] always use env trait through the builder this way we don't have to add context conditional - more deterministic --- src/Builder/Builder.php | 10 ++++------ src/Builder/Concerns/ManagesEnvFile.php | 21 ++++++++------------- src/Commands/Bifrost/InitCommand.php | 5 ++--- src/Commands/Bifrost/LoginCommand.php | 5 ++--- src/Commands/Bifrost/LogoutCommand.php | 5 ++--- tests/Build/CleanEnvFileTest.php | 5 +++-- 6 files changed, 21 insertions(+), 30 deletions(-) diff --git a/src/Builder/Builder.php b/src/Builder/Builder.php index 4be6bda1..4546a9e0 100644 --- a/src/Builder/Builder.php +++ b/src/Builder/Builder.php @@ -22,17 +22,15 @@ class Builder use CopiesBundleToBuildDirectory; public function __construct( - private string $buildPath, + private ?string $buildPath = null, private ?string $sourcePath = null, ) { - - $this->sourcePath = $sourcePath - ? $sourcePath - : base_path(); + $this->buildPath = $buildPath ?? base_path('build'); + $this->sourcePath = $sourcePath ?? base_path(); } public static function make( - string $buildPath, + ?string $buildPath = null, ?string $sourcePath = null ) { return new self($buildPath, $sourcePath); diff --git a/src/Builder/Concerns/ManagesEnvFile.php b/src/Builder/Concerns/ManagesEnvFile.php index 1d11f870..3478b586 100644 --- a/src/Builder/Concerns/ManagesEnvFile.php +++ b/src/Builder/Concerns/ManagesEnvFile.php @@ -11,6 +11,8 @@ trait ManagesEnvFile { + abstract public function buildPath(string $path = ''): string; + public array $overrideKeys = [ 'LOG_CHANNEL', 'LOG_STACK', @@ -21,12 +23,11 @@ trait ManagesEnvFile /** * Clean sensitive information from .env file for production builds */ - public function cleanEnvFile(): void + public function cleanEnvFile(?string $envPath = null): void { + $envFile = $envPath ?? $this->getEnvPath(); $cleanUpKeys = array_merge($this->overrideKeys, config('nativephp.cleanup_env_keys', [])); - $envFile = $this->getEnvPath(); - $contents = collect(file($envFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES)) // Remove cleanup keys ->filter(function (string $line) use ($cleanUpKeys) { @@ -49,7 +50,7 @@ public function cleanEnvFile(): void /** * Update or add an environment variable */ - protected function updateEnvFile(string $key, string $value, ?string $envPath = null): void + public function updateEnvFile(string $key, string $value, ?string $envPath = null): void { $envPath = $envPath ?? $this->getEnvPath(); $envContent = file_get_contents($envPath); @@ -68,7 +69,7 @@ protected function updateEnvFile(string $key, string $value, ?string $envPath = /** * Remove environment variables */ - protected function removeFromEnvFile(array $keys, ?string $envPath = null): void + public function removeFromEnvFile(array $keys, ?string $envPath = null): void { $envPath = $envPath ?? $this->getEnvPath(); $envContent = file_get_contents($envPath); @@ -87,7 +88,7 @@ protected function removeFromEnvFile(array $keys, ?string $envPath = null): void /** * Get an environment variable value */ - protected function getEnvValue(string $key, ?string $envPath = null): ?string + public function getEnvValue(string $key, ?string $envPath = null): ?string { $envPath = $envPath ?? $this->getEnvPath(); @@ -109,12 +110,6 @@ protected function getEnvValue(string $key, ?string $envPath = null): ?string */ private function getEnvPath(): string { - // If buildPath method exists (BuildCommand context), use build directory - if (method_exists($this, 'buildPath')) { - return $this->buildPath(app()->environmentFile()); - } - - // Default to base path (Bifrost commands context) - return base_path('.env'); + return $this->buildPath('app/' .app()->environmentFile()); } } diff --git a/src/Commands/Bifrost/InitCommand.php b/src/Commands/Bifrost/InitCommand.php index 24600626..3989bef7 100644 --- a/src/Commands/Bifrost/InitCommand.php +++ b/src/Commands/Bifrost/InitCommand.php @@ -5,9 +5,9 @@ use Exception; use Illuminate\Console\Command; use function Laravel\Prompts\intro; +use Native\Desktop\Builder\Builder; use function Laravel\Prompts\select; use Symfony\Component\Console\Attribute\AsCommand; -use Native\Desktop\Builder\Concerns\ManagesEnvFile; use Native\Desktop\Commands\Bifrost\Concerns\HandlesBifrost; #[AsCommand( @@ -17,7 +17,6 @@ class InitCommand extends Command { use HandlesBifrost; - use ManagesEnvFile; protected $signature = 'bifrost:init'; @@ -84,7 +83,7 @@ public function handle(): int } // Store project UUID in .env file - $this->updateEnvFile('BIFROST_PROJECT', $selectedProjectUuid); + Builder::make()->updateEnvFile('BIFROST_PROJECT', $selectedProjectUuid, app()->environmentFile()); $this->displaySuccessMessage($selectedProject); diff --git a/src/Commands/Bifrost/LoginCommand.php b/src/Commands/Bifrost/LoginCommand.php index b32c70f7..c91d705b 100644 --- a/src/Commands/Bifrost/LoginCommand.php +++ b/src/Commands/Bifrost/LoginCommand.php @@ -6,10 +6,10 @@ use Illuminate\Console\Command; use function Laravel\Prompts\text; use function Laravel\Prompts\intro; +use Native\Desktop\Builder\Builder; use Illuminate\Support\Facades\Http; use function Laravel\Prompts\password; use Symfony\Component\Console\Attribute\AsCommand; -use Native\Desktop\Builder\Concerns\ManagesEnvFile; use Native\Desktop\Commands\Bifrost\Concerns\HandlesBifrost; #[AsCommand( @@ -19,7 +19,6 @@ class LoginCommand extends Command { use HandlesBifrost; - use ManagesEnvFile; protected $signature = 'bifrost:login'; @@ -70,7 +69,7 @@ public function handle(): int $token = $responseData['data']['token']; // Store token in .env file - $this->updateEnvFile('BIFROST_TOKEN', $token); + Builder::make()->updateEnvFile('BIFROST_TOKEN', $token, app()->environmentFile()); // Fetch and display user info $this->displayUserInfo($token); diff --git a/src/Commands/Bifrost/LogoutCommand.php b/src/Commands/Bifrost/LogoutCommand.php index 105c8eb9..6581c9ba 100644 --- a/src/Commands/Bifrost/LogoutCommand.php +++ b/src/Commands/Bifrost/LogoutCommand.php @@ -3,10 +3,10 @@ namespace Native\Desktop\Commands\Bifrost; use Illuminate\Console\Command; +use Native\Desktop\Builder\Builder; use function Laravel\Prompts\intro; use Illuminate\Support\Facades\Http; use Symfony\Component\Console\Attribute\AsCommand; -use Native\Desktop\Builder\Concerns\ManagesEnvFile; use Native\Desktop\Commands\Bifrost\Concerns\HandlesBifrost; #[AsCommand( @@ -16,7 +16,6 @@ class LogoutCommand extends Command { use HandlesBifrost; - use ManagesEnvFile; protected $signature = 'bifrost:logout'; @@ -36,7 +35,7 @@ public function handle(): int ->post($this->baseUrl().'api/v1/auth/logout'); // Remove token and project from .env file regardless of server response - $this->removeFromEnvFile(['BIFROST_TOKEN', 'BIFROST_PROJECT']); + Builder::make()->removeFromEnvFile(['BIFROST_TOKEN', 'BIFROST_PROJECT'], app()->environmentFile()); $this->info('Successfully logged out!'); $this->line('Your API token and project selection have been removed.'); diff --git a/tests/Build/CleanEnvFileTest.php b/tests/Build/CleanEnvFileTest.php index dc34efb3..47a1a303 100644 --- a/tests/Build/CleanEnvFileTest.php +++ b/tests/Build/CleanEnvFileTest.php @@ -1,8 +1,9 @@ Date: Fri, 10 Oct 2025 12:49:32 +0000 Subject: [PATCH 4/4] Fix styling --- src/Builder/Builder.php | 14 +++---- src/Builder/Concerns/ManagesEnvFile.php | 2 +- .../Bifrost/DownloadBundleCommand.php | 6 +-- src/Commands/Bifrost/InitCommand.php | 7 ++-- src/Commands/Bifrost/LoginCommand.php | 11 +++--- src/Commands/Bifrost/LogoutCommand.php | 7 ++-- .../Electron/Commands/BuildCommand.php | 2 +- src/NativeServiceProvider.php | 38 +++++++++---------- tests/Build/CleanEnvFileTest.php | 3 +- 9 files changed, 46 insertions(+), 44 deletions(-) diff --git a/src/Builder/Builder.php b/src/Builder/Builder.php index 4546a9e0..b495af67 100644 --- a/src/Builder/Builder.php +++ b/src/Builder/Builder.php @@ -4,22 +4,22 @@ use Native\Desktop\Builder\Concerns\CopiesBundleToBuildDirectory; use Native\Desktop\Builder\Concerns\CopiesCertificateAuthority; -use Native\Desktop\Builder\Concerns\HasPreAndPostProcessing; use Native\Desktop\Builder\Concerns\CopiesToBuildDirectory; -use Native\Desktop\Builder\Concerns\PrunesVendorDirectory; +use Native\Desktop\Builder\Concerns\HasPreAndPostProcessing; use Native\Desktop\Builder\Concerns\LocatesPhpBinary; use Native\Desktop\Builder\Concerns\ManagesEnvFile; +use Native\Desktop\Builder\Concerns\PrunesVendorDirectory; use Symfony\Component\Filesystem\Path; class Builder { - use ManagesEnvFile; - use LocatesPhpBinary; - use PrunesVendorDirectory; + use CopiesBundleToBuildDirectory; + use CopiesCertificateAuthority; use CopiesToBuildDirectory; use HasPreAndPostProcessing; - use CopiesCertificateAuthority; - use CopiesBundleToBuildDirectory; + use LocatesPhpBinary; + use ManagesEnvFile; + use PrunesVendorDirectory; public function __construct( private ?string $buildPath = null, diff --git a/src/Builder/Concerns/ManagesEnvFile.php b/src/Builder/Concerns/ManagesEnvFile.php index 3478b586..e1ea62dc 100644 --- a/src/Builder/Concerns/ManagesEnvFile.php +++ b/src/Builder/Concerns/ManagesEnvFile.php @@ -110,6 +110,6 @@ public function getEnvValue(string $key, ?string $envPath = null): ?string */ private function getEnvPath(): string { - return $this->buildPath('app/' .app()->environmentFile()); + return $this->buildPath('app/'.app()->environmentFile()); } } diff --git a/src/Commands/Bifrost/DownloadBundleCommand.php b/src/Commands/Bifrost/DownloadBundleCommand.php index 015c5efa..33c950dc 100644 --- a/src/Commands/Bifrost/DownloadBundleCommand.php +++ b/src/Commands/Bifrost/DownloadBundleCommand.php @@ -2,15 +2,15 @@ namespace Native\Desktop\Commands\Bifrost; -use Exception; use Carbon\CarbonInterface; +use Exception; use Illuminate\Console\Command; -use function Laravel\Prompts\intro; use Illuminate\Support\Facades\Http; use Native\Desktop\Commands\Bifrost\Concerns\HandlesBifrost; - use Symfony\Component\Console\Attribute\AsCommand; +use function Laravel\Prompts\intro; + #[AsCommand( name: 'bifrost:download-bundle', description: 'Download the latest desktop bundle from Bifrost.', diff --git a/src/Commands/Bifrost/InitCommand.php b/src/Commands/Bifrost/InitCommand.php index 3989bef7..04fa1f9f 100644 --- a/src/Commands/Bifrost/InitCommand.php +++ b/src/Commands/Bifrost/InitCommand.php @@ -4,11 +4,12 @@ use Exception; use Illuminate\Console\Command; -use function Laravel\Prompts\intro; use Native\Desktop\Builder\Builder; -use function Laravel\Prompts\select; -use Symfony\Component\Console\Attribute\AsCommand; use Native\Desktop\Commands\Bifrost\Concerns\HandlesBifrost; +use Symfony\Component\Console\Attribute\AsCommand; + +use function Laravel\Prompts\intro; +use function Laravel\Prompts\select; #[AsCommand( name: 'bifrost:init', diff --git a/src/Commands/Bifrost/LoginCommand.php b/src/Commands/Bifrost/LoginCommand.php index c91d705b..e2414598 100644 --- a/src/Commands/Bifrost/LoginCommand.php +++ b/src/Commands/Bifrost/LoginCommand.php @@ -4,13 +4,14 @@ use Exception; use Illuminate\Console\Command; -use function Laravel\Prompts\text; -use function Laravel\Prompts\intro; -use Native\Desktop\Builder\Builder; use Illuminate\Support\Facades\Http; -use function Laravel\Prompts\password; -use Symfony\Component\Console\Attribute\AsCommand; +use Native\Desktop\Builder\Builder; use Native\Desktop\Commands\Bifrost\Concerns\HandlesBifrost; +use Symfony\Component\Console\Attribute\AsCommand; + +use function Laravel\Prompts\intro; +use function Laravel\Prompts\password; +use function Laravel\Prompts\text; #[AsCommand( name: 'bifrost:login', diff --git a/src/Commands/Bifrost/LogoutCommand.php b/src/Commands/Bifrost/LogoutCommand.php index 6581c9ba..74fc9cb3 100644 --- a/src/Commands/Bifrost/LogoutCommand.php +++ b/src/Commands/Bifrost/LogoutCommand.php @@ -3,11 +3,12 @@ namespace Native\Desktop\Commands\Bifrost; use Illuminate\Console\Command; -use Native\Desktop\Builder\Builder; -use function Laravel\Prompts\intro; use Illuminate\Support\Facades\Http; -use Symfony\Component\Console\Attribute\AsCommand; +use Native\Desktop\Builder\Builder; use Native\Desktop\Commands\Bifrost\Concerns\HandlesBifrost; +use Symfony\Component\Console\Attribute\AsCommand; + +use function Laravel\Prompts\intro; #[AsCommand( name: 'bifrost:logout', diff --git a/src/Drivers/Electron/Commands/BuildCommand.php b/src/Drivers/Electron/Commands/BuildCommand.php index 963d05ec..1a63f6cc 100644 --- a/src/Drivers/Electron/Commands/BuildCommand.php +++ b/src/Drivers/Electron/Commands/BuildCommand.php @@ -22,8 +22,8 @@ )] class BuildCommand extends Command { - use OsAndArch; use InstallsAppIcon; + use OsAndArch; use PatchesPackagesJson; protected $signature = 'native:build diff --git a/src/NativeServiceProvider.php b/src/NativeServiceProvider.php index cba0a5fd..caabb851 100644 --- a/src/NativeServiceProvider.php +++ b/src/NativeServiceProvider.php @@ -2,37 +2,37 @@ namespace Native\Desktop; -use Illuminate\Support\Arr; -use Illuminate\Support\Facades\DB; use Illuminate\Console\Application; -use Native\Desktop\Commands\Bifrost; +use Illuminate\Foundation\Application as Foundation; use Illuminate\Foundation\Http\Kernel; -use Native\Desktop\Exceptions\Handler; -use Native\Desktop\Logging\LogWatcher; +use Illuminate\Support\Arr; use Illuminate\Support\Facades\Artisan; -use Native\Desktop\Events\EventWatcher; -use Spatie\LaravelPackageTools\Package; +use Illuminate\Support\Facades\DB; +use Native\Desktop\ChildProcess as ChildProcessImplementation; +use Native\Desktop\Commands\Bifrost; use Native\Desktop\Commands\DebugCommand; use Native\Desktop\Commands\FreshCommand; +use Native\Desktop\Commands\LoadPHPConfigurationCommand; +use Native\Desktop\Commands\LoadStartupConfigurationCommand; use Native\Desktop\Commands\MigrateCommand; -use Native\Desktop\DataObjects\QueueConfig; use Native\Desktop\Commands\SeedDatabaseCommand; use Native\Desktop\Commands\WipeDatabaseCommand; -use Illuminate\Foundation\Application as Foundation; -use Spatie\LaravelPackageTools\PackageServiceProvider; -use Native\Desktop\Commands\LoadPHPConfigurationCommand; -use Native\Desktop\Commands\LoadStartupConfigurationCommand; -use Native\Desktop\Drivers\Electron\ElectronServiceProvider; -use Native\Desktop\ChildProcess as ChildProcessImplementation; -use Native\Desktop\PowerMonitor as PowerMonitorImplementation; -use Native\Desktop\Http\Middleware\PreventRegularBrowserAccess; -use Native\Desktop\Contracts\QueueWorker as QueueWorkerContract; use Native\Desktop\Contracts\ChildProcess as ChildProcessContract; +use Native\Desktop\Contracts\GlobalShortcut as GlobalShortcutContract; use Native\Desktop\Contracts\PowerMonitor as PowerMonitorContract; -use Native\Desktop\GlobalShortcut as GlobalShortcutImplementation; +use Native\Desktop\Contracts\QueueWorker as QueueWorkerContract; use Native\Desktop\Contracts\WindowManager as WindowManagerContract; -use Native\Desktop\Contracts\GlobalShortcut as GlobalShortcutContract; +use Native\Desktop\DataObjects\QueueConfig; +use Native\Desktop\Drivers\Electron\ElectronServiceProvider; +use Native\Desktop\Events\EventWatcher; +use Native\Desktop\Exceptions\Handler; +use Native\Desktop\GlobalShortcut as GlobalShortcutImplementation; +use Native\Desktop\Http\Middleware\PreventRegularBrowserAccess; +use Native\Desktop\Logging\LogWatcher; +use Native\Desktop\PowerMonitor as PowerMonitorImplementation; use Native\Desktop\Windows\WindowManager as WindowManagerImplementation; +use Spatie\LaravelPackageTools\Package; +use Spatie\LaravelPackageTools\PackageServiceProvider; class NativeServiceProvider extends PackageServiceProvider { diff --git a/tests/Build/CleanEnvFileTest.php b/tests/Build/CleanEnvFileTest.php index 47a1a303..79ec93b6 100644 --- a/tests/Build/CleanEnvFileTest.php +++ b/tests/Build/CleanEnvFileTest.php @@ -1,9 +1,8 @@