Saltar al contenido principal

Gestión de herramientas — Homebrew + mise

"Un lugar para cada cosa, y cada cosa en su lugar."

En Wiedii toda herramienta tiene un nivel definido. La arquitectura es de 3 niveles, cada uno con responsabilidad clara:

⚠️ Verificar versiones antes de usar — las versiones de runtimes y paquetes mencionados en esta nota (bun, node, python, uv, etc.) pueden estar desactualizadas. Usar mise ls-remote <herramienta> | tail -5 o brew info <formula> para la versión más reciente. Ver politicas-core.

Homebrew instala herramientas del sistema. mise global gestiona runtimes de lenguaje disponibles en toda la máquina. mise por proyecto fija las versiones exactas de cada repo.


El problema que este estándar resuelve

Sin una política clara de instalación, los entornos se degradan:

  • Dev A tiene bun 1.1.0 global, dev B tiene bun 1.2.0. El mismo comando produce resultados distintos.
  • Un npm install -g instala algo que rompe otra herramienta ya existente.
  • Una máquina nueva requiere recordar 15 comandos distintos de instalación.
  • curl | bash ejecuta código de internet sin revisión, con privilegios de tu usuario.

Este estándar elimina todos esos problemas con tres niveles y una regla por nivel.


Nivel 1 — Homebrew: herramientas del sistema

Herramientas que pertenecen al sistema operativo y no tienen versión por proyecto: CLIs de trabajo, aplicaciones de escritorio, utilidades del sistema.

HerramientaPor qué en Homebrew
miseEl gestor de versiones en sí debe estar en el sistema
git-flow-nextExtiende git — herramienta del sistema
glabCLI de GitLab — herramienta de trabajo
bat, eza, fd, ripgrep...Utilidades de terminal — disponibles en todo el sistema
OrbStack, Visual Studio Code, Microsoft Teams...Aplicaciones de escritorio
brew install mise git-flow-next glab
brew install bat eza fd ripgrep sd starship
brew install orbstack visual-studio-code microsoft-teams

brew install sin --cask: En Homebrew moderno no es necesario especificar --cask explícitamente. Homebrew resuelve automáticamente si el paquete es una formula (CLI/librería) o un cask (app de escritorio). El flag --cask sigue siendo válido pero genera acoplamiento innecesario con la estructura interna del repositorio de Homebrew, que puede cambiar. brew install <nombre> siempre funciona.

Tap Trust — confianza de taps no oficiales

Un tap puede contener fórmulas, casks y comandos externos, que son código Ruby ejecutable (no solo metadatos). Homebrew lo evalúa con los privilegios de tu usuario al resolver dependencias o cargar comandos. Por eso distingue entre taps oficiales (siempre confiables) y no oficiales (requieren confianza explícita).

Homebrew 6.0.0 (11 jun 2026) ya exige confianza explícita para taps no oficiales — dejó de ser una advertencia futura. brew install <formula-corta> desde un tap sin confiar falla hasta confiar el ítem o el tap completo. La excepción es el nombre completo (brew install user/repo/formula): confía solo ese ítem en el mismo comando, sin paso previo — ver el bloque de abajo.

Regla Wiedii: confía en el ítem específico que necesitas (fórmula/cask/comando), no en el tap completo — salvo que administres el tap o dependas mucho de él (p. ej. el tap propio homebrew-wiedii-tools). Un tap de terceros se evalúa con el mismo criterio que instalar software fuera de Homebrew: justificación y, si entra al estándar del equipo, visto bueno del TL.

# ✅ Preferido — confía solo en la fórmula/cask que instalas
brew install user/repo/formula # nombre completo = confía solo en ese ítem
brew install --cask user/repo/cask

# Por nombre corto: confiar el ítem primero
brew tap user/repo
brew trust --formula user/repo/formula # o --cask / --command
brew install formula

# Confiar el tap completo (solo taps propios o de uso intensivo)
brew tap user/repo
brew trust user/repo

Gestionar la confianza:

brew trust # listar lo confiado
brew untrust # listar taps/ítems sin confiar
brew untrust user/repo
brew untrust --formula user/repo/formula

En un Brewfile, un tap confiado se marca con trusted: true:

tap "user/repo", trusted: true

Adelántate al cambio (recomendado): exige la confianza ya con export HOMEBREW_REQUIRE_TAP_TRUST=1. No uses HOMEBREW_NO_REQUIRE_TAP_TRUST=1 — desactiva la protección y se eliminará en una versión futura.

Homebrew nunca instala runtimes de lenguaje en Wiedii:

# ❌ Nunca — los runtimes van en mise
brew install bun
brew install node
brew install python

