Skill

packaging-conventions

Install
1
Install the plugin
$
npx claudepluginhub jsamuelsen11/claude-config --plugin ccfg-typescript

Want just this skill?

Add to a custom plugin, then install with one command.

Description

This skill should be used when creating or editing package.json, tsconfig, managing npm dependencies, configuring monorepos, or publishing npm packages.

Tool Access

This skill uses the workspace's default tool permissions.

Skill Content

TypeScript Packaging and Publishing Conventions

This skill defines best practices for packaging, dependency management, monorepo configuration, and publishing TypeScript packages to npm with modern module formats and optimal developer experience.

package.json Configuration

Essential Fields

ALWAYS include these core fields in package.json.

CORRECT:

{
  "name": "@scope/package-name",
  "version": "1.2.3",
  "description": "Clear, concise package description",
  "keywords": ["typescript", "library", "utility"],
  "author": "Your Name <email@example.com>",
  "license": "MIT",
  "repository": {
    "type": "git",
    "url": "https://github.com/username/repo.git"
  },
  "bugs": {
    "url": "https://github.com/username/repo/issues"
  },
  "homepage": "https://github.com/username/repo#readme"
}

Module Type and Exports

ALWAYS use "type": "module" for ESM packages and configure exports properly.

CORRECT for ESM-only package:

{
  "name": "@scope/package-name",
  "version": "1.0.0",
  "type": "module",
  "main": "./dist/index.js",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "import": {
        "types": "./dist/index.d.ts",
        "default": "./dist/index.js"
      }
    }
  },
  "files": ["dist"],
  "sideEffects": false
}

CORRECT for dual ESM/CJS package:

{
  "name": "@scope/package-name",
  "version": "1.0.0",
  "type": "module",
  "main": "./dist/index.cjs",
  "module": "./dist/index.js",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "import": {
        "types": "./dist/index.d.ts",
        "default": "./dist/index.js"
      },
      "require": {
        "types": "./dist/index.d.cts",
        "default": "./dist/index.cjs"
      }
    },
    "./package.json": "./package.json"
  },
  "files": ["dist"],
  "sideEffects": false
}

CORRECT for multiple entry points:

{
  "name": "@scope/package-name",
  "version": "1.0.0",
  "type": "module",
  "exports": {
    ".": {
      "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"
      }
    },
    "./package.json": "./package.json"
  },
  "typesVersions": {
    "*": {
      "utils": ["./dist/utils.d.ts"]
    }
  }
}

Files Field

Explicitly list files to publish to avoid bloat.

CORRECT:

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

WRONG:

{
  "files": ["src", "dist", "tests", "node_modules"]
}

sideEffects Field

ALWAYS set sideEffects to enable tree-shaking.

CORRECT for pure library:

{
  "sideEffects": false
}

CORRECT for library with some side effects:

{
  "sideEffects": ["*.css", "./src/polyfills.ts"]
}

Package Manager Field

Specify packageManager for consistent dependency resolution.

CORRECT:

{
  "packageManager": "pnpm@8.15.0"
}

Scripts Convention

Standardize script names across projects.

CORRECT:

{
  "scripts": {
    "dev": "tsx watch src/index.ts",
    "build": "tsup",
    "test": "vitest",
    "test:ui": "vitest --ui",
    "test:coverage": "vitest run --coverage",
    "lint": "eslint .",
    "lint:fix": "eslint . --fix",
    "format": "prettier --write .",
    "format:check": "prettier --check .",
    "typecheck": "tsc --noEmit",
    "validate": "pnpm run typecheck && pnpm run lint && pnpm run test:coverage && pnpm run format:check",
    "clean": "rm -rf dist coverage",
    "prepublishOnly": "pnpm run validate && pnpm run build"
  }
}

Engines Field

Specify supported Node.js and package manager versions.

CORRECT:

{
  "engines": {
    "node": ">=18.0.0",
    "pnpm": ">=8.0.0"
  }
}

TypeScript Configuration

Library tsconfig.json

CORRECT for publishable library:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "lib": ["ES2023"],
    "outDir": "./dist",
    "rootDir": "./src",
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitOverride": true,
    "exactOptionalPropertyTypes": true,
    "noFallthroughCasesInSwitch": true,
    "noImplicitReturns": true,
    "noPropertyAccessFromIndexSignature": true,
    "allowUnusedLabels": false,
    "allowUnreachableCode": false,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "verbatimModuleSyntax": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
}

Application tsconfig.json with Path Aliases

