agentsclimarketplace

Npm package

Skill medy-gribkov/arcana/skills/npm-package

Universal AI development toolkit. 74 production-ready skills for every coding agent. Works with Claude Code, Cursor, Codex.

Install
npx -y skills add medy-gribkov/arcana --skill npm-package

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

npm and pnpm package authoring expertise. Covers tsup and unbuild bundling, dual ESM/CJS exports, package.json fields (main, module, exports, types, bin, files), semver discipline, changesets for versioning, monorepo setup with pnpm workspaces and turborepo, npm publish workflow, scoped packages, peer dependencies, tree-shaking optimization, package provenance, and .npmrc configuration. Use when creating npm packages, configuring exports, setting up monorepos, or publishing to npm.

SKILL.md

12.0 KB, as published. Nobody here has run it

You are a senior JavaScript package author who ships library code that works everywhere with zero configuration headaches for consumers.

Use this skill when

  • Creating a new npm package from scratch
  • Configuring package.json exports for dual ESM/CJS
  • Setting up tsup or unbuild for library bundling
  • Managing a monorepo with pnpm workspaces
  • Publishing packages to npm (public or private)
  • Debugging "Cannot find module" or "ERR_REQUIRE_ESM" in consumers
  • Setting up changesets for automated versioning

Package.json: The Complete Field Reference

{
  "name": "@scope/my-lib",
  "version": "1.0.0",
  "description": "One line, searchable on npm",
  "license": "MIT",
  "author": "Name <email>",
  "repository": { "type": "git", "url": "https://github.com/user/repo" },
  "type": "module",                    // ESM by default. Omit for CJS-first.
  "main": "./dist/index.cjs",         // CJS entry (Node <12, bundlers fallback)
  "module": "./dist/index.js",        // ESM entry (bundlers: webpack, rollup)
  "types": "./dist/index.d.ts",       // TypeScript declarations
  "exports": {                         // Modern entry point resolution (Node 12+)
    ".": {
      "import": { "types": "./dist/index.d.ts", "default": "./dist/index.js" },
      "require": { "types": "./dist/index.d.cts", "default": "./dist/index.cjs" }
    },
    "./utils": {
      "import": { "types": "./dist/utils.d.ts", "default": "./dist/utils.js" },
      "require": { "types": "./dist/utils.d.cts", "default": "./dist/utils.cjs" }
    }
  },
  "files": ["dist", "README.md"],     // Whitelist what gets published. Nothing else ships.
  "bin": { "my-cli": "./dist/cli.js" },
  "sideEffects": false,               // Enables tree-shaking in bundlers
  "engines": { "node": ">=18" },
  "keywords": ["relevant", "search", "terms"],
  "scripts": {
    "build": "tsup",
    "dev": "tsup --watch",
    "prepublishOnly": "pnpm build",
    "release": "changeset publish"
  }
}

Critical ordering in exports: types MUST come first in each condition block. Node resolves top-down and TypeScript needs to find declarations before the runtime file.

Bundling with tsup

tsup is the gold standard for library bundling. Zero-config, built on esbuild.

// tsup.config.ts
import { defineConfig } from "tsup";

export default defineConfig({
  entry: ["src/index.ts", "src/utils.ts"],
  format: ["esm", "cjs"],
  dts: true,               // Generate .d.ts files
  splitting: true,          // Code-split shared chunks (ESM only)
  clean: true,              // rm dist/ before build
  treeshake: true,          // Dead code elimination
  sourcemap: true,
  minify: false,            // Don't minify libraries. Let consumers decide.
  target: "node18",
  outDir: "dist",
  external: ["react", "react-dom"],  // Never bundle peer deps
});

unbuild Alternative

Use unbuild when you want passive (stub) builds during development:

// build.config.ts
import { defineBuildConfig } from "unbuild";

export default defineBuildConfig({
  entries: ["src/index"],
  declaration: true,
  clean: true,
  rollup: {
    emitCJS: true,
    inlineDependencies: false,
  },
});

Run unbuild --stub for development -- it creates a proxy that imports directly from source. No rebuild loop needed.

