Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
539a6b3fab | ||
|
|
e29133613a | ||
|
|
3d91e90b2f | ||
|
|
f00422bf08 | ||
|
|
8c5211a5ba | ||
|
|
58baf10a39 | ||
|
|
0503462d3d | ||
|
|
c6ead2c529 | ||
|
|
3a3b89f14a | ||
|
|
f9df4324b9 |
@@ -38,7 +38,7 @@ jobs:
|
|||||||
test -f dist/native-runtime/aurora-native-0.1.0-linux-x64.tar.gz
|
test -f dist/native-runtime/aurora-native-0.1.0-linux-x64.tar.gz
|
||||||
test -f dist/linux-unpacked/resources/native-runtime/aurora-native-0.1.0-linux-x64.tar.gz
|
test -f dist/linux-unpacked/resources/native-runtime/aurora-native-0.1.0-linux-x64.tar.gz
|
||||||
cmp dist/native-runtime/aurora-native-0.1.0-linux-x64.tar.gz dist/linux-unpacked/resources/native-runtime/aurora-native-0.1.0-linux-x64.tar.gz
|
cmp dist/native-runtime/aurora-native-0.1.0-linux-x64.tar.gz dist/linux-unpacked/resources/native-runtime/aurora-native-0.1.0-linux-x64.tar.gz
|
||||||
test -f dist/linux-unpacked/resources/module-catalog/wordpress-1.2.0.pac
|
test -f dist/linux-unpacked/resources/module-catalog/wordpress-1.2.2.pac
|
||||||
test -f dist/linux-unpacked/resources/module-catalog/drupal-1.0.0.pac
|
test -f dist/linux-unpacked/resources/module-catalog/drupal-1.0.0.pac
|
||||||
|
|
||||||
- name: Create checksums
|
- name: Create checksums
|
||||||
|
|||||||
@@ -76,10 +76,10 @@ jobs:
|
|||||||
|
|
||||||
- name: Verify embedded PAC catalog
|
- name: Verify embedded PAC catalog
|
||||||
run: |
|
run: |
|
||||||
test -f dist/module-catalog/wordpress-1.2.0.pac
|
test -f dist/module-catalog/wordpress-1.2.2.pac
|
||||||
test -f "dist/mac-universal/Aurora Dockside.app/Contents/Resources/module-catalog/wordpress-1.2.0.pac"
|
test -f "dist/mac-universal/Aurora Dockside.app/Contents/Resources/module-catalog/wordpress-1.2.2.pac"
|
||||||
cmp dist/module-catalog/wordpress-1.2.0.pac "dist/mac-universal/Aurora Dockside.app/Contents/Resources/module-catalog/wordpress-1.2.0.pac"
|
cmp dist/module-catalog/wordpress-1.2.2.pac "dist/mac-universal/Aurora Dockside.app/Contents/Resources/module-catalog/wordpress-1.2.2.pac"
|
||||||
unzip -t "dist/mac-universal/Aurora Dockside.app/Contents/Resources/module-catalog/wordpress-1.2.0.pac"
|
unzip -t "dist/mac-universal/Aurora Dockside.app/Contents/Resources/module-catalog/wordpress-1.2.2.pac"
|
||||||
|
|
||||||
- name: Create checksums
|
- name: Create checksums
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ jobs:
|
|||||||
- name: Build and verify Aurora Native for Windows
|
- name: Build and verify Aurora Native for Windows
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
run: |
|
run: |
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
$PSNativeCommandUseErrorActionPreference = $true
|
||||||
./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
|
$runtime = (Resolve-Path 'dist/native-runtime-stage/win32-x64').Path
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "aurora-dockside",
|
"name": "aurora-dockside",
|
||||||
"version": "2.0.0-alpha.31",
|
"version": "2.0.0-alpha.40",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "aurora-dockside",
|
"name": "aurora-dockside",
|
||||||
"version": "2.0.0-alpha.31",
|
"version": "2.0.0-alpha.40",
|
||||||
"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.31",
|
"version": "2.0.0-alpha.40",
|
||||||
"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,17 +38,29 @@ exports.projectCreate = async function projectCreate(context) {
|
|||||||
const s = context.settings
|
const s = context.settings
|
||||||
const title = String(s.title || '').trim() || context.projectName
|
const title = String(s.title || '').trim() || context.projectName
|
||||||
const native = context.environment.runtimeEngine === 'native'
|
const native = context.environment.runtimeEngine === 'native'
|
||||||
|
const nativePhpPrefix = native ? context.native.phpPrefixArgs || [] : []
|
||||||
const nativePrefix = native ? context.native.wpPrefixArgs || [] : []
|
const nativePrefix = native ? context.native.wpPrefixArgs || [] : []
|
||||||
const base = native ? [...nativePrefix, `--path=${context.directory}`] : wpArgs(context, false)
|
const base = native ? [...nativePrefix, `--path=${context.directory}`] : wpArgs(context, false)
|
||||||
const networkBase = native ? base : wpArgs(context, true)
|
const networkBase = native ? base : wpArgs(context, true)
|
||||||
const command = native ? context.native.wp : 'docker'
|
const command = native ? context.native.wp : 'docker'
|
||||||
const siteUrl = native ? context.urls.http : context.urls.https
|
const siteUrl = native ? context.urls.http : context.urls.https
|
||||||
const wp = (label, args) => context.run(label, command, [...networkBase, ...args])
|
const wp = (label, args) =>
|
||||||
|
context.run(
|
||||||
|
label,
|
||||||
|
command,
|
||||||
|
[...networkBase, ...args],
|
||||||
|
native ? context.native.commandEnvironment : undefined
|
||||||
|
)
|
||||||
if (native) {
|
if (native) {
|
||||||
await context.native.start()
|
await context.native.start()
|
||||||
const port = Number(context.native.databasePort)
|
const port = Number(context.native.databasePort)
|
||||||
const bootstrap = `$db=new mysqli('127.0.0.1','root','',null,${port});if($db->connect_error)throw new Exception($db->connect_error);$db->query('CREATE DATABASE IF NOT EXISTS db');$db->query("CREATE USER IF NOT EXISTS 'db'@'127.0.0.1' IDENTIFIED BY 'db'");$db->query("GRANT ALL ON db.* TO 'db'@'127.0.0.1'");`
|
const bootstrap = `$db=new mysqli('127.0.0.1','root','',null,${port});if($db->connect_error)throw new Exception($db->connect_error);$db->query('CREATE DATABASE IF NOT EXISTS db');$db->query("CREATE USER IF NOT EXISTS 'db'@'127.0.0.1' IDENTIFIED BY 'db'");$db->query("GRANT ALL ON db.* TO 'db'@'127.0.0.1'");`
|
||||||
await context.run('database', context.native.php, ['-r', bootstrap])
|
await context.run(
|
||||||
|
'database',
|
||||||
|
context.native.php,
|
||||||
|
[...nativePhpPrefix, '-r', bootstrap],
|
||||||
|
context.native.commandEnvironment
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
await context.ensureRouter()
|
await context.ensureRouter()
|
||||||
await context.run('start', 'docker', [
|
await context.run('start', 'docker', [
|
||||||
@@ -61,24 +73,40 @@ exports.projectCreate = async function projectCreate(context) {
|
|||||||
'--remove-orphans'
|
'--remove-orphans'
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
await context.run('download', command, [
|
if (native && nativePhpPrefix.length) {
|
||||||
...base,
|
const target = Buffer.from(context.directory, 'utf8').toString('base64')
|
||||||
'core',
|
const locale = encodeURIComponent(String(s.locale || 'en_US'))
|
||||||
'download',
|
const download = `$target=base64_decode('${target}');$work=$target.DIRECTORY_SEPARATOR.'.aurora'.DIRECTORY_SEPARATOR.'wordpress-download';$archive=$work.'.zip';@mkdir($work,0777,true);$api=json_decode(file_get_contents('https://api.wordpress.org/core/version-check/1.7/?locale=${locale}'),true);$offer=$api['offers'][0]??null;$url=$offer['packages']['full']??$offer['download']??null;if(!$url||!copy($url,$archive))throw new Exception('Unable to download WordPress.');$zip=new ZipArchive();if($zip->open($archive)!==true)throw new Exception('Unable to open WordPress archive.');if(!$zip->extractTo($work))throw new Exception('Unable to extract WordPress archive.');$zip->close();$source=$work.DIRECTORY_SEPARATOR.'wordpress';foreach(scandir($source) as $entry){if($entry==='.'||$entry==='..')continue;if(!rename($source.DIRECTORY_SEPARATOR.$entry,$target.DIRECTORY_SEPARATOR.$entry))throw new Exception('Unable to install WordPress file: '.$entry);}@rmdir($source);@rmdir($work);@unlink($archive);`
|
||||||
`--locale=${s.locale || 'en_US'}`,
|
await context.run(
|
||||||
'--force'
|
'download',
|
||||||
])
|
context.native.php,
|
||||||
await context.run('config', command, [
|
[...nativePhpPrefix, '-r', download],
|
||||||
...networkBase,
|
context.native.commandEnvironment
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
await context.run(
|
||||||
|
'download',
|
||||||
|
command,
|
||||||
|
[...base, 'core', 'download', `--locale=${s.locale || 'en_US'}`, '--force'],
|
||||||
|
native ? context.native.commandEnvironment : undefined
|
||||||
|
)
|
||||||
|
}
|
||||||
|
await context.run(
|
||||||
'config',
|
'config',
|
||||||
'create',
|
command,
|
||||||
'--dbname=db',
|
[
|
||||||
'--dbuser=db',
|
...networkBase,
|
||||||
'--dbpass=db',
|
'config',
|
||||||
`--dbhost=${native ? `127.0.0.1:${context.native.databasePort}` : 'db:3306'}`,
|
'create',
|
||||||
'--skip-check',
|
'--dbname=db',
|
||||||
'--force'
|
'--dbuser=db',
|
||||||
])
|
'--dbpass=db',
|
||||||
|
`--dbhost=${native ? `127.0.0.1:${context.native.databasePort}` : 'db:3306'}`,
|
||||||
|
'--skip-check',
|
||||||
|
'--force'
|
||||||
|
],
|
||||||
|
native ? context.native.commandEnvironment : undefined
|
||||||
|
)
|
||||||
await wp('install', [
|
await wp('install', [
|
||||||
'core',
|
'core',
|
||||||
'install',
|
'install',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"id": "wordpress",
|
"id": "wordpress",
|
||||||
"name": "WordPress",
|
"name": "WordPress",
|
||||||
"version": "1.2.0",
|
"version": "1.2.2",
|
||||||
"category": "application",
|
"category": "application",
|
||||||
"description": "WordPress CMS provisioning and WP-CLI tooling.",
|
"description": "WordPress CMS provisioning and WP-CLI tooling.",
|
||||||
"main": "main/index.cjs",
|
"main": "main/index.cjs",
|
||||||
@@ -12,15 +12,89 @@
|
|||||||
"settings": [],
|
"settings": [],
|
||||||
"creation": {
|
"creation": {
|
||||||
"databases": ["mariadb", "mysql"],
|
"databases": ["mariadb", "mysql"],
|
||||||
"intro": { "title": "WordPress install", "description": "Core downloads automatically and the project starts for setup.", "icon": "globe" },
|
"intro": {
|
||||||
|
"title": "WordPress install",
|
||||||
|
"description": "Core downloads automatically and the project starts for setup.",
|
||||||
|
"icon": "globe"
|
||||||
|
},
|
||||||
"setup": [
|
"setup": [
|
||||||
{ "id": "title", "label": "Site title", "type": "text", "default": "", "placeholder": "My WordPress Site", "icon": "title", "span": 2 },
|
{
|
||||||
{ "id": "admin_user", "label": "Admin username", "type": "text", "default": "admin", "required": true, "icon": "user" },
|
"id": "title",
|
||||||
{ "id": "admin_password", "label": "Admin password", "type": "text", "default": "", "required": true, "secret": true, "icon": "key" },
|
"label": "Site title",
|
||||||
{ "id": "admin_email", "label": "Admin email", "type": "text", "default": "", "required": true, "placeholder": "[email protected]", "icon": "mail", "span": 2 },
|
"type": "text",
|
||||||
{ "id": "locale", "label": "Select language", "type": "select", "default": "en_US", "advanced": true, "icon": "globe", "options": ["en_US", "en_GB", "de_DE", "es_ES", "fr_FR", "it_IT", "pt_BR", "nl_NL", "ja"], "optionLabels": { "en_US": "English (United States)", "en_GB": "English (UK)", "de_DE": "German", "es_ES": "Spanish (Spain)", "fr_FR": "French (France)", "it_IT": "Italian", "pt_BR": "Portuguese (Brazil)", "nl_NL": "Dutch", "ja": "Japanese" } },
|
"default": "",
|
||||||
{ "id": "multisite", "label": "Is this a WordPress Multisite?", "type": "select", "default": "none", "advanced": true, "icon": "settings", "options": ["none", "subdirectory", "subdomain"], "optionLabels": { "none": "No", "subdirectory": "Yes – Subdirectory", "subdomain": "Yes – Subdomain" } },
|
"placeholder": "My WordPress Site",
|
||||||
{ "id": "wp_debug", "label": "Enable WP_DEBUG", "type": "boolean", "default": true, "advanced": true }
|
"icon": "title",
|
||||||
|
"span": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "admin_user",
|
||||||
|
"label": "Admin username",
|
||||||
|
"type": "text",
|
||||||
|
"default": "admin",
|
||||||
|
"required": true,
|
||||||
|
"icon": "user"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "admin_password",
|
||||||
|
"label": "Admin password",
|
||||||
|
"type": "text",
|
||||||
|
"default": "",
|
||||||
|
"required": true,
|
||||||
|
"secret": true,
|
||||||
|
"icon": "key"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "admin_email",
|
||||||
|
"label": "Admin email",
|
||||||
|
"type": "text",
|
||||||
|
"default": "",
|
||||||
|
"required": true,
|
||||||
|
"placeholder": "[email protected]",
|
||||||
|
"icon": "mail",
|
||||||
|
"span": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "locale",
|
||||||
|
"label": "Select language",
|
||||||
|
"type": "select",
|
||||||
|
"default": "en_US",
|
||||||
|
"advanced": true,
|
||||||
|
"icon": "globe",
|
||||||
|
"options": ["en_US", "en_GB", "de_DE", "es_ES", "fr_FR", "it_IT", "pt_BR", "nl_NL", "ja"],
|
||||||
|
"optionLabels": {
|
||||||
|
"en_US": "English (United States)",
|
||||||
|
"en_GB": "English (UK)",
|
||||||
|
"de_DE": "German",
|
||||||
|
"es_ES": "Spanish (Spain)",
|
||||||
|
"fr_FR": "French (France)",
|
||||||
|
"it_IT": "Italian",
|
||||||
|
"pt_BR": "Portuguese (Brazil)",
|
||||||
|
"nl_NL": "Dutch",
|
||||||
|
"ja": "Japanese"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "multisite",
|
||||||
|
"label": "Is this a WordPress Multisite?",
|
||||||
|
"type": "select",
|
||||||
|
"default": "none",
|
||||||
|
"advanced": true,
|
||||||
|
"icon": "settings",
|
||||||
|
"options": ["none", "subdirectory", "subdomain"],
|
||||||
|
"optionLabels": {
|
||||||
|
"none": "No",
|
||||||
|
"subdirectory": "Yes – Subdirectory",
|
||||||
|
"subdomain": "Yes – Subdomain"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "wp_debug",
|
||||||
|
"label": "Enable WP_DEBUG",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"advanced": true
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"project": {
|
"project": {
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "@aurora/module-wordpress",
|
"name": "@aurora/module-wordpress",
|
||||||
"version": "1.2.0",
|
"version": "1.2.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "WordPress application module for Aurora Dockside",
|
"description": "WordPress application module for Aurora Dockside",
|
||||||
"files": ["manifest.json", "main", "README.md"]
|
"files": [
|
||||||
|
"manifest.json",
|
||||||
|
"main",
|
||||||
|
"README.md"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,19 @@ $mariaParent = Join-Path $stagePath "bin/mariadb-unpack"
|
|||||||
Expand-Archive $phpZip $phpRoot -Force
|
Expand-Archive $phpZip $phpRoot -Force
|
||||||
Expand-Archive $nginxZip $nginxParent -Force
|
Expand-Archive $nginxZip $nginxParent -Force
|
||||||
Expand-Archive $mariaZip $mariaParent -Force
|
Expand-Archive $mariaZip $mariaParent -Force
|
||||||
|
|
||||||
|
@'
|
||||||
|
extension_dir = "ext"
|
||||||
|
extension = mysqli
|
||||||
|
extension = pdo_mysql
|
||||||
|
extension = mbstring
|
||||||
|
extension = curl
|
||||||
|
extension = openssl
|
||||||
|
extension = zip
|
||||||
|
display_errors = On
|
||||||
|
log_errors = On
|
||||||
|
'@ | Set-Content (Join-Path $phpRoot "php.ini")
|
||||||
|
|
||||||
Move-Item (Join-Path $nginxParent "nginx-$NginxVersion") (Join-Path $stagePath "bin/nginx")
|
Move-Item (Join-Path $nginxParent "nginx-$NginxVersion") (Join-Path $stagePath "bin/nginx")
|
||||||
Move-Item (Join-Path $mariaParent "mariadb-$MariaDbVersion-winx64") (Join-Path $stagePath "bin/mariadb")
|
Move-Item (Join-Path $mariaParent "mariadb-$MariaDbVersion-winx64") (Join-Path $stagePath "bin/mariadb")
|
||||||
Remove-Item $nginxParent, $mariaParent -Recurse -Force
|
Remove-Item $nginxParent, $mariaParent -Recurse -Force
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { app } from 'electron'
|
|||||||
import { execFile } from 'child_process'
|
import { execFile } from 'child_process'
|
||||||
import { promisify } from 'util'
|
import { promisify } from 'util'
|
||||||
import { mkdir, readFile, writeFile, access, rm, readdir } from 'fs/promises'
|
import { mkdir, readFile, writeFile, access, rm, readdir } from 'fs/promises'
|
||||||
import { join } from 'path'
|
import { delimiter, join } from 'path'
|
||||||
import type {
|
import type {
|
||||||
AuroraProjectDetail,
|
AuroraProjectDetail,
|
||||||
AuroraProjectSummary,
|
AuroraProjectSummary,
|
||||||
@@ -28,8 +28,14 @@ import {
|
|||||||
} from './moduleRegistry'
|
} from './moduleRegistry'
|
||||||
|
|
||||||
const execFileAsync = promisify(execFile)
|
const execFileAsync = promisify(execFile)
|
||||||
const EXTRA_PATH_DIRS = ['/opt/homebrew/bin', '/usr/local/bin', '/opt/local/bin']
|
const EXTRA_PATH_DIRS =
|
||||||
export const AURORA_ENV = { ...process.env, PATH: [...EXTRA_PATH_DIRS, process.env.PATH].join(':') }
|
process.platform === 'win32'
|
||||||
|
? [process.env.SystemRoot ? join(process.env.SystemRoot, 'System32') : '']
|
||||||
|
: ['/opt/homebrew/bin', '/usr/local/bin', '/opt/local/bin']
|
||||||
|
export const AURORA_ENV = {
|
||||||
|
...process.env,
|
||||||
|
PATH: [...EXTRA_PATH_DIRS, process.env.PATH].filter(Boolean).join(delimiter)
|
||||||
|
}
|
||||||
|
|
||||||
export type AuroraConfig = {
|
export type AuroraConfig = {
|
||||||
name: string
|
name: string
|
||||||
|
|||||||
@@ -1,11 +1,88 @@
|
|||||||
import { spawn, type ChildProcess } from 'child_process'
|
import { spawn, type ChildProcess } from 'child_process'
|
||||||
import type { WebContents } from 'electron'
|
import type { WebContents } from 'electron'
|
||||||
import { AURORA_ENV, powerOffProjects } from './auroraEngine'
|
import { AURORA_ENV, powerOffProjects } from './auroraEngine'
|
||||||
const running = new Map<string, ChildProcess>(); const cancelledIds = new Set<string>()
|
const running = new Map<string, ChildProcess>()
|
||||||
const ANSI_PATTERN = /[\u001B\u009B][[\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\d/#&.:=?%@~_]+)*|[a-zA-Z\d]+(?:;[-a-zA-Z\d/#&.:=?%@~_]*)*)?\u0007)|(?:(?:\d{1,4}(?:;\d{0,4})*)?[\dA-PR-TZcf-ntqry=><~]))/g
|
const cancelledIds = new Set<string>()
|
||||||
const stripAnsi=(s:string)=>s.replace(ANSI_PATTERN,'')
|
const ANSI_PATTERN =
|
||||||
export function runCommandStreamed(operationId:string, command:string, args:string[], sender:WebContents, options:{cwd?:string;emitExit?:boolean}={}):Promise<void>{return new Promise((resolve,reject)=>{const child=spawn(command,args,{env:AURORA_ENV,cwd:options.cwd,stdio:['ignore','pipe','pipe']});running.set(operationId,child);const tail:string[]=[];const forward=(stream:'stdout'|'stderr')=>(data:Buffer)=>{const chunk=stripAnsi(data.toString());tail.push(chunk);if(tail.length>20)tail.shift();if(!sender.isDestroyed())sender.send('terminal:data',{operationId,stream,chunk})};child.stdout.on('data',forward('stdout'));child.stderr.on('data',forward('stderr'));child.on('error',e=>{running.delete(operationId);if(options.emitExit!==false&&!sender.isDestroyed())sender.send('terminal:exit',{operationId,exitCode:null,cancelled:false});reject(e)});child.on('close',code=>{running.delete(operationId);const cancelled=cancelledIds.delete(operationId);if(options.emitExit!==false&&!sender.isDestroyed())sender.send('terminal:exit',{operationId,exitCode:code,cancelled});if(cancelled)reject(new Error('Command cancelled'));else if(code===0)resolve();else reject(new Error(tail.join('').trim()||`${command} exited with code ${code}`))})})}
|
/[\u001B\u009B][[\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\d/#&.:=?%@~_]+)*|[a-zA-Z\d]+(?:;[-a-zA-Z\d/#&.:=?%@~_]*)*)?\u0007)|(?:(?:\d{1,4}(?:;\d{0,4})*)?[\dA-PR-TZcf-ntqry=><~]))/g
|
||||||
export function cancelCommand(id:string):boolean{const c=running.get(id);if(!c)return false;cancelledIds.add(id);c.kill();return true}
|
const stripAnsi = (s: string) => s.replace(ANSI_PATTERN, '')
|
||||||
export function startLogStream(operationId:string, command:string,args:string[],sender:WebContents,options:{cwd?:string}={}):void{const child=spawn(command,args,{env:AURORA_ENV,cwd:options.cwd,stdio:['ignore','pipe','pipe']});running.set(operationId,child);const f=(stream:'stdout'|'stderr')=>(d:Buffer)=>{if(!sender.isDestroyed())sender.send('logs:data',{operationId,stream,chunk:stripAnsi(d.toString())})};child.stdout.on('data',f('stdout'));child.stderr.on('data',f('stderr'));const done=()=>{running.delete(operationId);if(!sender.isDestroyed())sender.send('logs:exit',{operationId})};child.on('close',done);child.on('error',done)}
|
export function runCommandStreamed(
|
||||||
export function killAllRunningCommands():void{for(const c of running.values())c.kill();running.clear()}
|
operationId: string,
|
||||||
export async function powerOffAllProjects():Promise<void>{await powerOffProjects()}
|
command: string,
|
||||||
|
args: string[],
|
||||||
|
sender: WebContents,
|
||||||
|
options: { cwd?: string; emitExit?: boolean; env?: NodeJS.ProcessEnv } = {}
|
||||||
|
): Promise<void> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const child = spawn(command, args, {
|
||||||
|
env: { ...AURORA_ENV, ...options.env },
|
||||||
|
cwd: options.cwd,
|
||||||
|
stdio: ['ignore', 'pipe', 'pipe']
|
||||||
|
})
|
||||||
|
running.set(operationId, child)
|
||||||
|
const tail: string[] = []
|
||||||
|
const forward = (stream: 'stdout' | 'stderr') => (data: Buffer) => {
|
||||||
|
const chunk = stripAnsi(data.toString())
|
||||||
|
tail.push(chunk)
|
||||||
|
if (tail.length > 20) tail.shift()
|
||||||
|
if (!sender.isDestroyed()) sender.send('terminal:data', { operationId, stream, chunk })
|
||||||
|
}
|
||||||
|
child.stdout.on('data', forward('stdout'))
|
||||||
|
child.stderr.on('data', forward('stderr'))
|
||||||
|
child.on('error', (e) => {
|
||||||
|
running.delete(operationId)
|
||||||
|
if (options.emitExit !== false && !sender.isDestroyed())
|
||||||
|
sender.send('terminal:exit', { operationId, exitCode: null, cancelled: false })
|
||||||
|
reject(e)
|
||||||
|
})
|
||||||
|
child.on('close', (code) => {
|
||||||
|
running.delete(operationId)
|
||||||
|
const cancelled = cancelledIds.delete(operationId)
|
||||||
|
if (options.emitExit !== false && !sender.isDestroyed())
|
||||||
|
sender.send('terminal:exit', { operationId, exitCode: code, cancelled })
|
||||||
|
if (cancelled) reject(new Error('Command cancelled'))
|
||||||
|
else if (code === 0) resolve()
|
||||||
|
else reject(new Error(tail.join('').trim() || `${command} exited with code ${code}`))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
export function cancelCommand(id: string): boolean {
|
||||||
|
const c = running.get(id)
|
||||||
|
if (!c) return false
|
||||||
|
cancelledIds.add(id)
|
||||||
|
c.kill()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
export function startLogStream(
|
||||||
|
operationId: string,
|
||||||
|
command: string,
|
||||||
|
args: string[],
|
||||||
|
sender: WebContents,
|
||||||
|
options: { cwd?: string } = {}
|
||||||
|
): void {
|
||||||
|
const child = spawn(command, args, {
|
||||||
|
env: AURORA_ENV,
|
||||||
|
cwd: options.cwd,
|
||||||
|
stdio: ['ignore', 'pipe', 'pipe']
|
||||||
|
})
|
||||||
|
running.set(operationId, child)
|
||||||
|
const f = (stream: 'stdout' | 'stderr') => (d: Buffer) => {
|
||||||
|
if (!sender.isDestroyed())
|
||||||
|
sender.send('logs:data', { operationId, stream, chunk: stripAnsi(d.toString()) })
|
||||||
|
}
|
||||||
|
child.stdout.on('data', f('stdout'))
|
||||||
|
child.stderr.on('data', f('stderr'))
|
||||||
|
const done = () => {
|
||||||
|
running.delete(operationId)
|
||||||
|
if (!sender.isDestroyed()) sender.send('logs:exit', { operationId })
|
||||||
|
}
|
||||||
|
child.on('close', done)
|
||||||
|
child.on('error', done)
|
||||||
|
}
|
||||||
|
export function killAllRunningCommands(): void {
|
||||||
|
for (const c of running.values()) c.kill()
|
||||||
|
running.clear()
|
||||||
|
}
|
||||||
|
export async function powerOffAllProjects(): Promise<void> {
|
||||||
|
await powerOffProjects()
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { execFile } from 'child_process'
|
import { execFile } from 'child_process'
|
||||||
import { createRequire } from 'module'
|
import { createRequire } from 'module'
|
||||||
|
import { mkdir } from 'fs/promises'
|
||||||
import { promisify } from 'util'
|
import { promisify } from 'util'
|
||||||
import { dirname, join, resolve, sep } from 'path'
|
import { dirname, join, resolve, sep } from 'path'
|
||||||
import type { WebContents } from 'electron'
|
import type { WebContents } from 'electron'
|
||||||
@@ -78,7 +79,18 @@ export async function runModuleLifecycleHook(
|
|||||||
: options.projectName
|
: options.projectName
|
||||||
? projectUrls(options.projectName)
|
? projectUrls(options.projectName)
|
||||||
: undefined
|
: undefined
|
||||||
const run = async (label: string, command: string, args: string[]): Promise<void> => {
|
const nativeTemp =
|
||||||
|
nativeDefinition && process.platform === 'win32' ? join(runtimeRoot(), 'tmp') : undefined
|
||||||
|
if (nativeTemp) await mkdir(nativeTemp, { recursive: true })
|
||||||
|
const nativeCommandEnv = nativeTemp
|
||||||
|
? { TEMP: nativeTemp, TMP: nativeTemp, WP_CLI_CACHE_DIR: join(nativeTemp, 'wp-cli-cache') }
|
||||||
|
: undefined
|
||||||
|
const run = async (
|
||||||
|
label: string,
|
||||||
|
command: string,
|
||||||
|
args: string[],
|
||||||
|
env?: NodeJS.ProcessEnv
|
||||||
|
): Promise<void> => {
|
||||||
if (options.sender && options.operationId) {
|
if (options.sender && options.operationId) {
|
||||||
if (!options.sender.isDestroyed())
|
if (!options.sender.isDestroyed())
|
||||||
options.sender.send('terminal:data', {
|
options.sender.send('terminal:data', {
|
||||||
@@ -88,12 +100,13 @@ export async function runModuleLifecycleHook(
|
|||||||
})
|
})
|
||||||
return runCommandStreamed(options.operationId, command, args, options.sender, {
|
return runCommandStreamed(options.operationId, command, args, options.sender, {
|
||||||
cwd: directory ?? resolve(moduleDirectory(), moduleId),
|
cwd: directory ?? resolve(moduleDirectory(), moduleId),
|
||||||
emitExit: false
|
emitExit: false,
|
||||||
|
env
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
await execFileAsync(command, args, {
|
await execFileAsync(command, args, {
|
||||||
cwd: directory ?? resolve(moduleDirectory(), moduleId),
|
cwd: directory ?? resolve(moduleDirectory(), moduleId),
|
||||||
env: AURORA_ENV
|
env: { ...AURORA_ENV, ...env }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
await handler(
|
await handler(
|
||||||
@@ -118,6 +131,11 @@ export async function runModuleLifecycleHook(
|
|||||||
urls: urls ? Object.freeze(urls) : undefined,
|
urls: urls ? Object.freeze(urls) : undefined,
|
||||||
native: nativeDefinition
|
native: nativeDefinition
|
||||||
? Object.freeze({
|
? Object.freeze({
|
||||||
|
phpPrefixArgs:
|
||||||
|
process.platform === 'win32'
|
||||||
|
? ['-c', join(directory!, '.aurora', 'native', 'config', 'php.ini')]
|
||||||
|
: [],
|
||||||
|
commandEnvironment: nativeCommandEnv,
|
||||||
php:
|
php:
|
||||||
process.platform === 'win32'
|
process.platform === 'win32'
|
||||||
? join(runtimeRoot(), 'bin', 'php', 'php.exe')
|
? join(runtimeRoot(), 'bin', 'php', 'php.exe')
|
||||||
@@ -128,7 +146,13 @@ export async function runModuleLifecycleHook(
|
|||||||
: join(runtimeRoot(), 'bin', 'wp'),
|
: join(runtimeRoot(), 'bin', 'wp'),
|
||||||
wpPrefixArgs:
|
wpPrefixArgs:
|
||||||
process.platform === 'win32'
|
process.platform === 'win32'
|
||||||
? ['-d', 'memory_limit=512M', join(runtimeRoot(), 'tools', 'wp-cli.phar')]
|
? [
|
||||||
|
'-c',
|
||||||
|
join(directory!, '.aurora', 'native', 'config', 'php.ini'),
|
||||||
|
'-d',
|
||||||
|
'memory_limit=512M',
|
||||||
|
join(runtimeRoot(), 'tools', 'wp-cli.phar')
|
||||||
|
]
|
||||||
: [],
|
: [],
|
||||||
databaseHost: '127.0.0.1',
|
databaseHost: '127.0.0.1',
|
||||||
databasePort: nativeDefinition.ports.database,
|
databasePort: nativeDefinition.ports.database,
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import { execFile, spawn } 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 { mkdir, mkdtemp, readFile, writeFile } from 'fs/promises'
|
||||||
import { tmpdir } from 'os'
|
import { tmpdir } from 'os'
|
||||||
import { join, resolve } from 'path'
|
import { dirname, join, resolve } from 'path'
|
||||||
import { promisify } from 'util'
|
import { promisify } from 'util'
|
||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it } from 'vitest'
|
||||||
import {
|
import {
|
||||||
renderMariaDbConfig,
|
renderMariaDbConfig,
|
||||||
nativeConfigPath,
|
nativeConfigPath,
|
||||||
|
nativeServiceSpecs,
|
||||||
renderNativeAdminerBootstrap,
|
renderNativeAdminerBootstrap,
|
||||||
renderNginxConfig,
|
renderNginxConfig,
|
||||||
renderPhpFpmConfig,
|
renderPhpFpmConfig,
|
||||||
@@ -15,6 +16,7 @@ import {
|
|||||||
stopNativeProject
|
stopNativeProject
|
||||||
} from './nativeProject'
|
} from './nativeProject'
|
||||||
import { allocateNativePorts } from './portAllocator'
|
import { allocateNativePorts } from './portAllocator'
|
||||||
|
import { AURORA_ENV } from '../auroraEngine'
|
||||||
|
|
||||||
const projectRoot = join(tmpdir(), 'aurora demo')
|
const projectRoot = join(tmpdir(), 'aurora demo')
|
||||||
const installedRuntimeRoot = join(tmpdir(), 'aurora-runtime')
|
const installedRuntimeRoot = join(tmpdir(), 'aurora-runtime')
|
||||||
@@ -61,13 +63,26 @@ describe('native project configuration', () => {
|
|||||||
expect(config).toContain('listen 127.0.0.1:41001')
|
expect(config).toContain('listen 127.0.0.1:41001')
|
||||||
expect(config).toContain('fastcgi_pass 127.0.0.1:41002')
|
expect(config).toContain('fastcgi_pass 127.0.0.1:41002')
|
||||||
expect(config).toContain('location ~ \\.php$')
|
expect(config).toContain('location ~ \\.php$')
|
||||||
expect(config).toContain(resolve(projectRoot, 'public').replace(/\\/g, '\\\\'))
|
expect(config).toContain(nativeConfigPath(resolve(projectRoot, 'public')))
|
||||||
|
})
|
||||||
|
it('launches nginx with a project-relative config path', () => {
|
||||||
|
const web = nativeServiceSpecs({ ...project, installedRuntimeRoot }).find((service) =>
|
||||||
|
service.id.endsWith(':web')
|
||||||
|
)
|
||||||
|
expect(web?.args).toEqual([
|
||||||
|
'-p',
|
||||||
|
`${nativeConfigPath(join(projectRoot, '.aurora', 'native'))}/`,
|
||||||
|
'-c',
|
||||||
|
'config/nginx.conf'
|
||||||
|
])
|
||||||
})
|
})
|
||||||
it('isolates MariaDB data and networking', () => {
|
it('isolates MariaDB data and networking', () => {
|
||||||
const config = renderMariaDbConfig(project, installedRuntimeRoot)
|
const config = renderMariaDbConfig(project, installedRuntimeRoot)
|
||||||
expect(config).toContain('bind-address=127.0.0.1')
|
expect(config).toContain('bind-address=127.0.0.1')
|
||||||
expect(config).toContain('port=41003')
|
expect(config).toContain('port=41003')
|
||||||
expect(config).toContain(join(projectRoot, '.aurora', 'native', 'data', 'mariadb'))
|
expect(config).toContain(
|
||||||
|
nativeConfigPath(join(projectRoot, '.aurora', 'native', 'data', 'mariadb'))
|
||||||
|
)
|
||||||
})
|
})
|
||||||
it('creates a loopback Adminer endpoint with project database credentials', () => {
|
it('creates a loopback Adminer endpoint with project database credentials', () => {
|
||||||
expect(renderNginxConfig(project)).toContain('location = /__aurora/adminer/')
|
expect(renderNginxConfig(project)).toContain('location = /__aurora/adminer/')
|
||||||
@@ -88,14 +103,15 @@ describe('native project configuration', () => {
|
|||||||
it.runIf(Boolean(process.env.AURORA_NATIVE_SMOKE_ROOT))(
|
it.runIf(Boolean(process.env.AURORA_NATIVE_SMOKE_ROOT))(
|
||||||
'serves PHP through the complete native stack',
|
'serves PHP through the complete native stack',
|
||||||
async () => {
|
async () => {
|
||||||
const root = await mkdtemp(join(tmpdir(), 'aurora-native-project-'))
|
const runtime = process.env.AURORA_NATIVE_SMOKE_ROOT!
|
||||||
|
const root = await mkdtemp(join(dirname(runtime), 'aurora-native-project-'))
|
||||||
await writeFile(join(root, 'index.php'), '<?php echo "aurora-native-ok";')
|
await writeFile(join(root, 'index.php'), '<?php echo "aurora-native-ok";')
|
||||||
const definition = {
|
const definition = {
|
||||||
name: `smoke-${Date.now()}`,
|
name: `smoke-${Date.now()}`,
|
||||||
root,
|
root,
|
||||||
docroot: '',
|
docroot: '',
|
||||||
ports: await allocateNativePorts(),
|
ports: await allocateNativePorts(),
|
||||||
installedRuntimeRoot: process.env.AURORA_NATIVE_SMOKE_ROOT
|
installedRuntimeRoot: runtime
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await startNativeProject(definition)
|
await startNativeProject(definition)
|
||||||
@@ -112,7 +128,9 @@ it.runIf(Boolean(process.env.AURORA_NATIVE_WORDPRESS_SMOKE_ROOT))(
|
|||||||
'provisions WordPress with the bundled native runtime',
|
'provisions WordPress with the bundled native runtime',
|
||||||
async () => {
|
async () => {
|
||||||
const runtime = process.env.AURORA_NATIVE_WORDPRESS_SMOKE_ROOT!
|
const runtime = process.env.AURORA_NATIVE_WORDPRESS_SMOKE_ROOT!
|
||||||
const root = await mkdtemp(join(tmpdir(), 'aurora-native-wordpress-'))
|
const root = await mkdtemp(join(dirname(runtime), 'aurora-native-wordpress-'))
|
||||||
|
const commandTemp = join(dirname(runtime), 'wp-cli-temp')
|
||||||
|
await mkdir(commandTemp, { recursive: true })
|
||||||
const definition = {
|
const definition = {
|
||||||
name: `wordpress-${Date.now()}`,
|
name: `wordpress-${Date.now()}`,
|
||||||
root,
|
root,
|
||||||
@@ -143,6 +161,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: {
|
||||||
|
commandEnvironment: {
|
||||||
|
TEMP: commandTemp,
|
||||||
|
TMP: commandTemp,
|
||||||
|
WP_CLI_CACHE_DIR: join(commandTemp, 'cache')
|
||||||
|
},
|
||||||
|
phpPrefixArgs:
|
||||||
|
process.platform === 'win32'
|
||||||
|
? ['-c', join(root, '.aurora', 'native', 'config', 'php.ini')]
|
||||||
|
: [],
|
||||||
php: runtimeCommand(runtime, 'php'),
|
php: runtimeCommand(runtime, 'php'),
|
||||||
wp:
|
wp:
|
||||||
process.platform === 'win32'
|
process.platform === 'win32'
|
||||||
@@ -150,15 +177,21 @@ it.runIf(Boolean(process.env.AURORA_NATIVE_WORDPRESS_SMOKE_ROOT))(
|
|||||||
: join(runtime, 'bin', 'wp'),
|
: join(runtime, 'bin', 'wp'),
|
||||||
wpPrefixArgs:
|
wpPrefixArgs:
|
||||||
process.platform === 'win32'
|
process.platform === 'win32'
|
||||||
? ['-d', 'memory_limit=512M', join(runtime, 'tools', 'wp-cli.phar')]
|
? [
|
||||||
|
'-c',
|
||||||
|
join(root, '.aurora', 'native', 'config', 'php.ini'),
|
||||||
|
'-d',
|
||||||
|
'memory_limit=512M',
|
||||||
|
join(runtime, 'tools', 'wp-cli.phar')
|
||||||
|
]
|
||||||
: [],
|
: [],
|
||||||
databasePort: definition.ports.database,
|
databasePort: definition.ports.database,
|
||||||
start: () => startNativeProject(definition)
|
start: () => startNativeProject(definition)
|
||||||
},
|
},
|
||||||
run: async (_label: string, command: string, args: string[]) => {
|
run: async (_label: string, command: string, args: string[], env?: NodeJS.ProcessEnv) => {
|
||||||
await execFileAsync(command, args, {
|
await execFileAsync(command, args, {
|
||||||
cwd: root,
|
cwd: root,
|
||||||
env: process.env,
|
env: { ...AURORA_ENV, ...env },
|
||||||
maxBuffer: 32 * 1024 * 1024
|
maxBuffer: 32 * 1024 * 1024
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -258,10 +258,10 @@ export function nativeServiceSpecs(project: NativeProjectDefinition): NativeServ
|
|||||||
...common('web'),
|
...common('web'),
|
||||||
command: runtimeExecutable(project, 'nginx'),
|
command: runtimeExecutable(project, 'nginx'),
|
||||||
args: [
|
args: [
|
||||||
'-c',
|
|
||||||
nativeConfigPath(join(directory, 'config', 'nginx.conf')),
|
|
||||||
'-p',
|
'-p',
|
||||||
`${nativeConfigPath(directory)}/`
|
`${nativeConfigPath(directory)}/`,
|
||||||
|
'-c',
|
||||||
|
'config/nginx.conf'
|
||||||
],
|
],
|
||||||
ready: { port: project.ports.http }
|
ready: { port: project.ports.http }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ export async function installNativeRuntimeArchive(source: string): Promise<Auror
|
|||||||
await mkdir(runtimes, { recursive: true })
|
await mkdir(runtimes, { recursive: true })
|
||||||
const staging = await mkdtemp(join(runtimes, '.install-'))
|
const staging = await mkdtemp(join(runtimes, '.install-'))
|
||||||
const target = runtimeRoot()
|
const target = runtimeRoot()
|
||||||
const backup = `${target}.previous`
|
const backup = `${target}.previous-${Date.now()}`
|
||||||
try {
|
try {
|
||||||
const { stdout } = await execFileAsync('tar', ['-tzf', source], { maxBuffer: 16 * 1024 * 1024 })
|
const { stdout } = await execFileAsync('tar', ['-tzf', source], { maxBuffer: 16 * 1024 * 1024 })
|
||||||
validateArchiveEntries(stdout)
|
validateArchiveEntries(stdout)
|
||||||
@@ -148,15 +148,20 @@ export async function installNativeRuntimeArchive(source: string): Promise<Auror
|
|||||||
)
|
)
|
||||||
await rejectLinks(staging)
|
await rejectLinks(staging)
|
||||||
await verifyRuntimeAt(staging)
|
await verifyRuntimeAt(staging)
|
||||||
await rm(backup, { recursive: true, force: true })
|
|
||||||
try {
|
try {
|
||||||
await rename(target, backup)
|
await rename(target, backup)
|
||||||
} catch {
|
} catch (error) {
|
||||||
/* first installation */
|
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await rename(staging, target)
|
await rename(staging, target)
|
||||||
await rm(backup, { recursive: true, force: true })
|
try {
|
||||||
|
await rm(backup, { recursive: true, force: true })
|
||||||
|
} catch (error) {
|
||||||
|
// Windows keeps running executables locked. The uniquely named rollback folder can be
|
||||||
|
// removed after those old processes exit and must never block the new runtime install.
|
||||||
|
console.warn('Previous Aurora Native runtime is still in use; cleanup was deferred:', error)
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
try {
|
try {
|
||||||
await rename(backup, target)
|
await rename(backup, target)
|
||||||
|
|||||||
Reference in New Issue
Block a user