fix: harden Windows native project startup

This commit is contained in:
reaper
2026-08-28 01:24:27 -05:00
parent ff38e91a70
commit a942076724
8 changed files with 71 additions and 22 deletions
+11
View File
@@ -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
}
+25 -6
View File
@@ -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<void> {
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',
+19 -10
View File
@@ -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 }
}
]
+7 -1
View File
@@ -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}`
)
}
}