Dual ESM/CJS: Getting It Right

The most common source of package bugs. Follow these rules exactly:

  1. Set "type": "module" in package.json. Your source is ESM.
  2. tsup outputs .js (ESM) and .cjs (CJS) when format is ["esm", "cjs"].
  3. Map exports explicitly. Never rely on auto-resolution.
  4. Test both paths:
# Test ESM
node --input-type=module -e "import { foo } from './dist/index.js'; console.log(foo)"
# Test CJS
node -e "const { foo } = require('./dist/index.cjs'); console.log(foo)"

Common Pitfalls

  • Missing .cjs extension: If type: "module", CJS files MUST use .cjs. Node will try to parse .js as ESM.
  • Default export interop: CJS module.exports = x becomes import x from "pkg" in ESM, but import { default as x } in some bundlers. Prefer named exports for libraries.
  • Conditional exports mismatch: If exports field exists, main and module are IGNORED by Node. They only serve as fallbacks for old bundlers.

Semver Discipline

  • MAJOR (1.0.0 -> 2.0.0): Any breaking change. Removed exports, changed function signatures, dropped Node version support.
  • MINOR (1.0.0 -> 1.1.0): New features, new exports. Everything existing still works.
  • PATCH (1.0.0 -> 1.0.1): Bug fixes only. No new API surface.

Pre-1.0 (0.x.y): minor = breaking, patch = features. Get to 1.0 as fast as possible.

What counts as breaking:

  • Removing or renaming an exported function/type
  • Changing parameter order or types
  • Narrowing accepted input or widening output types
  • Dropping a Node.js version from engines
  • Changing default behavior

Changesets for Versioning

pnpm add -D @changesets/cli
pnpm changeset init

Workflow:

  1. Developer runs pnpm changeset and selects packages + bump type + writes summary.
  2. This creates .changeset/<random>.md -- committed with the PR.
  3. On merge to main, CI runs changeset version (consumes changesets, bumps versions, updates CHANGELOG).
  4. Then changeset publish pushes to npm.
# .github/workflows/release.yml
- uses: changesets/action@v1
  with:
    publish: pnpm changeset publish
    version: pnpm changeset version
    commit: "chore: release"
    title: "chore: release"
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

Monorepo with pnpm Workspaces

# pnpm-workspace.yaml
packages:
  - "packages/*"
  - "apps/*"
// packages/core/package.json
{
  "name": "@scope/core",
  "version": "1.0.0",
  "dependencies": {}
}

// packages/utils/package.json
{
  "name": "@scope/utils",
  "version": "1.0.0",
  "dependencies": {
    "@scope/core": "workspace:*"    // Resolved to local package
  }
}

workspace:* becomes the actual version on publish. pnpm handles the rewriting.

Turborepo for Task Orchestration

// turbo.json
{
  "$schema": "https://turbo.build/schema.json",
  "tasks": {
    "build": {
      "dependsOn": ["^build"],       // Build deps first
      "outputs": ["dist/**"]
    },
    "test": {
      "dependsOn": ["build"],
      "cache": false                  // Don't cache test results
    },
    "lint": {}                        // No deps, runs in parallel
  }
}

Run turbo build -- it builds in dependency order, caches outputs, skips unchanged packages.

Peer Dependencies

Use peer deps when your package wraps or extends another library:

{
  "peerDependencies": {
    "react": "^18.0.0 || ^19.0.0"
  },
  "peerDependenciesMeta": {
    "react": { "optional": false }
  },
  "devDependencies": {
    "react": "^19.0.0"              // Install for development/testing
  }
}

Rules:

  • NEVER bundle peer deps. Add them to external in your bundler config.
  • Keep peer dep ranges as wide as possible. ^18.0.0 || ^19.0.0 not ^19.1.3.
  • Always install peers as devDependencies for your own tests.

Tree-Shaking Optimization

For consumers to tree-shake your library:

  1. Ship ESM (import/export, not require/module.exports).
  2. Set "sideEffects": false in package.json.
  3. Use named exports, not default exports with namespace objects.
  4. Avoid top-level side effects (module-scoped console.log, addEventListener, mutation).
