Skip to content
Closed
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
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)
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 = ""

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()
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>")
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