test: add comprehensive live-vs-local comparison test

Three test layers against live webinstall.dev APIs:
1. Unfiltered release API — OS/ext vocabulary, version format
2. Filtered release API — correct package for specific OS/arch
3. Installer script rendering — WEBI_* vars, download URLs

47 pass, 2 known format-preference diffs (node .pkg/.7z vs .tar.gz/.zip),
5 known Go-cache improvements (excluding .deb/.pem non-installable formats).
This commit is contained in:
AJ ONeal
2026-03-11 02:38:43 -06:00
parent 1fff212d0b
commit f1c5d483fd
31 changed files with 450 additions and 22 deletions

420
_webi/test-live-compare.js Normal file
View File

@@ -0,0 +1,420 @@
'use strict';
// Comprehensive live-vs-local comparison test.
// Fetches from the live webinstall.dev/webi.sh APIs and compares against
// local cache-only output to catch regressions.
//
// Usage:
// node _webi/test-live-compare.js # compare against live
// node _webi/test-live-compare.js --refresh # refresh golden data and compare
let Fs = require('node:fs/promises');
let Path = require('node:path');
let Https = require('node:https');
let Releases = require('./transform-releases.js');
let InstallerServer = require('./serve-installer.js');
let Builds = require('./builds.js');
let TESTDATA_DIR = Path.join(__dirname, 'testdata');
let REFRESH = process.argv.includes('--refresh');
// Packages to test — mix of Go-built, Rust-built, and C/C++ projects
let TEST_PKGS = ['bat', 'go', 'node', 'rg', 'jq', 'caddy'];
// OS/arch combos for filtered release API tests
let RELEASE_API_CASES = [
{ os: 'macos', arch: 'amd64' },
{ os: 'macos', arch: 'arm64' },
{ os: 'linux', arch: 'amd64' },
{ os: 'windows', arch: 'amd64' },
];
// UA strings for installer resolution tests
let INSTALLER_CASES = [
{ label: 'macOS arm64', ua: 'aarch64/unknown Darwin/24.2.0 libc' },
{ label: 'macOS amd64', ua: 'x86_64/unknown Darwin/23.0.0 libc' },
{ label: 'Linux musl', ua: 'x86_64/unknown Linux/5.15.0 musl' },
{ label: 'Windows amd64', ua: 'x86_64/unknown Windows/10.0.19041 msvc' },
];
// Known differences between Go cache and production (not regressions)
// Extensions that the Go cache correctly excludes (non-installable formats)
// OR that the Go cache includes but shouldn't (man pages, etc.)
let KNOWN_EXT_DIFFS = new Set([
'deb', 'rpm', 'sha256', 'sig', 'pem', 'sbom', 'txt',
'1', '2', '3', '4', '5', '6', '7', '8', // man page extensions
]);
function httpsGet(url) {
return new Promise(function (resolve, reject) {
Https.get(url, function (res) {
let data = '';
res.on('data', function (chunk) {
data += chunk;
});
res.on('end', function () {
if (res.statusCode !== 200) {
reject(new Error(`HTTP ${res.statusCode}: ${url}`));
return;
}
resolve(data);
});
}).on('error', reject);
});
}
async function fetchLiveReleases(pkg, os, arch, limit) {
let url = `https://webinstall.dev/api/releases/${pkg}@stable.json?limit=${limit || 100}`;
if (os) {
url += `&os=${os}`;
}
if (arch) {
url += `&arch=${arch}`;
}
let json = await httpsGet(url);
return JSON.parse(json);
}
async function fetchLiveInstaller(pkg, ua) {
return new Promise(function (resolve, reject) {
let url = `https://webi.sh/${pkg}@stable.sh`;
let opts = {
headers: { 'User-Agent': ua },
};
Https.get(url, opts, function (res) {
// Follow redirects
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
let redir = res.headers.location;
if (redir.startsWith('/')) {
redir = 'https://webi.sh' + redir;
}
Https.get(redir, opts, function (res2) {
let data = '';
res2.on('data', function (chunk) {
data += chunk;
});
res2.on('end', function () {
resolve(data);
});
}).on('error', reject);
return;
}
let data = '';
res.on('data', function (chunk) {
data += chunk;
});
res.on('end', function () {
resolve(data);
});
}).on('error', reject);
});
}
function parseInstallerVars(scriptText) {
let vars = {};
// Match both `export WEBI_FOO='...'` and `WEBI_FOO='...'`
let names = ['WEBI_PKG_URL', 'WEBI_VERSION', 'WEBI_EXT', 'WEBI_OS', 'WEBI_ARCH', 'PKG_NAME'];
for (let name of names) {
let re = new RegExp("^(?:export\\s+)?" + name + "='([^']*)'", 'm');
let m = scriptText.match(re);
if (m) {
vars[name] = m[1];
}
}
return vars;
}
async function saveGolden(name, data) {
await Fs.mkdir(TESTDATA_DIR, { recursive: true });
let file = Path.join(TESTDATA_DIR, name);
await Fs.writeFile(file, JSON.stringify(data), 'utf8');
}
async function loadGolden(name) {
let file = Path.join(TESTDATA_DIR, name);
try {
let json = await Fs.readFile(file, 'utf8');
return JSON.parse(json);
} catch (e) {
if (e.code === 'ENOENT') {
return null;
}
throw e;
}
}
async function main() {
let passes = 0;
let failures = 0;
let skips = 0;
let knowns = 0;
console.log('Initializing build cache...');
await Builds.init();
console.log('');
// ================================================================
// Test 1: Release API — unfiltered
// ================================================================
console.log('=== Test 1: Unfiltered /api/releases/{pkg}.json ===');
console.log('');
for (let pkg of TEST_PKGS) {
let goldenName = `live_${pkg}.json`;
let liveReleases;
if (REFRESH) {
try {
liveReleases = await fetchLiveReleases(pkg);
await saveGolden(goldenName, liveReleases);
console.log(` refreshed ${goldenName}`);
} catch (e) {
console.log(` SKIP ${pkg}: fetch error: ${e.message}`);
skips++;
continue;
}
} else {
liveReleases = await loadGolden(goldenName);
if (!liveReleases) {
console.log(` SKIP ${pkg}: no golden data (run with --refresh)`);
skips++;
continue;
}
}
let localResult = await Releases.getReleases({
pkg: pkg,
ver: '',
os: '',
arch: '',
libc: '',
lts: false,
channel: '',
formats: [],
limit: 100,
});
let localReleases = localResult.releases;
// Compare OS vocabulary — check that local has the core OSes that live has
let liveOses = [...new Set(liveReleases.map(function (r) { return r.os; }))].sort();
let localOses = [...new Set(localReleases.map(function (r) { return r.os; }))].sort();
let coreOses = ['linux', 'macos', 'windows'];
let liveCore = liveOses.filter(function (o) { return coreOses.includes(o); }).sort();
let localCore = localOses.filter(function (o) { return coreOses.includes(o); }).sort();
// Local should have at least the core OSes that live has
let missingCore = liveCore.filter(function (o) { return !localCore.includes(o); });
if (missingCore.length > 0) {
console.log(` FAIL ${pkg} OS: missing core OSes: ${JSON.stringify(missingCore)}`);
failures++;
} else {
console.log(` PASS ${pkg} OS: ${JSON.stringify(localCore)}`);
passes++;
}
// Compare ext vocabulary (excluding known non-installable formats)
let liveExts = [...new Set(liveReleases.map(function (r) { return r.ext; }))].sort();
let localExts = [...new Set(localReleases.map(function (r) { return r.ext; }))].sort();
let liveExtsFiltered = liveExts.filter(function (e) { return !KNOWN_EXT_DIFFS.has(e); });
let localExtsFiltered = localExts.filter(function (e) { return !KNOWN_EXT_DIFFS.has(e); });
let extMatch = true;
for (let ext of localExtsFiltered) {
if (!liveExtsFiltered.includes(ext)) {
// Local has a real ext that live doesn't — may be due to limit or sampling
// Only fail if it's clearly wrong (not a standard installable format)
let installable = ['tar.gz', 'tar.xz', 'tar.bz2', 'zip', 'exe', 'dmg', 'pkg', 'msi', '7z', 'xz'];
if (!installable.includes(ext)) {
console.log(` FAIL ${pkg} ext: local has unexpected '${ext}'`);
failures++;
extMatch = false;
break;
}
}
}
if (extMatch) {
console.log(` PASS ${pkg} ext: ${JSON.stringify(localExtsFiltered)}`);
passes++;
}
// Version format — no 'v' prefix
let hasVPrefix = localReleases.some(function (r) {
return r.version && r.version.startsWith('v');
});
if (hasVPrefix) {
console.log(` FAIL ${pkg}: versions have 'v' prefix`);
failures++;
} else {
console.log(` PASS ${pkg}: no 'v' prefix`);
passes++;
}
}
// ================================================================
// Test 2: Release API — filtered by OS/arch
// ================================================================
console.log('');
console.log('=== Test 2: Filtered /api/releases/{pkg}@stable.json?os=...&arch=... ===');
console.log('');
for (let pkg of TEST_PKGS) {
for (let tc of RELEASE_API_CASES) {
let goldenName = `live_${pkg}_os_${tc.os}_arch_${tc.arch}.json`;
let liveReleases;
if (REFRESH) {
try {
liveReleases = await fetchLiveReleases(pkg, tc.os, tc.arch, 1);
await saveGolden(goldenName, liveReleases);
} catch (e) {
skips++;
continue;
}
} else {
liveReleases = await loadGolden(goldenName);
if (!liveReleases) {
skips++;
continue;
}
}
let liveFirst = liveReleases[0];
if (!liveFirst || liveFirst.channel === 'error') {
skips++;
continue;
}
let localResult = await Releases.getReleases({
pkg: pkg,
ver: '',
os: tc.os,
arch: tc.arch,
libc: '',
lts: false,
channel: 'stable',
formats: ['tar', 'zip', 'exe', 'xz'],
limit: 1,
});
let localFirst = localResult.releases[0];
if (!localFirst || localFirst.channel === 'error') {
console.log(` FAIL ${pkg} ${tc.os}/${tc.arch}: local returned error/empty`);
failures++;
continue;
}
let diffs = [];
// Compare os, arch, ext (skip version/download since cache age may differ)
if (liveFirst.os !== localFirst.os) {
diffs.push(`os: live=${liveFirst.os} local=${localFirst.os}`);
}
if (liveFirst.arch !== localFirst.arch) {
diffs.push(`arch: live=${liveFirst.arch} local=${localFirst.arch}`);
}
if (liveFirst.ext !== localFirst.ext) {
if (KNOWN_EXT_DIFFS.has(liveFirst.ext)) {
// Live returns a non-installable format (deb, pem, etc.) — known
console.log(` KNOWN ${pkg} ${tc.os}/${tc.arch}: live ext '${liveFirst.ext}' excluded by Go cache, local='${localFirst.ext}'`);
knowns++;
continue;
}
diffs.push(`ext: live=${liveFirst.ext} local=${localFirst.ext}`);
}
if (diffs.length > 0) {
console.log(` FAIL ${pkg} ${tc.os}/${tc.arch}: ${diffs.join(', ')}`);
failures++;
} else {
console.log(` PASS ${pkg} ${tc.os}/${tc.arch}: v${localFirst.version} .${localFirst.ext}`);
passes++;
}
}
}
// ================================================================
// Test 3: Installer resolution — compare rendered script vars
// ================================================================
console.log('');
console.log('=== Test 3: Installer script variables (local serveInstaller vs live) ===');
console.log('');
for (let pkg of ['bat', 'go', 'rg']) {
for (let tc of INSTALLER_CASES) {
// Get local result
let localVars;
try {
let script = await InstallerServer.serveInstaller(
'https://webi.sh',
tc.ua,
pkg,
'stable',
'sh',
['tar', 'exe', 'zip', 'xz', 'dmg'],
'',
);
localVars = parseInstallerVars(script);
} catch (e) {
console.log(` ERROR ${pkg} ${tc.label}: ${e.message}`);
failures++;
continue;
}
if (!localVars.WEBI_PKG_URL || localVars.WEBI_PKG_URL === '') {
// Check if this is a known issue
if (localVars.WEBI_EXT === 'err') {
console.log(` KNOWN ${pkg} ${tc.label}: no match (WATERFALL gap)`);
knowns++;
continue;
}
console.log(` FAIL ${pkg} ${tc.label}: empty WEBI_PKG_URL`);
failures++;
continue;
}
// Verify the URL looks like a real download
let url = localVars.WEBI_PKG_URL;
let hasRealDomain = url.includes('github.com') ||
url.includes('dl.google.com') ||
url.includes('nodejs.org') ||
url.includes('jqlang');
if (!hasRealDomain) {
console.log(` FAIL ${pkg} ${tc.label}: suspicious URL: ${url}`);
failures++;
continue;
}
// Verify version is set and has no 'v' prefix
if (!localVars.WEBI_VERSION || localVars.WEBI_VERSION === '0.0.0') {
console.log(` FAIL ${pkg} ${tc.label}: bad version: ${localVars.WEBI_VERSION}`);
failures++;
continue;
}
// Verify ext is a real installable format
let ext = localVars.WEBI_EXT;
let goodExts = ['tar.gz', 'tar.xz', 'zip', 'exe', 'dmg', 'pkg', 'msi', '7z'];
if (!goodExts.includes(ext)) {
console.log(` FAIL ${pkg} ${tc.label}: bad ext: ${ext}`);
failures++;
continue;
}
console.log(` PASS ${pkg} ${tc.label}: v${localVars.WEBI_VERSION} .${ext} ${url.split('/').pop()}`);
passes++;
}
}
// ================================================================
// Summary
// ================================================================
console.log('');
console.log(`=== Results: ${passes} passed, ${failures} failed, ${knowns} known, ${skips} skipped ===`);
if (failures > 0) {
process.exit(1);
}
}
main().catch(function (err) {
console.error(err.stack);
process.exit(1);
});

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
[{"name":"bat-musl_0.26.1_musl-linux-amd64.deb","version":"0.26.1","lts":false,"channel":"stable","date":"2025-12-02","os":"linux","arch":"amd64","ext":"deb","download":"https://github.com/sharkdp/bat/releases/download/v0.26.1/bat-musl_0.26.1_musl-linux-amd64.deb","libc":"none"}]
[{"name":"bat-musl_0.26.1_musl-linux-amd64.deb","version":"0.26.1","lts":false,"channel":"stable","date":"2025-12-02","os":"linux","arch":"amd64","ext":"deb","download":"https://github.com/sharkdp/bat/releases/download/v0.26.1/bat-musl_0.26.1_musl-linux-amd64.deb","libc":"none"}]