CORRECT:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "lib": ["ES2023", "DOM", "DOM.Iterable"],
    "jsx": "react-jsx",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitOverride": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"],
      "@components/*": ["./src/components/*"],
      "@utils/*": ["./src/utils/*"],
      "@hooks/*": ["./src/hooks/*"],
      "@types/*": ["./src/types/*"]
    },
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "verbatimModuleSyntax": true
  },
  "include": ["src"],
  "exclude": ["node_modules", "dist"]
}

Monorepo Base tsconfig.json

CORRECT base config (tsconfig.base.json):

{
  "compilerOptions": {
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitOverride": true,
    "exactOptionalPropertyTypes": true,
    "noFallthroughCasesInSwitch": true,
    "noImplicitReturns": true,
    "noPropertyAccessFromIndexSignature": true,
    "allowUnusedLabels": false,
    "allowUnreachableCode": false,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "verbatimModuleSyntax": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true
  }
}

Package-specific tsconfig:

{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "lib": ["ES2023"],
    "outDir": "./dist",
    "rootDir": "./src",
    "composite": true
  },
  "include": ["src"],
  "exclude": ["node_modules", "dist", "**/*.test.ts"]
}

Project References for Monorepos

CORRECT root tsconfig.json with references:

{
  "files": [],
  "references": [
    { "path": "./packages/core" },
    { "path": "./packages/utils" },
    { "path": "./packages/cli" },
    { "path": "./apps/web" }
  ]
}

Build with references:

tsc --build --verbose

Bundling Configuration

tsup for Library Bundling

ALWAYS use tsup for library bundling (ESM + CJS).

CORRECT tsup.config.ts:

import { defineConfig } from 'tsup';

export default defineConfig({
  entry: ['src/index.ts'],
  format: ['cjs', 'esm'],
  dts: true,
  splitting: false,
  sourcemap: true,
  clean: true,
  treeshake: true,
  minify: false,
  target: 'es2022',
  outDir: 'dist',
});

For multiple entry points:

import { defineConfig } from 'tsup';

export default defineConfig({
  entry: {
    index: 'src/index.ts',
    utils: 'src/utils.ts',
    cli: 'src/cli.ts',
  },
  format: ['cjs', 'esm'],
  dts: true,
  splitting: false,
  sourcemap: true,
  clean: true,
  treeshake: true,
  outDir: 'dist',
});

Vite for Application Bundling

CORRECT vite.config.ts:

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';

export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
    },
  },
  build: {
    target: 'es2022',
    outDir: 'dist',
    sourcemap: true,
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['react', 'react-dom'],
        },
      },
    },
  },
});

esbuild for Fast Builds

CORRECT build script with esbuild:

// build.ts
import * as esbuild from 'esbuild';

await esbuild.build({
  entryPoints: ['src/index.ts'],
  bundle: true,
  outfile: 'dist/index.js',
  platform: 'node',
  format: 'esm',
  target: 'es2022',
  sourcemap: true,
  external: ['node:*'],
  minify: false,
});

Dependency Management

Dependency Types

Place dependencies in the correct field.

CORRECT:

{
  "dependencies": {
    "zod": "^3.22.4",
    "date-fns": "^3.0.0"
  },
  "devDependencies": {
    "typescript": "^5.3.3",
    "vitest": "^1.2.0",
    "@types/node": "^20.11.0",
    "tsup": "^8.0.1",
    "prettier": "^3.2.4",
    "eslint": "^8.56.0"
  },
  "peerDependencies": {
    "react": "^18.0.0"
  },
  "peerDependenciesMeta": {
    "react": {
      "optional": false
    }
  }
}

WRONG:

{
  "dependencies": {
    "typescript": "^5.3.3",
    "vitest": "^1.2.0",
    "@types/node": "^20.11.0",
    "react": "^18.0.0"
  }
}

Version Ranges

Use conservative version ranges for libraries, flexible for applications.

CORRECT for libraries:

{
  "dependencies": {
    "zod": "^3.22.4",
    "date-fns": "^3.0.0"
  }
}

CORRECT for applications (can be more flexible):

{
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "zod": "^3.22.4"
  }
}

Peer Dependencies for Libraries

ALWAYS specify peer dependencies for libraries that extend other tools.

CORRECT React component library:

{
  "name": "@scope/ui-components",
  "peerDependencies": {
    "react": "^18.0.0",
    "react-dom": "^18.0.0"
  },
  "peerDependenciesMeta": {
    "react-dom": {
      "optional": false
    }
  },
  "devDependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  }
}

