Os npm Scripts no Node.js transformam comandos de desenvolvimento, teste, build e publicação em uma interface padronizada dentro do package.json. Em vez de cada pessoa memorizar opções ou instalar CLIs globalmente, o projeto declara scripts que usam executáveis locais do node_modules/.bin.
Scripts simples tornam o fluxo reproduzível e funcionam no terminal, editor e CI. Porém, cadeias longas com shell específico, lifecycle hooks inesperados e comandos destrutivos podem dificultar portabilidade e segurança.
Neste guia, você aprenderá a criar scripts, passar argumentos, usar pre e post hooks, compreender eventos de lifecycle, acessar variáveis, escrever scripts portáveis, trabalhar com workspaces e proteger instalações.
O campo scripts
{
"scripts": {
"dev": "node --watch src/server.js",
"start": "node dist/server.js",
"build": "tsc -p tsconfig.build.json",
"test": "node --test",
"lint": "eslint .",
"format": "prettier . --write"
}
}Execute:
npm run build
npm test
npm startstart, test, stop e restart possuem atalhos. Scripts arbitrários usam npm run nome.
Executáveis locais no PATH
A documentação oficial de npm Scripts explica que binários das dependências são adicionados ao PATH durante a execução. Por isso:
{
"devDependencies": {
"eslint": "10.7.0"
},
"scripts": {
"lint": "eslint ."
}
}Não é necessário escrever ./node_modules/.bin/eslint nem instalar globalmente.
Passando argumentos
npm run test -- --test-name-pattern="pedido"O separador -- encaminha os argumentos seguintes ao comando do script.
Compondo scripts
{
"scripts": {
"check": "npm run lint && npm run typecheck && npm test",
"lint": "eslint .",
"typecheck": "tsc --noEmit",
"test": "node --test"
}
}&& executa a próxima etapa somente quando a anterior termina com código zero. Para lógica complexa ou compatibilidade entre shells, crie um script Node.js.
Scripts Node.js
{
"scripts": {
"release:check": "node scripts/release-check.js"
}
}// scripts/release-check.js
import { readFile } from 'node:fs/promises';
const pkg = JSON.parse(
await readFile(new URL('../package.json', import.meta.url), 'utf8')
);
if (!pkg.version) {
console.error('Versão ausente');
process.exitCode = 1;
}JavaScript oferece validação, tratamento de erros e portabilidade melhores que uma linha de shell enorme.
Pre e post scripts
{
"scripts": {
"pretest": "npm run build:test",
"test": "node --test",
"posttest": "node scripts/cleanup-test.js"
}
}npm test executa pretest, test e posttest. Use hooks com moderação, porque comportamento implícito surpreende quem lê apenas o script principal.
Lifecycle de publicação
Eventos importantes:
prepare;prepublishOnly;prepack;postpack;publish;postpublish.
prepare
{
"scripts": {
"prepare": "npm run build",
"build": "tsc -p tsconfig.build.json"
}
}prepare roda antes de pack e publish, além de certas instalações locais ou por Git. Isso permite compilar TypeScript antes do pacote ser usado, mas também torna instalações mais lentas e exige devDependencies quando a origem é Git.
prepublish é confuso
O evento prepublish é legado e não roda como muitos esperam durante npm publish. Prefira:
preparepara build;prepublishOnlypara validação exclusiva antes de publicar;prepackpara preparar o tarball.
prepublishOnly
{
"scripts": {
"prepublishOnly": "npm run check && npm run build"
}
}Não confie apenas nesse hook em publicação automatizada. O CI também precisa bloquear release sem testes.
prepack
{
"scripts": {
"prepack": "npm run clean && npm run build"
}
}npm pack e npm publish executam o evento. Teste com:
npm pack --dry-runinstall e postinstall
Evite scripts de instalação sempre que possível. Eles executam durante instalação de dependências e ampliam riscos de supply chain. Casos legítimos incluem compilação nativa necessária à arquitetura, mas o npm já reconhece binding.gyp em situações padrão.
Para segurança, consulte Supply Chain no Node.js.
Ignorando scripts de dependências
npm ci --ignore-scriptsEssa opção reduz risco, mas pode impedir funcionamento de pacotes que precisam de build. Avalie dependências e aprove scripts conscientemente.
Diretório de execução
Scripts são executados na raiz do pacote, mesmo quando o comando é chamado de outro diretório. O ambiente INIT_CWD guarda o diretório original.
console.log(process.cwd());
console.log(process.env.INIT_CWD);Variáveis npm
Variáveis úteis:
npm_package_name;npm_package_version;npm_package_json;npm_lifecycle_event;npm_old_version;npm_new_version.
console.log({
name: process.env.npm_package_name,
version: process.env.npm_package_version,
event: process.env.npm_lifecycle_event
});Para outros campos, leia o arquivo indicado por npm_package_json.
Variáveis de ambiente
No Linux:
NODE_ENV=test node --testEssa sintaxe não funciona igual no Windows. Use cross-env quando o projeto precisa ser multiplataforma:
npm install --save-dev cross-env{
"scripts": {
"test": "cross-env NODE_ENV=test node --test"
}
}Consulte Variáveis de Ambiente no Node.js.
Shell e portabilidade
npm usa /bin/sh em sistemas POSIX e cmd.exe no Windows. Recursos como expansão de glob, remoção de diretórios e atribuição de variável podem divergir.
Prefira:
- CLIs multiplataforma;
- scripts Node.js;
- APIs nativas como
fs.rm; cross-envpara variáveis;- comandos pequenos.
Script de limpeza portável
// scripts/clean.js
import { rm } from 'node:fs/promises';
await Promise.all([
rm(new URL('../dist', import.meta.url), {
recursive: true,
force: true
}),
rm(new URL('../coverage', import.meta.url), {
recursive: true,
force: true
})
]);{
"scripts": {
"clean": "node scripts/clean.js"
}
}Execução paralela
Para tarefas independentes, uma ferramenta como npm-run-all2 ou concurrently pode ajudar. Fixe a dependência e trate sinais corretamente.
{
"scripts": {
"dev": "concurrently -k \"npm:dev:api\" \"npm:dev:worker\""
}
}Watch mode
{
"scripts": {
"dev": "node --watch src/server.js",
"test:watch": "node --test --watch"
}
}Veja Watch Mode no Node.js.
Workspaces
npm run test --workspace=@empresa/api
npm run test --workspaces --if-presentConsulte npm Workspaces no Node.js.
Scripts na raiz do monorepo
{
"scripts": {
"check": "npm run lint --workspaces --if-present && npm run test --workspaces --if-present",
"build": "npm run build --workspaces --if-present"
}
}Se a ordem de dependências importa, use grafo explícito ou ferramenta de monorepo.
CI
- run: npm ci
- run: npm run check
- run: npm run buildVeja CI para Node.js com GitHub Actions.
Códigos de saída
Quando um script termina com código diferente de zero, npm considera falha. Não esconda erros críticos:
try {
await validate();
} catch (error) {
console.error(error);
process.exitCode = 1;
}Use warning e código zero somente para recurso realmente opcional.
Sinais
Ferramentas de desenvolvimento que iniciam processos precisam encaminhar SIGINT e SIGTERM. Caso contrário, portas e containers podem permanecer ativos. Teste Ctrl+C no Windows e Linux.
Nomes consistentes
Convenções úteis:
dev;start;build;clean;lintelint:fix;formateformat:check;test,test:unit,test:integration;typecheck;check.
Não coloque segredos em scripts
// Nunca
"deploy": "TOKEN=segredo comando"Use secret store e variáveis protegidas. Scripts ficam no repositório e podem aparecer em logs.
Command injection
Não concatene entrada não confiável em comandos:
exec(`tool ${userInput}`);Use spawn com argumentos separados, validação e allowlist quando precisar executar processos. Veja Child Process no Node.js.
Scripts pequenos
Quando um script ultrapassa uma linha complexa, mova a lógica para arquivo. Isso permite teste unitário, comentários e tratamento de plataforma.
Erros comuns
- CLI global: versão diverge.
- Shell específico: falha no Windows.
- Pre/post escondidos: comportamento surpreende.
- postinstall desnecessário: risco e lentidão.
- Segredo inline: vazamento.
- Comando gigante: difícil de testar.
- Erro ignorado: CI passa incorretamente.
- Script duplicado: local e CI divergem.
Exemplo recomendado
{
"scripts": {
"dev": "node --watch src/server.js",
"clean": "node scripts/clean.js",
"build": "npm run clean && tsc -p tsconfig.build.json",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"format": "prettier . --write",
"format:check": "prettier . --check",
"typecheck": "tsc --noEmit",
"test": "node --test",
"check": "npm run lint && npm run format:check && npm run typecheck && npm test",
"start": "node dist/server.js",
"prepack": "npm run check && npm run build"
}
}Conclusão
Os npm Scripts no Node.js oferecem uma interface única para tarefas do projeto. Executáveis locais, nomes consistentes e scripts pequenos tornam desenvolvimento e CI reproduzíveis.
Use lifecycle hooks apenas quando a semântica corresponde, evite scripts de instalação desnecessários e mova lógica complexa para JavaScript. Com portabilidade e códigos de saída corretos, os scripts deixam de ser atalhos frágeis e se tornam parte confiável do processo de engenharia.



