feat(updater): bridge 3.3.108 clients to v2.0.1
This commit is contained in:
parent
95bff2581b
commit
c7fa422d9b
@ -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
4
package-lock.json
generated
@ -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",
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
61
tests/updater-version.test.js
Normal file
61
tests/updater-version.test.js
Normal 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"
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user