Skip to content

Commit 824f86a

Browse files
authored
feat: support checking if versions exist in NPM (#387)
This adds support for checking that NPM packages exist in the repository Signed-off-by: Gareth Jones <[email protected]>
1 parent 21b36c0 commit 824f86a

File tree

3 files changed

+117
-1
lines changed

3 files changed

+117
-1
lines changed

tools/osv-linter/internal/pkgchecker/ecosystems.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ func VersionsExistInEcosystem(pkg string, versions []string, ecosystem string) e
155155
case "MinimOS":
156156
return nil
157157
case "npm":
158-
return nil
158+
return versionsExistInNpm(pkg, versions)
159159
case "NuGet":
160160
return nil
161161
case "openSUSE":

tools/osv-linter/internal/pkgchecker/ecosystems_test.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,70 @@ func Test_versionsExistInGo(t *testing.T) {
102102
}
103103
}
104104

105+
func Test_versionsExistInNpm(t *testing.T) {
106+
t.Parallel()
107+
108+
type args struct {
109+
pkg string
110+
versions []string
111+
}
112+
tests := []struct {
113+
name string
114+
args args
115+
wantErr bool
116+
}{
117+
{
118+
name: "multiple_versions_which_all_exist",
119+
args: args{
120+
pkg: "semver",
121+
versions: []string{"1.0.1", "2.0.0-beta", "5.7.1"},
122+
},
123+
wantErr: false,
124+
},
125+
{
126+
name: "multiple_versions_with_one_that_does_not_exist",
127+
args: args{
128+
pkg: "semver",
129+
versions: []string{"1.1", "2.0.0-beta1", "3.1.5", "5.1rc1"},
130+
},
131+
wantErr: true,
132+
},
133+
{
134+
name: "an_invalid_version",
135+
args: args{
136+
pkg: "semver",
137+
versions: []string{"!"},
138+
},
139+
wantErr: true,
140+
},
141+
{
142+
name: "an_invalid_package",
143+
args: args{
144+
pkg: "!",
145+
versions: []string{"1.0.0"},
146+
},
147+
wantErr: true,
148+
},
149+
{
150+
name: "a_package_that_does_not_exit",
151+
args: args{
152+
pkg: "not-a-real-package-hopefully",
153+
versions: []string{"1.0.0"},
154+
},
155+
wantErr: true,
156+
},
157+
}
158+
for _, tt := range tests {
159+
t.Run(tt.name, func(t *testing.T) {
160+
t.Parallel()
161+
162+
if err := versionsExistInNpm(tt.args.pkg, tt.args.versions); (err != nil) != tt.wantErr {
163+
t.Errorf("versionsExistInNpm() error = %v, wantErr %v", err, tt.wantErr)
164+
}
165+
})
166+
}
167+
}
168+
105169
func Test_versionsExistInPackagist(t *testing.T) {
106170
t.Parallel()
107171

tools/osv-linter/internal/pkgchecker/version_check.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,58 @@ func goVersionsExist(versions []string) error {
181181
return nil
182182
}
183183

184+
// Confirm that all specified versions of a package exist in npm.
185+
func versionsExistInNpm(pkg string, versions []string) error {
186+
packageInstanceURL := fmt.Sprintf("%s/%s", EcosystemBaseURLs["npm"], pkg)
187+
188+
resp, err := faulttolerant.Get(packageInstanceURL)
189+
if err != nil {
190+
return fmt.Errorf("unable to validate package: %v", err)
191+
}
192+
defer resp.Body.Close()
193+
if resp.StatusCode != http.StatusOK {
194+
return fmt.Errorf("unable to validate package: %q for %s", resp.Status, packageInstanceURL)
195+
}
196+
197+
// Parse the known versions from the JSON.
198+
respJSON, err := io.ReadAll(resp.Body)
199+
if err != nil {
200+
return fmt.Errorf("unable to retrieve JSON for %q: %v", pkg, err)
201+
}
202+
// Fetch all known versions of package.
203+
versionsInRepository := []string{}
204+
releases := gjson.GetBytes(respJSON, "versions.@keys")
205+
releases.ForEach(func(key, value gjson.Result) bool {
206+
versionsInRepository = append(versionsInRepository, value.String())
207+
return true // keep iterating.
208+
})
209+
// Determine which referenced versions are missing.
210+
versionsMissing := []string{}
211+
for _, versionToCheckFor := range versions {
212+
versionFound := false
213+
vc, err := semantic.Parse(versionToCheckFor, "npm")
214+
if err != nil {
215+
versionsMissing = append(versionsMissing, versionToCheckFor)
216+
continue
217+
}
218+
for _, pkgversion := range versionsInRepository {
219+
if r, err := vc.CompareStr(pkgversion); r == 0 && err == nil {
220+
versionFound = true
221+
break
222+
}
223+
}
224+
if versionFound {
225+
continue
226+
}
227+
versionsMissing = append(versionsMissing, versionToCheckFor)
228+
}
229+
if len(versionsMissing) > 0 {
230+
return &MissingVersionsError{Package: pkg, Ecosystem: "npm", Missing: versionsMissing, Known: versionsInRepository}
231+
}
232+
233+
return nil
234+
}
235+
184236
// Confirm that all specified versions of a package exist in Packagist.
185237
func versionsExistInPackagist(pkg string, versions []string) error {
186238
packageInstanceURL := fmt.Sprintf("%s/%s.json", EcosystemBaseURLs["Packagist"], pkg)

0 commit comments

Comments
 (0)