| // 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. |
| |
| // Command pkgsite-cli queries the pkg.go.dev API for information about |
| // Go packages and modules. |
| // |
| // For more information, see https://go.dev/blog/pkgsite-api. |
| // |
| // Usage: |
| // |
| // pkgsite-cli package <package>[@version] [flags] Show package details. |
| // pkgsite-cli module <module>[@version] [flags] Show module details. |
| // pkgsite-cli search <query> [flags] Search for packages. |
| package main |
| |
| import ( |
| "context" |
| "encoding/json" |
| "errors" |
| "flag" |
| "fmt" |
| "io" |
| "os" |
| "path/filepath" |
| "strings" |
| |
| "golang.org/x/pkgsite/cmd/internal/pkgsite-cli/client" |
| "golang.org/x/telemetry" |
| ) |
| |
| func main() { |
| cfg := telemetry.Config{ |
| ReportCrashes: true, |
| } |
| telemetry.Start(cfg) |
| |
| os.Exit(run(os.Args[1:], os.Stdout, os.Stderr)) |
| } |
| |
| func run(args []string, stdout, stderr io.Writer) int { |
| return dispatch(args, commands(), stdout, stderr) |
| } |
| |
| func commands() []*command { |
| var pf packageFlags |
| pkgFS := flag.NewFlagSet(filepath.Base(os.Args[0]), flag.ContinueOnError) |
| pf.register(pkgFS) |
| |
| var mf moduleFlags |
| modFS := flag.NewFlagSet(filepath.Base(os.Args[0])+" module", flag.ContinueOnError) |
| mf.register(modFS) |
| |
| var sf searchFlags |
| searchFS := flag.NewFlagSet(filepath.Base(os.Args[0])+" search", flag.ContinueOnError) |
| sf.register(searchFS) |
| |
| pkgRun := func(fs *flag.FlagSet, stdout, stderr io.Writer) int { return runPackage(fs, &pf, stdout, stderr) } |
| |
| const packageDoc = `Queries information about a specific Go package from pkg.go.dev. |
| By default, this prints basic metadata. Use flags to request additional |
| information such as exported symbols, reverse dependencies, or rendered documentation. |
| |
| When using -json, the output is a JSON object with the following structure: |
| |
| type packageResult struct { |
| Package *Package // package information |
| Symbols *PaginatedResponse[Symbol] // symbols (with -symbols) |
| ImportedBy *PackageImportedBy // reverse dependencies (with -imported-by) |
| } |
| |
| type Package struct { |
| Path string |
| Name string |
| Synopsis string |
| IsRedistributable bool |
| ModulePath string |
| Version string |
| IsLatest bool |
| IsStandardLibrary bool |
| GOOS string |
| GOARCH string |
| Docs string // rendered documentation (with -doc) |
| Imports []string // imports (with -imports) |
| Licenses []License // licenses (with -licenses) |
| } |
| |
| type License struct { |
| Types []string |
| FilePath string |
| } |
| ` |
| |
| const moduleDoc = `Queries information about a specific Go module from pkg.go.dev. |
| By default, this prints basic metadata. Use flags to request additional |
| information such as versions, vulnerabilities, or packages contained in the module. |
| |
| When using -json, the output is a JSON object with the following structure: |
| |
| type moduleResult struct { |
| Module *Module // module information |
| Versions *PaginatedResponse[VersionResponse] // versions (with -versions) |
| Vulns *PaginatedResponse[Vulnerability] // vulnerabilities (with -vulns) |
| Packages *PaginatedResponse[ModulePackageResponse] // packages (with -packages) |
| } |
| |
| type Module struct { |
| Path string |
| Version string |
| CommitTime time.Time |
| IsLatest bool |
| IsRedistributable bool |
| IsStandardLibrary bool |
| HasGoMod bool |
| RepoURL string |
| GoModContents string |
| Readme *Readme |
| Licenses []License |
| } |
| ` |
| |
| const searchDoc = `Searches for Go packages on pkg.go.dev matching the given query. |
| By default, this prints a list of matching packages with their synopsis. |
| |
| When using -json, the output is a JSON object with the following structure: |
| |
| type PaginatedResponse[SearchResult] struct { |
| Items []SearchResult |
| Total int |
| NextPageToken string |
| } |
| |
| type SearchResult struct { |
| PackagePath string |
| ModulePath string |
| Version string |
| Synopsis string |
| } |
| ` |
| |
| var cmds []*command |
| cmds = []*command{ |
| { |
| name: "package", |
| args: "<package>[@version]", |
| summary: "Show package details", |
| description: strings.TrimSpace(packageDoc), |
| flags: pkgFS, |
| run: pkgRun, |
| }, |
| { |
| name: "module", |
| args: "<module>[@version]", |
| summary: "Show module details", |
| description: strings.TrimSpace(moduleDoc), |
| flags: modFS, |
| run: func(fs *flag.FlagSet, stdout, stderr io.Writer) int { return runModule(fs, &mf, stdout, stderr) }, |
| }, |
| { |
| name: "search", |
| args: "<query>", |
| summary: "Search for packages", |
| description: strings.TrimSpace(searchDoc), |
| flags: searchFS, |
| run: func(fs *flag.FlagSet, stdout, stderr io.Writer) int { return runSearch(fs, &sf, stdout, stderr) }, |
| }, |
| { |
| name: "help", |
| summary: "Show this help message", |
| run: func(_ *flag.FlagSet, stdout, _ io.Writer) int { printUsage(stdout, cmds); return 0 }, |
| }, |
| { |
| name: "version", |
| summary: "Print version information", |
| run: func(_ *flag.FlagSet, stdout, _ io.Writer) int { fmt.Fprintln(stdout, versionInfo()); return 0 }, |
| }, |
| } |
| return cmds |
| } |
| |
| // splitPathVersion splits "path@version" into its components. |
| // If there is no @, version is empty. |
| func splitPathVersion(s string) (path, version string) { |
| path, version, _ = strings.Cut(s, "@") |
| return path, version |
| } |
| |
| // handleErr writes an error message. In JSON mode, the error is written |
| // to stdout as a JSON object so callers can parse it. In text mode, it |
| // goes to stderr. |
| func handleErr(stdout, stderr io.Writer, err error, jsonMode bool) { |
| if jsonMode { |
| aerr, ok := err.(*client.Error) |
| if !ok { |
| aerr = &client.Error{Code: 1, Message: err.Error()} |
| } |
| if errors.Is(err, context.DeadlineExceeded) { |
| aerr.Message = "request timed out" |
| aerr.Fixes = []string{"use the -timeout flag to increase the timeout", "reduce size of request by limiting items returned"} |
| } |
| writeJSON(stdout, stderr, aerr) |
| return |
| } |
| if errors.Is(err, context.DeadlineExceeded) { |
| fmt.Fprintln(stderr, "Error: request timed out; use the -timeout flag to increase it") |
| } else { |
| fmt.Fprintln(stderr, err) |
| } |
| } |
| |
| func writeJSON(stdout, stderr io.Writer, v any) int { |
| enc := json.NewEncoder(stdout) |
| enc.SetIndent("", " ") |
| if err := enc.Encode(v); err != nil { |
| fmt.Fprintln(stderr, err) |
| return 1 |
| } |
| return 0 |
| } |