func LatestModuleVersions()

in internal/fetch/latest.go [48:147]


func LatestModuleVersions(ctx context.Context, modulePath string, prox *proxy.Client, hasGoMod func(v string) (bool, error)) (info *internal.LatestModuleVersions, err error) {
	defer derrors.WrapStack(&err, "LatestModuleVersions(%q)", modulePath)

	defer func() {
		if info != nil {
			log.Debugf(ctx, "LatestModuleVersions(%q) => (raw=%q cooked=%q, %v)", modulePath, info.RawVersion, info.CookedVersion, err)
		}
	}()

	if modulePath == stdlib.ModulePath {
		vs, err := stdlib.Versions()
		if err != nil {
			return nil, err
		}
		latest := version.LatestOf(vs)
		if latest == "" {
			return nil, errors.New("no versions for stdlib")
		}
		return internal.NewLatestModuleVersions(modulePath, latest, latest, "", []byte("module std"))
	}

	// Remember calls to hasGoMod because they can be expensive.
	hasGoModResults := map[string]bool{}
	hasGoModFunc := func(v string) (bool, error) {
		result, ok := hasGoModResults[v]
		if ok {
			return result, nil
		}
		err := derrors.NotFound
		if hasGoMod != nil {
			result, err = hasGoMod(v)
		}
		if err != nil && !errors.Is(err, derrors.NotFound) {
			return false, err
		}
		if err != nil {
			// hasGoMod doesn't know; download the zip.
			zr, err := prox.Zip(ctx, modulePath, v)
			if err != nil {
				return false, err
			}
			contentsDir, err := fs.Sub(zr, modulePath+"@"+v)
			if err != nil {
				return false, err
			}
			result = hasGoModFile(contentsDir)
		}
		hasGoModResults[v] = result
		return result, nil
	}

	// Get the raw latest version.
	versions, err := prox.Versions(ctx, modulePath)
	if err != nil {
		return nil, err
	}
	latestInfo, err := prox.Info(ctx, modulePath, version.Latest)
	if errors.Is(err, derrors.NotFound) || errors.Is(err, derrors.NotFetched) {
		// No information from the proxy, but not a showstopper either; we can
		// proceed with the result of the list endpoint.
	} else if err != nil {
		return nil, err
	} else {
		versions = append(versions, latestInfo.Version)
	}
	if len(versions) == 0 {
		// No tagged versions, and nothing from @latest: no version information.
		return nil, nil
	}
	rawLatest, err := version.LatestVersion(versions, hasGoModFunc)
	if err != nil {
		return nil, err
	}

	// Get the go.mod file at the raw latest version.
	modBytes, err := prox.Mod(ctx, modulePath, rawLatest)
	if err != nil {
		// Something's wrong with the go.mod file, so assume a minimal one instead of failing.
		log.Warningf(ctx, "proxy.Mod(%q, %q): %v; using minimal go.mod for latest version info",
			modulePath, rawLatest)
		modBytes = []byte(fmt.Sprintf("module %s", modulePath))
	}
	lmv, err := internal.NewLatestModuleVersions(modulePath, rawLatest, "", "", modBytes)
	if err != nil {
		// An error here means a bad go.mod file.
		return nil, fmt.Errorf("%v: %w", err, derrors.BadModule)
	}

	// Get the cooked latest version by disallowing retracted versions.
	unretractedVersions := version.RemoveIf(versions, lmv.IsRetracted)
	if len(versions) == len(unretractedVersions) {
		lmv.CookedVersion = lmv.RawVersion
	} else {
		lmv.CookedVersion, err = version.LatestVersion(unretractedVersions, hasGoModFunc)
		if err != nil {
			return nil, err
		}
	}
	return lmv, nil
}