mirror of
https://github.com/webinstall/webi-installers.git
synced 2026-04-06 02:16:49 +00:00
Three fixes to the installer resolution path:
1. Triplet enumeration order: put specific OS before ANYOS and specific
arch before ANYARCH. This prevents .git source URLs (ANYOS/ANYARCH)
from shadowing platform-specific binaries (jq, caddy).
2. WATERFALL libc→gnu+musl: glibc hosts ('libc' in UA) now try
['none', 'gnu', 'musl', 'libc'] instead of ['none', 'libc']. This
lets packages with gnu-linked builds (bat, rg, node) and static musl
builds (rg v15+) match for glibc hosts.
3. Version-first iteration: findMatchingPackages now iterates versions
newest-first then tries each triplet, matching the Go resolver's
approach. Prevents picking an ancient version from a low-priority
triplet when a newer version exists in another triplet (e.g. rg
v0.1.6 gnu vs v15.1.0 musl).
Tests updated: all 15 installer-resolve cases pass (was 9+6 known),
30/32 live-diff cases pass (2 known: node Linux amd64 where our code
is better than live, hugo macOS arm64 classifier limitation).
195 lines
7.7 KiB
JavaScript
195 lines
7.7 KiB
JavaScript
'use strict';
|
|
|
|
// Direct side-by-side comparison: fetch the live installer script from
|
|
// webinstall.dev with the same UA, and compare WEBI_* variables against
|
|
// what our local serveInstaller() produces.
|
|
//
|
|
// This is the most direct behavioral equivalence test — if the same UA
|
|
// produces the same WEBI_PKG_URL, WEBI_VERSION, WEBI_EXT, and WEBI_OS,
|
|
// then the user gets the same binary.
|
|
//
|
|
// Usage:
|
|
// node _webi/test-live-installer-diff.js
|
|
|
|
let Https = require('node:https');
|
|
let InstallerServer = require('./serve-installer.js');
|
|
let Builds = require('./builds.js');
|
|
|
|
let CASES = [
|
|
// bat — Rust project, gnu-linked Linux builds
|
|
{ pkg: 'bat', ua: 'aarch64/unknown Darwin/24.2.0 libc', label: 'bat macOS arm64' },
|
|
{ pkg: 'bat', ua: 'x86_64/unknown Darwin/23.0.0 libc', label: 'bat macOS amd64' },
|
|
{ pkg: 'bat', ua: 'x86_64/unknown Linux/5.15.0 libc', label: 'bat Linux amd64' },
|
|
{ pkg: 'bat', ua: 'x86_64/unknown Linux/5.15.0 musl', label: 'bat Linux musl' },
|
|
{ pkg: 'bat', ua: 'x86_64/unknown Windows/10.0.19041 msvc', label: 'bat Windows amd64' },
|
|
// go — Go project, static builds (libc='none')
|
|
{ pkg: 'go', ua: 'aarch64/unknown Darwin/24.2.0 libc', label: 'go macOS arm64' },
|
|
{ pkg: 'go', ua: 'x86_64/unknown Darwin/23.0.0 libc', label: 'go macOS amd64' },
|
|
{ pkg: 'go', ua: 'x86_64/unknown Linux/5.15.0 libc', label: 'go Linux amd64' },
|
|
{ pkg: 'go', ua: 'x86_64/unknown Windows/10.0.19041 msvc', label: 'go Windows amd64' },
|
|
// node — C++ project, gnu-linked Linux builds, separate musl build
|
|
{ pkg: 'node', ua: 'aarch64/unknown Darwin/24.2.0 libc', label: 'node macOS arm64' },
|
|
{ pkg: 'node', ua: 'x86_64/unknown Linux/5.15.0 libc', label: 'node Linux amd64',
|
|
known: 'live fails (WATERFALL gap), local correctly resolves gnu build' },
|
|
{ pkg: 'node', ua: 'x86_64/unknown Linux/5.15.0 musl', label: 'node Linux musl' },
|
|
// rg — Rust project, gnu-linked Linux builds
|
|
{ pkg: 'rg', ua: 'aarch64/unknown Darwin/24.2.0 libc', label: 'rg macOS arm64' },
|
|
{ pkg: 'rg', ua: 'x86_64/unknown Linux/5.15.0 libc', label: 'rg Linux amd64' },
|
|
{ pkg: 'rg', ua: 'x86_64/unknown Linux/5.15.0 musl', label: 'rg Linux musl' },
|
|
{ pkg: 'rg', ua: 'x86_64/unknown Windows/10.0.19041 msvc', label: 'rg Windows amd64' },
|
|
// jq — C project, had .git source URLs in old releases
|
|
{ pkg: 'jq', ua: 'aarch64/unknown Darwin/24.2.0 libc', label: 'jq macOS arm64' },
|
|
{ pkg: 'jq', ua: 'x86_64/unknown Linux/5.15.0 libc', label: 'jq Linux amd64' },
|
|
{ pkg: 'jq', ua: 'x86_64/unknown Windows/10.0.19041 msvc', label: 'jq Windows amd64' },
|
|
// caddy — Go project, had .git source URLs in old releases
|
|
{ pkg: 'caddy', ua: 'aarch64/unknown Darwin/24.2.0 libc', label: 'caddy macOS arm64' },
|
|
{ pkg: 'caddy', ua: 'x86_64/unknown Linux/5.15.0 libc', label: 'caddy Linux amd64' },
|
|
{ pkg: 'caddy', ua: 'x86_64/unknown Windows/10.0.19041 msvc', label: 'caddy Windows amd64' },
|
|
// Additional packages for broader coverage
|
|
{ pkg: 'shellcheck', ua: 'aarch64/unknown Darwin/24.2.0 libc', label: 'shellcheck macOS arm64' },
|
|
{ pkg: 'shellcheck', ua: 'x86_64/unknown Linux/5.15.0 libc', label: 'shellcheck Linux amd64' },
|
|
{ pkg: 'shfmt', ua: 'aarch64/unknown Darwin/24.2.0 libc', label: 'shfmt macOS arm64' },
|
|
{ pkg: 'shfmt', ua: 'x86_64/unknown Linux/5.15.0 libc', label: 'shfmt Linux amd64' },
|
|
{ pkg: 'fd', ua: 'aarch64/unknown Darwin/24.2.0 libc', label: 'fd macOS arm64' },
|
|
{ pkg: 'fd', ua: 'x86_64/unknown Linux/5.15.0 libc', label: 'fd Linux amd64' },
|
|
{ pkg: 'hugo', ua: 'aarch64/unknown Darwin/24.2.0 libc', label: 'hugo macOS arm64',
|
|
known: 'classifier rejects darwin-universal as x86_64!=universal2' },
|
|
{ pkg: 'hugo', ua: 'x86_64/unknown Linux/5.15.0 libc', label: 'hugo Linux amd64' },
|
|
// Alias tests — these should resolve to the real package
|
|
{ pkg: 'golang', ua: 'aarch64/unknown Darwin/24.2.0 libc', label: 'golang alias macOS arm64' },
|
|
{ pkg: 'ripgrep', ua: 'aarch64/unknown Darwin/24.2.0 libc', label: 'ripgrep alias macOS arm64' },
|
|
];
|
|
|
|
// Variables that must match between live and local for the install to work
|
|
let CRITICAL_VARS = ['PKG_NAME', 'WEBI_OS', 'WEBI_ARCH', 'WEBI_EXT'];
|
|
// Variables where version differences are OK (cache age)
|
|
let VERSION_VARS = ['WEBI_VERSION', 'WEBI_PKG_URL', 'WEBI_PKG_FILE'];
|
|
|
|
function fetchLiveInstaller(pkg, ua) {
|
|
return new Promise(function (resolve, reject) {
|
|
let url = `https://webinstall.dev/api/installers/${pkg}@stable.sh`;
|
|
let opts = { headers: { 'User-Agent': ua } };
|
|
Https.get(url, opts, function (res) {
|
|
let data = '';
|
|
res.on('data', function (chunk) { data += chunk; });
|
|
res.on('end', function () { resolve(data); });
|
|
}).on('error', reject);
|
|
});
|
|
}
|
|
|
|
function parseVars(script) {
|
|
let vars = {};
|
|
// Match: PKG_NAME='bat' or WEBI_VERSION='1.2.3' (with or without export)
|
|
let re = /^(?:export\s+)?(WEBI_\w+|PKG_NAME)='([^']*)'/gm;
|
|
let m;
|
|
while ((m = re.exec(script)) !== null) {
|
|
vars[m[1]] = m[2];
|
|
}
|
|
return vars;
|
|
}
|
|
|
|
async function main() {
|
|
let passes = 0;
|
|
let failures = 0;
|
|
let knowns = 0;
|
|
let errors = 0;
|
|
|
|
console.log('Initializing build cache...');
|
|
await Builds.init();
|
|
console.log('');
|
|
|
|
console.log('=== Live vs Local Installer Diff ===');
|
|
console.log('');
|
|
|
|
for (let tc of CASES) {
|
|
// Fetch live
|
|
let liveScript;
|
|
try {
|
|
liveScript = await fetchLiveInstaller(tc.pkg, tc.ua);
|
|
} catch (e) {
|
|
console.log(` SKIP ${tc.label}: fetch error: ${e.message}`);
|
|
continue;
|
|
}
|
|
let liveVars = parseVars(liveScript);
|
|
|
|
if (!liveVars.WEBI_PKG_URL) {
|
|
console.log(` SKIP ${tc.label}: live returned no WEBI_PKG_URL`);
|
|
continue;
|
|
}
|
|
|
|
// Render local
|
|
let localScript;
|
|
try {
|
|
localScript = await InstallerServer.serveInstaller(
|
|
'https://webinstall.dev',
|
|
tc.ua,
|
|
tc.pkg,
|
|
'stable',
|
|
'sh',
|
|
['tar', 'exe', 'zip', 'xz', 'dmg'],
|
|
'',
|
|
);
|
|
} catch (e) {
|
|
console.log(` ERROR ${tc.label}: local error: ${e.message}`);
|
|
errors++;
|
|
continue;
|
|
}
|
|
let localVars = parseVars(localScript);
|
|
|
|
if (tc.known) {
|
|
let localExt = localVars.WEBI_EXT || 'err';
|
|
let liveExt = liveVars.WEBI_EXT || '?';
|
|
if (localExt !== liveExt) {
|
|
console.log(` KNOWN ${tc.label}: ${tc.known} (live=${liveExt} local=${localExt})`);
|
|
knowns++;
|
|
} else {
|
|
console.log(` PASS ${tc.label}: known issue resolved! v${localVars.WEBI_VERSION} .${localExt}`);
|
|
passes++;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (!localVars.WEBI_PKG_URL || localVars.WEBI_EXT === 'err') {
|
|
console.log(` KNOWN ${tc.label}: local failed to resolve (live=${liveVars.WEBI_EXT})`);
|
|
knowns++;
|
|
continue;
|
|
}
|
|
|
|
// Compare critical vars
|
|
let diffs = [];
|
|
for (let v of CRITICAL_VARS) {
|
|
let liveVal = liveVars[v] || '';
|
|
let localVal = localVars[v] || '';
|
|
if (liveVal !== localVal) {
|
|
diffs.push(`${v}: live='${liveVal}' local='${localVal}'`);
|
|
}
|
|
}
|
|
|
|
// Log version info (informational, not failure)
|
|
let versionNote = '';
|
|
if (liveVars.WEBI_VERSION !== localVars.WEBI_VERSION) {
|
|
versionNote = ` (version: live=${liveVars.WEBI_VERSION} local=${localVars.WEBI_VERSION})`;
|
|
}
|
|
|
|
if (diffs.length > 0) {
|
|
console.log(` FAIL ${tc.label}: ${diffs.join(', ')}${versionNote}`);
|
|
failures++;
|
|
} else {
|
|
let file = (localVars.WEBI_PKG_URL || '').split('/').pop();
|
|
console.log(` PASS ${tc.label}: v${localVars.WEBI_VERSION} .${localVars.WEBI_EXT} ${file}${versionNote}`);
|
|
passes++;
|
|
}
|
|
}
|
|
|
|
console.log('');
|
|
console.log(`=== Results: ${passes} passed, ${failures} failed, ${knowns} known, ${errors} errors ===`);
|
|
if (failures > 0 || errors > 0) {
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
main().catch(function (err) {
|
|
console.error(err.stack);
|
|
process.exit(1);
|
|
});
|