Compare commits

..

2 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
76c957e8d2 fix(webi): handle special chars in USERPROFILE path and detect download failures
Co-authored-by: coolaj86 <122831+coolaj86@users.noreply.github.com>
2026-03-12 08:35:15 +00:00
copilot-swe-agent[bot]
97fc71a066 Initial plan 2026-03-12 08:23:10 +00:00
130 changed files with 5324 additions and 592 deletions

62
_common/brew.js Normal file
View File

@@ -0,0 +1,62 @@
'use strict';
let Fetcher = require('../_common/fetcher.js');
/**
* Gets releases from 'brew'.
*
* @param {null} _
* @param {string} formula
* @returns {Promise<any>}
*/
async function getDistributables(_, formula) {
if (!formula) {
return Promise.reject('missing formula for brew');
}
let resp;
try {
let url = `https://formulae.brew.sh/api/formula/${formula}.json`;
resp = await Fetcher.fetch(url, {
headers: { Accept: 'application/json' },
});
} catch (e) {
/** @type {Error & { code: string, response: { status: number, body: string } }} */ //@ts-expect-error
let err = e;
if (err.code === 'E_FETCH_RELEASES') {
err.message = `failed to fetch '${formula}' release data from 'brew': ${err.response.status} ${err.response.body}`;
}
throw e;
}
let body = JSON.parse(resp.body);
var ver = body.versions.stable;
var dl = (
body.bottle.stable.files.high_sierra || body.bottle.stable.files.catalina
).url.replace(new RegExp(ver.replace(/\./g, '\\.'), 'g'), '{{ v }}');
return [
{
version: ver,
download: dl.replace(/{{ v }}/g, ver),
},
].concat(
body.versioned_formulae.map(
/** @param {String} f */
function (f) {
var ver = f.replace(/.*@/, '');
return {
version: ver,
download: dl,
};
},
),
);
}
module.exports = getDistributables;
if (module === require.main) {
getDistributables(null, 'mariadb').then(function (all) {
console.info(JSON.stringify(all, null, 2));
});
}

56
_common/fetcher.js Normal file
View File

@@ -0,0 +1,56 @@
'use strict';
let Fetcher = module.exports;
/**
* @typedef ResponseSummary
* @prop {Boolean} ok
* @prop {Headers} headers
* @prop {Number} status
* @prop {String} body
*/
/**
* @param {String} url
* @param {RequestInit} opts
* @returns {Promise<ResponseSummary>}
*/
Fetcher.fetch = async function (url, opts) {
let resp = await fetch(url, opts);
let summary = Fetcher.throwIfNotOk(resp);
return summary;
};
/**
* @param {Response} resp
* @returns {Promise<ResponseSummary>}
*/
Fetcher.throwIfNotOk = async function (resp) {
let text = await resp.text();
if (!resp.ok) {
let headers = Array.from(resp.headers);
console.error('[Fetcher] error: Response Headers:', headers);
console.error('[Fetcher] error: Response Text:', text);
let err = new Error(`fetch was not ok`);
Object.assign({
status: 503,
code: 'E_FETCH_RELEASES',
response: {
status: resp.status,
headers: headers,
body: text,
},
});
throw err;
}
let summary = {
ok: resp.ok,
headers: resp.headers,
status: resp.status,
body: text,
};
return summary;
};

218
_common/git-tag.js Normal file
View File

@@ -0,0 +1,218 @@
'use strict';
require('dotenv').config({ path: '.env' });
var Crypto = require('crypto');
var util = require('util');
var exec = util.promisify(require('child_process').exec);
var Fs = require('node:fs/promises');
var FsSync = require('node:fs');
var Path = require('node:path');
var repoBaseDir = process.env.REPO_BASE_DIR || '';
if (!repoBaseDir) {
repoBaseDir = Path.resolve('./_repos');
// for stderr
console.error(`[Warn] REPO_BASE_DIR= not set, ${repoBaseDir}`);
}
var Repos = {};
Repos.clone = async function (repoPath, gitUrl) {
let uuid = Crypto.randomUUID();
let tmpPath = `${repoPath}.${uuid}.tmp`;
let bakPath = `${repoPath}.${uuid}.dup`;
await exec(`git clone --bare --filter=tree:0 ${gitUrl} ${tmpPath}`);
try {
FsSync.accessSync(repoPath);
return;
} catch (e) {
if (e.code !== 'ENOENT') {
throw e;
}
}
// sync to avoid race conditions
try {
FsSync.renameSync(repoPath, bakPath);
} catch (e) {
if (e.code !== 'ENOENT') {
throw e;
}
}
FsSync.renameSync(tmpPath, repoPath);
await Fs.rm(bakPath, { force: true, recursive: true });
};
Repos.checkExists = async function (repoPath) {
let err = await Fs.access(repoPath).catch(Object);
if (!err) {
return true;
}
if (err.code !== 'ENOENT') {
throw err;
}
return false;
};
Repos.fetch = async function (repoPath) {
await exec(`git --git-dir=${repoPath} fetch`);
};
Repos.getTags = async function (repoPath) {
var { stdout } = await exec(`git --git-dir=${repoPath} tag`);
var rawTags = stdout.trim().split('\n');
let tags = [];
for (let tag of rawTags) {
// ex: v1, v2, v1.1, 1.1.0-rc
let maybeVersionRe = /^(v\d+|v?\d+\.\d+)/;
let maybeVersion = maybeVersionRe.test(tag);
if (maybeVersion) {
tags.push(tag);
}
}
tags = tags.reverse();
return tags;
};
Repos.getTipInfo = async function (repoPath) {
var { stdout } = await exec(
`git --git-dir=${repoPath} rev-parse --abbrev-ref HEAD`,
);
var branch = stdout.trim();
var info = await Repos.getCommitInfo(repoPath, 'HEAD');
info.commitish = branch;
return info;
};
Repos.getCommitInfo = async function (repoPath, commitish) {
var { stdout } = await exec(
`git --git-dir=${repoPath} log -1 --format="%h %H %ad %cd" --date=iso-strict ${commitish}`,
);
stdout = stdout.trim();
var commitParts = stdout.split(/\s+/g);
return {
commitish: commitish,
commit_id: `${commitParts[0]}`,
commit: `${commitParts[1]}`,
date: commitParts[2],
date_authored: commitParts[3],
};
};
/**
* Lists GitHub Releases (w/ uploaded assets)
*
* @param request
* @param {string} owner
* @param {string} gitUrl
* @returns {PromiseLike<any> | Promise<any>}
*/
async function getDistributables(gitUrl) {
let all = {
releases: [],
download: '',
};
let repoName = gitUrl.split('/').pop();
repoName = repoName.replace(/\.git$/, '');
let repoPath = `${repoBaseDir}/${repoName}.git`;
let isCloned = await Repos.checkExists(repoPath);
if (!isCloned) {
await Repos.clone(repoPath, gitUrl);
} else {
await Repos.fetch(repoPath);
}
let commitInfos = [];
let tags = await Repos.getTags(repoPath);
for (let tag of tags) {
let commitInfo = await Repos.getCommitInfo(repoPath, tag);
Object.assign(commitInfo, { version: tag, channel: '' });
commitInfos.push(commitInfo);
}
{
let tipInfo = await Repos.getTipInfo(repoPath);
// "2024-01-01T00:00:00-05:00" => "2024-01-01T05:00:00"
let date = new Date(tipInfo.date);
// "2024-01-01T05:00:00" => "v2024.01.01-05.00.00"
let version = date.toISOString();
// strip '.000Z'
version = version.replace(/\.\d+Z/, '');
version = version.replace(/[:\-]/g, '.');
version = version.replace(/T/, '-');
Object.assign(tipInfo, { version: `v${version}`, channel: '' });
if (commitInfos.length > 1) {
tipInfo.channel = 'beta';
}
commitInfos.push(tipInfo);
}
let releases = [];
for (let commitInfo of commitInfos) {
let version = commitInfo.version.replace(/^v/, '');
let date = new Date(commitInfo.date);
let isoDate = date.toISOString();
isoDate = isoDate.replace(/\.\d+Z/, '');
// tags and HEAD qualify for '--branch <branchish>'
let branch = commitInfo.commitish;
let rel = {
name: `${repoName}-v${version}`,
version: version,
git_tag: branch,
git_commit_hash: commitInfo.commit_id,
lts: false,
channel: commitInfo.channel,
date: isoDate,
os: '*',
arch: '*',
ext: 'git',
download: gitUrl,
};
releases.push(rel);
}
all.releases = releases;
return all;
}
module.exports = getDistributables;
if (module === require.main) {
(async function main() {
let testRepos = [
// just a few tags, and a different HEAD
'https://github.com/tpope/vim-commentary.git',
// no tags, just HEAD
'https://github.com/ziglang/zig.vim.git',
// many, many tags
//'https://github.com/dense-analysis/ale.git',
];
for (let url of testRepos) {
let all = await getDistributables(url);
all = require('../_webi/normalize.js')(all);
console.info(JSON.stringify(all, null, 2));
}
})()
.then(function () {
process.exit(0);
})
.catch(function (err) {
console.error(err);
});
}

47
_common/gitea.js Normal file
View File

@@ -0,0 +1,47 @@
'use strict';
var GitHubish = require('./githubish.js');
/**
* Lists Gitea Releases (w/ uploaded assets)
*
* @param {null} _ - deprecated
* @param {String} owner
* @param {String} repo
* @param {String} baseurl
* @param {String} [username]
* @param {String} [token]
*/
async function getDistributables(
_,
owner,
repo,
baseurl,
username = '',
token = '',
) {
baseurl = `${baseurl}/api/v1`;
let all = await GitHubish.getDistributables({
owner,
repo,
baseurl,
username,
token,
});
return all;
}
module.exports = getDistributables;
if (module === require.main) {
getDistributables(
null,
'root',
'pathman',
'https://git.rootprojects.org',
'',
'',
).then(function (all) {
console.info(JSON.stringify(all, null, 2));
});
}

40
_common/github-source.js Normal file
View File

@@ -0,0 +1,40 @@
'use strict';
require('dotenv').config({ path: '.env' });
let GitHubSource = module.exports;
let GitHubishSource = require('./githubish-source.js');
/**
* @param {Object} opts
* @param {String} opts.owner
* @param {String} opts.repo
* @param {String} [opts.baseurl]
* @param {String} [opts.username]
* @param {String} [opts.token]
*/
GitHubSource.getDistributables = async function ({
owner,
repo,
baseurl = 'https://api.github.com',
username = process.env.GITHUB_USERNAME || '',
token = process.env.GITHUB_TOKEN || '',
}) {
let all = await GitHubishSource.getDistributables({
owner,
repo,
baseurl,
username,
token,
});
return all;
};
if (module === require.main) {
GitHubSource.getDistributables(null, 'BeyondCodeBootcamp', 'DuckDNS.sh').then(
function (all) {
console.info(JSON.stringify(all, null, 2));
},
);
}

42
_common/github.js Normal file
View File

@@ -0,0 +1,42 @@
'use strict';
require('dotenv').config({ path: '.env' });
let GitHubish = require('./githubish.js');
/**
* Lists GitHub Releases (w/ uploaded assets)
*
* @param {null} _ - deprecated
* @param {String} owner
* @param {String} repo
* @param {String} [baseurl]
* @param {String} [username]
* @param {String} [token]
*/
module.exports = async function (
_,
owner,
repo,
baseurl = 'https://api.github.com',
username = process.env.GITHUB_USERNAME || '',
token = process.env.GITHUB_TOKEN || '',
) {
let all = await GitHubish.getDistributables({
owner,
repo,
baseurl,
username,
token,
});
return all;
};
let GitHub = module.exports;
GitHub.getDistributables = module.exports;
if (module === require.main) {
GitHub.getDistributables(null, 'BurntSushi', 'ripgrep').then(function (all) {
console.info(JSON.stringify(all, null, 2));
});
}

174
_common/githubish-source.js Normal file
View File

@@ -0,0 +1,174 @@
'use strict';
let Fetcher = require('../_common/fetcher.js');
let GitHubishSource = module.exports;
/**
* Lists GitHub-Like Releases (source tarball & zip)
*
* @param {Object} opts
* @param {String} opts.owner
* @param {String} opts.repo
* @param {String} opts.baseurl
* @param {String} [opts.username]
* @param {String} [opts.token]
*/
GitHubishSource.getDistributables = async function ({
owner,
repo,
baseurl,
username = '',
token = '',
}) {
if (!owner) {
throw new Error('missing owner for repo');
}
if (!repo) {
throw new Error('missing repo name');
}
if (!baseurl) {
throw new Error('missing baseurl');
}
let url = `${baseurl}/repos/${owner}/${repo}/releases`;
let opts = {
headers: {
'Content-Type': 'appplication/json',
},
};
if (token) {
let userpass = `${username}:${token}`;
let basicAuth = btoa(userpass);
Object.assign(opts.headers, {
Authorization: `Basic ${basicAuth}`,
});
}
let resp;
try {
resp = await Fetcher.fetch(url, opts);
} catch (e) {
/** @type {Error & { code: string, response: { status: number, body: string } }} */ //@ts-expect-error
let err = e;
if (err.code === 'E_FETCH_RELEASES') {
err.message = `failed to fetch '${baseurl}' (githubish-source, user '${username}) release data: ${err.response.status} ${err.response.body}`;
}
throw e;
}
let gHubResp = JSON.parse(resp.body);
let all = {
/** @type {Array<BuildInfo>} */
releases: [],
download: '',
};
for (let release of gHubResp) {
let dists = GitHubishSource.releaseToDistributables(release);
for (let dist of dists) {
let updates =
await GitHubishSource.followDistributableDownloadAttachment(dist);
Object.assign(dist, updates);
all.releases.push(dist);
}
}
return all;
};
/**
* @typedef BuildInfo
* @prop {String} [name] - name to use instead of filename for hash urls
* @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} [_musl]
* @prop {Boolean} [lts]
* @prop {String} [size]
* @prop {String} os
*/
/**
* @param {any} ghRelease - TODO
* @returns {Array<BuildInfo>}
*/
GitHubishSource.releaseToDistributables = function (ghRelease) {
let ghTag = ghRelease['tag_name']; // TODO tags aren't always semver / sensical
let lts = /(\b|_)(lts)(\b|_)/.test(ghRelease['tag_name']);
let channel = 'stable';
if (ghRelease['prerelease']) {
channel = 'beta';
}
let date = ghRelease['published_at'] || '';
date = date.replace(/T.*/, '');
let urls = [ghRelease.tarball_url, ghRelease.zipball_url];
/** @type {Array<BuildInfo>} */
let dists = [];
for (let url of urls) {
dists.push({
name: '',
version: ghTag,
lts: lts,
channel: channel,
date: date,
os: '*',
arch: '*',
libc: '',
ext: '',
download: url,
});
}
return dists;
};
/**
* @param {BuildInfo} dist
*/
GitHubishSource.followDistributableDownloadAttachment = async function (dist) {
let abortCtrl = new AbortController();
let resp = await fetch(dist.download, {
method: 'HEAD',
redirect: 'follow',
signal: abortCtrl.signal,
});
let headers = Object.fromEntries(resp.headers);
// Workaround for bug where METHOD changes to GET
abortCtrl.abort();
await resp.text().catch(function (err) {
if (err.name !== 'AbortError') {
throw err;
}
});
// ex: content-disposition: attachment; filename=BeyondCodeBootcamp-DuckDNS.sh-v1.0.1-0-ga2f4bde.zip
// => BeyondCodeBootcamp-DuckDNS.sh-v1.0.1-0-ga2f4bde.zip
let name = headers['content-disposition'].replace(
/.*filename=([^;]+)(;|$)/,
'$1',
);
let download = resp.url;
return { name, download };
};
if (module === require.main) {
GitHubishSource.getDistributables({
owner: 'BeyondCodeBootcamp',
repo: 'DuckDNS.sh',
baseurl: 'https://api.github.com',
}).then(function (all) {
console.info(JSON.stringify(all, null, 2));
});
}

137
_common/githubish.js Normal file
View File

@@ -0,0 +1,137 @@
'use strict';
let Fetcher = require('../_common/fetcher.js');
/**
* @typedef DistributableRaw
* @prop {String} name
* @prop {String} version
* @prop {Boolean} lts
* @prop {String} [channel]
* @prop {String} date
* @prop {String} os
* @prop {String} arch
* @prop {String} ext
* @prop {String} download
*/
let GitHubish = module.exports;
/**
* Lists GitHub-Like Releases (w/ uploaded assets)
*
* @param {Object} opts
* @param {String} opts.owner
* @param {String} opts.repo
* @param {String} opts.baseurl
* @param {String} [opts.username]
* @param {String} [opts.token]
*/
GitHubish.getDistributables = async function ({
owner,
repo,
baseurl,
username = '',
token = '',
}) {
if (!owner) {
throw new Error('missing owner for repo');
}
if (!repo) {
throw new Error('missing repo name');
}
if (!baseurl) {
throw new Error('missing baseurl');
}
let url = `${baseurl}/repos/${owner}/${repo}/releases`;
let opts = {
headers: {
'Content-Type': 'appplication/json',
},
};
if (token) {
let userpass = `${username}:${token}`;
let basicAuth = btoa(userpass);
Object.assign(opts.headers, {
Authorization: `Basic ${basicAuth}`,
});
}
let resp;
try {
resp = await Fetcher.fetch(url, opts);
} catch (e) {
/** @type {Error & { code: string, response: { status: number, body: string } }} */ //@ts-expect-error
let err = e;
if (err.code === 'E_FETCH_RELEASES') {
err.message = `failed to fetch '${baseurl}' (githubish, user '${username}) release data: ${err.response.status} ${err.response.body}`;
}
throw e;
}
let gHubResp = JSON.parse(resp.body);
let all = {
/** @type {Array<DistributableRaw>} */
releases: [],
// todo make this ':baseurl' + ':releasename'
download: '',
};
try {
gHubResp.forEach(transformReleases);
} catch (e) {
/** @type {Error & { code: string, response: { status: number, body: string } }} */ //@ts-expect-error
let err = e;
console.error(err.message);
console.error('Error Headers:', resp.headers);
console.error('Error Body:', resp.body);
let msg = `failed to transform releases from '${baseurl}' with user '${username}'`;
throw new Error(msg);
}
/**
* @param {any} release - TODO
*/
function transformReleases(release) {
for (let asset of release['assets']) {
let name = asset['name'];
let date = release['published_at']?.replace(/T.*/, '');
let download = asset['browser_download_url'];
// TODO tags aren't always semver / sensical
let version = release['tag_name'];
let channel;
if (release['prerelease']) {
// -rcX, -preview, -beta, etc will be checked in _webi/normalize.js
channel = 'beta';
}
let lts = /(\b|_)(lts)(\b|_)/.test(release['tag_name']);
all.releases.push({
name: name,
version: version,
lts: lts,
channel: channel,
date: date,
os: '', // will be guessed by download filename
arch: '', // will be guessed by download filename
ext: '', // will be normalized
download: download,
});
}
}
return all;
};
if (module === require.main) {
GitHubish.getDistributables({
owner: 'BurntSushi',
repo: 'ripgrep',
baseurl: 'https://api.github.com',
}).then(function (all) {
console.info(JSON.stringify(all, null, 2));
});
}

