feat(updater): bridge 3.3.108 clients to v2.0.1

This commit is contained in:
Administrator 2026-08-07 18:02:16 +02:00
parent 95bff2581b
commit c7fa422d9b
5 changed files with 139 additions and 41 deletions

View File

@ -37,6 +37,14 @@ function isNewer(remote, current) {
return r.patch > c.patch;
}
function resolveReleaseVersion(release) {
for (const value of [release && release.name, release && release.tag_name]) {
const match = String(value || '').match(/(?:^|[^\d])v?(\d+\.\d+\.\d+)(?=$|[^\d.])/i);
if (match) return match[1];
}
return '';
}
function pickSetupAsset(assets) {
if (!Array.isArray(assets)) return null;
// Prefer asset with "setup" in the name (case-insensitive)
@ -90,11 +98,12 @@ async function checkForUpdate() {
}
const release = releases[0];
const remoteVersion = release.tag_name || release.name || '';
const remoteVersion = resolveReleaseVersion(release);
const transportTag = release.tag_name || '';
const currentVersion = getCurrentVersion();
if (!isNewer(remoteVersion, currentVersion)) {
cachedCheck = { available: false, currentVersion, remoteVersion };
cachedCheck = { available: false, currentVersion, remoteVersion, transportTag };
cachedCheckTs = Date.now();
return cachedCheck;
}
@ -109,7 +118,8 @@ async function checkForUpdate() {
cachedCheck = {
available: true,
currentVersion,
remoteVersion: remoteVersion.replace(/^v/i, ''),
remoteVersion,
transportTag,
releaseUrl: release.html_url,
assetUrl: setupAsset.browser_download_url,
assetSize: setupAsset.size,
@ -280,4 +290,4 @@ function abortUpdate() {
}
}
module.exports = { checkForUpdate, installUpdate, abortUpdate };
module.exports = { checkForUpdate, installUpdate, abortUpdate, isNewer, resolveReleaseVersion };

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "multi-hoster-uploader",
"version": "3.3.16",
"version": "2.0.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "multi-hoster-uploader",
"version": "3.3.16",
"version": "2.0.1",
"dependencies": {
"chokidar": "^3.6.0",
"undici": "^7.16.0",

View File

@ -1,6 +1,6 @@
{
"name": "multi-hoster-uploader",
"version": "3.3.108",
"version": "2.0.1",
"description": "Upload files to doodstream, voe, vidmoly, byse simultaneously",
"main": "main.js",
"scripts": {

View File

@ -1,27 +1,53 @@
#!/usr/bin/env node
import { execSync } from 'child_process';
import { createHash } from 'crypto';
import { readFileSync, writeFileSync, statSync, createReadStream, existsSync } from 'fs';
import { writeFileSync, statSync, createReadStream, existsSync } from 'fs';
import { resolve, basename } from 'path';
import { pathToFileURL } from 'url';
const ROOT = resolve(import.meta.dirname, '..');
const PKG_PATH = resolve(ROOT, 'package.json');
const RELEASE_DIR = resolve(ROOT, 'release');
const PRODUCT_NAME = 'Multi-Hoster-Upload';
// --- CLI args ---
const args = process.argv.slice(2);
const dryRun = args.includes('--dry-run');
const version = args.find(a => /^\d+\.\d+\.\d+$/.test(a));
const notes = args.filter(a => a !== version && a !== '--dry-run').join(' ') || '';
export function parseReleaseArgs(args) {
const version = Array.isArray(args) ? args[0] : '';
if (!/^\d+\.\d+\.\d+$/.test(version || '')) {
throw new Error('Usage: node scripts/release_gitea.mjs <version> --transport-tag <vX.Y.Z> [release notes] [--dry-run]');
}
if (!version) {
console.error('Usage: node scripts/release_gitea.mjs <version> [release notes] [--dry-run]');
console.error('Example: node scripts/release_gitea.mjs 1.0.1 "Bugfix release"');
process.exit(1);
const transportTagIndex = args.indexOf('--transport-tag');
const transportTag = transportTagIndex >= 0 ? args[transportTagIndex + 1] : '';
if (!/^v\d+\.\d+\.\d+$/.test(transportTag)) {
throw new Error('--transport-tag must match vX.Y.Z');
}
const excludedIndexes = new Set([0, transportTagIndex, transportTagIndex + 1]);
const notes = args.filter((arg, index) => !excludedIndexes.has(index) && arg !== '--dry-run').join(' ');
return { version, transportTag, notes, dryRun: args.includes('--dry-run') };
}
const tag = `v${version}`;
export function createReleasePlan(options) {
const releaseTitle = `${PRODUCT_NAME} v${options.version}`;
const setupName = `${PRODUCT_NAME} Setup ${options.version}.exe`;
const portableName = `${PRODUCT_NAME} ${options.version}.exe`;
return {
...options,
tag: options.transportTag,
releaseTitle,
releaseBody: options.notes || releaseTitle,
setupName,
portableName,
expectedArtifacts: [setupName, portableName, 'latest.yml'],
blockmapName: `${setupName}.blockmap`
};
}
export function renderLatestYml(plan, sha, size, releaseDate = new Date().toISOString()) {
return `version: ${plan.version}\nfiles:\n - url: ${plan.setupName}\n sha512: ${sha}\n size: ${size}\npath: ${plan.setupName}\nsha512: ${sha}\nreleaseDate: '${releaseDate}'\n`;
}
let dryRun = false;
// --- Helpers ---
function run(cmd, opts = {}) {
@ -109,8 +135,11 @@ async function uploadAsset(releaseId, filePath, token) {
}
// --- Main ---
async function main() {
console.log(`\nReleasing ${PRODUCT_NAME} ${tag}${dryRun ? ' [DRY RUN]' : ''}\n`);
async function main(args = process.argv.slice(2)) {
const plan = createReleasePlan(parseReleaseArgs(args));
const { version, tag } = plan;
dryRun = plan.dryRun;
console.log(`\nReleasing ${plan.releaseTitle} via ${tag}${dryRun ? ' [DRY RUN]' : ''}\n`);
// 1. Resolve remote
const remote = resolveGiteaRemote();
@ -138,18 +167,19 @@ async function main() {
if (!recoveryMode) {
// 4. Update package.json version
const pkg = JSON.parse(readFileSync(PKG_PATH, 'utf-8'));
pkg.version = version;
if (!dryRun) writeFileSync(PKG_PATH, JSON.stringify(pkg, null, 2) + '\n', 'utf-8');
console.log(`Updated package.json -> ${version}`);
run(`npm version ${version} --no-git-tag-version --allow-same-version`);
console.log(`Updated package.json and package-lock.json -> ${version}`);
// 5. Build
console.log('\nBuilding...');
run('npm run release:win', { stdio: 'inherit' });
// 6. Git commit + tag + push
run('git add package.json');
run(`git commit -m "release: ${tag}"`);
const versionStatus = run('git status --porcelain -- package.json package-lock.json', { allowDry: true });
if (versionStatus) {
run('git add package.json package-lock.json');
run(`git commit -m "release: ${tag}"`);
}
run(`git tag ${tag}`);
run(`git push ${remote.name} HEAD`);
run(`git push ${remote.name} ${tag}`);
@ -157,8 +187,7 @@ async function main() {
// 6b. Regenerate latest.yml to ensure correct SHA-512
{
const setupName = `${PRODUCT_NAME} Setup ${version}.exe`;
const setupPath = resolve(RELEASE_DIR, setupName);
const setupPath = resolve(RELEASE_DIR, plan.setupName);
if (existsSync(setupPath)) {
const sha = await new Promise((res, rej) => {
const h = createHash('sha512');
@ -168,18 +197,14 @@ async function main() {
s.on('error', rej);
});
const size = statSync(setupPath).size;
const yml = `version: ${version}\nfiles:\n - url: ${setupName}\n sha512: ${sha}\n size: ${size}\npath: ${setupName}\nsha512: ${sha}\nreleaseDate: '${new Date().toISOString()}'\n`;
const yml = renderLatestYml(plan, sha, size);
writeFileSync(resolve(RELEASE_DIR, 'latest.yml'), yml, 'utf-8');
console.log('Regenerated latest.yml with correct SHA-512');
}
}
// 7. Verify artifacts
const expectedArtifacts = [
`${PRODUCT_NAME} Setup ${version}.exe`,
`${PRODUCT_NAME} ${version}.exe`,
'latest.yml'
];
const expectedArtifacts = plan.expectedArtifacts;
for (const name of expectedArtifacts) {
const p = resolve(RELEASE_DIR, name);
@ -190,7 +215,7 @@ async function main() {
}
// Also check for blockmap
const blockmapName = `${PRODUCT_NAME} Setup ${version}.exe.blockmap`;
const blockmapName = plan.blockmapName;
const hasBlockmap = existsSync(resolve(RELEASE_DIR, blockmapName));
console.log('\nArtifacts verified.');
@ -203,14 +228,13 @@ async function main() {
}
// 9. Create release
const releaseBody = notes || `${PRODUCT_NAME} ${tag}`;
let releaseId;
const { status: createStatus, data: createData } = await giteaApi(
'POST',
`/api/v1/repos/Administrator/${PRODUCT_NAME}/releases`,
token,
{ tag_name: tag, name: `${PRODUCT_NAME} ${tag}`, body: releaseBody }
{ tag_name: tag, name: plan.releaseTitle, body: plan.releaseBody }
);
if (createStatus === 409 || createStatus === 422) {
@ -234,7 +258,10 @@ async function main() {
console.log(`\nDone! Release: ${process.env.GITEA_BASE_URL || 'https://git.24-music.de'}/Administrator/${PRODUCT_NAME}/releases/tag/${tag}\n`);
}
main().catch(err => {
console.error('\nRelease failed:', err.message);
process.exit(1);
});
const entryPoint = process.argv[1] ? pathToFileURL(resolve(process.argv[1])).href : '';
if (entryPoint === import.meta.url) {
main().catch(err => {
console.error('\nRelease failed:', err.message);
process.exit(1);
});
}

View File

@ -0,0 +1,61 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const path = require('node:path');
const { spawnSync } = require('node:child_process');
const { pathToFileURL } = require('node:url');
const { isNewer, resolveReleaseVersion } = require('../lib/updater');
test('bridge title resolves product version instead of transport tag', () => {
assert.equal(resolveReleaseVersion({ name: 'Multi-Hoster-Upload v2.0.1', tag_name: 'v3.3.109' }), '2.0.1');
assert.equal(isNewer('2.0.1', '2.0.1'), false);
assert.equal(isNewer('2.0.2', '2.0.1'), true);
});
test('release CLI rejects a malformed transport tag before release work', () => {
const script = path.resolve(__dirname, '../scripts/release_gitea.mjs');
const result = spawnSync(process.execPath, [script, '2.0.1', '--transport-tag', '3.3.109', 'Bridge', '--dry-run'], {
cwd: path.resolve(__dirname, '..'),
encoding: 'utf8'
});
assert.equal(result.status, 1);
assert.match(result.stderr, /--transport-tag must match vX\.Y\.Z/);
assert.doesNotMatch(result.stdout, /npm run release:win/);
});
test('release plan keeps product artifacts separate from the transport tag', () => {
const script = path.resolve(__dirname, '../scripts/release_gitea.mjs');
const moduleUrl = pathToFileURL(script).href;
const source = `
import { createReleasePlan, parseReleaseArgs, renderLatestYml } from ${JSON.stringify(moduleUrl)};
const plan = createReleasePlan(parseReleaseArgs(['2.0.1', '--transport-tag', 'v3.3.109', 'Bridge', 'notes']));
const latestYml = renderLatestYml(plan, 'abc123', 456, '2026-08-07T12:00:00.000Z');
process.stdout.write(JSON.stringify({
version: plan.version,
transportTag: plan.transportTag,
releaseTitle: plan.releaseTitle,
releaseBody: plan.releaseBody,
expectedArtifacts: plan.expectedArtifacts,
latestYml
}));
`;
const result = spawnSync(process.execPath, ['--input-type=module', '--eval', source], {
cwd: path.resolve(__dirname, '..'),
encoding: 'utf8'
});
assert.equal(result.status, 0, result.stderr);
assert.deepEqual(JSON.parse(result.stdout), {
version: '2.0.1',
transportTag: 'v3.3.109',
releaseTitle: 'Multi-Hoster-Upload v2.0.1',
releaseBody: 'Bridge notes',
expectedArtifacts: [
'Multi-Hoster-Upload Setup 2.0.1.exe',
'Multi-Hoster-Upload 2.0.1.exe',
'latest.yml'
],
latestYml: "version: 2.0.1\nfiles:\n - url: Multi-Hoster-Upload Setup 2.0.1.exe\n sha512: abc123\n size: 456\npath: Multi-Hoster-Upload Setup 2.0.1.exe\nsha512: abc123\nreleaseDate: '2026-08-07T12:00:00.000Z'\n"
});
});