flatlock 1.0.1 → 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 +55 -2
- package/bin/flatlock-cmp.js +109 -356
- package/dist/compare.d.ts +85 -0
- package/dist/compare.d.ts.map +1 -0
- package/dist/detect.d.ts +33 -0
- package/dist/detect.d.ts.map +1 -0
- package/dist/index.d.ts +60 -20
- package/dist/index.d.ts.map +1 -1
- package/dist/parsers/index.d.ts +5 -0
- package/dist/parsers/index.d.ts.map +1 -0
- package/dist/parsers/npm.d.ts +109 -0
- package/dist/parsers/npm.d.ts.map +1 -0
- 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 +2 -0
- package/dist/parsers/pnpm.d.ts.map +1 -0
- package/dist/parsers/types.d.ts +23 -0
- package/dist/parsers/types.d.ts.map +1 -0
- package/dist/parsers/yarn-berry.d.ts +154 -0
- package/dist/parsers/yarn-berry.d.ts.map +1 -0
- package/dist/parsers/yarn-classic.d.ts +110 -0
- package/dist/parsers/yarn-classic.d.ts.map +1 -0
- package/dist/result.d.ts +12 -0
- package/dist/result.d.ts.map +1 -0
- package/dist/set.d.ts +189 -0
- package/dist/set.d.ts.map +1 -0
- package/package.json +18 -7
- package/src/compare.js +620 -0
- package/src/detect.js +8 -7
- package/src/index.js +33 -15
- package/src/parsers/index.js +12 -4
- package/src/parsers/npm.js +70 -23
- 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 +12 -77
- package/src/parsers/types.js +10 -0
- package/src/parsers/yarn-berry.js +187 -38
- package/src/parsers/yarn-classic.js +85 -24
- package/src/result.js +2 -2
- package/src/set.js +618 -0
- package/src/types.d.ts +54 -0
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Parser for pnpm-lock.yaml v6+ format (v6.0 and v9.0)
|
|
3
|
+
*
|
|
4
|
+
* pnpm-lock.yaml v6+ format (2023+) characteristics:
|
|
5
|
+
* - File: pnpm-lock.yaml
|
|
6
|
+
* - Version field: lockfileVersion (string like '6.0', '9.0')
|
|
7
|
+
* - Package key format:
|
|
8
|
+
* - v6: /name@version or /@scope/name@version (with leading slash)
|
|
9
|
+
* - v9: name@version or @scope/name@version (no leading slash)
|
|
10
|
+
* - Peer dependency suffix: (peer@ver) in parentheses
|
|
11
|
+
* Example: /@babel/core@7.23.0(@types/node@20.0.0)
|
|
12
|
+
*
|
|
13
|
+
* Key differences from v5:
|
|
14
|
+
* - Uses @ separator between name and version instead of /
|
|
15
|
+
* - Peer suffix uses parentheses () instead of underscore _
|
|
16
|
+
* - Peer names are human-readable (no hashing)
|
|
17
|
+
* - v9 additionally removes leading slash from keys
|
|
18
|
+
*
|
|
19
|
+
* @module flatlock/parsers/pnpm/v6plus
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @typedef {Object} ParsedSpec
|
|
24
|
+
* @property {string|null} name - The package name (null if unparseable)
|
|
25
|
+
* @property {string|null} version - The package version (null if unparseable)
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Parse a pnpm-lock.yaml v6+ package spec.
|
|
30
|
+
*
|
|
31
|
+
* v6/v9 format uses:
|
|
32
|
+
* - @ separator between name and version: name@version
|
|
33
|
+
* - Leading slash in v6: /name@version, no slash in v9: name@version
|
|
34
|
+
* - Peer dependencies in parentheses: name@version(peer@ver)
|
|
35
|
+
* - Scoped packages: @scope/name@version
|
|
36
|
+
* - Multiple peers: name@version(peer1@ver)(peer2@ver)
|
|
37
|
+
*
|
|
38
|
+
* @param {string} spec - Package spec from pnpm-lock.yaml packages section
|
|
39
|
+
* @returns {ParsedSpec} Parsed name and version
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* // Unscoped package (v6 format with leading slash)
|
|
43
|
+
* parseSpecV6Plus('/lodash@4.17.21')
|
|
44
|
+
* // => { name: 'lodash', version: '4.17.21' }
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* // Unscoped package (v9 format without leading slash)
|
|
48
|
+
* parseSpecV6Plus('lodash@4.17.21')
|
|
49
|
+
* // => { name: 'lodash', version: '4.17.21' }
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* // Scoped package (v6)
|
|
53
|
+
* parseSpecV6Plus('/@babel/core@7.23.0')
|
|
54
|
+
* // => { name: '@babel/core', version: '7.23.0' }
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* // Scoped package (v9)
|
|
58
|
+
* parseSpecV6Plus('@babel/core@7.23.0')
|
|
59
|
+
* // => { name: '@babel/core', version: '7.23.0' }
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* // With peer dependency suffix
|
|
63
|
+
* parseSpecV6Plus('/@babel/core@7.23.0(@types/node@20.0.0)')
|
|
64
|
+
* // => { name: '@babel/core', version: '7.23.0' }
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* // With multiple peer dependencies
|
|
68
|
+
* parseSpecV6Plus('/@aleph-alpha/config-css@0.18.4(@unocss/core@66.5.2)(postcss@8.5.6)')
|
|
69
|
+
* // => { name: '@aleph-alpha/config-css', version: '0.18.4' }
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* // Prerelease version
|
|
73
|
+
* parseSpecV6Plus('/unusual-pkg@1.0.0-beta.1')
|
|
74
|
+
* // => { name: 'unusual-pkg', version: '1.0.0-beta.1' }
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* // Package with hyphenated name
|
|
78
|
+
* parseSpecV6Plus('/string-width@4.2.3')
|
|
79
|
+
* // => { name: 'string-width', version: '4.2.3' }
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* // Scoped package with hyphenated name
|
|
83
|
+
* parseSpecV6Plus('@babel/helper-compilation-targets@7.23.6')
|
|
84
|
+
* // => { name: '@babel/helper-compilation-targets', version: '7.23.6' }
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* // Complex nested peer dependencies (v9)
|
|
88
|
+
* parseSpecV6Plus('@testing-library/react@14.0.0(react-dom@18.2.0)(react@18.2.0)')
|
|
89
|
+
* // => { name: '@testing-library/react', version: '14.0.0' }
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* // link: protocol - skipped
|
|
93
|
+
* parseSpecV6Plus('link:packages/my-pkg')
|
|
94
|
+
* // => { name: null, version: null }
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* // file: protocol - skipped
|
|
98
|
+
* parseSpecV6Plus('file:../local-package')
|
|
99
|
+
* // => { name: null, version: null }
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* // Null input
|
|
103
|
+
* parseSpecV6Plus(null)
|
|
104
|
+
* // => { name: null, version: null }
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* // Build metadata version
|
|
108
|
+
* parseSpecV6Plus('esbuild@0.19.12+sha512.abc123')
|
|
109
|
+
* // => { name: 'esbuild', version: '0.19.12+sha512.abc123' }
|
|
110
|
+
*/
|
|
111
|
+
export function parseSpecV6Plus(spec) {
|
|
112
|
+
// Handle null/undefined input
|
|
113
|
+
if (spec == null || typeof spec !== 'string') {
|
|
114
|
+
return { name: null, version: null };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Skip special protocols
|
|
118
|
+
if (spec.startsWith('link:') || spec.startsWith('file:')) {
|
|
119
|
+
return { name: null, version: null };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Remove leading slash if present (v6 has it, v9 doesn't)
|
|
123
|
+
let cleaned = spec.startsWith('/') ? spec.slice(1) : spec;
|
|
124
|
+
|
|
125
|
+
// Handle empty string
|
|
126
|
+
if (!cleaned) {
|
|
127
|
+
return { name: null, version: null };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Strip peer dependency suffixes FIRST (before looking for @ separator)
|
|
131
|
+
// v6+/v9 format uses parentheses: "@babel/core@7.23.0(@types/node@20.0.0)"
|
|
132
|
+
const parenIndex = cleaned.indexOf('(');
|
|
133
|
+
if (parenIndex !== -1) {
|
|
134
|
+
cleaned = cleaned.slice(0, parenIndex);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Find the last @ which separates name from version
|
|
138
|
+
// For scoped packages like "@babel/core@7.23.0", we need the last @
|
|
139
|
+
const lastAtIndex = cleaned.lastIndexOf('@');
|
|
140
|
+
|
|
141
|
+
// If we found an @ that's not at position 0, use v6+ parsing
|
|
142
|
+
if (lastAtIndex > 0) {
|
|
143
|
+
const name = cleaned.slice(0, lastAtIndex);
|
|
144
|
+
const version = cleaned.slice(lastAtIndex + 1);
|
|
145
|
+
|
|
146
|
+
// Validate we have both name and version
|
|
147
|
+
if (!name || !version) {
|
|
148
|
+
return { name: null, version: null };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return { name, version };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// No valid @ separator found (or @ is at position 0 meaning just a scope)
|
|
155
|
+
return { name: null, version: null };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Check if a spec has peer dependency suffix (v6+ format).
|
|
160
|
+
*
|
|
161
|
+
* In v6+, peer dependencies are in parentheses: name@version(peer@ver)
|
|
162
|
+
*
|
|
163
|
+
* @param {string} spec - Package spec from pnpm-lock.yaml
|
|
164
|
+
* @returns {boolean} True if the spec has peer dependency suffix
|
|
165
|
+
*
|
|
166
|
+
* @example
|
|
167
|
+
* hasPeerSuffixV6Plus('/lodash@4.17.21') // => false
|
|
168
|
+
* hasPeerSuffixV6Plus('/@babel/core@7.23.0(@types/node@20.0.0)') // => true
|
|
169
|
+
* hasPeerSuffixV6Plus('lodash@4.17.21') // => false
|
|
170
|
+
* hasPeerSuffixV6Plus('@emotion/styled@10.0.27(react@17.0.2)') // => true
|
|
171
|
+
*/
|
|
172
|
+
export function hasPeerSuffixV6Plus(spec) {
|
|
173
|
+
if (spec == null || typeof spec !== 'string') {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return spec.includes('(') && spec.includes(')');
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Extract the peer dependency suffix from a v6+ spec.
|
|
182
|
+
*
|
|
183
|
+
* @param {string} spec - Package spec from pnpm-lock.yaml v6+
|
|
184
|
+
* @returns {string|null} The peer suffix (including parentheses) or null if none
|
|
185
|
+
*
|
|
186
|
+
* @example
|
|
187
|
+
* extractPeerSuffixV6Plus('/lodash@4.17.21') // => null
|
|
188
|
+
* extractPeerSuffixV6Plus('/@babel/core@7.23.0(@types/node@20.0.0)') // => '(@types/node@20.0.0)'
|
|
189
|
+
* extractPeerSuffixV6Plus('/@pkg@1.0.0(peer1@2.0.0)(peer2@3.0.0)') // => '(peer1@2.0.0)(peer2@3.0.0)'
|
|
190
|
+
*/
|
|
191
|
+
export function extractPeerSuffixV6Plus(spec) {
|
|
192
|
+
if (spec == null || typeof spec !== 'string') {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const parenIndex = spec.indexOf('(');
|
|
197
|
+
if (parenIndex === -1) {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return spec.slice(parenIndex);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Parse peer dependencies from a v6+ peer suffix.
|
|
206
|
+
*
|
|
207
|
+
* @param {string} peerSuffix - The peer suffix like '(peer1@1.0.0)(peer2@2.0.0)'
|
|
208
|
+
* @returns {Array<{name: string, version: string}>} Array of parsed peer dependencies
|
|
209
|
+
*
|
|
210
|
+
* @example
|
|
211
|
+
* // Single scoped peer
|
|
212
|
+
* parsePeerDependencies('(@types/node@20.0.0)')
|
|
213
|
+
* // => [{ name: '@types/node', version: '20.0.0' }]
|
|
214
|
+
*
|
|
215
|
+
* @example
|
|
216
|
+
* // Multiple unscoped peers
|
|
217
|
+
* parsePeerDependencies('(react@18.2.0)(typescript@5.3.3)')
|
|
218
|
+
* // => [{ name: 'react', version: '18.2.0' }, { name: 'typescript', version: '5.3.3' }]
|
|
219
|
+
*
|
|
220
|
+
* @example
|
|
221
|
+
* // Single unscoped peer
|
|
222
|
+
* parsePeerDependencies('(lodash@4.17.21)')
|
|
223
|
+
* // => [{ name: 'lodash', version: '4.17.21' }]
|
|
224
|
+
*
|
|
225
|
+
* @example
|
|
226
|
+
* // Multiple scoped peers
|
|
227
|
+
* parsePeerDependencies('(@babel/core@7.23.0)(@types/react@18.2.0)')
|
|
228
|
+
* // => [{ name: '@babel/core', version: '7.23.0' }, { name: '@types/react', version: '18.2.0' }]
|
|
229
|
+
*
|
|
230
|
+
* @example
|
|
231
|
+
* // Mixed scoped and unscoped peers
|
|
232
|
+
* parsePeerDependencies('(react@18.2.0)(@types/react@18.2.0)')
|
|
233
|
+
* // => [{ name: 'react', version: '18.2.0' }, { name: '@types/react', version: '18.2.0' }]
|
|
234
|
+
*
|
|
235
|
+
* @example
|
|
236
|
+
* // React ecosystem peers (common pattern)
|
|
237
|
+
* parsePeerDependencies('(react-dom@18.2.0)(react@18.2.0)')
|
|
238
|
+
* // => [{ name: 'react-dom', version: '18.2.0' }, { name: 'react', version: '18.2.0' }]
|
|
239
|
+
*
|
|
240
|
+
* @example
|
|
241
|
+
* // Many peers (complex component library)
|
|
242
|
+
* parsePeerDependencies('(@unocss/core@66.5.2)(postcss@8.5.6)(typescript@5.3.3)')
|
|
243
|
+
* // => [{ name: '@unocss/core', version: '66.5.2' }, { name: 'postcss', version: '8.5.6' }, { name: 'typescript', version: '5.3.3' }]
|
|
244
|
+
*
|
|
245
|
+
* @example
|
|
246
|
+
* // Prerelease peer version
|
|
247
|
+
* parsePeerDependencies('(next@14.0.0-canary.0)')
|
|
248
|
+
* // => [{ name: 'next', version: '14.0.0-canary.0' }]
|
|
249
|
+
*
|
|
250
|
+
* @example
|
|
251
|
+
* // Empty/null input
|
|
252
|
+
* parsePeerDependencies(null)
|
|
253
|
+
* // => []
|
|
254
|
+
*
|
|
255
|
+
* @example
|
|
256
|
+
* // No parentheses (invalid)
|
|
257
|
+
* parsePeerDependencies('react@18.2.0')
|
|
258
|
+
* // => []
|
|
259
|
+
*
|
|
260
|
+
* @example
|
|
261
|
+
* // Deeply scoped peer
|
|
262
|
+
* parsePeerDependencies('(@babel/helper-compilation-targets@7.23.6)')
|
|
263
|
+
* // => [{ name: '@babel/helper-compilation-targets', version: '7.23.6' }]
|
|
264
|
+
*/
|
|
265
|
+
export function parsePeerDependencies(peerSuffix) {
|
|
266
|
+
if (peerSuffix == null || typeof peerSuffix !== 'string') {
|
|
267
|
+
return [];
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const peers = [];
|
|
271
|
+
|
|
272
|
+
// Match each (name@version) group
|
|
273
|
+
const regex = /\(([^)]+)\)/g;
|
|
274
|
+
|
|
275
|
+
for (const match of peerSuffix.matchAll(regex)) {
|
|
276
|
+
const peerSpec = match[1];
|
|
277
|
+
const lastAtIndex = peerSpec.lastIndexOf('@');
|
|
278
|
+
|
|
279
|
+
if (lastAtIndex > 0) {
|
|
280
|
+
const name = peerSpec.slice(0, lastAtIndex);
|
|
281
|
+
const version = peerSpec.slice(lastAtIndex + 1);
|
|
282
|
+
|
|
283
|
+
if (name && version) {
|
|
284
|
+
peers.push({ name, version });
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return peers;
|
|
290
|
+
}
|
package/src/parsers/pnpm.js
CHANGED
|
@@ -1,81 +1,16 @@
|
|
|
1
|
-
import yaml from 'js-yaml';
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
|
-
* @
|
|
5
|
-
* @property {string} name - Package name
|
|
6
|
-
* @property {string} version - Resolved version
|
|
7
|
-
* @property {string} [integrity] - Integrity hash
|
|
8
|
-
* @property {string} [resolved] - Resolution URL
|
|
9
|
-
* @property {boolean} [link] - True if this is a symlink
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Parse pnpm package spec to extract name and version
|
|
14
|
-
* Examples:
|
|
15
|
-
* "/@babel/core@7.23.0" → { name: "@babel/core", version: "7.23.0" }
|
|
16
|
-
* "/lodash@4.17.21" → { name: "lodash", version: "4.17.21" }
|
|
17
|
-
* "link:packages/foo" → { name: null, version: null } (skip these)
|
|
2
|
+
* @fileoverview pnpm lockfile parser - re-exports from modular implementation
|
|
18
3
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const cleaned = spec.startsWith('/') ? spec.slice(1) : spec;
|
|
30
|
-
|
|
31
|
-
// Find the last @ which separates name from version
|
|
32
|
-
// For scoped packages like "@babel/core@7.23.0", we need the last @
|
|
33
|
-
const lastAtIndex = cleaned.lastIndexOf('@');
|
|
34
|
-
|
|
35
|
-
if (lastAtIndex === -1) {
|
|
36
|
-
return { name: null, version: null };
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const name = cleaned.slice(0, lastAtIndex);
|
|
40
|
-
const versionPart = cleaned.slice(lastAtIndex + 1);
|
|
41
|
-
|
|
42
|
-
// Extract version (may have additional info like "_@babel+core@7.23.0")
|
|
43
|
-
// For peer dependencies, format can be: "lodash@4.17.21(@types/node@20.0.0)"
|
|
44
|
-
const version = versionPart.split('(')[0];
|
|
45
|
-
|
|
46
|
-
return { name, version };
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Parse pnpm-lock.yaml (v5.4, v6, v9)
|
|
51
|
-
* @param {string} content - Lockfile content
|
|
52
|
-
* @param {Object} [options] - Parser options
|
|
53
|
-
* @returns {Generator<Dependency>}
|
|
4
|
+
* This file maintains backward compatibility by re-exporting the pnpm parser
|
|
5
|
+
* from its new modular location at ./pnpm/index.js
|
|
6
|
+
*
|
|
7
|
+
* Supported formats:
|
|
8
|
+
* - shrinkwrap.yaml v3/v4 (2016-2019)
|
|
9
|
+
* - pnpm-lock.yaml v5.x (2019-2022)
|
|
10
|
+
* - pnpm-lock.yaml v6.0 (2023)
|
|
11
|
+
* - pnpm-lock.yaml v9.0 (2024+)
|
|
12
|
+
*
|
|
13
|
+
* @module flatlock/parsers/pnpm
|
|
54
14
|
*/
|
|
55
|
-
export function* fromPnpmLock(content, _options = {}) {
|
|
56
|
-
const lockfile = yaml.load(content);
|
|
57
|
-
const packages = lockfile.packages || {};
|
|
58
|
-
|
|
59
|
-
for (const [spec, pkg] of Object.entries(packages)) {
|
|
60
|
-
const { name, version } = parseSpec(spec);
|
|
61
|
-
|
|
62
|
-
// Skip if we couldn't parse name/version
|
|
63
|
-
if (!name || !version) continue;
|
|
64
|
-
|
|
65
|
-
const resolution = pkg.resolution || {};
|
|
66
|
-
const integrity = resolution.integrity;
|
|
67
|
-
const resolved = resolution.tarball;
|
|
68
|
-
const link = spec.startsWith('link:') || resolution.type === 'directory';
|
|
69
|
-
|
|
70
|
-
// Skip workspace/link entries - flatlock only cares about external dependencies
|
|
71
|
-
if (link) continue;
|
|
72
|
-
|
|
73
|
-
const dep = { name, version };
|
|
74
|
-
if (integrity) dep.integrity = integrity;
|
|
75
|
-
if (resolved) dep.resolved = resolved;
|
|
76
|
-
yield dep;
|
|
77
|
-
}
|
|
78
15
|
|
|
79
|
-
|
|
80
|
-
// flatlock only cares about external dependencies
|
|
81
|
-
}
|
|
16
|
+
export * from './pnpm/index.js';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {Object} Dependency
|
|
3
|
+
* @property {string} name - Package name
|
|
4
|
+
* @property {string} version - Resolved version
|
|
5
|
+
* @property {string} [integrity] - Integrity hash
|
|
6
|
+
* @property {string} [resolved] - Resolution URL
|
|
7
|
+
* @property {boolean} [link] - True if this is a symlink
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export {};
|