Compare commits
2 Commits
bc47da504c
...
ba938f64c5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ba938f64c5 | ||
|
|
af00d69e5c |
9
.gitignore
vendored
9
.gitignore
vendored
@ -28,3 +28,12 @@ coverage/
|
|||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Forgejo deployment runtime files
|
||||||
|
deploy/forgejo/.env
|
||||||
|
deploy/forgejo/forgejo/
|
||||||
|
deploy/forgejo/postgres/
|
||||||
|
deploy/forgejo/caddy/data/
|
||||||
|
deploy/forgejo/caddy/config/
|
||||||
|
deploy/forgejo/caddy/logs/
|
||||||
|
deploy/forgejo/backups/
|
||||||
|
|||||||
23
CLAUDE.md
23
CLAUDE.md
@ -0,0 +1,23 @@
|
|||||||
|
## Release + Update Source (Wichtig)
|
||||||
|
|
||||||
|
- Primäre Plattform ist `https://git.24-music.de`
|
||||||
|
- Standard-Repo: `Administrator/real-debrid-downloader`
|
||||||
|
- Nicht mehr primär über Codeberg/GitHub releasen
|
||||||
|
|
||||||
|
## Releasen
|
||||||
|
|
||||||
|
1. Token setzen:
|
||||||
|
- PowerShell: `$env:GITEA_TOKEN="<token>"`
|
||||||
|
2. Release ausführen:
|
||||||
|
- `npm run release:gitea -- <version> [notes]`
|
||||||
|
|
||||||
|
Das Script:
|
||||||
|
- bumped `package.json`
|
||||||
|
- baut Windows-Artefakte
|
||||||
|
- pusht `main` + Tag
|
||||||
|
- erstellt Release auf `git.24-music.de`
|
||||||
|
- lädt Assets hoch
|
||||||
|
|
||||||
|
## Auto-Update
|
||||||
|
|
||||||
|
- Updater nutzt aktuell `git.24-music.de` als Standardquelle
|
||||||
31
README.md
31
README.md
@ -65,18 +65,18 @@ Desktop downloader with fast queue management, automatic extraction, and robust
|
|||||||
- Minimize-to-tray with tray menu controls.
|
- Minimize-to-tray with tray menu controls.
|
||||||
- Speed limits globally or per download.
|
- Speed limits globally or per download.
|
||||||
- Bandwidth schedules for time-based speed profiles.
|
- Bandwidth schedules for time-based speed profiles.
|
||||||
- Built-in auto-updater via Codeberg Releases.
|
- Built-in auto-updater via `git.24-music.de` Releases.
|
||||||
- Long path support (>260 characters) on Windows.
|
- Long path support (>260 characters) on Windows.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### Option A: prebuilt releases (recommended)
|
### Option A: prebuilt releases (recommended)
|
||||||
|
|
||||||
1. Download a release from the Codeberg Releases page.
|
1. Download a release from the `git.24-music.de` Releases page.
|
||||||
2. Run the installer or portable build.
|
2. Run the installer or portable build.
|
||||||
3. Add your debrid tokens in Settings.
|
3. Add your debrid tokens in Settings.
|
||||||
|
|
||||||
Releases: `https://codeberg.org/Sucukdeluxe/real-debrid-downloader/releases`
|
Releases: `https://git.24-music.de/Administrator/real-debrid-downloader/releases`
|
||||||
|
|
||||||
### Option B: build from source
|
### Option B: build from source
|
||||||
|
|
||||||
@ -103,21 +103,34 @@ npm run dev
|
|||||||
| `npm test` | Runs Vitest unit tests |
|
| `npm test` | Runs Vitest unit tests |
|
||||||
| `npm run self-check` | Runs integrated end-to-end self-checks |
|
| `npm run self-check` | Runs integrated end-to-end self-checks |
|
||||||
| `npm run release:win` | Creates Windows installer and portable build |
|
| `npm run release:win` | Creates Windows installer and portable build |
|
||||||
| `npm run release:codeberg -- <version> [notes]` | One-command version bump + build + tag + Codeberg release upload |
|
| `npm run release:gitea -- <version> [notes]` | One-command version bump + build + tag + release upload to `git.24-music.de` |
|
||||||
|
| `npm run release:codeberg -- <version> [notes]` | Legacy path for old Codeberg workflow |
|
||||||
|
|
||||||
### One-command Codeberg release
|
### One-command git.24-music release
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run release:codeberg -- 1.4.42 "- Maintenance update"
|
npm run release:gitea -- 1.6.31 "- Maintenance update"
|
||||||
```
|
```
|
||||||
|
|
||||||
This command will:
|
This command will:
|
||||||
|
|
||||||
1. Bump `package.json` version.
|
1. Bump `package.json` version.
|
||||||
2. Build setup/portable artifacts (`npm run release:win`).
|
2. Build setup/portable artifacts (`npm run release:win`).
|
||||||
3. Commit and push `main` to your Codeberg remote.
|
3. Commit and push `main` to your `git.24-music.de` remote.
|
||||||
4. Create and push tag `v<version>`.
|
4. Create and push tag `v<version>`.
|
||||||
5. Create/update the Codeberg release and upload required assets.
|
5. Create/update the Gitea release and upload required assets.
|
||||||
|
|
||||||
|
Required once before release:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git remote add gitea https://git.24-music.de/<user>/<repo>.git
|
||||||
|
```
|
||||||
|
|
||||||
|
PowerShell token setup:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
$env:GITEA_TOKEN="<dein-token>"
|
||||||
|
```
|
||||||
|
|
||||||
## Typical workflow
|
## Typical workflow
|
||||||
|
|
||||||
@ -154,7 +167,7 @@ The app stores runtime files in Electron's `userData` directory, including:
|
|||||||
|
|
||||||
## Changelog
|
## Changelog
|
||||||
|
|
||||||
Release history is available on [Codeberg Releases](https://codeberg.org/Sucukdeluxe/real-debrid-downloader/releases).
|
Release history is available on [git.24-music.de Releases](https://git.24-music.de/Administrator/real-debrid-downloader/releases).
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "real-debrid-downloader",
|
"name": "real-debrid-downloader",
|
||||||
"version": "1.6.30",
|
"version": "1.6.31",
|
||||||
"description": "Desktop downloader",
|
"description": "Desktop downloader",
|
||||||
"main": "build/main/main/main.js",
|
"main": "build/main/main/main.js",
|
||||||
"author": "Sucukdeluxe",
|
"author": "Sucukdeluxe",
|
||||||
@ -17,7 +17,9 @@
|
|||||||
"test": "vitest run",
|
"test": "vitest run",
|
||||||
"self-check": "tsx tests/self-check.ts",
|
"self-check": "tsx tests/self-check.ts",
|
||||||
"release:win": "npm run build && electron-builder --publish never --win nsis portable",
|
"release:win": "npm run build && electron-builder --publish never --win nsis portable",
|
||||||
"release:codeberg": "node scripts/release_codeberg.mjs"
|
"release:codeberg": "node scripts/release_codeberg.mjs",
|
||||||
|
"release:gitea": "node scripts/release_gitea.mjs",
|
||||||
|
"release:forgejo": "node scripts/release_gitea.mjs"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"adm-zip": "^0.5.16",
|
"adm-zip": "^0.5.16",
|
||||||
|
|||||||
321
scripts/release_gitea.mjs
Normal file
321
scripts/release_gitea.mjs
Normal file
@ -0,0 +1,321 @@
|
|||||||
|
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";
|
||||||
|
|
||||||
|
function run(command, args, options = {}) {
|
||||||
|
const result = spawnSync(command, args, {
|
||||||
|
cwd: process.cwd(),
|
||||||
|
encoding: "utf8",
|
||||||
|
stdio: options.capture ? ["pipe", "pipe", "pipe"] : "inherit"
|
||||||
|
});
|
||||||
|
if (result.status !== 0) {
|
||||||
|
const stderr = result.stderr ? String(result.stderr).trim() : "";
|
||||||
|
const stdout = result.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 options.capture ? String(result.stdout || "") : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function runCapture(command, args) {
|
||||||
|
const result = spawnSync(command, args, {
|
||||||
|
cwd: process.cwd(),
|
||||||
|
encoding: "utf8",
|
||||||
|
stdio: ["pipe", "pipe", "pipe"]
|
||||||
|
});
|
||||||
|
if (result.status !== 0) {
|
||||||
|
const stderr = String(result.stderr || "").trim();
|
||||||
|
throw new Error(stderr || `Command failed: ${command} ${args.join(" ")}`);
|
||||||
|
}
|
||||||
|
return String(result.stdout || "").trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function runWithInput(command, args, input) {
|
||||||
|
const result = spawnSync(command, args, {
|
||||||
|
cwd: process.cwd(),
|
||||||
|
encoding: "utf8",
|
||||||
|
input,
|
||||||
|
stdio: ["pipe", "pipe", "pipe"]
|
||||||
|
});
|
||||||
|
if (result.status !== 0) {
|
||||||
|
const stderr = String(result.stderr || "").trim();
|
||||||
|
throw new Error(stderr || `Command failed: ${command} ${args.join(" ")}`);
|
||||||
|
}
|
||||||
|
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 cleaned = args.filter((arg) => arg !== "--dry-run");
|
||||||
|
const version = cleaned[0] || "";
|
||||||
|
const notes = cleaned.slice(1).join(" ").trim();
|
||||||
|
return { help: false, dryRun, version, notes };
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseRemoteUrl(url) {
|
||||||
|
const raw = String(url || "").trim();
|
||||||
|
const httpsMatch = raw.match(/^https?:\/\/([^/]+)\/([^/]+)\/([^/]+?)(?:\.git)?$/i);
|
||||||
|
if (httpsMatch) {
|
||||||
|
return { host: httpsMatch[1], owner: httpsMatch[2], repo: httpsMatch[3] };
|
||||||
|
}
|
||||||
|
const sshMatch = raw.match(/^git@([^:]+):([^/]+)\/([^/]+?)(?:\.git)?$/i);
|
||||||
|
if (sshMatch) {
|
||||||
|
return { host: sshMatch[1], owner: sshMatch[2], repo: sshMatch[3] };
|
||||||
|
}
|
||||||
|
const sshAltMatch = raw.match(/^ssh:\/\/git@([^/:]+)(?::\d+)?\/([^/]+)\/([^/]+?)(?:\.git)?$/i);
|
||||||
|
if (sshAltMatch) {
|
||||||
|
return { host: sshAltMatch[1], owner: sshAltMatch[2], repo: sshAltMatch[3] };
|
||||||
|
}
|
||||||
|
throw new Error(`Cannot parse remote URL: ${raw}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeBaseUrl(url) {
|
||||||
|
const raw = String(url || "").trim().replace(/\/+$/, "");
|
||||||
|
if (!raw) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
if (!/^https?:\/\//i.test(raw)) {
|
||||||
|
throw new Error("GITEA_BASE_URL must start with http:// or https://");
|
||||||
|
}
|
||||||
|
return raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getGiteaRepo() {
|
||||||
|
const forcedRemote = String(process.env.GITEA_REMOTE || process.env.FORGEJO_REMOTE || "").trim();
|
||||||
|
const remotes = forcedRemote
|
||||||
|
? [forcedRemote]
|
||||||
|
: ["gitea", "forgejo", "origin", "github-new", "codeberg"];
|
||||||
|
|
||||||
|
const preferredBase = normalizeBaseUrl(process.env.GITEA_BASE_URL || process.env.FORGEJO_BASE_URL || "https://git.24-music.de");
|
||||||
|
|
||||||
|
for (const remote of remotes) {
|
||||||
|
try {
|
||||||
|
const remoteUrl = runCapture("git", ["remote", "get-url", remote]);
|
||||||
|
const parsed = parseRemoteUrl(remoteUrl);
|
||||||
|
const remoteBase = `https://${parsed.host}`.toLowerCase();
|
||||||
|
if (preferredBase && remoteBase !== preferredBase.toLowerCase()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return { remote, ...parsed, baseUrl: `https://${parsed.host}` };
|
||||||
|
} catch {
|
||||||
|
// try next remote
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preferredBase) {
|
||||||
|
throw new Error(
|
||||||
|
`No remote found for ${preferredBase}. Add one with: git remote add gitea ${preferredBase}/<owner>/<repo>.git`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error("No suitable remote found. Set GITEA_REMOTE or GITEA_BASE_URL.");
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAuthHeader(host) {
|
||||||
|
const explicitToken = String(process.env.GITEA_TOKEN || process.env.FORGEJO_TOKEN || "").trim();
|
||||||
|
if (explicitToken) {
|
||||||
|
return `token ${explicitToken}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const credentialText = runWithInput("git", ["credential", "fill"], `protocol=https\nhost=${host}\n\n`);
|
||||||
|
const map = new Map();
|
||||||
|
for (const line of credentialText.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 credentials for ${host}. Set GITEA_TOKEN or store credentials for this host in git credential helper.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const token = Buffer.from(`${username}:${password}`, "utf8").toString("base64");
|
||||||
|
return `Basic ${token}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
try {
|
||||||
|
parsed = text ? JSON.parse(text) : null;
|
||||||
|
} catch {
|
||||||
|
parsed = text;
|
||||||
|
}
|
||||||
|
return { ok: response.ok, status: response.status, body: parsed };
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureVersionString(version) {
|
||||||
|
const trimmed = String(version || "").trim();
|
||||||
|
if (!/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(trimmed)) {
|
||||||
|
throw new Error("Invalid version format. Expected e.g. 1.4.42");
|
||||||
|
}
|
||||||
|
return trimmed;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePackageVersion(rootDir, version) {
|
||||||
|
const packagePath = path.join(rootDir, "package.json");
|
||||||
|
const packageJson = JSON.parse(fs.readFileSync(packagePath, "utf8"));
|
||||||
|
if (String(packageJson.version || "") === version) {
|
||||||
|
throw new Error(`package.json is already at version ${version}`);
|
||||||
|
}
|
||||||
|
packageJson.version = version;
|
||||||
|
fs.writeFileSync(packagePath, `${JSON.stringify(packageJson, null, 2)}\n`, "utf8");
|
||||||
|
}
|
||||||
|
|
||||||
|
function patchLatestYml(releaseDir, version) {
|
||||||
|
const ymlPath = path.join(releaseDir, "latest.yml");
|
||||||
|
let content = fs.readFileSync(ymlPath, "utf8");
|
||||||
|
const setupName = `Real-Debrid-Downloader Setup ${version}.exe`;
|
||||||
|
const dashedName = `Real-Debrid-Downloader-Setup-${version}.exe`;
|
||||||
|
if (content.includes(dashedName)) {
|
||||||
|
content = content.split(dashedName).join(setupName);
|
||||||
|
fs.writeFileSync(ymlPath, content, "utf8");
|
||||||
|
process.stdout.write(`Patched latest.yml: replaced "${dashedName}" with "${setupName}"\n`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureAssetsExist(rootDir, version) {
|
||||||
|
const releaseDir = path.join(rootDir, "release");
|
||||||
|
const files = [
|
||||||
|
`Real-Debrid-Downloader Setup ${version}.exe`,
|
||||||
|
`Real-Debrid-Downloader ${version}.exe`,
|
||||||
|
"latest.yml",
|
||||||
|
`Real-Debrid-Downloader Setup ${version}.exe.blockmap`
|
||||||
|
];
|
||||||
|
for (const fileName of files) {
|
||||||
|
const fullPath = path.join(releaseDir, fileName);
|
||||||
|
if (!fs.existsSync(fullPath)) {
|
||||||
|
throw new Error(`Missing release artifact: ${fullPath}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
patchLatestYml(releaseDir, version);
|
||||||
|
return { releaseDir, files };
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureNoTrackedChanges() {
|
||||||
|
const output = runCapture("git", ["status", "--porcelain"]);
|
||||||
|
const lines = output.split(/\r?\n/).filter(Boolean);
|
||||||
|
const tracked = lines.filter((line) => !line.startsWith("?? "));
|
||||||
|
if (tracked.length > 0) {
|
||||||
|
throw new Error(`Working tree has tracked changes:\n${tracked.join("\n")}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureTagMissing(tag) {
|
||||||
|
const result = spawnSync("git", ["rev-parse", "--verify", `refs/tags/${tag}`], {
|
||||||
|
cwd: process.cwd(),
|
||||||
|
stdio: "ignore"
|
||||||
|
});
|
||||||
|
if (result.status === 0) {
|
||||||
|
throw new Error(`Tag already exists: ${tag}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createOrGetRelease(baseApi, tag, authHeader, notes) {
|
||||||
|
const byTag = await apiRequest("GET", `${baseApi}/releases/tags/${encodeURIComponent(tag)}`, authHeader);
|
||||||
|
if (byTag.ok) {
|
||||||
|
return byTag.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 uploadReleaseAssets(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) {
|
||||||
|
process.stdout.write(`Uploaded: ${fileName}\n`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (response.status === 409 || response.status === 422) {
|
||||||
|
process.stdout.write(`Skipped existing asset: ${fileName}\n`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
throw new Error(`Asset upload failed for ${fileName} (${response.status}): ${JSON.stringify(response.body)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const rootDir = process.cwd();
|
||||||
|
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_REMOTE, GITEA_TOKEN\n");
|
||||||
|
process.stdout.write("Compatibility envs still supported: FORGEJO_BASE_URL, FORGEJO_REMOTE, FORGEJO_TOKEN\n");
|
||||||
|
process.stdout.write("Example: npm run release:gitea -- 1.6.31 \"- Bugfixes\"\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const version = ensureVersionString(args.version);
|
||||||
|
const tag = `v${version}`;
|
||||||
|
const releaseNotes = args.notes || `- Release ${tag}`;
|
||||||
|
const repo = getGiteaRepo();
|
||||||
|
|
||||||
|
ensureNoTrackedChanges();
|
||||||
|
ensureTagMissing(tag);
|
||||||
|
updatePackageVersion(rootDir, version);
|
||||||
|
|
||||||
|
process.stdout.write(`Building release artifacts for ${tag}...\n`);
|
||||||
|
run(NPM_EXECUTABLE, ["run", "release:win"]);
|
||||||
|
const assets = ensureAssetsExist(rootDir, version);
|
||||||
|
|
||||||
|
if (args.dryRun) {
|
||||||
|
process.stdout.write(`Dry run complete. Assets exist for ${tag}.\n`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
run("git", ["add", "package.json"]);
|
||||||
|
run("git", ["commit", "-m", `Release ${tag}`]);
|
||||||
|
run("git", ["push", repo.remote, "main"]);
|
||||||
|
run("git", ["tag", tag]);
|
||||||
|
run("git", ["push", repo.remote, tag]);
|
||||||
|
|
||||||
|
const authHeader = getAuthHeader(repo.host);
|
||||||
|
const baseApi = `${repo.baseUrl}/api/v1/repos/${repo.owner}/${repo.repo}`;
|
||||||
|
const release = await createOrGetRelease(baseApi, tag, authHeader, releaseNotes);
|
||||||
|
await uploadReleaseAssets(baseApi, release.id, authHeader, assets.releaseDir, assets.files);
|
||||||
|
|
||||||
|
process.stdout.write(`Release published: ${release.html_url || `${repo.baseUrl}/${repo.owner}/${repo.repo}/releases/tag/${tag}`}\n`);
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((error) => {
|
||||||
|
process.stderr.write(`${String(error?.message || error)}\n`);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
@ -35,7 +35,7 @@ export const MAX_LINK_ARTIFACT_BYTES = 256 * 1024;
|
|||||||
export const SPEED_WINDOW_SECONDS = 1;
|
export const SPEED_WINDOW_SECONDS = 1;
|
||||||
export const CLIPBOARD_POLL_INTERVAL_MS = 2000;
|
export const CLIPBOARD_POLL_INTERVAL_MS = 2000;
|
||||||
|
|
||||||
export const DEFAULT_UPDATE_REPO = "Sucukdeluxe/real-debrid-downloader";
|
export const DEFAULT_UPDATE_REPO = "Administrator/real-debrid-downloader";
|
||||||
|
|
||||||
export function defaultSettings(): AppSettings {
|
export function defaultSettings(): AppSettings {
|
||||||
const baseDir = path.join(os.homedir(), "Downloads", "RealDebrid");
|
const baseDir = path.join(os.homedir(), "Downloads", "RealDebrid");
|
||||||
|
|||||||
@ -14,8 +14,32 @@ const DOWNLOAD_BODY_IDLE_TIMEOUT_MS = 45000;
|
|||||||
const RETRIES_PER_CANDIDATE = 3;
|
const RETRIES_PER_CANDIDATE = 3;
|
||||||
const RETRY_DELAY_MS = 1500;
|
const RETRY_DELAY_MS = 1500;
|
||||||
const UPDATE_USER_AGENT = `RD-Node-Downloader/${APP_VERSION}`;
|
const UPDATE_USER_AGENT = `RD-Node-Downloader/${APP_VERSION}`;
|
||||||
const UPDATE_WEB_BASE = "https://codeberg.org";
|
type UpdateSource = {
|
||||||
const UPDATE_API_BASE = "https://codeberg.org/api/v1";
|
name: string;
|
||||||
|
webBase: string;
|
||||||
|
apiBase: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const UPDATE_SOURCES: UpdateSource[] = [
|
||||||
|
{
|
||||||
|
name: "git24",
|
||||||
|
webBase: "https://git.24-music.de",
|
||||||
|
apiBase: "https://git.24-music.de/api/v1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "codeberg",
|
||||||
|
webBase: "https://codeberg.org",
|
||||||
|
apiBase: "https://codeberg.org/api/v1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "github",
|
||||||
|
webBase: "https://github.com",
|
||||||
|
apiBase: "https://api.github.com"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const PRIMARY_UPDATE_SOURCE = UPDATE_SOURCES[0];
|
||||||
|
const UPDATE_WEB_BASE = PRIMARY_UPDATE_SOURCE.webBase;
|
||||||
|
const UPDATE_API_BASE = PRIMARY_UPDATE_SOURCE.apiBase;
|
||||||
|
|
||||||
let activeUpdateAbortController: AbortController | null = null;
|
let activeUpdateAbortController: AbortController | null = null;
|
||||||
|
|
||||||
@ -57,9 +81,9 @@ export function normalizeUpdateRepo(repo: string): string {
|
|||||||
|
|
||||||
const normalizeParts = (input: string): string => {
|
const normalizeParts = (input: string): string => {
|
||||||
const cleaned = input
|
const cleaned = input
|
||||||
.replace(/^https?:\/\/(?:www\.)?(?:codeberg\.org|github\.com)\//i, "")
|
.replace(/^https?:\/\/(?:www\.)?(?:codeberg\.org|github\.com|git\.24-music\.de)\//i, "")
|
||||||
.replace(/^(?:www\.)?(?:codeberg\.org|github\.com)\//i, "")
|
.replace(/^(?:www\.)?(?:codeberg\.org|github\.com|git\.24-music\.de)\//i, "")
|
||||||
.replace(/^git@(?:codeberg\.org|github\.com):/i, "")
|
.replace(/^git@(?:codeberg\.org|github\.com|git\.24-music\.de):/i, "")
|
||||||
.replace(/\.git$/i, "")
|
.replace(/\.git$/i, "")
|
||||||
.replace(/^\/+|\/+$/g, "");
|
.replace(/^\/+|\/+$/g, "");
|
||||||
const parts = cleaned.split("/").filter(Boolean);
|
const parts = cleaned.split("/").filter(Boolean);
|
||||||
@ -76,7 +100,13 @@ export function normalizeUpdateRepo(repo: string): string {
|
|||||||
try {
|
try {
|
||||||
const url = new URL(raw);
|
const url = new URL(raw);
|
||||||
const host = url.hostname.toLowerCase();
|
const host = url.hostname.toLowerCase();
|
||||||
if (host === "codeberg.org" || host === "www.codeberg.org" || host === "github.com" || host === "www.github.com") {
|
if (
|
||||||
|
host === "codeberg.org"
|
||||||
|
|| host === "www.codeberg.org"
|
||||||
|
|| host === "github.com"
|
||||||
|
|| host === "www.github.com"
|
||||||
|
|| host === "git.24-music.de"
|
||||||
|
) {
|
||||||
const normalized = normalizeParts(url.pathname);
|
const normalized = normalizeParts(url.pathname);
|
||||||
if (normalized) {
|
if (normalized) {
|
||||||
return normalized;
|
return normalized;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user