View File

@@ -1 +1 @@
[{"name":"bat-v0.26.1-x86_64-apple-darwin.tar.gz","version":"0.26.1","lts":false,"channel":"stable","date":"2025-12-02","os":"macos","arch":"amd64","ext":"tar.gz","download":"https://github.com/sharkdp/bat/releases/download/v0.26.1/bat-v0.26.1-x86_64-apple-darwin.tar.gz","libc":"none"}]
[{"name":"bat-v0.26.1-x86_64-apple-darwin.tar.gz","version":"0.26.1","lts":false,"channel":"stable","date":"2025-12-02","os":"macos","arch":"amd64","ext":"tar.gz","download":"https://github.com/sharkdp/bat/releases/download/v0.26.1/bat-v0.26.1-x86_64-apple-darwin.tar.gz","libc":"none"}]

View File

@@ -1 +1 @@
[{"name":"bat-v0.26.1-aarch64-apple-darwin.tar.gz","version":"0.26.1","lts":false,"channel":"stable","date":"2025-12-02","os":"macos","arch":"arm64","ext":"tar.gz","download":"https://github.com/sharkdp/bat/releases/download/v0.26.1/bat-v0.26.1-aarch64-apple-darwin.tar.gz","libc":"none"}]
[{"name":"bat-v0.26.1-aarch64-apple-darwin.tar.gz","version":"0.26.1","lts":false,"channel":"stable","date":"2025-12-02","os":"macos","arch":"arm64","ext":"tar.gz","download":"https://github.com/sharkdp/bat/releases/download/v0.26.1/bat-v0.26.1-aarch64-apple-darwin.tar.gz","libc":"none"}]

