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