initial
This commit is contained in:
commit
f1a649641f
15
.vscode/launch.json
vendored
Normal file
15
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Launch Package",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "auto",
|
||||
"program": "${fileDirname}"
|
||||
}
|
||||
]
|
||||
}
|
||||
211
cmd/git-package-cleanup/main.go
Normal file
211
cmd/git-package-cleanup/main.go
Normal file
@ -0,0 +1,211 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type PackageVersion struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type GitHubClient struct {
|
||||
token string
|
||||
owner string
|
||||
client *http.Client
|
||||
baseURL string
|
||||
}
|
||||
|
||||
func NewGitHubClient(token, owner string) *GitHubClient {
|
||||
return &GitHubClient{
|
||||
token: token,
|
||||
owner: owner,
|
||||
client: &http.Client{},
|
||||
baseURL: "https://api.github.com",
|
||||
}
|
||||
}
|
||||
|
||||
func (g *GitHubClient) makeRequest(method, url string) (*http.Response, error) {
|
||||
req, err := http.NewRequest(method, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Authorization", "Bearer "+g.token)
|
||||
req.Header.Set("Accept", "application/vnd.github+json")
|
||||
req.Header.Set("X-GitHub-Api-Version", "2022-11-28")
|
||||
|
||||
return g.client.Do(req)
|
||||
}
|
||||
|
||||
func (g *GitHubClient) getPackageVersions(packageName, packageType string) ([]PackageVersion, error) {
|
||||
url := fmt.Sprintf("%s/users/%s/packages/%s/%s/versions", g.baseURL, g.owner, packageType, packageName)
|
||||
|
||||
var allVersions []PackageVersion
|
||||
page := 1
|
||||
|
||||
for {
|
||||
pageURL := fmt.Sprintf("%s?page=%d&per_page=100", url, page)
|
||||
resp, err := g.makeRequest("GET", pageURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("request failed: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return nil, fmt.Errorf("API error %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
var versions []PackageVersion
|
||||
if err := json.NewDecoder(resp.Body).Decode(&versions); err != nil {
|
||||
return nil, fmt.Errorf("decode failed: %w", err)
|
||||
}
|
||||
|
||||
if len(versions) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
allVersions = append(allVersions, versions...)
|
||||
page++
|
||||
}
|
||||
|
||||
return allVersions, nil
|
||||
}
|
||||
|
||||
func (g *GitHubClient) deleteVersion(packageName, packageType string, versionID int) error {
|
||||
url := fmt.Sprintf("%s/users/%s/packages/%s/%s/versions/%d",
|
||||
g.baseURL, g.owner, packageType, packageName, versionID)
|
||||
|
||||
resp, err := g.makeRequest("DELETE", url)
|
||||
if err != nil {
|
||||
return fmt.Errorf("request failed: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return fmt.Errorf("delete failed %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func findVersionsToDelete(versions []PackageVersion, keepVersion string) []PackageVersion {
|
||||
var toDelete []PackageVersion
|
||||
found := false
|
||||
|
||||
// Find the version to keep and mark everything after it for deletion
|
||||
for i := len(versions) - 1; i >= 0; i-- {
|
||||
if versions[i].Name == keepVersion {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
toDelete = append([]PackageVersion{versions[i]}, toDelete...)
|
||||
}
|
||||
|
||||
if !found {
|
||||
fmt.Printf("Warning: Version '%s' not found in package versions\n", keepVersion)
|
||||
return nil
|
||||
}
|
||||
|
||||
return toDelete
|
||||
}
|
||||
|
||||
func getUserInput(prompt string) string {
|
||||
fmt.Print(prompt)
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
scanner.Scan()
|
||||
return strings.TrimSpace(scanner.Text())
|
||||
}
|
||||
|
||||
func confirmDeletion(count int) bool {
|
||||
response := getUserInput(fmt.Sprintf("Delete %d versions? (y/N): ", count))
|
||||
return strings.ToLower(response) == "y"
|
||||
}
|
||||
|
||||
func main() {
|
||||
token := os.Getenv("GITHUB_TOKEN")
|
||||
if token == "" {
|
||||
fmt.Println("Error: GITHUB_TOKEN environment variable is required")
|
||||
fmt.Println("Create a token with 'packages:read' and 'packages:delete' scopes")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
owner := getUserInput("Owner (username/org): ")
|
||||
if owner == "" {
|
||||
fmt.Println("Error: Owner is required")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
packageName := getUserInput("Package name: ")
|
||||
if packageName == "" {
|
||||
fmt.Println("Error: Package name is required")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
packageType := getUserInput("Package type [npm]: ")
|
||||
if packageType == "" {
|
||||
packageType = "npm"
|
||||
}
|
||||
|
||||
client := NewGitHubClient(token, owner)
|
||||
|
||||
fmt.Printf("Fetching versions for %s/%s...\n", owner, packageName)
|
||||
versions, err := client.getPackageVersions(packageName, packageType)
|
||||
if err != nil {
|
||||
fmt.Printf("Error fetching versions: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if len(versions) == 0 {
|
||||
fmt.Println("No versions found")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("\nFound %d versions:\n", len(versions))
|
||||
for i, v := range versions {
|
||||
fmt.Printf("%d. %s (ID: %d)\n", i+1, v.Name, v.ID)
|
||||
}
|
||||
|
||||
keepVersion := getUserInput("\nVersion to keep (and newer): ")
|
||||
if keepVersion == "" {
|
||||
fmt.Println("Error: Version is required")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
toDelete := findVersionsToDelete(versions, keepVersion)
|
||||
if len(toDelete) == 0 {
|
||||
fmt.Println("No versions to delete")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("\nVersions to delete:\n")
|
||||
for _, v := range toDelete {
|
||||
fmt.Printf("- %s (ID: %d)\n", v.Name, v.ID)
|
||||
}
|
||||
|
||||
if !confirmDeletion(len(toDelete)) {
|
||||
fmt.Println("Cancelled")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("\nDeleting versions...")
|
||||
deleted := 0
|
||||
for _, v := range toDelete {
|
||||
if err := client.deleteVersion(packageName, packageType, v.ID); err != nil {
|
||||
fmt.Printf("Failed to delete %s: %v\n", v.Name, err)
|
||||
} else {
|
||||
fmt.Printf("Deleted %s\n", v.Name)
|
||||
deleted++
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("\nDeleted %d/%d versions\n", deleted, len(toDelete))
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user