@@ -36,6 +36,7 @@ import path from 'node:path'
3636import { WIN32 } from './constants/platform'
3737import { getPacoteCachePath } from './constants/packages'
3838import { generateCacheKey } from './dlx'
39+ import { getNpmPackageArg } from './external/npm-package-arg'
3940import { getPacote } from './external/pacote'
4041import { readJsonSync } from './fs'
4142import { normalizePath } from './path'
@@ -105,7 +106,7 @@ export interface DlxPackageResult {
105106}
106107
107108/**
108- * Parse package spec into name and version.
109+ * Parse package spec into name and version using npm-package-arg .
109110 * Examples:
110111 * - 'lodash@4.17.21' → { name: 'lodash', version: '4.17.21' }
111112 * - '@scope/pkg@1.0.0' → { name: '@scope /pkg', version: '1.0.0' }
@@ -115,33 +116,35 @@ function parsePackageSpec(spec: string): {
115116 name : string
116117 version : string | undefined
117118} {
118- // Handle scoped packages (@scope/name@version).
119- if ( spec . startsWith ( '@' ) ) {
120- const parts = spec . split ( '@' )
121- // parts[0] is empty string (before leading @)
122- // parts[1] is scope/name
123- // parts[2] is version (if present)
124- if ( parts . length === 3 ) {
125- // @scope /name@version .
126- return { name : `@${ parts [ 1 ] } ` , version : parts [ 2 ] }
119+ try {
120+ const npa = getNpmPackageArg ( )
121+ const parsed = npa ( spec )
122+
123+ // Extract version from different types of specs.
124+ // For registry specs, use fetchSpec (the version/range).
125+ // For git/file/etc, version will be undefined.
126+ const version =
127+ parsed . type === 'tag'
128+ ? parsed . fetchSpec
129+ : parsed . type === 'version' || parsed . type === 'range'
130+ ? parsed . fetchSpec
131+ : undefined
132+
133+ return {
134+ name : parsed . name || spec ,
135+ version,
127136 }
128- if ( parts . length === 2 ) {
129- // @scope /name with no version.
130- return { name : `@${ parts [ 1 ] } ` , version : undefined }
137+ } catch {
138+ // Fallback to simple parsing if npm-package-arg fails.
139+ const atIndex = spec . lastIndexOf ( '@' )
140+ if ( atIndex === - 1 || spec . startsWith ( '@' ) ) {
141+ // No version or scoped package without version.
142+ return { name : spec , version : undefined }
143+ }
144+ return {
145+ name : spec . slice ( 0 , atIndex ) ,
146+ version : spec . slice ( atIndex + 1 ) ,
131147 }
132- // Fallback for malformed input.
133- return { name : spec , version : undefined }
134- }
135-
136- // Handle unscoped packages (name@version).
137- const atIndex = spec . lastIndexOf ( '@' )
138- if ( atIndex === - 1 ) {
139- return { name : spec , version : undefined }
140- }
141-
142- return {
143- name : spec . slice ( 0 , atIndex ) ,
144- version : spec . slice ( atIndex + 1 ) ,
145148 }
146149}
147150
0 commit comments