Skip to content
Merged
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
25 changes: 22 additions & 3 deletions .github/ISSUE_TEMPLATE/add-script.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,33 @@ body:
id: category
attributes:
label: Category
description: Choose the most appropriate category.
description: |
Choose the most appropriate category. If none of these fit, leave this blank and
use the New Category field below instead.
options:
# --- BEGIN AUTO-GENERATED CATEGORIES (see scripts.schema.json: items.properties.category.examples) ---
- Audio & Media
- Flight Controller Config
- Games & Fun
- GPS & Mapping
- Logging & Analysis
- Radio Tools
- Telemetry & Widgets
# --- END AUTO-GENERATED CATEGORIES ---
validations:
required: true
required: false

- type: input
id: category_extra
attributes:
label: New Category
description: |
Only fill this in if none of the categories above fit — this creates a brand-new
gallery category (a new top-level tab on the site), so please only use it if you're
confident an existing one genuinely doesn't apply.
placeholder: e.g. Simulators
validations:
required: false

- type: textarea
id: description
Expand Down Expand Up @@ -72,6 +88,7 @@ body:
label: Tags
description: Check all that apply.
options:
# --- BEGIN AUTO-GENERATED TAGS (see scripts.schema.json: items.properties.tags.items.examples) ---
- label: ardupilot
- label: battery
- label: betaflight
Expand All @@ -85,12 +102,14 @@ body:
- label: gps
- label: heli
- label: inav
- label: logging
- label: multi-protocol
- label: quad
- label: rotorflight
- label: script
- label: spektrum
- label: tool
- label: widget
# --- END AUTO-GENERATED TAGS ---

- type: input
id: tags_extra
Expand Down
23 changes: 21 additions & 2 deletions .github/ISSUE_TEMPLATE/update-script.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,31 @@ body:
id: category
attributes:
label: Category
description: Leave unselected to keep the existing category.
description: |
Leave unselected to keep the existing category. If none of these fit, use the
New Category field below instead.
options:
# --- BEGIN AUTO-GENERATED CATEGORIES (see scripts.schema.json: items.properties.category.examples) ---
- Audio & Media
- Flight Controller Config
- Games & Fun
- GPS & Mapping
- Logging & Analysis
- Radio Tools
- Telemetry & Widgets
# --- END AUTO-GENERATED CATEGORIES ---
validations:
required: false

- type: input
id: category_extra
attributes:
label: New Category
description: |
Only fill this in if you want to change to a category not in the dropdown above —
this creates a brand-new gallery category (a new top-level tab on the site), so
please only use it if you're confident an existing one genuinely doesn't apply.
placeholder: e.g. Simulators
validations:
required: false

Expand Down Expand Up @@ -72,6 +88,7 @@ body:
Leave **all boxes unchecked** to keep existing tags unchanged.
Checking any box (or filling in Additional Tags) will **replace the full tag list**.
options:
# --- BEGIN AUTO-GENERATED TAGS (see scripts.schema.json: items.properties.tags.items.examples) ---
- label: ardupilot
- label: battery
- label: betaflight
Expand All @@ -85,12 +102,14 @@ body:
- label: gps
- label: heli
- label: inav
- label: logging
- label: multi-protocol
- label: quad
- label: rotorflight
- label: script
- label: spektrum
- label: tool
- label: widget
# --- END AUTO-GENERATED TAGS ---

- type: input
id: tags_extra
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/gh-pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ jobs:
steps:
- uses: actions/checkout@v6
- uses: astral-sh/setup-uv@v8.1.0
with:
enable-cache: false
- name: Generate site
run: uv run tools/generate_site.py --scripts-json scripts.json --assets-dir ASSETS --output-dir site
- name: Upload Pages artifact
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/script-submission.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ jobs:
- uses: actions/checkout@v6

- uses: astral-sh/setup-uv@v8.1.0
with:
enable-cache: false

- name: Write issue body to file
env:
Expand Down
159 changes: 159 additions & 0 deletions .github/workflows/validate-issue-templates.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
name: Validate Issue Template Tags & Categories

