flatlock 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -1
- package/bin/flatlock-cmp.js +71 -45
- package/dist/compare.d.ts +25 -3
- package/dist/compare.d.ts.map +1 -1
- package/dist/detect.d.ts.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/parsers/index.d.ts +2 -2
- package/dist/parsers/npm.d.ts +64 -37
- package/dist/parsers/npm.d.ts.map +1 -1
- package/dist/parsers/pnpm/detect.d.ts +136 -0
- package/dist/parsers/pnpm/detect.d.ts.map +1 -0
- package/dist/parsers/pnpm/index.d.ts +120 -0
- package/dist/parsers/pnpm/index.d.ts.map +1 -0
- package/dist/parsers/pnpm/internal.d.ts +5 -0
- package/dist/parsers/pnpm/internal.d.ts.map +1 -0
- package/dist/parsers/pnpm/shrinkwrap.d.ts +129 -0
- package/dist/parsers/pnpm/shrinkwrap.d.ts.map +1 -0
- package/dist/parsers/pnpm/v5.d.ts +139 -0
- package/dist/parsers/pnpm/v5.d.ts.map +1 -0
- package/dist/parsers/pnpm/v6plus.d.ts +212 -0
- package/dist/parsers/pnpm/v6plus.d.ts.map +1 -0
- package/dist/parsers/pnpm.d.ts +1 -59
- package/dist/parsers/pnpm.d.ts.map +1 -1
- package/dist/parsers/types.d.ts +23 -0
- package/dist/parsers/types.d.ts.map +1 -0
- package/dist/parsers/yarn-berry.d.ts +141 -52
- package/dist/parsers/yarn-berry.d.ts.map +1 -1
- package/dist/parsers/yarn-classic.d.ts +79 -33
- package/dist/parsers/yarn-classic.d.ts.map +1 -1
- package/dist/set.d.ts +189 -0
- package/dist/set.d.ts.map +1 -0
- package/package.json +7 -5
- package/src/compare.js +385 -28
- package/src/detect.js +3 -4
- package/src/index.js +9 -2
- package/src/parsers/index.js +10 -2
- package/src/parsers/npm.js +64 -16
- package/src/parsers/pnpm/detect.js +198 -0
- package/src/parsers/pnpm/index.js +289 -0
- package/src/parsers/pnpm/internal.js +41 -0
- package/src/parsers/pnpm/shrinkwrap.js +241 -0
- package/src/parsers/pnpm/v5.js +225 -0
- package/src/parsers/pnpm/v6plus.js +290 -0
- package/src/parsers/pnpm.js +11 -89
- package/src/parsers/types.js +10 -0
- package/src/parsers/yarn-berry.js +183 -36
- package/src/parsers/yarn-classic.js +81 -21
- package/src/set.js +618 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Parser for pnpm shrinkwrap.yaml (v3/v4) format
|
|
3
|
+
*
|
|
4
|
+
* Shrinkwrap format (2016-2019) characteristics:
|
|
5
|
+
* - File: shrinkwrap.yaml
|
|
6
|
+
* - Version field: shrinkwrapVersion (number, typically 3 or 4)
|
|
7
|
+
* - Package key format: /name/version or /@scope/name/version
|
|
8
|
+
* - Peer dependency suffix: /peer@ver with ! escaping for scoped packages
|
|
9
|
+
* Example: /foo/1.0.0/bar@2.0.0+@scope!qar@3.0.0
|
|
10
|
+
*
|
|
11
|
+
* @module flatlock/parsers/pnpm/shrinkwrap
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {Object} ParsedSpec
|
|
16
|
+
* @property {string|null} name - The package name (null if unparseable)
|
|
17
|
+
* @property {string|null} version - The package version (null if unparseable)
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Parse a shrinkwrap.yaml package spec (v3/v4 format).
|
|
22
|
+
*
|
|
23
|
+
* Shrinkwrap format uses:
|
|
24
|
+
* - Slash separator between name and version: /name/version
|
|
25
|
+
* - Peer dependencies after another slash: /name/version/peer@ver
|
|
26
|
+
* - Scoped packages: /@scope/name/version
|
|
27
|
+
* - Scoped peer dependencies use `!` to escape the `@`: `/name/1.0.0/peer@2.0.0+@scope!qar@3.0.0`
|
|
28
|
+
*
|
|
29
|
+
* @param {string} spec - Package spec from shrinkwrap.yaml packages section
|
|
30
|
+
* @returns {ParsedSpec} Parsed name and version
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* // Unscoped package
|
|
34
|
+
* parseSpecShrinkwrap('/lodash/4.17.21')
|
|
35
|
+
* // => { name: 'lodash', version: '4.17.21' }
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* // Scoped package
|
|
39
|
+
* parseSpecShrinkwrap('/@babel/core/7.23.0')
|
|
40
|
+
* // => { name: '@babel/core', version: '7.23.0' }
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* // With peer dependency suffix
|
|
44
|
+
* parseSpecShrinkwrap('/foo/1.0.0/bar@2.0.0')
|
|
45
|
+
* // => { name: 'foo', version: '1.0.0' }
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* // With scoped peer dependency (`!` escapes `@`)
|
|
49
|
+
* parseSpecShrinkwrap('/foo/1.0.0/bar@2.0.0+@scope!qar@3.0.0')
|
|
50
|
+
* // => { name: 'foo', version: '1.0.0' }
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* // Scoped package with peer deps
|
|
54
|
+
* parseSpecShrinkwrap('/@emotion/styled/10.0.27/react@17.0.2')
|
|
55
|
+
* // => { name: '@emotion/styled', version: '10.0.27' }
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* // Multiple peer dependencies
|
|
59
|
+
* parseSpecShrinkwrap('/styled-components/5.3.6/react-dom@17.0.2+react@17.0.2')
|
|
60
|
+
* // => { name: 'styled-components', version: '5.3.6' }
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* // Package with hyphenated name
|
|
64
|
+
* parseSpecShrinkwrap('/string-width/4.2.3')
|
|
65
|
+
* // => { name: 'string-width', version: '4.2.3' }
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* // Scoped package with hyphenated name
|
|
69
|
+
* parseSpecShrinkwrap('/@babel/helper-compilation-targets/7.23.6')
|
|
70
|
+
* // => { name: '@babel/helper-compilation-targets', version: '7.23.6' }
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* // link: protocol - skipped
|
|
74
|
+
* parseSpecShrinkwrap('link:packages/my-pkg')
|
|
75
|
+
* // => { name: null, version: null }
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* // file: protocol - skipped
|
|
79
|
+
* parseSpecShrinkwrap('file:../local-package')
|
|
80
|
+
* // => { name: null, version: null }
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* // Null input
|
|
84
|
+
* parseSpecShrinkwrap(null)
|
|
85
|
+
* // => { name: null, version: null }
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* // Empty string
|
|
89
|
+
* parseSpecShrinkwrap('')
|
|
90
|
+
* // => { name: null, version: null }
|
|
91
|
+
*/
|
|
92
|
+
export function parseSpecShrinkwrap(spec) {
|
|
93
|
+
// Handle null/undefined input
|
|
94
|
+
if (spec == null || typeof spec !== 'string') {
|
|
95
|
+
return { name: null, version: null };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Skip special protocols
|
|
99
|
+
if (spec.startsWith('link:') || spec.startsWith('file:')) {
|
|
100
|
+
return { name: null, version: null };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Remove leading slash if present
|
|
104
|
+
const cleaned = spec.startsWith('/') ? spec.slice(1) : spec;
|
|
105
|
+
|
|
106
|
+
// Handle empty string after removing slash
|
|
107
|
+
if (!cleaned) {
|
|
108
|
+
return { name: null, version: null };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Split by slash
|
|
112
|
+
const parts = cleaned.split('/');
|
|
113
|
+
|
|
114
|
+
// Determine if this is a scoped package
|
|
115
|
+
// Scoped packages start with @ and have format: @scope/name/version[/peer-suffix]
|
|
116
|
+
// Unscoped packages have format: name/version[/peer-suffix]
|
|
117
|
+
|
|
118
|
+
if (cleaned.startsWith('@')) {
|
|
119
|
+
// Scoped package: @scope/name/version[/peer-suffix]
|
|
120
|
+
// parts[0] = '@scope', parts[1] = 'name', parts[2] = 'version', parts[3+] = peer suffix
|
|
121
|
+
|
|
122
|
+
if (parts.length < 3) {
|
|
123
|
+
// Not enough parts for scoped package
|
|
124
|
+
return { name: null, version: null };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const scope = parts[0]; // e.g., '@babel'
|
|
128
|
+
const pkgName = parts[1]; // e.g., 'core'
|
|
129
|
+
const version = parts[2]; // e.g., '7.23.0'
|
|
130
|
+
|
|
131
|
+
// Validate scope format
|
|
132
|
+
if (!scope.startsWith('@') || !scope.slice(1)) {
|
|
133
|
+
return { name: null, version: null };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Validate we have both name and version
|
|
137
|
+
if (!pkgName || !version) {
|
|
138
|
+
return { name: null, version: null };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// The version might contain additional peer suffix parts that got split
|
|
142
|
+
// In shrinkwrap v3/v4, peer suffixes come after another slash
|
|
143
|
+
// But the version itself should be the semver string
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
name: `${scope}/${pkgName}`,
|
|
147
|
+
version: version
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Unscoped package: name/version[/peer-suffix]
|
|
152
|
+
// parts[0] = 'name', parts[1] = 'version', parts[2+] = peer suffix
|
|
153
|
+
|
|
154
|
+
if (parts.length < 2) {
|
|
155
|
+
// Not enough parts
|
|
156
|
+
return { name: null, version: null };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const name = parts[0];
|
|
160
|
+
const version = parts[1];
|
|
161
|
+
|
|
162
|
+
// Validate we have both name and version
|
|
163
|
+
if (!name || !version) {
|
|
164
|
+
return { name: null, version: null };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return { name, version };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Check if a spec has peer dependency suffix (shrinkwrap v3/v4 format).
|
|
172
|
+
*
|
|
173
|
+
* In shrinkwrap v3/v4, peer dependencies are appended after the version
|
|
174
|
+
* with another slash: /name/version/peer@ver+peer2@ver
|
|
175
|
+
*
|
|
176
|
+
* @param {string} spec - Package spec from shrinkwrap.yaml
|
|
177
|
+
* @returns {boolean} True if the spec has peer dependency suffix
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* hasPeerSuffix('/lodash/4.17.21') // => false
|
|
181
|
+
* hasPeerSuffix('/foo/1.0.0/bar@2.0.0') // => true
|
|
182
|
+
* hasPeerSuffix('/@babel/core/7.23.0') // => false
|
|
183
|
+
* hasPeerSuffix('/@emotion/styled/10.0.27/react@17.0.2') // => true
|
|
184
|
+
*/
|
|
185
|
+
export function hasPeerSuffix(spec) {
|
|
186
|
+
if (spec == null || typeof spec !== 'string') {
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Remove leading slash
|
|
191
|
+
const cleaned = spec.startsWith('/') ? spec.slice(1) : spec;
|
|
192
|
+
|
|
193
|
+
// Count slashes
|
|
194
|
+
const slashCount = (cleaned.match(/\//g) || []).length;
|
|
195
|
+
|
|
196
|
+
// Scoped packages have 2+ slashes (scope/name/version), peer adds more
|
|
197
|
+
// Unscoped packages have 1+ slash (name/version), peer adds more
|
|
198
|
+
|
|
199
|
+
if (cleaned.startsWith('@')) {
|
|
200
|
+
// Scoped: needs > 2 slashes for peer suffix
|
|
201
|
+
return slashCount > 2;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Unscoped: needs > 1 slash for peer suffix
|
|
205
|
+
return slashCount > 1;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Extract the peer dependency suffix from a shrinkwrap spec.
|
|
210
|
+
*
|
|
211
|
+
* @param {string} spec - Package spec from shrinkwrap.yaml
|
|
212
|
+
* @returns {string|null} The peer suffix or null if none
|
|
213
|
+
*
|
|
214
|
+
* @example
|
|
215
|
+
* extractPeerSuffix('/lodash/4.17.21') // => null
|
|
216
|
+
* extractPeerSuffix('/foo/1.0.0/bar@2.0.0') // => 'bar@2.0.0'
|
|
217
|
+
* extractPeerSuffix('/foo/1.0.0/bar@2.0.0+@scope!qar@3.0.0') // => 'bar@2.0.0+@scope!qar@3.0.0'
|
|
218
|
+
*/
|
|
219
|
+
export function extractPeerSuffix(spec) {
|
|
220
|
+
if (spec == null || typeof spec !== 'string') {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Remove leading slash
|
|
225
|
+
const cleaned = spec.startsWith('/') ? spec.slice(1) : spec;
|
|
226
|
+
const parts = cleaned.split('/');
|
|
227
|
+
|
|
228
|
+
if (cleaned.startsWith('@')) {
|
|
229
|
+
// Scoped: @scope/name/version[/peer-suffix...]
|
|
230
|
+
if (parts.length <= 3) {
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
return parts.slice(3).join('/');
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Unscoped: name/version[/peer-suffix...]
|
|
237
|
+
if (parts.length <= 2) {
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
return parts.slice(2).join('/');
|
|
241
|
+
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Parser for pnpm-lock.yaml v5.x format
|
|
3
|
+
*
|
|
4
|
+
* pnpm-lock.yaml v5.x format (2019-2022) characteristics:
|
|
5
|
+
* - File: pnpm-lock.yaml
|
|
6
|
+
* - Version field: lockfileVersion (number like 5, 5.1, 5.2, 5.3, 5.4)
|
|
7
|
+
* - Package key format: /name/version or /@scope/name/version
|
|
8
|
+
* - Peer dependency suffix: _peer@ver with + escaping for scoped packages
|
|
9
|
+
* Example: /foo/1.0.0_bar@2.0.0+@scope+qar@3.0.0
|
|
10
|
+
*
|
|
11
|
+
* Key differences from shrinkwrap v3/v4:
|
|
12
|
+
* - Peer suffix uses _ instead of /
|
|
13
|
+
* - Scoped peer packages escape @ with + instead of !
|
|
14
|
+
*
|
|
15
|
+
* @module flatlock/parsers/pnpm/v5
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @typedef {Object} ParsedSpec
|
|
20
|
+
* @property {string|null} name - The package name (null if unparseable)
|
|
21
|
+
* @property {string|null} version - The package version (null if unparseable)
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Parse a pnpm-lock.yaml v5.x package spec.
|
|
26
|
+
*
|
|
27
|
+
* v5 format uses:
|
|
28
|
+
* - Slash separator between name and version: /name/version
|
|
29
|
+
* - Peer dependencies after underscore: /name/version_peer@ver
|
|
30
|
+
* - Scoped packages: /@scope/name/version
|
|
31
|
+
* - Multiple peers joined with +: /name/1.0.0_peer1@2.0.0+peer2@3.0.0
|
|
32
|
+
* - Scoped peer dependencies use + to escape the /: _@scope+pkg@1.0.0
|
|
33
|
+
*
|
|
34
|
+
* @param {string} spec - Package spec from pnpm-lock.yaml packages section
|
|
35
|
+
* @returns {ParsedSpec} Parsed name and version
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* // Unscoped package
|
|
39
|
+
* parseSpecV5('/lodash/4.17.21')
|
|
40
|
+
* // => { name: 'lodash', version: '4.17.21' }
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* // Scoped package
|
|
44
|
+
* parseSpecV5('/@babel/core/7.23.0')
|
|
45
|
+
* // => { name: '@babel/core', version: '7.23.0' }
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* // With peer dependency suffix
|
|
49
|
+
* parseSpecV5('/styled-jsx/3.0.9_react@17.0.2')
|
|
50
|
+
* // => { name: 'styled-jsx', version: '3.0.9' }
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* // With multiple peer dependencies
|
|
54
|
+
* parseSpecV5('/pkg/1.0.0_react-dom@17.0.2+react@17.0.2')
|
|
55
|
+
* // => { name: 'pkg', version: '1.0.0' }
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* // Scoped package with peer deps
|
|
59
|
+
* parseSpecV5('/@emotion/styled/10.0.27_react@17.0.2')
|
|
60
|
+
* // => { name: '@emotion/styled', version: '10.0.27' }
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* // Prerelease version
|
|
64
|
+
* parseSpecV5('/@verdaccio/ui-theme/6.0.0-6-next.50')
|
|
65
|
+
* // => { name: '@verdaccio/ui-theme', version: '6.0.0-6-next.50' }
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* // Package with hyphenated name
|
|
69
|
+
* parseSpecV5('/string-width/4.2.3')
|
|
70
|
+
* // => { name: 'string-width', version: '4.2.3' }
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* // Scoped package with hyphenated name
|
|
74
|
+
* parseSpecV5('/@babel/helper-compilation-targets/7.23.6')
|
|
75
|
+
* // => { name: '@babel/helper-compilation-targets', version: '7.23.6' }
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* // Complex peer suffix with scoped peer
|
|
79
|
+
* parseSpecV5('/styled-components/5.3.6_@babel+core@7.23.0+react@18.2.0')
|
|
80
|
+
* // => { name: 'styled-components', version: '5.3.6' }
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* // link: protocol - skipped
|
|
84
|
+
* parseSpecV5('link:packages/my-pkg')
|
|
85
|
+
* // => { name: null, version: null }
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* // file: protocol - skipped
|
|
89
|
+
* parseSpecV5('file:../local-package')
|
|
90
|
+
* // => { name: null, version: null }
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* // Null input
|
|
94
|
+
* parseSpecV5(null)
|
|
95
|
+
* // => { name: null, version: null }
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* // Build metadata version
|
|
99
|
+
* parseSpecV5('/esbuild/0.19.12+sha512.abc123')
|
|
100
|
+
* // => { name: 'esbuild', version: '0.19.12+sha512.abc123' }
|
|
101
|
+
*/
|
|
102
|
+
export function parseSpecV5(spec) {
|
|
103
|
+
// Handle null/undefined input
|
|
104
|
+
if (spec == null || typeof spec !== 'string') {
|
|
105
|
+
return { name: null, version: null };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Skip special protocols
|
|
109
|
+
if (spec.startsWith('link:') || spec.startsWith('file:')) {
|
|
110
|
+
return { name: null, version: null };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Remove leading slash if present
|
|
114
|
+
let cleaned = spec.startsWith('/') ? spec.slice(1) : spec;
|
|
115
|
+
|
|
116
|
+
// Handle empty string after removing slash
|
|
117
|
+
if (!cleaned) {
|
|
118
|
+
return { name: null, version: null };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Strip peer dependency suffix FIRST (before splitting)
|
|
122
|
+
// v5 format uses underscore: pkg/1.0.0_peer@2.0.0+other@1.0.0
|
|
123
|
+
const underscoreIndex = cleaned.indexOf('_');
|
|
124
|
+
if (underscoreIndex !== -1) {
|
|
125
|
+
cleaned = cleaned.slice(0, underscoreIndex);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Now split by slash to get name and version parts
|
|
129
|
+
const parts = cleaned.split('/');
|
|
130
|
+
|
|
131
|
+
// Determine if this is a scoped package
|
|
132
|
+
if (cleaned.startsWith('@')) {
|
|
133
|
+
// Scoped package: @scope/name/version
|
|
134
|
+
// parts[0] = '@scope', parts[1] = 'name', parts[2] = 'version'
|
|
135
|
+
|
|
136
|
+
if (parts.length < 3) {
|
|
137
|
+
// Not enough parts for scoped package
|
|
138
|
+
return { name: null, version: null };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const scope = parts[0]; // e.g., '@babel'
|
|
142
|
+
const pkgName = parts[1]; // e.g., 'core'
|
|
143
|
+
const version = parts[2]; // e.g., '7.23.0'
|
|
144
|
+
|
|
145
|
+
// Validate scope format
|
|
146
|
+
if (!scope.startsWith('@') || !scope.slice(1)) {
|
|
147
|
+
return { name: null, version: null };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Validate we have both name and version
|
|
151
|
+
if (!pkgName || !version) {
|
|
152
|
+
return { name: null, version: null };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
name: `${scope}/${pkgName}`,
|
|
157
|
+
version: version
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Unscoped package: name/version
|
|
162
|
+
// parts[0] = 'name', parts[1] = 'version'
|
|
163
|
+
|
|
164
|
+
if (parts.length < 2) {
|
|
165
|
+
// Not enough parts
|
|
166
|
+
return { name: null, version: null };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const name = parts[0];
|
|
170
|
+
const version = parts[1];
|
|
171
|
+
|
|
172
|
+
// Validate we have both name and version
|
|
173
|
+
if (!name || !version) {
|
|
174
|
+
return { name: null, version: null };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return { name, version };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Check if a spec has peer dependency suffix (v5 format).
|
|
182
|
+
*
|
|
183
|
+
* In v5, peer dependencies are appended after the version
|
|
184
|
+
* with an underscore: /name/version_peer@ver+peer2@ver
|
|
185
|
+
*
|
|
186
|
+
* @param {string} spec - Package spec from pnpm-lock.yaml
|
|
187
|
+
* @returns {boolean} True if the spec has peer dependency suffix
|
|
188
|
+
*
|
|
189
|
+
* @example
|
|
190
|
+
* hasPeerSuffixV5('/lodash/4.17.21') // => false
|
|
191
|
+
* hasPeerSuffixV5('/foo/1.0.0_bar@2.0.0') // => true
|
|
192
|
+
* hasPeerSuffixV5('/@babel/core/7.23.0') // => false
|
|
193
|
+
* hasPeerSuffixV5('/@emotion/styled/10.0.27_react@17.0.2') // => true
|
|
194
|
+
*/
|
|
195
|
+
export function hasPeerSuffixV5(spec) {
|
|
196
|
+
if (spec == null || typeof spec !== 'string') {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return spec.includes('_');
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Extract the peer dependency suffix from a v5 spec.
|
|
205
|
+
*
|
|
206
|
+
* @param {string} spec - Package spec from pnpm-lock.yaml v5
|
|
207
|
+
* @returns {string|null} The peer suffix or null if none
|
|
208
|
+
*
|
|
209
|
+
* @example
|
|
210
|
+
* extractPeerSuffixV5('/lodash/4.17.21') // => null
|
|
211
|
+
* extractPeerSuffixV5('/foo/1.0.0_bar@2.0.0') // => 'bar@2.0.0'
|
|
212
|
+
* extractPeerSuffixV5('/foo/1.0.0_bar@2.0.0+@scope+qar@3.0.0') // => 'bar@2.0.0+@scope+qar@3.0.0'
|
|
213
|
+
*/
|
|
214
|
+
export function extractPeerSuffixV5(spec) {
|
|
215
|
+
if (spec == null || typeof spec !== 'string') {
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const underscoreIndex = spec.indexOf('_');
|
|
220
|
+
if (underscoreIndex === -1) {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return spec.slice(underscoreIndex + 1);
|
|
225
|
+
}
|