Skip to content

🔍 cli_inspection — 包检查与依赖溯源

本示例演示如何用 Composer Skills 在本地 PHP 项目里查看已安装包信息、绘制依赖树、做 why/why-not 依赖溯源,并检查资金与许可证合规情况。

🎯 示例定位

这个示例在学什么:

  • 📋 如何用 ShowAllPackages() / ShowPackage() 查看项目里装了哪些包、单个包的详细信息
  • 🌳 如何用 ShowDependencyTree() / ShowReverseDependencies() 正向画依赖树、反向追溯谁依赖了我
  • ⏳ 如何用 OutdatedPackages() / OutdatedPackagesDirect() 找出过时包,区分「全部」与「仅直接依赖」两种口径
  • ❓ 如何用 WhyPackage() / WhyNotPackage() 回答「为什么装了它」和「为什么装不上某个版本」两个高频排错问题
  • 💰 如何用 Fund() / FundWithJSON() / HasFunding() 拿到资金捐赠链接,并做 JSON 结构化处理
  • 📜 如何用 Licenses() / LicensesWithFormat() / CheckLicenses() 输出许可证清单并做兼容性检查
  • 🔎 如何用 Search() / Suggests() 在项目内搜索包、查看 suggest 建议项

它对应的 SDK 方法都属于 pkg/composer 包(Composer 类型,本质是本地 Composer CLI 的 Go 封装,需要本地安装 PHP 和 Composer):

用途方法
查看包列表 / 单包详情ShowAllPackages()ShowPackage(packageName string)
依赖树 / 反向依赖ShowDependencyTree(packageName string)ShowReverseDependencies(packageName string)
过时包OutdatedPackages()OutdatedPackagesDirect()
why / why-not 分析WhyPackage(packageName string)WhyNotPackage(packageName, version string)
资金信息Fund()FundWithJSON()HasFunding()
许可证Licenses()LicensesWithFormat(format string)CheckLicenses()
搜索 / 建议Search(query string)Suggests()

💻 完整代码

示例文件 examples/cli_inspection/01_show_why_fund.go 包含四个独立函数,下面按职责分块贴出关键逻辑。

go
package cli_inspection