on:
push:
paths:
- 'scripts.json'
- 'scripts.schema.json'
- '.github/ISSUE_TEMPLATE/add-script.yml'
- '.github/ISSUE_TEMPLATE/update-script.yml'
- 'tools/sync_issue_template_options.py'
- 'tools/test_sync_issue_template_options.py'
- '.github/workflows/validate-issue-templates.yml'
pull_request:
paths:
- 'scripts.json'
- 'scripts.schema.json'
- '.github/ISSUE_TEMPLATE/add-script.yml'
- '.github/ISSUE_TEMPLATE/update-script.yml'
- 'tools/sync_issue_template_options.py'
- 'tools/test_sync_issue_template_options.py'
- '.github/workflows/validate-issue-templates.yml'
pull_request_target:
paths:
- 'scripts.json'
- 'scripts.schema.json'
- '.github/ISSUE_TEMPLATE/add-script.yml'
- '.github/ISSUE_TEMPLATE/update-script.yml'
- 'tools/sync_issue_template_options.py'
- 'tools/test_sync_issue_template_options.py'
- '.github/workflows/validate-issue-templates.yml'

permissions:
contents: read

jobs:
# Read-only check. Runs for every PR, including forks (which get a
# restricted, non-writable GITHUB_TOKEN here — that's fine, this job never
# writes anything).
check:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: astral-sh/setup-uv@v8.1.0
with:
enable-cache: false
- name: Run sync script tests
run: uv run tools/test_sync_issue_template_options.py
- name: Check tags/category sync
run: uv run tools/sync_issue_template_options.py --check

# Self-heals same-repo branches (this workflow's own commit is made with
# GITHUB_TOKEN, so it never re-triggers itself or any other workflow —
# GitHub Actions doesn't fire new workflow-triggering events for
# GITHUB_TOKEN-authored pushes).
sync:
if: github.event_name == 'push'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v6
- uses: astral-sh/setup-uv@v8.1.0
with:
enable-cache: false
- name: Run sync script tests
run: uv run tools/test_sync_issue_template_options.py
- name: Sync tags/category
run: uv run tools/sync_issue_template_options.py --write | tee /tmp/sync-output.txt
- name: Annotate new categories
run: |
line=$(grep '^new_categories=' /tmp/sync-output.txt || true)
if [ -n "$line" ]; then
echo "::warning::New categories introduced: ${line#new_categories=} — please confirm these aren't duplicates/typos before merging."
fi
- name: Annotate unused categories
run: |
line=$(grep '^unused_categories=' /tmp/sync-output.txt || true)
if [ -n "$line" ]; then
echo "::notice::Categories with no current entries: ${line#unused_categories=} — consider pruning scripts.schema.json if any of these are stale."
fi
- name: Commit corrections if needed
run: |
if ! git diff --quiet -- scripts.schema.json .github/ISSUE_TEMPLATE/add-script.yml .github/ISSUE_TEMPLATE/update-script.yml; then
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add scripts.schema.json .github/ISSUE_TEMPLATE/add-script.yml .github/ISSUE_TEMPLATE/update-script.yml
git commit -m "chore: sync issue template tags/categories"
git push origin "HEAD:${{ github.ref_name }}"
fi

