Compare commits

..

3 Commits
v4.2.0 ... main

Author SHA1 Message Date
xRangerDE
8d52df23c7 docs: add AI release runbook for gitea 2026-03-05 01:04:18 +01:00
xRangerDE
99f71a8aab release: 4.2.1 switch updater fully to gitea 2026-03-05 00:58:11 +01:00
xRangerDE
935125a83e chore: switch updater and release flow to gitea 2026-03-05 00:49:30 +01:00
5 changed files with 271 additions and 12 deletions

90
README_AI_RELEASE.md Normal file
View File

@ -0,0 +1,90 @@
# AI Release Guide (Gitea)
Diese Datei ist die verbindliche Anleitung fuer KI-Agenten, um Releases fuer dieses Projekt zu erstellen.
## Zielsystem
- Git Host: `https://git.24-music.de`
- Repo: `Administrator/Twitch-VOD-Manager`
- Default Branch: `main`
- Release-Assets muessen auf Gitea liegen, nicht auf GitHub/Codeberg.
## Sicherheitsregeln
- Token niemals in Dateien committen.
- Token niemals in Logs, README oder Code schreiben.
- Nur als Environment Variable nutzen:
- PowerShell: ``$env:GITEA_TOKEN="<token>"``
- Wenn Token kompromittiert wirkt: sofort rotieren.
## Voraussetzungen vor Release
1. `origin` muss auf Gitea zeigen:
- `https://git.24-music.de/Administrator/Twitch-VOD-Manager.git`
2. Working Tree darf keine ungewollten Aenderungen enthalten.
3. DNS fuer `git.24-music.de` muss auf `159.195.45.29` aufloesen.
4. Die App verwendet Gitea-Updater-URLs:
- In `src/main.ts` via `GITEA_*` Konstanten/API.
- In `package.json`:
- `"build.publish.url": "https://git.24-music.de/Administrator/Twitch-VOD-Manager/releases/download/v${version}/"`
## Standardablauf fuer neues Release
Beispiel fuer `4.2.2`.
1. Version setzen:
- `npm version 4.2.2 --no-git-tag-version`
2. Build + Schnelltest:
- `npm run build`
- `npm run test:e2e:update-logic`
3. Windows Installer bauen:
- `npx electron-builder --win`
4. Pruefen, dass Dateien existieren:
- `release/Twitch-VOD-Manager-Setup-4.2.2.exe`
- `release/Twitch-VOD-Manager-Setup-4.2.2.exe.blockmap`
- `release/latest.yml`
5. Commit + Tag:
- `git add package.json package-lock.json`
- `git commit -m "release: 4.2.2"`
- `git tag v4.2.2`
6. Push:
- `git push origin main`
- `git push origin v4.2.2`
7. Release auf Gitea anlegen und Assets hochladen (API):
- Endpoint: `POST /api/v1/repos/Administrator/Twitch-VOD-Manager/releases`
- Endpoint: `POST /api/v1/repos/Administrator/Twitch-VOD-Manager/releases/{id}/assets?name=<file>`
8. Verifikation:
- `GET /api/v1/repos/Administrator/Twitch-VOD-Manager/releases/latest` muss `v4.2.2` liefern.
- Download-Links muessen `200` geben:
- `/releases/download/v4.2.2/latest.yml`
- `/releases/download/v4.2.2/Twitch-VOD-Manager-Setup-4.2.2.exe`
## Verbindliche Asset-Regel
Pro aktuellem Release muessen mindestens diese 3 Dateien vorhanden sein:
- `Twitch-VOD-Manager-Setup-<version>.exe`
- `Twitch-VOD-Manager-Setup-<version>.exe.blockmap`
- `latest.yml`
Ohne diese Dateien funktioniert der Auto-Updater nicht zuverlaessig.
## Script-Hinweis
Es gibt ein Script: `scripts/release_gitea.mjs` (npm: `release:gitea`).
- Vor Nutzung sicherstellen, dass:
- `GITEA_TOKEN` gesetzt ist.
- `origin` korrekt ist.
- der Tag existiert oder der Workflow diesen sauber erstellt.
Wenn das Script fehlschlaegt, immer auf den manuellen Standardablauf oben zurueckfallen.
## DNS/Netzwerk Fallback (nur wenn lokal noetig)
Wenn lokales DNS noch alt cached und Requests fehlschlagen, HTTP-Tests mit erzwungener Aufloesung:
- `curl --resolve git.24-music.de:443:159.195.45.29 https://git.24-music.de/api/v1/version`
Nur fuer Tests nutzen, nicht als dauerhaften Workaround.

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "twitch-vod-manager", "name": "twitch-vod-manager",
"version": "4.2.0", "version": "4.2.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "twitch-vod-manager", "name": "twitch-vod-manager",
"version": "4.2.0", "version": "4.2.1",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"axios": "^1.6.0", "axios": "^1.6.0",