import (
	"fmt"
	"log"

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

// Example01ShowPackages 演示如何查看包信息、依赖树与过时包
func Example01ShowPackages() {
	c, err := composer.New(composer.DefaultOptions())
	if err != nil {
		log.Fatalf("无法创建Composer实例: %v", err)
	}
	c.SetWorkingDir("/path/to/project")

	// 显示所有已安装的包
	output, err := c.ShowAllPackages()
	if err != nil {
		log.Printf("获取包列表失败: %v", err)
	} else {
		fmt.Println(output)
	}

	// 显示特定包的详细信息
	output, err = c.ShowPackage("symfony/console")
	// ...错误处理后输出 output

	// 显示依赖树
	output, err = c.ShowDependencyTree("symfony/console")
	// ...错误处理后输出 output

	// 显示反向依赖(哪些包依赖此包)
	output, err = c.ShowReverseDependencies("symfony/polyfill-mbstring")
	// ...错误处理后输出 output

	// 查看过时的包(全部 / 仅直接依赖)
	output, _ = c.OutdatedPackages()
	output, _ = c.OutdatedPackagesDirect()
}

// Example02WhyAnalysis 演示 why/why-not 分析
func Example02WhyAnalysis() {
	c, _ := composer.New(composer.DefaultOptions())
	c.SetWorkingDir("/path/to/project")

	// 解释为什么安装了某个包
	output, err := c.WhyPackage("symfony/polyfill-mbstring")
	// ...错误处理后输出 output

	// 解释为什么不能安装某个版本
	output, err = c.WhyNotPackage("symfony/console", "v6.0.0")
	// ...错误处理后输出 output
}

// Example03FundAndLicenses 演示资金和许可证信息
func Example03FundAndLicenses() {
	c, _ := composer.New(composer.DefaultOptions())
	c.SetWorkingDir("/path/to/project")

	// 查看资金信息(纯文本 / JSON / 是否有资金)
	output, _ := c.Fund()
	fundingInfo, _ := c.FundWithJSON()
	for _, info := range fundingInfo {
		if info.Funding {
			fmt.Printf("  包: %s, URL: %v\n", info.Name, info.URLs)
		}
	}
	hasFunding, _ := c.HasFunding()

	// 查看许可证信息(默认 / 指定格式 / 兼容性检查)
	output, _ = c.Licenses()
	output, _ = c.LicensesWithFormat("json")
	output, _ = c.CheckLicenses()
}

// Example04Search 演示搜索功能
func Example04Search() {
	c, _ := composer.New(composer.DefaultOptions())
	c.SetWorkingDir("/path/to/project")

	// 搜索包
	output, err := c.Search("logger")
	// ...错误处理后输出 output

	// 查看建议安装的包
	err = c.Suggests()
}

🧩 代码讲解

  • 🏗️ 创建实例composer.New(composer.DefaultOptions()) 返回一个 *Composer,它内部会探测本地 composer 可执行文件;DefaultOptions() 提供合理默认值。创建失败用 log.Fatalf 直接退出,因为没实例后面什么都做不了。
  • 📂 设置工作目录c.SetWorkingDir("/path/to/project") 把后续所有命令的 --working-dir 锚定到目标 PHP 项目,相当于在 cd /path/to/project 之后跑 composer。示例里是占位路径,实际运行需替换成真实项目。
  • 📋 查看包列表ShowAllPackages() 对应 composer show,返回项目里全部已安装包(含 vendor)的纯文本表格;ShowPackage("symfony/console") 对应 composer show symfony/console,输出该包的版本、类型、源、依赖等详细信息。
  • 🌳 依赖树ShowDependencyTree("symfony/console") 对应 composer show --tree symfony/console,递归打印该包的一整棵依赖树;ShowReverseDependencies("symfony/polyfill-mbstring") 则反向回答「谁在依赖 polyfill-mbstring」,是排查「这个间接依赖能不能去掉」的关键。
  • 过时检测OutdatedPackages() 返回所有有新版本的包,OutdatedPackagesDirect() 只看 require 里写明的直接依赖。两者口径不同——前者告诉你「全量有多少要升级」,后者告诉你「我自己声明的有多少要升级」,后者通常更有行动价值。
  • why 溯源WhyPackage("symfony/polyfill-mbstring") 对应 composer why,输出依赖链上谁把它拉进来的;WhyNotPackage("symfony/console", "v6.0.0") 对应 composer why-not,解释为什么装不上指定版本(往往是某条约束卡住了)。这两个是日常排错最高频的命令。
  • 💰 资金信息Fund() 输出纯文本捐赠链接;FundWithJSON() 返回结构化切片,可遍历过滤 info.Funding == true 的包拿到名称和 URL;HasFunding() 给一个布尔值,适合在 CI 里做「是否有包可以赞助」的提醒。
  • 📜 许可证Licenses() 默认表格输出,LicensesWithFormat("json") 切到 JSON 便于程序解析,CheckLicenses() 触发兼容性检查,可在上线前卡一道许可证合规。
  • 🔎 搜索与建议Search("logger") 对应 composer search logger,在 Packagist 上按关键字找包;Suggests() 对应 composer suggests,打印各包 suggest 字段里推荐的可选增强包。注意 Suggests() 返回的是 error 而非字符串,它直接把输出写到 composer 的 stdout。
  • 🛡️ 错误处理:除创建实例外,其余调用都用 log.Printf 记录错误后继续,而非 Fatal——因为检查类命令之间相互独立,一个失败不该中断整个巡检流程。

▶️ 运行方式

该示例是 package cli_inspection 下的多个 Example* 函数,没有 main 入口,需自行写一个 main.go 调用,或用 Go 的测试/示例机制运行。最简方式:

bash
cd examples/cli_inspection
go run 01_show_why_fund.go   # 需补充 main 包入口,或并入 main.go 调用上述 Example 函数

运行前需满足:

  • 🐘 本地已安装 PHP 与 Composer(否则 SDK 会触发自动安装逻辑)
  • 📁 把 SetWorkingDir("/path/to/project") 里的路径替换成你真实的、已执行过 composer install 的 PHP 项目
  • 🔧 部分命令(如 OutdatedPackages)会联网比对 Packagist 最新版本,确保网络可用

🔗 涉及的 SDK 方法

方法所属包文档
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

🚀 进阶

  • 🤖 一键巡检:把四个 Example* 函数串成一个 Inspect() 流水线,先 ShowAllPackagesOutdatedPackagesCheckLicenses,最后把纯文本输出拼成一份 Markdown 巡检报告,挂到 CI artifact。
  • 📊 过时包可视化OutdatedPackages 的纯文本不好解析,可改用底层带 --format=json 的结构化方法(如 get-outdated-info),把当前版本、最新版本、是否抽象差量塞进表格,按「落后大版本数」排序突出风险包。
  • 🧭 反向依赖图:把 ShowReverseDependencies 的输出解析成节点-边,配合 graphviz 画出「如果删除 polyfill-mbstring 会影响谁」的影响范围图,做删包决策前的安全检查。
  • why-not 自动归因:批量遍历一组目标版本调用 WhyNotPackage,把失败原因聚合成「约束冲突来源包」排行,定位最常卡版本的「瓶颈依赖」。
  • 💾 资金 + 许可证落地FundWithJSON() 的结果直接 json.Marshal 写文件,作为每周「可赞助包清单」;LicensesWithFormat("json") 配合 SPDX 白名单做自动合规扫描,发现非白名单许可证就在 CI 里 fail。
  • 🔎 搜索 + 建议联动:用 Search 找候选包后,再调 Packagist 侧的 GetPackage 拉下载量和 Star,做个「候选包选型打分器」,把 Suggests() 输出作为加分项纳入评分。

基于 MIT 许可证发布