# Fork PRs can never be auto-committed to (GITHUB_TOKEN cannot push to a
# different repository under any trigger, pull_request_target included —
# that event only grants a writable token for *this* repo). Instead this
# job safely computes the same fix and posts it as a sticky PR comment.
suggest-fix-for-fork-prs:
if: github.event_name == 'pull_request_target' && github.event.pull_request.head.repo.full_name != github.repository
runs-on: ubuntu-latest
permissions:
pull-requests: write
contents: read
steps:
# Deliberately no `ref:` override: this checks out the *base* branch's
# trusted copy of the sync script, never the PR's own version — a
# malicious PR can't smuggle in a modified script for this elevated
# job to execute.
- uses: actions/checkout@v6
- uses: astral-sh/setup-uv@v8.1.0
with:
enable-cache: false
- name: Fetch PR's scripts.json as data only (never executed)
run: |
git fetch origin "pull/${{ github.event.pull_request.number }}/head"
git show FETCH_HEAD:scripts.json > /tmp/pr-scripts.json
- name: Compute what --write would change, without committing
id: diff
run: |
uv run tools/sync_issue_template_options.py --write --scripts-json /tmp/pr-scripts.json || true
if git diff --quiet -- scripts.schema.json .github/ISSUE_TEMPLATE/add-script.yml .github/ISSUE_TEMPLATE/update-script.yml; then
echo "has_diff=false" >> "$GITHUB_OUTPUT"
else
git diff -- scripts.schema.json .github/ISSUE_TEMPLATE/add-script.yml .github/ISSUE_TEMPLATE/update-script.yml | head -n 200 > /tmp/sync.diff
echo "has_diff=true" >> "$GITHUB_OUTPUT"
fi
# Discard the local write — nothing from this job is ever committed.
git checkout -- scripts.schema.json .github/ISSUE_TEMPLATE/add-script.yml .github/ISSUE_TEMPLATE/update-script.yml
- name: Post or update sticky PR comment
uses: actions/github-script@v9
with:
script: |
const fs = require('fs');
const marker = '<!-- sync-issue-template-options-bot -->';
const hasDiff = '${{ steps.diff.outputs.has_diff }}' === 'true';
let body;
if (hasDiff) {
const diff = fs.existsSync('/tmp/sync.diff') ? fs.readFileSync('/tmp/sync.diff', 'utf8') : '';
body = marker + '\n\n⚠️ This PR introduces a new tag or category not yet reflected in `scripts.schema.json` / the issue form templates.\n\n' +
'Since this PR comes from a fork, this can\'t be fixed automatically here. To resolve it:\n' +
'- a maintainer can pull this branch into the repo (the automatic fix runs on push), or\n' +
'- you (or a maintainer) can run `uv run tools/sync_issue_template_options.py --write` locally and commit the result to this branch.\n\n' +
'<details><summary>Diff that would be applied (truncated to 200 lines)</summary>\n\n```diff\n' + diff + '\n```\n</details>';
} else {
body = marker + '\n\n✅ Tags/categories are in sync.';
}
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
const existing = comments.find(c => c.body.includes(marker));
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner, repo: context.repo.repo, comment_id: existing.id, body,
});
} else if (hasDiff) {
await github.rest.issues.createComment({
owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, body,
});
}
2 changes: 2 additions & 0 deletions .github/workflows/validate-scripts-json.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,7 @@ jobs:
steps:
- uses: actions/checkout@v6
- uses: astral-sh/setup-uv@v8.1.0
with:
enable-cache: false
- name: Validate scripts.json
run: uv run tools/validate_scripts.py --scripts-json scripts.json
8 changes: 8 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"json.schemas": [
{
"fileMatch": ["/scripts.json"],
"url": "./scripts.schema.json"
}
]
}
87 changes: 87 additions & 0 deletions scripts.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "EdgeTX Lua Scripts Gallery - scripts.json",
"description": "Schema for scripts.json. Also the canonical source for the known 'category' and 'tags' vocabularies, read and written at runtime by tools/sync_issue_template_options.py to keep the Issue Form templates in sync with actual usage in scripts.json. 'category.examples' and 'tags.items.examples' are machine-generated - do not hand-edit them, they will be overwritten on the next sync. category.examples grows monotonically (never auto-pruned); tags.items.examples is a pure snapshot of current usage (shrinks when a tag falls out of use).",
"type": "array",
"items": {
"type": "object",
"title": "ScriptEntry",
"required": [
"name",
"category",
"description",
"infourl",
"tags"
],
"properties": {
"name": {
"type": "string",
"minLength": 1,
"description": "Display name as shown in the gallery."
},
"category": {
"type": "string",
"minLength": 1,
"description": "Gallery category - drives the site's top-level navigation tabs. Machine-generated examples list below (monotonic add-only, see tools/sync_issue_template_options.py).",
"examples": [
"Audio & Media",
"Flight Controller Config",
"Games & Fun",
"GPS & Mapping",
"Logging & Analysis",
"Radio Tools",
"Telemetry & Widgets"
]
},
"description": {
"type": "string",
"minLength": 1,
"description": "Short 1-3 sentence description of the script."
},
"infourl": {
"type": "string",
"format": "uri",
"description": "Link to the project page, GitHub repo, or documentation. Must start with http:// or https:// (enforced by tools/validate_scripts.py and tools/issue_to_scripts.py, not by this schema)."
},
"images": {
"type": "array",
"items": {
"type": "string"
},
"description": "Screenshot references: local ASSETS/ paths or external image URLs."
},
"tags": {
"type": "array",
"minItems": 1,
"items": {
"type": "string",
"minLength": 1,
"description": "Machine-generated examples list below (current usage snapshot, see tools/sync_issue_template_options.py).",
"examples": [
"ardupilot",
"battery",
"betaflight",
"black-and-white",
"color",
"crawler",
"crossfire",
"expresslrs",
"fixed-wing",
"game",
"gps",
"heli",
"inav",
"multi-protocol",
"quad",
"rotorflight",
"script",
"spektrum",
"tool",
"widget"
]
}
}
},
"additionalProperties": true
}
}
Loading