Uma das maiores vantagens do Node.js não é o que ele faz sozinho — é o que a comunidade construiu em cima dele. O NPM (Node Package Manager) é o maior repositório de código aberto do mundo, com mais de 2 milhões de pacotes disponíveis. Autenticação, envio de email, validação de dados, geração de PDFs, conexão com bancos de dados — existe um pacote para tudo.
Neste artigo você vai aprender a gerenciar dependências com NPM, entender o package.json a fundo, trabalhar com scripts, e adotar boas práticas que fazem diferença em projetos reais.
O que é o NPM
O NPM tem três partes:
1. Registro (registry)
Site: https://npmjs.com
Repositório online com todos os pacotes publicados
2. CLI (command line interface)
Ferramenta de linha de comando instalada com o Node
Comando: npm
3. package.json
Arquivo que descreve seu projeto e suas dependências
Iniciando um projeto
# Cria o package.json interativamente
npm init
# Cria com valores padrão (recomendado para começar rápido)
npm init -y
O package.json gerado:
{
"name": "meu-projeto",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
Instalando pacotes
# Instala e adiciona em "dependencies" (produção)
npm install express
npm install express mongoose dotenv
# Abreviação
npm i express
# Instala e adiciona em "devDependencies" (apenas desenvolvimento)
npm install --save-dev nodemon jest eslint
npm i -D nodemon jest
# Instala globalmente (disponível em qualquer projeto)
npm install -g nodemon
npm install -g typescript
# Instala versão específica
npm install express@4.18.2
# Instala a mais recente de uma versão maior
npm install express@4
# Instala de um repositório GitHub
npm install github:usuario/repositorio
O package.json em profundidade
{
"name": "api-clientes",
"version": "2.1.0",
"description": "API REST para gerenciamento de clientes",
"main": "src/index.js",
"type": "module",
"scripts": {
"start": "node src/index.js",
"dev": "nodemon src/index.js",
"test": "jest --coverage",
"test:watch": "jest --watch",
"lint": "eslint src/",
"lint:fix": "eslint src/ --fix",
"build": "tsc",
"db:migrate": "node src/db/migrate.js",
"db:seed": "node src/db/seed.js"
},
"dependencies": {
"express": "^4.18.2",
"mongoose": "^7.6.0",
"dotenv": "^16.3.1",
"bcrypt": "^5.1.1",
"jsonwebtoken": "^9.0.2",
"zod": "^3.22.4"
},
"devDependencies": {
"nodemon": "^3.0.2",
"jest": "^29.7.0",
"eslint": "^8.54.0",
"@types/express": "^4.17.21"
},
"engines": {
"node": ">=18.0.0",
"npm": ">=9.0.0"
},
"keywords": ["api", "rest", "express", "mongodb"],
"author": "Ricardo Matos <ricardo@email.com>",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/usuario/api-clientes"
}
}
Versionamento semântico — SemVer
Entender os números de versão é essencial para evitar quebras inesperadas:
MAJOR.MINOR.PATCH
2 . 1 . 0
MAJOR → quebra compatibilidade com versões anteriores
MINOR → adiciona funcionalidade mantendo compatibilidade
PATCH → correção de bug, sem novas funcionalidades
Os prefixos no package.json:
{
"dependencies": {
"express": "4.18.2", // exatamente esta versão
"express": "~4.18.2", // >= 4.18.2, < 4.19.0 (só patches)
"express": "^4.18.2", // >= 4.18.2, < 5.0.0 (minor e patches)
"express": "*", // qualquer versão (perigoso!)
"express": ">=4.0.0", // qualquer versão >= 4
"express": "4.x" // qualquer 4.x.x
}
}
Na prática, ^ (caret) é o padrão — permite updates de minor e patch, mas não de major.
O package-lock.json — reprodutibilidade garantida
# Quando você roda npm install pela primeira vez,
# o NPM cria o package-lock.json
# Este arquivo registra a versão EXATA de cada pacote
# e de cada dependência das dependências (árvore completa)
// package-lock.json (trecho)
{
"name": "meu-projeto",
"version": "1.0.0",
"lockfileVersion": 3,
"packages": {
"node_modules/express": {
"version": "4.18.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
"integrity": "sha512-...",
"dependencies": {
"accepts": "~1.3.8",
"body-parser": "1.20.1"
}
}
}
}
# Instala as versões EXATAS do package-lock.json
# Use em produção e CI/CD para garantir reprodutibilidade
npm ci
# vs npm install → pode atualizar dentro das regras do ^
Regra de ouro: sempre commite o package-lock.json no Git. Nunca commite node_modules.
O node_modules e o .gitignore
# O node_modules pode ter centenas de MB
# NUNCA commite no Git
# .gitignore
node_modules/
.env
.env.local
dist/
build/
*.log
# Para restaurar os node_modules em outro computador:
git clone https://github.com/usuario/projeto
cd projeto
npm install # lê o package.json e restaura tudo
Scripts NPM — automatizando tarefas
Os scripts são um dos recursos mais poderosos do NPM:
{
"scripts": {
"start": "node src/index.js",
"dev": "nodemon src/index.js --watch src",
"test": "jest",
"test:coverage": "jest --coverage",
"lint": "eslint .",
"format": "prettier --write .",
"clean": "rm -rf dist node_modules",
"build": "npm run lint && npm run test && node build.js",
"prepare": "npm run build",
"postinstall": "node scripts/setup.js"
}
}
# Rodar scripts
npm start # convenção para produção
npm test # convenção para testes
npm run dev # scripts customizados precisam de "run"
npm run lint
npm run build
# Hooks automáticos — executam antes/depois
# pre[script] → executa ANTES do script
# post[script] → executa DEPOIS do script
# npm run build → executa: prebuild → build → postbuild
Pacotes essenciais — os que você vai usar sempre
Desenvolvimento:
# Reinicia o servidor automaticamente ao salvar arquivos
npm i -D nodemon
# Linter — encontra problemas no código
npm i -D eslint
# Formatador — padroniza estilo de código
npm i -D prettier
# Framework de testes
npm i -D jest
Produção:
# Framework web
npm i express
# Variáveis de ambiente
npm i dotenv
# Validação de dados
npm i zod
# Hash de senhas
npm i bcrypt
# JWT (autenticação)
npm i jsonwebtoken
# ORM para MongoDB
npm i mongoose
# ORM para SQL
npm i prisma
# Requisições HTTP (alternativa ao fetch nativo)
npm i axios
# Datas e horas
npm i date-fns
dotenv — variáveis de ambiente
Nunca coloque senhas, tokens ou URLs de banco de dados diretamente no código:
# Instalar
npm i dotenv
# .env (nunca commite este arquivo!)
PORT=3000
NODE_ENV=development
DATABASE_URL=mongodb://localhost:27017/meuapp
JWT_SECRET=uma-string-secreta-muito-longa-e-aleatoria
EMAIL_HOST=smtp.gmail.com
EMAIL_USER=meu@email.com
EMAIL_PASS=senha-do-email
// No início da aplicação — antes de tudo
require("dotenv").config();
// ou com ES Modules:
import "dotenv/config";
// Agora process.env tem as variáveis do .env
const porta = process.env.PORT || 3000;
const dbUrl = process.env.DATABASE_URL;
const secret = process.env.JWT_SECRET;
if (!dbUrl) {
console.error("❌ DATABASE_URL não definida!");
process.exit(1);
}
console.log(`Rodando em modo: ${process.env.NODE_ENV}`);
# .env.example — versão sem valores reais, ESTE sim vai no Git
PORT=3000
NODE_ENV=development
DATABASE_URL=
JWT_SECRET=
EMAIL_HOST=
EMAIL_USER=
EMAIL_PASS=
nodemon — desenvolvimento ágil
Sem nodemon, você precisaria parar e reiniciar o servidor manualmente a cada mudança:
Antes de instalar, saiba que o Node já faz isso sozinho. Desde a versão 18.11 existe a flag --watch, estável a partir do Node 20, e para o caso comum ela dispensa a dependência por completo:
// package.json — sem instalar nada
{
"scripts": {
"dev": "node --watch src/index.js",
// --watch-path limita o que é observado; sem ele, o Node segue os
// arquivos que o seu código realmente importa, o que já costuma bastar
"dev:src": "node --watch-path=./src src/index.js",
// e este recarrega também o .env a cada mudança (Node 20+)
"dev:env": "node --watch --env-file=.env src/index.js"
}
}
O nodemon continua útil quando você precisa de mais controle — observar extensões específicas, ignorar pastas, aplicar um atraso antes de reiniciar, executar um comando que não é node. Para node src/index.js e nada mais, o nativo basta.
⚠️ Windows com WSL, e Docker com volume montado: se o projeto está numa pasta do Windows acessada pelo WSL — qualquer caminho sob /mnt/c, /mnt/d — o watch simplesmente não dispara. O motivo é que a notificação de mudança de arquivo no Linux usa o inotify, e ele não atravessa a fronteira entre o sistema de arquivos do Windows e o do Linux: você salva, nada acontece, e a impressão é de que a ferramenta está quebrada. A saída é trocar a detecção por evento pela verificação periódica, o chamado polling:
# -L é o atalho de --legacy-watch: o nodemon passa a CONSULTAR os arquivos
# em intervalos, em vez de esperar um evento que nunca chega.
nodemon -L src/index.js
# no package.json, para o time inteiro:
# "dev": "nodemon -L src/index.js"
# ou pelo nodemon.json, que é mais limpo:
# { "legacyWatch": true, "pollingInterval": 1000 }
# O mesmo vale em Docker quando o código vem de um volume do host:
# CHOKIDAR_USEPOLLING=true npm run dev
Duas ressalvas que valem o parágrafo. A primeira é que o node --watch sofre do mesmo problema e não tem equivalente ao -L — nesse ambiente específico, o nodemon ainda é a resposta. A segunda é que o polling custa CPU, porque varre os arquivos de tempo em tempo; num projeto grande isso aparece no ventilador. A solução de fundo, quando possível, é manter o projeto dentro do sistema de arquivos do Linux (algo como ~/projetos, e não /mnt/d/projetos) — além de o watch voltar a funcionar por evento, a leitura de arquivo fica várias vezes mais rápida, o que o npm install agradece.
npm i -D nodemon
// package.json
{
"scripts": {
"dev": "nodemon src/index.js"
}
}
// nodemon.json (configuração opcional)
{
"watch": ["src"],
"ext": "js,json",
"ignore": ["src/tests/**"],
"delay": 500,
"env": {
"NODE_ENV": "development"
}
}
npm run dev
# [nodemon] watching path(s): src/**/*
# [nodemon] starting `node src/index.js`
# Servidor rodando na porta 3000
# (você salva um arquivo...)
# [nodemon] restarting due to changes...
# [nodemon] starting `node src/index.js`
# Servidor rodando na porta 3000
Comandos NPM essenciais
# ── Instalação ───────────────────────────────────
npm install # instala tudo do package.json
npm ci # instala versões exatas do lock
npm i pacote # instala e salva em dependencies
npm i -D pacote # instala e salva em devDependencies
npm i -g pacote # instala globalmente
# ── Remoção ──────────────────────────────────────
npm uninstall pacote # remove e atualiza package.json
npm uninstall -g pacote # remove global
# ── Atualização ──────────────────────────────────
npm update # atualiza tudo (dentro do semver)
npm update pacote # atualiza pacote específico
npm outdated # lista pacotes desatualizados
# ── Informações ──────────────────────────────────
npm list # lista dependências instaladas
npm list --depth=0 # apenas nível 1 (sem transitivas)
npm info pacote # informações do pacote no registry
npm search termo # busca no registry
# ── Auditoria de segurança ────────────────────────
npm audit # verifica vulnerabilidades
npm audit fix # tenta corrigir automaticamente
# ── Cache ─────────────────────────────────────────
npm cache clean --force # limpa o cache
# ── Publicação ───────────────────────────────────
npm login # autentica no registry
npm publish # publica o pacote
npm version patch # incrementa patch (1.0.0 → 1.0.1)
npm version minor # incrementa minor (1.0.0 → 1.1.0)
npm version major # incrementa major (1.0.0 → 2.0.0)
Alternativas ao NPM
# Yarn — criado pelo Facebook, mais rápido no passado
npm i -g yarn
yarn add express
yarn add -D nodemon
yarn install
# pnpm — mais eficiente em espaço em disco
# usa links simbólicos para compartilhar pacotes entre projetos
npm i -g pnpm
pnpm add express
pnpm add -D nodemon
pnpm install
# bun — runtime e gerenciador de pacotes ultra-rápido (2023)
# compatível com NPM
bun add express
bun install
Para projetos novos em 2025, pnpm é uma excelente escolha pela eficiência. NPM ainda é o padrão da indústria e o mais universal.
Exemplo completo — estrutura de projeto profissional
meu-projeto/
├── src/
│ ├── index.js # ponto de entrada
│ ├── config/
│ │ └── index.js # configurações centralizadas
│ ├── routes/ # rotas da API
│ ├── controllers/ # lógica das rotas
│ ├── services/ # regras de negócio
│ ├── models/ # modelos de dados
│ └── utils/ # utilitários
├── tests/ # testes
├── .env # variáveis (não vai no Git)
├── .env.example # modelo (vai no Git)
├── .gitignore
├── .eslintrc.json
├── nodemon.json
├── package.json
└── package-lock.json
// src/config/index.js — configuração centralizada
require("dotenv").config();
const config = {
porta: Number(process.env.PORT) || 3000,
nodeEnv: process.env.NODE_ENV || "development",
dbUrl: process.env.DATABASE_URL,
jwtSecret: process.env.JWT_SECRET,
isDev: process.env.NODE_ENV === "development",
isProd: process.env.NODE_ENV === "production",
isTest: process.env.NODE_ENV === "test",
};
// Valida variáveis obrigatórias
const obrigatorias = ["DATABASE_URL", "JWT_SECRET"];
const faltando = obrigatorias.filter(v => !process.env[v]);
if (faltando.length > 0) {
console.error(`❌ Variáveis de ambiente faltando: ${faltando.join(", ")}`);
process.exit(1);
}
module.exports = config;
Tarefa para você
Crie um projeto Node.js do zero com a seguinte estrutura:
# 1. Inicie o projeto com npm init -y
# 2. Instale as dependências:
# - dotenv (produção)
# - nodemon (dev)
# - date-fns (produção)
# 3. Configure os scripts:
# - start: node src/index.js
# - dev: nodemon src/index.js
# - info: node src/info.js
# 4. Crie src/info.js que imprime:
# - Nome e versão do projeto (lidos do package.json)
# - Data e hora atual formatada com date-fns
# - Variáveis de ambiente: PORT e NODE_ENV
# - Informações do sistema: OS, Node version, memória
# 5. Crie um .env com PORT=3000 e NODE_ENV=development
# 6. Crie um .gitignore adequado
# 7. Crie um .env.example documentado
# Bônus: use o package.json como fonte de verdade
// src/info.js
const pkg = require("../package.json");
console.log(`${pkg.name} v${pkg.version}`);
Ver solução — o projeto inteiro: package.json, .env, .gitignore e o info.js
// Os comandos, na ordem:
//
// npm init -y
// npm install dotenv date-fns
// npm install --save-dev nodemon
//
// `--save-dev` não é detalhe: nodemon em `dependencies` vai junto para
// produção, incha a imagem e instala um observador de arquivos num
// servidor que não precisa dele.
// package.json
// {
// "name": "projeto-npm",
// "version": "1.0.0",
// "description": "Exercício de NPM: scripts, dependências e ambiente",
// "main": "src/index.js",
// "type": "commonjs",
// "scripts": {
// "start": "node src/index.js",
// "dev": "nodemon src/index.js",
// "info": "node src/info.js"
// },
// "engines": { "node": ">=18" },
// "dependencies": {
// "date-fns": "^3.6.0",
// "dotenv": "^16.4.5"
// },
// "devDependencies": {
// "nodemon": "^3.1.0"
// }
// }
//
// O `engines` documenta o mínimo — e evita o chamado de suporte de
// quem rodou no Node 14 e recebeu erro de sintaxe.
// .env (NUNCA versionado)
// PORT=3000
// NODE_ENV=development
// .env.example (versionado, sem valor real — é a documentação)
// # Porta do servidor HTTP
// PORT=3000
// # development | production | test
// NODE_ENV=development
// # Chave da API de pagamentos — peça ao time, não invente
// API_KEY=
// .gitignore
// node_modules/
// .env
// .env.local
// *.log
// npm-debug.log*
// coverage/
// dist/
// .DS_Store
//
// `node_modules/` fica de fora porque o package-lock.json já descreve
// exatamente o que instalar. Versionar a pasta é subir dezenas de
// milhares de arquivos que o `npm ci` reconstrói em segundos.
//
// `.env` fica de fora porque contém segredo. Se ele já foi commitado
// alguma vez, tirar do repositório NÃO basta: continua no histórico.
// A credencial tem de ser trocada.
// ---- src/info.js
require("dotenv").config();
const os = require("node:os");
const { format } = require("date-fns");
const { ptBR } = require("date-fns/locale");
// O package.json como fonte de verdade: nome e versão não são
// repetidos no código, então não há como divergirem.
const pkg = require("../package.json");
const emMB = (bytes) => `${(bytes / 1024 / 1024).toFixed(0)} MB`;
// process.env é SEMPRE string — inclusive "3000" e "false". Sem
// conversão, `PORT + 1` daria "30001", e `Boolean("false")` é true.
const porta = Number(process.env.PORT ?? 3000);
const ambiente = process.env.NODE_ENV ?? "development";
console.log(`${pkg.name} v${pkg.version}`);
console.log(pkg.description);
console.log("─".repeat(48));
console.log(`Data : ${format(new Date(), "dd/MM/yyyy 'às' HH:mm:ss", { locale: ptBR })}`);
console.log(`Dia : ${format(new Date(), "EEEE", { locale: ptBR })}`);
console.log("─".repeat(48));
console.log(`PORT : ${porta}`);
console.log(`NODE_ENV : ${ambiente}`);
// Aviso útil: variável ausente costuma aparecer como bug esquisito
// três camadas adiante, não como erro no boot.
for (const chave of ["PORT", "NODE_ENV"]) {
if (process.env[chave] === undefined) {
console.warn(`⚠️ ${chave} não definida — usando o padrão. Copie o .env.example.`);
}
}
console.log("─".repeat(48));
console.log(`SO : ${os.type()} ${os.release()} (${os.arch()})`);
console.log(`Node : ${process.version}`);
console.log(`CPUs : ${os.cpus().length}`);
console.log(`Memória : ${emMB(os.totalmem() - os.freemem())} em uso de ${emMB(os.totalmem())}`);
console.log(`Uptime : ${(os.uptime() / 3600).toFixed(1)} h`);
// ---------------------------------------------------------------
// O detalhe que morde: ^ no package.json e o package-lock
// ---------------------------------------------------------------
// "date-fns": "^3.6.0" aceita qualquer 3.x — 3.7, 3.9, 3.20. Quem
// instalar amanhã pode receber código diferente do seu, e o bug que
// só acontece na máquina de um colega nasce daí.
//
// Quem congela isso é o package-lock.json, que registra a versão
// exata de cada pacote e de cada dependência das dependências. Por
// isso ele É versionado, e por isso o CI usa:
//
// npm ci ← instala exatamente o lock, apaga node_modules antes
// npm install ← pode ATUALIZAR o lock, é para desenvolvimento
//
// Usar `npm install` no CI é abrir mão da reprodutibilidade
// silenciosamente.
process.env devolve string sempre: PORT + 1 vira "30001" e Boolean("false") é true. Converta na entrada. E no CI use npm ci, não npm install — só o primeiro respeita o package-lock.json à risca.
Dois arquivos governam um projeto Node: o package.json, que declara o que você aceita, e o package-lock.json, que registra o que foi de fato instalado. Confundir o papel dos dois é a origem do "na minha máquina funciona" — daí o lock ir versionado e o CI usar npm ci, nunca npm install. O resto é vocabulário: -D para o que só serve em desenvolvimento, scripts para não decorar comando, e o .env fora do Git, sempre.
Fontes e Referências
- NPM Docs: https://docs.npmjs.com
- NPM Registry: https://npmjs.com
- Semantic Versioning — SemVer: https://semver.org
- dotenv — Documentação: https://github.com/motdotla/dotenv
- nodemon — Documentação: https://nodemon.io
- pnpm — Documentação: https://pnpm.io
- Node.js — package.json fields: https://docs.npmjs.com/cli/v10/configuring-npm/package-json
- Node.js Design Patterns — Mario Casciaro (Packt Publishing)
- npm — versionamento semântico: https://docs.npmjs.com/about-semantic-versioning
Exercícios
Exercício 1
Estas quatro linhas estão no package.json. Que versões cada uma autoriza o NPM a instalar?
{
"dependencies": {
"express": "^4.18.2",
"express-rate-limit": "^0.5.1",
"mongoose": "~7.6.0",
"zod": "*"
}
}
Ver resposta
✓ Resposta: ^4.18.2 aceita de 4.18.2 até abaixo de 5.0.0; ~7.6.0 aceita de 7.6.0 até abaixo de 7.7.0, só patches; * aceita qualquer coisa, inclusive a próxima major que quebra tudo. A segunda linha é a pegadinha: ^0.5.1 não permite até a 1.0.0. Abaixo da versão 1, o caret se comporta como o til e trava no minor — de 0.5.1 até abaixo de 0.6.0. A regra existe porque o SemVer considera que a fase 0.x é instável por definição, e ali a própria especificação libera o autor a quebrar compatibilidade num minor. Consequência prática: um pacote em 0.x não recebe correção nenhuma pelo npm update quando o autor publica a 0.6, e isso passa despercebido justamente em bibliotecas pequenas, que são as que mais vivem em 0.x. Vale saber ainda que ^ e ~ descrevem o que é permitido, não o que está instalado — quem responde isso é o package-lock.json.
Exercício 2
O código funciona na sua máquina e quebra no servidor de integração, sem que ninguém tenha mudado uma linha. O package-lock.json está commitado. O que explica a diferença?
# na sua máquina, três meses atrás:
npm install
# no pipeline, hoje:
npm install
Ver resposta
✓ Resposta: Porque npm install tem licença para atualizar o lock. Ele lê o package.json, e sempre que uma versão mais nova ainda satisfaz o intervalo do ^, ele a instala e reescreve o package-lock.json de acordo. Três meses depois, ^4.18.2 pode significar 4.19.7 — outro código, com outro bug. O comando para ambiente automatizado é npm ci: ele exige o lock, apaga o node_modules antes, instala exatamente as versões registradas e falha se o lock estiver dessincronizado do package.json — que é justamente o aviso que você quer receber. Três consequências que vêm junto: o npm ci nunca altera nenhum dos dois arquivos, então um pipeline não pode "consertar" o lock por acidente; ele é bem mais rápido, porque não precisa resolver a árvore de dependências; e, como falha em vez de improvisar, um lock esquecido vira erro de build em vez de um deploy silenciosamente diferente. A regra curta: npm install é para quando você quer mudar as dependências, npm ci para todo o resto.
Exercício 3
O deploy usa npm ci --omit=dev para economizar espaço na imagem. O servidor sobe e morre na hora. Onde está o erro?
{
"scripts": {
"start": "nodemon src/index.js"
},
"dependencies": {
"express": "^4.18.2",
"dotenv": "^16.4.5"
},
"devDependencies": {
"nodemon": "^3.1.0",
"jest": "^29.7.0"
}
}
Ver resposta
✓ Resposta: O start chama o nodemon, que é uma devDependency — e o --omit=dev não o instalou. O processo morre com sh: nodemon: not found, uma mensagem que aponta para o ambiente quando o erro está na configuração. O start é a porta de entrada de produção e só pode usar o que existe em produção: "start": "node src/index.js", deixando o nodemon no dev, que é a convenção que o artigo usa. Repare que a separação entre os dois grupos não é tecnicamente imposta: em desenvolvimento, um npm install normal instala os dois e tudo funciona igual, e é por isso que o engano só aparece no deploy. Vale conhecer o erro simétrico, mais comum e mais caro: colocar em devDependencies algo de que o runtime precisa — um driver de banco, um validador — porque só apareceu na fase de testes. O sintoma é o mesmo, Cannot find module, em produção. O teste mental é direto: se este pacote sumisse do servidor, a aplicação ainda atende uma requisição?
Exercício 4
O .env foi commitado por engano há dois dias, com a senha do banco e a chave da API de pagamentos. Alguém abre este PR. Ele resolve?
echo ".env" >> .gitignore
git add .gitignore
git commit -m "chore: ignora o .env"
git push
Ver resposta
✓ Resposta: Não resolve nada, e por dois motivos independentes. O primeiro é mecânico: o .gitignore só vale para arquivos não rastreados. O .env já está no índice, então o Git continua versionando cada alteração dele como se o .gitignore não existisse — para pará-lo é preciso git rm --cached .env, que o remove do rastreamento sem apagar o arquivo do disco. O segundo é o que realmente importa: mesmo depois de removido, o segredo continua no histórico, em cada commit dos últimos dois dias, e qualquer pessoa com acesso ao repositório — ou com um clone antigo — o recupera com um comando. Para um repositório público, assuma que já foi lido: existem robôs varrendo commits novos em busca exatamente disso, e o intervalo entre o push e a primeira tentativa de uso costuma ser de minutos. Por isso a primeira providência não é mexer no Git, é rotacionar as credenciais: trocar a senha do banco e revogar a chave de pagamentos. Limpar o histórico vem depois, é operação destrutiva, reescreve os hashes e obriga todo mundo a refazer o clone — e não devolve a senha que já vazou.
Exercício 5
Dois desenvolvedores rodam o mesmo comando no mesmo projeto e recebem resultados diferentes. O CI, um terceiro. Por quê — e o que a segunda forma muda?
# como cada um instalou, uma vez, há muito tempo:
npm install -g eslint
# e como rodam:
eslint src/
# a alternativa:
npx eslint src/
Ver resposta
✓ Resposta: Porque o -g instala fora do projeto: cada máquina tem a versão que instalou no dia em que instalou, e nada no repositório diz qual deveria ser. Um roda a 8, outro a 9 — que mudou o formato do arquivo de configuração —, o CI roda a que estiver na imagem, e o resultado é a discussão sobre "na minha máquina passa" aplicada ao próprio linter. O npx muda a origem da ferramenta: ele procura primeiro em node_modules/.bin do projeto, ou seja, usa a versão que está no package.json e travada no package-lock.json, igual para todo mundo. Por isso a forma correta é npm i -D eslint mais um script — "lint": "eslint src/" —, já que os scripts do NPM também enxergam o node_modules/.bin e dispensam o npx na linha de comando. Duas ressalvas úteis: quando o pacote não está instalado, o npx baixa uma cópia temporária e executa, o que é ótimo para rodar um gerador uma vez só (npx create-vite) e péssimo como dependência diária, porque volta a depender do que houver no registro naquele instante; e como ele executa código baixado na hora, um nome digitado errado é um vetor de ataque conhecido — conferir o nome do pacote antes de dar Enter não é paranoia.