Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: bundle builds #179

Merged
merged 29 commits into from
Mar 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
2bdf295
feat: native:bundle command
SRWieZ Mar 4, 2025
08a3726
feat: native:bundle --clear
SRWieZ Mar 4, 2025
a2f9888
feat: php.ts run with bundle
SRWieZ Mar 4, 2025
3535d22
fix: require nativephp/laravel on dev-feat/bundle-builds
SRWieZ Mar 4, 2025
b9c6ccf
wip: bundle cached services?
SRWieZ Mar 4, 2025
37eca6a
wip: try with everything first
SRWieZ Mar 4, 2025
613d955
wip: fix .env missing
SRWieZ Mar 4, 2025
666f68b
fix: bundling
SRWieZ Mar 5, 2025
130323a
feat: bundling a base L12 app works 🎉
SRWieZ Mar 5, 2025
e920c44
feat: native:reset command
SRWieZ Mar 10, 2025
b3ecbf6
fix: realpath() returns false if the directory does not exists
SRWieZ Mar 10, 2025
bb7b06a
feat: failsafe
SRWieZ Mar 10, 2025
15de455
wip: it works, but need some minor cleaning/refactoring/testing
SRWieZ Mar 10, 2025
118c85e
perf: fixes and perf of Laravel on subsequent launches
SRWieZ Mar 11, 2025
5724b18
fix: running the bundle in dev mode
SRWieZ Mar 11, 2025
72b29df
fix: consistent appData directory
SRWieZ Mar 11, 2025
ecb8a03
wip: fix running artisan commands in bundle
SRWieZ Mar 11, 2025
029bd2e
feat: artisan commands in bundles
SRWieZ Mar 11, 2025
3e660a7
feat: artisan commands in bundles
SRWieZ Mar 11, 2025
b8f54b9
fix: native:serve will no longer run the bundle
SRWieZ Mar 11, 2025
cba4063
refactor: even more consistency + reducing the size of the bundled bi…
SRWieZ Mar 12, 2025
d28ed6b
feat: Better reset command
SRWieZ Mar 14, 2025
4156153
chore: upgrade outdated packages
SRWieZ Mar 14, 2025
a85d895
docs: added some comments
SRWieZ Mar 14, 2025
6f6fd36
feat: include symlinked composer packages
SRWieZ Mar 14, 2025
07dfc02
Merge branch 'main' into feat/bundle-builds
SRWieZ Mar 14, 2025
22122a5
refactor: per Simon's review
SRWieZ Mar 15, 2025
ed76171
Attempt to understand
simonhamp Mar 18, 2025
45231cd
Attempted fix
simonhamp Mar 18, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,11 @@
"php": "^8.1",
"illuminate/contracts": "^10.0|^11.0|^12.0",
"laravel/prompts": "^0.1.1|^0.2|^0.3",
"nativephp/laravel": "^1.0-beta.2",
"nativephp/laravel": "dev-feat/bundle-builds",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs reverting before merge

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! It was on purpose so @simonhamp could test the PR.

If I have a PR with both repos, I often leave the dependency to help reviewers.

I don't know if it's a good way to do it or not. Feedback is welcome.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No worries! Just a message as a reminder is all! :)