37
_example/releases.js Normal file
View File

@@ -0,0 +1,37 @@
'use strict';
var github = require('../_common/github.js');
var owner = 'BurntSushi';
var repo = 'ripgrep';
/******************************************************************************/
/** Note: Delete this Comment! **/
/** **/
/** Need a an example that filters out miscellaneous release files? **/
/** See `deno`, `gitea`, or `caddy` **/
/** **/
/******************************************************************************/
let Releases = module.exports;
Releases.latest = async function () {
let all = await github(null, owner, repo);
return all;
};
Releases.sample = async function () {
let normalize = require('../_webi/normalize.js');
let all = await Releases.latest();
all = normalize(all);
// just select the first 5 for demonstration
all.releases = all.releases.slice(0, 5);
return all;
};
if (module === require.main) {
(async function () {
let samples = await Releases.sample();
console.info(JSON.stringify(samples, null, 2));
})();
}

View File

@@ -13,6 +13,7 @@ async function main() {
caches: CACHE_DIR,
installers: INSTALLERS_DIR,
});
bc.freshenRandomPackage(600 * 1000);
// let dirs = await bc.getProjectsByType();
// let projNames = Object.keys(dirs.valid);

View File

@@ -3,16 +3,13 @@
var BuildsCacher = module.exports;
let Fs = require('node:fs/promises');
let Os = require('node:os');
let Path = require('node:path');
let LEGACY_CACHE_DIR = Path.join(Os.homedir(), '.cache/webi/legacy');
let HostTargets = require('./build-classifier/host-targets.js');
let Lexver = require('./build-classifier/lexver.js');
let Triplet = require('./build-classifier/triplet.js');
var ALIAS_RE = /^alias: ([\w.-]+)$/m;
var ALIAS_RE = /^alias: (\w+)$/m;
var LEGACY_ARCH_MAP = {
'*': 'ANYARCH',
@@ -129,8 +126,61 @@ async function readFirstBytes(path) {
return str;
}
BuildsCacher.create = function ({ ALL_TERMS, installers }) {
let promises = {};
async function getLatestBuilds(Releases, installersDir, cacheDir, name, date) {
console.info(`[INFO] getLatestBuilds: ${name}`);
if (!Releases) {
Releases = require(`${installersDir}/${name}/releases.js`);
}
// TODO update all releases files with module.exports.xxxx = 'foo';
if (!Releases.latest) {
Releases.latest = Releases;
}
let id = `${cacheDir}/${name}`;
if (!promises[id]) {
promises[id] = Promise.resolve();
}
promises[id] = promises[id].then(async function () {
return await getLatestBuildsInner(Releases, cacheDir, name, date);
});
return await promises[id];
}
async function getLatestBuildsInner(Releases, cacheDir, name, date) {
let data = await Releases.latest();
if (!date) {
date = new Date();
}
let isoDate = date.toISOString();
let yearMonth = isoDate.slice(0, 7);
// TODO hash file
let dataFile = `${cacheDir}/${yearMonth}/${name}.json`;
// TODO fsstat releases.js vs require-ing time as well
let tsFile = `${cacheDir}/${yearMonth}/${name}.updated.txt`;
let dirPath = Path.dirname(dataFile);
await Fs.mkdir(dirPath, { recursive: true });
let json = JSON.stringify(data, null, 2);
await Fs.writeFile(dataFile, json, 'utf8');
let seconds = date.valueOf();
let ms = seconds / 1000;
let msStr = ms.toFixed(3);
await Fs.writeFile(tsFile, msStr, 'utf8');
return data;
}
BuildsCacher.create = function ({ ALL_TERMS, installers, caches }) {
let installersDir = installers;
let cacheDir = caches;
if (!ALL_TERMS) {
ALL_TERMS = Triplet.TERMS_PRIMARY_MAP;
@@ -145,11 +195,9 @@ BuildsCacher.create = function ({ ALL_TERMS, installers }) {
bc._triplets = {};
bc._targetsByBuildIdCache = {};
bc._caches = {};
bc._staleAge = 15 * 60 * 1000;
bc._allFormats = {};
bc._allTriplets = {};
// Per-name lock: serializes cold-cache getPackages so concurrent
// callers can't corrupt bc._caches[name] via a transformAndUpdate race.
bc._inflight = {};
for (let term of TERMS_META) {
delete bc.orphanTerms[term];
@@ -168,6 +216,19 @@ BuildsCacher.create = function ({ ALL_TERMS, installers }) {
let entries = await Fs.readdir(installersDir, { withFileTypes: true });
for (let entry of entries) {
let meta = await bc.getProjectTypeByEntry(entry);
if (meta.type === 'not_found') {
let err = meta.detail;
console.error('');
console.error('PROBLEM');
console.error(` ${err.message}`);
console.error('');
console.error('SOLUTION');
console.error(' npm clean-install');
console.error('');
throw new Error(
'[SANITY FAIL] should never have missing modules in prod',
);
}
dirs[meta.type][entry.name] = meta.detail;
}
@@ -236,16 +297,19 @@ BuildsCacher.create = function ({ ALL_TERMS, installers }) {
return { type: 'alias', detail: link };
}
let cacheFile = `${LEGACY_CACHE_DIR}/${entry.name}.json`;
let hasCacheFile = await Fs.access(cacheFile)
.then(function () {
return true;
})
.catch(function () {
return false;
});
if (!hasCacheFile) {
return { type: 'selfhosted', detail: true };
let releasesPath = Path.join(path, 'releases.js');
try {
void require(releasesPath);
} catch (err) {
if (err.code !== 'MODULE_NOT_FOUND') {
return { type: 'errors', detail: err };
}
if (err.message.includes(`Cannot find module '${releasesPath}'`)) {
return { type: 'selfhosted', detail: true };
}
return { type: 'not_found', detail: err };
}
return { type: 'valid', detail: true };
@@ -253,26 +317,14 @@ BuildsCacher.create = function ({ ALL_TERMS, installers }) {
// Typically a package is organized by release (ex: go has 1.20, 1.21, etc),
// but we will organize by the build (ex: go1.20-darwin-arm64.tar.gz, etc).
bc.getPackages = async function (args) {
let name = args.name;
let warm = bc._caches[name];
if (warm) {
return _doGetPackages(args);
bc.getPackages = async function ({ Releases, name, date }) {
if (!date) {
date = new Date();
}
let inflight = bc._inflight[name];
if (inflight) {
return inflight;
}
let p = _doGetPackages(args).finally(function () {
delete bc._inflight[name];
});
bc._inflight[name] = p;
return p;
};
async function _doGetPackages({ name }) {
let dataFile = `${LEGACY_CACHE_DIR}/${name}.json`;
let tsFile = `${LEGACY_CACHE_DIR}/${name}.updated.txt`;
let isoDate = date.toISOString();
let yearMonth = isoDate.slice(0, 7);
let dataFile = `${cacheDir}/${yearMonth}/${name}.json`;
let tsFile = `${cacheDir}/${yearMonth}/${name}.updated.txt`;
let tsDate;
{
@@ -326,7 +378,7 @@ BuildsCacher.create = function ({ ALL_TERMS, installers }) {
}
}
if (!projInfo) {
return meta;
projInfo = await getLatestBuilds(Releases, installersDir, cacheDir, name);
}
let latestProjInfo = await BuildsCacher.transformAndUpdate(
name,
@@ -337,8 +389,63 @@ BuildsCacher.create = function ({ ALL_TERMS, installers }) {
);
bc._caches[name] = latestProjInfo;
return latestProjInfo;
}
process.nextTick(async function () {
let now = date.valueOf();
let age = now - projInfo.updated;
let fresh = age < bc._staleAge;
if (fresh) {
return;
}
projInfo = await getLatestBuilds(Releases, installersDir, cacheDir, name);
let latestProjInfo = BuildsCacher.transformAndUpdate(
name,
projInfo,
meta,
date,
bc,
);
bc._caches[name] = latestProjInfo;
});
return projInfo;
};
// Makes sure that packages are updated once an hour, on average
bc._staleNames = [];
bc._freshenTimeout = null;
bc.freshenRandomPackage = async function (minDelay) {
if (!minDelay) {
minDelay = 15 * 1000;
}
if (bc._staleNames.length === 0) {
let dirs = await bc.getProjectsByType();
bc._staleNames = Object.keys(dirs.valid);
bc._staleNames.sort(function () {
return 0.5 - Math.random();
});
}
let name = bc._staleNames.pop();
void (await bc.getPackages({
//Releases: Releases,
name: name,
date: new Date(),
}));
console.info(`[INFO] freshenRandomPackage: ${name}`);
let hour = 60 * 60 * 1000;
let delay = minDelay;
let spread = hour / bc._staleNames.length;
let seed = Math.random();
delay += seed * spread;
clearTimeout(bc._freshenTimeout);
bc._freshenTimeout = setTimeout(bc.freshenRandomPackage, delay);
bc._freshenTimeout.unref();
};
/**
* Given a list of acceptable formats, get the sorted list of of formats.
@@ -430,10 +537,7 @@ BuildsCacher.create = function ({ ALL_TERMS, installers }) {
exts.push('.gz');
exts.push('.sh');
}
let hasGit = formats.includes('git') || formats.includes('.git');
if (hasGit) {
exts.push('.git');
}
exts.push('.git');
// Fallbacks
// (we include everything to bubble an extract error over not found)
@@ -542,19 +646,29 @@ BuildsCacher.create = function ({ ALL_TERMS, installers }) {
return null;
}
// Version-first iteration, not triplet-first: take the newest
// version even when its only build lives in a fallback triplet
// (e.g. serviceman v1.0.1 only exists at posix_2017-ANYARCH-none).
for (let lexver of projInfo.lexvers) {
let ver = projInfo.lexversMap[lexver] || lexver;
for (let _triplet of triplets) {
let targetReleases = projInfo.releasesByTriplet[_triplet];
if (!targetReleases) {
continue;
}
for (let _triplet of triplets) {
let targetReleases = projInfo.releasesByTriplet[_triplet];
if (!targetReleases) {
continue;
}
let versions = Object.keys(targetReleases);
//console.log('dbg: targetRelease versions', versions);
let lexvers = [];
for (let version of versions) {
let lexPrefix = Lexver.parseVersion(version);
lexvers.push(lexPrefix);
}
lexvers.sort();
lexvers.reverse();
// TODO get the other matchInfo props
// Make sure that these releases are the expected version
// (ex: jq1.7 => darwin-arm64-libc, jq1.6 => darwin-x86_64-libc)
for (let matchver of lexvers) {
let ver = projInfo.lexversMap[matchver] || matchver;
let packages = targetReleases[ver];
//console.log('dbg: packages', packages);
if (!packages) {
continue;
}
@@ -605,32 +719,22 @@ BuildsCacher.create = function ({ ALL_TERMS, installers }) {
return triplets;
}
// Prefer platform-specific matches over ANYOS/ANYARCH fallbacks.
// This ensures e.g. darwin-aarch64-none matches before
// ANYOS-ANYARCH-none (.git source URLs from old releases).
let oses = [];
if (hostTarget.os === 'windows') {
oses = ['windows', 'ANYOS'];
oses = ['ANYOS', 'windows'];
} else if (hostTarget.os === 'android') {
oses = ['android', 'linux', 'posix_2017', 'posix_2024', 'ANYOS'];
oses = ['ANYOS', 'posix_2017', 'posix_2024', 'android', 'linux'];
} else {
oses = [hostTarget.os, 'posix_2017', 'posix_2024', 'ANYOS'];
oses = ['ANYOS', 'posix_2017', 'posix_2024', hostTarget.os];
}
let waterfall = HostTargets.WATERFALL[hostTarget.os] || {};
let arches = waterfall[hostTarget.arch] ||
HostTargets.WATERFALL.ANYOS[hostTarget.arch] || [hostTarget.arch];
arches = arches.concat(['ANYARCH']);
arches = ['ANYARCH'].concat(arches);
let libcs = waterfall[hostTarget.libc] ||
HostTargets.WATERFALL.ANYOS[hostTarget.libc] || [hostTarget.libc];
// Extend the glibc-host waterfall: the table only lists [none, libc]
// but Rust projects (bat, rg) and node ship libc='gnu' builds, and
// static musl builds also run on glibc hosts.
if (hostTarget.libc === 'libc' && !libcs.includes('gnu')) {
libcs = ['none', 'gnu', 'musl', 'libc'];
}
for (let os of oses) {
for (let arch of arches) {
for (let libc of libcs) {
@@ -658,17 +762,10 @@ BuildsCacher.create = function ({ ALL_TERMS, installers }) {
};
BuildsCacher._classify = function (bc, projInfo, build) {
/* jshint maxcomplexity: 30 */
// Cache entries arrive pre-classified (os/arch/libc/ext set). Skip
// maybeInstallable for those — it false-rejects names ending in a
// version tag (`serviceman-v1.0.1`, `v1.0.1.zip`).
let cacheClassified =
build.os && build.arch && build.libc && build.ext;
if (!cacheClassified) {
let maybeInstallable = Triplet.maybeInstallable(projInfo, build);
if (!maybeInstallable) {
return null;
}
/* jshint maxcomplexity: 25 */
let maybeInstallable = Triplet.maybeInstallable(projInfo, build);
if (!maybeInstallable) {
return null;
}
if (LEGACY_OS_MAP[build.os]) {
@@ -732,26 +829,16 @@ BuildsCacher._classify = function (bc, projInfo, build) {
bc.unknownTerms[term] = true;
}
// Skip termsToTarget for cache-classified entries: it false-flags
// e.g. .git URLs as os=ANYOS while the cache says os=posix_2017,
// and the mismatch check throws.
// {NAME}.windows.x86_64v2.musl.exe
// windows-x86_64_v2-musl
target = { triplet: '' };
if (cacheClassified) {
target.os = build.os;
target.arch = build.arch;
target.libc = build.libc;
target.vendor = build.vendor || 'unknown';
target.android = false;
target.unknownTerms = [];
} else {
try {
void Triplet.termsToTarget(target, projInfo, build, terms);
} catch (e) {
console.error(`PACKAGE FORMAT CHANGE for '${projInfo.name}':`);
console.error(e.message);
console.error(build);
return null;
}
try {
void Triplet.termsToTarget(target, projInfo, build, terms);
} catch (e) {
console.error(`PACKAGE FORMAT CHANGE for '${projInfo.name}':`);
console.error(e.message);
console.error(build);
return null;
}
target.triplet = `${target.arch}-${target.vendor}-${target.os}-${target.libc}`;

View File

@@ -15,8 +15,11 @@ let bc = BuildsCacher.create({
caches: CACHE_DIR,
installers: INSTALLERS_DIR,
});
bc.freshenRandomPackage(600 * 1000);
Builds.init = async function () {
bc.freshenRandomPackage(600 * 1000);
let dirs = await bc.getProjectsByType();
let projNames = Object.keys(dirs.valid);
@@ -24,6 +27,7 @@ Builds.init = async function () {
await Parallel.run(parallel, projNames, getAll);
async function getAll(name) {
void (await bc.getPackages({
//Releases: Releases,
name: name,
date: new Date(),
}));

View File

@@ -1,14 +1,11 @@
'use strict';
let Fs = require('node:fs/promises');
let Os = require('node:os');
let Path = require('node:path');
// let Builds = require('./builds.js');
let BuildsCacher = require('./builds-cacher.js');
let Triplet = require('./build-classifier/triplet.js');
let LEGACY_CACHE_DIR = Path.join(Os.homedir(), '.cache/webi/legacy');
async function main() {
let projName = process.argv[2];
if (!projName) {
@@ -39,11 +36,16 @@ async function main() {
arches: [],
libcs: [],
formats: [],
// TODO channels: [],
};
let dataFile = Path.join(LEGACY_CACHE_DIR, `${projName}.json`);
let json = await Fs.readFile(dataFile, 'utf8');
let projInfo = JSON.parse(json);
let installersDir = Path.join(__dirname, '..');
let Releases = require(`${installersDir}/${projName}/releases.js`);
if (!Releases.latest) {
Releases.latest = Releases;
}
let projInfo = await Releases.latest();
// let packages = await Builds.getPackage({ name: projName });
// console.log(packages);
@@ -68,11 +70,9 @@ async function main() {
console.log(`[DEBUG] transformed`);
let sample = transformed.packages.slice(0, 20);
console.log('packages:', sample, ':packages');
let firstTriplet = Object.keys(transformed.releasesByTriplet)[0];
let firstVersion = transformed.versions[0];
console.log(
`releasesByTriplet[${firstTriplet}][${firstVersion}]:`,
transformed.releasesByTriplet[firstTriplet]?.[firstVersion],
'releasesByTriplet:',
transformed.releasesByTriplet['linux-x86_64-none'][transformed.versions[0]],
':releasesByTriplet',
);
console.log('versions:', transformed.versions, ':versions');

View File

@@ -7,6 +7,11 @@
New-Item -Path "$Env:USERPROFILE\Downloads\webi" -ItemType Directory -Force | Out-Null
New-Item -Path "$Env:USERPROFILE\.local\bin" -ItemType Directory -Force | Out-Null
if ($null -eq $Env:WEBI_HOST -or $Env:WEBI_HOST -eq "") { $Env:WEBI_HOST = "https://webinstall.dev" }
curl.exe -s -A "windows" "$Env:WEBI_HOST/packages/webi/webi-pwsh.ps1" -o "$Env:USERPROFILE\.local\bin\webi-pwsh.ps1"
$b_webi_ps1 = "$Env:USERPROFILE\.local\bin\webi-pwsh.ps1"
curl.exe -s -A "windows" "$Env:WEBI_HOST/packages/webi/webi-pwsh.ps1" | Out-File -Encoding utf8 "$b_webi_ps1"
if ($LASTEXITCODE -ne 0 -or -not (Test-Path "$b_webi_ps1") -or (Get-Item "$b_webi_ps1").Length -lt 100) {
Write-Error "error: failed to download '$Env:WEBI_HOST/packages/webi/webi-pwsh.ps1'"
exit 1
}
Set-ExecutionPolicy -Scope Process Bypass
& "$Env:USERPROFILE\.local\bin\webi-pwsh.ps1" "{{ exename }}"
& "$b_webi_ps1" "{{ exename }}"

View File

@@ -139,6 +139,8 @@ async function main() {
console.info('');
}
bc.freshenRandomPackage(600 * 1000);
let rows = [];
let triples = [];
let valids = Object.keys(dirs.valid);

259
_webi/normalize.js Normal file
View File

@@ -0,0 +1,259 @@
'use strict';
// this may need customizations between packages
var osMap = {
macos: /(\b|_)(apple|os(\s_-)?x\b|mac|darwin|iPhone|iOS|iPad)/i,
linux: /(\b|_)(linux)/i,
freebsd: /(\b|_)(freebsd)/i,
windows: /(\b|_)(win|microsoft|msft)/i,
sunos: /(\b|_)(sun)/i,
aix: /(\b|_)(aix)/i,
};
var maps = {
oses: {},
arches: {},
libcs: {},
formats: {},
};
Object.keys(osMap).forEach(function (name) {
maps.oses[name] = true;
});
var formats = ['zip', 'xz', 'tar', 'pkg', 'msi', 'git', 'exe', 'dmg', 'git'];
formats.forEach(function (name) {
maps.formats[name] = true;
});
// evaluation order matters
// (i.e. otherwise x86 and x64 can cross match)
var arches = [
// arm64/aarch64 has very high specificity, so it comes first
'arm64',
// arm 7 is also generic aarch/arm/arm32
'armv7l',
// arm6 can run on armv7
'armv6l',
// amd64 is more likely and less often specified than arm64
'amd64',
'x86',
'ppc64le',
'ppc64',
's390x',
];
// Used for detecting system arch from package download url, for example:
//
// https://git.com/org/foo/releases/v0.7.9/foo-aarch64-linux-musl.tar.gz
// https://git.com/org/foo/releases/v0.7.9/foo-arm-linux-musleabihf.tar.gz
// https://git.com/org/foo/releases/v0.7.9/foo-armv7-linux-musleabihf.tar.gz
// https://git.com/org/foo/releases/v0.7.9/foo-x86_64-linux-musl.tar.gz
//
var archMap = {
arm64: /(\b|_)(aarch64|arm64)/i,
armv7l: /(\b|_)(arm32|arm[_\-]?v?7l?)/i,
armv6l: /(\b|_)(arm|aarch32|arm[_\-]?v?6l?)(\b|_)/i,
//amd64: /(amd.?64|x64|[_\-]64)/i,
amd64:
/(\b|_|amd|(dar)?win(dows)?|mac(os)?|linux|osx|x)64([_\-]?bit)?(\b|_)/i,
//x86: /(86)(\b|_)/i,
x86: /(\b|_|amd|(dar)?win(dows)?|mac(os)?|linux|osx|x)(86|32|i?386)([_\-]?bit)?(\b|_)/i,
ppc64le: /(\b|_)(ppc64le)/i,
ppc64: /(\b|_)(ppc64)(\b|_)/i,
s390x: /(\b|_)(s390x)/i,
};
arches.forEach(function (name) {
maps.arches[name] = true;
});
var libcs = ['none', 'musl', 'gnu', 'msvc', 'libc'];
libcs.forEach(function (name) {
maps.libcs[name] = true;
});
function normalize(all) {
/* jshint maxcomplexity:50 */
/* jshint maxdepth:10 */
var supported = {
oses: {},
arches: {},
libcs: {},
formats: {},
};
for (let rel of all.releases) {
rel.version = rel.version.replace(/^v/i, '');
if (!rel.name) {
rel.name = rel.download.replace(/.*\//, '');
}
if (!rel.os) {
rel.os = 'unknown';
let osNames = Object.keys(osMap);
for (let osName of osNames) {
let relName = rel.name || rel.download;
let osRegExp = osMap[osName];
let matches = osRegExp.test(relName);
if (matches) {
rel.os = osName;
break;
}
}
}
supported.oses[rel.os] = true;
if (!rel.arch) {
for (let arch of arches) {
let name = rel.name || rel.download;
let isArch = name.match(archMap[arch]);
if (isArch) {
rel.arch = arch;
break;
}
}
}
if (!rel.arch) {
if ('macos' === rel.os) {
rel.arch = 'amd64';
}
}
supported.arches[rel.arch] = true;
// note: depends on rel.os
if (!rel.libc) {
let isMusl;
let isMsvc;
let isStatic;
let isGnu;
// extra blocks to prevent copy pasta errors
{
let muslRe = /(\b|_)(musl)(\b|_)/i;
isMusl = muslRe.test(rel.download) || muslRe.test(rel.name);
}
{
let msvcRe = /(\b|_)(msvc)(\b|_)/i;
isMsvc = msvcRe.test(rel.download) || msvcRe.test(rel.name);
}
{
let staticRe = /(\b|_)(static)(\b|_)/i;
isStatic = staticRe.test(rel.download) || staticRe.test(rel.name);
}
{
let gnuRe = /(\b|_)(gnu|glibc|libc)(\b|_)/i;
isGnu = gnuRe.test(rel.download) || gnuRe.test(rel.name);
}
if (isMusl) {
// we specifically tag things that need musl++ in their own releases
rel.libc = 'none';
} else if (isStatic) {
rel.libc = 'none';
} else if (isGnu) {
rel.libc = 'gnu';
if (rel.os === 'windows') {
// windows gnu is static
rel.libc = 'none';
} else if (rel.os === 'darwin') {
// if glibc is required on macos, it'll be static
rel.libc = 'none';
}
} else if (isMsvc) {
rel.libc = 'msvc';
} else {
// The default is no requirement for any particular libc
// (Go, Zig, POSIX Shell, JS, etc)
// and hopefully we never have to worry about mingw and friends
rel.libc = 'none';
}
}
supported.libcs[rel.libc] = true;
var tarExt;
if (!rel.ext) {
// pkg-v1.0.tar.gz => ['gz', 'tar', '0', 'pkg-v1']
// pkg-v1.0.tar => ['tar', '0' ,'pkg-v1']
// pkg-v1.0.zip => ['zip', '0', 'pkg-v1']
var exts = (rel.name || rel.download).split('.');
if (1 === exts.length) {
// for bare releases in the format of foo-linux-amd64
rel.ext = 'exe';
}
exts = exts.reverse().slice(0, 2);
if ('tar' === exts[1]) {
rel.ext = exts.reverse().join('.');
tarExt = 'tar';
} else if ('tgz' === exts[0]) {
rel.ext = 'tar.gz';
tarExt = 'tar';
} else {
rel.ext = exts[0];
}
if (/\-|linux|mac|os[_\-]?x|arm|amd|86|64|mip/i.test(rel.ext)) {
// for bare releases in the format of foo.linux-amd64
rel.ext = 'exe';
}
}
supported.formats[tarExt || rel.ext] = true;
if (!rel.channel) {
// basically like this: (+.-_)(beta|rc)(0-9)(+.-_)
// matches:
// - v1.0-beta
// - v1.0-beta1.1
// - v1.0-beta-11
// won't match:
// - v1.0beta
// - v1.0-beta1b
let isBetaRe =
/(\b|_)(alpha|beta|dev|developer|prev|preview|rc)(\d+)(\b|_)/;
let isBeta = isBetaRe.test(rel.name);
if (isBeta) {
rel.channel = 'beta';
} else {
rel.channel = 'stable';
}
}
if (all.download) {
rel.download = all.download.replace(/{{ download }}/, rel.download);
}
}
all.oses = Object.keys(supported.oses).filter(function (name) {
return maps.oses[name];
});
all.arches = Object.keys(supported.arches).filter(function (name) {
return maps.arches[name];
});
all.libcs = Object.keys(supported.libcs).filter(function (name) {
return maps.libcs[name];
});
all.formats = Object.keys(supported.formats).filter(function (name) {
return maps.formats[name];
});
return all;
}
module.exports = normalize;
module.exports._debug = function (all) {
all = normalize(all);
all.releases = all.releases
.filter(function (r) {
return ['windows', 'macos', 'linux'].includes(r.os) && 'amd64' === r.arch;
})
.slice(0, 10);
return all;
};
// NOT in order of priority (which would be tar, xz, zip, ...)
module.exports.formats = formats;
module.exports.arches = arches;
module.exports.libcs = libcs;
module.exports.formatsMap = maps.formats;

View File

@@ -49,7 +49,7 @@ var baseurl = 'https://webinstall.dev';
var maxLen = 0;
console.info('');
console.info('Has the necessary files?');
['README.md', 'install.sh', 'install.ps1']
['README.md', 'releases.js', 'install.sh', 'install.ps1']
.map(function (node) {
maxLen = Math.max(maxLen, node.length);
return node;

View File

@@ -2,30 +2,75 @@
var Releases = module.exports;
var Fs = require('node:fs/promises');
var Os = require('node:os');
var path = require('path');
var _normalize = require('./normalize.js');
var cache = {};
//var staleAge = 5 * 1000;
//var expiredAge = 15 * 1000;
var staleAge = 5 * 60 * 1000;
var expiredAge = 15 * 60 * 1000;
var LEGACY_CACHE_DIR = path.join(Os.homedir(), '.cache/webi/legacy');
let installerDir = path.join(__dirname, '..');
// Sort releases by ext preference and libc within the same version.
// The cache is already sorted by version (stable before beta, newest first),
// so we only re-order within the same version string.
Releases.get = async function (pkgdir) {
let get;
try {
get = require(`${pkgdir}/releases.js`);
// TODO update all releases files with module.exports.xxxx = 'foo';
if (!get.latest) {
get.latest = get;
}
} catch (e) {
let err = new Error('no releases.js for', pkgdir.split(/[\/\\]+/).pop());
err.code = 'E_NO_RELEASE';
throw err;
}
let all = await get.latest();
return _normalize(all);
};
// TODO needs a proper test, and more accurate (though perhaps far less simple) code
function createFormatsSorter(formats) {
return function sortByExtLibc(a, b) {
if (a.version !== b.version) {
// Array.sort is stable (V8, ES2019), so returning 0 across
// versions preserves the cache's pre-sorted version-desc order.
return 0;
return function sortByVerExt(a, b) {
function lexver(semver) {
// v1.20.156 => 00001.00020.00156.zzzzz
// TODO BUG: v1.20.156-rc2 => 00001.00020.00156.rc2zz
var parts = semver.split(/[+\.\-]/g);
while (parts.length < 4) {
parts.push('');
}
return parts
.map(function (num, i) {
if (3 === i) {
return num.toString().padEnd(10, 'z');
}
return num.toString().padStart(10, '0');
})
.join('.');
}
var aver = lexver(a.version);
var bver = lexver(b.version);
if (aver > bver) {
//console.log(aver, '>', bver);
return -1;
}
if (aver < bver) {
//console.log(aver, '<', bver);
return 1;
}
var aExtPri = formats.indexOf(a.ext.replace(/tar\..*/, 'tar'));
var bExtPri = formats.indexOf(b.ext.replace(/tar\..*/, 'tar'));
if (aExtPri > bExtPri) {
//console.log(a.ext, aExtPri, '>', b.ext, bExtPri);
return -1;
}
if (aExtPri < bExtPri) {
//console.log(a.ext, aExtPri, '<', b.ext, bExtPri);
return 1;
}
@@ -42,39 +87,99 @@ function createFormatsSorter(formats) {
}
async function getCachedReleases(pkg) {
// returns { download: '', releases: [{ version, date, os, arch, lts, channel, download}] }
// returns { download: '<template string>', releases: [{ version, date, os, arch, lts, channel, download}] }
if (cache[pkg]) {
return cache[pkg];
async function chainCachePromise(fn) {
cache[pkg].promise = cache[pkg].promise.then(fn);
return cache[pkg].promise;
}
let dataFile = `${LEGACY_CACHE_DIR}/${pkg}.json`;
async function sleep(ms) {
return await new Promise(function (resolve, reject) {
setTimeout(resolve, ms);
});
}
let json = await Fs.readFile(dataFile, 'utf8').catch(function (err) {
if (err.code === 'ENOENT') {
return null;
async function putCache() {
var age = Date.now() - cache[pkg].updatedAt;
if (age < staleAge) {
//console.debug('NOT STALE ANYMORE - updated in previous promise');
return cache[pkg].all;
}
throw err;
});
if (!json) {
let empty = { download: '', releases: [] };
cache[pkg] = empty;
return empty;
//console.debug('DOWNLOADING NEW "%s" releases', pkg);
var pkgdir = path.join(installerDir, pkg);
// workaround for request timeout seeming to not work
let complete = false;
await Promise.race([
Releases.get(pkgdir)
.catch(function (err) {
if ('E_NO_RELEASE' === err.code) {
let all = { _error: 'E_NO_RELEASE', download: '', releases: [] };
return all;
}
throw err;
})
.catch(function (err) {
let hasReleases = cache[pkg].all?.releases?.length > 1;
if (!hasReleases) {
throw err;
}
console.error(`Error: the BOOGEYMAN got us!`);
console.error(err.stack);
return cache[pkg].all;
})
.then(function (all) {
// Note: it is possible for slightly older data
// to replace slightly newer data, but this is better
// than being in a cycle where release updates _always_
// take longer than expected.
//console.debug('DOWNLOADED NEW "%s" releases', pkg);
cache[pkg].updatedAt = Date.now();
cache[pkg].all = all;
complete = true;
}),
sleep(15000).then(function () {
if (complete) {
return;
}
console.error(`request timeout waiting for '${pkg}' release info`);
}),
]);
return cache[pkg].all;
}
let all;
try {
all = JSON.parse(json);
} catch (e) {
console.error(`error: ${dataFile}:\n\t${e.message}`);
let empty = { download: '', releases: [] };
cache[pkg] = empty;
return empty;
if (!cache[pkg]) {
cache[pkg] = {
updatedAt: 0,
all: { download: '', releases: [] },
promise: Promise.resolve(),
};
}
cache[pkg] = all;
return all;
var bgRenewal;
var age = Date.now() - cache[pkg].updatedAt;
var fresh = age < staleAge;
if (!fresh) {
bgRenewal = chainCachePromise(putCache);
}
var tooStale = age > expiredAge;
if (!tooStale) {
return await cache[pkg].all;
}
return await Promise.race([
bgRenewal,
sleep(5000).then(function () {
return cache[pkg].all;
}),
]);
}
async function filterReleases(
@@ -85,22 +190,15 @@ async function filterReleases(
// sort the most compatible format first
// (i.e. so that we don't do .pkg on linux except on purpose)
var rformats = formats.slice(0).reverse();
var sortByExtLibc = createFormatsSorter(rformats);
var sortByVerExt = createFormatsSorter(rformats);
var reVer = new RegExp('^' + ver + '\\b');
function selectMatches(rel) {
/* jshint maxcomplexity: 25 */
if (os) {
// '*' = any OS (matches anything, including windows).
// 'posix' / 'posix_20xx' = any POSIX OS (matches linux, macos,
// freebsd, etc., but NOT windows).
let isPosix = rel.os === 'posix' || rel.os.startsWith('posix_20');
let osMatches =
rel.os === '*' ||
rel.os === os ||
(isPosix && os !== 'windows');
if (!osMatches) {
return false;
if (rel.os !== '*') {
if (rel.os !== os) {
return false;
}
}
}
@@ -112,10 +210,7 @@ async function filterReleases(
}
}
// libc='libc' is serve-releases.js's default when the caller
// didn't pin one — treat it as 'no preference', not a filter.
let isMeaningfulLibc = libc && libc !== 'libc' && rel.libc !== 'none';
if (isMeaningfulLibc) {
if (rel.libc !== 'none') {
let releaseRequiresMusl = rel.libc === 'musl';
// goal: handle non-glibc (Alpine / Docker / musl)
let osHasMusl = libc === 'musl';
@@ -162,7 +257,7 @@ async function filterReleases(
return true;
}
var sortedRels = all.releases.filter(selectMatches).sort(sortByExtLibc);
var sortedRels = all.releases.filter(selectMatches).sort(sortByVerExt);
//console.log(sortedRels.slice(0, 4));
return sortedRels.slice(0, limit || 1000);
@@ -321,8 +416,8 @@ Releases.getReleases = function ({
};
if (require.main === module) {
return Releases
.getReleases({
return module
.exports({
pkg: 'node',
ver: '',
os: 'macos',

22
aliasman/releases.js Normal file
View File

@@ -0,0 +1,22 @@
'use strict';
let Releases = module.exports;
let GitHubSource = require('../_common/github-source.js');
let owner = 'BeyondCodeBootcamp';
let repo = 'aliasman';
Releases.latest = async function () {
let all = await GitHubSource.getDistributables({ owner, repo });
for (let pkg of all.releases) {
pkg.os = 'posix_2017';
}
return all;
};
if (module === require.main) {
Releases.latest().then(function (all) {
all = require('../_webi/normalize.js')(all);
console.info(JSON.stringify(all, null, 2));
});
}

21
arc/releases.js Normal file
View File

@@ -0,0 +1,21 @@
'use strict';
var github = require('../_common/github.js');
var owner = 'mholt';
var repo = 'archiver';
module.exports = function () {
return github(null, owner, repo).then(function (all) {
all._names = ['archiver', 'arc'];
return all;
});
};
if (module === require.main) {
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);
console.info(JSON.stringify(all, null, 2));
});
}

79
atomicparsley/releases.js Normal file
View File

@@ -0,0 +1,79 @@
'use strict';
var github = require('../_common/github.js');
var owner = 'wez';
var repo = 'atomicparsley';
let targets = {
x86win: {
os: 'windows',
arch: 'x86',
libc: 'msvc',
},
x64win: {
os: 'windows',
arch: 'amd64',
// https://github.com/wez/atomicparsley/issues/6#issuecomment-1364523028
libc: 'msvc',
},
x64mac: {
os: 'macos',
arch: 'amd64',
},
x64lin: {
os: 'linux',
arch: 'amd64',
libc: 'gnu',
},
x64musl: {
os: 'linux',
arch: 'amd64',
libc: 'musl',
},
};
module.exports = function () {
return github(null, owner, repo).then(function (all) {
for (let rel of all.releases) {
let windows32 = rel.name.includes('WindowsX86.');
if (windows32) {
Object.assign(rel, targets.x86win);
continue;
}
let windows64 = rel.name.includes('Windows.');
if (windows64) {
Object.assign(rel, targets.x64win);
continue;
}
let macos64 = rel.name.includes('MacOS');
if (macos64) {
Object.assign(rel, targets.x64mac);
continue;
}
let musl64 = rel.name.includes('Alpine');
if (musl64) {
Object.assign(rel, targets.x64musl);
continue;
}
let lin64 = rel.name.includes('Linux.');
if (lin64) {
Object.assign(rel, targets.x64lin);
continue;
}
}
all._names = ['AtomicParsley', 'atomicparsley'];
return all;
});
};
if (module === require.main) {
module.exports().then(function (all) {
all = require('../_webi/normalize.js')(all);
console.info(JSON.stringify(all));
//console.info(JSON.stringify(all, null, 2));
});
}

22
awless/releases.js Normal file
View File

@@ -0,0 +1,22 @@
'use strict';
var github = require('../_common/github.js');
var owner = 'wallix';
var repo = 'awless';
module.exports = function () {
return github(null, owner, repo).then(function (all) {
// remove checksums and .deb
all.releases = all.releases.filter(function (rel) {
return !/(\.txt)|(\.deb)$/i.test(rel.name);
});
return all;
});
};
if (module === require.main) {
module.exports().then(function (all) {
all = require('../_webi/normalize.js')(all);
console.info(JSON.stringify(all));
});
}

View File

@@ -28,22 +28,6 @@ __init_bat() {
# chmod a+x ~/.local/opt/bat-v0.15.4/bin/bat
chmod a+x "$pkg_src_cmd"
# install completions if present (autocomplete/)
if test -d ./bat-*/autocomplete; then
mkdir -p "$pkg_src_dir/share/bash-completion/completions"
mkdir -p "$pkg_src_dir/share/fish/vendor_completions.d"
mkdir -p "$pkg_src_dir/share/zsh/site-functions"
mv ./bat-*/autocomplete/bat.bash "$pkg_src_dir/share/bash-completion/completions/bat" 2>/dev/null || true
mv ./bat-*/autocomplete/bat.fish "$pkg_src_dir/share/fish/vendor_completions.d/bat.fish" 2>/dev/null || true
mv ./bat-*/autocomplete/bat.zsh "$pkg_src_dir/share/zsh/site-functions/_bat" 2>/dev/null || true
fi
# install man page if present
if test -f ./bat-*/bat.1; then
mkdir -p "$pkg_src_dir/share/man/man1"
mv ./bat-*/bat.1 "$pkg_src_dir/share/man/man1/bat.1"
fi
if ! [ -e ~/.config/bat/config ]; then
mkdir -p ~/.config/bat/
touch ~/.config/bat/config

20
bat/releases.js Normal file
View File

@@ -0,0 +1,20 @@
'use strict';
var github = require('../_common/github.js');
var owner = 'sharkdp';
var repo = 'bat';
module.exports = function () {
return github(null, owner, repo).then(function (all) {
return all;
});
};
if (module === require.main) {
module.exports().then(function (all) {
all = require('../_webi/normalize.js')(all);
all.releases = all.releases.slice(0, 10);
//console.info(JSON.stringify(all));
console.info(JSON.stringify(all, null, 2));
});
}

61
bun/releases.js Normal file
View File

@@ -0,0 +1,61 @@
'use strict';
var github = require('../_common/github.js');
var owner = 'oven-sh';
var repo = 'bun';
module.exports = function () {
return github(null, owner, repo).then(function (all) {
// collect baseline asset names so we can prefer them over non-baseline
// (baseline builds avoid SIGILL on older/container CPUs)
let baselineNames = new Set();
all.releases.forEach(function (r) {
if (r.name.includes('-baseline')) {
baselineNames.add(r.name.replace('-baseline', ''));
}
});
all.releases = all.releases
.filter(function (r) {
if (r.name.includes('-profile')) {
return false;
}
if (r.name.endsWith('.txt') || r.name.endsWith('.asc')) {
return false;
}
// drop the non-baseline asset when a baseline twin exists
if (!r.name.includes('-baseline') && baselineNames.has(r.name)) {
return false;
}
let isMusl = r.name.includes('-musl');
if (isMusl) {
r._musl = true;
r.libc = 'musl';
} else if (r.name.includes('-linux-')) {
r.libc = 'gnu';
}
return true;
})
.map(function (r) {
// bun-linux-x64-baseline.zip => bun-linux-x64
r.name = r.name.replace('-baseline', '').replace(/\.zip$/, '');
// bun-v0.5.1 => v0.5.1
r.version = r.version.replace(/bun-/g, '');
return r;
});
return all;
});
};
if (module === require.main) {
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);
console.info(JSON.stringify(all, null, 2));
});
}

27
caddy/releases.js Normal file
View File

@@ -0,0 +1,27 @@
'use strict';
var github = require('../_common/github.js');
var owner = 'caddyserver';
var repo = 'caddy';
module.exports = function () {
return github(null, owner, repo).then(function (all) {
// remove checksums and .deb
all.releases = all.releases.filter(function (rel) {
let isOneOffAsset = rel.download.includes('buildable-artifact');
if (isOneOffAsset) {
return false;
}
return true;
});
return all;
});
};
if (module === require.main) {
module.exports().then(function (all) {
all = require('../_webi/normalize.js')(all);
console.info(JSON.stringify(all));
});
}

101
chromedriver/releases.js Normal file
View File

@@ -0,0 +1,101 @@
'use strict';
let Fetcher = require('../_common/fetcher.js');
// See <https://googlechromelabs.github.io/chrome-for-testing/>
const releaseApiUrl =
'https://googlechromelabs.github.io/chrome-for-testing/known-good-versions-with-downloads.json';
// {
// "timestamp": "2023-11-15T21:08:56.730Z",
// "versions": [
// {
// "version": "121.0.6120.0",
// "revision": "1222902",
// "downloads": {
// "chrome": [],
// "chromedriver": [
// {
// "platform": "linux64",
// "url": "https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing/121.0.6120.0/linux64/chromedriver-linux64.zip"
// },
// {
// "platform": "mac-arm64",
// "url": "https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing/121.0.6120.0/mac-arm64/chromedriver-mac-arm64.zip"
// },
// {
// "platform": "mac-x64",
// "url": "https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing/121.0.6120.0/mac-x64/chromedriver-mac-x64.zip"
// },
// {
// "platform": "win32",
// "url": "https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing/121.0.6120.0/win32/chromedriver-win32.zip"
// },
// {
// "platform": "win64",
// "url": "https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing/121.0.6120.0/win64/chromedriver-win64.zip"
// }
// ],
// "chrome-headless-shell": []
// }
// }
// ]
// }
module.exports = async function () {
let resp;
try {
resp = await Fetcher.fetch(releaseApiUrl, {
headers: { Accept: 'application/json' },
});
} catch (e) {
/** @type {Error & { code: string, response: { status: number, body: string } }} */ //@ts-expect-error
let err = e;
if (err.code === 'E_FETCH_RELEASES') {
err.message = `failed to fetch 'chromedriver' release data: ${err.response.status} ${err.response.body}`;
}
throw e;
}
let data = JSON.parse(resp.body);
let builds = [];
for (let release of data.versions) {
if (!release.downloads.chromedriver) {
continue;
}
let version = release.version;
for (let asset of release.downloads.chromedriver) {
let build = {
version: version,
download: asset.url,
// I' not sure that this is actually statically built but it
// seems to be and at worst we'll just get bug reports for Alpine
libc: 'none',
};
builds.push(build);
}
}
let all = {
download: '',
releases: builds,
};
return all;
};
if (module === require.main) {
module
.exports()
.then(function (all) {
all = require('../_webi/normalize.js')(all);
// just select the latest 20 for demonstration
all.releases = all.releases.slice(-20);
console.info(JSON.stringify(all, null, 2));
})
.catch(function (err) {
console.error('Error:', err);
});
}

21
cilium/releases.js Normal file
View File

@@ -0,0 +1,21 @@
'use strict';
var github = require('../_common/github.js');
var owner = 'cilium';
var repo = 'cilium-cli';
module.exports = async function () {
let all = await github(null, owner, repo);
return all;
};
if (module === require.main) {
(async function () {
let normalize = require('../_webi/normalize.js');
let all = await module.exports();
all = normalize(all);
// just select the first 5 for demonstration
all.releases = all.releases.slice(0, 5);
console.info(JSON.stringify(all, null, 2));
})();
}

53
cmake/releases.js Normal file
View File

@@ -0,0 +1,53 @@
'use strict';
var github = require('../_common/github.js');
var owner = 'Kitware';
var repo = 'CMake';
module.exports = function () {
return github(null, owner, repo).then(function (all) {
for (let rel of all.releases) {
if (rel.version.startsWith('v')) {
rel._version = rel.version.slice(1);
}
{
let linuxRe = /(\b|_)(linux|gnu)(\b|_)/i;
let isLinux = linuxRe.test(rel.download) || linuxRe.test(rel.name);
if (isLinux) {
let muslRe = /(\b|_)(musl|alpine)(\b|_)/i;
let isMusl = muslRe.test(rel.download) || muslRe.test(rel.name);
if (isMusl) {
rel.libc = 'musl';
} else {
rel.libc = 'gnu';
}
continue;
}
}
{
let windowsRe = /(\b|_)(win\d*|windows\d*)(\b|_)/i;
let isWindows =
windowsRe.test(rel.download) || windowsRe.test(rel.name);
if (isWindows) {
rel.libc = 'msvc';
continue;
}
}
}
return all;
});
};
if (module === require.main) {
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);
console.info(JSON.stringify(all, null, 2));
});
}

40
comrak/releases.js Normal file
View File

@@ -0,0 +1,40 @@
'use strict';
var github = require('../_common/github.js');
var owner = 'kivikakk';
var repo = 'comrak';
var ODDITIES = ['-musleabihf.1-'];
module.exports = function () {
return github(null, owner, repo).then(function (all) {
let builds = [];
loopBuilds: for (let build of all.releases) {
let isOddity;
for (let oddity of ODDITIES) {
isOddity = build.name.includes(oddity);
if (isOddity) {
break;
}
}
if (isOddity) {
continue;
}
builds.push(build);
}
all.releases = builds;
return all;
});
};
if (module === require.main) {
module.exports().then(function (all) {
all = require('../_webi/normalize.js')(all);
all.releases = all.releases.slice(0, 10);
//console.info(JSON.stringify(all));
console.info(JSON.stringify(all, null, 2));
});
}

31
crabz/releases.js Normal file
View File

@@ -0,0 +1,31 @@
'use strict';
var github = require('../_common/github.js');
var owner = 'sstadick';
var repo = 'crabz';
module.exports = async function () {
let all = await github(null, owner, repo);
let releases = [];
for (let rel of all.releases) {
let isSrc = rel.download.includes('-src.');
if (isSrc) {
continue;
}
releases.push(rel);
}
all.releases = releases;
return all;
};
if (module === require.main) {
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);
console.info(JSON.stringify(all, null, 2));
});
}

21
curlie/releases.js Normal file
View File

@@ -0,0 +1,21 @@
'use strict';
var github = require('../_common/github.js');
var owner = 'rs';
var repo = 'curlie';
module.exports = function () {
return github(null, owner, repo).then(function (all) {
all._names = ['curlie', 'curl-httpie'];
return all;
});
};
if (module === require.main) {
module.exports().then(function (all) {
all = require('../_webi/normalize.js')(all);
all.releases = all.releases.slice(0, 10);
//console.info(JSON.stringify(all));
console.info(JSON.stringify(all, null, 2));
});
}

31
dashcore/releases.js Normal file
View File

@@ -0,0 +1,31 @@
'use strict';
var github = require('../_common/github.js');
var owner = 'dashpay';
var repo = 'dash';
module.exports = function () {
return github(null, owner, repo).then(function (all) {
all.releases.forEach(function (rel) {
if (rel.name.includes('osx64')) {
rel.os = 'macos';
}
if (rel.version.startsWith('v')) {
rel._version = rel.version.slice(1);
}
});
all._names = ['dashd', 'dashcore'];
return all;
});
};
if (module === require.main) {
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);
console.info(JSON.stringify(all, null, 2));
});
}

3
dashd/releases.js Normal file
View File

@@ -0,0 +1,3 @@
'use strict';
module.exports = require('../dashcore/releases.js');

18
dashmsg/releases.js Normal file
View File

@@ -0,0 +1,18 @@
'use strict';
var github = require('../_common/github.js');
var owner = 'dashhive';
var repo = 'dashmsg';
module.exports = function () {
return github(null, owner, repo).then(function (all) {
return all;
});
};
if (module === require.main) {
module.exports().then(function (all) {
all = require('../_webi/normalize.js')(all);
console.info(JSON.stringify(all, null, 2));
});
}

20
delta/releases.js Normal file
View File

@@ -0,0 +1,20 @@
'use strict';
var github = require('../_common/github.js');
var owner = 'dandavison';
var repo = 'delta';
module.exports = function () {
return github(null, owner, repo).then(function (all) {
return all;
});
};
if (module === require.main) {
module.exports().then(function (all) {
all = require('../_webi/normalize.js')(all);
// just select the first 15 for demonstration
all.releases = all.releases.slice(0, 15);
console.info(JSON.stringify(all, null, 2));
});
}

42
deno/releases.js Normal file
View File

@@ -0,0 +1,42 @@
'use strict';
var path = require('path');
var github = require('../_common/github.js');
var owner = 'denoland';
var repo = 'deno';
module.exports = function () {
return github(null, owner, repo).then(function (all) {
// remove checksums and .deb
all.releases = all.releases
.filter(function (rel) {
let isMeta = rel.name.endsWith('.d.ts');
if (isMeta) {
return false;
}
return true;
})
.map(function (rel) {
var ext;
if (!rel.name.match(rel.version)) {
ext = path.extname(rel.name);
rel.filename =
rel.name.slice(0, rel.name.length - ext.length) +
'-' +
rel.version +
ext;
}
return rel;
});
return all;
});
};
if (module === require.main) {
module.exports().then(function (all) {
all = require('../_webi/normalize.js')(all);
console.info(JSON.stringify(all, null, 2));
});
}

20
dotenv-linter/releases.js Normal file
View File

@@ -0,0 +1,20 @@
'use strict';
var github = require('../_common/github.js');
var owner = 'dotenv-linter';
var repo = 'dotenv-linter';
module.exports = function () {
return github(null, owner, repo).then(function (all) {
return all;
});
};
if (module === require.main) {
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);
console.info(JSON.stringify(all, null, 2));
});
}

18
dotenv/releases.js Normal file
View File

@@ -0,0 +1,18 @@
'use strict';
var github = require('../_common/github.js');
var owner = 'therootcompany';
var repo = 'dotenv';
module.exports = function () {
return github(null, owner, repo).then(function (all) {
return all;
});
};
if (module === require.main) {
module.exports().then(function (all) {
all = require('../_webi/normalize.js')(all);
console.info(JSON.stringify(all));
});
}

22
duckdns.sh/releases.js Normal file
View File

@@ -0,0 +1,22 @@
'use strict';
let Releases = module.exports;
let GitHubSource = require('../_common/github-source.js');
let owner = 'BeyondCodeBootcamp';
let repo = 'DuckDNS.sh';
Releases.latest = async function () {
let all = await GitHubSource.getDistributables({ owner, repo });
for (let pkg of all.releases) {
pkg.os = 'posix_2017';
}
return all;
};
if (module === require.main) {
Releases.latest().then(function (all) {
all = require('../_webi/normalize.js')(all);
console.info(JSON.stringify(all, null, 2));
});
}

View File

@@ -27,22 +27,6 @@ __init_fd() {
# chmod a+x "$HOME/.local/opt/fd-v8.1.1/bin/fd"
chmod a+x "$pkg_src_cmd"
# install completions if present (autocomplete/{fd.bash,fd.fish,_fd})
if test -d ./fd-*/autocomplete; then
mkdir -p "$pkg_src_dir/share/bash-completion/completions"
mkdir -p "$pkg_src_dir/share/fish/vendor_completions.d"
mkdir -p "$pkg_src_dir/share/zsh/site-functions"
mv ./fd-*/autocomplete/fd.bash "$pkg_src_dir/share/bash-completion/completions/fd" 2>/dev/null || true
mv ./fd-*/autocomplete/fd.fish "$pkg_src_dir/share/fish/vendor_completions.d/fd.fish" 2>/dev/null || true
mv ./fd-*/autocomplete/_fd "$pkg_src_dir/share/zsh/site-functions/_fd" 2>/dev/null || true
fi
# install man page if present
if test -f ./fd-*/fd.1; then
mkdir -p "$pkg_src_dir/share/man/man1"
mv ./fd-*/fd.1 "$pkg_src_dir/share/man/man1/fd.1"
fi
}
}

31
fd/releases.js Normal file
View File

@@ -0,0 +1,31 @@
'use strict';
var github = require('../_common/github.js');
var owner = 'sharkdp';
var repo = 'fd';
module.exports = function () {
return github(null, owner, repo).then(function (all) {
let builds = [];
for (let build of all.releases) {
if (build.name === 'fd') {
continue;
}
builds.push(build);
}
all.releases = builds;
return all;
});
};
if (module === require.main) {
module.exports().then(function (all) {
all = require('../_webi/normalize.js')(all);
all.releases = all.releases.slice(0, 10);
//console.info(JSON.stringify(all));
console.info(JSON.stringify(all, null, 2));
});
}

45
ffmpeg/releases.js Normal file
View File

@@ -0,0 +1,45 @@
'use strict';
var path = require('path');
var github = require('../_common/github.js');
var owner = 'eugeneware';
var repo = 'ffmpeg-static';
module.exports = function () {
return github(null, owner, repo).then(function (all) {
all.releases = all.releases
.filter(function (rel) {
let isFfmpeg = rel.name.includes('ffmpeg');
if (!isFfmpeg) {
return;
}
// remove README and LICENSE
return !['.README', '.LICENSE'].includes(path.extname(rel.name));
})
.map(function (rel) {
rel.version = rel.version.replace(/^b/, '');
if (/win32/.test(rel.name)) {
rel.os = 'windows';
rel.ext = 'exe';
}
if (/ia32/.test(rel.name)) {
rel.arch = '386';
} else if (/x64/.test(rel.name)) {
rel.arch = 'amd64';
}
return rel;
});
return all;
});
};
if (module === require.main) {
module.exports().then(function (all) {
all = require('../_webi/normalize.js')(all);
console.info(JSON.stringify(all));
});
}

20
ffuf/releases.js Normal file
View File

@@ -0,0 +1,20 @@
'use strict';
var github = require('../_common/github.js');
var owner = 'ffuf';
var repo = 'ffuf';
module.exports = function () {
return github(null, owner, repo).then(function (all) {
return all;
});
};
if (module === require.main) {
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);
console.info(JSON.stringify(all, null, 2));
});
}

39
fish/releases.js Normal file
View File

@@ -0,0 +1,39 @@
'use strict';
var github = require('../_common/github.js');
var owner = 'fish-shell';
var repo = 'fish-shell';
var ODDITIES = ['bundledpcre'];
module.exports = function () {
return github(null, owner, repo).then(function (all) {
all.releases = all.releases
.map(function (rel) {
for (let oddity of ODDITIES) {
let isOddity = rel.name.includes(oddity);
if (isOddity) {
return;
}
}
// We can extract the macos bins from the .app
if (/\.app\.zip$/.test(rel.name)) {
rel.os = 'macos';
rel.arch = 'amd64';
return rel;
}
})
.filter(Boolean);
return all;
});
};
if (module === require.main) {
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);
console.info(JSON.stringify(all, null, 2));
});
}

132
flutter/releases.js Normal file
View File

@@ -0,0 +1,132 @@
'use strict';
let Fetcher = require('../_common/fetcher.js');
let FLUTTER_OSES = ['macos', 'linux', 'windows'];
/**
* stable, beta, dev
* @type {Object.<String, Boolean>}
*/
let channelMap = {};
// This can be spot-checked against
// https://docs.flutter.dev/release/archive?tab=windows
// The release URLs are
// - https://storage.googleapis.com/flutter_infra_release/releases/releases_macos.json
// - https://storage.googleapis.com/flutter_infra_release/releases/releases_linux.json
// - https://storage.googleapis.com/flutter_infra_release/releases/releases_windows.json
// The old release URLs are
// - https://storage.googleapis.com/flutter_infra/releases/releases_macos.json
// - https://storage.googleapis.com/flutter_infra/releases/releases_linux.json
// - https://storage.googleapis.com/flutter_infra/releases/releases_windows.json
// The data looks like
// {
// "base_url": "https://storage.googleapis.com/flutter_infra/releases",
// "current_release": {
// "beta": "b22742018b3edf16c6cadd7b76d9db5e7f9064b5",
// "dev": "fa5883b78e566877613ad1ccb48dd92075cb5c23",
// "stable": "02c026b03cd31dd3f867e5faeb7e104cce174c5f"
// },
// "releases": [
// {
// "hash": "fa5883b78e566877613ad1ccb48dd92075cb5c23",
// "channel": "dev",
// "version": "2.3.0-16.0.pre",
// "release_date": "2021-05-27T23:58:47.683121Z",
// "archive": "dev/macos/flutter_macos_2.3.0-16.0.pre-dev.zip",
// "sha256": "f572b42d36714e6c58a3ed170b93bb414e2ced3ca4bde5094fbe18061cbcba6c"
// },
// {
// "hash": "02c026b03cd31dd3f867e5faeb7e104cce174c5f",
// "channel": "stable",
// "version": "2.2.1",
// "release_date": "2021-05-27T23:06:07.243882Z",
// "archive": "stable/macos/flutter_macos_2.2.1-stable.zip",
// "sha256": "6373d39ec563c337600baf42a42b258420208e4523d85479373e113d61d748df"
// },
// {
// "hash": "b22742018b3edf16c6cadd7b76d9db5e7f9064b5",
// "channel": "beta",
// "version": "2.2.0",
// "release_date": "2021-05-19T21:14:59.281482Z",
// "archive": "beta/macos/flutter_macos_2.2.0-beta.zip",
// "sha256": "31ab530e708f8d1274712211253a27a4ce7d676f139d30f2ec021df22382f052"
// }
// ]
// }
/**
* @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) {
let resp;
try {
let url = `https://storage.googleapis.com/flutter_infra_release/releases/releases_${osname}.json`;
resp = await Fetcher.fetch(url, {
headers: { Accept: 'application/json' },
});
} catch (e) {
/** @type {Error & { code: string, response: { status: number, body: string } }} */ //@ts-expect-error
let err = e;
if (err.code === 'E_FETCH_RELEASES') {
err.message = `failed to fetch 'flutter' release data for ${osname}: ${err.response.status} ${err.response.body}`;
}
throw e;
}
let data = JSON.parse(resp.body);
let osBaseUrl = data.base_url;
let osReleases = data.releases;
for (let asset of osReleases) {
if (!channelMap[asset.channel]) {
channelMap[asset.channel] = true;
}
all.releases.push({
version: asset.version,
_version: `${asset.version}-${asset.channel}`,
lts: false,
channel: asset.channel,
date: asset.release_date.replace(/T.*/, ''),
download: `${osBaseUrl}/${asset.archive}`,
_filename: asset.archive,
});
}
}
all.channels = Object.keys(channelMap);
// note: versions have a waterfall relationship with channels:
// - a release that is in beta today may become stable tomorrow
// - semver prereleases are either beta or dev
return all;
};
if (module === require.main) {
module.exports().then(function (all) {
all.releases = all.releases.slice(25);
console.info(JSON.stringify(all, null, 2));
});
}

20
fzf/releases.js Normal file
View File

@@ -0,0 +1,20 @@
'use strict';
var github = require('../_common/github.js');
var owner = 'junegunn';
var repo = 'fzf';
module.exports = function () {
return github(null, owner, repo).then(function (all) {
return all;
});
};
if (module === require.main) {
module.exports().then(function (all) {
all = require('../_webi/normalize.js')(all);
all.releases = all.releases.slice(0, 10);
//console.info(JSON.stringify(all));
console.info(JSON.stringify(all, null, 2));
});
}

View File

@@ -25,12 +25,6 @@ __init_gh() {
# mv ./gh-*/gh ~/.local/opt/gh-v0.99.9/bin/gh
mv ./"$pkg_cmd_name"*/bin/gh "$pkg_src_cmd"
# install man pages if present
if test -d ./"$pkg_cmd_name"*/share/man; then
mkdir -p "$pkg_src_dir/share"
mv ./"$pkg_cmd_name"*/share/man "$pkg_src_dir/share/man"
fi
}
# pkg_get_current_version is recommended, but (soon) not required

20
gh/releases.js Normal file
View File

@@ -0,0 +1,20 @@
'use strict';
var github = require('../_common/github.js');
var owner = 'cli';
var repo = 'cli';
module.exports = function () {
return github(null, owner, repo).then(function (all) {
return all;
});
};
if (module === require.main) {
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);
console.info(JSON.stringify(all, null, 2));
});
}

33
git/releases.js Normal file
View File

@@ -0,0 +1,33 @@
'use strict';
var github = require('../_common/github.js');
var owner = 'git-for-windows';
var repo = 'git';
module.exports = function () {
// TODO support mac and linux tarballs
return github(null, owner, repo).then(function (all) {
// See https://github.com/git-for-windows/git/wiki/MinGit
// also consider https://github.com/git-for-windows/git/wiki/Silent-or-Unattended-Installation
all.releases = all.releases
.filter(function (rel) {
rel.os = 'windows';
rel._version = rel.version.replace(/\.windows.1.*/, '');
rel._version = rel._version.replace(/\.windows(\.\d)/, '$1');
return (
/MinGit/i.test(rel.name || rel.download) &&
!/busybox/i.test(rel.name || rel.download)
);
})
.slice(0, 20);
all._names = ['MinGit', 'git'];
return all;
});
};
if (module === require.main) {
module.exports().then(function (all) {
all = require('../_webi/normalize.js')(all);
console.info(JSON.stringify(all, null, 2));
});
}

18
gitdeploy/releases.js Normal file
View File

@@ -0,0 +1,18 @@
'use strict';
var github = require('../_common/github.js');
var owner = 'therootcompany';
var repo = 'gitdeploy';
module.exports = function () {
return github(null, owner, repo).then(function (all) {
return all;
});
};
if (module === require.main) {
module.exports().then(function (all) {
all = require('../_webi/normalize.js')(all);
console.info(JSON.stringify(all));
});
}

34
gitea/releases.js Normal file
View File

@@ -0,0 +1,34 @@
'use strict';
var github = require('../_common/github.js');
var owner = 'go-gitea';
var repo = 'gitea';
var ODDITIES = ['-gogit-', '-docs-'];
module.exports = function () {
return github(null, owner, repo).then(function (all) {
// remove checksums and .deb
all.releases = all.releases.filter(function (rel) {
for (let oddity of ODDITIES) {
let isOddity = rel.name.includes(oddity);
if (isOddity) {
return false;
}
}
return true;
});
// "windows-4.0" as a nod to Windows NT ¯\_(ツ)_/¯
all._names = ['gitea', '-4.0-'];
return all;
});
};
if (module === require.main) {
module.exports().then(function (all) {
all = require('../_webi/normalize.js')(all);
console.info(JSON.stringify(all));
});
}

130
go/releases.js Normal file
View File

@@ -0,0 +1,130 @@
'use strict';
let Fetcher = require('../_common/fetcher.js');
/** @type {Object.<String, String>} */
let osMap = {
darwin: 'macos',
};
/** @type {Object.<String, String>} */
let archMap = {
386: 'x86',
};
let ODDITIES = ['bootstrap', '-arm6.'];
/**
* @param {String} filename
*/
function isOdd(filename) {
for (let oddity of ODDITIES) {
let isOddity = filename.includes(oddity);
if (isOddity) {
return true;
}
}
}
/**
* @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() {
/*
{
version: 'go1.13.8',
stable: true,
files: [
{
filename: 'go1.13.8.src.tar.gz',
os: '',
arch: '',
version: 'go1.13.8',
sha256:
'b13bf04633d4d8cf53226ebeaace8d4d2fd07ae6fa676d0844a688339debec34',
size: 21631178,
kind: 'source'
}
]
};
*/
let resp;
try {
let url = 'https://golang.org/dl/?mode=json&include=all';
resp = await Fetcher.fetch(url, {
headers: { Accept: 'application/json' },
});
} catch (e) {
/** @type {Error & { code: string, response: { status: number, body: string } }} */ //@ts-expect-error
let err = e;
if (err.code === 'E_FETCH_RELEASES') {
err.message = `failed to fetch 'Go' release data: ${err.response.status} ${err.response.body}`;
}
throw e;
}
let goReleases = JSON.parse(resp.body);
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().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));
});
}

3
golang/releases.js Normal file
View File

@@ -0,0 +1,3 @@
'use strict';
module.exports = require('../go/releases.js');

View File

@@ -25,23 +25,6 @@ __init_goreleaser() {
# mv ./goreleaser-*/goreleaser ~/.local/opt/goreleaser-v1.21.2/bin/goreleaser
mv ./goreleaser "$pkg_src_cmd"
# install completions if present (completions/{goreleaser.bash,.fish,.zsh})
if test -d ./completions; then
mkdir -p "$pkg_src_dir/share/bash-completion/completions"
mkdir -p "$pkg_src_dir/share/fish/vendor_completions.d"
mkdir -p "$pkg_src_dir/share/zsh/site-functions"
mv ./completions/goreleaser.bash "$pkg_src_dir/share/bash-completion/completions/goreleaser" 2>/dev/null || true
mv ./completions/goreleaser.fish "$pkg_src_dir/share/fish/vendor_completions.d/goreleaser.fish" 2>/dev/null || true
mv ./completions/goreleaser.zsh "$pkg_src_dir/share/zsh/site-functions/_goreleaser" 2>/dev/null || true
fi
# install man page if present (manpages/goreleaser.1.gz)
if test -d ./manpages; then
mkdir -p "$pkg_src_dir/share/man/man1"
mv ./manpages/*.1.gz "$pkg_src_dir/share/man/man1/" 2>/dev/null || true
mv ./manpages/*.1 "$pkg_src_dir/share/man/man1/" 2>/dev/null || true
fi
}
# pkg_get_current_version is recommended, but (soon) not required

21
goreleaser/releases.js Normal file
View File

@@ -0,0 +1,21 @@
'use strict';
var github = require('../_common/github.js');
var owner = 'goreleaser';
var repo = 'goreleaser';
module.exports = function () {
return github(null, owner, repo).then(function (all) {
all._names = ['goreleaser', '1'];
return all;
});
};
if (module === require.main) {
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);
console.info(JSON.stringify(all, null, 2));
});
}

129
gpg/releases.js Normal file
View File

@@ -0,0 +1,129 @@
'use strict';
let Fetcher = require('../_common/fetcher.js');
let ltsRe = /GnuPG-(2\.2\.[\d\.]+)/;
function createRssMatcher() {
return new RegExp(
'<link>(https://sourceforge\\.net/projects/gpgosx/files/GnuPG-([\\d\\.]+)\\.dmg/download)</link>',
'g',
);
}
function createUrlMatcher() {
return new RegExp(
'https://sourceforge\\.net/projects/gpgosx/files/(GnuPG-([\\d\\.]+)\\.dmg)/download',
'',
);
}
/**
* @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 resp;
try {
let url = 'https://sourceforge.net/projects/gpgosx/rss?path=/';
resp = await Fetcher.fetch(url, {
headers: { Accept: 'application/rss+xml' },
});
} catch (e) {
/** @type {Error & { code: string, response: { status: number, body: string } }} */ //@ts-expect-error
let err = e;
if (err.code === 'E_FETCH_RELEASES') {
err.message = `failed to fetch 'gpg' release data: ${err.response.status} ${err.response.body}`;
}
throw e;
}
let contentType = resp.headers.get('Content-Type');
if (!contentType?.includes('xml')) {
throw new Error(`Unexpected content type: ${contentType}`);
}
let matcher = createRssMatcher();
let links = [];
for (;;) {
let m = matcher.exec(resp.body);
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 builds = [];
for (let link of links) {
let isLts = ltsRe.test(link);
let parts = link.match(matcher);
if (!parts || !parts[2]) {
continue;
}
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: builds,
};
}
async function getDistributables() {
let releases = await getRawReleases();
let all = transformReleases(releases);
return all;
}
module.exports = getDistributables;
if (module === require.main) {
getDistributables().then(function (all) {
all = require('../_webi/normalize.js')(all);
all.releases = all.releases.slice(0, 10000);
console.info(JSON.stringify(all, null, 2));
});
}

20
gprox/releases.js Normal file
View File

@@ -0,0 +1,20 @@
'use strict';
var github = require('../_common/github.js');
var owner = 'creedasaurus';
var repo = 'gprox';
module.exports = function () {
return github(null, owner, repo).then(function (all) {
return all;
});
};
if (module === require.main) {
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);
console.info(JSON.stringify(all, null, 2));
});
}

20
grype/releases.js Normal file
View File

@@ -0,0 +1,20 @@
'use strict';
var github = require('../_common/github.js');
var owner = 'anchore';
var repo = 'grype';
module.exports = function () {
return github(null, owner, repo).then(function (all) {
return all;
});
};
if (module === require.main) {
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);
console.info(JSON.stringify(all, null, 2));
});
}

20
hexyl/releases.js Normal file
View File

@@ -0,0 +1,20 @@
'use strict';
var github = require('../_common/github.js');
var owner = 'sharkdp';
var repo = 'hexyl';
module.exports = function () {
return github(null, owner, repo).then(function (all) {
return all;
});
};
if (module === require.main) {
module.exports().then(function (all) {
all = require('../_webi/normalize.js')(all);
all.releases = all.releases.slice(0, 10);
//console.info(JSON.stringify(all));
console.info(JSON.stringify(all, null, 2));
});
}

32
hugo-extended/releases.js Normal file
View File

@@ -0,0 +1,32 @@
'use strict';
var github = require('../_common/github.js');
var owner = 'gohugoio';
var repo = 'hugo';
module.exports = async function () {
let all = await github(null, owner, repo);
all.releases = all.releases.filter(function (rel) {
let isExtended = rel.name.includes('_extended_');
if (!isExtended) {
return false;
}
let isOldAlias = rel.name.includes('Linux-64bit');
if (isOldAlias) {
return false;
}
return true;
});
return all;
};
if (module === require.main) {
module.exports().then(function (all) {
all = require('../_webi/normalize.js')(all);
console.info(JSON.stringify(all));
});
}

32
hugo/releases.js Normal file
View File

@@ -0,0 +1,32 @@
'use strict';
var github = require('../_common/github.js');
var owner = 'gohugoio';
var repo = 'hugo';
module.exports = async function () {
let all = await github(null, owner, repo);
all.releases = all.releases.filter(function (rel) {
let isExtended = rel.name.includes('_extended_');
if (isExtended) {
return false;
}
let isOldAlias = rel.name.includes('Linux-64bit');
if (isOldAlias) {
return false;
}
return true;
});
return all;
};
if (module === require.main) {
module.exports().then(function (all) {
all = require('../_webi/normalize.js')(all);
console.info(JSON.stringify(all));
});
}

93
iterm2/releases.js Normal file
View File

@@ -0,0 +1,93 @@
'use strict';
let Fetcher = require('../_common/fetcher.js');
async function getRawReleases() {
let resp;
try {
let url = 'https://iterm2.com/downloads.html';
resp = await Fetcher.fetch(url, {
headers: { Accept: 'text/html' },
});
} catch (e) {
/** @type {Error & { code: string, response: { status: number, body: string } }} */ //@ts-expect-error
let err = e;
if (err.code === 'E_FETCH_RELEASES') {
err.message = `failed to fetch 'iterm2' release data: ${err.response.status} ${err.response.body}`;
}
throw e;
}
let contentType = resp.headers.get('Content-Type');
if (!contentType || !contentType.includes('text/html')) {
throw new Error(`Unexpected Content-Type: ${contentType}`);
}
let lines = resp.body.split(/[<>]+/g);
/** @type {Array<String>} */
let links = [];
for (let str of lines) {
let m = str.match(/href="(https:\/\/iterm2\.com\/downloads\/.*\.zip)"/);
if (m && /iTerm2-[34]/.test(m[1])) {
if (m[1]) {
links.push(m[1]);
}
}
}
return links;
}
/**
* @param {Array<String>} links
*/
function transformReleases(links) {
let builds = [];
for (let link of links) {
let channel = /\/stable\//.test(link) ? 'stable' : 'beta';
let parts = link.replace(/.*\/iTerm2[-_]v?(\d_.*)\.zip/, '$1').split('_');
let 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: builds,
};
}
async function getDistributables() {
let rawReleases = await getRawReleases();
let all = transformReleases(rawReleases);
return all;
}
module.exports = getDistributables;
if (module === require.main) {
getDistributables().then(function (all) {
all = require('../_webi/normalize.js')(all);
all.releases = all.releases.slice(0, 10000);
console.info(JSON.stringify(all, null, 2));
});
}

43
jq/releases.js Normal file
View File

@@ -0,0 +1,43 @@
'use strict';
var github = require('../_common/github.js');
var owner = 'stedolan';
var repo = 'jq';
let ODDITIES = ['-no-oniguruma'];
function isOdd(build) {
for (let oddity of ODDITIES) {
let isOddity = build.name.includes(oddity);
if (isOddity) {
return true;
}
}
}
module.exports = function () {
return github(null, owner, repo).then(function (all) {
let builds = [];
for (let build of all.releases) {
let odd = isOdd(build);
if (odd) {
continue;
}
build.version = build.version.replace(/^jq\-/, '');
builds.push(build);
}
all.releases = builds;
return all;
});
};
if (module === require.main) {
module.exports().then(function (all) {
all = require('../_webi/normalize.js')(all);
console.info(JSON.stringify(all));
//console.info(JSON.stringify(all, null, 2));
});
}

181
julia/releases.js Normal file
View File

@@ -0,0 +1,181 @@
'use strict';
let Fetcher = require('../_common/fetcher.js');
/** @type {Object.<String, String>} */
let osMap = {
winnt: 'windows',
mac: 'darwin',
};
/** @type {Object.<String, String>} */
let archMap = {
armv7l: 'armv7',
i686: 'x86',
powerpc64le: 'ppc64le',
};
/**
* @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} [_musl]
* @prop {Boolean} [lts]
* @prop {String} [size]
* @prop {String} os
*/
async function getDistributables() {
let all = {
/** @type {Array<BuildInfo>} */
releases: [],
download: '',
_names: ['julia', 'macaarch64'],
};
let resp;
try {
let url = 'https://julialang-s3.julialang.org/bin/versions.json';
resp = await Fetcher.fetch(url, {
headers: { Accept: 'application/json' },
});
} catch (e) {
/** @type {Error & { code: string, response: { status: number, body: string } }} */ //@ts-expect-error
let err = e;
if (err.code === 'E_FETCH_RELEASES') {
err.message = `failed to fetch 'julia' release data: ${err.response.status} ${err.response.body}`;
}
throw e;
}
let buildsByVersion = JSON.parse(resp.body);
/*
{
"url": "https://julialang-s3.julialang.org/bin/mac/aarch64/1.9/julia-1.9.4-macaarch64.tar.gz",
"triplet": "aarch64-apple-darwin14",
"kind": "archive",
"arch": "aarch64",
"asc": "-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCAAdFiEENnPfUp2QSUd/drN1ZuPH3APW5JUFAmVTQBcACgkQZuPH3APW\n5JWUqw//QF/CJLAxXZdcXqpBulLUs/AX+x/8aERGcKxZqfeYOwA5efOzma8sASa/\nUzYCLp9E31x/RMDoZah6vPRRjBR+uVI6CLlXCCCmbAJP3lD2vlcY9LKe2/7s3Ba8\nhwITRaL6R5zNr+YfSHW1Hoj2tWgAQh9Y+Te7bP3jzwp5dlFygFO0pzoN+aeJbPNA\nbgT0ry8tgh78/tgNjgt4Ev3E2t3ehhrDGK4tgkkKieO6sdFz8jOacZVZkR1kLVEg\nMBIqmqZfk+5/HMf/6gHwd5GOXW8+GakN7vYXO+9VFETA2EiD5Z5k4Edq/VrNCn4O\npC6WHpBmVBBYX4aQtHkJyQaV8PtFd1j9338jUWlDaa6BVtX2hjRtU1k1oLZB1TTX\nl4awzYgFqdCRnFmOtzTdMDBcfedOiIHdTyxXPjJCX3i0GXmeuk89e5dE4P6sTT9n\n24GeBVQgMaXuNorg9L0oKrsQ8RDT20yEnVbfhy4Cvoq7dNIks6IxLZt10tjJFp1j\n0oJ5f6KucGyqFM9UhXRcuLj8Z+Q+JDzBs5c2pPe/bEzv6nChRNv252e5dve17esg\nK7tHkhXzM+6wl60oyRtpWghOubXyBDsNu1MH3qC9lWy3wmWuMN7no+yX0vGFyhMT\naxjLJSeYdccKD3SuzYotp3XwBKk05PFX9lWy0vuIjVj1sGWcES8=\n=G1tO\n-----END PGP SIGNATURE-----\n",
"sha256": "67542975e86102eec95bc4bb7c30c5d8c7ea9f9a0b388f0e10f546945363b01a",
"size": 119559478,
"version": "1.9.4",
"os": "mac",
"extension": "tar.gz"
}
*/
let versions = Object.keys(buildsByVersion);
for (let version of versions) {
let release = buildsByVersion[version];
// let odd = isOdd(asset.filename);
// if (odd) {
// return;
// }
for (let build of release.files) {
// // For debugging
// let paths = build.url.split('/');
// let extlen = build.extension.length + 1;
// let name = paths.at(-1);
// name = name.replace(`-${build.version}`, '');
// name = name.slice('julia-'.length);
// name = name.slice(0, -extlen);
// console.log(`name: ${name}`);
// console.log(`triplet: ${build.triplet}`);
// console.log(`arch: ${build.arch}`);
// console.log(`os: ${build.os}`);
// console.log(`kind: ${build.kind}`);
// console.log(`extension: ${build.extension}`);
// console.log(`version: ${build.version}`);
if (build.kind === 'installer') {
continue;
}
let arch = archMap[build.arch] || build.arch || '-';
let os = osMap[build.os] || build.os || '-';
let libc = '';
let hardMusl = /\b(musl)\b/.test(build.url);
if (hardMusl) {
libc = 'musl';
} else if (os === 'linux') {
libc = 'gnu';
}
let webiBuild = {
version: build.version,
_version: build.version,
lts: false,
channel: '', // autodetect by filename (-beta1, -alpha1, -rc1)
date: '1970-01-01', // the world may never know
os: os,
arch: arch,
libc: libc,
_musl: hardMusl,
ext: '', // let normalize run the split/test/join
hash: '-', // build.sha256 not ready to standardize this yet
download: build.url,
};
all.releases.push(webiBuild);
}
}
all.releases.sort(sortByVersion);
return all;
}
/**
* @param {Object} a
* @param {String} a.version
* @param {Object} b
* @param {String} b.version
*/
function sortByVersion(a, b) {
let [aVer, aPre] = a.version.split('-');
let [bVer, bPre] = b.version.split('-');
let aVers = aVer.split('.');
let bVers = bVer.split('.');
for (let i = 0; i < 3; i += 1) {
aVers[i] = aVers[i].padStart(4, '0');
bVers[i] = bVers[i].padStart(4, '0');
}
aVer = aVers.join('.');
if (aPre) {
aVer += `-${aPre}`;
}
bVer = bVers.join('.');
if (bPre) {
bVer += `-${aPre}`;
}
if (aVer > bVer) {
return -1;
}
if (aVer < bVer) {
return 1;
}
return 0;
}
module.exports = getDistributables;
if (module === require.main) {
getDistributables().then(function (all) {
all = require('../_webi/normalize.js')(all);
all.releases = all.releases.slice(0, 10);
console.info(JSON.stringify(all, null, 2));
});
}

20
k9s/releases.js Normal file
View File

@@ -0,0 +1,20 @@
'use strict';
var github = require('../_common/github.js');
var owner = 'derailed';
var repo = 'k9s';
module.exports = function () {
return github(null, owner, repo).then(function (all) {
return all;
});
};
if (module === require.main) {
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);
console.info(JSON.stringify(all, null, 2));
});
}

