Tips & Tricks (Actualizado: 2/6/2026)

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.

Crear y publicar un paquete npm con Claude Code

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.

ÁreaDecisión del ejemploQué debe revisar Claude Code
Nombre@acme/string-kitsi un paquete scoped público necesita --access public
UsuariosNode.js y TypeScriptsi funcionan ESM import y CJS require
Buildtsup genera ESM, CJS y tipossi dist contiene lo que apunta exports
Contenidosolo dist, README.md y LICENSEsi npm pack --dry-run incluye archivos inesperados
PublicaciónGitHub Actions + npm Trusted Publishingsi 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.

#Claude Code #npm #繝代ャ繧ア繝シ繧ク蜈ャ髢・ #TypeScript #OSS
Gratis

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.

Masa

Sobre el autor

Masa

Ingeniero enfocado en workflows prácticos con Claude Code.