🎯 Ejemplos recomendados
Balanced sample collections from various categories for you to explore
Ejemplos de pnpm Package Manager
Ejemplos de gestor de paquetes rápido y eficiente en espacio en disco incluyendo monorepo, configuración de workspace, y flujos de trabajo avanzados
💻 Configuración Básica de pnpm json
🟢 simple
⭐⭐
Configuración completa de pnpm para proyectos JavaScript/TypeScript con configuración workspace y mejores prácticas
⏱️ 20 min
🏷️ pnpm, configuration, setup
Prerequisites:
Node.js, Basic package management concepts
// pnpm Basic Setup and Configuration
// 1. Installation
// npm install -g pnpm
// or use corepack: corepack enable && corepack prepare pnpm@latest --activate
// 2. Basic package.json
{
"name": "my-pnpm-project",
"version": "1.0.0",
"description": "A project managed with pnpm",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"test": "vitest",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"format": "prettier --write .",
"clean": "rm -rf node_modules dist",
"reinstall": "pnpm clean && pnpm install"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"@vitejs/plugin-react": "^4.0.0",
"eslint": "^8.45.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.0",
"prettier": "^3.0.0",
"typescript": "^5.0.2",
"vite": "^5.0.0",
"vitest": "^0.34.0"
},
"engines": {
"node": ">=18.0.0",
"pnpm": ">=8.0.0"
},
"packageManager": "[email protected]"
}
// 3. .npmrc - pnpm configuration
# Store directory for packages (shared across projects)
store-dir=~/.pnpm-store
# Shamefully hoist packages to node_modules root
shamefully-hoist=false
# Preferred workspace version strategy
prefer-workspace-packages=true
# Link dependencies from workspace
link-workspace-packages=true
# Registry configuration
registry=https://registry.npmjs.org
# Save exact versions
save-exact=false
# Save production dependencies
save-prod=true
# Auto install peer dependencies
auto-install-peers=true
# Resolve peer dependencies from workspace
resolve-peers-from-workspace-root=true
# Strict peer dependencies
strict-peer-dependencies=true
# Use symlinks for dependencies
symlink=true
# Use dependencies on the highest package version
resolution-mode=highest
# Lock file version
lockfile-version=6
# Extend npmrc files
@company:registry=https://npm.company.com
//npm.company.com/:_authToken=${NPM_TOKEN}
# Cache directory
cache-dir=~/.pnpm-cache
# Maximum number of concurrent requests
max-sockets=100
# Network timeout
network-timeout=60000
# Keep original case in node_modules
preserve-symlinks=true
# Never install optional dependencies
optional=false
# Enforce strict dependencies
strict-dependencies=true
# Use git branch for npm packages
git-branch-dependency-url=false
# Prefer offline mode
prefer-offline=false
# Force offline mode
offline=false
# Fix permissions
fix-problems=true
# Check for outdated dependencies
update-notifier=true
# Commit hook for package changes
git-commit-hook=true
# Extend config with additional files
config=./config/.npmrc
# Use 'x' style locked dependencies
use-running-installer=false
# Extended lockfile format
extended-lockfile=true
# Enable deduplication of dependencies
dedupe-peer-dependents=true
# Enable inject mode (for testing)
inject=false
# Enable shamefully hoisting
shamely-hoist=true
# Install dev dependencies for root packages
include-workspace-root=true
# Node linking
node-linker=isolated
# Prefer symlink packages
prefer-symlinked-executables=true
# Use shim in node_modules
node-modules-dir=true
// 4. .pnpmfile.cjs - JavaScript configuration file
function readPackage(pkg, context) {
// Override package properties
if (pkg.name === 'some-package') {
pkg.dependencies = {
...pkg.dependencies,
'react': '^18.2.0'
};
}
// Remove certain dependencies
if (pkg.dependencies && pkg.dependencies.unwanted-depd) {
delete pkg.dependencies.unwanted_depd;
}
// Add dev dependencies
if (pkg.name === 'development-package') {
pkg.devDependencies = {
...pkg.devDependencies,
'vitest': '^0.34.0'
};
}
return pkg;
}
module.exports = {
hooks: {
readPackage
}
};
// 5. pnpm-workspace.yaml - Workspace configuration
packages:
- 'apps/*'
- 'packages/*'
- 'tools/*'
# Alternatively, with specific package patterns:
packages:
- 'packages/*'
- '!packages/**/test/**'
# With custom catalog configuration:
packages:
- 'packages/*'
catalog:
'react': '^18.2.0'
'react-dom': '^18.2.0'
'typescript': '^5.0.0'
'vite': '^5.0.0'
// 6. Basic commands and usage
/*
# Install all dependencies
pnpm install
# Install specific package
pnpm add react
# Add dev dependency
pnpm add -D vitest
# Add exact version
pnpm add [email protected] -E
# Install from git
pnpm add https://github.com/user/repo.git
# Install from local directory
pnpm add ./local-package
# Update packages
pnpm update
# Update specific package
pnpm update react
# Update to latest versions
pnpm update --latest
# Remove package
pnpm remove react
# List outdated packages
pnpm outdated
# List dependencies
pnpm list
# List dependencies in tree format
pnpm list --depth=0
# List global packages
pnpm list -g
# Run script
pnpm run dev
# Run script with arguments
pnpm run build -- --analyze
# Clean node_modules
pnpm clean
# Prune unused dependencies
pnpm prune
# Check for outdated
pnpm outdated
# Audit packages
pnpm audit
# Fix vulnerabilities
pnpm audit --fix
# Generate lockfile
pnpm install --lockfile-only
# Install from lockfile only
pnpm install --frozen-lockfile
# Create project
pnpm create vite my-project
# Patch package
pnpm patch [email protected]
# List patches
pnpm patch-list
# Publish package
pnpm publish
# Run script in specific workspace
pnpm --filter @my-org/my-app run dev
# Run script in dependent workspaces
pnpm --filter "@my-org/*" test
# Install only production dependencies
pnpm install --prod
# Create development environment
pnpm dlx create-next-app
# Execute command with different Node.js version
pnpm exec node --version
# Validate dependencies
pnpm validate
# Check if packages have duplicate dependencies
pnpm list --dedupe
# Deduplicate dependencies
pnpm dedupe
*/
// 7. Environment variables configuration
/*
# pnpm config keys
NPM_CONFIG_REGISTRY=https://registry.npmjs.org
NPM_CONFIG_CACHE=~/.npm-cache
NPM_CONFIG_USER_AGENT=pnpm/8.15.0
# Authentication
NPM_TOKEN=your-npm-token
# Custom registries
NPM_CONFIG_@COMPANY_REGISTRY=https://npm.company.com
# Proxy settings
HTTPS_PROXY=http://proxy.company.com:8080
HTTP_PROXY=http://proxy.company.com:8080
# pnpm specific
PNPM_HOME=/path/to/pnpm
PNPM_STORE_DIR=~/.pnpm-store
PNPM_CACHE_DIR=~/.pnpm-cache
# Disable prompts
PNPM_NO_UPDATE_NOTIFIER=true
PNPM_NO_PEER_DEPENDENCIES_CHECK=false
# Auto-install dependencies
PNPM_AUTO_INSTALL_PEERS=true
# Git configuration
GIT_ASKPASS=false
// 8. TypeScript configuration for monorepo
// tsconfig.json (root)
{
"compilerOptions": {
"target": "ES2020",
"lib": ["ES2020"],
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"allowJs": true,
"strict": true,
"noEmit": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"incremental": true,
"jsx": "react-jsx",
"paths": {
"@app/*": ["apps/*/src/*"],
"@pkg/*": ["packages/*/src/*"],
"@utils/*": ["packages/utils/src/*"],
"@types/*": ["packages/types/src/*"]
}
},
"include": [
"packages/*/src/**/*",
"apps/*/src/**/*"
],
"exclude": [
"node_modules",
"dist",
"build"
],
"references": [
{ "path": "./packages/utils" },
{ "path": "./packages/types" },
{ "path": "./packages/ui" },
{ "path": "./apps/web" }
]
}
// 9. Lint-staged configuration for pnpm projects
// .lintstagedrc.json
{
"*.{js,jsx,ts,tsx}": [
"eslint --fix",
"prettier --write"
],
"*.{json,md,yml,yaml}": [
"prettier --write"
],
"*.{css,scss,less}": [
"stylelint --fix",
"prettier --write"
],
"package.json": [
"pnpm sort-package-json",
"prettier --write"
],
"pnpm-lock.yaml": [
"pnpm dedupe"
]
}
// 10. Husky hooks setup
// .husky/pre-commit
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx lint-staged
# Check for uncommitted pnpm changes
if ! git diff --quiet HEAD~1 HEAD pnpm-lock.yaml; then
echo "pnpm-lock.yaml has changed. Please run 'pnpm install' and commit the new lock file."
exit 1
fi
// .husky/commit-msg
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx --no -- commitlint --edit "${1}"
// .husky/pre-push
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
pnpm test
pnpm build
echo "All checks passed! 🚀"
💻 Monorepo y Workspaces de pnpm yaml
🟡 intermediate
⭐⭐⭐⭐
Configuración avanzada de monorepo con workspaces pnpm, gestión de dependencias, y coordinación multi-paquete
⏱️ 35 min
🏷️ pnpm, monorepo, workspaces
Prerequisites:
pnpm basics, Monorepo concepts, Package management
// pnpm Monorepo and Workspace Management
// 1. pnpm-workspace.yaml - Complete workspace configuration
packages:
# Application packages
- 'apps/*'
# Shared packages
- 'packages/*'
# Documentation
- 'docs'
# Development tools
- 'tools/*'
# Excluding test directories
- '!packages/**/test/**'
- '!apps/**/__tests__/**'
# Shared dependencies catalog
catalog:
# Core dependencies
'react': '^18.2.0'
'react-dom': '^18.2.0'
'typescript': '^5.0.0'
# Build tools
'vite': '^5.0.0'
'esbuild': '^0.19.0'
'rollup': '^4.0.0'
# Testing
'vitest': '^0.34.0'
'jest': '^29.7.0'
'playwright': '^1.40.0'
# Style tools
'tailwindcss': '^3.3.0'
'postcss': '^8.4.0'
'sass': '^1.69.0'
# Utilities
'lodash-es': '^4.17.0'
'date-fns': '^2.30.0'
'clsx': '^2.0.0'
// 2. Root package.json with workspace scripts
{
"name": "@company/monorepo",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
// Development workflows
"dev": "concurrently "pnpm --filter @company/web dev" "pnpm --filter @company/api dev"",
"dev:web": "pnpm --filter @company/web dev",
"dev:api": "pnpm --filter @company/api dev",
"dev:docs": "pnpm --filter docs dev",
// Building
"build": "pnpm -r build",
"build:affected": "pnpm --filter "...[origin/main]" build",
"build:packages": "pnpm --filter "./packages/*" build",
"build:apps": "pnpm --filter "./apps/*" build",
// Testing
"test": "pnpm -r test",
"test:affected": "pnpm --filter "...[origin/main]" test",
"test:coverage": "pnpm -r test --coverage",
"test:e2e": "pnpm --filter @company/web test:e2e",
// Linting and formatting
"lint": "pnpm -r lint",
"lint:fix": "pnpm -r lint:fix",
"format": "prettier --write "**/*.{ts,tsx,js,jsx,json,md,yml,yaml}"",
"format:check": "prettier --check "**/*.{ts,tsx,js,jsx,json,md,yml,yaml}"",
// Package management
"clean": "pnpm -r clean && rm -rf node_modules",
"reinstall": "pnpm clean && pnpm install",
"update": "pnpm update --latest",
"outdated": "pnpm -r outdated",
"audit": "pnpm audit",
"audit:fix": "pnpm audit --fix",
// Dependency management
"dedupe": "pnpm dedupe",
"validate": "pnpm -r validate",
// Release workflow
"changeset": "changeset",
"version": "changeset version",
"release": "pnpm build && pnpm changeset publish",
"release:snapshot": "pnpm build && pnpm changeset publish --tag snapshot",
// Database
"db:generate": "pnpm --filter @company/api db:generate",
"db:migrate": "pnpm --filter @company/api db:migrate",
"db:seed": "pnpm --filter @company/api db:seed",
// Docker
"docker:build": "pnpm -r docker:build",
"docker:push": "pnpm -r docker:push",
// Storybook
"storybook": "pnpm --filter @company/ui storybook",
"build-storybook": "pnpm --filter @company/ui build-storybook",
// Bundle analysis
"analyze": "pnpm --filter @company/web analyze",
"size-limit": "pnpm -r size-limit"
},
"devDependencies": {
"@changesets/cli": "^2.27.0",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"concurrently": "^8.2.0",
"eslint": "^8.45.0",
"prettier": "^3.0.0",
"size-limit": "^11.0.0",
"typescript": "^5.0.0"
},
"engines": {
"node": ">=18.0.0",
"pnpm": ">=8.0.0"
},
"packageManager": "[email protected]"
}
// 3. App package example (apps/web/package.json)
{
"name": "@company/web",
"version": "1.0.0",
"type": "module",
"private": true,
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"test": "vitest",
"test:e2e": "playwright test",
"lint": "eslint src --max-warnings 0",
"lint:fix": "eslint src --fix",
"analyze": "vite-bundle-analyzer dist/static/js/*.js",
"docker:build": "docker build -t company/web .",
"docker:push": "docker push company/web"
},
"dependencies": {
"@company/ui": "workspace:*",
"@company/utils": "workspace:*",
"@company/types": "workspace:*",
"react": "catalog:",
"react-dom": "catalog:",
"react-router-dom": "^6.8.0",
"zustand": "^4.4.0",
"axios": "^1.6.0"
},
"devDependencies": {
"@types/react": "catalog:",
"@types/react-dom": "catalog:",
"@vitejs/plugin-react": "^4.0.0",
"vite": "catalog:",
"vitest": "catalog:",
"@playwright/test": "^1.40.0",
"vite-bundle-analyzer": "^0.7.0"
}
}
// 4. Shared package example (packages/ui/package.json)
{
"name": "@company/ui",
"version": "1.0.0",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.js",
"types": "./dist/index.d.ts"
},
"./styles.css": "./dist/styles.css"
},
"files": [
"dist",
"README.md"
],
"scripts": {
"build": "vite build",
"dev": "vite build --watch",
"test": "vitest",
"lint": "eslint src --max-warnings 0",
"lint:fix": "eslint src --fix",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build",
"size-limit": "size-limit",
"clean": "rm -rf dist"
},
"dependencies": {
"@company/types": "workspace:*",
"@company/utils": "workspace:*",
"clsx": "catalog:",
"react": "catalog:",
"react-dom": "catalog:"
},
"devDependencies": {
"@storybook/addon-essentials": "^7.5.0",
"@storybook/react": "^7.5.0",
"@storybook/react-vite": "^7.5.0",
"@types/react": "catalog:",
"@types/react-dom": "catalog:",
"size-limit": "^11.0.0",
"tailwindcss": "catalog:",
"vite": "catalog:",
"vite-plugin-dts": "^3.6.0",
"vitest": "catalog:"
},
"peerDependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0"
},
"publishConfig": {
"access": "restricted"
}
}
// 5. .pnpmfile.cjs with workspace utilities
const path = require('path');
function readPackage(pkg, context) {
// Add common dev dependencies to all packages
if (pkg.devDependencies) {
pkg.devDependencies = {
'eslint': '^8.45.0',
'prettier': '^3.0.0',
'typescript': '^5.0.0',
...pkg.devDependencies
};
}
// Use workspace packages when available
if (pkg.dependencies) {
Object.keys(pkg.dependencies).forEach(dep => {
if (dep.startsWith('@company/')) {
pkg.dependencies[dep] = 'workspace:*';
}
});
}
// Remove dev dependencies from published packages
if (pkg.private !== true && pkg.devDependencies) {
delete pkg.devDependencies.eslint;
delete pkg.devDependencies.prettier;
}
return pkg;
}
module.exports = {
hooks: {
readPackage,
// Hook for package validation
validatePackage: (packageName, packagePath) => {
const pkg = require(path.join(packagePath, 'package.json'));
// Ensure private packages have proper naming
if (pkg.private && !packageName.startsWith('@company/')) {
console.warn(`Warning: Private package ${packageName} should use @company/ namespace`);
}
// Validate scripts
if (pkg.scripts) {
const requiredScripts = ['lint', 'build'];
requiredScripts.forEach(script => {
if (!pkg.scripts[script] && !pkg.private) {
console.warn(`Warning: Package ${packageName} missing required script: ${script}`);
}
});
}
}
}
};
// 6. Multi-environment pnpm-workspace files
// pnpm-workspace.yaml (development)
packages:
- 'apps/*'
- 'packages/*'
- 'tools/*'
catalog:
# Use latest versions in development
'react': '^18.2.0'
'typescript': '^5.1.0'
'vite': '^5.0.0'
// pnpm-workspace.yaml (production)
packages:
- 'apps/*'
- 'packages/*'
catalog:
# Lock versions in production
'react': '18.2.0'
'typescript': '5.0.0'
'vite': '5.0.0'
// 7. Automated dependency management scripts
// scripts/update-dependencies.js
const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');
async function updateDependencies() {
console.log('🔄 Updating dependencies...');
// Update workspace dependencies
execSync('pnpm update --latest --recursive', { stdio: 'inherit' });
// Update root dependencies
execSync('pnpm update --latest', { stdio: 'inherit' });
// Check for outdated packages
console.log('\n📋 Checking for outdated packages...');
execSync('pnpm outdated', { stdio: 'inherit' });
// Deduplicate dependencies
console.log('\n🔧 Deduplicating dependencies...');
execSync('pnpm dedupe', { stdio: 'inherit' });
// Validate workspace
console.log('\n✅ Validating workspace...');
execSync('pnpm -r validate', { stdio: 'inherit' });
console.log('\n🚀 Dependency update completed!');
}
updateDependencies().catch(console.error);
// scripts/check-dependencies.js
const { execSync } = require('child_process');
function checkDependencies() {
console.log('🔍 Checking dependency health...');
// Check for security vulnerabilities
console.log('\n🛡️ Security audit...');
try {
execSync('pnpm audit', { stdio: 'inherit' });
} catch (error) {
console.error('❌ Security vulnerabilities found!');
process.exit(1);
}
// Check for outdated packages
console.log('\n📦 Outdated packages...');
const outdated = execSync('pnpm outdated --json').toString();
const outdatedPackages = JSON.parse(outdated);
if (Object.keys(outdatedPackages).length > 0) {
console.warn('⚠️ Outdated packages found:');
Object.entries(outdatedPackages).forEach(([name, info]) => {
console.log(` ${name}: ${info.current} → ${info.latest}`);
});
}
// Check for duplicate dependencies
console.log('\n🔄 Checking for duplicates...');
try {
execSync('pnpm list --dedupe', { stdio: 'inherit' });
} catch (error) {
console.error('❌ Duplicate dependencies found!');
process.exit(1);
}
console.log('\n✅ All dependency checks passed!');
}
checkDependencies();
// 8. GitHub Actions workflow for monorepo
// .github/workflows/monorepo.yml
name: Monorepo CI/CD
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
changes:
runs-on: ubuntu-latest
outputs:
apps: ${{ steps.changes.outputs.apps }}
packages: ${{ steps.changes.outputs.packages }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v2
id: changes
with:
filters: |
apps:
- 'apps/**'
packages:
- 'packages/**'
root:
- 'pnpm-lock.yaml'
- '.github/workflows/**'
install:
runs-on: ubuntu-latest
needs: changes
if: needs.changes.outputs.apps == 'true' || needs.changes.outputs.packages == 'true' || needs.changes.outputs.root == 'true'
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'
- name: Install pnpm
uses: pnpm/action-setup@v2
with:
version: 8.15.0
- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- name: Setup pnpm cache
uses: actions/cache@v3
with:
path: ${{ env.STORE_PATH }}
key: pnpm-store-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
pnpm-store-${{ runner.os }}-
- name: Install dependencies
run: pnpm install --frozen-lockfile
test:
runs-on: ubuntu-latest
needs: [changes, install]
if: needs.changes.outputs.apps == 'true' || needs.changes.outputs.packages == 'true'
strategy:
matrix:
workspace: [${{ needs.changes.outputs.apps == 'true' && 'apps' || '' }}, ${{ needs.changes.outputs.packages == 'true' && 'packages' || '' }}]
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
with:
version: 8.15.0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Run tests
run: pnpm --filter "./${{ matrix.workspace }}/*" test
build:
runs-on: ubuntu-latest
needs: [changes, install]
if: needs.changes.outputs.apps == 'true' || needs.changes.outputs.packages == 'true'
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
with:
version: 8.15.0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build packages
run: pnpm -r build
- name: Upload build artifacts
uses: actions/upload-artifact@v3
with:
name: build-${{ github.sha }}
path: |
apps/*/dist
packages/*/dist
// 9. Docker Compose for monorepo development
// docker-compose.dev.yml
version: '3.8'
services:
# PostgreSQL database
postgres:
image: postgres:15
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: company_db
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
# Redis cache
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
# API service
api:
build:
context: .
dockerfile: apps/api/Dockerfile.dev
ports:
- "3001:3001"
environment:
NODE_ENV: development
DATABASE_URL: postgresql://postgres:postgres@postgres:5432/company_db
REDIS_URL: redis://redis:6379
depends_on:
- postgres
- redis
volumes:
- ./apps/api:/app/apps/api
- ./packages:/app/packages
- /app/node_modules
command: pnpm dev
# Web service
web:
build:
context: .
dockerfile: apps/web/Dockerfile.dev
ports:
- "3000:3000"
environment:
NODE_ENV: development
VITE_API_URL: http://localhost:3001
depends_on:
- api
volumes:
- ./apps/web:/app/apps/web
- ./packages:/app/packages
- /app/node_modules
command: pnpm dev
volumes:
postgres_data:
redis_data:
💻 Workflows Avanzados y Automatización de pnpm javascript
🔴 complex
⭐⭐⭐⭐⭐
Workflows avanzados de pnpm incluyendo automatización, publicación, seguridad, y optimización de rendimiento
⏱️ 50 min
🏷️ pnpm, automation, advanced, workflows
Prerequisites:
Advanced pnpm, Node.js, CLI tools, CI/CD
// pnpm Advanced Workflows and Automation
// 1. Automated Publishing Pipeline
// scripts/publish-packages.js
const { execSync } = require('child_process');
const path = require('path');
const fs = require('fs');
const semver = require('semver');
class PackagePublisher {
constructor() {
this.packages = this.getPackages();
this.publishedPackages = new Set();
}
getPackages() {
const packages = [];
const packagesDir = path.resolve('packages');
if (!fs.existsSync(packagesDir)) {
return packages;
}
const dirs = fs.readdirSync(packagesDir);
dirs.forEach(dir => {
const packageJsonPath = path.join(packagesDir, dir, 'package.json');
if (fs.existsSync(packageJsonPath)) {
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
if (!packageJson.private) {
packages.push({
name: packageJson.name,
path: path.join(packagesDir, dir),
version: packageJson.version,
packageJson
});
}
}
});
return packages;
}
async checkForUpdates() {
console.log('🔍 Checking for package updates...');
for (const pkg of this.packages) {
try {
const result = execSync(`npm view ${pkg.name} version`, { encoding: 'utf8' }).trim();
const latestVersion = semver.clean(result);
const currentVersion = semver.clean(pkg.version);
if (semver.gt(latestVersion, currentVersion)) {
console.log(`⬆️ ${pkg.name}: ${currentVersion} → ${latestVersion}`);
pkg.needsUpdate = true;
pkg.latestVersion = latestVersion;
} else {
pkg.needsUpdate = false;
}
} catch (error) {
console.log(`📦 ${pkg.name}: Not published yet`);
pkg.needsUpdate = false;
pkg.needsPublish = true;
}
}
}
async updateVersions() {
console.log('\n📝 Updating package versions...');
const packagesToUpdate = this.packages.filter(pkg => pkg.needsUpdate);
if (packagesToUpdate.length === 0) {
console.log('✅ All packages are up to date');
return;
}
for (const pkg of packagesToUpdate) {
// Update package.json
pkg.packageJson.version = pkg.latestVersion;
fs.writeFileSync(
path.join(pkg.path, 'package.json'),
JSON.stringify(pkg.packageJson, null, 2) + '\n'
);
console.log(`✅ Updated ${pkg.name} to ${pkg.latestVersion}`);
}
// Update workspace lockfile
console.log('\n🔐 Updating pnpm-lock.yaml...');
execSync('pnpm install', { stdio: 'inherit' });
}
async buildPackages() {
console.log('\n🏗️ Building packages...');
for (const pkg of this.packages) {
const packageDir = pkg.path;
if (fs.existsSync(path.join(packageDir, 'package.json'))) {
process.chdir(packageDir);
try {
console.log(`Building ${pkg.name}...`);
execSync('pnpm build', { stdio: 'inherit' });
} catch (error) {
console.error(`❌ Failed to build ${pkg.name}`);
throw error;
}
}
}
process.chdir(path.resolve('../../'));
}
async publishPackages(dryRun = false) {
console.log(`${dryRun ? '🔍' : '🚀'} ${dryRun ? 'Simulating' : 'Publishing'} packages...\n`);
// Sort packages by dependency order
const sortedPackages = this.topologicalSort(this.packages);
for (const pkg of sortedPackages) {
try {
process.chdir(pkg.path);
const command = dryRun
? 'pnpm publish --dry-run --no-git-checks'
: 'pnpm publish --no-git-checks';
console.log(`${dryRun ? '📦 Would publish' : '📦 Publishing'} ${pkg.name}@${pkg.version}`);
if (!dryRun) {
execSync(command, { stdio: 'inherit' });
this.publishedPackages.add(pkg.name);
}
} catch (error) {
console.error(`❌ Failed to publish ${pkg.name}`);
throw error;
}
}
process.chdir(path.resolve('../../'));
if (dryRun) {
console.log('\n✅ Dry run completed successfully!');
} else {
console.log(`\n✅ Published ${this.publishedPackages.size} packages successfully!`);
}
}
topologicalSort(packages) {
const sorted = [];
const visited = new Set();
const visiting = new Set();
const visit = (pkg) => {
if (visiting.has(pkg.name)) {
throw new Error(`Circular dependency detected: ${pkg.name}`);
}
if (visited.has(pkg.name)) {
return;
}
visiting.add(pkg.name);
// Visit dependencies
const deps = [
...Object.keys(pkg.packageJson.dependencies || {}),
...Object.keys(pkg.packageJson.peerDependencies || {})
].filter(dep => dep.startsWith('@company/'));
deps.forEach(dep => {
const depPkg = packages.find(p => p.name === dep);
if (depPkg) {
visit(depPkg);
}
});
visiting.delete(pkg.name);
visited.add(pkg.name);
sorted.push(pkg);
};
packages.forEach(visit);
return sorted;
}
async run(options = {}) {
try {
console.log('🚀 Starting automated publishing pipeline...\n');
await this.checkForUpdates();
if (options.update) {
await this.updateVersions();
}
await this.buildPackages();
await this.publishPackages(options.dryRun);
} catch (error) {
console.error('💥 Publishing pipeline failed:', error);
process.exit(1);
}
}
}
// CLI usage
const args = process.argv.slice(2);
const options = {
update: args.includes('--update'),
dryRun: args.includes('--dry-run'),
help: args.includes('--help')
};
if (options.help) {
console.log(`
Usage: node scripts/publish-packages.js [options]
Options:
--update Update package versions to latest
--dry-run Simulate publishing without actually publishing
--help Show this help message
`);
process.exit(0);
}
const publisher = new PackagePublisher();
publisher.run(options);
// 2. Security Audit and Vulnerability Scanner
// scripts/security-audit.js
const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');
class SecurityAuditor {
constructor() {
this.vulnerabilities = [];
this.licenseIssues = [];
}
async auditPackages() {
console.log('🔒 Auditing packages for vulnerabilities...');
try {
const auditOutput = execSync('pnpm audit --json', { encoding: 'utf8' });
const auditData = JSON.parse(auditOutput);
if (auditData.vulnerabilities) {
Object.entries(auditData.vulnerabilities).forEach(([name, vuln]) => {
this.vulnerabilities.push({
name,
severity: vuln.severity,
type: vuln.type,
url: vuln.url,
fixAvailable: vuln.fixAvailable
});
});
}
if (this.vulnerabilities.length > 0) {
console.log(`\n⚠️ Found ${this.vulnerabilities.length} vulnerabilities:`);
this.vulnerabilities.forEach(vuln => {
console.log(` ${vuln.name}: ${vuln.severity} - ${vuln.type}`);
if (vuln.fixAvailable) {
console.log(` Fix: ${vuln.fixAvailable}`);
}
});
} else {
console.log('✅ No vulnerabilities found');
}
} catch (error) {
console.error('❌ Audit failed:', error);
throw error;
}
}
async checkLicenses() {
console.log('\n📄 Checking package licenses...');
try {
const licensesOutput = execSync('pnpm licenses list --json', { encoding: 'utf8' });
const licenses = JSON.parse(licensesOutput);
const disallowedLicenses = ['GPL', 'AGPL', 'LGPL'];
Object.entries(licenses).forEach(([name, info]) => {
if (disallowedLicenses.some(license => info.license?.includes(license))) {
this.licenseIssues.push({
name,
license: info.license,
version: info.version
});
}
});
if (this.licenseIssues.length > 0) {
console.log(`⚠️ Found ${this.licenseIssues.length} license issues:`);
this.licenseIssues.forEach(issue => {
console.log(` ${issue.name}: ${issue.license}`);
});
} else {
console.log('✅ All licenses are compliant');
}
} catch (error) {
console.error('❌ License check failed:', error);
throw error;
}
}
async generateReport() {
console.log('\n📊 Generating security report...');
const report = {
timestamp: new Date().toISOString(),
vulnerabilities: this.vulnerabilities,
licenseIssues: this.licenseIssues,
summary: {
totalVulnerabilities: this.vulnerabilities.length,
criticalVulnerabilities: this.vulnerabilities.filter(v => v.severity === 'critical').length,
highVulnerabilities: this.vulnerabilities.filter(v => v.severity === 'high').length,
totalLicenseIssues: this.licenseIssues.length
}
};
const reportPath = 'security-report.json';
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
console.log(`📄 Report saved to ${reportPath}`);
// Generate HTML report
await this.generateHTMLReport(report);
return report;
}
async generateHTMLReport(report) {
const html = `
<!DOCTYPE html>
<html>
<head>
<title>Security Audit Report</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.header { background: #f5f5f5; padding: 20px; border-radius: 5px; }
.vulnerabilities, .licenses { margin: 20px 0; }
.critical { color: #d32f2f; }
.high { color: #f57c00; }
.medium { color: #fbc02d; }
.low { color: #388e3c; }
table { width: 100%; border-collapse: collapse; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background-color: #f2f2f2; }
</style>
</head>
<body>
<div class="header">
<h1>Security Audit Report</h1>
<p>Generated on: ${report.timestamp}</p>
<div>
<strong>Summary:</strong>
<ul>
<li>Total Vulnerabilities: ${report.summary.totalVulnerabilities}</li>
<li>Critical: ${report.summary.criticalVulnerabilities}</li>
<li>High: ${report.summary.highVulnerabilities}</li>
<li>License Issues: ${report.summary.totalLicenseIssues}</li>
</ul>
</div>
</div>
${report.vulnerabilities.length > 0 ? `
<div class="vulnerabilities">
<h2>Vulnerabilities</h2>
<table>
<tr>
<th>Package</th>
<th>Severity</th>
<th>Type</th>
<th>Fix Available</th>
</tr>
${report.vulnerabilities.map(v => `
<tr>
<td>${v.name}</td>
<td class="${v.severity}">${v.severity}</td>
<td>${v.type}</td>
<td>${v.fixAvailable ? 'Yes' : 'No'}</td>
</tr>
`).join('')}
</table>
</div>
` : '<div class="vulnerabilities"><h2>Vulnerabilities</h2><p>✅ No vulnerabilities found</p></div>'}
${report.licenseIssues.length > 0 ? `
<div class="licenses">
<h2>License Issues</h2>
<table>
<tr>
<th>Package</th>
<th>License</th>
<th>Version</th>
</tr>
${report.licenseIssues.map(l => `
<tr>
<td>${l.name}</td>
<td>${l.license}</td>
<td>${l.version}</td>
</tr>
`).join('')}
</table>
</div>
` : '<div class="licenses"><h2>License Issues</h2><p>✅ All licenses are compliant</p></div>'}
</body>
</html>
`;
fs.writeFileSync('security-report.html', html);
console.log('📄 HTML report saved to security-report.html');
}
async run(options = {}) {
try {
await this.auditPackages();
await this.checkLicenses();
const report = await this.generateReport();
// Exit with error code if issues found
if (report.summary.totalVulnerabilities > 0 || report.summary.totalLicenseIssues > 0) {
process.exit(1);
}
} catch (error) {
console.error('💥 Security audit failed:', error);
process.exit(1);
}
}
}
// CLI usage
const args = process.argv.slice(2);
const auditor = new SecurityAuditor();
auditor.run();
// 3. Dependency Update Automation
// scripts/smart-update.js
const { execSync } = require('child_process');
const semver = require('semver');
class SmartUpdater {
constructor() {
this.updates = [];
this.conflicts = [];
}
async getDependencyTree() {
console.log('📊 Analyzing dependency tree...');
const output = execSync('pnpm list --depth=0 --json', { encoding: 'utf8' });
return JSON.parse(output);
}
async checkPackageCompatibility(packageName, version) {
try {
// Check if package has peer dependencies
const result = execSync(`npm view ${packageName}@${version} peerDependencies`, {
encoding: 'utf8',
stdio: ['pipe', 'pipe', 'ignore']
}).trim();
if (result) {
return JSON.parse(result || '{}');
}
} catch (error) {
// Package or version not found
return {};
}
return {};
}
async getLatestVersion(packageName, type = 'latest') {
try {
const result = execSync(`npm view ${packageName}@${type} version`, {
encoding: 'utf8'
}).trim();
return result;
} catch (error) {
console.warn(`⚠️ Could not fetch version for ${packageName}@${type}`);
return null;
}
}
async suggestUpdates(dependencies) {
console.log('\n🔍 Suggesting updates...');
for (const [name, info] of Object.entries(dependencies)) {
const currentVersion = info.version;
// Get latest version
const latestVersion = await this.getLatestVersion(name, 'latest');
if (!latestVersion) continue;
// Get next version (for non-breaking updates)
const nextVersion = await this.getLatestVersion(name, 'next');
if (semver.gt(latestVersion, currentVersion)) {
const changeType = semver.diff(currentVersion, latestVersion);
const compatible = this.isVersionCompatible(name, currentVersion, latestVersion);
this.updates.push({
name,
currentVersion,
latestVersion,
nextVersion,
changeType,
compatible,
recommendation: this.getRecommendation(changeType, compatible)
});
}
}
this.sortUpdates();
}
isVersionCompatible(packageName, current, latest) {
const changeType = semver.diff(current, latest);
// Major updates are potentially incompatible
if (changeType === 'major') {
return false;
}
// Minor and patch updates are generally compatible
return true;
}
getRecommendation(changeType, compatible) {
if (!compatible && changeType === 'major') {
return 'manual-review';
}
if (compatible && changeType === 'minor') {
return 'auto-update';
}
if (changeType === 'patch') {
return 'auto-update';
}
return 'test-before-update';
}
sortUpdates() {
const priority = {
'auto-update': 1,
'test-before-update': 2,
'manual-review': 3
};
this.updates.sort((a, b) => {
return priority[a.recommendation] - priority[b.recommendation];
});
}
async applyAutoUpdates() {
const autoUpdates = this.updates.filter(u => u.recommendation === 'auto-update');
if (autoUpdates.length === 0) {
console.log('✅ No automatic updates available');
return;
}
console.log(`\n🚀 Applying ${autoUpdates.length} automatic updates...`);
for (const update of autoUpdates) {
console.log(`Updating ${update.name}: ${update.currentVersion} → ${update.latestVersion}`);
try {
execSync(`pnpm add ${update.name}@${update.latestVersion}`, { stdio: 'inherit' });
} catch (error) {
console.error(`❌ Failed to update ${update.name}`);
}
}
}
async checkConflicts() {
console.log('\n🔍 Checking for potential conflicts...');
// Check for version conflicts between packages
const dependencyTree = await this.getDependencyTree();
const versionMap = new Map();
for (const [name, info] of Object.entries(dependencyTree)) {
if (!versionMap.has(name)) {
versionMap.set(name, []);
}
versionMap.get(name).push(info.version);
}
for (const [name, versions] of versionMap.entries()) {
const uniqueVersions = [...new Set(versions)];
if (uniqueVersions.length > 1) {
this.conflicts.push({
name,
versions: uniqueVersions,
severity: this.getConflictSeverity(uniqueVersions)
});
}
}
}
getConflictSeverity(versions) {
const majorVersions = versions.map(v => semver.major(v));
const uniqueMajors = [...new Set(majorVersions)];
if (uniqueMajors.length > 1) {
return 'high';
}
if (uniqueMajors.length === 1 && versions.length > 1) {
return 'medium';
}
return 'low';
}
generateReport() {
console.log('\n📋 Update Report:');
console.log('='.repeat(50));
// Auto-update recommendations
const autoUpdates = this.updates.filter(u => u.recommendation === 'auto-update');
const testUpdates = this.updates.filter(u => u.recommendation === 'test-before-update');
const manualUpdates = this.updates.filter(u => u.recommendation === 'manual-review');
console.log(`\n✅ Auto-update (${autoUpdates.length}):`);
autoUpdates.forEach(update => {
console.log(` ${update.name}: ${update.currentVersion} → ${update.latestVersion}`);
});
console.log(`\n⚠️ Test before update (${testUpdates.length}):`);
testUpdates.forEach(update => {
console.log(` ${update.name}: ${update.currentVersion} → ${update.latestVersion} (${update.changeType})`);
});
console.log(`\n🔍 Manual review required (${manualUpdates.length}):`);
manualUpdates.forEach(update => {
console.log(` ${update.name}: ${update.currentVersion} → ${update.latestVersion} (MAJOR)`);
});
// Conflicts
if (this.conflicts.length > 0) {
console.log(`\n⚠️ Version conflicts detected:`);
this.conflicts.forEach(conflict => {
console.log(` ${conflict.name}: ${conflict.versions.join(', ')} (${conflict.severity})`);
});
}
return {
updates: this.updates,
conflicts: this.conflicts,
summary: {
totalUpdates: this.updates.length,
autoUpdates: autoUpdates.length,
testUpdates: testUpdates.length,
manualUpdates: manualUpdates.length,
conflicts: this.conflicts.length
}
};
}
async run(options = {}) {
try {
console.log('🔧 Starting smart dependency update...');
const dependencyTree = await this.getDependencyTree();
await this.suggestUpdates(dependencyTree);
await this.checkConflicts();
const report = this.generateReport();
if (options.autoApply) {
await this.applyAutoUpdates();
}
console.log('\n✅ Smart update completed!');
} catch (error) {
console.error('💥 Smart update failed:', error);
process.exit(1);
}
}
}
// CLI usage
const args = process.argv.slice(2);
const options = {
autoApply: args.includes('--auto-apply'),
help: args.includes('--help')
};
if (options.help) {
console.log(`
Usage: node scripts/smart-update.js [options]
Options:
--auto-apply Automatically apply safe updates
--help Show this help message
`);
process.exit(0);
}
const updater = new SmartUpdater();
updater.run(options);
// 4. Performance Optimization Script
// scripts/optimize-workspace.js
const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');
class WorkspaceOptimizer {
constructor() {
this.stats = {};
}
async analyzeWorkspace() {
console.log('📊 Analyzing workspace performance...');
// Analyze disk usage
await this.analyzeDiskUsage();
// Analyze install times
await this.analyzeInstallTimes();
// Analyze dependency graph
await this.analyzeDependencyGraph();
// Check for optimization opportunities
await this.findOptimizationOpportunities();
}
async analyzeDiskUsage() {
console.log('\n💾 Analyzing disk usage...');
try {
const output = execSync('pnpm why -r node_modules', { encoding: 'utf8' });
const lines = output.split('\n');
let totalSize = 0;
const packageSizes = {};
lines.forEach(line => {
const match = line.match(/\s+(\d+)B\s+(.+)/);
if (match) {
const [, size, pkg] = match;
const bytes = parseInt(size);
totalSize += bytes;
if (!packageSizes[pkg]) {
packageSizes[pkg] = 0;
}
packageSizes[pkg] += bytes;
}
});
this.stats.diskUsage = {
totalSize,
packageSizes,
largestPackages: Object.entries(packageSizes)
.sort(([,a], [,b]) => b - a)
.slice(0, 10)
};
console.log(`Total workspace size: ${(totalSize / 1024 / 1024).toFixed(2)} MB`);
console.log(`Largest packages:`);
this.stats.diskUsage.largestPackages.forEach(([pkg, size]) => {
console.log(` ${pkg}: ${(size / 1024 / 1024).toFixed(2)} MB`);
});
} catch (error) {
console.warn('Could not analyze disk usage');
}
}
async analyzeInstallTimes() {
console.log('\n⏱️ Measuring install times...');
// Clean install
console.log('Testing clean install...');
const start = Date.now();
execSync('pnpm clean', { stdio: 'pipe' });
execSync('pnpm install --no-frozen-lockfile', { stdio: 'pipe' });
const cleanTime = Date.now() - start;
// Fresh install from lockfile
console.log('Testing install from lockfile...');
const start2 = Date.now();
execSync('pnpm install --frozen-lockfile', { stdio: 'pipe' });
const lockfileTime = Date.now() - start2;
this.stats.installTimes = {
cleanInstall: cleanTime,
lockfileInstall: lockfileTime,
speedup: ((cleanTime - lockfileTime) / cleanTime * 100).toFixed(1)
};
console.log(`Clean install: ${cleanTime}ms`);
console.log(`Lockfile install: ${lockfileTime}ms`);
console.log(`Speedup: ${this.stats.installTimes.speedup}%`);
}
async analyzeDependencyGraph() {
console.log('\n🔍 Analyzing dependency graph...');
try {
const output = execSync('pnpm list --depth=2 --json', { encoding: 'utf8' });
const graph = JSON.parse(output);
const dependencyCount = {};
let totalDependencies = 0;
Object.entries(graph).forEach(([pkg, info]) => {
const deps = Object.keys(info.dependencies || {});
const devDeps = Object.keys(info.devDependencies || {});
const totalDeps = deps.length + devDeps.length;
dependencyCount[pkg] = totalDeps;
totalDependencies += totalDeps;
});
this.stats.dependencyGraph = {
totalDependencies,
averageDependencies: totalDependencies / Object.keys(dependencyCount).length,
mostDeps: Object.entries(dependencyCount)
.sort(([,a], [,b]) => b - a)
.slice(0, 5)
};
console.log(`Total dependencies: ${totalDependencies}`);
console.log(`Average dependencies per package: ${this.stats.dependencyGraph.averageDependencies.toFixed(1)}`);
console.log(`Packages with most dependencies:`);
this.stats.dependencyGraph.mostDeps.forEach(([pkg, count]) => {
console.log(` ${pkg}: ${count}`);
});
} catch (error) {
console.warn('Could not analyze dependency graph');
}
}
async findOptimizationOpportunities() {
console.log('\n💡 Finding optimization opportunities...');
const opportunities = [];
// Check for duplicate dependencies
try {
execSync('pnpm list --dedupe --json', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] });
opportunities.push({
type: 'dedupe',
description: 'Run "pnpm dedupe" to remove duplicate dependencies',
impact: 'medium'
});
} catch (error) {
// No duplicates found
}
// Check for unused dependencies
if (fs.existsSync('pnpm-lock.yaml')) {
const lockfileSize = fs.statSync('pnpm-lock.yaml').size;
if (lockfileSize > 10 * 1024 * 1024) { // 10MB
opportunities.push({
type: 'lockfile-size',
description: 'Consider pruning unused dependencies',
impact: 'high'
});
}
}
// Check workspace configuration
const workspaceConfig = this.analyzeWorkspaceConfig();
if (workspaceConfig.improvements.length > 0) {
opportunities.push(...workspaceConfig.improvements);
}
// Check cache settings
const cacheOpportunities = this.analyzeCacheUsage();
opportunities.push(...cacheOpportunities);
this.stats.opportunities = opportunities;
console.log(`Found ${opportunities.length} optimization opportunities:`);
opportunities.forEach((opp, index) => {
console.log(` ${index + 1}. [${opp.impact.toUpperCase()}] ${opp.description}`);
});
}
analyzeWorkspaceConfig() {
const improvements = [];
if (fs.existsSync('.npmrc')) {
const config = fs.readFileSync('.npmrc', 'utf8');
if (!config.includes('prefer-workspace-packages=true')) {
improvements.push({
type: 'workspace-config',
description: 'Add "prefer-workspace-packages=true" to .npmrc',
impact: 'high'
});
}
if (!config.includes('link-workspace-packages=true')) {
improvements.push({
type: 'workspace-config',
description: 'Add "link-workspace-packages=true" to .npmrc',
impact: 'medium'
});
}
}
return { improvements };
}
analyzeCacheUsage() {
const opportunities = [];
try {
// Check store directory
const storeOutput = execSync('pnpm store path', { encoding: 'utf8' }).trim();
if (fs.existsSync(storeOutput)) {
const stats = fs.statSync(storeOutput);
if (stats.isDirectory()) {
// Calculate store size (simplified)
opportunities.push({
type: 'cache-maintenance',
description: 'Consider running "pnpm store prune" to clean up old packages',
impact: 'low'
});
}
}
} catch (error) {
// Ignore errors
}
return opportunities;
}
async optimize() {
console.log('\n🚀 Applying optimizations...');
for (const opportunity of this.stats.opportunities) {
switch (opportunity.type) {
case 'dedupe':
console.log('🔧 Removing duplicate dependencies...');
execSync('pnpm dedupe', { stdio: 'inherit' });
break;
case 'lockfile-size':
console.log('🧹 Cleaning up unused dependencies...');
execSync('pnpm prune', { stdio: 'inherit' });
break;
case 'cache-maintenance':
console.log('💾 Cleaning package store...');
execSync('pnpm store prune', { stdio: 'inherit' });
break;
default:
console.log(`ℹ️ Manual action required: ${opportunity.description}`);
}
}
console.log('✅ Optimization completed!');
}
async run(options = {}) {
try {
await this.analyzeWorkspace();
if (options.optimize) {
await this.optimize();
}
// Generate report
const reportPath = 'workspace-optimization-report.json';
fs.writeFileSync(reportPath, JSON.stringify(this.stats, null, 2));
console.log(`\n📄 Report saved to ${reportPath}`);
} catch (error) {
console.error('💥 Workspace optimization failed:', error);
process.exit(1);
}
}
}
// CLI usage
const args = process.argv.slice(2);
const options = {
optimize: args.includes('--optimize'),
help: args.includes('--help')
};
if (options.help) {
console.log(`
Usage: node scripts/optimize-workspace.js [options]
Options:
--optimize Apply automatic optimizations
--help Show this help message
`);
process.exit(0);
}
const optimizer = new WorkspaceOptimizer();
optimizer.run(options);