🔍 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 viewsuggestentries
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):
| Purpose | Method |
|---|---|
| View package list / single-package details | ShowAllPackages(), ShowPackage(packageName string) |
| Dependency tree / reverse dependencies | ShowDependencyTree(packageName string), ShowReverseDependencies(packageName string) |
| Outdated packages | OutdatedPackages(), OutdatedPackagesDirect() |
| why / why-not analysis | WhyPackage(packageName string), WhyNotPackage(packageName, version string) |
| Funding info | Fund(), FundWithJSON(), HasFunding() |
| Licenses | Licenses(), LicensesWithFormat(format string), CheckLicenses() |
| Search / suggests | Search(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.
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*Composerthat internally probes for the localcomposerexecutable;DefaultOptions()provides sensible defaults. On creation failure, uselog.Fatalfto exit directly, because nothing can be done without an instance. - 📂 Set the working directory:
c.SetWorkingDir("/path/to/project")anchors the--working-dirof all subsequent commands to the target PHP project, equivalent to running composer aftercd /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 tocomposer showand returns a plain-text table of all installed packages in the project (including vendor);ShowPackage("symfony/console")corresponds tocomposer show symfony/consoleand outputs that package's version, type, source, dependencies, and other details. - 🌳 Dependency tree:
ShowDependencyTree("symfony/console")corresponds tocomposer show --tree symfony/consoleand 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, andOutdatedPackagesDirect()looks only at the direct dependencies declared inrequire. 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 tocomposer whyand outputs who pulled it in along the dependency chain;WhyNotPackage("symfony/console", "v6.0.0")corresponds tocomposer why-notand 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, filteringinfo.Funding == trueto 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, andCheckLicenses()triggers a compatibility check — useful as a license-compliance gate before release. - 🔎 Search and suggests:
Search("logger")corresponds tocomposer search loggerand searches Packagist by keyword;Suggests()corresponds tocomposer suggestsand prints the optional enhancement packages recommended in each package'ssuggestfield. Note thatSuggests()returns anerrorrather than a string — it writes directly to composer's stdout. - 🛡️ Error handling: apart from creating the instance, all other calls use
log.Printfto record errors and continue rather thanFatal— 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:
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 aboveBefore 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 runcomposer install - 🔧 Some commands (like
OutdatedPackages) query Packagist for the latest version online — make sure the network is available
🔗 SDK Methods Involved
| Method | Package | Docs |
|---|---|---|
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 anInspect()pipeline — firstShowAllPackages, thenOutdatedPackages, thenCheckLicenses, and finally concatenate the plain-text outputs into a Markdown inspection report attached as a CI artifact. - 📊 Outdated-package visualization: the plain text from
OutdatedPackagesis hard to parse; switch to the underlying structured method with--format=json(likeget-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
ShowReverseDependenciesinto 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.Marshalthe results ofFundWithJSON()directly to a file as a weekly "sponsorable packages list"; pairLicensesWithFormat("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, callGetPackageon the Packagist side to pull downloads and stars, and build a "candidate-package selection scorer" — fold the output ofSuggests()into the score as a bonus.