Nivel 2 — mise global: runtimes de lenguaje

Runtimes de lenguaje disponibles en todo el sistema como versión base o fallback cuando un proyecto no especifica la suya. Se configuran en ~/.config/mise/config.toml.

Config global de Wiedii (~/.config/mise/config.toml):

[tools]
bun = "1.3.14"
node = "24.16.0"
pnpm = "11.6.0"
python = "3.14.6"
uv = "0.11.21"

[settings]
experimental = true # habilita features experimentales (p. ej. [hooks])
lockfile = true # habilita lectura/escritura del lock (se crea con `mise lock`)
all_compile = false # evita instalaciones implícitas por compilación (no compila si no hay binario)

Estos runtimes están disponibles en cualquier directorio, incluso fuera de un proyecto:

cd ~
bun --version # ✅ 1.3.14 (desde mise global)
node --version # ✅ v24.16.0
python --version # ✅ Python 3.14.6

Para actualizar una versión global:

mise use --global bun@1.3.14
mise use --global node@24.16.0

pnpm está disponible globalmente pero los proyectos Wiedii usan bun. pnpm queda como fallback de compatibilidad.


Nivel 3 — mise por proyecto: versiones exactas

Cada repo declara en mise.toml las versiones exactas que necesita. Al entrar al directorio, mise activa esas versiones sobreescribiendo las del nivel global.

# mise.toml en un repo Wiedii
[tools]
bun = "1.3.14" # idéntica a la global, pero fijada explícitamente por reproducibilidad
lefthook = "2.1.9" # solo disponible en este directorio

[hooks]
# instala lefthook tras `mise install` (no dentro de la task); se salta en CI
postinstall = "[ -z "$CI" ] && lefthook install || true"

[tasks.setup]
# Una sola cadena con && (NO array): el hook taplo (reorder_arrays) reordena arrays al commitear.
# NO empieza con `mise trust` (sería circular): el trust va antes → `mise trust && mise run setup`.
run = "mise install --yes && mise lock && pkg=$(find . -maxdepth 2 -name 'package.json' ! -path '*/node_modules/*' | head -1) && { [ -n "$pkg" ] && bun install --cwd "$(dirname "$pkg")" || true; }"
description = "Configura el entorno completo del proyecto"
# Prioridad en acción:
cd proyecto-alpha && bun --version # 1.2.5 (del mise.toml del proyecto)
cd proyecto-beta && bun --version # 1.1.8 (del mise.toml del proyecto)
cd ~ && bun --version # 1.3.14 (del mise global)

Contaminación cero entre proyectos. El proyecto siempre gana sobre el global.


Agregar herramientas a mise: elegir el backend correcto

Cuando una herramienta va en mise.toml, la siguiente pregunta es: ¿qué backend usar?

El registro de mise

mise-versions.jdx.dev es el catálogo oficial de herramientas disponibles en mise con todos sus backends. Antes de declarar cualquier herramienta en un mise.toml, verificar en este registro qué backends tiene disponibles.

Ejemplo: terraform tiene múltiples backends — mise-versions.jdx.dev/tools/terraform. Siempre se elige aqua si está disponible.

Prioridad de backends

Core tools (runtimes): node, bun, python, go, ruby, rust, deno, java, swift, zig, elixir, erlang → nombre corto, sin prefijo (core: es redundante). Para el resto de herramientas, la prioridad de backend (con prefijo obligatorio) es:

PrioridadBackendCuándo usarlo
aquaSiempre que esté disponible. Es el registro curado de mise con checksums verificados.
npmCLI de npm que no tiene sentido en package.json (herramienta global de proyecto, no dependencia).
pipxCLIs de Python que no van en pyproject.toml.
cargoHerramientas Rust no disponibles en aqua.
asdfSolo si ningún otro backend está disponible. Es la opción heredada.
# Ejemplo: terraform usa aqua (backend preferido)
[tools]
terraform = "1.8.0" # aqua por defecto si está en el registro

# Si la herramienta solo existe en cargo:
"cargo:taplo-cli" = "latest"

# Si la herramienta solo existe como paquete npm y no tiene sentido en package.json:
"npm:aws-cdk" = "latest"

📄 Registro completo de herramientas


La regla del package manager nativo

Si una herramienta pertenece al ecosistema del lenguaje del proyecto, va en el gestor de paquetes nativo — no en mise.toml.

El criterio: ¿esta herramienta se instala con bun install, pip install, go get? Si sí, va en el archivo de dependencias del proyecto, no en mise.