18
keypairs/releases.js Normal file
View File

@@ -0,0 +1,18 @@
'use strict';
var github = require('../_common/github.js');
var owner = 'therootcompany';
var repo = 'keypairs';
module.exports = function () {
return github(null, owner, repo).then(function (all) {
return all;
});
};
if (module === require.main) {
module.exports().then(function (all) {
all = require('../_webi/normalize.js')(all);
console.info(JSON.stringify(all, null, 2));
});
}

28
kind/releases.js Normal file
View File

@@ -0,0 +1,28 @@
'use strict';
var github = require('../_common/github.js');
var owner = 'kubernetes-sigs';
var repo = 'kind';
/******************************************************************************/
/** Note: Delete this Comment! **/
/** **/
/** Need a an example that filters out miscellaneous release files? **/
/** See `deno`, `gitea`, or `caddy` **/
/** **/
/******************************************************************************/
module.exports = function () {
return github(null, owner, repo).then(function (all) {
return all;
});
};
if (module === require.main) {
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);
console.info(JSON.stringify(all, null, 2));
});
}

18
koji/releases.js Normal file
View File

@@ -0,0 +1,18 @@
'use strict';
var github = require('../_common/github.js');
var owner = 'cococonscious';
var repo = 'koji';
module.exports = function () {
return github(null, owner, repo).then(function (all) {
return all;
});
};
if (module === require.main) {
module.exports().then(function (all) {
all = require('../_webi/normalize.js')(all);
console.info(JSON.stringify(all));
});
}

