forked from
npmx.dev/npmx.dev
[READ-ONLY]
a fast, modern browser for the npm registry
1import type { JsrPackageInfo } from '#shared/types/jsr'
2import { getCreateShortName } from '#shared/utils/package-analysis'
3
4// @unocss-include
5export const packageManagers = [
6 {
7 id: 'npm',
8 label: 'npm',
9 action: 'install',
10 executeLocal: 'npx',
11 executeRemote: 'npx',
12 create: 'npm create',
13 icon: 'i-simple-icons:npm',
14 },
15 {
16 id: 'pnpm',
17 label: 'pnpm',
18 action: 'add',
19 executeLocal: 'pnpm exec',
20 executeRemote: 'pnpm dlx',
21 create: 'pnpm create',
22 icon: 'i-simple-icons:pnpm',
23 },
24 {
25 id: 'yarn',
26 label: 'yarn',
27 action: 'add',
28 // For both yarn v1 and v2+ support
29 // local exec defers to npx instead
30 executeLocal: 'npx',
31 executeRemote: 'yarn dlx',
32 create: 'yarn create',
33 icon: 'i-simple-icons:yarn',
34 },
35 {
36 id: 'bun',
37 label: 'bun',
38 action: 'add',
39 executeLocal: 'bunx',
40 executeRemote: 'bunx',
41 create: 'bun create',
42 icon: 'i-simple-icons:bun',
43 },
44 {
45 id: 'deno',
46 label: 'deno',
47 action: 'add',
48 executeLocal: 'deno run',
49 executeRemote: 'deno run',
50 create: 'deno run',
51 icon: 'i-simple-icons:deno',
52 },
53 {
54 id: 'vlt',
55 label: 'vlt',
56 action: 'install',
57 executeLocal: 'vlx',
58 executeRemote: 'vlx',
59 create: 'vlx',
60 icon: 'i-custom-vlt',
61 },
62] as const
63
64export type PackageManagerId = (typeof packageManagers)[number]['id']
65
66export interface InstallCommandOptions {
67 packageName: string
68 packageManager: PackageManagerId
69 version?: string | null
70 jsrInfo?: JsrPackageInfo | null
71 dev?: boolean
72}
73
74export function getDevDependencyFlag(packageManager: PackageManagerId): '-D' | '-d' {
75 return packageManager === 'bun' ? '-d' : '-D'
76}
77
78/**
79 * Get the package specifier for a given package manager.
80 * Handles jsr: prefix for deno (when available on JSR).
81 */
82export function getPackageSpecifier(options: InstallCommandOptions): string {
83 const { packageName, packageManager, jsrInfo } = options
84
85 if (packageManager === 'deno') {
86 if (jsrInfo?.exists && jsrInfo.scope && jsrInfo.name) {
87 // Native JSR package: jsr:@scope/name
88 return `jsr:@${jsrInfo.scope}/${jsrInfo.name}`
89 }
90 // npm compatibility: npm:package
91 return `npm:${packageName}`
92 }
93
94 // Standard package managers (npm, pnpm, yarn, bun, vlt)
95 return packageName
96}
97
98/**
99 * Generate the full install command for a package.
100 */
101export function getInstallCommand(options: InstallCommandOptions): string {
102 return getInstallCommandParts(options).join(' ')
103}
104
105/**
106 * Generate install command as an array of parts.
107 * First element is the command (e.g., "npm"), rest are arguments.
108 * Useful for rendering with different styling for command vs args.
109 */
110export function getInstallCommandParts(options: InstallCommandOptions): string[] {
111 const pm = packageManagers.find(p => p.id === options.packageManager)
112 if (!pm) return []
113
114 const spec = getPackageSpecifier(options)
115 const version = options.version ? `@${options.version}` : ''
116 const devFlag = options.dev ? [getDevDependencyFlag(options.packageManager)] : []
117
118 return [pm.label, pm.action, ...devFlag, `${spec}${version}`]
119}
120
121export interface ExecuteCommandOptions extends InstallCommandOptions {
122 /** Whether this is a binary-only package (download & run vs local run) */
123 isBinaryOnly?: boolean
124 /** Whether this is a create-* package (uses shorthand create command) */
125 isCreatePackage?: boolean
126}
127
128export function getExecuteCommand(options: ExecuteCommandOptions): string {
129 return getExecuteCommandParts(options).join(' ')
130}
131
132export function getExecuteCommandParts(options: ExecuteCommandOptions): string[] {
133 const pm = packageManagers.find(p => p.id === options.packageManager)
134 if (!pm) return []
135
136 // For create-* packages, use the shorthand create command
137 if (options.isCreatePackage) {
138 const shortName = getCreateShortName(options.packageName)
139 if (shortName !== options.packageName) {
140 return [...pm.create.split(' '), shortName]
141 }
142 }
143
144 // Choose remote or local execute based on package type
145 const executeCmd = options.isBinaryOnly ? pm.executeRemote : pm.executeLocal
146 return [...executeCmd.split(' '), getPackageSpecifier(options)]
147}