View File

@ -1,6 +1,6 @@
{ {
"name": "twitch-vod-manager", "name": "twitch-vod-manager",
"version": "4.2.0", "version": "4.2.1",
"description": "Twitch VOD Manager - Download Twitch VODs easily", "description": "Twitch VOD Manager - Download Twitch VODs easily",
"main": "dist/main.js", "main": "dist/main.js",
"author": "xRangerDE", "author": "xRangerDE",
@ -16,7 +16,8 @@
"test:e2e:stress": "npm run test:e2e:release && npm run test:e2e:release && npm run test:e2e:release", "test:e2e:stress": "npm run test:e2e:release && npm run test:e2e:release && npm run test:e2e:release",
"pack": "npm run build && electron-builder --dir", "pack": "npm run build && electron-builder --dir",
"dist": "npm run build && electron-builder", "dist": "npm run build && electron-builder",
"dist:win": "npm run test:e2e:release && electron-builder --win" "dist:win": "npm run test:e2e:release && electron-builder --win",
"release:gitea": "node scripts/release_gitea.mjs"
}, },
"dependencies": { "dependencies": {
"axios": "^1.6.0", "axios": "^1.6.0",
@ -50,7 +51,7 @@
}, },
"publish": { "publish": {
"provider": "generic", "provider": "generic",
"url": "https://codeberg.org/Sucukdeluxe/Twitch-VOD-Manager/releases/download/latest/" "url": "https://git.24-music.de/Administrator/Twitch-VOD-Manager/releases/download/v${version}/"
} }
} }
} }

159
scripts/release_gitea.mjs Normal file
View File

