Ir para o conteúdo
Backend

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

Marcos Soares
Atualizado em 
13 minutos de leitura
Ilustracao 3D de esteira automatizada com ferramentas luminosas representando automacao de tarefas JavaScript
Ouça este artigo
0:00Ferramentas 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.

Bash
# Instalação com versões explícitas para reprodutibilidade
npm install --save-dev husky@9 lint-staged@15
npx husky init
JSON
// 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"
    ]
  }
}
Bash
# .husky/pre-commit
# O hook chama lint-staged, que filtra só arquivos no staging area.
npx lint-staged

O --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érioChangesetssemantic-release
Modelo de commitArquivo .changeset por PRConventional Commits obrigatório
Controle do devEscolhe tipo de bump e descrição por PRAutomático baseado no prefixo do commit
MonorepoSuporte nativo com workspace protocolRequer plugins extras
Curva de aprendizadoBaixa (comando interativo)Média (configuração de plugins)
Quando usarTimes que querem revisar o changelog antes do publishProjetos 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.

Bash
npm install --save-dev @changesets/cli@2
npx changeset init
TypeScript
// 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.

Bash
npm install --save-dev better-npm-audit@3
JSON
// .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"
    }
  }
}
YAML
# .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 moderate

O --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.

YAML
# .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.

JSON
{
  "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

TypeScript
// 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 ."
  }
}
TypeScript
// 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

Bash
# 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
Bash
# 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 build

O 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.

YAML
# .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: 5

Agrupar 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.

YAML
# 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 CI

Separar 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.

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.

YAML
# .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: true

A 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.

Bash
#!/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çãoCompensa a partir deOverhead de setupManutenção contínua
lint-staged + husky1 dev, 1 repo10 minQuase zero
Changesets2+ devs ou lib publicada20 minBaixa (review de changeset por PR)
better-npm-auditQualquer projeto em produção15 minRevisar exceções a cada 3 meses
Release via CILib publicada no npm30 minAtualizar tokens quando expiram
Dependabot com grupos10+ dependências10 minRevisar PRs agrupados semanalmente
Link checkProjeto com README > 50 linhas5 minZero
Setup scriptProjeto open source com contribuidores externos15 minAtualizar 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.

Marcos Soares

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.