CI: publish the MSI as a Forgejo release on tags
Some checks failed
CI / Build og unit tests (push) Successful in 13s
CI / MSI-installer (push) Successful in 31s
CI / Integration (mcr.microsoft.com/mssql/server:2017-latest) (push) Has been cancelled
CI / Integration (mcr.microsoft.com/mssql/server:2019-latest) (push) Has been cancelled
CI / Integration (mcr.microsoft.com/mssql/server:2022-latest) (push) Has been cancelled
CI / Integration (mcr.microsoft.com/mssql/server:2025-latest) (push) Has been cancelled

On a v* tag the msi job creates the release (if missing) and attaches the
MSI under its own name and as SQLInsight-Pro-x64.msi, so the newest can be
fetched without login from releases/latest/download/. The artifact upload
stays for ordinary pushes. Uses the Actions token, or RELEASE_TOKEN if set.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Søren Eggert Lundsteen Olsen 2026-10-01 07:31:43 +02:00
parent b9afc30436
commit c91908adce
2 changed files with 80 additions and 0 deletions

View file

@ -41,6 +41,14 @@ jobs:
with: with:
name: SQLInsight-Pro-msi name: SQLInsight-Pro-msi
path: dist/*.msi path: dist/*.msi
- name: Udgiv som release
# Kun tags (v1.2.0): MSI'en vedhæftes releasen og kan hentes uden login fra
# <repo>/releases/latest/download/SQLInsight-Pro-x64.msi. RELEASE_TOKEN (valgfri secret) bruges,
# hvis Actions' automatiske token ikke må oprette releases.
if: startsWith(github.ref, 'refs/tags/v')
env:
TOKEN: ${{ secrets.RELEASE_TOKEN || secrets.GITHUB_TOKEN }}
run: python3 installer/publish-release.py dist/*.msi
integration: integration:
name: Integration (${{ matrix.image }}) name: Integration (${{ matrix.image }})

72
installer/publish-release.py Executable file
View file

@ -0,0 +1,72 @@
#!/usr/bin/env python3
"""Attaches the MSI to a Forgejo release for the current tag (created if missing). Run by ci.yml on tags v*.
publish-release.py dist/SQLInsight-Pro-1.2.0-x64.msi
Environment (set by Forgejo Actions): GITHUB_SERVER_URL, GITHUB_REPOSITORY, GITHUB_REF_NAME, and TOKEN with write
access to the repository. The MSI is attached twice: under its own name and as SQLInsight-Pro-x64.msi, so the
newest one can always be fetched from .../releases/latest/download/SQLInsight-Pro-x64.msi.
"""
import json
import os
import sys
import urllib.error
import urllib.request
import uuid
LATEST_NAME = "SQLInsight-Pro-x64.msi"
SERVER = os.environ["GITHUB_SERVER_URL"].rstrip("/")
REPO = os.environ["GITHUB_REPOSITORY"]
TAG = os.environ["GITHUB_REF_NAME"]
TOKEN = os.environ["TOKEN"]
API = f"{SERVER}/api/v1/repos/{REPO}"
def call(method, url, body=None, content_type="application/json"):
data = json.dumps(body).encode() if isinstance(body, dict) else body
request = urllib.request.Request(url, data=data, method=method, headers={
"Authorization": f"token {TOKEN}", "Accept": "application/json", "Content-Type": content_type,
})
try:
with urllib.request.urlopen(request, timeout=300) as response:
text = response.read().decode()
return json.loads(text) if text else {}
except urllib.error.HTTPError as error:
if error.code == 404 and method == "GET":
return None
sys.exit(f"{method} {url}: {error.code} {error.read().decode()[:300]}")
def upload(release, path, name):
for asset in release.get("assets") or []:
if asset["name"] == name: # a re-run of the same tag replaces the file
call("DELETE", f"{API}/releases/{release['id']}/assets/{asset['id']}")
boundary = uuid.uuid4().hex
with open(path, "rb") as f:
content = f.read()
body = (f"--{boundary}\r\nContent-Disposition: form-data; name=\"attachment\"; filename=\"{name}\"\r\n"
f"Content-Type: application/octet-stream\r\n\r\n").encode() + content + f"\r\n--{boundary}--\r\n".encode()
call("POST", f"{API}/releases/{release['id']}/assets?name={name}", body, f"multipart/form-data; boundary={boundary}")
print(f"vedhæftet {name} ({len(content) // 1024 // 1024} MB)")
def main():
if len(sys.argv) != 2:
sys.exit(__doc__)
msi = sys.argv[1]
release = call("GET", f"{API}/releases/tags/{TAG}")
if release is None:
release = call("POST", f"{API}/releases", {
"tag_name": TAG, "name": f"SQLInsight Pro {TAG.removeprefix('v')}",
"body": "MSI-installer til Windows (agent og web). Se docs/installation.md.",
"draft": False, "prerelease": "-" in TAG,
})
print(f"release {TAG} oprettet")
upload(release, msi, os.path.basename(msi))
upload(call("GET", f"{API}/releases/{release['id']}"), msi, LATEST_NAME)
print(f"{SERVER}/{REPO}/releases/tag/{TAG}")
if __name__ == "__main__":
main()