Ferramentas Open Source que Automatizam o Trabalho Chato do Seu Dia a Dia em JavaScript

Conteúdo técnico toda semana
Receba artigos sobre arquitetura, padrões de projeto e engenharia de software. Direto no seu e-mail, sem enrolação.
Sem spam. Cancele a qualquer momento com 1 clique.
Neste artigo
O problema não é falta de ferramenta, é excesso de trabalho manual disfarçado de processo
Toda equipe que trabalha com JavaScript acumula tarefas manuais que ninguém automatiza porque "leva cinco minutos". Atualizar changelog antes do release. Verificar se alguma dependência tem CVE. Rodar lint só quando alguém lembra. Criar tag de versão copiando o número do package.json. Cada uma dessas tarefas leva cinco minutos. Multiplicadas por semana, por repositório, por pessoa, consomem horas que poderiam ir para código que importa.
Este post cobre oito automações concretas usando ferramentas open source, com scripts copy-paste ready, comparações entre alternativas e os anti-patterns que transformam automação em mais uma fonte de atrito. O foco é JavaScript/TypeScript, mas vários workflows se aplicam a qualquer stack que rode em CI.
Automação 1: Lint e formatação no commit, não no PR
Rodar lint na CI é o mínimo. Rodar lint antes do commit evita que o PR sequer chegue com ruído. A combinação lint-staged + husky resolve isso com zero overhead para quem já tem ESLint e Prettier configurados.
# Instalação com versões explícitas para reprodutibilidade
npm install --save-dev husky@9 lint-staged@15
npx husky init// package.json — seção lint-staged
// Roda Prettier apenas nos arquivos staged, não no projeto inteiro.
// Isso mantém o hook rápido mesmo em monorepos com milhares de arquivos.
{
"lint-staged": {
"*.{ts,tsx,js,jsx}": [
"eslint --fix --max-warnings=0",
"prettier --write"
],
"*.{json,md,yaml}": [
"prettier --write"
]
}
}# .husky/pre-commit
# O hook chama lint-staged, que filtra só arquivos no staging area.
npx lint-stagedO --max-warnings=0 é intencional: warnings que ninguém corrige são ruído que treina o time a ignorar alertas. Se o warning não importa, desabilite a regra. Se importa, trate como erro.
Para quem quer entender como closures e escopo afetam regras de lint mais avançadas, vale ler Coerção, Closures e Protótipos: As Mecânicas do JavaScript que Você Usa Sem Entender.
Automação 2: Changelog e versionamento com Changesets
Gerar changelog manualmente é trabalho que escala mal. Duas abordagens dominam o ecossistema:
| Critério | Changesets | semantic-release |
|---|---|---|
| Modelo de commit | Arquivo .changeset por PR | Conventional Commits obrigatório |
| Controle do dev | Escolhe tipo de bump e descrição por PR | Automático baseado no prefixo do commit |
| Monorepo | Suporte nativo com workspace protocol | Requer plugins extras |
| Curva de aprendizado | Baixa (comando interativo) | Média (configuração de plugins) |
| Quando usar | Times que querem revisar o changelog antes do publish | Projetos solo ou libs com release contínuo |
Para a maioria dos projetos com mais de um contribuidor, Changesets oferece mais controle sem sacrificar automação.
npm install --save-dev @changesets/cli@2
npx changeset init// scripts/validate-changeset.ts
// Roda na CI para garantir que todo PR que toca código inclua um changeset.
// Sem isso, PRs passam sem entrada no changelog e o histórico fica incompleto.
import { execSync } from "node:child_process";
import { readdirSync } from "node:fs";
import { join } from "node:path";
const changesetDir = join(process.cwd(), ".changeset");
function hasNewChangeset(): boolean {
const mainBranch = "origin/main";
const diffOutput = execSync(
`git diff --name-only ${mainBranch}...HEAD`,
{ encoding: "utf-8" }
);
const changedFiles = diffOutput.split("\n").filter(Boolean);
return changedFiles.some(
(file) => file.startsWith(".changeset/") && file.endsWith(".md")
);
}
function hasCodeChanges(): boolean {
const mainBranch = "origin/main";
const diffOutput = execSync(
`git diff --name-only ${mainBranch}...HEAD`,
{ encoding: "utf-8" }
);
const codeExtensions = [".ts", ".tsx", ".js", ".jsx"];
return diffOutput
.split("\n")
.filter(Boolean)
.some((file) => codeExtensions.some((ext) => file.endsWith(ext)));
}
if (hasCodeChanges() && !hasNewChangeset()) {
console.error(
"Este PR altera código mas não inclui um changeset. Rode: npx changeset"
);
process.exit(1);
}
console.log("Changeset validado.");Integre esse script no workflow de CI como step obrigatório. O resultado: nenhum PR com mudança de código entra sem descrição de changelog.
Automação 3: Auditoria de dependências que bloqueia de verdade
npm audit gera ruído demais. Muitas vulnerabilidades reportadas estão em dependências de desenvolvimento que nunca rodam em produção. O better-npm-audit filtra por severidade e permite exceções documentadas.
npm install --save-dev better-npm-audit@3// .nsprc — arquivo de exceções com justificativa
// Cada exceção exige data de expiração. Sem isso, exceções viram permanentes.
{
"exceptions": {
"1096012": {
"reason": "Afeta apenas dev dependency (storybook), sem impacto em runtime",
"expiry": "2025-09-01"
}
}
}# .github/workflows/audit.yml
name: Dependency Audit
on:
schedule:
- cron: "0 8 * * 1" # Segunda-feira às 8h UTC
pull_request:
paths:
- "package-lock.json"
- "package.json"
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: "npm"
- run: npm ci
- run: npx better-npm-audit audit --level moderateO --level moderate ignora vulnerabilidades de severidade low. Se o seu projeto lida com dados sensíveis (auth, pagamento), use --level low e aceite o ruído extra.
Quem mantém APIs em produção e quer entender o impacto de dependências vulneráveis no runtime pode consultar 5 Lacunas Críticas que Todo Dev JavaScript Ignora em APIs, npm e Open Source.
Automação 4: Release automatizado com GitHub Actions
Combinar Changesets com GitHub Actions elimina o processo manual de bump de versão, geração de tag e publicação no npm.
# .github/workflows/release.yml
name: Release
on:
push:
branches: [main]
# concurrency evita que dois pushes simultâneos publiquem versões conflitantes
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false
jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Changesets precisa do histórico completo para calcular diff
- uses: actions/setup-node@v4
with:
node-version: 22
cache: "npm"
registry-url: "https://registry.npmjs.org"
- run: npm ci
- uses: changesets/action@v1
with:
publish: npm run release
title: "chore: version packages"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}O fetch-depth: 0 não é opcional: sem histórico completo, o Changesets não consegue calcular quais pacotes mudaram desde a última release. Esse detalhe causa falhas silenciosas em monorepos.
Para quem já tem CI na Vercel e quer entender como esse workflow se integra, vale conferir Como configurar CI/CD para Next.js na Vercel com GitHub Actions.
Automação 5: Scripts npm como interface unificada
Todo repositório deveria expor suas operações como scripts no package.json. Isso elimina a necessidade de documentar comandos longos e garante que CI e desenvolvimento local usem os mesmos comandos.
{
"scripts": {
"dev": "tsx watch src/server.ts",
"build": "tsc --project tsconfig.build.json",
"test": "vitest run",
"test:watch": "vitest",
"lint": "eslint . --max-warnings=0",
"format": "prettier --write .",
"format:check": "prettier --check .",
"typecheck": "tsc --noEmit",
"validate": "npm run typecheck && npm run lint && npm run test",
"release": "changeset publish",
"prepare": "husky"
}
}O script validate é o que a CI roda. É o mesmo que um dev roda localmente antes de abrir PR. Uma interface, dois contextos. Se npm run validate passa local, passa na CI. Se não passa, o problema é ambiental, não de código.
O que NÃO fazer: anti-patterns de automação
Anti-pattern 1: Hook de pre-commit que roda o projeto inteiro
// ERRADO — roda ESLint em todos os arquivos a cada commit
// Em um projeto com 500+ arquivos, esse hook leva 15-30 segundos.
// Devs começam a usar --no-verify e o hook vira decoração.
// package.json
{
"scripts": {
"pre-commit": "eslint . && prettier --check ."
}
}// CORRETO — lint-staged roda apenas nos arquivos que mudaram
// Hook executa em 1-3 segundos mesmo em projetos grandes.
{
"lint-staged": {
"*.{ts,tsx}": ["eslint --fix --max-warnings=0", "prettier --write"]
}
}A diferença entre 2 segundos e 25 segundos no hook decide se o time mantém a automação ligada ou desabilita com git commit --no-verify.
Anti-pattern 2: Automação sem feedback claro de erro
# ERRADO — script que falha silenciosamente
npm run build && npm run test && echo "OK"
# Se build falha, test não roda, mas a mensagem de erro se perde no output do build# CORRETO — cada step com identificação clara
set -euo pipefail
echo "=== Typecheck ==="
npm run typecheck
echo "=== Lint ==="
npm run lint
echo "=== Tests ==="
npm run test
echo "=== Build ==="
npm run buildO set -euo pipefail garante que o script pare no primeiro erro. Sem isso, um npm run lint que falha não impede o build de rodar, e o dev vê o erro do build em vez do erro do lint.
Anti-pattern 3: Dependabot sem filtro de review
Habilitar Dependabot sem configuração gera dezenas de PRs por semana. A maioria é bump de patch em dependências de desenvolvimento que não afetam o bundle de produção. O time para de revisar, e o PR com CVE real se perde no meio.
# .github/dependabot.yml — configuração que reduz ruído
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
# Agrupa bumps de patch em um único PR por semana
groups:
dev-dependencies:
dependency-type: "development"
update-types: ["patch", "minor"]
production-minor:
dependency-type: "production"
update-types: ["minor", "patch"]
# PRs de major ficam individuais para review dedicado
ignore:
- dependency-name: "*"
update-types: ["version-update:semver-major"]
open-pull-requests-limit: 5Agrupar dependências de desenvolvimento em um PR semanal reduz o número de PRs de 20+ para 2-3. Major updates ficam de fora do agrupamento porque exigem atenção individual.
Automação 6: Type-checking como gate de CI separado
Muitos projetos rodam tsc como parte do build e consideram suficiente. O problema: erros de tipo se misturam com erros de compilação, e o feedback chega tarde. Um step separado de typecheck na CI dá feedback mais rápido e mais claro.
# Dentro do job de validação
- name: Typecheck
run: npx tsc --noEmit --pretty
# --noEmit garante que não gera arquivos de output
# --pretty formata erros com cores e contexto no log da CISeparar typecheck do build permite que ele rode em paralelo com lint e testes. Em projetos com build de 60+ segundos, isso economiza tempo real no ciclo de feedback.
Quem quer aprofundar em design patterns que aproveitam o sistema de tipos do TypeScript encontra exemplos de como tipos bem definidos reduzem a necessidade de testes defensivos.
Automação 7: Verificação de links mortos em documentação
Projetos open source com README extenso acumulam links quebrados. O lychee é uma CLI escrita em Rust que verifica links em Markdown, HTML e texto puro.
# .github/workflows/links.yml
name: Link Check
on:
schedule:
- cron: "0 6 * * 3" # Quarta-feira às 6h UTC
pull_request:
paths:
- "**/*.md"
jobs:
links:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: lycheeverse/lychee-action@v2
with:
args: >-
--verbose
--no-progress
--accept 200,204,301,302
--exclude-mail
"**/*.md"
fail: trueA flag --exclude-mail evita falsos positivos com endereços de email. O --accept inclui redirects (301, 302) como válidos, porque muitos sites legítimos redirecionam.
Automação 8: Script de setup do repositório para novos contribuidores
Projetos open source perdem contribuidores no onboarding. Um script setup.sh que valida pré-requisitos e configura o ambiente elimina a maior barreira de entrada.
#!/usr/bin/env bash
# scripts/setup.sh — roda uma vez ao clonar o repo
set -euo pipefail
REQUIRED_NODE="22"
CURRENT_NODE=$(node --version | cut -d'.' -f1 | tr -d 'v')
if [ "$CURRENT_NODE" -lt "$REQUIRED_NODE" ]; then
echo "Node.js $REQUIRED_NODE+ é necessário. Versão atual: $(node --version)"
echo "Use nvm: nvm install $REQUIRED_NODE && nvm use $REQUIRED_NODE"
exit 1
fi
echo "=== Instalando dependências ==="
npm ci
echo "=== Configurando hooks ==="
npm run prepare
echo "=== Verificando ambiente ==="
npm run validate
echo ""
echo "Setup completo. Rode 'npm run dev' para iniciar."Documentar npm run validate como o comando canônico de verificação cria uma linguagem comum entre contribuidores, CI e review. Quem quiser entender como essa abordagem se conecta a uma arquitetura de projeto real, o post sobre a stack do blog detalha as decisões.
Matriz de decisão: quando cada automação compensa
| Automação | Compensa a partir de | Overhead de setup | Manutenção contínua |
|---|---|---|---|
| lint-staged + husky | 1 dev, 1 repo | 10 min | Quase zero |
| Changesets | 2+ devs ou lib publicada | 20 min | Baixa (review de changeset por PR) |
| better-npm-audit | Qualquer projeto em produção | 15 min | Revisar exceções a cada 3 meses |
| Release via CI | Lib publicada no npm | 30 min | Atualizar tokens quando expiram |
| Dependabot com grupos | 10+ dependências | 10 min | Revisar PRs agrupados semanalmente |
| Link check | Projeto com README > 50 linhas | 5 min | Zero |
| Setup script | Projeto open source com contribuidores externos | 15 min | Atualizar quando pré-requisitos mudam |
Se o seu projeto é solo e privado, lint-staged e audit já cobrem 80% do valor. Se é open source com contribuidores, as sete automações juntas levam menos de duas horas para configurar e economizam dezenas de horas por trimestre.
Para quem quer conectar essas automações com práticas de resiliência no código de fetch e integração, a automação de audit é particularmente relevante: dependências de HTTP client são vetor frequente de CVEs.
FAQ
Lint-staged funciona com pnpm e yarn?
Funciona. O lint-staged não depende do package manager: ele recebe a lista de arquivos staged do git e executa os comandos configurados. O husky v9 também é agnóstico. A única diferença é o comando de instalação (pnpm add -D ou yarn add -D).
Changesets ou Conventional Commits: qual escolher para um projeto solo?
Para projeto solo com release contínuo (merge na main publica automaticamente), semantic-release com Conventional Commits é mais simples: não exige criar arquivo de changeset por PR. Para projetos com release planejado (você decide quando publicar), Changesets dá mais controle.
O Dependabot com agrupamento não esconde breaking changes em dependências de produção? O agrupamento configurado no exemplo só junta patch e minor. Major updates ficam em PRs individuais. Patch e minor seguem semver: não deveriam conter breaking changes. Se uma dependência não respeita semver, o problema é dela, e a solução é pinnar a versão ou trocar de lib.
Vale a pena rodar audit na CI se já uso Dependabot?
São complementares. Dependabot abre PR para atualizar. O audit na CI bloqueia deploy se uma vulnerabilidade conhecida entrar por outro caminho (alguém fez npm install local e commitou o lock com versão vulnerável). Um não substitui o outro.
Como evitar que hooks de pre-commit fiquem lentos em monorepos?
Configure o lint-staged para rodar lint apenas nos pacotes que contêm arquivos alterados. Ferramentas como turbo e nx oferecem filtro por pacotes afetados. Se o hook passar de 5 segundos, devs vão desabilitar. Meça o tempo do hook e trate como SLA.
Automação é código: trate como tal
Cada workflow YAML, cada script de setup, cada configuração de lint-staged é código que precisa de review, versionamento e manutenção. A diferença entre automação que funciona e automação que o time ignora não é a ferramenta: é o tempo de execução do hook, a clareza da mensagem de erro e a facilidade de atualizar quando o projeto muda.
Se um hook leva mais de 5 segundos, será desabilitado. Se um workflow de CI falha com mensagem genérica, será ignorado. Se o setup de um projeto open source exige 12 passos manuais, contribuidores desistem no passo 4. Automação que não respeita o tempo do dev não é produtividade: é burocracia com nome bonito.

Escrito por
Marcos Soares
Fullstack Developer · CEO da Agência Poti
Fullstack Developer e CEO da Agência Poti. Mais de 20 anos construindo arquiteturas cloud-native com React, Next.js e sistemas distribuídos. Parceiro comercial do estúdio iellou design. Fundador do Vivo de Código.
Comentários
Participe da discussão
Seja o primeiro a comentar!
Continue Aprofundando
Conteúdo técnico toda semana
Receba artigos sobre arquitetura, padrões de projeto e engenharia de software. Direto no seu e-mail, sem enrolação.
Sem spam. Cancele a qualquer momento com 1 clique.