@ -0,0 +1,159 @@
import fs from "node:fs";
import path from "node:path";
import { spawnSync } from "node:child_process";
const NPM_EXECUTABLE = process.platform === "win32" ? "npm.cmd" : "npm";
const BASE_URL = String(process.env.GITEA_BASE_URL || "https://git.24-music.de").replace(/\/+$/, "");
const OWNER = String(process.env.GITEA_REPO_OWNER || "Administrator").trim();
const REPO = String(process.env.GITEA_REPO_NAME || "Twitch-VOD-Manager").trim();
function run(command, args, options = {}) {
const result = spawnSync(command, args, {
cwd: process.cwd(),
encoding: "utf8",
input: options.input,
stdio: options.capture ? ["pipe", "pipe", "pipe"] : "inherit"
});
if (result.status !== 0) {
const stderr = String(result.stderr || "").trim();
const stdout = String(result.stdout || "").trim();
const details = [stderr, stdout].filter(Boolean).join("\n");
throw new Error(`Command failed: ${command} ${args.join(" ")}${details ? `\n${details}` : ""}`);
}
return String(result.stdout || "");
}
function parseArgs(argv) {
const args = argv.slice(2);
if (args.includes("--help") || args.includes("-h")) {
return { help: true };
}
const dryRun = args.includes("--dry-run");
const version = args.find((arg) => arg !== "--dry-run") || "";
const notes = args.filter((arg) => arg !== "--dry-run").slice(1).join(" ").trim();
return { help: false, dryRun, version, notes };
}
function ensureVersion(version) {
if (!/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(String(version || "").trim())) {
throw new Error("Invalid version format. Expected e.g. 4.2.0");
}
return String(version).trim();
}
function getAuthHeader() {
const token = String(process.env.GITEA_TOKEN || process.env.FORGEJO_TOKEN || "").trim();
if (token) {
return `token ${token}`;
}
const output = run("git", ["credential", "fill"], {
capture: true,
input: `protocol=https\nhost=${new URL(BASE_URL).host}\n\n`
});
const map = new Map();
for (const line of output.split(/\r?\n/)) {
if (!line.includes("=")) continue;
const [key, value] = line.split("=", 2);
map.set(key, value);
}
const username = map.get("username");
const password = map.get("password");
if (!username || !password) {
throw new Error("Missing Gitea credentials. Set GITEA_TOKEN or configure git credential helper.");
}
return `Basic ${Buffer.from(`${username}:${password}`, "utf8").toString("base64")}`;
}
async function apiRequest(method, url, authHeader, body, contentType = "application/json") {
const headers = { Accept: "application/json", Authorization: authHeader };
if (body !== undefined) headers["Content-Type"] = contentType;
const response = await fetch(url, { method, headers, body });
const text = await response.text();
let parsed = null;
try { parsed = text ? JSON.parse(text) : null; } catch { parsed = text; }
return { ok: response.ok, status: response.status, body: parsed };
}
function ensureAssets(version) {
const releaseDir = path.join(process.cwd(), "release");
const files = [
`Twitch-VOD-Manager-Setup-${version}.exe`,
`Twitch-VOD-Manager-Setup-${version}.exe.blockmap`,
"latest.yml"
];
for (const file of files) {
const fullPath = path.join(releaseDir, file);
if (!fs.existsSync(fullPath)) {
throw new Error(`Missing release artifact: ${fullPath}`);
}
}
return { releaseDir, files };
}
async function createOrGetRelease(baseApi, tag, authHeader, notes) {
const existing = await apiRequest("GET", `${baseApi}/releases/tags/${encodeURIComponent(tag)}`, authHeader);
if (existing.ok) return existing.body;
const payload = {
tag_name: tag,
target_commitish: "main",
name: tag,
body: notes || `Release ${tag}`,
draft: false,
prerelease: false
};
const created = await apiRequest("POST", `${baseApi}/releases`, authHeader, JSON.stringify(payload));
if (!created.ok) {
throw new Error(`Failed to create release (${created.status}): ${JSON.stringify(created.body)}`);
}
return created.body;
}
async function uploadAssets(baseApi, releaseId, authHeader, releaseDir, files) {
for (const fileName of files) {
const filePath = path.join(releaseDir, fileName);
const fileData = fs.readFileSync(filePath);
const uploadUrl = `${baseApi}/releases/${releaseId}/assets?name=${encodeURIComponent(fileName)}`;
const response = await apiRequest("POST", uploadUrl, authHeader, fileData, "application/octet-stream");
if (response.ok || response.status === 409 || response.status === 422) {
continue;
}
throw new Error(`Asset upload failed for ${fileName} (${response.status}): ${JSON.stringify(response.body)}`);
}
}
async function main() {
const args = parseArgs(process.argv);
if (args.help) {
process.stdout.write("Usage: npm run release:gitea -- <version> [release notes] [--dry-run]\n");
process.stdout.write("Env: GITEA_BASE_URL, GITEA_REPO_OWNER, GITEA_REPO_NAME, GITEA_TOKEN\n");
return;
}
const version = ensureVersion(args.version);
const tag = `v${version}`;
const authHeader = getAuthHeader();
const baseApi = `${BASE_URL}/api/v1/repos/${OWNER}/${REPO}`;
run("git", ["fetch", "--tags"]);
if (!args.dryRun) {
run("git", ["push", "origin", "main"]);
run("git", ["push", "origin", tag]);
}
run(NPM_EXECUTABLE, ["run", "dist:win"]);
const assets = ensureAssets(version);
if (args.dryRun) {
process.stdout.write(`Dry run complete for ${tag}\n`);
return;
}
const release = await createOrGetRelease(baseApi, tag, authHeader, args.notes);
await uploadAssets(baseApi, release.id, authHeader, assets.releaseDir, assets.files);
process.stdout.write(`Release published: ${release.html_url || `${BASE_URL}/${OWNER}/${REPO}/releases/tag/${tag}`}\n`);
}
main().catch((error) => {
process.stderr.write(`${String(error?.message || error)}\n`);
process.exit(1);
});

