diff --git a/src/main/ipc/index.ts b/src/main/ipc/index.ts index 38b286f..cb17edb 100644 --- a/src/main/ipc/index.ts +++ b/src/main/ipc/index.ts @@ -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 } + }) } diff --git a/src/main/services/CliAliasService.ts b/src/main/services/CliAliasService.ts index 5451010..c8b6902 100644 --- a/src/main/services/CliAliasService.ts +++ b/src/main/services/CliAliasService.ts @@ -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[]) { diff --git a/src/main/services/CliBinService.ts b/src/main/services/CliBinService.ts new file mode 100644 index 0000000..e2f96cf --- /dev/null +++ b/src/main/services/CliBinService.ts @@ -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() diff --git a/src/main/services/ConfigService.ts b/src/main/services/ConfigService.ts index cfdc6ea..1c2623d 100644 --- a/src/main/services/ConfigService.ts +++ b/src/main/services/ConfigService.ts @@ -14,7 +14,10 @@ export class ConfigService { mariaDbPort: '3306', mariaDbPorts: {} as Record, allowRemoteAccess: false, - language: 'tr' + language: 'tr', + cliEnvEnabled: true, + defaultPhpVersion: '', + defaultMariaDbVersion: '' } constructor() { diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index 9eecb68..5f40b04 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -119,6 +119,7 @@ function App(): JSX.Element { }) const [activeTab, setActiveTab] = useState('dashboard') const [projectSearch, setProjectSearch] = useState('') + const [cliConfig, setCliConfig] = useState({ enabled: true, defaultPhp: '', defaultMariaDb: '', binDir: '' }) const [settingsTab, setSettingsTab] = useState(0) const [dashboardTab, setDashboardTab] = useState(0) const [projects, setProjects] = useState([]) @@ -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 { } iconPosition="start" /> } iconPosition="start" /> } iconPosition="start" /> - } iconPosition="start" /> + } iconPosition="start" /> + } iconPosition="start" /> @@ -1215,6 +1225,107 @@ function App(): JSX.Element { )} + + {settingsTab === 5 && ( + + + + + {t('settings.cli_env')} + + + {t('settings.cli_env_desc')} + + + + + { + const newConf = { ...cliConfig, enabled: e.target.checked } + setCliConfig(newConf) + await window.api.invoke('cli:set-config', newConf) + }} + /> + } + label={ + + {t('settings.cli_enabled')} + {t('settings.cli_enabled_desc')} + + } + /> + + + + + + + {t('settings.cli_default_php')} + + {t('settings.cli_default_php_desc')} + + + + {t('settings.cli_default_mariadb')} + + {t('settings.cli_default_mariadb_desc')} + + + + + + + + + {cliConfig.enabled ? t('settings.cli_path_found') : t('settings.cli_path_not_found')} + + + {cliConfig.binDir || '...'} + + + + + + + + )} )} diff --git a/src/renderer/src/locales/en.json b/src/renderer/src/locales/en.json index 1011dcc..2702f97 100644 --- a/src/renderer/src/locales/en.json +++ b/src/renderer/src/locales/en.json @@ -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": { diff --git a/src/renderer/src/locales/tr.json b/src/renderer/src/locales/tr.json index 11b09f2..f09aa01 100644 --- a/src/renderer/src/locales/tr.json +++ b/src/renderer/src/locales/tr.json @@ -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": {