· Oleksandr Oleniuk · Development · 9 min de leitura
A pequena função Bash que tornou o Nx menos irritante

Quem trabalha com monorepos grandes conhece a situação: um programador sabe o que precisa de fazer — arrancar uma API, testar uma biblioteca, executar uma migração — mas não sabe os nomes exatos do projeto e do target. Passar para uma parte menos familiar da base de código implica mais uma volta pelos ficheiros de configuração antes de começar o trabalho propriamente dito.
Foi esse atrito que levou a uma pequena experiência num monorepo Nx com que trabalho. Um seletor de tarefas no IDE ajuda a descobrir o que existe, mas percorrer uma árvore de projetos continua a ser uma pesquisa visual que alguns caracteres escritos poderiam simplificar. Se o Nx Console também ficar lento ou consumir recursos consideráveis, lançar uma tarefa simples começa a parecer desnecessariamente pesado.
Para quem trabalha sobretudo no terminal, há ainda outro compromisso: o Nx Console lança as tarefas no terminal integrado do VS Code. Num fluxo de trabalho organizado em torno de uma sessão tmux separada, os resultados das tarefas passam a aparecer noutro sítio. Manter a descoberta e a execução no mesmo terminal evitaria esse desvio.
O objetivo é simples: os programadores devem conseguir encontrar uma tarefa sem memorizar o espaço de trabalho nem sair do seu ambiente habitual. Bastaram algumas linhas de Bash para experimentar a ideia: escrever, escolher, executar.
A primeira versão cabia numa função de shell
Com nx, jq e fzf disponíveis no PATH, isto bastava para experimentar a partir da raiz do espaço de trabalho:
fnx() {
local project target
project="$(nx show projects | fzf)" || return
target="$(
nx show project "$project" --json |
jq -r '.targets | keys[]' |
fzf
)" || return
nx run "$project:$target"
}
A CLI do Nx fornece a lista de projetos e os respetivos metadados. O primeiro fzf apresenta os projetos. Depois, o jq extrai os nomes dos targets do projeto escolhido, um segundo seletor apresenta esses targets e o nx run executa o selecionado. Cancelar qualquer um dos seletores termina a função.
O Nx já tinha os dados. O fzf já tinha a interface. A função só tinha de os ligar.
Obrigar os programadores a memorizar nomes exatos é uma péssima ideia. Um fragmento — qualquer coisa sobre pagamentos, qualquer coisa sobre migrações — costuma ser mais fácil de recordar. A correspondência aproximada torna esses fragmentos úteis. Alguns caracteres reduzem a lista até a escolha certa ser óbvia, sem ser preciso ler cada entrada à procura dela.
Na primeira versão, parti do princípio de que as ferramentas estavam instaladas e de que estava na raiz do espaço de trabalho. São pressupostos razoáveis para uma função na minha própria shell. Já não são tão razoáveis para um atalho que os colegas também devem conseguir utilizar.
Ninguém memoriza o monorepo inteiro
Os comandos do dia a dia tornam-se automáticos. Os mais difíceis de recordar pertencem a um serviço que outro colega costuma manter, a uma migração necessária duas vezes por mês ou a uma biblioteca em que ninguém mexe há semanas. Saber como funciona um serviço é importante. Memorizar o nome de cada target não devia ser o preço a pagar para trabalhar nele.
Os targets variam de projeto para projeto. Um tem serve, outro tem build e test, e outro disponibiliza um target de migração específico. Uma lista fixa de atalhos fica desatualizada; perguntar ao Nx dá aos programadores as opções disponíveis para esse projeto.
Por exemplo, escrever pay, reconhecer payments-api, depois escrever mig e selecionar migrate. O resultado é nx run payments-api:migrate, executado no terminal atual. O seletor permite descobrir o comando mesmo quando esse projeto não faz parte do trabalho diário.
À medida que uma base de código partilhada cresce, «basta decorar o comando» torna-se uma interface pouco útil. Ler as tarefas disponíveis no espaço de trabalho permite aos programadores encontrá-las sem manter mais uma lista de atalhos.
Depois, build passou a ter mais do que um significado
Escolher um projeto e um target nem sempre chega. Um target build pode ter configurações development e production.
O seletor precisava de lidar com isso sem acrescentar uma terceira pergunta a cada execução:
- Sem configurações com nome: executar o target diretamente.
- Uma configuração com nome: selecioná-la automaticamente.
- Várias configurações com nome: apresentar uma escolha com
fzf.
Esta última escolha produz um comando como nx run my-app:build:production.
Selecionar automaticamente a única configuração disponível foi uma decisão que tomei no wrapper; o nx run, por si só, tem o seu próprio comportamento quanto à configuração predefinida. Uma configuração indicada explicitamente tem prioridade. O seletor não tem motivo para fazer uma pergunta a que o comando já responde.
A minha regra para ferramentas interativas: só perguntar quando há uma decisão a tomar. Uma pergunta com uma única resposta possível é trabalho inútil disfarçado de interface. Carregar mais uma vez em Enter não torna a ferramenta mais útil.
Contar as opções, tratar o caso em que não há nenhuma e perguntar quando a escolha realmente ajuda. Era toda a interface de que isto precisava.
Um atalho útil deve funcionar para além de um único portátil
Uma função num ficheiro de configuração pessoal tem a vida facilitada: um único utilizador que conhece todas as suas particularidades. Partilhá-la com colegas muda as expectativas. Ninguém devia precisar da configuração de shell do autor ou de uma visita guiada só para executar uma tarefa.
O primeiro pressuposto era o simples comando nx. Um espaço de trabalho pode ter o Nx instalado localmente sem um executável global. A ferramenta deve dar preferência a node_modules/.bin/nx dentro do espaço de trabalho, recorrer a nx no PATH como alternativa e explicar o problema se nenhum existir. Preferir o executável local também mantém a versão da CLI alinhada com o repositório.
O segundo pressuposto era a localização. Um programador pode invocar a ferramenta a partir de apps/web/src/, vários diretórios abaixo da raiz do repositório. Ter de voltar à raiz só para abrir um seletor traria o mesmo atrito de volta. Subir pelos diretórios até encontrar o nx.json mais próximo permite à ferramenta localizar o espaço de trabalho por si própria.
Depois há as dependências. O executável usa Bash e precisa de fzf, jq e nx. Se faltarem ferramentas, deve apresentar um erro útil e instruções de instalação, para que um colega consiga resolver os problemas de configuração sem vasculhar o código-fonte. Um seletor de tarefas não deve instalar software silenciosamente por conta de quem o utiliza.
Por fim, o seletor precisa de saber quando sair do caminho. Indicar um projeto deve dispensar essa pergunta. Indicar um target deve dispensar outra. Os argumentos adicionais têm de chegar intactos ao Nx. Quem explora um projeto desconhecido precisa das perguntas; quem repete um comando conhecido deve poder saltá-las.
É este trabalho aborrecido que faz valer a pena partilhar uma pequena ferramenta. O seletor com correspondência aproximada foi a parte fácil; eliminar os motivos para praguejar com ele exigiu mais reflexão.
Uma pequena CLI, com uma pequena função
O resultado é o fuzzy-nx, invocado como fnx. A interface para o dia a dia continua a ser curta:
fnx # Escolher um projeto e um target
fnx my-app # Escolher um target para este projeto
fnx my-app test # Indicar ambos; as configurações continuam a aplicar-se
fnx my-app test --watch # Passar o argumento adicional ao Nx
Substitua my-app e test por nomes do seu espaço de trabalho. O README explica a instalação e as restantes opções; os comandos acima cobrem o fluxo de trabalho habitual.
Executar fnx começa pelo seletor de projetos. Aqui está num espaço de trabalho de exemplo, com feature-products em destaque:

Depois de escolher um projeto, o seletor seguinte apresenta os respetivos targets. A CLI completa também mostra uma pré-visualização da configuração do target em destaque, para dar algum contexto antes de carregar em Enter:

A sua função termina depois de escolher um projeto, um target e uma configuração, devolvendo a execução ao Nx. A gestão da cache, as dependências entre tarefas, o cálculo dos projetos afetados e a orquestração ficam a cargo do Nx. Os programadores já têm uma ferramenta para executar tarefas. Acrescentar outra criaria mais coisas que a equipa teria de compreender e manter.
Se a função original faz tudo o que precisa, mantenha-a. Transformá-la numa CLI tornou-a mais fácil de partilhar ao eliminar pressupostos sobre a configuração de um único programador. Mais código não é, por si só, uma melhoria.
Se isto puder ajudar os programadores da sua equipa, espreite o fuzzy-nx no GitHub e experimente-o num espaço de trabalho partilhado. Sugestões e relatos de problemas são bem-vindos: um target que a ferramenta não trata bem, uma pergunta confusa ou dificuldades de configuração que um colega encontre. Abra uma issue e conte o que aconteceu — esses pormenores ajudam a tornar a ferramenta útil em diferentes repositórios e hábitos de trabalho.
O fzf é uma interface para tudo o que se possa listar
Provavelmente estamos a tirar pouco partido do fzf. A pesquisa de ficheiros e o histórico da shell dão jeito, mas mal começam a explicar a utilidade da ferramenta. Recebe linhas com opções e devolve uma seleção. Nada exige que essas linhas sejam nomes de ficheiros.
O padrão aqui é simples:
Metadados do Nx → jq → fzf → Comando Nx
A mesma abordagem pode servir para selecionar um contentor Docker, um branch Git, um servidor SSH ou uma unidade systemd. Obter as opções disponíveis junto da ferramenta que as gere, apresentar a parte útil e passar a seleção ao comando seguinte.
Mesmo dentro do Nx, há algumas extensões naturais: escolher um projeto para inspecionar com nx show ou selecionar vários projetos para nx run-many. Podem ser adições úteis, mas o nx run continua a ser a grande vantagem no dia a dia: encontrar a tarefa, lançá-la e voltar ao trabalho.
Há muito poucos mecanismos novos aqui. O Nx conhece o espaço de trabalho, o jq extrai os nomes e o fzf trata da interação. Ligá-los torna as tarefas existentes mais fáceis de encontrar para os programadores.
Quando os programadores passam o tempo a percorrer resultados e a copiar nomes para outro comando, há uma pequena oportunidade de melhorar as ferramentas que merece ser explorada. Um incómodo pessoal pode ser um bom ponto de partida para melhorar um fluxo de trabalho partilhado. O computador já tem a lista. Deixemos que trate da pesquisa.