View File

@ -9,8 +9,13 @@ import { compareUpdateVersions, isNewerUpdateVersion, normalizeUpdateVersion } f
// ========================================== // ==========================================
// CONFIG & CONSTANTS // CONFIG & CONSTANTS
// ========================================== // ==========================================
const APP_VERSION = '4.1.13'; const APP_VERSION = app.getVersion();
const UPDATE_CHECK_URL = 'http://24-music.de/version.json'; const UPDATE_CHECK_URL = 'http://24-music.de/version.json';
const GITEA_BASE_URL = (process.env.GITEA_BASE_URL || 'https://git.24-music.de').replace(/\/+$/, '');
const GITEA_REPO_OWNER = process.env.GITEA_REPO_OWNER || 'Administrator';
const GITEA_REPO_NAME = process.env.GITEA_REPO_NAME || 'Twitch-VOD-Manager';
const GITEA_RELEASES_API_LATEST_URL = `${GITEA_BASE_URL}/api/v1/repos/${GITEA_REPO_OWNER}/${GITEA_REPO_NAME}/releases/latest`;
const GITEA_RELEASES_DOWNLOAD_BASE_URL = `${GITEA_BASE_URL}/${GITEA_REPO_OWNER}/${GITEA_REPO_NAME}/releases/download`;
// Paths // Paths
const APPDATA_DIR = path.join(process.env.PROGRAMDATA || 'C:\\ProgramData', 'Twitch_VOD_Manager'); const APPDATA_DIR = path.join(process.env.PROGRAMDATA || 'C:\\ProgramData', 'Twitch_VOD_Manager');
@ -2969,19 +2974,23 @@ async function requestUpdateCheck(source: UpdateCheckSource, force = false): Pro
try { try {
try { try {
const codebergRes = await axios.get('https://codeberg.org/api/v1/repos/Sucukdeluxe/Twitch-VOD-Manager/releases/latest', { const giteaRes = await axios.get(GITEA_RELEASES_API_LATEST_URL, {
timeout: 5000 timeout: 5000,
headers: {
'Accept': 'application/json',
'User-Agent': 'Twitch-VOD-Manager'
}
}); });
const tagName = codebergRes.data?.tag_name; const tagName = giteaRes.data?.tag_name;
if (tagName) { if (tagName) {
autoUpdater.setFeedURL({ autoUpdater.setFeedURL({
provider: 'generic', provider: 'generic',
url: `https://codeberg.org/Sucukdeluxe/Twitch-VOD-Manager/releases/download/${tagName}` url: `${GITEA_RELEASES_DOWNLOAD_BASE_URL}/${tagName}`
}); });
appendDebugLog('codeberg-feed-url-set', tagName); appendDebugLog('gitea-feed-url-set', { tagName, owner: GITEA_REPO_OWNER, repo: GITEA_REPO_NAME });
} }
} catch (apiErr) { } catch (apiErr) {
appendDebugLog('codeberg-api-failed', String(apiErr)); appendDebugLog('gitea-api-failed', String(apiErr));
} }
let timeoutHandle: NodeJS.Timeout | null = null; let timeoutHandle: NodeJS.Timeout | null = null;