Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions scripts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Scripts

## Bulk delete old Vercel deployments

`list-old-deployments.py` fetches all deployments for a given app via the Vercel API and writes UIDs of non-current-production deployments to `old-deployment-uids.txt`.

Check warning on line 5 in scripts/README.md

View workflow job for this annotation

GitHub Actions / spellcheck

Unknown word (uids)

### Prerequisites

- Vercel CLI installed and authenticated (`vercel login`)
- Access to the `saleorcommerce` scope

Check warning on line 10 in scripts/README.md

View workflow job for this annotation

GitHub Actions / spellcheck

Unknown word (saleorcommerce)

### Usage

1. Edit `APP_NAME` in the script to the target app.

2. Run the script to generate the list:

```bash
python3 scripts/list-old-deployments.py
```
Comment on lines +7 to +20
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This script uses Python 3.10+ typing syntax (list[dict], int | None). The README only says python3; please document the minimum required Python version (or switch to typing.List/Optional if you want broader compatibility).

Copilot uses AI. Check for mistakes.

This prints a table of all old deployments and writes their UIDs to `old-deployment-uids.txt` (one per line).

Check warning on line 22 in scripts/README.md

View workflow job for this annotation

GitHub Actions / spellcheck

Unknown word (uids)

3. Review the file and remove any deployments you want to keep (e.g. staging aliases):

```bash
vim old-deployment-uids.txt

Check warning on line 27 in scripts/README.md

View workflow job for this annotation

GitHub Actions / spellcheck

Unknown word (uids)
```

4. Delete all remaining deployments (Vercel will show the list and ask for confirmation):

```bash
vercel remove $(cat old-deployment-uids.txt) --scope saleorcommerce

Check warning on line 33 in scripts/README.md

View workflow job for this annotation

GitHub Actions / spellcheck

Unknown word (saleorcommerce)

Check warning on line 33 in scripts/README.md

View workflow job for this annotation

GitHub Actions / spellcheck

Unknown word (uids)
```
Comment on lines +30 to +34
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sed -i '' ... examples are BSD/macOS-specific; on GNU/Linux sed -i syntax differs. Please either mention the platform assumption or provide a Linux-compatible alternative command in the README so the instructions work for all contributors.

Copilot uses AI. Check for mistakes.

If there are too many for a single command, delete in batches of 50:

```bash
vercel remove $(head -50 old-deployment-uids.txt) --scope saleorcommerce

Check warning on line 39 in scripts/README.md

View workflow job for this annotation

GitHub Actions / spellcheck

Unknown word (saleorcommerce)

Check warning on line 39 in scripts/README.md

View workflow job for this annotation

GitHub Actions / spellcheck

Unknown word (uids)
# After confirming, remove processed lines:
sed -i '' '1,50d' old-deployment-uids.txt
# To skip ahead (e.g. next 50 starting at line 51):
vercel remove $(sed -n '51,100p' old-deployment-uids.txt) --scope saleorcommerce

Check warning on line 43 in scripts/README.md

View workflow job for this annotation

GitHub Actions / spellcheck

Unknown word (saleorcommerce)
```

5. Re-run the script to verify everything was cleaned up:

```bash
python3 scripts/list-old-deployments.py
```

### Notes

- Vercel performs safe deletion — it will skip the current production deployment automatically.
- Vercel warns about alias removals before confirming — review these warnings before accepting.
- The `-s` flag skips the confirmation prompt (`vercel remove ... -s`). Use `-y` to auto-confirm.
123 changes: 123 additions & 0 deletions scripts/list-old-deployments.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
#!/usr/bin/env python3
"""List all Vercel deployments for saleor-app-smtp that are older than the current production deployment."""
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The module docstring says this lists deployments for saleor-app-smtp and that they are "older than the current production deployment", but the script defaults APP_NAME to saleor-app-products-feed and the later filter includes all non-current-production deployments regardless of age. Please update the docstring to match the actual behavior (or adjust the filtering logic to match the docstring).

Suggested change
"""List all Vercel deployments for saleor-app-smtp that are older than the current production deployment."""
"""List Vercel deployments for the app defined in APP_NAME (default: saleor-app-products-feed), showing the current production deployment and all other non-current-production deployments."""

Copilot uses AI. Check for mistakes.

import json
import subprocess
import sys
from datetime import datetime, timezone