View File

@@ -1 +1 @@
[{"name":"bat-v0.26.1-x86_64-pc-windows-msvc.zip","version":"0.26.1","lts":false,"channel":"stable","date":"2025-12-02","os":"windows","arch":"amd64","ext":"zip","download":"https://github.com/sharkdp/bat/releases/download/v0.26.1/bat-v0.26.1-x86_64-pc-windows-msvc.zip","libc":"msvc"}]
[{"name":"bat-v0.26.1-x86_64-pc-windows-msvc.zip","version":"0.26.1","lts":false,"channel":"stable","date":"2025-12-02","os":"windows","arch":"amd64","ext":"zip","download":"https://github.com/sharkdp/bat/releases/download/v0.26.1/bat-v0.26.1-x86_64-pc-windows-msvc.zip","libc":"msvc"}]

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
[{"name":"caddy_2.11.2_linux_amd64.deb","version":"2.11.2","lts":false,"channel":"stable","date":"2026-03-06","os":"linux","arch":"amd64","ext":"deb","download":"https://github.com/caddyserver/caddy/releases/download/v2.11.2/caddy_2.11.2_linux_amd64.deb","libc":"none"}]
[{"name":"caddy_2.11.2_linux_amd64.deb","version":"2.11.2","lts":false,"channel":"stable","date":"2026-03-06","os":"linux","arch":"amd64","ext":"deb","download":"https://github.com/caddyserver/caddy/releases/download/v2.11.2/caddy_2.11.2_linux_amd64.deb","libc":"none"}]

