mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-09-12 05:16:19 -07:00
feat(anime): support shared bridge installs and updates
- Prefer configured or package-managed bridge bundles before downloading - Select compatible upstream releases and offer managed-bundle updates - Document bridge configuration and package dependencies
This commit is contained in:
@@ -4,14 +4,18 @@ import { mkdtemp, mkdir, writeFile } from 'node:fs/promises';
|
||||
import { tmpdir } from 'node:os';
|
||||
import path from 'node:path';
|
||||
import {
|
||||
BUNDLE_MARKER_FILE,
|
||||
bundleReleaseUrl,
|
||||
bundleVersionFromJar,
|
||||
compareBundleVersions,
|
||||
findBundleBinaries,
|
||||
PINNED_BUNDLE_SHA256,
|
||||
PINNED_BUNDLE_TAG,
|
||||
MIN_BUNDLE_VERSION,
|
||||
parseBundleVersion,
|
||||
readBundleMarker,
|
||||
resolveBundleAssetName,
|
||||
selectBundleAsset,
|
||||
sha256,
|
||||
verifyPinnedBundle,
|
||||
systemBundleDirs,
|
||||
writeBundleMarker,
|
||||
} from './sidecar-bundle';
|
||||
|
||||
test('resolveBundleAssetName maps supported platform/arch pairs', () => {
|
||||
@@ -27,71 +31,71 @@ test('resolveBundleAssetName returns null for unpublished combinations', () => {
|
||||
assert.equal(resolveBundleAssetName('freebsd', 'x64'), null);
|
||||
});
|
||||
|
||||
const PINNED_RELEASE = {
|
||||
tag_name: PINNED_BUNDLE_TAG,
|
||||
assets: [
|
||||
{
|
||||
name: 'macOS-arm64-bundle.zip',
|
||||
browser_download_url: 'https://example.test/macOS-arm64-bundle.zip',
|
||||
size: 133_058_560,
|
||||
},
|
||||
],
|
||||
};
|
||||
function release(tag: string, assetNames: string[], extra: Record<string, unknown> = {}) {
|
||||
return {
|
||||
tag_name: tag,
|
||||
assets: assetNames.map((name) => ({
|
||||
name,
|
||||
browser_download_url: `https://example.test/${tag}/${name}`,
|
||||
size: 1,
|
||||
})),
|
||||
...extra,
|
||||
};
|
||||
}
|
||||
|
||||
test('selectBundleAsset reads the by-tag endpoint payload', () => {
|
||||
const asset = selectBundleAsset(PINNED_RELEASE, 'macOS-arm64-bundle.zip');
|
||||
assert.equal(asset?.tagName, PINNED_BUNDLE_TAG);
|
||||
assert.equal(asset?.downloadUrl, 'https://example.test/macOS-arm64-bundle.zip');
|
||||
assert.equal(asset?.sizeBytes, 133_058_560);
|
||||
});
|
||||
|
||||
test('selectBundleAsset skips releases without a matching asset', () => {
|
||||
test('selectBundleAsset takes the newest release that ships the asset', () => {
|
||||
const releases = [
|
||||
// The iOS runtime release carries no desktop bundle.
|
||||
{ tag_name: 'ios-runtime-v7', assets: [{ name: 'MExtensionServer-ios.jar' }] },
|
||||
PINNED_RELEASE,
|
||||
release('ios-runtime-v7', ['MExtensionServer-ios.jar']),
|
||||
release('v1.0.6.1', ['linux-x64-bundle.zip', 'macOS-arm64-bundle.zip']),
|
||||
// Older than the one below it: list order must not decide.
|
||||
release('v1.0.6.2', ['linux-x64-bundle.zip']),
|
||||
];
|
||||
|
||||
const asset = selectBundleAsset(releases, 'macOS-arm64-bundle.zip');
|
||||
assert.equal(asset?.tagName, PINNED_BUNDLE_TAG);
|
||||
const asset = selectBundleAsset(releases, 'linux-x64-bundle.zip');
|
||||
assert.equal(asset?.tagName, 'v1.0.6.2');
|
||||
assert.equal(asset?.downloadUrl, 'https://example.test/v1.0.6.2/linux-x64-bundle.zip');
|
||||
|
||||
// The newest release lacks the macOS asset, so the one before it wins there.
|
||||
assert.equal(selectBundleAsset(releases, 'macOS-arm64-bundle.zip')?.tagName, 'v1.0.6.1');
|
||||
});
|
||||
|
||||
test('selectBundleAsset ignores releases newer than the pin', () => {
|
||||
test('selectBundleAsset skips releases below the minimum, drafts, and prereleases', () => {
|
||||
const releases = [
|
||||
{
|
||||
tag_name: 'v9.9.9.9',
|
||||
assets: [
|
||||
{
|
||||
name: 'macOS-arm64-bundle.zip',
|
||||
browser_download_url: 'https://example.test/unpinned.zip',
|
||||
size: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
PINNED_RELEASE,
|
||||
release('v1.0.5.9', ['linux-x64-bundle.zip']),
|
||||
release('v2.0.0.0', ['linux-x64-bundle.zip'], { draft: true }),
|
||||
release('v2.0.0.1', ['linux-x64-bundle.zip'], { prerelease: true }),
|
||||
];
|
||||
assert.equal(selectBundleAsset(releases, 'linux-x64-bundle.zip'), null);
|
||||
|
||||
const asset = selectBundleAsset(releases, 'macOS-arm64-bundle.zip');
|
||||
assert.equal(asset?.tagName, PINNED_BUNDLE_TAG);
|
||||
assert.equal(asset?.downloadUrl, 'https://example.test/macOS-arm64-bundle.zip');
|
||||
releases.push(release(MIN_BUNDLE_VERSION, ['linux-x64-bundle.zip']));
|
||||
assert.equal(selectBundleAsset(releases, 'linux-x64-bundle.zip')?.tagName, MIN_BUNDLE_VERSION);
|
||||
});
|
||||
|
||||
test('selectBundleAsset returns null when nothing matches', () => {
|
||||
assert.equal(
|
||||
selectBundleAsset([{ tag_name: PINNED_BUNDLE_TAG, assets: [] }], 'linux-x64-bundle.zip'),
|
||||
null,
|
||||
);
|
||||
assert.equal(selectBundleAsset([release('v1.0.6.0', [])], 'linux-x64-bundle.zip'), null);
|
||||
assert.equal(selectBundleAsset([], 'linux-x64-bundle.zip'), null);
|
||||
assert.equal(selectBundleAsset({ message: 'rate limited' }, 'linux-x64-bundle.zip'), null);
|
||||
});
|
||||
|
||||
test('bundleReleaseUrl targets the pinned tag', () => {
|
||||
test('bundleReleaseUrl lists upstream releases', () => {
|
||||
assert.equal(
|
||||
bundleReleaseUrl(),
|
||||
`https://api.github.com/repos/1Selxo/M-Extension-Server/releases/tags/${PINNED_BUNDLE_TAG}`,
|
||||
'https://api.github.com/repos/1Selxo/M-Extension-Server/releases?per_page=20',
|
||||
);
|
||||
});
|
||||
|
||||
test('parseBundleVersion and compareBundleVersions order tags numerically', () => {
|
||||
assert.deepEqual(parseBundleVersion('v1.0.6.2'), [1, 0, 6, 2]);
|
||||
assert.deepEqual(parseBundleVersion('1.0.10'), [1, 0, 10]);
|
||||
assert.deepEqual(parseBundleVersion('v1.0.6.0-r1'), [1, 0, 6, 0]);
|
||||
assert.equal(parseBundleVersion('ios-runtime-v7'), null);
|
||||
assert.equal(compareBundleVersions('v1.0.6.2', 'v1.0.6.1'), 1);
|
||||
assert.equal(compareBundleVersions('v1.0.10', 'v1.0.9'), 1);
|
||||
assert.equal(compareBundleVersions('v1.0.6', 'v1.0.6.0'), 0);
|
||||
assert.equal(compareBundleVersions('v1.0.5.9', 'v1.0.6.0'), -1);
|
||||
});
|
||||
|
||||
test('findBundleBinaries locates the nested jre and jar', async () => {
|
||||
const root = await mkdtemp(path.join(tmpdir(), 'subminer-bundle-'));
|
||||
await mkdir(path.join(root, 'jre', 'jre', 'bin'), { recursive: true });
|
||||
@@ -121,39 +125,24 @@ test('findBundleBinaries returns null when the bundle is incomplete', async () =
|
||||
assert.equal(await findBundleBinaries(root), null);
|
||||
});
|
||||
|
||||
test('sha256 produces lowercase hex digests matching known vectors', () => {
|
||||
const encode = (value: string) => new TextEncoder().encode(value);
|
||||
assert.equal(
|
||||
sha256(encode('')),
|
||||
'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855',
|
||||
);
|
||||
assert.equal(
|
||||
sha256(encode('subminer')),
|
||||
'f3b7fdb2037add4cd8f122c090a727243b46b1b9d8a6c379f71573e2df120885',
|
||||
);
|
||||
test('bundleVersionFromJar reads the tag out of the jar name, dropping rebuild suffixes', () => {
|
||||
assert.equal(bundleVersionFromJar('/x/MExtensionServer-v1.0.6.0-r1.jar'), 'v1.0.6.0');
|
||||
assert.equal(bundleVersionFromJar('/x/MExtensionServer-v1.0.6.2.jar'), 'v1.0.6.2');
|
||||
assert.equal(bundleVersionFromJar('/x/MExtensionServer-1.0.6.0.jar'), 'v1.0.6.0');
|
||||
assert.equal(bundleVersionFromJar('/x/MExtensionServer.jar'), null);
|
||||
});
|
||||
|
||||
test('verifyPinnedBundle accepts a matching hash and rejects a mismatch', () => {
|
||||
const asset = 'macOS-arm64-bundle.zip';
|
||||
const wrong = verifyPinnedBundle(asset, new TextEncoder().encode('not the bundle'));
|
||||
assert.equal(wrong.ok, false);
|
||||
assert.match((wrong as { reason: string }).reason, /Checksum mismatch/);
|
||||
test('bundle marker round-trips and tolerates a missing or malformed file', async () => {
|
||||
const root = await mkdtemp(path.join(tmpdir(), 'subminer-bundle-'));
|
||||
assert.equal(await readBundleMarker(root), null);
|
||||
await writeBundleMarker(root, 'v1.0.6.0');
|
||||
assert.equal(await readBundleMarker(root), 'v1.0.6.0');
|
||||
await writeFile(path.join(root, BUNDLE_MARKER_FILE), '{not json');
|
||||
assert.equal(await readBundleMarker(root), null);
|
||||
});
|
||||
|
||||
test('verifyPinnedBundle refuses an asset that has no pin', () => {
|
||||
const result = verifyPinnedBundle('windows-x64-bundle.zip', new Uint8Array([1, 2, 3]));
|
||||
assert.equal(result.ok, false);
|
||||
assert.match((result as { reason: string }).reason, /No pinned checksum/);
|
||||
});
|
||||
|
||||
test('the pinned tag and hashes are the verified release', () => {
|
||||
assert.equal(PINNED_BUNDLE_TAG, 'v1.0.6.0');
|
||||
assert.equal(
|
||||
PINNED_BUNDLE_SHA256['macOS-arm64-bundle.zip'],
|
||||
'5f4fb03abfe88bc46ddf5f4d8221156ee2d66b9cbad7c4bc3ade4baf3a4266e6',
|
||||
);
|
||||
assert.equal(
|
||||
PINNED_BUNDLE_SHA256['linux-x64-bundle.zip'],
|
||||
'c2b869d3905b06a308517fec0b44f70ff76f7212230c60710bba39a7025c3a69',
|
||||
);
|
||||
test('systemBundleDirs names the AUR package location on Linux only', () => {
|
||||
assert.deepEqual(systemBundleDirs('linux'), ['/usr/share/mangatan/extension_server']);
|
||||
assert.deepEqual(systemBundleDirs('darwin'), []);
|
||||
assert.deepEqual(systemBundleDirs('win32'), []);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user