Crear y publicar un paquete npm con Claude Code
Crea un paquete npm con Claude Code usando tsup, exports, tipos, npm pack, README generado y CI de publicación.
Pedirle a Claude Code “crea un paquete npm” suele producir archivos rápidamente. El problema aparece cuando quieres publicar algo que otra persona pueda instalar sin sorpresas. Un paquete real necesita un package.json coherente, exports correctos, declaraciones de tipos, README alineado con la API, revisión de npm pack y un flujo de CI que publique de forma controlada.
En esta guía construiremos un paquete pequeño de utilidades de texto en TypeScript. Claude Code no será quien decida publicar a ciegas; lo usaremos como asistente para generar, revisar y explicar riesgos. Para las reglas oficiales, consulta la documentación de npm sobre package.json, scoped public packages, npm pack, trusted publishing y la documentación oficial de Claude Code.
Define el contrato antes de generar archivos
El primer prompt no debería ser genérico. Especifica nombre del paquete, usuarios, runtime, formatos de módulo, comandos de verificación y política de publicación. type: "module", main, exports y types dependen entre sí. Si Claude Code genera uno sin revisar los demás, el paquete puede compilar pero fallar al consumirlo.
| Área | Decisión del ejemplo | Qué debe revisar Claude Code |
|---|---|---|
| Nombre | @acme/string-kit | si un paquete scoped público necesita --access public |
| Usuarios | Node.js y TypeScript | si funcionan ESM import y CJS require |
| Build | tsup genera ESM, CJS y tipos | si dist contiene lo que apunta exports |
| Contenido | solo dist, README.md y LICENSE | si npm pack --dry-run incluye archivos inesperados |
| Publicación | GitHub Actions + npm Trusted Publishing | si evita tokens npm de larga duración |
Este mapa evita que Claude Code se concentre solo en el código y olvide el empaquetado.
flowchart LR
A["Brief del paquete"] --> 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"]
Si tu paquete será una CLI, complementa esta guía con desarrollo de herramientas CLI con Claude Code. Para mejorar la forma de pedir cambios, revisa también tips de productividad con Claude Code.
Crea el proyecto mínimo
Empieza en una carpeta limpia. En un repositorio grande, muchos errores se mezclan con configuración del workspace, dependencias heredadas o scripts previos. Un paquete mínimo ayuda a verificar el contrato antes de integrarlo.
mkdir string-kit
cd string-kit
npm init -y
npm install -D typescript tsup vitest @types/node
mkdir src scripts
El package.json siguiente es deliberadamente explícito. main sirve a consumidores CJS, module ayuda a algunos bundlers, types apunta a TypeScript y exports define la entrada moderna. files reduce el riesgo de publicar pruebas, borradores, mapas de fuente o configuración local.
{
"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 se usa para comprobar tipos; tsup se encarga de emitir JavaScript. Esta separación hace más fácil pedirle a Claude Code que revise si los archivos generados coinciden con exports.
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"declaration": true,
"declarationMap": true,
"skipLibCheck": true,
"noEmit": true
},
"include": ["src", "tsup.config.ts"]
}
Implementación real y pruebas
No publiques pseudocódigo como ejemplo de paquete. Este módulo exporta cuatro funciones pequeñas pero útiles: slugify para slugs ASCII, truncate para limitar texto visible, interpolate para plantillas simples y byteLength para 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;
}
Las pruebas cubren casos normales, límites, excepciones y Unicode. Cuando uses Claude Code, no pidas solo “agrega tests”; pide acentos, placeholders desconocidos, longitudes inválidas y 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 y README generado
tsup mantiene ligero el empaquetado. outExtension emite ESM como .js y CJS como .cjs, alineado con package.json. En este ejemplo sourcemap está desactivado para no publicar mapas de fuente sin intención.
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" };
},
});
El README se desactualiza con facilidad. Un generador pequeño evita que el ejemplo de instalación y uso quede separado de la 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);
Verifica con npm pack
npm publish no debe ser la primera verificación real. npm pack --dry-run muestra qué archivos viajarán al registro. Revisa si entran README, LICENSE, declaraciones de tipos y salidas ESM/CJS, y si quedan fuera tests o borradores.
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 una prueba más fuerte, instala el .tgz en otra carpeta. Así confirmas que el paquete funciona sin depender del src local.
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)))"
Tres usos reales justifican este patrón. Un equipo de producto puede compartir reglas de texto entre web, admin y documentación. Un flujo de contenido puede usar truncate e interpolate para descripciones, tarjetas y notas de versión. Un design system o CLI puede publicar utilidades pequeñas para que las apps las actualicen con SemVer.
Publica desde GitHub Actions
Publicar desde CI deja un camino repetible. npm Trusted Publishing permite publicar con OIDC desde proveedores compatibles y reduce la dependencia de tokens npm largos. Configura el trusted publisher en npm y limita la publicación a un 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
Si necesitas notas de versión y cambios SemVer más claros, conecta este flujo con versionado con Claude Code y Changesets.
Errores frecuentes
El primer error es usar solo main y olvidar exports. El segundo es publicar demasiado por no restringir files. El tercero es dejar un README viejo después de cambiar exports o nombres de funciones. El cuarto es prometer soporte Unicode que el código no cumple: este truncate usa longitud de string de JavaScript, no segmentación visual con emojis. El quinto es delegar a Claude Code decisiones humanas como nombre final, scope, 2FA, Trusted Publishing y aprobación 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: convierte la publicación en una plantilla
Publicar en npm no termina con la primera versión. Cambian README, CI, dependencias, permisos y estrategia SemVer. Empieza con la chuleta gratuita de Claude Code para tener prompts seguros y comandos de verificación. Para plantillas reutilizables, revisa productos de ClaudeCodeLab. Para equipos que necesitan CLAUDE.md, CI, permisos y revisión, consulta formación y consultoría de Claude Code.
Probé este flujo en un directorio temporal de Windows: pasaron npm install, npm test, npm run build, npm pack --dry-run y las pruebas ESM/CJS con node -e. En el trabajo diario de Masa, pedir a Claude Code que explique la salida de npm pack antes del release ayuda a detectar README desactualizado, tipos faltantes y archivos inesperados en el tarball.
Resumen
Claude Code acelera la creación de paquetes npm, pero el contrato de publicación debe quedar explícito. Une package.json, exports, tipos, tsup, pruebas, README, npm pack y CI en un solo flujo. Antes de publicar, confirma tres cosas: revisaste dist, revisaste el tarball y probaste las mismas entradas que usará el consumidor.
PDF gratis: cheatsheet de Claude Code
Introduce tu email y descarga una hoja con comandos, hábitos de revisión y flujos seguros.
Cuidamos tus datos y no enviamos spam.
Sobre el autor
Masa
Ingeniero enfocado en workflows prácticos con Claude Code.
Artículos relacionados
Escalera de permisos de Claude Code para ampliar acceso sin perder control
Pasa de read-only a ediciones limitadas, comandos de prueba y checks de deploy con menos riesgo.
Claude Code Small PR Proof Pack: cambios pequeños que sí se pueden revisar
Un paquete de prueba para PRs de Claude Code: diff, checks, URL pública, CTA y rollback.
Gate de revisión antes del commit con Claude Code
Cómo revisar con Claude Code antes del commit: diff, build, URL pública, Gumroad, consultoría, tests y archivos ajenos.