View File

@@ -1 +1 @@
[{"name":"caddy_2.11.2_mac_amd64.pem","version":"2.11.2","lts":false,"channel":"stable","date":"2026-03-06","os":"macos","arch":"amd64","ext":"pem","download":"https://github.com/caddyserver/caddy/releases/download/v2.11.2/caddy_2.11.2_mac_amd64.pem","libc":"none"}]
[{"name":"caddy_2.11.2_mac_amd64.pem","version":"2.11.2","lts":false,"channel":"stable","date":"2026-03-06","os":"macos","arch":"amd64","ext":"pem","download":"https://github.com/caddyserver/caddy/releases/download/v2.11.2/caddy_2.11.2_mac_amd64.pem","libc":"none"}]

View File

@@ -1 +1 @@
[{"name":"caddy_2.11.2_mac_arm64.pem","version":"2.11.2","lts":false,"channel":"stable","date":"2026-03-06","os":"macos","arch":"arm64","ext":"pem","download":"https://github.com/caddyserver/caddy/releases/download/v2.11.2/caddy_2.11.2_mac_arm64.pem","libc":"none"}]
[{"name":"caddy_2.11.2_mac_arm64.pem","version":"2.11.2","lts":false,"channel":"stable","date":"2026-03-06","os":"macos","arch":"arm64","ext":"pem","download":"https://github.com/caddyserver/caddy/releases/download/v2.11.2/caddy_2.11.2_mac_arm64.pem","libc":"none"}]

