[READ-ONLY] a fast, modern browser for the npm registry
at main 109 lines 3.6 kB view raw
1import type { JsrPackageInfo } from '#shared/types/jsr' 2import { getPackageSpecifier, packageManagers } from './install-command' 3import type { PackageManagerId } from './install-command' 4 5/** 6 * Information about executable commands provided by a package. 7 */ 8export interface ExecutableInfo { 9 /** Primary command name (typically the package name or first bin key) */ 10 primaryCommand: string 11 /** All available command names */ 12 commands: string[] 13 /** Whether this package has any executables */ 14 hasExecutable: boolean 15} 16 17/** 18 * Extract executable command information from a package's bin field. 19 * Handles both string format ("bin": "./cli.js") and object format ("bin": { "cmd": "./cli.js" }). 20 */ 21export function getExecutableInfo( 22 packageName: string, 23 bin: string | Record<string, string> | undefined, 24): ExecutableInfo { 25 if (!bin) { 26 return { primaryCommand: '', commands: [], hasExecutable: false } 27 } 28 29 // String format: package name becomes the command 30 if (typeof bin === 'string') { 31 return { 32 primaryCommand: packageName, 33 commands: [packageName], 34 hasExecutable: true, 35 } 36 } 37 38 // Object format: keys are command names 39 const commands = Object.keys(bin) 40 const firstCommand = commands[0] 41 if (!firstCommand) { 42 return { primaryCommand: '', commands: [], hasExecutable: false } 43 } 44 45 // Prefer command matching package name if it exists, otherwise use first 46 const baseName = packageName.startsWith('@') ? packageName.split('/')[1] : packageName 47 const primaryCommand = baseName && commands.includes(baseName) ? baseName : firstCommand 48 49 return { 50 primaryCommand, 51 commands, 52 hasExecutable: true, 53 } 54} 55 56export interface RunCommandOptions { 57 packageName: string 58 packageManager: PackageManagerId 59 version?: string | null 60 jsrInfo?: JsrPackageInfo | null 61 /** Specific command to run (for packages with multiple bin entries) */ 62 command?: string 63 /** Whether this is a binary-only package (affects which execute command to use) */ 64 isBinaryOnly?: boolean 65} 66 67/** 68 * Generate run command as an array of parts. 69 * First element is the package manager label (e.g., "pnpm"), rest are arguments. 70 * For example: ["pnpm", "exec", "eslint"] or ["pnpm", "dlx", "create-vite"] 71 */ 72export function getRunCommandParts(options: RunCommandOptions): string[] { 73 const pm = packageManagers.find(p => p.id === options.packageManager) 74 if (!pm) return [] 75 76 const spec = getPackageSpecifier(options) 77 78 // Choose execute command based on package type 79 const executeCmd = options.isBinaryOnly ? pm.executeRemote : pm.executeLocal 80 const executeParts = executeCmd.split(' ') 81 82 // For deno, always use the package specifier 83 if (options.packageManager === 'deno') { 84 return [...executeParts, spec] 85 } 86 87 // For local execute with specific command name different from package name 88 // e.g., `pnpm exec tsc` for typescript package 89 if (options.command && options.command !== options.packageName) { 90 const baseName = options.packageName.startsWith('@') 91 ? options.packageName.split('/')[1] 92 : options.packageName 93 // If command matches base package name, use the package spec 94 if (options.command === baseName) { 95 return [...executeParts, spec] 96 } 97 // Otherwise use the command name directly 98 return [...executeParts, options.command] 99 } 100 101 return [...executeParts, spec] 102} 103 104/** 105 * Generate the full run command for a package. 106 */ 107export function getRunCommand(options: RunCommandOptions): string { 108 return getRunCommandParts(options).join(' ') 109}