fix: harden Windows native project startup
This commit is contained in:
@@ -6,7 +6,7 @@ on:
|
|||||||
release_tag:
|
release_tag:
|
||||||
description: Existing GitHub release tag to receive the macOS files
|
description: Existing GitHub release tag to receive the macOS files
|
||||||
required: true
|
required: true
|
||||||
default: v2.0.0-alpha.30
|
default: v2.0.0-alpha.31
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- 'v*'
|
- 'v*'
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ on:
|
|||||||
release_tag:
|
release_tag:
|
||||||
description: Existing GitHub release tag to receive the Windows installer
|
description: Existing GitHub release tag to receive the Windows installer
|
||||||
required: true
|
required: true
|
||||||
default: v2.0.0-alpha.30
|
default: v2.0.0-alpha.31
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- 'v*'
|
- 'v*'
|
||||||
@@ -42,6 +42,10 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
./runtime-build/windows-x64/stage-runtime.ps1
|
./runtime-build/windows-x64/stage-runtime.ps1
|
||||||
node scripts/smoke-native-runtime.cjs dist/native-runtime-stage/win32-x64
|
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
|
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
|
- name: Read application version
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "aurora-dockside",
|
"name": "aurora-dockside",
|
||||||
"version": "2.0.0-alpha.30",
|
"version": "2.0.0-alpha.31",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "aurora-dockside",
|
"name": "aurora-dockside",
|
||||||
"version": "2.0.0-alpha.30",
|
"version": "2.0.0-alpha.31",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@electron-toolkit/preload": "^3.0.2",
|
"@electron-toolkit/preload": "^3.0.2",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "aurora-dockside",
|
"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.",
|
"description": "A modern, modular Docker development platform for building, running, and managing web applications locally.",
|
||||||
"desktopName": "aurora-dockside.desktop",
|
"desktopName": "aurora-dockside.desktop",
|
||||||
"main": "./out/main/index.js",
|
"main": "./out/main/index.js",
|
||||||
|
|||||||
@@ -38,6 +38,15 @@ function sendExit(sender: WebContents, operationId: string, exitCode: number): v
|
|||||||
sender.send('terminal:exit', { operationId, exitCode, cancelled: false })
|
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 {
|
function loadModule(moduleId: string, main: string): LoadedModule {
|
||||||
const root = resolve(moduleDirectory(), moduleId)
|
const root = resolve(moduleDirectory(), moduleId)
|
||||||
const entry = resolve(root, main)
|
const entry = resolve(root, main)
|
||||||
@@ -186,6 +195,7 @@ export async function runModuleProjectCreate(
|
|||||||
})
|
})
|
||||||
sendExit(sender, operationId, 0)
|
sendExit(sender, operationId, 0)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
sendError(sender, operationId, error)
|
||||||
sendExit(sender, operationId, 1)
|
sendExit(sender, operationId, 1)
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
@@ -216,6 +226,7 @@ export async function runModuleProjectTool(
|
|||||||
if (!handled) throw new Error(`Module '${moduleId}' does not implement projectTool`)
|
if (!handled) throw new Error(`Module '${moduleId}' does not implement projectTool`)
|
||||||
sendExit(sender, operationId, 0)
|
sendExit(sender, operationId, 0)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
sendError(sender, operationId, error)
|
||||||
sendExit(sender, operationId, 1)
|
sendExit(sender, operationId, 1)
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { promisify } from 'util'
|
|||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it } from 'vitest'
|
||||||
import {
|
import {
|
||||||
renderMariaDbConfig,
|
renderMariaDbConfig,
|
||||||
|
nativeConfigPath,
|
||||||
renderNativeAdminerBootstrap,
|
renderNativeAdminerBootstrap,
|
||||||
renderNginxConfig,
|
renderNginxConfig,
|
||||||
renderPhpFpmConfig,
|
renderPhpFpmConfig,
|
||||||
@@ -25,6 +26,12 @@ const project = {
|
|||||||
}
|
}
|
||||||
const execFileAsync = promisify(execFile)
|
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> {
|
function runWithInput(command: string, args: string[], cwd: string, input: string): Promise<void> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const child = spawn(command, args, { cwd, env: process.env, stdio: ['pipe', 'pipe', 'pipe'] })
|
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', () => {
|
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', () =>
|
it('isolates PHP-FPM on its allocated loopback port', () =>
|
||||||
expect(renderPhpFpmConfig(project)).toContain('listen = 127.0.0.1:41002'))
|
expect(renderPhpFpmConfig(project)).toContain('listen = 127.0.0.1:41002'))
|
||||||
it('routes nginx PHP requests to the project PHP-FPM service', () => {
|
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}`
|
https: `http://127.0.0.1:${definition.ports.http}`
|
||||||
},
|
},
|
||||||
native: {
|
native: {
|
||||||
php: join(runtime, 'bin', 'php'),
|
php: runtimeCommand(runtime, 'php'),
|
||||||
wp: join(runtime, 'bin', 'wp'),
|
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,
|
databasePort: definition.ports.database,
|
||||||
start: () => startNativeProject(definition)
|
start: () => startNativeProject(definition)
|
||||||
},
|
},
|
||||||
@@ -161,12 +180,12 @@ it.runIf(Boolean(process.env.AURORA_NATIVE_WORDPRESS_SMOKE_ROOT))(
|
|||||||
'db'
|
'db'
|
||||||
]
|
]
|
||||||
const dump = await execFileAsync(
|
const dump = await execFileAsync(
|
||||||
join(runtime, 'bin', 'mariadb-dump'),
|
runtimeCommand(runtime, 'mariadb-dump'),
|
||||||
[...databaseArgs.slice(0, -1), '--single-transaction', 'db'],
|
[...databaseArgs.slice(0, -1), '--single-transaction', 'db'],
|
||||||
{ cwd: root, env: process.env, maxBuffer: 32 * 1024 * 1024 }
|
{ cwd: root, env: process.env, maxBuffer: 32 * 1024 * 1024 }
|
||||||
)
|
)
|
||||||
await execFileAsync(
|
await execFileAsync(
|
||||||
join(runtime, 'bin', 'mariadb'),
|
runtimeCommand(runtime, 'mariadb'),
|
||||||
[
|
[
|
||||||
...databaseArgs,
|
...databaseArgs,
|
||||||
'-e',
|
'-e',
|
||||||
@@ -174,9 +193,9 @@ it.runIf(Boolean(process.env.AURORA_NATIVE_WORDPRESS_SMOKE_ROOT))(
|
|||||||
],
|
],
|
||||||
{ cwd: root, env: process.env }
|
{ 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(
|
const restored = await execFileAsync(
|
||||||
join(runtime, 'bin', 'mariadb'),
|
runtimeCommand(runtime, 'mariadb'),
|
||||||
[
|
[
|
||||||
...databaseArgs,
|
...databaseArgs,
|
||||||
'--batch',
|
'--batch',
|
||||||
|
|||||||
@@ -34,7 +34,11 @@ function nativeDirectory(root: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function quoteNginx(value: 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 {
|
export function renderPhpFpmConfig(project: NativeProjectDefinition): string {
|
||||||
@@ -152,15 +156,15 @@ export function renderMariaDbConfig(
|
|||||||
process.platform === 'win32'
|
process.platform === 'win32'
|
||||||
? join(installedRuntimeRoot, 'bin', 'mariadb')
|
? join(installedRuntimeRoot, 'bin', 'mariadb')
|
||||||
: join(installedRuntimeRoot, 'root', 'usr')
|
: join(installedRuntimeRoot, 'root', 'usr')
|
||||||
|
const configPath = (value: string): string => nativeConfigPath(value)
|
||||||
return `[mariadbd]
|
return `[mariadbd]
|
||||||
basedir=${basedir}
|
basedir="${configPath(basedir)}"
|
||||||
datadir=${join(directory, 'data', 'mariadb')}
|
datadir="${configPath(join(directory, 'data', 'mariadb'))}"
|
||||||
tmpdir=${join(directory, 'tmp')}
|
tmpdir="${configPath(join(directory, 'tmp'))}"
|
||||||
bind-address=127.0.0.1
|
bind-address=127.0.0.1
|
||||||
port=${project.ports.database}
|
port=${project.ports.database}
|
||||||
socket=${join(directory, 'mariadb.sock')}
|
${process.platform === 'win32' ? '' : `socket=${join(directory, 'mariadb.sock')}\n`}pid-file="${configPath(join(directory, 'pids', 'mariadb.pid'))}"
|
||||||
pid-file=${join(directory, 'pids', 'mariadb.pid')}
|
log-error="${configPath(join(directory, 'logs', 'mariadb.log'))}"
|
||||||
log-error=${join(directory, 'logs', 'mariadb.log')}
|
|
||||||
skip-name-resolve
|
skip-name-resolve
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
@@ -192,7 +196,7 @@ export async function provisionNativeProject(project: NativeProjectDefinition):
|
|||||||
? [
|
? [
|
||||||
writeFile(
|
writeFile(
|
||||||
join(directory, 'config', 'php.ini'),
|
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'),
|
...common('database'),
|
||||||
command: runtimeExecutable(project, 'mariadbd'),
|
command: runtimeExecutable(project, 'mariadbd'),
|
||||||
args: [
|
args: [
|
||||||
`--defaults-file=${join(directory, 'config', 'mariadb.cnf')}`,
|
`--defaults-file=${nativeConfigPath(join(directory, 'config', 'mariadb.cnf'))}`,
|
||||||
...(process.platform === 'win32' ? ['--console'] : [])
|
...(process.platform === 'win32' ? ['--console'] : [])
|
||||||
],
|
],
|
||||||
ready: { port: project.ports.database, timeoutMs: 30000 }
|
ready: { port: project.ports.database, timeoutMs: 30000 }
|
||||||
@@ -253,7 +257,12 @@ export function nativeServiceSpecs(project: NativeProjectDefinition): NativeServ
|
|||||||
{
|
{
|
||||||
...common('web'),
|
...common('web'),
|
||||||
command: runtimeExecutable(project, 'nginx'),
|
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 }
|
ready: { port: project.ports.http }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -144,7 +144,13 @@ export class NativeProcessSupervisor {
|
|||||||
await terminate(child.pid)
|
await terminate(child.pid)
|
||||||
this.children.delete(spec.id)
|
this.children.delete(spec.id)
|
||||||
await rm(spec.pidPath, { force: true })
|
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}`
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user