blob: 013b041495c51b98f543b50fc8751d2fd9e9aa6a [file] [edit]
// 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
}