Skip to content

📋 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 its List method to get []*Package, and traverse the Name field for output and exact string matching.
  • 🔗 Corresponding SDK method: (*Repository).List in the pkg/repository package.
  • 💡 Difference from download_index: download_index uses the package-level function repository.DownloadIndex to pull back raw JSON bytes, suitable for persisting to disk / mirror initialization; this example goes through the Repository instance method, which returns an already-parsed Package struct slice, suitable for direct consumption in a program (display, filter, search).

💻 Full Code

go
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 like repository.NewRepository(options) to actually inject options into repo (the example omits the injection detail for simplicity).
  • 🏗️ Creating the client instance: repo := &repository.Repository{} obtains a repository client. Methods like List hang off *Repository, so you must take a pointer.
  • 🌐 Issuing the list request: repo.List(ctx) internally requests the list.json endpoint and deserializes the JSON into []*Package. The caller gets already-structured data and doesn't need to handle encoding/json themselves.
  • 🔢 Sampled printing: first take len(packages) to see the total, then guard the loop with a maxPrint cap 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 packages comparing pkg.Name == searchTerm, paired with found >= 5 to break early, demonstrating the plainest form of "in-memory search on an already-loaded list."
  • 🛡️ Error handling: when List returns an error, print and return directly to avoid dereferencing a nil slice; in production, swap in structured logging or retry logic.

▶️ How to Run

Run directly from the example directory:

bash
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 NamePackageEndpointDoc Link
(*Repository).Listpkg/repositoryGET /packages/list.json/sdk/packagist/methods/list-packages

📝 Note: Repository.List and PackagistClient.ListPackages both hit the same list.json endpoint behind the scenes. The difference is that Repository goes 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 the Package struct field definitions.

🚀 Going Further

  • ⏱️ Add timeout control: replace the bare context.Background() with ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) and defer 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, use PackagistClient.ListPackagesByType to filter server-side and reduce full-set transfer.
  • 🔎 Build an in-memory index: traverse packages once and build a map[string]*Package keyed by pkg.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.GetPackage to pull each package's metadata — be sure to add a token-bucket rate limiter to respect Packagist's rate limits.
  • 💾 Cache the list: serialize packages to disk (see DownloadIndexToFile in download_index); on the next startup, load the cache first and then incrementally refresh as needed, reducing dependence on the remote API.

Released under the MIT License