Skip to content

Commit bfbc40c

Browse files
feat: integrate Laravel Nightwatch support (#68)
* feat: integrate Laravel Nightwatch support Added `laravel/nightwatch` dependency to composer.json and implemented environment variable and server startup logic for Nightwatch integration. Updated API routes to include Nightwatch middleware for enhanced functionality. * refactor: replace Laravel Nightwatch middleware with OptionalNightwatchNever Removed `laravel/nightwatch` dependency and introduced `OptionalNightwatchNever` middleware to conditionally handle Nightwatch functionality. Updated API routes and server logic to reflect middleware replacement and improve compatibility. * plugin build --------- Co-authored-by: Willem Leuverink <willem@leuver.ink>
1 parent 5aa0959 commit bfbc40c

File tree

5 files changed

+167
-4
lines changed

5 files changed

+167
-4
lines changed

config/nativephp-internal.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989
'storage/hot',
9090

9191
// Only needed for local testing
92+
'vendor/nativephp/desktop/.git',
9293
'vendor/nativephp/desktop/resources',
9394
'vendor/nativephp/desktop/vendor',
9495
'vendor/nativephp/php-bin',

resources/electron/electron-plugin/dist/server/php.js

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
77
step((generator = generator.apply(thisArg, _arguments || [])).next());
88
});
99
};
10-
import { mkdirSync, statSync, writeFileSync, existsSync } from 'fs';
10+
import { mkdirSync, statSync, writeFileSync, existsSync, readFileSync } from 'fs';
1111
import fs_extra from 'fs-extra';
1212
const { copySync, mkdirpSync } = fs_extra;
1313
import Store from 'electron-store';
@@ -40,6 +40,53 @@ function shouldMigrateDatabase(store) {
4040
function shouldOptimize(store) {
4141
return process.env.NODE_ENV !== 'development';
4242
}
43+
function hasNightwatchInstalled(appPath) {
44+
const candidateRoots = [
45+
appPath,
46+
join(appPath, "build", "__nativephp_app_bundle")
47+
];
48+
for (const root of candidateRoots) {
49+
if (existsSync(join(root, "vendor", "laravel", "nightwatch"))) {
50+
return true;
51+
}
52+
const composerLock = join(root, "composer.lock");
53+
if (!existsSync(composerLock)) {
54+
continue;
55+
}
56+
try {
57+
if (readFileSync(composerLock, "utf8").includes("\"name\": \"laravel/nightwatch\"")) {
58+
return true;
59+
}
60+
}
61+
catch (_a) {
62+
}
63+
}
64+
return false;
65+
}
66+
function getNightwatchToken(appPath) {
67+
if (process.env.NIGHTWATCH_TOKEN) {
68+
return process.env.NIGHTWATCH_TOKEN;
69+
}
70+
const candidateRoots = [
71+
appPath,
72+
join(appPath, "build", "__nativephp_app_bundle")
73+
];
74+
for (const root of candidateRoots) {
75+
const envPath = join(root, ".env");
76+
if (!existsSync(envPath)) {
77+
continue;
78+
}
79+
try {
80+
const content = readFileSync(envPath, "utf8");
81+
const match = content.match(/^NIGHTWATCH_TOKEN=(.+)$/m);
82+
if (match && match[1]) {
83+
return match[1].replace(/^['"]|['"]$/g, "");
84+
}
85+
}
86+
catch (_a) {
87+
}
88+
}
89+
}
4390
function getPhpPort() {
4491
return __awaiter(this, void 0, void 0, function* () {
4592
const suggestedPort = yield getPort({
@@ -229,13 +276,28 @@ function serveApp(secret, apiPort, phpIniSettings) {
229276
ensureAppFoldersAreAvailable();
230277
console.log('Making sure app folders are available');
231278
const env = getDefaultEnvironmentVariables(secret, apiPort);
279+
const nightwatchToken = getNightwatchToken(appPath);
280+
let phpNightWatchPort;
281+
if (nightwatchToken && hasNightwatchInstalled(appPath)) {
282+
phpNightWatchPort = yield getPhpPort();
283+
env.NIGHTWATCH_TOKEN = nightwatchToken;
284+
env.NIGHTWATCH_INGEST_URI = `127.0.0.1:${phpNightWatchPort}`;
285+
}
286+
else if (nightwatchToken) {
287+
console.log("Skipping Nightwatch: package not installed.");
288+
}
232289
const phpOptions = {
233290
cwd: appPath,
234291
env
235292
};
236293
const store = new Store({
237294
name: 'nativephp',
238295
});
296+
if (env.NIGHTWATCH_INGEST_URI && phpNightWatchPort) {
297+
console.log('Starting Nightwatch server...');
298+
callPhp(['artisan', 'nightwatch:agent', `--listen-on=${env.NIGHTWATCH_INGEST_URI}`], phpOptions, phpIniSettings);
299+
console.log('Nightwatch server started on port:', phpNightWatchPort);
300+
}
239301
if (shouldOptimize(store)) {
240302
console.log('Caching view and routes...');
241303
let result = callPhpSync(['artisan', 'optimize'], phpOptions, phpIniSettings);

resources/electron/electron-plugin/src/server/php.ts

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {mkdirSync, statSync, writeFileSync, existsSync} from 'fs'
1+
import { mkdirSync, statSync, writeFileSync, existsSync, readFileSync } from 'fs';
22
import fs_extra from 'fs-extra';
33

44
const {copySync, mkdirpSync} = fs_extra;
@@ -49,6 +49,65 @@ function shouldOptimize(store) {
4949
// return runningSecureBuild() && store.get('optimized_version') !== app.getVersion();
5050
}
5151

52+
function hasNightwatchInstalled(appPath: string) {
53+
const candidateRoots = [
54+
appPath,
55+
join(appPath, "build", "__nativephp_app_bundle")
56+
];
57+
58+
for (const root of candidateRoots) {
59+
if (existsSync(join(root, "vendor", "laravel", "nightwatch"))) {
60+
return true;
61+
}
62+
63+
const composerLock = join(root, "composer.lock");
64+
65+
if (!existsSync(composerLock)) {
66+
continue;
67+
}
68+
69+
try {
70+
if (readFileSync(composerLock, "utf8").includes("\"name\": \"laravel/nightwatch\"")) {
71+
return true;
72+
}
73+
} catch {
74+
// ignore and keep looking
75+
}
76+
}
77+
78+
return false;
79+
}
80+
81+
function getNightwatchToken(appPath: string) {
82+
if (process.env.NIGHTWATCH_TOKEN) {
83+
return process.env.NIGHTWATCH_TOKEN;
84+
}
85+
86+
const candidateRoots = [
87+
appPath,
88+
join(appPath, "build", "__nativephp_app_bundle")
89+
];
90+
91+
for (const root of candidateRoots) {
92+
const envPath = join(root, ".env");
93+
94+
if (!existsSync(envPath)) {
95+
continue;
96+
}
97+
98+
try {
99+
const content = readFileSync(envPath, "utf8");
100+
const match = content.match(/^NIGHTWATCH_TOKEN=(.+)$/m);
101+
102+
if (match && match[1]) {
103+
return match[1].replace(/^['"]|['"]$/g, "");
104+
}
105+
} catch {
106+
// ignore and keep looking
107+
}
108+
}
109+
}
110+
52111
async function getPhpPort() {
53112
// Try get-port first (fast path)
54113
const suggestedPort = await getPort({
@@ -277,6 +336,9 @@ interface EnvironmentVariables {
277336
APP_ROUTES_CACHE?: string;
278337
APP_EVENTS_CACHE?: string;
279338
VIEW_COMPILED_PATH?: string;
339+
340+
NIGHTWATCH_TOKEN?: string;
341+
NIGHTWATCH_INGEST_URI?: string;
280342
}
281343

282344
function getDefaultEnvironmentVariables(secret?: string, apiPort?: number): EnvironmentVariables {
@@ -342,6 +404,17 @@ function serveApp(secret, apiPort, phpIniSettings): Promise<ProcessResult> {
342404

343405
const env = getDefaultEnvironmentVariables(secret, apiPort);
344406

407+
408+
const nightwatchToken = getNightwatchToken(appPath);
409+
let phpNightWatchPort: number | undefined;
410+
if (nightwatchToken && hasNightwatchInstalled(appPath)) {
411+
phpNightWatchPort = await getPhpPort();
412+
env.NIGHTWATCH_TOKEN = nightwatchToken;
413+
env.NIGHTWATCH_INGEST_URI = `127.0.0.1:${phpNightWatchPort}`;
414+
} else if (nightwatchToken) {
415+
console.log("Skipping Nightwatch: package not installed.");
416+
}
417+
345418
const phpOptions = {
346419
cwd: appPath,
347420
env
@@ -351,6 +424,12 @@ function serveApp(secret, apiPort, phpIniSettings): Promise<ProcessResult> {
351424
name: 'nativephp', // So it doesn't conflict with settings of the app
352425
});
353426

427+
if(env.NIGHTWATCH_INGEST_URI && phpNightWatchPort) {
428+
console.log('Starting Nightwatch server...');
429+
callPhp(['artisan', 'nightwatch:agent', `--listen-on=${env.NIGHTWATCH_INGEST_URI}`], phpOptions, phpIniSettings)
430+
console.log('Nightwatch server started on port:', phpNightWatchPort);
431+
}
432+
354433
// Cache the project
355434
if (shouldOptimize(store)) {
356435
console.log('Caching view and routes...');

routes/api.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@
55
use Native\Desktop\Http\Controllers\CreateSecurityCookieController;
66
use Native\Desktop\Http\Controllers\DispatchEventFromAppController;
77
use Native\Desktop\Http\Controllers\NativeAppBootedController;
8+
use Native\Desktop\Http\Middleware\OptionalNightwatchNever;
89
use Native\Desktop\Http\Middleware\PreventRegularBrowserAccess;
910

10-
Route::group(['middleware' => PreventRegularBrowserAccess::class], function () {
11+
Route::group(['middleware' => [OptionalNightwatchNever::class, PreventRegularBrowserAccess::class]], function () {
1112
Route::post('_native/api/booted', NativeAppBootedController::class);
1213
Route::post('_native/api/events', DispatchEventFromAppController::class);
1314
})->withoutMiddleware(VerifyCsrfToken::class);
1415

15-
Route::get('_native/api/cookie', CreateSecurityCookieController::class);
16+
Route::get('_native/api/cookie', CreateSecurityCookieController::class)->middleware(OptionalNightwatchNever::class);
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
namespace Native\Desktop\Http\Middleware;
4+
5+
use Closure;
6+
use Illuminate\Http\Request;
7+
8+
class OptionalNightwatchNever
9+
{
10+
public function handle(Request $request, Closure $next): mixed
11+
{
12+
if (class_exists(\Laravel\Nightwatch\Http\Middleware\Sample::class)) {
13+
$middleware = app(\Laravel\Nightwatch\Http\Middleware\Sample::class);
14+
15+
return $middleware->handle($request, $next, 0.0);
16+
}
17+
18+
return $next($request);
19+
}
20+
}

0 commit comments

Comments
 (0)