HerramientaDónde vaPor qué
prettierpackage.json devDependenciesEs una dep JS — se instala con bun install, se versiona con el proyecto
eslintpackage.json devDependenciesÍdem
typescriptpackage.json devDependenciesÍdem
ruffpyproject.toml o mise.tomlHerramienta Python — ambas opciones válidas
mypypyproject.toml o mise.tomlÍdem
golangci-lintmise.toml ✅ (ver excepción Go)No puede ir en go.mod
lefthookmise.tomlGit hook runner — versión exacta por proyecto, fuera del ecosistema JS/Go/Python
taplomise.tomlFormateador TOML — no pertenece a ningún ecosistema concreto
terraformmise.tomlCLI de infraestructura — sin relación con el lenguaje del proyecto
# ❌ No: prettier en mise.toml
[tools]
prettier = "3.2.5" # prettier pertenece al package.json

# ✅ Correcto: prettier en package.json
# "devDependencies": { "prettier": "^3.2.5" }
# Se instala con: bun install
# Se ejecuta con: bun prettier

La razón es práctica: prettier en package.json significa que todos los devs lo obtienen con bun install, tiene lockfile de bun, se puede ejecutar con bun prettier sin PATH magic, y el CI lo instala en el mismo paso que las demás dependencias.


La excepción de Go

En proyectos Go, go.mod solo registra librerías que se importan en el código. Go es estricto en esto por diseño: si un paquete no tiene un import en el código, go mod tidy lo elimina del go.mod.

Esto significa que las herramientas de desarrollo de Go (linters, generadores de código, herramientas de migración…) no pueden ir en go.mod aunque estén escritas en Go. Son ejecutables, no librerías.

Por eso, todas las CLIs de Go que el proyecto necesita van en mise.toml:

# mise.toml — proyecto Go
[tools]
go = "1.22.0"

# ← Herramientas Go que NO pueden ir en go.mod (son executables, no imports):
golangci-lint = "1.57.2" # linter
migrate = "4.17.0" # golang-migrate — migraciones de DB
mockgen = "0.3.0" # generador de mocks
air = "1.51.0" # live reload para desarrollo

Verificar el backend disponible para cada una antes de agregarla:


La regla de instalación — árbol de decisión

¿Es una herramienta que usaré en cualquier directorio del sistema?

├── SÍ → ¿Es un runtime de lenguaje (bun, node, python, uv…)?
│ ├── SÍ → mise use --global <tool>@<version>
│ │ (en ~/.config/mise/config.toml)
│ └── NO → ¿Existe en Homebrew?
│ ├── SÍ → brew install <herramienta>
│ └── NO → Solicitar autorización al TL
│ → Instalar por método oficial del proveedor
│ → Documentar excepción en el vault

└── NO → Es una herramienta específica de un proyecto

├── ¿Pertenece al ecosistema del lenguaje del proyecto?
│ (prettier/eslint en JS, ruff/mypy en Python…)
│ │
│ ├── SÍ → ¿Es un proyecto Go?
│ │ ├── SÍ → mise.toml (go.mod no acepta executables)
│ │ └── NO → package.json / pyproject.toml / etc.
│ │ (bun install / uv sync / etc.)
│ │
│ └── NO → mise.toml del repo
│ └── Verificar backend en mise-versions.jdx.dev
│ (prioridad: aqua > npm > pipx > cargo > asdf)

└── mise install --yes (o mise run setup)

La regla se extiende a todo — incluyendo GUI apps

Homebrew gestiona todo lo que entra en el sistema. Esto incluye navegadores, editores, clientes de comunicación y aplicaciones de escritorio.

Nunca se descarga un instalador .dmg, .pkg o .zip de una página web en Wiedii.

# ✅ Correcto — navegadores, editores y apps por brew
brew install brave-browser
brew install visual-studio-code
brew install microsoft-teams

# ❌ Nunca en Wiedii
# Ir a chrome.com → descargar .dmg → arrastrar a /Applications
# Ir a code.visualstudio.com → descargar .zip → instalar manualmente

brew bundle dump genera un Brewfile para replicar el entorno completo en una máquina nueva:

brew bundle dump --file=~/Brewfile --force # snapshot
brew bundle install --file=~/Brewfile # restaurar en máquina nueva

Apps del App Store — mas (solo las que no están en Homebrew)

Para las pocas apps que solo existen en el Mac App Store (no en Homebrew), mas (Mac App Store CLI, brew install mas) las integra al flujo del Brewfile. Es una herramienta de gestión y automatización (replicar y mantener un entorno que ya tienes), no de descubrimiento: sigue siendo Homebrew-first, mas es solo para apps exclusivas del App Store.

  • mas list — apps del App Store instaladas · mas outdated — con actualización pendiente · mas upgrade — actualiza todas desde terminal.
  • En el Brewfile: mas "NombreApp", id: 1234567890 (replica el entorno sin clics).

