From 9f630944600721b6f481044149742af374847b52 Mon Sep 17 00:00:00 2001 From: Simon Hamp Date: Fri, 29 May 2026 15:38:15 +0900 Subject: [PATCH 1/4] Fix custom PHP binary directory being deleted when path is app root Trim the configured binary package path before pruning so values that normalize to a no-op (./, ., /, trailing /.) no longer collapse to the app root and delete the entire built app. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../Concerns/PrunesVendorDirectory.php | 4 +- tests/Build/PruneVendorDirectoryTest.php | 83 +++++++++++++++++++ 2 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 tests/Build/PruneVendorDirectoryTest.php diff --git a/src/Builder/Concerns/PrunesVendorDirectory.php b/src/Builder/Concerns/PrunesVendorDirectory.php index 7551aaf1..343ffd77 100644 --- a/src/Builder/Concerns/PrunesVendorDirectory.php +++ b/src/Builder/Concerns/PrunesVendorDirectory.php @@ -25,8 +25,8 @@ public function pruneVendorDirectory() ]); // Remove custom php binary package directory - $binaryPackageDirectory = $this->binaryPackageDirectory(); - if (! empty($binaryPackageDirectory) && $filesystem->exists($this->buildPath("app/{$binaryPackageDirectory}"))) { + $binaryPackageDirectory = trim($this->binaryPackageDirectory(), "./\\ \t\n\r\0\x0B"); + if ($binaryPackageDirectory !== '' && $filesystem->exists($this->buildPath("app/{$binaryPackageDirectory}"))) { $filesystem->remove($this->buildPath("app/{$binaryPackageDirectory}")); } } diff --git a/tests/Build/PruneVendorDirectoryTest.php b/tests/Build/PruneVendorDirectoryTest.php new file mode 100644 index 00000000..6bed312c --- /dev/null +++ b/tests/Build/PruneVendorDirectoryTest.php @@ -0,0 +1,83 @@ +remove($buildPath); +}); + +afterEach(function () use ($buildPath) { + $filesystem = new Filesystem; + $filesystem->remove($buildPath); +}); + +/* +|-------------------------------------------------------------------------- +| Mock Build command with anonymous class +|-------------------------------------------------------------------------- +*/ +$command = new class($buildPath) +{ + use LocatesPhpBinary; + use PrunesVendorDirectory; + + public function __construct( + public $buildPath + ) {} + + public function sourcePath(string $path = ''): string + { + return app()->joinPaths($this->buildPath, $path); + } + + public function buildPath(string $path = ''): string + { + return app()->joinPaths($this->buildPath, $path); + } +}; + +/* +|-------------------------------------------------------------------------- +| Tests +|-------------------------------------------------------------------------- +*/ +it('removes the custom php binary package directory', function () use ($buildPath, $command) { + putenv('NATIVEPHP_PHP_BINARY_PATH=php-bin/'); + + createFiles([ + "$buildPath/app/index.php", + "$buildPath/app/php-bin/bin/php", + ]); + + $command->pruneVendorDirectory(); + + expect("$buildPath/app/php-bin")->not->toBeDirectory(); + expect("$buildPath/app/index.php")->toBeFile(); +})->after(fn () => putenv('NATIVEPHP_PHP_BINARY_PATH')); + +it('does not delete the app when the binary path normalizes to the app root', function () use ($buildPath, $command) { + putenv('NATIVEPHP_PHP_BINARY_PATH=./'); + + createFiles([ + "$buildPath/app/index.php", + "$buildPath/app/bin/win/x64/php.exe", + ]); + + $command->pruneVendorDirectory(); + + expect("$buildPath/app/index.php")->toBeFile(); + expect("$buildPath/app/bin/win/x64/php.exe")->toBeFile(); +})->after(fn () => putenv('NATIVEPHP_PHP_BINARY_PATH')); From 783b28e9d81f1e515c8165ec233b18d2487a6ee7 Mon Sep 17 00:00:00 2001 From: simonhamp <31628+simonhamp@users.noreply.github.com> Date: Fri, 29 May 2026 06:38:55 +0000 Subject: [PATCH 2/4] Fix styling --- config/nativephp.php | 4 +++- .../Electron/Updater/UpdaterManager.php | 22 ++++++++++--------- src/Facades/Menu.php | 3 ++- src/Facades/MenuBar.php | 3 ++- .../Middleware/OptionalNightwatchNever.php | 5 +++-- src/NativeServiceProvider.php | 3 ++- src/System.php | 2 +- 7 files changed, 25 insertions(+), 17 deletions(-) diff --git a/config/nativephp.php b/config/nativephp.php index 08ed4177..50df403b 100644 --- a/config/nativephp.php +++ b/config/nativephp.php @@ -1,5 +1,7 @@ \App\Providers\NativeAppServiceProvider::class, + 'provider' => NativeAppServiceProvider::class, /** * A list of environment keys that should be removed from the diff --git a/src/Drivers/Electron/Updater/UpdaterManager.php b/src/Drivers/Electron/Updater/UpdaterManager.php index 5ee904db..2df11012 100644 --- a/src/Drivers/Electron/Updater/UpdaterManager.php +++ b/src/Drivers/Electron/Updater/UpdaterManager.php @@ -2,14 +2,16 @@ namespace Native\Desktop\Drivers\Electron\Updater; +use Illuminate\Contracts\Foundation\Application; use InvalidArgumentException; +use Native\Desktop\Drivers\Electron\Updater\Contracts\Updater; class UpdaterManager { /** * The application instance. * - * @var \Illuminate\Contracts\Foundation\Application + * @var Application */ protected $app; @@ -23,7 +25,7 @@ class UpdaterManager /** * Create a new Updater manager instance. * - * @param \Illuminate\Contracts\Foundation\Application $app + * @param Application $app * @return void */ public function __construct($app) @@ -35,7 +37,7 @@ public function __construct($app) * Get a updater provider instance by name, wrapped in a repository. * * @param string|null $name - * @return \Native\Desktop\Drivers\Electron\Updater\Contracts\Updater + * @return Updater */ public function provider($name = null) { @@ -48,7 +50,7 @@ public function provider($name = null) * Get a updater provider instance. * * @param string|null $driver - * @return \Native\Desktop\Drivers\Electron\Updater\Contracts\Updater + * @return Updater */ public function driver($driver = null) { @@ -59,9 +61,9 @@ public function driver($driver = null) * Resolve the given store. * * @param string $name - * @return \Native\Desktop\Drivers\Electron\Updater\Contracts\Updater + * @return Updater * - * @throws \InvalidArgumentException + * @throws InvalidArgumentException */ public function resolve($name) { @@ -119,7 +121,7 @@ public function setDefaultDriver($name) /** * Set the application instance used by the manager. * - * @param \Illuminate\Contracts\Foundation\Application $app + * @param Application $app * @return $this */ public function setApplication($app) @@ -132,7 +134,7 @@ public function setApplication($app) /** * Create an instance of the spaces updater driver. * - * @return \Native\Desktop\Drivers\Electron\Updater\Contracts\Updater + * @return Updater */ protected function createSpacesDriver(array $config) { @@ -142,7 +144,7 @@ protected function createSpacesDriver(array $config) /** * Create an instance of the spaces updater driver. * - * @return \Native\Desktop\Drivers\Electron\Updater\Contracts\Updater + * @return Updater */ protected function createS3Driver(array $config) { @@ -152,7 +154,7 @@ protected function createS3Driver(array $config) /** * Create an instance of the GitHub updater driver. * - * @return \Native\Desktop\Drivers\Electron\Updater\Contracts\Updater + * @return Updater */ protected function createGitHubDriver(array $config) { diff --git a/src/Facades/Menu.php b/src/Facades/Menu.php index 53e162e1..c93a5493 100644 --- a/src/Facades/Menu.php +++ b/src/Facades/Menu.php @@ -10,6 +10,7 @@ use Native\Desktop\Menu\Items\Radio; use Native\Desktop\Menu\Items\Role; use Native\Desktop\Menu\Items\Separator; +use Native\Desktop\Menu\MenuBuilder; /** * @method static \Native\Desktop\Menu\Menu make(MenuItem ...$items) @@ -48,6 +49,6 @@ class Menu extends Facade { protected static function getFacadeAccessor() { - return \Native\Desktop\Menu\MenuBuilder::class; + return MenuBuilder::class; } } diff --git a/src/Facades/MenuBar.php b/src/Facades/MenuBar.php index 162f16e2..c067e890 100644 --- a/src/Facades/MenuBar.php +++ b/src/Facades/MenuBar.php @@ -4,6 +4,7 @@ use Illuminate\Support\Facades\Facade; use Native\Desktop\Menu\Menu; +use Native\Desktop\MenuBar\MenuBarManager; /** * @method static \Native\Desktop\MenuBar\PendingCreateMenuBar create() @@ -21,6 +22,6 @@ class MenuBar extends Facade { protected static function getFacadeAccessor() { - return \Native\Desktop\MenuBar\MenuBarManager::class; + return MenuBarManager::class; } } diff --git a/src/Http/Middleware/OptionalNightwatchNever.php b/src/Http/Middleware/OptionalNightwatchNever.php index 9bd9df06..7cf3bd2d 100644 --- a/src/Http/Middleware/OptionalNightwatchNever.php +++ b/src/Http/Middleware/OptionalNightwatchNever.php @@ -4,13 +4,14 @@ use Closure; use Illuminate\Http\Request; +use Laravel\Nightwatch\Http\Middleware\Sample; class OptionalNightwatchNever { public function handle(Request $request, Closure $next): mixed { - if (class_exists(\Laravel\Nightwatch\Http\Middleware\Sample::class)) { - $middleware = app(\Laravel\Nightwatch\Http\Middleware\Sample::class); + if (class_exists(Sample::class)) { + $middleware = app(Sample::class); return $middleware->handle($request, $next, 0.0); } diff --git a/src/NativeServiceProvider.php b/src/NativeServiceProvider.php index 4d240ddd..138a7510 100644 --- a/src/NativeServiceProvider.php +++ b/src/NativeServiceProvider.php @@ -3,6 +3,7 @@ namespace Native\Desktop; use Illuminate\Console\Application; +use Illuminate\Contracts\Debug\ExceptionHandler; use Illuminate\Foundation\Application as Foundation; use Illuminate\Foundation\Http\Kernel; use Illuminate\Support\Arr; @@ -100,7 +101,7 @@ public function packageRegistered() if (config('nativephp-internal.running')) { $this->app->singleton( - \Illuminate\Contracts\Debug\ExceptionHandler::class, + ExceptionHandler::class, Handler::class ); diff --git a/src/System.php b/src/System.php index 7fc8104f..fa2d0c7e 100644 --- a/src/System.php +++ b/src/System.php @@ -44,7 +44,7 @@ public function decrypt(string $string): ?string } /** - * @return array<\Native\Desktop\DataObjects\Printer> + * @return array */ public function printers(): array { From bdc6011bf9a0ed0f6099e1ef27e43ee4201aefe7 Mon Sep 17 00:00:00 2001 From: Willem Leuverink Date: Wed, 3 Jun 2026 03:18:54 +0200 Subject: [PATCH 3/4] prune only the php binary archives, not the whole bin dir (#121) --- .../Concerns/PrunesVendorDirectory.php | 14 +++++--- tests/Build/PruneVendorDirectoryTest.php | 34 ++++++++++++++----- 2 files changed, 35 insertions(+), 13 deletions(-) diff --git a/src/Builder/Concerns/PrunesVendorDirectory.php b/src/Builder/Concerns/PrunesVendorDirectory.php index 343ffd77..4d60b380 100644 --- a/src/Builder/Concerns/PrunesVendorDirectory.php +++ b/src/Builder/Concerns/PrunesVendorDirectory.php @@ -24,10 +24,14 @@ public function pruneVendorDirectory() $this->buildPath('app/vendor/nativephp/php-bin'), ]); - // Remove custom php binary package directory - $binaryPackageDirectory = trim($this->binaryPackageDirectory(), "./\\ \t\n\r\0\x0B"); - if ($binaryPackageDirectory !== '' && $filesystem->exists($this->buildPath("app/{$binaryPackageDirectory}"))) { - $filesystem->remove($this->buildPath("app/{$binaryPackageDirectory}")); - } + // Remove the bundled PHP binaries for a custom binary path. + // They get duplicated into the app's build resources, so + // the copies left in the source tree are dead weight. + // + // We remove only the archives we manage, never the + // parent directory, so the user's files are kept, + // and a root path never wipes the app (#115). + $binaryDirectory = $this->buildPath('app/'.$this->binaryPackageDirectory().'bin'); + $filesystem->remove(glob("{$binaryDirectory}/*/*/php-*.zip")); } } diff --git a/tests/Build/PruneVendorDirectoryTest.php b/tests/Build/PruneVendorDirectoryTest.php index 6bed312c..c85d56de 100644 --- a/tests/Build/PruneVendorDirectoryTest.php +++ b/tests/Build/PruneVendorDirectoryTest.php @@ -20,6 +20,8 @@ }); afterEach(function () use ($buildPath) { + putenv('NATIVEPHP_PHP_BINARY_PATH'); + $filesystem = new Filesystem; $filesystem->remove($buildPath); }); @@ -54,30 +56,46 @@ public function buildPath(string $path = ''): string | Tests |-------------------------------------------------------------------------- */ -it('removes the custom php binary package directory', function () use ($buildPath, $command) { +it('prunes the bundled php binary archives from a custom binary directory', function () use ($buildPath, $command) { putenv('NATIVEPHP_PHP_BINARY_PATH=php-bin/'); createFiles([ "$buildPath/app/index.php", - "$buildPath/app/php-bin/bin/php", + "$buildPath/app/php-bin/bin/win/x64/php-8.4.zip", + "$buildPath/app/php-bin/bin/mac/arm64/php-8.4.zip", ]); $command->pruneVendorDirectory(); - expect("$buildPath/app/php-bin")->not->toBeDirectory(); + // The redundant archives are stripped... + expect("$buildPath/app/php-bin/bin/win/x64/php-8.4.zip")->not->toBeFile(); + expect("$buildPath/app/php-bin/bin/mac/arm64/php-8.4.zip")->not->toBeFile(); + + // ...while the rest of the app is left intact. expect("$buildPath/app/index.php")->toBeFile(); -})->after(fn () => putenv('NATIVEPHP_PHP_BINARY_PATH')); +}); -it('does not delete the app when the binary path normalizes to the app root', function () use ($buildPath, $command) { +it('does not delete the app when the binary path is the project root', function () use ($buildPath, $command) { + // Regression test for #115: a binary path that normalises to the app root + // (e.g. binaries kept directly in the project's bin/ directory) must not + // take the whole app down with the prune. putenv('NATIVEPHP_PHP_BINARY_PATH=./'); createFiles([ "$buildPath/app/index.php", - "$buildPath/app/bin/win/x64/php.exe", + "$buildPath/app/bin/win/x64/php-8.4.zip", + "$buildPath/app/bin/mac/arm64/php-8.4.zip", + // Unrelated tooling a user happens to keep in bin/ must survive. + "$buildPath/app/bin/deploy.sh", ]); $command->pruneVendorDirectory(); + // The app and any non-NativePHP files in bin/ are preserved... expect("$buildPath/app/index.php")->toBeFile(); - expect("$buildPath/app/bin/win/x64/php.exe")->toBeFile(); -})->after(fn () => putenv('NATIVEPHP_PHP_BINARY_PATH')); + expect("$buildPath/app/bin/deploy.sh")->toBeFile(); + + // ...but the bundled php archives are still pruned. + expect("$buildPath/app/bin/win/x64/php-8.4.zip")->not->toBeFile(); + expect("$buildPath/app/bin/mac/arm64/php-8.4.zip")->not->toBeFile(); +}); From 01dfd9359432420b7e094bb93e9387ede63f14cb Mon Sep 17 00:00:00 2001 From: gwleuverink Date: Wed, 3 Jun 2026 09:40:16 +0200 Subject: [PATCH 4/4] Cast API secret header to string for Laravel 13.13 compatibility Laravel 13.13.0 tightened the HTTP client's header casting to throw on non-scalar values. The X-NativePHP-Secret header is set from config('nativephp-internal.secret'), which is null when NATIVEPHP_SECRET is unset, so every request blew up with an InvalidArgumentException. Casting to a string keeps null as an empty string, which the client accepts. This is what older Laravel versions did implicitly. --- src/Client/Client.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client/Client.php b/src/Client/Client.php index d6dd7749..cbafee66 100644 --- a/src/Client/Client.php +++ b/src/Client/Client.php @@ -16,7 +16,7 @@ public function __construct() ->baseUrl(config('nativephp-internal.api_url', '')) ->timeout(60 * 60) ->withHeaders([ - 'X-NativePHP-Secret' => config('nativephp-internal.secret'), + 'X-NativePHP-Secret' => (string) config('nativephp-internal.secret'), ]) ->asJson(); }