📋 list_packages — List Packages
This example demonstrates how to fetch the list of all available packages in the Composer repository (Packagist), and perform basic traversal, sampled printing, and exact-match lookup on the returned structured package data.
🎯 Example Positioning
list_packages is the 3rd example in the Packagist API Remote Operations series, following download_index. It focuses on "what you can do once you have the package list": instead of stopping at downloading raw bytes, it goes through a typed method to obtain a structured Package slice, then displays and searches it in memory.
- 📚 What you'll learn: how to initialize
repository.Repository, call itsListmethod to get[]*Package, and traverse theNamefield for output and exact string matching. - 🔗 Corresponding SDK method:
(*Repository).Listin thepkg/repositorypackage. - 💡 Difference from
download_index:download_indexuses the package-level functionrepository.DownloadIndexto pull back raw JSON bytes, suitable for persisting to disk / mirror initialization; this example goes through theRepositoryinstance method, which returns an already-parsedPackagestruct slice, suitable for direct consumption in a program (display, filter, search).
💻 Full Code
package main
import (
"context"
"fmt"
"github.com/scagogogo/composer-skills/pkg/repository"
)
func main() {
// Example 3: List packages in the Composer repository
// Shows how to fetch the list of all available packages and how to process this package information.
// Step 1: Initialize the repository client
options := &repository.Options{
ServerUrl: "https://packagist.org", // Use the official repository
}
fmt.Printf("Using server URL: %s\n", options.ServerUrl)
// Create the repository client instance
repo := &repository.Repository{}
_ = options // In a real project, inject options via a constructor
// Step 2: List all packages
fmt.Println("Fetching the package list...")
ctx := context.Background()
packages, err := repo.List(ctx)
if err != nil {
fmt.Printf("Failed to fetch the package list: %v\n", err)
return
}
// Step 3: Process the package list
fmt.Printf("Successfully fetched %d packages\n", len(packages))
// Print the names of the first 10 packages
fmt.Println("\nFirst 10 packages:")
maxPrint := 10
if len(packages) < maxPrint {
maxPrint = len(packages)
}
for i := 0; i < maxPrint; i++ {
fmt.Printf(" %d. %s\n", i+1, packages[i].Name)
}
// Step 4: Search packages by name (exact match example)
searchTerm := "symfony/console"
fmt.Printf("\nSearching for packages containing '%s':\n", searchTerm)
found := 0
for _, pkg := range packages {
if found >= 5 {
break // Only show the first 5 matching results
}
if pkg.Name == searchTerm {
fmt.Printf(" Found exact match: %s\n", pkg.Name)
found++
}
}
if found == 0 {
fmt.Printf(" No exact match found for '%s'\n", searchTerm)
}
// Example output:
// Using server URL: https://packagist.org
// Fetching the package list...
// Successfully fetched 25000 packages
//
// First 10 packages:
// 1. symfony/polyfill
// 2. symfony/console
// ...
//
// Searching for packages containing 'symfony/console':
// Found exact match: symfony/console
}🧩 Code Walkthrough
- 🧱 Constructing repository options:
repository.Options{ServerUrl: ...}specifies the target repository URL. The example uses a literal construction; in a real project, use a constructor likerepository.NewRepository(options)to actually injectoptionsintorepo(the example omits the injection detail for simplicity). - 🏗️ Creating the client instance:
repo := &repository.Repository{}obtains a repository client. Methods likeListhang off*Repository, so you must take a pointer. - 🌐 Issuing the list request:
repo.List(ctx)internally requests thelist.jsonendpoint and deserializes the JSON into[]*Package. The caller gets already-structured data and doesn't need to handleencoding/jsonthemselves. - 🔢 Sampled printing: first take
len(packages)to see the total, then guard the loop with amaxPrintcap to avoid flooding the screen when the list is long; this is a common pattern for handling large API return values. - 🔍 Exact-match lookup: iterate
packagescomparingpkg.Name == searchTerm, paired withfound >= 5tobreakearly, demonstrating the plainest form of "in-memory search on an already-loaded list." - 🛡️ Error handling: when
Listreturns anerror, print andreturndirectly to avoid dereferencing a nil slice; in production, swap in structured logging or retry logic.
▶️ How to Run
Run directly from the example directory:
cd /home/cc11001100/github/scagogogo/composer-skills/examples/list_packages
go run main.go⚠️ This example makes real Packagist API calls, and the returned package list is large (tens of thousands of entries). Avoid running it at high frequency, and ensure adequate network and memory.
📚 SDK Methods Involved
| Method Name | Package | Endpoint | Doc Link |
|---|---|---|---|
(*Repository).List | pkg/repository | GET /packages/list.json | /sdk/packagist/methods/list-packages |
📝 Note:
Repository.ListandPackagistClient.ListPackagesboth hit the samelist.jsonendpoint behind the scenes. The difference is thatRepositorygoes through the repository abstraction layer and returns[]*repository.Package; the link above points to the typed-method docs for that endpoint, where you can cross-reference thePackagestruct field definitions.
🚀 Going Further
- ⏱️ Add timeout control: replace the bare
context.Background()withctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)anddefer cancel()to prevent the whole process from hanging when the network stalls. - 🏷️ Filter by vendor/type: upgrade the exact match to
strings.HasPrefix(pkg.Name, vendor+"/")for vendor-based filtering; if you need to filter by type, usePackagistClient.ListPackagesByTypeto filter server-side and reduce full-set transfer. - 🔎 Build an in-memory index: traverse
packagesonce and build amap[string]*Packagekeyed bypkg.Name; subsequent lookups drop from O(n) to O(1), suitable for scenarios that need repeated retrieval. - 🧵 Batch detail enrichment: after getting the package-name list, use a worker pool to concurrently call
PackagistClient.GetPackageto pull each package's metadata — be sure to add a token-bucket rate limiter to respect Packagist's rate limits. - 💾 Cache the list: serialize
packagesto disk (seeDownloadIndexToFileindownload_index); on the next startup, load the cache first and then incrementally refresh as needed, reducing dependence on the remote API.