ref(webi): complete transition from 'request' for 'fetch'

This commit is contained in:
AJ ONeal
2024-12-15 06:48:52 +00:00
parent ba94ad883b
commit 14cebeeb61
12 changed files with 478 additions and 398 deletions

View File

@@ -41,9 +41,7 @@ if (module === require.main) {
'https://git.rootprojects.org',
'',
'',
).then(
function (all) {
console.info(JSON.stringify(all, null, 2));
},
);
).then(function (all) {
console.info(JSON.stringify(all, null, 2));
});
}

View File

@@ -9,8 +9,6 @@ let HostTargets = require('./build-classifier/host-targets.js');
let Lexver = require('./build-classifier/lexver.js');
let Triplet = require('./build-classifier/triplet.js');
let request = require('@root/request');
var ALIAS_RE = /^alias: (\w+)$/m;
var LEGACY_ARCH_MAP = {
@@ -153,7 +151,7 @@ async function getLatestBuilds(Releases, installersDir, cacheDir, name, date) {
}
async function getLatestBuildsInner(Releases, cacheDir, name, date) {
let data = await Releases.latest(request);
let data = await Releases.latest();
if (!date) {
date = new Date();

View File

@@ -6,8 +6,6 @@ let Path = require('node:path');
let BuildsCacher = require('./builds-cacher.js');
let Triplet = require('./build-classifier/triplet.js');
let request = require('@root/request');
async function main() {
let projName = process.argv[2];
if (!projName) {
@@ -47,7 +45,7 @@ async function main() {
Releases.latest = Releases;
}
let projInfo = await Releases.latest(request);
let projInfo = await Releases.latest();
// let packages = await Builds.getPackage({ name: projName });
// console.log(packages);

View File

@@ -3,7 +3,6 @@
var Releases = module.exports;
var path = require('path');
var request = require('@root/request');
var _normalize = require('./normalize.js');
var cache = {};
@@ -28,7 +27,7 @@ Releases.get = async function (pkgdir) {
throw err;
}
let all = await get.latest(request);
let all = await get.latest();
return _normalize(all);
};

View File

@@ -2,7 +2,10 @@
var FLUTTER_OSES = ['macos', 'linux', 'windows'];
// stable, beta, dev
/**
* stable, beta, dev
* @type {Object.<String, Boolean>}
*/
var channelMap = {};
// This can be spot-checked against
@@ -53,25 +56,39 @@ var channelMap = {};
// ]
// }
/**
* @typedef BuildInfo
* @prop {String} version
* @prop {String} [_version]
* @prop {Boolean} lts
* @prop {String} channel
* @prop {String} date
* @prop {String} download
* @prop {String} [_filename]
*/
module.exports = async function () {
let all = {
download: '',
/** @type {Array<BuildInfo>} */
releases: [],
/** @type {Array<String>} */
channels: [],
};
for (let osname of FLUTTER_OSES) {
const response = await fetch(`https://storage.googleapis.com/flutter_infra_release/releases/releases_${osname}.json`, {
method: 'GET',
headers: { Accept: 'application/json' },
});
let response = await fetch(
`https://storage.googleapis.com/flutter_infra_release/releases/releases_${osname}.json`,
{ headers: { Accept: 'application/json' } },
);
if (!response.ok) {
throw new Error(`Failed to fetch data for ${osname}: ${response.statusText}`);
throw new Error(
`Failed to fetch data for ${osname}: ${response.statusText}`,
);
}
const respBody = await response.json();
let osBaseUrl = respBody.base_url;
let osReleases = respBody.releases;
let data = await response.json();
let osBaseUrl = data.base_url;
let osReleases = data.releases;
for (let asset of osReleases) {
if (!channelMap[asset.channel]) {
@@ -100,7 +117,7 @@ module.exports = async function () {
};
if (module === require.main) {
module.exports(require('@root/request')).then(function (all) {
module.exports().then(function (all) {
all.releases = all.releases.slice(25);
console.info(JSON.stringify(all, null, 2));
});

View File

@@ -1,14 +1,19 @@
'use strict';
/** @type {Object.<String, String>} */
var osMap = {
darwin: 'macos',
};
/** @type {Object.<String, String>} */
var archMap = {
386: 'x86',
};
let ODDITIES = ['bootstrap', '-arm6.'];
/**
* @param {String} filename
*/
function isOdd(filename) {
for (let oddity of ODDITIES) {
let isOddity = filename.includes(oddity);
@@ -18,6 +23,21 @@ function isOdd(filename) {
}
}
/**
* @typedef BuildInfo
* @prop {String} version
* @prop {String} [_version]
* @prop {String} arch
* @prop {String} channel
* @prop {String} date
* @prop {String} download
* @prop {String} ext
* @prop {String} [_filename]
* @prop {String} hash
* @prop {Boolean} lts
* @prop {String} os
*/
async function getDistributables() {
/*
{
@@ -37,60 +57,63 @@ async function getDistributables() {
]
};
*/
const response = await fetch('https://golang.org/dl/?mode=json&include=all', {
method: 'GET',
headers: { Accept: 'application/json' },
});
if (!response.ok) {
throw new Error(`Failed to fetch Go releases: ${response.statusText}`);
}
const goReleases = await response.json();
const all = {
releases: [],
download: '',
};
goReleases.forEach((release) => {
// Strip 'go' prefix and standardize version
const parts = release.version.slice(2).split('.');
while (parts.length < 3) {
parts.push('0');
}
const version = parts.join('.');
const fileversion = release.version.slice(2);
release.files.forEach((asset) => {
if (isOdd(asset.filename)) {
return;
}
const filename = asset.filename;
const os = osMap[asset.os] || asset.os || '-';
const arch = archMap[asset.arch] || asset.arch || '-';
all.releases.push({
version: version,
_version: fileversion,
lts: (parts[0] > 0 && release.stable) || false,
channel: (release.stable && 'stable') || 'beta',
date: '1970-01-01', // Placeholder
os: os,
arch: arch,
ext: '', // Let normalize run the split/test/join
hash: '-', // Placeholder for hash
download: `https://dl.google.com/go/${filename}`,
});
});
});
return all;
let response = await fetch('https://golang.org/dl/?mode=json&include=all', {
method: 'GET',
headers: { Accept: 'application/json' },
});
if (!response.ok) {
throw new Error(`Failed to fetch Go releases: ${response.statusText}`);
}
let goReleases = await response.json();
let all = {
/** @type {Array<BuildInfo>} */
releases: [],
download: '',
};
for (let release of goReleases) {
// Strip 'go' prefix, standardize version
let parts = release.version.slice(2).split('.');
while (parts.length < 3) {
parts.push('0');
}
let version = parts.join('.');
let fileversion = release.version.slice(2);
for (let asset of release.files) {
if (isOdd(asset.filename)) {
continue;
}
let filename = asset.filename;
let os = osMap[asset.os] || asset.os || '-';
let arch = archMap[asset.arch] || asset.arch || '-';
let build = {
version: version,
_version: fileversion,
lts: (parts[0] > 0 && release.stable) || false,
channel: (release.stable && 'stable') || 'beta',
date: '1970-01-01', // the world may never know
os: os,
arch: arch,
ext: '', // let normalize run the split/test/join
hash: '-', // not ready to standardize this yet
download: `https://dl.google.com/go/${filename}`,
};
all.releases.push(build);
}
}
return all;
}
module.exports = getDistributables;
if (module === require.main) {
getDistributables(require('@root/request')).then(function (all) {
getDistributables().then(function (all) {
all = require('../_webi/normalize.js')(all);
//@ts-expect-error
all.releases = all.releases.slice(0, 10);
console.info(JSON.stringify(all, null, 2));
});

View File

@@ -16,84 +16,97 @@ function createUrlMatcher() {
);
}
/**
* @typedef BuildInfo
* @prop {String} version
* @prop {String} [_version]
* @prop {String} arch
* @prop {String} channel
* @prop {String} date
* @prop {String} download
* @prop {String} ext
* @prop {String} [_filename]
* @prop {String} hash
* @prop {Boolean} lts
* @prop {String} os
*/
async function getRawReleases() {
let matcher = createRssMatcher();
const response = await fetch('https://sourceforge.net/projects/gpgosx/rss?path=/', {
method: 'GET',
headers: {
'Accept': 'application/rss+xml', // Ensure the correct content type is requested
},
let resp = await fetch('https://sourceforge.net/projects/gpgosx/rss?path=/', {
headers: { Accept: 'application/rss+xml' },
});
// Validate the response status
if (!response.ok) {
throw new Error(`Failed to fetch RSS feed: HTTP ${response.status} - ${response.statusText}`);
let text = await resp.text(); // Fetch RSS feed as plain text
if (!resp.ok) {
throw new Error(`Failed to fetch RSS feed: HTTP ${resp.status}: ${text}`);
}
const contentType = response.headers.get('Content-Type');
let contentType = resp.headers.get('Content-Type');
if (!contentType || !contentType.includes('xml')) {
throw new Error(`Unexpected content type: ${contentType}`);
}
const body = await response.text(); // Fetch RSS feed as plain text
let links = [];
for (;;) {
let m = matcher.exec(body);
let m = matcher.exec(text);
if (!m) {
break;
}
links.push(m[1]);
}
return links;
}
/**
* @param {Array<String>} links
*/
function transformReleases(links) {
//console.log(JSON.stringify(links, null, 2));
//console.log(links.length);
let matcher = createUrlMatcher();
let releases = links
.map(function (link) {
let isLts = ltsRe.test(link);
let parts = link.match(matcher);
if (!parts || !parts[2]) {
return null;
}
let segs = parts[2].split('.');
let version = segs.slice(0, 3).join('.');
if (segs.length > 3) {
version += '+' + segs.slice(3);
}
let fileversion = segs.join('.');
let builds = [];
for (let link of links) {
let isLts = ltsRe.test(link);
let parts = link.match(matcher);
if (!parts || !parts[2]) {
continue;
}
return {
name: parts[1],
version: version,
_version: fileversion,
// all go versions >= 1.0.0 are effectively LTS
lts: isLts,
channel: 'stable',
// TODO <pubDate>Sat, 19 Nov 2016 16:17:33 UT</pubDate>
date: '1970-01-01', // the world may never know
os: 'macos',
arch: 'amd64',
ext: 'dmg',
download: link,
};
})
.filter(Boolean);
let segs = parts[2].split('.');
let version = segs.slice(0, 3).join('.');
if (segs.length > 3) {
version += '+' + segs.slice(3);
}
let fileversion = segs.join('.');
let build = {
name: parts[1],
version: version,
_version: fileversion,
lts: isLts,
channel: 'stable',
// TODO <pubDate>Sat, 19 Nov 2016 16:17:33 UT</pubDate>
date: '1970-01-01', // the world may never know
os: 'macos',
arch: 'amd64',
ext: 'dmg',
download: link,
};
builds.push(build);
}
return {
_names: ['GnuPG', 'gpgosx'],
releases: releases,
releases: builds,
};
}
async function getDistributables(request) {
let releases = await getRawReleases(request);
async function getDistributables() {
let releases = await getRawReleases();
let all = transformReleases(releases);
return all;
}
@@ -101,7 +114,7 @@ async function getDistributables(request) {
module.exports = getDistributables;
if (module === require.main) {
getDistributables(require('@root/request')).then(function (all) {
getDistributables().then(function (all) {
all = require('../_webi/normalize.js')(all);
all.releases = all.releases.slice(0, 10000);
console.info(JSON.stringify(all, null, 2));

View File

@@ -1,89 +1,82 @@
'use strict';
async function getRawReleases() {
const response = await fetch('https://iterm2.com/downloads.html', {
method: 'GET',
headers: {
'Accept': 'text/html', // Explicitly request HTML content
},
let resp = await fetch('https://iterm2.com/downloads.html', {
headers: { Accept: 'text/html' },
});
// Validate HTTP response
if (!response.ok) {
throw new Error(`Failed to fetch releases: HTTP ${response.status} - ${response.statusText}`);
let text = await resp.text();
if (!resp.ok) {
throw new Error(`Failed to fetch releases: HTTP ${resp.status}: ${text}`);
}
// Validate Content-Type header
const contentType = response.headers.get('Content-Type');
let contentType = resp.headers.get('Content-Type');
if (!contentType || !contentType.includes('text/html')) {
throw new Error(`Unexpected Content-Type: ${contentType}`);
}
// Parse HTML content
const body = await response.text();
var links = body
.split(/[<>]+/g)
.map(function (str) {
var m = str.match(
/href="(https:\/\/iterm2\.com\/downloads\/.*\.zip)"/,
);
let lines = text.split(/[<>]+/g);
/** @type {Array<String>} */
let links = [];
for (let str of lines) {
var m = str.match(/href="(https:\/\/iterm2\.com\/downloads\/.*\.zip)"/);
if (m && /iTerm2-[34]/.test(m[1])) {
return m[1];
if (m[1]) {
links.push(m[1]);
}
}
})
.filter(Boolean);
}
return links;
}
/**
* @param {Array<String>} links
*/
function transformReleases(links) {
//console.log(JSON.stringify(links, null, 2));
//console.log(links.length);
let builds = [];
for (let link of links) {
var channel = /\/stable\//.test(link) ? 'stable' : 'beta';
var parts = link.replace(/.*\/iTerm2[-_]v?(\d_.*)\.zip/, '$1').split('_');
var version = parts.join('.').replace(/([_-])?beta/, '-beta');
// ex: 3.5.0-beta17 => 3_5_0beta17
// ex: 3.0.2-preview => 3_0_2-preview
let fileversion = version.replace(/\./g, '_');
fileversion = fileversion.replace(/-beta/g, 'beta');
let build = {
version: version,
_version: fileversion,
lts: 'stable' === channel,
channel: channel,
date: '1970-01-01', // the world may never know
os: 'macos',
arch: 'amd64',
ext: '', // let normalize run the split/test/join
download: link,
};
builds.push(build);
}
return {
_names: ['iTerm2', 'iterm2'],
releases: links
.map(function (link) {
var channel = /\/stable\//.test(link) ? 'stable' : 'beta';
var parts = link
.replace(/.*\/iTerm2[-_]v?(\d_.*)\.zip/, '$1')
.split('_');
var version = parts.join('.').replace(/([_-])?beta/, '-beta');
// ex: 3.5.0-beta17 => 3_5_0beta17
// ex: 3.0.2-preview => 3_0_2-preview
let fileversion = version.replace(/\./g, '_');
fileversion = fileversion.replace(/-beta/g, 'beta');
return {
version: version,
_version: fileversion,
// all go versions >= 1.0.0 are effectively LTS
lts: 'stable' === channel,
channel: channel,
date: '1970-01-01', // the world may never know
os: 'macos',
arch: 'amd64',
ext: '', // let normalize run the split/test/join
download: link,
};
})
.filter(Boolean),
releases: builds,
};
}
function getDistributables(request) {
return getRawReleases(request)
.then(transformReleases)
.then(function (all) {
return all;
});
async function getDistributables() {
let rawReleases = await getRawReleases();
let all = transformReleases(rawReleases);
return all;
}
module.exports = getDistributables;
if (module === require.main) {
getDistributables(require('@root/request')).then(function (all) {
getDistributables().then(function (all) {
all = require('../_webi/normalize.js')(all);
all.releases = all.releases.slice(0, 10000);
console.info(JSON.stringify(all, null, 2));

View File

@@ -40,65 +40,90 @@ var headers = {
'Accept-Language': 'en-US,en;q=0.9,sq;q=0.8',
};
/**
* @param {typeof oses[0]} os
*/
async function fetchReleasesForOS(os) {
// Fetch the webpage for the given OS
const response = await fetch(os.url, {
method: 'GET',
let resp = await fetch(os.url, {
headers: headers,
});
// Validate HTTP response
if (!response.ok) {
throw new Error(`Failed to fetch URL: ${os.url}. HTTP ${response.status} - ${response.statusText}`);
let text = await resp.text();
if (!resp.ok) {
throw new Error(
`Failed to fetch URL: ${os.url}. HTTP ${resp.status}: ${text}`,
);
}
// Parse the response body
const body = await response.text();
// Extract the download link
const match = body.match(/(http[^>]+Install[^>]+\.dmg)/);
return match ? match[1] : null;
let match = text.match(/(http[^>]+Install[^>]+\.dmg)/);
if (match) {
return match[1];
}
}
/**
* @typedef BuildInfo
* @prop {String} version
* @prop {String} [_version]
* @prop {String} arch
* @prop {String} channel
* @prop {String} date
* @prop {String} download
* @prop {String} ext
* @prop {String} [_filename]
* @prop {String} hash
* @prop {Boolean} lts
* @prop {String} os
*/
let osnames = ['macos', 'linux'];
async function getDistributables() {
const all = {
let all = {
_names: ['InstallOS'],
download: '',
/** @type {Array<BuildInfo>} */
releases: [],
};
// Fetch data for each OS and populate the releases array
await Promise.all(
oses.map(async (os) => {
try {
const download = await fetchReleasesForOS(os);
for (let os of oses) {
let download = await fetchReleasesForOS(os);
if (!download) {
continue;
}
// Add releases for macOS and Linux
['macos', 'linux'].forEach((osname) => {
all.releases.push({
version: os.version,
lts: os.lts || false,
channel: os.channel || 'beta',
date: os.date,
os: osname,
arch: 'amd64',
ext: 'dmg',
hash: '-', // Placeholder for hash
download: download,
});
});
} catch (err) {
console.error(`Error fetching for ${os.name}: ${err.message}`);
}
}),
);
// Add releases for macOS and Linux
for (let osname of osnames) {
let build = {
version: os.version,
lts: os.lts || false,
channel: os.channel || 'beta',
date: os.date,
os: osname,
arch: 'amd64',
ext: 'dmg',
hash: '-',
download: download,
};
all.releases.push(build);
}
}
// Sort releases
all.releases.sort((a, b) => {
all.releases.sort(function (a, b) {
if (a.version === '10.11.6') {
return -1;
}
return a.date > b.date ? 1 : -1;
if (a.date > b.date) {
return 1;
} else if (a.date < b.date) {
return -1;
}
return 0;
});
return all;
@@ -107,7 +132,7 @@ async function getDistributables() {
module.exports = getDistributables;
if (module === require.main) {
module.exports(require('@root/request')).then(function (all) {
module.exports().then(function (all) {
console.info(JSON.stringify(all, null, 2));
});
}

View File

@@ -7,6 +7,7 @@
const END_OF_LIFE = 366 * 24 * 60 * 60 * 1000;
// OSes
/** @type {Object.<String, String>} */
let osMap = {
osx: 'macos', // NOTE: filename is 'darwin'
linux: 'linux',
@@ -16,6 +17,7 @@ let osMap = {
};
// CPU architectures
/** @type {Object.<String, String>} */
let archMap = {
x64: 'amd64',
x86: 'x86',
@@ -28,6 +30,7 @@ let archMap = {
};
// file extensions
/** @type {Object.<String, Array<String>>} */
let pkgMap = {
pkg: ['pkg'],
//exe: ['exe'], // disable
@@ -40,8 +43,25 @@ let pkgMap = {
musl: ['tar.gz', 'tar.xz'],
};
/**
* @typedef BuildInfo
* @prop {String} version
* @prop {String} [_version]
* @prop {String} arch
* @prop {String} channel
* @prop {String} date
* @prop {String} download
* @prop {String} ext
* @prop {String} [_filename]
* @prop {String} [hash]
* @prop {String} libc
* @prop {Boolean} lts
* @prop {String} os
*/
async function getDistributables() {
let all = {
/** @type {Array<BuildInfo>} */
releases: [],
download: '',
};
@@ -64,51 +84,52 @@ async function getDistributables() {
]
*/
// Alternate: 'https://nodejs.org/dist/index.json',
let baseUrl = `https://nodejs.org/download/release`;
{
// Alternate: 'https://nodejs.org/dist/index.json',
let baseUrl = `https://nodejs.org/download/release`;
// Fetch official builds
let officialP = fetch(`${baseUrl}/index.json`, {
method: 'GET',
headers: { Accept: 'application/json' },
}).then((response) => {
if (!response.ok) {
throw new Error(`Failed to fetch official builds: HTTP ${response.status} - ${response.statusText}`);
}
return response.json();
})
.then((data) => {
transform(baseUrl, data);
});
// Fetch unofficial builds
let unofficialBaseUrl = `https://unofficial-builds.nodejs.org/download/release`;
let unofficialP = fetch(`${unofficialBaseUrl}/index.json`, {
method: 'GET',
headers: { Accept: 'application/json' },
})
.then((response) => {
if (!response.ok) {
throw new Error(`Failed to fetch unofficial builds: HTTP ${response.status} - ${response.statusText}`);
}
return response.json();
})
.then((data) => {
transform(unofficialBaseUrl, data);
})
.catch((err) => {
console.error('failed to fetch unofficial-builds');
console.error(err);
// Fetch official builds
let resp = await fetch(`${baseUrl}/index.json`, {
headers: { Accept: 'application/json' },
});
let text = await resp.text();
if (!resp.ok) {
throw new Error(
`Failed to fetch official builds: HTTP ${resp.status}: ${text}`,
);
}
let data = JSON.parse(text);
void transform(baseUrl, data);
}
{
// Fetch unofficial builds
let unofficialBaseUrl = `https://unofficial-builds.nodejs.org/download/release`;
let resp = await fetch(`${unofficialBaseUrl}/index.json`, {
headers: { Accept: 'application/json' },
});
let text = await resp.text();
if (!resp.ok) {
throw new Error(
`Failed to fetch official builds: HTTP ${resp.status}: ${text}`,
);
}
let data = JSON.parse(text);
transform(unofficialBaseUrl, data);
}
/**
* @param {String} baseUrl
* @param {Array<any>} builds
*/
function transform(baseUrl, builds) {
builds.forEach(function (build) {
for (let build of builds) {
let buildDate = new Date(build.date).valueOf();
let age = Date.now() - buildDate;
let maintained = age < END_OF_LIFE;
if (!maintained) {
return;
continue;
}
let lts = false !== build.lts;
@@ -122,9 +143,9 @@ async function getDistributables() {
channel = 'beta';
}
build.files.forEach(function (file) {
for (let file of build.files) {
if ('src' === file || 'headers' === file) {
return;
continue;
}
let fileParts = file.split('-');
@@ -140,7 +161,7 @@ async function getDistributables() {
pkgs = pkgMap.tar;
}
if (!pkgs?.length) {
return;
continue;
}
let extra = '';
@@ -157,7 +178,7 @@ async function getDistributables() {
osPart = 'darwin';
}
pkgs.forEach(function (pkg) {
for (let pkg of pkgs) {
let filename = `node-${build.version}-${osPart}-${archPart}${extra}.${pkg}`;
if ('msi' === pkg) {
filename = `node-${build.version}-${archPart}${extra}.${pkg}`;
@@ -178,20 +199,17 @@ async function getDistributables() {
};
all.releases.push(release);
});
});
});
}
}
}
}
await officialP;
await unofficialP;
return all;
}
module.exports = getDistributables;
if (module === require.main) {
getDistributables(require('@root/request')).then(function (all) {
getDistributables().then(function (all) {
all = require('../_webi/normalize.js')(all);
console.info(JSON.stringify(all));
//console.info(JSON.stringify(all, null, 2));

View File

@@ -1,59 +1,53 @@
'use strict';
/**
* @typedef BuildInfo
* @prop {String} version
* @prop {String} download
*/
async function getDistributables() {
try {
// Fetch the Terraform releases JSON
const response = await fetch('https://releases.hashicorp.com/terraform/index.json', {
method: 'GET',
headers: { Accept: 'application/json' },
});
// Validate the HTTP response
if (!response.ok) {
throw new Error(`Failed to fetch releases: HTTP ${response.status} - ${response.statusText}`);
}
// Parse the JSON response
const releases = await response.json();
let all = {
releases: [],
download: '', // Full URI provided in response body
};
function getBuildsForVersion(version) {
releases.versions[version].builds.forEach(function (build) {
let r = {
version: build.version,
download: build.url,
// These are generic enough for the autodetect,
// and the per-file logic has proven to get outdated sooner
//os: convert[build.os],
//arch: convert[build.arch],
//channel: 'stable|-rc|-beta|-alpha',
};
all.releases.push(r);
});
}
// Releases are listed chronologically, we want the latest first.
const allVersions = Object.keys(releases.versions).reverse();
allVersions.forEach(function (version) {
getBuildsForVersion(version);
});
return all;
} catch (err) {
console.error('Error fetching Terraform releases:', err.message);
return { releases: [], download: '' };
let resp = await fetch(
'https://releases.hashicorp.com/terraform/index.json',
{ headers: { Accept: 'application/json' } },
);
let text = await resp.text();
if (!resp.ok) {
throw new Error(`Failed to fetch releases: HTTP ${resp.status}: ${text}`);
}
let releases = JSON.parse(text);
let all = {
/** @type {Array<BuildInfo>} */
releases: [],
download: '',
};
let allVersions = Object.keys(releases.versions);
allVersions.reverse(); // Releases are listed chronologically, we want the latest first.
for (let version of allVersions) {
for (let build of releases.versions[version].builds) {
let r = {
version: build.version,
download: build.url,
// These are generic enough for the autodetect,
// and the per-file logic has proven to get outdated sooner
//os: convert[build.os],
//arch: convert[build.arch],
//channel: 'stable|-rc|-beta|-alpha',
};
all.releases.push(r);
}
}
return all;
}
module.exports = getDistributables;
if (module === require.main) {
getDistributables(require('@root/request')).then(function (all) {
getDistributables().then(function (all) {
all = require('../_webi/normalize.js')(all);
console.info(JSON.stringify(all));
});

View File

@@ -3,100 +3,104 @@
var NON_BUILDS = ['bootstrap', 'src'];
var ODDITIES = NON_BUILDS.concat(['armv6kz-linux']);
/**
* @typedef BuildInfo
* @prop {String} version
* @prop {String} [_version]
* @prop {String} [arch]
* @prop {String} channel
* @prop {String} date
* @prop {String} download
* @prop {String} [ext]
* @prop {String} [_filename]
* @prop {String} [hash]
* @prop {String} [libc]
* @prop {Boolean} [lts]
* @prop {String} [size]
* @prop {String} os
*/
module.exports = async function () {
try {
// Fetch the Zig language download index JSON
const response = await fetch('https://ziglang.org/download/index.json', {
method: 'GET',
headers: { Accept: 'application/json' },
});
let resp = await fetch('https://ziglang.org/download/index.json', {
method: 'GET',
headers: { Accept: 'application/json' },
});
let text = await resp.text();
if (!resp.ok) {
throw new Error(`Failed to fetch releases: HTTP ${resp.status}: ${text}`);
}
let versions = JSON.parse(text);
// Validate HTTP response
if (!response.ok) {
throw new Error(`Failed to fetch releases: HTTP ${response.status} - ${response.statusText}`);
/** @type {Array<BuildInfo>} */
let releases = [];
let refs = Object.keys(versions);
for (let ref of refs) {
let pkgs = versions[ref];
let version = pkgs.version || ref;
// "platform" = arch + os combo
let platforms = Object.keys(pkgs);
for (let platform of platforms) {
let pkg = pkgs[platform];
// don't grab 'date' or 'notes', which are (confusingly)
// at the same level as platform releases
let isNotPackage = !pkg || 'object' !== typeof pkg || !pkg.tarball;
if (isNotPackage) {
continue;
}
let isOdd = ODDITIES.includes(platform);
if (isOdd) {
continue;
}
// Ex: aarch64-macos => ['aarch64', 'macos']
let parts = platform.split('-');
//let arch = parts[0];
let os = parts[1];
if (parts.length > 2) {
console.warn(`unexpected platform name with multiple '-': ${platform}`);
continue;
}
let p = {
version: version,
date: pkgs.date,
channel: 'stable',
// linux, macos, windows
os: os,
// TODO map explicitly (rather than normalization auto-detect)
//arch: arch,
download: pkg.tarball,
hash: pkg.shasum,
size: pkg.size,
// TODO docs + release notes?
//docs: 'https://ziglang.org/documentation/0.9.1/',
//stdDocs: 'https://ziglang.org/documentation/0.9.1/std/',
//notes: 'https://ziglang.org/download/0.9.1/release-notes.html'
};
// Mark branches or tags as beta (for now)
// Ex: 'master'
// Also mark prereleases (with build tags) as beta
// Ex: 0.10.0-dev.1606+97a53bb8a
let isNotStable = !/\./.test(ref) || /\+|-/.test(p.version);
if (isNotStable) {
p.channel = 'beta';
}
releases.push(p);
}
}
// Parse the JSON response
const versions = await response.json();
let releases = [];
let refs = Object.keys(versions);
refs.forEach(function (ref) {
let pkgs = versions[ref];
let version = pkgs.version || ref;
// "platform" = arch + os combo
let platforms = Object.keys(pkgs);
platforms.forEach(function (platform) {
let pkg = pkgs[platform];
// don't grab 'date' or 'notes', which are (confusingly)
// at the same level as platform releases
let isNotPackage = !pkg || 'object' !== typeof pkg || !pkg.tarball;
if (isNotPackage) {
return;
}
let isOdd = ODDITIES.includes(platform);
if (isOdd) {
return;
}
// Ex: aarch64-macos => ['aarch64', 'macos']
let parts = platform.split('-');
//let arch = parts[0];
let os = parts[1];
if (parts.length > 2) {
console.warn(
`unexpected platform name with multiple '-': ${platform}`,
);
return;
}
let p = {
version: version,
date: pkgs.date,
channel: 'stable',
// linux, macos, windows
os: os,
// TODO map explicitly (rather than normalization auto-detect)
//arch: arch,
download: pkg.tarball,
hash: pkg.shasum,
size: pkg.size,
// TODO docs + release notes?
//docs: 'https://ziglang.org/documentation/0.9.1/',
//stdDocs: 'https://ziglang.org/documentation/0.9.1/std/',
//notes: 'https://ziglang.org/download/0.9.1/release-notes.html'
};
// Mark branches or tags as beta (for now)
// Ex: 'master'
// Also mark prereleases (with build tags) as beta
// Ex: 0.10.0-dev.1606+97a53bb8a
let isNotStable = !/\./.test(ref) || /\+|-/.test(p.version);
if (isNotStable) {
p.channel = 'beta';
}
releases.push(p);
});
});
return {
releases: releases,
};
}catch (err) {
console.error('Error fetching Zig releases:', err.message);
return {
releases: [],
};
return {
releases: releases,
};
}
};
if (module === require.main) {
module.exports(require('@root/request')).then(function (all) {
module.exports().then(function (all) {
all = require('../_webi/normalize.js')(all);
// just select the first 5 for demonstration
all.releases = all.releases.slice(0, 5);