// Bad: barrel file re-exports everything, defeats tree-shaking
export * from "./button";
export * from "./modal";
export * from "./table";

// Good: separate entry points in exports map
// "exports": { "./button": "...", "./modal": "...", "./table": "..." }

Publishing Checklist

# 1. Verify package contents
pnpm pack --dry-run          # Shows exactly what ships. No secrets, no tests, no src.

# 2. Check exports resolve
npx publint                  # Catches exports/types mismatches
npx arethetypeswrong         # Tests if types resolve for all consumers

# 3. Publish with provenance
npm publish --provenance      # Links package to GitHub commit (npm provenance)

.npmrc for Publishing

# .npmrc (project root)
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
access=public
provenance=true

Scoped Packages

  • @scope/name requires "access": "public" for free npm accounts.
  • First publish: npm publish --access public. After that, access is remembered.
  • Private packages: npm publish (default access is restricted for scoped).

Files Field vs .npmignore

Always use "files" whitelist. Never use .npmignore.

"files": ["dist", "README.md", "LICENSE"]

This is a whitelist. Only these paths end up in the tarball. package.json is always included automatically. node_modules is always excluded automatically. The whitelist approach is safer than blacklisting with .npmignore because new files you add (like .env.local) never accidentally ship.

bin Field for CLIs

{
  "bin": { "my-tool": "./dist/cli.js" }
}

The CLI entry file needs a shebang:

#!/usr/bin/env node
// dist/cli.js
import { run } from "./index.js";
run(process.argv.slice(2));

Ensure the built file has the shebang. tsup preserves it if your source has it. Add banner: { js: "#!/usr/bin/env node" } in tsup config if needed.

Wrong Exports Edge Case: ESM/CJS Mismatch Debugging

SYMPTOM: "Cannot find module" or "ERR_REQUIRE_ESM" in consumers

Diagnosis Checklist

# 1. Verify package contents
npx publint              # Catches exports issues
npx arethetypeswrong     # Tests TypeScript types across formats

# 2. Check what Node.js resolves
node -p "require.resolve('@scope/my-lib')"           # CJS resolution
node --input-type=module -e "import('@scope/my-lib')" # ESM resolution

# 3. Inspect actual exports in published package
npm pack --dry-run
tar -tzf *.tgz | grep dist/

Common Mismatches

// ❌ BAD: Exports mismatch with actual files
{
  "type": "module",
  "exports": {
    ".": {
      "import": "./dist/index.js",   // File exists
      "require": "./dist/index.cjs"  // ❌ File missing or wrong extension
    }
  }
}

// ✅ GOOD: Match tsup output exactly
// tsup outputs: index.js (ESM), index.cjs (CJS)
{
  "type": "module",
  "exports": {
    ".": {
      "import": { "types": "./dist/index.d.ts", "default": "./dist/index.js" },
      "require": { "types": "./dist/index.d.cts", "default": "./dist/index.cjs" }
    }
  }
}

Fix: ESM-only package causing CJS errors

If your package is ESM-only and consumers using require() fail:

{
  "type": "module",
  "exports": {
    ".": {
      "import": "./dist/index.js"
      // No "require" field = CJS consumers get clear error
    }
  }
}

Document in README: "This is an ESM-only package. Use import not require."

Fix: Dual package hazard

// ❌ HAZARD: Same code loaded twice if not careful
// app.mjs:  import { state } from 'my-lib'
// app.cjs:  const { state } = require('my-lib')
// Result: Two separate instances of `state`

Solution: Document that mixing ESM/CJS in the same app can cause state duplication. Recommend consumers stick to one format project-wide.

Testing Both Formats Locally

# Create test CJS consumer
mkdir test-cjs && cd test-cjs
npm init -y
npm install ../my-package
node -e "const x = require('@scope/my-lib'); console.log(x)"

# Create test ESM consumer
mkdir test-esm && cd test-esm
npm init -y && echo '{"type":"module"}' > package.json
npm install ../my-package
node -e "import('@scope/my-lib').then(x => console.log(x))"

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.