🔍 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()流水线,先ShowAllPackages再OutdatedPackages再CheckLicenses,最后把纯文本输出拼成一份 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()输出作为加分项纳入评分。