diff --git a/composer.json b/composer.json
index 9fe38e01..53c6ac6f 100644
--- a/composer.json
+++ b/composer.json
@@ -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",
         "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",
diff --git a/phpstan.neon b/phpstan.neon
index b02c336c..9c47a6b8 100644
--- a/phpstan.neon
+++ b/phpstan.neon
@@ -4,7 +4,6 @@ parameters:
         - src
         - config
         - database
-    tmpDir: build/phpstan
     checkOctaneCompatibility: true
     checkModelProperties: true
-
+    noEnvCallsOutsideOfConfig: false
diff --git a/resources/js/electron-builder.js b/resources/js/electron-builder.js
index ef6defdb..28bc1e6e 100644
--- a/resources/js/electron-builder.js
+++ b/resources/js/electron-builder.js
@@ -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 {
diff --git a/resources/js/electron-plugin/dist/index.js b/resources/js/electron-plugin/dist/index.js
index a85a2cd5..a63ba8ad 100644
--- a/resources/js/electron-plugin/dist/index.js
+++ b/resources/js/electron-plugin/dist/index.js
@@ -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* () {
@@ -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,
+                            },
+                        });
+                    });
+                }
             }
         }
     }
diff --git a/resources/js/electron-plugin/dist/server/api/childProcess.js b/resources/js/electron-plugin/dist/server/api/childProcess.js
index 94b28de5..64ec1dad 100644
--- a/resources/js/electron-plugin/dist/server/api/childProcess.js
+++ b/resources/js/electron-plugin/dist/server/api/childProcess.js
@@ -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);
 }
diff --git a/resources/js/electron-plugin/dist/server/api/notification.js b/resources/js/electron-plugin/dist/server/api/notification.js
index 8433dcce..6058a5df 100644
--- a/resources/js/electron-plugin/dist/server/api/notification.js
+++ b/resources/js/electron-plugin/dist/server/api/notification.js
@@ -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,
@@ -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;
diff --git a/resources/js/electron-plugin/dist/server/php.js b/resources/js/electron-plugin/dist/server/php.js
index 06742711..2334f17d 100644
--- a/resources/js/electron-plugin/dist/server/php.js
+++ b/resources/js/electron-plugin/dist/server/php.js
@@ -9,19 +9,32 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
 };
 import { mkdirSync, statSync, writeFileSync, existsSync } from 'fs';
 import fs_extra from 'fs-extra';
-const { copySync } = fs_extra;
+const { copySync, mkdirpSync } = fs_extra;
 import Store from 'electron-store';
 import { promisify } from 'util';
 import { join } from 'path';
 import { app } from 'electron';
-import { execFile, spawn } from 'child_process';
+import { execFile, spawn, spawnSync } from 'child_process';
 import state from "./state.js";
 import getPort, { portNumbers } from 'get-port';
 const storagePath = join(app.getPath('userData'), 'storage');
 const databasePath = join(app.getPath('userData'), 'database');
 const databaseFile = join(databasePath, 'database.sqlite');
+const bootstrapCache = join(app.getPath('userData'), 'bootstrap', 'cache');
 const argumentEnv = getArgumentEnv();
 const appPath = getAppPath();
