| // Copyright 2026 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| package main | |
| import ( | |
| "context" | |
| "flag" | |
| "fmt" | |
| "io" | |
| "golang.org/x/pkgsite/cmd/internal/pkgsite-cli/client" | |
| "golang.org/x/sync/errgroup" | |
| ) | |
| func runModule(fs *flag.FlagSet, m *moduleFlags, stdout, stderr io.Writer) int { | |
| if fs.NArg() != 1 { | |
| fmt.Fprintf(stderr, "Error: expected exactly 1 module argument, got %d\n", fs.NArg()) | |
| fs.Usage() | |
| return 2 | |
| } | |
| path, version := splitPathVersion(fs.Arg(0)) | |
| ctx, cancel := context.WithTimeout(context.Background(), m.timeout) | |
| defer cancel() | |
| c, err := client.New(m.server) | |
| if err != nil { | |
| handleErr(stdout, stderr, err, m.jsonOut) | |
| return 1 | |
| } | |
| c.PrintURLs = m.printURLs | |
| c.Output = stderr | |
| mod, err := c.GetModule(ctx, path, version, client.ModuleOptions{ | |
| Readme: m.readme, | |
| Licenses: m.licenses, | |
| }) | |
| if err != nil { | |
| handleErr(stdout, stderr, err, m.jsonOut) | |
| return 1 | |
| } | |
| result := moduleResult{Module: mod} | |
| var ( | |
| vers *client.PaginatedResponse[client.VersionResponse] | |
| vulns *client.PaginatedResponse[client.Vulnerability] | |
| pkgs *client.PaginatedResponse[client.ModulePackageResponse] | |
| ) | |
| g, gctx := errgroup.WithContext(ctx) | |
| if m.versions { | |
| g.Go(func() error { | |
| fetch := func(token string, limit int) (*client.PaginatedResponse[client.VersionResponse], error) { | |
| // Pass limit to API to limit server-side page size. | |
| return c.GetVersions(gctx, path, client.PaginationOptions{ | |
| Limit: limit, | |
| Token: token, | |
| }) | |
| } | |
| // Pass limit to AllItems to stop fetching when limit is reached. | |
| items, total, nextToken, err := client.AllItems(m.versionsToken, m.effectiveLimit(), fetch) | |
| if err != nil { | |
| if client.Is429(err) { | |
| vers = &client.PaginatedResponse[client.VersionResponse]{ | |
| Items: items, | |
| Total: total, | |
| NextPageToken: nextToken, | |
| } | |
| } | |
| return err | |
| } | |
| vers = &client.PaginatedResponse[client.VersionResponse]{ | |
| Items: items, | |
| Total: total, | |
| } | |
| return nil | |
| }) | |
| } | |
| if m.vulns { | |
| g.Go(func() error { | |
| fetch := func(token string, limit int) (*client.PaginatedResponse[client.Vulnerability], error) { | |
| // Pass limit to API to limit server-side page size. | |
| return c.GetVulns(gctx, path, version, client.PaginationOptions{ | |
| Limit: limit, | |
| Token: token, | |
| }) | |
| } | |
| // Pass limit to AllItems to stop fetching when limit is reached. | |
| items, total, nextToken, err := client.AllItems(m.vulnsToken, m.effectiveLimit(), fetch) | |
| if err != nil { | |
| if client.Is429(err) { | |
| vulns = &client.PaginatedResponse[client.Vulnerability]{ | |
| Items: items, | |
| Total: total, | |
| NextPageToken: nextToken, | |
| } | |
| } | |
| return err | |
| } | |
| vulns = &client.PaginatedResponse[client.Vulnerability]{ | |
| Items: items, | |
| Total: total, | |
| } | |
| return nil | |
| }) | |
| } | |
| if m.packages { | |
| g.Go(func() error { | |
| fetch := func(token string, limit int) (*client.PaginatedResponse[client.ModulePackageResponse], error) { | |
| // Pass limit to API to limit server-side page size. | |
| return c.GetPackages(gctx, path, version, client.PaginationOptions{ | |
| Limit: limit, | |
| Token: token, | |
| }) | |
| } | |
| // Pass limit to AllItems to stop fetching when limit is reached. | |
| items, total, nextToken, err := client.AllItems(m.packagesToken, m.effectiveLimit(), fetch) | |
| if err != nil { | |
| if client.Is429(err) { | |
| pkgs = &client.PaginatedResponse[client.ModulePackageResponse]{ | |
| Items: items, | |
| Total: total, | |
| NextPageToken: nextToken, | |
| } | |
| } | |
| return err | |
| } | |
| pkgs = &client.PaginatedResponse[client.ModulePackageResponse]{ | |
| Items: items, | |
| Total: total, | |
| } | |
| return nil | |
| }) | |
| } | |
| if err := g.Wait(); err != nil { | |
| if client.Is429(err) { | |
| result.Versions = vers | |
| result.Vulns = vulns | |
| result.Packages = pkgs | |
| return printPartialModuleResult(stdout, stderr, result, m.jsonOut) | |
| } | |
| handleErr(stdout, stderr, err, m.jsonOut) | |
| return 1 | |
| } | |
| result.Versions = vers | |
| result.Vulns = vulns | |
| result.Packages = pkgs | |
| if m.jsonOut { | |
| return writeJSON(stdout, stderr, result) | |
| } | |
| formatModule(stdout, result) | |
| return 0 | |
| } | |
| // moduleFlags are flags for the module subcommand. | |
| type moduleFlags struct { | |
| commonFlags | |
| readme bool | |
| licenses bool | |
| versions bool | |
| vulns bool | |
| packages bool | |
| versionsToken string | |
| vulnsToken string | |
| packagesToken string | |
| } | |
| func (f *moduleFlags) register(fs *flag.FlagSet) { | |
| f.commonFlags.register(fs) | |
| fs.BoolVar(&f.readme, "readme", false, "include README") | |
| fs.BoolVar(&f.licenses, "licenses", false, "show license information") | |
| fs.BoolVar(&f.versions, "versions", false, "list versions") | |
| fs.BoolVar(&f.vulns, "vulns", false, "list vulnerabilities") | |
| fs.BoolVar(&f.packages, "packages", false, "list packages") | |
| fs.StringVar(&f.versionsToken, "versions-token", "", "page token for versions pagination") | |
| fs.StringVar(&f.vulnsToken, "vulns-token", "", "page token for vulns pagination") | |
| fs.StringVar(&f.packagesToken, "packages-token", "", "page token for packages pagination") | |
| } | |
| func printPartialModuleResult(stdout, stderr io.Writer, result moduleResult, jsonMode bool) int { | |
| if jsonMode { | |
| writeJSON(stdout, stderr, result) | |
| } else { | |
| formatModule(stdout, result) | |
| fmt.Fprintln(stderr, "Warning: hit rate limit (429); results are incomplete.") | |
| } | |
| return 1 | |
| } |