CORRECT TypeScript plugin:

{
  "name": "@scope/typescript-plugin",
  "peerDependencies": {
    "typescript": "^5.0.0"
  },
  "devDependencies": {
    "typescript": "^5.3.3"
  }
}

Monorepo Configuration

pnpm Workspaces

ALWAYS use pnpm workspaces for monorepos.

CORRECT pnpm-workspace.yaml:

packages:
  - 'packages/*'
  - 'apps/*'
  - 'tools/*'

CORRECT root package.json:

{
  "name": "monorepo-root",
  "private": true,
  "packageManager": "pnpm@8.15.0",
  "scripts": {
    "dev": "pnpm -r --parallel dev",
    "build": "pnpm -r --filter '!@scope/app-*' build",
    "test": "pnpm -r test",
    "lint": "pnpm -r lint",
    "typecheck": "pnpm -r typecheck",
    "clean": "pnpm -r clean && rm -rf node_modules"
  },
  "devDependencies": {
    "typescript": "^5.3.3",
    "prettier": "^3.2.4",
    "eslint": "^8.56.0"
  }
}

Package Dependencies in Monorepo

CORRECT cross-package dependency:

{
  "name": "@scope/app-web",
  "dependencies": {
    "@scope/core": "workspace:*",
    "@scope/ui": "workspace:*"
  }
}

Turborepo for Build Orchestration

CORRECT turbo.json:

{
  "$schema": "https://turbo.build/schema.json",
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**", ".next/**"]
    },
    "test": {
      "dependsOn": ["^build"],
      "outputs": ["coverage/**"]
    },
    "lint": {
      "outputs": []
    },
    "typecheck": {
      "dependsOn": ["^build"],
      "outputs": []
    },
    "dev": {
      "cache": false,
      "persistent": true
    }
  }
}

Run with Turborepo:

pnpm turbo build
pnpm turbo test --filter=@scope/core
pnpm turbo dev --parallel

Shared Configurations in Monorepo

CORRECT shared TypeScript config:

monorepo/
├── tsconfig.base.json
├── packages/
│   ├── core/
│   │   └── tsconfig.json (extends ../../tsconfig.base.json)
│   └── utils/
│       └── tsconfig.json (extends ../../tsconfig.base.json)
└── apps/
    └── web/
        └── tsconfig.json (extends ../../tsconfig.base.json)

CORRECT shared ESLint config:

// packages/eslint-config/index.js
import js from '@eslint/js';
import tseslint from 'typescript-eslint';

export default tseslint.config(js.configs.recommended, ...tseslint.configs.strictTypeChecked);

Use in packages:

// packages/core/eslint.config.js
import baseConfig from '@scope/eslint-config';

export default [
  ...baseConfig,
  {
    languageOptions: {
      parserOptions: {
        project: true,
        tsconfigRootDir: import.meta.dirname,
      },
    },
  },
];

Publishing to npm

Pre-publish Checklist

Before publishing, ensure:

  1. All tests pass
  2. Type checking passes
  3. Linting passes
  4. Build succeeds
  5. README is complete
  6. CHANGELOG is updated
  7. Version is bumped
  8. Git tag is created

Changesets for Versioning

ALWAYS use Changesets for version management.

Install:

pnpm add -D @changesets/cli
pnpm exec changeset init

CORRECT .changeset/config.json:

{
  "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
  "changelog": "@changesets/cli/changelog",
  "commit": false,
  "fixed": [],
  "linked": [],
  "access": "public",
  "baseBranch": "main",
  "updateInternalDependencies": "patch",
  "ignore": []
}

Add changeset:

pnpm exec changeset

Version packages:

pnpm exec changeset version

Publish:

pnpm exec changeset publish

Package.json for Publishing

CORRECT for public package:

{
  "name": "@scope/package-name",
  "version": "1.0.0",
  "description": "Package description",
  "keywords": ["typescript", "library"],
  "author": "Your Name",
  "license": "MIT",
  "repository": {
    "type": "git",
    "url": "https://github.com/username/repo.git"
  },
  "publishConfig": {
    "access": "public"
  },
  "scripts": {
    "prepublishOnly": "pnpm run validate && pnpm run build"
  }
}

CORRECT for private package:

{
  "name": "@scope/internal-package",
  "version": "1.0.0",
  "private": true
}

npm Scripts for Publishing

CORRECT:

{
  "scripts": {
    "prepublishOnly": "pnpm run validate && pnpm run build",
    "prepack": "pnpm run build",
    "postpublish": "git push --follow-tags"
  }
}

.npmignore

CORRECT .npmignore:

# Source files
src/
tests/
e2e/
**/*.test.ts
**/*.spec.ts
**/__tests__/

# Configuration
tsconfig.json
vitest.config.ts
eslint.config.js
.prettierrc
.editorconfig

# Development
.vscode/
.idea/
*.log
.env
.env.*

# Build artifacts
coverage/
.vitest/
node_modules/

# Git
.git/
.gitignore

Or use files field in package.json (preferred):

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

Dual Package Hazard Prevention

Correct Dual ESM/CJS Setup

CORRECT:

{
  "type": "module",
  "main": "./dist/index.cjs",
  "module": "./dist/index.js",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "import": {
        "types": "./dist/index.d.ts",
        "default": "./dist/index.js"
      },
      "require": {
        "types": "./dist/index.d.cts",
        "default": "./dist/index.cjs"
      }
    }
  }
}