+mkdirpSync(bootstrapCache);
+function runningSecureBuild() {
+    return existsSync(join(appPath, 'build', '__nativephp_app_bundle'))
+        && process.env.NODE_ENV !== 'development';
+}
+function shouldMigrateDatabase(store) {
+    return store.get('migrated_version') !== app.getVersion()
+        && process.env.NODE_ENV !== 'development';
+}
+function shouldOptimize(store) {
+    return true;
+}
 function getPhpPort() {
     return __awaiter(this, void 0, void 0, function* () {
         return yield getPort({
@@ -32,42 +45,64 @@ function getPhpPort() {
 }
 function retrievePhpIniSettings() {
     return __awaiter(this, void 0, void 0, function* () {
-        const env = {
-            NATIVEPHP_RUNNING: 'true',
-            NATIVEPHP_STORAGE_PATH: storagePath,
-            NATIVEPHP_DATABASE_PATH: databaseFile,
-        };
+        const env = getDefaultEnvironmentVariables();
         const phpOptions = {
             cwd: appPath,
             env
         };
-        return yield promisify(execFile)(state.php, ['artisan', 'native:php-ini'], phpOptions);
+        let command = ['artisan', 'native:php-ini'];
+        if (runningSecureBuild()) {
+            command.unshift(join(appPath, 'build', '__nativephp_app_bundle'));
+        }
+        return yield promisify(execFile)(state.php, command, phpOptions);
     });
 }
 function retrieveNativePHPConfig() {
     return __awaiter(this, void 0, void 0, function* () {
-        const env = {
-            NATIVEPHP_RUNNING: 'true',
-            NATIVEPHP_STORAGE_PATH: storagePath,
-            NATIVEPHP_DATABASE_PATH: databaseFile,
-        };
+        const env = getDefaultEnvironmentVariables();
         const phpOptions = {
             cwd: appPath,
             env
         };
-        return yield promisify(execFile)(state.php, ['artisan', 'native:config'], phpOptions);
+        let command = ['artisan', 'native:config'];
+        if (runningSecureBuild()) {
+            command.unshift(join(appPath, 'build', '__nativephp_app_bundle'));
+        }
+        return yield promisify(execFile)(state.php, command, phpOptions);
     });
 }
 function callPhp(args, options, phpIniSettings = {}) {
+    if (args[0] === 'artisan' && runningSecureBuild()) {
+        args.unshift(join(appPath, 'build', '__nativephp_app_bundle'));
+    }
     let iniSettings = Object.assign(getDefaultPhpIniSettings(), phpIniSettings);
     Object.keys(iniSettings).forEach(key => {
         args.unshift('-d', `${key}=${iniSettings[key]}`);
     });
+    if (parseInt(process.env.SHELL_VERBOSITY) > 0) {
+        console.log('Calling PHP', state.php, args);
+    }
     return spawn(state.php, args, {
         cwd: options.cwd,
         env: Object.assign(Object.assign({}, process.env), options.env),
     });
 }
+function callPhpSync(args, options, phpIniSettings = {}) {
+    if (args[0] === 'artisan' && runningSecureBuild()) {
+        args.unshift(join(appPath, 'build', '__nativephp_app_bundle'));
+    }
+    let iniSettings = Object.assign(getDefaultPhpIniSettings(), phpIniSettings);
+    Object.keys(iniSettings).forEach(key => {
+        args.unshift('-d', `${key}=${iniSettings[key]}`);
+    });
+    if (parseInt(process.env.SHELL_VERBOSITY) > 0) {
+        console.log('Calling PHP', state.php, args);
+    }
+    return spawnSync(state.php, args, {
+        cwd: options.cwd,
+        env: Object.assign(Object.assign({}, process.env), options.env)
+    });
+}
 function getArgumentEnv() {
     const envArgs = process.argv.filter(arg => arg.startsWith('--env.'));
     const env = {};
@@ -85,7 +120,10 @@ function getAppPath() {
     return appPath;
 }
 function ensureAppFoldersAreAvailable() {
+    console.log('Copying storage folder...');
+    console.log('Storage path:', storagePath);
     if (!existsSync(storagePath) || process.env.NODE_ENV === 'development') {
+        console.log("App path:", appPath);
         copySync(join(appPath, 'storage'), storagePath);
     }
     mkdirSync(databasePath, { recursive: true });
@@ -113,14 +151,13 @@ function getPath(name) {
     }
 }
 function getDefaultEnvironmentVariables(secret, apiPort) {
-    return {
+    let variables = {
         APP_ENV: process.env.NODE_ENV === 'development' ? 'local' : 'production',
         APP_DEBUG: process.env.NODE_ENV === 'development' ? 'true' : 'false',
+        LARAVEL_STORAGE_PATH: storagePath,
+        NATIVEPHP_RUNNING: 'true',
         NATIVEPHP_STORAGE_PATH: storagePath,
         NATIVEPHP_DATABASE_PATH: databaseFile,
-        NATIVEPHP_API_URL: `http://localhost:${apiPort}/api/`,
-        NATIVEPHP_RUNNING: 'true',
-        NATIVEPHP_SECRET: secret,
         NATIVEPHP_USER_HOME_PATH: getPath('home'),
         NATIVEPHP_APP_DATA_PATH: getPath('appData'),
         NATIVEPHP_USER_DATA_PATH: getPath('userData'),
@@ -132,6 +169,18 @@ function getDefaultEnvironmentVariables(secret, apiPort) {
         NATIVEPHP_VIDEOS_PATH: getPath('videos'),
         NATIVEPHP_RECENT_PATH: getPath('recent'),
     };
+    if (secret && apiPort) {
+        variables.NATIVEPHP_API_URL = `http://localhost:${apiPort}/api/`;
+        variables.NATIVEPHP_SECRET = secret;
+    }
+    if (runningSecureBuild()) {
+        variables.APP_SERVICES_CACHE = join(bootstrapCache, 'services.php');
+        variables.APP_PACKAGES_CACHE = join(bootstrapCache, 'packages.php');
+        variables.APP_CONFIG_CACHE = join(bootstrapCache, 'config.php');
+        variables.APP_ROUTES_CACHE = join(bootstrapCache, 'routes-v7.php');
+        variables.APP_EVENTS_CACHE = join(bootstrapCache, 'events.php');
+    }
+    return variables;
 }
 function getDefaultPhpIniSettings() {
     return {
@@ -151,53 +200,81 @@ function serveApp(secret, apiPort, phpIniSettings) {
             cwd: appPath,
             env
         };
-        const store = new Store();
-        callPhp(['artisan', 'storage:link', '--force'], phpOptions, phpIniSettings);
-        if (store.get('migrated_version') !== app.getVersion() && process.env.NODE_ENV !== 'development') {
+        const store = new Store({
+            name: 'nativephp',
+        });
+        if (!runningSecureBuild()) {
+            console.log('Linking storage path...');
+            callPhpSync(['artisan', 'storage:link', '--force'], phpOptions, phpIniSettings);
+        }
+        if (shouldOptimize(store)) {
+            console.log('Caching view and routes...');
+            let result = callPhpSync(['artisan', 'optimize'], phpOptions, phpIniSettings);
+            if (result.status !== 0) {
+                console.error('Failed to cache view and routes:', result.stderr.toString());
+            }
+            else {
+                store.set('optimized_version', app.getVersion());
+            }
+        }
+        if (shouldMigrateDatabase(store)) {
             console.log('Migrating database...');
-            callPhp(['artisan', 'migrate', '--force'], phpOptions, phpIniSettings);
-            store.set('migrated_version', app.getVersion());
+            if (parseInt(process.env.SHELL_VERBOSITY) > 0) {
+                console.log('Database path:', databaseFile);
+            }
+            let result = callPhpSync(['artisan', 'migrate', '--force'], phpOptions, phpIniSettings);
+            if (result.status !== 0) {
+                console.error('Failed to migrate database:', result.stderr.toString());
+            }
+            else {
+                store.set('migrated_version', app.getVersion());
+            }
         }
         if (process.env.NODE_ENV === 'development') {
             console.log('Skipping Database migration while in development.');
             console.log('You may migrate manually by running: php artisan native:migrate');
         }
+        console.log('Starting PHP server...');
         const phpPort = yield getPhpPort();
-        const serverPath = join(appPath, 'vendor', 'laravel', 'framework', 'src', 'Illuminate', 'Foundation', 'resources', 'server.php');
+        let serverPath;
+        let cwd;
+        if (runningSecureBuild()) {
+            serverPath = join(appPath, 'build', '__nativephp_app_bundle');
+        }
+        else {
+            console.log('* * * Running from source * * *');
+            serverPath = join(appPath, 'vendor', 'laravel', 'framework', 'src', 'Illuminate', 'Foundation', 'resources', 'server.php');
+            cwd = join(appPath, 'public');
+        }
         const phpServer = callPhp(['-S', `127.0.0.1:${phpPort}`, serverPath], {
-            cwd: join(appPath, 'public'),
+            cwd: cwd,
             env
         }, phpIniSettings);
         const portRegex = /Development Server \(.*:([0-9]+)\) started/gm;
         phpServer.stdout.on('data', (data) => {
-            const match = portRegex.exec(data.toString());
-            if (match) {
-                console.log("PHP Server started on port: ", match[1]);
-                const port = parseInt(match[1]);
-                resolve({
-                    port,
-                    process: phpServer
-                });
+            if (parseInt(process.env.SHELL_VERBOSITY) > 0) {
+                console.log(data.toString().trim());
             }
         });
         phpServer.stderr.on('data', (data) => {
             const error = data.toString();
-            const match = portRegex.exec(error);
+            const match = portRegex.exec(data.toString());
             if (match) {
                 const port = parseInt(match[1]);
                 console.log("PHP Server started on port: ", port);
                 resolve({
                     port,
-                    process: phpServer
+                    process: phpServer,
                 });
             }
             else {
-                if (error.startsWith('[NATIVE_EXCEPTION]: ', 27)) {
+                if (error.includes('[NATIVE_EXCEPTION]:')) {
+                    let logFile = join(storagePath, 'logs');
                     console.log();
                     console.error('Error in PHP:');
-                    console.error('  ' + error.slice(47));
-                    console.log('Please check your log file:');
-                    console.log('  ' + join(appPath, 'storage', 'logs', 'laravel.log'));
+                    console.error('  ' + error.split('[NATIVE_EXCEPTION]:')[1].trim());
+                    console.log('Please check your log files:');
+                    console.log('  ' + logFile);
                     console.log();
                 }
             }
@@ -205,6 +282,9 @@ function serveApp(secret, apiPort, phpIniSettings) {
         phpServer.on('error', (error) => {
             reject(error);
         });
+        phpServer.on('close', (code) => {
+            console.log(`PHP server exited with code ${code}`);
+        });
     }));
 }
-export { startScheduler, serveApp, getAppPath, retrieveNativePHPConfig, retrievePhpIniSettings, getDefaultEnvironmentVariables, getDefaultPhpIniSettings };
+export { startScheduler, serveApp, getAppPath, retrieveNativePHPConfig, retrievePhpIniSettings, getDefaultEnvironmentVariables, getDefaultPhpIniSettings, runningSecureBuild };
diff --git a/resources/js/electron-plugin/src/server/api/childProcess.ts b/resources/js/electron-plugin/src/server/api/childProcess.ts
index bb19c114..c7fd6b5f 100644
--- a/resources/js/electron-plugin/src/server/api/childProcess.ts
+++ b/resources/js/electron-plugin/src/server/api/childProcess.ts
@@ -1,99 +1,159 @@
 import express from 'express';
-import { utilityProcess } from 'electron';
+import {utilityProcess} from 'electron';
 import state from '../state.js';
-import { notifyLaravel } from "../utils.js";
-import { getDefaultEnvironmentVariables, getDefaultPhpIniSettings } from "../php.js";
+import {notifyLaravel} from "../utils.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: {
-                ...process.env,
-                ...env,
+    try {
+        const proc = utilityProcess.fork(
+            fileURLToPath(new URL('../../electron-plugin/dist/server/childProcess.js', import.meta.url)),
+            cmd,
+            {
+                cwd,
+                stdio: 'pipe',
+                serviceName: alias,
+                env: {
+                    ...process.env,
+                    ...env,
+                }
             }
-        }
-    );
-
-    proc.stdout.on('data', (data) => {
-        notifyLaravel('events', {
-            event: 'Native\\Laravel\\Events\\ChildProcess\\MessageReceived',
-            payload: {
-                alias,
-                data: data.toString(),
+        );
+
+        // Set timeout to detect if process never spawns
+        const startTimeout = setTimeout(() => {
+            if (!state.processes[alias] || !state.processes[alias].pid) {
+                console.error(`Process [${alias}] failed to start within timeout period`);
+
+                // Attempt to clean up
+                try {
+                    proc.kill();
+                } catch (e) {
+                    // Ignore kill errors
+                }
+
+                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());
+        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(),
-            }
+            notifyLaravel('events', {
+                event: 'Native\\Laravel\\Events\\ChildProcess\\ErrorReceived',
+                payload: {
+                    alias,
+                    data: data.toString(),
+                }
+            });
         });
-    });
 
-    proc.on('spawn', () => {
-        console.log('Process [' + alias + '] spawned!');
+        // Experimental feature on Electron,
+        // I keep this here to remember and retry when we upgrade
+        // https://www.electronjs.org/docs/latest/api/utility-process#event-error-experimental
+        // proc.on('error', (error) => {
+        //     clearTimeout(startTimeout);
+        //     console.error(`Process [${alias}] error: ${error.message}`);
+        //
+        //     notifyLaravel('events', {
+        //         event: 'Native\\Laravel\\Events\\ChildProcess\\StartupError',
+        //         payload: {
+        //             alias,
+        //             error: error.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 = {...getSettings(alias)};
+            delete state.processes[alias];
+
+            if (settings?.persistent) {
+                console.log('Process [' + alias + '] watchdog restarting...');
+                // Add delay to prevent rapid restart loops
+                setTimeout(() => startProcess(settings), 1000);
+            }
+        });
 
-        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 = {...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) {
@@ -103,18 +163,22 @@ function startPhpProcess(settings) {
     );
 
     // Construct command args from ini settings
-    const iniSettings = { ...getDefaultPhpIniSettings(), ...state.phpIni };
+    const customIniSettings = settings.iniSettings || {};
+    const iniSettings = {...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 = {
         ...settings,
         // Prepend cmd with php executable path & ini settings
-        cmd: [ state.php, ...iniArgs, ...settings.cmd ],
+        cmd: [state.php, ...iniArgs, ...settings.cmd],
         // Mix in the internal NativePHP env
-        env: { ...settings.env, ...defaultEnv }
+        env: {...settings.env, ...defaultEnv}
     };
 
     return startProcess(settings);
diff --git a/resources/js/electron-plugin/src/server/php.ts b/resources/js/electron-plugin/src/server/php.ts
index 48b08a51..5bda7b24 100644
--- a/resources/js/electron-plugin/src/server/php.ts
+++ b/resources/js/electron-plugin/src/server/php.ts
@@ -1,22 +1,48 @@
 import {mkdirSync, statSync, writeFileSync, existsSync} from 'fs'
 import fs_extra from 'fs-extra';
-const { copySync } = fs_extra;
+
+const {copySync, mkdirpSync} = fs_extra;
 
 import Store from 'electron-store'
 import {promisify} from 'util'
 import {join} from 'path'
 import {app} from 'electron'
-import {execFile, spawn} from 'child_process'
+import {execFile, spawn, spawnSync} from 'child_process'
 import state from "./state.js";
 import getPort, {portNumbers} from 'get-port';
-import { ProcessResult } from "./ProcessResult.js";
+import {ProcessResult} from "./ProcessResult.js";
 
+// TODO: maybe in dev, don't go to the userData folder and stay in the Laravel app folder
 const storagePath = join(app.getPath('userData'), 'storage')
 const databasePath = join(app.getPath('userData'), 'database')
 const databaseFile = join(databasePath, 'database.sqlite')
+const bootstrapCache = join(app.getPath('userData'), 'bootstrap', 'cache')
 const argumentEnv = getArgumentEnv();
 const appPath = getAppPath();
 
+mkdirpSync(bootstrapCache);
+
+function runningSecureBuild() {
+    return existsSync(join(appPath, 'build', '__nativephp_app_bundle'))
+        && process.env.NODE_ENV !== 'development';
+}
+
+function shouldMigrateDatabase(store) {
+    return store.get('migrated_version') !== app.getVersion()
+        && process.env.NODE_ENV !== 'development';
+}
+
+function shouldOptimize(store) {
+    /*
+     * For some weird reason,
+     * the cached config is not picked up on subsequent launches,
+     * so we'll just rebuilt it every time for now
+     */
+    return true;
+    // return runningSecureBuild();
+    // return runningSecureBuild() && store.get('optimized_version') !== app.getVersion();
+}
+
 async function getPhpPort() {
     return await getPort({
         host: '127.0.0.1',
@@ -25,43 +51,55 @@ async function getPhpPort() {
 }
 
 async function retrievePhpIniSettings() {
-  const env = {
-    NATIVEPHP_RUNNING: 'true',
-    NATIVEPHP_STORAGE_PATH: storagePath,
-    NATIVEPHP_DATABASE_PATH: databaseFile,
-  };
-
-  const phpOptions = {
-    cwd: appPath,
-    env
-  };
-
-  return await promisify(execFile)(state.php, ['artisan', 'native:php-ini'], phpOptions);
+    const env = getDefaultEnvironmentVariables() as any;
+
+    const phpOptions = {
+        cwd: appPath,
+        env
+    };
+
+    let command = ['artisan', 'native:php-ini'];
+
+    if (runningSecureBuild()) {
+        command.unshift(join(appPath, 'build', '__nativephp_app_bundle'));
+    }
+
+    return await promisify(execFile)(state.php, command, phpOptions);
 }
 
 async function retrieveNativePHPConfig() {
-    const env = {
-        NATIVEPHP_RUNNING: 'true',
-        NATIVEPHP_STORAGE_PATH: storagePath,
-        NATIVEPHP_DATABASE_PATH: databaseFile,
-    };
+    const env = getDefaultEnvironmentVariables() as any;
 
     const phpOptions = {
         cwd: appPath,
         env
     };
 
-    return await promisify(execFile)(state.php, ['artisan', 'native:config'], phpOptions);
+    let command = ['artisan', 'native:config'];
+
+    if (runningSecureBuild()) {
+        command.unshift(join(appPath, 'build', '__nativephp_app_bundle'));
+    }
+
+    return await promisify(execFile)(state.php, command, phpOptions);
 }
 
 function callPhp(args, options, phpIniSettings = {}) {
 
+    if (args[0] === 'artisan' && runningSecureBuild()) {
+        args.unshift(join(appPath, 'build', '__nativephp_app_bundle'));
+    }
+
     let iniSettings = Object.assign(getDefaultPhpIniSettings(), phpIniSettings);
 
     Object.keys(iniSettings).forEach(key => {
-      args.unshift('-d', `${key}=${iniSettings[key]}`);
+        args.unshift('-d', `${key}=${iniSettings[key]}`);
     });
 
+    if (parseInt(process.env.SHELL_VERBOSITY) > 0) {
+        console.log('Calling PHP', state.php, args);
+    }
+
     return spawn(
         state.php,
         args,
@@ -75,15 +113,42 @@ function callPhp(args, options, phpIniSettings = {}) {
     );
 }
 
+function callPhpSync(args, options, phpIniSettings = {}) {
+
+    if (args[0] === 'artisan' && runningSecureBuild()) {
+        args.unshift(join(appPath, 'build', '__nativephp_app_bundle'));
+    }
+
+    let iniSettings = Object.assign(getDefaultPhpIniSettings(), phpIniSettings);
+
+    Object.keys(iniSettings).forEach(key => {
+        args.unshift('-d', `${key}=${iniSettings[key]}`);
+    });
+
+    if (parseInt(process.env.SHELL_VERBOSITY) > 0) {
+        console.log('Calling PHP', state.php, args);
+    }
+
+    return spawnSync(
+        state.php,
+        args,
+        {
+            cwd: options.cwd,
+            env: {
+                ...process.env,
+                ...options.env
+            }
+        }
+    );
+}
+
 function getArgumentEnv() {
     const envArgs = process.argv.filter(arg => arg.startsWith('--env.'));
 
     const env: {
-      TESTING?: number,
-      APP_PATH?: string
-    } = {
-
-    };
+        TESTING?: number,
+        APP_PATH?: string
+    } = {};
     envArgs.forEach(arg => {
         const [key, value] = arg.slice(6).split('=');
         env[key] = value;
@@ -102,9 +167,15 @@ function getAppPath() {
 }
 
 function ensureAppFoldersAreAvailable() {
-    if (! existsSync(storagePath) || process.env.NODE_ENV === 'development') {
-        copySync(join(appPath, 'storage'), storagePath)
-    }
+
+    // if (!runningSecureBuild()) {
+    console.log('Copying storage folder...');
+    console.log('Storage path:', storagePath);
+        if (!existsSync(storagePath) || process.env.NODE_ENV === 'development') {
+            console.log("App path:", appPath);
+            copySync(join(appPath, 'storage'), storagePath)
+        }
+    // }
 
     mkdirSync(databasePath, {recursive: true})
 
@@ -128,34 +199,81 @@ function startScheduler(secret, apiPort, phpIniSettings = {}) {
 }
 
 function getPath(name: string) {
-  try {
-    // @ts-ignore
-    return app.getPath(name);
-  } catch (error) {
-    return '';
-  }
+    try {
+        // @ts-ignore
+        return app.getPath(name);
+    } catch (error) {
+        return '';
+    }
 }
 
-function getDefaultEnvironmentVariables(secret, apiPort) {
-  return {
-    APP_ENV: process.env.NODE_ENV === 'development' ? 'local' : 'production',
-    APP_DEBUG: process.env.NODE_ENV === 'development' ? 'true' : 'false',
-    NATIVEPHP_STORAGE_PATH: storagePath,
-    NATIVEPHP_DATABASE_PATH: databaseFile,
-    NATIVEPHP_API_URL: `http://localhost:${apiPort}/api/`,
-    NATIVEPHP_RUNNING: 'true',
-    NATIVEPHP_SECRET: secret,
-    NATIVEPHP_USER_HOME_PATH: getPath('home'),
-    NATIVEPHP_APP_DATA_PATH: getPath('appData'),
-    NATIVEPHP_USER_DATA_PATH: getPath('userData'),
-    NATIVEPHP_DESKTOP_PATH: getPath('desktop'),
-    NATIVEPHP_DOCUMENTS_PATH: getPath('documents'),
-    NATIVEPHP_DOWNLOADS_PATH: getPath('downloads'),
-    NATIVEPHP_MUSIC_PATH: getPath('music'),
-    NATIVEPHP_PICTURES_PATH: getPath('pictures'),
-    NATIVEPHP_VIDEOS_PATH: getPath('videos'),
-    NATIVEPHP_RECENT_PATH: getPath('recent'),
-  };
+// Define an interface for the environment variables
+interface EnvironmentVariables {
+    APP_ENV: string;
+    APP_DEBUG: string;
+    LARAVEL_STORAGE_PATH: string;
+    NATIVEPHP_STORAGE_PATH: string;
+    NATIVEPHP_DATABASE_PATH: string;
+    NATIVEPHP_API_URL?: string;
+    NATIVEPHP_RUNNING: string;
+    NATIVEPHP_SECRET?: string;
+    NATIVEPHP_USER_HOME_PATH: string;
+    NATIVEPHP_APP_DATA_PATH: string;
+    NATIVEPHP_USER_DATA_PATH: string;
+    NATIVEPHP_DESKTOP_PATH: string;
+    NATIVEPHP_DOCUMENTS_PATH: string;
+    NATIVEPHP_DOWNLOADS_PATH: string;
+    NATIVEPHP_MUSIC_PATH: string;
+    NATIVEPHP_PICTURES_PATH: string;
+    NATIVEPHP_VIDEOS_PATH: string;
+    NATIVEPHP_RECENT_PATH: string;
+    // Cache variables
+    APP_SERVICES_CACHE?: string;
+    APP_PACKAGES_CACHE?: string;
+    APP_CONFIG_CACHE?: string;
+    APP_ROUTES_CACHE?: string;
+    APP_EVENTS_CACHE?: string;
+    VIEW_COMPILED_PATH?: string;
+}
+
+function getDefaultEnvironmentVariables(secret?: string, apiPort?: number): EnvironmentVariables {
+    // Base variables with string values (no null values)
+    let variables: EnvironmentVariables = {
+        APP_ENV: process.env.NODE_ENV === 'development' ? 'local' : 'production',
+        APP_DEBUG: process.env.NODE_ENV === 'development' ? 'true' : 'false',
+        LARAVEL_STORAGE_PATH: storagePath,
+        NATIVEPHP_RUNNING: 'true',
+        NATIVEPHP_STORAGE_PATH: storagePath,
+        NATIVEPHP_DATABASE_PATH: databaseFile,
+        NATIVEPHP_USER_HOME_PATH: getPath('home'),
+        NATIVEPHP_APP_DATA_PATH: getPath('appData'),
+        NATIVEPHP_USER_DATA_PATH: getPath('userData'),
+        NATIVEPHP_DESKTOP_PATH: getPath('desktop'),
+        NATIVEPHP_DOCUMENTS_PATH: getPath('documents'),
+        NATIVEPHP_DOWNLOADS_PATH: getPath('downloads'),
+        NATIVEPHP_MUSIC_PATH: getPath('music'),
+        NATIVEPHP_PICTURES_PATH: getPath('pictures'),
+        NATIVEPHP_VIDEOS_PATH: getPath('videos'),
+        NATIVEPHP_RECENT_PATH: getPath('recent'),
+    };
+
+    // Only if the server has already started
+    if (secret && apiPort) {
+        variables.NATIVEPHP_API_URL = `http://localhost:${apiPort}/api/`;
+        variables.NATIVEPHP_SECRET = secret;
+    }
+
+    // Only add cache paths if in production mode
+    if (runningSecureBuild()) {
+        variables.APP_SERVICES_CACHE = join(bootstrapCache, 'services.php'); // Should be present and writable
+        variables.APP_PACKAGES_CACHE = join(bootstrapCache, 'packages.php'); // Should be present and writable
+        variables.APP_CONFIG_CACHE = join(bootstrapCache, 'config.php');
+        variables.APP_ROUTES_CACHE = join(bootstrapCache, 'routes-v7.php');
+        variables.APP_EVENTS_CACHE = join(bootstrapCache, 'events.php');
+        // variables.VIEW_COMPILED_PATH; // TODO: keep those in the phar file if we can.
+    }
+
+    return variables;
 }
 
 function getDefaultPhpIniSettings() {
@@ -183,17 +301,50 @@ function serveApp(secret, apiPort, phpIniSettings): Promise<ProcessResult> {
             env
         };
 
-        const store = new Store();
+        const store = new Store({
+            name: 'nativephp', // So it doesn't conflict with settings of the app
+        });
 
         // Make sure the storage path is linked - as people can move the app around, we
         // need to run this every time the app starts
-        callPhp(['artisan', 'storage:link', '--force'], phpOptions, phpIniSettings)
+        if (!runningSecureBuild()) {
+            /*
+              * Simon: Note for later that we should strip out using storage:link
+              * all of the necessary files for the app to function should be a part of the bundle
+              * (whether it's a secured bundle or not), so symlinking feels redundant
+             */
+            console.log('Linking storage path...');
+            callPhpSync(['artisan', 'storage:link', '--force'], phpOptions, phpIniSettings)
+        }
+
+        // Cache the project
+        if (shouldOptimize(store)) {
+            console.log('Caching view and routes...');
+
+            let result = callPhpSync(['artisan', 'optimize'], phpOptions, phpIniSettings);
+
+            if (result.status !== 0) {
+                console.error('Failed to cache view and routes:', result.stderr.toString());
+            } else {
+                store.set('optimized_version', app.getVersion())
+            }
+        }
 
         // Migrate the database
-        if (store.get('migrated_version') !== app.getVersion() && process.env.NODE_ENV !== 'development') {
-            console.log('Migrating database...')
-            callPhp(['artisan', 'migrate', '--force'], phpOptions, phpIniSettings)
-            store.set('migrated_version', app.getVersion())
+        if (shouldMigrateDatabase(store)) {
+            console.log('Migrating database...');
+
+            if(parseInt(process.env.SHELL_VERBOSITY) > 0) {
+                console.log('Database path:', databaseFile);
+            }
+
+            let result = callPhpSync(['artisan', 'migrate', '--force'], phpOptions, phpIniSettings);
+
+            if (result.status !== 0) {
+                console.error('Failed to migrate database:', result.stderr.toString());
+            } else {
+                store.set('migrated_version', app.getVersion())
+            }
         }
 
         if (process.env.NODE_ENV === 'development') {
@@ -201,56 +352,82 @@ function serveApp(secret, apiPort, phpIniSettings): Promise<ProcessResult> {
             console.log('You may migrate manually by running: php artisan native:migrate')
         }
 
+        console.log('Starting PHP server...');
         const phpPort = await getPhpPort();
 
-        const serverPath = join(appPath, 'vendor', 'laravel', 'framework', 'src', 'Illuminate', 'Foundation', 'resources', 'server.php')
+
+        let serverPath: string;
+        let cwd: string;
+
+        if (runningSecureBuild()) {
+            serverPath = join(appPath, 'build', '__nativephp_app_bundle');
+        } else {
+            console.log('* * * Running from source * * *');
+            serverPath = join(appPath, 'vendor', 'laravel', 'framework', 'src', 'Illuminate', 'Foundation', 'resources', 'server.php');
+            cwd = join(appPath, 'public');
+        }
+
         const phpServer = callPhp(['-S', `127.0.0.1:${phpPort}`, serverPath], {
-            cwd: join(appPath, 'public'),
+            cwd: cwd,
             env
         }, phpIniSettings)
 
         const portRegex = /Development Server \(.*:([0-9]+)\) started/gm
 
+        // Show urls called
         phpServer.stdout.on('data', (data) => {
-            const match = portRegex.exec(data.toString())
-            if (match) {
-                console.log("PHP Server started on port: ", match[1])
-                const port = parseInt(match[1])
-                resolve({
-                    port,
-                    process: phpServer
-                })
+            // [Tue Jan 14 19:51:00 2025] 127.0.0.1:52779 [POST] URI: /_native/api/events
+
+            if (parseInt(process.env.SHELL_VERBOSITY) > 0) {
+                console.log(data.toString().trim());
             }
         })
 
+        // Show PHP errors and indicate which port the server is running on
         phpServer.stderr.on('data', (data) => {
             const error = data.toString();
-            const match = portRegex.exec(error);
+            const match = portRegex.exec(data.toString());
 
             if (match) {
                 const port = parseInt(match[1]);
                 console.log("PHP Server started on port: ", port);
                 resolve({
                     port,
-                    process: phpServer
+                    process: phpServer,
                 });
             } else {
-                // 27 is the length of the php -S output preamble
-                if (error.startsWith('[NATIVE_EXCEPTION]: ', 27)) {
+                if (error.includes('[NATIVE_EXCEPTION]:')) {
+                    let logFile = join(storagePath, 'logs');
+
                     console.log();
                     console.error('Error in PHP:');
-                    console.error('  ' + error.slice(47));
-                    console.log('Please check your log file:');
-                    console.log('  ' + join(appPath, 'storage', 'logs', 'laravel.log'));
+                    console.error('  ' + error.split('[NATIVE_EXCEPTION]:')[1].trim());
+                    console.log('Please check your log files:');
+                    console.log('  ' + logFile);
                     console.log();
                 }
             }
         });
 
+        // Log when any error occurs (not started, not killed, couldn't send message, etc)
         phpServer.on('error', (error) => {
             reject(error)
-        })
+        });
+
+        // Log when the PHP server exits
+        phpServer.on('close', (code) => {
+            console.log(`PHP server exited with code ${code}`);
+        });
     })
 }
 
-export {startScheduler, serveApp, getAppPath, retrieveNativePHPConfig, retrievePhpIniSettings, getDefaultEnvironmentVariables, getDefaultPhpIniSettings}
+export {
+    startScheduler,
+    serveApp,
+    getAppPath,
+    retrieveNativePHPConfig,
+    retrievePhpIniSettings,
+    getDefaultEnvironmentVariables,
+    getDefaultPhpIniSettings,
+    runningSecureBuild
+}
diff --git a/resources/js/package.json b/resources/js/package.json
index fdf42c94..d5a4d51e 100644
--- a/resources/js/package.json
+++ b/resources/js/package.json
@@ -37,7 +37,7 @@
     },
     "dependencies": {
         "@electron-toolkit/preload": "^3.0.1",
-        "@electron-toolkit/utils": "^3.0.0",
+        "@electron-toolkit/utils": "^4.0.0",
         "@electron/remote": "^2.1.2",
         "axios": "^1.7.9",
         "body-parser": "^1.20.3",
@@ -79,26 +79,25 @@
         "electron": "^32.2.7",
         "electron-builder": "^25.1.8",
         "electron-chromedriver": "^32.2.6",
-        "electron-vite": "^2.3.0",
+        "electron-vite": "^3.0.0",
         "eslint": "^9.17.0",
-        "eslint-config-prettier": "^9.1.0",
+        "eslint-config-prettier": "^10.0.0",
         "eslint-plugin-prettier": "^5.2.1",
-        "eslint-plugin-unicorn": "^56.0.1",
-        "eslint-plugin-vue": "^9.32.0",
-        "globals": "^15.14.0",
+        "eslint-plugin-unicorn": "^57.0.0",
+        "globals": "^16.0.0",
         "jest": "^29.7.0",
         "less": "^4.2.1",
         "prettier": "^3.4.2",
         "rimraf": "^6.0.1",
         "stylelint": "^16.12.0",
-        "stylelint-config-recommended": "^14.0.1",
+        "stylelint-config-recommended": "^15.0.0",
         "stylelint-config-sass-guidelines": "^12.1.0",
         "ts-jest": "^29.2.5",
         "ts-node": "^10.9.2",
         "tslib": "^2.8.1",
         "typescript": "^5.7.2",
         "typescript-eslint": "^8.18.1",
-        "vite": "^5.0.0"
+        "vite": "^6.2.1"
     },
     "exports": "./electron-plugin/dist/index.js",
     "imports": {
diff --git a/src/Commands/BuildCommand.php b/src/Commands/BuildCommand.php
index 20d6fd62..82d24112 100644
--- a/src/Commands/BuildCommand.php
+++ b/src/Commands/BuildCommand.php
@@ -7,8 +7,8 @@
 use Illuminate\Support\Str;
 use Native\Electron\Facades\Updater;
 use Native\Electron\Traits\CleansEnvFile;
+use Native\Electron\Traits\CopiesBundleToBuildDirectory;
 use Native\Electron\Traits\CopiesCertificateAuthority;
-use Native\Electron\Traits\CopiesToBuildDirectory;
 use Native\Electron\Traits\HasPreAndPostProcessing;
 use Native\Electron\Traits\InstallsAppIcon;
 use Native\Electron\Traits\LocatesPhpBinary;
@@ -22,8 +22,8 @@
 class BuildCommand extends Command
 {
     use CleansEnvFile;
+    use CopiesBundleToBuildDirectory;
     use CopiesCertificateAuthority;
-    use CopiesToBuildDirectory;
     use HasPreAndPostProcessing;
     use InstallsAppIcon;
     use LocatesPhpBinary;
@@ -36,7 +36,11 @@ class BuildCommand extends Command
         {arch? : The Processor Architecture to build for (x64, x86, arm64)}
         {--publish : to publish the app}';
 
-    protected $availableOs = ['win', 'linux', 'mac', 'all'];
+    protected array $availableOs = ['win', 'linux', 'mac', 'all'];
+
+    private string $buildCommand;
+
+    private string $buildOS;
 
     protected function buildPath(string $path = ''): string
     {
@@ -50,32 +54,56 @@ protected function sourcePath(string $path = ''): string
 
     public function handle(): void
     {
-        $os = $this->selectOs($this->argument('os'));
+        $this->buildOS = $this->selectOs($this->argument('os'));
 
-        $buildCommand = 'build';
-        if ($os != 'all') {
-            $arch = $this->selectArchitectureForOs($os, $this->argument('arch'));
+        $this->buildCommand = 'build';
+        if ($this->buildOS != 'all') {
+            $arch = $this->selectArchitectureForOs($this->buildOS, $this->argument('arch'));
 
-            $os .= $arch != 'all' ? "-{$arch}" : '';
+            $this->buildOS .= $arch != 'all' ? "-{$arch}" : '';
 
             // Should we publish?
-            if ($publish = $this->option('publish')) {
-                $buildCommand = 'publish';
+            if ($this->option('publish')) {
+                $this->buildCommand = 'publish';
             }
         }
 
-        $this->preProcess();
+        if ($this->hasBundled()) {
+            $this->buildBundle();
+        } else {
+            $this->warnUnsecureBuild();
+            $this->buildUnsecure();
+        }
+    }
+
+    private function buildBundle(): void
+    {
+        $this->setAppName();
 
-        $this->setAppName(slugify: true);
+        $this->updateElectronDependencies();
 
         $this->newLine();
-        intro('Updating Electron dependencies...');
-        Process::path(__DIR__.'/../../resources/js/')
-            ->env($this->getEnvironmentVariables())
-            ->forever()
-            ->run('npm ci', function (string $type, string $output) {
-                echo $output;
-            });
+        intro('Copying Bundle to build directory...');
+        $this->copyBundleToBuildDirectory();
+        $this->keepRequiredDirectories();
+
+        $this->newLine();
+        $this->copyCertificateAuthorityCertificate();
+
+        $this->newLine();
+        intro('Copying app icons...');
+        $this->installIcon();
+
+        $this->buildOrPublish();
+    }
+
+    private function buildUnsecure(): void
+    {
+        $this->preProcess();
+
+        $this->setAppName();
+
+        $this->updateElectronDependencies();
 
         $this->newLine();
         intro('Copying App to build directory...');
@@ -96,15 +124,7 @@ public function handle(): void
         intro('Pruning vendor directory');
         $this->pruneVendorDirectory();
 
-        $this->newLine();
-        intro((($publish ?? false) ? 'Publishing' : 'Building')." for {$os}");
-        Process::path(__DIR__.'/../../resources/js/')
-            ->env($this->getEnvironmentVariables())
-            ->forever()
-            ->tty(SymfonyProcess::isTtySupported() && ! $this->option('no-interaction'))
-            ->run("npm run {$buildCommand}:{$os}", function (string $type, string $output) {
-                echo $output;
-            });
+        $this->buildOrPublish();
 
         $this->postProcess();
     }
@@ -129,4 +149,29 @@ protected function getEnvironmentVariables(): array
             Updater::environmentVariables(),
         );
     }
+
+    private function updateElectronDependencies(): void
+    {
+        $this->newLine();
+        intro('Updating Electron dependencies...');
+        Process::path(__DIR__.'/../../resources/js/')
+            ->env($this->getEnvironmentVariables())
+            ->forever()
+            ->run('npm ci', function (string $type, string $output) {
+                echo $output;
+            });
+    }
+
+    private function buildOrPublish(): void
+    {
+        $this->newLine();
+        intro((($this->buildCommand == 'publish') ? 'Publishing' : 'Building')." for {$this->buildOS}");
+        Process::path(__DIR__.'/../../resources/js/')
+            ->env($this->getEnvironmentVariables())
+            ->forever()
+            ->tty(SymfonyProcess::isTtySupported() && ! $this->option('no-interaction'))
+            ->run("npm run {$this->buildCommand}:{$this->buildOS}", function (string $type, string $output) {
+                echo $output;
+            });
+    }
 }
diff --git a/src/Commands/BundleCommand.php b/src/Commands/BundleCommand.php
new file mode 100644
index 00000000..a2762056
--- /dev/null
+++ b/src/Commands/BundleCommand.php
@@ -0,0 +1,401 @@
+<?php
+
+namespace Native\Electron\Commands;
+
+use Carbon\CarbonInterface;
+use Illuminate\Console\Command;
+use Illuminate\Http\Client\Response;
+use Illuminate\Support\Facades\Http;
+use Illuminate\Support\Facades\Process;
+use Illuminate\Support\Number;
+use Illuminate\Support\Str;
+use Native\Electron\Traits\CleansEnvFile;
+use Native\Electron\Traits\CopiesToBuildDirectory;
+use Native\Electron\Traits\HandlesZephpyr;
+use Native\Electron\Traits\HasPreAndPostProcessing;
+use Native\Electron\Traits\InstallsAppIcon;
+use Native\Electron\Traits\PrunesVendorDirectory;
+use Native\Electron\Traits\SetsAppName;
+use Symfony\Component\Finder\Finder;
+use ZipArchive;
+
+use function Laravel\Prompts\intro;
+
+class BundleCommand extends Command
+{
+    use CleansEnvFile;
+    use CopiesToBuildDirectory;
+    use HandlesZephpyr;
+    use HasPreAndPostProcessing;
+    use InstallsAppIcon;
+    use PrunesVendorDirectory;
+    use SetsAppName;
+
+    protected $signature = 'native:bundle {--fetch} {--clear} {--without-cleanup}';
+
+    protected $description = 'Bundle your application for distribution.';
+
+    private ?string $key;
+
+    private string $zipPath;
+
+    private string $zipName;
+
+    public function handle(): int
+    {
+        // Remove the bundle
+        if ($this->option('clear')) {
+            if (file_exists(base_path('build/__nativephp_app_bundle'))) {
+                unlink(base_path('build/__nativephp_app_bundle'));
+            }
+
+            $this->info('Bundle removed. Building in this state would be unsecure.');
+
+            return static::SUCCESS;
+        }
+
+        // Check for ZEPHPYR_KEY
+        if (! $this->checkForZephpyrKey()) {
+            return static::FAILURE;
+        }
+
+        // Check for ZEPHPYR_TOKEN
+        if (! $this->checkForZephpyrToken()) {
+            return static::FAILURE;
+        }
+
+        // Check if the token is valid
+        if (! $this->checkAuthenticated()) {
+            $this->error('Invalid API token: check your ZEPHPYR_TOKEN on '.$this->baseUrl().'user/api-tokens');
+
+            return static::FAILURE;
+        }
+
+        // Download the latest bundle if requested
+        if ($this->option('fetch')) {
+            if (! $this->fetchLatestBundle()) {
+
+                return static::FAILURE;
+            }
+
+            $this->info('Latest bundle downloaded.');
+
+            return static::SUCCESS;
+        }
+
+        $this->preProcess();
+
+        $this->setAppName();
+        intro('Copying App to build directory...');
+
+        // We update composer.json later,
+        $this->copyToBuildDirectory();
+
+        $this->newLine();
+        intro('Cleaning .env file...');
+        $this->cleanEnvFile();
+
+        $this->newLine();
+        intro('Copying app icons...');
+        $this->installIcon();
+
+        $this->newLine();
+        intro('Pruning vendor directory');
+        $this->pruneVendorDirectory();
+
+        $this->cleanEnvFile();
+
+        // Check composer.json for symlinked or private packages
+        if (! $this->checkComposerJson()) {
+            return static::FAILURE;
+        }
+
+        // Package the app up into a zip
+        if (! $this->zipApplication()) {
+            $this->error("Failed to create zip archive at {$this->zipPath}.");
+
+            return static::FAILURE;
+        }
+
+        // Send the zip file
+        $result = $this->sendToZephpyr();
+        $this->handleApiErrors($result);
+
+        // Success
+        $this->info('Successfully uploaded to Zephpyr.');
+        $this->line('Use native:bundle --fetch to retrieve the latest bundle.');
+
+        // Clean up temp files
+        $this->cleanUp();
+
+        return static::SUCCESS;
+    }
+
+    private function zipApplication(): bool
+    {
+        $this->zipName = 'app_'.str()->random(8).'.zip';
+        $this->zipPath = $this->zipPath($this->zipName);
+
+        // Create zip path
+        if (! @mkdir(dirname($this->zipPath), recursive: true) && ! is_dir(dirname($this->zipPath))) {
+            return false;
+        }
+
+        $zip = new ZipArchive;
+
+        if ($zip->open($this->zipPath, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
+            return false;
+        }
+
+        $this->addFilesToZip($zip);
+
+        $zip->close();
+
+        return true;
+    }
+
+    private function checkComposerJson(): bool
+    {
+        $composerJson = json_decode(file_get_contents($this->buildPath('composer.json')), true);
+
+        // // Fail if there is symlinked packages
+        // foreach ($composerJson['repositories'] ?? [] as $repository) {
+        //
+        //     $symlinked = $repository['options']['symlink'] ?? true;
+        //     if ($repository['type'] === 'path' && $symlinked) {
+        //         $this->error('Symlinked packages are not supported. Please remove them from your composer.json.');
+        //
+        //         return false;
+        //     }
+        //     // Work with private packages but will not in the future
+        //     // elseif ($repository['type'] === 'composer') {
+        //     //     if (! $this->checkComposerPackageAuth($repository['url'])) {
+        //     //         $this->error('Cannot authenticate with '.$repository['url'].'.');
+        //     //         $this->error('Go to '.$this->baseUrl().' and add your composer package credentials.');
+        //     //
+        //     //         return false;
+        //     //     }
+        //     // }
+        // }
+
+        // Remove repositories with type path, we include symlinked packages
+        if (! empty($composerJson['repositories'])) {
+
+            $this->newLine();
+            intro('Patching composer.json in development mode…');
+
+            $filteredRepo = array_filter($composerJson['repositories'],
+                fn ($repository) => $repository['type'] !== 'path');
+
+            if (count($filteredRepo) !== count($composerJson['repositories'])) {
+                $composerJson['repositories'] = $filteredRepo;
+                file_put_contents($this->buildPath('composer.json'),
+                    json_encode($composerJson, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
+
+                // Process::path($this->buildPath())
+                //     ->run('composer install --no-dev', function (string $type, string $output) {
+                //         echo $output;
+                //     });
+            }
+
+        }
+
+        return true;
+    }
+
+    // private function checkComposerPackageAuth(string $repositoryUrl): bool
+    // {
+    //     // Check if the user has authenticated the package on Zephpyr
+    //     $host = parse_url($repositoryUrl, PHP_URL_HOST);
+    //     $this->line('Checking '.$host.' authentication…');
+    //
+    //     return Http::acceptJson()
+    //         ->withToken(config('nativephp-internal.zephpyr.token'))
+    //         ->get($this->baseUrl().'api/v1/project/'.$this->key.'/composer/auth/'.$host)
+    //         ->successful();
+    // }
+
+    private function addFilesToZip(ZipArchive $zip): void
+    {
+        $this->newLine();
+        intro('Creating zip archive…');
+
+        $finder = (new Finder)->files()
+            ->followLinks()
+            // ->ignoreVCSIgnored(true) // TODO: Make our own list of ignored files
+            ->in($this->buildPath())
+            ->exclude([
+                // We add those a few lines below and they are ignored by most .gitignore anyway
+                'vendor',
+                'node_modules',
+
+                // Exclude the following directories
+                'dist', // Compiled nativephp assets
+                'build', // Compiled box assets
+                'temp', // Temp files
+                'tests', // Tests
+            ])
+            ->exclude(config('nativephp.cleanup_exclude_files', []));
+
+        $this->finderToZip($finder, $zip);
+
+        // Why do I have to force this? please someone explain.
+        $this->finderToZip(
+            (new Finder)->files()
+                ->followLinks()
+                ->in($this->buildPath('public/build')), $zip, 'public/build');
+
+        // Add .env file manually because Finder ignores VCS and dot files
+        $zip->addFile($this->buildPath('.env'), '.env');
+
+        // Add auth.json file to support private packages
+        // WARNING: Only for testing purposes, don't uncomment this
+        // $zip->addFile($this->buildPath('auth.json'), 'auth.json');
+
+        // Custom binaries
+        $binaryPath = Str::replaceStart($this->buildPath('vendor'), '', config('nativephp.binary_path'));
+
+        // Add composer dependencies without unnecessary files
+        $vendor = (new Finder)->files()
+            ->exclude(array_filter([
+                'nativephp/php-bin',
+                'nativephp/electron/resources/js',
+                '*/*/vendor', // Exclude sub-vendor directories
+                $binaryPath,
+            ]))
+            ->in($this->buildPath('vendor'));
+
+        $this->finderToZip($vendor, $zip, 'vendor');
+
+        // Add javascript dependencies
+        if (file_exists($this->buildPath('node_modules'))) {
+            $nodeModules = (new Finder)->files()
+                ->in($this->buildPath('node_modules'));
+
+            $this->finderToZip($nodeModules, $zip, 'node_modules');
+        }
+    }
+
+    private function finderToZip(Finder $finder, ZipArchive $zip, ?string $path = null): void
+    {
+        foreach ($finder as $file) {
+            if ($file->getRealPath() === false) {
+                continue;
+            }
+
+            $zip->addFile($file->getRealPath(), str($path)->finish(DIRECTORY_SEPARATOR).$file->getRelativePathname());
+        }
+    }
+
+    private function sendToZephpyr()
+    {
+        intro('Uploading zip to Zephpyr…');
+
+        return Http::acceptJson()
+            ->timeout(300) // 5 minutes
+            ->withoutRedirecting() // Upload won't work if we follow redirects (it transform POST to GET)
+            ->withToken(config('nativephp-internal.zephpyr.token'))
+            ->attach('archive', fopen($this->zipPath, 'r'), $this->zipName)
+            ->post($this->baseUrl().'api/v1/project/'.$this->key.'/build/');
+    }
+
+    private function fetchLatestBundle(): bool
+    {
+        intro('Fetching latest bundle…');
+
+        $response = Http::acceptJson()
+            ->withToken(config('nativephp-internal.zephpyr.token'))
+            ->get($this->baseUrl().'api/v1/project/'.$this->key.'/build/download');
+
+        if ($response->failed()) {
+
+            if ($response->status() === 404) {
+                $this->error('Project or bundle not found.');
+            } elseif ($response->status() === 500) {
+                $url = $response->json('url');
+
+                if ($url) {
+                    $this->error('Build failed. Inspect the build here: '.$url);
+                } else {
+                    $this->error('Build failed. Please try again later.');
+                }
+            } elseif ($response->status() === 503) {
+                $retryAfter = intval($response->header('Retry-After'));
+                $diff = now()->addSeconds($retryAfter);
+                $diffMessage = $retryAfter <= 60 ? 'a minute' : $diff->diffForHumans(syntax: CarbonInterface::DIFF_ABSOLUTE);
+                $this->warn('Bundle not ready. Please try again in '.$diffMessage.'.');
+            } else {
+                $this->handleApiErrors($response);
+            }
+
+            return false;
+        }
+
+        // Save the bundle
+        @mkdir(base_path('build'), recursive: true);
+        file_put_contents(base_path('build/__nativephp_app_bundle'), $response->body());
+
+        return true;
+    }
+
+    protected function exitWithMessage(string $message): void
+    {
+        $this->error($message);
+        $this->cleanUp();
+
+        exit(static::FAILURE);
+    }
+
+    private function handleApiErrors(Response $result): void
+    {
+        if ($result->status() === 413) {
+            $fileSize = Number::fileSize(filesize($this->zipPath));
+            $this->exitWithMessage('File is too large to upload ('.$fileSize.'). Please contact support.');
+        } elseif ($result->status() === 422) {
+            $this->error('Request refused:'.$result->json('message'));
+        } elseif ($result->status() === 429) {
+            $this->exitWithMessage('Too many requests. Please try again in '.now()->addSeconds(intval($result->header('Retry-After')))->diffForHumans(syntax: CarbonInterface::DIFF_ABSOLUTE).'.');
+        } elseif ($result->failed()) {
+            $this->exitWithMessage("Request failed. Error code: {$result->status()}");
+        }
+    }
+
+    protected function cleanUp(): void
+    {
+        $this->postProcess();
+
+        if ($this->option('without-cleanup')) {
+            return;
+        }
+
+        $previousBuilds = glob($this->zipPath().'/app_*.zip');
+        $failedZips = glob($this->zipPath().'/app_*.part');
+
+        $deleteFiles = array_merge($previousBuilds, $failedZips);
+
+        if (empty($deleteFiles)) {
+            return;
+        }
+
+        $this->line('Cleaning up…');
+
+        foreach ($deleteFiles as $file) {
+            @unlink($file);
+        }
+    }
+
+    protected function buildPath(string $path = ''): string
+    {
+        return base_path('build/app/'.$path);
+    }
+
+    protected function zipPath(string $path = ''): string
+    {
+        return base_path('build/zip/'.$path);
+    }
+
+    protected function sourcePath(string $path = ''): string
+    {
+        return base_path($path);
+    }
+}
diff --git a/src/Commands/DevelopCommand.php b/src/Commands/DevelopCommand.php
index f855cfa6..e2ffadfe 100644
--- a/src/Commands/DevelopCommand.php
+++ b/src/Commands/DevelopCommand.php
@@ -7,6 +7,7 @@
 use Native\Electron\Traits\Developer;
 use Native\Electron\Traits\Installer;
 use Native\Electron\Traits\InstallsAppIcon;
+use Native\Electron\Traits\SetsAppName;
 
 use function Laravel\Prompts\intro;
 use function Laravel\Prompts\note;
@@ -17,6 +18,7 @@ class DevelopCommand extends Command
     use Developer;
     use Installer;
     use InstallsAppIcon;
+    use SetsAppName;
 
     protected $signature = 'native:serve {--no-queue} {--D|no-dependencies} {--installer=npm}';
 
@@ -40,7 +42,7 @@ public function handle(): void
             $this->patchPlist();
         }
 
-        $this->patchPackageJson();
+        $this->setAppName(developmentMode: true);
 
         $this->installIcon();
 
@@ -70,14 +72,4 @@ protected function patchPlist(): void
 
         file_put_contents(__DIR__.'/../../resources/js/node_modules/electron/dist/Electron.app/Contents/Info.plist', $pList);
     }
-
-    protected function patchPackageJson(): void
-    {
-        $packageJsonPath = __DIR__.'/../../resources/js/package.json';
-        $packageJson = json_decode(file_get_contents($packageJsonPath), true);
-
-        $packageJson['name'] = config('app.name');
-
-        file_put_contents($packageJsonPath, json_encode($packageJson, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
-    }
 }
diff --git a/src/Commands/ResetCommand.php b/src/Commands/ResetCommand.php
new file mode 100644
index 00000000..bf5261a6
--- /dev/null
+++ b/src/Commands/ResetCommand.php
@@ -0,0 +1,83 @@
+<?php
+
+namespace Native\Electron\Commands;
+
+use Illuminate\Console\Command;
+use Native\Electron\Traits\SetsAppName;
+use Symfony\Component\Filesystem\Filesystem;
+
+use function Laravel\Prompts\intro;
+
+class ResetCommand extends Command
+{
+    use SetsAppName;
+
+    protected $signature = 'native:reset {--with-app-data : Clear the app data as well}';
+
+    protected $description = 'Clear all build and dist files';
+
+    public function handle(): int
+    {
+        intro('Clearing build and dist directories...');
+
+        // Removing and recreating the native serve resource path
+        $nativeServeResourcePath = realpath(__DIR__.'/../../resources/js/resources/app/');
+        $this->line('Clearing: '.$nativeServeResourcePath);
+
+        $filesystem = new Filesystem;
+        $filesystem->remove($nativeServeResourcePath);
+        $filesystem->mkdir($nativeServeResourcePath);
+
+        // Removing the bundling directories
+        $bundlingPath = base_path('build/');
+        $this->line('Clearing: '.$bundlingPath);
+
+        if ($filesystem->exists($bundlingPath)) {
+            $filesystem->remove($bundlingPath);
+        }
+
+        // Removing the built path
+        $builtPath = base_path('dist/');
+        $this->line('Clearing: '.$builtPath);
+
+        if ($filesystem->exists($builtPath)) {
+            $filesystem->remove($builtPath);
+        }
+
+        if ($this->option('with-app-data')) {
+
+            foreach ([true, false] as $developmentMode) {
+                $appName = $this->setAppName($developmentMode);
+
+                // Eh, just in case, I don't want to delete all user data by accident.
+                if (! empty($appName)) {
+                    $appDataPath = $this->appDataDirectory($appName);
+                    $this->line('Clearing: '.$appDataPath);
+
+                    if ($filesystem->exists($appDataPath)) {
+                        $filesystem->remove($appDataPath);
+                    }
+                }
+            }
+        }
+
+        return 0;
+    }
+
+    protected function appDataDirectory(string $name): string
+    {
+        /*
+         * Platform	Location
+         * macOS	~/Library/Application Support
+         * Linux	$XDG_CONFIG_HOME or ~/.config
+         * Windows	%APPDATA%
+         */
+
+        return match (PHP_OS_FAMILY) {
+            'Darwin' => $_SERVER['HOME'].'/Library/Application Support/'.$name,
+            'Linux' => $_SERVER['XDG_CONFIG_HOME'] ?? $_SERVER['HOME'].'/.config/'.$name,
+            'Windows' => $_SERVER['APPDATA'].'/'.$name,
+            default => $_SERVER['HOME'].'/.config/'.$name,
+        };
+    }
+}
diff --git a/src/ElectronServiceProvider.php b/src/ElectronServiceProvider.php
index 9e609362..568bdf2a 100644
--- a/src/ElectronServiceProvider.php
+++ b/src/ElectronServiceProvider.php
@@ -4,9 +4,11 @@
 
 use Illuminate\Foundation\Application;
 use Native\Electron\Commands\BuildCommand;
+use Native\Electron\Commands\BundleCommand;
 use Native\Electron\Commands\DevelopCommand;
 use Native\Electron\Commands\InstallCommand;
 use Native\Electron\Commands\PublishCommand;
+use Native\Electron\Commands\ResetCommand;
 use Native\Electron\Updater\UpdaterManager;
 use Spatie\LaravelPackageTools\Package;
 use Spatie\LaravelPackageTools\PackageServiceProvider;
@@ -23,6 +25,8 @@ public function configurePackage(Package $package): void
                 DevelopCommand::class,
                 BuildCommand::class,
                 PublishCommand::class,
+                BundleCommand::class,
+                ResetCommand::class,
             ]);
     }
 
diff --git a/src/Traits/CopiesBundleToBuildDirectory.php b/src/Traits/CopiesBundleToBuildDirectory.php
new file mode 100644
index 00000000..5d0f0989
--- /dev/null
+++ b/src/Traits/CopiesBundleToBuildDirectory.php
@@ -0,0 +1,53 @@
+<?php
+
+namespace Native\Electron\Traits;
+
+use Symfony\Component\Filesystem\Filesystem;
+
+use function Laravel\Prompts\warning;
+
+trait CopiesBundleToBuildDirectory
+{
+    use CopiesToBuildDirectory;
+
+    protected static string $bundlePath = 'build/__nativephp_app_bundle';
+
+    protected function hasBundled(): bool
+    {
+        return (new Filesystem)->exists($this->sourcePath(self::$bundlePath));
+    }
+
+    public function copyBundleToBuildDirectory(): bool
+    {
+        $filesystem = new Filesystem;
+
+        $this->line('Copying secure app bundle to build directory...');
+        $this->line('From: '.realpath(dirname($this->sourcePath(self::$bundlePath))));
+        $this->line('To: '.realpath(dirname($this->buildPath(self::$bundlePath))));
+
+        // Clean and create build directory
+        $filesystem->remove($this->buildPath());
+        $filesystem->mkdir($this->buildPath());
+
+        $filesToCopy = [
+            self::$bundlePath,
+            // '.env',
+        ];
+        foreach ($filesToCopy as $file) {
+            $filesystem->copy($this->sourcePath($file), $this->buildPath($file), true);
+        }
+        // $this->keepRequiredDirectories();
+
+        return true;
+    }
+
+    public function warnUnsecureBuild(): void
+    {
+        warning('===================================================================');
+        warning('                    * * * INSECURE BUILD * * *');
+        warning('===================================================================');
+        warning('Secure app bundle not found! Building with exposed source files.');
+        warning('See https://nativephp.com/docs/publishing/building#security');
+        warning('===================================================================');
+    }
+}
diff --git a/src/Traits/CopiesCertificateAuthority.php b/src/Traits/CopiesCertificateAuthority.php
index b8ed6486..ce627cce 100644
--- a/src/Traits/CopiesCertificateAuthority.php
+++ b/src/Traits/CopiesCertificateAuthority.php
@@ -18,6 +18,7 @@ protected function copyCertificateAuthorityCertificate(): void
             $phpBinaryDirectory = base_path('vendor/nativephp/php-bin/');
 
             // Check if the class this trait is used in also uses LocatesPhpBinary
+            /* @phpstan-ignore-next-line */
             if (method_exists($this, 'phpBinaryPath')) {
                 // Get binary directory but up one level
                 $phpBinaryDirectory = dirname(base_path($this->phpBinaryPath()));
diff --git a/src/Traits/CopiesToBuildDirectory.php b/src/Traits/CopiesToBuildDirectory.php
index f9627934..4c9cf9d5 100644
--- a/src/Traits/CopiesToBuildDirectory.php
+++ b/src/Traits/CopiesToBuildDirectory.php
@@ -27,6 +27,8 @@ abstract protected function sourcePath(string $path = ''): string;
         // .git and dev directories
         '.git',
         'dist',
+        'build',
+        'temp',
         'docker',
         'packages',
         '**/.github',
@@ -53,7 +55,7 @@ abstract protected function sourcePath(string $path = ''): string;
         'vendor/bin',
     ];
 
-    public function copyToBuildDirectory()
+    public function copyToBuildDirectory(): bool
     {
         $sourcePath = $this->sourcePath();
         $buildPath = $this->buildPath();
@@ -69,7 +71,10 @@ public function copyToBuildDirectory()
         $filesystem->mkdir($buildPath);
 
         // A filtered iterator that will exclude files matching our skip patterns
-        $directory = new RecursiveDirectoryIterator($sourcePath, RecursiveDirectoryIterator::SKIP_DOTS | RecursiveDirectoryIterator::FOLLOW_SYMLINKS);
+        $directory = new RecursiveDirectoryIterator(
+            $sourcePath,
+            RecursiveDirectoryIterator::SKIP_DOTS | RecursiveDirectoryIterator::FOLLOW_SYMLINKS
+        );
 
         $filter = new RecursiveCallbackFilterIterator($directory, function ($current) use ($patterns) {
             $relativePath = substr($current->getPathname(), strlen($this->sourcePath()) + 1);
@@ -109,6 +114,8 @@ public function copyToBuildDirectory()
         }
 
         $this->keepRequiredDirectories();
+
+        return true;
     }
 
     private function keepRequiredDirectories()
diff --git a/src/Traits/HandlesZephpyr.php b/src/Traits/HandlesZephpyr.php
new file mode 100644
index 00000000..26749968
--- /dev/null
+++ b/src/Traits/HandlesZephpyr.php
@@ -0,0 +1,64 @@
+<?php
+
+namespace Native\Electron\Traits;
+
+use Illuminate\Support\Facades\Http;
+
+use function Laravel\Prompts\intro;
+
+trait HandlesZephpyr
+{
+    private function baseUrl(): string
+    {
+        return str(config('nativephp-internal.zephpyr.host'))->finish('/');
+    }
+
+    private function checkAuthenticated()
+    {
+        intro('Checking authentication…');
+
+        return Http::acceptJson()
+            ->withToken(config('nativephp-internal.zephpyr.token'))
+            ->get($this->baseUrl().'api/v1/user')->successful();
+    }
+
+    private function checkForZephpyrKey()
+    {
+        $this->key = config('nativephp-internal.zephpyr.key');
+
+        if (! $this->key) {
+            $this->line('');
+            $this->warn('No ZEPHPYR_KEY found. Cannot bundle!');
+            $this->line('');
+            $this->line('Add this app\'s ZEPHPYR_KEY to its .env file:');
+            $this->line(base_path('.env'));
+            $this->line('');
+            $this->info('Not set up with Zephpyr yet? Secure your NativePHP app builds and more!');
+            $this->info('Check out '.$this->baseUrl().'');
+            $this->line('');
+
+            return false;
+        }
+
+        return true;
+    }
+
+    private function checkForZephpyrToken()
+    {
+        if (! config('nativephp-internal.zephpyr.token')) {
+            $this->line('');
+            $this->warn('No ZEPHPYR_TOKEN found. Cannot bundle!');
+            $this->line('');
+            $this->line('Add your Zephpyr API token to your .env file (ZEPHPYR_TOKEN):');
+            $this->line(base_path('.env'));
+            $this->line('');
+            $this->info('Not set up with Zephpyr yet? Secure your NativePHP app builds and more!');
+            $this->info('Check out '.$this->baseUrl().'');
+            $this->line('');
+
+            return false;
+        }
+
+        return true;
+    }
+}
diff --git a/src/Traits/SetsAppName.php b/src/Traits/SetsAppName.php
index 5281db64..c97f148c 100644
--- a/src/Traits/SetsAppName.php
+++ b/src/Traits/SetsAppName.php
@@ -4,19 +4,26 @@
 
 trait SetsAppName
 {
-    protected function setAppName(bool $slugify = false): void
+    protected function setAppName($developmentMode = false): string
     {
         $packageJsonPath = __DIR__.'/../../resources/js/package.json';
         $packageJson = json_decode(file_get_contents($packageJsonPath), true);
 
-        $name = config('app.name');
+        $name = str(config('app.name'))->slug();
 
-        if ($slugify) {
-            $name = str($name)->lower()->kebab();
+        /*
+         * Suffix the app name with '-dev' if it's a development build
+         * this way, when the developer test his freshly built app,
+         * configs, migrations won't be mixed up with the production app
+         */
+        if ($developmentMode) {
+            $name .= '-dev';
         }
 
         $packageJson['name'] = $name;
 
         file_put_contents($packageJsonPath, json_encode($packageJson, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
+
+        return $name;
     }
 }
diff --git a/tests/Feature/BootingTest.php b/tests/Feature/BootingTest.php
index c90f942c..8f5c9f79 100644
--- a/tests/Feature/BootingTest.php
+++ b/tests/Feature/BootingTest.php
@@ -19,15 +19,16 @@
     // $process->wait(); // Uncomment this line to debug
 
     try {
-        retry(30, function () use ($output) {
+        retry(10, function () use ($output) {
             // Wait until port 8100 is open
             dump('Waiting for port 8100 to open...');
 
-            $fp = @fsockopen('localhost', 8100, $errno, $errstr, 1);
+            $fp = @fsockopen('127.0.0.1', 8100, $errno, $errstr, 1);
             if ($fp === false) {
                 throw new Exception(sprintf(
-                    'Port 8100 is not open yet. Output: "%s"',
+                    'Port 8100 is not open yet. Output: "%s", Errstr: "%s"',
                     $output,
+                    $errstr
                 ));
             }
         }, 5000);