[READ-ONLY] a fast, modern browser for the npm registry
at main 248 lines 7.5 kB view raw
1import { describe, expect, it } from 'vitest' 2import { 3 getExecutableInfo, 4 getRunCommand, 5 getRunCommandParts, 6} from '../../../../app/utils/run-command' 7import { isBinaryOnlyPackage, isCreatePackage } from '../../../../shared/utils/binary-detection' 8import type { JsrPackageInfo } from '../../../../shared/types/jsr' 9 10describe('executable detection and run commands', () => { 11 const jsrNotAvailable: JsrPackageInfo = { exists: false } 12 13 describe('getExecutableInfo', () => { 14 it('returns hasExecutable: false for undefined bin', () => { 15 const info = getExecutableInfo('some-package', undefined) 16 expect(info).toEqual({ 17 primaryCommand: '', 18 commands: [], 19 hasExecutable: false, 20 }) 21 }) 22 23 it('handles string bin format (package name becomes command)', () => { 24 const info = getExecutableInfo('eslint', './bin/eslint.js') 25 expect(info).toEqual({ 26 primaryCommand: 'eslint', 27 commands: ['eslint'], 28 hasExecutable: true, 29 }) 30 }) 31 32 it('handles object bin format with single command', () => { 33 const info = getExecutableInfo('cowsay', { cowsay: './index.js' }) 34 expect(info).toEqual({ 35 primaryCommand: 'cowsay', 36 commands: ['cowsay'], 37 hasExecutable: true, 38 }) 39 }) 40 41 it('handles object bin format with multiple commands', () => { 42 const info = getExecutableInfo('typescript', { 43 tsc: './bin/tsc', 44 tsserver: './bin/tsserver', 45 }) 46 expect(info).toEqual({ 47 primaryCommand: 'tsc', 48 commands: ['tsc', 'tsserver'], 49 hasExecutable: true, 50 }) 51 }) 52 53 it('prefers command matching package name as primary', () => { 54 const info = getExecutableInfo('eslint', { 55 'eslint-cli': './cli.js', 56 'eslint': './index.js', 57 }) 58 expect(info.primaryCommand).toBe('eslint') 59 }) 60 61 it('prefers command matching base name for scoped packages', () => { 62 const info = getExecutableInfo('@scope/myapp', { 63 'myapp': './index.js', 64 'myapp-extra': './extra.js', 65 }) 66 expect(info.primaryCommand).toBe('myapp') 67 }) 68 69 it('returns empty for empty bin object', () => { 70 const info = getExecutableInfo('some-package', {}) 71 expect(info).toEqual({ 72 primaryCommand: '', 73 commands: [], 74 hasExecutable: false, 75 }) 76 }) 77 }) 78 79 describe('getRunCommandParts', () => { 80 // Default behavior uses local execute (for installed packages) 81 it.each([ 82 ['npm', ['npx', 'eslint']], 83 ['pnpm', ['pnpm', 'exec', 'eslint']], 84 ['yarn', ['npx', 'eslint']], 85 ['bun', ['bunx', 'eslint']], 86 ['deno', ['deno', 'run', 'npm:eslint']], 87 ['vlt', ['vlx', 'eslint']], 88 ] as const)('%s (local) → %s', (pm, expected) => { 89 expect( 90 getRunCommandParts({ 91 packageName: 'eslint', 92 packageManager: pm, 93 jsrInfo: jsrNotAvailable, 94 }), 95 ).toEqual(expected) 96 }) 97 98 // Binary-only packages use remote execute (download & run) 99 it.each([ 100 ['npm', ['npx', 'create-vite']], 101 ['pnpm', ['pnpm', 'dlx', 'create-vite']], 102 ['yarn', ['yarn', 'dlx', 'create-vite']], 103 ['bun', ['bunx', 'create-vite']], 104 ['deno', ['deno', 'run', 'npm:create-vite']], 105 ['vlt', ['vlx', 'create-vite']], 106 ] as const)('%s (remote) → %s', (pm, expected) => { 107 expect( 108 getRunCommandParts({ 109 packageName: 'create-vite', 110 packageManager: pm, 111 jsrInfo: jsrNotAvailable, 112 isBinaryOnly: true, 113 }), 114 ).toEqual(expected) 115 }) 116 117 it('uses command name directly for multi-bin packages', () => { 118 const parts = getRunCommandParts({ 119 packageName: 'typescript', 120 packageManager: 'npm', 121 command: 'tsserver', 122 jsrInfo: jsrNotAvailable, 123 }) 124 // npx tsserver runs the tsserver command (not npx typescript/tsserver) 125 expect(parts).toEqual(['npx', 'tsserver']) 126 }) 127 128 it('uses base name directly when command matches package base name', () => { 129 const parts = getRunCommandParts({ 130 packageName: '@scope/myapp', 131 packageManager: 'npm', 132 command: 'myapp', 133 jsrInfo: jsrNotAvailable, 134 }) 135 expect(parts).toEqual(['npx', '@scope/myapp']) 136 }) 137 138 it('returns empty array for invalid package manager', () => { 139 const parts = getRunCommandParts({ 140 packageName: 'eslint', 141 packageManager: 'invalid' as any, 142 jsrInfo: jsrNotAvailable, 143 }) 144 expect(parts).toEqual([]) 145 }) 146 }) 147 148 describe('getRunCommand', () => { 149 it('generates full run command string', () => { 150 expect( 151 getRunCommand({ 152 packageName: 'eslint', 153 packageManager: 'npm', 154 jsrInfo: jsrNotAvailable, 155 }), 156 ).toBe('npx eslint') 157 }) 158 159 it('generates correct bun run command with specific command', () => { 160 expect( 161 getRunCommand({ 162 packageName: 'typescript', 163 packageManager: 'bun', 164 command: 'tsserver', 165 jsrInfo: jsrNotAvailable, 166 }), 167 ).toBe('bunx tsserver') 168 }) 169 170 it('joined parts match getRunCommand output', () => { 171 const options = { 172 packageName: 'eslint', 173 packageManager: 'pnpm' as const, 174 jsrInfo: jsrNotAvailable, 175 } 176 const parts = getRunCommandParts(options) 177 const command = getRunCommand(options) 178 expect(parts.join(' ')).toBe(command) 179 }) 180 }) 181 182 describe('isBinaryOnlyPackage', () => { 183 it('returns true for create-* packages', () => { 184 expect(isBinaryOnlyPackage({ name: 'create-vite' })).toBe(true) 185 expect(isBinaryOnlyPackage({ name: 'create-next-app' })).toBe(true) 186 }) 187 188 it('returns true for scoped create packages', () => { 189 expect(isBinaryOnlyPackage({ name: '@vue/create-app' })).toBe(true) 190 expect(isBinaryOnlyPackage({ name: '@scope/create-something' })).toBe(true) 191 }) 192 193 it('returns true for packages with bin but no entry points', () => { 194 expect( 195 isBinaryOnlyPackage({ 196 name: 'degit', 197 bin: { degit: './bin.js' }, 198 }), 199 ).toBe(true) 200 }) 201 202 it('returns false for packages with bin AND entry points', () => { 203 expect( 204 isBinaryOnlyPackage({ 205 name: 'eslint', 206 bin: { eslint: './bin/eslint.js' }, 207 main: './lib/api.js', 208 }), 209 ).toBe(false) 210 }) 211 212 it('returns false for packages with exports', () => { 213 expect( 214 isBinaryOnlyPackage({ 215 name: 'some-package', 216 bin: { cmd: './bin.js' }, 217 exports: { '.': './index.js' }, 218 }), 219 ).toBe(false) 220 }) 221 222 it('returns false for packages without bin', () => { 223 expect( 224 isBinaryOnlyPackage({ 225 name: 'lodash', 226 main: './lodash.js', 227 }), 228 ).toBe(false) 229 }) 230 }) 231 232 describe('isCreatePackage', () => { 233 it('returns true for create-* packages', () => { 234 expect(isCreatePackage('create-vite')).toBe(true) 235 expect(isCreatePackage('create-next-app')).toBe(true) 236 }) 237 238 it('returns true for scoped create packages', () => { 239 expect(isCreatePackage('@vue/create-app')).toBe(true) 240 }) 241 242 it('returns false for regular packages', () => { 243 expect(isCreatePackage('eslint')).toBe(false) 244 expect(isCreatePackage('lodash')).toBe(false) 245 expect(isCreatePackage('@scope/utils')).toBe(false) 246 }) 247 }) 248})