feat: implement CLI environment management services and add internationalization support
This commit is contained in:
@@ -13,6 +13,7 @@ import { portMappingService } from '../services/PortMappingService'
|
||||
import path from 'path'
|
||||
import { mariaDbOperationService } from '../services/MariaDbOperationService'
|
||||
import { cliAliasService } from '../services/CliAliasService'
|
||||
import { cliBinService } from '../services/CliBinService'
|
||||
|
||||
function findExecutable(dir: string, name: string): string | null {
|
||||
if (!fs.existsSync(dir)) return null
|
||||
@@ -1041,4 +1042,32 @@ export function registerIpcHandlers(): void {
|
||||
rootPass: rootPass || ''
|
||||
}, dbName, savePath, compress, (msg) => log(msg))
|
||||
})
|
||||
|
||||
// CLI & Environment
|
||||
ipcMain.handle('cli:get-config', async () => {
|
||||
const settings = configService.getSettings()
|
||||
return {
|
||||
enabled: settings.cliEnvEnabled !== false,
|
||||
defaultPhp: settings.defaultPhpVersion,
|
||||
defaultMariaDb: settings.defaultMariaDbVersion,
|
||||
binDir: cliBinService.getBinDir()
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.handle('cli:set-config', async (_event, config: any) => {
|
||||
const settings = configService.getSettings()
|
||||
configService.updateSettings({
|
||||
...settings,
|
||||
cliEnvEnabled: config.enabled,
|
||||
defaultPhpVersion: config.defaultPhp,
|
||||
defaultMariaDbVersion: config.defaultMariaDb
|
||||
})
|
||||
await cliAliasService.generateAliases()
|
||||
return { success: true }
|
||||
})
|
||||
|
||||
ipcMain.handle('cli:sync', async () => {
|
||||
await cliAliasService.generateAliases()
|
||||
return { success: true }
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import fs from 'fs'
|
||||
import { phpManagerService } from './PhpManagerService'
|
||||
import { mariaDbManagerService } from './MariaDbManagerService'
|
||||
import { configService } from './ConfigService'
|
||||
import { cliBinService } from './CliBinService'
|
||||
|
||||
export class CliAliasService {
|
||||
async generateAliases() {
|
||||
@@ -13,6 +14,7 @@ export class CliAliasService {
|
||||
|
||||
await this.generatePowerShellAliases(installedPhp, installedMariaDb)
|
||||
await this.generateBashAliases(installedPhp, installedMariaDb)
|
||||
await cliBinService.sync()
|
||||
}
|
||||
|
||||
private async generatePowerShellAliases(phpVersions: any[], mariaDbVersions: any[]) {
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { app } from 'electron'
|
||||
import { exec } from 'child_process'
|
||||
import { promisify } from 'util'
|
||||
import { phpManagerService } from './PhpManagerService'
|
||||
import { mariaDbManagerService } from './MariaDbManagerService'
|
||||
import { configService } from './ConfigService'
|
||||
|
||||
const execAsync = promisify(exec)
|
||||
|
||||
export class CliBinService {
|
||||
private binDir: string
|
||||
|
||||
constructor() {
|
||||
// We store shims in the app's roaming/user path to avoid permission issues
|
||||
this.binDir = path.join(app.getPath('userData'), 'cli-bin')
|
||||
if (!fs.existsSync(this.binDir)) {
|
||||
fs.mkdirSync(this.binDir, { recursive: true })
|
||||
}
|
||||
}
|
||||
|
||||
async sync() {
|
||||
const phpVersions = await phpManagerService.getVersions()
|
||||
const mariaDbVersions = await mariaDbManagerService.getVersions()
|
||||
const settings = configService.getSettings()
|
||||
|
||||
const installedPhp = phpVersions.filter(v => v.status === 'installed')
|
||||
const installedMariaDb = mariaDbVersions.filter(v => v.status === 'installed')
|
||||
|
||||
// Clean current shims
|
||||
const files = fs.readdirSync(this.binDir)
|
||||
for (const file of files) {
|
||||
fs.unlinkSync(path.join(this.binDir, file))
|
||||
}
|
||||
|
||||
// Generate PHP shims
|
||||
for (const v of installedPhp) {
|
||||
const binaryPath = await phpManagerService.getPhpBinaryPath(v.version)
|
||||
if (binaryPath) {
|
||||
// version: 8.1.2 -> php812, php81, php8, php8.1
|
||||
const vParts = v.version.split('.')
|
||||
const aliases = [
|
||||
`php${v.version.replace(/\./g, '')}`, // php812
|
||||
`php${vParts[0]}${vParts[1]}`, // php81
|
||||
`php${vParts[0]}.${vParts[1]}` // php8.1
|
||||
]
|
||||
|
||||
for (const alias of aliases) {
|
||||
this.createWindowsShim(alias, binaryPath)
|
||||
}
|
||||
|
||||
// If this is the chosen default binary for general 'php'
|
||||
if (settings.defaultPhpVersion === v.version) {
|
||||
this.createWindowsShim('php', binaryPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no default is set, use the highest version as generic 'php'
|
||||
if (!settings.defaultPhpVersion && installedPhp.length > 0) {
|
||||
const latest = installedPhp[0]
|
||||
const path = await phpManagerService.getPhpBinaryPath(latest.version)
|
||||
if (path) this.createWindowsShim('php', path)
|
||||
}
|
||||
|
||||
// Generate MariaDB shims
|
||||
for (const v of installedMariaDb) {
|
||||
const binaryPath = await mariaDbManagerService.getMariaDbBinaryPath(v.version)
|
||||
if (binaryPath) {
|
||||
const vParts = v.version.split('.')
|
||||
const aliases = [
|
||||
`mysql${vParts[0]}${vParts[1]}`,
|
||||
`mariadb${vParts[0]}${vParts[1]}`,
|
||||
`mysql${vParts[0]}.${vParts[1]}`,
|
||||
`mariadb${vParts[0]}.${vParts[1]}`
|
||||
]
|
||||
|
||||
for (const alias of aliases) {
|
||||
this.createWindowsShim(alias, binaryPath)
|
||||
}
|
||||
|
||||
if (settings.defaultMariaDbVersion === v.id) {
|
||||
this.createWindowsShim('mysql', binaryPath)
|
||||
this.createWindowsShim('mariadb', binaryPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Default mysql/mariadb if none selected
|
||||
if (!settings.defaultMariaDbVersion && installedMariaDb.length > 0) {
|
||||
const latest = installedMariaDb[0]
|
||||
const path = await mariaDbManagerService.getMariaDbBinaryPath(latest.version)
|
||||
if (path) {
|
||||
this.createWindowsShim('mysql', path)
|
||||
this.createWindowsShim('mariadb', path)
|
||||
}
|
||||
}
|
||||
|
||||
// Always ensure bin folder is in User PATH if enabled
|
||||
if (settings.cliEnvEnabled !== false) {
|
||||
await this.ensureInPath()
|
||||
}
|
||||
}
|
||||
|
||||
private createWindowsShim(name: string, targetPath: string) {
|
||||
if (process.platform !== 'win32') {
|
||||
// For Unix we could use symlinks, but Multi-PHP target is mainly Windows for now
|
||||
// Symlinks are also fine: fs.symlinkSync(targetPath, path.join(this.binDir, name))
|
||||
return
|
||||
}
|
||||
|
||||
// Create a small .cmd proxy for each command
|
||||
// Using "@%* " to pass all arguments correctly
|
||||
const content = `@echo off\n"${targetPath}" %*\n`
|
||||
fs.writeFileSync(path.join(this.binDir, `${name}.cmd`), content)
|
||||
|
||||
// Also create a .bat just in case
|
||||
fs.writeFileSync(path.join(this.binDir, `${name}.bat`), content)
|
||||
}
|
||||
|
||||
async ensureInPath() {
|
||||
if (process.platform !== 'win32') return
|
||||
|
||||
try {
|
||||
// Get current User PATH
|
||||
const { stdout } = await execAsync('powershell -Command "[Environment]::GetEnvironmentVariable(\'Path\', \'User\')"')
|
||||
const currentPath = stdout.trim()
|
||||
|
||||
if (!currentPath.includes(this.binDir)) {
|
||||
const newPath = `${currentPath}${currentPath.endsWith(';') ? '' : ';'}${this.binDir}`
|
||||
// Update User PATH safely using PowerShell
|
||||
await execAsync(`powershell -Command "[Environment]::SetEnvironmentVariable('Path', '${newPath}', 'User')"`)
|
||||
console.log(`Added Multi-PHP bin to User PATH: ${this.binDir}`)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to update PATH:', e)
|
||||
}
|
||||
}
|
||||
|
||||
getBinDir() {
|
||||
return this.binDir
|
||||
}
|
||||
}
|
||||
|
||||
export const cliBinService = new CliBinService()
|
||||
@@ -14,7 +14,10 @@ export class ConfigService {
|
||||
mariaDbPort: '3306',
|
||||
mariaDbPorts: {} as Record<string, string>,
|
||||
allowRemoteAccess: false,
|
||||
language: 'tr'
|
||||
language: 'tr',
|
||||
cliEnvEnabled: true,
|
||||
defaultPhpVersion: '',
|
||||
defaultMariaDbVersion: ''
|
||||
}
|
||||
|
||||
constructor() {
|
||||
|
||||
+112
-1
@@ -119,6 +119,7 @@ function App(): JSX.Element {
|
||||
})
|
||||
const [activeTab, setActiveTab] = useState('dashboard')
|
||||
const [projectSearch, setProjectSearch] = useState('')
|
||||
const [cliConfig, setCliConfig] = useState<any>({ enabled: true, defaultPhp: '', defaultMariaDb: '', binDir: '' })
|
||||
const [settingsTab, setSettingsTab] = useState(0)
|
||||
const [dashboardTab, setDashboardTab] = useState(0)
|
||||
const [projects, setProjects] = useState<any[]>([])
|
||||
@@ -188,6 +189,13 @@ function App(): JSX.Element {
|
||||
}
|
||||
}
|
||||
|
||||
const fetchCliConfig = async () => {
|
||||
if (window.api && window.api.invoke) {
|
||||
const config = await window.api.invoke('cli:get-config')
|
||||
if (config) setCliConfig(config)
|
||||
}
|
||||
}
|
||||
|
||||
const fetchProjects = async () => {
|
||||
if (window.api && window.api.getProjects) {
|
||||
const list = await window.api.getProjects()
|
||||
@@ -264,6 +272,7 @@ function App(): JSX.Element {
|
||||
fetchNginxVersions()
|
||||
fetchApacheVersions()
|
||||
fetchSettings()
|
||||
fetchCliConfig()
|
||||
fetchPmaStatus()
|
||||
|
||||
const removePhpListener = window.api.onBinaryProgress ? window.api.onBinaryProgress((data) => {
|
||||
@@ -975,7 +984,8 @@ function App(): JSX.Element {
|
||||
<Tab label="MariaDB" icon={<DbIcon />} iconPosition="start" />
|
||||
<Tab label="Nginx" icon={<WebIcon />} iconPosition="start" />
|
||||
<Tab label="Apache" icon={<ServerIcon />} iconPosition="start" />
|
||||
<Tab label={t('settings.server_settings')} icon={<ServerIcon />} iconPosition="start" />
|
||||
<Tab label={t('settings.server_settings')} icon={<SettingsIcon />} iconPosition="start" />
|
||||
<Tab label={t('settings.cli_env')} icon={<TerminalIcon />} iconPosition="start" />
|
||||
</Tabs>
|
||||
</Box>
|
||||
|
||||
@@ -1215,6 +1225,107 @@ function App(): JSX.Element {
|
||||
</Grid>
|
||||
</Paper>
|
||||
)}
|
||||
|
||||
{settingsTab === 5 && (
|
||||
<Box>
|
||||
<Paper sx={{ p: 3, mb: 3, bgcolor: 'rgba(255, 255, 255, 0.03)', borderRadius: 2 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 1, gap: 1 }}>
|
||||
<TerminalIcon color="primary" />
|
||||
<Typography variant="h6" sx={{ fontWeight: 'bold' }}>{t('settings.cli_env')}</Typography>
|
||||
</Box>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}>
|
||||
{t('settings.cli_env_desc')}
|
||||
</Typography>
|
||||
|
||||
<Stack spacing={3}>
|
||||
<Box>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={cliConfig.enabled}
|
||||
onChange={async (e) => {
|
||||
const newConf = { ...cliConfig, enabled: e.target.checked }
|
||||
setCliConfig(newConf)
|
||||
await window.api.invoke('cli:set-config', newConf)
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<Box>
|
||||
<Typography variant="subtitle2" sx={{ fontWeight: 600 }}>{t('settings.cli_enabled')}</Typography>
|
||||
<Typography variant="caption" color="text.secondary" display="block">{t('settings.cli_enabled_desc')}</Typography>
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Divider />
|
||||
|
||||
<Box sx={{ display: 'flex', gap: 3, flexDirection: { xs: 'column', md: 'row' } }}>
|
||||
<FormControl fullWidth size="small">
|
||||
<Typography variant="subtitle2" sx={{ mb: 1, fontWeight: 600 }}>{t('settings.cli_default_php')}</Typography>
|
||||
<Select
|
||||
value={cliConfig.defaultPhp}
|
||||
onChange={async (e) => {
|
||||
const newConf = { ...cliConfig, defaultPhp: e.target.value }
|
||||
setCliConfig(newConf)
|
||||
await window.api.invoke('cli:set-config', newConf)
|
||||
}}
|
||||
>
|
||||
<MenuItem value=""><em>{t('common.ready')}</em></MenuItem>
|
||||
{phpVersions.filter(v => v.status === 'installed').map(v => (
|
||||
<MenuItem key={v.id} value={v.version}>PHP {v.version}</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
<Typography variant="caption" color="text.secondary" sx={{ mt: 0.5 }}>{t('settings.cli_default_php_desc')}</Typography>
|
||||
</FormControl>
|
||||
|
||||
<FormControl fullWidth size="small">
|
||||
<Typography variant="subtitle2" sx={{ mb: 1, fontWeight: 600 }}>{t('settings.cli_default_mariadb')}</Typography>
|
||||
<Select
|
||||
value={cliConfig.defaultMariaDb}
|
||||
onChange={async (e) => {
|
||||
const newConf = { ...cliConfig, defaultMariaDb: e.target.value }
|
||||
setCliConfig(newConf)
|
||||
await window.api.invoke('cli:set-config', newConf)
|
||||
}}
|
||||
>
|
||||
<MenuItem value=""><em>{t('common.ready')}</em></MenuItem>
|
||||
{mariaDbVersions.filter(v => v.status === 'installed').map(v => (
|
||||
<MenuItem key={v.id} value={v.id}>MariaDB {v.version}</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
<Typography variant="caption" color="text.secondary" sx={{ mt: 0.5 }}>{t('settings.cli_default_mariadb_desc')}</Typography>
|
||||
</FormControl>
|
||||
</Box>
|
||||
|
||||
<Divider />
|
||||
|
||||
<Box sx={{ p: 2, bgcolor: 'rgba(0,0,0,0.2)', borderRadius: 2, display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<Box>
|
||||
<Typography variant="caption" sx={{ color: 'rgba(255,255,255,0.4)', textTransform: 'uppercase', fontWeight: 800, mb: 0.5, display: 'block' }}>
|
||||
{cliConfig.enabled ? t('settings.cli_path_found') : t('settings.cli_path_not_found')}
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ fontFamily: 'monospace', color: cliConfig.enabled ? '#33d9b2' : 'error.main' }}>
|
||||
{cliConfig.binDir || '...'}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Button
|
||||
variant="outlined"
|
||||
size="small"
|
||||
startIcon={<RefreshIcon />}
|
||||
onClick={async () => {
|
||||
await window.api.invoke('cli:sync')
|
||||
setNotification({ open: true, message: t('settings.cli_sync_success'), severity: 'success' })
|
||||
}}
|
||||
>
|
||||
{t('settings.cli_sync_btn')}
|
||||
</Button>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Paper>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
|
||||
@@ -99,9 +99,21 @@
|
||||
"php_versions_desc": "Download and manage required PHP versions here.",
|
||||
"mariadb_versions_desc": "Manage MariaDB versions here.",
|
||||
"nginx_versions_desc": "Manage Nginx versions here.",
|
||||
"apache_versions_desc": "Manage Apache versions here.",
|
||||
"server_settings": "Server Settings",
|
||||
"nginx_port_desc": "Port Nginx will run on (Default: 80)"
|
||||
"apache_versions_desc": "You can manage Apache versions from here.",
|
||||
"nginx_port_desc": "Port on which Nginx runs (Default: 80)",
|
||||
"cli_env": "CLI & Environment Variables",
|
||||
"cli_env_desc": "Manage PATH settings to run PHP and MariaDB commands directly from any terminal (CMD, PowerShell).",
|
||||
"cli_enabled": "System PATH Integration Active",
|
||||
"cli_enabled_desc": "When ON, the Multi-PHP 'bin' folder is added to your user PATH variable.",
|
||||
"cli_default_php": "Default PHP Version (php)",
|
||||
"cli_default_php_desc": "Choose which version will be used when you run the 'php' command.",
|
||||
"cli_default_mariadb": "Default MariaDB Version (mysql)",
|
||||
"cli_default_mariadb_desc": "Default version for 'mysql' or 'mariadb' commands.",
|
||||
"cli_sync_btn": "SYNC SETTINGS",
|
||||
"cli_path_found": "PATH Found:",
|
||||
"cli_path_not_found": "PATH Not Set Yet",
|
||||
"cli_sync_success": "CLI settings and PATH integration updated successfully.",
|
||||
"server_settings": "Server Settings"
|
||||
},
|
||||
"server": {
|
||||
"nginx": {
|
||||
|
||||
@@ -101,7 +101,19 @@
|
||||
"mariadb_versions_desc": "MariaDB sürümlerini buradan yönetebilirsiniz.",
|
||||
"nginx_versions_desc": "Nginx sürümlerini buradan yönetebilirsiniz.",
|
||||
"apache_versions_desc": "Apache sürümlerini buradan yönetebilirsiniz.",
|
||||
"nginx_port_desc": "Nginx'in çalışacağı port (Varsayılan: 80)"
|
||||
"nginx_port_desc": "Nginx'in çalışacağı port (Varsayılan: 80)",
|
||||
"cli_env": "CLI & Ortam Değişkenleri",
|
||||
"cli_env_desc": "PHP ve MariaDB komutlarını herhangi bir terminalden (CMD, PowerShell) doğrudan çalıştırabilmeniz için PATH ayarlarını yönetin.",
|
||||
"cli_enabled": "Sistem PATH Entegrasyonu Aktif",
|
||||
"cli_enabled_desc": "Açık olduğunda, Multi-PHP 'bin' klasörü sisteminizin kullanıcı PATH değişkenine eklenir.",
|
||||
"cli_default_php": "Varsayılan PHP Sürümü (php)",
|
||||
"cli_default_php_desc": "'php' komutunu çalıştırdığınızda hangi sürümün kullanılacağını seçin.",
|
||||
"cli_default_mariadb": "Varsayılan MariaDB Sürümü (mysql)",
|
||||
"cli_default_mariadb_desc": "'mysql' veya 'mariadb' komutu için varsayılan sürüm.",
|
||||
"cli_sync_btn": "AYARLARI SENKRONİZE ET",
|
||||
"cli_path_found": "PATH Ayarı Bulundu:",
|
||||
"cli_path_not_found": "PATH Ayarı Henüz Yapılmamış",
|
||||
"cli_sync_success": "CLI ayarları ve PATH entegrasyonu başarıyla güncellendi."
|
||||
},
|
||||
"server": {
|
||||
"nginx": {
|
||||
|
||||
Reference in New Issue
Block a user