37
kubectx/releases.js Normal file
View File

@@ -0,0 +1,37 @@
'use strict';
var github = require('../_common/github.js');
var owner = 'ahmetb';
var repo = 'kubectx';
module.exports = function () {
return github(null, owner, repo).then(function (all) {
let builds = [];
for (let build of all.releases) {
// this installs separately
if (build.name.includes('kubens')) {
continue;
}
// this is the legacy bash script
if (build.name === 'kubectx') {
continue;
}
builds.push(build);
}
all.releases = builds;
return all;
});
};
if (module === require.main) {
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);
console.info(JSON.stringify(all, null, 2));
});
}

37
kubens/releases.js Normal file
View File

@@ -0,0 +1,37 @@
'use strict';
var github = require('../_common/github.js');
var owner = 'ahmetb';
var repo = 'kubectx';
module.exports = function () {
return github(null, owner, repo).then(function (all) {
let builds = [];
for (let build of all.releases) {
// this installs separately
if (build.name.includes('kubectx')) {
continue;
}
// this is the legacy bash script
if (build.name === 'kubens') {
continue;
}
builds.push(build);
}
all.releases = builds;
return all;
});
};
if (module === require.main) {
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);
console.info(JSON.stringify(all, null, 2));
});
}

25
lf/releases.js Normal file
View File

