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