6 Commits

Author SHA1 Message Date
florian.berthold 5e67e7ef9c ci: respect GITHUB_REPOSITORY + tolerate per-asset upload failures
release / release (push) Successful in 13s
2026-05-25 12:17:01 +02:00
florian.berthold a842d5b761 ci: add Gitea Actions release workflow (per-addon git-archive zip)
release / release (push) Failing after 12s
2026-05-25 12:01:47 +02:00
florian.berthold 5c369f7886 chore: remove .github/ (upstream templates, not relevant on Gitea) 2026-05-25 11:02:53 +02:00
florian.berthold 40cd0f684f chore: align with Exiles fork-layout convention (standard .gitignore + .gitattributes) 2026-05-25 10:59:33 +02:00
florian.berthold 6078989cb1 fix(login): defer downgrade popup, guard data, include CoA classes
lint / lint (push) Has been cancelled
Three fixes addressing the reported 'auras silently vanish on next save'
bug, plus collateral robustness in the same code path:

1. WeakAuras.lua PLAYER_LOGIN handler (~L1297-1308): the downgrade branch
   fired StaticPopup_Show('WEAKAURAS_CONFIRM_REPAIR', ...) synchronously
   inside the event handler. On the CoA reworked StaticPopup system this
   fires too early and silently fails to show the dialog, which means
   neither OnAccept nor OnCancel ever runs, so Private.Login() is never
   called. With no displays loaded, the next PLAYER_LOGOUT serializes an
   empty table over WeakAurasSaved and the user loses all their auras.
   Wrap the call in C_Timer.After(0, ...) so it fires after the event
   frame stack has unwound and the popup system is ready.

   This is the most likely root cause of the user report 'my auras don't
   save anymore' that surfaced on the PTR last week.

2. WeakAuras.lua WEAKAURAS_CONFIRM_REPAIR popup (~L2263-2277): OnShow and
   OnCancel both unconditionally dereferenced self.data.reason. If the
   popup is ever fired with nil or malformed data this throws and blocks
   Private.Login() from running via the OnCancel fallback. Guard with a
   nil check and default reason to 'unknown' (treated as the automatic /
   downgrade path, which is the safe default that still invokes Login).

3. Types.lua WeakAuras.class_types (~L1187): only populated from
   CLASS_SORT_ORDER, which on CoA contains only the 11 vanilla classes.
   The 21 custom CoA classes were silently missing from every class
   filter dropdown in the options UI. Add a fallback loop over
   LOCALIZED_CLASS_NAMES_MALE for anything CLASS_SORT_ORDER didn't
   already register, mirroring the pattern the spec builder uses at
   ~L3829.
