Try Live
Add Docs
Rankings
Pricing
Docs
Install
Theme
Install
Docs
Pricing
More...
More...
Try Live
Rankings
Enterprise
Create API Key
Add Docs
nps
https://github.com/sezna/nps
Admin
nps (npm-package-scripts) is a tool that enhances npm scripts by allowing them to be defined in
...
Tokens:
11,761
Snippets:
104
Trust Score:
9.4
Update:
3 months ago
Context
Skills
Chat
Benchmark
87.5
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# nps (npm-package-scripts) ## Introduction nps is a powerful CLI tool that solves the problem of bloated and unmaintainable npm scripts in package.json files. Instead of managing complex scripts within the constraints of JSON format (no comments, limited readability, platform-specific challenges), nps allows you to define all your project scripts in a JavaScript or YAML configuration file. This approach provides the full power of JavaScript - including variables, functions, conditional logic, and comments - while maintaining all the benefits of npm scripts such as automatic PATH management for node_modules/.bin. The tool acts as a script runner that executes commands defined in package-scripts.js or package-scripts.yml files. It supports hierarchical script organization using dot notation, prefix-based matching for quick access, concurrent and serial script execution, and automatic migration from existing npm scripts. With features like bash completion, customizable logging levels, and cross-platform compatibility, nps transforms script management from a maintenance burden into an organized, scalable solution that grows with your project. ## CLI Commands and Configuration ### Running Scripts with nps Execute scripts defined in your configuration file using the nps command with various options and flags. ```bash # Run a single script nps build # Run multiple scripts in series nps lint test build # Run scripts with arguments (use quotes) nps "test --coverage" "build --production" # Run nested scripts using dot notation nps test.unit nps build.prod # Use prefix matching for shortcuts nps b.p # runs build.prod if it's the only match # Use prefix option to scope all script executions nps --prefix=test unit functional # runs test.unit then test.functional # Silent mode (suppress nps output) nps --silent build # Hide script command text nps --no-scripts test # Use custom config file nps --config ./config/my-scripts.js lint # Set log level nps --log-level error build # Preload modules (useful for babel-register, ts-node) nps --require babel-register test # Get help for specific script nps help build nps help test.watch # View all available scripts nps help nps --help ``` ### Initializing nps in a Project Automatically migrate from package.json scripts to nps configuration. ```bash # Initialize with JavaScript config (default) nps init # Initialize with YAML config nps init --type yml # This will: # 1. Read scripts from your package.json # 2. Create package-scripts.js (or .yml) # 3. Update package.json scripts to use nps # 4. Prompt before overwriting existing config # After initialization, your package.json will look like: # { # "scripts": { # "start": "nps", # "test": "nps test" # } # } ``` ### Bash Completion Setup Enable shell autocompletion for nps commands and script names. ```bash # Install nps globally first npm install --global nps # Add completion to bash profile nps completion >> ~/.bash_profile # Or for zsh (requires bash compatibility mode enabled) nps completion >> ~/.zshrc # Reload your shell source ~/.bash_profile # Now you can use tab completion nps tes<TAB> # completes to 'test' nps build.<TAB> # shows all build.* scripts ``` ### CLI Configuration File (.npsrc) Configure nps behavior with a JSON configuration file to avoid repeating command-line flags. ```json { "require": "ts-node/register/transpile-only", "config": "package-scripts.ts" } ``` ```bash # With .npsrc in place, nps will automatically: # - Load TypeScript support via ts-node # - Use package-scripts.ts instead of .js # You can still override these with CLI flags nps --config other-config.js build ``` ## JavaScript Configuration (package-scripts.js) ### Basic Script Configuration Define scripts as strings, objects with metadata, or nested hierarchies. ```javascript // package-scripts.js module.exports = { scripts: { // Simple string script default: 'node index.js', // Script runs when you execute: nps (with no arguments) lint: 'eslint .', // Object with description test: { default: { script: 'jest', description: 'Run all tests with Jest' }, // Nested script: nps test.watch watch: { script: 'jest --watch', description: 'Run Jest in watch mode' }, // Deeper nesting: nps test.unit.watch unit: { default: 'jest --testPathPattern=unit', watch: 'jest --watch --testPathPattern=unit' } }, // Hidden from help output debug: { script: 'node --inspect-brk index.js', hiddenFromHelp: true }, // Kebab-case is automatically converted 'build-prod': 'webpack --mode production', // Can be run as: nps build-prod OR nps buildProd // Run multiple commands in series validate: 'nps lint && nps test && nps build' }, // Global options options: { silent: false, logLevel: 'info' } }; ``` ### Dynamic Script Configuration with Functions Use functions to create dynamic scripts based on runtime input or environment. ```javascript // package-scripts.js const isWindows = process.platform === 'win32'; const removeDist = isWindows ? 'rmdir /s /q dist' : 'rm -rf dist'; module.exports = { scripts: { // Static scripts available to all clean: removeDist, // Function-based configuration build: (input) => { // input contains the script name parts const isDev = input && input.includes('dev'); const isProd = input && input.includes('prod'); return { default: `${removeDist} && webpack`, dev: `${removeDist} && webpack --mode development`, prod: `${removeDist} && webpack --mode production --optimize-minimize`, watch: 'webpack --watch' }; }, // Environment-aware scripts serve: { default: isWindows ? 'set PORT=3000 && node server.js' : 'PORT=3000 node server.js', prod: isWindows ? 'set NODE_ENV=production && node server.js' : 'NODE_ENV=production node server.js' }, // Dynamic script generation test: (() => { const testFiles = ['unit', 'integration', 'e2e']; const scripts = { default: 'jest', all: 'jest --coverage' }; testFiles.forEach(name => { scripts[name] = `jest --testPathPattern=${name}`; }); return scripts; })() } }; // Usage: // nps clean // nps build.dev // nps test.unit // nps test.integration ``` ### Concurrent and Serial Execution with nps-utils Organize complex script workflows using nps-utils helpers for parallel and sequential execution. ```javascript // package-scripts.js const npsUtils = require('nps-utils'); const {series, concurrent, rimraf} = npsUtils; module.exports = { scripts: { // Serial execution - runs in order build: { default: series( rimraf('dist'), 'babel src --out-dir dist', 'echo Build complete!' ), // With descriptions full: { description: 'Clean, build, and test', script: series.nps('clean', 'build.transpile', 'test') }, transpile: 'babel src --out-dir dist' }, // Concurrent execution - runs in parallel validate: { description: 'Run all validation checks concurrently', script: concurrent.nps('lint', 'test', 'typecheck') }, // Concurrent with detailed configuration dev: { description: 'Run development environment', script: concurrent({ server: { script: 'nodemon server.js', color: 'green.bold' }, webpack: { script: 'webpack --watch', color: 'blue.bold' }, tests: { script: 'jest --watch', color: 'yellow.bold' } }) }, // Mix serial and concurrent ci: { description: 'Full CI pipeline', script: series( 'echo Starting CI...', rimraf('dist', 'coverage'), concurrent.nps('lint', 'typecheck'), 'nps test.coverage', 'nps build', 'echo CI complete!' ) }, clean: rimraf('dist', 'coverage', '*.log'), lint: 'eslint src', test: { default: 'jest', coverage: 'jest --coverage' }, typecheck: 'tsc --noEmit' } }; // Usage: // nps validate # Runs lint, test, and typecheck in parallel // nps dev # Runs server, webpack, and tests simultaneously with colored output // nps ci # Runs full pipeline: clean → parallel checks → test → build ``` ### Cross-Platform Script Configuration Handle platform differences elegantly within your script configuration. ```javascript // package-scripts.js const os = require('os'); const path = require('path'); const isWindows = os.platform() === 'win32'; const isMac = os.platform() === 'darwin'; const isLinux = os.platform() === 'linux'; // Platform-specific commands const commands = { open: isWindows ? 'start' : isMac ? 'open' : 'xdg-open', copy: isWindows ? 'copy' : 'cp', remove: isWindows ? 'rmdir /s /q' : 'rm -rf', envVar: (name, value) => isWindows ? `set ${name}=${value} &&` : `${name}=${value}` }; module.exports = { scripts: { // Open browser after build build: { default: `webpack && ${commands.open} dist/index.html`, watch: 'webpack --watch' }, // Platform-specific environment variables start: { default: `${commands.envVar('NODE_ENV', 'development')} node server.js`, prod: `${commands.envVar('NODE_ENV', 'production')} node server.js` }, // Clean up build artifacts clean: `${commands.remove} dist && ${commands.remove} coverage`, // Copy files 'copy-assets': isWindows ? 'xcopy /E /I assets dist\\assets' : 'cp -r assets dist/assets', // Platform-specific testing test: { default: 'jest', // Run platform-specific tests platform: isWindows ? 'jest --testPathPattern=windows' : isMac ? 'jest --testPathPattern=macos' : 'jest --testPathPattern=linux', // Integration tests with platform-specific setup integration: [ isWindows ? 'set TEST_ENV=integration &&' : 'TEST_ENV=integration', 'jest --testPathPattern=integration', isWindows ? '' : '--' ].filter(Boolean).join(' ') }, // Install OS-specific dependencies setup: { description: 'Install dependencies based on platform', script: isWindows ? 'npm install --no-optional && npm install --save-dev windows-specific-dep' : isMac ? 'npm install' : 'npm install && sudo apt-get install -y some-linux-dep' } } }; // Usage: // nps clean # Works on all platforms // nps start.prod # Sets NODE_ENV correctly for your OS // nps test.platform # Runs platform-specific tests ``` ## YAML Configuration (package-scripts.yml) ### Basic YAML Configuration Define scripts using YAML syntax as an alternative to JavaScript. ```yaml # package-scripts.yml scripts: default: node index.js lint: eslint . test: # Comments are supported in YAML! default: jest watch: script: jest --watch description: Run Jest in watch mode coverage: script: jest --coverage description: Run tests with coverage report build: default: webpack prod: webpack --mode production dev: webpack --mode development watch: webpack --watch # Run multiple commands validate: concurrent "nps lint" "nps test" "nps build" # Hidden scripts debug: script: node --inspect-brk index.js hiddenFromHelp: true # Nested scripts docker: build: docker build -t myapp . run: docker run -p 3000:3000 myapp stop: docker stop $(docker ps -q --filter ancestor=myapp) clean: docker rmi myapp # Global options options: silent: false help-style: all ``` ```bash # Usage remains the same as JavaScript config nps test.watch nps build.prod nps docker.build ``` ## Programmatic API ### runPackageScripts Function Use nps programmatically in Node.js scripts or build tools. ```javascript // build.js - Custom build script using nps const runPackageScripts = require('nps'); const path = require('path'); // Basic usage async function simpleBuild() { try { await runPackageScripts({ scriptConfig: { build: 'webpack --mode production', test: 'jest' }, scripts: ['build'], options: { silent: false, logLevel: 'info', scripts: true } }); console.log('Build completed successfully!'); } catch (error) { console.error('Build failed:', error.message); process.exit(1); } } // Advanced usage with dynamic configuration async function advancedBuild() { const config = { clean: 'rimraf dist', build: { default: 'webpack', prod: 'webpack --mode production --optimize-minimize' }, test: { default: 'jest', coverage: 'jest --coverage' } }; try { // Run multiple scripts in sequence await runPackageScripts({ scriptConfig: config, scripts: ['clean', 'build.prod', 'test.coverage'], options: { silent: false, logLevel: 'info' } }); console.log('All tasks completed!'); } catch (error) { console.error('Task failed:', error); throw error; } } // Conditional execution based on environment async function conditionalBuild() { const isDev = process.env.NODE_ENV !== 'production'; const config = { build: 'webpack', test: 'jest', lint: 'eslint src' }; const scriptsToRun = ['build']; if (isDev) { scriptsToRun.push('lint', 'test'); } await runPackageScripts({ scriptConfig: config, scripts: scriptsToRun, options: { silent: isDev, logLevel: isDev ? 'warn' : 'info' } }); } // Using with prefix option async function prefixedBuild() { const config = { test: { unit: 'jest --testPathPattern=unit', integration: 'jest --testPathPattern=integration', e2e: 'jest --testPathPattern=e2e' } }; // Run all test.* scripts with prefix await runPackageScripts({ scriptConfig: config, scripts: ['unit', 'integration'], options: { prefix: 'test', // Prepends 'test.' to all script names logLevel: 'info' } }); } // Error handling and recovery async function buildWithRetry() { const maxRetries = 3; let attempt = 0; while (attempt < maxRetries) { try { await runPackageScripts({ scriptConfig: { build: 'webpack' }, scripts: ['build'], options: {logLevel: 'error'} }); break; // Success } catch (error) { attempt++; if (attempt >= maxRetries) { throw new Error(`Build failed after ${maxRetries} attempts: ${error.message}`); } console.log(`Build failed, retrying (${attempt}/${maxRetries})...`); await new Promise(resolve => setTimeout(resolve, 1000)); } } } // Run the build simpleBuild(); ``` ### Loading and Parsing Config Files Programmatically load and validate nps configuration files. ```javascript // config-loader.js const path = require('path'); const {loadConfig} = require('nps/dist/bin-utils'); // Load JavaScript config function loadJSConfig() { const configPath = path.resolve(__dirname, 'package-scripts.js'); try { const config = loadConfig(configPath); console.log('Loaded config:', config); console.log('Available scripts:', Object.keys(config.scripts)); // Access nested scripts if (config.scripts.test) { console.log('Test scripts:', Object.keys(config.scripts.test)); } return config; } catch (error) { console.error('Failed to load config:', error.message); process.exit(1); } } // Load YAML config function loadYAMLConfig() { const configPath = path.resolve(__dirname, 'package-scripts.yml'); const config = loadConfig(configPath); return config; } // Load config with input parameter (for function-based configs) function loadDynamicConfig() { const configPath = path.resolve(__dirname, 'package-scripts.js'); // Pass input array to config function const input = ['build', 'prod']; const config = loadConfig(configPath, input); return config; } // Validate config structure function validateConfig(configPath) { try { const config = loadConfig(configPath); // Check required structure if (!config.scripts || typeof config.scripts !== 'object') { throw new Error('Config must have a scripts object'); } // Validate each script Object.entries(config.scripts).forEach(([name, script]) => { if (typeof script !== 'string' && typeof script !== 'object') { throw new Error(`Script "${name}" must be a string or object`); } if (typeof script === 'object' && !script.script && !script.default) { console.warn(`Script "${name}" has no executable script defined`); } }); console.log('Config validation passed!'); return true; } catch (error) { console.error('Config validation failed:', error.message); return false; } } // Usage const config = loadJSConfig(); validateConfig('./package-scripts.js'); ``` ## Summary nps provides a comprehensive solution for managing project automation scripts outside the constraints of package.json. The tool excels in three primary use cases: organizing complex build pipelines with nested script hierarchies and dot notation access, enabling cross-platform compatibility through JavaScript-based conditional logic, and maintaining large-scale projects where dozens of scripts would otherwise clutter package.json. Teams benefit from using actual programming constructs like variables, functions, and comments to document their automation workflows, while the automatic PATH management for node_modules/.bin ensures locally installed tools work seamlessly. Integration patterns typically start with running "nps init" to migrate existing npm scripts, then gradually refactoring the generated package-scripts.js file to leverage JavaScript features like conditionals for platform-specific behavior, nps-utils for concurrent execution, and functions for dynamic script generation. The tool integrates naturally into existing workflows - developers can keep using "npm start" or "npm test" by maintaining minimal scripts in package.json that delegate to nps, while power users can install nps globally and use bash completion for faster access. Whether building Node.js applications, React frontends, or complex monorepos, nps scales from simple projects with a handful of commands to sophisticated CI/CD pipelines with dozens of interdependent scripts.