Skip to content

Commit 03e3c2d

Browse files
committed
refactor version resolution
1 parent 03e4de9 commit 03e3c2d

1 file changed

Lines changed: 119 additions & 148 deletions

File tree

server/npmrc.go

Lines changed: 119 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -126,202 +126,174 @@ func (npmrc *NpmRC) getRegistryByPackageName(packageName string) *NpmRegistry {
126126
return &npmrc.NpmRegistry
127127
}
128128

129-
func (npmrc *NpmRC) getPackageInfo(pkgName string, version string) (packageJson *npm.PackageJSON, err error) {
129+
type versionResolver func(metadata *npm.PackageMetadata, version string) (string, error)
130+
131+
func (npmrc *NpmRC) fetchPackageMetadata(pkgName string, version string, useVersionSpecificEndpoint bool) (*npm.PackageMetadata, *npm.PackageJSONRaw, error) {
130132
reg := npmrc.getRegistryByPackageName(pkgName)
131-
getCacheKey := func(pkgName string, pkgVersion string) string {
132-
return reg.Registry + pkgName + "@" + pkgVersion
133+
regUrl := reg.Registry + pkgName
134+
135+
if useVersionSpecificEndpoint && strings.HasPrefix(regUrl, npmRegistry) {
136+
regUrl += "/" + version
133137
}
134138

135-
version = npm.NormalizePackageVersion(version)
136-
return withCache(getCacheKey(pkgName, version), time.Duration(config.NpmQueryCacheTTL)*time.Second, func() (*npm.PackageJSON, string, error) {
137-
// check if the package has been installed
138-
if !npm.IsDistTag(version) && npm.IsExactVersion(version) {
139-
var raw npm.PackageJSONRaw
140-
pkgJsonPath := path.Join(npmrc.StoreDir(), pkgName+"@"+version, "node_modules", pkgName, "package.json")
141-
if utils.ParseJSONFile(pkgJsonPath, &raw) == nil {
142-
return raw.ToNpmPackage(), "", nil
143-
}
139+
u, err := url.Parse(regUrl)
140+
if err != nil {
141+
return nil, nil, err
142+
}
143+
144+
header := http.Header{}
145+
if reg.Token != "" {
146+
header.Set("Authorization", "Bearer "+reg.Token)
147+
} else if reg.User != "" && reg.Password != "" {
148+
header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(reg.User+":"+reg.Password)))
149+
}
150+
151+
fetchClient, recycle := fetch.NewClient("esmd/"+VERSION, 15, false, nil)
152+
defer recycle()
153+
154+
retryTimes := 0
155+
RETRY:
156+
res, err := fetchClient.Fetch(u, header)
157+
if err != nil {
158+
if retryTimes < 3 {
159+
retryTimes++
160+
time.Sleep(time.Duration(retryTimes) * 100 * time.Millisecond)
161+
goto RETRY
144162
}
163+
return nil, nil, err
164+
}
165+
defer res.Body.Close()
145166

146-
regUrl := reg.Registry + pkgName
147-
// For exact versions and dist tags, use version-specific endpoint for better performance
148-
isWellknownVersion := (npm.IsExactVersion(version) || npm.IsDistTag(version)) && strings.HasPrefix(regUrl, npmRegistry)
149-
if isWellknownVersion {
150-
// npm registry supports url like `https://registry.npmjs.org/<name>/<version>`
151-
regUrl += "/" + version
167+
if res.StatusCode == 404 || res.StatusCode == 401 {
168+
if useVersionSpecificEndpoint {
169+
return nil, nil, fmt.Errorf("version %s of '%s' not found", version, pkgName)
170+
} else {
171+
return nil, nil, fmt.Errorf("package '%s' not found", pkgName)
152172
}
173+
}
153174

154-
u, err := url.Parse(regUrl)
175+
if res.StatusCode != 200 {
176+
return nil, nil, fmt.Errorf("npm registry returned %d for package '%s'", res.StatusCode, pkgName)
177+
}
178+
179+
if useVersionSpecificEndpoint {
180+
var raw npm.PackageJSONRaw
181+
err = json.NewDecoder(res.Body).Decode(&raw)
155182
if err != nil {
156-
return nil, "", err
183+
return nil, nil, err
157184
}
185+
return nil, &raw, nil
186+
}
158187

159-
header := http.Header{}
160-
if reg.Token != "" {
161-
header.Set("Authorization", "Bearer "+reg.Token)
162-
} else if reg.User != "" && reg.Password != "" {
163-
header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(reg.User+":"+reg.Password)))
164-
}
188+
var metadata npm.PackageMetadata
189+
err = json.NewDecoder(res.Body).Decode(&metadata)
190+
if err != nil {
191+
return nil, nil, err
192+
}
165193

166-
fetchClient, recycle := fetch.NewClient("esmd/"+VERSION, 15, false, nil)
167-
defer recycle()
194+
if len(metadata.Versions) == 0 {
195+
return nil, nil, fmt.Errorf("no versions found for package '%s'", pkgName)
196+
}
197+
198+
return &metadata, nil, nil
199+
}
168200

169-
retryTimes := 0
170-
RETRY:
171-
res, err := fetchClient.Fetch(u, header)
201+
func resolveSemverVersion(metadata *npm.PackageMetadata, version string) (string, error) {
202+
CHECK:
203+
distVersion, ok := metadata.DistTags[version]
204+
if ok {
205+
_, ok := metadata.Versions[distVersion]
206+
if ok {
207+
return distVersion, nil
208+
}
209+
} else {
210+
if version == "lastest" {
211+
return "", fmt.Errorf("version %s not found", version)
212+
}
213+
c, err := semver.NewConstraint(version)
172214
if err != nil {
173-
if retryTimes < 3 {
174-
retryTimes++
175-
time.Sleep(time.Duration(retryTimes) * 100 * time.Millisecond)
176-
goto RETRY
215+
version = "latest"
216+
goto CHECK
217+
}
218+
vs := make([]*semver.Version, len(metadata.Versions))
219+
i := 0
220+
for v := range metadata.Versions {
221+
if !strings.ContainsRune(version, '-') && strings.ContainsRune(v, '-') {
222+
continue
223+
}
224+
sv, err := semver.NewVersion(v)
225+
if err == nil && c.Check(sv) {
226+
vs[i] = sv
227+
i++
177228
}
178-
return nil, "", err
179229
}
180-
defer res.Body.Close()
181-
182-
if res.StatusCode == 404 || res.StatusCode == 401 {
183-
if isWellknownVersion {
184-
err = fmt.Errorf("version %s of '%s' not found", version, pkgName)
185-
} else {
186-
err = fmt.Errorf("package '%s' not found", pkgName)
230+
if i > 0 {
231+
vs = vs[:i]
232+
if i > 1 {
233+
sort.Sort(semver.Collection(vs))
187234
}
188-
return nil, "", err
235+
return vs[i-1].String(), nil
189236
}
237+
}
238+
return "", fmt.Errorf("version %s not found", version)
239+
}
190240

191-
if res.StatusCode != 200 {
192-
return nil, "", fmt.Errorf("npm registry returned %d for package '%s'", res.StatusCode, pkgName)
193-
}
241+
func (npmrc *NpmRC) getPackageInfo(pkgName string, version string) (packageJson *npm.PackageJSON, err error) {
242+
reg := npmrc.getRegistryByPackageName(pkgName)
243+
getCacheKey := func(pkgName string, pkgVersion string) string {
244+
return reg.Registry + pkgName + "@" + pkgVersion
245+
}
194246

195-
if isWellknownVersion {
196-
// for version-specific endpoint, the response is the package.json directly
247+
version = npm.NormalizePackageVersion(version)
248+
return withCache(getCacheKey(pkgName, version), time.Duration(config.NpmQueryCacheTTL)*time.Second, func() (*npm.PackageJSON, string, error) {
249+
if !npm.IsDistTag(version) && npm.IsExactVersion(version) {
197250
var raw npm.PackageJSONRaw
198-
err = json.NewDecoder(res.Body).Decode(&raw)
199-
if err != nil {
200-
return nil, "", err
251+
pkgJsonPath := path.Join(npmrc.StoreDir(), pkgName+"@"+version, "node_modules", pkgName, "package.json")
252+
if utils.ParseJSONFile(pkgJsonPath, &raw) == nil {
253+
return raw.ToNpmPackage(), "", nil
201254
}
202-
return raw.ToNpmPackage(), getCacheKey(pkgName, raw.Version), nil
203255
}
204256

205-
// for package-level endpoint, the response contains all versions
206-
var metadata npm.PackageMetadata
207-
err = json.NewDecoder(res.Body).Decode(&metadata)
257+
isWellknownVersion := (npm.IsExactVersion(version) || npm.IsDistTag(version)) && strings.HasPrefix(reg.Registry, npmRegistry)
258+
metadata, raw, err := npmrc.fetchPackageMetadata(pkgName, version, isWellknownVersion)
208259
if err != nil {
209260
return nil, "", err
210261
}
211262

212-
if len(metadata.Versions) == 0 {
263+
if raw != nil {
264+
return raw.ToNpmPackage(), getCacheKey(pkgName, raw.Version), nil
265+
}
266+
267+
resolvedVersion, err := resolveSemverVersion(metadata, version)
268+
if err != nil {
213269
return nil, "", fmt.Errorf("version %s of '%s' not found", version, pkgName)
214270
}
215271

216-
CHECK:
217-
distVersion, ok := metadata.DistTags[version]
218-
if ok {
219-
raw, ok := metadata.Versions[distVersion]
220-
if ok {
221-
return raw.ToNpmPackage(), getCacheKey(pkgName, raw.Version), nil
222-
}
223-
} else {
224-
if version == "lastest" {
225-
return nil, "", fmt.Errorf("version %s of '%s' not found", version, pkgName)
226-
}
227-
var c *semver.Constraints
228-
c, err = semver.NewConstraint(version)
229-
if err != nil {
230-
// fallback to latest if semverOrDistTag is not a valid semver
231-
version = "latest"
232-
goto CHECK
233-
}
234-
vs := make([]*semver.Version, len(metadata.Versions))
235-
i := 0
236-
for v := range metadata.Versions {
237-
// ignore prerelease versions
238-
if !strings.ContainsRune(version, '-') && strings.ContainsRune(v, '-') {
239-
continue
240-
}
241-
sv, err := semver.NewVersion(v)
242-
if err == nil && c.Check(sv) {
243-
vs[i] = sv
244-
i++
245-
}
246-
}
247-
if i > 0 {
248-
vs = vs[:i]
249-
if i > 1 {
250-
sort.Sort(semver.Collection(vs))
251-
}
252-
raw, ok := metadata.Versions[vs[i-1].String()]
253-
if ok {
254-
return raw.ToNpmPackage(), getCacheKey(pkgName, raw.Version), nil
255-
}
256-
}
272+
rawData, ok := metadata.Versions[resolvedVersion]
273+
if !ok {
274+
return nil, "", fmt.Errorf("version %s of '%s' not found", version, pkgName)
257275
}
258276

259-
return nil, "", fmt.Errorf("version %s of '%s' not found", version, pkgName)
277+
return rawData.ToNpmPackage(), getCacheKey(pkgName, rawData.Version), nil
260278
})
261279
}
262280

263281
func (npmrc *NpmRC) getPackageInfoByDate(pkgName string, dateVersion string) (packageJson *npm.PackageJSON, err error) {
264-
// Convert date to time
265282
targetTime, err := npm.ConvertDateVersionToTime(dateVersion)
266283
if err != nil {
267284
return nil, err
268285
}
269286

270-
// Create cache key specifically for date-based resolution
271287
reg := npmrc.getRegistryByPackageName(pkgName)
272288
cacheKey := reg.Registry + pkgName + "@date=" + dateVersion
273289

274290
return withCache(cacheKey, time.Duration(config.NpmQueryCacheTTL)*time.Second, func() (*npm.PackageJSON, string, error) {
275-
// Get full package metadata (not version-specific)
276-
regUrl := reg.Registry + pkgName
277-
u, err := url.Parse(regUrl)
291+
metadata, _, err := npmrc.fetchPackageMetadata(pkgName, dateVersion, false)
278292
if err != nil {
279293
return nil, "", err
280294
}
281295

282-
header := http.Header{}
283-
if reg.Token != "" {
284-
header.Set("Authorization", "Bearer "+reg.Token)
285-
} else if reg.User != "" && reg.Password != "" {
286-
header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(reg.User+":"+reg.Password)))
287-
}
288-
289-
fetchClient, recycle := fetch.NewClient("esmd/"+VERSION, 15, false, nil)
290-
defer recycle()
291-
292-
retryTimes := 0
293-
RETRY:
294-
res, err := fetchClient.Fetch(u, header)
295-
if err != nil {
296-
if retryTimes < 3 {
297-
retryTimes++
298-
time.Sleep(time.Duration(retryTimes) * 100 * time.Millisecond)
299-
goto RETRY
300-
}
301-
return nil, "", err
302-
}
303-
defer res.Body.Close()
304-
305-
if res.StatusCode == 404 || res.StatusCode == 401 {
306-
return nil, "", fmt.Errorf("package '%s' not found", pkgName)
307-
}
308-
309-
if res.StatusCode != 200 {
310-
return nil, "", fmt.Errorf("npm registry returned %d for package '%s'", res.StatusCode, pkgName)
311-
}
312-
313-
var metadata npm.PackageMetadata
314-
err = json.NewDecoder(res.Body).Decode(&metadata)
315-
if err != nil {
316-
return nil, "", err
317-
}
318-
319-
if len(metadata.Versions) == 0 {
320-
return nil, "", fmt.Errorf("no versions found for package '%s'", pkgName)
321-
}
322-
323-
// Resolve version using target time
324-
resolvedVersion, err := npm.ResolveVersionByTime(&metadata, targetTime)
296+
resolvedVersion, err := npm.ResolveVersionByTime(metadata, targetTime)
325297
if err != nil {
326298
return nil, "", fmt.Errorf("date-based version resolution failed for %s@%s: %s", pkgName, dateVersion, err.Error())
327299
}
@@ -331,7 +303,6 @@ func (npmrc *NpmRC) getPackageInfoByDate(pkgName string, dateVersion string) (pa
331303
return nil, "", fmt.Errorf("resolved version %s of '%s' not found", resolvedVersion, pkgName)
332304
}
333305

334-
// Cache the result under the exact version for future lookups
335306
exactVersionCacheKey := reg.Registry + pkgName + "@" + raw.Version
336307
return raw.ToNpmPackage(), exactVersionCacheKey, nil
337308
})

0 commit comments

Comments
 (0)