WRONG (can cause dual package hazard):

{
  "main": "./dist/index.js",
  "module": "./dist/index.esm.js",
  "exports": "./dist/index.js"
}

tsup Configuration for Dual Format

CORRECT:

import { defineConfig } from 'tsup';

export default defineConfig({
  entry: ['src/index.ts'],
  format: ['cjs', 'esm'],
  dts: true,
  splitting: false,
  sourcemap: true,
  clean: true,
  outDir: 'dist',
  // Ensures .cjs and .js extensions
  outExtension({ format }) {
    return {
      js: format === 'cjs' ? '.cjs' : '.js',
    };
  },
});

Documentation

README Template

CORRECT README.md structure:

# @scope/package-name

Brief description of the package.

## Installation

```bash
pnpm add @scope/package-name
```

## Usage

```typescript
import { someFunction } from '@scope/package-name';

const result = someFunction('input');
```

## API

### `someFunction(input: string): string`

Description of what the function does.

**Parameters:**

- `input` - Description of parameter

**Returns:** Description of return value

**Example:**

```typescript
const result = someFunction('test');
// result: 'TEST'
```

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md)

## License

MIT © Your Name

API Documentation with TSDoc

CORRECT:

/**
 * Validates an email address.
 *
 * @param email - The email address to validate
 * @returns `true` if valid, `false` otherwise
 *
 * @example
 * ```ts
 * isValidEmail('user@example.com'); // true
 * isValidEmail('invalid'); // false
 * ```
 *
 * @public
 */
export function isValidEmail(email: string): boolean {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}

Generate Documentation with TypeDoc

Install:

pnpm add -D typedoc

CORRECT typedoc.json:

{
  "entryPoints": ["src/index.ts"],
  "out": "docs",
  "plugin": ["typedoc-plugin-markdown"],
  "readme": "README.md",
  "exclude": ["**/*.test.ts", "**/*.spec.ts"]
}

Generate:

pnpm exec typedoc

CI/CD for Publishing

GitHub Actions Workflow

CORRECT .github/workflows/publish.yml:

name: Publish Package

on:
  push:
    branches:
      - main

jobs:
  publish:
    runs-on: ubuntu-latest
    permissions:
      contents: write
      id-token: write
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - uses: actions/setup-node@v4
        with:
          node-version: 20
          registry-url: 'https://registry.npmjs.org'

      - uses: pnpm/action-setup@v2
        with:
          version: 8

      - run: pnpm install

      - run: pnpm run validate

      - run: pnpm run build

      - name: Create Release Pull Request or Publish
        uses: changesets/action@v1
        with:
          publish: pnpm exec changeset publish
          version: pnpm exec changeset version
          commit: 'chore: version packages'
          title: 'chore: version packages'
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

Best Practices Summary

When configuring packages:

  1. Use "type": "module" for ESM
  2. Configure proper exports field
  3. Specify files to publish
  4. Set sideEffects for tree-shaking
  5. Use packageManager field
  6. Standardize script names
  7. Specify engine requirements
  8. Use strict TypeScript config
  9. Bundle with tsup for libraries
  10. Place dependencies correctly
  11. Use pnpm workspaces for monorepos
  12. Use Turborepo for orchestration
  13. Use Changesets for versioning
  14. Write comprehensive README
  15. Add TSDoc comments
  16. Configure CI/CD for publishing

These conventions ensure professional, maintainable npm packages.

Stats
Stars0
Forks0
Last CommitFeb 10, 2026
Actions

Similar Skills