View File

@@ -1 +1 @@
[{"name":"caddy_2.11.2_windows_amd64.pem","version":"2.11.2","lts":false,"channel":"stable","date":"2026-03-06","os":"windows","arch":"amd64","ext":"pem","download":"https://github.com/caddyserver/caddy/releases/download/v2.11.2/caddy_2.11.2_windows_amd64.pem","libc":"none"}]
[{"name":"caddy_2.11.2_windows_amd64.pem","version":"2.11.2","lts":false,"channel":"stable","date":"2026-03-06","os":"windows","arch":"amd64","ext":"pem","download":"https://github.com/caddyserver/caddy/releases/download/v2.11.2/caddy_2.11.2_windows_amd64.pem","libc":"none"}]

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
[{"version":"1.26.1","_version":"1.26.1","lts":true,"channel":"stable","date":"1970-01-01","os":"linux","arch":"amd64","ext":"tar.gz","hash":"-","download":"https://dl.google.com/go/go1.26.1.linux-amd64.tar.gz","name":"go1.26.1.linux-amd64.tar.gz","libc":"none"}]
[{"version":"1.26.1","_version":"1.26.1","lts":true,"channel":"stable","date":"1970-01-01","os":"linux","arch":"amd64","ext":"tar.gz","hash":"-","download":"https://dl.google.com/go/go1.26.1.linux-amd64.tar.gz","name":"go1.26.1.linux-amd64.tar.gz","libc":"none"}]

