diff --git a/src/commands/update-ide.js b/src/commands/update-ide.js index 8719a50..e4b5a95 100644 --- a/src/commands/update-ide.js +++ b/src/commands/update-ide.js @@ -11,40 +11,49 @@ var { cloneWithFallback, cleanupTemp } = require('../utils/git'); var SSH_URL = 'ssh://git@git.davinti.com.br:2222/davinTI/ide-config.git'; var HTTPS_URL = 'https://git.davinti.com.br/davinTI/ide-config.git'; -var SUPPORTED_IDES = ['vscode']; +// ── Helpers ────────────────────────────────────────────────────────────────── /** - * Returns the VSCode User snippets directory for the current OS. - * Linux/macOS: ~/.config/Code/User/snippets/ - * Windows: %APPDATA%\Code\User\snippets\ + * Resolves the snippets directory for a vscode-fork IDE entry. + * Linux base: ~/. Windows base: %APPDATA%\ */ -function getSnippetsDir() { - if (process.platform === 'win32') { +function resolveSnippetsDir(ideEntry) { + var platform = process.platform === 'win32' ? 'win32' : 'linux'; + var rel = ideEntry.snippetsDir[platform]; + if (!rel) throw new Error('snippetsDir not defined for platform ' + platform); + if (platform === 'win32') { var appData = process.env.APPDATA; - if (!appData) { - throw new Error('APPDATA environment variable is not set.'); - } - return path.join(appData, 'Code', 'User', 'snippets'); + if (!appData) throw new Error('APPDATA environment variable is not set.'); + return path.join(appData, rel); } - return path.join(os.homedir(), '.config', 'Code', 'User', 'snippets'); + return path.join(os.homedir(), rel); } /** - * Returns the `code` CLI binary name for the current OS. - * Windows ships it as `code.cmd` on PATH. + * Returns all existing JetBrains config directories. + * Linux: ~/.config/JetBrains// + * Windows: %APPDATA%\JetBrains\\ */ -function getCodeBin() { - return process.platform === 'win32' ? 'code.cmd' : 'code'; +function findJetBrainsDirs() { + var base; + if (process.platform === 'win32') { + base = path.join(process.env.APPDATA || '', 'JetBrains'); + } else { + base = path.join(os.homedir(), '.config', 'JetBrains'); + } + if (!fs.existsSync(base)) return []; + return fs.readdirSync(base) + .map(function (name) { return path.join(base, name); }) + .filter(function (p) { return fs.statSync(p).isDirectory(); }); } /** - * Checks whether a VSCode extension is currently installed. - * Returns true/false. Fails silently (returns false) if `code` is unavailable. + * Checks whether a VSCode-fork extension is currently installed by listing + * extensions via the IDE's own CLI. Returns true/false; fails silently. */ -function isExtensionInstalled(extensionId) { +function isExtensionInstalled(bin, extensionId) { try { - var codeBin = getCodeBin(); - var output = execSync(codeBin + ' --list-extensions', { stdio: 'pipe' }).toString(); + var output = execSync(bin + ' --list-extensions', { stdio: 'pipe' }).toString(); return output.split('\n').some(function (line) { return line.trim().toLowerCase() === extensionId.toLowerCase(); }); @@ -54,131 +63,172 @@ function isExtensionInstalled(extensionId) { } /** - * Installs a .vsix file using the `code` CLI. - * Always passes --force so it also updates an already-installed extension. + * Prompts the user to select an IDE from the manifest. + * Returns the selected IDE key string. */ -function installVsix(vsixPath) { - var codeBin = getCodeBin(); - execSync(codeBin + ' --install-extension "' + vsixPath + '" --force', { stdio: 'inherit' }); -} - -/** - * Prompts the user to pick an IDE from a numbered list. - * Resolves with the chosen IDE string. - */ -function promptIde() { +function promptIde(ideKeys, manifest) { return new Promise(function (resolve) { var rl = readline.createInterface({ input: process.stdin, output: process.stdout }); - console.log(''); console.log('IDEs disponíveis:'); - SUPPORTED_IDES.forEach(function (ide, i) { - console.log(' ' + (i + 1) + '. ' + ide); + ideKeys.forEach(function (key, i) { + console.log(' ' + (i + 1) + '. ' + manifest[key].label + ' (' + key + ')'); }); console.log(''); - rl.question('Escolha a IDE [1]: ', function (answer) { rl.close(); var trimmed = answer.trim(); - var index; - if (trimmed === '' || trimmed === '1') { - index = 0; - } else { - index = parseInt(trimmed, 10) - 1; - } - if (isNaN(index) || index < 0 || index >= SUPPORTED_IDES.length) { + var index = (trimmed === '' || trimmed === '1') ? 0 : parseInt(trimmed, 10) - 1; + if (isNaN(index) || index < 0 || index >= ideKeys.length) { console.error('Opção inválida: ' + trimmed); process.exit(1); } - resolve(SUPPORTED_IDES[index]); + resolve(ideKeys[index]); }); }); } +// ── Installers ──────────────────────────────────────────────────────────────── + +function installVscodeFork(ideEntry, repoRoot) { + var platform = process.platform === 'win32' ? 'win32' : 'linux'; + var bin = ideEntry.bin[platform]; + + // Extension + if (ideEntry.extension) { + var vsixPath = path.join(repoRoot, ideEntry.extension); + if (!fs.existsSync(vsixPath)) { + console.log('Aviso: arquivo de extensão não encontrado em ' + vsixPath + '. Pulando extensão.'); + } else { + var extId = path.basename(vsixPath, '.vsix'); + var alreadyInstalled = isExtensionInstalled(bin, extId); + console.log((alreadyInstalled ? 'Atualizando' : 'Instalando') + ' extensão ' + extId + '...'); + try { + execSync(bin + ' --install-extension "' + vsixPath + '" --force', { stdio: 'inherit' }); + console.log('Extensão instalada com sucesso.'); + } catch (e) { + console.error('Erro ao instalar a extensão: ' + e.message); + console.error('Certifique-se de que o comando "' + bin + '" está disponível no PATH.'); + process.exit(1); + } + } + } + + // Snippets + if (ideEntry.snippets) { + var snippetsSrc = path.join(repoRoot, ideEntry.snippets); + if (!fs.existsSync(snippetsSrc)) { + console.log('Aviso: arquivo de snippets não encontrado em ' + snippetsSrc + '. Pulando snippets.'); + } else { + var snippetsDir; + try { + snippetsDir = resolveSnippetsDir(ideEntry); + } catch (e) { + console.error('Erro ao determinar diretório de snippets: ' + e.message); + process.exit(1); + } + fse.ensureDirSync(snippetsDir); + var snippetFile = path.basename(snippetsSrc); + var snippetDest = path.join(snippetsDir, snippetFile); + var action = fs.existsSync(snippetDest) ? 'atualizado' : 'instalado'; + fse.copySync(snippetsSrc, snippetDest); + console.log('Snippet ' + snippetFile + ' ' + action + ' em ' + snippetDest); + } + } +} + +function installJetBrains(ideEntry, repoRoot) { + if (!ideEntry.templates) { + console.log('Aviso: nenhum arquivo de templates definido para JetBrains. Pulando.'); + return; + } + + var templatesSrc = path.join(repoRoot, ideEntry.templates); + if (!fs.existsSync(templatesSrc)) { + console.log('Aviso: arquivo de templates não encontrado em ' + templatesSrc + '. Pulando.'); + return; + } + + var jbDirs = findJetBrainsDirs(); + if (jbDirs.length === 0) { + console.log('Nenhuma instalação do JetBrains encontrada no sistema.'); + console.log('Copie manualmente o arquivo abaixo para o diretório "templates" da sua IDE:'); + console.log(' ' + templatesSrc); + return; + } + + var templateFile = path.basename(templatesSrc); + var installed = 0; + jbDirs.forEach(function (jbDir) { + var templatesDir = path.join(jbDir, 'templates'); + fse.ensureDirSync(templatesDir); + var dest = path.join(templatesDir, templateFile); + var action = fs.existsSync(dest) ? 'atualizado' : 'instalado'; + fse.copySync(templatesSrc, dest); + console.log(' ' + path.basename(jbDir) + ' — templates ' + action); + installed++; + }); + + console.log(installed + ' instalação(ões) JetBrains atualizada(s).'); + console.log('Reinicie sua IDE para carregar os novos templates.'); + console.log(''); + console.log('Nota: a instalação do plugin Vitruvio para JetBrains deve ser feita manualmente.'); + console.log('Acesse Settings → Plugins → Install Plugin from Disk e selecione o arquivo .jar.'); +} + +// ── Command ─────────────────────────────────────────────────────────────────── + function register(program) { program .command('update-ide') .description('Instala ou atualiza a extensão Vitruvio e os snippets na IDE escolhida') .action(function () { - promptIde().then(function (ide) { - console.log('IDE selecionada: ' + ide); - console.log('Buscando repositório ide-config...'); + console.log('Buscando repositório ide-config...'); - var tmpDir = null; - try { - tmpDir = cloneWithFallback(SSH_URL, HTTPS_URL); - } catch (e) { - console.error('Erro: não foi possível clonar o repositório ide-config.'); - console.error(e.message); - process.exit(1); + var tmpDir = null; + try { + tmpDir = cloneWithFallback(SSH_URL, HTTPS_URL); + } catch (e) { + console.error('Erro: não foi possível clonar o repositório ide-config.'); + console.error(e.message); + process.exit(1); + } + + var manifest; + try { + var manifestPath = path.join(tmpDir, 'ide-manifest.json'); + if (!fs.existsSync(manifestPath)) { + throw new Error('ide-manifest.json não encontrado no repositório ide-config.'); } + manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')); + } catch (e) { + cleanupTemp(tmpDir); + console.error('Erro ao ler o manifesto de IDEs: ' + e.message); + process.exit(1); + } + + var ideKeys = Object.keys(manifest); + if (ideKeys.length === 0) { + cleanupTemp(tmpDir); + console.error('Nenhuma IDE encontrada no manifesto.'); + process.exit(1); + } + + promptIde(ideKeys, manifest).then(function (ideKey) { + var ideEntry = manifest[ideKey]; + console.log('IDE selecionada: ' + ideEntry.label); + console.log(''); try { - var ideDir = path.join(tmpDir, ide); // e.g. /vscode - - if (!fs.existsSync(ideDir)) { - console.error('Erro: diretório "' + ide + '" não encontrado no repositório ide-config.'); + if (ideEntry.type === 'vscode-fork') { + installVscodeFork(ideEntry, tmpDir); + } else if (ideEntry.type === 'jetbrains') { + installJetBrains(ideEntry, tmpDir); + } else { + console.error('Tipo de IDE desconhecido: ' + ideEntry.type); process.exit(1); } - // ── Extension ───────────────────────────────────────────────────── - var vsixFiles = fs.readdirSync(ideDir).filter(function (f) { - return f.endsWith('.vsix'); - }); - - if (vsixFiles.length === 0) { - console.log('Aviso: nenhum arquivo .vsix encontrado em ' + ideDir + '. Pulando extensão.'); - } else { - var vsixFile = vsixFiles[0]; - var vsixPath = path.join(ideDir, vsixFile); - - // Derive extension ID from the vsix filename (publisher.name.vsix → publisher.name) - var extId = vsixFile.replace(/\.vsix$/i, ''); - var alreadyInstalled = isExtensionInstalled(extId); - - if (alreadyInstalled) { - console.log('Extensão já instalada, atualizando ' + extId + '...'); - } else { - console.log('Instalando extensão ' + extId + '...'); - } - - try { - installVsix(vsixPath); - console.log('Extensão instalada com sucesso.'); - } catch (e) { - console.error('Erro ao instalar a extensão: ' + e.message); - console.error('Certifique-se de que o comando "code" está disponível no PATH.'); - process.exit(1); - } - } - - // ── Snippets ─────────────────────────────────────────────────────── - var snippetFiles = fs.readdirSync(ideDir).filter(function (f) { - return f.endsWith('.code-snippets'); - }); - - if (snippetFiles.length === 0) { - console.log('Aviso: nenhum arquivo .code-snippets encontrado em ' + ideDir + '. Pulando snippets.'); - } else { - var snippetsDir; - try { - snippetsDir = getSnippetsDir(); - } catch (e) { - console.error('Erro ao determinar diretório de snippets: ' + e.message); - process.exit(1); - } - - fse.ensureDirSync(snippetsDir); - - snippetFiles.forEach(function (snippetFile) { - var src = path.join(ideDir, snippetFile); - var dest = path.join(snippetsDir, snippetFile); - var action = fs.existsSync(dest) ? 'atualizado' : 'instalado'; - fse.copySync(src, dest); - console.log('Snippet ' + snippetFile + ' ' + action + ' em ' + dest); - }); - } - console.log(''); console.log('Configuração da IDE concluída.'); } finally {