@@ -0,0 +1,25 @@
'use strict';
var github = require('../_common/github.js');
var owner = 'gokcehan';
var repo = 'lf';
module.exports = function () {
return github(null, owner, repo).then(function (all) {
all.releases = all.releases.map(function (r) {
// r21 -> 0.21.0
if (/^r/.test(r.version)) {
r.version = '0.' + r.version.replace('r', '') + '.0';
}
return r;
});
return all;
});
};
if (module === require.main) {
module.exports().then(function (all) {
all = require('../_webi/normalize.js')._debug(all);
console.info(JSON.stringify(all, null, 2));
});
}

View File

@@ -25,22 +25,6 @@ __init_lsd() {
# mv ./lsd-*/lsd ~/.local/opt/lsd-v0.17.0/bin/lsd
mv ./lsd-*/lsd "$pkg_src_cmd"
# install completions if present (autocomplete/{_lsd,lsd.fish,lsd.bash-completion})
if test -d ./lsd-*/autocomplete; then
mkdir -p "$pkg_src_dir/share/bash-completion/completions"
mkdir -p "$pkg_src_dir/share/fish/vendor_completions.d"
mkdir -p "$pkg_src_dir/share/zsh/site-functions"
mv ./lsd-*/autocomplete/lsd.bash-completion "$pkg_src_dir/share/bash-completion/completions/lsd" 2>/dev/null || true
mv ./lsd-*/autocomplete/lsd.fish "$pkg_src_dir/share/fish/vendor_completions.d/lsd.fish" 2>/dev/null || true
mv ./lsd-*/autocomplete/_lsd "$pkg_src_dir/share/zsh/site-functions/_lsd" 2>/dev/null || true
fi
# install man page if present
if test -f ./lsd-*/lsd.1; then
mkdir -p "$pkg_src_dir/share/man/man1"
mv ./lsd-*/lsd.1 "$pkg_src_dir/share/man/man1/lsd.1"
fi
}
# pkg_get_current_version is recommended, but (soon) not required

