Skip to content

Commit dc7cc45

Browse files
authored
fix: don't panic on pnpm lockfiles with an invalid path (#280)
1 parent 5fd7399 commit dc7cc45

File tree

3 files changed

+128
-9
lines changed

3 files changed

+128
-9
lines changed
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
lockfileVersion: 5.4
2+
3+
specifiers:
4+
'@types/jsdom': ^20.0.1
5+
axios: ^1.2.5
6+
pinia: ^2.0.28
7+
stream: 0.0.2
8+
typescript: ~4.7.4
9+
10+
dependencies:
11+
axios: 1.2.5
12+
pinia: 2.0.28_e7lp6ggkpgyi5vqd44m2kxvk6i
13+
stream: 0.0.2
14+
15+
devDependencies:
16+
'@types/jsdom': 20.0.1
17+
npm-run-all: 4.1.5
18+
19+
packages:
20+
21+
/@babel/helper-string-parser/7.19.4:
22+
resolution: {integrity: sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==}
23+
engines: {node: '>=6.9.0'}
24+
25+
/@babel/helper-validator-identifier/7.19.1:
26+
resolution: {integrity: sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==}
27+
engines: {node: '>=6.9.0'}
28+
29+
/@babel/parser/7.20.7:
30+
resolution: {integrity: sha512-T3Z9oHybU+0vZlY9CiDSJQTD5ZapcW18ZctFMi0MOAl/4BjFF4ul7NVSARLdbGO5vDqy9eQiGTV0LtKfvCYvcg==}
31+
engines: {node: '>=6.0.0'}
32+
hasBin: true
33+
dependencies:
34+
'@babel/types': 7.20.7
35+
36+
/@types/jsdom/20.0.1:
37+
resolution: {integrity: sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==}
38+
dependencies:
39+
'@types/node': 18.11.18
40+
'@types/tough-cookie': 4.0.2
41+
parse5: 7.1.2
42+
dev: true
43+
44+
45+
resolution: {integrity: sha512-9pU/8mmjSSOb4CXVsvGIevN+MlO/t9OWtKadTaLuN85Gge3HGorUckgp8A/2FH4V4hJ7JuQ3LIeI7KAV9ITZrQ==}
46+
dependencies:
47+
follow-redirects: 1.15.2
48+
form-data: 4.0.0
49+
proxy-from-env: 1.1.0
50+
transitivePeerDependencies:
51+
- debug
52+
dev: false
53+
54+
/stream/0.0.2:
55+
resolution: {integrity: sha512-gCq3NDI2P35B2n6t76YJuOp7d6cN/C7Rt0577l91wllh0sY9ZBuw9KaSGqH/b0hzn3CWWJbpbW0W0WvQ1H/Q7g==}
56+
dependencies:
57+
emitter-component: 1.1.1
58+
dev: false
59+
60+
/typescript/4.7.4:
61+
resolution: {integrity: sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==}
62+
engines: {node: '>=4.2.0'}
63+
hasBin: true
64+
65+
/pinia/2.0.28_e7lp6ggkpgyi5vqd44m2kxvk6i:
66+
resolution: {integrity: sha512-YClq9DkqCblq9rlyUual7ezMu/iICWdBtfJrDt4oWU9Zxpijyz7xB2xTwx57DaBQ96UGvvTMORzALr+iO5PVMw==}
67+
peerDependencies:
68+
'@vue/composition-api': ^1.4.0
69+
typescript: '>=4.4.4'
70+
vue: ^2.6.14 || ^3.2.0
71+
peerDependenciesMeta:
72+
'@vue/composition-api':
73+
optional: true
74+
typescript:
75+
optional: true
76+
dependencies:
77+
'@vue/devtools-api': 6.4.5
78+
typescript: 4.7.4
79+
vue: 3.2.45
80+
81+
dev: false
82+
83+
/http-proxy-agent/5.0.0:
84+
resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==}
85+
engines: {node: '>= 6'}
86+
dependencies:
87+
'@tootallnate/once': 2.0.0
88+
agent-base: 6.0.2
89+
debug: 4.3.4
90+
transitivePeerDependencies:
91+
- supports-color
92+
dev: true

pkg/lockfile/parse-pnpm-lock.go

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package lockfile
22

