/// import fs from 'node:fs' import path from 'node:path' import { exec } from 'node:child_process' import os from 'node:os' import { defineConfig, loadEnv, type Plugin } from 'vite' import react from '@vitejs/plugin-react' import tailwindcss from '@tailwindcss/vite' import babel from '@babel/core' import addDataLocator from './plugins/add-data-locator.cjs' function getIdeCandidates(): string[] { const isWindows = os.platform() === 'win32' const isMac = os.platform() === 'darwin' const home = os.homedir() const candidates: string[] = [] if (isMac) { const macPaths = [ '/Applications/Antigravity IDE.app/Contents/Resources/app/bin/antigravity-ide', path.join(home, 'Applications/Antigravity IDE.app/Contents/Resources/app/bin/antigravity-ide'), path.join(home, '.gemini/antigravity/bin/antigravity-ide'), '/Applications/Cursor.app/Contents/Resources/app/bin/cursor', path.join(home, 'Applications/Cursor.app/Contents/Resources/app/bin/cursor'), '/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code', path.join(home, 'Applications/Visual Studio Code.app/Contents/Resources/app/bin/code'), path.join(home, '.gemini/antigravity/bin/code'), '/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code-insiders', path.join(home, 'Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code-insiders'), '/usr/local/bin/antigravity-ide', '/usr/local/bin/cursor', '/usr/local/bin/code', '/opt/homebrew/bin/antigravity-ide', '/opt/homebrew/bin/cursor', '/opt/homebrew/bin/code', ] for (const p of macPaths) { if (fs.existsSync(p) && !candidates.includes(p)) { candidates.push(p) } } candidates.push('antigravity-ide', 'cursor', 'code') } else if (isWindows) { const localAppData = process.env.LOCALAPPDATA || '' const programFiles = process.env.ProgramFiles || 'C:\\Program Files' const winPaths = [ path.join(localAppData, 'Programs', 'Antigravity IDE', 'bin', 'antigravity-ide.cmd'), path.join(localAppData, 'Programs', 'cursor', 'resources', 'app', 'bin', 'cursor.cmd'), path.join(localAppData, 'Programs', 'Microsoft VS Code', 'bin', 'code.cmd'), path.join(programFiles, 'Microsoft VS Code', 'bin', 'code.cmd'), 'antigravity-ide.cmd', 'cursor.cmd', 'code.cmd', ] for (const p of winPaths) { if (p.includes(path.sep)) { if (fs.existsSync(p) && !candidates.includes(p)) candidates.push(p) } else if (!candidates.includes(p)) { candidates.push(p) } } } else { // Linux const linuxPaths = [ '/usr/local/bin/antigravity-ide', '/usr/bin/antigravity-ide', '/usr/local/bin/cursor', '/usr/bin/cursor', '/usr/local/bin/code', '/usr/bin/code', '/snap/bin/code', 'antigravity-ide', 'cursor', 'code', ] for (const p of linuxPaths) { if (p.startsWith('/') ? fs.existsSync(p) : true) { if (!candidates.includes(p)) candidates.push(p) } } } return candidates.length > 0 ? candidates : ['antigravity-ide', 'code'] } function openViaCli(locator: string, callback: (err: Error | null, bin?: string) => void) { const isWindows = os.platform() === 'win32' const home = os.homedir() const extraPaths = isWindows ? [] : [ path.join(home, '.gemini/antigravity/bin'), path.join(home, 'Library/Application Support/Antigravity/bin'), '/usr/local/bin', '/opt/homebrew/bin', '/opt/homebrew/sbin', '/usr/bin', '/bin', ] const augmentedPath = [...extraPaths, process.env.PATH || ''].filter(Boolean).join(isWindows ? ';' : ':') const env = { ...process.env, PATH: augmentedPath } const candidates = getIdeCandidates() let index = 0 let lastError: Error | null = null function tryNext() { if (index >= candidates.length) { return callback(lastError || new Error('No IDE command succeeded to open locator')) } const bin = candidates[index++] const quotedBin = bin.includes(' ') ? `"${bin}"` : bin const cmd = `${quotedBin} -r -g "${locator}"` exec(cmd, { env }, (error) => { if (error) { lastError = error tryNext() } else { callback(null, bin) } }) } tryNext() } function openInIde(locator: string, callback: (err: Error | null, bin?: string) => void) { const isWindows = os.platform() === 'win32' const isMac = os.platform() === 'darwin' const cleanPath = locator.replace(/^[/\\]+/, '') const targetUri = `/${cleanPath}` if (isMac) { // Specifically target Antigravity IDE.app to prevent opening regular Antigravity or other apps const ideAppPath = fs.existsSync('/Applications/Antigravity IDE.app') ? '/Applications/Antigravity IDE.app' : path.join(os.homedir(), 'Applications/Antigravity IDE.app') const hasIdeApp = fs.existsSync(ideAppPath) const cmd = hasIdeApp ? `open -a "${ideAppPath}" "antigravity-ide://file${targetUri}"` : `open "antigravity-ide://file${targetUri}" || open "cursor://file${targetUri}" || open "vscode://file${targetUri}"` exec(cmd, (error) => { // Specifically bring Antigravity IDE to the foreground (never 'Antigravity') exec( `osascript -e 'tell application id "com.google.antigravity-ide" to activate' -e 'tell application "Antigravity IDE" to activate' 2>/dev/null`, () => {} ) if (!error) { return callback(null, 'Antigravity IDE') } openViaCli(locator, callback) }) return } if (isWindows) { const cmd = `start "" /MAX "antigravity-ide://file${targetUri}" || start "" /MAX "cursor://file${targetUri}" || start "" /MAX "vscode://file${targetUri}"` exec(cmd, (error) => { if (!error) { return callback(null, 'antigravity-ide://file') } openViaCli(locator, callback) }) return } // Linux const cmd = `xdg-open "antigravity-ide://file${targetUri}" || xdg-open "vscode://file${targetUri}"` exec(cmd, (error) => { if (!error) { return callback(null, 'antigravity-ide://file') } openViaCli(locator, callback) }) } function openInIdePlugin(): Plugin { return { name: 'open-in-ide-plugin', apply: 'serve', configureServer(server) { server.middlewares.use((req, res, next) => { const url = req.url?.split('?')[0] if (url === '/api/open-in-ide' && req.method === 'POST') { let body = '' req.on('data', (chunk) => { body += chunk }) req.on('end', () => { try { const { locator } = JSON.parse(body) if (!locator) { res.statusCode = 400 res.setHeader('Content-Type', 'application/json') res.end(JSON.stringify({ message: 'Locator is required' })) return } openInIde(locator, (error, bin) => { if (error) { console.error(`[open-in-ide] Error opening "${locator}": ${error.message}`) res.statusCode = 500 res.setHeader('Content-Type', 'application/json') res.end( JSON.stringify({ message: 'Failed to open file in IDE', error: error.message, }) ) return } res.statusCode = 200 res.setHeader('Content-Type', 'application/json') res.end(JSON.stringify({ success: true, bin })) }) } catch (err) { console.error('[open-in-ide] Error parsing body:', err, 'raw body:', body) res.statusCode = 500 res.setHeader('Content-Type', 'application/json') res.end(JSON.stringify({ message: 'Internal server error', error: String(err), rawBody: body })) } }) return } if ( (url === '/api/remote-logs' || url === '/api/remote-network' || url === '/api/dev-storage') && req.method === 'POST' ) { res.statusCode = 200 res.setHeader('Content-Type', 'application/json') res.end(JSON.stringify({ ok: true })) return } next() }) }, } } function addDataLocatorPlugin(): Plugin { return { name: 'add-data-locator-plugin', enforce: 'pre', apply: 'serve', transform(code, id) { const cleanId = id.split('?')[0] if (cleanId.includes('node_modules') || !/\.[jt]sx$/.test(cleanId)) { return null } try { const result = babel.transformSync(code, { filename: cleanId, configFile: false, babelrc: false, parserOpts: { plugins: ['jsx', 'typescript'] }, plugins: [addDataLocator], }) if (result?.code) { return { code: result.code, map: result.map } } } catch (err) { console.error('[add-data-locator-plugin] Transform error:', err) } return null }, } } export default defineConfig(({ mode }) => { const env = loadEnv(mode, process.cwd(), '') /* مقصد بک‌اند برای پراکسیِ dev — همهٔ '/api/*' بدون CORS به این‌جا می‌رود. */ const apiTarget = env.VITE_API_TARGET ?? 'http://localhost:8001' return { plugins: [ openInIdePlugin(), addDataLocatorPlugin(), react(), tailwindcss(), ], resolve: { alias: { '@': path.resolve(__dirname, './src') }, }, server: { proxy: { '/api': { target: apiTarget, changeOrigin: true, }, '/media': { target: apiTarget, changeOrigin: true, }, '/static': { target: apiTarget, changeOrigin: true, }, }, }, test: { environment: 'node', include: ['src/**/*.test.{ts,tsx}'], }, } })