23
lsd/releases.js Normal file
View File

@@ -0,0 +1,23 @@
'use strict';
var github = require('../_common/github.js');
var owner = 'lsd-rs';
var repo = 'lsd';
module.exports = function () {
return github(null, owner, repo).then(function (all) {
all.releases = all.releases.filter(function (rel) {
return !/(-msvc\.)|(\.deb$)/.test(rel.name);
});
return all;
});
};
if (module === require.main) {
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);
console.info(JSON.stringify(all, null, 2));
});
}

144
macos/releases.js Normal file
View File

@@ -0,0 +1,144 @@
'use strict';
let Fetcher = require('../_common/fetcher.js');
let oses = [
{
name: 'macOS Sierra',
version: '10.12.6',
date: '2018-09-26',
channel: 'beta',
url: 'https://support.apple.com/en-us/HT208202',
},
{
name: 'OS X El Capitan',
version: '10.11.6',
date: '2018-07-09',
lts: true,
channel: 'stable',
url: 'https://support.apple.com/en-us/HT206886',
},
{
name: 'OS X Yosemite',
version: '10.10.5',
date: '2017-07-19',
channel: 'beta',
url: 'https://support.apple.com/en-us/HT210717',
},
];
let headers = {
Connection: 'keep-alive',
'Cache-Control': 'max-age=0',
'Upgrade-Insecure-Requests': '1',
'User-Agent':
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36',
'Sec-Fetch-Dest': 'document',
Accept:
'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
'Sec-Fetch-Site': 'none',
'Sec-Fetch-Mode': 'navigate',
'Sec-Fetch-User': '?1',
'Accept-Language': 'en-US,en;q=0.9,sq;q=0.8',
};
/**
* @param {typeof oses[0]} os
*/
async function fetchReleasesForOS(os) {
let resp;
try {
resp = await Fetcher.fetch(os.url, {
headers: headers,
});
} catch (e) {
/** @type {Error & { code: string, response: { status: number, body: string } }} */ //@ts-expect-error
let err = e;
if (err.code === 'E_FETCH_RELEASES') {
err.message = `failed to fetch 'macos' release data: ${err.response.status} ${err.response.body}`;
}
throw e;
}
// Extract the download link
let match = resp.body.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() {
let all = {
_names: ['InstallOS'],
download: '',
/** @type {Array<BuildInfo>} */
releases: [],
};
// Fetch data for each OS and populate the releases array
for (let os of oses) {
let download = await fetchReleasesForOS(os);
if (!download) {
continue;
}
// 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(function (a, b) {
if (a.version === '10.11.6') {
return -1;
}
if (a.date > b.date) {
return 1;
} else if (a.date < b.date) {
return -1;
}
return 0;
});
return all;
}
module.exports = getDistributables;
if (module === require.main) {
module.exports().then(function (all) {
console.info(JSON.stringify(all, null, 2));
});
}

