From a2de481b9ffb0ad14c88ad2c9181c8ac49e25b47 Mon Sep 17 00:00:00 2001 From: Saeed Vaziry Date: Mon, 6 Jul 2026 00:02:09 +0200 Subject: [PATCH 1/6] wip desktop --- .env.desktop.example | 37 + .gitignore | 4 + app/Console/Commands/GenerateKeysCommand.php | 27 +- app/Http/Controllers/ConsoleController.php | 15 +- .../Controllers/DesktopAuthController.php | 152 + app/Http/Controllers/EventsController.php | 15 +- app/Http/Controllers/HomeController.php | 3 +- app/Http/Middleware/Authenticate.php | 7 +- app/Http/Middleware/HandleInertiaRequests.php | 4 + app/Providers/AuthServiceProvider.php | 27 +- app/Support/DesktopRuntime.php | 76 + app/Support/helpers.php | 4 +- bootstrap/app.php | 14 + config/desktop.php | 8 + config/queue.php | 9 + desktop/README.md | 12 + desktop/electron-builder.yml | 27 + desktop/package-lock.json | 3627 +++++++++++++++++ desktop/package.json | 18 + desktop/src/env-file.ts | 61 + desktop/src/health-check.ts | 24 + desktop/src/laravel-runtime.ts | 89 + desktop/src/loading-page.ts | 63 + desktop/src/logging.ts | 24 + desktop/src/main.ts | 88 + desktop/src/ports.ts | 19 + desktop/src/preload.ts | 5 + desktop/src/process-supervisor.ts | 155 + desktop/src/runtime-paths.ts | 144 + desktop/tsconfig.json | 15 + docs/desktop-app-plan.md | 794 ++++ package.json | 3 + resources/js/components/user-menu-content.tsx | 15 +- resources/js/pages/auth/desktop-login.tsx | 209 + resources/js/types/index.d.ts | 3 + tests/Feature/DesktopAuthTest.php | 170 + .../Unit/Commands/GenerateKeysCommandTest.php | 28 + tests/Unit/DesktopRuntimeTest.php | 39 + 38 files changed, 5985 insertions(+), 49 deletions(-) create mode 100644 .env.desktop.example create mode 100644 app/Http/Controllers/DesktopAuthController.php create mode 100644 app/Support/DesktopRuntime.php create mode 100644 config/desktop.php create mode 100644 desktop/README.md create mode 100644 desktop/electron-builder.yml create mode 100644 desktop/package-lock.json create mode 100644 desktop/package.json create mode 100644 desktop/src/env-file.ts create mode 100644 desktop/src/health-check.ts create mode 100644 desktop/src/laravel-runtime.ts create mode 100644 desktop/src/loading-page.ts create mode 100644 desktop/src/logging.ts create mode 100644 desktop/src/main.ts create mode 100644 desktop/src/ports.ts create mode 100644 desktop/src/preload.ts create mode 100644 desktop/src/process-supervisor.ts create mode 100644 desktop/src/runtime-paths.ts create mode 100644 desktop/tsconfig.json create mode 100644 docs/desktop-app-plan.md create mode 100644 resources/js/pages/auth/desktop-login.tsx create mode 100644 tests/Feature/DesktopAuthTest.php create mode 100644 tests/Unit/Commands/GenerateKeysCommandTest.php create mode 100644 tests/Unit/DesktopRuntimeTest.php diff --git a/.env.desktop.example b/.env.desktop.example new file mode 100644 index 000000000..a14de66a9 --- /dev/null +++ b/.env.desktop.example @@ -0,0 +1,37 @@ +APP_NAME=Vito +APP_ENV=production +APP_KEY= +APP_DEBUG=false +APP_URL=http://127.0.0.1 + +VITO_DESKTOP=true +VITO_DATA_PATH= +VITO_ENV_PATH= +VITO_STORAGE_PATH= + +APP_SERVICES_CACHE= +APP_PACKAGES_CACHE= +APP_CONFIG_CACHE= +APP_ROUTES_CACHE= +APP_EVENTS_CACHE= +VIEW_COMPILED_PATH= + +DB_CONNECTION=sqlite +DB_DATABASE=database.sqlite + +QUEUE_CONNECTION=database +DB_QUEUE=default +QUEUE_FAILED_DRIVER=database-uuids + +CACHE_DRIVER=file +SESSION_DRIVER=file +FILESYSTEM_DISK=local + +MAIL_MAILER=log + +WS_HOST=127.0.0.1 +WS_PORT=8085 +WS_ALLOWED_ORIGINS= +WS_BROADCAST_SECRET= + +SCRIBE_AUTH_KEY="YOUR-API-KEY" diff --git a/.gitignore b/.gitignore index 4149fe675..7dcb4c91f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,9 @@ /.phpunit.cache /node_modules +/desktop/node_modules +/desktop/dist +/desktop/release +/desktop-build /public/hot /public/storage /storage/*.key diff --git a/app/Console/Commands/GenerateKeysCommand.php b/app/Console/Commands/GenerateKeysCommand.php index 64187f5a8..43fdff0d4 100644 --- a/app/Console/Commands/GenerateKeysCommand.php +++ b/app/Console/Commands/GenerateKeysCommand.php @@ -22,11 +22,28 @@ public function handle(): void return; } - exec('openssl genpkey -algorithm RSA -out '.$privateKeyPath); - exec('chmod 600 '.$privateKeyPath); - exec('ssh-keygen -y -f '.$privateKeyPath.' > '.$publicKeyPath); - exec('chown -R '.get_current_user().':'.get_current_user().' '.$privateKeyPath); - exec('chown -R '.get_current_user().':'.get_current_user().' '.$publicKeyPath); + $privateKey = escapeshellarg($privateKeyPath); + $publicKey = escapeshellarg($publicKeyPath); + + exec("openssl genpkey -algorithm RSA -out {$privateKey}", $output, $resultCode); + + if ($resultCode !== 0) { + $this->error('Unable to generate private key.'); + + return; + } + + chmod($privateKeyPath, 0600); + + exec("ssh-keygen -y -f {$privateKey} > {$publicKey}", $output, $resultCode); + + if ($resultCode !== 0) { + $this->error('Unable to generate public key.'); + + return; + } + + chmod($publicKeyPath, 0644); $this->info('Keys generated successfully.'); } diff --git a/app/Http/Controllers/ConsoleController.php b/app/Http/Controllers/ConsoleController.php index 8e3ffb7ba..6a64500d5 100644 --- a/app/Http/Controllers/ConsoleController.php +++ b/app/Http/Controllers/ConsoleController.php @@ -5,6 +5,7 @@ use App\Actions\WebSockets\GenerateWebSocketToken; use App\Http\Resources\ServerResource; use App\Models\Server; +use App\Support\DesktopRuntime; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Validation\Rule; @@ -48,19 +49,7 @@ public function token(Server $server, Request $request): JsonResponse 'ssh_user' => $request->input('user'), ]); - $appUrl = parse_url(config('app.ws_url') ?: config('app.url')); - $isSecure = ($appUrl['scheme'] ?? 'http') === 'https'; - $wsProtocol = $isSecure ? 'wss' : 'ws'; - $host = $appUrl['host'] ?? 'localhost'; - $port = $appUrl['port'] ?? ($isSecure ? 443 : 80); - - if (app()->environment('local')) { - $wsPort = config('core.ws_port', 8085); - $result['url'] = "{$wsProtocol}://{$host}:{$wsPort}/ws/terminal"; - } else { - $portSuffix = (($isSecure && $port == 443) || (! $isSecure && $port == 80)) ? '' : ":{$port}"; - $result['url'] = "{$wsProtocol}://{$host}{$portSuffix}/ws/terminal"; - } + $result['url'] = DesktopRuntime::websocketUrl('/ws/terminal'); return response()->json($result); } diff --git a/app/Http/Controllers/DesktopAuthController.php b/app/Http/Controllers/DesktopAuthController.php new file mode 100644 index 000000000..100dfbcca --- /dev/null +++ b/app/Http/Controllers/DesktopAuthController.php @@ -0,0 +1,152 @@ +lockedUserId($request); + + $users = User::query() + ->orderBy('name') + ->get(['id', 'name', 'email', 'is_admin', 'created_at', 'updated_at']) + ->map(fn (User $user): array => [ + 'id' => $user->id, + 'name' => $user->name, + 'email' => $user->email, + 'is_admin' => $user->is_admin, + 'created_at' => $user->created_at, + 'updated_at' => $user->updated_at, + ]); + + return Inertia::render('auth/desktop-login', [ + 'users' => $users, + 'setup_required' => $users->isEmpty(), + 'locked' => $lockedUserId !== null, + 'locked_user_id' => $lockedUserId, + ]); + } + + #[Post('/setup', name: 'desktop.setup')] + #[Middleware('guest')] + public function setup(Request $request): RedirectResponse + { + abort_unless(DesktopRuntime::enabled(), 404); + abort_unless(User::query()->doesntExist(), 404); + + $validated = $request->validate([ + 'name' => ['required', 'string', 'max:255'], + 'email' => ['required', 'string', 'email', 'max:255', 'unique:users,email'], + 'password' => ['required', 'string', 'min:8', 'confirmed'], + ]); + + /** @var User $user */ + $user = User::query()->create([ + 'name' => $validated['name'], + 'email' => $validated['email'], + 'password' => Hash::make($validated['password']), + 'timezone' => 'UTC', + 'is_admin' => true, + ]); + + $user->ensureHasDefaultProject(); + + Auth::login($user); + + $request->session()->forget([self::LOCKED_USER_ID, self::LOCKED_AT]); + $request->session()->regenerate(); + + return redirect()->intended(RouteServiceProvider::HOME); + } + + #[Post('/login/{user}', name: 'desktop.login.store')] + #[Middleware('guest')] + public function store(Request $request, User $user): RedirectResponse + { + abort_unless(DesktopRuntime::enabled(), 404); + + if ($this->lockedUserId($request) !== null) { + $validated = $request->validate([ + 'password' => ['required', 'string'], + ]); + + if (! Hash::check($validated['password'], $user->password)) { + throw ValidationException::withMessages([ + 'password' => __('auth.password'), + ]); + } + } + + Auth::login($user); + + $request->session()->forget([self::LOCKED_USER_ID, self::LOCKED_AT]); + $request->session()->regenerate(); + + $user->ensureHasDefaultProject(); + + return redirect()->intended(RouteServiceProvider::HOME); + } + + #[Post('/lock', name: 'desktop.lock')] + #[Middleware('auth')] + public function lock(Request $request): RedirectResponse + { + abort_unless(DesktopRuntime::enabled(), 404); + + /** @var User $user */ + $user = $request->user(); + + Auth::guard('web')->logout(); + + $request->session()->migrate(true); + $request->session()->put(self::LOCKED_USER_ID, $user->id); + $request->session()->put(self::LOCKED_AT, now()->timestamp); + $request->session()->regenerateToken(); + + return to_route('desktop.login'); + } + + private function lockedUserId(Request $request): ?int + { + $lockedUserId = $request->session()->get(self::LOCKED_USER_ID); + + if (! is_numeric($lockedUserId)) { + return null; + } + + $lockedUserId = (int) $lockedUserId; + + if (! User::query()->whereKey($lockedUserId)->exists()) { + $request->session()->forget([self::LOCKED_USER_ID, self::LOCKED_AT]); + + return null; + } + + return $lockedUserId; + } +} diff --git a/app/Http/Controllers/EventsController.php b/app/Http/Controllers/EventsController.php index 1215693e5..2cc383ca4 100644 --- a/app/Http/Controllers/EventsController.php +++ b/app/Http/Controllers/EventsController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers; use App\Actions\WebSockets\GenerateWebSocketToken; +use App\Support\DesktopRuntime; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Spatie\RouteAttributes\Attributes\Middleware; @@ -19,19 +20,7 @@ public function token(Request $request): JsonResponse 'project_id' => $request->user()->current_project_id, ]); - $appUrl = parse_url(config('app.ws_url') ?: config('app.url')); - $isSecure = ($appUrl['scheme'] ?? 'http') === 'https'; - $wsProtocol = $isSecure ? 'wss' : 'ws'; - $host = $appUrl['host'] ?? 'localhost'; - $port = $appUrl['port'] ?? ($isSecure ? 443 : 80); - - if (app()->environment('local')) { - $wsPort = config('core.ws_port', 8085); - $result['url'] = "{$wsProtocol}://{$host}:{$wsPort}/ws/events"; - } else { - $portSuffix = (($isSecure && $port == 443) || (! $isSecure && $port == 80)) ? '' : ":{$port}"; - $result['url'] = "{$wsProtocol}://{$host}{$portSuffix}/ws/events"; - } + $result['url'] = DesktopRuntime::websocketUrl('/ws/events'); return response()->json($result); } diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index 97ad73c9b..9570aecd3 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers; +use App\Support\DesktopRuntime; use Illuminate\Http\RedirectResponse; use Spatie\RouteAttributes\Attributes\Get; @@ -14,6 +15,6 @@ public function __invoke(): RedirectResponse return redirect()->route('servers'); } - return redirect()->route('login'); + return redirect()->route(DesktopRuntime::enabled() ? 'desktop.login' : 'login'); } } diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php index aa87d364e..0b7397f49 100644 --- a/app/Http/Middleware/Authenticate.php +++ b/app/Http/Middleware/Authenticate.php @@ -2,6 +2,7 @@ namespace App\Http\Middleware; +use App\Support\DesktopRuntime; use Illuminate\Auth\Middleware\Authenticate as Middleware; use Illuminate\Http\Request; @@ -12,6 +13,10 @@ class Authenticate extends Middleware */ protected function redirectTo(Request $request): ?string { - return $request->expectsJson() ? null : url('/'); + if ($request->expectsJson()) { + return null; + } + + return route(DesktopRuntime::enabled() ? 'desktop.login' : 'login'); } } diff --git a/app/Http/Middleware/HandleInertiaRequests.php b/app/Http/Middleware/HandleInertiaRequests.php index 85ded423c..28d95a6a5 100644 --- a/app/Http/Middleware/HandleInertiaRequests.php +++ b/app/Http/Middleware/HandleInertiaRequests.php @@ -10,6 +10,7 @@ use App\Models\Server; use App\Models\Site; use App\Models\User; +use App\Support\DesktopRuntime; use Illuminate\Foundation\Inspiring; use Illuminate\Http\Request; use Inertia\Middleware; @@ -86,6 +87,9 @@ public function share(Request $request): array 'env' => config('app.env'), 'demo' => config('app.demo'), 'quote' => ['message' => trim($message), 'author' => trim($author)], + 'desktop' => [ + 'enabled' => DesktopRuntime::enabled(), + ], 'auth' => $user ? [ 'user' => UserResource::make($user->load('projects')), 'currentProject' => ProjectResource::make($currentProject), diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index 8bbdc4ea2..ac751e361 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -5,6 +5,7 @@ use App\Actions\User\ResetUserPassword; use App\Actions\User\UpdateUserPassword; use App\Actions\User\UpdateUserProfileInformation; +use App\Support\DesktopRuntime; use Illuminate\Cache\RateLimiting\Limit; use Illuminate\Http\Request; use Illuminate\Support\Facades\RateLimiter; @@ -25,14 +26,24 @@ public function boot(): void Fortify::resetUserPasswordsUsing(ResetUserPassword::class); Fortify::redirectUserForTwoFactorAuthenticationUsing(RedirectIfTwoFactorAuthenticatable::class); - Fortify::loginView(fn () => Inertia::render('auth/login')); - Fortify::requestPasswordResetLinkView(fn () => Inertia::render('auth/forgot-password')); - Fortify::confirmPasswordView(fn () => Inertia::render('auth/confirm-password')); - Fortify::resetPasswordView(fn (Request $request) => Inertia::render('auth/reset-password', [ - 'email' => $request->email, - 'token' => $request->route('token'), - ])); - Fortify::twoFactorChallengeView(fn () => Inertia::render('auth/two-factor')); + Fortify::loginView(fn () => DesktopRuntime::enabled() + ? to_route('desktop.login') + : Inertia::render('auth/login')); + Fortify::requestPasswordResetLinkView(fn () => DesktopRuntime::enabled() + ? to_route('desktop.login') + : Inertia::render('auth/forgot-password')); + Fortify::confirmPasswordView(fn () => DesktopRuntime::enabled() + ? to_route('desktop.login') + : Inertia::render('auth/confirm-password')); + Fortify::resetPasswordView(fn (Request $request) => DesktopRuntime::enabled() + ? to_route('desktop.login') + : Inertia::render('auth/reset-password', [ + 'email' => $request->email, + 'token' => $request->route('token'), + ])); + Fortify::twoFactorChallengeView(fn () => DesktopRuntime::enabled() + ? to_route('desktop.login') + : Inertia::render('auth/two-factor')); RateLimiter::for('login', function (Request $request) { $throttleKey = Str::transliterate(Str::lower($request->input(Fortify::username()).'|'.$request->ip())); diff --git a/app/Support/DesktopRuntime.php b/app/Support/DesktopRuntime.php new file mode 100644 index 000000000..f936ad594 --- /dev/null +++ b/app/Support/DesktopRuntime.php @@ -0,0 +1,76 @@ +environment('local') || self::enabled()) { + return "{$wsProtocol}://{$host}:".self::websocketPort().$path; + } + + $portSuffix = (($isSecure && $port == 443) || (! $isSecure && $port == 80)) ? '' : ":{$port}"; + + return "{$wsProtocol}://{$host}{$portSuffix}{$path}"; + } + + private static function joinPath(string $basePath, string $path): string + { + if ($path === '') { + return $basePath; + } + + return rtrim($basePath, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.ltrim($path, DIRECTORY_SEPARATOR); + } +} diff --git a/app/Support/helpers.php b/app/Support/helpers.php index 56debe476..e996b73bb 100755 --- a/app/Support/helpers.php +++ b/app/Support/helpers.php @@ -14,12 +14,12 @@ function generate_public_key(string $privateKeyPath, string $publicKeyPath): void { chmod($privateKeyPath, 0400); - exec("ssh-keygen -y -f {$privateKeyPath} > {$publicKeyPath}"); + exec('ssh-keygen -y -f '.escapeshellarg($privateKeyPath).' > '.escapeshellarg($publicKeyPath)); } function generate_key_pair(string $path): void { - exec("ssh-keygen -t ed25519 -m PEM -N '' -f {$path}"); + exec('ssh-keygen -t ed25519 -m PEM -N \'\' -f '.escapeshellarg($path)); chmod($path, 0400); } diff --git a/bootstrap/app.php b/bootstrap/app.php index b4a5fc363..c406b154a 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -20,6 +20,20 @@ $_ENV['APP_BASE_PATH'] ?? dirname(__DIR__) ); +$envValue = static function (string $key): ?string { + $value = $_ENV[$key] ?? getenv($key); + + return is_string($value) && $value !== '' ? $value : null; +}; + +if (($environmentPath = $envValue('VITO_ENV_PATH')) !== null) { + $app->useEnvironmentPath($environmentPath); +} + +if (($storagePath = $envValue('VITO_STORAGE_PATH')) !== null) { + $app->useStoragePath($storagePath); +} + /* |-------------------------------------------------------------------------- | Bind Important Interfaces diff --git a/config/desktop.php b/config/desktop.php new file mode 100644 index 000000000..5b0e79cff --- /dev/null +++ b/config/desktop.php @@ -0,0 +1,8 @@ + filter_var(env('VITO_DESKTOP', false), FILTER_VALIDATE_BOOL), + 'data_path' => env('VITO_DATA_PATH'), + 'env_path' => env('VITO_ENV_PATH'), + 'storage_path' => env('VITO_STORAGE_PATH'), +]; diff --git a/config/queue.php b/config/queue.php index 89056c241..fdb353b31 100755 --- a/config/queue.php +++ b/config/queue.php @@ -53,6 +53,15 @@ 'after_commit' => false, ], + 'database' => [ + 'driver' => 'database', + 'connection' => env('DB_CONNECTION', 'sqlite'), + 'table' => 'jobs', + 'queue' => env('DB_QUEUE', 'default'), + 'retry_after' => max(300, (int) env('BACKUP_RUN_TIMEOUT', 3600)) + 60, + 'after_commit' => false, + ], + 'ssh' => [ 'driver' => 'redis', 'connection' => 'default', diff --git a/desktop/README.md b/desktop/README.md new file mode 100644 index 000000000..0ff5f53d1 --- /dev/null +++ b/desktop/README.md @@ -0,0 +1,12 @@ +# Vito Desktop + +This is the initial desktop shell for Vito. It runs the Laravel app as a local backend process, supervises the queue, scheduler, and websocket workers, then opens the existing Inertia UI in Electron. + +Development: + +```sh +npm install --prefix desktop +npm run desktop:dev +``` + +The dev shell uses the repository root as the Laravel app path and the system PHP binary unless `PHP_BINARY` is set. Packaged builds are expected to place a prepared Laravel app in `desktop-build/app` and platform PHP binaries in `desktop/resources/bin`. diff --git a/desktop/electron-builder.yml b/desktop/electron-builder.yml new file mode 100644 index 000000000..881cd56c0 --- /dev/null +++ b/desktop/electron-builder.yml @@ -0,0 +1,27 @@ +appId: com.vitodeploy.vito +productName: Vito +directories: + output: release +files: + - dist/**/* + - package.json +extraResources: + - from: ../desktop-build/app + to: app + filter: + - "**/*" + - from: resources/bin + to: bin + filter: + - "**/*" +mac: + category: public.app-category.developer-tools + target: + - dmg + - zip +win: + target: + - nsis +linux: + target: + - AppImage diff --git a/desktop/package-lock.json b/desktop/package-lock.json new file mode 100644 index 000000000..3f4b02318 --- /dev/null +++ b/desktop/package-lock.json @@ -0,0 +1,3627 @@ +{ + "name": "@vitodeploy/vito-desktop", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@vitodeploy/vito-desktop", + "version": "0.1.0", + "devDependencies": { + "@types/node": "^26.1.0", + "electron": "^43.0.0", + "electron-builder": "^26.15.3", + "typescript": "^6.0.3" + } + }, + "node_modules/@electron-internal/extract-zip": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@electron-internal/extract-zip/-/extract-zip-1.0.4.tgz", + "integrity": "sha512-Zr1Vs7E9tpCNhZHDAbFVXc2gEVCG9RqPDjrno5+bdgB6LRAuvgyMHJut4NCVyYwtAieapMzc3fiQ3CSTi75ARg==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@electron/asar": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.4.1.tgz", + "integrity": "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^5.0.0", + "glob": "^7.1.6", + "minimatch": "^3.0.4" + }, + "bin": { + "asar": "bin/asar.js" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/@electron/asar/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@electron/asar/node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@electron/asar/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@electron/fuses": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@electron/fuses/-/fuses-1.8.0.tgz", + "integrity": "sha512-zx0EIq78WlY/lBb1uXlziZmDZI4ubcCXIMJ4uGjXzZW0nS19TjSPeXPAjzzTmKQlJUZm0SbmZhPKP7tuQ1SsEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.1", + "fs-extra": "^9.0.1", + "minimist": "^1.2.5" + }, + "bin": { + "electron-fuses": "dist/bin.js" + } + }, + "node_modules/@electron/fuses/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@electron/get": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-5.0.0.tgz", + "integrity": "sha512-pjoBpru1KdEtcExBnuHAP1cAc/5faoedw0hzJkL3o4/IJp7HNF1+fbrdxT3gMYRX2oJfvnA/WXeCTVQpYYxyJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "env-paths": "^3.0.0", + "graceful-fs": "^4.2.11", + "progress": "^2.0.3", + "semver": "^7.6.3", + "sumchecker": "^3.0.1" + }, + "engines": { + "node": ">=22.12.0" + }, + "optionalDependencies": { + "undici": "^7.24.4" + } + }, + "node_modules/@electron/notarize": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-2.5.0.tgz", + "integrity": "sha512-jNT8nwH1f9X5GEITXaQ8IF/KdskvIkOFfB2CvwumsveVidzpSc+mvhhTMdAGSYF3O+Nq49lJ7y+ssODRXu06+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "fs-extra": "^9.0.1", + "promise-retry": "^2.0.1" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@electron/notarize/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@electron/osx-sign": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@electron/osx-sign/-/osx-sign-1.3.3.tgz", + "integrity": "sha512-KZ8mhXvWv2rIEgMbWZ4y33bDHyUKMXnx4M0sTyPNK/vcB81ImdeY9Ggdqy0SWbMDgmbqyQ+phgejh6V3R2QuSg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "compare-version": "^0.1.2", + "debug": "^4.3.4", + "fs-extra": "^10.0.0", + "isbinaryfile": "^4.0.8", + "minimist": "^1.2.6", + "plist": "^3.0.5" + }, + "bin": { + "electron-osx-flat": "bin/electron-osx-flat.js", + "electron-osx-sign": "bin/electron-osx-sign.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@electron/osx-sign/node_modules/isbinaryfile": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", + "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/@electron/rebuild": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@electron/rebuild/-/rebuild-4.1.0.tgz", + "integrity": "sha512-GFky+1JeO8fpSqtZCh+2wFRN/MVbAhm7tXI2M7BA1Os79OR8LEoxRtAbRPyxvmKWhEMF/p10rNJkkgP1klI13g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@malept/cross-spawn-promise": "^2.0.0", + "debug": "^4.1.1", + "node-abi": "^4.2.0", + "node-api-version": "^0.2.1", + "node-gyp": "^12.2.0", + "read-binary-file-arch": "^1.0.6" + }, + "bin": { + "electron-rebuild": "lib/cli.js" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@electron/universal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@electron/universal/-/universal-2.0.3.tgz", + "integrity": "sha512-Wn9sPYIVFRFl5HmwMJkARCCf7rqK/EurkfQ/rJZ14mHP3iYTjZSIOSVonEAnhWeAXwtw7zOekGRlc6yTtZ0t+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron/asar": "^3.3.1", + "@malept/cross-spawn-promise": "^2.0.0", + "debug": "^4.3.1", + "dir-compare": "^4.2.0", + "fs-extra": "^11.1.1", + "minimatch": "^9.0.3", + "plist": "^3.1.0" + }, + "engines": { + "node": ">=16.4" + } + }, + "node_modules/@electron/universal/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@electron/universal/node_modules/brace-expansion": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", + "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@electron/universal/node_modules/fs-extra": { + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.6.tgz", + "integrity": "sha512-w8ZNZr2mKIc7qeNaQ9AVPT1+iFaI+Avd4xudVOvdDJ8VytREi1Ft5Ih7hd9jjehod8vAM5GMsfQ/TpPf4EyoEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@electron/universal/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@electron/windows-sign": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@electron/windows-sign/-/windows-sign-1.2.2.tgz", + "integrity": "sha512-dfZeox66AvdPtb2lD8OsIIQh12Tp0GNCRUDfBHIKGpbmopZto2/A8nSpYYLoedPIHpqkeblZ/k8OV0Gy7PYuyQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "peer": true, + "dependencies": { + "cross-dirname": "^0.1.0", + "debug": "^4.3.4", + "fs-extra": "^11.1.1", + "minimist": "^1.2.8", + "postject": "^1.0.0-alpha.6" + }, + "bin": { + "electron-windows-sign": "bin/electron-windows-sign.js" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@electron/windows-sign/node_modules/fs-extra": { + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.6.tgz", + "integrity": "sha512-w8ZNZr2mKIc7qeNaQ9AVPT1+iFaI+Avd4xudVOvdDJ8VytREi1Ft5Ih7hd9jjehod8vAM5GMsfQ/TpPf4EyoEA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@malept/cross-spawn-promise": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-2.0.0.tgz", + "integrity": "sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/malept" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/subscription/pkg/npm-.malept-cross-spawn-promise?utm_medium=referral&utm_source=npm_fund" + } + ], + "license": "Apache-2.0", + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/@malept/flatpak-bundler": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@malept/flatpak-bundler/-/flatpak-bundler-0.4.0.tgz", + "integrity": "sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "fs-extra": "^9.0.0", + "lodash": "^4.17.15", + "tmp-promise": "^3.0.2" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@malept/flatpak-bundler/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@noble/hashes": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.2.0.tgz", + "integrity": "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@peculiar/asn1-schema": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.8.0.tgz", + "integrity": "sha512-7YT0U/ze0tF2QOBbE15gKZwy5tvgGyLRiRHLzhlbOpf7BT032oBSd0haZqXn5W6l26WLlu3dyxzjM+2638/z2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/utils": "^2.0.2", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/json-schema": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@peculiar/json-schema/-/json-schema-1.1.12.tgz", + "integrity": "sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@peculiar/utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@peculiar/utils/-/utils-2.0.3.tgz", + "integrity": "sha512-+oL3HPFRIZ1St2K50lWCXiioIgSoxzz7R1J3uF6neO2yl1sgmpgY6XXJH4BdpoDkMWznQTeYF6oWNDZLCdQ4eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/webcrypto": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@peculiar/webcrypto/-/webcrypto-1.7.1.tgz", + "integrity": "sha512-ODOov0sGMJMf3jPonOkgGqPknTsu+DdQ7kD++gz8aI+aFMOMHFbWAA2taqXXVTdP+OTOQR/znGvSpmkeI0WTYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.7.0", + "@peculiar/json-schema": "^1.1.12", + "@peculiar/utils": "^2.0.2", + "tslib": "^2.8.1", + "webcrypto-core": "^1.9.2" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "dev": true, + "license": "MIT", + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/fs-extra": { + "version": "9.0.13", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", + "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/keyv": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.0.tgz", + "integrity": "sha512-O0A1G3xPGy4w7AgQdAQYUlQ+BKk2Oovw8eRpofyp5KdBZULnbe+WqaOVNrm705SHphCiG4XHsACrSmPu1f+Kgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~8.3.0" + } + }, + "node_modules/@types/responselike": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.13", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.13.tgz", + "integrity": "sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/abbrev": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-4.0.0.tgz", + "integrity": "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/app-builder-lib": { + "version": "26.15.3", + "resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-26.15.3.tgz", + "integrity": "sha512-2VnyWkqsP5v5XbBhL3tD5Syx8iNPBYsoU7kY4S2fz7wg8Rj/nztWKCUzGKaFRTv0Xwf3/H058CR1Kvtd/3lRow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron/asar": "3.4.1", + "@electron/fuses": "^1.8.0", + "@electron/get": "^3.0.0", + "@electron/notarize": "2.5.0", + "@electron/osx-sign": "1.3.3", + "@electron/rebuild": "^4.0.4", + "@electron/universal": "2.0.3", + "@malept/flatpak-bundler": "^0.4.0", + "@noble/hashes": "^2.2.0", + "@peculiar/webcrypto": "^1.7.1", + "@types/fs-extra": "9.0.13", + "ajv": "^8.18.0", + "asn1js": "^3.0.10", + "async-exit-hook": "^2.0.1", + "builder-util": "26.15.3", + "builder-util-runtime": "9.7.0", + "chromium-pickle-js": "^0.2.0", + "ci-info": "4.3.1", + "debug": "^4.3.4", + "dotenv": "^16.4.5", + "dotenv-expand": "^11.0.6", + "ejs": "^3.1.8", + "electron-publish": "26.15.3", + "fs-extra": "^10.1.0", + "hosted-git-info": "^4.1.0", + "isbinaryfile": "^5.0.0", + "jiti": "^2.4.2", + "js-yaml": "^4.1.0", + "json5": "^2.2.3", + "lazy-val": "^1.0.5", + "minimatch": "^10.2.5", + "pkijs": "^3.4.0", + "plist": "3.1.0", + "proper-lockfile": "^4.1.2", + "resedit": "^1.7.0", + "semver": "~7.7.3", + "tar": "^7.5.7", + "temp-file": "^3.4.0", + "tiny-async-pool": "1.3.0", + "unzipper": "^0.12.3", + "which": "^5.0.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "dmg-builder": "26.15.3", + "electron-builder-squirrel-windows": "26.15.3" + } + }, + "node_modules/app-builder-lib/node_modules/@electron/get": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-3.1.0.tgz", + "integrity": "sha512-F+nKc0xW+kVbBRhFzaMgPy3KwmuNTYX1fx6+FxxoSnNgwYX6LD7AKBTWkU0MQ6IBoe7dz069CNkR673sPAgkCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "env-paths": "^2.2.0", + "fs-extra": "^8.1.0", + "got": "^11.8.5", + "progress": "^2.0.3", + "semver": "^6.2.0", + "sumchecker": "^3.0.1" + }, + "engines": { + "node": ">=14" + }, + "optionalDependencies": { + "global-agent": "^3.0.0" + } + }, + "node_modules/app-builder-lib/node_modules/@electron/get/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/app-builder-lib/node_modules/@electron/get/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/app-builder-lib/node_modules/ci-info": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", + "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/app-builder-lib/node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/app-builder-lib/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/app-builder-lib/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/app-builder-lib/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/asn1js": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.10.tgz", + "integrity": "sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.5", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-exit-hook": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-2.0.1.tgz", + "integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/aws4": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", + "dev": true, + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/boolean": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", + "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/brace-expansion": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", + "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/builder-util": { + "version": "26.15.3", + "resolved": "https://registry.npmjs.org/builder-util/-/builder-util-26.15.3.tgz", + "integrity": "sha512-q2hn7Mbo2nFNkVekPiHFx6Nfo3hURmES3tfBn+k5Pqxl2RkmP3QGqZUhH/q9Pch/4G05NRhPjDlVj1O8q4Txvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/debug": "^4.1.6", + "builder-util-runtime": "9.7.0", + "chalk": "^4.1.2", + "cross-spawn": "^7.0.6", + "debug": "^4.3.4", + "fs-extra": "^10.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "js-yaml": "^4.1.0", + "sanitize-filename": "^1.6.3", + "source-map-support": "^0.5.19", + "stat-mode": "^1.0.0", + "temp-file": "^3.4.0", + "tiny-async-pool": "1.3.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/builder-util-runtime": { + "version": "9.7.0", + "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.7.0.tgz", + "integrity": "sha512-g/kR520giAFYkSXTzcmF3kqQq7wi8F6N6SzeDgZrqTBN+VHdmgWOyTdD1yD7AATDId/yXLvuP34CxW46/BwCdw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "sax": "^1.2.4" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/bytestreamjs": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/bytestreamjs/-/bytestreamjs-2.0.1.tgz", + "integrity": "sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/cacheable-request": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/chromium-pickle-js": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz", + "integrity": "sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/ci-info": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/compare-version": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz", + "integrity": "sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-dirname": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/cross-dirname/-/cross-dirname-0.1.0.tgz", + "integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/dir-compare": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/dir-compare/-/dir-compare-4.2.0.tgz", + "integrity": "sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimatch": "^3.0.5", + "p-limit": "^3.1.0 " + } + }, + "node_modules/dir-compare/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/dir-compare/node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/dir-compare/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/dmg-builder": { + "version": "26.15.3", + "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-26.15.3.tgz", + "integrity": "sha512-O3zJUFUYHJKgzPqioHxfxzBzlSC1eXCSr79gMSBKBP5AgjjpmrydMsMLotEg9fAJF36vdUncb+4ndRNxoPdlSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "app-builder-lib": "26.15.3", + "builder-util": "26.15.3", + "fs-extra": "^10.1.0", + "js-yaml": "^4.1.0" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-expand": { + "version": "11.0.7", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.7.tgz", + "integrity": "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dotenv": "^16.4.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "readable-stream": "^2.0.2" + } + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron": { + "version": "43.0.0", + "resolved": "https://registry.npmjs.org/electron/-/electron-43.0.0.tgz", + "integrity": "sha512-PV60GsWU6qufhuOhw3n+Yix3WPDcqDtBqE8orbEQGQGHEkgp9o/JCPgb7L4vIL0r1HnfPdqSRtboOTqbDkcFDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron-internal/extract-zip": "^1.0.1", + "@electron/get": "^5.0.0", + "@types/node": "^24.9.0" + }, + "bin": { + "electron": "cli.js", + "install-electron": "install.js" + }, + "engines": { + "node": ">= 22.12.0" + } + }, + "node_modules/electron-builder": { + "version": "26.15.3", + "resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-26.15.3.tgz", + "integrity": "sha512-a1KM5heqS3gQCZzizXEI8RjJy3QVogULPdeSknt76uLDpBIW/HDGsMg/XgP0riP6PI9COsRvFITKKGDqA8fJxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "app-builder-lib": "26.15.3", + "builder-util": "26.15.3", + "builder-util-runtime": "9.7.0", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "dmg-builder": "26.15.3", + "fs-extra": "^10.1.0", + "lazy-val": "^1.0.5", + "simple-update-notifier": "2.0.0", + "yargs": "^17.6.2" + }, + "bin": { + "electron-builder": "cli.js", + "install-app-deps": "install-app-deps.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/electron-builder-squirrel-windows": { + "version": "26.15.3", + "resolved": "https://registry.npmjs.org/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-26.15.3.tgz", + "integrity": "sha512-Jc19XPV9y9+2bAdZPkXuVNGNIEFBq9poHC61l8Kv6FdK7DRG3+Ic0rerC0DXOaeHNz8yW0fg/JnF8GQROOF5MA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "app-builder-lib": "26.15.3", + "builder-util": "26.15.3", + "electron-winstaller": "5.4.0" + } + }, + "node_modules/electron-publish": { + "version": "26.15.3", + "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-26.15.3.tgz", + "integrity": "sha512-g/2bn8YTavY4cuS5F+jOS7zmZbXXBV8KZ8yHKfJjFPoKtzBqrpCdNPxBd3tqdBwP7BVd0lGzf7Bk2s0KesWZ4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/fs-extra": "^9.0.11", + "aws4": "^1.13.2", + "builder-util": "26.15.3", + "builder-util-runtime": "9.7.0", + "chalk": "^4.1.2", + "form-data": "^4.0.5", + "fs-extra": "^10.1.0", + "lazy-val": "^1.0.5", + "mime": "^2.5.2" + } + }, + "node_modules/electron-winstaller": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/electron-winstaller/-/electron-winstaller-5.4.0.tgz", + "integrity": "sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@electron/asar": "^3.2.1", + "debug": "^4.1.1", + "fs-extra": "^7.0.1", + "lodash": "^4.17.21", + "temp": "^0.9.0" + }, + "engines": { + "node": ">=8.0.0" + }, + "optionalDependencies": { + "@electron/windows-sign": "^1.1.2" + } + }, + "node_modules/electron-winstaller/node_modules/fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/electron-winstaller/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "peer": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-winstaller/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/electron/node_modules/@types/node": { + "version": "24.13.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.2.tgz", + "integrity": "sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/electron/node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/env-paths": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", + "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/exponential-backoff": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.3.tgz", + "integrity": "sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/filelist": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz", + "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", + "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/global-agent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", + "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "es6-error": "^4.1.1", + "matcher": "^3.0.0", + "roarr": "^2.15.3", + "semver": "^7.3.2", + "serialize-error": "^7.0.1" + }, + "engines": { + "node": ">=10.0" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/isbinaryfile": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.7.tgz", + "integrity": "sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/isexe": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", + "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/jake": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.6", + "filelist": "^1.0.4", + "picocolors": "^1.1.1" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-yaml": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", + "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/lazy-val": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz", + "integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/matcher": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", + "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "escape-string-regexp": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-abi": { + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-4.33.0.tgz", + "integrity": "sha512-vLBWCKb+7LWsX+TbfzWOkw0W81m377tyx3hOweBTjO43CXZnRGS1/JPWs20fr0PgZyDXk6ROYrylsEycK8raDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.6.3" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/node-api-version": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-api-version/-/node-api-version-0.2.1.tgz", + "integrity": "sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + } + }, + "node_modules/node-gyp": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.4.0.tgz", + "integrity": "sha512-OMcPNvqTCFUnNaBlmdgq+lfNqY7gTiSmNRDjY3uAXRyudeKZEZxu3CLtjMQrx4zZxCX2b/mpNqTtwuCJgXhHkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "graceful-fs": "^4.2.6", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "tar": "^7.5.4", + "tinyglobby": "^0.2.12", + "undici": "^6.25.0", + "which": "^6.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/node-gyp/node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/node-gyp/node_modules/isexe": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=20" + } + }, + "node_modules/node-gyp/node_modules/undici": { + "version": "6.27.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.27.0.tgz", + "integrity": "sha512-YmfV3YnEDzXRC5lZ2jWtWWHKGUm1zIt8AhesR1tens+HTNv+YZlN/dp6G727LOvMJ8xjP9Be7Y2Sdr96LDm+pg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/node-gyp/node_modules/which": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", + "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^4.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/nopt": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-9.0.0.tgz", + "integrity": "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^4.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pe-library": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/pe-library/-/pe-library-0.4.1.tgz", + "integrity": "sha512-eRWB5LBz7PpDu4PUlwT0PhnQfTQJlDDdPa35urV4Osrm0t0AqQFGn+UIkU3klZvwJ8KPO3VbBFsXquA6p6kqZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jet2jet" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkijs": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/pkijs/-/pkijs-3.4.0.tgz", + "integrity": "sha512-emEcLuomt2j03vxD54giVB4SxTjnsqkU692xZOZXHDVoYyypEm+b3jpiTcc+Cf+myooc+/Ly0z01jqeNHVgJGw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@noble/hashes": "1.4.0", + "asn1js": "^3.0.6", + "bytestreamjs": "^2.0.1", + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.3", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/pkijs/node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/plist": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz", + "integrity": "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xmldom/xmldom": "^0.8.8", + "base64-js": "^1.5.1", + "xmlbuilder": "^15.1.1" + }, + "engines": { + "node": ">=10.4.0" + } + }, + "node_modules/postject": { + "version": "1.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/postject/-/postject-1.0.0-alpha.6.tgz", + "integrity": "sha512-b9Eb8h2eVqNE8edvKdwqkrY6O7kAwmI8kcnBv1NScolYJbo59XUF0noFq+lxbC1yN20bmC0WBEbDC5H/7ASb0A==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "commander": "^9.4.0" + }, + "bin": { + "postject": "dist/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/postject/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/pvtsutils": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz", + "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/pvutils": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.5.tgz", + "integrity": "sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-binary-file-arch": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/read-binary-file-arch/-/read-binary-file-arch-1.0.6.tgz", + "integrity": "sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "bin": { + "read-binary-file-arch": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resedit": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/resedit/-/resedit-1.7.2.tgz", + "integrity": "sha512-vHjcY2MlAITJhC0eRD/Vv8Vlgmu9Sd3LX9zZvtGzU5ZImdTN3+d6e/4mnTyV8vEbyf1sgNIrWxhWlrys52OkEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pe-library": "^0.4.1" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jet2jet" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lowercase-keys": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/roarr": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", + "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "detect-node": "^2.0.4", + "globalthis": "^1.0.1", + "json-stringify-safe": "^5.0.1", + "semver-compare": "^1.0.0", + "sprintf-js": "^1.1.2" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/sanitize-filename": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.4.tgz", + "integrity": "sha512-9ZyI08PsvdQl2r/bBIGubpVdR3RR9sY6RDiWFPreA21C/EFlQhmgo20UZlNjZMMZNubusLhAQozkA0Od5J21Eg==", + "dev": true, + "license": "WTFPL OR ISC", + "dependencies": { + "truncate-utf8-bytes": "^1.0.0" + } + }, + "node_modules/sax": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/serialize-error": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", + "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "type-fest": "^0.13.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/stat-mode": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-1.0.0.tgz", + "integrity": "sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/sumchecker": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", + "integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.1.0" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar": { + "version": "7.5.19", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.19.tgz", + "integrity": "sha512-4LeEWl96twnS2Q7Bz4MGqgazLqO+hJN63GZxXoIqh1T3VweYD997gbU1ItNsQafqqXTXd5WFyFdReLtwvRBNiw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/temp": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/temp/-/temp-0.9.4.tgz", + "integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "mkdirp": "^0.5.1", + "rimraf": "~2.6.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/temp-file": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/temp-file/-/temp-file-3.4.0.tgz", + "integrity": "sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-exit-hook": "^2.0.1", + "fs-extra": "^10.0.0" + } + }, + "node_modules/tiny-async-pool": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tiny-async-pool/-/tiny-async-pool-1.3.0.tgz", + "integrity": "sha512-01EAw5EDrcVrdgyCLgoSPvqznC0sVxDSVeiOz09FUpjh71G79VCqneOr+xvt7T1r76CF6ZZfPjHorN2+d+3mqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^5.5.0" + } + }, + "node_modules/tiny-async-pool/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tmp": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz", + "integrity": "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/tmp-promise": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-3.0.3.tgz", + "integrity": "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tmp": "^0.2.0" + } + }, + "node_modules/truncate-utf8-bytes": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", + "integrity": "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==", + "dev": true, + "license": "WTFPL", + "dependencies": { + "utf8-byte-length": "^1.0.1" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz", + "integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/undici-types": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", + "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unzipper": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.12.5.tgz", + "integrity": "sha512-tXYOi9R57Uj/2Z25SOs5RRSzq886MBQj2gY8dPL+xl/kv6s6SvByoKfAtvfVeEuhntWDgjd2o9p2lb4TVPAz0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "bluebird": "~3.7.2", + "duplexer2": "~0.1.4", + "fs-extra": "11.3.1", + "graceful-fs": "^4.2.2", + "node-int64": "^0.4.0" + } + }, + "node_modules/unzipper/node_modules/fs-extra": { + "version": "11.3.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.1.tgz", + "integrity": "sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/utf8-byte-length": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz", + "integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==", + "dev": true, + "license": "(WTFPL OR MIT)" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/webcrypto-core": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.9.2.tgz", + "integrity": "sha512-gsXecm82UQNlTBURJGuqOWy1Ww08S3kZUcr3aOJS02Pk0xLtkfeUAVC0u0xhgdonFme80edSJUIJyuvL/7250Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.7.0", + "@peculiar/json-schema": "^1.1.12", + "@peculiar/utils": "^2.0.2", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + } + }, + "node_modules/which": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/xmlbuilder": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", + "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/desktop/package.json b/desktop/package.json new file mode 100644 index 000000000..ff51f6d2c --- /dev/null +++ b/desktop/package.json @@ -0,0 +1,18 @@ +{ + "name": "@vitodeploy/vito-desktop", + "private": true, + "version": "0.1.0", + "type": "module", + "main": "dist/main.js", + "scripts": { + "build": "tsc -p tsconfig.json", + "dev": "electron .", + "package": "electron-builder --config electron-builder.yml" + }, + "devDependencies": { + "@types/node": "^26.1.0", + "electron": "^43.0.0", + "electron-builder": "^26.15.3", + "typescript": "^6.0.3" + } +} diff --git a/desktop/src/env-file.ts b/desktop/src/env-file.ts new file mode 100644 index 000000000..7cd565263 --- /dev/null +++ b/desktop/src/env-file.ts @@ -0,0 +1,61 @@ +import { randomBytes } from 'node:crypto'; +import { existsSync, readFileSync, writeFileSync } from 'node:fs'; + +const SIMPLE_VALUE = /^[A-Za-z0-9_./:@-]*$/; + +export function readEnvFile(path: string): Record { + if (!existsSync(path)) { + return {}; + } + + const values: Record = {}; + const lines = readFileSync(path, 'utf8').split(/\r?\n/); + + for (const line of lines) { + const trimmed = line.trim(); + if (!trimmed || trimmed.startsWith('#')) { + continue; + } + + const equalsAt = trimmed.indexOf('='); + if (equalsAt === -1) { + continue; + } + + const key = trimmed.slice(0, equalsAt).trim(); + let value = trimmed.slice(equalsAt + 1).trim(); + + if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) { + value = value.slice(1, -1); + } + + values[key] = value; + } + + return values; +} + +export function writeEnvFile(path: string, values: Record): void { + const lines = Object.entries(values).map(([key, value]) => `${key}=${formatEnvValue(value)}`); + writeFileSync(path, `${lines.join('\n')}\n`, 'utf8'); +} + +export function appKey(): string { + return `base64:${randomBytes(32).toString('base64')}`; +} + +export function secret(): string { + return randomBytes(32).toString('hex'); +} + +function formatEnvValue(value: string): string { + if (value === '') { + return ''; + } + + if (SIMPLE_VALUE.test(value)) { + return value; + } + + return `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`; +} diff --git a/desktop/src/health-check.ts b/desktop/src/health-check.ts new file mode 100644 index 000000000..f6ffa2d80 --- /dev/null +++ b/desktop/src/health-check.ts @@ -0,0 +1,24 @@ +export async function waitForHttp(url: string, timeoutMs: number): Promise { + const deadline = Date.now() + timeoutMs; + let lastError: unknown; + + while (Date.now() < deadline) { + try { + const response = await fetch(url); + if (response.ok) { + return; + } + lastError = new Error(`Health check failed with ${response.status}`); + } catch (error) { + lastError = error; + } + + await delay(500); + } + + throw lastError instanceof Error ? lastError : new Error('Health check timed out'); +} + +function delay(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} diff --git a/desktop/src/laravel-runtime.ts b/desktop/src/laravel-runtime.ts new file mode 100644 index 000000000..cc8e9b04c --- /dev/null +++ b/desktop/src/laravel-runtime.ts @@ -0,0 +1,89 @@ +import { existsSync } from 'node:fs'; +import { join } from 'node:path'; +import { DesktopLog } from './logging.js'; +import { ProcessSupervisor, runCommand } from './process-supervisor.js'; +import { RuntimeContext } from './runtime-paths.js'; + +export async function prepareLaravelRuntime(context: RuntimeContext, log: DesktopLog): Promise { + await artisan(context, log, ['optimize:clear'], 'artisan:optimize-clear', 120000); + await artisan(context, log, ['migrate', '--force'], 'artisan:migrate', 120000); + await artisan(context, log, ['optimize'], 'artisan:optimize', 120000); + await ensureSshKeys(context, log); +} + +export function startLaravelProcesses(context: RuntimeContext, supervisor: ProcessSupervisor): void { + const common = { + cwd: context.appRoot, + env: context.env, + }; + + supervisor.start({ + name: 'vito-websocket', + command: context.phpBinary, + args: ['artisan', 'ws:serve', '--host=127.0.0.1', `--port=${context.websocketPort}`], + ...common, + }); + + supervisor.start({ + name: 'vito-queue', + command: context.phpBinary, + args: [ + 'artisan', + 'queue:work', + 'database', + '--queue=default,ssh,ssh-certbot', + '--sleep=3', + '--timeout=3660', + '--tries=1', + ], + ...common, + }); + + supervisor.start({ + name: 'vito-scheduler', + command: context.phpBinary, + args: ['artisan', 'schedule:work'], + ...common, + }); + + supervisor.start({ + name: 'vito-http', + command: context.phpBinary, + args: ['artisan', 'serve', '--host=127.0.0.1', `--port=${context.httpPort}`, '--no-reload'], + maxRestarts: 3, + restartWindowMs: 60000, + ...common, + }); +} + +function artisan( + context: RuntimeContext, + log: DesktopLog, + args: string[], + name: string, + timeoutMs?: number, +): Promise { + return runCommand(context.phpBinary, ['artisan', ...args], { + cwd: context.appRoot, + env: context.env, + log, + name, + timeoutMs, + }); +} + +async function ensureSshKeys(context: RuntimeContext, log: DesktopLog): Promise { + const publicKey = join(context.storagePath, 'ssh-public.key'); + const privateKey = join(context.storagePath, 'ssh-private.pem'); + + if (existsSync(publicKey) && existsSync(privateKey)) { + return; + } + + try { + await artisan(context, log, ['ssh-key:generate'], 'artisan:ssh-key-generate', 120000); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + log.error(`Unable to generate SSH keys during desktop startup: ${message}`); + } +} diff --git a/desktop/src/loading-page.ts b/desktop/src/loading-page.ts new file mode 100644 index 000000000..419a81ace --- /dev/null +++ b/desktop/src/loading-page.ts @@ -0,0 +1,63 @@ +export function loadingPage(message: string): string { + return dataPage('Starting Vito', message); +} + +export function errorPage(message: string): string { + return dataPage('Vito could not start', message); +} + +function dataPage(title: string, message: string): string { + const html = ` + + + + + ${escapeHtml(title)} + + + +
+

