A better npm publish tool with automated workflows, version bumping, testing, and git integration
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Multi-package-manager abstraction supporting npm, Yarn (Classic/Berry), pnpm, and Bun with unified configuration and command generation.
Functions for detecting and configuring package managers.
/**
* Get package manager configuration for a project
* @param rootDirectory - Project root directory
* @param package_ - Package.json object
* @returns Package manager configuration object
*/
function getPackageManagerConfig(
rootDirectory: string,
package_: {packageManager?: string}
): PackageManagerConfig;
/**
* Find lockfile for a specific package manager
* @param rootDirectory - Project root directory
* @param config - Package manager configuration
* @returns Lockfile path or undefined if not found
*/
function findLockfile(
rootDirectory: string,
config: PackageManagerConfig
): string | undefined;
/**
* Format command for display purposes
* @param command - Command tuple [cli, arguments]
* @returns Formatted command string
*/
function printCommand([cli, arguments_]: [string, string[]]): string;Usage Examples:
import { getPackageManagerConfig, findLockfile, printCommand } from "np/source/package-manager/index.js";
// Detect package manager
const packageManager = getPackageManagerConfig(process.cwd(), packageJson);
console.log(`Using package manager: ${packageManager.id}`);
// Find lockfile
const lockfile = findLockfile(process.cwd(), packageManager);
if (lockfile) {
console.log(`Found lockfile: ${lockfile}`);
}
// Format command for display
const installCmd = packageManager.installCommand;
console.log(`Install command: ${printCommand(installCmd)}`);Unified configuration interface for all supported package managers.
interface PackageManagerConfig {
/** Package manager identifier */
id: 'npm' | 'yarn' | 'pnpm' | 'bun';
/** CLI command name */
cli: string;
/** Install command with lockfile */
installCommand: [string, string[]];
/** Install command without lockfile */
installCommandNoLockfile: [string, string[]];
/** Generate version bump command */
versionCommand(input: string): [string, string[]];
/** Custom publish command (optional) */
publishCommand?: (args: string[]) => [string, string[]];
/** Whether to throw error on external registries */
throwOnExternalRegistry?: boolean;
}Configuration objects for each supported package manager.
/**
* NPM package manager configuration
*/
const npmConfig: PackageManagerConfig = {
id: 'npm',
cli: 'npm',
installCommand: ['npm', ['ci']],
installCommandNoLockfile: ['npm', ['install']],
versionCommand: (input) => ['npm', ['version', input]],
throwOnExternalRegistry: false
};
/**
* PNPM package manager configuration
*/
const pnpmConfig: PackageManagerConfig = {
id: 'pnpm',
cli: 'pnpm',
installCommand: ['pnpm', ['install', '--frozen-lockfile']],
installCommandNoLockfile: ['pnpm', ['install', '--no-lockfile']],
versionCommand: (input) => ['pnpm', ['version', input]],
throwOnExternalRegistry: true
};
/**
* Yarn Classic package manager configuration
*/
const yarnConfig: PackageManagerConfig = {
id: 'yarn',
cli: 'yarn',
installCommand: ['yarn', ['install', '--frozen-lockfile']],
installCommandNoLockfile: ['yarn', ['install', '--no-lockfile']],
versionCommand: (input) => ['yarn', ['version', '--new-version', input]],
throwOnExternalRegistry: false
};
/**
* Yarn Berry package manager configuration
*/
const yarnBerryConfig: PackageManagerConfig = {
id: 'yarn',
cli: 'yarn',
installCommand: ['yarn', ['install', '--immutable']],
installCommandNoLockfile: ['yarn', ['install']],
versionCommand: (input) => ['yarn', ['version', input]],
publishCommand: (args) => ['yarn', ['npm', 'publish', ...args]],
throwOnExternalRegistry: false
};
/**
* Bun package manager configuration
*/
const bunConfig: PackageManagerConfig = {
id: 'bun',
cli: 'bun',
installCommand: ['bun', ['install', '--frozen-lockfile']],
installCommandNoLockfile: ['bun', ['install', '--no-save']],
versionCommand: (input) => ['npm', ['version', input]], // Bun uses npm for versioning
throwOnExternalRegistry: false
};Usage Examples:
import { npmConfig, pnpmConfig, yarnConfig } from "np/source/package-manager/configs.js";
// Use specific package manager
const config = pnpmConfig;
const [cli, args] = config.versionCommand('patch');
console.log(`Version command: ${cli} ${args.join(' ')}`);
// Check external registry support
if (config.throwOnExternalRegistry && isExternalRegistry(packageJson)) {
throw new Error(`External registry not supported with ${config.id}`);
}Package manager detection follows this priority:
--package-manager optionpackageManager field specificationSupport for package.json packageManager field:
{
"packageManager": "pnpm@8.5.0",
"packageManager": "yarn@3.4.1",
"packageManager": "npm@9.0.0"
}Automatic detection based on lockfiles:
// Lockfile to package manager mapping
const lockfileMap = {
'package-lock.json': 'npm',
'yarn.lock': 'yarn',
'pnpm-lock.yaml': 'pnpm',
'bun.lockb': 'bun'
};
// Detection logic
function detectFromLockfile(rootDirectory) {
for (const [lockfile, manager] of Object.entries(lockfileMap)) {
if (fs.existsSync(path.join(rootDirectory, lockfile))) {
return manager;
}
}
return 'npm'; // Default fallback
}Different install behavior based on lockfile presence:
// With lockfile (exact dependencies)
config.installCommand // ['pnpm', ['install', '--frozen-lockfile']]
// Without lockfile (resolve latest)
config.installCommandNoLockfile // ['pnpm', ['install', '--no-lockfile']]Version bump commands vary by package manager:
// NPM
npmConfig.versionCommand('patch') // ['npm', ['version', 'patch']]
// Yarn Classic
yarnConfig.versionCommand('patch') // ['yarn', ['version', '--new-version', 'patch']]
// PNPM
pnpmConfig.versionCommand('patch') // ['pnpm', ['version', 'patch']]
// Bun (uses npm for versioning)
bunConfig.versionCommand('patch') // ['npm', ['version', 'patch']]Publishing commands with custom handling:
// Standard (npm, pnpm, yarn classic)
const args = ['publish', '--tag', 'beta'];
// Uses: npm publish --tag beta
// Yarn Berry (custom publish command)
yarnBerryConfig.publishCommand(args) // ['yarn', ['npm', 'publish', '--tag', 'beta']]yarn npm publish commandPackage managers handle monorepos differently:
// Yarn workspaces
{
"workspaces": [
"packages/*",
"apps/*"
]
}
// PNPM workspaces (pnpm-workspace.yaml)
packages:
- 'packages/*'
- 'apps/*'
// NPM workspaces
{
"workspaces": [
"packages/*"
]
}Publishing from workspace packages:
// Detect if in workspace
const isWorkspace = packageJson.workspaces ||
fs.existsSync('pnpm-workspace.yaml');
// Adjust commands for workspace context
if (isWorkspace) {
// Use workspace-aware commands
const publishCmd = config.publishCommand || defaultPublishCommand;
}Package manager selection in CLI:
# Force specific package manager
np --package-manager pnpm
# Auto-detect from lockfile
np # Uses detected package manager
# Override packageManager field
np --package-manager yarn// Force specific package manager
const options = {
packageManager: 'pnpm'
};
// Let np auto-detect
const packageManager = getPackageManagerConfig(rootDirectory, packageJson);
// Use in publishing
await np('patch', {...options, packageManager}, context);Detection Errors:
Command Errors:
Registry Errors:
Package manager error handling strategies:
// Package manager not found
if (error.code === 'ENOENT') {
throw new Error(`Package manager '${config.id}' not found. Please install it first.`);
}
// External registry restriction
if (config.throwOnExternalRegistry && isExternalRegistry(package_)) {
throw new Error(`External registry not supported with ${config.id}`);
}
// Version mismatch
if (error.message.includes('version')) {
console.warn(`${config.id} version may be incompatible. Consider upgrading.`);
}Package manager compatibility with np features:
| Feature | npm | Yarn Classic | Yarn Berry | pnpm | Bun |
|---|---|---|---|---|---|
| External Registry | ✅ | ✅ | ✅ | ❌ | ✅ |
| 2FA Setup | ✅ | ✅ | ✅ | ✅ | ✅ |
| Workspaces | ✅ | ✅ | ✅ | ✅ | ✅ |
| Custom Publish | ✅ | ✅ | ✅ | ✅ | ✅ |
| Version Bumping | ✅ | ✅ | ✅ | ✅ | ➡️ npm |
✅ Full support, ❌ Not supported, ➡️ Delegates to another tool
Install with Tessl CLI
npx tessl i tessl/npm-np