181
mariadb/releases.js Normal file
View File

@@ -0,0 +1,181 @@
'use strict';
let Fetcher = require('../_common/fetcher.js');
let Releases = module.exports;
let PRODUCT = `mariadb`;
// `https://downloads.mariadb.org/rest-api/${PRODUCT}/${minor}/`
// `https://downloads.mariadb.org/rest-api/mariadb/10.5/`
// https://github.com/MariaDB/mariadb-documentation/issues/41
Releases.latest = async function () {
let packages = [];
let versionData = await getVersionIds();
for (let verData of versionData.major_releases) {
let isVersion = /^\d+[.]\d+$/.test(verData.release_id);
if (!isVersion) {
continue;
}
let releaseData = await getReleases(verData.release_id);
let versions = Object.keys(releaseData.releases);
for (let ver of versions) {
let relData = releaseData.releases[ver];
for (let fileData of relData.files) {
let packageData = pluckData(verData, relData, fileData);
if (!packageData) {
continue;
}
packages.push(packageData);
}
}
}
let all = { releases: packages };
return all;
};
/** @type {Object.<String?, String>} */
let channelsMap = {
// 'Long Term Support': 'stable',
// 'Short Term Support': 'stable',
// 'Rolling': null,
Stable: 'stable',
RC: 'rc',
Alpha: 'preview',
null: 'preview',
};
/** @type {Object.<String, String>} */
let cpusMap = {
x86_64: 'amd64',
};
/**
* @param {MajorRelease} verData
* @param {Release} relData
* @param {File} fileData
*/
function pluckData(verData, relData, fileData) {
let lts =
verData.release_status === 'Stable' &&
verData.release_support_type === 'Long Term Support';
let cpu = fileData.cpu || '';
cpu = cpu.trim();
let isNotBinary = !fileData.os || !cpu; // "Source" or some such
if (isNotBinary) {
return null;
}
let isDebug = /debug/.test(fileData.file_name);
if (isDebug) {
return null;
}
let pkgData = {
name: fileData.file_name,
version: relData.release_id,
lts: lts,
channel: channelsMap[verData.release_status],
date: relData.date_of_release,
os: fileData.os?.toLowerCase(),
arch: cpusMap[cpu] || cpu,
hash: fileData.checksum.sha256sum,
download: fileData.file_download_url,
};
return pkgData;
}
/**
* @typedef {String} ISODate - YYYY-MM-DD (ISO 8601 format)
*/
/**
* @typedef MajorRelease
* @prop {String} release_id - version-like for stable versions, otherwise a title
* @prop {String} release_name - same as id for MariaDB
* @prop {String} release_status - Stable|RC|Alpha
* @prop {String?} release_support_type - Long Term Support|Short Term Support|Rolling|null
* @prop {ISODate?} release_eol_date
*/
/**
* @typedef MajorReleasesWrapper
* @prop {Array<MajorRelease>} major_releases
*/
/**
* @typedef Release
* @prop {String} release_id - "11.4.4" or "11.6.0 Vector"
* @prop {String} release_name - "MariaDB Server 11.8.0 Preview"
* @prop {ISODate} date_of_release
* @prop {String} release_notes_url
* @prop {String} change_log
* @prop {Array<File>} files - release assets (packages, docs, etc)
*/
/**
* @typedef ReleasesWrapper
* @prop {Object.<String, Release>} releases
*/
/**
* @typedef File
* @prop {Number} file_id
* @prop {String} file_name
* @prop {String?} package_type - "gzipped tar file" or "ZIP file"
* @prop {String?} os - "Linux" or "Windows"
* @prop {String?} cpu - "x86_64" (or null)
* @prop {Checksum} checksum
* @prop {String} file_download_url
* @prop {String?} signature
* @prop {String} checksum_url
* @prop {String} signature_url
*/
/**
* @typedef Checksum
* @prop {String?} md5sum
* @prop {String?} sha1sum
* @prop {String?} sha256sum
* @prop {String?} sha512sum
*/
/**
* @returns {Promise<MajorReleasesWrapper>}
*/
async function getVersionIds() {
let url = `https://downloads.mariadb.org/rest-api/${PRODUCT}/`;
let resp = await Fetcher.fetch(url, {
headers: { Accept: 'application/json' },
});
let result = JSON.parse(resp.body);
return result;
}
/**
* @param {String} verId
* @returns {Promise<ReleasesWrapper>}
*/
async function getReleases(verId) {
let url = `https://downloads.mariadb.org/rest-api/${PRODUCT}/${verId}`;
let resp = await Fetcher.fetch(url, {
headers: { Accept: 'application/json' },
});
let result = JSON.parse(resp.body);
return result;
}
if (module === require.main) {
Releases.latest().then(function (all) {
let normalize = require('../_webi/normalize.js');
all = normalize(all);
let json = JSON.stringify(all, null, 2);
console.info(json);
});
}

37
monorel/releases.js Normal file
View File

@@ -0,0 +1,37 @@
'use strict';
var github = require('../_common/github.js');
var owner = 'therootcompany';
var repo = 'golib';
let Releases = module.exports;
Releases.latest = async function () {
let all = await github(null, owner, repo);
// This is a monorepo — keep only monorel releases and strip the
// path prefix from the version so normalize.js sees plain semver.
all.releases = all.releases.filter(function (rel) {
return rel.version.startsWith('tools/monorel/');
});
all.releases.forEach(function (rel) {
rel.version = rel.version.replace(/^tools\/monorel\//, '');
});
return all;
};
Releases.sample = async function () {
let normalize = require('../_webi/normalize.js');
let all = await Releases.latest();
all = normalize(all);
all.releases = all.releases.slice(0, 5);
return all;
};
if (module === require.main) {
(async function () {
let samples = await Releases.sample();
console.info(JSON.stringify(samples, null, 2));
})();
}

18
mutagen/releases.js Normal file
View File

@@ -0,0 +1,18 @@
'use strict';
var github = require('../_common/github.js');
var owner = 'mutagen-io';
var repo = 'mutagen';
module.exports = function () {
return github(null, owner, repo).then(function (all) {
return all;
});
};
if (module === require.main) {
module.exports().then(function (all) {
all = require('../_webi/normalize.js')._debug(all);
console.info(JSON.stringify(all, null, 2));
});
}

229
node/releases.js Normal file
View File

@@ -0,0 +1,229 @@
'use strict';
let Fetcher = require('../_common/fetcher.js');
// https://blog.risingstack.com/update-nodejs-8-end-of-life-no-support/
// 6 mos "current" + 18 mos LTS "active" + 12 mos LTS "maintenance"
//let endOfLife = 3 * 366 * 24 * 60 * 60 * 1000;
// If there have been no updates in 12 months, it's almost certainly end-of-life
const END_OF_LIFE = 366 * 24 * 60 * 60 * 1000;
// OSes
/** @type {Object.<String, String>} */
let osMap = {
osx: 'macos', // NOTE: filename is 'darwin'
linux: 'linux',
win: 'windows', // windows
sunos: 'sunos',
aix: 'aix',
};
// CPU architectures
/** @type {Object.<String, String>} */
let archMap = {
x64: 'amd64',
x86: 'x86',
ppc64: 'ppc64',
ppc64le: 'ppc64le',
arm64: 'arm64',
armv7l: 'armv7l',
armv6l: 'armv6l',
s390x: 's390x',
};
// file extensions
/** @type {Object.<String, Array<String>>} */
let pkgMap = {
pkg: ['pkg'],
//exe: ['exe'], // disable
'7z': ['7z'],
zip: ['zip'],
tar: ['tar.gz', 'tar.xz'],
// oddity - no os in download
msi: ['msi'],
// oddity - no pkg info
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: '',
};
/*
[
{
"version":"v20.3.1",
"date":"2023-06-20",
"files":["headers","linux-armv6l","linux-x64-musl","linux-x64-pointer-compression"],
"npm":"9.6.7",
"v8":"11.3.244.8",
"uv":"1.45.0",
"zlib":"1.2.13.1-motley",
"openssl":"3.0.9+quic",
"modules":"115",
"lts":false,
"security":true
},
]
*/
{
// Alternate: 'https://nodejs.org/dist/index.json',
let baseUrl = `https://nodejs.org/download/release`;
// Fetch official builds
let resp;
try {
resp = await Fetcher.fetch(`${baseUrl}/index.json`, {
headers: { Accept: 'application/json' },
});
} catch (e) {
/** @type {Error & { code: string, response: { status: number, body: string } }} */ //@ts-expect-error
let err = e;
if (err.code === 'E_FETCH_RELEASES') {
err.message = `failed to fetch 'node' release data: ${err.response.status} ${err.response.body}`;
}
throw e;
}
let data = JSON.parse(resp.body);
void transform(baseUrl, data);
}
{
let unofficialBaseUrl = `https://unofficial-builds.nodejs.org/download/release`;
// Fetch unofficial builds
let resp;
try {
resp = await Fetcher.fetch(`${unofficialBaseUrl}/index.json`, {
headers: { Accept: 'application/json' },
});
} catch (e) {
/** @type {Error & { code: string, response: { status: number, body: string } }} */ //@ts-expect-error
let err = e;
if (err.code === 'E_FETCH_RELEASES') {
err.message = `failed to fetch 'node' (unofficial) release data: ${err.response.status} ${err.response.body}`;
}
throw e;
}
let data = JSON.parse(resp.body);
transform(unofficialBaseUrl, data);
}
/**
* @param {String} baseUrl
* @param {Array<any>} builds
*/
function transform(baseUrl, builds) {
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) {
continue;
}
let lts = false !== build.lts;
// skip 'v'
let vparts = build.version.slice(1).split('.');
let major = parseInt(vparts[0], 10);
let channel = 'stable';
let isEven = 0 === major % 2;
if (!isEven) {
channel = 'beta';
}
for (let file of build.files) {
if ('src' === file || 'headers' === file) {
continue;
}
let fileParts = file.split('-');
let osPart = fileParts[0];
let os = osMap[osPart];
let archPart = fileParts[1];
let arch = archMap[archPart];
let libc = '';
let pkgPart = fileParts[2];
let pkgs = pkgMap[pkgPart];
if (!pkgPart) {
pkgs = pkgMap.tar;
}
if (!pkgs?.length) {
continue;
}
let extra = '';
let muslNative;
if (fileParts[2] === 'musl') {
extra = '-musl';
muslNative = true;
libc = 'musl';
} else if (os === 'linux') {
libc = 'gnu';
}
if (osPart === 'osx') {
osPart = 'darwin';
}
for (let pkg of pkgs) {
let filename = `node-${build.version}-${osPart}-${archPart}${extra}.${pkg}`;
if ('msi' === pkg) {
filename = `node-${build.version}-${archPart}${extra}.${pkg}`;
}
let downloadUrl = `${baseUrl}/${build.version}/${filename}`;
let release = {
name: filename,
version: build.version,
lts: lts,
channel: channel,
date: build.date,
os: os,
arch: arch,
ext: pkg,
download: downloadUrl,
libc: libc,
};
all.releases.push(release);
}
}
}
}
return all;
}
module.exports = getDistributables;
if (module === require.main) {
getDistributables().then(function (all) {
all = require('../_webi/normalize.js')(all);
console.info(JSON.stringify(all));
//console.info(JSON.stringify(all, null, 2));
});
}

59
ollama/releases.js Normal file
View File

@@ -0,0 +1,59 @@
'use strict';
var github = require('../_common/github.js');
var owner = 'jmorganca';
var repo = 'ollama';
module.exports = async function () {
let all = await github(null, owner, repo);
let releases = [];
for (let rel of all.releases) {
// this is a janky, sudo-wantin' .app
let isJank = rel.name.startsWith('Ollama-darwin');
if (isJank) {
continue;
}
let isUniversal = rel.name === 'ollama-darwin';
if (isUniversal) {
let x64 = Object.assign({ arch: 'x86_64' }, rel);
releases.push(x64);
rel.arch = 'aarch64';
}
let isROCm = rel.name.includes('-rocm');
if (isROCm) {
Object.assign(rel, { arch: 'x86_64_rocm' });
}
let oddballs = {
tgz: 'tar.gz',
tbz2: 'tar.bz2',
txz: 'tar.xz',
};
let oddExts = Object.keys(oddballs);
for (let oddExt of oddExts) {
let isOddball = rel.name.endsWith(`.${oddExt}`);
if (isOddball) {
let ext = oddballs[oddExt];
rel.name = rel.name.replace(`.${oddExt}`, `.${ext}`);
rel.ext = ext;
}
}
releases.push(rel);
}
all.releases = releases;
return all;
};
if (module === require.main) {
module.exports().then(function (all) {
all = require('../_webi/normalize.js')(all);
console.info(JSON.stringify(all));
//console.info(JSON.stringify(all, null, 2));
});
}

20
ots/releases.js Normal file
View File

@@ -0,0 +1,20 @@
'use strict';
var github = require('../_common/github.js');
var owner = 'emdneto';
var repo = 'otsgo';
module.exports = function () {
return github(null, owner, repo).then(function (all) {
return all;
});
};
if (module === require.main) {
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);
console.info(JSON.stringify(all, null, 2));
});
}

View File

@@ -25,12 +25,6 @@ __init_pandoc() {
# mv ./pandoc-*/pandoc ~/.local/opt/pandoc-v2.10.1/bin/pandoc
mv ./pandoc-*/bin/pandoc "$pkg_src_cmd"
# install man pages if present (share/man/man1/pandoc*.1.gz)
if test -d ./pandoc-*/share/man; then
mkdir -p "$pkg_src_dir/share"
mv ./pandoc-*/share/man "$pkg_src_dir/share/man"
fi
}
# pkg_get_current_version is recommended, but (soon) not required

20
pandoc/releases.js Normal file
View File

@@ -0,0 +1,20 @@
'use strict';
var github = require('../_common/github.js');
var owner = 'jgm';
var repo = 'pandoc';
module.exports = function () {
return github(null, owner, repo).then(function (all) {
return all;
});
};
if (module === require.main) {
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);
console.info(JSON.stringify(all, null, 2));
});
}

28
pathman/releases.js Normal file
View File

@@ -0,0 +1,28 @@
'use strict';
var github = require('../_common/gitea.js');
var owner = 'root';
var repo = 'pathman';
var baseurl = 'https://git.rootprojects.org';
module.exports = function () {
return github(null, owner, repo, baseurl).then(function (all) {
all.releases = all.releases.filter(function (release) {
release._filename = release.name;
let isOldAlias = release.name.includes('armv8');
if (isOldAlias) {
return false;
}
return true;
});
return all;
});
};
if (module === require.main) {
module.exports().then(function (all) {
console.info(JSON.stringify(all, null, 2));
});
}

View File

