From a94207672420265de549031c9a4fbf0363dff8f6 Mon Sep 17 00:00:00 2001 From: reaper Date: Fri, 28 Aug 2026 01:24:27 -0500 Subject: [PATCH] fix: harden Windows native project startup --- .github/workflows/macos-release.yml | 2 +- .github/workflows/windows-release.yml | 6 +++++- package-lock.json | 4 ++-- package.json | 2 +- src/main/moduleRuntime.ts | 11 ++++++++++ src/main/native/nativeProject.test.ts | 31 +++++++++++++++++++++------ src/main/native/nativeProject.ts | 29 ++++++++++++++++--------- src/main/native/processSupervisor.ts | 8 ++++++- 8 files changed, 71 insertions(+), 22 deletions(-) diff --git a/.github/workflows/macos-release.yml b/.github/workflows/macos-release.yml index 1bc3aaa..950ba90 100644 --- a/.github/workflows/macos-release.yml +++ b/.github/workflows/macos-release.yml @@ -6,7 +6,7 @@ on: release_tag: description: Existing GitHub release tag to receive the macOS files required: true - default: v2.0.0-alpha.30 + default: v2.0.0-alpha.31 push: tags: - 'v*' diff --git a/.github/workflows/windows-release.yml b/.github/workflows/windows-release.yml index 4ea694a..07b6978 100644 --- a/.github/workflows/windows-release.yml +++ b/.github/workflows/windows-release.yml @@ -6,7 +6,7 @@ on: release_tag: description: Existing GitHub release tag to receive the Windows installer required: true - default: v2.0.0-alpha.30 + default: v2.0.0-alpha.31 push: tags: - 'v*' @@ -42,6 +42,10 @@ jobs: run: | ./runtime-build/windows-x64/stage-runtime.ps1 node scripts/smoke-native-runtime.cjs dist/native-runtime-stage/win32-x64 + $runtime = (Resolve-Path 'dist/native-runtime-stage/win32-x64').Path + $env:AURORA_NATIVE_SMOKE_ROOT = $runtime + $env:AURORA_NATIVE_WORDPRESS_SMOKE_ROOT = $runtime + npx vitest run src/main/native/nativeProject.test.ts node scripts/package-native-runtime.cjs dist/native-runtime-stage/win32-x64 dist/native-runtime/aurora-native-0.1.0-win32-x64.tar.gz - name: Read application version diff --git a/package-lock.json b/package-lock.json index bffec0d..d3043d9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "aurora-dockside", - "version": "2.0.0-alpha.30", + "version": "2.0.0-alpha.31", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "aurora-dockside", - "version": "2.0.0-alpha.30", + "version": "2.0.0-alpha.31", "hasInstallScript": true, "dependencies": { "@electron-toolkit/preload": "^3.0.2", diff --git a/package.json b/package.json index 55d84f5..8e1f4de 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "aurora-dockside", - "version": "2.0.0-alpha.30", + "version": "2.0.0-alpha.31", "description": "A modern, modular Docker development platform for building, running, and managing web applications locally.", "desktopName": "aurora-dockside.desktop", "main": "./out/main/index.js", diff --git a/src/main/moduleRuntime.ts b/src/main/moduleRuntime.ts index e9ffb9f..b02872d 100644 --- a/src/main/moduleRuntime.ts +++ b/src/main/moduleRuntime.ts @@ -38,6 +38,15 @@ function sendExit(sender: WebContents, operationId: string, exitCode: number): v sender.send('terminal:exit', { operationId, exitCode, cancelled: false }) } +function sendError(sender: WebContents, operationId: string, error: unknown): void { + if (sender.isDestroyed()) return + sender.send('terminal:data', { + operationId, + stream: 'stderr', + chunk: `\n${error instanceof Error ? error.message : String(error)}\n` + }) +} + function loadModule(moduleId: string, main: string): LoadedModule { const root = resolve(moduleDirectory(), moduleId) const entry = resolve(root, main) @@ -186,6 +195,7 @@ export async function runModuleProjectCreate( }) sendExit(sender, operationId, 0) } catch (error) { + sendError(sender, operationId, error) sendExit(sender, operationId, 1) throw error } @@ -216,6 +226,7 @@ export async function runModuleProjectTool( if (!handled) throw new Error(`Module '${moduleId}' does not implement projectTool`) sendExit(sender, operationId, 0) } catch (error) { + sendError(sender, operationId, error) sendExit(sender, operationId, 1) throw error } diff --git a/src/main/native/nativeProject.test.ts b/src/main/native/nativeProject.test.ts index 6d62ccd..ab1ea21 100644 --- a/src/main/native/nativeProject.test.ts +++ b/src/main/native/nativeProject.test.ts @@ -7,6 +7,7 @@ import { promisify } from 'util' import { describe, expect, it } from 'vitest' import { renderMariaDbConfig, + nativeConfigPath, renderNativeAdminerBootstrap, renderNginxConfig, renderPhpFpmConfig, @@ -25,6 +26,12 @@ const project = { } const execFileAsync = promisify(execFile) +function runtimeCommand(runtime: string, name: string): string { + if (process.platform !== 'win32') return join(runtime, 'bin', name) + if (name === 'php') return join(runtime, 'bin', 'php', 'php.exe') + return join(runtime, 'bin', 'mariadb', 'bin', `${name}.exe`) +} + function runWithInput(command: string, args: string[], cwd: string, input: string): Promise { return new Promise((resolve, reject) => { const child = spawn(command, args, { cwd, env: process.env, stdio: ['pipe', 'pipe', 'pipe'] }) @@ -42,6 +49,11 @@ function runWithInput(command: string, args: string[], cwd: string, input: strin } describe('native project configuration', () => { + it('writes portable forward-slash paths into Windows service configuration', () => { + expect(nativeConfigPath('C:\\Users\\Aurora Dragon\\project', 'win32')).toBe( + 'C:/Users/Aurora Dragon/project' + ) + }) it('isolates PHP-FPM on its allocated loopback port', () => expect(renderPhpFpmConfig(project)).toContain('listen = 127.0.0.1:41002')) it('routes nginx PHP requests to the project PHP-FPM service', () => { @@ -131,8 +143,15 @@ it.runIf(Boolean(process.env.AURORA_NATIVE_WORDPRESS_SMOKE_ROOT))( https: `http://127.0.0.1:${definition.ports.http}` }, native: { - php: join(runtime, 'bin', 'php'), - wp: join(runtime, 'bin', 'wp'), + php: runtimeCommand(runtime, 'php'), + wp: + process.platform === 'win32' + ? runtimeCommand(runtime, 'php') + : join(runtime, 'bin', 'wp'), + wpPrefixArgs: + process.platform === 'win32' + ? ['-d', 'memory_limit=512M', join(runtime, 'tools', 'wp-cli.phar')] + : [], databasePort: definition.ports.database, start: () => startNativeProject(definition) }, @@ -161,12 +180,12 @@ it.runIf(Boolean(process.env.AURORA_NATIVE_WORDPRESS_SMOKE_ROOT))( 'db' ] const dump = await execFileAsync( - join(runtime, 'bin', 'mariadb-dump'), + runtimeCommand(runtime, 'mariadb-dump'), [...databaseArgs.slice(0, -1), '--single-transaction', 'db'], { cwd: root, env: process.env, maxBuffer: 32 * 1024 * 1024 } ) await execFileAsync( - join(runtime, 'bin', 'mariadb'), + runtimeCommand(runtime, 'mariadb'), [ ...databaseArgs, '-e', @@ -174,9 +193,9 @@ it.runIf(Boolean(process.env.AURORA_NATIVE_WORDPRESS_SMOKE_ROOT))( ], { cwd: root, env: process.env } ) - await runWithInput(join(runtime, 'bin', 'mariadb'), databaseArgs, root, dump.stdout) + await runWithInput(runtimeCommand(runtime, 'mariadb'), databaseArgs, root, dump.stdout) const restored = await execFileAsync( - join(runtime, 'bin', 'mariadb'), + runtimeCommand(runtime, 'mariadb'), [ ...databaseArgs, '--batch', diff --git a/src/main/native/nativeProject.ts b/src/main/native/nativeProject.ts index c78121b..8e133a1 100644 --- a/src/main/native/nativeProject.ts +++ b/src/main/native/nativeProject.ts @@ -34,7 +34,11 @@ function nativeDirectory(root: string): string { } function quoteNginx(value: string): string { - return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + return nativeConfigPath(value).replace(/"/g, '\\"') +} + +export function nativeConfigPath(value: string, platform = process.platform): string { + return platform === 'win32' ? value.replace(/\\/g, '/') : value } export function renderPhpFpmConfig(project: NativeProjectDefinition): string { @@ -152,15 +156,15 @@ export function renderMariaDbConfig( process.platform === 'win32' ? join(installedRuntimeRoot, 'bin', 'mariadb') : join(installedRuntimeRoot, 'root', 'usr') + const configPath = (value: string): string => nativeConfigPath(value) return `[mariadbd] -basedir=${basedir} -datadir=${join(directory, 'data', 'mariadb')} -tmpdir=${join(directory, 'tmp')} +basedir="${configPath(basedir)}" +datadir="${configPath(join(directory, 'data', 'mariadb'))}" +tmpdir="${configPath(join(directory, 'tmp'))}" bind-address=127.0.0.1 port=${project.ports.database} -socket=${join(directory, 'mariadb.sock')} -pid-file=${join(directory, 'pids', 'mariadb.pid')} -log-error=${join(directory, 'logs', 'mariadb.log')} +${process.platform === 'win32' ? '' : `socket=${join(directory, 'mariadb.sock')}\n`}pid-file="${configPath(join(directory, 'pids', 'mariadb.pid'))}" +log-error="${configPath(join(directory, 'logs', 'mariadb.log'))}" skip-name-resolve ` } @@ -192,7 +196,7 @@ export async function provisionNativeProject(project: NativeProjectDefinition): ? [ writeFile( join(directory, 'config', 'php.ini'), - `extension_dir="${join(installedRoot(project), 'bin', 'php', 'ext')}"\nextension=mysqli\nextension=pdo_mysql\nextension=mbstring\nextension=curl\nextension=openssl\nextension=zip\ndisplay_errors=On\nlog_errors=On\nerror_log="${join(directory, 'logs', 'php.log')}"\n` + `extension_dir="${nativeConfigPath(join(installedRoot(project), 'bin', 'php', 'ext'))}"\nextension=mysqli\nextension=pdo_mysql\nextension=mbstring\nextension=curl\nextension=openssl\nextension=zip\ndisplay_errors=On\nlog_errors=On\nerror_log="${nativeConfigPath(join(directory, 'logs', 'php.log'))}"\n` ) ] : []) @@ -229,7 +233,7 @@ export function nativeServiceSpecs(project: NativeProjectDefinition): NativeServ ...common('database'), command: runtimeExecutable(project, 'mariadbd'), args: [ - `--defaults-file=${join(directory, 'config', 'mariadb.cnf')}`, + `--defaults-file=${nativeConfigPath(join(directory, 'config', 'mariadb.cnf'))}`, ...(process.platform === 'win32' ? ['--console'] : []) ], ready: { port: project.ports.database, timeoutMs: 30000 } @@ -253,7 +257,12 @@ export function nativeServiceSpecs(project: NativeProjectDefinition): NativeServ { ...common('web'), command: runtimeExecutable(project, 'nginx'), - args: ['-c', join(directory, 'config', 'nginx.conf'), '-p', `${directory}/`], + args: [ + '-c', + nativeConfigPath(join(directory, 'config', 'nginx.conf')), + '-p', + `${nativeConfigPath(directory)}/` + ], ready: { port: project.ports.http } } ] diff --git a/src/main/native/processSupervisor.ts b/src/main/native/processSupervisor.ts index 9f7ec55..5371170 100644 --- a/src/main/native/processSupervisor.ts +++ b/src/main/native/processSupervisor.ts @@ -144,7 +144,13 @@ export class NativeProcessSupervisor { await terminate(child.pid) this.children.delete(spec.id) await rm(spec.pidPath, { force: true }) - throw error + const reason = error instanceof Error ? error.message : String(error) + const details = log.trim() + throw new Error( + details + ? `${spec.id} failed to start: ${reason}\n\n${details}` + : `${spec.id} failed to start: ${reason} See ${spec.logPath}` + ) } }