initial
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
name: 🐞 Bug report
|
||||
description: Report a bug or an issue.
|
||||
title: "bug: "
|
||||
labels: ["Bug report"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
# Patches template bug report
|
||||
|
||||
Before creating a new bug report, please keep the following in mind:
|
||||
|
||||
- **Do not submit a duplicate bug report**: Search for existing bug reports [here](https://github.com/TEMPLATE/template-patches-template/issues?q=label%3A%22Bug+report%22).
|
||||
- **Review the contribution guidelines**: Make sure your bug report adheres to it. You can find the guidelines [here](https://github.com/TEMPLATE/template-patches-template/blob/main/CONTRIBUTING.md).
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Bug description
|
||||
description: |
|
||||
- Describe your bug in detail
|
||||
- Add steps to reproduce the bug if possible (Step 1. ... Step 2. ...)
|
||||
- Add images and videos if possible
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Error logs
|
||||
description: Exceptions can be captured by running `logcat | grep AndroidRuntime` in a shell.
|
||||
render: shell
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Solution
|
||||
description: If applicable, add a possible solution to the bug.
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add additional context here.
|
||||
- type: checkboxes
|
||||
id: acknowledgements
|
||||
attributes:
|
||||
label: Acknowledgements
|
||||
description: Your bug report will be closed if you don't follow the checklist below.
|
||||
options:
|
||||
- label: I have checked all open and closed bug reports and this is not a duplicate.
|
||||
required: true
|
||||
- label: I have chosen an appropriate title.
|
||||
required: true
|
||||
- label: All requested information has been provided properly.
|
||||
required: true
|
||||
@@ -0,0 +1 @@
|
||||
blank_issues_enabled: false
|
||||
@@ -0,0 +1,44 @@
|
||||
name: ⭐ Feature request
|
||||
description: Create a detailed request for a new feature.
|
||||
title: "feat: "
|
||||
labels: ["Feature request"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
# Template Patches template feature request
|
||||
|
||||
Before creating a new feature request, please keep the following in mind:
|
||||
|
||||
- **Do not submit a duplicate feature request**: Search for existing feature requests [here](https://github.com/TEMPlATE/TEMPLATE-patches-template/issues?q=label%3A%22Feature+request%22).
|
||||
- **Review the contribution guidelines**: Make sure your feature request adheres to it. You can find the guidelines [here](https://github.com/TEMPLATE/TEMPLATE-patches-template/blob/main/CONTRIBUTING.md).
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Feature description
|
||||
description: |
|
||||
- Describe your feature in detail
|
||||
- Add images, videos, links, examples, references, etc. if possible
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Motivation
|
||||
description: |
|
||||
A strong motivation is necessary for a feature request to be considered.
|
||||
|
||||
- Why should this feature be implemented?
|
||||
- What is the explicit use case?
|
||||
- What are the benefits?
|
||||
- What makes this feature important?
|
||||
validations:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
id: acknowledgements
|
||||
attributes:
|
||||
label: Acknowledgements
|
||||
description: Your feature request will be closed if you don't follow the checklist below.
|
||||
options:
|
||||
- label: I have checked all open and closed feature requests and this is not a duplicate
|
||||
required: true
|
||||
- label: I have chosen an appropriate title.
|
||||
required: true
|
||||
- label: All requested information has been provided properly.
|
||||
required: true
|
||||
@@ -0,0 +1,26 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: github-actions
|
||||
labels: []
|
||||
directory: /
|
||||
target-branch: dev
|
||||
schedule:
|
||||
interval: monthly
|
||||
|
||||
- package-ecosystem: npm
|
||||
labels: []
|
||||
directory: /
|
||||
target-branch: dev
|
||||
schedule:
|
||||
interval: monthly
|
||||
ignore:
|
||||
# version 10.x and later may not generate release notes correctly
|
||||
# https://github.com/conventional-changelog/conventional-changelog/issues/1495
|
||||
- dependency-name: "conventional-changelog-conventionalcommits"
|
||||
|
||||
- package-ecosystem: gradle
|
||||
labels: []
|
||||
directory: /
|
||||
target-branch: dev
|
||||
schedule:
|
||||
interval: monthly
|
||||
@@ -0,0 +1,227 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Generates the patches section of README.md from patches-list.json
|
||||
and injects it between <!-- PATCHES_START --> / <!-- PATCHES_END --> markers.
|
||||
|
||||
Spoilers are expanded (open by default) if:
|
||||
1. Total patch count <= AUTO_EXPAND_THRESHOLD.
|
||||
2. The README marker explicitly says: <!-- PATCHES_START EXPANDED -->
|
||||
|
||||
python3 generate_patches_readme.py <owner/repo> <branch> [patches-list.json] [README.md]
|
||||
"""
|
||||
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
if len(sys.argv) < 3:
|
||||
print("Usage: generate_patches_readme.py <owner/repo> <branch> [json] [readme]")
|
||||
sys.exit(1)
|
||||
|
||||
repo_full = sys.argv[1]
|
||||
branch = sys.argv[2]
|
||||
json_path = Path(sys.argv[3]) if len(sys.argv) > 3 else Path("patches-list.json")
|
||||
readme_path = Path(sys.argv[4]) if len(sys.argv) > 4 else Path("README.md")
|
||||
|
||||
|
||||
if "/" not in repo_full:
|
||||
raise ValueError(f"Invalid repo format: {repo_full} (expected owner/repo)")
|
||||
|
||||
owner, repo = repo_full.split("/", 1)
|
||||
|
||||
|
||||
with open(json_path, encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
|
||||
|
||||
def pkg_emoji(pkg):
|
||||
"""Return a standard package emoji regardless of the package name."""
|
||||
return "📦"
|
||||
|
||||
# Group patches by package; patches with no compatiblePackages are universal.
|
||||
# JSON structure: compatiblePackages is a list of objects with
|
||||
# { packageName, name, targets: [{ version, isExperimental, description }] }
|
||||
by_pkg = {} # packageName -> { name, emoji, patches, targets }
|
||||
universal = {}
|
||||
|
||||
for patch in data["patches"]:
|
||||
cp = patch.get("compatiblePackages")
|
||||
if not cp:
|
||||
# Deduplicate universal patches by name
|
||||
if patch["name"] not in universal:
|
||||
universal[patch["name"]] = patch
|
||||
continue
|
||||
for pkg_entry in cp:
|
||||
pkg = pkg_entry["packageName"]
|
||||
name = pkg_entry.get("name") or pkg # fall back to package name if no label
|
||||
if pkg not in by_pkg:
|
||||
by_pkg[pkg] = {
|
||||
"name": name,
|
||||
"emoji": pkg_emoji(pkg),
|
||||
"patches": {},
|
||||
"targets": pkg_entry.get("targets", []),
|
||||
}
|
||||
# Deduplicate patches that appear across multiple packages
|
||||
if patch["name"] not in by_pkg[pkg]["patches"]:
|
||||
by_pkg[pkg]["patches"][patch["name"]] = patch
|
||||
|
||||
|
||||
def anchor(name):
|
||||
"""Convert a patch name to a GitHub-compatible anchor slug."""
|
||||
return re.sub(r"-+", "-", re.sub(r"[^a-z0-9]+", "-", name.lower())).strip("-")
|
||||
|
||||
|
||||
def patches_table(patches):
|
||||
"""Render a sorted markdown table of patches with name, description, and options."""
|
||||
rows = [
|
||||
"| 💊 Patch | 📜 Description | ⚙️ Options |",
|
||||
"|----------|----------------|-----------|",
|
||||
]
|
||||
for p in sorted(patches, key=lambda x: x["name"]):
|
||||
a = anchor(p["name"])
|
||||
options = p.get("options") or []
|
||||
if options:
|
||||
# Show only option titles as a bullet list
|
||||
parts = [opt.get("title") or opt.get("key") or "" for opt in options]
|
||||
opts_cell = "<br>".join(f"• {t}" for t in parts)
|
||||
else:
|
||||
opts_cell = ""
|
||||
desc = (p.get("description") or "").replace("\n", "<br>")
|
||||
rows.append(f"| [{p['name']}](#{a}) | {desc} | {opts_cell} |")
|
||||
return "\n".join(rows)
|
||||
|
||||
|
||||
def versions_table(targets):
|
||||
"""Render a markdown table of supported versions.
|
||||
Experimental versions get a 🧪 prefix.
|
||||
Versions with a description get it shown in a second row below.
|
||||
"""
|
||||
if not targets:
|
||||
return ""
|
||||
|
||||
cells = []
|
||||
for t in targets:
|
||||
ver = t["version"]
|
||||
if ver is None:
|
||||
continue
|
||||
label = f"🧪 {ver}" if t.get("isExperimental") else ver
|
||||
cells.append(label)
|
||||
|
||||
if not cells:
|
||||
return ""
|
||||
|
||||
header = "| " + " | ".join(cells) + " |"
|
||||
sep = "| " + " | ".join(":---:" for _ in cells) + " |"
|
||||
rows = [header, sep]
|
||||
|
||||
# Optional description row — only rendered if at least one target has one
|
||||
descs = [(t.get("description") or "").replace("\n", "<br>") for t in targets]
|
||||
if any(descs):
|
||||
rows.append("| " + " | ".join(descs) + " |")
|
||||
|
||||
return "\n".join(rows)
|
||||
|
||||
|
||||
def spoiler(label, count, targets, tbl, expanded=False):
|
||||
"""Wrap a patches table in a <details> spoiler with a versions sub-table.
|
||||
If expanded=True, the spoiler is open by default (for repos with few patches).
|
||||
"""
|
||||
noun = "patch" if count == 1 else "patches"
|
||||
vtbl = versions_table(targets)
|
||||
versions_section = f"**🎯 Supported versions:**\n\n{vtbl}\n\n" if vtbl else ""
|
||||
tag = "<details open>" if expanded else "<details>"
|
||||
return f"""{tag}
|
||||
<summary>{label} • {count} {noun}</summary>
|
||||
<br>
|
||||
|
||||
{versions_section}{tbl}
|
||||
|
||||
</details>"""
|
||||
|
||||
|
||||
def build_content(expanded=False):
|
||||
"""Build the full generated patches section."""
|
||||
lines = [
|
||||
f"> **[v{ver}](https://github.com/{owner}/{repo}/releases/tag/v{ver})**"
|
||||
f" • `{branch}` • "
|
||||
f"{total} patches total"
|
||||
]
|
||||
|
||||
# One spoiler per app, in the order they appear in the JSON
|
||||
for pkg, entry in by_pkg.items():
|
||||
patches = list(entry["patches"].values())
|
||||
label = f"{entry['emoji']} {entry['name']}"
|
||||
lines.append(spoiler(label, len(patches), entry["targets"], patches_table(patches), expanded))
|
||||
lines.append("")
|
||||
|
||||
# Universal patches (no specific app)
|
||||
if universal:
|
||||
uni_patches = list(universal.values())
|
||||
noun = "patch" if len(uni_patches) == 1 else "patches"
|
||||
tag = "<details open>" if expanded else "<details>"
|
||||
lines.append(f"""{tag}
|
||||
<summary>🌐 Universal • {len(uni_patches)} {noun}</summary>
|
||||
<br>
|
||||
|
||||
{patches_table(uni_patches)}
|
||||
|
||||
</details>""")
|
||||
lines.append("")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
# Build and inject
|
||||
raw_ver = data["version"]
|
||||
# Strip leading "v" if present
|
||||
ver = raw_ver.lstrip("v")
|
||||
total = sum(len(e["patches"]) for e in by_pkg.values()) + len(universal)
|
||||
|
||||
readme = readme_path.read_text(encoding="utf-8")
|
||||
|
||||
# Marker pattern — matches both <!-- PATCHES_START --> and <!-- PATCHES_START EXPANDED -->
|
||||
START_PATTERN = r"<!-- PATCHES_START(?:\s+EXPANDED)?\s*-->"
|
||||
END_MARKER = "<!-- PATCHES_END -->"
|
||||
|
||||
marker_match = re.search(START_PATTERN, readme)
|
||||
|
||||
if not marker_match or END_MARKER not in readme:
|
||||
# Fallback: print to stdout so CI can catch the issue
|
||||
print(build_content(expanded=False))
|
||||
sys.stderr.write(
|
||||
f"⚠️ Markers <!-- PATCHES_START [EXPANDED] --> / {END_MARKER} not found in {readme_path}. "
|
||||
"Printed to stdout instead.\n"
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
actual_start = marker_match.group(0)
|
||||
|
||||
# Auto-expand threshold
|
||||
AUTO_EXPAND_THRESHOLD = 20
|
||||
|
||||
# Spoilers are expanded if:
|
||||
# 1. Total patch count is small (≤ AUTO_EXPAND_THRESHOLD)
|
||||
# with only a few patches where collapsing adds no benefit.
|
||||
# 2. The README marker explicitly requests it: <!-- PATCHES_START EXPANDED -->
|
||||
expanded = (
|
||||
total <= AUTO_EXPAND_THRESHOLD or
|
||||
"EXPANDED" in actual_start
|
||||
)
|
||||
|
||||
generated = build_content(expanded=expanded)
|
||||
|
||||
# Replace template links if present
|
||||
readme = readme.replace("https://morphe.software/add-source?github=xyz-user/xyz-patches", f"https://morphe.software/add-source?github={repo_full}")
|
||||
readme = readme.replace("https://github.com/xyz-user/xyz-patches", f"https://github.com/{repo_full}")
|
||||
|
||||
new_readme = re.sub(
|
||||
rf"{START_PATTERN}.*?{re.escape(END_MARKER)}",
|
||||
f"{actual_start}\n{generated}\n{END_MARKER}",
|
||||
readme,
|
||||
flags=re.DOTALL,
|
||||
)
|
||||
readme_path.write_text(new_readme, encoding="utf-8")
|
||||
print(f"✅ Injected patches section into {readme_path} (v{ver}, branch={branch}, {total} patches, expanded={expanded})")
|
||||
@@ -0,0 +1,49 @@
|
||||
name: Open a PR to main
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
pull-request:
|
||||
name: Open pull request
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Open pull request
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
SOURCE_BRANCH: ${{ github.ref_name }}
|
||||
TARGET_BRANCH: main
|
||||
run: |
|
||||
# Check if PR already exists (base=main, head=dev)
|
||||
if gh pr list --base "$TARGET_BRANCH" --head "$SOURCE_BRANCH" --json number | jq -e '. | length > 0' >/dev/null; then
|
||||
echo "Pull request already exists."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check if there are any commits between branches
|
||||
COMMITS=$(gh api "repos/${{ github.repository }}/compare/${TARGET_BRANCH}...${SOURCE_BRANCH}" --jq '.total_commits')
|
||||
if [ "$COMMITS" -eq 0 ]; then
|
||||
echo "No commits between $TARGET_BRANCH and $SOURCE_BRANCH. Skipping PR creation."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Try to create the PR
|
||||
gh pr create \
|
||||
--base "$TARGET_BRANCH" \
|
||||
--head "$SOURCE_BRANCH" \
|
||||
--title 'chore: Merge branch `dev` to `main`' \
|
||||
--body "When dev (pre-release) is ready, then merge this pull request to create a new stable release." \
|
||||
|| {
|
||||
echo "::error::Pull request creation failed. Ensure 'Allow GitHub Actions to create and approve pull requests' is enabled in the repo Settings > Actions > General > Workflow permissions"
|
||||
exit 1
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Release
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
id-token: write
|
||||
attestations: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v5.7.0
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '21'
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: 'lts/*'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
|
||||
# Semantic release runs its own Gradle build command.
|
||||
# Only dev and main branch will generate a release as specified by .releaserc
|
||||
- name: Release
|
||||
uses: cycjimmy/semantic-release-action@v6
|
||||
id: release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Attest
|
||||
if: steps.release.outputs.new_release_published == 'true'
|
||||
uses: actions/attest-build-provenance@v4
|
||||
with:
|
||||
subject-name: 'Morphe Patches ${{ steps.release.outputs.new_release_git_tag }}'
|
||||
subject-path: patches/build/libs/patches-*.mpp
|
||||
|
||||
# Always verify compilation when semantic-release did not publish a release.
|
||||
# This covers non-main/dev branches, chore commits, docs commits, etc.
|
||||
- name: Verify project compiles
|
||||
if: steps.release.outputs.new_release_published != 'true'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: ./gradlew :patches:buildAndroid clean --no-daemon
|
||||
Reference in New Issue
Block a user