🛠️ Package Management
Perform write operations on packages on Packagist: create, edit, update (trigger re-fetch). These three methods all require API credentials (Packagist username + API token), used to register your repository to Packagist or update registration after repository address changes.
Credentials Required
All three methods require the client to be configured with username and API token via WithAPICredentials, otherwise will directly return error `"API credentials are required for ...". Credentials are sent as URL query parameters, please read from environment variables, do not hardcode.
When to Use
- 🛠️ Publish new package: Register your GitHub/GitLab repository to Packagist, let it start fetching versions.
- 📦 Repository migration: Package source repository address changed, use
EditPackageto update registered repository URL. - 🔄 Force refresh: Repository has pushed new tag but Packagist hasn't updated yet, use
UpdatePackageto trigger re-fetch. - 🤖 Automated release pipeline: After release, automatically notify Packagist to update.
Data Models
Request and response structures are defined in pkg/domain/create_package.go.
PackageCreateRequest / Response
type PackageCreateRequest struct {
Repository string `json:"repository"`
}
type PackageCreateResponse struct {
Status string `json:"status"`
}| Field | Type | Description |
|---|---|---|
PackageCreateRequest.Repository | string | Repository URL to register (e.g., https://github.com/vendor/package) |
PackageCreateResponse.Status | string | Operation status, e.g., success |
PackageEditRequest / Response
type PackageEditRequest struct {
Repository string `json:"repository"`
}
type PackageEditResponse struct {
Status string `json:"status"`
}| Field | Type | Description |
|---|---|---|
PackageEditRequest.Repository | string | New repository URL |
PackageEditResponse.Status | string | Operation status |
PackageUpdateRequest / Response
type PackageUpdateRequest struct {
Repository string `json:"repository"`
}
type PackageUpdateResponse struct {
Status string `json:"status"`
Jobs []string `json:"jobs,omitempty"`
}| Field | Type | Description |
|---|---|---|
PackageUpdateRequest.Repository | string | Repository URL to update |
PackageUpdateResponse.Status | string | Operation status |
PackageUpdateResponse.Jobs | []string | Triggered backend job ID list, can be used to track fetch progress |
CreatePackage
🛠️ Create (register) a new package on Packagist. Corresponds to POST https://packagist.org/api/create-package?username={user}&apiToken={token}.
Signature
func (c *ComposerClient) CreatePackage(ctx context.Context, request *domain.PackageCreateRequest) (*domain.PackageCreateResponse, error)Parameters
| Parameter | Type | Description |
|---|---|---|
ctx | context.Context | Context for timeout and cancellation |
request | *domain.PackageCreateRequest | Contains Repository URL |
Return Values
| Value | Type | Description |
|---|---|---|
| Result | *domain.PackageCreateResponse | Contains Status |
| Error | error | Returned when credentials missing, HTTP failure, non-200, or JSON parsing failure |
Example
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("Failed to create package: %v", err)
}
fmt.Printf("Status: %s\n", resp.Status)
}Non-empty Error Message
When resp.StatusCode != 200, SDK will put response body as string into error ("failed to create package: %s"), to see specific reason returned by Packagist (e.g., "repository already registered by another package").
EditPackage
🛠️ Edit repository address of an existing package. Corresponds to PUT https://packagist.org/api/packages/{name}?username={user}&apiToken={token}.
Signature
func (c *ComposerClient) EditPackage(ctx context.Context, packageName string, request *domain.PackageEditRequest) (*domain.PackageEditResponse, error)Parameters
| Parameter | Type | Description |
|---|---|---|
ctx | context.Context | Context |
packageName | string | Package name to edit (vendor/package) |
request | *domain.PackageEditRequest | Contains new Repository URL |
Return Values
| Value | Type | Description |
|---|---|---|
| Result | *domain.PackageEditResponse | Contains Status |
| Error | error | Returned when credentials missing, HTTP failure, non-200, or JSON parsing failure |
Example
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("Failed to edit package: %v", err)
}
fmt.Printf("Status: %s\n", resp.Status)Repository Migration Scenario
When your package source migrates from GitHub to GitLab (or changes org), the repository address in package's composer.json will change. Use EditPackage to change the registered repository URL on Packagist, afterwards Packagist will fetch versions from new repository.
UpdatePackage
🛠️ Trigger Packagist to re-fetch specified repository (force update). Corresponds to POST https://packagist.org/api/update-package?username={user}&apiToken={token}.
Signature
func (c *ComposerClient) UpdatePackage(ctx context.Context, request *domain.PackageUpdateRequest) (*domain.PackageUpdateResponse, error)Parameters
| Parameter | Type | Description |
|---|---|---|
ctx | context.Context | Context |
request | *domain.PackageUpdateRequest | Contains Repository URL |
Return Values
| Value | Type | Description |
|---|---|---|
| Result | *domain.PackageUpdateResponse | Contains Status and Jobs (backend job IDs) |
| Error | error | Returned when credentials missing, HTTP failure, non-200, or JSON parsing failure |
Example
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("Failed to update package: %v", err)
}
fmt.Printf("Status: %s\n", resp.Status)
fmt.Printf("Jobs: %v\n", resp.Jobs)When to Use Update vs Edit
- EditPackage: Change repository address itself (package points to different source repository).
- UpdatePackage: Repository address unchanged, but want Packagist to re-fetch immediately (e.g., just pushed new tag, can't wait for automatic fetch).
UpdatePackage returns Jobs which are Packagist backend fetch job IDs, can be used to track whether fetch is complete.
Advanced Topics
Comparison of Three Write Operations
| Method | HTTP | Path | Purpose |
|---|---|---|---|
CreatePackage | POST | /api/create-package | Register new package |
EditPackage | PUT | /api/packages/{name} | Modify package repository address |
UpdatePackage | POST | /api/update-package | Trigger re-fetch |
Automated Release Pipeline
Typical "tag → release" pipeline fragment:
// 1. After pushing tag, trigger Packagist update
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("Failed to trigger update: %v\n", err)
return
}
log.Printf("Update triggered, jobs: %v\n", resp.Jobs)
// 2. Poll GetPackage to confirm new version is indexed
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("New version indexed")
break
}
}
}🔗 Related
- 🔌 Credentials configuration see WithAPICredentials.
- 📦 Confirm update result see GetPackage / GetPackageChanges.