Skip to content

🔍 cli_inspection — Package Inspection and Dependency Tracing

This example demonstrates how to use Composer Skills in a local PHP project to view installed package info, draw dependency trees, do why/why-not dependency tracing, and check funding and license compliance.

🎯 Example Positioning

What this example teaches:

  • 📋 How to use ShowAllPackages() / ShowPackage() to view which packages are installed in the project and the details of a single package
  • 🌳 How to use ShowDependencyTree() / ShowReverseDependencies() to draw a forward dependency tree and reverse-trace who depends on a package
  • ⏳ How to use OutdatedPackages() / OutdatedPackagesDirect() to find outdated packages, distinguishing between "all" and "direct dependencies only"
  • ❓ How to use WhyPackage() / WhyNotPackage() to answer two high-frequency troubleshooting questions: "why is it installed" and "why can't a certain version be installed"
  • 💰 How to use Fund() / FundWithJSON() / HasFunding() to get funding donation links and do JSON structured processing
  • 📜 How to use Licenses() / LicensesWithFormat() / CheckLicenses() to output a license list and do compatibility checks
  • 🔎 How to use Search() / Suggests() to search for packages within the project and view suggest entries

The corresponding SDK methods all belong to the pkg/composer package (the Composer type, essentially a Go wrapper around the local Composer CLI that requires PHP and Composer installed locally):

PurposeMethod
View package list / single-package detailsShowAllPackages(), ShowPackage(packageName string)
Dependency tree / reverse dependenciesShowDependencyTree(packageName string), ShowReverseDependencies(packageName string)
Outdated packagesOutdatedPackages(), OutdatedPackagesDirect()
why / why-not analysisWhyPackage(packageName string), WhyNotPackage(packageName, version string)
Funding infoFund(), FundWithJSON(), HasFunding()
LicensesLicenses(), LicensesWithFormat(format string), CheckLicenses()
Search / suggestsSearch(query string), Suggests()

💻 Full Code

The example file examples/cli_inspection/01_show_why_fund.go contains four independent functions; below, the key logic is presented by responsibility.

go
package cli_inspection

import (
	"fmt"
	"log"

	"github.com/scagogogo/composer-skills/pkg/composer"
)

// Example01ShowPackages demonstrates viewing package info, dependency trees, and outdated packages
func Example01ShowPackages() {
	c, err := composer.New(composer.DefaultOptions())
	if err != nil {
		log.Fatalf("Failed to create Composer instance: %v", err)
	}
	c.SetWorkingDir("/path/to/project")

	// Show all installed packages
	output, err := c.ShowAllPackages()
	if err != nil {
		log.Printf("Failed to get package list: %v", err)
	} else {
		fmt.Println(output)
	}

	// Show details for a specific package
	output, err = c.ShowPackage("symfony/console")
	// ...error handling, then print output

	// Show the dependency tree
	output, err = c.ShowDependencyTree("symfony/console")
	// ...error handling, then print output

	// Show reverse dependencies (which packages depend on this package)
	output, err = c.ShowReverseDependencies("symfony/polyfill-mbstring")
	// ...error handling, then print output

	// View outdated packages (all / direct dependencies only)
	output, _ = c.OutdatedPackages()
	output, _ = c.OutdatedPackagesDirect()
}

// Example02WhyAnalysis demonstrates why/why-not analysis
func Example02WhyAnalysis() {
	c, _ := composer.New(composer.DefaultOptions())
	c.SetWorkingDir("/path/to/project")

	// Explain why a package is installed
	output, err := c.WhyPackage("symfony/polyfill-mbstring")
	// ...error handling, then print output

	// Explain why a certain version can't be installed
	output, err = c.WhyNotPackage("symfony/console", "v6.0.0")
	// ...error handling, then print output
}

// Example03FundAndLicenses demonstrates funding and license info
func Example03FundAndLicenses() {
	c, _ := composer.New(composer.DefaultOptions())
	c.SetWorkingDir("/path/to/project")

	// View funding info (plain text / JSON / has-funding)
	output, _ := c.Fund()
	fundingInfo, _ := c.FundWithJSON()
	for _, info := range fundingInfo {
		if info.Funding {
			fmt.Printf("  Package: %s, URL: %v\n", info.Name, info.URLs)
		}
	}
	hasFunding, _ := c.HasFunding()

	// View license info (default / specified format / compatibility check)
	output, _ = c.Licenses()
	output, _ = c.LicensesWithFormat("json")
	output, _ = c.CheckLicenses()
}

// Example04Search demonstrates search functionality
func Example04Search() {
	c, _ := composer.New(composer.DefaultOptions())
	c.SetWorkingDir("/path/to/project")

	// Search for packages
	output, err := c.Search("logger")
	// ...error handling, then print output

	// View suggested packages
	err = c.Suggests()
}