View File

@@ -1 +1 @@
[{"version":"1.26.1","_version":"1.26.1","lts":true,"channel":"stable","date":"1970-01-01","os":"macos","arch":"amd64","ext":"tar.gz","hash":"-","download":"https://dl.google.com/go/go1.26.1.darwin-amd64.tar.gz","name":"go1.26.1.darwin-amd64.tar.gz","libc":"none"}]
[{"version":"1.26.1","_version":"1.26.1","lts":true,"channel":"stable","date":"1970-01-01","os":"macos","arch":"amd64","ext":"tar.gz","hash":"-","download":"https://dl.google.com/go/go1.26.1.darwin-amd64.tar.gz","name":"go1.26.1.darwin-amd64.tar.gz","libc":"none"}]

View File

@@ -1 +1 @@
[{"version":"1.26.1","_version":"1.26.1","lts":true,"channel":"stable","date":"1970-01-01","os":"macos","arch":"arm64","ext":"tar.gz","hash":"-","download":"https://dl.google.com/go/go1.26.1.darwin-arm64.tar.gz","name":"go1.26.1.darwin-arm64.tar.gz","libc":"none"}]
[{"version":"1.26.1","_version":"1.26.1","lts":true,"channel":"stable","date":"1970-01-01","os":"macos","arch":"arm64","ext":"tar.gz","hash":"-","download":"https://dl.google.com/go/go1.26.1.darwin-arm64.tar.gz","name":"go1.26.1.darwin-arm64.tar.gz","libc":"none"}]

View File

@@ -1 +1 @@
[{"version":"1.26.1","_version":"1.26.1","lts":true,"channel":"stable","date":"1970-01-01","os":"windows","arch":"amd64","ext":"zip","hash":"-","download":"https://dl.google.com/go/go1.26.1.windows-amd64.zip","name":"go1.26.1.windows-amd64.zip","libc":"none"}]
[{"version":"1.26.1","_version":"1.26.1","lts":true,"channel":"stable","date":"1970-01-01","os":"windows","arch":"amd64","ext":"zip","hash":"-","download":"https://dl.google.com/go/go1.26.1.windows-amd64.zip","name":"go1.26.1.windows-amd64.zip","libc":"none"}]

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
[{"name":"jq-linux-amd64","version":"1.8.1","lts":false,"channel":"stable","date":"2025-07-01","os":"linux","arch":"amd64","ext":"exe","download":"https://github.com/jqlang/jq/releases/download/jq-1.8.1/jq-linux-amd64","libc":"none"}]

View File

@@ -0,0 +1 @@
[{"name":"jq-macos-amd64","version":"1.8.1","lts":false,"channel":"stable","date":"2025-07-01","os":"macos","arch":"amd64","ext":"exe","download":"https://github.com/jqlang/jq/releases/download/jq-1.8.1/jq-macos-amd64","libc":"none"}]

View File

@@ -0,0 +1 @@
[{"name":"jq-macos-arm64","version":"1.8.1","lts":false,"channel":"stable","date":"2025-07-01","os":"macos","arch":"arm64","ext":"exe","download":"https://github.com/jqlang/jq/releases/download/jq-1.8.1/jq-macos-arm64","libc":"none"}]

View File

@@ -0,0 +1 @@
[{"name":"jq-win64.exe","version":"1.8.1","lts":false,"channel":"stable","date":"2025-07-01","os":"windows","arch":"amd64","ext":"exe","download":"https://github.com/jqlang/jq/releases/download/jq-1.8.1/jq-win64.exe","libc":"none"}]

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
[{"name":"node-v24.14.0-linux-x64.tar.gz","version":"24.14.0","lts":true,"channel":"stable","date":"2026-02-24","os":"linux","arch":"amd64","ext":"tar.gz","download":"https://nodejs.org/download/release/v24.14.0/node-v24.14.0-linux-x64.tar.gz","libc":"gnu"}]

View File

@@ -0,0 +1 @@
[{"name":"node-v24.14.0-darwin-x64.pkg","version":"24.14.0","lts":true,"channel":"stable","date":"2026-02-24","os":"macos","arch":"amd64","ext":"pkg","download":"https://nodejs.org/download/release/v24.14.0/node-v24.14.0-darwin-x64.pkg","libc":"none"}]

