Criar e publicar um pacote npm com Claude Code
Crie um pacote npm com Claude Code usando tsup, exports, tipos, npm pack, README gerado e publicação via CI.
Pedir ao Claude Code para “criar um pacote npm” é simples. Publicar um pacote que outra pessoa consiga instalar com confiança exige mais cuidado. Você precisa de package.json consistente, exports corretos, tipos gerados, README alinhado com a API, checagem com npm pack e uma esteira de CI que publique somente quando os critérios forem cumpridos.
Neste guia vamos criar um pequeno pacote TypeScript de utilitários de string. O Claude Code será usado como parceiro de implementação e revisão, não como autoridade para publicar sem supervisão. Para decisões reais, consulte as fontes oficiais: npm package.json, scoped public packages, npm pack, trusted publishing e a documentação do Claude Code.
Defina o contrato do pacote
O primeiro prompt não deve ser genérico. Informe nome do pacote, público, runtime, formatos de módulo, comandos de verificação e política de publicação. type: "module", main, exports e types precisam apontar para a mesma história. Se um deles for gerado isoladamente, o pacote pode compilar e ainda assim falhar no projeto do usuário.
| Área | Decisão do exemplo | O que o Claude Code deve verificar |
|---|---|---|
| Nome | @acme/string-kit | se pacote scoped público exige --access public |
| Usuários | Node.js e TypeScript | se ESM import e CJS require funcionam |
| Build | tsup gera ESM, CJS e tipos | se dist contém os arquivos em exports |
| Conteúdo | apenas dist, README.md, LICENSE | se npm pack --dry-run mostra arquivos inesperados |
| Publicação | GitHub Actions + npm Trusted Publishing | se evita tokens npm de longa duração |
O diagrama abaixo ajuda a manter implementação, testes, pacote e publicação no mesmo fluxo.
flowchart LR
A["Brief do pacote"] --> B["package.json"]
B --> C["src/index.ts"]
C --> D["Vitest"]
D --> E["tsup build"]
E --> F["npm pack dry-run"]
F --> G["CI publish"]
Se o pacote for uma CLI, veja também desenvolvimento de CLI com Claude Code. Para melhorar prompts e verificações, leia dicas de produtividade com Claude Code.
Crie o projeto mínimo
Comece em uma pasta limpa. Em um repositório grande, erros de pacote podem se misturar com workspace, dependências antigas e scripts já existentes.
mkdir string-kit
cd string-kit
npm init -y
npm install -D typescript tsup vitest @types/node
mkdir src scripts
O package.json é o contrato público. main atende CJS, module ajuda alguns bundlers, types atende TypeScript e exports controla os pontos de entrada modernos. files fica restrito para evitar publicar testes, rascunhos, sourcemaps ou configurações locais.
{
"name": "@acme/string-kit",
"version": "0.1.0",
"description": "Small TypeScript string utilities used as an npm package example.",
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.cjs"
},
"./package.json": "./package.json"
},
"files": ["dist", "README.md", "LICENSE"],
"sideEffects": false,
"scripts": {
"build": "tsup",
"test": "vitest run",
"docs": "node scripts/write-readme.mjs",
"test:pack": "npm pack --dry-run",
"prepublishOnly": "npm run test && npm run build && npm run test:pack"
},
"keywords": ["string", "typescript", "utilities"],
"license": "MIT",
"devDependencies": {
"@types/node": "^22.15.0",
"tsup": "^8.5.0",
"typescript": "^5.8.0",
"vitest": "^3.2.0"
}
}
TypeScript verifica os tipos e o tsup gera os arquivos finais. Essa divisão deixa claro para o Claude Code que dist precisa corresponder aos caminhos expostos.
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"declaration": true,
"declarationMap": true,
"skipLibCheck": true,
"noEmit": true
},
"include": ["src", "tsup.config.ts"]
}
Implementação real e testes
Evite exemplo que parece código mas não roda. Este pacote exporta quatro funções pequenas: slugify para slugs ASCII, truncate para limitar texto, interpolate para templates simples e byteLength para contar bytes UTF-8.
export function slugify(input: string): string {
return input
.normalize("NFKD")
.replace(/[\u0300-\u036f]/g, "")
.toLowerCase()
.trim()
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-+|-+$/g, "");
}
export function truncate(input: string, maxLength: number, suffix = "..."): string {
if (!Number.isInteger(maxLength) || maxLength < 1) {
throw new RangeError("maxLength must be a positive integer");
}
if (suffix.length >= maxLength) {
throw new RangeError("suffix must be shorter than maxLength");
}
if (input.length <= maxLength) return input;
return `${input.slice(0, maxLength - suffix.length)}${suffix}`;
}
export function interpolate(template: string, values: Record<string, string | number>): string {
return template.replace(/\{\{\s*([\w.-]+)\s*\}\}/g, (match, key: string) => {
return Object.hasOwn(values, key) ? String(values[key]) : match;
});
}
export function byteLength(input: string): number {
return new TextEncoder().encode(input).length;
}
Os testes cobrem casos normais, limites, exceções e Unicode. Ao pedir testes ao Claude Code, seja explícito: acentos, placeholders desconhecidos, comprimentos inválidos e bytes UTF-8.
import { describe, expect, it } from "vitest";
import { byteLength, interpolate, slugify, truncate } from "./index";
describe("slugify", () => {
it("turns a title into an npm-friendly slug", () => {
expect(slugify("Hello npm Package!")).toBe("hello-npm-package");
});
it("removes accents before replacing separators", () => {
expect(slugify("Crème brûlée utils")).toBe("creme-brulee-utils");
});
});
describe("truncate", () => {
it("keeps short text unchanged", () => {
expect(truncate("short", 10)).toBe("short");
});
it("adds a suffix inside the requested length", () => {
expect(truncate("Claude Code package", 12)).toBe("Claude Co...");
});
it("rejects invalid lengths", () => {
expect(() => truncate("abc", 2)).toThrow(RangeError);
});
});
describe("interpolate", () => {
it("replaces known placeholders and keeps unknown ones", () => {
expect(interpolate("Hi {{ name }}, ship {{pkg}} {{missing}}", {
name: "Masa",
pkg: "@acme/string-kit",
})).toBe("Hi Masa, ship @acme/string-kit {{missing}}");
});
});
describe("byteLength", () => {
it("counts UTF-8 bytes", () => {
expect(byteLength("npm")).toBe(3);
expect(byteLength("日本語")).toBe(9);
});
});
tsup e README gerado
A configuração do tsup é pequena. outExtension emite ESM como .js e CJS como .cjs, alinhado com package.json. O sourcemap fica desligado para não publicar mapeamentos internos sem uma decisão consciente.
import { defineConfig } from "tsup";
export default defineConfig({
entry: ["src/index.ts"],
format: ["esm", "cjs"],
dts: true,
clean: true,
sourcemap: false,
minify: false,
target: "es2022",
outDir: "dist",
outExtension({ format }) {
return { js: format === "esm" ? ".js" : ".cjs" };
},
});
README desatualizado é uma fonte comum de suporte. Um gerador simples mantém instalação e uso próximos da API real.
import { writeFile } from "node:fs/promises";
const fence = String.fromCharCode(96).repeat(3);
const readme = `# @acme/string-kit
Small TypeScript string utilities packaged with tsup.
## Install
${fence}bash
npm install @acme/string-kit
${fence}
## Usage
${fence}ts
import { slugify, truncate } from "@acme/string-kit";
console.log(slugify("Hello npm Package!"));
console.log(truncate("Claude Code package", 12));
${fence}
`;
await writeFile(new URL("../README.md", import.meta.url), readme);
Verifique com npm pack
npm publish não deve ser a primeira verificação real. npm pack --dry-run mostra os arquivos que serão enviados. Revise README, LICENSE, tipos, ESM/CJS e a ausência de testes ou rascunhos.
npm run docs
npm test
npm run build
npm pack --dry-run
node -e "import('./dist/index.js').then((m)=>console.log(m.slugify('Hello ESM')))"
node -e "const m=require('./dist/index.cjs'); console.log(m.slugify('Hello CJS'))"
Para uma prova mais próxima do usuário, instale o .tgz em outra pasta.
npm pack
mkdir ../string-kit-smoke
cd ../string-kit-smoke
npm init -y
npm install ../string-kit/acme-string-kit-0.1.0.tgz
node -e "import('@acme/string-kit').then((m)=>console.log(m.truncate('Claude Code package', 12)))"
Três usos práticos aparecem muito. Um time de produto compartilha regras de texto entre web, admin e documentação. Um fluxo de conteúdo usa truncate e interpolate em descrições, cards e notas de versão. Um design system ou CLI publica utilitários pequenos para que apps atualizem por SemVer.
Publique via GitHub Actions
Publicar por CI torna o caminho revisável. npm Trusted Publishing permite publicação por OIDC e reduz tokens npm longos. Configure o trusted publisher no npm e publique apenas em evento de release.
name: package
on:
push:
branches: [main]
pull_request:
release:
types: [published]
permissions:
contents: read
id-token: write
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
registry-url: https://registry.npmjs.org
cache: npm
- run: npm ci
- run: npm run docs
- run: npm test
- run: npm run build
- run: npm pack --dry-run
publish:
if: github.event_name == 'release'
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
registry-url: https://registry.npmjs.org
cache: npm
- run: npm ci
- run: npm run docs
- run: npm test
- run: npm run build
- run: npm publish --access public
Para changelog e SemVer mais organizados, combine com Claude Code e Changesets.
Armadilhas comuns
A primeira armadilha é usar só main e esquecer exports. A segunda é publicar arquivos demais por não limitar files. A terceira é manter README antigo depois de mudar nomes de funções ou package scope. A quarta é prometer Unicode completo quando truncate usa comprimento de string JavaScript, não graphemes visuais. A quinta é deixar Claude Code decidir nome, scope, 2FA, Trusted Publishing e aprovação final de release.
Prompt para Claude Code
Create a TypeScript npm package.
Goal:
- Package name: @acme/string-kit
- Support both ESM import and CJS require
- Use tsup to emit dist/index.js, dist/index.cjs, and dist/index.d.ts
- Include README generation, Vitest tests, and npm pack verification
Constraints:
- Only touch package.json, tsconfig.json, tsup.config.ts, src, scripts, and .github/workflows
- Do not use pseudocode; the project must run after npm install
- Do not publish source maps or unnecessary test files in the package tarball
Acceptance criteria:
- npm test passes
- npm run build passes
- npm pack --dry-run output is summarized
- ESM import and CJS require smoke tests are shown
- List the human release checks before npm publish
CTA: transforme publicação em modelo
Publicar em npm não termina na primeira versão. README, CI, dependências, versões e permissões mudam. Comece pela cola gratuita de Claude Code para guardar prompts seguros e comandos de verificação. Para modelos reutilizáveis, veja os produtos ClaudeCodeLab. Para times que precisam organizar CLAUDE.md, CI, permissões e revisão, use treinamento e consultoria Claude Code.
Testei este fluxo em uma pasta temporária no Windows: npm install, npm test, npm run build, npm pack --dry-run e smoke tests ESM/CJS com node -e passaram. Na rotina de Masa, pedir ao Claude Code que explique a saída de npm pack antes do release encontra README desatualizado, tipos ausentes e arquivos inesperados no tarball.
Resumo
Claude Code acelera a criação de pacotes npm, mas o contrato de publicação precisa ser explícito. Una package.json, exports, tipos, tsup, testes, README, npm pack e CI. Antes de publicar, verifique dist, o tarball e os pontos de entrada que o usuário realmente usará.
PDF grátis: cheatsheet do Claude Code
Informe seu e-mail e baixe uma página com comandos, hábitos de revisão e workflows seguros.
Cuidamos dos seus dados e não enviamos spam.
Sobre o autor
Masa
Engenheiro focado em workflows práticos com Claude Code.
Artigos relacionados
Como pedir ao Claude Code para mexer em um único arquivo
Do desastre em que um 'deixa melhor' alterou 40 linhas nasceu um template de prompt que limita o escopo, valida e permite reverter.
Recuperar de negações de permissão no Claude Code sem enfraquecer guardrails
Transforme um comando negado em plano seguro com motivo, alternativa, provas e critérios de nova tentativa.
Claude Code Harness Smoke Test: prova de 15 minutos antes de confiar em um agente
Um smoke test para escopo, áreas bloqueadas, comandos de prova, URL pública e CTAs de receita no Claude Code.