diff --git a/docs/README.skills.md b/docs/README.skills.md index 90484ebdf..65f555725 100644 --- a/docs/README.skills.md +++ b/docs/README.skills.md @@ -226,6 +226,7 @@ See [CONTRIBUTING.md](../CONTRIBUTING.md#adding-skills) for guidelines on how to | [tldr-prompt](../skills/tldr-prompt/SKILL.md) | Create tldr summaries for GitHub Copilot files (prompts, agents, instructions, collections), MCP servers, or documentation from URLs and queries. | None | | [transloadit-media-processing](../skills/transloadit-media-processing/SKILL.md) | Process media files (video, audio, images, documents) using Transloadit. Use when asked to encode video to HLS/MP4, generate thumbnails, resize or watermark images, extract audio, concatenate clips, add subtitles, OCR documents, or run any media processing pipeline. Covers 86+ processing robots for file transformation at scale. | None | | [typescript-mcp-server-generator](../skills/typescript-mcp-server-generator/SKILL.md) | Generate a complete MCP server project in TypeScript with tools, resources, and proper configuration | None | +| [typescript-package-manager](../skills/typescript-package-manager/SKILL.md) | Expert 10x Software engineer specializing in TypeScript with deep knowledge of all popular package management tools including npm, yarn, pnpm, bun, and deno. Use when asked to configure package managers, manage dependencies, set up workspaces, resolve package conflicts, optimize package.json files, troubleshoot installation issues, or work with monorepos. Expertise covers package scripts, version management, lock files, and build tool integration. | `assets/package-json-template.md`
`assets/package-manager-comparison.md`
`references/integration-with-build-tools.md`
`references/package-management.md`
`scripts/bun-workflow.js`
`scripts/bun-workflow.md`
`scripts/health-check.js`
`scripts/health-check.md`
`scripts/npm-workflow.js`
`scripts/npm-workflow.md`
`scripts/pnpm-workflow.md`
`scripts/yarn-workflow.md` | | [typespec-api-operations](../skills/typespec-api-operations/SKILL.md) | Add GET, POST, PATCH, and DELETE operations to a TypeSpec API plugin with proper routing, parameters, and adaptive cards | None | | [typespec-create-agent](../skills/typespec-create-agent/SKILL.md) | Generate a complete TypeSpec declarative agent with instructions, capabilities, and conversation starters for Microsoft 365 Copilot | None | | [typespec-create-api-plugin](../skills/typespec-create-api-plugin/SKILL.md) | Generate a TypeSpec API plugin with REST operations, authentication, and Adaptive Cards for Microsoft 365 Copilot | None | diff --git a/skills/typescript-package-manager/SKILL.md b/skills/typescript-package-manager/SKILL.md new file mode 100644 index 000000000..4f63aef49 --- /dev/null +++ b/skills/typescript-package-manager/SKILL.md @@ -0,0 +1,308 @@ +--- +name: typescript-package-manager +description: 'Expert 10x Software engineer specializing in TypeScript with deep knowledge of all popular package management tools including npm, yarn, pnpm, bun, and deno. Use when asked to configure package managers, manage dependencies, set up workspaces, resolve package conflicts, optimize package.json files, troubleshoot installation issues, or work with monorepos. Expertise covers package scripts, version management, lock files, and build tool integration.' +--- + +# TypeScript Package Manager Skill + +An expert skill for managing TypeScript projects with comprehensive knowledge of modern package management tools and ecosystem best practices. + +## When to Use This Skill + +- Setting up or configuring package managers (npm, yarn, pnpm, bun, deno) +- Managing project dependencies and resolving conflicts +- Optimizing package.json configuration +- Working with monorepos and workspaces +- Troubleshooting installation or dependency issues +- Configuring package scripts and build pipelines +- Understanding lock files and version constraints +- Integrating package managers with build tools +- Migrating between package managers +- Performance optimization of package operations + +## Prerequisites + +- Node.js environment (for npm, yarn, pnpm, bun) +- Basic understanding of TypeScript/JavaScript projects +- Terminal/command line access +- Understanding of semantic versioning (semver) + +## Shorthand Keywords + +Keywords to trigger this skill quickly: + +```javascript +openPrompt = ["typescript-package-manager", "ts-pkg", "pkg-manager"] +``` + +Use these shorthand commands for quick invocation: + +- `ts-pkg --install ` +- `pkg-manager --compare-tools` +- `ts-pkg migrate to pnpm` +- `pkg-manager --optimize workspace` + +## Core Capabilities + +### Package Manager Expertise + +This skill provides deep knowledge across all major JavaScript/TypeScript package managers: + +- **npm**: The default Node.js package manager, widely used and battle-tested +- **yarn**: Fast, reliable package manager with enhanced features +- **pnpm**: Disk-space efficient with strict dependency isolation +- **bun**: Ultra-fast all-in-one toolkit with native package management +- **deno**: Secure-by-default runtime with built-in tooling + +### Dependency Management + +- Installing, updating, and removing dependencies +- Managing dev dependencies vs production dependencies +- Understanding peer dependencies and optional dependencies +- Resolving version conflicts and compatibility issues +- Audit and security vulnerability management +- Dependency deduplication and optimization + +### Configuration and Optimization + +- package.json structure and best practices +- Lock file management (package-lock.json, yarn.lock, pnpm-lock.yaml) +- Workspace and monorepo configuration +- Scripts and lifecycle hooks +- Registry configuration and private packages +- CI/CD integration + +### Ecosystem Integration + +- TypeScript compiler (tsc) integration with package managers +- Build tool configuration (Vite, webpack, esbuild, Rollup, Turbopack) +- Testing framework setup (Vitest, Jest, Mocha) +- Linting and formatting tools (ESLint, Prettier) +- Development server configuration and hot reload +- CI/CD pipeline optimization + +## Key Concepts + +### Semantic Versioning (semver) + +Understanding version constraints in package.json: + +- **^1.2.3** (caret): >=1.2.3 <2.0.0 - Allows minor and patch updates (recommended) +- **~1.2.3** (tilde): >=1.2.3 <1.3.0 - Allows only patch updates +- **1.2.3** (exact): Exact version only +- **>=1.2.3 <2.0.0** (range): Custom version range +- **latest**: Always use latest version (dangerous, not recommended) + +### Lock Files + +Lock files ensure reproducible installations across environments: + +- **package-lock.json** (npm): JSON format, exact dependency tree +- **yarn.lock** (yarn): YAML-like format, deterministic resolution +- **pnpm-lock.yaml** (pnpm): YAML format with content-addressable storage +- **bun.lockb** (bun): Binary format for fast parsing + +**Best Practice:** Always commit lock files to version control. + +### Workspaces (Monorepos) + +Manage multiple packages in a single repository: + +```json +{ + "workspaces": ["packages/*", "apps/*"] +} +``` + +**Benefits:** +- Shared dependencies across packages +- Cross-package development and testing +- Unified versioning and release coordination +- Simplified dependency management + +## Best Practices + +1. **Choose the Right Tool**: Understand trade-offs between package managers for your project needs + - npm: Maximum compatibility, widest ecosystem support + - yarn: Enhanced features, good monorepo support + - pnpm: Best disk space efficiency, strict dependencies + - bun: Maximum speed, all-in-one tooling + - deno: Security-first, URL-based imports + +2. **Lock File Discipline**: Always commit lock files to ensure reproducible builds across environments + +3. **Semantic Versioning**: Use appropriate version constraints + - Use `^` (caret) for most dependencies (recommended) + - Use `~` (tilde) for conservative updates + - Use exact versions only when necessary + +4. **Security First**: Regularly audit dependencies for vulnerabilities + ```bash + npm audit # npm + yarn audit # yarn + pnpm audit # pnpm + bun pm audit # bun + ``` + +5. **Workspace Optimization**: Leverage workspaces for monorepo efficiency and shared dependencies + +6. **Script Consistency**: Use cross-platform compatible scripts (avoid shell-specific commands) + +7. **Minimal Dependencies**: Avoid unnecessary packages to reduce complexity, security surface, and bundle size + +8. **Documentation**: Document package manager choice, special configurations, and setup instructions in README.md + +9. **CI/CD Optimization**: Use frozen lockfiles in CI for deterministic builds + ```bash + npm ci # npm + yarn install --frozen-lockfile # yarn + pnpm install --frozen-lockfile # pnpm + bun install --frozen-lockfile # bun + ``` + +10. **Version Management**: Use `packageManager` field in package.json to enforce consistency + ```json + { + "packageManager": "pnpm@8.15.0" + } + ``` + +## Choosing the Right Package Manager + +### Decision Factors + +**npm (Default Choice)** +- ✅ Maximum compatibility and ecosystem support +- ✅ Default with Node.js, no additional setup +- ✅ Well-tested and mature +- ❌ Slower than alternatives +- ❌ Less disk-efficient + +**yarn (Enhanced Features)** +- ✅ Good performance and caching +- ✅ Excellent monorepo/workspace support +- ✅ Plug'n'Play mode (Berry) for zero-installs +- ❌ Classic vs Berry version confusion +- ❌ Plug'n'Play requires IDE setup + +**pnpm (Recommended for Most Projects)** +- ✅ 60-70% disk space savings +- ✅ 2-3x faster than npm +- ✅ Strict dependency isolation prevents phantom dependencies +- ✅ Excellent monorepo support +- ❌ May expose hidden dependency issues + +**bun (Cutting Edge)** +- ✅ Blazing fast (5-10x faster than npm) +- ✅ All-in-one: runtime, bundler, test runner +- ✅ Native TypeScript support +- ❌ Newer and less mature +- ❌ Some npm package incompatibilities + +**deno (Security-First)** +- ✅ Secure by default with explicit permissions +- ✅ Native TypeScript support +- ✅ No package.json or node_modules +- ❌ Different paradigm (URL-based imports) +- ❌ Smaller ecosystem than npm + +### Quick Recommendation + +- **New TypeScript project**: pnpm or bun +- **Existing project**: Keep current manager unless migrating for specific benefits +- **Monorepo**: pnpm or yarn (berry) +- **Maximum compatibility**: npm +- **Security-critical**: deno or pnpm + +## Common Tasks + +### Installing Dependencies + +```bash +# npm +npm install + +# yarn +yarn install + +# pnpm +pnpm install + +# bun +bun install +``` + +### Adding New Dependencies + +```bash +# Production dependency +npm install package-name +yarn add package-name +pnpm add package-name +bun add package-name + +# Development dependency +npm install --save-dev package-name +yarn add --dev package-name +pnpm add --save-dev package-name +bun add --dev package-name +``` + +### Running Scripts + +```bash +# All package managers support npm scripts +npm run script-name +yarn script-name +pnpm script-name +bun run script-name +``` + +## Troubleshooting + +### Common Issues + +**Installation Failures**: +- Clear cache and retry +- Delete node_modules and lock file, reinstall +- Check for incompatible peer dependencies +- Verify Node.js version compatibility + +**Version Conflicts**: +- Use resolutions/overrides in package.json +- Upgrade conflicting dependencies +- Consider using pnpm for better isolation + +**Performance Issues**: +- Use pnpm or bun for faster operations +- Enable caching mechanisms +- Optimize workspace configuration + +**Lock File Conflicts**: +- Resolve merge conflicts carefully +- Regenerate lock file if corrupted +- Ensure team uses same package manager + +## References + +See the `references/` folder for detailed documentation: + +- **package-management.md** - Comprehensive guide to JavaScript/TypeScript package managers including npm, yarn, pnpm, bun, deno, with concepts, version management, security, and best practices +- **integration-with-build-tools.md** - Complete guide to integrating package managers with Vite, webpack, esbuild, Rollup, TypeScript compiler (tsc), and CI/CD pipelines + +## Scripts + +See the `scripts/` folder for workflow guides and automation: + +- **npm-workflow.md** / **npm-workflow.js** - Complete npm workflow from initialization to publishing +- **yarn-workflow.md** - Complete yarn workflow covering Classic (1.x) and Berry (3.x+) versions +- **pnpm-workflow.md** - Complete pnpm workflow with monorepo and workspace features +- **bun-workflow.md** / **bun-workflow.js** - Complete bun workflow for ultra-fast package management +- **health-check.md** / **health-check.js** - Package manager health check and diagnostics scripts + +## Assets + +See the `assets/` folder for templates and reference materials: + +- **package-manager-comparison.md** - Detailed comparison matrix, benchmarks, and decision tree for choosing package managers +- **package-json-template.md** - Production-ready package.json template with field-by-field explanations and best practices diff --git a/skills/typescript-package-manager/assets/package-json-template.md b/skills/typescript-package-manager/assets/package-json-template.md new file mode 100644 index 000000000..f193d2f17 --- /dev/null +++ b/skills/typescript-package-manager/assets/package-json-template.md @@ -0,0 +1,523 @@ +# Package.json Template for TypeScript Projects + +A comprehensive, production-ready `package.json` template with best practices for TypeScript projects. + +## Complete Template + +```json +{ + "name": "@scope/my-typescript-project", + "version": "1.0.0", + "description": "A production-ready TypeScript project", + "keywords": ["typescript", "nodejs", "library"], + "homepage": "https://github.com/username/project#readme", + "bugs": { + "url": "https://github.com/username/project/issues" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/username/project.git" + }, + "license": "MIT", + "author": { + "name": "Your Name", + "email": "you@example.com", + "url": "https://yourwebsite.com" + }, + "contributors": [], + "type": "module", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.cjs" + }, + "./utils": { + "types": "./dist/utils.d.ts", + "import": "./dist/utils.js" + }, + "./package.json": "./package.json" + }, + "main": "./dist/index.cjs", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "bin": { + "my-cli": "./dist/cli.js" + }, + "files": [ + "dist", + "README.md", + "LICENSE" + ], + "scripts": { + "dev": "tsx watch src/index.ts", + "build": "npm run build:clean && npm run build:tsc && npm run build:bundle", + "build:clean": "rimraf dist", + "build:tsc": "tsc", + "build:bundle": "tsup src/index.ts --format cjs,esm --dts", + "test": "vitest run", + "test:watch": "vitest", + "test:coverage": "vitest run --coverage", + "type-check": "tsc --noEmit", + "lint": "eslint src --ext .ts,.tsx", + "lint:fix": "eslint src --ext .ts,.tsx --fix", + "format": "prettier --write \"src/**/*.{ts,tsx,json,md}\"", + "format:check": "prettier --check \"src/**/*.{ts,tsx,json,md}\"", + "validate": "npm-run-all --parallel type-check lint test", + "prepare": "husky install", + "prepublishOnly": "npm run validate && npm run build", + "release": "semantic-release", + "clean": "rimraf dist node_modules coverage .turbo" + }, + "dependencies": { + "zod": "^3.22.4" + }, + "devDependencies": { + "@types/node": "^20.11.0", + "@typescript-eslint/eslint-plugin": "^6.19.0", + "@typescript-eslint/parser": "^6.19.0", + "eslint": "^8.56.0", + "husky": "^8.0.3", + "lint-staged": "^15.2.0", + "npm-run-all": "^4.1.5", + "prettier": "^3.2.4", + "rimraf": "^5.0.5", + "semantic-release": "^23.0.0", + "tsup": "^8.0.1", + "tsx": "^4.7.0", + "typescript": "^5.3.3", + "vitest": "^1.2.0" + }, + "peerDependencies": { + "react": "^18.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + } + }, + "optionalDependencies": { + "fsevents": "^2.3.3" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=9.0.0" + }, + "packageManager": "pnpm@8.15.0", + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/" + }, + "lint-staged": { + "*.{ts,tsx}": [ + "eslint --fix", + "prettier --write" + ], + "*.{json,md}": [ + "prettier --write" + ] + } +} +``` + +## Field-by-Field Explanation + +### Essential Metadata + +```json +{ + "name": "@scope/my-typescript-project", + "version": "1.0.0", + "description": "A production-ready TypeScript project" +} +``` + +- **name**: Use scoped packages (@scope/name) for namespacing +- **version**: Follow semantic versioning (MAJOR.MINOR.PATCH) +- **description**: Clear, concise (under 200 chars) + +### Module System + +```json +{ + "type": "module" +} +``` + +- **module**: Default to ESM for modern projects +- Omit or use `"type": "commonjs"` for CJS only + +### Export Configuration (Node.js 12+) + +```json +{ + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.cjs" + }, + "./utils": { + "types": "./dist/utils.d.ts", + "import": "./dist/utils.js" + }, + "./package.json": "./package.json" + } +} +``` + +**Benefits:** +- Multiple entry points +- TypeScript type definitions +- Dual CJS/ESM support +- Subpath exports + +### Legacy Fields (Backward Compatibility) + +```json +{ + "main": "./dist/index.cjs", + "module": "./dist/index.js", + "types": "./dist/index.d.ts" +} +``` + +Keep these for tools that don't support exports field yet. + +### CLI Binary + +```json +{ + "bin": { + "my-cli": "./dist/cli.js" + } +} +``` + +Creates executable command when installed globally or in node_modules/.bin/ + +**Requirements:** +- Shebang in CLI file: `#!/usr/bin/env node` +- Make executable: `chmod +x dist/cli.js` + +### Files to Publish + +```json +{ + "files": [ + "dist", + "README.md", + "LICENSE" + ] +} +``` + +**Whitelist approach** - only listed files are published. + +**Always include:** +- Build output (dist/) +- README.md +- LICENSE +- Type definitions + +**Never include:** +- Source files (src/) +- Tests +- Configuration files +- node_modules + +### Development Scripts + +```json +{ + "scripts": { + "dev": "tsx watch src/index.ts", + "build": "npm run build:clean && npm run build:tsc", + "test": "vitest run", + "lint": "eslint src --ext .ts,.tsx" + } +} +``` + +**Essential scripts:** +- **dev**: Development server with watch mode +- **build**: Production build +- **test**: Run test suite +- **lint**: Code quality checks +- **format**: Code formatting +- **type-check**: TypeScript validation without emit + +### CI/CD Scripts + +```json +{ + "scripts": { + "validate": "npm-run-all --parallel type-check lint test", + "prepublishOnly": "npm run validate && npm run build" + } +} +``` + +**validate**: Run all checks in parallel +**prepublishOnly**: Lifecycle hook before npm publish + +### Dependency Types + +```json +{ + "dependencies": { + "zod": "^3.22.4" + }, + "devDependencies": { + "typescript": "^5.3.3" + }, + "peerDependencies": { + "react": "^18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.3" + } +} +``` + +**dependencies**: Required at runtime +**devDependencies**: Build/test only +**peerDependencies**: Expected from consumer +**optionalDependencies**: Nice to have, not required + +### Peer Dependencies Metadata + +```json +{ + "peerDependenciesMeta": { + "react": { + "optional": true + } + } +} +``` + +Make peer dependencies optional to avoid warnings for optional features. + +### Engine Requirements + +```json +{ + "engines": { + "node": ">=18.0.0", + "npm": ">=9.0.0" + } +} +``` + +**Best practices:** +- Use >= for minimum versions +- Specify LTS Node.js versions +- Include pnpm/yarn if required + +### Package Manager Enforcement + +```json +{ + "packageManager": "pnpm@8.15.0" +} +``` + +**Corepack feature** (Node.js 16.9+): +- Enforces specific package manager +- Automatically downloads correct version +- Prevents version mismatches + +### Publishing Configuration + +```json +{ + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/" + } +} +``` + +**access**: "public" for scoped packages (default is restricted) +**registry**: Override default registry + +### Git Hooks Configuration + +```json +{ + "lint-staged": { + "*.{ts,tsx}": [ + "eslint --fix", + "prettier --write" + ] + } +} +``` + +Used with husky for pre-commit hooks. + +## Workspace Configuration (Monorepos) + +### Root package.json + +```json +{ + "name": "my-monorepo", + "private": true, + "workspaces": [ + "packages/*", + "apps/*" + ], + "scripts": { + "build": "turbo run build", + "test": "turbo run test", + "lint": "turbo run lint" + }, + "devDependencies": { + "turbo": "^1.12.0", + "typescript": "^5.3.3" + } +} +``` + +### Package-specific package.json + +```json +{ + "name": "@monorepo/shared", + "version": "1.0.0", + "private": true, + "main": "./src/index.ts", + "types": "./src/index.ts", + "dependencies": { + "zod": "workspace:^" + } +} +``` + +**workspace:*** - Use workspace version of dependency + +## Version Constraints Explained + +```json +{ + "dependencies": { + "exact": "1.2.3", + "caret": "^1.2.3", + "tilde": "~1.2.3", + "range": ">=1.2.3 <2.0.0", + "latest": "latest", + "workspace": "workspace:*" + } +} +``` + +- **1.2.3** (exact): Only 1.2.3 +- **^1.2.3** (caret): >=1.2.3 <2.0.0 (recommended) +- **~1.2.3** (tilde): >=1.2.3 <1.3.0 +- **>= <** (range): Custom range +- **latest**: Always use latest (dangerous) +- **workspace:*** : Use local workspace package + +## Environment-Specific Templates + +### Library Package + +```json +{ + "name": "@scope/library", + "type": "module", + "exports": "./dist/index.js", + "files": ["dist"], + "scripts": { + "build": "tsup src/index.ts --format esm,cjs --dts" + } +} +``` + +### CLI Tool + +```json +{ + "name": "my-cli", + "bin": { + "my-cli": "./dist/cli.js" + }, + "preferGlobal": true, + "scripts": { + "build": "tsup src/cli.ts --format esm --shims" + } +} +``` + +### Application (Not Published) + +```json +{ + "name": "my-app", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "start": "node dist/server.js" + } +} +``` + +## Best Practices + +1. **Always specify types**: Include TypeScript definitions +2. **Use exports field**: For modern module resolution +3. **Lock down engines**: Prevent incompatible Node.js versions +4. **Whitelist files**: Only publish necessary files +5. **Private by default**: Add "private": true for non-published packages +6. **Semantic versioning**: Follow semver strictly +7. **Meaningful scripts**: Clear, consistent naming +8. **Document packageManager**: Help contributors use correct tool +9. **Lint-staged integration**: Automate code quality +10. **Test before publish**: Use prepublishOnly hook + +## Common Patterns + +### TypeScript Library + +Focus on type definitions, dual CJS/ESM, and exports field. + +### CLI Tool + +Emphasize bin field, shebang, and preferGlobal. + +### Monorepo Package + +Use workspaces, workspace: protocol, and Turborepo/Nx. + +### Full-stack Application + +Private package, separate build scripts, environment configuration. + +## Troubleshooting + +### Module Resolution Issues + +Ensure `type`, `exports`, `main`, and `module` fields align with your build output. + +### TypeScript Not Found + +Add types field pointing to .d.ts files. + +### CLI Not Executable + +1. Add shebang: `#!/usr/bin/env node` +2. Make executable: `chmod +x dist/cli.js` +3. Ensure bin field is correct + +### Peer Dependency Warnings + +Use peerDependenciesMeta to mark optional. + +## Further Reading + +- [Package.json Documentation](https://docs.npmjs.com/cli/v9/configuring-npm/package-json) +- [Node.js Packages](https://nodejs.org/api/packages.html) +- [TypeScript Module Resolution](https://www.typescriptlang.org/docs/handbook/module-resolution.html) diff --git a/skills/typescript-package-manager/assets/package-manager-comparison.md b/skills/typescript-package-manager/assets/package-manager-comparison.md new file mode 100644 index 000000000..9e0a99660 --- /dev/null +++ b/skills/typescript-package-manager/assets/package-manager-comparison.md @@ -0,0 +1,289 @@ +# Package Manager Comparison Chart + +A comprehensive comparison of popular JavaScript/TypeScript package managers to help you choose the right tool for your project. + +## Quick Comparison Matrix + +| Feature | npm | yarn (classic) | yarn (berry) | pnpm | bun | deno | +|---------|-----|----------------|--------------|------|-----|------| +| **Speed** | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | +| **Disk Space** | ⭐⭐ | ⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | +| **Monorepo Support** | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐ | +| **Node.js Compatibility** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ | +| **Maturity** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ | +| **Ecosystem** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ | +| **Security** | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | + +## Detailed Comparison + +### Installation Speed + +**Benchmark (Average installation time for 1000 packages):** +- **bun**: ~5s (Fastest - native implementation) +- **pnpm**: ~8s (Content-addressable store) +- **yarn (berry)**: ~12s (Optimized fetching) +- **yarn (classic)**: ~15s (Parallel downloads) +- **npm v9+**: ~20s (Improved caching) +- **deno**: N/A (URL-based imports) + +### Disk Space Usage + +**Space savings compared to npm (baseline):** +- **pnpm**: 60-70% less (hard links, single store) +- **yarn (berry)**: 30-40% less (zero-installs optional) +- **bun**: 20-30% less (efficient caching) +- **deno**: 40-50% less (centralized cache) +- **yarn (classic)**: Similar to npm +- **npm**: Baseline + +### Lock File Format + +| Manager | File | Format | Merge-friendly | +|---------|------|--------|----------------| +| npm | package-lock.json | JSON | ❌ No | +| yarn (classic) | yarn.lock | Custom | ✅ Yes | +| yarn (berry) | yarn.lock | Custom | ✅ Yes | +| pnpm | pnpm-lock.yaml | YAML | ✅ Yes | +| bun | bun.lockb | Binary | ❌ No | +| deno | deno.lock | JSON | ❌ No | + +## Command Comparison + +### Basic Operations + +| Operation | npm | yarn | pnpm | bun | +|-----------|-----|------|------|-----| +| **Install all** | `npm install` | `yarn install` | `pnpm install` | `bun install` | +| **Add package** | `npm install pkg` | `yarn add pkg` | `pnpm add pkg` | `bun add pkg` | +| **Add dev** | `npm install -D pkg` | `yarn add -D pkg` | `pnpm add -D pkg` | `bun add -d pkg` | +| **Add global** | `npm install -g pkg` | `yarn global add pkg` | `pnpm add -g pkg` | `bun add -g pkg` | +| **Remove** | `npm uninstall pkg` | `yarn remove pkg` | `pnpm remove pkg` | `bun remove pkg` | +| **Update** | `npm update` | `yarn upgrade` | `pnpm update` | `bun update` | +| **Update pkg** | `npm update pkg` | `yarn upgrade pkg` | `pnpm update pkg` | `bun update pkg` | +| **Run script** | `npm run script` | `yarn script` | `pnpm script` | `bun run script` | +| **List packages** | `npm list` | `yarn list` | `pnpm list` | `bun pm ls` | +| **Clean install** | `npm ci` | `yarn install --frozen-lockfile` | `pnpm install --frozen-lockfile` | `bun install --frozen-lockfile` | + +### Advanced Operations + +| Operation | npm | yarn | pnpm | bun | +|-----------|-----|------|------|-----| +| **Audit** | `npm audit` | `yarn audit` | `pnpm audit` | `bun pm audit` | +| **Cache clean** | `npm cache clean --force` | `yarn cache clean` | `pnpm store prune` | `bun pm cache rm` | +| **Workspace run** | `npm run -ws script` | `yarn workspaces run script` | `pnpm -r script` | `bun run --filter '*' script` | +| **Dedupe** | `npm dedupe` | `yarn dedupe` | `pnpm dedupe` | N/A (automatic) | +| **Why package** | `npm why pkg` | `yarn why pkg` | `pnpm why pkg` | `bun pm ls pkg` | + +## Use Case Recommendations + +### 🏢 Enterprise Projects +**Recommended: pnpm or npm** +- pnpm: Best disk space efficiency, strict dependencies +- npm: Maximum compatibility, enterprise support +- Avoid: bun (too new), deno (different paradigm) + +### 🚀 Startups & New Projects +**Recommended: bun or pnpm** +- bun: Maximum speed, all-in-one tooling +- pnpm: Excellent performance, mature ecosystem +- Consider: yarn (berry) for advanced features + +### 📦 Monorepos +**Recommended: pnpm, yarn (berry), or Turborepo** +- pnpm: Native workspace support, isolated dependencies +- yarn (berry): Plug'n'Play, constraints +- Turborepo: Works with any package manager, adds caching + +### 🔧 Libraries & Open Source +**Recommended: npm or pnpm** +- npm: Widest compatibility for contributors +- pnpm: Performance benefits, growing adoption +- Document choice clearly in README + +### 🎨 Frontend Applications +**Recommended: bun or pnpm** +- bun: Fast builds with built-in bundler +- pnpm: Great with Vite, Next.js, Nuxt +- yarn: Good Plug'n'Play support + +### 🖥️ Backend Services +**Recommended: npm or bun** +- npm: Proven stability, wide deployment support +- bun: Native TypeScript, fast cold starts +- pnpm: Good for microservices + +### 🔐 Security-Critical +**Recommended: deno or pnpm** +- deno: Secure by default, explicit permissions +- pnpm: Strict dependency isolation +- yarn (berry): Constraints for policy enforcement + +## Migration Considerations + +### From npm to pnpm + +**Benefits:** +- ✅ 60-70% disk space savings +- ✅ 2-3x faster installs +- ✅ Stricter dependency management +- ✅ Drop-in replacement + +**Challenges:** +- ⚠️ Phantom dependencies may break +- ⚠️ Some dev tools need configuration +- ⚠️ CI/CD pipelines need updates + +### From npm to bun + +**Benefits:** +- ✅ 5-10x faster installs +- ✅ Built-in bundler and test runner +- ✅ Native TypeScript support +- ✅ Single runtime for everything + +**Challenges:** +- ⚠️ Newer, less mature +- ⚠️ Some npm packages incompatible +- ⚠️ Limited IDE support +- ⚠️ Breaking changes more frequent + +### From npm to yarn + +**Benefits:** +- ✅ Better monorepo support +- ✅ Plug'n'Play (berry) for speed +- ✅ Enhanced security features +- ✅ Offline installation + +**Challenges:** +- ⚠️ yarn 1 vs yarn 3+ confusion +- ⚠️ Plug'n'Play requires IDE support +- ⚠️ Different command syntax +- ⚠️ Maintenance mode for classic + +## Feature Deep Dive + +### Workspaces (Monorepos) + +**pnpm (Best for isolation):** +```yaml +packages: + - 'packages/*' + - 'apps/*' +``` + +**yarn (Best for constraints):** +```json +{ + "workspaces": ["packages/*"] +} +``` + +**npm (Simplest):** +```json +{ + "workspaces": ["packages/*"] +} +``` + +### Peer Dependencies + +| Manager | Handling | +|---------|----------| +| npm 7+ | Auto-installs peers | +| yarn | Auto-installs peers | +| pnpm | Strict warnings, must install | +| bun | Auto-installs peers | + +### Offline Installation + +| Manager | Support | Method | +|---------|---------|--------| +| npm | ⚠️ Limited | Cache only | +| yarn | ✅ Excellent | Offline mirror | +| pnpm | ✅ Good | Store reuse | +| bun | ✅ Good | Cache | + +### Plugin Ecosystem + +| Manager | Plugins | Examples | +|---------|---------|----------| +| npm | ❌ None | N/A | +| yarn (berry) | ✅ Extensive | workspace-tools, version, typescript | +| pnpm | ⚠️ Limited | Few official plugins | +| bun | ❌ None | N/A | + +## Performance Benchmarks + +### Real-World Project Tests + +**Small Project (~50 dependencies):** +- bun: 2s +- pnpm: 4s +- yarn: 6s +- npm: 8s + +**Medium Project (~200 dependencies):** +- bun: 5s +- pnpm: 8s +- yarn: 12s +- npm: 20s + +**Large Project (~1000 dependencies):** +- bun: 15s +- pnpm: 25s +- yarn: 40s +- npm: 80s + +**Monorepo (10 packages, ~500 deps):** +- pnpm: 12s +- bun: 15s +- yarn: 25s +- npm: 45s + +*Benchmarks are approximate and vary by hardware/network* + +## Decision Tree + +``` +Start Here +│ +├─ Need maximum speed? +│ └─ bun (if stable enough) or pnpm +│ +├─ Working with monorepo? +│ └─ pnpm or yarn (berry) +│ +├─ Maximum compatibility needed? +│ └─ npm (default, widest support) +│ +├─ Disk space constrained? +│ └─ pnpm (60-70% savings) +│ +├─ Security-critical application? +│ └─ deno (secure by default) or pnpm (strict) +│ +├─ Existing project migration? +│ ├─ Low risk tolerance: npm (safest) +│ └─ High performance need: pnpm (best balance) +│ +└─ New project, flexible: + └─ bun (cutting edge) or pnpm (proven) +``` + +## Conclusion + +**2026 Recommendations:** + +1. **Default Choice**: pnpm (best all-around) +2. **Maximum Speed**: bun (if stability acceptable) +3. **Enterprise/Legacy**: npm (widest support) +4. **Monorepos**: pnpm or yarn (berry) +5. **Security-First**: deno or pnpm + +**General Advice:** +- Use pnpm for most new TypeScript projects +- Stick with npm for maximum compatibility +- Try bun for greenfield projects wanting speed +- Consider yarn (berry) for advanced monorepo features +- Explore deno for security-critical applications diff --git a/skills/typescript-package-manager/references/integration-with-build-tools.md b/skills/typescript-package-manager/references/integration-with-build-tools.md new file mode 100644 index 000000000..68ef7fe2a --- /dev/null +++ b/skills/typescript-package-manager/references/integration-with-build-tools.md @@ -0,0 +1,627 @@ +# Integration with Build Tools + +Comprehensive guide to integrating TypeScript package managers with modern build tools and development workflows. + +## Overview + +Modern TypeScript projects rely on seamless integration between package managers and build tools. This reference covers best practices, configurations, and common patterns for connecting these essential development tools. + +## TypeScript Build Tool Integration + +### Official TypeScript Documentation + +[TypeScript: Integrating with Build Tools](https://www.typescriptlang.org/docs/handbook/integrating-with-build-tools.html) + +The official TypeScript documentation provides comprehensive guides for integrating with various build systems. + +## Popular Build Tools + +### Vite + +**Modern Frontend Build Tool** + +Fast, opinionated build tool with native TypeScript support. + +**Installation:** +```bash +npm create vite@latest my-app -- --template vanilla-ts +# or with package manager of choice +pnpm create vite my-app --template vanilla-ts +bun create vite my-app --template vanilla-ts +``` + +**Configuration (vite.config.ts):** +```typescript +import { defineConfig } from 'vite' + +export default defineConfig({ + build: { + target: 'esnext', + outDir: 'dist' + }, + resolve: { + alias: { + '@': '/src' + } + } +}) +``` + +**Package Manager Integration:** +- Works seamlessly with npm, yarn, pnpm, bun +- Respects lock files automatically +- Fast dependency pre-bundling + +### webpack + +**Mature Module Bundler** + +Highly configurable bundler with extensive plugin ecosystem. + +**Installation:** +```bash +npm install --save-dev webpack webpack-cli ts-loader typescript +``` + +**Configuration (webpack.config.js):** +```javascript +module.exports = { + entry: './src/index.ts', + module: { + rules: [ + { + test: /\.tsx?$/, + use: 'ts-loader', + exclude: /node_modules/ + } + ] + }, + resolve: { + extensions: ['.tsx', '.ts', '.js'] + }, + output: { + filename: 'bundle.js', + path: path.resolve(__dirname, 'dist') + } +} +``` + +**Package Manager Scripts:** +```json +{ + "scripts": { + "build": "webpack --mode production", + "dev": "webpack --mode development --watch" + } +} +``` + +### esbuild + +**Extremely Fast Bundler** + +JavaScript bundler written in Go, focused on speed. + +**Installation:** +```bash +npm install --save-dev esbuild +``` + +**Build Script:** +```javascript +require('esbuild').build({ + entryPoints: ['src/index.ts'], + bundle: true, + outfile: 'dist/bundle.js', + platform: 'node', + target: 'node18' +}) +``` + +**Package Manager Integration:** +- Native TypeScript/JSX support +- No configuration needed for basic usage +- Works with all major package managers + +### Rollup + +**Module Bundler for Libraries** + +Optimized for bundling JavaScript libraries with tree-shaking. + +**Installation:** +```bash +npm install --save-dev rollup @rollup/plugin-typescript +``` + +**Configuration (rollup.config.js):** +```javascript +import typescript from '@rollup/plugin-typescript' + +export default { + input: 'src/index.ts', + output: { + file: 'dist/bundle.js', + format: 'esm' + }, + plugins: [typescript()] +} +``` + +### Turbopack + +**Next-Generation Bundler** + +Successor to webpack, built in Rust for maximum performance. + +**Integration:** +```bash +# Used automatically in Next.js 13+ +npm install next@latest +``` + +**Next.js Config:** +```javascript +/** @type {import('next').NextConfig} */ +module.exports = { + experimental: { + turbo: {} // Enable Turbopack + } +} +``` + +## Package.json Scripts Integration + +### Common Build Patterns + +```json +{ + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview", + "type-check": "tsc --noEmit", + "lint": "eslint src --ext ts,tsx", + "format": "prettier --write src/**/*.ts", + "test": "vitest", + "clean": "rimraf dist" + } +} +``` + +### Cross-Package Manager Scripts + +Use tools like `cross-env` for platform independence: + +```json +{ + "scripts": { + "build": "cross-env NODE_ENV=production webpack", + "start": "cross-env NODE_ENV=development webpack serve" + } +} +``` + +### Parallel and Sequential Scripts + +```json +{ + "scripts": { + "build:parallel": "npm-run-all --parallel build:*", + "build:tsc": "tsc", + "build:bundle": "vite build", + "build:sequential": "npm-run-all clean build:tsc build:bundle" + } +} +``` + +## Monorepo Build Tool Integration + +### Turborepo + +**High-Performance Build System** + +```json +{ + "pipeline": { + "build": { + "dependsOn": ["^build"], + "outputs": ["dist/**"] + }, + "test": { + "dependsOn": ["build"], + "outputs": [] + } + } +} +``` + +**Installation with Different Package Managers:** +```bash +# With pnpm (recommended) +pnpm add -Dw turbo + +# With npm +npm install --save-dev turbo --workspace-root + +# With yarn +yarn add -DW turbo +``` + +### Nx + +**Smart Build System** + +```bash +# Create new Nx workspace +npx create-nx-workspace@latest my-workspace + +# Generate TypeScript application +nx generate @nx/node:application my-app +``` + +**Build Caching:** +- Intelligent task scheduling +- Distributed computation +- Affected command optimization + +### Lerna + +**Original Monorepo Tool** + +```bash +# Initialize Lerna +npx lerna init + +# Build all packages +lerna run build +``` + +**Package Manager Integration:** +```json +{ + "npmClient": "pnpm", + "useWorkspaces": true +} +``` + +## TypeScript Compiler Integration + +### TSC (TypeScript Compiler) + +**Direct Integration:** +```json +{ + "scripts": { + "build": "tsc", + "watch": "tsc --watch", + "check": "tsc --noEmit" + } +} +``` + +**tsconfig.json for Build:** +```json +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "declaration": true, + "declarationMap": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} +``` + +### Project References + +**For Complex Builds:** +```json +{ + "compilerOptions": { + "composite": true, + "incremental": true + }, + "references": [ + { "path": "../shared" }, + { "path": "../utils" } + ] +} +``` + +## Task Runners + +### npm/yarn/pnpm Scripts + +**Built-in Task Running:** +- Pre/post hooks automatically +- No additional dependencies +- Cross-platform with care + +### just + +**Command Runner Alternative:** +```just +# justfile +build: + tsc && vite build + +dev: + vite + +test: + vitest run +``` + +### make + +**Traditional Build Automation:** +```makefile +# Makefile +.PHONY: build test clean + +build: + npm run build + +test: + npm test + +clean: + rm -rf dist node_modules +``` + +## CI/CD Integration + +### GitHub Actions + +```yaml +name: Build and Test + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '20' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Build + run: npm run build + + - name: Test + run: npm test +``` + +### Package Manager Specific Caching + +**npm:** +```yaml +- uses: actions/cache@v3 + with: + path: ~/.npm + key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} +``` + +**pnpm:** +```yaml +# This GitHub Actions example uses the third-party action pnpm/action-setup@v2 with only the mutable tag +# v2, so CI will execute whatever code that tag references at the time of the run. A compromised action or +# retargeted tag could be used to execute arbitrary code in your pipeline, exposing repository data and secrets. +# To harden the workflow, pin pnpm/action-setup to a specific commit SHA rather than a floating tag so that +# only vetted code is executed. +- uses: pnpm/action-setup@v2 + with: + version: 8 +- uses: actions/setup-node@v3 + with: + cache: 'pnpm' +``` + +**bun:** +```yaml +# This caching example uses oven-sh/setup-bun@v1 and bun-version: latest, which means each CI run can +# download and execute new, unpinned code for both the action and the Bun runtime. A compromised action +# release or a malicious latest Bun build could be used to execute arbitrary code in your pipeline with access to +# repository contents and secrets. Pin oven-sh/setup-bun to a specific commit SHA and configure a fixed Bun +# version rather than latest to keep the workflow on audited, immutable artifacts. +- uses: oven-sh/setup-bun@v1 + with: + bun-version: latest +``` + +## Development Server Integration + +### Vite Dev Server + +```typescript +// vite.config.ts +export default defineConfig({ + server: { + port: 3000, + proxy: { + '/api': 'http://localhost:8080' + } + } +}) +``` + +### webpack Dev Server + +```javascript +// webpack.config.js +module.exports = { + devServer: { + static: './dist', + hot: true, + port: 3000 + } +} +``` + +### Custom Dev Servers + +```json +{ + "scripts": { + "dev": "concurrently \"npm:dev:*\"", + "dev:tsc": "tsc --watch", + "dev:server": "node --watch dist/server.js" + } +} +``` + +## Testing Framework Integration + +### Vitest + +**Fast Unit Test Framework** + +```bash +npm install --save-dev vitest +``` + +```typescript +// vitest.config.ts +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + globals: true, + environment: 'node' + } +}) +``` + +### Jest + +**Popular Testing Framework** + +```bash +npm install --save-dev jest ts-jest @types/jest +``` + +```javascript +// jest.config.js +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + roots: ['/src'], + testMatch: ['**/__tests__/**/*.ts', '**/?(*.)+(spec|test).ts'] +} +``` + +## Best Practices + +### 1. Lock File Consistency + +Always commit lock files and use `npm ci` (or equivalent) in CI: +```bash +# CI installation +npm ci # npm +yarn install --frozen-lockfile # yarn +pnpm install --frozen-lockfile # pnpm +bun install --frozen-lockfile # bun +``` + +### 2. Build Output Management + +```json +{ + "files": ["dist"], + "main": "./dist/index.js", + "types": "./dist/index.d.ts" +} +``` + +### 3. Script Organization + +Group related scripts: +```json +{ + "scripts": { + "build": "npm-run-all build:*", + "build:clean": "rimraf dist", + "build:tsc": "tsc", + "build:bundle": "vite build" + } +} +``` + +### 4. Environment Variables + +Use dotenv for build-time configuration: +```bash +npm install --save-dev dotenv-cli +``` + +```json +{ + "scripts": { + "build:prod": "dotenv -e .env.production -- vite build" + } +} +``` + +### 5. TypeScript Path Mapping + +Sync with build tool: +```json +{ + "compilerOptions": { + "paths": { + "@/*": ["./src/*"] + } + } +} +``` + +Match in Vite/webpack configuration. + +## Troubleshooting + +### Common Issues + +**TypeScript Not Found:** +```bash +npm install --save-dev typescript +``` + +**Module Resolution Errors:** +- Check `moduleResolution` in tsconfig.json +- Verify `paths` configuration +- Ensure build tool alias matches TypeScript + +**Build Performance:** +- Enable incremental builds +- Use `skipLibCheck: true` +- Leverage caching (Turbopack, Vite) +- Consider esbuild for transpilation + +**Lock File Conflicts:** +- Regenerate: delete lock file and `node_modules`, reinstall +- Use same package manager across team +- Configure in `.npmrc` or `package.json` + +## Further Reading + +- [TypeScript Handbook: Integrating with Build Tools](https://www.typescriptlang.org/docs/handbook/integrating-with-build-tools.html) +- [Vite Guide](https://vitejs.dev/guide/) +- [webpack Documentation](https://webpack.js.org/concepts/) +- [esbuild Documentation](https://esbuild.github.io/) +- [Turborepo Handbook](https://turbo.build/repo/docs) diff --git a/skills/typescript-package-manager/references/package-management.md b/skills/typescript-package-manager/references/package-management.md new file mode 100644 index 000000000..89eb86f8e --- /dev/null +++ b/skills/typescript-package-manager/references/package-management.md @@ -0,0 +1,316 @@ +# Package Management + +A comprehensive guide to understanding package managers in the JavaScript/TypeScript ecosystem and beyond. + +## Overview + +Package managers are essential tools for modern software development, handling dependency installation, version management, and project configuration. This reference covers the landscape of package management with focus on TypeScript/JavaScript tools. + +## JavaScript/TypeScript Package Managers + +### npm (Node Package Manager) + +[npm](https://en.wikipedia.org/wiki/Npm) is the default package manager for Node.js, serving as the largest software registry in the world. + +**Key Features:** +- Default Node.js package manager +- Massive public registry (npmjs.com) +- Semantic versioning support +- Scripts and lifecycle hooks +- Workspaces support (v7+) + +**Best For:** +- Standard Node.js projects +- Wide compatibility requirements +- Projects requiring maximum ecosystem support + +### yarn + +Yarn was created by Facebook to address npm's early performance and consistency issues. + +**Key Features:** +- Fast parallel installation +- Offline mode support +- Deterministic dependency resolution +- Plug'n'Play mode (v2+) +- Workspaces (monorepo support) + +**Best For:** +- Large-scale applications +- Teams requiring reproducible builds +- Monorepo projects + +### pnpm + +[pnpm](https://pnpm.io/) uses a content-addressable filesystem to save disk space and boost installation speed. + +**Key Features:** +- Disk space efficient (hard links) +- Strict dependency isolation +- Fast installation +- Built-in monorepo support +- Drop-in npm replacement + +**Best For:** +- Teams with many projects +- CI/CD optimization +- Strict dependency management +- Monorepos + +### bun + +[bun](https://github.com/oven-sh/bun) is an all-in-one JavaScript runtime, package manager, bundler, and test runner. + +**Key Features:** +- Blazing fast (written in Zig) +- Compatible with npm packages +- Built-in bundler and test runner +- Native TypeScript support +- Drop-in Node.js replacement + +**Best For:** +- New projects prioritizing speed +- Full-stack TypeScript applications +- Projects wanting unified tooling + +### deno + +[deno](https://github.com/denoland/deno) is a secure runtime that uses URLs for modules instead of a centralized registry. + +**Key Features:** +- Secure by default (permissions required) +- Native TypeScript support +- No package.json or node_modules +- URL-based imports +- Built-in tooling (formatter, linter, test runner) + +**Best For:** +- Security-critical applications +- Projects avoiding centralized registries +- Modern TypeScript-first development + +## Package Management Concepts + +### Semantic Versioning + +Package versioning follows the [Semantic Versioning](https://semver.org/) specification: + +**Format:** MAJOR.MINOR.PATCH + +- **MAJOR**: Breaking changes +- **MINOR**: New features (backward compatible) +- **PATCH**: Bug fixes + +**Version Constraints:** +- `^1.2.3` - Compatible with 1.2.3 (allows MINOR and PATCH updates) +- `~1.2.3` - Approximately 1.2.3 (allows PATCH updates only) +- `1.2.3` - Exact version +- `>=1.2.3 <2.0.0` - Range specification + +### Dependency Types + +**dependencies**: Required for production runtime +```json +"dependencies": { + "express": "^4.18.0" +} +``` + +**devDependencies**: Only needed for development +```json +"devDependencies": { + "typescript": "^5.0.0", + "@types/node": "^20.0.0" +} +``` + +**peerDependencies**: Required from consuming project +```json +"peerDependencies": { + "react": "^18.0.0" +} +``` + +**optionalDependencies**: Optional enhancements +```json +"optionalDependencies": { + "fsevents": "^2.3.0" +} +``` + +### Lock Files + +Lock files ensure reproducible installations across environments: + +- **package-lock.json** (npm) - Exact dependency tree +- **yarn.lock** (yarn) - Deterministic resolution +- **pnpm-lock.yaml** (pnpm) - Content-addressable storage +- **bun.lockb** (bun) - Binary lock file + +**Best Practices:** +- Always commit lock files to version control +- Keep lock files in sync with package.json +- Regenerate if corrupted +- Resolve conflicts carefully during merges + +### Workspaces and Monorepos + +Workspaces allow managing multiple packages in a single repository: + +**npm/yarn/pnpm workspaces structure:** +```json +{ + "workspaces": [ + "packages/*" + ] +} +``` + +**Benefits:** +- Shared dependencies +- Cross-package development +- Unified versioning +- Simplified dependency management + +**Tools for Monorepos:** +- Lerna - Multi-package repository management +- Nx - Smart monorepo tools +- Turborepo - High-performance build system + +## Version Management Tools + +### Node Version Managers + +Tools for managing multiple Node.js versions: + +- **[n](https://github.com/tj/n)** - Simple Node.js version management +- **[fnm](https://github.com/Schniz/fnm)** - Fast Node Manager (Rust-based) +- **[volta](https://github.com/volta-cli/volta)** - Hassle-free JavaScript tooling + +**Why Use Version Managers:** +- Switch between Node.js versions per project +- Ensure team consistency +- Test across multiple Node.js versions +- Isolate global packages + +## Registry Management + +### Public Registries + +- **npmjs.com** - Default npm registry +- **GitHub Packages** - Package hosting on GitHub +- **jsDelivr** - CDN for npm packages + +### Private Registries + +- **npm Enterprise** - Self-hosted npm registry +- **Verdaccio** - Lightweight private registry +- **JFrog Artifactory** - Enterprise artifact management +- **Azure Artifacts** - Microsoft's package hosting + +**Configuration:** +```bash +# Set registry +npm config set registry https://registry.company.com + +# Scope-specific registry +npm config set @mycompany:registry https://npm.company.com +``` + +## Security and Auditing + +### Vulnerability Scanning + +```bash +# npm +npm audit +npm audit fix + +# yarn +yarn audit + +# pnpm +pnpm audit + +# bun +bun pm audit +``` + +### Security Best Practices + +1. **Regular Audits**: Run security checks regularly +2. **Update Dependencies**: Keep packages up-to-date +3. **Review Lock Files**: Check for unexpected changes +4. **Use .npmrc**: Configure secure defaults +5. **Enable 2FA**: Protect publishing accounts +6. **Verify Packages**: Check package integrity + +## Performance Optimization + +### Installation Speed + +**Comparison:** +- bun: Fastest (native implementation) +- pnpm: Very fast (content-addressable) +- yarn: Fast (parallel downloads) +- npm: Baseline (improved in v7+) + +### Disk Space + +**pnpm Advantage:** +- Single global store +- Hard links to packages +- Saves 50-70% disk space + +### CI/CD Optimization + +```yaml +# Cache dependencies +- uses: actions/cache@v3 + with: + path: ~/.npm + key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} +``` + +**Strategies:** +- Use lock file caching +- Leverage frozen lockfiles +- Parallelize tests +- Use workspace features + +## Related Package Managers + +### System Package Managers + +- **[apt](https://en.wikipedia.org/wiki/APT_(software))** - Debian/Ubuntu package manager +- **[dnf](https://en.wikipedia.org/wiki/DNF_(software))** - Fedora package manager +- **[Zypp](https://en.wikipedia.org/wiki/Zypp)** - openSUSE package manager +- **[Homebrew](https://en.wikipedia.org/wiki/Homebrew_(package_manager))** - macOS package manager + +### Language-Specific + +- **[pip](https://en.wikipedia.org/wiki/Pip_(package_manager))** - Python package installer +- **[Cargo](https://en.wikipedia.org/wiki/Rust_(programming_language)#Cargo)** - Rust package manager +- **[Maven](https://en.wikipedia.org/wiki/Apache_Maven)** - Java project management +- **[Conda](https://en.wikipedia.org/wiki/Conda_(package_manager))** - Cross-language package manager + +### Modern Alternatives + +- **[uv](https://github.com/astral-sh/uv)** - Ultra-fast Python package installer +- **[poetry](https://github.com/python-poetry/poetry)** - Python dependency management +- **[PDM](https://pdm-project.org/en/latest/)** - Modern Python package manager +- **[pip-tools](https://pypi.org/project/pip-tools/)** - Python requirements management + +## Container and Universal Packaging + +- **[OCI containers](https://en.wikipedia.org/wiki/Open_Container_Initiative)** - Standard container format +- **[Flatpak](https://en.wikipedia.org/wiki/Flatpak)** - Universal Linux applications +- **[Snap](https://en.wikipedia.org/wiki/Snap_(software))** - Ubuntu universal packages +- **[AppImage](https://en.wikipedia.org/wiki/AppImage)** - Portable Linux apps + +## Further Reading + +- [Package Managers and Package Management: A Guide for the Perplexed](https://flox.dev/blog/package-managers-and-package-management-a-guide-for-the-perplexed/) +- [Nixpkgs](https://github.com/NixOS/nixpkgs) - Nix package collection +- [Flox Catalog](https://hub.flox.dev/packages) - Declarative development environments +- [Docker Best Practices](https://docs.docker.com/build/building/best-practices/) - Container package pinning diff --git a/skills/typescript-package-manager/scripts/bun-workflow.js b/skills/typescript-package-manager/scripts/bun-workflow.js new file mode 100644 index 000000000..4ce7279dc --- /dev/null +++ b/skills/typescript-package-manager/scripts/bun-workflow.js @@ -0,0 +1,378 @@ +#!/usr/bin/env node +/** + * Bun TypeScript Workflow Helper + * + * Detects Bun, generates Bun-optimised configs, and guides setup. + * + * Usage: + * node bun-workflow.js --detect # Detect Bun and show version + * node bun-workflow.js --tsconfig # Print Bun-optimised tsconfig.json + * node bun-workflow.js --bunfig # Print bunfig.toml template + * node bun-workflow.js --scripts # Print package.json scripts for Bun + * node bun-workflow.js --migrate # Show Node.js → Bun migration steps + * node bun-workflow.js --test # Run built-in makeshift tests + * + * Self-contained — no external dependencies required. + */ + +import { execSync } from 'child_process'; +import { existsSync } from 'fs'; + +// ─── CLI flags ─────────────────────────────────────────────────────────────── +const args = process.argv.slice(2); +const DETECT = args.includes('--detect'); +const SHOW_TSCONFIG = args.includes('--tsconfig'); +const SHOW_BUNFIG = args.includes('--bunfig'); +const SHOW_SCRIPTS = args.includes('--scripts'); +const SHOW_MIGRATE = args.includes('--migrate'); +const TEST = args.includes('--test'); +const SHOW_HELP = args.length === 0 || args.includes('--help'); + +// ─── Colours ───────────────────────────────────────────────────────────────── +const c = { + green: (s) => `\x1b[32m${s}\x1b[0m`, + red: (s) => `\x1b[31m${s}\x1b[0m`, + yellow: (s) => `\x1b[33m${s}\x1b[0m`, + cyan: (s) => `\x1b[36m${s}\x1b[0m`, + bold: (s) => `\x1b[1m${s}\x1b[0m`, + dim: (s) => `\x1b[2m${s}\x1b[0m`, +}; + +// ─── Utilities ─────────────────────────────────────────────────────────────── + +function tryRun(cmd) { + try { + return execSync(cmd, { encoding: 'utf8', stdio: 'pipe' }).trim(); + } catch { + return null; + } +} + +/** Detect Bun and return version string, or null if not installed. */ +function detectBun() { + return tryRun('bun --version'); +} + +// ─── Template generators ───────────────────────────────────────────────────── + +/** + * Returns Bun-optimised tsconfig.json content. + * @param {'app'|'lib'|'react'} [target='app'] + * @returns {object} + */ +function buildBunTsconfig(target = 'app') { + const base = { + compilerOptions: { + // Bun supports these natively + target: 'ESNext', + lib: target === 'react' ? ['ESNext', 'DOM', 'DOM.Iterable'] : ['ESNext'], + module: 'Preserve', // best for Bun + moduleResolution: 'Bundler', // required for module: Preserve + // Output + outDir: './dist', + rootDir: './src', + declaration: true, + declarationMap: true, + sourceMap: true, + // Strictness + strict: true, + noUncheckedIndexedAccess: true, + exactOptionalPropertyTypes: true, + noImplicitOverride: true, + noImplicitReturns: true, + noFallthroughCasesInSwitch: true, + noUnusedLocals: true, + noUnusedParameters: true, + // Interop + esModuleInterop: true, + allowSyntheticDefaultImports: true, + skipLibCheck: true, + forceConsistentCasingInFileNames: true, + // Bun globals + types: target === 'app' ? ['bun'] : [], + }, + include: ['src'], + exclude: ['node_modules', 'dist'], + }; + + if (target === 'react') { + base.compilerOptions.jsx = 'react-jsx'; + } + + return base; +} + +/** + * Returns a bunfig.toml template string. + * @param {object} opts + * @param {string} [opts.testDir='src'] + * @param {boolean} [opts.coverage=true] + * @returns {string} + */ +function buildBunfig(opts = {}) { + const { testDir = 'src', coverage = true } = opts; + return `# bunfig.toml — Bun configuration +# Reference: https://bun.sh/docs/runtime/bunfig + +[install] +# Whether to use exact versions (like npm --save-exact) +exact = false +# Lifecycle scripts (postinstall etc.) — set false for untrusted packages +lifecycle = true +# Auto-install types on install +auto = "auto" +# Dedupe on install +dedupe = false + +[install.scopes] +# Scoped registry configuration (example) +# "@myorg" = { url = "https://registry.myorg.com/", token = "$NPM_TOKEN" } + +[run] +# Default shell for bun run scripts +# shell = "system" +# Automatically load .env file +# bun automatically loads .env, .env.local, .env.development, .env.production + +[test] +# Root directory for test files +root = "${testDir}" +# Coverage +coverage = ${coverage} +coverageThreshold = { line = 80, function = 80, statement = 80 } +# Timeout per test (ms) +timeout = 5000 +# Reporter +reporter = "default" +# Preload file (equivalent to jest setupFilesAfterFramework) +# preload = ["./src/test-setup.ts"] + +[build] +# Default entrypoint (used by \`bun build\`) +# entrypoints = ["./src/index.ts"] +# Default output directory +# outdir = "./dist" +# Minify in production +# minify = true +`; +} + +/** + * Returns Bun-specific package.json scripts. + * @param {boolean} [react=false] + * @returns {object} + */ +function buildBunScripts(react = false) { + return { + dev: 'bun run --watch src/index.ts', + start: 'bun run src/index.ts', + build: 'bun build src/index.ts --outdir dist --target bun', + 'build:minify': 'bun build src/index.ts --outdir dist --target bun --minify', + test: 'bun test', + 'test:watch': 'bun test --watch', + 'test:coverage': 'bun test --coverage', + typecheck: 'tsc --noEmit', + lint: `eslint src --ext .ts${react ? ',.tsx' : ''}`, + 'lint:fix': `eslint src --ext .ts${react ? ',.tsx' : ''} --fix`, + format: `prettier --write "${react ? 'src/**/*.{ts,tsx}' : 'src/**/*.ts'}"`, + clean: 'node -e "require(\'fs\').rmSync(\'dist\', { recursive: true, force: true })"', + ci: 'bun run typecheck && bun test --coverage && bun run build', + }; +} + +// ─── Migration steps ───────────────────────────────────────────────────────── + +function getMigrationSteps() { + return [ + { + step: 1, + title: 'Install Bun', + cmd: 'curl -fsSL https://bun.sh/install | bash # macOS/Linux\n powershell -c "irm bun.sh/install.ps1 | iex" # Windows', + }, + { + step: 2, + title: 'Install dependencies with Bun (replaces npm install)', + cmd: 'bun install', + }, + { + step: 3, + title: 'Update tsconfig.json for Bun', + cmd: 'node bun-workflow.js --tsconfig (copy output to tsconfig.json)', + }, + { + step: 4, + title: 'Create bunfig.toml', + cmd: 'node bun-workflow.js --bunfig > bunfig.toml', + }, + { + step: 5, + title: 'Add Bun type definitions (@types/bun)', + cmd: 'bun add -D @types/bun', + }, + { + step: 6, + title: 'Replace npm scripts with Bun equivalents', + cmd: 'node bun-workflow.js --scripts (copy output to package.json)', + }, + { + step: 7, + title: 'Migrate Jest tests to bun:test (optional)', + cmd: 'Replace: import { describe, it, expect } from "@jest/globals"\nWith: import { describe, it, expect } from "bun:test"', + }, + { + step: 8, + title: 'Update CI/CD (if any)', + cmd: 'Replace: actions/setup-node\nWith: oven-sh/setup-bun', + }, + ]; +} + +// ─── Output actions ─────────────────────────────────────────────────────────── + +function printHelp() { + console.log(` +${c.bold(c.cyan('Bun TypeScript Workflow Helper'))} + +${c.bold('Usage:')} + node bun-workflow.js [option] + +${c.bold('Options:')} + --detect Detect Bun installation and version + --tsconfig Print Bun-optimised tsconfig.json + --bunfig Print bunfig.toml template + --scripts Print package.json scripts for Bun projects + --migrate Show Node.js → Bun migration steps + --test Run built-in makeshift tests + --help Show this help +`); +} + +function detectAndPrint() { + const version = detectBun(); + if (version) { + console.log(`\n ${c.green('✅')} Bun detected: ${c.bold('v' + version)}`); + const nodeVersion = tryRun('node --version'); + if (nodeVersion) console.log(` ${c.dim('ℹ️')} Node.js also available: ${nodeVersion}`); + } else { + console.log(`\n ${c.yellow('⚠️')} Bun is not installed or not on PATH.`); + console.log(`\n Install with:`); + console.log(` ${c.cyan('curl -fsSL https://bun.sh/install | bash')} # macOS/Linux`); + console.log(` ${c.cyan('powershell -c "irm bun.sh/install.ps1 | iex"')} # Windows`); + } + console.log(); +} + +function printTsconfig() { + console.log(c.bold('\n⚙️ Bun-optimised tsconfig.json (app)\n')); + console.log(JSON.stringify(buildBunTsconfig('app'), null, 2)); + console.log(c.dim('\n Note: module: "Preserve" + moduleResolution: "Bundler" is the recommended pair for Bun.\n')); +} + +function printBunfig() { + console.log(c.bold('\n📄 bunfig.toml template\n')); + console.log(buildBunfig()); +} + +function printScripts() { + console.log(c.bold('\n📜 package.json scripts for Bun projects\n')); + console.log(JSON.stringify({ scripts: buildBunScripts() }, null, 2)); +} + +function printMigration() { + console.log(c.bold('\n🔄 Node.js → Bun Migration Steps\n')); + getMigrationSteps().forEach(({ step, title, cmd }) => { + console.log(` ${c.bold(c.cyan(step + '.'))} ${title}`); + console.log(c.dim(' ' + cmd.replace(/\n/g, '\n '))); + console.log(); + }); +} + +// ─── Makeshift tests ───────────────────────────────────────────────────────── + +function runTests() { + console.log(c.bold('\n═══ Makeshift Tests ═══\n')); + let passed = 0; let failed = 0; + + function assert(label, actual, expected) { + const ok = JSON.stringify(actual) === JSON.stringify(expected); + console.log(` ${ok ? '\x1b[32m✅ PASS\x1b[0m' : '\x1b[31m❌ FAIL\x1b[0m'} ${label}`); + console.log(c.dim(` Expected: ${JSON.stringify(expected)}`)); + if (!ok) console.log(c.dim(` Got: ${JSON.stringify(actual)}`)); + ok ? passed++ : failed++; + } + + // Test 1: buildBunTsconfig sets module: Preserve + const cfg = buildBunTsconfig('app'); + assert('buildBunTsconfig("app").compilerOptions.module', cfg.compilerOptions.module, 'Preserve'); + // Expected: 'Preserve' + + assert('buildBunTsconfig("app").compilerOptions.moduleResolution', cfg.compilerOptions.moduleResolution, 'Bundler'); + // Expected: 'Bundler' + + assert('buildBunTsconfig("app").compilerOptions.strict', cfg.compilerOptions.strict, true); + // Expected: true + + // Test 2: buildBunTsconfig react adds jsx + DOM + const reactCfg = buildBunTsconfig('react'); + assert('buildBunTsconfig("react") sets jsx', reactCfg.compilerOptions.jsx, 'react-jsx'); + // Expected: 'react-jsx' + + assert('buildBunTsconfig("react") includes DOM lib', reactCfg.compilerOptions.lib.includes('DOM'), true); + // Expected: true + + assert('buildBunTsconfig("app") excludes DOM lib', cfg.compilerOptions.lib.includes('DOM'), false); + // Expected: false + + // Test 3: buildBunfig is a non-empty string + const bunfig = buildBunfig(); + assert('buildBunfig() returns a string', typeof bunfig, 'string'); + // Expected: 'string' + + assert('buildBunfig() contains [test] section', bunfig.includes('[test]'), true); + // Expected: true + + assert('buildBunfig() contains [install] section', bunfig.includes('[install]'), true); + // Expected: true + + // Test 4: buildBunfig coverage option + const noCov = buildBunfig({ coverage: false }); + assert('buildBunfig({coverage:false}) sets coverage = false', noCov.includes('coverage = false'), true); + // Expected: true + + // Test 5: buildBunScripts includes bun test + const scripts = buildBunScripts(); + assert('buildBunScripts().test uses bun test', scripts.test, 'bun test'); + // Expected: 'bun test' + + assert('buildBunScripts().dev uses bun run --watch', scripts.dev.startsWith('bun run --watch'), true); + // Expected: true + + // Test 6: getMigrationSteps returns 8 steps + const steps = getMigrationSteps(); + assert('getMigrationSteps() returns 8 items', steps.length, 8); + // Expected: 8 + + assert('First migration step is step 1', steps[0].step, 1); + // Expected: 1 + + // Test 7: tryRun returns string for node version + const nodeVer = tryRun('node --version'); + assert('tryRun("node --version") returns a string', typeof nodeVer, 'string'); + // Expected: 'string' + + assert('tryRun("node --version") starts with v', nodeVer.startsWith('v'), true); + // Expected: true + + console.log(c.bold(`\n Tests: ${c.green(passed + ' passed')} ${failed > 0 ? '\x1b[31m' + failed + ' failed\x1b[0m' : c.dim('0 failed')}\n`)); + if (failed > 0) process.exit(1); +} + +// ─── Entry point ───────────────────────────────────────────────────────────── + +if (TEST) runTests(); +else if (DETECT) detectAndPrint(); +else if (SHOW_TSCONFIG) printTsconfig(); +else if (SHOW_BUNFIG) printBunfig(); +else if (SHOW_SCRIPTS) printScripts(); +else if (SHOW_MIGRATE) printMigration(); +else printHelp(); diff --git a/skills/typescript-package-manager/scripts/bun-workflow.md b/skills/typescript-package-manager/scripts/bun-workflow.md new file mode 100644 index 000000000..581264e0a --- /dev/null +++ b/skills/typescript-package-manager/scripts/bun-workflow.md @@ -0,0 +1,688 @@ +# Bun TypeScript Workflow + +A guide for using Bun as your TypeScript runtime, package manager, bundler, and test runner — no compilation step required. + +**Reference:** https://bun.sh/docs + +--- + +## Installing Bun + +### macOS / Linux + +```bash +curl -fsSL https://bun.sh/install | bash +``` + +### Windows (via PowerShell) + +```powershell +powershell -c "irm bun.sh/install.ps1 | iex" +``` + +### Via npm (cross-platform) + +```bash +npm install -g bun +``` + +### Verify Installation + +```bash +bun --version +``` + +--- + +## Creating a Bun TypeScript Project + +### Interactive Init + +```bash +mkdir my-bun-project && cd my-bun-project +bun init +``` + +The `bun init` prompt creates: + +``` +package.json +tsconfig.json +index.ts +.gitignore +README.md +``` + +### Non-interactive Init + +```bash +bun init -y +``` + +### Starting from Scratch + +```bash +# Create entry point +echo 'console.log("Hello from Bun + TypeScript!");' > index.ts +``` + +--- + +## Using Bun as Runtime (No tsc Required) + +Bun executes TypeScript files directly using its built-in transpiler — no `tsc`, `ts-node`, or `tsx` needed: + +```bash +# Run TypeScript directly +bun run index.ts + +# Or simply +bun index.ts +``` + +### What Bun Handles Natively + +- TypeScript syntax stripping +- JSX/TSX +- Top-level await +- ES modules and CommonJS +- Path aliases from `tsconfig.json` +- `.env` file loading + +### What Bun Does NOT Do + +- Type checking (still use `tsc --noEmit` for that) +- Declaration file generation (use `tsc` for publishing) + +--- + +## `bun run` for TypeScript Files + +### Running Scripts + +```bash +# Run a TypeScript file +bun run src/server.ts + +# Run a package.json script +bun run build +bun run dev +bun run test + +# Short aliases for common scripts +bun start # runs "start" script +bun test # runs Bun's test runner (not the "test" script) +``` + +### Watch Mode + +```bash +# Run with file watching (restarts on change) +bun --watch run src/index.ts + +# Hot reload (preserves state where possible) +bun --hot run src/server.ts +``` + +### Running with Environment Variables + +```bash +NODE_ENV=production bun run src/index.ts +``` + +Bun automatically loads `.env`, `.env.local`, `.env.production`, etc. + +--- + +## `bun test` with TypeScript + +Bun has a built-in test runner compatible with Jest's API — no configuration needed. + +### Writing Tests + +```typescript +// src/__tests__/math.test.ts +import { describe, it, expect, beforeEach, afterEach, mock } from 'bun:test'; + +function add(a: number, b: number): number { + return a + b; +} + +describe('math utilities', () => { + it('adds two numbers', () => { + expect(add(2, 3)).toBe(5); + }); + + it('handles negative numbers', () => { + expect(add(-1, 1)).toBe(0); + }); +}); +``` + +### Running Tests + +```bash +# Run all tests +bun test + +# Run specific file +bun test src/__tests__/math.test.ts + +# Watch mode +bun test --watch + +# Coverage report +bun test --coverage + +# Bail on first failure +bun test --bail + +# Timeout per test (ms) +bun test --timeout 5000 +``` + +### Jest-Compatible APIs Available + +```typescript +import { + describe, it, test, expect, + beforeAll, afterAll, beforeEach, afterEach, + mock, spyOn, jest // 'jest' is aliased to 'bun:test' APIs +} from 'bun:test'; +``` + +### Snapshot Testing + +```typescript +import { expect, test } from 'bun:test'; + +test('snapshot', () => { + const result = { name: 'Alice', age: 30 }; + expect(result).toMatchSnapshot(); +}); +``` + +--- + +## `bun build` for Bundling TypeScript + +Bun includes a fast bundler that handles TypeScript natively. + +### Basic Build + +```bash +# Bundle for browser +bun build ./src/index.ts --outdir ./dist + +# Bundle for Node.js +bun build ./src/index.ts --outdir ./dist --target node + +# Bundle for Bun +bun build ./src/index.ts --outdir ./dist --target bun +``` + +### Build Options + +```bash +# Single file output +bun build ./src/index.ts --outfile ./dist/bundle.js + +# Minify +bun build ./src/index.ts --outdir ./dist --minify + +# Source maps +bun build ./src/index.ts --outdir ./dist --sourcemap=external + +# Watch mode +bun build ./src/index.ts --outdir ./dist --watch + +# Define constants (like process.env) +bun build ./src/index.ts --outdir ./dist --define "process.env.NODE_ENV='production'" + +# External packages (don't bundle) +bun build ./src/index.ts --outdir ./dist --external react --external react-dom +``` + +### Programmatic Build API + +```typescript +// build.ts +import { build } from 'bun'; + +const result = await build({ + entrypoints: ['./src/index.ts'], + outdir: './dist', + target: 'browser', + format: 'esm', + minify: true, + sourcemap: 'external', + splitting: true, + external: ['react', 'react-dom'], + define: { + 'process.env.NODE_ENV': '"production"' + } +}); + +if (!result.success) { + for (const message of result.logs) { + console.error(message); + } + process.exit(1); +} + +console.log(`Built ${result.outputs.length} files`); +``` + +```bash +bun run build.ts +``` + +--- + +## `bun install` vs npm + +### Installing Packages + +```bash +# Install all dependencies (reads package.json) +bun install + +# Add a dependency +bun add express +bun add zod + +# Add dev dependency +bun add --dev typescript @types/node + +# Add global package +bun add --global typescript + +# Remove a dependency +bun remove express + +# Update packages +bun update +bun update express # specific package +``` + +### Speed Comparison + +Bun uses a binary lockfile (`bun.lockb`) and installs packages ~10-25x faster than npm. + +### Lockfile + +```bash +# bun.lockb is binary — add to .gitignore or commit it +# To read the lockfile as text: +bun pm ls + +# Verify lockfile integrity +bun install --frozen-lockfile # fails if lockfile would change (CI use) +``` + +### Trust and Security + +```bash +# Bun runs lifecycle scripts by default; trust specific packages +bun install --trust-scripts + +# Or in bunfig.toml: +# [install.scopes] +# "@myorg" = { token = "..." } +``` + +--- + +## `bunfig.toml` Configuration + +`bunfig.toml` is Bun's configuration file (similar to `.npmrc` or `.yarnrc`): + +```toml +# bunfig.toml + +# Test runner configuration +[test] +preload = ["./src/test-setup.ts"] +timeout = 10000 +coverage = true +coverageReporter = ["text", "lcov"] +coverageDir = "./coverage" +coverageThreshold = 0.80 + +# Install configuration +[install] +# Default registry +registry = "https://registry.npmjs.org/" +# Exact versions +saveExact = true + +# Scoped registry for private packages +[install.scopes] +"@mycompany" = { url = "https://npm.mycompany.com/", token = "$NPM_TOKEN" } + +# Build configuration (programmatic defaults) +[build] +target = "browser" +minify = true + +# Run configuration +[run] +# Shell for bun run +shell = "bun" +``` + +--- + +## Bun + TypeScript `tsconfig` Recommendations + +Bun has specific recommendations for `tsconfig.json`: + +```json +{ + "compilerOptions": { + // Bun's runtime supports modern JavaScript + "target": "ESNext", + "module": "Preserve", + "moduleResolution": "Bundler", + "lib": ["ESNext", "DOM"], + + // Type checking strictness + "strict": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + + // JSX (if using React or other JSX) + "jsx": "react-jsx", + "jsxImportSource": "react", + + // Path resolution + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + }, + + // Output (only needed if using tsc for publishing) + "outDir": "./dist", + "rootDir": "./src", + "declaration": true, + "declarationMap": true, + "sourceMap": true, + + // Bun compatibility + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "resolveJsonModule": true, + "allowImportingTsExtensions": true, + + // Type checking only (no emit, since Bun handles running) + "noEmit": true + }, + "include": ["src/**/*", "**/*.ts"], + "exclude": ["node_modules", "dist"] +} +``` + +### Bun's Built-In Types + +Add Bun-specific type definitions: + +```bash +bun add --dev @types/bun +``` + +```typescript +// Now you have Bun globals typed: +const file = Bun.file('./data.json'); +const content = await file.json(); + +const server = Bun.serve({ + port: 3000, + fetch(request) { + return new Response('Hello from Bun!'); + } +}); +``` + +--- + +## Complete `package.json` + `bunfig.toml` Template + +### `package.json` + +```json +{ + "name": "my-bun-project", + "version": "1.0.0", + "description": "A TypeScript project powered by Bun", + "module": "dist/index.js", + "types": "dist/index.d.ts", + "exports": { + ".": { + "import": "./dist/index.js", + "types": "./dist/index.d.ts" + } + }, + "files": ["dist/"], + "scripts": { + "dev": "bun --hot run src/index.ts", + "dev:watch": "bun --watch run src/index.ts", + "start": "bun run src/index.ts", + "start:prod": "NODE_ENV=production bun run src/index.ts", + "build": "bun run scripts/build.ts", + "build:types": "tsc --emitDeclarationOnly --declaration --outDir dist", + "typecheck": "tsc --noEmit", + "typecheck:watch": "tsc --noEmit --watch", + "test": "bun test", + "test:watch": "bun test --watch", + "test:coverage": "bun test --coverage", + "test:ci": "bun test --ci --coverage", + "lint": "eslint src --ext .ts,.tsx", + "lint:fix": "eslint src --ext .ts,.tsx --fix", + "format": "prettier --write \"src/**/*.{ts,tsx,json,md}\"", + "format:check": "prettier --check \"src/**/*.{ts,tsx,json,md}\"", + "clean": "rm -rf dist coverage", + "validate": "bun run typecheck && bun run lint && bun run format:check && bun run test", + "ci": "bun run validate && bun run build" + }, + "devDependencies": { + "@types/bun": "latest", + "typescript": "^5.0.0", + "eslint": "^8.0.0", + "@typescript-eslint/eslint-plugin": "^7.0.0", + "@typescript-eslint/parser": "^7.0.0", + "prettier": "^3.0.0" + }, + "engines": { + "bun": ">=1.0.0" + } +} +``` + +### `bunfig.toml` + +```toml +[test] +preload = ["./src/test-setup.ts"] +timeout = 15000 +coverage = true +coverageReporter = ["text", "lcov"] +coverageDir = "./coverage" +coverageThreshold = 0.80 + +[install] +saveExact = false +production = false +dryRun = false + +[run] +bun = true +``` + +--- + +## Migrating from Node.js/npm to Bun + +### Step 1: Install Bun and Reinstall Dependencies + +```bash +# Install Bun +curl -fsSL https://bun.sh/install | bash + +# Remove node_modules and reinstall with Bun +rm -rf node_modules +bun install +``` + +### Step 2: Replace npm Scripts with Bun + +| Before (npm) | After (Bun) | +|---|---| +| `npm install` | `bun install` | +| `npm run dev` | `bun dev` or `bun run dev` | +| `npm test` | `bun test` | +| `npm run build` | `bun run build` | +| `npx ts-node src/index.ts` | `bun src/index.ts` | +| `npx tsx src/index.ts` | `bun src/index.ts` | +| `node -e "..."` | `bun -e "..."` | + +### Step 3: Update `tsconfig.json` + +Switch to Bun-optimized settings: + +```json +{ + "compilerOptions": { + "target": "ESNext", + "module": "Preserve", + "moduleResolution": "Bundler" + } +} +``` + +### Step 4: Add Bun Types + +```bash +bun add --dev @types/bun +``` + +### Step 5: Update Node.js-Specific APIs (if any) + +Replace Node.js built-ins with Bun equivalents where beneficial: + +```typescript +// Before (Node.js) +import { readFileSync } from 'fs'; +const content = readFileSync('./data.json', 'utf-8'); +const data = JSON.parse(content); + +// After (Bun native API — faster) +const file = Bun.file('./data.json'); +const data = await file.json(); +``` + +```typescript +// Before (Node.js HTTP) +import http from 'http'; +const server = http.createServer((req, res) => { + res.end('Hello'); +}); +server.listen(3000); + +// After (Bun native — much faster) +const server = Bun.serve({ + port: 3000, + fetch(request) { + return new Response('Hello'); + } +}); +``` + +### Step 6: Update CI/CD + +```yaml +# .github/workflows/ci.yml +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 +# In this CI snippet, oven-sh/setup-bun@v2 is referenced by the mutable tag v2 and configured with +# bun-version: latest, so every run may pull new, unreviewed code for both the action and the runtime. If either +# the action or the Bun distribution served for latest is compromised, an attacker could execute arbitrary code +# in the workflow with access to the repository and secrets. Pin oven-sh/setup-bun to a specific commit SHA +# and use a fixed Bun version (for example, an exact semantic version) to ensure the pipeline runs only known, +# trusted artifacts. + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + - run: bun install --frozen-lockfile + - run: bun run typecheck + - run: bun test --coverage + - run: bun run build +``` + +### Step 7: Migrate Tests from Jest to `bun:test` + +Most Jest tests work without changes. Update imports: + +```typescript +// Before (Jest) +import { describe, it, expect } from '@jest/globals'; +// or relying on Jest globals (no import needed) + +// After (Bun) — explicit import recommended +import { describe, it, expect } from 'bun:test'; +``` + +Remove Jest configuration and dependencies: + +```bash +bun remove jest ts-jest @types/jest babel-jest jest-environment-node +``` + +Remove from `package.json`: + +```json +// Remove these: +"jest": { ... }, +"babel": { ... } +``` + +--- + +## Bun vs npm vs Node: Key Differences + +| Feature | Node.js + npm | Bun | +|---------|--------------|-----| +| TypeScript execution | Requires ts-node/tsx | Native, no extra tools | +| Install speed | Baseline | ~10-25x faster | +| Test runner | Requires Jest | Built-in (`bun:test`) | +| Bundler | Requires webpack/esbuild | Built-in (`bun build`) | +| Lockfile | `package-lock.json` (text) | `bun.lockb` (binary) | +| `.env` loading | Requires dotenv | Built-in | +| Type checking | `tsc --noEmit` | Still `tsc --noEmit` | +| npm compatibility | Full | Full (reads `package.json`) | +| Node.js API compatibility | Full | Mostly full (99%+) | + +--- + +## Quick Reference + +| Command | Purpose | +|---------|---------| +| `bun init` | Create new project | +| `bun run file.ts` | Execute TypeScript file | +| `bun --watch run file.ts` | Run with file watching | +| `bun --hot run file.ts` | Run with hot reload | +| `bun test` | Run tests | +| `bun test --watch` | Run tests in watch mode | +| `bun test --coverage` | Run tests with coverage | +| `bun build ./src/index.ts --outdir dist` | Bundle TypeScript | +| `bun install` | Install dependencies | +| `bun add package-name` | Add dependency | +| `bun add --dev package-name` | Add dev dependency | +| `bun remove package-name` | Remove dependency | +| `bun update` | Update all dependencies | +| `bun pm ls` | List installed packages | +| `bun x package-name` | Run package (like npx) | diff --git a/skills/typescript-package-manager/scripts/health-check.js b/skills/typescript-package-manager/scripts/health-check.js new file mode 100644 index 000000000..0291cfc26 --- /dev/null +++ b/skills/typescript-package-manager/scripts/health-check.js @@ -0,0 +1,337 @@ +#!/usr/bin/env node +/** + * TypeScript Project Health Check + * + * Runs diagnostic checks on a TypeScript project and reports results. + * + * Usage: + * node health-check.js # Run all checks + * node health-check.js --fix # Run checks in fix mode (reserved for future auto-fix behavior) + * node health-check.js --ci # CI mode (exit 1 on failures) + * node health-check.js --test # Run built-in makeshift tests + * + * Dependencies (install as needed): + * npm install -D type-coverage knip madge @arethetypeswrong/cli + */ + +import { execSync } from 'child_process'; +import { existsSync, readFileSync } from 'fs'; +import path from 'path'; + +// ─── Colour helpers ────────────────────────────────────────────────────────── +const c = { + green: (s) => `\x1b[32m${s}\x1b[0m`, + red: (s) => `\x1b[31m${s}\x1b[0m`, + yellow: (s) => `\x1b[33m${s}\x1b[0m`, + cyan: (s) => `\x1b[36m${s}\x1b[0m`, + bold: (s) => `\x1b[1m${s}\x1b[0m`, + dim: (s) => `\x1b[2m${s}\x1b[0m`, +}; + +const PASS = c.green('✅ PASS'); +const FAIL = c.red('❌ FAIL'); +const WARN = c.yellow('⚠️ WARN'); +const SKIP = c.dim('⏭️ SKIP'); + +// ─── CLI flags ─────────────────────────────────────────────────────────────── +const args = process.argv.slice(2); +const FIX = args.includes('--fix'); +const CI = args.includes('--ci'); +const TEST = args.includes('--test'); + +// ─── Utilities ─────────────────────────────────────────────────────────────── + +/** Run a shell command and return stdout/stderr output. + * Returns: + * - string: combined trimmed output when the command runs (even if exit code ≠ 0) + * - null: when the command cannot be spawned (e.g. ENOENT) or produces no output + */ +function tryRun(cmd, opts = {}) { + try { + return execSync(cmd, { encoding: 'utf8', stdio: 'pipe', ...opts }).trim(); + } catch (err) { + // If the command could not be spawned at all (e.g. not found), report null + if (err && (err.code === 'ENOENT' || err.errno === 'ENOENT')) { + return null; + } + // For non-zero exit codes, surface any available stdout/stderr so callers + // can present diagnostic output from tools like knip/madge/attw. + const stdout = err && err.stdout ? err.stdout.toString() : ''; + const stderr = err && err.stderr ? err.stderr.toString() : ''; + const combined = `${stdout}${stdout && stderr ? '\n' : ''}${stderr}`.trim(); + return combined || null; + } +} + +/** Check whether a CLI tool is available on PATH. */ +function hasTool(name) { + try { + execSync(`${process.platform === 'win32' ? 'where' : 'which'} ${name}`, { + stdio: 'pipe', + }); + return true; + } catch { + return false; + } +} + +/** Parse the nearest tsconfig.json and return its parsed content, or null. */ +function loadTsconfig(cwd = process.cwd()) { + const loc = path.join(cwd, 'tsconfig.json'); + if (!existsSync(loc)) return null; + try { + // Strip JS-style comments before parsing + const raw = readFileSync(loc, 'utf8').replace(/\/\/[^\n]*/g, '').replace(/\/\*[\s\S]*?\*\//g, ''); + return JSON.parse(raw); + } catch { + return null; + } +} + +/** Accumulate results */ +const results = { pass: 0, fail: 0, warn: 0, skip: 0 }; + +function report(status, label, detail = '') { + const icon = status === 'pass' ? PASS : status === 'fail' ? FAIL : status === 'warn' ? WARN : SKIP; + results[status]++; + console.log(` ${icon} ${label}${detail ? c.dim(' — ' + detail) : ''}`); +} + +// ─── Check functions ───────────────────────────────────────────────────────── + +function checkTsconfig() { + console.log(c.bold('\n🔧 tsconfig.json audit')); + const cfg = loadTsconfig(); + if (!cfg) { + report('fail', 'tsconfig.json not found', 'run: npx tsc --init'); + return; + } + report('pass', 'tsconfig.json found and parseable'); + + const co = (cfg.compilerOptions || {}); + + const strictFlags = ['strict', 'noImplicitAny', 'strictNullChecks', 'strictFunctionTypes', + 'strictBindCallApply', 'strictPropertyInitialization', 'noImplicitThis', + 'alwaysStrict']; + const enabled = strictFlags.filter(f => co[f] === true || co.strict === true); + if (co.strict === true) { + report('pass', 'strict: true enabled'); + } else if (enabled.length >= 4) { + report('warn', `${enabled.length}/${strictFlags.length} strict flags enabled`, 'consider strict: true'); + } else { + report('fail', `Only ${enabled.length}/${strictFlags.length} strict flags enabled`, 'add strict: true'); + } + + if (co.noUncheckedIndexedAccess) report('pass', 'noUncheckedIndexedAccess enabled'); + else report('warn', 'noUncheckedIndexedAccess not set', 'catches undefined array access'); + + if (co.exactOptionalPropertyTypes) report('pass', 'exactOptionalPropertyTypes enabled'); + else report('warn', 'exactOptionalPropertyTypes not set'); + + if (co.noImplicitOverride) report('pass', 'noImplicitOverride enabled'); + else report('warn', 'noImplicitOverride not set'); +} + +function checkTypeCoverage() { + console.log(c.bold('\n📊 Type coverage')); + const typeCoverageBin = process.platform === 'win32' ? 'type-coverage.cmd' : 'type-coverage'; + if (!hasTool('type-coverage') && !existsSync(path.join('node_modules', '.bin', typeCoverageBin))) { + report('skip', 'type-coverage not installed', 'npm install -D type-coverage'); + return; + } +/* +The percent parsing regex only matches values with decimals ((\d+\.\d+)%). Some type-coverage outputs can be integers (e.g., 100%), which would incorrectly hit the “Could not parse” path. Consider allowing optional decimals when extracting the percentage. +*/ + const out = tryRun('npx type-coverage --detail'); + if (!out) { report('fail', 'type-coverage failed to run'); return; } + const match = out.match(/(\d+(?:\.\d+)?)%/); + if (match) { + const pct = parseFloat(match[1]); + if (pct >= 95) report('pass', `Type coverage ${pct}%`, 'Excellent'); + else if (pct >= 80) report('warn', `Type coverage ${pct}%`, 'aim for ≥95%'); + else report('fail', `Type coverage ${pct}%`, 'high risk — many any types'); + } else { + report('warn', 'Could not parse type-coverage output'); + } +} + +function checkDeadCode() { + console.log(c.bold('\n🧹 Dead code detection')); + if (!hasTool('knip') && !existsSync('node_modules/.bin/knip')) { + report('skip', 'knip not installed', 'npm install -D knip'); + return; + } + const out = tryRun('npx knip --reporter compact 2>&1'); + if (!out) { report('fail', 'knip failed'); return; } + if (out.includes('No issues found')) { + report('pass', 'No dead code / unused exports detected'); + } else { + const lines = out.split('\n').filter(Boolean).length; + const status = lines > 10 ? 'fail' : 'warn'; + report(status, `knip found ${lines} potential issues`, 'review knip output above'); + } +} + +function checkCircularDeps() { + console.log(c.bold('\n🔄 Circular dependencies')); + if (!hasTool('madge') && !existsSync('node_modules/.bin/madge')) { + report('skip', 'madge not installed', 'npm install -D madge'); + return; + } + const src = existsSync('src') ? 'src' : '.'; + const out = tryRun(`npx madge --circular --extensions ts,tsx ${src} 2>&1`); + if (!out) { report('warn', 'madge returned no output'); return; } + if (out.includes('No circular dependency found') || out.trim() === '') { + report('pass', 'No circular dependencies found'); + } else { + report('fail', 'Circular dependencies detected', 'use shared interface files to break cycles'); + console.log(c.dim(out.split('\n').slice(0, 5).join('\n'))); + } +} + +function checkDeclarationFiles() { + console.log(c.bold('\n📦 Declaration file validation')); + if (!hasTool('attw') && !existsSync('node_modules/.bin/attw')) { + report('skip', '@arethetypeswrong/cli not installed', 'npm install -D @arethetypeswrong/cli'); + return; + } + const pkg = existsSync('package.json') + ? JSON.parse(readFileSync('package.json', 'utf8')) + : null; + if (!pkg) { report('skip', 'No package.json found'); return; } + + const out = tryRun('npx attw --pack . 2>&1'); + if (!out) { report('warn', 'attw returned no output'); return; } + if (out.includes('No problems found')) { + report('pass', 'Declaration files look correct'); + } else { + report('warn', 'attw found potential issues', 'review output above'); + } +} + +function checkTypePerformance() { + console.log(c.bold('\n⚡ TypeScript compile performance')); + const out = tryRun('npx tsc --noEmit --extendedDiagnostics 2>&1'); + if (!out) { report('warn', 'tsc not available or compile errors'); return; } + + const instantiations = out.match(/Instantiations:\s+([\d,]+)/); + const checkTime = out.match(/Check time:\s+([\d.]+)s/); + + if (instantiations) { + const count = parseInt(instantiations[1].replace(/,/g, ''), 10); + if (count < 1_000_000) report('pass', `Type instantiations: ${instantiations[1]}`, 'healthy'); + else if (count < 5_000_000) report('warn', `Type instantiations: ${instantiations[1]}`, 'consider simplifying complex generics'); + else report('fail', `Type instantiations: ${instantiations[1]}`, 'very slow — run tsc --generateTrace for profiling'); + } + if (checkTime) { + const secs = parseFloat(checkTime[1]); + if (secs < 5) report('pass', `Check time: ${checkTime[1]}s`); + else if (secs < 15) report('warn', `Check time: ${checkTime[1]}s`, 'consider incremental builds'); + else report('fail', `Check time: ${checkTime[1]}s`, 'enable incremental: true in tsconfig'); + } +} + +// ─── Main ──────────────────────────────────────────────────────────────────── + +function main() { + console.log(c.bold(c.cyan('\n╔══════════════════════════════════════╗'))); + console.log(c.bold(c.cyan('║ TypeScript Project Health Check ║'))); + console.log(c.bold(c.cyan('╚══════════════════════════════════════╝'))); + console.log(c.dim(` Project: ${process.cwd()}`)); + + checkTsconfig(); + checkTypeCoverage(); + checkDeadCode(); + checkCircularDeps(); + checkDeclarationFiles(); + checkTypePerformance(); + + // Summary + console.log(c.bold('\n── Summary ──────────────────────────────')); + console.log(` ${c.green('✅')} Pass: ${results.pass} ${c.red('❌')} Fail: ${results.fail} ${c.yellow('⚠️')} Warn: ${results.warn} ${c.dim('⏭️')} Skip: ${results.skip}`); + + if (results.fail === 0 && results.warn === 0) { + console.log(c.green('\n 🎉 Project health is excellent!\n')); + } else if (results.fail === 0) { + console.log(c.yellow('\n 👍 Project is healthy with some minor warnings.\n')); + } else { + console.log(c.red(`\n 🚨 ${results.fail} check(s) failed. Review output above.\n`)); + } + + if (CI && results.fail > 0) process.exit(1); +} + +// ─── Makeshift Tests ───────────────────────────────────────────────────────── + +function runTests() { + console.log(c.bold('\n═══ Makeshift Tests ═══\n')); + let passed = 0; + let failed = 0; + + function assert(label, actual, expected) { + if (actual === expected) { + console.log(`${PASS} ${label}`); + console.log(c.dim(` Expected: ${JSON.stringify(expected)}`)); + passed++; + } else { + console.log(`${FAIL} ${label}`); + console.log(c.dim(` Expected: ${JSON.stringify(expected)}`)); + console.log(c.dim(` Got: ${JSON.stringify(actual)}`)); + failed++; + } + } + + // Test 1: Color helpers produce ANSI codes + const green = c.green('ok'); + assert('c.green() wraps in ANSI green', green.includes('\x1b[32m'), true); + // Expected: true — string contains ANSI escape code + + const red = c.red('err'); + assert('c.red() wraps in ANSI red', red.includes('\x1b[31m'), true); + // Expected: true + + // Test 2: tryRun returns null on bad command + const bad = tryRun('this-command-does-not-exist-xyz-123 2>/dev/null'); + assert('tryRun() returns null for unknown command', bad, null); + // Expected: null + + // Test 3: tryRun executes simple echo + const echo = tryRun(`node -e "console.log('hello')"`); + assert('tryRun() returns stdout of valid command', echo, 'hello'); + // Expected: 'hello' + + // Test 4: hasTool detects node + const nodeFound = hasTool('node'); + assert('hasTool("node") returns true', nodeFound, true); + // Expected: true (node is running this script) + + // Test 5: hasTool returns false for fake tool + const fakeFound = hasTool('this-tool-xyz-does-not-exist-abc'); + assert('hasTool() returns false for nonexistent tool', fakeFound, false); + // Expected: false + + // Test 6: loadTsconfig returns null when no tsconfig.json in a temp path + const tmpCfg = loadTsconfig('/tmp'); + assert('loadTsconfig() returns null for missing tsconfig', tmpCfg, null); + // Expected: null + + // Test 7: Results accumulator starts at 0 + const fresh = { pass: 0, fail: 0, warn: 0, skip: 0 }; + assert('results.pass initial value', fresh.pass, 0); + // Expected: 0 + + // Test 8: PASS constant includes green ANSI code + assert('PASS constant contains green ANSI', PASS.includes('\x1b[32m'), true); + // Expected: true + + console.log(c.bold(`\n Tests: ${c.green(passed + ' passed')} ${failed > 0 ? c.red(failed + ' failed') : c.dim('0 failed')}\n`)); + if (failed > 0) process.exit(1); +} + +// ─── Entry point ───────────────────────────────────────────────────────────── + +if (TEST) { + runTests(); +} else { + main(); +} diff --git a/skills/typescript-package-manager/scripts/health-check.md b/skills/typescript-package-manager/scripts/health-check.md new file mode 100644 index 000000000..994c40305 --- /dev/null +++ b/skills/typescript-package-manager/scripts/health-check.md @@ -0,0 +1,984 @@ +# TypeScript Project Health Check + +A comprehensive diagnostic and improvement guide for TypeScript projects. Use this to audit type safety, detect dead code, analyze performance, and ensure your project follows best practices. + +**References:** +- https://www.typescriptlang.org/tsconfig/ +- https://www.typescriptlang.org/docs/handbook/declaration-files/do-s-and-don-ts.html + +--- + +## Overview + +Run a full health check to identify and fix issues across these dimensions: + +| Check | Tool | What It Finds | +|-------|------|---------------| +| Type Coverage | `type-coverage` | Percentage of typed expressions | +| Strict Mode | `tsc` flags | Unsafe type checking gaps | +| Missing Types | `npm ls` + `@types` | Untyped dependencies | +| Dead Code | `knip` or `ts-prune` | Unused exports/files | +| Circular Deps | `madge` | Import cycles | +| Bundle Size | `size-limit` | Regression in output size | +| Type Performance | `tsc --extendedDiagnostics` | Slow compilation hotspots | +| Declaration Files | `@arethetypeswrong/cli` | Broken `.d.ts` exports | + +--- + +## 1. Type Coverage Check + +Measure what percentage of your TypeScript code is actually typed (not `any`). + +### Using `type-coverage` + +```bash +# Install +npm install --save-dev type-coverage + +# Run +npx type-coverage + +# Strict: fail if coverage drops below 90% +npx type-coverage --at-least 90 + +# Show all uncovered lines +npx type-coverage --detail + +# Ignore catch blocks and well-known any patterns +npx type-coverage --ignore-catch --ignore-files "src/**/*.test.ts" +``` + +### Using `typescript-coverage-report` + +```bash +# Install +npm install --save-dev typescript-coverage-report + +# Generate HTML report +npx typescript-coverage-report + +# Open coverage/index.html in your browser +``` + +### Interpreting Results + +| Coverage | Status | +|----------|--------| +| 95-100% | Excellent | +| 85-94% | Good | +| 70-84% | Needs improvement | +| Below 70% | High risk — prioritize fixing | + +### Add to `package.json` + +```json +{ + "scripts": { + "health:coverage": "type-coverage --at-least 90 --detail" + } +} +``` + +--- + +## 2. Strict Mode Audit + +Enable TypeScript strict flags progressively to improve type safety without breaking everything at once. + +### Checklist: Strict Flags + +Enable these in order (each one is harder to fix than the last): + +```json +{ + "compilerOptions": { + "noImplicitAny": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "strictBindCallApply": true, + "strictPropertyInitialization": true, + "noImplicitThis": true, + "alwaysStrict": true, + "strict": true, + + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "exactOptionalPropertyTypes": true, + + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noImplicitReturns": true + } +} +``` + +### Step-by-Step Migration to `strict: true` + +```bash +# Step 1: Count current errors with noImplicitAny +npx tsc --noImplicitAny --noEmit 2>&1 | grep "error TS" | wc -l + +# Step 2: Count errors with strictNullChecks +npx tsc --strictNullChecks --noEmit 2>&1 | grep "error TS" | wc -l + +# Step 3: Count all strict errors +npx tsc --strict --noEmit 2>&1 | grep "error TS" | wc -l +``` + +### Finding Remaining `any` Types + +```bash +# Count explicit 'any' in source +grep -r ": any" src/ --include="*.ts" | wc -l + +# Find ts-ignore suppression comments +grep -r "@ts-ignore\|@ts-nocheck" src/ --include="*.ts" + +# Find explicit any casts +grep -r "as any" src/ --include="*.ts" +``` + +### Add to `package.json` + +```json +{ + "scripts": { + "health:strict": "tsc --strict --noEmit" + } +} +``` + +--- + +## 3. Dependency Type Audit + +Find dependencies that lack TypeScript type definitions. + +### Check for Missing `@types` Packages + +```bash +# List all dependencies and check for types +attw +``` + +### Manual Check Script + +```bash +# List production dependencies without @types counterpart +node -e " +const pkg = require('./package.json'); +const deps = Object.keys(pkg.dependencies || {}); +const devDeps = Object.keys(pkg.devDependencies || {}); +const typePkgs = devDeps.filter(d => d.startsWith('@types/')); + +deps.forEach(dep => { + const typePkg = '@types/' + dep.replace('@', '').replace('/', '__'); + if (!typePkgs.includes(typePkg)) { + // Check if package bundles its own types + try { + const depPkg = require('./node_modules/' + dep + '/package.json'); + if (!depPkg.types && !depPkg.typings) { + console.log('Missing types:', dep, '-> try:', typePkg); + } + } catch (e) { + console.log('Cannot check:', dep); + } + } +}); +" +``` + +### Bulk Install Missing Types + +```bash +# Install types for common packages if needed +npm install --save-dev \ + @types/node \ + @types/express \ + @types/lodash \ + @types/jest \ + @types/react \ + @types/react-dom +``` + +### Add to `package.json` + +```json +{ + "scripts": { + "health:types": "attw" + } +} +``` + +--- + +## 4. Dead Code Detection + +Find unused exports, files, and dependencies that bloat your project. + +### Using `knip` (Recommended) + +```bash +# Install +npm install --save-dev knip + +# Run +npx knip + +# Only report unused exports +npx knip --include exports + +# Fix automatically (removes unused exports) +npx knip --fix +``` + +### `knip.json` Configuration + +```json +{ + "entry": ["src/index.ts"], + "project": ["src/**/*.ts"], + "ignoreDependencies": ["some-peer-dep"], + "ignoreExportsUsedInFile": true, + "rules": { + "files": "warn", + "exports": "warn", + "types": "warn", + "dependencies": "warn" + } +} +``` + +### Using `ts-prune` (Alternative) + +```bash +# Install +npm install --save-dev ts-prune + +# Find unused exports +npx ts-prune + +# Ignore test files +npx ts-prune --ignore ".*test.*|.*spec.*" +``` + +### Using ESLint Rules for Unused Code + +```json +{ + "compilerOptions": { + "noUnusedLocals": true, + "noUnusedParameters": true + } +} +``` + +### Add to `package.json` + +```json +{ + "scripts": { + "health:dead-code": "knip" + } +} +``` + +--- + +## 5. Circular Dependency Check + +Circular imports cause runtime issues, make code harder to test, and slow down compilation. + +### Using `madge` + +```bash +# Install +npm install --save-dev madge + +# Check for circular dependencies +npx madge --circular src/ + +# TypeScript files +npx madge --circular --ts-config tsconfig.json src/ + +# Generate a dependency graph image (requires graphviz) +npx madge --image graph.svg src/ + +# Show all dependencies +npx madge src/index.ts +``` + +### Interpreting Circular Dependency Output + +``` +Circular dependency found! +src/auth/user.service.ts -> src/auth/auth.service.ts -> src/auth/user.service.ts +``` + +### Breaking Circular Dependencies + +```typescript +// Pattern: Extract shared interface to a separate file + +// Before (circular): +// user.service.ts imports AuthService +// auth.service.ts imports UserService + +// After (no circular): +// types/auth.types.ts — shared interfaces only +export interface IAuthService { + validateToken(token: string): Promise; +} +export interface IUserService { + findById(id: string): Promise; +} + +// auth.service.ts — depends on IUserService interface, not UserService class +import type { IUserService } from '../types/auth.types'; + +// user.service.ts — depends on IAuthService interface, not AuthService class +import type { IAuthService } from '../types/auth.types'; +``` + +### Add to `package.json` + +```json +{ + "scripts": { + "health:circular": "madge --circular --ts-config tsconfig.json src/" + } +} +``` + +--- + +## 6. Bundle Size Analysis + +Prevent unintentional bundle size growth when publishing libraries or building apps. + +### Using `size-limit` + +```bash +# Install +npm install --save-dev size-limit @size-limit/preset-small-lib + +# Or for apps +npm install --save-dev size-limit @size-limit/preset-app +``` + +### `.size-limit.json` + +```json +[ + { + "path": "dist/index.js", + "limit": "10 kB", + "gzip": true + }, + { + "path": "dist/esm/index.js", + "limit": "10 kB", + "gzip": true, + "import": "{ mainExport }" + } +] +``` + +### Run Size Check + +```bash +# Check bundle sizes +npx size-limit + +# Show detailed breakdown +npx size-limit --why +``` + +### `package.json` Integration + +```json +{ + "scripts": { + "health:size": "size-limit", + "build": "tsc && npm run health:size" + }, + "size-limit": [ + { + "path": "dist/index.js", + "limit": "10 kB" + } + ] +} +``` + +--- + +## 7. Type Performance Analysis + +Identify slow TypeScript compilation and complex types that slow down editor responsiveness. + +### Extended Diagnostics + +```bash +# Show detailed compilation statistics +npx tsc --extendedDiagnostics --noEmit 2>&1 | head -50 +``` + +Key metrics to watch: + +``` +Files: Total files included in compilation +Lines: Total lines of code +Instantiations: Number of type instantiations (high = complex types) +Memory used: Peak memory usage +Parse time: Time to parse files +Bind time: Time to bind symbols +Check time: Time to type-check +Total time: Overall compilation time +``` + +### Generate a Performance Trace + +```bash +# Generate trace for analysis +npx tsc --generateTrace ./trace-output --noEmit + +# Analyze trace in Chrome DevTools: +# 1. Open chrome://tracing +# 2. Load ./trace-output/trace.json +``` + +### Identify Problematic Files + +```bash +# Show per-file check times +npx tsc --diagnostics --noEmit 2>&1 | grep -E "Check time|\.ts" +``` + +### Common Performance Issues + +| Issue | Symptom | Fix | +|-------|---------|-----| +| Deep generic recursion | High instantiation count | Add type parameter bounds | +| Complex conditional types | Slow editor | Simplify or cache with `type` alias | +| Large union types | High check time | Use discriminated unions | +| `skipLibCheck: false` | Slow compilation | Enable `skipLibCheck: true` | +| No `incremental` | Full rebuild every time | Enable `incremental: true` | + +### Incremental Compilation + +```json +{ + "compilerOptions": { + "incremental": true, + "tsBuildInfoFile": ".tsbuildinfo" + } +} +``` + +```bash +# Add to .gitignore +echo ".tsbuildinfo" >> .gitignore +``` + +### Add to `package.json` + +```json +{ + "scripts": { + "health:perf": "tsc --extendedDiagnostics --noEmit", + "health:trace": "tsc --generateTrace ./trace-output --noEmit && echo 'Load trace-output/trace.json in chrome://tracing'" + } +} +``` + +--- + +## 8. Declaration File Validation + +Ensure your published `.d.ts` files work correctly for all consumers (ESM, CJS, bundlers). + +### Using `@arethetypeswrong/cli` + +```bash +# Install +npm install --save-dev @arethetypeswrong/cli + +# Check your built package +npx attw --pack . + +# Check a published package +npx attw some-npm-package + +# Check from tarball +npm pack +npx attw my-package-1.0.0.tgz +``` + +### Common Issues Detected + +| Problem | Description | Fix | +|---------|-------------|-----| +| `missing-exports` | Export in `package.json` has no types | Add matching `.d.ts` file | +| `false-cjs` | Package claims CJS but types say ESM | Fix `exports` condition | +| `cjs-resolves-to-esm` | CJS import resolves to ESM types | Provide separate CJS types | +| `no-resolution` | Cannot resolve the package | Check `main`/`module` fields | + +### Validate Declaration Files Manually + +```bash +# Check that all public exports have types +npx tsc --declaration --emitDeclarationOnly --outDir ./dist-check + +# Check for missing re-exports +node -e " +const fs = require('fs'); +const dts = fs.readFileSync('./dist/index.d.ts', 'utf-8'); +console.log('Exports found:', (dts.match(/^export /mg) || []).length); +" +``` + +### Add to `package.json` + +```json +{ + "scripts": { + "health:declarations": "attw --pack .", + "prepublishOnly": "npm run health:declarations && npm run build" + } +} +``` + +--- + +## The `health-check.sh` Shell Script + +A single script that runs all health checks in sequence and produces a report. + +```bash +#!/usr/bin/env bash +# health-check.sh — TypeScript Project Health Check +# Usage: bash health-check.sh [--fix] [--ci] + +set -euo pipefail + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +BOLD='\033[1m' +NC='\033[0m' # No Color + +# Options +FIX=false +CI=false +FAILED=0 +WARNINGS=0 + +for arg in "$@"; do + case $arg in + --fix) FIX=true ;; + --ci) CI=true ;; + esac +done + +# Helper functions +pass() { echo -e "${GREEN} PASS${NC} $1"; } +fail() { echo -e "${RED} FAIL${NC} $1"; ((FAILED++)) || true; } +warn() { echo -e "${YELLOW} WARN${NC} $1"; ((WARNINGS++)) || true; } +section() { echo -e "\n${BOLD}${BLUE}=== $1 ===${NC}"; } + +echo -e "${BOLD}TypeScript Project Health Check${NC}" +echo "Project: $(node -p "require('./package.json').name" 2>/dev/null || echo 'unknown')" +echo "Date: $(date '+%Y-%m-%d %H:%M:%S')" +echo "---" + +# ───────────────────────────────────────────────────────────────────────────── +section "1. TypeScript Compilation" +# ───────────────────────────────────────────────────────────────────────────── + +if npx tsc --noEmit --pretty false 2>&1 | tail -5; then + pass "No TypeScript errors" +else + fail "TypeScript compilation errors found" +fi + +# ───────────────────────────────────────────────────────────────────────────── +section "2. Type Coverage" +# ───────────────────────────────────────────────────────────────────────────── + + +# The health-check.sh snippet uses npx --yes type-coverage to auto-install and execute the latest version of a +# third-party tool during the health check. When this script is wired into CI (as suggested later in the doc), it will +# pull unaudited code directly from the npm registry at build time, bypassing package.json and lockfile pinning, +# so a compromised type-coverage release could exfiltrate secrets or tamper with build artifacts. To reduce +# supply chain risk, require type-coverage to be present as a dev dependency and invoke the local binary (for +# example via npx with --no-install or a direct package.json script) instead of dynamically downloading it in CI. + +if command -v npx &>/dev/null && npx --yes type-coverage --version &>/dev/null; then + COVERAGE_OUTPUT=$(npx type-coverage --at-least 80 2>&1 || true) + if echo "$COVERAGE_OUTPUT" | grep -q "type-coverage:"; then + PERCENT=$(echo "$COVERAGE_OUTPUT" | grep -oP '\d+\.\d+(?=%)' | head -1) + if (( $(echo "$PERCENT >= 90" | bc -l) )); then + pass "Type coverage: ${PERCENT}%" + elif (( $(echo "$PERCENT >= 80" | bc -l) )); then + warn "Type coverage: ${PERCENT}% (target: 90%+)" + else + fail "Type coverage: ${PERCENT}% (below 80% threshold)" + fi + else + warn "Could not parse type coverage output" + fi +else + warn "type-coverage not installed — run: npm install --save-dev type-coverage" +fi + +# ───────────────────────────────────────────────────────────────────────────── +section "3. Strict Mode Audit" +# ───────────────────────────────────────────────────────────────────────────── + +TSCONFIG=$(cat tsconfig.json 2>/dev/null || echo '{}') + +check_flag() { + local flag="$1" + if echo "$TSCONFIG" | grep -q "\"$flag\": true" || echo "$TSCONFIG" | grep -q "\"strict\": true"; then + pass "$flag is enabled" + else + warn "$flag is NOT enabled" + fi +} + +check_flag "strict" +check_flag "noUncheckedIndexedAccess" +check_flag "noImplicitOverride" +check_flag "exactOptionalPropertyTypes" + +# Count explicit 'any' usages +ANY_COUNT=$(grep -r ": any\|as any\| any " src/ --include="*.ts" 2>/dev/null | wc -l || echo "0") +if [ "$ANY_COUNT" -eq 0 ]; then + pass "No explicit 'any' types found" +elif [ "$ANY_COUNT" -lt 10 ]; then + warn "Found ${ANY_COUNT} explicit 'any' usage(s) — consider replacing" +else + fail "Found ${ANY_COUNT} explicit 'any' usages — significant type safety gap" +fi + +# Count ts-ignore +IGNORE_COUNT=$(grep -r "@ts-ignore\|@ts-nocheck" src/ --include="*.ts" 2>/dev/null | wc -l || echo "0") +if [ "$IGNORE_COUNT" -eq 0 ]; then + pass "No @ts-ignore or @ts-nocheck comments" +else + warn "Found ${IGNORE_COUNT} @ts-ignore/@ts-nocheck comment(s)" +fi + +# ───────────────────────────────────────────────────────────────────────────── +section "4. Dead Code Detection" +# ───────────────────────────────────────────────────────────────────────────── + +# This npx --yes knip call auto-installs and executes the latest knip package +# whenever the health check runs, which in CI means arbitrary third-party code +# is fetched and executed at build time outside of your dependency and lockfile +# management. If knip (or its transitive dependencies) were compromised, an +# attacker could run code in the CI environment with access to repository +# contents and any configured secrets. Prefer declaring knip as a dev +# dependency and invoking the locally installed version (or using npx with +# installation disabled) so CI never implicitly downloads and runs unpinned +# tools. +if npx --yes knip --version &>/dev/null 2>&1; then + if $FIX; then + npx knip --fix 2>&1 || warn "knip --fix completed with warnings" + pass "Dead code fix attempted" + else + KNIP_OUTPUT=$(npx knip 2>&1 || true) + if echo "$KNIP_OUTPUT" | grep -q "No issues found"; then + pass "No dead code detected" + else + warn "Potential dead code found — run 'npx knip' for details" + fi + fi +else + warn "knip not installed — run: npm install --save-dev knip" +fi + +# ───────────────────────────────────────────────────────────────────────────── +section "5. Circular Dependencies" +# ───────────────────────────────────────────────────────────────────────────── + +# Similarly, this npx --yes madge usage will download and execute the newest madge release on each run if it +# is not already installed, which introduces supply chain risk when the script runs in CI. Because this bypasses +# package.json and lockfile pinning, a compromised madge version could run arbitrary code in your pipeline. It +# is safer to require madge as a dev dependency and only execute the local binary, avoiding any automatic +# install/execute behavior during CI. +if npx --yes madge --version &>/dev/null 2>&1; then + CIRCULAR=$(npx madge --circular --ts-config tsconfig.json src/ 2>&1 || true) + if echo "$CIRCULAR" | grep -q "No circular"; then + pass "No circular dependencies found" + elif echo "$CIRCULAR" | grep -q "Circular dependency found"; then + CYCLE_COUNT=$(echo "$CIRCULAR" | grep -c "Circular" || echo "1") + fail "Found ${CYCLE_COUNT} circular dependency cycle(s)" + echo "$CIRCULAR" | grep -A 2 "Circular" | head -20 + else + warn "Could not parse madge output" + fi +else + warn "madge not installed — run: npm install --save-dev madge" +fi + +# ───────────────────────────────────────────────────────────────────────────── +section "6. Dependencies" +# ───────────────────────────────────────────────────────────────────────────── + +# Check for security vulnerabilities +echo "Running npm audit..." +AUDIT_OUTPUT=$(npm audit --json 2>/dev/null || echo '{"metadata":{"vulnerabilities":{}}}') +CRITICAL=$(echo "$AUDIT_OUTPUT" | node -pe "JSON.parse(require('fs').readFileSync('/dev/stdin','utf8')).metadata.vulnerabilities.critical || 0" 2>/dev/null || echo "?") +HIGH=$(echo "$AUDIT_OUTPUT" | node -pe "JSON.parse(require('fs').readFileSync('/dev/stdin','utf8')).metadata.vulnerabilities.high || 0" 2>/dev/null || echo "?") + +if [ "$CRITICAL" = "0" ] && [ "$HIGH" = "0" ]; then + pass "No critical/high vulnerabilities" +else + fail "Found ${CRITICAL} critical, ${HIGH} high severity vulnerabilities — run 'npm audit fix'" +fi + +# Check for outdated packages +OUTDATED=$(npm outdated --json 2>/dev/null || echo '{}') +OUTDATED_COUNT=$(echo "$OUTDATED" | node -pe "Object.keys(JSON.parse(require('fs').readFileSync('/dev/stdin','utf8'))).length" 2>/dev/null || echo "0") +if [ "$OUTDATED_COUNT" = "0" ]; then + pass "All packages are up to date" +else + warn "${OUTDATED_COUNT} package(s) are outdated — run 'npm outdated' for details" +fi + +# ───────────────────────────────────────────────────────────────────────────── +section "7. Build & Performance" +# ───────────────────────────────────────────────────────────────────────────── + +echo "Measuring compilation time..." +START_TIME=$(date +%s%N) +npx tsc --noEmit 2>/dev/null +END_TIME=$(date +%s%N) +COMPILE_TIME=$(( (END_TIME - START_TIME) / 1000000 )) + +if [ "$COMPILE_TIME" -lt 5000 ]; then + pass "Compilation time: ${COMPILE_TIME}ms (fast)" +elif [ "$COMPILE_TIME" -lt 15000 ]; then + warn "Compilation time: ${COMPILE_TIME}ms (consider enabling incremental)" +else + fail "Compilation time: ${COMPILE_TIME}ms (slow — run 'tsc --extendedDiagnostics' to profile)" +fi + +# Check for incremental compilation +if echo "$TSCONFIG" | grep -q "\"incremental\": true"; then + pass "Incremental compilation is enabled" +else + warn "incremental compilation not enabled — add '\"incremental\": true' to tsconfig.json" +fi + +# ───────────────────────────────────────────────────────────────────────────── +section "8. Declaration File Validation" +# ───────────────────────────────────────────────────────────────────────────── + +# Only check if we have a dist/ directory (published package) +if [ -d "dist" ]; then + # The npx --yes @arethetypeswrong/cli invocation also auto-installs and runs the + # latest version of this tool whenever the health check is executed, which in a CI + # context means unpinned third-party code is fetched and executed with access to the + # build environment. This pattern bypasses dependency pinning and lockfiles, so a + # compromised @arethetypeswrong/cli release could be used to inject malicious behavior + # into your pipeline. To mitigate this, add @arethetypeswrong/cli as a dev dependency + # and run the local binary (or use npx without on-the-fly installation) instead of + # dynamically downloading it in CI. + if npx --yes @arethetypeswrong/cli --version &>/dev/null 2>&1; then + echo "Validating declaration files..." + if npx attw --pack . 2>&1 | grep -q "No problems found"; then + pass "Declaration files are valid" + else + warn "Declaration file issues found — run 'npx attw --pack .' for details" + fi + else + warn "@arethetypeswrong/cli not installed — run: npm install --save-dev @arethetypeswrong/cli" + fi +else + warn "No dist/ directory found — build first to validate declaration files" +fi + +# ───────────────────────────────────────────────────────────────────────────── +section "Summary" +# ───────────────────────────────────────────────────────────────────────────── + +echo "" +if [ "$FAILED" -eq 0 ] && [ "$WARNINGS" -eq 0 ]; then + echo -e "${GREEN}${BOLD}All checks passed!${NC} Your TypeScript project is in excellent health." +elif [ "$FAILED" -eq 0 ]; then + echo -e "${YELLOW}${BOLD}${WARNINGS} warning(s), 0 failures.${NC} Good health with some improvements possible." +else + echo -e "${RED}${BOLD}${FAILED} failure(s), ${WARNINGS} warning(s).${NC} Action required." +fi + +echo "" +echo "Failures: $FAILED" +echo "Warnings: $WARNINGS" + +if $CI && [ "$FAILED" -gt 0 ]; then + exit 1 +fi +``` + +### Making the Script Executable + +```bash +chmod +x health-check.sh + +# Run all checks +bash health-check.sh + +# Run with auto-fix where possible +bash health-check.sh --fix + +# Fail the process in CI if checks fail +bash health-check.sh --ci +``` + +--- + +## Complete `package.json` Health Check Scripts Block + +Add these scripts to your `package.json`: + +```json +{ + "scripts": { + "health": "bash scripts/health-check.sh", + "health:fix": "bash scripts/health-check.sh --fix", + "health:ci": "bash scripts/health-check.sh --ci", + "health:coverage": "type-coverage --at-least 90 --detail", + "health:strict": "tsc --strict --noEmit", + "health:dead-code": "knip", + "health:circular": "madge --circular --ts-config tsconfig.json src/", + "health:size": "size-limit", + "health:perf": "tsc --extendedDiagnostics --noEmit 2>&1 | head -30", + "health:trace": "tsc --generateTrace ./trace-output --noEmit", + "health:audit": "npm audit", + "health:declarations": "attw --pack .", + "health:outdated": "npm outdated" + }, + "devDependencies": { + "type-coverage": "^2.0.0", + "knip": "^5.0.0", + "madge": "^8.0.0", + "size-limit": "^11.0.0", + "@size-limit/preset-small-lib": "^11.0.0", + "@arethetypeswrong/cli": "^0.15.0" + } +} +``` + +--- + +## One-Time Setup: Install All Health Check Tools + +```bash +npm install --save-dev \ + type-coverage \ + typescript-coverage-report \ + knip \ + madge \ + size-limit \ + @size-limit/preset-small-lib \ + @arethetypeswrong/cli +``` + +--- + +## CI Integration + +Add the health check to your CI pipeline: + +```yaml +# .github/workflows/health-check.yml +name: TypeScript Health Check + +on: + push: + branches: [main, develop] + pull_request: + branches: [main] + schedule: + # Run weekly on Monday at 9am UTC + - cron: '0 9 * * 1' + +jobs: + health-check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + + - run: npm ci + +# In the sample GitHub Actions workflow, the Type coverage, Dead code check, and Circular dependency check +# steps use bare npx type-coverage, npx knip, and npx madge, which may cause GitHub runners to download +# and execute whatever versions of these third-party CLIs are current on npm. Because these tools are not +# pinned via devDependencies and a lockfile, a compromised package could execute arbitrary code within your +# CI job and access repository data or secrets. To reduce this supply-chain risk, install these CLIs as dev +# dependencies and have the workflow invoke the local binaries (or npm scripts) instead of relying on +# unpinned npx downloads. + - name: Type check + run: npx tsc --noEmit + + - name: Type coverage + run: npx type-coverage --at-least 90 + + - name: Dead code check + run: npx knip + + - name: Circular dependency check + run: npx madge --circular --ts-config tsconfig.json src/ + + - name: Security audit + run: npm audit --audit-level=high + + - name: Full health check + run: bash scripts/health-check.sh --ci +``` + +--- + +## Quick Wins Checklist + +When you first run the health check, start with these high-impact fixes: + +- [ ] Enable `"strict": true` in `tsconfig.json` and fix errors +- [ ] Replace all `as any` casts with proper types or `unknown` +- [ ] Remove all `@ts-ignore` comments (fix the underlying issue instead) +- [ ] Install `@types/*` for all untyped dependencies +- [ ] Break circular dependency cycles +- [ ] Enable `"incremental": true` for faster builds +- [ ] Set `"noUnusedLocals": true` and remove dead code +- [ ] Add type coverage threshold to CI (start at 80%, raise to 90%+) + +--- + +## Health Check Score Card + +Use this to track progress over time: + +| Check | Current | Target | Status | +|-------|---------|--------|--------| +| TypeScript errors | 0 | 0 | | +| Type coverage | -% | 90%+ | | +| Strict mode | off/on | on | | +| Explicit `any` count | - | 0 | | +| @ts-ignore count | - | 0 | | +| Circular dependencies | - | 0 | | +| Critical vulnerabilities | - | 0 | | +| Build time (ms) | - | <5000 | | +| Bundle size (kB) | - | limit | | diff --git a/skills/typescript-package-manager/scripts/npm-workflow.js b/skills/typescript-package-manager/scripts/npm-workflow.js new file mode 100644 index 000000000..dd3de3b74 --- /dev/null +++ b/skills/typescript-package-manager/scripts/npm-workflow.js @@ -0,0 +1,336 @@ +#!/usr/bin/env node +/** + * TypeScript npm Workflow Helper + * + * Provides utilities for common TypeScript + npm project tasks: + * - Generate a package.json scripts block for TypeScript projects + * - Generate tsconfig.json templates + * - Print setup checklists + * - Audit @types packages against dependencies + * + * Usage: + * node npm-workflow.js --scripts # Print recommended package.json scripts + * node npm-workflow.js --tsconfig # Print recommended tsconfig.json + * node npm-workflow.js --checklist # Print TypeScript project setup checklist + * node npm-workflow.js --audit-types # Audit @types packages vs dependencies + * node npm-workflow.js --test # Run built-in makeshift tests + * + * Self-contained — no external dependencies required. + */ + +import { existsSync, readFileSync } from 'fs'; + +// ─── CLI flags ─────────────────────────────────────────────────────────────── +const args = process.argv.slice(2); +const SHOW_SCRIPTS = args.includes('--scripts'); +const SHOW_TSCONFIG = args.includes('--tsconfig'); +const SHOW_CHECKLIST = args.includes('--checklist'); +const AUDIT_TYPES = args.includes('--audit-types'); +const TEST = args.includes('--test'); +const SHOW_HELP = args.includes('--help') || args.length === 0; + +// ─── Colours ───────────────────────────────────────────────────────────────── +const c = { + green: (s) => `\x1b[32m${s}\x1b[0m`, + yellow: (s) => `\x1b[33m${s}\x1b[0m`, + cyan: (s) => `\x1b[36m${s}\x1b[0m`, + bold: (s) => `\x1b[1m${s}\x1b[0m`, + dim: (s) => `\x1b[2m${s}\x1b[0m`, +}; + +// ─── Templates ─────────────────────────────────────────────────────────────── + +/** + * Returns recommended package.json scripts for a TypeScript project. + * @param {object} opts + * @param {boolean} [opts.includeTest=true] + * @param {boolean} [opts.includeLint=true] + * @returns {object} scripts map + */ +function buildScripts(opts = {}) { + const { includeTest = true, includeLint = true } = opts; + const scripts = { + build: 'tsc --project tsconfig.build.json', + 'build:watch': 'tsc --project tsconfig.build.json --watch', + typecheck: 'tsc --noEmit', + 'typecheck:watch': 'tsc --noEmit --watch', + clean: 'node -e "require(\'fs\').rmSync(\'dist\', { recursive: true, force: true })"', + dev: 'tsx watch src/index.ts', + start: 'node dist/index.js', + prepare: 'npm run build', + }; + + if (includeTest) { + Object.assign(scripts, { + test: 'jest', + 'test:watch': 'jest --watch', + 'test:coverage': 'jest --coverage', + }); + } + + if (includeLint) { + Object.assign(scripts, { + lint: 'eslint src --ext .ts,.tsx', + 'lint:fix': 'eslint src --ext .ts,.tsx --fix', + format: 'prettier --write "src/**/*.{ts,tsx}"', + 'format:check': 'prettier --check "src/**/*.{ts,tsx}"', + }); + } + + const validateParts = ['npm run typecheck']; + if (includeLint) { + validateParts.push('npm run lint'); + } + if (includeTest) { + validateParts.push('npm run test'); + } + + Object.assign(scripts, { + validate: validateParts.join(' && '), + ci: 'npm run validate && npm run build', + prepublishOnly: 'npm run ci', + }); + + return scripts; +} + +/** + * Returns a recommended tsconfig.json for a TypeScript project. + * @param {'node'|'react'|'lib'} [target='node'] + * @returns {object} + */ +function buildTsconfig(target = 'node') { + const base = { + compilerOptions: { + target: 'ES2022', + lib: ['ES2022'], + module: target === 'react' ? 'ESNext' : 'CommonJS', + moduleResolution: target === 'react' ? 'Bundler' : 'Node10', + outDir: './dist', + rootDir: './src', + declaration: true, + declarationMap: true, + sourceMap: true, + strict: true, + noUncheckedIndexedAccess: true, + exactOptionalPropertyTypes: true, + noImplicitOverride: true, + noImplicitReturns: true, + noFallthroughCasesInSwitch: true, + noUnusedLocals: true, + noUnusedParameters: true, + esModuleInterop: true, + skipLibCheck: true, + forceConsistentCasingInFileNames: true, + }, + include: ['src'], + exclude: ['node_modules', 'dist', '**/*.test.ts', '**/*.spec.ts'], + }; + + if (target === 'react') { + base.compilerOptions.jsx = 'react-jsx'; + base.compilerOptions.lib = ['ES2022', 'DOM', 'DOM.Iterable']; + } + + if (target === 'lib') { + base.compilerOptions.declarationDir = './types'; + } + + return base; +} + +/** + * Returns the list of @types packages that are likely needed for common deps. + * @param {string[]} deps - array of dependency names + * @returns {string[]} recommended @types packages + */ +function suggestTypes(deps) { + const typeMap = { + express: '@types/express', + node: '@types/node', + jest: '@types/jest', + mocha: '@types/mocha', + chai: '@types/chai', + react: '@types/react', + 'react-dom': '@types/react-dom', + lodash: '@types/lodash', + uuid: '@types/uuid', + ws: '@types/ws', + cors: '@types/cors', + helmet: '@types/helmet', + morgan: '@types/morgan', + multer: '@types/multer', + passport: '@types/passport', + jsonwebtoken: '@types/jsonwebtoken', + bcrypt: '@types/bcrypt', + supertest: '@types/supertest', + }; + + return deps + .filter(dep => typeMap[dep]) + .map(dep => typeMap[dep]); +} + +// ─── Actions ───────────────────────────────────────────────────────────────── + +function printHelp() { + console.log(` +${c.bold(c.cyan('TypeScript npm Workflow Helper'))} + +${c.bold('Usage:')} + node npm-workflow.js [option] + +${c.bold('Options:')} + --scripts Print recommended package.json scripts block + --tsconfig Print recommended tsconfig.json + --checklist Print TypeScript project setup checklist + --audit-types Audit @types packages against your package.json + --test Run built-in makeshift tests + --help Show this help +`); +} + +function printScripts() { + console.log(c.bold('\n📜 Recommended package.json scripts\n')); + const scripts = buildScripts({ includeTest: true, includeLint: true }); + const block = JSON.stringify({ scripts }, null, 2); + console.log(block); + console.log(c.dim('\n Add the above to your package.json\n')); +} + +function printTsconfig() { + console.log(c.bold('\n⚙️ Recommended tsconfig.json (Node.js)\n')); + console.log(JSON.stringify(buildTsconfig('node'), null, 2)); + console.log(c.dim('\n Tip: extend this script if you need additional presets (e.g., React or library tsconfig variants).\n')); +} + +function printChecklist() { + console.log(c.bold('\n📋 TypeScript Project Setup Checklist\n')); + const steps = [ + ['Initialize project', 'npm init -y'], + ['Install TypeScript', 'npm install -D typescript'], + ['Install Node types', 'npm install -D @types/node'], + ['Install tsx (dev runner)', 'npm install -D tsx'], + ['Install ts-jest', 'npm install -D jest ts-jest @types/jest'], + ['Install ESLint + TS', 'npm install -D eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin'], + ['Install Prettier', 'npm install -D prettier'], + ['Create tsconfig.json', 'npx tsc --init (or use --tsconfig flag above)'], + ['Create src/index.ts', 'mkdir src && echo "export {};" > src/index.ts'], + ['Add package.json scripts', 'node npm-workflow.js --scripts (copy output)'], + ['First build', 'npm run build'], + ['Run type check', 'npm run typecheck'], + ]; + + steps.forEach(([label, cmd], i) => { + console.log(` ${c.green(String(i + 1).padStart(2, ' '))}. ${label.padEnd(30)} ${c.dim(cmd)}`); + }); + console.log(); +} + +function auditTypes() { + console.log(c.bold('\n🔍 @types package audit\n')); + if (!existsSync('package.json')) { + console.log(c.yellow(' No package.json found in current directory.\n')); + return; + } + const pkg = JSON.parse(readFileSync('package.json', 'utf8')); + const dependencies = pkg.dependencies || {}; + const devDependencies = pkg.devDependencies || {}; + const allDeps = Object.keys({ ...dependencies, ...devDependencies }); + const suggested = suggestTypes(allDeps); + const devDeps = Object.keys(devDependencies); + const missing = suggested.filter(t => !devDeps.includes(t)); + + if (missing.length === 0) { + console.log(c.green(' ✅ All detected dependencies have @types packages installed.\n')); + } else { + console.log(c.yellow(` ⚠️ Missing @types packages:\n`)); + missing.forEach(t => console.log(` npm install -D ${t}`)); + console.log(); + console.log(c.dim(' Install all at once:')); + console.log(` npm install -D ${missing.join(' ')}\n`); + } +} + +// ─── Makeshift tests ───────────────────────────────────────────────────────── + +function runTests() { + console.log(c.bold('\n═══ Makeshift Tests ═══\n')); + let passed = 0; let failed = 0; + + function assert(label, actual, expected) { + const ok = JSON.stringify(actual) === JSON.stringify(expected); + console.log(` ${ok ? '\x1b[32m✅ PASS\x1b[0m' : '\x1b[31m❌ FAIL\x1b[0m'} ${label}`); + console.log(c.dim(` Expected: ${JSON.stringify(expected)}`)); + if (!ok) console.log(c.dim(` Got: ${JSON.stringify(actual)}`)); + ok ? passed++ : failed++; + } + + // Test 1: buildScripts returns expected keys + const scripts = buildScripts(); + assert('buildScripts() includes "build" key', 'build' in scripts, true); + // Expected: true + + assert('buildScripts() includes "typecheck" key', 'typecheck' in scripts, true); + // Expected: true + + assert('buildScripts() includes "test" key', 'test' in scripts, true); + // Expected: true + + assert('buildScripts() includes "lint" key', 'lint' in scripts, true); + // Expected: true + + // Test 2: buildScripts excludes test when includeTest=false + const noTestScripts = buildScripts({ includeTest: false }); + assert('buildScripts({includeTest:false}) excludes "test"', 'test' in noTestScripts, false); + // Expected: false + + // Test 3: buildTsconfig returns strict: true + const tsconfig = buildTsconfig('node'); + assert('buildTsconfig("node").compilerOptions.strict', tsconfig.compilerOptions.strict, true); + // Expected: true + + assert('buildTsconfig("node").compilerOptions.target', tsconfig.compilerOptions.target, 'ES2022'); + // Expected: 'ES2022' + + // Test 4: buildTsconfig react adds jsx + const reactCfg = buildTsconfig('react'); + assert('buildTsconfig("react") sets jsx', reactCfg.compilerOptions.jsx, 'react-jsx'); + // Expected: 'react-jsx' + + // Test 5: suggestTypes maps express + const suggestions = suggestTypes(['express', 'lodash', 'unknown-pkg']); + assert('suggestTypes() maps express → @types/express', suggestions.includes('@types/express'), true); + // Expected: true + + assert('suggestTypes() maps lodash → @types/lodash', suggestions.includes('@types/lodash'), true); + // Expected: true + + assert('suggestTypes() ignores unknown packages', suggestions.length, 2); + // Expected: 2 (only express and lodash match) + + // Test 6: suggestTypes returns empty for no matches + const empty = suggestTypes(['some-internal-package']); + assert('suggestTypes() returns [] for no matches', empty, []); + // Expected: [] + + // Test 7: buildScripts validate command includes typecheck + assert('buildScripts() validate runs typecheck', scripts.validate.includes('typecheck'), true); + // Expected: true + + // Test 8: buildTsconfig node excludes DOM lib + assert('buildTsconfig("node") excludes DOM lib', tsconfig.compilerOptions.lib.includes('DOM'), false); + // Expected: false + + console.log(c.bold(`\n Tests: ${c.green(passed + ' passed')} ${failed > 0 ? '\x1b[31m' + failed + ' failed\x1b[0m' : c.dim('0 failed')}\n`)); + if (failed > 0) process.exit(1); +} + +// ─── Entry point ───────────────────────────────────────────────────────────── + +if (TEST) runTests(); +else if (SHOW_SCRIPTS) printScripts(); +else if (SHOW_TSCONFIG) printTsconfig(); +else if (SHOW_CHECKLIST) printChecklist(); +else if (AUDIT_TYPES) auditTypes(); +else printHelp(); diff --git a/skills/typescript-package-manager/scripts/npm-workflow.md b/skills/typescript-package-manager/scripts/npm-workflow.md new file mode 100644 index 000000000..1a7c5c6ea --- /dev/null +++ b/skills/typescript-package-manager/scripts/npm-workflow.md @@ -0,0 +1,692 @@ +# npm TypeScript Workflow + +A comprehensive guide for managing TypeScript projects with npm — from initialization to publishing, monorepos, and security. + +**Reference:** https://docs.npmjs.com/ + +--- + +## Initializing a TypeScript Project + +### 1. Create the Project + +```bash +mkdir my-ts-project && cd my-ts-project +npm init -y +``` + +### 2. Install TypeScript + +```bash +# Install TypeScript as a dev dependency +npm install --save-dev typescript + +# Install Node.js type definitions +npm install --save-dev @types/node + +# Optional: ts-node for running TypeScript directly +npm install --save-dev ts-node + +# Optional: tsx (faster ts-node alternative) +npm install --save-dev tsx +``` + +### 3. Initialize TypeScript Configuration + +```bash +npx tsc --init +``` + +--- + +## Essential `package.json` Scripts + +### Complete `package.json` Template + +```json +{ + "name": "my-ts-project", + "version": "1.0.0", + "description": "A TypeScript project", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "exports": { + ".": { + "import": "./dist/esm/index.js", + "require": "./dist/cjs/index.js", + "types": "./dist/index.d.ts" + } + }, + "files": [ + "dist/", + "!dist/**/*.test.*", + "!dist/**/*.spec.*" + ], + "scripts": { + "build": "tsc -p tsconfig.build.json", + "build:watch": "tsc -p tsconfig.build.json --watch", + "build:clean": "npm run clean && npm run build", + "dev": "tsx watch src/index.ts", + "start": "node dist/index.js", + "typecheck": "tsc --noEmit", + "typecheck:watch": "tsc --noEmit --watch", + "test": "jest", + "test:watch": "jest --watch", + "test:coverage": "jest --coverage", + "test:ci": "jest --ci --coverage --runInBand", + "lint": "eslint src --ext .ts,.tsx", + "lint:fix": "eslint src --ext .ts,.tsx --fix", + "format": "prettier --write \"src/**/*.{ts,tsx,json,md}\"", + "format:check": "prettier --check \"src/**/*.{ts,tsx,json,md}\"", + "clean": "rimraf dist", + "prepare": "npm run build", + "prepublishOnly": "npm run typecheck && npm run lint && npm run test && npm run build", + "version": "npm run format && git add -A src", + "postversion": "git push && git push --tags" + }, + "devDependencies": { + "@types/jest": "^29.0.0", + "@types/node": "^20.0.0", + "@typescript-eslint/eslint-plugin": "^7.0.0", + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.0.0", + "eslint-config-prettier": "^9.0.0", + "jest": "^29.0.0", + "prettier": "^3.0.0", + "rimraf": "^5.0.0", + "ts-jest": "^29.0.0", + "tsx": "^4.0.0", + "typescript": "^5.0.0" + }, + "engines": { + "node": ">=18.0.0" + } +} +``` + +### Script Reference + +| Script | Command | Purpose | +|--------|---------|---------| +| `build` | `tsc -p tsconfig.build.json` | Compile TypeScript to JavaScript | +| `build:watch` | `tsc -p tsconfig.build.json --watch` | Compile in watch mode | +| `build:clean` | `npm run clean && npm run build` | Clean then compile | +| `dev` | `tsx watch src/index.ts` | Run with live reload (no compile) | +| `start` | `node dist/index.js` | Run compiled output | +| `typecheck` | `tsc --noEmit` | Type-check without emitting files | +| `test` | `jest` | Run test suite | +| `test:coverage` | `jest --coverage` | Run tests with coverage report | +| `lint` | `eslint src --ext .ts` | Lint TypeScript files | +| `lint:fix` | `eslint src --ext .ts --fix` | Auto-fix lint issues | +| `format` | `prettier --write` | Format source files | +| `format:check` | `prettier --check` | Check formatting without changing files | +| `clean` | `rimraf dist` | Delete build output | +| `prepublishOnly` | full check pipeline | Run before `npm publish` | + +--- + +## Managing `@types/*` Packages + +### Installing Type Definitions + +```bash +# Node.js built-ins +npm install --save-dev @types/node + +# Popular libraries +npm install --save-dev @types/express +npm install --save-dev @types/lodash +npm install --save-dev @types/jest +npm install --save-dev @types/react @types/react-dom + +# Check if a package has built-in types first +# (look for "types" field in the package's package.json) +``` + +### Finding the Right `@types` Package + +```bash +# Search for type packages +npm search @types/library-name + +# Check if types are bundled +npm info library-name | grep types +``` + +### When Types Are Missing + +If no `@types/*` package exists, create a local declaration: + +```typescript +// src/types/missing-library.d.ts +declare module 'missing-library' { + export function doSomething(value: string): void; + export const version: string; +} +``` + +Reference it in `tsconfig.json`: + +```json +{ + "compilerOptions": { + "typeRoots": ["./node_modules/@types", "./src/types"] + } +} +``` + +### Type Resolution Strategies + +```json +{ + "compilerOptions": { + "types": ["node", "jest"], + "typeRoots": [ + "./node_modules/@types", + "./types" + ] + } +} +``` + +- `types`: Limit which `@types` packages are automatically included +- `typeRoots`: Directories to search for type packages + +--- + +## Publishing TypeScript Packages + +### 1. Separate Build `tsconfig` + +Keep a `tsconfig.build.json` that excludes tests: + +```json +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src", + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "stripInternal": true + }, + "include": ["src/**/*"], + "exclude": [ + "node_modules", + "dist", + "**/*.test.ts", + "**/*.spec.ts", + "**/__tests__/**" + ] +} +``` + +### 2. Exports Map (`package.json`) + +Support both CommonJS and ESM consumers: + +```json +{ + "name": "my-library", + "version": "1.0.0", + "main": "./dist/cjs/index.js", + "module": "./dist/esm/index.js", + "types": "./dist/types/index.d.ts", + "exports": { + ".": { + "import": { + "types": "./dist/types/index.d.ts", + "default": "./dist/esm/index.js" + }, + "require": { + "types": "./dist/types/index.d.ts", + "default": "./dist/cjs/index.js" + } + }, + "./utils": { + "import": "./dist/esm/utils.js", + "require": "./dist/cjs/utils.js", + "types": "./dist/types/utils.d.ts" + } + }, + "files": [ + "dist/", + "README.md", + "LICENSE" + ] +} +``` + +### 3. Dual Build Script + +```json +{ + "scripts": { + "build:cjs": "tsc -p tsconfig.cjs.json", + "build:esm": "tsc -p tsconfig.esm.json", + "build": "npm run build:cjs && npm run build:esm" + } +} +``` + +`tsconfig.cjs.json`: + +```json +{ + "extends": "./tsconfig.build.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "./dist/cjs" + } +} +``` + +`tsconfig.esm.json`: + +```json +{ + "extends": "./tsconfig.build.json", + "compilerOptions": { + "module": "esnext", + "outDir": "./dist/esm" + } +} +``` + +### 4. Verify Package Contents Before Publishing + +```bash +# Dry run to see what will be published +npm pack --dry-run + +# Pack to a tarball for local testing +npm pack +``` + +### 5. Publish + +```bash +# Login +npm login + +# Publish publicly +npm publish --access public + +# Publish beta version +npm version prerelease --preid=beta +npm publish --tag beta +``` + +--- + +## npm Workspaces for TypeScript Monorepos + +### Monorepo Structure + +``` +my-monorepo/ +├── package.json # Root workspace config +├── tsconfig.base.json # Shared TypeScript config +├── packages/ +│ ├── core/ +│ │ ├── package.json +│ │ ├── tsconfig.json +│ │ └── src/ +│ ├── utils/ +│ │ ├── package.json +│ │ ├── tsconfig.json +│ │ └── src/ +│ └── api/ +│ ├── package.json +│ ├── tsconfig.json +│ └── src/ +└── apps/ + └── web/ + ├── package.json + ├── tsconfig.json + └── src/ +``` + +### Root `package.json` + +```json +{ + "name": "my-monorepo", + "private": true, + "workspaces": [ + "packages/*", + "apps/*" + ], + "scripts": { + "build": "npm run build --workspaces --if-present", + "test": "npm run test --workspaces --if-present", + "typecheck": "npm run typecheck --workspaces --if-present", + "lint": "npm run lint --workspaces --if-present", + "clean": "npm run clean --workspaces --if-present && rimraf node_modules" + }, + "devDependencies": { + "typescript": "^5.0.0", + "rimraf": "^5.0.0" + } +} +``` + +### Shared `tsconfig.base.json` + +```json +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "composite": true + } +} +``` + +### Package `tsconfig.json` (extends base) + +```json +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src" + }, + "references": [ + { "path": "../utils" } + ], + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} +``` + +### Cross-Package Dependencies + +```json +{ + "name": "@my-monorepo/api", + "dependencies": { + "@my-monorepo/core": "*", + "@my-monorepo/utils": "*" + } +} +``` + +Install all workspace dependencies from root: + +```bash +npm install +``` + +Run commands in a specific workspace: + +```bash +npm run build --workspace=packages/core +npm run test --workspace=apps/web +``` + +### TypeScript Project References + +Use `tsc --build` for correct build ordering: + +```bash +# Build all packages in dependency order +npx tsc --build packages/core packages/utils packages/api +``` + +Root `tsconfig.json` for project references: + +```json +{ + "files": [], + "references": [ + { "path": "./packages/core" }, + { "path": "./packages/utils" }, + { "path": "./packages/api" }, + { "path": "./apps/web" } + ] +} +``` + +--- + +## Security: Audit and Lock Files + +### Running Security Audits + +```bash +# Check for vulnerabilities +npm audit + +# Auto-fix low/moderate issues +npm audit fix + +# Fix including breaking changes (review first) +npm audit fix --force + +# JSON output for CI +npm audit --json > audit-report.json +``` + +### Lock File Best Practices + +```bash +# Always commit package-lock.json +git add package-lock.json + +# Install exactly as specified in lock file (CI) +npm ci + +# Update lock file without upgrading major versions +npm update + +# Update a specific package +npm update typescript +``` + +### `.npmrc` for Security + +```ini +# Require exact versions in lock file +save-exact=true + +# Prevent installing packages with known vulnerabilities +audit=true + +# Use npm's official registry +registry=https://registry.npmjs.org/ +``` + +### Checking Outdated Packages + +```bash +# List outdated packages +npm outdated + +# Interactive update with npx +npx npm-check-updates + +# Update all to latest +npx npm-check-updates -u && npm install +``` + +--- + +## Common TypeScript-Specific npm Scripts + +### Complete Dev Toolchain Setup + +```bash +# ESLint + TypeScript +npm install --save-dev eslint @typescript-eslint/eslint-plugin @typescript-eslint/parser + +# Prettier +npm install --save-dev prettier eslint-config-prettier + +# Jest + TypeScript +npm install --save-dev jest ts-jest @types/jest + +# Husky + lint-staged (pre-commit hooks) +npm install --save-dev husky lint-staged +npx husky init +``` + +### `package.json` Scripts for Quality Gates + +```json +{ + "scripts": { + "validate": "npm run typecheck && npm run lint && npm run format:check && npm run test", + "ci": "npm run validate && npm run build" + }, + "lint-staged": { + "*.{ts,tsx}": [ + "eslint --fix", + "prettier --write" + ], + "*.{json,md}": [ + "prettier --write" + ] + } +} +``` + +### Jest Configuration for TypeScript + +`jest.config.ts`: + +```typescript +import type { Config } from 'jest'; + +const config: Config = { + preset: 'ts-jest', + testEnvironment: 'node', + roots: ['/src'], + testMatch: ['**/__tests__/**/*.ts', '**/*.test.ts', '**/*.spec.ts'], + transform: { + '^.+\\.tsx?$': ['ts-jest', { + tsconfig: 'tsconfig.json' + }] + }, + collectCoverageFrom: [ + 'src/**/*.{ts,tsx}', + '!src/**/*.d.ts', + '!src/**/__tests__/**' + ], + coverageThreshold: { + global: { + branches: 80, + functions: 80, + lines: 80, + statements: 80 + } + } +}; + +export default config; +``` + +### ESLint Configuration for TypeScript + +`eslint.config.js` (flat config): + +```javascript +import eslint from '@eslint/js'; +import tseslint from 'typescript-eslint'; + +export default tseslint.config( + eslint.configs.recommended, + ...tseslint.configs.recommendedTypeChecked, + { + languageOptions: { + parserOptions: { + project: true, + tsconfigRootDir: import.meta.dirname + } + }, + rules: { + '@typescript-eslint/no-explicit-any': 'warn', + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }] + } + } +); +``` + +### Prettier Configuration + +`.prettierrc`: + +```json +{ + "semi": true, + "singleQuote": true, + "trailingComma": "all", + "printWidth": 100, + "tabWidth": 2, + "arrowParens": "always" +} +``` + +--- + +## npm Version Management + +### Semantic Versioning Workflow + +```bash +# Bump patch version (1.0.0 -> 1.0.1) +npm version patch + +# Bump minor version (1.0.0 -> 1.1.0) +npm version minor + +# Bump major version (1.0.0 -> 2.0.0) +npm version major + +# Pre-release versions +npm version prerelease --preid=alpha # 1.0.0-alpha.0 +npm version prerelease --preid=beta # 1.0.0-beta.0 +npm version prerelease --preid=rc # 1.0.0-rc.0 +``` + +### Version Lifecycle Scripts + +```json +{ + "scripts": { + "preversion": "npm run validate", + "version": "npm run build && git add -A dist", + "postversion": "git push && git push --tags && npm publish" + } +} +``` + +--- + +## Quick Reference + +| Task | Command | +|------|---------| +| Install TypeScript | `npm install --save-dev typescript` | +| Install types | `npm install --save-dev @types/package-name` | +| Initialize tsconfig | `npx tsc --init` | +| Type-check only | `npx tsc --noEmit` | +| Build | `npx tsc` | +| Run TS directly | `npx tsx src/index.ts` | +| Clean install (CI) | `npm ci` | +| Audit vulnerabilities | `npm audit` | +| Check outdated | `npm outdated` | +| Pack for review | `npm pack --dry-run` | +| Publish | `npm publish` | +| Workspace run | `npm run build --workspace=packages/core` | diff --git a/skills/typescript-package-manager/scripts/pnpm-workflow.md b/skills/typescript-package-manager/scripts/pnpm-workflow.md new file mode 100644 index 000000000..f7a941996 --- /dev/null +++ b/skills/typescript-package-manager/scripts/pnpm-workflow.md @@ -0,0 +1,699 @@ +# pnpm TypeScript Workflow + +A comprehensive guide for managing TypeScript projects with pnpm — the fast, disk space efficient package manager with strict dependency isolation. + +**Reference:** https://pnpm.io/ + +--- + +## Why pnpm? + +### Key Advantages + +- **60-70% Disk Space Savings**: Content-addressable store with hard links +- **2-3x Faster**: Optimized installation and caching +- **Strict Dependencies**: Prevents accessing undeclared dependencies +- **Native Monorepo Support**: Excellent workspace features +- **Drop-in Replacement**: Compatible with npm commands + +### When to Use pnpm + +✅ **Perfect for:** +- Monorepos and multi-package projects +- Teams with many Node.js projects +- CI/CD optimization +- Strict dependency management +- Disk space constrained environments + +⚠️ **Consider alternatives if:** +- Maximum ecosystem compatibility is critical +- Team unfamiliar with pnpm +- Legacy tooling incompatibilities + +--- + +## Installation + +### Installing pnpm + +```bash +# Using npm +npm install -g pnpm + +# Using Corepack (Node.js 16.9+, recommended) +corepack enable +corepack prepare pnpm@latest --activate + +# Using standalone script (Windows) +iwr https://get.pnpm.io/install.ps1 -useb | iex + +# Using standalone script (macOS/Linux) +curl -fsSL https://get.pnpm.io/install.sh | sh - +``` + +### Verify Installation + +```bash +pnpm --version +pnpm -v +``` + +--- + +## Initializing a TypeScript Project + +### 1. Create the Project + +```bash +mkdir my-ts-project && cd my-ts-project +pnpm init +``` + +### 2. Install TypeScript + +```bash +# Install TypeScript and type definitions +pnpm add -D typescript @types/node + +# Optional: ts-node for running TypeScript +pnpm add -D ts-node + +# Optional: tsx (recommended, faster) +pnpm add -D tsx + +# Optional: tsup for bundling +pnpm add -D tsup +``` + +### 3. Initialize TypeScript Configuration + +```bash +pnpm exec tsc --init +``` + +--- + +## Essential package.json Configuration + +```json +{ + "name": "my-ts-project", + "version": "1.0.0", + "type": "module", + "packageManager": "pnpm@8.15.0", + "scripts": { + "dev": "tsx watch src/index.ts", + "build": "tsc", + "build:bundle": "tsup src/index.ts --format esm,cjs --dts", + "start": "node dist/index.js", + "test": "vitest run", + "test:watch": "vitest", + "type-check": "tsc --noEmit", + "lint": "eslint src --ext .ts", + "format": "prettier --write src/**/*.ts", + "clean": "rimraf dist node_modules" + }, + "devDependencies": { + "@types/node": "^20.11.0", + "tsx": "^4.7.0", + "typescript": "^5.3.3" + } +} +``` + +--- + +## pnpm Commands Cheat Sheet + +### Installation + +```bash +# Install all dependencies +pnpm install +pnpm i + +# Install from fresh (like npm ci) +pnpm install --frozen-lockfile + +# Install without optional deps +pnpm install --no-optional + +# Force reinstall +pnpm install --force +``` + +### Adding Dependencies + +```bash +# Add production dependency +pnpm add +pnpm add express zod + +# Add dev dependency +pnpm add -D +pnpm add --save-dev typescript + +# Add peer dependency +pnpm add -P +pnpm add --save-peer react + +# Add optional dependency +pnpm add -O +pnpm add --save-optional fsevents + +# Add exact version +pnpm add -E +pnpm add --save-exact react@18.2.0 + +# Add to specific workspace +pnpm add --filter +``` + +### Removing Dependencies + +```bash +# Remove package +pnpm remove +pnpm rm express + +# Remove from all workspaces +pnpm -r remove +``` + +### Updating Dependencies + +```bash +# Update all to latest within ranges +pnpm update +pnpm up + +# Update specific package +pnpm update + +# Update to latest (ignore ranges) +pnpm update --latest +pnpm up -L + +# Interactive update +pnpm update --interactive +pnpm up -i + +# Update all workspaces +pnpm -r update +``` + +### Running Scripts + +```bash +# Run package script +pnpm +pnpm dev +pnpm test + +# Explicit run command +pnpm run + +# Run in all workspaces +pnpm -r +pnpm --recursive test + +# Run in specific workspace +pnpm --filter