APP_NAME = "saleor-app-products-feed"
SCOPE = "saleorcommerce"


def vercel_api(endpoint: str) -> dict:
result = subprocess.run(
["vercel", "api", endpoint, "--scope", SCOPE],
capture_output=True,
text=True,
)
if result.returncode != 0:
print(f"Error calling vercel api: {result.stderr}", file=sys.stderr)
sys.exit(1)
return json.loads(result.stdout)


def fetch_all_deployments() -> list[dict]:
"""Fetch all deployments, paginating through all pages."""
all_deps = []
next_ts = None

while True:
url = f"/v6/deployments?app={APP_NAME}&limit=100"
if next_ts:
url += f"&until={next_ts}"

data = vercel_api(url)
deps = data.get("deployments", [])
pagination = data.get("pagination", {})

all_deps.extend(deps)
print(
f" Fetched {len(deps)} deployments (total: {len(all_deps)})",
file=sys.stderr,
)

next_ts = pagination.get("next")
if not next_ts or len(deps) == 0:
break

return all_deps


def format_ts(ts: int | None) -> str:
if not ts:
return "N/A"
return datetime.fromtimestamp(ts / 1000, tz=timezone.utc).strftime("%Y-%m-%d %H:%M")


def main():
print("Fetching all deployments...", file=sys.stderr)
all_deps = fetch_all_deployments()

# Find current production: newest deployment with target=production and readySubstate=PROMOTED
prod_deps = [
d
for d in all_deps
if d.get("target") == "production" and d.get("readySubstate") == "PROMOTED"
]
if not prod_deps:
print("ERROR: No current production deployment found!", file=sys.stderr)
sys.exit(1)

current_prod = max(prod_deps, key=lambda d: d["created"])
print("\nCurrent production deployment:", file=sys.stderr)
print(f" URL: {current_prod['url']}", file=sys.stderr)
print(f" UID: {current_prod['uid']}", file=sys.stderr)
print(f" Created: {format_ts(current_prod['created'])}", file=sys.stderr)
sha = current_prod.get("meta", {}).get("githubCommitSha", "N/A")
print(f" Commit: {sha[:8]}", file=sys.stderr)

# Filter: everything except current production, not already soft-deleted
old_deps = [
d
for d in all_deps
if d["uid"] != current_prod["uid"] and not d.get("softDeletedByRetention")
]

print(
f"\nOld deployments to delete: {len(old_deps)} (out of {len(all_deps)} total)\n",
file=sys.stderr,
)
Comment on lines +80 to +90
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

old_deps is described/printed as "Old deployments" but the filter currently removes only the current promoted production deployment. This will also include preview (and even newer-than-production) deployments, which makes it easy to accidentally delete recent previews someone may still be using. If the intent is truly "older than current production", filter by created < current_prod['created'] (and possibly also keep the newest preview per branch); otherwise rename the variable/output to reflect "non-current production" instead of "old".

Copilot uses AI. Check for mistakes.

# Print table to stdout
header = f"{'URL':<60} {'Target':<12} {'State':<10} {'Commit':<10} {'Branch':<20} {'Creator':<20} {'Created':<18} {'Message'}"
print(header)
print("-" * len(header))

for d in sorted(old_deps, key=lambda x: x["created"], reverse=True):
meta = d.get("meta", {})
sha = meta.get("githubCommitSha", "")[:8]
ref = meta.get("githubCommitRef", "")
msg = meta.get("githubCommitMessage", "")[:60].replace("\n", " ")
target = d.get("target") or "preview"
user = d.get("creator", {}).get("username", "?")
created = format_ts(d.get("created"))

print(
f"{d['url']:<60} {target:<12} {d['state']:<10} {sha:<10} {ref:<20} {user:<20} {created:<18} {msg}"
)

# Output deployment UIDs for bulk deletion (one per line)
uids = [d["uid"] for d in old_deps]
uid_file = "old-deployment-uids.txt"
with open(uid_file, "w") as f:
for uid in uids:
f.write(uid + "\n")
print(f"\nWrote {len(uids)} deployment UIDs to {uid_file}", file=sys.stderr)
print(f"To delete 50 at a time:", file=sys.stderr)
print(f" vercel remove $(head -50 {uid_file}) --scope {SCOPE}", file=sys.stderr)
print(f" sed -i '' '1,50d' {uid_file}", file=sys.stderr)


if __name__ == "__main__":
main()
Loading