2026-05-24 17:38:22 +02:00
florian.berthold 43b5f05a4c fix: pcall-guard C_ClassInfo.GetSpecInfo in Types.lua spec iteration
lint / lint (push) Has been cancelled
Same CoA client issue as coa-details: GetAllSpecs(class) returns items
that GetSpecInfo rejects with 'Expected string' at arg #2. Wrap the
addSpec(class, GetSpecInfo(class, spec)) call in pcall so bad iterations
skip silently rather than spamming Error.txt.
2026-05-23 00:14:38 +02:00
12 changed files with 182 additions and 410 deletions
+71
View File
@@ -0,0 +1,71 @@
name: release
on:
push:
tags:
- '*-coa.*' # Asc-1.1.6-coa.2, 9.1.40-coa.3, etc.
- 'v*' # v0.3.0 for repos without an upstream version
jobs:
release:
runs-on: linux-amd64
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0 # build_zip uses git archive HEAD; full history is fine
- name: Build per-addon zip(s)
run: bash tools/build_zip.sh
- name: Publish release (Gitea API direct; no action dependency)
env:
GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
TAG: ${{ github.ref_name }}
API: ${{ github.server_url }}/api/v1
# Gitea attachment ceiling is 200 MiB (see roles/gitea config).
# Skip anything larger so one oversized asset doesn't fail the job.
MAX_BYTES: 209715200
run: |
set -euo pipefail
# Create the release (or reuse if it already exists for this tag).
RID=$(curl -s -H "Authorization: token $GITEA_TOKEN" \
"$API/repos/$REPO/releases/tags/$TAG" 2>/dev/null \
| jq -r '.id // empty')
if [ -z "$RID" ]; then
RID=$(curl -sf -X POST -H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
"$API/repos/$REPO/releases" \
-d "$(jq -nc --arg t "$TAG" '{tag_name:$t,name:$t,draft:false,prerelease:false}')" \
| jq -r '.id')
fi
echo "release id: $RID"
# Upload every dist/*.zip. Per-asset failures don't fail the job —
# we want partial releases to still publish rather than block the
# whole pipeline on one big file.
failed=0
uploaded=0
for f in dist/*.zip; do
name=$(basename "$f")
size=$(stat -c '%s' "$f")
if [ "$size" -gt "$MAX_BYTES" ]; then
echo "::warning::skip $name (${size} B > ${MAX_BYTES} B Gitea limit; host on CDN instead)"
failed=$((failed+1))
continue
fi
echo "uploading $name ($(numfmt --to=iec "$size"))"
if curl -sf -X POST -H "Authorization: token $GITEA_TOKEN" \
-F "attachment=@$f" \
"$API/repos/$REPO/releases/$RID/assets?name=$name" \
| jq -r '" -> " + .browser_download_url'; then
uploaded=$((uploaded+1))
else
echo "::warning::upload failed for $name"
failed=$((failed+1))
fi
done
echo "release published: $uploaded uploaded, $failed skipped/failed"
# Only fail the job if NO assets uploaded — a release with zero
# attachments isn't useful to anyone.
[ "$uploaded" -gt 0 ]
-14
View File
@@ -1,14 +0,0 @@
# These are supported funding model platforms
github: [NoM0Re] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
custom: https://streamelements.com/nom0ree/tip
-123
View File
@@ -1,123 +0,0 @@
name: "Bug Report"
description: Create a report to help us improve
labels: ['🐛 Bug']
body:
- type: checkboxes
attributes:
label: Is there an existing issue for this?
description: Please [search for existing issues](https://github.com/Ascension-Addons/WeakAuras-Ascension/issues) to see if an open or closed one already exists for the bug you encountered. If a bug exists and it is closed as complete it may not yet be in a stable release.
options:
- label: I have searched the existing open and closed issues.
required: true
- type: textarea
attributes:
label: Description
description: What did you expect to happen and what happened instead?
validations:
required: true
- type: input
attributes:
label: WeakAuras Version
description: |
You can see the current version in the title bar of the options window, if the options do not open, check the `## Version:` field in the WeakAuras.toc file.
placeholder: "WeakAuras 5.0.0"
validations:
required: true
- type: dropdown
id: flavor
attributes:
label: World of Warcraft Flavor
description: What version of World of Warcraft are are you running?
options:
- WotLK 3.3.5a
validations:
required: true
- type: dropdown
id: language
attributes:
label: World of Warcraft Language
description: In which language do you play World of Warcraft?
options:
- enGB/enUS
- deDE
- frFR
- itIT
- esES
- esMX
- koKR
- ptBR
- ruRU
- zhCN
- zhTW
validations:
required: true
- type: input
id: server
attributes:
label: Server
description: On which server/realm are you playing?
placeholder: Warmane-Icecrown
validations:
required: true
- type: checkboxes
id: testing
attributes:
label: Tested with only WeakAuras
description: Sometimes, other addons can interfere with WeakAuras. We recommend testing with only WeakAuras enabled to see if the issue persists.
options:
- label: I got this issue with only WeakAuras enabled
required: true
- type: textarea
attributes:
label: Lua Error
description: |
Do you have an error log of what happened? If you don't see any errors, make sure that error reporting is enabled (`/console scriptErrors 1`) or install [BugSack](https://www.curseforge.com/wow/addons/bugsack) & [BugGrabber](https://www.curseforge.com/wow/addons/bug-grabber), yes both are needed.
Note that if the error looks like `[string "--[[ Error in ' my awesome aura' ]` then the bug is in the aura that got mentioned, not in WeakAuras itself.
render: Text
validations:
required: false
- type: textarea
attributes:
label: Reproduction Steps
description: Please list out the steps to reproduce your bug. Please verify that your reproduction steps are enough to reproduce the problem.
placeholder: |
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
validations:
required: true
- type: input
attributes:
label: Last Good Version
description: |
Was it working in a previous version? If yes, which was the last good one?
placeholder: "WeakAuras 5.0.0"
validations:
required: false
- type: textarea
attributes:
label: Screenshots
description: If applicable, add screenshots to help explain your problem.
placeholder: Click here to attach your screenshots via the editor button in the top right.
validations:
required: false
- type: textarea
attributes:
label: Export String
description: If you do not know which aura is causing issues for you, please attach a ZIP archive of your WeakAuras SavedVariables file, it's the `WeakAuras.lua` file in `World of Warcraft\_retail_\WTF\Account\YOUR_ACCOUNT\SavedVariables\`. In case you do, please export the string and paste it below.
placeholder: Paste your exported WeakAuras string here.
render: Text
validations:
required: false
-5
View File
@@ -1,5 +0,0 @@
blank_issues_enabled: false
contact_links:
- name: Question
url: https://discord.gg/classlesswow
about: Please ask and answer questions here.
-20
View File
@@ -1,20 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: "\U0001F3A8 Feature Request"
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
-115
View File
@@ -1,115 +0,0 @@
name: Autoclose after 2 days
on:
schedule:
- cron: '0 0 * * *'
issues:
types: [labeled]
pull_request:
types: [labeled]
permissions:
issues: write
pull-requests: write
jobs:
autoclose:
runs-on: ubuntu-latest
steps:
- name: Close Issues/PRs labeled 'Auto Close' after 2 days, if label set by collaborator and no recent comments
uses: actions/github-script@v7
with:
script: |
const { owner, repo } = context.repo;
const labelName = '⏳Auto Close';
const maxAgeDays = 2;
const now = new Date();
const issues = await github.paginate(github.rest.issues.listForRepo, {
owner,
repo,
labels: labelName,
state: 'open',
per_page: 100,
});
for (const issue of issues) {
const issue_number = issue.number;
const events = await github.paginate(github.rest.issues.listEventsForTimeline, {
owner,
repo,
issue_number,
per_page: 100,
});
const labelEvent = events.find(event =>
event.event === 'labeled' &&
event.label?.name === labelName
);
if (!labelEvent) continue;
let labelDate = new Date(labelEvent.created_at);
const comments = await github.paginate(
issue.pull_request
? github.rest.pulls.listReviewComments
: github.rest.issues.listComments,
{
owner,
repo,
issue_number,
per_page: 100,
}
);
const recentCommentsAfterLabel = comments
.filter(c => new Date(c.created_at) > labelDate);
let latestDate = labelDate;
if (recentCommentsAfterLabel.length > 0) {
latestDate = new Date(Math.max(...recentCommentsAfterLabel.map(c => new Date(c.created_at).getTime())));
}
const diffDays = (now - latestDate) / (1000 * 60 * 60 * 24);
if (diffDays < maxAgeDays) continue;
const actor = labelEvent.actor?.login;
if (!actor) continue;
try {
await github.rest.repos.checkCollaborator({
owner,
repo,
username: actor,
});
} catch (error) {
if (error.status === 404) {
await github.rest.issues.removeLabel({
owner,
repo,
issue_number,
name: labelName,
});
continue;
} else {
throw error;
}
}
await github.rest.issues.update({
owner,
repo,
issue_number,
state: 'closed',
});
await github.rest.issues.removeLabel({
owner,
repo,
issue_number,
name: labelName,
});
}
-62
View File
@@ -1,62 +0,0 @@
name: lint
on:
pull_request:
paths:
- '**.lua'
jobs:
lint:
runs-on: ubuntu-latest
env:
LUA_VERSION: 5.1.5
LUAROCKS_VERSION: 3.11.1
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Cache Lua
uses: actions/cache@v3
id: luacache
with:
path: .lua
key: ${{ runner.os }}-lua-${{ env.LUA_VERSION }}
restore-keys: |
${{ runner.os }}-lua-${{ env.LUA_VERSION }}
- name: Cache LuaRocks
uses: actions/cache@v3
id: luarockscache
with:
path: .luarocks
key: ${{ runner.os }}-luarocks-${{ env.LUAROCKS_VERSION }}
restore-keys: |
${{ runner.os }}-luarocks-${{ env.LUAROCKS_VERSION }}
- name: Install Lua
if: steps.luacache.outputs.cache-hit != 'true'
run: |
sudo apt-get install libreadline-dev libncurses-dev
wget https://www.lua.org/ftp/lua-${{ env.LUA_VERSION }}.tar.gz -O - | tar -xzf -
cd lua-${{ env.LUA_VERSION }}
make linux
make -j INSTALL_TOP=$GITHUB_WORKSPACE/.lua install
rm -rf $GITHUB_WORKSPACE/lua-${{ env.LUA_VERSION }}
- name: Install LuaRocks and Luacheck
if: steps.luarockscache.outputs.cache-hit != 'true'
run: |
wget https://luarocks.org/releases/luarocks-${{ env.LUAROCKS_VERSION }}.tar.gz -O - | tar -xzf -
cd luarocks-${{ env.LUAROCKS_VERSION }}
./configure --with-lua-bin=$GITHUB_WORKSPACE/.lua/bin --prefix=$GITHUB_WORKSPACE/.luarocks
make build
make install
PATH=$PATH:$GITHUB_WORKSPACE/.luarocks/bin
luarocks install luacheck
luarocks install lanes
rm -rf $GITHUB_WORKSPACE/luarocks-${{ env.LUAROCKS_VERSION }}
- name: Luacheck
run: .luarocks/bin/luacheck . -q
-66
View File
@@ -1,66 +0,0 @@
name: lint
on:
push:
paths:
- '.github/workflows/**.yml'
- '**.lua'
pull_request:
paths:
- '**.lua'
jobs:
lint:
runs-on: ubuntu-latest
env:
LUA_VERSION: 5.1.5
LUAROCKS_VERSION: 3.11.1
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Cache Lua
uses: actions/cache@v3
id: luacache
with:
path: .lua
key: ${{ runner.os }}-lua-${{ env.LUA_VERSION }}
restore-keys: |
${{ runner.os }}-lua-${{ env.LUA_VERSION }}
- name: Cache LuaRocks
uses: actions/cache@v3
id: luarockscache
with:
path: .luarocks
key: ${{ runner.os }}-luarocks-${{ env.LUAROCKS_VERSION }}
restore-keys: |
${{ runner.os }}-luarocks-${{ env.LUAROCKS_VERSION }}
- name: Install Lua
if: steps.luacache.outputs.cache-hit != 'true'
run: |
sudo apt-get install libreadline-dev libncurses-dev
wget https://www.lua.org/ftp/lua-${{ env.LUA_VERSION }}.tar.gz -O - | tar -xzf -
cd lua-${{ env.LUA_VERSION }}
make linux
make -j INSTALL_TOP=$GITHUB_WORKSPACE/.lua install
rm -rf $GITHUB_WORKSPACE/lua-${{ env.LUA_VERSION }}
- name: Install LuaRocks and Luacheck
if: steps.luarockscache.outputs.cache-hit != 'true'
run: |
wget https://luarocks.org/releases/luarocks-${{ env.LUAROCKS_VERSION }}.tar.gz -O - | tar -xzf -
cd luarocks-${{ env.LUAROCKS_VERSION }}
./configure --with-lua-bin=$GITHUB_WORKSPACE/.lua/bin --prefix=$GITHUB_WORKSPACE/.luarocks
make build
make install
PATH=$PATH:$GITHUB_WORKSPACE/.luarocks/bin
luarocks install luacheck
luarocks install lanes
rm -rf $GITHUB_WORKSPACE/luarocks-${{ env.LUAROCKS_VERSION }}
- name: Luacheck
run: .luarocks/bin/luacheck .
+3
View File
@@ -1 +1,4 @@
.idea
.idea
dist/
+14 -1
View File
@@ -1187,6 +1187,16 @@ WeakAuras.class_types = {}
for i, class in ipairs(CLASS_SORT_ORDER) do
WeakAuras.class_types[class] = WrapTextInColorCode(LOCALIZED_CLASS_NAMES_MALE[class], WA_GetClassColor(class))
end
-- CoA: CLASS_SORT_ORDER only contains the 11 vanilla classes, missing the 21
-- custom CoA classes. Fall back to LOCALIZED_CLASS_NAMES_MALE for anything not
-- yet registered. Same pattern used by the spec builder further down this file.
if LOCALIZED_CLASS_NAMES_MALE then
for class in pairs(LOCALIZED_CLASS_NAMES_MALE) do
if not WeakAuras.class_types[class] then
WeakAuras.class_types[class] = WrapTextInColorCode(LOCALIZED_CLASS_NAMES_MALE[class], WA_GetClassColor(class))
end
end
end
if WeakAuras.IsClassicPlus() then
WeakAuras.class_types["DEATHKNIGHT"] = nil
end
@@ -3839,7 +3849,10 @@ do
local specs = C_ClassInfo.GetAllSpecs(class)
if specs then
for _, spec in ipairs(specs) do
addSpec(class, C_ClassInfo.GetSpecInfo(class, spec))
local ok, info = pcall(C_ClassInfo.GetSpecInfo, class, spec)
if ok and info then
addSpec(class, info)
end
end
end
end
+23 -4
View File
@@ -1304,8 +1304,16 @@ loadedFrame:SetScript("OnEvent", function(self, event, ...)
if dbIsValid then
Private.Login(takeNewSnapshots)
else
-- db isn't valid. Request permission to run repair tool before logging in
StaticPopup_Show("WEAKAURAS_CONFIRM_REPAIR", nil, nil, {reason = "downgrade"})
-- db isn't valid. Request permission to run repair tool before logging in.
-- CoA: defer the StaticPopup_Show by one frame so it fires after the
-- PLAYER_LOGIN event-frame stack has unwound. On the CoA reworked
-- StaticPopup system, firing this synchronously during PLAYER_LOGIN
-- silently fails to show the popup, which means Private.Login() is
-- never invoked, no auras load, and the next logout writes an empty
-- WeakAurasSaved over the user's profile.
C_Timer.After(0, function()
StaticPopup_Show("WEAKAURAS_CONFIRM_REPAIR", nil, nil, {reason = "downgrade"})
end)
end
elseif event == "PLAYER_LOGOUT" then
for id in pairs(db.displays) do
@@ -2264,14 +2272,25 @@ StaticPopupDialogs["WEAKAURAS_CONFIRM_REPAIR"] = {
local AutomaticRepairText = L["WeakAuras has detected that it has been downgraded.\nYour saved auras may no longer work properly.\nWould you like to run the |cffff0000EXPERIMENTAL|r repair tool? This will overwrite any changes you have made since the last database upgrade.\nLast upgrade: %s\n\n|cffff0000You should BACKUP your WTF folder BEFORE pressing this button.|r"]
local ManualRepairText = L["Are you sure you want to run the |cffff0000EXPERIMENTAL|r repair tool?\nThis will overwrite any changes you have made since the last database upgrade.\nLast upgrade: %s"]
if self.data.reason == "user" then
-- CoA: guard against malformed data; default reason to "unknown" so the
-- popup can't error out and block Private.Login() from ever running.
local reason = "unknown"
if self.data then
reason = self.data.reason or "unknown"
end
if reason == "user" then
self.text:SetText(ManualRepairText:format(LastUpgrade()))
else
self.text:SetText(AutomaticRepairText:format(LastUpgrade()))
end
end,
OnCancel = function(self)
if self.data.reason ~= "user" then
local reason = "unknown"
if self.data then
reason = self.data.reason or "unknown"
end
if reason ~= "user" then
Private.Login()
end
end,
+71
View File
@@ -0,0 +1,71 @@
#!/usr/bin/env bash
# Build per-addon zip artefacts from HEAD via git-archive.
#
# - Discovers top-level addon folders (Foo/Foo.toc).
# - Re-creates dist/ each run.
# - Always archives HEAD, so the working tree state is irrelevant.
# - If more than one addon folder is present, also emits <repo>-all.zip
# with every addon folder side-by-side at the zip root.
# - When run inside Gitea Actions the working tree lives under a
# per-job dir like /var/lib/act_runner/work/.../hostexecutor, so the
# repo name comes from $GITHUB_REPOSITORY (set by the runner) and
# only falls back to the toplevel basename for local invocations.
set -euo pipefail
root=$(git rev-parse --show-toplevel)
cd "$root"
# Gitea Actions sets GITHUB_REPOSITORY=owner/repo. The basename of
# `git rev-parse --show-toplevel` inside the runner is the worker dir
# (e.g. `hostexecutor`), which would name the bundle wrong.
if [ -n "${GITHUB_REPOSITORY:-}" ]; then
repo_name="${GITHUB_REPOSITORY##*/}"
else
repo_name=$(basename "$root")
fi
dist="$root/dist"
# Find Foo/Foo.toc pairs at depth 1; ignore libs nested deeper.
addons=()
while IFS= read -r toc; do
dir=$(dirname "$toc")
folder=$(basename "$dir")
base=$(basename "$toc" .toc)
# Accept Foo.toc and Foo_Wrath.toc style flavour variants; folder must match
# at least one toc basename prefix (Foo).
case "$base" in
"$folder"|"$folder"_*) addons+=("$folder") ;;
esac
done < <(command find . -mindepth 2 -maxdepth 2 -type f -name '*.toc' | sed 's|^\./||' | sort)
# Dedupe (a folder with Foo.toc + Foo_Wrath.toc shows up twice).
if [ ${#addons[@]} -gt 0 ]; then
mapfile -t addons < <(printf '%s\n' "${addons[@]}" | awk '!seen[$0]++')
fi
if [ ${#addons[@]} -eq 0 ]; then
echo "no addon folders found (looking for */Foo.toc with matching folder name)" >&2
exit 1
fi
rm -rf "$dist"
mkdir -p "$dist"
for folder in "${addons[@]}"; do
out="$dist/$folder.zip"
# No --prefix: the folder already sits at the repo root, so git-archive
# emits entries as <folder>/... which is exactly what
# Interface/AddOns/ expects after extraction.
git archive HEAD --format=zip -o "$out" -- "$folder"
echo "built dist/$folder.zip"
done
# Combined bundle only makes sense when there are multiple addons.
if [ ${#addons[@]} -gt 1 ]; then
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT
git archive HEAD --format=tar -- "${addons[@]}" | tar -x -C "$tmp"
out="$dist/$repo_name-all.zip"
( cd "$tmp" && zip -qr "$out" "${addons[@]}" )
echo "built dist/$repo_name-all.zip"
fi