Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 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
106 changes: 106 additions & 0 deletions .github/scripts/generate_develop_release.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import os
import subprocess
import requests
import datetime
import json
from pathlib import Path

REPO = "digital-preservation/droid"
RELEASE_TAG = "develop"
API_BASE = "https://api.github.com"
TOKEN = os.environ["GITHUB_TOKEN"]
HEADERS = {
"Authorization": f"Bearer {TOKEN}",
"Accept": "application/vnd.github+json"
}

def run(cmd):
return subprocess.check_output(cmd, text=True).strip()

def get_commit_sha(branch):
return run(["git", "rev-parse", f"origin/{branch}"])

def is_ancestor(ancestor, descendant):
return subprocess.call(["git", "merge-base", "--is-ancestor", ancestor, descendant]) == 0

def fetch_merged_prs():
url = f"{API_BASE}/repos/{REPO}/pulls"
params = {"state": "closed", "base": "develop", "per_page": 100}
r = requests.get(url, headers=HEADERS, params=params)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Maybe prs?

r.raise_for_status()
return [pr for pr in r.json() if pr.get("merged_at")]

def get_release():
url = f"{API_BASE}/repos/{REPO}/releases/tags/{RELEASE_TAG}"
r = requests.get(url, headers=HEADERS)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Maybe release?

r.raise_for_status()
return r.json()

def update_release_notes(release_id, body):
url = f"{API_BASE}/repos/{REPO}/releases/{release_id}"
data = {"body": body}
r = requests.patch(url, headers=HEADERS, json=data)
r.raise_for_status()

def delete_assets(asset_ids):
for asset_id in asset_ids:
url = f"{API_BASE}/repos/{REPO}/releases/assets/{asset_id}"
r = requests.delete(url, headers=HEADERS)
r.raise_for_status()
print(f"🗑️ Deleted asset {asset_id}")

def upload_asset(upload_url, file_path):
file_name = Path(file_path).name
with open(file_path, "rb") as f:
print(f"⬆️ Uploading {file_name}...")
headers = HEADERS.copy()
headers["Content-Type"] = "application/zip"
r = requests.post(f"{upload_url}?name={file_name}", headers=headers, data=f)
r.raise_for_status()

def main():
# Ensure Git branches are up to date
run(["git", "fetch", "origin", "develop", "main"])
develop_sha = get_commit_sha("develop")
main_sha = get_commit_sha("main")

print("🔍 Fetching merged PRs...")
prs = fetch_merged_prs()
new_prs = ""

for pr in prs:
pr_number = pr["number"]
pr_title = pr["title"]
pr_author = pr["user"]["login"]
merge_commit = pr["merge_commit_sha"]

if not is_ancestor(merge_commit, main_sha):
new_prs += f"- PR [#{pr_number}] {pr_title} (@{pr_author})\n"

if not new_prs:
print("✅ No new PRs to include.")
return

timestamp = datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M UTC")
body = f"### Changes not yet in main:\n\n{new_prs}\n_Last updated: {timestamp}_"

release = get_release()
release_id = release["id"]
upload_url = release["upload_url"].split("{")[0]

update_release_notes(release_id, body)

print("📦 Deleting existing assets...")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Maybe the bin emoji that you used earlier/a bin emoji might be better than the box one?

asset_ids = [a["id"] for a in release.get("assets", [])]
delete_assets(asset_ids)

print("📦 Uploading new assets...")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Maybe the up arrow emoji that you used earlier/a bin emoji might be better than the box one?

windows_jar = next(Path("droid-binary/target").glob("*win64-with-jre.zip"))
generic_jar = next(Path("droid-binary/target").glob("*bin.zip"))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Maybe a print here that lets us know that the Windows jar is uploading, like print("Uploading Windows Jar...")

upload_asset(upload_url, windows_jar)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Maybe a print here that lets us know that the generic jar is uploading, like print("Uploading Generic Jar...")

upload_asset(upload_url, generic_jar)

print("✅ Release updated successfully.")