${escapeHtml(title)}

+

${escapeHtml(message)}

+
+ +`; + + return `data:text/html;charset=utf-8,${encodeURIComponent(html)}`; +} + +function escapeHtml(value: string): string { + return value + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +} diff --git a/desktop/src/logging.ts b/desktop/src/logging.ts new file mode 100644 index 000000000..91830f5ac --- /dev/null +++ b/desktop/src/logging.ts @@ -0,0 +1,24 @@ +import { appendFileSync, mkdirSync } from 'node:fs'; +import { dirname } from 'node:path'; + +export class DesktopLog { + constructor(private readonly path: string) { + mkdirSync(dirname(path), { recursive: true }); + } + + info(message: string): void { + this.write('info', message); + } + + error(message: string): void { + this.write('error', message); + } + + line(source: string, message: string): void { + appendFileSync(this.path, `[${new Date().toISOString()}] [${source}] ${message}`, 'utf8'); + } + + private write(level: 'info' | 'error', message: string): void { + appendFileSync(this.path, `[${new Date().toISOString()}] [desktop:${level}] ${message}\n`, 'utf8'); + } +} diff --git a/desktop/src/main.ts b/desktop/src/main.ts new file mode 100644 index 000000000..961d8a8ef --- /dev/null +++ b/desktop/src/main.ts @@ -0,0 +1,88 @@ +import { app, BrowserWindow, dialog } from 'electron'; +import { join } from 'node:path'; +import { waitForHttp } from './health-check.js'; +import { prepareLaravelRuntime, startLaravelProcesses } from './laravel-runtime.js'; +import { loadingPage, errorPage } from './loading-page.js'; +import { DesktopLog } from './logging.js'; +import { ProcessSupervisor } from './process-supervisor.js'; +import { createRuntimeContext } from './runtime-paths.js'; + +let mainWindow: BrowserWindow | null = null; +let supervisor: ProcessSupervisor | null = null; + +const gotLock = app.requestSingleInstanceLock(); + +if (!gotLock) { + app.quit(); +} + +app.on('second-instance', () => { + if (!mainWindow) { + return; + } + + if (mainWindow.isMinimized()) { + mainWindow.restore(); + } + + mainWindow.focus(); +}); + +app.on('before-quit', async (event) => { + if (!supervisor) { + return; + } + + event.preventDefault(); + const currentSupervisor = supervisor; + supervisor = null; + await currentSupervisor.stopAll(); + app.exit(0); +}); + +app.whenReady().then(async () => { + mainWindow = createWindow(); + await mainWindow.loadURL(loadingPage('Preparing local runtime...')); + + try { + const context = await createRuntimeContext(); + const log = new DesktopLog(context.logPath); + supervisor = new ProcessSupervisor(log); + + log.info(`Using Laravel app path: ${context.appRoot}`); + log.info(`Using desktop data path: ${context.dataPath}`); + + await mainWindow.loadURL(loadingPage('Migrating local database...')); + await prepareLaravelRuntime(context, log); + + await mainWindow.loadURL(loadingPage('Starting backend services...')); + startLaravelProcesses(context, supervisor); + await waitForHttp(`${context.appUrl}/api/health`, 45000); + + await mainWindow.loadURL(`${context.appUrl}/desktop/login`); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + await mainWindow.loadURL(errorPage(message)); + dialog.showErrorBox('Vito could not start', message); + } +}); + +app.on('window-all-closed', () => { + app.quit(); +}); + +function createWindow(): BrowserWindow { + return new BrowserWindow({ + width: 1440, + height: 960, + minWidth: 1024, + minHeight: 700, + title: 'Vito', + webPreferences: { + contextIsolation: true, + nodeIntegration: false, + preload: join(app.getAppPath(), 'dist', 'preload.js'), + sandbox: false, + }, + }); +} diff --git a/desktop/src/ports.ts b/desktop/src/ports.ts new file mode 100644 index 000000000..f4dac0dd8 --- /dev/null +++ b/desktop/src/ports.ts @@ -0,0 +1,19 @@ +import { createServer } from 'node:net'; + +export function getAvailablePort(): Promise { + return new Promise((resolve, reject) => { + const server = createServer(); + + server.once('error', reject); + server.listen(0, '127.0.0.1', () => { + const address = server.address(); + if (typeof address === 'string' || address === null) { + server.close(() => reject(new Error('Unable to allocate a TCP port'))); + return; + } + + const port = address.port; + server.close(() => resolve(port)); + }); + }); +} diff --git a/desktop/src/preload.ts b/desktop/src/preload.ts new file mode 100644 index 000000000..55ce53009 --- /dev/null +++ b/desktop/src/preload.ts @@ -0,0 +1,5 @@ +import { contextBridge } from 'electron'; + +contextBridge.exposeInMainWorld('vitoDesktop', { + platform: process.platform, +}); diff --git a/desktop/src/process-supervisor.ts b/desktop/src/process-supervisor.ts new file mode 100644 index 000000000..a9946e713 --- /dev/null +++ b/desktop/src/process-supervisor.ts @@ -0,0 +1,155 @@ +import { ChildProcessWithoutNullStreams, spawn } from 'node:child_process'; +import { DesktopLog } from './logging.js'; + +export type ManagedProcessOptions = { + name: string; + command: string; + args: string[]; + cwd: string; + env: NodeJS.ProcessEnv; + persistent?: boolean; + maxRestarts?: number; + restartWindowMs?: number; +}; + +export class ProcessSupervisor { + private readonly processes = new Map(); + + constructor(private readonly log: DesktopLog) {} + + start(options: ManagedProcessOptions): void { + if (this.processes.has(options.name)) { + return; + } + + const process = new ManagedProcess(options, this.log); + this.processes.set(options.name, process); + process.start(); + } + + async stopAll(): Promise { + const processes = [...this.processes.values()].reverse(); + await Promise.all(processes.map((process) => process.stop())); + this.processes.clear(); + } +} + +class ManagedProcess { + private child: ChildProcessWithoutNullStreams | null = null; + private stopping = false; + private restarts: number[] = []; + + constructor( + private readonly options: ManagedProcessOptions, + private readonly log: DesktopLog, + ) {} + + start(): void { + this.stopping = false; + this.log.info(`Starting ${this.options.name}: ${this.options.command} ${this.options.args.join(' ')}`); + + this.child = spawn(this.options.command, this.options.args, { + cwd: this.options.cwd, + env: this.options.env, + stdio: 'pipe', + windowsHide: true, + }); + + this.child.stdout.on('data', (chunk: Buffer) => this.log.line(this.options.name, chunk.toString())); + this.child.stderr.on('data', (chunk: Buffer) => this.log.line(`${this.options.name}:stderr`, chunk.toString())); + + this.child.on('error', (error) => { + this.log.error(`${this.options.name} failed to start: ${error.message}`); + }); + + this.child.on('exit', (code, signal) => { + this.log.info(`${this.options.name} exited with code=${code ?? 'null'} signal=${signal ?? 'null'}`); + this.child = null; + + if (!this.stopping && this.options.persistent !== false && this.canRestart()) { + setTimeout(() => this.start(), 1000); + } + }); + } + + async stop(timeoutMs = 8000): Promise { + this.stopping = true; + + if (!this.child || this.child.killed) { + return; + } + + const child = this.child; + + await new Promise((resolve) => { + const timeout = setTimeout(() => { + if (!child.killed) { + child.kill('SIGKILL'); + } + resolve(); + }, timeoutMs); + + child.once('exit', () => { + clearTimeout(timeout); + resolve(); + }); + + child.kill('SIGTERM'); + }); + } + + private canRestart(): boolean { + const restartWindowMs = this.options.restartWindowMs ?? 60000; + const maxRestarts = this.options.maxRestarts ?? Number.POSITIVE_INFINITY; + const now = Date.now(); + + this.restarts = this.restarts.filter((timestamp) => now - timestamp < restartWindowMs); + + if (this.restarts.length >= maxRestarts) { + this.log.error(`${this.options.name} restart limit reached`); + return false; + } + + this.restarts.push(now); + return true; + } +} + +export function runCommand( + command: string, + args: string[], + options: { cwd: string; env: NodeJS.ProcessEnv; log: DesktopLog; name: string; timeoutMs?: number }, +): Promise { + options.log.info(`Running ${options.name}: ${command} ${args.join(' ')}`); + + return new Promise((resolve, reject) => { + const child = spawn(command, args, { + cwd: options.cwd, + env: options.env, + stdio: 'pipe', + windowsHide: true, + }); + + const timeout = options.timeoutMs + ? setTimeout(() => { + child.kill('SIGKILL'); + reject(new Error(`${options.name} timed out`)); + }, options.timeoutMs) + : null; + + child.stdout.on('data', (chunk: Buffer) => options.log.line(options.name, chunk.toString())); + child.stderr.on('data', (chunk: Buffer) => options.log.line(`${options.name}:stderr`, chunk.toString())); + child.on('error', reject); + child.on('exit', (code) => { + if (timeout) { + clearTimeout(timeout); + } + + if (code === 0) { + resolve(); + } else { + reject(new Error(`${options.name} exited with code ${code}`)); + } + }); + }); +} diff --git a/desktop/src/runtime-paths.ts b/desktop/src/runtime-paths.ts new file mode 100644 index 000000000..53ce3ee30 --- /dev/null +++ b/desktop/src/runtime-paths.ts @@ -0,0 +1,144 @@ +import { app } from 'electron'; +import { existsSync, mkdirSync, writeFileSync } from 'node:fs'; +import { join, resolve } from 'node:path'; +import { appKey, readEnvFile, secret, writeEnvFile } from './env-file.js'; +import { getAvailablePort } from './ports.js'; + +export type RuntimeContext = { + appRoot: string; + dataPath: string; + envPath: string; + envFile: string; + storagePath: string; + runtimePath: string; + bootstrapCachePath: string; + logPath: string; + httpPort: number; + websocketPort: number; + appUrl: string; + phpBinary: string; + env: NodeJS.ProcessEnv; +}; + +export async function createRuntimeContext(): Promise { + const dataPath = app.getPath('userData'); + const runtimePath = join(dataPath, 'runtime'); + const envPath = runtimePath; + const envFile = join(envPath, '.env'); + const storagePath = join(dataPath, 'storage'); + const bootstrapCachePath = join(runtimePath, 'cache'); + const logPath = join(storagePath, 'logs', 'desktop-processes.log'); + const httpPort = await getAvailablePort(); + const websocketPort = await getAvailablePort(); + const appUrl = `http://127.0.0.1:${httpPort}`; + const appRoot = resolveAppRoot(); + const phpBinary = resolvePhpBinary(); + + ensureRuntimeDirectories(storagePath, runtimePath, bootstrapCachePath); + ensureFile(join(storagePath, 'database.sqlite')); + + const existing = readEnvFile(envFile); + const envValues = { + ...existing, + APP_NAME: existing.APP_NAME || 'Vito', + APP_ENV: 'production', + APP_KEY: existing.APP_KEY || appKey(), + APP_DEBUG: 'false', + APP_URL: appUrl, + VITO_DESKTOP: 'true', + VITO_DATA_PATH: dataPath, + VITO_ENV_PATH: envPath, + VITO_STORAGE_PATH: storagePath, + APP_SERVICES_CACHE: join(bootstrapCachePath, 'services.php'), + APP_PACKAGES_CACHE: join(bootstrapCachePath, 'packages.php'), + APP_CONFIG_CACHE: join(bootstrapCachePath, 'config.php'), + APP_ROUTES_CACHE: join(bootstrapCachePath, 'routes.php'), + APP_EVENTS_CACHE: join(bootstrapCachePath, 'events.php'), + VIEW_COMPILED_PATH: join(storagePath, 'framework', 'views'), + DB_CONNECTION: 'sqlite', + DB_DATABASE: 'database.sqlite', + QUEUE_CONNECTION: 'database', + DB_QUEUE: 'default', + QUEUE_FAILED_DRIVER: 'database-uuids', + CACHE_DRIVER: 'file', + SESSION_DRIVER: 'file', + FILESYSTEM_DISK: 'local', + MAIL_MAILER: existing.MAIL_MAILER || 'log', + WS_HOST: '127.0.0.1', + WS_PORT: String(websocketPort), + WS_ALLOWED_ORIGINS: appUrl, + WS_BROADCAST_SECRET: existing.WS_BROADCAST_SECRET || secret(), + }; + + writeEnvFile(envFile, envValues); + + const env = { + ...process.env, + ...envValues, + VITO_ENV_PATH: envPath, + VITO_STORAGE_PATH: storagePath, + }; + + return { + appRoot, + dataPath, + envPath, + envFile, + storagePath, + runtimePath, + bootstrapCachePath, + logPath, + httpPort, + websocketPort, + appUrl, + phpBinary, + env, + }; +} + +function ensureRuntimeDirectories(storagePath: string, runtimePath: string, bootstrapCachePath: string): void { + const directories = [ + runtimePath, + bootstrapCachePath, + join(storagePath, 'app'), + join(storagePath, 'app', 'public'), + join(storagePath, 'framework'), + join(storagePath, 'framework', 'cache'), + join(storagePath, 'framework', 'cache', 'data'), + join(storagePath, 'framework', 'sessions'), + join(storagePath, 'framework', 'views'), + join(storagePath, 'logs'), + join(storagePath, 'plugins'), + ]; + + for (const directory of directories) { + mkdirSync(directory, { recursive: true }); + } +} + +function ensureFile(path: string): void { + if (!existsSync(path)) { + writeFileSync(path, ''); + } +} + +function resolveAppRoot(): string { + if (app.isPackaged) { + return join(process.resourcesPath, 'app'); + } + + return resolve(app.getAppPath(), '..'); +} + +function resolvePhpBinary(): string { + if (process.env.PHP_BINARY) { + return process.env.PHP_BINARY; + } + + if (!app.isPackaged) { + return 'php'; + } + + const exe = process.platform === 'win32' ? 'php.exe' : 'php'; + return join(process.resourcesPath, 'bin', process.platform, process.arch, exe); +} diff --git a/desktop/tsconfig.json b/desktop/tsconfig.json new file mode 100644 index 000000000..ff8154067 --- /dev/null +++ b/desktop/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "strict": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true, + "outDir": "dist", + "rootDir": "src", + "types": ["node", "electron"] + }, + "include": ["src/**/*.ts"] +} diff --git a/docs/desktop-app-plan.md b/docs/desktop-app-plan.md new file mode 100644 index 000000000..b23f18ef4 --- /dev/null +++ b/docs/desktop-app-plan.md @@ -0,0 +1,794 @@ +# Vito Desktop App Conversion Plan + +Status: draft +Date: 2026-07-05 + +## Summary + +Vito should become a desktop app by keeping the Laravel application mostly intact and adding a desktop runtime around it. The stable production path is a desktop shell that starts a local Laravel backend, supervises its background processes, stores all mutable data outside the installed app bundle, and opens the existing Inertia/React UI in a native desktop window. + +Recommended production path: + +1. Build a custom Electron shell. +2. Bundle a PHP runtime, preferably FrankenPHP if the required PHP 8.4 extensions are available. +3. Run Laravel locally on `127.0.0.1`. +4. Move desktop mode away from Redis/Horizon by default and use SQLite-backed queues plus file or database cache/session. +5. Run the websocket server, queue workers, and scheduler as supervised child processes. +6. Add per-platform installers, code signing, auto-update, and smoke tests. + +NativePHP should still be spiked first because it may give a faster Laravel-native path. It should be accepted only if it handles Vito's PHP 8.4 extension needs, queue topology, scheduler, websocket child process, build signing, and updates without fragile custom work. + +## Goals + +- Ship a normal desktop installer for macOS, Windows, and Linux. +- Do not require users to install PHP, Composer, Node, Redis, Docker, or a web server. +- Preserve the existing Laravel/Inertia app as the primary product surface. +- Preserve long-running server-management workflows: SSH jobs, backups, metrics, SSL checks, websocket events, and scheduled tasks. +- Keep user data stable across app upgrades. +- Make process startup, shutdown, crash recovery, migrations, and updates deterministic. +- Keep Docker-based deployment untouched for the existing server product. + +## Non-goals + +- Rewriting the app as a native Swift, Kotlin, .NET, or Rust UI. +- Building a fully offline server-management tool. Vito still needs network access to managed servers and external APIs. +- Running the desktop backend as a system service after the app quits. +- Shipping Docker Desktop as a required dependency. +- Removing the hosted/web version architecture. + +## Current Repository Constraints + +- The app requires PHP `^8.4` plus `ext-ftp`, `ext-intl`, and `ext-zip` in `composer.json`. +- The frontend is already Inertia/React/Vite, so the UI can run inside Electron, NativePHP, or Tauri with minimal UI rewrite. +- The app currently depends on Redis for queues, cache, and session by default: + - `config/queue.php` defaults to a Redis-backed `default` connection. + - `config/cache.php` defaults to Redis. + - `config/session.php` defaults to Redis. +- Horizon is required by Composer and used as the queue worker in Docker. +- The app already has a SQLite default database connection. +- A `jobs` migration already exists, so database queues are feasible. +- The app schedules many operational commands in `app/Console/Kernel.php`, including every-minute tasks. +- The app has a custom websocket command, `php artisan ws:serve`, defaulting to `127.0.0.1:8085`. +- The Docker runtime already proves the needed process set: web server, Redis, migrations, queue worker, scheduler/cron, and websocket. + +## Decision: Production Architecture + +Use a custom Electron shell with a bundled Laravel backend. + +### Why Electron + +Electron is heavier than Tauri, but it is the lowest-risk custom desktop shell for this app because: + +- It has mature packaging, signing, auto-update, and installer ecosystems. +- It can supervise multiple child processes from a predictable Node main process. +- It lets the Laravel app continue serving normal HTTP routes to Chromium. +- NativePHP itself uses Electron under the hood for desktop, so the fallback is aligned with the Laravel-first path. + +### Why Not Docker Desktop + +Docker is useful for development and server deployment, but not for a stable consumer-style desktop app: + +- It requires a large external dependency. +- Windows/macOS installation and permissions are support-heavy. +- Startup can be slow and resource-hungry. +- Updates become a mix of app updates, image updates, volumes, ports, and Docker state. +- It makes code signing and native app lifecycle harder. + +### Why Reduce Redis/Horizon in Desktop Mode + +Redis and Horizon are excellent for the server product, but they add a local service dependency to desktop packaging. For a single-user desktop app, SQLite-backed jobs are simpler and more stable: + +- Fewer bundled binaries. +- Fewer ports and processes. +- Easier crash recovery. +- Easier backup and migration. +- Good enough queue throughput for local use. + +Desktop mode should use: + +```env +DB_CONNECTION=sqlite +QUEUE_CONNECTION=database +CACHE_DRIVER=file +SESSION_DRIVER=file +``` + +If database queues cannot handle a specific high-concurrency workload, add a desktop-only Redis sidecar later. Do not make Redis the first production desktop design. + +## Architecture + +### Process Model + +The Electron main process is the desktop supervisor. It owns the lifecycle of all backend processes. + +Required processes: + +1. `vito-http` + - Starts Laravel on a loopback HTTP port. + - Preferred command: bundled FrankenPHP serving `public/`. + - Fallback command: bundled PHP with a production-grade local server strategy. + +2. `vito-queue` + - Runs Laravel queue workers. + - Command shape: + + ```sh + php artisan queue:work database --queue=default,ssh,ssh-certbot --sleep=3 --timeout=1200 --tries=1 + ``` + + - A separate long-timeout worker may be needed for backup jobs: + + ```sh + php artisan queue:work database --queue=ssh,ssh-certbot --sleep=3 --timeout=3660 --tries=1 + ``` + +3. `vito-scheduler` + - Runs scheduled tasks while the app is open. + - Command: + + ```sh + php artisan schedule:work + ``` + +4. `vito-websocket` + - Runs the existing websocket server. + - Command: + + ```sh + php artisan ws:serve --host=127.0.0.1 --port= + ``` + +Optional process: + +5. `vito-redis` + - Only if the database queue path fails acceptance testing. + - Must be a bundled sidecar, never a user prerequisite. + +### Startup Flow + +1. Acquire a single-instance app lock. +2. Resolve the app data directory: + - macOS: `~/Library/Application Support/Vito` + - Windows: `%APPDATA%/Vito` + - Linux: `~/.config/Vito` +3. Create app data subdirectories: + - `storage/` + - `storage/app/` + - `storage/framework/` + - `storage/logs/` + - `storage/plugins/` + - `runtime/` +4. Generate or load `.env.desktop`. +5. Allocate local ports for HTTP and websocket. +6. Write runtime env values: + - `APP_URL=http://127.0.0.1:` + - `WS_HOST=127.0.0.1` + - `WS_PORT=` + - `WS_ALLOWED_ORIGINS=http://127.0.0.1:` +7. Ensure `APP_KEY` exists. +8. Ensure SSH keypair exists. +9. Ensure SQLite database exists. +10. Run `php artisan migrate --force`. +11. Run safe optimization for production: + - `php artisan optimize:clear` + - `php artisan optimize` +12. Start `vito-websocket`. +13. Start `vito-queue`. +14. Start `vito-scheduler`. +15. Start `vito-http`. +16. Poll `/api/health` until ready. +17. Open the Electron window to `APP_URL`. + +### Shutdown Flow + +1. Stop accepting new window actions. +2. Ask Laravel workers to quit gracefully where possible. +3. Stop scheduler. +4. Stop websocket server. +5. Stop HTTP server. +6. Kill remaining child processes after a timeout. +7. Release the single-instance lock. + +### Crash Recovery + +Each process must have a restart policy: + +- `vito-http`: restart up to 3 times in 60 seconds, then show a fatal backend error screen. +- `vito-websocket`: restart indefinitely with backoff, show a degraded realtime status if down. +- `vito-queue`: restart indefinitely with backoff, surface worker status in diagnostics. +- `vito-scheduler`: restart indefinitely with backoff, surface scheduler status in diagnostics. + +All child process stdout/stderr must be written to: + +```text +/storage/logs/desktop-processes.log +``` + +## Laravel Changes + +### Desktop Runtime Detection + +Add a small desktop runtime helper instead of scattering `env()` checks. + +Suggested file: + +```text +app/Support/DesktopRuntime.php +``` + +Responsibilities: + +- `enabled(): bool` +- `storagePath(): ?string` +- `httpPort(): ?int` +- `websocketPort(): ?int` +- `dataPath(string $path = ''): string` + +Environment flag: + +```env +VITO_DESKTOP=true +``` + +### Mutable Storage Path + +The installed app bundle must be treated as read-only. Desktop mode must move Laravel `storage_path()` to the per-user app data directory. + +Update `bootstrap/app.php` after the application is created: + +```php +if (($storagePath = $_ENV['VITO_STORAGE_PATH'] ?? null) !== null) { + $app->useStoragePath($storagePath); +} +``` + +The Electron shell should set: + +```env +VITO_STORAGE_PATH=/storage +``` + +### Desktop Env File + +Add: + +```text +.env.desktop.example +``` + +Baseline: + +```env +APP_NAME=Vito +APP_ENV=production +APP_DEBUG=false +APP_URL=http://127.0.0.1 +APP_KEY= + +VITO_DESKTOP=true +VITO_DATA_PATH= +VITO_ENV_PATH= +VITO_STORAGE_PATH= + +APP_SERVICES_CACHE= +APP_PACKAGES_CACHE= +APP_CONFIG_CACHE= +APP_ROUTES_CACHE= +APP_EVENTS_CACHE= +VIEW_COMPILED_PATH= + +DB_CONNECTION=sqlite +DB_DATABASE=database.sqlite + +QUEUE_CONNECTION=database +DB_QUEUE=default +QUEUE_FAILED_DRIVER=database-uuids + +CACHE_DRIVER=file +SESSION_DRIVER=file +FILESYSTEM_DISK=local + +WS_HOST=127.0.0.1 +WS_PORT=8085 +WS_ALLOWED_ORIGINS= +WS_BROADCAST_SECRET= + +MAIL_MAILER=log +``` + +### Queue Configuration + +Add an explicit database queue connection to `config/queue.php`: + +```php +'database' => [ + 'driver' => 'database', + 'connection' => env('DB_CONNECTION', 'sqlite'), + 'table' => 'jobs', + 'queue' => env('DB_QUEUE', 'default'), + 'retry_after' => max(300, (int) env('BACKUP_RUN_TIMEOUT', 3600)) + 60, + 'after_commit' => false, +], +``` + +Desktop workers must consume all relevant queue names: + +```text +default,ssh,ssh-certbot +``` + +### Cache And Session + +Use file cache and file sessions for the first desktop build. This avoids adding a cache table migration. + +If file locking becomes unreliable on Windows under load, switch desktop cache/session to database and add the standard Laravel cache table migration. + +### Horizon + +Do not run Horizon in desktop mode. + +Options: + +1. Keep `laravel/horizon` installed for server deployments. +2. Disable Horizon routes/features in desktop mode if needed. +3. Add a lightweight desktop diagnostics page later that reads queue state from the `jobs` and `failed_jobs` tables. + +### Websocket Configuration + +The websocket port must become dynamic in desktop mode. Avoid hardcoding `8085` because it may already be in use. + +Electron should allocate the port and pass it through env. Laravel should expose the websocket URL in the frontend bootstrap data. + +### Desktop Authentication + +Desktop mode should keep Laravel's session guard, but replace the normal web auth pages with a desktop-local user picker. + +Implemented behavior: + +1. Desktop-only routes live under `/desktop`. +2. Unauthenticated desktop requests redirect to `desktop.login`. +3. The desktop login screen lists existing local users. +4. Before the app has been locked, choosing a user logs in without showing the normal email/password page. +5. Locking the desktop app logs the current user out, marks the session as locked, and redirects to the desktop login screen. +6. Unlocking after a lock requires the selected user's password. +7. Fortify login, password reset, password confirmation, and two-factor challenge views redirect back to the desktop picker in desktop mode. +8. If no users exist, the desktop login screen becomes a first-run setup form that creates the first local admin user, creates its default project, and logs it in. + +Desktop must not ship with a fixed default password. The setup route must stay desktop-only and must reject requests after any user exists. During development and smoke testing, `php artisan user:create` remains a valid way to seed users manually. + +## Desktop Shell Structure + +Suggested repo layout: + +```text +desktop/ + package.json + tsconfig.json + electron-builder.yml + src/ + main.ts + preload.ts + process-supervisor.ts + runtime-paths.ts + first-run.ts + health-check.ts + logging.ts + resources/ + bin/ + mac-arm64/ + mac-x64/ + win-x64/ + linux-x64/ + icons/ +``` + +Generated build artifact layout: + +```text +desktop/dist/ + app/ + artisan + bootstrap/ + config/ + database/ + public/ + resources/ + routes or route-attribute cache output + storage-template/ + vendor/ +``` + +Do not copy development-only files into the desktop app: + +- `node_modules/` +- `tests/` +- `.git/` +- local `.env` +- Docker files unless needed for source compliance bundles + +## Build Pipeline + +### Local Build Script + +Add a top-level script later: + +```sh +composer desktop:prepare +npm run desktop:build +``` + +The preparation step should: + +1. Install Composer dependencies with `--no-dev --optimize-autoloader`. +2. Run `npm ci`. +3. Run `npm run build`. +4. Create a clean Laravel distribution directory. +5. Copy required runtime files. +6. Validate required PHP extensions against the bundled PHP binary. +7. Package Electron. + +### CI Matrix + +Build on each target OS, not by cross-compiling everything from one machine: + +- macOS arm64 +- macOS x64 if supported +- Windows x64 +- Linux x64 + +Each CI job must run: + +```sh +composer install +npm ci +npm run build +php artisan test +desktop build +desktop smoke test +``` + +### Code Signing And Notarization + +macOS: + +- Sign the app bundle. +- Notarize release artifacts. +- Verify first launch on a clean machine. + +Windows: + +- Sign installer and executable. +- Prefer MSIX or NSIS depending on updater choice. + +Linux: + +- AppImage first for portability. +- Add `.deb` later if there is demand. + +### Auto Updates + +Preferred Electron path: + +- Use `electron-builder` plus `electron-updater`. +- Host releases on GitHub Releases, S3, or a dedicated update endpoint. +- Stable and beta channels should be separate. + +Update safety requirements: + +- Never overwrite app data. +- Run migrations only after the new backend starts. +- Backup the SQLite database before migrations. +- Roll back the app binary if health checks fail after update. +- Keep release notes visible before applying updates. + +## NativePHP Spike + +Run this before committing fully to custom Electron. + +### Spike Steps + +1. Create a throwaway branch. +2. Install NativePHP Desktop. +3. Add `.env.desktop.example`. +4. Configure database queues, file cache, and file sessions. +5. Configure NativePHP queue workers for: + - `default` + - `ssh` + - `ssh-certbot` +6. Start `ws:serve` as a persistent child process. +7. Verify scheduler behavior. +8. Build a macOS app locally. +9. Confirm the app launches without installed PHP, Composer, Node, Redis, or Docker. +10. Confirm required PHP extensions: + - ftp + - intl + - zip + - sqlite/pdo_sqlite + - openssl + - curl +11. Run core workflows: + - first-run admin creation + - server creation flow up to SSH validation + - queue job execution + - websocket terminal/events + - scheduled metrics/check tasks + - app restart + - migration after version bump + +### Accept NativePHP If + +- Packaged app starts reliably on macOS and at least one other target OS. +- Required PHP extensions are available or custom binaries are practical. +- Queue workers can process `default`, `ssh`, and `ssh-certbot` without Horizon. +- Scheduler runs while the app is open. +- `ws:serve` can be supervised and restarted. +- User storage lives outside the signed app bundle. +- Auto-update and signing path is acceptable. + +### Reject NativePHP If + +- PHP 8.4 or required extensions require unsupported custom binaries. +- Background workers or websocket process supervision is fragile. +- Build customization requires forking too much NativePHP internals. +- Update/signing behavior cannot be tested cleanly. + +If rejected, continue with the custom Electron path using lessons from the spike. + +## Tauri Option + +Tauri remains a viable second fallback if app size becomes a hard requirement. It should not be the first implementation because it requires more custom process supervision and less Laravel-specific tooling. + +Use Tauri only if: + +- Electron bundle size is unacceptable. +- The team is comfortable maintaining Rust/Tauri process orchestration. +- The sidecar binary pipeline for PHP/FrankenPHP is proven on all target OSes. + +## Security Requirements + +- Bind all local services to `127.0.0.1`, never `0.0.0.0`. +- Use dynamic ports where possible. +- Enforce single-instance mode. +- Set Electron `nodeIntegration: false`. +- Set Electron `contextIsolation: true`. +- Expose only minimal APIs through `preload.ts`. +- Restrict websocket allowed origins to the current local app URL. +- Generate a desktop-local broadcast secret on first run. +- Do not ship default credentials. +- Do not store secrets in the installed app bundle. +- Keep private SSH keys in the app data directory with restrictive permissions. +- Redact secrets in desktop process logs. + +## Diagnostics + +Add a desktop diagnostics screen or command before public release. + +It should show: + +- App version. +- Backend version. +- App data path. +- HTTP port. +- Websocket port. +- Database path. +- Process status for HTTP, queue, scheduler, websocket. +- Last 200 lines of desktop process logs. +- Last 200 lines of Laravel logs. +- Queue depth by queue name. +- Failed job count. +- Button to export a support bundle. + +Support bundle must redact: + +- APP_KEY +- WS_BROADCAST_SECRET +- API tokens +- SSH private keys +- provider credentials + +## Testing Plan + +### Unit And Feature Tests + +Keep existing tests. Add desktop-specific feature tests for: + +- desktop storage path override +- desktop env generation +- database queue config +- first-run user creation guard +- websocket URL generation +- desktop user picker login +- desktop lock and password unlock + +### Process Integration Tests + +Run from the desktop package: + +1. Start app. +2. Wait for `/api/health`. +3. Assert SQLite database exists in app data. +4. Assert migrations are applied. +5. Assert queue worker processes a test job. +6. Assert scheduler process is running. +7. Assert websocket port accepts connection. +8. Restart app and verify no duplicate processes. +9. Quit app and verify child processes are gone. + +### Packaging Smoke Tests + +For each target OS: + +1. Install fresh app. +2. Launch app. +3. Complete first-run admin setup. +4. Reload app. +5. Quit app. +6. Relaunch app. +7. Verify login persists. +8. Verify logs are created in app data. +9. Verify no writes happen inside the signed app bundle. + +### Update Tests + +1. Install version N. +2. Create user and sample data. +3. Install/update to version N+1. +4. Verify app data remains. +5. Verify migrations run once. +6. Verify rollback behavior on forced failed health check. + +## Implementation Phases + +### Phase 0: NativePHP Decision Spike + +Deliverables: + +- Spike branch. +- Written decision note. +- List of extension/build blockers. +- Pass/fail against acceptance criteria. + +Exit: + +- Choose NativePHP or custom Electron. + +### Phase 1: Desktop Runtime Foundation + +Deliverables: + +- `.env.desktop.example` +- `VITO_DESKTOP` runtime detection. +- `VITO_STORAGE_PATH` support in `bootstrap/app.php`. +- Database queue connection in `config/queue.php`. +- File cache/session desktop profile. +- First-run admin creation plan or route. + +Exit: + +- Laravel can run locally in desktop mode without Redis. + +### Phase 2: Electron Shell Prototype + +Deliverables: + +- `desktop/` package. +- Electron main window. +- Runtime path resolver. +- Process supervisor. +- Health check polling. +- App data directory creation. +- Dynamic port allocation. + +Exit: + +- `npm run desktop:dev` launches Vito through Electron. + +### Phase 3: Background Process Reliability + +Deliverables: + +- Supervised HTTP, queue, scheduler, websocket processes. +- Restart/backoff policies. +- Unified process logging. +- Single-instance lock. +- Graceful shutdown. + +Exit: + +- App restart/quit leaves no orphaned child processes. + +### Phase 4: Desktop UX And Diagnostics + +Deliverables: + +- First-run admin setup. +- Backend starting screen. +- Backend error screen. +- Diagnostics/export support bundle. +- Update/restart prompts. + +Exit: + +- Non-technical user can install, start, diagnose, and update the app. + +### Phase 5: Packaging And Signing + +Deliverables: + +- macOS signed/notarized build. +- Windows signed installer. +- Linux AppImage. +- CI matrix builds. +- Release artifact publishing. + +Exit: + +- A clean machine can install and run each platform build. + +### Phase 6: Auto Update + +Deliverables: + +- Stable and beta update channels. +- Update download/install flow. +- Database backup before migration. +- Failed-update health check handling. + +Exit: + +- Version N updates to N+1 without data loss. + +### Phase 7: Beta Hardening + +Deliverables: + +- 20 to 30 real workflow test runs. +- Long-running backup/SSH job tests. +- Sleep/wake tests. +- Offline/online transition tests. +- Port conflict tests. +- Antivirus/Defender check on Windows. + +Exit: + +- Known blocking bugs fixed or explicitly accepted. + +## Work Checklist + +- [ ] Run NativePHP spike. +- [ ] Decide NativePHP vs custom Electron. +- [x] Add desktop env profile. +- [x] Add storage path override. +- [x] Add database queue connection. +- [ ] Confirm jobs table migration covers all queue needs. +- [x] Remove Redis requirement from desktop mode. +- [ ] Disable Horizon runtime in desktop mode. +- [x] Make websocket port dynamic. +- [ ] Add desktop first-run admin setup. +- [x] Add Electron shell. +- [x] Add process supervisor. +- [x] Add app data path management. +- [x] Add health check based launch. +- [x] Add graceful shutdown. +- [ ] Add diagnostics. +- [ ] Add desktop smoke tests. +- [ ] Add packaging. +- [ ] Add code signing. +- [ ] Add auto-update. +- [ ] Run beta hardening matrix. + +## Open Questions + +- Which platforms are required for v1: macOS only, or macOS plus Windows/Linux? +- Does the desktop app need to run jobs while the window is closed but the app is still in the tray? +- Should desktop support plugins from `storage/plugins/*/*` in v1? +- Should the desktop app use the same AGPL distribution model, or dual-license the desktop release? +- Should updates be hosted on GitHub Releases, S3, or the Vito website? +- Is app-store distribution required, or are direct downloads enough? +- Should the first desktop beta keep Horizon available behind a debug flag for comparison? + +## Initial Recommendation + +Run the NativePHP spike first, but plan production around custom Electron unless the spike clears every process, extension, and update requirement cleanly. + +The custom Electron path is more work, but it gives Vito direct control over the exact things that matter for stability: process supervision, local data paths, updates, diagnostics, and platform-specific packaging. diff --git a/package.json b/package.json index d3297e0b4..dff13de25 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,9 @@ "scripts": { "build": "vite build", "build:ssr": "vite build && vite build --ssr", + "desktop:build": "npm --prefix desktop run build", + "desktop:dev": "npm --prefix desktop run dev", + "desktop:package": "npm --prefix desktop run package", "dev": "vite", "format": "prettier --write resources/", "format:check": "prettier --check resources/", diff --git a/resources/js/components/user-menu-content.tsx b/resources/js/components/user-menu-content.tsx index 8ffc55b52..c530d103c 100644 --- a/resources/js/components/user-menu-content.tsx +++ b/resources/js/components/user-menu-content.tsx @@ -1,9 +1,10 @@ import { DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator } from '@/components/ui/dropdown-menu'; import { UserInfo } from '@/components/user-info'; import { useMobileNavigation } from '@/hooks/use-mobile-navigation'; +import { type SharedData } from '@/types'; import { type User } from '@/types/user'; -import { Link, router } from '@inertiajs/react'; -import { LogOut, Settings } from 'lucide-react'; +import { Link, router, usePage } from '@inertiajs/react'; +import { LockKeyhole, LogOut, Settings } from 'lucide-react'; import AppearanceToggleTab from '@/components/appearance-tabs'; import { useBootstrapStore } from '@/stores/bootstrap-store'; @@ -13,8 +14,10 @@ interface UserMenuContentProps { export function UserMenuContent({ user }: UserMenuContentProps) { const cleanup = useMobileNavigation(); + const { desktop } = usePage().props; + const isDesktop = desktop?.enabled === true; - const handleLogout = () => { + const handleSessionExit = () => { cleanup(); router.flushAll(); useBootstrapStore.getState().clear(); @@ -40,9 +43,9 @@ export function UserMenuContent({ user }: UserMenuContentProps) { - - - Log out + + {isDesktop ? : } + {isDesktop ? 'Lock app' : 'Log out'} diff --git a/resources/js/pages/auth/desktop-login.tsx b/resources/js/pages/auth/desktop-login.tsx new file mode 100644 index 000000000..d20e0a715 --- /dev/null +++ b/resources/js/pages/auth/desktop-login.tsx @@ -0,0 +1,209 @@ +import InputError from '@/components/ui/input-error'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { cn } from '@/lib/utils'; +import AuthLayout from '@/layouts/auth/layout'; +import { useInitials } from '@/hooks/use-initials'; +import { type User } from '@/types/user'; +import { Head, router, useForm, usePage } from '@inertiajs/react'; +import { LoaderCircleIcon, LockKeyholeIcon, UserPlusIcon, UserRoundIcon } from 'lucide-react'; +import { FormEvent, useMemo, useState } from 'react'; + +type DesktopUser = Pick; + +type DesktopLoginProps = { + users: DesktopUser[]; + setup_required: boolean; + locked: boolean; + locked_user_id: number | null; +}; + +export default function DesktopLogin() { + const { users, setup_required, locked, locked_user_id } = usePage().props; + const initials = useInitials(); + const [selectedUserId, setSelectedUserId] = useState(locked_user_id); + const loginForm = useForm<{ password: string }>({ + password: '', + }); + const setupForm = useForm<{ + name: string; + email: string; + password: string; + password_confirmation: string; + }>({ + name: '', + email: '', + password: '', + password_confirmation: '', + }); + + const selectedUser = useMemo(() => users.find((user) => user.id === selectedUserId) ?? null, [selectedUserId, users]); + const title = setup_required ? 'Set up Vito' : locked ? 'Unlock Vito' : 'Choose a user'; + const description = setup_required + ? 'Create the first local desktop administrator.' + : locked + ? 'Enter the password for the selected user.' + : 'Select a local desktop user to continue.'; + + const chooseUser = (user: DesktopUser) => { + if (!locked) { + router.post(route('desktop.login.store', { user: user.id })); + return; + } + + loginForm.clearErrors(); + loginForm.reset('password'); + setSelectedUserId(user.id); + }; + + const setup = (e: FormEvent) => { + e.preventDefault(); + + setupForm.post(route('desktop.setup'), { + onFinish: () => setupForm.reset('password', 'password_confirmation'), + }); + }; + + const unlock = (e: FormEvent) => { + e.preventDefault(); + + if (!selectedUser) { + return; + } + + loginForm.post(route('desktop.login.store', { user: selectedUser.id }), { + onFinish: () => loginForm.reset('password'), + }); + }; + + return ( + + + +
+ {setup_required ? ( +
+
+ + setupForm.setData('name', e.target.value)} + placeholder="Name" + /> + +
+ +
+ + setupForm.setData('email', e.target.value)} + placeholder="Email" + /> + +
+ +
+ + setupForm.setData('password', e.target.value)} + placeholder="Password" + /> + +
+ +
+ + setupForm.setData('password_confirmation', e.target.value)} + placeholder="Confirm password" + /> + +
+ + +
+ ) : users.length === 0 ? ( +
+

No users found.

+

Create a user before continuing.

+
+ ) : ( +
+ {users.map((user) => { + const selected = selectedUserId === user.id; + + return ( + + ); + })} +
+ )} + + {locked && selectedUser && ( +
+
+ + loginForm.setData('password', e.target.value)} + placeholder="Password" + /> + +
+ + +
+ )} +
+
+ ); +} diff --git a/resources/js/types/index.d.ts b/resources/js/types/index.d.ts index b2d725096..5d1d66171 100644 --- a/resources/js/types/index.d.ts +++ b/resources/js/types/index.d.ts @@ -134,6 +134,9 @@ export interface SharedData { version: string; env: string; demo: boolean; + desktop?: { + enabled: boolean; + }; quote: { message: string; author: string }; auth: Auth; ziggy?: Config & { location: string }; diff --git a/tests/Feature/DesktopAuthTest.php b/tests/Feature/DesktopAuthTest.php new file mode 100644 index 000000000..58f6f87e6 --- /dev/null +++ b/tests/Feature/DesktopAuthTest.php @@ -0,0 +1,170 @@ +set('desktop.enabled', false); + + $this->get(route('desktop.login'))->assertNotFound(); + } + + public function test_desktop_login_screen_lists_users(): void + { + config()->set('desktop.enabled', true); + + $this->withoutVite(); + + $this->get(route('desktop.login')) + ->assertOk() + ->assertInertia(fn (AssertableInertia $page) => $page + ->component('auth/desktop-login') + ->where('setup_required', false) + ->where('locked', false) + ->where('locked_user_id', null) + ->has('users', 1) + ->where('users.0.id', $this->user->id) + ); + } + + public function test_desktop_first_run_setup_screen_is_shown_when_no_users_exist(): void + { + config()->set('desktop.enabled', true); + + User::query()->delete(); + + $this->withoutVite(); + + $this->get(route('desktop.login')) + ->assertOk() + ->assertInertia(fn (AssertableInertia $page) => $page + ->component('auth/desktop-login') + ->where('setup_required', true) + ->where('locked', false) + ->where('locked_user_id', null) + ->has('users', 0) + ); + } + + public function test_desktop_first_run_setup_creates_admin_and_logs_in(): void + { + config()->set('desktop.enabled', true); + + User::query()->delete(); + + $response = $this->post(route('desktop.setup'), [ + 'name' => 'Desktop Admin', + 'email' => 'desktop@example.com', + 'password' => 'secret-password', + 'password_confirmation' => 'secret-password', + ]); + + /** @var User $user */ + $user = User::query()->where('email', 'desktop@example.com')->firstOrFail(); + + $this->assertAuthenticatedAs($user); + $this->assertTrue($user->is_admin); + $this->assertSame('UTC', $user->timezone); + $this->assertNotNull($user->current_project_id); + $this->assertDatabaseHas('user_project', [ + 'user_id' => $user->id, + 'project_id' => $user->current_project_id, + ]); + $response->assertRedirect(RouteServiceProvider::HOME); + } + + public function test_desktop_first_run_setup_is_unavailable_when_user_exists(): void + { + config()->set('desktop.enabled', true); + + $this->post(route('desktop.setup'), [ + 'name' => 'Second Admin', + 'email' => 'second@example.com', + 'password' => 'secret-password', + 'password_confirmation' => 'secret-password', + ])->assertNotFound(); + + $this->assertDatabaseMissing('users', [ + 'email' => 'second@example.com', + ]); + } + + public function test_desktop_first_run_setup_is_unavailable_outside_desktop_mode(): void + { + config()->set('desktop.enabled', false); + + User::query()->delete(); + + $this->post(route('desktop.setup'), [ + 'name' => 'Desktop Admin', + 'email' => 'desktop@example.com', + 'password' => 'secret-password', + 'password_confirmation' => 'secret-password', + ])->assertNotFound(); + + $this->assertDatabaseMissing('users', [ + 'email' => 'desktop@example.com', + ]); + } + + public function test_desktop_users_can_login_by_clicking_user_before_locking(): void + { + config()->set('desktop.enabled', true); + + $response = $this->post(route('desktop.login.store', $this->user)); + + $this->assertAuthenticatedAs($this->user); + $response->assertRedirect(RouteServiceProvider::HOME); + } + + public function test_desktop_lock_logs_out_and_marks_session_locked(): void + { + config()->set('desktop.enabled', true); + + $response = $this->actingAs($this->user)->post(route('desktop.lock')); + + $this->assertGuest(); + $response + ->assertRedirect(route('desktop.login')) + ->assertSessionHas('desktop.locked_user_id', $this->user->id); + } + + public function test_desktop_unlock_requires_password_after_locking(): void + { + config()->set('desktop.enabled', true); + + /** @var User $user */ + $user = User::factory()->create([ + 'password' => Hash::make('secret-password'), + ]); + + $this->withSession(['desktop.locked_user_id' => $user->id]) + ->post(route('desktop.login.store', $user), [ + 'password' => 'wrong-password', + ]) + ->assertSessionHasErrors('password'); + + $this->assertGuest(); + + $response = $this->withSession(['desktop.locked_user_id' => $user->id]) + ->post(route('desktop.login.store', $user), [ + 'password' => 'secret-password', + ]); + + $this->assertAuthenticatedAs($user); + $response + ->assertRedirect(RouteServiceProvider::HOME) + ->assertSessionMissing('desktop.locked_user_id'); + } +} diff --git a/tests/Unit/Commands/GenerateKeysCommandTest.php b/tests/Unit/Commands/GenerateKeysCommandTest.php new file mode 100644 index 000000000..a84c35aad --- /dev/null +++ b/tests/Unit/Commands/GenerateKeysCommandTest.php @@ -0,0 +1,28 @@ +app->useStoragePath($storagePath); + + $this->artisan('ssh-key:generate', ['--force' => true]) + ->assertSuccessful(); + + $this->assertFileExists($storagePath.'/ssh-private.pem'); + $this->assertFileExists($storagePath.'/ssh-public.key'); + $this->assertNotEmpty(file_get_contents($storagePath.'/ssh-public.key')); + + File::deleteDirectory($storagePath); + } +} diff --git a/tests/Unit/DesktopRuntimeTest.php b/tests/Unit/DesktopRuntimeTest.php new file mode 100644 index 000000000..dcb5fcce9 --- /dev/null +++ b/tests/Unit/DesktopRuntimeTest.php @@ -0,0 +1,39 @@ +set('app.url', 'http://127.0.0.1:47291'); + config()->set('app.ws_url', null); + config()->set('core.ws_port', 48125); + config()->set('desktop.enabled', true); + + $this->assertSame('ws://127.0.0.1:48125/ws/events', DesktopRuntime::websocketUrl('/ws/events')); + } + + public function test_builds_server_websocket_url_without_desktop_port(): void + { + config()->set('app.url', 'https://vito.example.com'); + config()->set('app.ws_url', null); + config()->set('core.ws_port', 48125); + config()->set('desktop.enabled', false); + + $this->assertSame('wss://vito.example.com/ws/events', DesktopRuntime::websocketUrl('/ws/events')); + } + + public function test_joins_desktop_storage_paths(): void + { + config()->set('desktop.storage_path', '/tmp/vito/storage'); + + $this->assertSame('/tmp/vito/storage/framework/views', DesktopRuntime::storagePath('framework/views')); + } +} From c9f8b24dbfeb762a4925234dc94f203852f3d169 Mon Sep 17 00:00:00 2001 From: Saeed Vaziry Date: Fri, 17 Jul 2026 21:58:47 +0200 Subject: [PATCH 2/6] Desktop app: packaging, release pipeline, and runtime hardening - Fix desktop auth routes: guest/auth middleware was silently ignored (Spatie Middleware attribute is class-target only) - Generate SSH keys with phpseclib instead of shelling out to openssl/ssh-keygen (required for Windows) - Serve HTTP via supervised php -S with the framework router and PHP_CLI_SERVER_WORKERS instead of artisan serve - Kill process groups on shutdown and reap stale processes from crashed sessions on startup - Add dedicated desktop asset build (public/build-desktop, gitignored) - Add packaging scripts: app dist assembly, portable PHP runtime (static-php-cli on macOS/Linux, php.net build on Windows), smoke test - Add desktop-release workflow (macOS arm64/x64, Windows x64, Linux x64) wired into the release workflow; installers upload to the GitHub release; optional code signing/notarization via secrets - Add desktop-ci workflow: typecheck + full Linux packaging smoke --- .github/workflows/desktop-ci.yml | 86 +++++++ .github/workflows/desktop-release.yml | 146 ++++++++++++ .github/workflows/releases.yml | 9 + .gitignore | 2 + app/Console/Commands/GenerateKeysCommand.php | 22 +- .../Controllers/DesktopAuthController.php | 13 +- app/Http/Middleware/HandleInertiaRequests.php | 4 + app/Providers/AppServiceProvider.php | 6 + desktop/README.md | 62 +++++- desktop/build/icon.png | Bin 0 -> 251649 bytes desktop/electron-builder.yml | 16 ++ desktop/entitlements.mac.plist | 18 ++ desktop/package.json | 1 + desktop/src/laravel-runtime.ts | 32 ++- desktop/src/main.ts | 60 ++++- desktop/src/process-supervisor.ts | 210 +++++++++++++++++- desktop/src/runtime-paths.ts | 34 ++- docs/desktop-app-plan.md | 13 +- package.json | 1 + scripts/desktop/build-app.sh | 67 ++++++ scripts/desktop/fetch-php.sh | 121 ++++++++++ scripts/desktop/smoke-test.sh | 129 +++++++++++ tests/Feature/NotificationChannelsTest.php | 8 +- tests/Feature/ServerProvidersTest.php | 10 +- tests/Feature/SourceControlsTest.php | 8 +- tests/Feature/StorageProvidersTest.php | 8 +- vite.config.ts | 5 +- 27 files changed, 1008 insertions(+), 83 deletions(-) create mode 100644 .github/workflows/desktop-ci.yml create mode 100644 .github/workflows/desktop-release.yml create mode 100644 desktop/build/icon.png create mode 100644 desktop/entitlements.mac.plist create mode 100644 scripts/desktop/build-app.sh create mode 100644 scripts/desktop/fetch-php.sh create mode 100644 scripts/desktop/smoke-test.sh diff --git a/.github/workflows/desktop-ci.yml b/.github/workflows/desktop-ci.yml new file mode 100644 index 000000000..b4d882a85 --- /dev/null +++ b/.github/workflows/desktop-ci.yml @@ -0,0 +1,86 @@ +name: desktop-ci + +on: + pull_request: + paths: + - 'desktop/**' + - 'scripts/desktop/**' + - '.github/workflows/desktop-ci.yml' + - '.github/workflows/desktop-release.yml' + - 'vite.config.ts' + - 'bootstrap/app.php' + - 'config/desktop.php' + - 'app/Support/DesktopRuntime.php' + +jobs: + typecheck: + runs-on: ubuntu-24.04 + + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '22.x' + + - name: Install desktop shell dependencies + run: npm ci + working-directory: desktop + + - name: Build desktop shell + run: npm run build + working-directory: desktop + + package-smoke: + runs-on: ubuntu-24.04 + + steps: + - uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.4' + extensions: ctype, curl, dom, fileinfo, filter, ftp, iconv, intl, mbstring, openssl, pdo_sqlite, sqlite3, zip + + - name: Install Composer dependencies + run: composer install --prefer-dist --no-progress --no-dev --optimize-autoloader + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '22.x' + + - name: Install NPM dependencies + run: npm install --force + + - name: Build desktop frontend assets + run: npm run build:desktop + + - name: Assemble desktop app distribution + run: bash scripts/desktop/build-app.sh + + - name: Cache PHP runtime + uses: actions/cache@v4 + with: + path: desktop/resources/bin + key: desktop-php-linux-x64-${{ hashFiles('scripts/desktop/fetch-php.sh') }} + + - name: Fetch PHP runtime + run: bash scripts/desktop/fetch-php.sh linux x64 + + - name: Smoke test desktop app distribution + run: bash scripts/desktop/smoke-test.sh "$PWD/desktop/resources/bin/linux/x64/php" + + - name: Install desktop shell dependencies + run: npm ci + working-directory: desktop + + - name: Build desktop shell + run: npm run build + working-directory: desktop + + - name: Package desktop app (unpacked) + run: npx electron-builder --config electron-builder.yml --linux --x64 --dir --publish never + working-directory: desktop diff --git a/.github/workflows/desktop-release.yml b/.github/workflows/desktop-release.yml new file mode 100644 index 000000000..01babba0e --- /dev/null +++ b/.github/workflows/desktop-release.yml @@ -0,0 +1,146 @@ +name: desktop-release + +on: + workflow_dispatch: + inputs: + version: + description: 'Existing release tag to build and attach desktop apps to (e.g. 4.1.0)' + required: true + workflow_call: + inputs: + version: + description: 'Existing release tag to build and attach desktop apps to' + required: true + type: string + +permissions: + contents: write + +jobs: + build: + name: Build ${{ matrix.name }} + runs-on: ${{ matrix.os }} + + strategy: + fail-fast: false + matrix: + include: + - name: macos-arm64 + os: macos-15 + platform: darwin + arch: arm64 + build-args: --mac --arm64 + - name: macos-x64 + os: macos-15-intel + platform: darwin + arch: x64 + build-args: --mac --x64 + - name: windows-x64 + os: windows-latest + platform: win32 + arch: x64 + build-args: --win --x64 + - name: linux-x64 + os: ubuntu-24.04 + platform: linux + arch: x64 + build-args: --linux --x64 + + steps: + - name: Remove optional "v" prefix + id: version + shell: bash + run: echo "version=${VERSION#v}" >> "$GITHUB_OUTPUT" + env: + VERSION: ${{ inputs.version }} + + - name: Checkout release tag + uses: actions/checkout@v4 + with: + ref: ${{ steps.version.outputs.version }} + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.4' + extensions: ctype, curl, dom, fileinfo, filter, ftp, iconv, intl, mbstring, openssl, pdo_sqlite, sqlite3, zip + + - name: Install Composer dependencies + run: composer install --prefer-dist --no-progress --no-dev --optimize-autoloader + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '22.x' + + - name: Install NPM dependencies + run: npm install --force + + - name: Build desktop frontend assets + run: npm run build:desktop + + - name: Assemble desktop app distribution + shell: bash + run: bash scripts/desktop/build-app.sh + + - name: Cache PHP runtime + uses: actions/cache@v4 + with: + path: desktop/resources/bin + key: desktop-php-${{ matrix.platform }}-${{ matrix.arch }}-${{ hashFiles('scripts/desktop/fetch-php.sh') }} + + - name: Fetch PHP runtime + shell: bash + run: bash scripts/desktop/fetch-php.sh "${{ matrix.platform }}" "${{ matrix.arch }}" + + - name: Smoke test desktop app distribution + shell: bash + run: | + PHP_BIN="desktop/resources/bin/${{ matrix.platform }}/${{ matrix.arch }}/php" + if [ "${{ matrix.platform }}" = "win32" ]; then + PHP_BIN="$PHP_BIN.exe" + fi + bash scripts/desktop/smoke-test.sh "$PWD/$PHP_BIN" + + - name: Install desktop shell dependencies + shell: bash + run: npm ci + working-directory: desktop + + - name: Build desktop shell + shell: bash + run: npm run build + working-directory: desktop + + - name: Set desktop app version + shell: bash + run: npm version "${{ steps.version.outputs.version }}" --no-git-tag-version --allow-same-version + working-directory: desktop + + - name: Package desktop app + shell: bash + run: npx electron-builder --config electron-builder.yml ${{ matrix.build-args }} --publish never + working-directory: desktop + env: + CSC_LINK: ${{ secrets.DESKTOP_MAC_CSC_LINK }} + CSC_KEY_PASSWORD: ${{ secrets.DESKTOP_MAC_CSC_KEY_PASSWORD }} + WIN_CSC_LINK: ${{ secrets.DESKTOP_WIN_CSC_LINK }} + WIN_CSC_KEY_PASSWORD: ${{ secrets.DESKTOP_WIN_CSC_KEY_PASSWORD }} + APPLE_ID: ${{ secrets.DESKTOP_APPLE_ID }} + APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.DESKTOP_APPLE_APP_SPECIFIC_PASSWORD }} + APPLE_TEAM_ID: ${{ secrets.DESKTOP_APPLE_TEAM_ID }} + CSC_IDENTITY_AUTO_DISCOVERY: ${{ secrets.DESKTOP_MAC_CSC_LINK != '' }} + + - name: Upload desktop apps to release + shell: bash + run: | + shopt -s nullglob + FILES=(desktop/release/*.dmg desktop/release/*-mac-*.zip desktop/release/*.exe desktop/release/*.AppImage) + if [ ${#FILES[@]} -eq 0 ]; then + echo "No desktop artifacts found to upload" >&2 + exit 1 + fi + gh release upload "$VERSION" "${FILES[@]}" --clobber + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VERSION: ${{ steps.version.outputs.version }} diff --git a/.github/workflows/releases.yml b/.github/workflows/releases.yml index 13d55fca5..f0f46b162 100644 --- a/.github/workflows/releases.yml +++ b/.github/workflows/releases.yml @@ -179,6 +179,15 @@ jobs: echo "Discord notification failed; release succeeded regardless" fi + desktop: + needs: release + permissions: + contents: write + uses: ./.github/workflows/desktop-release.yml + with: + version: ${{ needs.release.outputs.version }} + secrets: inherit + docker-tags: runs-on: ubuntu-24.04 needs: release diff --git a/.gitignore b/.gitignore index 7dcb4c91f..547889349 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,9 @@ /desktop/node_modules /desktop/dist /desktop/release +/desktop/resources/bin /desktop-build +/public/build-desktop /public/hot /public/storage /storage/*.key diff --git a/app/Console/Commands/GenerateKeysCommand.php b/app/Console/Commands/GenerateKeysCommand.php index 43fdff0d4..47145bf13 100644 --- a/app/Console/Commands/GenerateKeysCommand.php +++ b/app/Console/Commands/GenerateKeysCommand.php @@ -4,6 +4,7 @@ use Illuminate\Console\Command; use Illuminate\Support\Facades\File; +use phpseclib3\Crypt\RSA; class GenerateKeysCommand extends Command { @@ -22,27 +23,12 @@ public function handle(): void return; } - $privateKey = escapeshellarg($privateKeyPath); - $publicKey = escapeshellarg($publicKeyPath); - - exec("openssl genpkey -algorithm RSA -out {$privateKey}", $output, $resultCode); - - if ($resultCode !== 0) { - $this->error('Unable to generate private key.'); - - return; - } + $privateKey = RSA::createKey(2048); + File::put($privateKeyPath, $privateKey->toString('PKCS8')); chmod($privateKeyPath, 0600); - exec("ssh-keygen -y -f {$privateKey} > {$publicKey}", $output, $resultCode); - - if ($resultCode !== 0) { - $this->error('Unable to generate public key.'); - - return; - } - + File::put($publicKeyPath, $privateKey->getPublicKey()->toString('OpenSSH', ['comment' => 'vito'])); chmod($publicKeyPath, 0644); $this->info('Keys generated successfully.'); diff --git a/app/Http/Controllers/DesktopAuthController.php b/app/Http/Controllers/DesktopAuthController.php index 100dfbcca..7c9cc0d79 100644 --- a/app/Http/Controllers/DesktopAuthController.php +++ b/app/Http/Controllers/DesktopAuthController.php @@ -13,7 +13,6 @@ use Inertia\Inertia; use Inertia\Response; use Spatie\RouteAttributes\Attributes\Get; -use Spatie\RouteAttributes\Attributes\Middleware; use Spatie\RouteAttributes\Attributes\Post; use Spatie\RouteAttributes\Attributes\Prefix; @@ -24,8 +23,7 @@ class DesktopAuthController extends Controller private const LOCKED_AT = 'desktop.locked_at'; - #[Get('/login', name: 'desktop.login')] - #[Middleware('guest')] + #[Get('/login', name: 'desktop.login', middleware: 'guest')] public function create(Request $request): Response { abort_unless(DesktopRuntime::enabled(), 404); @@ -52,8 +50,7 @@ public function create(Request $request): Response ]); } - #[Post('/setup', name: 'desktop.setup')] - #[Middleware('guest')] + #[Post('/setup', name: 'desktop.setup', middleware: 'guest')] public function setup(Request $request): RedirectResponse { abort_unless(DesktopRuntime::enabled(), 404); @@ -84,8 +81,7 @@ public function setup(Request $request): RedirectResponse return redirect()->intended(RouteServiceProvider::HOME); } - #[Post('/login/{user}', name: 'desktop.login.store')] - #[Middleware('guest')] + #[Post('/login/{user}', name: 'desktop.login.store', middleware: 'guest')] public function store(Request $request, User $user): RedirectResponse { abort_unless(DesktopRuntime::enabled(), 404); @@ -112,8 +108,7 @@ public function store(Request $request, User $user): RedirectResponse return redirect()->intended(RouteServiceProvider::HOME); } - #[Post('/lock', name: 'desktop.lock')] - #[Middleware('auth')] + #[Post('/lock', name: 'desktop.lock', middleware: 'auth')] public function lock(Request $request): RedirectResponse { abort_unless(DesktopRuntime::enabled(), 404); diff --git a/app/Http/Middleware/HandleInertiaRequests.php b/app/Http/Middleware/HandleInertiaRequests.php index 28d95a6a5..fac2d8eea 100644 --- a/app/Http/Middleware/HandleInertiaRequests.php +++ b/app/Http/Middleware/HandleInertiaRequests.php @@ -34,6 +34,10 @@ class HandleInertiaRequests extends Middleware */ public function version(Request $request): ?string { + if (DesktopRuntime::enabled() && file_exists($manifest = public_path('build-desktop/manifest.json'))) { + return md5_file($manifest) ?: null; + } + return parent::version($request); } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 419d9be3c..1b86bfe4f 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -13,9 +13,11 @@ use App\Listeners\HandleSiteDeletedStats; use App\Listeners\SocketEventListener; use App\Models\PersonalAccessToken; +use App\Support\DesktopRuntime; use Illuminate\Http\Resources\Json\ResourceCollection; use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\URL; +use Illuminate\Support\Facades\Vite; use Illuminate\Support\ServiceProvider; use Laravel\Sanctum\Sanctum; @@ -30,6 +32,10 @@ public function boot(): void { ResourceCollection::withoutWrapping(); + if (DesktopRuntime::enabled()) { + Vite::useBuildDirectory('build-desktop'); + } + // facades $this->app->bind('ssh', fn (): SSH => new SSH); $this->app->bind('notifier', fn (): Notifier => new Notifier); diff --git a/desktop/README.md b/desktop/README.md index 0ff5f53d1..e0b3eca50 100644 --- a/desktop/README.md +++ b/desktop/README.md @@ -1,12 +1,68 @@ # Vito Desktop -This is the initial desktop shell for Vito. It runs the Laravel app as a local backend process, supervises the queue, scheduler, and websocket workers, then opens the existing Inertia UI in Electron. +The desktop shell runs the Laravel app as a local backend and opens the existing Inertia UI in an Electron window. Everything ships in a single installer: the Electron shell, the Laravel application, the built frontend assets, and a portable PHP 8.4 runtime. No PHP, Redis, Docker, or web server is required on the user's machine. -Development: +## Architecture + +The Electron main process supervises four backend processes, all bound to `127.0.0.1` on dynamically allocated ports: + +| Process | Command | +| --- | --- | +| `vito-http` | `php -S 127.0.0.1: -t public ` | +| `vito-queue` | `php artisan queue:work database --queue=default,ssh,ssh-certbot` | +| `vito-scheduler` | `php artisan schedule:work` | +| `vito-websocket` | `php artisan ws:serve --host=127.0.0.1 --port=` | + +Desktop mode uses SQLite, database queues, and file cache/sessions. All mutable data lives outside the app bundle: + +- macOS: `~/Library/Application Support/Vito` +- Windows: `%APPDATA%/Vito` +- Linux: `~/.config/Vito` + +## Development ```sh npm install --prefix desktop +npm run dev # or: npm run build:desktop (desktop assets) npm run desktop:dev ``` -The dev shell uses the repository root as the Laravel app path and the system PHP binary unless `PHP_BINARY` is set. Packaged builds are expected to place a prepared Laravel app in `desktop-build/app` and platform PHP binaries in `desktop/resources/bin`. +The dev shell uses the repository root as the Laravel app path and the system PHP binary unless `PHP_BINARY` is set. Frontend assets come from the Vite dev server (`public/hot`) or from `public/build-desktop` (`npm run build:desktop`), never from the committed `public/build`. + +## Packaging locally + +```sh +composer install --no-dev --optimize-autoloader +npm install +npm run build:desktop +bash scripts/desktop/build-app.sh +bash scripts/desktop/fetch-php.sh darwin arm64 # platform: darwin|linux|win32, arch: arm64|x64 +bash scripts/desktop/smoke-test.sh "$PWD/desktop/resources/bin/darwin/arm64/php" +npm --prefix desktop ci +npm run desktop:build +npm run desktop:package -- --mac --arm64 # --win --x64 / --linux --x64 +``` + +Installers land in `desktop/release/`. + +`fetch-php.sh` builds a static PHP with [static-php-cli](https://github.com/crazywhalecc/static-php-cli) on macOS/Linux (slow the first time, cached afterwards) and downloads the official php.net build on Windows. + +## Releasing + +The `releases` workflow builds and attaches desktop installers (macOS arm64/x64 dmg+zip, Windows x64 NSIS installer, Linux x86_64 AppImage) to every GitHub release automatically. The `desktop-release` workflow can also be dispatched on its own with an existing release tag to (re)build and upload the desktop apps for that release. + +### Code signing secrets (all optional) + +Without these secrets the workflows produce unsigned builds (macOS users must right-click → Open on first launch). + +| Secret | Purpose | +| --- | --- | +| `DESKTOP_MAC_CSC_LINK` | Developer ID Application certificate (.p12, base64) | +| `DESKTOP_MAC_CSC_KEY_PASSWORD` | Password for the .p12 | +| `DESKTOP_APPLE_ID` | Apple ID for notarization | +| `DESKTOP_APPLE_APP_SPECIFIC_PASSWORD` | App-specific password for notarization | +| `DESKTOP_APPLE_TEAM_ID` | Apple team ID for notarization | +| `DESKTOP_WIN_CSC_LINK` | Windows code signing certificate (.pfx, base64) | +| `DESKTOP_WIN_CSC_KEY_PASSWORD` | Password for the .pfx | + +Notarization runs automatically when the three `DESKTOP_APPLE_*` secrets are present together with the macOS certificate. diff --git a/desktop/build/icon.png b/desktop/build/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3035fd2f8e897dc952345ba32e0015f6a8523e11 GIT binary patch literal 251649 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

WYB>$1h1EO#DIxd9vjDa0s;;L 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 O=dk_p14a8T-u_SJvZV+B literal 0 HcmV?d00001 diff --git a/desktop/electron-builder.yml b/desktop/electron-builder.yml index 881cd56c0..0eaf701bb 100644 --- a/desktop/electron-builder.yml +++ b/desktop/electron-builder.yml @@ -1,5 +1,6 @@ appId: com.vitodeploy.vito productName: Vito +artifactName: Vito-${version}-${os}-${arch}.${ext} directories: output: release files: @@ -16,12 +17,27 @@ extraResources: - "**/*" mac: category: public.app-category.developer-tools + hardenedRuntime: true + gatekeeperAssess: false + entitlements: entitlements.mac.plist + entitlementsInherit: entitlements.mac.plist + binaries: + - "Resources/bin/darwin/${arch}/php" target: - dmg - zip +dmg: + sign: false win: target: - nsis +nsis: + oneClick: false + allowToChangeInstallationDirectory: true linux: + category: Development + maintainer: VitoDeploy target: - AppImage +publish: null +npmRebuild: false diff --git a/desktop/entitlements.mac.plist b/desktop/entitlements.mac.plist new file mode 100644 index 000000000..645f4beff --- /dev/null +++ b/desktop/entitlements.mac.plist @@ -0,0 +1,18 @@ + + + + + com.apple.security.cs.allow-jit + + com.apple.security.cs.allow-unsigned-executable-memory + + com.apple.security.cs.allow-dyld-environment-variables + + com.apple.security.cs.disable-library-validation + + com.apple.security.network.client + + com.apple.security.network.server + + + diff --git a/desktop/package.json b/desktop/package.json index ff51f6d2c..3436918c4 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -1,5 +1,6 @@ { "name": "@vitodeploy/vito-desktop", + "productName": "Vito", "private": true, "version": "0.1.0", "type": "module", diff --git a/desktop/src/laravel-runtime.ts b/desktop/src/laravel-runtime.ts index cc8e9b04c..c9ac30ed4 100644 --- a/desktop/src/laravel-runtime.ts +++ b/desktop/src/laravel-runtime.ts @@ -20,7 +20,7 @@ export function startLaravelProcesses(context: RuntimeContext, supervisor: Proce supervisor.start({ name: 'vito-websocket', command: context.phpBinary, - args: ['artisan', 'ws:serve', '--host=127.0.0.1', `--port=${context.websocketPort}`], + args: [...context.phpArgs, 'artisan', 'ws:serve', '--host=127.0.0.1', `--port=${context.websocketPort}`], ...common, }); @@ -28,31 +28,42 @@ export function startLaravelProcesses(context: RuntimeContext, supervisor: Proce name: 'vito-queue', command: context.phpBinary, args: [ + ...context.phpArgs, 'artisan', 'queue:work', 'database', '--queue=default,ssh,ssh-certbot', '--sleep=3', - '--timeout=3660', + '--timeout=3600', '--tries=1', ], + gracefulStop: { + command: context.phpBinary, + args: [...context.phpArgs, 'artisan', 'queue:restart'], + }, ...common, }); supervisor.start({ name: 'vito-scheduler', command: context.phpBinary, - args: ['artisan', 'schedule:work'], + args: [...context.phpArgs, 'artisan', 'schedule:work'], ...common, }); supervisor.start({ name: 'vito-http', command: context.phpBinary, - args: ['artisan', 'serve', '--host=127.0.0.1', `--port=${context.httpPort}`, '--no-reload'], + args: [ + ...context.phpArgs, + '-S', + `127.0.0.1:${context.httpPort}`, + join(context.appRoot, 'vendor', 'laravel', 'framework', 'src', 'Illuminate', 'Foundation', 'resources', 'server.php'), + ], maxRestarts: 3, restartWindowMs: 60000, - ...common, + cwd: join(context.appRoot, 'public'), + env: process.platform === 'win32' ? context.env : { ...context.env, PHP_CLI_SERVER_WORKERS: '8' }, }); } @@ -63,7 +74,7 @@ function artisan( name: string, timeoutMs?: number, ): Promise { - return runCommand(context.phpBinary, ['artisan', ...args], { + return runCommand(context.phpBinary, [...context.phpArgs, 'artisan', ...args], { cwd: context.appRoot, env: context.env, log, @@ -80,10 +91,9 @@ async function ensureSshKeys(context: RuntimeContext, log: DesktopLog): Promise< return; } - try { - await artisan(context, log, ['ssh-key:generate'], 'artisan:ssh-key-generate', 120000); - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - log.error(`Unable to generate SSH keys during desktop startup: ${message}`); + await artisan(context, log, ['ssh-key:generate'], 'artisan:ssh-key-generate', 120000); + + if (!existsSync(publicKey) || !existsSync(privateKey)) { + throw new Error('SSH key generation completed without creating both required key files'); } } diff --git a/desktop/src/main.ts b/desktop/src/main.ts index 961d8a8ef..48992d63c 100644 --- a/desktop/src/main.ts +++ b/desktop/src/main.ts @@ -1,14 +1,15 @@ -import { app, BrowserWindow, dialog } from 'electron'; +import { app, BrowserWindow, dialog, shell } from 'electron'; import { join } from 'node:path'; import { waitForHttp } from './health-check.js'; import { prepareLaravelRuntime, startLaravelProcesses } from './laravel-runtime.js'; import { loadingPage, errorPage } from './loading-page.js'; import { DesktopLog } from './logging.js'; -import { ProcessSupervisor } from './process-supervisor.js'; +import { cleanupStaleProcesses, ProcessSupervisor } from './process-supervisor.js'; import { createRuntimeContext } from './runtime-paths.js'; let mainWindow: BrowserWindow | null = null; let supervisor: ProcessSupervisor | null = null; +let quitting = false; const gotLock = app.requestSingleInstanceLock(); @@ -16,6 +17,12 @@ if (!gotLock) { app.quit(); } +for (const signal of ['SIGINT', 'SIGTERM'] as const) { + process.on(signal, () => { + app.quit(); + }); +} + app.on('second-instance', () => { if (!mainWindow) { return; @@ -29,11 +36,18 @@ app.on('second-instance', () => { }); app.on('before-quit', async (event) => { + if (quitting) { + event.preventDefault(); + + return; + } + if (!supervisor) { return; } event.preventDefault(); + quitting = true; const currentSupervisor = supervisor; supervisor = null; await currentSupervisor.stopAll(); @@ -47,11 +61,26 @@ app.whenReady().then(async () => { try { const context = await createRuntimeContext(); const log = new DesktopLog(context.logPath); - supervisor = new ProcessSupervisor(log); + const pidFilePath = join(context.runtimePath, 'processes.json'); + + cleanupStaleProcesses(pidFilePath, log); + + supervisor = new ProcessSupervisor(log, pidFilePath, (name) => { + if (quitting || name !== 'vito-http') { + return; + } + + const message = 'The local Vito backend repeatedly crashed and could not be restarted.'; + log.error(message); + void mainWindow?.loadURL(errorPage(message)); + dialog.showErrorBox('Vito backend stopped', message); + }); log.info(`Using Laravel app path: ${context.appRoot}`); log.info(`Using desktop data path: ${context.dataPath}`); + attachNavigationGuards(mainWindow, context.appUrl); + await mainWindow.loadURL(loadingPage('Migrating local database...')); await prepareLaravelRuntime(context, log); @@ -82,7 +111,30 @@ function createWindow(): BrowserWindow { contextIsolation: true, nodeIntegration: false, preload: join(app.getAppPath(), 'dist', 'preload.js'), - sandbox: false, + sandbox: true, }, }); } + +function attachNavigationGuards(window: BrowserWindow, appUrl: string): void { + const openExternally = (url: string): void => { + if (url.startsWith('http://') || url.startsWith('https://')) { + void shell.openExternal(url); + } + }; + + window.webContents.setWindowOpenHandler(({ url }) => { + openExternally(url); + + return { action: 'deny' }; + }); + + window.webContents.on('will-navigate', (event, url) => { + if (url === appUrl || url.startsWith(`${appUrl}/`)) { + return; + } + + event.preventDefault(); + openExternally(url); + }); +} diff --git a/desktop/src/process-supervisor.ts b/desktop/src/process-supervisor.ts index a9946e713..05ed3c7cb 100644 --- a/desktop/src/process-supervisor.ts +++ b/desktop/src/process-supervisor.ts @@ -1,4 +1,6 @@ -import { ChildProcessWithoutNullStreams, spawn } from 'node:child_process'; +import { ChildProcessWithoutNullStreams, execFileSync, spawn } from 'node:child_process'; +import { existsSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import { basename } from 'node:path'; import { DesktopLog } from './logging.js'; export type ManagedProcessOptions = { @@ -10,42 +12,171 @@ export type ManagedProcessOptions = { persistent?: boolean; maxRestarts?: number; restartWindowMs?: number; + restartBackoffMaxMs?: number; + gracefulStop?: { + command: string; + args: string[]; + timeoutMs?: number; + }; }; export class ProcessSupervisor { private readonly processes = new Map(); - constructor(private readonly log: DesktopLog) {} + constructor( + private readonly log: DesktopLog, + private readonly pidFilePath: string, + private readonly onRestartLimitReached?: (name: string) => void, + ) {} start(options: ManagedProcessOptions): void { if (this.processes.has(options.name)) { return; } - const process = new ManagedProcess(options, this.log); + const process = new ManagedProcess(options, this.log, this.onRestartLimitReached, () => this.recordPids()); this.processes.set(options.name, process); process.start(); } async stopAll(): Promise { - const processes = [...this.processes.values()].reverse(); - await Promise.all(processes.map((process) => process.stop())); + const shutdownOrder = ['vito-queue', 'vito-scheduler', 'vito-websocket', 'vito-http']; + const ordered = shutdownOrder + .map((name) => this.processes.get(name)) + .filter((process): process is ManagedProcess => process !== undefined); + const remaining = [...this.processes.entries()] + .filter(([name]) => !shutdownOrder.includes(name)) + .map(([, process]) => process); + + for (const process of [...ordered, ...remaining]) { + await process.stop(); + } this.processes.clear(); + rmSync(this.pidFilePath, { force: true }); + } + + private recordPids(): void { + const entries = [...this.processes.values()] + .map((process) => process.describe()) + .filter((entry): entry is StaleProcessEntry => entry !== null); + + try { + writeFileSync(this.pidFilePath, JSON.stringify(entries), 'utf8'); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + this.log.error(`Unable to record supervised process pids: ${message}`); + } + } +} + +type StaleProcessEntry = { + pid: number; + name: string; + command: string; +}; + +export function cleanupStaleProcesses(pidFilePath: string, log: DesktopLog): void { + if (!existsSync(pidFilePath)) { + return; + } + + try { + const entries = JSON.parse(readFileSync(pidFilePath, 'utf8')) as StaleProcessEntry[]; + + for (const entry of entries) { + if (typeof entry?.pid !== 'number' || typeof entry?.command !== 'string') { + continue; + } + + for (const pid of findStalePids(entry)) { + log.info(`Killing stale ${entry.name} process ${pid} left over from a previous session`); + + try { + process.kill(pid, 'SIGKILL'); + } catch {} + } + } + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + log.error(`Unable to clean up stale processes: ${message}`); + } + + rmSync(pidFilePath, { force: true }); +} + +function findStalePids(entry: StaleProcessEntry): number[] { + try { + if (process.platform === 'win32') { + const output = execFileSync('tasklist', ['/FI', `PID eq ${entry.pid}`, '/FO', 'CSV', '/NH'], { encoding: 'utf8' }); + + return output.toLowerCase().includes(basename(entry.command.split(' ')[0]).toLowerCase()) ? [entry.pid] : []; + } + + const output = execFileSync('ps', ['-axo', 'pid=,pgid=,command='], { encoding: 'utf8', maxBuffer: 16 * 1024 * 1024 }); + const pids: number[] = []; + + for (const line of output.split('\n')) { + const match = line.match(/^\s*(\d+)\s+(\d+)\s+(.*)$/); + if (!match) { + continue; + } + + const pid = Number(match[1]); + const pgid = Number(match[2]); + + if ((pid === entry.pid || pgid === entry.pid) && match[3].includes(entry.command)) { + pids.push(pid); + } + } + + return pids; + } catch { + return []; } } +function killProcessTree(child: ChildProcessWithoutNullStreams, signal: NodeJS.Signals): void { + if (process.platform !== 'win32' && typeof child.pid === 'number') { + try { + process.kill(-child.pid, signal); + + return; + } catch {} + } + + child.kill(signal); +} + class ManagedProcess { private child: ChildProcessWithoutNullStreams | null = null; private stopping = false; private restarts: number[] = []; + private consecutiveRestarts = 0; + private startedAt = 0; + private restartTimer: NodeJS.Timeout | null = null; constructor( private readonly options: ManagedProcessOptions, private readonly log: DesktopLog, + private readonly onRestartLimitReached?: (name: string) => void, + private readonly onChange?: () => void, ) {} + describe(): { pid: number; name: string; command: string } | null { + if (!this.child || typeof this.child.pid !== 'number') { + return null; + } + + return { + pid: this.child.pid, + name: this.options.name, + command: [this.options.command, ...this.options.args].join(' '), + }; + } + start(): void { this.stopping = false; + this.startedAt = Date.now(); this.log.info(`Starting ${this.options.name}: ${this.options.command} ${this.options.args.join(' ')}`); this.child = spawn(this.options.command, this.options.args, { @@ -53,8 +184,11 @@ class ManagedProcess { env: this.options.env, stdio: 'pipe', windowsHide: true, + detached: process.platform !== 'win32', }); + this.onChange?.(); + this.child.stdout.on('data', (chunk: Buffer) => this.log.line(this.options.name, chunk.toString())); this.child.stderr.on('data', (chunk: Buffer) => this.log.line(`${this.options.name}:stderr`, chunk.toString())); @@ -65,9 +199,20 @@ class ManagedProcess { this.child.on('exit', (code, signal) => { this.log.info(`${this.options.name} exited with code=${code ?? 'null'} signal=${signal ?? 'null'}`); this.child = null; + this.onChange?.(); + + if (Date.now() - this.startedAt >= 60000) { + this.consecutiveRestarts = 0; + } if (!this.stopping && this.options.persistent !== false && this.canRestart()) { - setTimeout(() => this.start(), 1000); + const delay = Math.min(1000 * 2 ** this.consecutiveRestarts, this.options.restartBackoffMaxMs ?? 30000); + this.consecutiveRestarts += 1; + this.log.info(`Restarting ${this.options.name} in ${delay}ms`); + this.restartTimer = setTimeout(() => { + this.restartTimer = null; + this.start(); + }, delay); } }); } @@ -75,16 +220,60 @@ class ManagedProcess { async stop(timeoutMs = 8000): Promise { this.stopping = true; - if (!this.child || this.child.killed) { + if (this.restartTimer) { + clearTimeout(this.restartTimer); + this.restartTimer = null; + } + + if (!this.child || this.child.exitCode !== null || this.child.signalCode !== null) { return; } const child = this.child; + if (this.options.gracefulStop) { + try { + await runCommand(this.options.gracefulStop.command, this.options.gracefulStop.args, { + cwd: this.options.cwd, + env: this.options.env, + log: this.log, + name: `${this.options.name}:graceful-stop`, + timeoutMs: this.options.gracefulStop.timeoutMs ?? 30000, + }); + await this.waitForExit(child, timeoutMs); + return; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + this.log.error(`${this.options.name} graceful stop failed: ${message}`); + } + } + + await this.terminate(child, timeoutMs); + } + + private waitForExit(child: ChildProcessWithoutNullStreams, timeoutMs: number): Promise { + return new Promise((resolve, reject) => { + if (child.exitCode !== null || child.signalCode !== null) { + resolve(); + return; + } + + const timeout = setTimeout(() => { + reject(new Error(`${this.options.name} did not exit gracefully`)); + }, timeoutMs); + + child.once('exit', () => { + clearTimeout(timeout); + resolve(); + }); + }); + } + + private async terminate(child: ChildProcessWithoutNullStreams, timeoutMs: number): Promise { await new Promise((resolve) => { const timeout = setTimeout(() => { - if (!child.killed) { - child.kill('SIGKILL'); + if (child.exitCode === null && child.signalCode === null) { + killProcessTree(child, 'SIGKILL'); } resolve(); }, timeoutMs); @@ -94,7 +283,7 @@ class ManagedProcess { resolve(); }); - child.kill('SIGTERM'); + killProcessTree(child, 'SIGTERM'); }); } @@ -107,6 +296,7 @@ class ManagedProcess { if (this.restarts.length >= maxRestarts) { this.log.error(`${this.options.name} restart limit reached`); + this.onRestartLimitReached?.(this.options.name); return false; } diff --git a/desktop/src/runtime-paths.ts b/desktop/src/runtime-paths.ts index 53ce3ee30..0303600d0 100644 --- a/desktop/src/runtime-paths.ts +++ b/desktop/src/runtime-paths.ts @@ -1,6 +1,6 @@ import { app } from 'electron'; import { existsSync, mkdirSync, writeFileSync } from 'node:fs'; -import { join, resolve } from 'node:path'; +import { dirname, join, resolve } from 'node:path'; import { appKey, readEnvFile, secret, writeEnvFile } from './env-file.js'; import { getAvailablePort } from './ports.js'; @@ -17,6 +17,7 @@ export type RuntimeContext = { websocketPort: number; appUrl: string; phpBinary: string; + phpArgs: string[]; env: NodeJS.ProcessEnv; }; @@ -33,6 +34,7 @@ export async function createRuntimeContext(): Promise { const appUrl = `http://127.0.0.1:${httpPort}`; const appRoot = resolveAppRoot(); const phpBinary = resolvePhpBinary(); + const phpArgs = resolvePhpArgs(phpBinary); ensureRuntimeDirectories(storagePath, runtimePath, bootstrapCachePath); ensureFile(join(storagePath, 'database.sqlite')); @@ -72,13 +74,19 @@ export async function createRuntimeContext(): Promise { writeEnvFile(envFile, envValues); - const env = { + const env: NodeJS.ProcessEnv = { ...process.env, ...envValues, VITO_ENV_PATH: envPath, VITO_STORAGE_PATH: storagePath, }; + const caBundle = join(dirname(phpBinary), 'cacert.pem'); + if (existsSync(caBundle)) { + env.SSL_CERT_FILE = caBundle; + env.CURL_CA_BUNDLE = caBundle; + } + return { appRoot, dataPath, @@ -92,6 +100,7 @@ export async function createRuntimeContext(): Promise { websocketPort, appUrl, phpBinary, + phpArgs, env, }; } @@ -142,3 +151,24 @@ function resolvePhpBinary(): string { const exe = process.platform === 'win32' ? 'php.exe' : 'php'; return join(process.resourcesPath, 'bin', process.platform, process.arch, exe); } + +function resolvePhpArgs(phpBinary: string): string[] { + const args: string[] = []; + const binDir = dirname(phpBinary); + + if (app.isPackaged) { + args.push('-d', 'memory_limit=512M'); + } + + const caBundle = join(binDir, 'cacert.pem'); + if (existsSync(caBundle)) { + args.push('-d', `curl.cainfo=${caBundle}`, '-d', `openssl.cafile=${caBundle}`); + } + + const extensionDir = join(binDir, 'ext'); + if (process.platform === 'win32' && existsSync(extensionDir)) { + args.push('-d', `extension_dir=${extensionDir}`); + } + + return args; +} diff --git a/docs/desktop-app-plan.md b/docs/desktop-app-plan.md index b23f18ef4..2b2b49b84 100644 --- a/docs/desktop-app-plan.md +++ b/docs/desktop-app-plan.md @@ -755,25 +755,24 @@ Exit: ## Work Checklist -- [ ] Run NativePHP spike. -- [ ] Decide NativePHP vs custom Electron. +- [x] Decide NativePHP vs custom Electron: custom Electron shell bundling a static PHP built with static-php-cli (the same runtime approach NativePHP uses). - [x] Add desktop env profile. - [x] Add storage path override. - [x] Add database queue connection. -- [ ] Confirm jobs table migration covers all queue needs. +- [x] Confirm jobs table migration covers all queue needs. - [x] Remove Redis requirement from desktop mode. - [ ] Disable Horizon runtime in desktop mode. - [x] Make websocket port dynamic. -- [ ] Add desktop first-run admin setup. +- [x] Add desktop first-run admin setup. - [x] Add Electron shell. - [x] Add process supervisor. - [x] Add app data path management. - [x] Add health check based launch. - [x] Add graceful shutdown. - [ ] Add diagnostics. -- [ ] Add desktop smoke tests. -- [ ] Add packaging. -- [ ] Add code signing. +- [x] Add desktop smoke tests (scripts/desktop/smoke-test.sh, run in CI). +- [x] Add packaging (electron-builder + scripts/desktop + desktop-release workflow). +- [x] Add code signing (optional GitHub secrets, unsigned builds without them). - [ ] Add auto-update. - [ ] Run beta hardening matrix. diff --git a/package.json b/package.json index dff13de25..49f862729 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "type": "module", "scripts": { "build": "vite build", + "build:desktop": "vite build --mode desktop", "build:ssr": "vite build && vite build --ssr", "desktop:build": "npm --prefix desktop run build", "desktop:dev": "npm --prefix desktop run dev", diff --git a/scripts/desktop/build-app.sh b/scripts/desktop/build-app.sh new file mode 100644 index 000000000..bf47feaea --- /dev/null +++ b/scripts/desktop/build-app.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env bash +# +# Assembles the Laravel application distribution consumed by the desktop +# Electron shell into desktop-build/app. +# +# Prerequisites (run from the repository root): +# composer install --no-dev --optimize-autoloader +# npm run build:desktop +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +DIST="$ROOT/desktop-build/app" + +if [ ! -f "$ROOT/vendor/autoload.php" ]; then + echo "vendor/autoload.php is missing. Run: composer install --no-dev --optimize-autoloader" >&2 + exit 1 +fi + +if [ -f "$ROOT/vendor/bin/phpunit" ]; then + echo "Warning: vendor contains dev dependencies. Run composer install with --no-dev for release builds." >&2 +fi + +if [ ! -f "$ROOT/public/build-desktop/manifest.json" ]; then + echo "public/build-desktop/manifest.json is missing. Run: npm run build:desktop" >&2 + exit 1 +fi + +rm -rf "$ROOT/desktop-build" +mkdir -p "$DIST" + +INCLUDES=( + app + bootstrap + config + database + lang + public + resources/deployment-scripts + resources/views + vendor + artisan + composer.json + composer.lock +) + +tar -cf - \ + -C "$ROOT" \ + --exclude='public/build' \ + --exclude='public/hot' \ + --exclude='public/storage' \ + --exclude='bootstrap/cache/*.php' \ + --exclude='.DS_Store' \ + "${INCLUDES[@]}" | tar -xf - -C "$DIST" + +for file in \ + "$DIST/artisan" \ + "$DIST/vendor/autoload.php" \ + "$DIST/public/index.php" \ + "$DIST/public/build-desktop/manifest.json" \ + "$DIST/resources/views/ssh/services/webserver/nginx/vhost.mustache"; do + if [ ! -f "$file" ]; then + echo "Desktop app distribution is incomplete: missing $file" >&2 + exit 1 + fi +done + +echo "Desktop app distribution assembled at $DIST" diff --git a/scripts/desktop/fetch-php.sh b/scripts/desktop/fetch-php.sh new file mode 100644 index 000000000..13bd154e5 --- /dev/null +++ b/scripts/desktop/fetch-php.sh @@ -0,0 +1,121 @@ +#!/usr/bin/env bash +# +# Provisions the portable PHP runtime bundled with the desktop app into +# desktop/resources/bin///. +# +# macOS/Linux: builds a static PHP CLI binary with static-php-cli (spc) using +# the exact extension set Vito needs. CI caches the output directory, so the +# build only runs when this script changes. +# Windows: downloads the official php.net NTS build and generates a php.ini +# enabling the required extension DLLs. +# +# Usage: scripts/desktop/fetch-php.sh +set -euo pipefail + +PLATFORM="${1:?usage: fetch-php.sh }" +ARCH="${2:?usage: fetch-php.sh }" + +PHP_SERIES="8.4" +SPC_VERSION="2.8.5" +COMMON_EXTENSIONS="bcmath,ctype,curl,dom,fileinfo,filter,ftp,gmp,iconv,intl,mbstring,mbregex,opcache,openssl,pdo,pdo_sqlite,phar,session,simplexml,sockets,sodium,sqlite3,tokenizer,xml,xmlreader,xmlwriter,zip,zlib" +UNIX_EXTENSIONS="$COMMON_EXTENSIONS,pcntl,posix" + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +TARGET="$ROOT/desktop/resources/bin/$PLATFORM/$ARCH" +mkdir -p "$TARGET" + +PHP_BINARY="$TARGET/php" +if [ "$PLATFORM" = "win32" ]; then + PHP_BINARY="$TARGET/php.exe" +fi + +if [ ! -f "$TARGET/cacert.pem" ]; then + curl -fsSL --retry 3 https://curl.se/ca/cacert.pem -o "$TARGET/cacert.pem" +fi + +if [ -f "$PHP_BINARY" ]; then + echo "PHP runtime already present at $PHP_BINARY" + "$PHP_BINARY" -v + exit 0 +fi + +fetch_windows() { + local zip="$TARGET/php-windows.zip" + + curl -fsSL --retry 3 \ + "https://windows.php.net/downloads/releases/latest/php-${PHP_SERIES}-nts-Win32-vs17-x64-latest.zip" \ + -o "$zip" + unzip -qo "$zip" -d "$TARGET" + rm -f "$zip" + + cat > "$TARGET/php.ini" <<'INI' +extension_dir = "ext" +extension = curl +extension = fileinfo +extension = ftp +extension = gmp +extension = intl +extension = mbstring +extension = openssl +extension = pdo_sqlite +extension = sockets +extension = sodium +extension = sqlite3 +extension = zip +zend_extension = opcache +memory_limit = 512M +INI + + local system32="/c/Windows/System32" + for dll in vcruntime140.dll vcruntime140_1.dll msvcp140.dll; do + if [ -f "$system32/$dll" ] && [ ! -f "$TARGET/$dll" ]; then + cp "$system32/$dll" "$TARGET/" + fi + done +} + +fetch_unix() { + local spc_platform + local spc_arch + + case "$PLATFORM" in + darwin) spc_platform="macos" ;; + linux) spc_platform="linux" ;; + *) echo "Unsupported platform: $PLATFORM" >&2; exit 1 ;; + esac + + case "$ARCH" in + arm64) spc_arch="aarch64" ;; + x64) spc_arch="x86_64" ;; + *) echo "Unsupported arch: $ARCH" >&2; exit 1 ;; + esac + + local workdir + workdir="$(mktemp -d)" + + curl -fsSL --retry 3 \ + "https://github.com/crazywhalecc/static-php-cli/releases/download/${SPC_VERSION}/spc-${spc_platform}-${spc_arch}.tar.gz" \ + -o "$workdir/spc.tar.gz" + tar -xzf "$workdir/spc.tar.gz" -C "$workdir" + chmod +x "$workdir/spc" + + ( + cd "$workdir" + ./spc doctor --auto-fix + ./spc download --with-php="$PHP_SERIES" --for-extensions="$UNIX_EXTENSIONS" --prefer-pre-built + ./spc build --build-cli "$UNIX_EXTENSIONS" + ) + + cp "$workdir/buildroot/bin/php" "$PHP_BINARY" + chmod +x "$PHP_BINARY" + rm -rf "$workdir" +} + +if [ "$PLATFORM" = "win32" ]; then + fetch_windows +else + fetch_unix +fi + +"$PHP_BINARY" -v +echo "PHP runtime installed at $PHP_BINARY" diff --git a/scripts/desktop/smoke-test.sh b/scripts/desktop/smoke-test.sh new file mode 100644 index 000000000..981d9a275 --- /dev/null +++ b/scripts/desktop/smoke-test.sh @@ -0,0 +1,129 @@ +#!/usr/bin/env bash +# +# Boots the assembled desktop app distribution (desktop-build/app) with the +# given PHP binary the same way the Electron shell does: verifies required +# extensions, runs migrations, caches config/routes/views, then serves HTTP +# and asserts the health endpoint responds. +# +# Usage: scripts/desktop/smoke-test.sh +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +APP="$ROOT/desktop-build/app" +PHP_BIN="${1:?usage: smoke-test.sh }" + +if [ ! -f "$APP/artisan" ]; then + echo "desktop-build/app is missing. Run scripts/desktop/build-app.sh first." >&2 + exit 1 +fi + +to_php_path() { + if command -v cygpath >/dev/null 2>&1; then + cygpath -m "$1" + else + echo "$1" + fi +} + +IS_WINDOWS=false +case "$(uname -s)" in + MINGW* | MSYS* | CYGWIN*) IS_WINDOWS=true ;; +esac + +BIN_DIR="$(cd "$(dirname "$PHP_BIN")" && pwd)" +PHP=("$PHP_BIN") + +if [ -f "$BIN_DIR/cacert.pem" ]; then + CA_PATH="$(to_php_path "$BIN_DIR/cacert.pem")" + PHP+=(-d "curl.cainfo=$CA_PATH" -d "openssl.cafile=$CA_PATH") +fi + +if [ "$IS_WINDOWS" = "true" ] && [ -d "$BIN_DIR/ext" ]; then + PHP+=(-d "extension_dir=$(to_php_path "$BIN_DIR/ext")") +fi + +"${PHP[@]}" -v + +REQUIRED_EXTENSIONS=(ctype curl dom fileinfo filter ftp iconv intl mbstring openssl pdo_sqlite phar session simplexml sockets sqlite3 tokenizer xml xmlreader xmlwriter zip zlib) +if [ "$IS_WINDOWS" != "true" ]; then + REQUIRED_EXTENSIONS+=(pcntl posix) +fi + +LOADED="$("${PHP[@]}" -m)" +for extension in "${REQUIRED_EXTENSIONS[@]}"; do + if ! grep -qix "$extension" <<< "$LOADED"; then + echo "Bundled PHP is missing required extension: $extension" >&2 + exit 1 + fi +done + +DATA="$(mktemp -d)" +trap 'rm -rf "$DATA"' EXIT + +mkdir -p \ + "$DATA/storage/app/public" \ + "$DATA/storage/framework/cache/data" \ + "$DATA/storage/framework/sessions" \ + "$DATA/storage/framework/views" \ + "$DATA/storage/logs" \ + "$DATA/runtime/cache" + +touch "$DATA/storage/database.sqlite" + +PHP_DATA="$(to_php_path "$DATA")" + +export VITO_DESKTOP=true +export VITO_DATA_PATH="$PHP_DATA" +export VITO_ENV_PATH="$PHP_DATA/runtime" +export VITO_STORAGE_PATH="$PHP_DATA/storage" +export APP_ENV=production +export APP_DEBUG=false +export APP_KEY="base64:$("${PHP[@]}" -r 'echo base64_encode(random_bytes(32));')" +export APP_URL="http://127.0.0.1:18211" +export APP_SERVICES_CACHE="$PHP_DATA/runtime/cache/services.php" +export APP_PACKAGES_CACHE="$PHP_DATA/runtime/cache/packages.php" +export APP_CONFIG_CACHE="$PHP_DATA/runtime/cache/config.php" +export APP_ROUTES_CACHE="$PHP_DATA/runtime/cache/routes.php" +export APP_EVENTS_CACHE="$PHP_DATA/runtime/cache/events.php" +export VIEW_COMPILED_PATH="$PHP_DATA/storage/framework/views" +export DB_CONNECTION=sqlite +export DB_DATABASE=database.sqlite +export QUEUE_CONNECTION=database +export CACHE_DRIVER=file +export SESSION_DRIVER=file +export FILESYSTEM_DISK=local +export MAIL_MAILER=log +export WS_HOST=127.0.0.1 +export WS_PORT=18212 +export WS_BROADCAST_SECRET=smoke-test-secret + +touch "$DATA/runtime/.env" + +cd "$APP" + +"${PHP[@]}" artisan --version +"${PHP[@]}" artisan migrate --force +"${PHP[@]}" artisan optimize + +ROUTER="$(to_php_path "$APP/vendor/laravel/framework/src/Illuminate/Foundation/resources/server.php")" +(cd "$APP/public" && exec "${PHP[@]}" -S 127.0.0.1:18211 "$ROUTER") & +SERVER_PID=$! +trap 'kill "$SERVER_PID" 2>/dev/null || true; rm -rf "$DATA"' EXIT + +HEALTHY=false +for _ in $(seq 1 30); do + if curl -fsS "http://127.0.0.1:18211/api/health" | grep -q '"success":true'; then + HEALTHY=true + break + fi + sleep 1 +done + +if [ "$HEALTHY" != "true" ]; then + echo "Desktop app HTTP health check failed" >&2 + exit 1 +fi + +curl -fsS "http://127.0.0.1:18211/desktop/login" >/dev/null + +echo "Desktop app smoke test passed" diff --git a/tests/Feature/NotificationChannelsTest.php b/tests/Feature/NotificationChannelsTest.php index 72da209a9..2753cb663 100644 --- a/tests/Feature/NotificationChannelsTest.php +++ b/tests/Feature/NotificationChannelsTest.php @@ -274,16 +274,16 @@ public function test_guest_cannot_access_notification_channels(): void ]); $this->get(route('notification-channels')) - ->assertRedirect('/'); + ->assertRedirect(route('login')); $this->post(route('notification-channels.store'), []) - ->assertRedirect('/'); + ->assertRedirect(route('login')); $this->patch(route('notification-channels.update', $channel), []) - ->assertRedirect('/'); + ->assertRedirect(route('login')); $this->delete(route('notification-channels.destroy', $channel)) - ->assertRedirect('/'); + ->assertRedirect(route('login')); } public function test_cannot_manipulate_user_id_on_creation(): void diff --git a/tests/Feature/ServerProvidersTest.php b/tests/Feature/ServerProvidersTest.php index 32310f7a2..54a8f5f61 100644 --- a/tests/Feature/ServerProvidersTest.php +++ b/tests/Feature/ServerProvidersTest.php @@ -183,19 +183,19 @@ public function test_guest_cannot_access_server_providers(): void ]); $this->get(route('server-providers')) - ->assertRedirect('/'); + ->assertRedirect(route('login')); $this->get(route('server-providers.regions', $serverProvider)) - ->assertRedirect('/'); + ->assertRedirect(route('login')); $this->post(route('server-providers.store'), []) - ->assertRedirect('/'); + ->assertRedirect(route('login')); $this->patch(route('server-providers.update', $serverProvider), []) - ->assertRedirect('/'); + ->assertRedirect(route('login')); $this->delete(route('server-providers.destroy', $serverProvider)) - ->assertRedirect('/'); + ->assertRedirect(route('login')); } public function test_cannot_manipulate_user_id_on_creation(): void diff --git a/tests/Feature/SourceControlsTest.php b/tests/Feature/SourceControlsTest.php index b175876bc..4eb505c9c 100644 --- a/tests/Feature/SourceControlsTest.php +++ b/tests/Feature/SourceControlsTest.php @@ -191,16 +191,16 @@ public function test_guest_cannot_access_source_controls(): void ]); $this->get(route('source-controls')) - ->assertRedirect('/'); + ->assertRedirect(route('login')); $this->post(route('source-controls.store'), []) - ->assertRedirect('/'); + ->assertRedirect(route('login')); $this->patch(route('source-controls.update', $sourceControl), []) - ->assertRedirect('/'); + ->assertRedirect(route('login')); $this->delete(route('source-controls.destroy', $sourceControl)) - ->assertRedirect('/'); + ->assertRedirect(route('login')); } public function test_cannot_manipulate_user_id_on_creation(): void diff --git a/tests/Feature/StorageProvidersTest.php b/tests/Feature/StorageProvidersTest.php index 660628f7f..698478d06 100644 --- a/tests/Feature/StorageProvidersTest.php +++ b/tests/Feature/StorageProvidersTest.php @@ -239,16 +239,16 @@ public function test_guest_cannot_access_storage_providers(): void ]); $this->get(route('storage-providers')) - ->assertRedirect('/'); + ->assertRedirect(route('login')); $this->post(route('storage-providers.store'), []) - ->assertRedirect('/'); + ->assertRedirect(route('login')); $this->patch(route('storage-providers.update', $storageProvider), []) - ->assertRedirect('/'); + ->assertRedirect(route('login')); $this->delete(route('storage-providers.destroy', $storageProvider)) - ->assertRedirect('/'); + ->assertRedirect(route('login')); } public function test_cannot_manipulate_user_id_on_creation(): void diff --git a/vite.config.ts b/vite.config.ts index 17ddb32a1..d98dc565e 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -5,12 +5,13 @@ import laravel from 'laravel-vite-plugin'; import { resolve } from 'node:path'; import { defineConfig } from 'vite'; -export default defineConfig({ +export default defineConfig(({ mode }) => ({ plugins: [ laravel({ input: ['resources/css/app.css', 'resources/js/app.tsx'], ssr: 'resources/js/ssr.tsx', refresh: true, + buildDirectory: mode === 'desktop' ? 'build-desktop' : 'build', }), inertia(), react(), @@ -25,4 +26,4 @@ export default defineConfig({ 'decimal.js-light': resolve(__dirname, 'node_modules/decimal.js-light/decimal.mjs'), }, }, -}); +})); From 64be7ae72f0a4b0432c788b7cdabb69fd700dd7a Mon Sep 17 00:00:00 2001 From: Saeed Vaziry Date: Fri, 17 Jul 2026 22:19:15 +0200 Subject: [PATCH 3/6] Address desktop review findings - Enforce 2FA on desktop unlock (TOTP + recovery codes) and rate limit desktop auth routes - Move desktop mode check into EnsureDesktopMode middleware; extract SetupDesktopAdmin and VerifyDesktopUnlock actions composing CreateUser - Reuse UserResource and UserInfo on the desktop login screen - Fix single-instance race: losing instance exits before booting runtime - Redact APP_KEY/WS_BROADCAST_SECRET in desktop process logs; write runtime .env, sqlite database, and data directories with restrictive permissions - Unescape quoted values when reading the runtime .env back - Add per-attempt timeout to health checks; handle whenReady rejections - Gate VITO_ENV_PATH/VITO_STORAGE_PATH overrides behind VITO_DESKTOP - Verify checksums for spc and php.net downloads; pass workflow secrets explicitly instead of inherit --- .github/workflows/desktop-release.yml | 15 ++++ .github/workflows/releases.yml | 9 ++- app/Actions/Desktop/SetupDesktopAdmin.php | 35 +++++++++ app/Actions/Desktop/VerifyDesktopUnlock.php | 54 +++++++++++++ .../Controllers/DesktopAuthController.php | 73 ++++-------------- app/Http/Kernel.php | 2 + app/Http/Middleware/EnsureDesktopMode.php | 18 +++++ app/Http/Middleware/HandleInertiaRequests.php | 2 +- app/Providers/AppServiceProvider.php | 2 +- app/Providers/AuthServiceProvider.php | 40 +++++----- bootstrap/app.php | 12 +-- config/desktop.php | 1 + desktop/src/env-file.ts | 12 ++- desktop/src/health-check.ts | 2 +- desktop/src/logging.ts | 23 +++++- desktop/src/main.ts | 75 +++++++++++-------- desktop/src/runtime-paths.ts | 6 +- resources/js/pages/auth/desktop-login.tsx | 60 ++++++++++----- resources/js/types/index.d.ts | 2 +- scripts/desktop/fetch-php.sh | 40 +++++++++- tests/Feature/DesktopAuthTest.php | 65 ++++++++++++++++ 21 files changed, 401 insertions(+), 147 deletions(-) create mode 100644 app/Actions/Desktop/SetupDesktopAdmin.php create mode 100644 app/Actions/Desktop/VerifyDesktopUnlock.php create mode 100644 app/Http/Middleware/EnsureDesktopMode.php diff --git a/.github/workflows/desktop-release.yml b/.github/workflows/desktop-release.yml index 01babba0e..a97f0c53f 100644 --- a/.github/workflows/desktop-release.yml +++ b/.github/workflows/desktop-release.yml @@ -12,6 +12,21 @@ on: description: 'Existing release tag to build and attach desktop apps to' required: true type: string + secrets: + DESKTOP_MAC_CSC_LINK: + required: false + DESKTOP_MAC_CSC_KEY_PASSWORD: + required: false + DESKTOP_WIN_CSC_LINK: + required: false + DESKTOP_WIN_CSC_KEY_PASSWORD: + required: false + DESKTOP_APPLE_ID: + required: false + DESKTOP_APPLE_APP_SPECIFIC_PASSWORD: + required: false + DESKTOP_APPLE_TEAM_ID: + required: false permissions: contents: write diff --git a/.github/workflows/releases.yml b/.github/workflows/releases.yml index f0f46b162..54936618c 100644 --- a/.github/workflows/releases.yml +++ b/.github/workflows/releases.yml @@ -186,7 +186,14 @@ jobs: uses: ./.github/workflows/desktop-release.yml with: version: ${{ needs.release.outputs.version }} - secrets: inherit + secrets: + DESKTOP_MAC_CSC_LINK: ${{ secrets.DESKTOP_MAC_CSC_LINK }} + DESKTOP_MAC_CSC_KEY_PASSWORD: ${{ secrets.DESKTOP_MAC_CSC_KEY_PASSWORD }} + DESKTOP_WIN_CSC_LINK: ${{ secrets.DESKTOP_WIN_CSC_LINK }} + DESKTOP_WIN_CSC_KEY_PASSWORD: ${{ secrets.DESKTOP_WIN_CSC_KEY_PASSWORD }} + DESKTOP_APPLE_ID: ${{ secrets.DESKTOP_APPLE_ID }} + DESKTOP_APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.DESKTOP_APPLE_APP_SPECIFIC_PASSWORD }} + DESKTOP_APPLE_TEAM_ID: ${{ secrets.DESKTOP_APPLE_TEAM_ID }} docker-tags: runs-on: ubuntu-24.04 diff --git a/app/Actions/Desktop/SetupDesktopAdmin.php b/app/Actions/Desktop/SetupDesktopAdmin.php new file mode 100644 index 000000000..45aef6061 --- /dev/null +++ b/app/Actions/Desktop/SetupDesktopAdmin.php @@ -0,0 +1,35 @@ + $input + */ + public function create(array $input): User + { + Validator::make($input, [ + 'password' => ['required', 'string', 'confirmed'], + ])->validate(); + + return DB::transaction(function () use ($input): User { + $user = app(CreateUser::class)->create([ + 'name' => $input['name'] ?? null, + 'email' => $input['email'] ?? null, + 'password' => $input['password'] ?? null, + 'role' => UserRole::ADMIN->value, + ]); + + $user->ensureHasDefaultProject(); + + return $user; + }); + } +} diff --git a/app/Actions/Desktop/VerifyDesktopUnlock.php b/app/Actions/Desktop/VerifyDesktopUnlock.php new file mode 100644 index 000000000..bc1f8fa3f --- /dev/null +++ b/app/Actions/Desktop/VerifyDesktopUnlock.php @@ -0,0 +1,54 @@ + $input + */ + public function verify(User $user, array $input): void + { + $validated = Validator::make($input, [ + 'password' => ['required', 'string'], + 'code' => ['nullable', 'string'], + ])->validate(); + + if (! Hash::check($validated['password'], $user->password)) { + throw ValidationException::withMessages([ + 'password' => __('auth.password'), + ]); + } + + if ($user->hasEnabledTwoFactorAuthentication()) { + $this->verifyTwoFactorCode($user, (string) ($validated['code'] ?? '')); + } + } + + private function verifyTwoFactorCode(User $user, string $code): void + { + if ($code !== '') { + if (app(TwoFactorAuthenticationProvider::class)->verify(decrypt($user->two_factor_secret), $code)) { + return; + } + + foreach ($user->recoveryCodes() as $recoveryCode) { + if (hash_equals($recoveryCode, $code)) { + $user->replaceRecoveryCode($code); + + return; + } + } + } + + throw ValidationException::withMessages([ + 'code' => __('The provided two factor authentication code was invalid.'), + ]); + } +} diff --git a/app/Http/Controllers/DesktopAuthController.php b/app/Http/Controllers/DesktopAuthController.php index 7c9cc0d79..a0e08564a 100644 --- a/app/Http/Controllers/DesktopAuthController.php +++ b/app/Http/Controllers/DesktopAuthController.php @@ -2,105 +2,67 @@ namespace App\Http\Controllers; +use App\Actions\Desktop\SetupDesktopAdmin; +use App\Actions\Desktop\VerifyDesktopUnlock; +use App\Http\Resources\UserResource; use App\Models\User; use App\Providers\RouteServiceProvider; -use App\Support\DesktopRuntime; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; -use Illuminate\Support\Facades\Hash; -use Illuminate\Validation\ValidationException; use Inertia\Inertia; use Inertia\Response; use Spatie\RouteAttributes\Attributes\Get; +use Spatie\RouteAttributes\Attributes\Middleware; use Spatie\RouteAttributes\Attributes\Post; use Spatie\RouteAttributes\Attributes\Prefix; #[Prefix('desktop')] +#[Middleware('desktop')] class DesktopAuthController extends Controller { private const LOCKED_USER_ID = 'desktop.locked_user_id'; - private const LOCKED_AT = 'desktop.locked_at'; - #[Get('/login', name: 'desktop.login', middleware: 'guest')] public function create(Request $request): Response { - abort_unless(DesktopRuntime::enabled(), 404); - $lockedUserId = $this->lockedUserId($request); - $users = User::query() - ->orderBy('name') - ->get(['id', 'name', 'email', 'is_admin', 'created_at', 'updated_at']) - ->map(fn (User $user): array => [ - 'id' => $user->id, - 'name' => $user->name, - 'email' => $user->email, - 'is_admin' => $user->is_admin, - 'created_at' => $user->created_at, - 'updated_at' => $user->updated_at, - ]); + $users = User::query()->orderBy('name')->get(); return Inertia::render('auth/desktop-login', [ - 'users' => $users, + 'users' => UserResource::collection($users), 'setup_required' => $users->isEmpty(), 'locked' => $lockedUserId !== null, 'locked_user_id' => $lockedUserId, ]); } - #[Post('/setup', name: 'desktop.setup', middleware: 'guest')] - public function setup(Request $request): RedirectResponse + #[Post('/setup', name: 'desktop.setup', middleware: ['guest', 'throttle:desktop-auth'])] + public function setup(Request $request, SetupDesktopAdmin $setupDesktopAdmin): RedirectResponse { - abort_unless(DesktopRuntime::enabled(), 404); abort_unless(User::query()->doesntExist(), 404); - $validated = $request->validate([ - 'name' => ['required', 'string', 'max:255'], - 'email' => ['required', 'string', 'email', 'max:255', 'unique:users,email'], - 'password' => ['required', 'string', 'min:8', 'confirmed'], - ]); - - /** @var User $user */ - $user = User::query()->create([ - 'name' => $validated['name'], - 'email' => $validated['email'], - 'password' => Hash::make($validated['password']), - 'timezone' => 'UTC', - 'is_admin' => true, - ]); - - $user->ensureHasDefaultProject(); + $user = $setupDesktopAdmin->create($request->all()); Auth::login($user); - $request->session()->forget([self::LOCKED_USER_ID, self::LOCKED_AT]); + $request->session()->forget(self::LOCKED_USER_ID); $request->session()->regenerate(); return redirect()->intended(RouteServiceProvider::HOME); } - #[Post('/login/{user}', name: 'desktop.login.store', middleware: 'guest')] - public function store(Request $request, User $user): RedirectResponse + #[Post('/login/{user}', name: 'desktop.login.store', middleware: ['guest', 'throttle:desktop-auth'])] + public function store(Request $request, User $user, VerifyDesktopUnlock $verifyDesktopUnlock): RedirectResponse { - abort_unless(DesktopRuntime::enabled(), 404); - if ($this->lockedUserId($request) !== null) { - $validated = $request->validate([ - 'password' => ['required', 'string'], - ]); - - if (! Hash::check($validated['password'], $user->password)) { - throw ValidationException::withMessages([ - 'password' => __('auth.password'), - ]); - } + $verifyDesktopUnlock->verify($user, $request->all()); } Auth::login($user); - $request->session()->forget([self::LOCKED_USER_ID, self::LOCKED_AT]); + $request->session()->forget(self::LOCKED_USER_ID); $request->session()->regenerate(); $user->ensureHasDefaultProject(); @@ -111,8 +73,6 @@ public function store(Request $request, User $user): RedirectResponse #[Post('/lock', name: 'desktop.lock', middleware: 'auth')] public function lock(Request $request): RedirectResponse { - abort_unless(DesktopRuntime::enabled(), 404); - /** @var User $user */ $user = $request->user(); @@ -120,7 +80,6 @@ public function lock(Request $request): RedirectResponse $request->session()->migrate(true); $request->session()->put(self::LOCKED_USER_ID, $user->id); - $request->session()->put(self::LOCKED_AT, now()->timestamp); $request->session()->regenerateToken(); return to_route('desktop.login'); @@ -137,7 +96,7 @@ private function lockedUserId(Request $request): ?int $lockedUserId = (int) $lockedUserId; if (! User::query()->whereKey($lockedUserId)->exists()) { - $request->session()->forget([self::LOCKED_USER_ID, self::LOCKED_AT]); + $request->session()->forget(self::LOCKED_USER_ID); return null; } diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 6465107fa..d281d55a9 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -5,6 +5,7 @@ use App\Http\Middleware\Authenticate; use App\Http\Middleware\CanSeeProjectMiddleware; use App\Http\Middleware\EncryptCookies; +use App\Http\Middleware\EnsureDesktopMode; use App\Http\Middleware\HandleAppearance; use App\Http\Middleware\HandleInertiaRequests; use App\Http\Middleware\HasProjectMiddleware; @@ -98,5 +99,6 @@ class Kernel extends HttpKernel 'has-project' => HasProjectMiddleware::class, 'can-see-project' => CanSeeProjectMiddleware::class, 'must-be-admin' => MustBeAdminMiddleware::class, + 'desktop' => EnsureDesktopMode::class, ]; } diff --git a/app/Http/Middleware/EnsureDesktopMode.php b/app/Http/Middleware/EnsureDesktopMode.php new file mode 100644 index 000000000..db247107d --- /dev/null +++ b/app/Http/Middleware/EnsureDesktopMode.php @@ -0,0 +1,18 @@ + DesktopRuntime::enabled() - ? to_route('desktop.login') - : Inertia::render('auth/login')); - Fortify::requestPasswordResetLinkView(fn () => DesktopRuntime::enabled() - ? to_route('desktop.login') - : Inertia::render('auth/forgot-password')); - Fortify::confirmPasswordView(fn () => DesktopRuntime::enabled() - ? to_route('desktop.login') - : Inertia::render('auth/confirm-password')); - Fortify::resetPasswordView(fn (Request $request) => DesktopRuntime::enabled() - ? to_route('desktop.login') - : Inertia::render('auth/reset-password', [ - 'email' => $request->email, - 'token' => $request->route('token'), - ])); - Fortify::twoFactorChallengeView(fn () => DesktopRuntime::enabled() - ? to_route('desktop.login') - : Inertia::render('auth/two-factor')); + Fortify::loginView($this->desktopAware(fn () => Inertia::render('auth/login'))); + Fortify::requestPasswordResetLinkView($this->desktopAware(fn () => Inertia::render('auth/forgot-password'))); + Fortify::confirmPasswordView($this->desktopAware(fn () => Inertia::render('auth/confirm-password'))); + Fortify::resetPasswordView($this->desktopAware(fn (Request $request) => Inertia::render('auth/reset-password', [ + 'email' => $request->email, + 'token' => $request->route('token'), + ]))); + Fortify::twoFactorChallengeView($this->desktopAware(fn () => Inertia::render('auth/two-factor'))); RateLimiter::for('login', function (Request $request) { $throttleKey = Str::transliterate(Str::lower($request->input(Fortify::username()).'|'.$request->ip())); @@ -54,5 +46,17 @@ public function boot(): void RateLimiter::for('two-factor', function (Request $request) { return Limit::perMinute(5)->by($request->session()->get('login.id')); }); + + RateLimiter::for('desktop-auth', function (Request $request) { + $user = $request->route('user'); + $userKey = $user instanceof User ? $user->id : (string) $user; + + return Limit::perMinute(5)->by($userKey.'|'.$request->ip()); + }); + } + + private function desktopAware(Closure $view): Closure + { + return fn (Request $request) => DesktopRuntime::enabled() ? to_route('desktop.login') : $view($request); } } diff --git a/bootstrap/app.php b/bootstrap/app.php index c406b154a..a231f2620 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -26,12 +26,14 @@ return is_string($value) && $value !== '' ? $value : null; }; -if (($environmentPath = $envValue('VITO_ENV_PATH')) !== null) { - $app->useEnvironmentPath($environmentPath); -} +if (filter_var($envValue('VITO_DESKTOP') ?? false, FILTER_VALIDATE_BOOL)) { + if (($environmentPath = $envValue('VITO_ENV_PATH')) !== null) { + $app->useEnvironmentPath($environmentPath); + } -if (($storagePath = $envValue('VITO_STORAGE_PATH')) !== null) { - $app->useStoragePath($storagePath); + if (($storagePath = $envValue('VITO_STORAGE_PATH')) !== null) { + $app->useStoragePath($storagePath); + } } /* diff --git a/config/desktop.php b/config/desktop.php index 5b0e79cff..b74ac92d2 100644 --- a/config/desktop.php +++ b/config/desktop.php @@ -5,4 +5,5 @@ 'data_path' => env('VITO_DATA_PATH'), 'env_path' => env('VITO_ENV_PATH'), 'storage_path' => env('VITO_STORAGE_PATH'), + 'build_directory' => 'build-desktop', ]; diff --git a/desktop/src/env-file.ts b/desktop/src/env-file.ts index 7cd565263..1fe856349 100644 --- a/desktop/src/env-file.ts +++ b/desktop/src/env-file.ts @@ -1,5 +1,5 @@ import { randomBytes } from 'node:crypto'; -import { existsSync, readFileSync, writeFileSync } from 'node:fs'; +import { chmodSync, existsSync, readFileSync, writeFileSync } from 'node:fs'; const SIMPLE_VALUE = /^[A-Za-z0-9_./:@-]*$/; @@ -25,7 +25,9 @@ export function readEnvFile(path: string): Record { const key = trimmed.slice(0, equalsAt).trim(); let value = trimmed.slice(equalsAt + 1).trim(); - if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) { + if (value.startsWith('"') && value.endsWith('"')) { + value = value.slice(1, -1).replace(/\\(["\\])/g, '$1'); + } else if (value.startsWith("'") && value.endsWith("'")) { value = value.slice(1, -1); } @@ -37,7 +39,11 @@ export function readEnvFile(path: string): Record { export function writeEnvFile(path: string, values: Record): void { const lines = Object.entries(values).map(([key, value]) => `${key}=${formatEnvValue(value)}`); - writeFileSync(path, `${lines.join('\n')}\n`, 'utf8'); + writeFileSync(path, `${lines.join('\n')}\n`, { encoding: 'utf8', mode: 0o600 }); + + try { + chmodSync(path, 0o600); + } catch {} } export function appKey(): string { diff --git a/desktop/src/health-check.ts b/desktop/src/health-check.ts index f6ffa2d80..34bc672cd 100644 --- a/desktop/src/health-check.ts +++ b/desktop/src/health-check.ts @@ -4,7 +4,7 @@ export async function waitForHttp(url: string, timeoutMs: number): Promise while (Date.now() < deadline) { try { - const response = await fetch(url); + const response = await fetch(url, { signal: AbortSignal.timeout(3000) }); if (response.ok) { return; } diff --git a/desktop/src/logging.ts b/desktop/src/logging.ts index 91830f5ac..d3e6a69f2 100644 --- a/desktop/src/logging.ts +++ b/desktop/src/logging.ts @@ -2,7 +2,10 @@ import { appendFileSync, mkdirSync } from 'node:fs'; import { dirname } from 'node:path'; export class DesktopLog { - constructor(private readonly path: string) { + constructor( + private readonly path: string, + private readonly redactions: string[] = [], + ) { mkdirSync(dirname(path), { recursive: true }); } @@ -15,10 +18,24 @@ export class DesktopLog { } line(source: string, message: string): void { - appendFileSync(this.path, `[${new Date().toISOString()}] [${source}] ${message}`, 'utf8'); + const redacted = this.redact(message); + const terminated = redacted.endsWith('\n') ? redacted : `${redacted}\n`; + appendFileSync(this.path, `[${new Date().toISOString()}] [${source}] ${terminated}`, 'utf8'); } private write(level: 'info' | 'error', message: string): void { - appendFileSync(this.path, `[${new Date().toISOString()}] [desktop:${level}] ${message}\n`, 'utf8'); + appendFileSync(this.path, `[${new Date().toISOString()}] [desktop:${level}] ${this.redact(message)}\n`, 'utf8'); + } + + private redact(message: string): string { + let redacted = message; + + for (const secret of this.redactions) { + if (secret !== '') { + redacted = redacted.split(secret).join('[redacted]'); + } + } + + return redacted; } } diff --git a/desktop/src/main.ts b/desktop/src/main.ts index 48992d63c..cbf9c10da 100644 --- a/desktop/src/main.ts +++ b/desktop/src/main.ts @@ -14,7 +14,7 @@ let quitting = false; const gotLock = app.requestSingleInstanceLock(); if (!gotLock) { - app.quit(); + app.exit(0); } for (const signal of ['SIGINT', 'SIGTERM'] as const) { @@ -54,47 +54,56 @@ app.on('before-quit', async (event) => { app.exit(0); }); -app.whenReady().then(async () => { - mainWindow = createWindow(); - await mainWindow.loadURL(loadingPage('Preparing local runtime...')); +app + .whenReady() + .then(async () => { + if (!gotLock) { + return; + } - try { - const context = await createRuntimeContext(); - const log = new DesktopLog(context.logPath); - const pidFilePath = join(context.runtimePath, 'processes.json'); + mainWindow = createWindow(); + await mainWindow.loadURL(loadingPage('Preparing local runtime...')); - cleanupStaleProcesses(pidFilePath, log); + try { + const context = await createRuntimeContext(); + const log = new DesktopLog(context.logPath, context.redactions); + const pidFilePath = join(context.runtimePath, 'processes.json'); - supervisor = new ProcessSupervisor(log, pidFilePath, (name) => { - if (quitting || name !== 'vito-http') { - return; - } + cleanupStaleProcesses(pidFilePath, log); - const message = 'The local Vito backend repeatedly crashed and could not be restarted.'; - log.error(message); - void mainWindow?.loadURL(errorPage(message)); - dialog.showErrorBox('Vito backend stopped', message); - }); + supervisor = new ProcessSupervisor(log, pidFilePath, (name) => { + if (quitting || name !== 'vito-http') { + return; + } - log.info(`Using Laravel app path: ${context.appRoot}`); - log.info(`Using desktop data path: ${context.dataPath}`); + const message = 'The local Vito backend repeatedly crashed and could not be restarted.'; + log.error(message); + void mainWindow?.loadURL(errorPage(message)); + dialog.showErrorBox('Vito backend stopped', message); + }); - attachNavigationGuards(mainWindow, context.appUrl); + log.info(`Using Laravel app path: ${context.appRoot}`); + log.info(`Using desktop data path: ${context.dataPath}`); - await mainWindow.loadURL(loadingPage('Migrating local database...')); - await prepareLaravelRuntime(context, log); + attachNavigationGuards(mainWindow, context.appUrl); - await mainWindow.loadURL(loadingPage('Starting backend services...')); - startLaravelProcesses(context, supervisor); - await waitForHttp(`${context.appUrl}/api/health`, 45000); + await mainWindow.loadURL(loadingPage('Migrating local database...')); + await prepareLaravelRuntime(context, log); - await mainWindow.loadURL(`${context.appUrl}/desktop/login`); - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - await mainWindow.loadURL(errorPage(message)); - dialog.showErrorBox('Vito could not start', message); - } -}); + await mainWindow.loadURL(loadingPage('Starting backend services...')); + startLaravelProcesses(context, supervisor); + await waitForHttp(`${context.appUrl}/api/health`, 45000); + + await mainWindow.loadURL(`${context.appUrl}/desktop/login`); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + await mainWindow.loadURL(errorPage(message)); + dialog.showErrorBox('Vito could not start', message); + } + }) + .catch((error) => { + dialog.showErrorBox('Vito could not start', error instanceof Error ? error.message : String(error)); + }); app.on('window-all-closed', () => { app.quit(); diff --git a/desktop/src/runtime-paths.ts b/desktop/src/runtime-paths.ts index 0303600d0..fb918b3b5 100644 --- a/desktop/src/runtime-paths.ts +++ b/desktop/src/runtime-paths.ts @@ -19,6 +19,7 @@ export type RuntimeContext = { phpBinary: string; phpArgs: string[]; env: NodeJS.ProcessEnv; + redactions: string[]; }; export async function createRuntimeContext(): Promise { @@ -102,6 +103,7 @@ export async function createRuntimeContext(): Promise { phpBinary, phpArgs, env, + redactions: [envValues.APP_KEY, envValues.WS_BROADCAST_SECRET], }; } @@ -121,13 +123,13 @@ function ensureRuntimeDirectories(storagePath: string, runtimePath: string, boot ]; for (const directory of directories) { - mkdirSync(directory, { recursive: true }); + mkdirSync(directory, { recursive: true, mode: 0o700 }); } } function ensureFile(path: string): void { if (!existsSync(path)) { - writeFileSync(path, ''); + writeFileSync(path, '', { mode: 0o600 }); } } diff --git a/resources/js/pages/auth/desktop-login.tsx b/resources/js/pages/auth/desktop-login.tsx index d20e0a715..147ff3bf7 100644 --- a/resources/js/pages/auth/desktop-login.tsx +++ b/resources/js/pages/auth/desktop-login.tsx @@ -4,16 +4,14 @@ import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { cn } from '@/lib/utils'; import AuthLayout from '@/layouts/auth/layout'; -import { useInitials } from '@/hooks/use-initials'; +import { UserInfo } from '@/components/user-info'; import { type User } from '@/types/user'; import { Head, router, useForm, usePage } from '@inertiajs/react'; -import { LoaderCircleIcon, LockKeyholeIcon, UserPlusIcon, UserRoundIcon } from 'lucide-react'; +import { LoaderCircleIcon, LockKeyholeIcon, UserPlusIcon } from 'lucide-react'; import { FormEvent, useMemo, useState } from 'react'; -type DesktopUser = Pick; - type DesktopLoginProps = { - users: DesktopUser[]; + users: User[]; setup_required: boolean; locked: boolean; locked_user_id: number | null; @@ -21,10 +19,11 @@ type DesktopLoginProps = { export default function DesktopLogin() { const { users, setup_required, locked, locked_user_id } = usePage().props; - const initials = useInitials(); const [selectedUserId, setSelectedUserId] = useState(locked_user_id); - const loginForm = useForm<{ password: string }>({ + const [choosing, setChoosing] = useState(false); + const loginForm = useForm<{ password: string; code: string }>({ password: '', + code: '', }); const setupForm = useForm<{ name: string; @@ -46,14 +45,25 @@ export default function DesktopLogin() { ? 'Enter the password for the selected user.' : 'Select a local desktop user to continue.'; - const chooseUser = (user: DesktopUser) => { + const chooseUser = (user: User) => { if (!locked) { - router.post(route('desktop.login.store', { user: user.id })); + if (choosing) { + return; + } + + setChoosing(true); + router.post( + route('desktop.login.store', { user: user.id }), + {}, + { + onFinish: () => setChoosing(false), + }, + ); return; } loginForm.clearErrors(); - loginForm.reset('password'); + loginForm.reset('password', 'code'); setSelectedUserId(user.id); }; @@ -73,7 +83,7 @@ export default function DesktopLogin() { } loginForm.post(route('desktop.login.store', { user: selectedUser.id }), { - onFinish: () => loginForm.reset('password'), + onFinish: () => loginForm.reset('password', 'code'), }); }; @@ -161,18 +171,14 @@ export default function DesktopLogin() { key={user.id} type="button" onClick={() => chooseUser(user)} + disabled={!locked && choosing} className={cn( - 'border-input bg-background hover:bg-accent hover:text-accent-foreground flex w-full items-center gap-3 rounded-md border p-3 text-left transition-colors', + 'border-input bg-background hover:bg-accent hover:text-accent-foreground flex w-full items-center gap-3 rounded-md border p-3 text-left transition-colors disabled:pointer-events-none disabled:opacity-50', selected && 'border-ring ring-ring/40 ring-2', )} > - - {initials(user.name) || } - - - {user.name} - {user.email} - + + {!locked && choosing && } {locked && selected && } ); @@ -197,6 +203,22 @@ export default function DesktopLogin() { + {selectedUser.two_factor_enabled && ( +

+ + loginForm.setData('code', e.target.value)} + placeholder="Authentication or recovery code" + /> + +
+ )} +