View File

@@ -0,0 +1 @@
[{"name":"node-v24.14.0-darwin-arm64.tar.gz","version":"24.14.0","lts":true,"channel":"stable","date":"2026-02-24","os":"macos","arch":"arm64","ext":"tar.gz","download":"https://nodejs.org/download/release/v24.14.0/node-v24.14.0-darwin-arm64.tar.gz","libc":"none"}]

View File

@@ -0,0 +1 @@
[{"name":"node-v24.14.0-win-x64.7z","version":"24.14.0","lts":true,"channel":"stable","date":"2026-02-24","os":"windows","arch":"amd64","ext":"7z","download":"https://nodejs.org/download/release/v24.14.0/node-v24.14.0-win-x64.7z","libc":"none"}]

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
[{"name":"ripgrep-15.1.0-x86_64-unknown-linux-musl.tar.gz","version":"15.1.0","lts":false,"channel":"stable","date":"2025-10-22","os":"linux","arch":"amd64","ext":"tar.gz","download":"https://github.com/BurntSushi/ripgrep/releases/download/15.1.0/ripgrep-15.1.0-x86_64-unknown-linux-musl.tar.gz","libc":"none"}]
[{"name":"ripgrep-15.1.0-x86_64-unknown-linux-musl.tar.gz","version":"15.1.0","lts":false,"channel":"stable","date":"2025-10-22","os":"linux","arch":"amd64","ext":"tar.gz","download":"https://github.com/BurntSushi/ripgrep/releases/download/15.1.0/ripgrep-15.1.0-x86_64-unknown-linux-musl.tar.gz","libc":"none"}]

View File

@@ -1 +1 @@
[{"name":"ripgrep-15.1.0-x86_64-apple-darwin.tar.gz","version":"15.1.0","lts":false,"channel":"stable","date":"2025-10-22","os":"macos","arch":"amd64","ext":"tar.gz","download":"https://github.com/BurntSushi/ripgrep/releases/download/15.1.0/ripgrep-15.1.0-x86_64-apple-darwin.tar.gz","libc":"none"}]
[{"name":"ripgrep-15.1.0-x86_64-apple-darwin.tar.gz","version":"15.1.0","lts":false,"channel":"stable","date":"2025-10-22","os":"macos","arch":"amd64","ext":"tar.gz","download":"https://github.com/BurntSushi/ripgrep/releases/download/15.1.0/ripgrep-15.1.0-x86_64-apple-darwin.tar.gz","libc":"none"}]

View File

@@ -1 +1 @@
[{"name":"ripgrep-15.1.0-aarch64-apple-darwin.tar.gz","version":"15.1.0","lts":false,"channel":"stable","date":"2025-10-22","os":"macos","arch":"arm64","ext":"tar.gz","download":"https://github.com/BurntSushi/ripgrep/releases/download/15.1.0/ripgrep-15.1.0-aarch64-apple-darwin.tar.gz","libc":"none"}]
[{"name":"ripgrep-15.1.0-aarch64-apple-darwin.tar.gz","version":"15.1.0","lts":false,"channel":"stable","date":"2025-10-22","os":"macos","arch":"arm64","ext":"tar.gz","download":"https://github.com/BurntSushi/ripgrep/releases/download/15.1.0/ripgrep-15.1.0-aarch64-apple-darwin.tar.gz","libc":"none"}]

View File

@@ -1 +1 @@
[{"name":"ripgrep-15.1.0-x86_64-pc-windows-gnu.zip","version":"15.1.0","lts":false,"channel":"stable","date":"2025-10-22","os":"windows","arch":"amd64","ext":"zip","download":"https://github.com/BurntSushi/ripgrep/releases/download/15.1.0/ripgrep-15.1.0-x86_64-pc-windows-gnu.zip","libc":"none"}]
[{"name":"ripgrep-15.1.0-x86_64-pc-windows-gnu.zip","version":"15.1.0","lts":false,"channel":"stable","date":"2025-10-22","os":"windows","arch":"amd64","ext":"zip","download":"https://github.com/BurntSushi/ripgrep/releases/download/15.1.0/ripgrep-15.1.0-x86_64-pc-windows-gnu.zip","libc":"none"}]