Skip to content

🛠️ 包管理

对 Packagist 上的包执行写操作:创建、编辑、更新(触发重新抓取)。这三个方法都需要 API 凭证(Packagist 用户名 + API token),用于把你的仓库注册到 Packagist 或在仓库地址变更后更新注册。

需要凭证

三个方法都要求客户端通过 WithAPICredentials 配置了用户名与 API token,否则直接返回错误 "API credentials are required for ..."。凭证会作为 URL 查询参数发出,请从环境变量读取,勿硬编码。

何时使用

  • 🛠️ 发布新包:把你的 GitHub/GitLab 仓库注册到 Packagist,让它开始抓取版本。
  • 📦 仓库迁移:包的源码仓库地址变了,用 EditPackage 更新注册的仓库 URL。
  • 🔄 强制刷新:仓库已推新 tag 但 Packagist 还没更新,用 UpdatePackage 触发重新抓取。
  • 🤖 自动化发布流水线:发版后自动通知 Packagist 更新。

数据模型

请求与响应结构定义在 pkg/domain/create_package.go

PackageCreateRequest / Response

go
type PackageCreateRequest struct {
    Repository string `json:"repository"`
}

type PackageCreateResponse struct {
    Status string `json:"status"`
}
字段类型说明
PackageCreateRequest.Repositorystring要注册的仓库 URL(如 https://github.com/vendor/package
PackageCreateResponse.Statusstring操作状态,如 success

PackageEditRequest / Response

go
type PackageEditRequest struct {
    Repository string `json:"repository"`
}

type PackageEditResponse struct {
    Status string `json:"status"`
}
字段类型说明
PackageEditRequest.Repositorystring新的仓库 URL
PackageEditResponse.Statusstring操作状态

PackageUpdateRequest / Response

go
type PackageUpdateRequest struct {
    Repository string `json:"repository"`
}

type PackageUpdateResponse struct {
    Status string `json:"status"`
    Jobs   []string `json:"jobs,omitempty"`
}
字段类型说明
PackageUpdateRequest.Repositorystring要更新的仓库 URL
PackageUpdateResponse.Statusstring操作状态
PackageUpdateResponse.Jobs[]string触发的后台作业 ID 列表,可用于跟踪抓取进度

CreatePackage

🛠️ 在 Packagist 上创建(注册)一个新包。对应 POST https://packagist.org/api/create-package?username={user}&apiToken={token}

签名

go
func (c *ComposerClient) CreatePackage(ctx context.Context, request *domain.PackageCreateRequest) (*domain.PackageCreateResponse, error)

参数

参数类型说明
ctxcontext.Context上下文,用于超时与取消
request*domain.PackageCreateRequestRepository 仓库 URL

返回值

类型说明
结果*domain.PackageCreateResponseStatus
错误error凭证缺失、HTTP 失败、非 200、JSON 解析失败时返回

示例

go
package main

import (
    "context"
    "fmt"
    "log"
    "os"
    "time"

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

func main() {
    c := client.NewComposerClient(
        30*time.Second,
        client.WithAPICredentials(
            os.Getenv("PACKAGIST_USERNAME"),
            os.Getenv("PACKAGIST_API_TOKEN"),
        ),
    )

    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()

    resp, err := c.CreatePackage(ctx, &domain.PackageCreateRequest{
        Repository: "https://github.com/myorg/my-package",
    })
    if err != nil {
        log.Fatalf("创建包失败: %v", err)
    }
    fmt.Printf("状态: %s\n", resp.Status)
}

非空错误信息

resp.StatusCode != 200 时,SDK 会把响应体作为字符串塞进 error("failed to create package: %s"),便于看到 Packagist 返回的具体原因(如「仓库已被其他包注册」)。


EditPackage

🛠️ 编辑已存在包的仓库地址。对应 PUT https://packagist.org/api/packages/{name}?username={user}&apiToken={token}

签名

go
func (c *ComposerClient) EditPackage(ctx context.Context, packageName string, request *domain.PackageEditRequest) (*domain.PackageEditResponse, error)

参数

参数类型说明
ctxcontext.Context上下文
packageNamestring要编辑的包名(vendor/package
request*domain.PackageEditRequest含新的 Repository 仓库 URL

返回值

类型说明
结果*domain.PackageEditResponseStatus
错误error凭证缺失、HTTP 失败、非 200、JSON 解析失败时返回

示例

go
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

resp, err := c.EditPackage(ctx, "myorg/my-package", &domain.PackageEditRequest{
    Repository: "https://github.com/myorg/my-package-new-location",
})
if err != nil {
    log.Fatalf("编辑包失败: %v", err)
}
fmt.Printf("状态: %s\n", resp.Status)

仓库迁移场景

当你的包源码从 GitHub 迁移到 GitLab(或换 org)后,包的 composer.json 里的仓库地址会变。用 EditPackage 把 Packagist 注册的仓库 URL 改成新地址,之后 Packagist 会从新仓库抓取版本。


UpdatePackage

🛠️ 触发 Packagist 重新抓取指定仓库(强制更新)。对应 POST https://packagist.org/api/update-package?username={user}&apiToken={token}

签名

go
func (c *ComposerClient) UpdatePackage(ctx context.Context, request *domain.PackageUpdateRequest) (*domain.PackageUpdateResponse, error)

参数

参数类型说明
ctxcontext.Context上下文
request*domain.PackageUpdateRequestRepository 仓库 URL

返回值

类型说明
结果*domain.PackageUpdateResponseStatusJobs(后台作业 ID)
错误error凭证缺失、HTTP 失败、非 200、JSON 解析失败时返回

示例

go
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

resp, err := c.UpdatePackage(ctx, &domain.PackageUpdateRequest{
    Repository: "https://github.com/myorg/my-package",
})
if err != nil {
    log.Fatalf("更新包失败: %v", err)
}
fmt.Printf("状态: %s\n", resp.Status)
fmt.Printf("作业: %v\n", resp.Jobs)

何时用 Update vs Edit

  • EditPackage:改仓库地址本身(包指向了不同的源码仓库)。
  • UpdatePackage:仓库地址没变,但想让 Packagist 立刻重新抓取(如刚 push 了新 tag,等不及自动抓取)。

UpdatePackage 返回的 Jobs 是 Packagist 后台抓取作业的 ID,可用于跟踪抓取是否完成。

进阶

三个写操作对比

方法HTTP路径作用
CreatePackagePOST/api/create-package注册新包
EditPackagePUT/api/packages/{name}修改包的仓库地址
UpdatePackagePOST/api/update-package触发重新抓取

自动化发布流水线

典型的「打 tag → 发布」流水线片段:

go
// 1. 推送 tag 后触发 Packagist 更新
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

resp, err := c.UpdatePackage(ctx, &domain.PackageUpdateRequest{
    Repository: "https://github.com/myorg/my-package",
})
if err != nil {
    log.Printf("触发更新失败: %v\n", err)
    return
}
log.Printf("已触发更新,作业: %v\n", resp.Jobs)

// 2. 轮询 GetPackage 确认新版本已收录
for i := 0; i < 10; i++ {
    time.Sleep(15 * time.Second)
    info, err := c.GetPackage("myorg/my-package")
    if err == nil {
        if _, ok := info.Package.Versions["v1.2.3"]; ok {
            log.Println("新版本已收录")
            break
        }
    }
}

🔗 相关

基于 MIT 许可证发布