mirror of
https://github.com/ByteWelder/Tactility.git
synced 2026-08-21 17:35:06 +00:00
Compare commits
No commits in common. "35fd7dd5367ec9c52037775d145bc800e6dce7a8" and "2f5e183f184f02a26d57fd9a11683f404e2b00ea" have entirely different histories.
35fd7dd536
...
2f5e183f18
@ -1,135 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""Download all artifacts from a GitHub Actions workflow run."""
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import os
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import zipfile
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import requests
|
|
||||||
|
|
||||||
API_URL = "https://api.github.com"
|
|
||||||
|
|
||||||
|
|
||||||
def get_token(cli_token: str | None) -> str:
|
|
||||||
if cli_token:
|
|
||||||
return cli_token
|
|
||||||
env_token = os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN")
|
|
||||||
if env_token:
|
|
||||||
return env_token
|
|
||||||
try:
|
|
||||||
result = subprocess.run(
|
|
||||||
["gh", "auth", "token"], capture_output=True, text=True, check=True
|
|
||||||
)
|
|
||||||
return result.stdout.strip()
|
|
||||||
except (FileNotFoundError, subprocess.CalledProcessError):
|
|
||||||
sys.exit(
|
|
||||||
"No token found. Pass --token, set GITHUB_TOKEN/GH_TOKEN, or run `gh auth login`."
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def get_repo(cli_repo: str | None) -> str:
|
|
||||||
if cli_repo:
|
|
||||||
return cli_repo
|
|
||||||
try:
|
|
||||||
result = subprocess.run(
|
|
||||||
["git", "remote", "get-url", "origin"],
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
check=True,
|
|
||||||
)
|
|
||||||
url = result.stdout.strip()
|
|
||||||
except (FileNotFoundError, subprocess.CalledProcessError):
|
|
||||||
sys.exit("Could not detect repo from git remote. Pass --repo owner/name.")
|
|
||||||
|
|
||||||
# Handle both SSH and HTTPS remote URLs.
|
|
||||||
if url.startswith("git@"):
|
|
||||||
path = url.split(":", 1)[1]
|
|
||||||
else:
|
|
||||||
path = url.split("github.com/", 1)[-1]
|
|
||||||
return path.removesuffix(".git")
|
|
||||||
|
|
||||||
|
|
||||||
def download_artifacts(repo: str, run_id: str, token: str, out_dir: Path):
|
|
||||||
headers = {
|
|
||||||
"Authorization": f"Bearer {token}",
|
|
||||||
"Accept": "application/vnd.github+json",
|
|
||||||
"X-GitHub-Api-Version": "2022-11-28",
|
|
||||||
}
|
|
||||||
|
|
||||||
out_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
url = f"{API_URL}/repos/{repo}/actions/runs/{run_id}/artifacts"
|
|
||||||
artifacts = []
|
|
||||||
while url:
|
|
||||||
resp = requests.get(url, headers=headers, params={"per_page": 100})
|
|
||||||
resp.raise_for_status()
|
|
||||||
data = resp.json()
|
|
||||||
artifacts.extend(data["artifacts"])
|
|
||||||
url = resp.links.get("next", {}).get("url")
|
|
||||||
|
|
||||||
if not artifacts:
|
|
||||||
print(f"No artifacts found for run {run_id}.")
|
|
||||||
return
|
|
||||||
|
|
||||||
for artifact in artifacts:
|
|
||||||
name = artifact["name"]
|
|
||||||
if name == "all-artifacts":
|
|
||||||
print(f"Skipping {name}")
|
|
||||||
continue
|
|
||||||
if artifact.get("expired"):
|
|
||||||
print(f"Skipping expired artifact: {name}")
|
|
||||||
continue
|
|
||||||
|
|
||||||
print(f"Downloading {name} ({artifact['size_in_bytes']} bytes)...")
|
|
||||||
download_url = artifact["archive_download_url"]
|
|
||||||
resp = requests.get(download_url, headers=headers, stream=True)
|
|
||||||
resp.raise_for_status()
|
|
||||||
|
|
||||||
zip_path = out_dir / f"{name}.zip"
|
|
||||||
with open(zip_path, "wb") as f:
|
|
||||||
for chunk in resp.iter_content(chunk_size=8192):
|
|
||||||
f.write(chunk)
|
|
||||||
|
|
||||||
if name.endswith(".bin"):
|
|
||||||
with zipfile.ZipFile(zip_path) as zf:
|
|
||||||
zf.extractall(out_dir)
|
|
||||||
zip_path.unlink()
|
|
||||||
print(f" Extracted to {out_dir}")
|
|
||||||
else:
|
|
||||||
print(f" Saved to {zip_path}")
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
parser = argparse.ArgumentParser(description=__doc__)
|
|
||||||
parser.add_argument("run_id", help="GitHub Actions run ID")
|
|
||||||
parser.add_argument(
|
|
||||||
"--repo",
|
|
||||||
help="owner/repo (default: detected from git remote 'origin')",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--token",
|
|
||||||
help="GitHub token (default: $GITHUB_TOKEN, $GH_TOKEN, or `gh auth token`)",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--out",
|
|
||||||
default="artifacts",
|
|
||||||
help="Output directory (default: ./artifacts)",
|
|
||||||
)
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
repo = get_repo(args.repo)
|
|
||||||
token = get_token(args.token)
|
|
||||||
out_dir = Path(args.out)
|
|
||||||
|
|
||||||
print(f"Repo: {repo}")
|
|
||||||
print(f"Run ID: {args.run_id}")
|
|
||||||
print(f"Output: {out_dir.resolve()}")
|
|
||||||
|
|
||||||
download_artifacts(repo, args.run_id, token, out_dir)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@ -1,126 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""Summarize commits between the last 2 pushed git tags into commits.md and pull-requests.md."""
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import re
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
PR_REF_RE = re.compile(r"#(\d+)")
|
|
||||||
|
|
||||||
|
|
||||||
def get_repo(cli_repo: str | None) -> str:
|
|
||||||
if cli_repo:
|
|
||||||
return cli_repo
|
|
||||||
try:
|
|
||||||
result = subprocess.run(
|
|
||||||
["git", "remote", "get-url", "origin"],
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
check=True,
|
|
||||||
)
|
|
||||||
url = result.stdout.strip()
|
|
||||||
except (FileNotFoundError, subprocess.CalledProcessError):
|
|
||||||
sys.exit("Could not detect repo from git remote. Pass --repo owner/name.")
|
|
||||||
|
|
||||||
if url.startswith("git@"):
|
|
||||||
path = url.split(":", 1)[1]
|
|
||||||
else:
|
|
||||||
path = url.split("github.com/", 1)[-1]
|
|
||||||
return path.removesuffix(".git")
|
|
||||||
|
|
||||||
|
|
||||||
def get_last_two_tags() -> tuple[str, str]:
|
|
||||||
result = subprocess.run(
|
|
||||||
["git", "tag", "--sort=-creatordate"],
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
check=True,
|
|
||||||
)
|
|
||||||
tags = [t for t in result.stdout.splitlines() if t]
|
|
||||||
if len(tags) < 2:
|
|
||||||
sys.exit(f"Need at least 2 tags, found {len(tags)}.")
|
|
||||||
return tags[1], tags[0] # previous, latest
|
|
||||||
|
|
||||||
|
|
||||||
def get_commits(prev_tag: str, latest_tag: str) -> list[tuple[str, str]]:
|
|
||||||
result = subprocess.run(
|
|
||||||
[
|
|
||||||
"git",
|
|
||||||
"log",
|
|
||||||
"--pretty=format:%H\x1f%s",
|
|
||||||
f"{prev_tag}..{latest_tag}",
|
|
||||||
],
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
check=True,
|
|
||||||
)
|
|
||||||
commits = []
|
|
||||||
for line in result.stdout.splitlines():
|
|
||||||
if not line:
|
|
||||||
continue
|
|
||||||
sha, subject = line.split("\x1f", 1)
|
|
||||||
commits.append((sha, subject))
|
|
||||||
return commits
|
|
||||||
|
|
||||||
|
|
||||||
def write_commits_md(path: Path, repo: str, prev_tag: str, latest_tag: str, commits: list[tuple[str, str]]):
|
|
||||||
lines = [f"# Commits: {prev_tag}..{latest_tag}", ""]
|
|
||||||
for sha, subject in commits:
|
|
||||||
link = f"https://github.com/{repo}/commit/{sha}"
|
|
||||||
lines.append(f"- [{subject}]({link})")
|
|
||||||
path.write_text("\n".join(lines) + "\n")
|
|
||||||
|
|
||||||
|
|
||||||
def write_pull_requests_md(path: Path, repo: str, commits: list[tuple[str, str]]):
|
|
||||||
pr_numbers = []
|
|
||||||
seen = set()
|
|
||||||
for _, subject in commits:
|
|
||||||
for match in PR_REF_RE.finditer(subject):
|
|
||||||
number = match.group(1)
|
|
||||||
if number not in seen:
|
|
||||||
seen.add(number)
|
|
||||||
pr_numbers.append(number)
|
|
||||||
|
|
||||||
lines = ["# Pull Requests", ""]
|
|
||||||
for number in pr_numbers:
|
|
||||||
lines.append(f"- https://github.com/{repo}/pull/{number}")
|
|
||||||
path.write_text("\n".join(lines) + "\n")
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
parser = argparse.ArgumentParser(description=__doc__)
|
|
||||||
parser.add_argument(
|
|
||||||
"--repo",
|
|
||||||
help="owner/repo (default: detected from git remote 'origin')",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--out",
|
|
||||||
default=".",
|
|
||||||
help="Output directory for commits.md and pull-requests.md (default: current directory)",
|
|
||||||
)
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
repo = get_repo(args.repo)
|
|
||||||
prev_tag, latest_tag = get_last_two_tags()
|
|
||||||
commits = get_commits(prev_tag, latest_tag)
|
|
||||||
|
|
||||||
out_dir = Path(args.out)
|
|
||||||
out_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
commits_path = out_dir / "release-commits.md"
|
|
||||||
prs_path = out_dir / "release-pull-requests.md"
|
|
||||||
|
|
||||||
write_commits_md(commits_path, repo, prev_tag, latest_tag, commits)
|
|
||||||
write_pull_requests_md(prs_path, repo, commits)
|
|
||||||
|
|
||||||
print(f"Repo: {repo}")
|
|
||||||
print(f"Tags: {prev_tag}..{latest_tag}")
|
|
||||||
print(f"Commits: {len(commits)}")
|
|
||||||
print(f"Wrote {commits_path}")
|
|
||||||
print(f"Wrote {prs_path}")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@ -1,84 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""Generate a release summary markdown by combining gh-release-summary.py output with Claude."""
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
SCRIPT_DIR = Path(__file__).resolve().parent
|
|
||||||
|
|
||||||
SECTIONS = [
|
|
||||||
"New Devices",
|
|
||||||
"New Features",
|
|
||||||
"New Apps",
|
|
||||||
"Improvements & Fixes",
|
|
||||||
"Other Changes",
|
|
||||||
"TactilitySDK",
|
|
||||||
"Known Issues",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def run_gh_release_summary(out_dir: Path, repo: str | None):
|
|
||||||
cmd = [sys.executable, str(SCRIPT_DIR / "gh-release-summary.py"), "--out", str(out_dir)]
|
|
||||||
if repo:
|
|
||||||
cmd += ["--repo", repo]
|
|
||||||
subprocess.run(cmd, check=True)
|
|
||||||
|
|
||||||
|
|
||||||
def build_prompt() -> str:
|
|
||||||
section_list = "\n".join(f"- {s}" for s in SECTIONS)
|
|
||||||
return (
|
|
||||||
"You are given a list of commits (with GitHub commit links) and a list of pull "
|
|
||||||
"request links from a software release. Write a release summary in Markdown, "
|
|
||||||
"organizing changes into these sections (omit a section if it has no relevant "
|
|
||||||
"changes):\n\n"
|
|
||||||
f"{section_list}\n\n"
|
|
||||||
"Use concise bullet points. Base every bullet strictly on the given commits and "
|
|
||||||
"pull requests, referencing the pull request link where applicable. Output only "
|
|
||||||
"the Markdown, no commentary before or after it."
|
|
||||||
"If a pull request or commit has multiple changes add them as separate entries."
|
|
||||||
"Ignore irrelevant changes: documentation, minor fixes, non-descript fixes, scripts, github workflows."
|
|
||||||
"For 'New Devices': only mention the device name, don't like pull request."
|
|
||||||
"For 'Improvements & Fixes': Avoid non-descript entries, write specifics."
|
|
||||||
"For 'TactilitySDK': Avoid generic changes, be specific. Mention API changes."
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def run_claude(commits_md: str, prs_md: str) -> str:
|
|
||||||
prompt = build_prompt()
|
|
||||||
stdin_content = f"## Commits\n\n{commits_md}\n\n## Pull Requests\n\n{prs_md}"
|
|
||||||
result = subprocess.run(
|
|
||||||
["claude", "-p", prompt],
|
|
||||||
input=stdin_content,
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
check=True,
|
|
||||||
)
|
|
||||||
return result.stdout
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
parser = argparse.ArgumentParser(description=__doc__)
|
|
||||||
parser.add_argument("--repo", help="owner/repo (default: detected from git remote 'origin')")
|
|
||||||
parser.add_argument("--out", default=".", help="Output directory (default: current directory)")
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
out_dir = Path(args.out)
|
|
||||||
out_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
run_gh_release_summary(out_dir, args.repo)
|
|
||||||
|
|
||||||
commits_md = (out_dir / "release-commits.md").read_text()
|
|
||||||
prs_md = (out_dir / "release-pull-requests.md").read_text()
|
|
||||||
|
|
||||||
print("Generating release summary via claude...")
|
|
||||||
summary = run_claude(commits_md, prs_md)
|
|
||||||
|
|
||||||
summary_path = out_dir / "release-summary.md"
|
|
||||||
summary_path.write_text(summary)
|
|
||||||
print(f"Wrote {summary_path}")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@ -4,22 +4,24 @@
|
|||||||
2. Set the new version in the Tactility repository:
|
2. Set the new version in the Tactility repository:
|
||||||
1. Update `version.txt` in the Tactility project and create a branch to start a build for it
|
1. Update `version.txt` in the Tactility project and create a branch to start a build for it
|
||||||
2. Merge the branch and wait for a build.
|
2. Merge the branch and wait for a build.
|
||||||
3. Push a tag e.g. `v1.2.3` - the `v` prefix is crucial for the pipelines.
|
3. Upload the new Tactility SDK to the CDN
|
||||||
4. Wait for the SDK and firmwares to be published on the CDN.
|
1. Upload it to the [CDN](https://dash.cloudflare.com)
|
||||||
5. Test the firmwares on all devices with the web installer
|
2. Update `sdk.json` from [TactilityTool](https://github.com/TactilityProject/TactilityTool) and upload it to [CDN](https://dash.cloudflare.com)
|
||||||
6. Run `Buildscripts/gh-download-artifacts.py [actionId] --token [token]` and let it run in the background.
|
4. Update the [TactilityApps](https://github.com/TactilityProject/TactilityApps) with the new SDK and also release these to the CDN:
|
||||||
Get a token from [here](https://github.com/settings/personal-access-tokens)
|
1. Download the `cdn-files.zip` from the pipelines
|
||||||
7. Update the [TactilityApps](https://github.com/TactilityProject/TactilityApps) with the new SDK version and bump the app versions.
|
2. Upload them to the CDN at `apps/x.y.z/`
|
||||||
8. After merging the apps, manually upload to CDN, then test them.
|
3. Also upload them to the CDN for the upcoming version, because the upcoming a.b.c version will also need some basic apps to download
|
||||||
|
5. Test the latest unstable version of Tactility on several devices using the [web installer](https://install.tactilityproject.org). Pay special attention to:
|
||||||
|
1. The version of the unstable channel (should be updated!)
|
||||||
|
2. App Hub
|
||||||
|
3. Wi-Fi
|
||||||
|
6. Test the firmwares on all devices with the local web installer
|
||||||
|
7. Push a tag e.g. `v1.2.3` - the `v` prefix is crucial for the pipelines
|
||||||
|
8. The pipelines should now kick off a build that releases the build to the stable channel of the Web Installer. Verify that.
|
||||||
9. Make a new version of the docs available at [TactilityDocs](https://github.com/TactilityProject/TactilityDocs)
|
9. Make a new version of the docs available at [TactilityDocs](https://github.com/TactilityProject/TactilityDocs)
|
||||||
1. Copy `docs/versions/main/` into new version folder
|
|
||||||
2. Edit `index.html` to include the new version
|
|
||||||
3. Push code directly to `main` branch
|
|
||||||
10. Make a new [GitHub release](https://github.com/TactilityProject/Tactility/releases/new)
|
10. Make a new [GitHub release](https://github.com/TactilityProject/Tactility/releases/new)
|
||||||
Run `Buildscripts/release-summary.py` to generate a release, then review it
|
|
||||||
11. Double-check that all CDN/TactilityApps/Tactility repository changes are merged.
|
11. Double-check that all CDN/TactilityApps/Tactility repository changes are merged.
|
||||||
12. Add the `-dev` postfix from `version.txt` and bump the version. Merge it to `main`.
|
12. Ensure that the CDN is not in development mode anymore.
|
||||||
13. Ensure that the CDN is not in development mode anymore.
|
|
||||||
|
|
||||||
### Post-release
|
### Post-release
|
||||||
|
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
0.8.0-dev
|
0.7.0-dev
|
||||||
Loading…
x
Reference in New Issue
Block a user