feat: add native logs and database portability
This commit is contained in:
@@ -26,11 +26,12 @@ Every project receives reserved loopback ports, generated service configuration,
|
|||||||
2. Native process supervisor and loopback port allocator.
|
2. Native process supervisor and loopback port allocator.
|
||||||
3. Linux x64 bundle with PHP 8.5, nginx, MariaDB 11.8, and WP-CLI.
|
3. Linux x64 bundle with PHP 8.5, nginx, MariaDB 11.8, and WP-CLI.
|
||||||
4. Native project lifecycle and single-site WordPress provisioning.
|
4. Native project lifecycle and single-site WordPress provisioning.
|
||||||
5. Native logs, database import/export, and database administration.
|
5. Native service logs, compressed snapshots, and database import/export.
|
||||||
6. macOS arm64/x64 and Windows x64 bundles.
|
6. Native database administration.
|
||||||
7. Additional PHP/database versions, Apache, Drupal, Node.js, and developer services.
|
7. macOS arm64/x64 and Windows x64 bundles.
|
||||||
|
8. Additional PHP/database versions, Apache, Drupal, Node.js, and developer services.
|
||||||
|
|
||||||
Dockside enables native project creation only when an installed platform bundle contains the selected PHP branch, nginx, MariaDB 11.8, and WP-CLI. Existing projects default to the container engine for backward compatibility. The native smoke suite starts all three services, serves PHP through FastCGI, provisions a real WordPress site, and verifies its HTTP response and cleanup.
|
Dockside enables native project creation only when an installed platform bundle contains the selected PHP branch, nginx, MariaDB 11.8, and WP-CLI. Existing projects default to the container engine for backward compatibility. The native smoke suite starts all three services, serves PHP through FastCGI, provisions a real WordPress site, round-trips its database through the bundled dump and restore clients, and verifies its HTTP response and cleanup.
|
||||||
|
|
||||||
## Runtime update notifications
|
## Runtime update notifications
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ copy_binary_and_libraries /usr/local/bin/php
|
|||||||
copy_binary_and_libraries /usr/local/sbin/php-fpm
|
copy_binary_and_libraries /usr/local/sbin/php-fpm
|
||||||
copy_binary_and_libraries /usr/sbin/nginx
|
copy_binary_and_libraries /usr/sbin/nginx
|
||||||
copy_binary_and_libraries /usr/bin/mariadbd
|
copy_binary_and_libraries /usr/bin/mariadbd
|
||||||
|
copy_binary_and_libraries /usr/bin/mariadb
|
||||||
|
copy_binary_and_libraries /usr/bin/mariadb-dump
|
||||||
copy_binary_and_libraries /usr/bin/mariadb-install-db
|
copy_binary_and_libraries /usr/bin/mariadb-install-db
|
||||||
copy_binary_and_libraries /usr/bin/my_print_defaults
|
copy_binary_and_libraries /usr/bin/my_print_defaults
|
||||||
copy_binary_and_libraries /usr/bin/resolveip
|
copy_binary_and_libraries /usr/bin/resolveip
|
||||||
@@ -79,7 +81,7 @@ done
|
|||||||
# Put the ELF programs behind relocatable wrappers so those calls also use the
|
# Put the ELF programs behind relocatable wrappers so those calls also use the
|
||||||
# bundled musl loader instead of the host's /lib interpreter.
|
# bundled musl loader instead of the host's /lib interpreter.
|
||||||
mkdir -p "$root/usr/libexec/aurora"
|
mkdir -p "$root/usr/libexec/aurora"
|
||||||
for name in mariadbd my_print_defaults resolveip; do
|
for name in mariadbd mariadb mariadb-dump my_print_defaults resolveip; do
|
||||||
mv "$root/usr/bin/$name" "$root/usr/libexec/aurora/$name"
|
mv "$root/usr/bin/$name" "$root/usr/libexec/aurora/$name"
|
||||||
cat > "$root/usr/bin/$name" <<EOF
|
cat > "$root/usr/bin/$name" <<EOF
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
@@ -103,6 +105,14 @@ exec "$(dirname "$0")/aurora-exec" /usr/libexec/aurora/mariadbd "$@"
|
|||||||
EOF
|
EOF
|
||||||
chmod +x "$stage/bin/mariadbd"
|
chmod +x "$stage/bin/mariadbd"
|
||||||
|
|
||||||
|
for name in mariadb mariadb-dump; do
|
||||||
|
cat > "$stage/bin/$name" <<EOF
|
||||||
|
#!/bin/sh
|
||||||
|
exec "\$(dirname "\$0")/aurora-exec" "/usr/libexec/aurora/$name" "\$@"
|
||||||
|
EOF
|
||||||
|
chmod +x "$stage/bin/$name"
|
||||||
|
done
|
||||||
|
|
||||||
cp /tmp/wp-cli.phar "$root/usr/local/bin/wp-cli.phar"
|
cp /tmp/wp-cli.phar "$root/usr/local/bin/wp-cli.phar"
|
||||||
cat > "$stage/bin/wp" <<'EOF'
|
cat > "$stage/bin/wp" <<'EOF'
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
@@ -124,6 +134,8 @@ cat > "$stage/runtime.template.json" <<EOF
|
|||||||
{ "id": "php", "version": "$php_version", "executable": "bin/php-fpm" },
|
{ "id": "php", "version": "$php_version", "executable": "bin/php-fpm" },
|
||||||
{ "id": "nginx", "version": "$nginx_version", "executable": "bin/nginx" },
|
{ "id": "nginx", "version": "$nginx_version", "executable": "bin/nginx" },
|
||||||
{ "id": "mariadb", "version": "$mariadb_version", "executable": "bin/mariadbd" },
|
{ "id": "mariadb", "version": "$mariadb_version", "executable": "bin/mariadbd" },
|
||||||
|
{ "id": "mariadb-client", "version": "$mariadb_version", "executable": "bin/mariadb" },
|
||||||
|
{ "id": "mariadb-dump", "version": "$mariadb_version", "executable": "bin/mariadb-dump" },
|
||||||
{ "id": "wp-cli", "version": "2.12.0", "executable": "bin/wp" }
|
{ "id": "wp-cli", "version": "2.12.0", "executable": "bin/wp" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,18 @@ const checks = [
|
|||||||
join(root, 'bin/wp'),
|
join(root, 'bin/wp'),
|
||||||
['--version'],
|
['--version'],
|
||||||
template.components.find((item) => item.id === 'wp-cli')?.version
|
template.components.find((item) => item.id === 'wp-cli')?.version
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'MariaDB client',
|
||||||
|
join(root, 'bin/mariadb'),
|
||||||
|
['--version'],
|
||||||
|
template.components.find((item) => item.id === 'mariadb')?.version
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'MariaDB dump',
|
||||||
|
join(root, 'bin/mariadb-dump'),
|
||||||
|
['--version'],
|
||||||
|
template.components.find((item) => item.id === 'mariadb')?.version
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
for (const [name, command, args, version] of checks) {
|
for (const [name, command, args, version] of checks) {
|
||||||
|
|||||||
@@ -702,11 +702,8 @@ export async function getNativeProjectDefinition(
|
|||||||
return config.runtimeEngine === 'native' ? nativeDefinition(root, config) : null
|
return config.runtimeEngine === 'native' ? nativeDefinition(root, config) : null
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getProjectConfigByRoot(
|
export async function getProjectConfigByRoot(root: string): Promise<AuroraConfig> {
|
||||||
root: string
|
return readConfig(root)
|
||||||
): Promise<{ database: 'mariadb' | 'mysql' | 'postgres'; databaseVersion: string; name: string }> {
|
|
||||||
const config = await readConfig(root)
|
|
||||||
return { database: config.database, databaseVersion: config.databaseVersion, name: config.name }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function ensureCertificateAuthority(): Promise<void> {
|
async function ensureCertificateAuthority(): Promise<void> {
|
||||||
|
|||||||
+129
-25
@@ -5,6 +5,8 @@ import { basename, join } from 'path'
|
|||||||
import { spawn } from 'child_process'
|
import { spawn } from 'child_process'
|
||||||
import { createGunzip, createGzip } from 'zlib'
|
import { createGunzip, createGzip } from 'zlib'
|
||||||
import { AURORA_ENV, getProjectConfigByRoot } from '../auroraEngine'
|
import { AURORA_ENV, getProjectConfigByRoot } from '../auroraEngine'
|
||||||
|
import { startNativeProject } from '../native/nativeProject'
|
||||||
|
import { runtimeRoot } from '../nativeRuntime'
|
||||||
import type { AuroraSnapshot } from '../../shared/types'
|
import type { AuroraSnapshot } from '../../shared/types'
|
||||||
|
|
||||||
const snapshotsDir = (root: string): string => join(root, '.aurora', 'snapshots')
|
const snapshotsDir = (root: string): string => join(root, '.aurora', 'snapshots')
|
||||||
@@ -16,7 +18,10 @@ function safeSnapshotName(value?: string): string {
|
|||||||
return safe || fallback
|
return safe || fallback
|
||||||
}
|
}
|
||||||
|
|
||||||
function dbCommand(type: 'mariadb' | 'mysql' | 'postgres', mode: 'dump' | 'restore'): { command: string; args: string[] } {
|
function dbCommand(
|
||||||
|
type: 'mariadb' | 'mysql' | 'postgres',
|
||||||
|
mode: 'dump' | 'restore'
|
||||||
|
): { command: string; args: string[] } {
|
||||||
if (type === 'postgres') {
|
if (type === 'postgres') {
|
||||||
return mode === 'dump'
|
return mode === 'dump'
|
||||||
? { command: 'pg_dump', args: ['-U', 'db', '-d', 'db', '--clean', '--if-exists'] }
|
? { command: 'pg_dump', args: ['-U', 'db', '-d', 'db', '--clean', '--if-exists'] }
|
||||||
@@ -24,29 +29,72 @@ function dbCommand(type: 'mariadb' | 'mysql' | 'postgres', mode: 'dump' | 'resto
|
|||||||
}
|
}
|
||||||
if (type === 'mysql') {
|
if (type === 'mysql') {
|
||||||
return mode === 'dump'
|
return mode === 'dump'
|
||||||
? { command: 'mysqldump', args: ['-udb', '-pdb', '--single-transaction', '--routines', '--triggers', 'db'] }
|
? {
|
||||||
|
command: 'mysqldump',
|
||||||
|
args: ['-udb', '-pdb', '--single-transaction', '--routines', '--triggers', 'db']
|
||||||
|
}
|
||||||
: { command: 'mysql', args: ['-udb', '-pdb', 'db'] }
|
: { command: 'mysql', args: ['-udb', '-pdb', 'db'] }
|
||||||
}
|
}
|
||||||
return mode === 'dump'
|
return mode === 'dump'
|
||||||
? { command: 'mariadb-dump', args: ['-udb', '-pdb', '--single-transaction', '--routines', '--triggers', 'db'] }
|
? {
|
||||||
|
command: 'mariadb-dump',
|
||||||
|
args: ['-udb', '-pdb', '--single-transaction', '--routines', '--triggers', 'db']
|
||||||
|
}
|
||||||
: { command: 'mariadb', args: ['-udb', '-pdb', 'db'] }
|
: { command: 'mariadb', args: ['-udb', '-pdb', 'db'] }
|
||||||
}
|
}
|
||||||
|
|
||||||
function runDatabasePipe(root: string, type: 'mariadb' | 'mysql' | 'postgres', mode: 'dump' | 'restore', filePath: string): Promise<void> {
|
async function runDatabasePipe(
|
||||||
|
root: string,
|
||||||
|
mode: 'dump' | 'restore',
|
||||||
|
filePath: string
|
||||||
|
): Promise<void> {
|
||||||
|
const config = await getProjectConfigByRoot(root)
|
||||||
|
let command: string
|
||||||
|
let args: string[]
|
||||||
|
if (config.runtimeEngine === 'native') {
|
||||||
|
if (!config.nativePorts) throw new Error('Native project has no allocated database port.')
|
||||||
|
if (config.database !== 'mariadb')
|
||||||
|
throw new Error('Aurora Native database portability currently supports MariaDB.')
|
||||||
|
await startNativeProject({
|
||||||
|
name: config.name,
|
||||||
|
root,
|
||||||
|
docroot: config.docroot,
|
||||||
|
ports: config.nativePorts
|
||||||
|
})
|
||||||
|
command = join(runtimeRoot(), 'bin', mode === 'dump' ? 'mariadb-dump' : 'mariadb')
|
||||||
|
args = [
|
||||||
|
'--host=127.0.0.1',
|
||||||
|
`--port=${config.nativePorts.database}`,
|
||||||
|
'--user=db',
|
||||||
|
'--password=db',
|
||||||
|
...(mode === 'dump' ? ['--single-transaction', '--routines', '--triggers'] : []),
|
||||||
|
'db'
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
const db = dbCommand(config.database, mode)
|
||||||
|
command = 'docker'
|
||||||
|
args = ['compose', '-f', composeFile(root), 'exec', '-T', 'db', db.command, ...db.args]
|
||||||
|
}
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const db = dbCommand(type, mode)
|
const child = spawn(command, args, {
|
||||||
const child = spawn('docker', ['compose', '-f', composeFile(root), 'exec', '-T', 'db', db.command, ...db.args], {
|
cwd: root,
|
||||||
cwd: root, env: AURORA_ENV, stdio: ['pipe', 'pipe', 'pipe']
|
env: AURORA_ENV,
|
||||||
|
stdio: ['pipe', 'pipe', 'pipe']
|
||||||
})
|
})
|
||||||
let stderr = ''
|
let stderr = ''
|
||||||
child.stderr.on('data', (chunk) => { stderr += chunk.toString() })
|
child.stderr.on('data', (chunk) => {
|
||||||
|
stderr += chunk.toString()
|
||||||
|
})
|
||||||
const gz = filePath.endsWith('.gz')
|
const gz = filePath.endsWith('.gz')
|
||||||
let outputFinished = Promise.resolve()
|
let outputFinished = Promise.resolve()
|
||||||
if (mode === 'dump') {
|
if (mode === 'dump') {
|
||||||
const output = createWriteStream(filePath)
|
const output = createWriteStream(filePath)
|
||||||
const source = gz ? child.stdout.pipe(createGzip()) : child.stdout
|
const source = gz ? child.stdout.pipe(createGzip()) : child.stdout
|
||||||
source.pipe(output)
|
source.pipe(output)
|
||||||
outputFinished = new Promise<void>((done, fail) => { output.on('finish', done); output.on('error', fail) })
|
outputFinished = new Promise<void>((done, fail) => {
|
||||||
|
output.on('finish', done)
|
||||||
|
output.on('error', fail)
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
const input = createReadStream(filePath)
|
const input = createReadStream(filePath)
|
||||||
const source = gz ? input.pipe(createGunzip()) : input
|
const source = gz ? input.pipe(createGunzip()) : input
|
||||||
@@ -55,8 +103,14 @@ function runDatabasePipe(root: string, type: 'mariadb' | 'mysql' | 'postgres', m
|
|||||||
}
|
}
|
||||||
child.on('error', reject)
|
child.on('error', reject)
|
||||||
child.on('close', async (code) => {
|
child.on('close', async (code) => {
|
||||||
if (code !== 0) return reject(new Error(stderr.trim() || `Database command exited with code ${code}`))
|
if (code !== 0)
|
||||||
try { await outputFinished; resolve() } catch (error) { reject(error) }
|
return reject(new Error(stderr.trim() || `Database command exited with code ${code}`))
|
||||||
|
try {
|
||||||
|
await outputFinished
|
||||||
|
resolve()
|
||||||
|
} catch (error) {
|
||||||
|
reject(error)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -65,22 +119,72 @@ async function listSnapshots(root: string): Promise<AuroraSnapshot[]> {
|
|||||||
const dir = snapshotsDir(root)
|
const dir = snapshotsDir(root)
|
||||||
await mkdir(dir, { recursive: true })
|
await mkdir(dir, { recursive: true })
|
||||||
const files = (await readdir(dir)).filter((name) => name.endsWith('.sql.gz'))
|
const files = (await readdir(dir)).filter((name) => name.endsWith('.sql.gz'))
|
||||||
const rows = await Promise.all(files.map(async (name) => ({ Name: name.replace(/\.sql\.gz$/, ''), Created: (await stat(join(dir, name))).mtime.toISOString() })))
|
const rows = await Promise.all(
|
||||||
|
files.map(async (name) => ({
|
||||||
|
Name: name.replace(/\.sql\.gz$/, ''),
|
||||||
|
Created: (await stat(join(dir, name))).mtime.toISOString()
|
||||||
|
}))
|
||||||
|
)
|
||||||
return rows.sort((a, b) => b.Created.localeCompare(a.Created))
|
return rows.sort((a, b) => b.Created.localeCompare(a.Created))
|
||||||
}
|
}
|
||||||
|
|
||||||
export function registerDatabaseIpc(): void {
|
export function registerDatabaseIpc(): void {
|
||||||
ipcMain.handle('database:listSnapshots', (_event, _name: string, root: string) => listSnapshots(root))
|
ipcMain.handle('database:listSnapshots', (_event, _name: string, root: string) =>
|
||||||
ipcMain.handle('database:createSnapshot', async (_event, _operationId: string, root: string, name?: string) => {
|
listSnapshots(root)
|
||||||
const config = await getProjectConfigByRoot(root); await mkdir(snapshotsDir(root), { recursive: true })
|
)
|
||||||
await runDatabasePipe(root, config.database, 'dump', join(snapshotsDir(root), `${safeSnapshotName(name)}.sql.gz`))
|
ipcMain.handle(
|
||||||
})
|
'database:createSnapshot',
|
||||||
ipcMain.handle('database:restoreSnapshot', async (_event, _operationId: string, root: string, name: string) => {
|
async (_event, _operationId: string, root: string, name?: string) => {
|
||||||
const config = await getProjectConfigByRoot(root); await runDatabasePipe(root, config.database, 'restore', join(snapshotsDir(root), `${safeSnapshotName(name)}.sql.gz`))
|
await mkdir(snapshotsDir(root), { recursive: true })
|
||||||
})
|
await runDatabasePipe(
|
||||||
ipcMain.handle('database:deleteSnapshot', async (_event, _operationId: string, root: string, name: string) => rm(join(snapshotsDir(root), `${safeSnapshotName(name)}.sql.gz`), { force: true }))
|
root,
|
||||||
ipcMain.handle('database:importFile', async (_event, _operationId: string, root: string, filePath: string) => { const config = await getProjectConfigByRoot(root); await runDatabasePipe(root, config.database, 'restore', filePath) })
|
'dump',
|
||||||
ipcMain.handle('database:exportFile', async (_event, _operationId: string, root: string, filePath: string) => { const config = await getProjectConfigByRoot(root); await runDatabasePipe(root, config.database, 'dump', filePath) })
|
join(snapshotsDir(root), `${safeSnapshotName(name)}.sql.gz`)
|
||||||
ipcMain.handle('database:pickImportFile', async () => { const r=await dialog.showOpenDialog({properties:['openFile'],filters:[{name:'SQL dumps',extensions:['sql','gz']}]}); return r.canceled?null:r.filePaths[0] })
|
)
|
||||||
ipcMain.handle('database:pickExportPath', async (_e,name:string) => { const r=await dialog.showSaveDialog({defaultPath: basename(name).endsWith('.gz') ? name : `${name}.sql.gz`, filters:[{name:'Compressed SQL dump',extensions:['gz']},{name:'SQL dump',extensions:['sql']}]}); return r.canceled?null:r.filePath })
|
}
|
||||||
|
)
|
||||||
|
ipcMain.handle(
|
||||||
|
'database:restoreSnapshot',
|
||||||
|
async (_event, _operationId: string, root: string, name: string) => {
|
||||||
|
await runDatabasePipe(
|
||||||
|
root,
|
||||||
|
'restore',
|
||||||
|
join(snapshotsDir(root), `${safeSnapshotName(name)}.sql.gz`)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
ipcMain.handle(
|
||||||
|
'database:deleteSnapshot',
|
||||||
|
async (_event, _operationId: string, root: string, name: string) =>
|
||||||
|
rm(join(snapshotsDir(root), `${safeSnapshotName(name)}.sql.gz`), { force: true })
|
||||||
|
)
|
||||||
|
ipcMain.handle(
|
||||||
|
'database:importFile',
|
||||||
|
async (_event, _operationId: string, root: string, filePath: string) => {
|
||||||
|
await runDatabasePipe(root, 'restore', filePath)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
ipcMain.handle(
|
||||||
|
'database:exportFile',
|
||||||
|
async (_event, _operationId: string, root: string, filePath: string) => {
|
||||||
|
await runDatabasePipe(root, 'dump', filePath)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
ipcMain.handle('database:pickImportFile', async () => {
|
||||||
|
const r = await dialog.showOpenDialog({
|
||||||
|
properties: ['openFile'],
|
||||||
|
filters: [{ name: 'SQL dumps', extensions: ['sql', 'gz'] }]
|
||||||
|
})
|
||||||
|
return r.canceled ? null : r.filePaths[0]
|
||||||
|
})
|
||||||
|
ipcMain.handle('database:pickExportPath', async (_e, name: string) => {
|
||||||
|
const r = await dialog.showSaveDialog({
|
||||||
|
defaultPath: basename(name).endsWith('.gz') ? name : `${name}.sql.gz`,
|
||||||
|
filters: [
|
||||||
|
{ name: 'Compressed SQL dump', extensions: ['gz'] },
|
||||||
|
{ name: 'SQL dump', extensions: ['sql'] }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
return r.canceled ? null : r.filePath
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+41
-2
@@ -1,4 +1,43 @@
|
|||||||
import { ipcMain } from 'electron'
|
import { ipcMain } from 'electron'
|
||||||
import { getProjectRoot } from '../auroraEngine'
|
import { join } from 'path'
|
||||||
|
import { getProjectConfigByRoot, getProjectRoot } from '../auroraEngine'
|
||||||
import { startLogStream } from '../commandRunner'
|
import { startLogStream } from '../commandRunner'
|
||||||
export function registerLogsIpc():void{ipcMain.handle('logs:start',async(event,id:string,name:string,service:string)=>{const root=await getProjectRoot(name);startLogStream(id,'docker',['compose','-f',`${root}/.aurora/compose.yaml`,'logs','-f','--tail','200',service],event.sender,{cwd:root})})}
|
|
||||||
|
function nativeLogFiles(root: string, service: string): string[] {
|
||||||
|
const logs = join(root, '.aurora', 'native', 'logs')
|
||||||
|
const files: Record<string, string[]> = {
|
||||||
|
database: ['database-process.log', 'mariadb.log'],
|
||||||
|
php: ['php-process.log', 'php.log'],
|
||||||
|
web: ['web-process.log', 'nginx.log', 'nginx-access.log']
|
||||||
|
}
|
||||||
|
const selected = files[service]
|
||||||
|
if (!selected) throw new Error(`Unknown native service '${service}'.`)
|
||||||
|
return selected.map((file) => join(logs, file))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function registerLogsIpc(): void {
|
||||||
|
ipcMain.handle(
|
||||||
|
'logs:start',
|
||||||
|
async (event, operationId: string, name: string, service: string) => {
|
||||||
|
const root = await getProjectRoot(name)
|
||||||
|
const config = await getProjectConfigByRoot(root)
|
||||||
|
if (config.runtimeEngine === 'native') {
|
||||||
|
startLogStream(
|
||||||
|
operationId,
|
||||||
|
'tail',
|
||||||
|
['-n', '200', '-F', ...nativeLogFiles(root, service)],
|
||||||
|
event.sender,
|
||||||
|
{ cwd: root }
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
startLogStream(
|
||||||
|
operationId,
|
||||||
|
'docker',
|
||||||
|
['compose', '-f', `${root}/.aurora/compose.yaml`, 'logs', '-f', '--tail', '200', service],
|
||||||
|
event.sender,
|
||||||
|
{ cwd: root }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { execFile } from 'child_process'
|
import { execFile, spawn } from 'child_process'
|
||||||
import { createRequire } from 'module'
|
import { createRequire } from 'module'
|
||||||
import { mkdtemp, readFile, writeFile } from 'fs/promises'
|
import { mkdtemp, readFile, writeFile } from 'fs/promises'
|
||||||
import { tmpdir } from 'os'
|
import { tmpdir } from 'os'
|
||||||
@@ -22,6 +22,22 @@ const project = {
|
|||||||
}
|
}
|
||||||
const execFileAsync = promisify(execFile)
|
const execFileAsync = promisify(execFile)
|
||||||
|
|
||||||
|
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'] })
|
||||||
|
let stderr = ''
|
||||||
|
child.stderr.on('data', (chunk) => {
|
||||||
|
stderr += chunk.toString()
|
||||||
|
})
|
||||||
|
child.on('error', reject)
|
||||||
|
child.on('close', (code) => {
|
||||||
|
if (code === 0) resolve()
|
||||||
|
else reject(new Error(stderr.trim() || `${command} exited with code ${code}`))
|
||||||
|
})
|
||||||
|
child.stdin.end(input)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
describe('native project configuration', () => {
|
describe('native project configuration', () => {
|
||||||
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'))
|
||||||
@@ -119,6 +135,41 @@ it.runIf(Boolean(process.env.AURORA_NATIVE_WORDPRESS_SMOKE_ROOT))(
|
|||||||
const response = await fetch(`http://127.0.0.1:${definition.ports.http}`)
|
const response = await fetch(`http://127.0.0.1:${definition.ports.http}`)
|
||||||
expect(response.ok).toBe(true)
|
expect(response.ok).toBe(true)
|
||||||
expect(await response.text()).toContain('Aurora native smoke')
|
expect(await response.text()).toContain('Aurora native smoke')
|
||||||
|
|
||||||
|
const databaseArgs = [
|
||||||
|
'--host=127.0.0.1',
|
||||||
|
`--port=${definition.ports.database}`,
|
||||||
|
'--user=db',
|
||||||
|
'--password=db',
|
||||||
|
'db'
|
||||||
|
]
|
||||||
|
const dump = await execFileAsync(
|
||||||
|
join(runtime, 'bin', 'mariadb-dump'),
|
||||||
|
[...databaseArgs.slice(0, -1), '--single-transaction', 'db'],
|
||||||
|
{ cwd: root, env: process.env, maxBuffer: 32 * 1024 * 1024 }
|
||||||
|
)
|
||||||
|
await execFileAsync(
|
||||||
|
join(runtime, 'bin', 'mariadb'),
|
||||||
|
[
|
||||||
|
...databaseArgs,
|
||||||
|
'-e',
|
||||||
|
"UPDATE wp_options SET option_value='Changed' WHERE option_name='blogname'"
|
||||||
|
],
|
||||||
|
{ cwd: root, env: process.env }
|
||||||
|
)
|
||||||
|
await runWithInput(join(runtime, 'bin', 'mariadb'), databaseArgs, root, dump.stdout)
|
||||||
|
const restored = await execFileAsync(
|
||||||
|
join(runtime, 'bin', 'mariadb'),
|
||||||
|
[
|
||||||
|
...databaseArgs,
|
||||||
|
'--batch',
|
||||||
|
'--skip-column-names',
|
||||||
|
'-e',
|
||||||
|
"SELECT option_value FROM wp_options WHERE option_name='blogname'"
|
||||||
|
],
|
||||||
|
{ cwd: root, env: process.env }
|
||||||
|
)
|
||||||
|
expect(restored.stdout.trim()).toBe('Aurora native smoke')
|
||||||
} finally {
|
} finally {
|
||||||
await stopNativeProject(definition)
|
await stopNativeProject(definition)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user