"nativephp/php-bin": "^0.6",
"spatie/laravel-package-tools": "^1.16.4",
"symfony/filesystem": "^6.4|^7.2"
"symfony/filesystem": "^6.4|^7.2",
"ext-zip": "*"
},
"require-dev": {
"laravel/pint": "^1.0",
Expand Down
3 changes: 1 addition & 2 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ parameters:
- src
- config
- database
tmpDir: build/phpstan
checkOctaneCompatibility: true
checkModelProperties: true

noEnvCallsOutsideOfConfig: false
8 changes: 1 addition & 7 deletions resources/js/electron-builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,7 @@ try {
}

if (isBuilding) {
console.log();
console.log('===================================================================');
console.log(' Building for ' + targetOs);
console.log('===================================================================');
console.log();
console.log('Updater config', updaterConfig);
console.log();
console.log(' • updater config', updaterConfig);
}

export default {
Expand Down
28 changes: 17 additions & 11 deletions resources/js/electron-plugin/dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,16 +67,6 @@ class NativePHP {
}
event.preventDefault();
});
if (process.platform === 'win32') {
app.on('second-instance', (event, commandLine, workingDirectory) => {
if (this.mainWindow) {
if (this.mainWindow.isMinimized())
this.mainWindow.restore();
this.mainWindow.focus();
}
this.handleDeepLink(commandLine.pop());
});
}
}
bootstrapApp(app) {
return __awaiter(this, void 0, void 0, function* () {
Expand Down Expand Up @@ -135,12 +125,28 @@ class NativePHP {
else {
app.setAsDefaultProtocolClient(deepLinkProtocol);
}
if (process.platform === 'win32') {
if (process.platform !== "darwin") {
const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) {
app.quit();
return;
}
else {
app.on("second-instance", (event, commandLine, workingDirectory) => {
if (this.mainWindow) {
if (this.mainWindow.isMinimized())
this.mainWindow.restore();
this.mainWindow.focus();
}
notifyLaravel("events", {
event: "\\Native\\Laravel\\Events\\App\\OpenedFromURL",
payload: {
url: commandLine[commandLine.length - 1],
workingDirectory: workingDirectory,
},
});
});
}
}
}
}
Expand Down
140 changes: 91 additions & 49 deletions resources/js/electron-plugin/dist/server/api/childProcess.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,80 +11,122 @@ import express from 'express';
import { utilityProcess } from 'electron';
import state from '../state.js';
import { notifyLaravel } from "../utils.js";
import { getDefaultEnvironmentVariables, getDefaultPhpIniSettings } from "../php.js";
import { getAppPath, getDefaultEnvironmentVariables, getDefaultPhpIniSettings, runningSecureBuild } from "../php.js";
import killSync from "kill-sync";
import { fileURLToPath } from "url";
import { join } from "path";
const router = express.Router();
function startProcess(settings) {
const { alias, cmd, cwd, env, persistent } = settings;
const { alias, cmd, cwd, env, persistent, spawnTimeout = 30000 } = settings;
if (getProcess(alias) !== undefined) {
return state.processes[alias];
}
const proc = utilityProcess.fork(fileURLToPath(new URL('../../electron-plugin/dist/server/childProcess.js', import.meta.url)), cmd, {
cwd,
stdio: 'pipe',
serviceName: alias,
env: Object.assign(Object.assign({}, process.env), env)
});
proc.stdout.on('data', (data) => {
notifyLaravel('events', {
event: 'Native\\Laravel\\Events\\ChildProcess\\MessageReceived',
payload: {
alias,
data: data.toString(),
try {
const proc = utilityProcess.fork(fileURLToPath(new URL('../../electron-plugin/dist/server/childProcess.js', import.meta.url)), cmd, {
cwd,
stdio: 'pipe',
serviceName: alias,
env: Object.assign(Object.assign({}, process.env), env)
});
const startTimeout = setTimeout(() => {
if (!state.processes[alias] || !state.processes[alias].pid) {
console.error(`Process [${alias}] failed to start within timeout period`);
try {
proc.kill();
}
catch (e) {
}
notifyLaravel('events', {
event: 'Native\\Laravel\\Events\\ChildProcess\\StartupError',
payload: {
alias,
error: 'Startup timeout exceeded',
}
});
}
}, spawnTimeout);
proc.stdout.on('data', (data) => {
notifyLaravel('events', {
event: 'Native\\Laravel\\Events\\ChildProcess\\MessageReceived',
payload: {
alias,
data: data.toString(),
}
});
});
});
proc.stderr.on('data', (data) => {
console.error('Error received from process [' + alias + ']:', data.toString());
notifyLaravel('events', {
event: 'Native\\Laravel\\Events\\ChildProcess\\ErrorReceived',
payload: {
alias,
data: data.toString(),
proc.stderr.on('data', (data) => {
console.error('Process [' + alias + '] ERROR:', data.toString().trim());
notifyLaravel('events', {
event: 'Native\\Laravel\\Events\\ChildProcess\\ErrorReceived',
payload: {
alias,
data: data.toString(),
}
});
});
proc.on('spawn', () => {
clearTimeout(startTimeout);
console.log('Process [' + alias + '] spawned!');
state.processes[alias] = {
pid: proc.pid,
proc,
settings
};
notifyLaravel('events', {
event: 'Native\\Laravel\\Events\\ChildProcess\\ProcessSpawned',
payload: [alias, proc.pid]
});
});
proc.on('exit', (code) => {
clearTimeout(startTimeout);
console.log(`Process [${alias}] exited with code [${code}].`);
notifyLaravel('events', {
event: 'Native\\Laravel\\Events\\ChildProcess\\ProcessExited',
payload: {
alias,
code,
}
});
const settings = Object.assign({}, getSettings(alias));
delete state.processes[alias];
if (settings === null || settings === void 0 ? void 0 : settings.persistent) {
console.log('Process [' + alias + '] watchdog restarting...');
setTimeout(() => startProcess(settings), 1000);
}
});
});
proc.on('spawn', () => {
console.log('Process [' + alias + '] spawned!');
state.processes[alias] = {
pid: proc.pid,
return {
pid: null,
proc,
settings
};
}
catch (error) {
console.error(`Failed to create process [${alias}]: ${error.message}`);
notifyLaravel('events', {
event: 'Native\\Laravel\\Events\\ChildProcess\\ProcessSpawned',
payload: [alias, proc.pid]
});
});
proc.on('exit', (code) => {
console.log(`Process [${alias}] exited with code [${code}].`);
notifyLaravel('events', {
event: 'Native\\Laravel\\Events\\ChildProcess\\ProcessExited',
event: 'Native\\Laravel\\Events\\ChildProcess\\StartupError',
payload: {
alias,
code,
error: error.toString(),
}
});
const settings = Object.assign({}, getSettings(alias));
delete state.processes[alias];
if (settings.persistent) {
console.log('Process [' + alias + '] watchdog restarting...');
startProcess(settings);
}
});
return {
pid: null,
proc,
settings
};
return {
pid: null,
proc: null,
settings,
error: error.message
};
}
}
function startPhpProcess(settings) {
const defaultEnv = getDefaultEnvironmentVariables(state.randomSecret, state.electronApiPort);
const iniSettings = Object.assign(Object.assign({}, getDefaultPhpIniSettings()), state.phpIni);
const customIniSettings = settings.iniSettings || {};
const iniSettings = Object.assign(Object.assign(Object.assign({}, getDefaultPhpIniSettings()), state.phpIni), customIniSettings);
const iniArgs = Object.keys(iniSettings).map(key => {
return ['-d', `${key}=${iniSettings[key]}`];
}).flat();
if (settings.cmd[0] === 'artisan' && runningSecureBuild()) {
settings.cmd.unshift(join(getAppPath(), 'build', '__nativephp_app_bundle'));
}
settings = Object.assign(Object.assign({}, settings), { cmd: [state.php, ...iniArgs, ...settings.cmd], env: Object.assign(Object.assign({}, settings.env), defaultEnv) });
return startProcess(settings);
}
Expand Down
43 changes: 39 additions & 4 deletions resources/js/electron-plugin/dist/server/api/notification.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import { Notification } from 'electron';
import { notifyLaravel } from "../utils.js";
const router = express.Router();
router.post('/', (req, res) => {
const { title, body, subtitle, silent, icon, hasReply, timeoutType, replyPlaceholder, sound, urgency, actions, closeButtonText, toastXml, event: customEvent } = req.body;
const { title, body, subtitle, silent, icon, hasReply, timeoutType, replyPlaceholder, sound, urgency, actions, closeButtonText, toastXml, event: customEvent, reference, } = req.body;
const eventName = customEvent !== null && customEvent !== void 0 ? customEvent : '\\Native\\Laravel\\Events\\Notifications\\NotificationClicked';
const notificationReference = reference !== null && reference !== void 0 ? reference : (Date.now() + '.' + Math.random().toString(36).slice(2, 9));
const notification = new Notification({
title,
body,
Expand All @@ -22,11 +23,45 @@ router.post('/', (req, res) => {
});
notification.on("click", (event) => {
notifyLaravel('events', {
event: eventName,
payload: JSON.stringify(event)
event: eventName || '\\Native\\Laravel\\Events\\Notifications\\NotificationClicked',
payload: {
reference: notificationReference,
event: JSON.stringify(event),
},
});
});
notification.on("action", (event, index) => {
notifyLaravel('events', {
event: '\\Native\\Laravel\\Events\\Notifications\\NotificationActionClicked',
payload: {
reference: notificationReference,
index,
event: JSON.stringify(event),
},
});
});
notification.on("reply", (event, reply) => {
notifyLaravel('events', {
event: '\\Native\\Laravel\\Events\\Notifications\\NotificationReply',
payload: {
reference: notificationReference,
reply,
event: JSON.stringify(event),
},
});
});
notification.on("close", (event) => {
notifyLaravel('events', {
event: '\\Native\\Laravel\\Events\\Notifications\\NotificationClosed',
payload: {
reference: notificationReference,
event: JSON.stringify(event),
},
});
});
notification.show();
res.sendStatus(200);
res.status(200).json({
reference: notificationReference,
});
});
export default router;
Loading
Loading