Matiz clave — get vs install (causa de confusión frecuente):

  • mas get <id> (alias mas purchase) — adquiere e instala por primera vez, incluso en una cuenta fresca. Es lo que sirve para apps nuevas (gratuitas o de pago) desde la terminal.
  • mas install <id>solo re-descarga apps ya adquiridas con tu Apple ID; falla en apps nunca adquiridas (Error: Redownload Unavailable...).
  • brew bundle usa mas install por defecto → para apps no adquiridas antes falla; la recomendación actual (Homebrew #21559) es usar mas get. Requiere sesión iniciada en el App Store.

Conclusión: mas sí permite instalar desde cero, pero con mas get, no con mas install (que es solo para re-descargar lo ya adquirido). Adquirir la app la primera vez ya no obliga a abrir el App Store gráfico.


✅ Correcto vs ❌ Incorrecto

# ✅ CLI del sistema → Homebrew
brew install glab git-flow-next jq bat eza

# ✅ Aplicación de escritorio → Homebrew (resuelve formula o cask automáticamente)
brew install orbstack visual-studio-code microsoft-teams

# ✅ Runtime de lenguaje global → mise global
mise use --global bun@1.3.14
mise use --global node@24.16.0

# ✅ Herramienta de proyecto → mise.toml del repo
# (en mise.toml) bun = "1.3.14"
mise run setup

# ❌ Nunca curl | bash (salvo Homebrew en sí)
curl https://bun.sh/install | bash
curl https://mise.jdx.dev/install.sh | sh

# ❌ Nunca instalaciones globales de npm/bun
npm install -g typescript
bun install -g eslint

# ❌ Nunca runtimes de lenguaje por brew
brew install bun
brew install node

# ❌ Nunca binarios descargados a mano ni instaladores de webs (sin autorización del TL)
wget https://ejemplo.com/herramienta.tar.gz
# chrome.com → .dmg, teams.microsoft.com → .pkg, etc.

# ⚠️ Excepción — si la herramienta NO existe en Homebrew:
# Solicitar autorización al TL → documentar en el vault → instalar
# Ver sección "Excepciones documentadas" más abajo

Excepciones documentadas

Excepción 1 — Bootstrap de Homebrew: Homebrew mismo se instala con curl | bash porque no hay alternativa en macOS. Es el único curl | bash aceptado. Una vez instalado, todo lo demás va por sus 3 niveles.

Excepción 2 — Herramienta no disponible en Homebrew: Si una herramienta genuinamente no existe en Homebrew (ni como formula ni como cask), puede instalarse por el método que el proveedor oficial indique — pero únicamente con autorización explícita del TL antes de proceder.

El proceso:

  1. Verificar que la herramienta no está en Homebrew: brew search <herramienta> + buscar en formulae.brew.sh
  2. Solicitar autorización al TL con justificación: por qué se necesita, de dónde se instala, qué permisos requiere
  3. El TL autoriza y documenta la excepción en la nota de la herramienta dentro del vault
  4. La instalación queda registrada en el CLAUDE.md o en la nota correspondiente del proyecto

Sin autorización del TL, la política Homebrew-first aplica sin excepción. La duda es siempre razón para preguntar antes de instalar.


Por qué no npm install -g ni bun install -g

  • Quedan fuera del control de versiones — nadie sabe qué hay instalado ni por qué.
  • Pueden colisionar con otras herramientas globales.
  • No tienen lockfile — cada dev acaba con versiones distintas sin saberlo.
  • No se desinstalan limpiamente.

La alternativa correcta siempre es mise use --global (para runtimes) o declarar en mise.toml del proyecto.


Beneficios del modelo

Reproducibilidad: mise run setup en una máquina nueva produce exactamente el mismo entorno. Sin pasos manuales extra.

Prioridad clara: proyecto > global > sistema. Nunca hay ambigüedad sobre qué versión se usa.

Limpieza: brew list muestra herramientas del sistema. mise ls muestra runtimes activos. Inventario completo y auditable.

Seguridad: Homebrew y mise descargan con checksums verificados. curl | bash ejecuta código arbitrario sin verificación.

Onboarding predecible: brew install mise git-flow-next glab → configurar git y SSH → copiar ~/.config/mise/config.toml → clonar repo → mise run setup. Sin ambigüedad.


Referencias oficiales

mise

Homebrew


Documentos relacionados

  • mise — nota completa: bondades, features y comandos
  • guia-nuevo-dev — aplica este estándar paso a paso
  • mise-tooling — política detallada de mise.toml y comandos
  • KISS — el principio que inspira este estándar