if __name__ == "__main__":
main()
217 changes: 116 additions & 101 deletions .github/scripts/generate_release.py
Original file line number Diff line number Diff line change
@@ -1,106 +1,121 @@
import os
import sys
import subprocess
import requests
import datetime
import json
from pathlib import Path

REPO = "digital-preservation/droid"
RELEASE_TAG = "develop"
API_BASE = "https://api.github.com"
TOKEN = os.environ["GITHUB_TOKEN"]
HEADERS = {
"Authorization": f"Bearer {TOKEN}",
"Accept": "application/vnd.github+json"
}

def run(cmd):
return subprocess.check_output(cmd, text=True).strip()

def get_commit_sha(branch):
return run(["git", "rev-parse", f"origin/{branch}"])

def is_ancestor(ancestor, descendant):
return subprocess.call(["git", "merge-base", "--is-ancestor", ancestor, descendant]) == 0

def fetch_merged_prs():
url = f"{API_BASE}/repos/{REPO}/pulls"
params = {"state": "closed", "base": "develop", "per_page": 100}
r = requests.get(url, headers=HEADERS, params=params)
r.raise_for_status()
return [pr for pr in r.json() if pr.get("merged_at")]

def get_release():
url = f"{API_BASE}/repos/{REPO}/releases/tags/{RELEASE_TAG}"
r = requests.get(url, headers=HEADERS)
r.raise_for_status()
return r.json()

def update_release_notes(release_id, body):
url = f"{API_BASE}/repos/{REPO}/releases/{release_id}"
data = {"body": body}
r = requests.patch(url, headers=HEADERS, json=data)
r.raise_for_status()

def delete_assets(asset_ids):
for asset_id in asset_ids:
url = f"{API_BASE}/repos/{REPO}/releases/assets/{asset_id}"
r = requests.delete(url, headers=HEADERS)
r.raise_for_status()
print(f"🗑️ Deleted asset {asset_id}")

def upload_asset(upload_url, file_path):
file_name = Path(file_path).name
with open(file_path, "rb") as f:
print(f"⬆️ Uploading {file_name}...")
headers = HEADERS.copy()
headers["Content-Type"] = "application/zip"
r = requests.post(f"{upload_url}?name={file_name}", headers=headers, data=f)
r.raise_for_status()

def main():
# Ensure Git branches are up to date
run(["git", "fetch", "origin", "develop", "main"])
develop_sha = get_commit_sha("develop")
main_sha = get_commit_sha("main")

print("🔍 Fetching merged PRs...")
prs = fetch_merged_prs()
new_prs = ""
from datetime import datetime, timezone
import glob

# === CONFIGURATION ===
REPO = "digital-preservation/droid" # e.g., "octocat/Hello-World"
ASSET_FILES = sorted(
glob.glob("droid-binary/target/droid-binary-*-bin-win64-with-jre.zip") +
glob.glob("droid-binary/target/droid-binary-*-bin.zip")
)

if not ASSET_FILES:
print("No matching files found for upload.")
sys.exit(1)

TOKEN = os.getenv("GITHUB_TOKEN")

if not TOKEN:
print("GITHUB_TOKEN environment variable not set.")
sys.exit(1)

if len(sys.argv) != 2:
print("Usage: python create_github_release_raw.py <release_version>")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is there a clearer error message we could prepend to this? Like "Error - Expected 2 arguments..."

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

This is fairly standard if you mess up a CLI tool. You get how you were supposed to do it. I'll leave it.

sys.exit(1)

RELEASE_VERSION = sys.argv[1]
API_BASE = f"https://api.github.com/repos/{REPO}"
HEADERS = {"Authorization": f"Bearer {TOKEN}", "Accept": "application/vnd.github+json"}

# === FETCH RELEASES ===
print("Fetching existing releases...")
releases_resp = requests.get(f"{API_BASE}/releases", headers=HEADERS)
releases_resp.raise_for_status()
releases = releases_resp.json()

