-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathspec.ts
More file actions
70 lines (65 loc) · 2.15 KB
/
spec.ts
File metadata and controls
70 lines (65 loc) · 2.15 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
/**
* @fileoverview Parse `package@version` specs into `{name, version}`.
*
* Split out of `dlx/package.ts` for size hygiene. Uses npm-package-arg
* for the full grammar; falls back to a hand-rolled last-`@` parser
* when npm-package-arg can't handle the input.
*/
import npmPackageArg from '../external/npm-package-arg'
import {
StringPrototypeLastIndexOf,
StringPrototypeSlice,
} from '../primordials/string'
/**
* Parse package spec into name and version using npm-package-arg.
* Examples:
* - 'lodash@4.17.21' → { name: 'lodash', version: '4.17.21' }
* - '@scope/pkg@1.0.0' → { name: '@scope/pkg', version: '1.0.0' }
* - 'lodash' → { name: 'lodash', version: undefined }
*
* @example
* ```typescript
* parsePackageSpec('lodash@4.17.21')
* // { name: 'lodash', version: '4.17.21' }
*
* parsePackageSpec('@scope/pkg')
* // { name: '@scope/pkg', version: undefined }
* ```
*/
export function parsePackageSpec(spec: string): {
name: string
version: string | undefined
} {
try {
// npmPackageArg is imported at the top
/* c8 ignore next - External npm-package-arg call */
const parsed = npmPackageArg(spec)
// Extract version from different types of specs.
// For registry specs, use fetchSpec (the version/range).
// For git/file/etc, version will be undefined.
const version =
parsed.type === 'tag'
? parsed.fetchSpec
: parsed.type === 'version' || parsed.type === 'range'
? parsed.fetchSpec
: undefined
return {
name: parsed.name || spec,
version,
}
} catch {
// Fallback to simple parsing if npm-package-arg fails.
const atIndex = StringPrototypeLastIndexOf(spec, '@')
if (atIndex === -1 || atIndex === 0) {
// No version or scoped package without version (@ only at position 0).
return { name: spec, version: undefined }
}
const sliced = StringPrototypeSlice(spec, atIndex + 1)
return {
name: StringPrototypeSlice(spec, 0, atIndex),
// A trailing `@` (e.g. `'pkg@'`) yields an empty slice — normalize
// to undefined so downstream "no version" checks behave.
version: sliced || undefined,
}
}
}