🎯 Recommended Samples
Balanced sample collections from various categories for you to explore
pnpm Package Manager Samples
Fast, disk space efficient package manager examples including monorepo management, workspace configuration, and advanced workflows
💻 pnpm Basic Setup and Configuration json
🟢 simple
⭐⭐
Complete pnpm setup for JavaScript/TypeScript projects with workspace configuration and best practices
⏱️ 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! 🚀"
💻 pnpm Monorepo and Workspaces yaml
🟡 intermediate
⭐⭐⭐⭐
Advanced monorepo setup with pnpm workspaces, dependency management, and multi-package coordination
⏱️ 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:
💻 pnpm Advanced Workflows and Automation javascript
🔴 complex
⭐⭐⭐⭐⭐
Advanced pnpm workflows including automation, publishing, security, and performance optimization
⏱️ 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);