commit c5621bcb495018011c3060a3d998c8a1f0425c79 Author: jb Date: Tue Apr 14 16:06:19 2026 -0300 batman diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c2658d7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..9abb70a --- /dev/null +++ b/README.md @@ -0,0 +1,153 @@ +# vitruvio-cli + +Ferramentas de linha de comando para criação e manutenção de repositórios Vitruvio. + +--- + +## Pré-requisitos + +- **Node.js** 18 ou superior +- **Git** instalado e acessível no terminal +- **Chave SSH** configurada no servidor Gitea da davinTI (necessária para clonar via SSH — há fallback automático para HTTPS) + +--- + +## Instalação + +### Linux + +```bash +# 1. Clone o repositório +git clone ssh://git@git.davinti.com.br:2222/davinTI/vitruvio-cli.git +cd vitruvio-cli + +# 2. Instale as dependências +npm install + +# 3. Registre o comando globalmente +npm link +``` + +Se você usa **mise** para gerenciar versões do Node, rode também: + +```bash +mise reshim +``` + +Verifique a instalação: + +```bash +vitruvio --help +``` + +--- + +### Windows + +Abra o **PowerShell** ou **Prompt de Comando** como administrador. + +```powershell +# 1. Clone o repositório +git clone ssh://git@git.davinti.com.br:2222/davinTI/vitruvio-cli.git +cd vitruvio-cli + +# 2. Instale as dependências +npm install + +# 3. Registre o comando globalmente +npm link +``` + +Verifique a instalação: + +```powershell +vitruvio --help +``` + +> **Nota:** Caso o terminal não reconheça o comando `vitruvio` após o `npm link`, verifique se o diretório de binários globais do npm está no PATH do sistema. Para descobrir o caminho: +> ```powershell +> npm config get prefix +> ``` +> Adicione o caminho retornado (ex: `C:\Users\SeuUsuario\AppData\Roaming\npm`) nas variáveis de ambiente do sistema em **Painel de Controle → Sistema → Variáveis de Ambiente → Path**. + +--- + +## Comandos + +### `vitruvio init ` + +Cria um novo repositório Vitruvio a partir do template base. + +```bash +vitruvio init meu-modulo +``` + +- Clona o template `projeto-base` para um diretório com o nome informado +- Remove o histórico git do template e inicializa um repositório limpo sem remotes +- Pronto para você adicionar seu próprio remote e fazer o primeiro commit + +**Após o init:** + +```bash +cd meu-modulo + +# Edite o vitruvio.json com a chave e nome do seu módulo +# Depois: +git remote add origin +git add . +git commit -m "feat: scaffold inicial" +``` + +--- + +### `vitruvio update-repo` + +Atualiza os arquivos `CLAUDE.md` gerenciados no repositório atual com a versão mais recente do template. + +```bash +# Execute dentro de um repositório Vitruvio (onde há vitruvio.json) +cd meu-modulo +vitruvio update-repo +``` + +Arquivos atualizados por este comando: + +``` +CLAUDE.md +endpoints/CLAUDE.md +panels/CLAUDE.md +patches/CLAUDE.md +processes/CLAUDE.md +queries/CLAUDE.md +reports/CLAUDE.md +scripts/CLAUDE.md +``` + +Nenhum outro arquivo do repositório é tocado. + +--- + +### `vitruvio update-base` + +Sincroniza os diretórios gerenciados do diretório de plataforma local com o repositório `base-claude`. + +```bash +vitruvio update-base +``` + +Diretório de plataforma atualizado: + +| Sistema | Caminho | +|---------|---------| +| Linux | `~/.local/share/vitruvio-platform/` | +| Windows | `%LOCALAPPDATA%\vitruvio-platform\` | + +Diretórios gerenciados (substituídos completamente): + +``` +libs/ +docs/ +examples/ +``` + +Qualquer outro diretório ou arquivo fora dessa lista não é alterado. diff --git a/bin/vitruvio.js b/bin/vitruvio.js new file mode 100755 index 0000000..43e1c8e --- /dev/null +++ b/bin/vitruvio.js @@ -0,0 +1,22 @@ +#!/usr/bin/env node +'use strict'; + +var { Command } = require('commander'); +var pkg = require('../package.json'); + +var initCmd = require('../src/commands/init'); +var updateRepoCmd = require('../src/commands/update-repo'); +var updateBaseCmd = require('../src/commands/update-base'); + +var program = new Command(); + +program + .name('vitruvio') + .description('Ferramentas de linha de comando para repositórios Vitruvio') + .version(pkg.version); + +initCmd.register(program); +updateRepoCmd.register(program); +updateBaseCmd.register(program); + +program.parse(process.argv); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..906371f --- /dev/null +++ b/package-lock.json @@ -0,0 +1,69 @@ +{ + "name": "vitruvio-cli", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "vitruvio-cli", + "version": "1.0.0", + "dependencies": { + "commander": "^12.0.0", + "fs-extra": "^11.2.0" + }, + "bin": { + "vitruvio": "bin/vitruvio.js" + } + }, + "node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/fs-extra": { + "version": "11.3.4", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", + "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..b234882 --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "name": "vitruvio-cli", + "version": "1.0.0", + "description": "CLI tooling for Vitruvio content repositories", + "bin": { + "vitruvio": "bin/vitruvio.js" + }, + "scripts": { + "start": "node bin/vitruvio.js" + }, + "dependencies": { + "commander": "^12.0.0", + "fs-extra": "^11.2.0" + } +} diff --git a/src/commands/init.js b/src/commands/init.js new file mode 100644 index 0000000..dda0638 --- /dev/null +++ b/src/commands/init.js @@ -0,0 +1,63 @@ +'use strict'; + +var path = require('path'); +var fs = require('fs'); +var { execSync } = require('child_process'); +var fse = require('fs-extra'); +var { cloneWithFallback, cleanupTemp } = require('../utils/git'); + +var SSH_URL = 'ssh://git@git.davinti.com.br:2222/davinTI/projeto-base.git'; +var HTTPS_URL = 'https://git.davinti.com.br/davinTI/projeto-base.git'; + +function register(program) { + program + .command('init ') + .description('Cria um novo repositório Vitruvio a partir do template base') + .action(function (name) { + var targetDir = path.resolve(process.cwd(), name); + + if (fs.existsSync(targetDir)) { + console.error('Erro: o diretório já existe: ' + targetDir); + process.exit(1); + } + + console.log('Clonando template para ' + targetDir + '...'); + + var tmpDir = null; + try { + tmpDir = cloneWithFallback(SSH_URL, HTTPS_URL); + } catch (e) { + console.error('Erro: não foi possível clonar o repositório template.'); + console.error(e.message); + process.exit(1); + } + + try { + fse.copySync(tmpDir, targetDir, { + filter: function (src) { + var rel = path.relative(tmpDir, src); + return rel !== '.git' && !rel.startsWith('.git' + path.sep); + } + }); + } finally { + cleanupTemp(tmpDir); + } + + execSync('git init', { cwd: targetDir, stdio: 'pipe' }); + + console.log(''); + console.log('Pronto! Seu novo repositório Vitruvio está em:'); + console.log(' ' + targetDir); + console.log(''); + console.log('Próximos passos:'); + console.log(' 1. Edite o vitruvio.json — atualize metadata.key e metadata.name do seu módulo'); + console.log(' 2. Adicione o seu remote:'); + console.log(' cd ' + name); + console.log(' git remote add origin '); + console.log(' 3. Faça o commit inicial:'); + console.log(' git add .'); + console.log(' git commit -m "feat: scaffold inicial"'); + }); +} + +module.exports = { register: register }; diff --git a/src/commands/update-base.js b/src/commands/update-base.js new file mode 100644 index 0000000..24ef92c --- /dev/null +++ b/src/commands/update-base.js @@ -0,0 +1,76 @@ +'use strict'; + +var path = require('path'); +var fs = require('fs'); +var fse = require('fs-extra'); +var { cloneWithFallback, cleanupTemp } = require('../utils/git'); +var { getPlatformDir } = require('../utils/platform'); + +var SSH_URL = 'ssh://git@git.davinti.com.br:2222/davinTI/base-claude.git'; +var HTTPS_URL = 'https://git.davinti.com.br/davinTI/base-claude.git'; + +// Directories inside the platform dir that are fully managed by this command. +// Each of these is replaced in its entirety — files removed from the source +// are also removed locally. Directories not in this list are never touched. +var MANAGED_DIRS = ['libs', 'docs', 'examples']; + +function register(program) { + program + .command('update-base') + .description('Sincroniza os diretórios gerenciados do diretório de plataforma Vitruvio com o repositório base-claude') + .action(function () { + var platformDir; + try { + platformDir = getPlatformDir(); + } catch (e) { + console.error('Erro: ' + e.message); + process.exit(1); + } + + console.log('Diretório de plataforma: ' + platformDir); + console.log('Buscando repositório base-claude...'); + + var tmpDir = null; + try { + tmpDir = cloneWithFallback(SSH_URL, HTTPS_URL); + } catch (e) { + console.error('Erro: não foi possível clonar o repositório base-claude.'); + console.error(e.message); + process.exit(1); + } + + try { + fse.ensureDirSync(platformDir); + + var results = []; + + MANAGED_DIRS.forEach(function (dirName) { + var src = path.join(tmpDir, dirName); + var dest = path.join(platformDir, dirName); + + if (!fs.existsSync(src)) { + results.push({ dir: dirName, status: 'não encontrado no repositório' }); + return; + } + + // Full replacement: remove existing dir, copy fresh from source + if (fs.existsSync(dest)) { + fse.removeSync(dest); + } + fse.copySync(src, dest); + results.push({ dir: dirName, status: 'atualizado' }); + }); + + console.log(''); + results.forEach(function (r) { + console.log(' ' + r.dir + '/ — ' + r.status); + }); + console.log(''); + console.log('Atualização concluída.'); + } finally { + cleanupTemp(tmpDir); + } + }); +} + +module.exports = { register: register }; diff --git a/src/commands/update-repo.js b/src/commands/update-repo.js new file mode 100644 index 0000000..3ba97e9 --- /dev/null +++ b/src/commands/update-repo.js @@ -0,0 +1,82 @@ +'use strict'; + +var path = require('path'); +var fs = require('fs'); +var fse = require('fs-extra'); +var { cloneWithFallback, cleanupTemp } = require('../utils/git'); + +var SSH_URL = 'ssh://git@git.davinti.com.br:2222/davinTI/projeto-base.git'; +var HTTPS_URL = 'https://git.davinti.com.br/davinTI/projeto-base.git'; + +// CLAUDE.md files managed by the template — relative paths from repo root +var MANAGED_FILES = [ + 'CLAUDE.md', + 'endpoints/CLAUDE.md', + 'panels/CLAUDE.md', + 'patches/CLAUDE.md', + 'processes/CLAUDE.md', + 'queries/CLAUDE.md', + 'reports/CLAUDE.md', + 'scripts/CLAUDE.md' +]; + +function register(program) { + program + .command('update-repo') + .description('Atualiza os arquivos CLAUDE.md gerenciados neste repositório com a versão mais recente do template') + .action(function () { + var repoDir = process.cwd(); + + if (!fs.existsSync(path.join(repoDir, 'vitruvio.json'))) { + console.error('Erro: nenhum vitruvio.json encontrado no diretório atual.'); + console.error('Execute este comando dentro de um repositório Vitruvio.'); + process.exit(1); + } + + console.log('Buscando template mais recente...'); + + var tmpDir = null; + try { + tmpDir = cloneWithFallback(SSH_URL, HTTPS_URL); + } catch (e) { + console.error('Erro: não foi possível clonar o repositório template.'); + console.error(e.message); + process.exit(1); + } + + try { + var updated = []; + var skipped = []; + + MANAGED_FILES.forEach(function (relPath) { + var src = path.join(tmpDir, relPath); + var dest = path.join(repoDir, relPath); + + if (!fs.existsSync(src)) { + skipped.push(relPath); + return; + } + + fse.ensureDirSync(path.dirname(dest)); + fse.copySync(src, dest, { overwrite: true }); + updated.push(relPath); + }); + + console.log(''); + if (updated.length > 0) { + console.log('Arquivos atualizados (' + updated.length + '):'); + updated.forEach(function (f) { console.log(' ' + f); }); + } + if (skipped.length > 0) { + console.log('Não encontrados no template (' + skipped.length + '):'); + skipped.forEach(function (f) { console.log(' ' + f); }); + } + console.log(''); + console.log('Atualização concluída.'); + } finally { + cleanupTemp(tmpDir); + } + }); +} + +module.exports = { register: register }; diff --git a/src/utils/git.js b/src/utils/git.js new file mode 100644 index 0000000..a06587e --- /dev/null +++ b/src/utils/git.js @@ -0,0 +1,48 @@ +'use strict'; + +var path = require('path'); +var os = require('os'); +var fs = require('fs'); +var { execSync } = require('child_process'); + +/** + * Shallow-clones a git URL into a unique temp directory. + * Returns the path to the cloned directory. + * Throws on failure. + */ +function cloneToTemp(url) { + var tmpBase = os.tmpdir(); + var tmpDir = path.join(tmpBase, 'vitruvio-' + Date.now() + '-' + Math.random().toString(36).slice(2)); + fs.mkdirSync(tmpDir, { recursive: true }); + execSync('git clone --depth 1 ' + url + ' ' + tmpDir, { stdio: 'pipe' }); + return tmpDir; +} + +/** + * Tries SSH clone first; if it fails, tries HTTPS clone. + * Returns the path to the cloned directory. + */ +function cloneWithFallback(sshUrl, httpsUrl) { + try { + return cloneToTemp(sshUrl); + } catch (e) { + console.log('Clone SSH falhou, tentando HTTPS...'); + return cloneToTemp(httpsUrl); + } +} + +/** + * Removes a temporary directory created by cloneToTemp. + */ +function cleanupTemp(tmpDir) { + if (!tmpDir || !tmpDir.includes('vitruvio-')) { + throw new Error('Refusing to delete unexpected path: ' + tmpDir); + } + fs.rmSync(tmpDir, { recursive: true, force: true }); +} + +module.exports = { + cloneToTemp: cloneToTemp, + cloneWithFallback: cloneWithFallback, + cleanupTemp: cleanupTemp +}; diff --git a/src/utils/platform.js b/src/utils/platform.js new file mode 100644 index 0000000..5ab8298 --- /dev/null +++ b/src/utils/platform.js @@ -0,0 +1,22 @@ +'use strict'; + +var path = require('path'); +var os = require('os'); + +/** + * Returns the path to the Vitruvio platform directory for the current OS. + * Linux/macOS: ~/.local/share/vitruvio-platform/ + * Windows: %LOCALAPPDATA%\vitruvio-platform\ + */ +function getPlatformDir() { + if (process.platform === 'win32') { + var localAppData = process.env.LOCALAPPDATA; + if (!localAppData) { + throw new Error('LOCALAPPDATA environment variable is not set.'); + } + return path.join(localAppData, 'vitruvio-platform'); + } + return path.join(os.homedir(), '.local', 'share', 'vitruvio-platform'); +} + +module.exports = { getPlatformDir: getPlatformDir };