@@ -1,203 +0,0 @@
---
title: pg-essentials
homepage: https://github.com/bnnanet/pg-essentials
tagline: |
pg-essentials: client and server scripts for working with postgres
---
To update or switch versions, run `webi pg-essentials@stable` (or `@v1.0.0`,
`@beta`, etc).
### Files
These are the files / directories that are created and/or modified with this
install:
```text
~/.local/bin/
~/.local/opt/pg-essentials/
~/.pgpass
~/.psqlrc
~/.config/psql/psqlrc.sql
~/.config/psql/history
```
## Cheat Sheet
> `pg-essentials` includes scripts to manage credentials, backups, and
> preferences.
```sh
psql-store-credential 'postgres://my-userdb:my-token@my-host:5432/my-userdb'
psql-backup 'postgres://my-userdb@my-host:5432/my-userdb'
```
```sh
pg-register-service '5432'
pg-addgroup 'hostssl' 'remote_users' 5432
pg-adduser 'my-user-prefix' '5432' 'remote_users'
pg-passwd 'my-user-prefix-and-suffix' 5432
```
### Client Scripts
#### How to Store Credentials
This will parse the PG URL and put it in the correct credential format in
`~/.pgpass`.
```sh
psql-store-credential 'postgres://my-userdb:my-token@my-host:5432/my-userdb'
```
This is the same as manually editing `~/.pgpass` to add
```text
# export PGPASSFILE="$HOME/.pgpass"
# hostname:port:database:username:password
my-host:5432:my-userdb:my-userdb:my-token
```
#### How to Backup
This uses `pg_dump` to create an easy-to-restore backup (using the correct
permission options), with the schema and data separated.
```sh
psql-backup 'postgres://my-userdb@my-host:5432/my-userdb'
```
Or with positional arguments:
```sh
psql-backup 'my-userdb' 'my-host' 5432 'my-userdb'
```
```text
my-userdb.schema.drop.sql # drops and then creates schema
my-userdb.schema.sql # creates schema, without dropping
my-userdb.data.sql # inserts data
```
This is the same as:
```sh
pg_dump --no-privileges --no-owner --schema-only --clean \
--username 'my-userdb' --no-password --host 'my-host' --port 5432 \
-f ./my-userdb.schema.drop.sql 'my-userdb'
pg_dump --no-privileges --no-owner --schema-only \
--username 'my-userdb' --no-password --host 'my-host' --port 5432 \
-f ./my-userdb.schema.sql 'my-userdb'
pg_dump --no-privileges --no-owner --data-only \
--username 'my-userdb' --no-password --host 'my-host' --port 5432 \
-f ./my-userdb.data.sql 'my-userdb'
```
### Server Scripts
These assume a conflict-free installation of postgres at
`~/.local/share/postgres/var/`.
The scripts can easily be manually modified for other locations.
#### How to Register Service
```sh
pg-register-service '5432'
```
This is the same as
```sh
curl https://webi.sh/serviceman | sh
source ~/.config/envman/PATH.env
mkdir -p ~/.local/share/postgres
serviceman add --name 'postgres' -- \
postgres -D ~/.local/share/postgres/var -p 5432
```
#### How to add Remote Role (Group)
This will add a role (group) which allows users (named the same as their
database name) to access the pg database remotely, using TLS with SNI (ALPN will
be set to 'postgresql' and must be explicitly accepted by proxies).
```sh
pg-addgroup 'hostssl' 'remote_users' 5432
```
Other connection types:
```sh
pg-addgroup 'host' 'subnet_users' 5432
pg-addgroup 'localhost' 'local_users' 5432
pg-addgroup 'local' 'unix_users' 5432
```
This is the same as adding a remote users role and editing
`~/.local/share/postgres/var/pg_hba.conf`
```sql
CREATE ROLE "remote_users" NOLOGIN;
```
```ini
hostssl sameuser +remote_users 0.0.0.0/0 scram-sha-256
hostssl sameuser +remote_users ::0/0 scram-sha-256
```
#### How to add Remote User
You must first add a group for the user to belong to.
This will create a user and database of the same name, with the given prefix
(followed by a random suffix), as a member of 'remote_users':
```sh
pg-addgroup 'hostssl' 'remote_users' 5432
pg-adduser 'my-user-prefix' 5432 'remote_users'
```
This is the same as generating a random suffix and password (ex: using `uuidgen`
or `xxd -l 16 -ps /dev/urandom`), and creating the `DATABASE`, `ROLE`, and
granting `PRIVILEGES`:
```sql
CREATE DATABASE "my-user-prefix-1234";
CREATE ROLE "my-user-prefix-1234" LOGIN INHERIT IN ROLE "remote_users" ENCRYPTED PASSWORD 'supersecret';
GRANT ALL PRIVILEGES ON DATABASE "my-user-prefix-1234" to "my-user-prefix-1234";
```
Note: the password is NOT encrypted, just hashed - a misnomer from days of yore
#### How to Set a User's Password
This generates a new random password for the user/db.
```sh
pg-passwd 'my-user-prefix-and-suffix'
```
This is the same as generating a random password and running the following:
```sql
ALTER USER "my-user-prefix-and-suffix" WITH PASSWORD 'supersecret';
```
### Building Postgres from Source
These scripts will build postgres in `~/relocatable`, from source.
```sh
pg-build "$(hostname)" 18.1
```
Or use the platform-specific scripts directly:
```sh
pg-build-linux "$(hostname)" 18.1
pg-build-macos "$(hostname)" 18.1
```

View File

@@ -1,64 +0,0 @@
#!/bin/sh
__init_pg_essentials() {
set -e
set -u
#########################
# Install pg-essentials #
#########################
# Every package should define these 6 variables
pkg_cmd_name="pg-essentials"
pkg_dst_cmd="$HOME/.local/bin/psql-backup"
pkg_dst="$pkg_dst_cmd"
pkg_src_cmd="$HOME/.local/opt/pg-essentials-v$WEBI_VERSION/psql-backup"
pkg_src_bin="$HOME/.local/opt/pg-essentials-v$WEBI_VERSION"
pkg_src_dir="$HOME/.local/opt/pg-essentials-v$WEBI_VERSION"
pkg_src="$pkg_src_cmd"
pkg_install() {
rm -rf "${pkg_src_dir}"
# mv ./bnnanet-pg-essentials-* "$HOME/.local/opt/pg-essentials-v1.0.0"
mv ./*"$pkg_cmd_name"*/ "${pkg_src_dir}"
}
pkg_link() {
(
cd ~/.local/opt/ || return 1
rm -rf ./pg-essentials
ln -s "pg-essentials-v$WEBI_VERSION" 'pg-essentials'
)
(
mkdir -p ~/.local/bin/
cd ~/.local/opt/pg-essentials/ || return 1
for b_file in pg-*; do
rm -rf ../../bin/"${b_file}"
ln -s "../opt/pg-essentials-v$WEBI_VERSION/${b_file}" ../../bin/
done
for b_file in psql-*; do
rm -rf ../../bin/"${b_file}"
ln -s "../opt/pg-essentials-v$WEBI_VERSION/${b_file}" ../../bin/
done
)
}
pkg_get_current_version() {
# 'psql-backup -V' has output in this format:
# psql-backup v1.0.0 - creates portable (across instances) SQL schema & data backups
#
# USAGE
# psql-backup <user> [host] [port] [dbname]
#
# ...
# This trims it down to just the version number:
# 1.0.0
psql-backup --version 2> /dev/null | head -n 1 | cut -d' ' -f2 | sed 's:^v::'
}
}
__init_pg_essentials

View File

@@ -1 +0,0 @@
git_url = https://github.com/bnnanet/pg-essentials.git

50
pg/releases.js Normal file
View File

@@ -0,0 +1,50 @@
'use strict';
let Releases = module.exports;
var Github = require('../_common/github.js');
var owner = 'bnnanet';
var repo = 'postgresql-releases';
Releases.latest = async function () {
let all = await Github.getDistributables(null, owner, repo);
/** @type {Array<Awaited<ReturnType<typeof Github.getDistributables>>>[Number]["releases"]} */
let distributables = [];
for (let dist of all.releases) {
let isBaseline = dist.name.includes('baseline');
if (isBaseline) {
continue;
}
let isServer = dist.name.includes('postgres');
if (!isServer) {
continue;
}
// REL_17_0 => 17.0
dist.version = dist.version.replace(/REL_/g, '');
dist.version = dist.version.replace(/_/g, '.');
let isHardMusl = dist.name.includes('musl');
if (isHardMusl) {
Object.assign(dist, { libc: 'musl', _musl: true });
}
distributables.push(dist);
}
all.releases = distributables;
Object.assign(all, { _names: ['postgres', 'postgresql', 'pgsql', 'psql'] });
return all;
};
if (module === require.main) {
Releases.latest().then(function (all) {
let normalize = require('../_webi/normalize.js');
all = normalize(all);
let json = JSON.stringify(all, null, 2);
console.info(json);
});
}

View File

@@ -5,8 +5,6 @@ tagline: |
PostgreSQL: The World's Most Advanced Open Source Relational Database.
---
TODO
To update or switch versions, run `webi postgres@stable` (or `@v10`, `@beta`,
etc).
@@ -49,7 +47,7 @@ To login: \
# as Postgres admin
psql "postgres://postgres:postgres@localhost:5432/postgres"
# as remote user (over standard TLS and SNI, with ALPN set to 'postgresql')
# as remote user
psql "postgres://db-xxxx@pg-1.example.com:5432/db-xxxx?sslmode=require&sslnegotiation=direct"
```

129
postgres/releases.js Normal file
View File

@@ -0,0 +1,129 @@
'use strict';
let Releases = module.exports;
var Github = require('../_common/github.js');
var owner = 'bnnanet';
var repo = 'postgresql-releases';
let originalReleases = {
_names: ['PostgreSQL', 'postgresql', 'Postgres', 'postgres', 'binaries'],
releases: [
{
name: 'postgresql-10.12-1-linux-x64-binaries.tar.gz',
version: '10.12',
lts: false,
channel: 'stable',
date: '',
os: 'linux',
arch: 'amd64',
libc: 'gnu',
ext: 'tar',
download: '',
},
{
name: 'postgresql-10.12-1-linux-binaries.tar.gz',
version: '10.12',
lts: false,
channel: 'stable',
date: '',
os: 'linux',
arch: 'x86',
libc: 'gnu',
ext: 'tar',
download: '',
},
{
name: 'postgresql-10.12-1-osx-binaries.zip',
version: '10.12',
lts: false,
channel: 'stable',
date: '',
os: 'macos',
arch: 'amd64',
ext: 'zip',
download: '',
},
{
name: 'postgresql-10.13-1-osx-binaries.zip',
version: '10.13',
lts: false,
channel: 'stable',
date: '',
os: 'macos',
arch: 'amd64',
ext: 'zip',
download: '',
},
{
name: 'postgresql-11.8-1-osx-binaries.zip',
version: '11.8',
lts: false,
channel: 'stable',
date: '',
os: 'macos',
arch: 'amd64',
ext: 'zip',
download: '',
},
{
name: 'postgresql-12.3-1-osx-binaries.zip',
version: '12.3',
lts: false,
channel: 'stable',
date: '',
os: 'macos',
arch: 'amd64',
ext: 'zip',
download: '',
},
].map(function (rel) {
//@ts-ignore - it's a special property
rel._version = `${rel.version}-1`;
rel.download = `https://get.enterprisedb.com/postgresql/${rel.name}?ls=Crossover&type=Crossover`;
return rel;
}),
download: '',
};
Releases.latest = async function () {
let all = await Github.getDistributables(null, owner, repo);
/** @type {Array<Awaited<ReturnType<typeof Github.getDistributables>>>[Number]["releases"]} */
let distributables = [];
for (let dist of all.releases) {
console.log(dist);
console.log(dist.name, 'niche', /psql|baseline/.test(dist.name));
let isNiche = /psql|baseline/.test(dist.name);
if (isNiche) {
continue;
}
// REL_17_0 => 17.0
dist.version = dist.version.replace(/REL_/g, '');
dist.version = dist.version.replace(/_/g, '.');
let isHardMusl = dist.name.includes('musl');
if (isHardMusl) {
Object.assign(dist, { libc: 'musl', _musl: true });
}
distributables.push(dist);
}
all.releases = distributables;
Object.assign(all, { _names: originalReleases._names });
//@ts-ignore - mixing old and new release types
all.releases = all.releases.concat(originalReleases.releases);
return all;
};
if (module === require.main) {
Releases.latest().then(function (all) {
let normalize = require('../_webi/normalize.js');
all = normalize(all);
let json = JSON.stringify(all, null, 2);
console.info(json);
});
}

View File

@@ -38,7 +38,7 @@ psql "postgres://db-xxxx:secret123@pg-1.example.com:5432/db-xxxx"
```
For remote / public networks: \
(with `sslmode` & `sslnegotiation` for standard TLS and SNI, with ALPN set to 'postgresql')
(with `sslmode` & `sslnegotiation`)
```sh
psql "postgres://db-xxxx@pg-1.example.com:5432/db-xxxx?sslmode=require&sslnegotiation=direct"

50
psql/releases.js Normal file
View File

@@ -0,0 +1,50 @@
'use strict';
let Releases = module.exports;
var Github = require('../_common/github.js');
var owner = 'bnnanet';
var repo = 'postgresql-releases';
Releases.latest = async function () {
let all = await Github.getDistributables(null, owner, repo);
/** @type {Array<Awaited<ReturnType<typeof Github.getDistributables>>>[Number]["releases"]} */
let distributables = [];
for (let dist of all.releases) {
let isBaseline = dist.name.includes('baseline');
if (isBaseline) {
continue;
}
let isClient = dist.name.includes('psql');
if (!isClient) {
continue;
}
// REL_17_0 => 17.0
dist.version = dist.version.replace(/REL_/g, '');
dist.version = dist.version.replace(/_/g, '.');
let isHardMusl = dist.name.includes('musl');
if (isHardMusl) {
Object.assign(dist, { libc: 'musl', _musl: true });
}
distributables.push(dist);
}
all.releases = distributables;
Object.assign(all, { _names: ['postgres', 'postgresql', 'pgsql', 'psql'] });
return all;
};
if (module === require.main) {
Releases.latest().then(function (all) {
let normalize = require('../_webi/normalize.js');
all = normalize(all);
let json = JSON.stringify(all, null, 2);
console.info(json);
});
}

51
pwsh/releases.js Normal file
View File

@@ -0,0 +1,51 @@
'use strict';
var github = require('../_common/github.js');
var owner = 'powershell';
var repo = 'powershell';
let ODDITIES = ['-fxdependent'];
function isOdd(build) {
for (let oddity of ODDITIES) {
let isOddity = build.name.includes(oddity);
if (isOddity) {
return true;
}
}
}
module.exports = function () {
return github(null, owner, repo).then(function (all) {
// remove checksums and .deb
all.releases = all.releases.filter(function (rel) {
let odd = isOdd(rel);
if (odd) {
return false;
}
let isPreview = rel.name.includes('-preview.');
if (isPreview) {
rel.channel = 'beta';
}
let isMusl = rel.download.match(/(\b|_)(musl|alpine)(\b|_)/i);
if (isMusl) {
// not a fully static build, not gnu-compatible
rel.libc = 'musl';
}
return true;
});
all._names = ['PowerShell', 'powershell'];
return all;
});
};
if (module === require.main) {
module.exports().then(function (all) {
all = require('../_webi/normalize.js')(all);
console.info(JSON.stringify(all));
});
}

19
rclone/releases.js Normal file
View File

@@ -0,0 +1,19 @@
'use strict';
var github = require('../_common/github.js');
var owner = 'rclone';
var repo = 'rclone';
module.exports = function () {
return github(null, owner, repo).then(function (all) {
return all;
});
};
if (module === require.main) {
module.exports().then(function (all) {
all = require('../_webi/normalize.js')(all);
all.releases = all.releases.slice(0, 10);
console.info(JSON.stringify(all, null, 2));
});
}

View File

@@ -26,22 +26,6 @@ __init_rg() {
# mv ./ripgrep-*/rg ~/.local/opt/rg-v12.1.1/bin/rg
mv ./ripgrep-*/rg "$pkg_src_cmd"
# install completions if present (complete/_rg, complete/rg.bash, complete/rg.fish)
if test -d ./ripgrep-*/complete; then
mkdir -p "$pkg_src_dir/share/bash-completion/completions"
mkdir -p "$pkg_src_dir/share/fish/vendor_completions.d"
mkdir -p "$pkg_src_dir/share/zsh/site-functions"
mv ./ripgrep-*/complete/rg.bash "$pkg_src_dir/share/bash-completion/completions/rg" 2>/dev/null || true
mv ./ripgrep-*/complete/rg.fish "$pkg_src_dir/share/fish/vendor_completions.d/rg.fish" 2>/dev/null || true
mv ./ripgrep-*/complete/_rg "$pkg_src_dir/share/zsh/site-functions/_rg" 2>/dev/null || true
fi
# install man page if present
if test -f ./ripgrep-*/doc/rg.1; then
mkdir -p "$pkg_src_dir/share/man/man1"
mv ./ripgrep-*/doc/rg.1 "$pkg_src_dir/share/man/man1/rg.1"
fi
if ! [ -e ~/.ripgreprc ]; then
touch ~/.ripgreprc
fi

21
rg/releases.js Normal file
View File

@@ -0,0 +1,21 @@
'use strict';
var github = require('../_common/github.js');
var owner = 'BurntSushi';
var repo = 'ripgrep';
module.exports = function () {
return github(null, owner, repo).then(function (all) {
all._names = ['ripgrep', 'rg'];
return all;
});
};
if (module === require.main) {
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);
console.info(JSON.stringify(all, null, 2));
});
}

19
ripgrep/releases.js Normal file
View File

@@ -0,0 +1,19 @@
'use strict';
var github = require('../_common/github.js');
var owner = 'BurntSushi';
var repo = 'ripgrep';
module.exports = function () {
return github(null, owner, repo).then(function (all) {
return all;
});
};
if (module === require.main) {
module.exports().then(function (all) {
all = require('../_webi/normalize.js')(all);
console.info(JSON.stringify(all));
//console.info(JSON.stringify(all, null, 2));
});
}

23
runzip/releases.js Normal file
View File

@@ -0,0 +1,23 @@
'use strict';
let Releases = module.exports;
let GitHub = require('../_common/github.js');
let owner = 'therootcompany';
let repo = 'runzip';
Releases.latest = async function () {
let all = await GitHub.getDistributables(null, owner, repo);
return all;
};
if (module === require.main) {
(async function () {
let normalize = require('../_webi/normalize.js');
let all = await Releases.latest();
all = normalize(all);
// just select the first 5 for demonstration
all.releases = all.releases.slice(0, 5);
console.info(JSON.stringify(all, null, 2));
})();
}

46
sass/releases.js Normal file
View File

@@ -0,0 +1,46 @@
'use strict';
let Releases = module.exports;
let Github = require('../_common/github.js');
let owner = 'sass';
let repo = 'dart-sass';
// https://github.com/sass/dart-sass/releases/
/** @type {Object.<String, String>} */
let archMap = {
ia32: 'x86',
x64: 'amd64',
arm: 'armv7',
};
let keys = Object.keys(archMap);
let keyList = keys.join('|');
let archRe = new RegExp(`\\b(${keyList})\\b`);
Releases.latest = function () {
return Github.getDistributables(null, owner, repo).then(function (all) {
Object.assign(all, {
_names: ['dart-sass', 'sass'],
});
for (let asset of all.releases) {
let m = asset.name.match(archRe);
let arch = m?.[1];
if (arch) {
asset.arch = archMap[arch];
}
}
return all;
});
};
if (module === require.main) {
Releases.latest().then(function (all) {
all = require('../_webi/normalize.js')(all);
// just select the first 5 for demonstration
all.releases = all.releases.slice(0, 10);
console.info(JSON.stringify(all, null, 2));
});
}

Some files were not shown because too many files have changed in this diff Show More