import assert from 'node:assert/strict';
import { mkdtempSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import path from 'node:path';
import test from 'node:test';
import {
compareReleaseVersions,
resolveReleaseChannel,
validateReleaseVersion,
} from './release-channel.mjs';
import { setBuildVersion } from './set-build-version.mjs';
import { decodeMinisignPublicKey } from './write-minisign-public-key.mjs';
const RAW_PUBLIC_KEY = `untrusted comment: minisign public key E3E0874CEC1C22C3
RWTDIhzsTIfg41w2Gwiei0zNDKaLYm9dQVpEWNQ/Ulpyt2mbS2JE1U2M`;
test('stable and beta channels resolve to isolated updater feeds', () => {
const stable = resolveReleaseChannel('stable');
const beta = resolveReleaseChannel('beta');
assert.match(stable.primaryUpdaterEndpoint, /releases\/latest\/download/);
assert.match(beta.primaryUpdaterEndpoint, /releases\/download\/channel-beta/);
assert.equal(beta.fallbackUpdaterEndpoint, 'https://openbitfun.com/release/beta/latest.json');
assert.notEqual(beta.primaryUpdaterEndpoint, stable.primaryUpdaterEndpoint);
});
test('channel promotion follows SemVer including beta precedence', () => {
assert.equal(compareReleaseVersions('0.2.18-beta.2', '0.2.18-beta.1'), 1);
assert.equal(compareReleaseVersions('0.2.18', '0.2.18-beta.9'), 1);
assert.equal(compareReleaseVersions('0.2.19-beta.1', '0.2.18'), 1);
assert.equal(compareReleaseVersions('0.2.18', '0.2.19-beta.1'), -1);
assert.equal(compareReleaseVersions('0.2.18-beta.2', '0.2.18-beta.2'), 0);
});
test('release versions must match their channel', () => {
assert.equal(validateReleaseVersion('stable', '0.2.18'), '0.2.18');
assert.equal(validateReleaseVersion('beta', '0.2.18-beta.1'), '0.2.18-beta.1');
assert.throws(() => validateReleaseVersion('stable', '0.2.18-beta.1'));
assert.throws(() => validateReleaseVersion('beta', '0.2.18'));
assert.throws(() => validateReleaseVersion('beta', '0.2.18-beta.0'));
assert.equal(
validateReleaseVersion('nightly', '0.2.18-nightly.20260811'),
'0.2.18-nightly.20260811',
);
});
test('release public key export accepts raw and legacy base64 values', () => {
const expected = `${RAW_PUBLIC_KEY}\n`;
assert.equal(decodeMinisignPublicKey(RAW_PUBLIC_KEY), expected);
assert.equal(
decodeMinisignPublicKey(Buffer.from(expected).toString('base64')),
expected,
);
assert.throws(() => decodeMinisignPublicKey('not-a-key'));
});
test('build version projection updates every release-owned version file', () => {
const root = mkdtempSync(path.join(tmpdir(), 'bitfun-build-version-'));
const jsonFiles = [
'package.json',
'package-lock.json',
'BitFun-Installer/package.json',
'BitFun-Installer/package-lock.json',
];
for (const relative of jsonFiles) {
const file = path.join(root, relative);
mkdirSync(path.dirname(file), { recursive: true });
writeFileSync(file, JSON.stringify({ version: '1.0.0', packages: { '': { version: '1.0.0' } } }));
}
writeFixture(root, 'Cargo.toml', 'version = "1.0.0" # x-release-please-version\n');
writeFixture(
root,
'src/apps/relay-server/Cargo.toml',
'version = "1.0.0" # x-release-please-version\n',
);
writeFixture(root, 'BitFun-Installer/src-tauri/Cargo.toml', 'version = "1.0.0"\n');
setBuildVersion(root, '1.1.0-beta.2');
for (const relative of jsonFiles) {
const data = JSON.parse(readFileSync(path.join(root, relative), 'utf8'));
assert.equal(data.version, '1.1.0-beta.2');
assert.equal(data.packages[''].version, '1.1.0-beta.2');
}
assert.match(readFileSync(path.join(root, 'Cargo.toml'), 'utf8'), /1\.1\.0-beta\.2/);
assert.match(
readFileSync(path.join(root, 'src/apps/relay-server/Cargo.toml'), 'utf8'),
/1\.1\.0-beta\.2/,
);
});
function writeFixture(root, relative, content) {
const file = path.join(root, relative);
mkdirSync(path.dirname(file), { recursive: true });
writeFileSync(file, content);
}