33
import (
4+
"errors"
45
"fmt"
56
"os"
67
"strconv"
@@ -10,6 +11,8 @@ import (
1011
"gopkg.in/yaml.v3"
1112
)
1213

14+
var errInvalidPackagePath = errors.New("invalid package path")
15+
1316
type PnpmLockPackageResolution struct {
1417
Tarball string `yaml:"tarball"`
1518
Commit string `yaml:"commit"`
@@ -62,12 +65,12 @@ func startsWithNumber(str string) bool {
6265

6366
// extractPnpmPackageNameAndVersion parses a dependency path, attempting to
6467
// extract the name and version of the package it represents
65-
func extractPnpmPackageNameAndVersion(dependencyPath string, lockfileVersion float64) (string, string) {
68+
func extractPnpmPackageNameAndVersion(dependencyPath string, lockfileVersion float64) (string, string, error) {
6669
// file dependencies must always have a name property to be installed,
6770
// and their dependency path never has the version encoded, so we can
6871
// skip trying to extract either from their dependency path
6972
if strings.HasPrefix(dependencyPath, "file:") {
70-
return "", ""
73+
return "", "", nil
7174
}
7275

7376
// v9.0 specifies the dependencies as <package>@<version> rather than as a path
@@ -81,10 +84,15 @@ func extractPnpmPackageNameAndVersion(dependencyPath string, lockfileVersion flo
8184
name = "@" + name
8285
}
8386

84-
return name, version
87+
return name, version, nil
8588
}
8689

8790
parts := strings.Split(dependencyPath, "/")
91+
92+
if len(parts) == 1 {
93+
return "", "", errInvalidPackagePath
94+
}
95+
8896
var name string
8997

9098
parts = parts[1:]
@@ -108,14 +116,14 @@ func extractPnpmPackageNameAndVersion(dependencyPath string, lockfileVersion flo
108116
}
109117

110118
if version == "" || !startsWithNumber(version) {
111-
return "", ""
119+
return "", "", nil
112120
}
113121

114122
// peer dependencies in v5 lockfiles are attached to the end of the version
115123
// with an "_", so we always want the first element if an "_" is present
116124
version, _, _ = strings.Cut(version, "_")
117125

118-
return name, version
126+
return name, version, nil
119127
}
120128

121129
func parseNameAtVersion(value string) (name string, version string) {
@@ -129,11 +137,15 @@ func parseNameAtVersion(value string) (name string, version string) {
129137
return matches[1], matches[2]
130138
}
131139

132-
func parsePnpmLock(lockfile PnpmLockfile) []PackageDetails {
140+
func parsePnpmLock(lockfile PnpmLockfile) ([]PackageDetails, error) {
133141
packages := make([]PackageDetails, 0, len(lockfile.Packages))
134142

135143
for s, pkg := range lockfile.Packages {
136-
name, version := extractPnpmPackageNameAndVersion(s, lockfile.Version)
144+
name, version, err := extractPnpmPackageNameAndVersion(s, lockfile.Version)
145+
146+
if err != nil {
147+
return nil, err
148+
}
137149

138150
// "name" is only present if it's not in the dependency path and takes
139151
// priority over whatever name we think we've extracted (if any)
@@ -171,7 +183,7 @@ func parsePnpmLock(lockfile PnpmLockfile) []PackageDetails {
171183
})
172184
}
173185

174-
return packages
186+
return packages, nil
175187
}
176188

177189
func ParsePnpmLock(pathToLockfile string) ([]PackageDetails, error) {
@@ -194,5 +206,11 @@ func ParsePnpmLock(pathToLockfile string) ([]PackageDetails, error) {
194206
parsedLockfile = &PnpmLockfile{}
195207
}
196208

197-
return parsePnpmLock(*parsedLockfile), nil
209+
packageDetails, err := parsePnpmLock(*parsedLockfile)
210+
211+
if err != nil {
212+
return []PackageDetails{}, fmt.Errorf("could not parse %s: %w", pathToLockfile, err)
213+
}
214+
215+
return packageDetails, nil
198216
}

pkg/lockfile/parse-pnpm-lock_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -615,6 +615,15 @@ func TestParsePnpmLock_Commits(t *testing.T) {
615615
})
616616
}
617617

618+
func TestParsePnpmLock_InvalidPackagePath(t *testing.T) {
619+
t.Parallel()
620+
621+
packages, err := lockfile.ParsePnpmLock("fixtures/pnpm/invalid-package-path.yaml")
622+
623+
expectErrContaining(t, err, "invalid package path")
624+
expectPackages(t, packages, []lockfile.PackageDetails{})
625+
}
626+
618627
func TestParsePnpmLock_Files(t *testing.T) {
619628
t.Parallel()
620629

0 commit comments

Comments
 (0)