Exemples de pnpm Package Manager

Exemples de gestionnaire de paquets rapide et économe en espace disque incluant monorepo, configuration workspace, et workflows avancés

💻 Configuration de Base pnpm json

🟢 simple ⭐⭐

Configuration complète de pnpm pour projets JavaScript/TypeScript avec configuration workspace et meilleures pratiques

⏱️ 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 et Workspaces pnpm yaml

🟡 intermediate ⭐⭐⭐⭐

Configuration avancée de monorepo avec workspaces pnpm, gestion de dépendances, et coordination multi-paquets

⏱️ 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 Avancés et Automatisation pnpm javascript

🔴 complex ⭐⭐⭐⭐⭐

Workflows avancés pnpm incluant automatisation, publication, sécurité, et optimisation de performance

⏱️ 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);