def parse_github_datetime(dt_str):
# Format: "2023-12-01T10:00:00Z"
return datetime.strptime(dt_str, "%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=timezone.utc)

last_release_date = datetime(1970, 1, 1, tzinfo=timezone.utc)
for r in releases:
if r["tag_name"] != RELEASE_VERSION and r["tag_name"] != 'develop':
last_release_date = parse_github_datetime(r["created_at"])
break

print(f"Last release date: {last_release_date.isoformat()}")

# === GET MERGED PRs SINCE LAST RELEASE ===
print("Fetching merged pull requests...")
merged_prs = []
page = 1
while True:
resp = requests.get(
f"{API_BASE}/pulls",
headers=HEADERS,
params={"state": "closed", "sort": "updated", "direction": "desc", "per_page": 100, "page": page}
)
resp.raise_for_status()
prs = resp.json()
if not prs:
break

for pr in prs:
pr_number = pr["number"]
pr_title = pr["title"]
pr_author = pr["user"]["login"]
merge_commit = pr["merge_commit_sha"]

if not is_ancestor(merge_commit, main_sha):
new_prs += f"- PR [#{pr_number}] {pr_title} (@{pr_author})\n"

if not new_prs:
print("✅ No new PRs to include.")
return

timestamp = datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M UTC")
body = f"### Changes not yet in main:\n\n{new_prs}\n_Last updated: {timestamp}_"

release = get_release()
release_id = release["id"]
upload_url = release["upload_url"].split("{")[0]

update_release_notes(release_id, body)

print("📦 Deleting existing assets...")
asset_ids = [a["id"] for a in release.get("assets", [])]
delete_assets(asset_ids)

print("📦 Uploading new assets...")
windows_jar = next(Path("droid-binary/target").glob("*win64-with-jre.zip"))
generic_jar = next(Path("droid-binary/target").glob("*bin.zip"))
upload_asset(upload_url, windows_jar)
upload_asset(upload_url, generic_jar)

print("✅ Release updated successfully.")

if __name__ == "__main__":
main()
merged_at = pr.get("merged_at")
if not merged_at:
continue
merged_dt = parse_github_datetime(merged_at)
if merged_dt <= last_release_date:
break
if pr["user"]["login"].startswith("dependabot"):
continue
merged_prs.append(f"- #{pr['number']} {pr['title']}")

page += 1

release_notes = "\n".join(reversed(merged_prs)) or "No non-Dependabot PRs since last release."

# === CREATE RELEASE ===
print(f"Creating GitHub release for {RELEASE_VERSION}...")
release_resp = requests.post(
f"{API_BASE}/releases",
headers=HEADERS,
json={
"tag_name": RELEASE_VERSION,
"name": f"Release {RELEASE_VERSION}",
"body": release_notes,
"draft": False,
"prerelease": False
}
)
release_resp.raise_for_status()
release = release_resp.json()
upload_url = release["upload_url"].split("{")[0]

# === UPLOAD FILES ===
for file_path in ASSET_FILES:
if not os.path.exists(file_path):
print(f"File not found: {file_path}")
continue

file_name = os.path.basename(file_path)
print(f"Uploading {file_name}...")
with open(file_path, "rb") as f:
upload_resp = requests.post(
f"{upload_url}?name={file_name}",
headers={
"Authorization": f"Bearer {TOKEN}",
"Content-Type": "application/octet-stream"
},
data=f.read()
)

if upload_resp.status_code == 201:
print(f"Uploaded {file_name}")
else:
print(f"Failed to upload {file_name}: {upload_resp.status_code}\n{upload_resp.text}")

print(f"✅ Release {RELEASE_VERSION} created and assets uploaded.")
11 changes: 11 additions & 0 deletions .github/scripts/settings.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 https://maven.apache.org/xsd/settings-1.0.0.xsd">
<servers>
<server>
<id>ossrh</id>
<username>${env.MAVEN_USERNAME}</username>
<password>${env.MAVEN_CENTRAL_TOKEN}</password>
</server>
</servers>
</settings>
2 changes: 1 addition & 1 deletion .github/workflows/develop_release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
run: |
mvn -V -B package -DnvdApiKey=${{ secrets.NVD_API_KEY }}
pip install requests
python .github/scripts/generate_release.py
python .github/scripts/generate_develop_release.py

env:
AWS_REGION: eu-west-2
Expand Down
Loading
Loading