🧩 Code Walkthrough

  • 🏗️ Create the instance: composer.New(composer.DefaultOptions()) returns a *Composer that internally probes for the local composer executable; DefaultOptions() provides sensible defaults. On creation failure, use log.Fatalf to exit directly, because nothing can be done without an instance.
  • 📂 Set the working directory: c.SetWorkingDir("/path/to/project") anchors the --working-dir of all subsequent commands to the target PHP project, equivalent to running composer after cd /path/to/project. This is a placeholder path in the example; replace it with a real project at runtime.
  • 📋 View the package list: ShowAllPackages() corresponds to composer show and returns a plain-text table of all installed packages in the project (including vendor); ShowPackage("symfony/console") corresponds to composer show symfony/console and outputs that package's version, type, source, dependencies, and other details.
  • 🌳 Dependency tree: ShowDependencyTree("symfony/console") corresponds to composer show --tree symfony/console and recursively prints the package's entire dependency tree; ShowReverseDependencies("symfony/polyfill-mbstring") answers in reverse "who depends on polyfill-mbstring" — key for investigating "can this indirect dependency be removed."
  • Outdated detection: OutdatedPackages() returns all packages with newer versions, and OutdatedPackagesDirect() looks only at the direct dependencies declared in require. The two differ in scope — the former tells you "how many of everything need upgrading," the latter tells you "how many of what I declared need upgrading." The latter is usually more actionable.
  • why tracing: WhyPackage("symfony/polyfill-mbstring") corresponds to composer why and outputs who pulled it in along the dependency chain; WhyNotPackage("symfony/console", "v6.0.0") corresponds to composer why-not and explains why a specific version can't be installed (usually some constraint blocks it). These are the most frequent troubleshooting commands in daily work.
  • 💰 Funding info: Fund() outputs plain-text donation links; FundWithJSON() returns a structured slice that you can iterate, filtering info.Funding == true to get names and URLs; HasFunding() gives a boolean, suitable for a "are there packages to sponsor" reminder in CI.
  • 📜 Licenses: Licenses() outputs a default table, LicensesWithFormat("json") switches to JSON for programmatic parsing, and CheckLicenses() triggers a compatibility check — useful as a license-compliance gate before release.
  • 🔎 Search and suggests: Search("logger") corresponds to composer search logger and searches Packagist by keyword; Suggests() corresponds to composer suggests and prints the optional enhancement packages recommended in each package's suggest field. Note that Suggests() returns an error rather than a string — it writes directly to composer's stdout.
  • 🛡️ Error handling: apart from creating the instance, all other calls use log.Printf to record errors and continue rather than Fatal — because inspection commands are independent of each other, and one failure shouldn't abort the whole inspection flow.

▶️ How to Run

This example consists of multiple Example* functions under package cli_inspection with no main entry point. You'll need to write your own main.go to call them, or use Go's test/example mechanism to run them. The simplest way:

bash
cd examples/cli_inspection
go run 01_show_why_fund.go   # Requires supplementing a main package entry, or merging into main.go to call the Example functions above

Before running, ensure:

  • 🐘 PHP and Composer are installed locally (otherwise the SDK triggers the auto-install logic)
  • 📁 Replace the path in SetWorkingDir("/path/to/project") with a real PHP project where you've run composer install
  • 🔧 Some commands (like OutdatedPackages) query Packagist for the latest version online — make sure the network is available

🔗 SDK Methods Involved

MethodPackageDocs
ShowAllPackages()pkg/composer (Composer)/sdk/composer/methods/show-all-packages
ShowPackage(packageName string)pkg/composer (Composer)/sdk/composer/methods/show-package
ShowDependencyTree(packageName string)pkg/composer (Composer)/sdk/composer/methods/show-dependency-tree
ShowReverseDependencies(packageName string)pkg/composer (Composer)/sdk/composer/methods/show-reverse-dependencies
OutdatedPackages()pkg/composer (Composer)/sdk/composer/methods/outdated-packages
OutdatedPackagesDirect()pkg/composer (Composer)/sdk/composer/methods/outdated-packages-direct
WhyPackage(packageName string)pkg/composer (Composer)/sdk/composer/methods/why-package
WhyNotPackage(packageName, version string)pkg/composer (Composer)/sdk/composer/methods/why-not-package
Fund()pkg/composer (Composer)/sdk/composer/methods/fund
FundWithJSON()pkg/composer (Composer)/sdk/composer/methods/fund-with-json
HasFunding()pkg/composer (Composer)/sdk/composer/methods/has-funding
Licenses()pkg/composer (Composer)/sdk/composer/methods/licenses
LicensesWithFormat(format string)pkg/composer (Composer)/sdk/composer/methods/licenses-with-format
CheckLicenses()pkg/composer (Composer)/sdk/composer/methods/check-licenses
Search(query string)pkg/composer (Composer)/sdk/composer/methods/search
Suggests()pkg/composer (Composer)/sdk/composer/methods/suggests
New(opts *Options)pkg/composer/sdk/composer/methods/default-options

🚀 Going Further

  • 🤖 One-shot inspection: chain the four Example* functions into an Inspect() pipeline — first ShowAllPackages, then OutdatedPackages, then CheckLicenses, and finally concatenate the plain-text outputs into a Markdown inspection report attached as a CI artifact.
  • 📊 Outdated-package visualization: the plain text from OutdatedPackages is hard to parse; switch to the underlying structured method with --format=json (like get-outdated-info), stuff current version, latest version, and whether abstract into a table, and sort by "major versions behind" to highlight risky packages.
  • 🧭 Reverse-dependency graph: parse the output of ShowReverseDependencies into nodes and edges, and with graphviz draw an "if I delete polyfill-mbstring, who's affected" blast-radius diagram as a safety check before removing a package.
  • why-not auto-attribution: batch-iterate a set of target versions calling WhyNotPackage, aggregate the failure reasons into a "constraint-conflict source package" ranking, and locate the "bottleneck dependency" that most often blocks versions.
  • 💾 Funding + license persistence: json.Marshal the results of FundWithJSON() directly to a file as a weekly "sponsorable packages list"; pair LicensesWithFormat("json") with an SPDX allowlist for automated compliance scanning, and fail CI when a non-allowlist license is found.
  • 🔎 Search + suggests integration: after finding candidate packages with Search, call GetPackage on the Packagist side to pull downloads and stars, and build a "candidate-package selection scorer" — fold the output of Suggests() into the score as a bonus.

Released under the MIT License