Compare commits
No commits in common. "0e3cffd78dac6e524f1c356d2c15d8983e95bfc3" and "11335d572a1686551f5de8d96c515876e1db6b05" have entirely different histories.
0e3cffd78d
...
11335d572a
15
.vscode/launch.json
vendored
15
.vscode/launch.json
vendored
@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
// 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}"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -1,211 +0,0 @@
|
|||||||
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