Compare commits

..

1 Commits

Author SHA1 Message Date
AJ ONeal
24fe84b030 doc: show example formatted 'installer' string 2023-10-18 18:23:55 -06:00
273 changed files with 2767 additions and 13113 deletions

View File

@@ -10,7 +10,7 @@ jobs:
strategy:
matrix:
node-version:
#- 20.x
- 20.x
- latest
steps:
- uses: actions/checkout@v3
@@ -23,15 +23,10 @@ jobs:
sh ./_scripts/install-ci-deps
echo "${HOME}/.local/bin" >> $GITHUB_PATH
echo "${HOME}/.local/opt/pwsh" >> $GITHUB_PATH
- run: |
git submodule init
git submodule update
- run: shfmt --version
- run: shellcheck -V
- run: node --version
- run: npm run fmt
- run: npm ci
- run: npm run lint
- env:
GITHUB_TOKEN: ${{ github.token }}
run: npm run test
- run: npm run test

3
.gitmodules vendored
View File

@@ -1,3 +0,0 @@
[submodule "_webi/build-classifier"]
path = _webi/build-classifier
url = https://github.com/webinstall/webi-build-classifier.git

View File

@@ -98,10 +98,9 @@ You just fill in the blanks.
Just create an empty directory and run the tests until you get a good result.
```sh
git clone git@github.com:webinstall/webi-installers.git
pushd ./webi-installers/
git submodule update --init
npm clean-install
git clone git@github.com:webinstall/packages.git
pushd packages
npm install
```
```sh

View File

@@ -1,218 +0,0 @@
'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 getAllReleases(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 = getAllReleases;
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 getAllReleases(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);
});
}

View File

@@ -1,46 +1,35 @@
'use strict';
var GitHubish = require('./githubish.js');
var ghRelease = require('./github.js');
/**
* Lists Gitea Releases (w/ uploaded assets)
* Gets the releases for 'ripgrep'. This function could be trimmed down and made
* for use with any github release.
*
* @param {any} _request - deprecated
* @param {String} owner
* @param {String} repo
* @param {String} baseurl
* @param {String} [username]
* @param {String} [token]
* @param request
* @param {string} owner
* @param {string} repo
* @returns {PromiseLike<any> | Promise<any>}
*/
async function getAllReleases(
_request,
owner,
repo,
baseurl,
username = '',
token = '',
) {
baseurl = `${baseurl}/api/v1`;
let all = await GitHubish.getAllReleases({
owner,
repo,
baseurl,
username,
token,
});
return all;
function getAllReleases(request, owner, repo, baseurl) {
if (!baseurl) {
return Promise.reject('missing baseurl');
}
return ghRelease(request, owner, repo, baseurl + '/api/v1').then(
function (all) {
return all;
},
);
}
module.exports = getAllReleases;
if (module === require.main) {
getAllReleases(
null,
'root',
'pathman',
'https://git.rootprojects.org',
'',
'',
require('@root/request'),
'coolaj86',
'go-pathman',
'https://git.coolaj86.com',
).then(
//getAllReleases(require('@root/request'), 'root', 'serviceman', 'https://git.rootprojects.org').then(
function (all) {

View File

@@ -1,41 +1,91 @@
'use strict';
require('dotenv').config({ path: '.env' });
let GitHubish = require('./githubish.js');
require('dotenv').config();
/**
* Lists GitHub Releases (w/ uploaded assets)
* Gets the releases for 'ripgrep'. This function could be trimmed down and made
* for use with any github release.
*
* @param {any} _request - deprecated
* @param {String} owner
* @param {String} repo
* @param {String} [baseurl]
* @param {String} [username]
* @param {String} [token]
* @param request
* @param {string} owner
* @param {string} repo
* @returns {PromiseLike<any> | Promise<any>}
*/
async function getAllReleases(
_request,
request,
owner,
repo,
baseurl = 'https://api.github.com',
username = process.env.GITHUB_USERNAME || '',
token = process.env.GITHUB_TOKEN || '',
) {
let all = await GitHubish.getAllReleases({
owner,
repo,
baseurl,
username,
token,
});
if (!owner) {
throw new Error('missing owner for repo');
}
if (!repo) {
throw new Error('missing repo name');
}
var req = {
url: `${baseurl}/repos/${owner}/${repo}/releases`,
json: true,
};
// TODO I really don't like global config, find a way to do better
if (process.env.GITHUB_USERNAME) {
req.auth = {
user: process.env.GITHUB_USERNAME,
pass: process.env.GITHUB_TOKEN,
};
}
let resp = await request(req);
if (!resp.ok) {
console.error('Bad Resp Headers:', resp.headers);
console.error('Bad Resp Body:', resp.body);
throw new Error('the elusive releases BOOGEYMAN strikes again');
}
let gHubResp = resp.body;
let all = {
releases: [],
// todo make this ':baseurl' + ':releasename'
download: '',
};
try {
gHubResp.forEach(transformReleases);
} catch (e) {
console.error(e.message);
console.error('Error Headers:', resp.headers);
console.error('Error Body:', resp.body);
throw e;
}
function transformReleases(release) {
release['assets'].forEach(function (asset) {
let name = asset['name'];
all.releases.push({
name: name,
version: release['tag_name'], // TODO tags aren't always semver / sensical
lts: /(\b|_)(lts)(\b|_)/.test(release['tag_name']),
channel: !release['prerelease'] ? 'stable' : 'beta',
date: (release['published_at'] || '').replace(/T.*/, ''),
os: '', // will be guessed by download filename
arch: '', // will be guessed by download filename
ext: '', // will be normalized
download: asset['browser_download_url'],
});
});
}
return all;
}
module.exports = getAllReleases;
if (module === require.main) {
getAllReleases(null, 'BurntSushi', 'ripgrep').then(function (all) {
console.info(JSON.stringify(all, null, 2));
});
getAllReleases(require('@root/request'), 'BurntSushi', 'ripgrep').then(
function (all) {
console.info(JSON.stringify(all, null, 2));
},
);
}

View File

@@ -1,124 +0,0 @@
'use strict';
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.getAllReleases = 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 = await fetch(url, opts);
if (!resp.ok) {
let headers = Array.from(resp.headers);
console.error('Bad Resp Headers:', headers);
let text = await resp.text();
console.error('Bad Resp Body:', text);
let msg = `failed to fetch releases from '${baseurl}' with user '${username}'`;
throw new Error(msg);
}
let respText = await resp.text();
let gHubResp;
try {
gHubResp = JSON.parse(respText);
} catch (e) {
console.error('Bad Resp JSON:', respText);
console.error(e.message);
let msg = `failed to parse releases from '${baseurl}' with user '${username}'`;
throw new Error(msg);
}
let all = {
releases: [],
// todo make this ':baseurl' + ':releasename'
download: '',
};
try {
gHubResp.forEach(transformReleases);
} catch (e) {
console.error(e.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);
}
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.getAllReleases({
owner: 'BurntSushi',
repo: 'ripgrep',
baseurl: 'https://api.github.com',
}).then(function (all) {
console.info(JSON.stringify(all, null, 2));
});
}

View File

@@ -23,7 +23,7 @@ install:
```text
~/.config/envman/PATH.env
~/.local/bin/foo
~/.local/opt/foo/
~/.local/opt/foo
```
## Cheat Sheet

View File

@@ -12,19 +12,17 @@ var repo = 'ripgrep';
/** **/
/******************************************************************************/
module.exports = async function (request) {
let all = await github(request, owner, repo);
return all;
module.exports = function (request) {
return github(request, owner, repo).then(function (all) {
return all;
});
};
if (module === require.main) {
(async function () {
let request = require('@root/request');
let normalize = require('../_webi/normalize.js');
let all = await module.exports(request);
all = normalize(all);
module.exports(require('@root/request')).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
_webi/bootstrap.bat Normal file
View File

@@ -0,0 +1,28 @@
setlocal
@echo off
pushd "%userprofile%" || goto :error
IF NOT EXIST .local (
mkdir .local || goto :error
)
IF NOT EXIST .local\bin (
mkdir .local\bin || goto :error
)
rem {{ baseurl }}
rem {{ version }}
pushd .local\bin || goto :error
if NOT EXIST webi.bat (
rem without SilentlyContinue this is SLOOOOOOOOOOOOOOOW!
powershell $ProgressPreference = 'SilentlyContinue'; Invoke-WebRequest https://webinstall.dev/packages/_webi/webi.bat -OutFile webi.bat || goto :error
)
call .\webi {{ exename }} || goto :error
rem pathman add "%userprofile%\.local\bin" >nul 2>&1 || goto :error
pathman add "%userprofile%\.local\bin" || goto :error
popd || goto :error
popd
goto :EOF
:error
echo Failed with error #%errorlevel%.
exit /b %errorlevel%

View File

@@ -7,6 +7,6 @@
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"
curl.exe -s -A "windows" "$Env:WEBI_HOST/packages/_webi/webi-pwsh.ps1" -o "$Env:USERPROFILE\.local\bin\webi-pwsh.ps1"
Set-ExecutionPolicy -Scope Process Bypass
& "$Env:USERPROFILE\.local\bin\webi-pwsh.ps1" "{{ exename }}"

234
_webi/bootstrap.sh Normal file
View File

@@ -0,0 +1,234 @@
#!/bin/sh
#<pre>
############################################################
# <h1>Cheat Sheet at CHEATSHEET_URL</h1>
# <meta http-equiv="refresh" content="3; URL='CHEATSHEET_URL'" />
############################################################
#set -x
__install_webi() {
#WEBI_PKG=
#WEBI_HOST=https://webinstall.dev
export WEBI_HOST
echo ""
printf "Thanks for using webi to install '\e[32m%s\e[0m' on '\e[33m%s/%s\e[0m'.\n" "${WEBI_PKG-}" "$(uname -s)/$(uname -r)" "$(uname -m)"
echo "Have a problem? Experience a bug? Please let us know:"
printf " \e[2m\e[36mhttps://github.com/webinstall/webi-installers/issues\e[0m\n"
echo ""
printf "\e[35mLovin'\e[0m it? Say thanks with a \e[1m\e[33mStar on GitHub\e[0m:\n"
printf " \e[36mhttps://github.com/webinstall/webi-installers\e[0m\n"
echo ""
WEBI_WELCOME=true
export WEBI_WELCOME
set -e
set -u
mkdir -p "$HOME/.local/bin"
cat << EOF > "$HOME/.local/bin/webi"
#!/bin/sh
set -e
set -u
#set -x
__webi_main() {
export WEBI_TIMESTAMP="\$(date +%F_%H-%M-%S)"
export _webi_tmp="\${_webi_tmp:-\$(mktemp -d -t webi-\$WEBI_TIMESTAMP.XXXXXXXX)}"
if [ -n "\${_WEBI_PARENT:-}" ]; then
export _WEBI_CHILD=true
else
export _WEBI_CHILD=
fi
export _WEBI_PARENT=true
##
## Detect acceptable package formats
##
my_ext=""
set +e
# NOTE: the order here is least favorable to most favorable
if [ -n "\$(command -v pkgutil)" ]; then
my_ext="pkg,\$my_ext"
fi
# disable this check for the sake of building the macOS installer on Linux
#if [ -n "\$(command -v diskutil)" ]; then
# note: could also detect via hdiutil
my_ext="dmg,\$my_ext"
#fi
if [ -n "\$(command -v git)" ]; then
my_ext="git,\$my_ext"
fi
if [ -n "\$(command -v unxz)" ]; then
my_ext="xz,\$my_ext"
fi
if [ -n "\$(command -v unzip)" ]; then
my_ext="zip,\$my_ext"
fi
# for mac/linux 'exe' refers to the uncompressed binary without extension
my_ext="exe,\$my_ext"
if [ -n "\$(command -v tar)" ]; then
my_ext="tar,\$my_ext"
fi
my_ext="\$(echo "\$my_ext" | sed 's/,$//')" # nix trailing comma
set -e
##
## Detect http client
##
set +e
export WEBI_CURL="\$(command -v curl)"
export WEBI_WGET="\$(command -v wget)"
set -e
my_libc=''
if ldd /bin/ls 2> /dev/null | grep -q 'musl' 2> /dev/null; then
my_libc=' musl-native'
fi
export WEBI_HOST="\${WEBI_HOST:-https://webinstall.dev}"
export WEBI_UA="\$(uname -s)/\$(uname -r) \$(uname -m)/unknown\${my_libc}"
webinstall() {
my_package="\${1:-}"
if [ -z "\$my_package" ]; then
echo >&2 "Usage: webi <package>@<version> ..."
echo >&2 "Example: webi node@lts rg"
exit 1
fi
export WEBI_BOOT="\$(mktemp -d -t "\$my_package-bootstrap.\$WEBI_TIMESTAMP.XXXXXXXX")"
my_installer_url="\$WEBI_HOST/api/installers/\$my_package.sh?formats=\$my_ext"
if [ -n "\$WEBI_CURL" ]; then
if ! curl -fsSL "\$my_installer_url" -H "User-Agent: curl \$WEBI_UA" \\
-o "\$WEBI_BOOT/\$my_package-bootstrap.sh"; then
echo >&2 "error fetching '\$my_installer_url'"
exit 1
fi
else
if ! wget -q "\$my_installer_url" --user-agent="wget \$WEBI_UA" \\
-O "\$WEBI_BOOT/\$my_package-bootstrap.sh"; then
echo >&2 "error fetching '\$my_installer_url'"
exit 1
fi
fi
(
cd "\$WEBI_BOOT"
sh "\$my_package-bootstrap.sh"
)
rm -rf "\$WEBI_BOOT"
}
show_path_updates() {
if test -z "\${_WEBI_CHILD}"; then
if test -f "\$_webi_tmp/.PATH.env"; then
my_paths=\$(sort -u < "\$_webi_tmp/.PATH.env")
if test -n "\$my_paths"; then
printf 'PATH.env updated with:\\n'
printf "%s\\n" "\$my_paths"
printf '\\n'
printf "\\e[1m\\e[35mTO FINISH\\e[0m: copy, paste & run the following command:\\n"
printf "\\n"
printf " \\e[1m\\e[32msource ~/.config/envman/PATH.env\\e[0m\\n"
printf " (newly opened terminal windows will update automatically)\\n"
fi
rm -f "\$_webi_tmp/.PATH.env"
fi
fi
}
version() {
my_version=v1.1.15
printf "\\e[35mwebi\\e[32m %s\\e[0m Copyright 2020+ AJ ONeal\\n" "\${my_version}"
printf " \\e[36mhttps://webinstall.dev/webi\\e[0m\\n"
}
# show help if no params given or help flags are used
usage() {
echo ""
version
echo ""
printf "\\e[1mSUMMARY\\e[0m\\n"
echo " Webi is the best way to install the modern developer tools you love."
echo " It's fast, easy-to-remember, and conflict free."
echo ""
printf "\\e[1mUSAGE\\e[0m\\n"
echo " webi <thing1>[@version] [thing2] ..."
echo ""
printf "\\e[1mUNINSTALL\\e[0m\\n"
echo " Almost everything that is installed with webi is scoped to"
echo " ~/.local/opt/<thing1>, so you can remove it like so:"
echo ""
echo " rm -rf ~/.local/opt/<thing1>"
echo " rm -f ~/.local/bin/<thing1>"
echo ""
echo " Some packages have special uninstall instructions, check"
echo " https://webinstall.dev/<thing1> to be sure."
echo ""
printf "\\e[1mFAQ\\e[0m\\n"
printf " See \\e[34mhttps://webinstall.dev/faq\\e[0m\\n"
echo ""
printf "\\e[1mALWAYS REMEMBER\\e[0m\\n"
echo " Friends don't let friends use brew for simple, modern tools that don't need it."
echo " (and certainly not apt either **shudder**)"
echo ""
}
if [ \$# -eq 0 ] || echo "\$1" | grep -q -E '^(-V|--version|version)$'; then
version
exit 0
fi
if echo "\$1" | grep -q -E '^(-h|--help|help)$'; then
usage "\$@"
exit 0
fi
for pkgname in "\$@"; do
webinstall "\$pkgname"
done
show_path_updates
}
__webi_main "\$@"
EOF
chmod a+x "$HOME/.local/bin/webi"
if [ -n "${WEBI_PKG-}" ]; then
"$HOME/.local/bin/webi" "${WEBI_PKG}"
else
echo ""
echo "Hmm... no WEBI_PKG was specified. This is probably an error in the script."
echo ""
echo "Please open an issue with this information: Package '${WEBI_PKG-}' on '$(uname -s)/$(uname -r) $(uname -m)'"
echo " https://github.com/webinstall/packages/issues"
echo ""
fi
}
__install_webi

View File

@@ -1,73 +0,0 @@
'use strict';
let Path = require('node:path');
let BuildsCacher = require('./builds-cacher.js');
// let Parallel = require('./parallel.js');
var INSTALLERS_DIR = Path.join(__dirname, '..');
var CACHE_DIR = Path.join(__dirname, '../_cache');
async function main() {
let bc = BuildsCacher.create({
caches: CACHE_DIR,
installers: INSTALLERS_DIR,
});
bc.freshenRandomPackage(600 * 1000);
// let dirs = await bc.getProjectsByType();
// let projNames = Object.keys(dirs.valid);
let lastUpdate;
let projName = 'k9s';
{
let packages = await bc.getPackages({
//Releases: Releases,
name: projName,
date: new Date(),
});
lastUpdate = packages.updated;
console.info(
`Last update for '${projName}': ${packages.updated} (${packages.releases.length} assets)`,
);
}
console.info('Waiting 5s');
{
setTimeout(async function () {
let packages = await bc.getPackages({
//Releases: Releases,
name: projName,
date: new Date(),
});
console.info(
`Last update for '${projName}': ${packages.updated} (${packages.releases.length} assets)`,
);
if (lastUpdate < packages.updated) {
console.info(`PASS`);
} else {
console.info(`MAYBE fail`);
}
}, 5 * 1000);
}
//let parallel = 25;
//await Parallel.run(parallel, projNames, getAll);
//async function getAll(name) {
// void (await bc.getPackages({
// //Releases: Releases,
// name: name,
// date: new Date(),
// }));
//}
}
main()
.then(function () {
console.log('Done');
})
.catch(function (e) {
console.error(e.stack || e);
process.exit(1);
});

View File

@@ -1,995 +0,0 @@
'use strict';
var BuildsCacher = module.exports;
let Fs = require('node:fs/promises');
let Path = require('node:path');
let HostTargets = require('./build-classifier/host-targets.js');
let Lexver = require('./build-classifier/lexver.js');
let Triplet = require('./build-classifier/triplet.js');
let request = require('@root/request');
var ALIAS_RE = /^alias: (\w+)$/m;
var LEGACY_ARCH_MAP = {
'*': 'ANYARCH',
arm64: 'aarch64',
armv6l: 'armv6',
armv7l: 'armv7',
amd64: 'x86_64',
mipsle: 'mipsel',
mips64le: 'mips64el',
mipsr6le: 'mipsr6el',
mips64r6le: 'mips64r6el',
// yes... el for arm and mips, but le for ppc
// (perhaps the joke got old?)
ppc64el: 'ppc64le',
386: 'x86',
};
var LEGACY_OS_MAP = {
'*': 'ANYOS',
macos: 'darwin',
posix: 'posix_2017',
};
var TERMS_META = [
// pattern
'{ARCH}',
'{EXT}',
'{LIBC}',
'{NAME}',
'{OS}',
'{VENDOR}',
// // os-/arch-indepedent
// 'ANYARCH',
// 'ANYOS',
// // libc
// 'none',
// channel
'beta',
'dev',
'preview',
'stable',
];
/** @typedef {String} TripletString - {arch}-{vendor}-{os}-{libc} */
/** @typedef {String} VersionString */
/** @typedef {Object.<VersionString, Array<BuildAsset>>} PackagesByRelease */
/**
* @typedef ProjectInfo
* @prop {Array<BuildAsset>} releases
* @prop {Array<BuildAsset>} packages
* @prop {Object.<TripletString, PackagesByRelease>} releasesByTriplet
* @prop {Array<import('./build-classifier/types.js').ArchString>} arches
* @prop {Array<import('./build-classifier/types.js').OsString>} oses
* @prop {Array<import('./build-classifier/types.js').LibcString>} libcs
* @prop {Array<String>} channels
* @prop {Array<String>} formats
* @prop {Array<String>} triplets
* @prop {Array<String>} versions
* @prop {Array<String>} lexvers
* @prop {Object.<String, String>} lexversMap
*/
/**
* @typedef BuildAsset
* @prop {String} name
* @prop {String} version
* @prop {Boolean} lts
* @prop {String} date
* @prop {String} arch
* @prop {String} os
* @prop {String} libc
* @prop {String} ext
* @prop {String} download
*/
/**
* @typedef VersionTarget
* @prop {String} version
* @prop {Boolean} lts
* @prop {String} channel
*/
/** @typedef {TargetTriplet & HostTargetPartial} HostTarget */
/** @typedef {import('./build-classifier/types.js').TargetTriplet} TargetTriplet */
/**
* @typedef HostTargetPartial
* @prop {String} target.triplet - os-vendor-arch-libc
* @prop {Error} target.error
*/
async function getPartialHeader(path) {
let readme = `${path}/README.md`;
let head = await readFirstBytes(readme).catch(function (err) {
if (err.code !== 'ENOENT') {
console.warn(`warn: ${path}: ${err.message}`);
}
return null;
});
return head;
}
// let fsOpen = util.promisify(Fs.open);
// let fsRead = util.promisify(Fs.read);
async function readFirstBytes(path) {
let start = 0;
let n = 1024;
let fh = await Fs.open(path, 'r');
let buf = Buffer.alloc(n);
let result = await fh.read(buf, start, n);
let str = result.buffer.toString('utf8');
await fh.close();
return str;
}
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(request);
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;
}
let bc = {};
bc.ALL_TERMS = ALL_TERMS;
bc.orphanTerms = Object.assign({}, bc.ALL_TERMS);
bc.unknownTerms = {};
bc.usedTerms = {};
bc.formats = [];
bc._triplets = {};
bc._targetsByBuildIdCache = {};
bc._caches = {};
bc._staleAge = 15 * 60 * 1000;
bc._allFormats = {};
bc._allTriplets = {};
for (let term of TERMS_META) {
delete bc.orphanTerms[term];
}
bc.getProjectsByType = async function () {
let dirs = {
hidden: {},
errors: {},
alias: {},
invalid: {},
selfhosted: {},
valid: {},
};
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;
}
return dirs;
};
/**
* Get project type and detail - alias, selfhosted, valid (and the invalids)
* @param {String} name - filename
*/
bc.getProjectType = async function (name) {
let filepath = Path.join(installersDir, name);
let entry;
try {
entry = await Fs.lstat(filepath);
Object.assign(entry, { name: name });
} catch (e) {
return { type: 'errors', detail: 'not found' };
}
let info = await bc.getProjectTypeByEntry(entry);
return info;
};
/**
* Get project type and detail - alias, selfhosted, valid (and the invalids)
* @param {fs.Stats|fs.Dirent} entry
*/
bc.getProjectTypeByEntry = async function (entry) {
let path = Path.join(installersDir, entry.name);
// skip non-installer dirs
if (entry.isSymbolicLink()) {
let link = await Fs.readlink(path);
return { type: 'alias', detail: link };
}
if (!entry.isDirectory()) {
return { type: 'hidden', detail: '!directory' };
}
if (entry.name === 'node_modules') {
return { type: 'hidden', detail: 'node_modules' };
}
if (entry.name.startsWith('_')) {
return { type: 'hidden', detail: '_*' };
}
if (entry.name.startsWith('.')) {
return { type: 'hidden', detail: '.*' };
}
if (entry.name.startsWith('~')) {
return { type: 'hidden', detail: '~*' };
}
if (entry.name.endsWith('~')) {
return { type: 'hidden', detail: '*~' };
}
// skip invalid installers
let head = await getPartialHeader(path);
if (!head) {
return { type: 'invalid', detail: '!README.md' };
}
let alias = head.match(ALIAS_RE);
if (alias) {
let link = alias[1];
return { type: 'alias', detail: link };
}
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 };
};
// 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 ({ Releases, name, date }) {
if (!date) {
date = new Date();
}
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;
{
let secondsStr = await Fs.readFile(tsFile, 'ascii').catch(function (err) {
if (err.code !== 'ENOENT') {
throw err;
}
return '0';
});
secondsStr = secondsStr.trim();
let seconds = parseFloat(secondsStr) || 0;
let ms = seconds * 1000;
tsDate = new Date(ms);
}
let projInfo = bc._caches[name];
let meta = {
// version info
versions: projInfo?.versions || [],
lexvers: projInfo?.lexvers || [],
lexversMap: projInfo?.lexversMap || {},
// culled release assets
packages: projInfo?.packages || [],
releasesByTriplet: projInfo?.releasesByTriplet || {},
// target info
triplets: projInfo?.triplets || [],
oses: projInfo?.oses || [],
arches: projInfo?.arches || [],
libcs: projInfo?.libcs || [],
formats: projInfo?.formats || [],
// TODO channels: projInfo?.channels || [],
};
if (!projInfo) {
let json = await Fs.readFile(dataFile, 'ascii').catch(
async function (err) {
if (err.code !== 'ENOENT') {
throw err;
}
return null;
},
);
try {
projInfo = JSON.parse(json);
} catch (e) {
console.error(`error: ${dataFile}:\n\t${e.message}`);
projInfo = null;
}
}
if (!projInfo) {
projInfo = await getLatestBuilds(Releases, installersDir, cacheDir, name);
}
let latestProjInfo = await BuildsCacher.transformAndUpdate(
name,
projInfo,
meta,
tsDate,
bc,
);
bc._caches[name] = 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.
* Actually used (as per node _webi/lint-builds.js):
* .7z
* .app.zip
* .dmg
* .exe
* .exe.xz
* .git
* .gz
* .msi
* .pkg
* .pkg.tar.zst
* .sh
* .tar.gz
* .tar.xz
* .xz
* .zip
*/
bc.getSortedFormats = function (formats) {
/* jshint maxcomplexity: 25 */
formats.sort();
let id = formats.join(',');
if (bc._allFormats[id]) {
return bc._allFormats[id];
}
// we don't know how to handle any of these yet
// let exclude = [];
// let isAndroid = false;
// if (!isAndroid) {
// exclude.push('.apk');
// }
// let isDebian = false;
// if (!isDebian) {
// exclude.push('.deb');
// }
// let isEnterpriseLinux = false;
// if (!isEnterpriseLinux) {
// exclude.push('.rpm');
// }
// let isArch = false;
// if (!isArch) {
// exclude.push('.pkg.tar.zst');
// }
let hasExe = formats.includes('exe') || formats.includes('.exe');
/** @type {Array<String>} */
let exts = [];
let hasXz = formats.includes('xz') || formats.includes('.xz');
if (hasXz) {
exts.push('.tar.xz');
if (hasExe) {
exts.push('.exe.xz');
}
exts.push('.xz');
}
let hasZst = formats.includes('zst') || formats.includes('.zst');
if (hasZst) {
exts.push('.tar.zst');
exts.push('.zst');
}
let hasZip = formats.includes('zip') || formats.includes('.zip');
if (hasZip) {
exts.push('.zip');
}
let has7z = false;
if (has7z) {
exts.push('.7z');
}
// let hasBz2 = formats.includes('bz2') || formats.includes('.bz2');
// if (hasBz2) {
// exts.push('.bz2');
// }
if (hasExe) {
if (!hasZip) {
exts.push('.zip');
}
exts.push('.tar.gz');
exts.push('.gz');
exts.push('.exe');
exts.push('.msi');
//exts.push('.msixbundle');
} else {
exts.push('.tar.gz');
exts.push('.gz');
exts.push('.sh');
}
exts.push('.git');
// Fallbacks
// (we include everything to bubble an extract error over not found)
exts.push('.app.zip');
exts.push('.dmg');
exts.push('.pkg');
if (!hasXz) {
exts.push('.tar.xz');
if (hasExe) {
exts.push('.exe.xz');
}
exts.push('.xz');
}
if (!hasZip) {
if (!hasExe) {
exts.push('.zip');
}
}
if (!hasZst) {
exts.push('.tar.zst');
exts.push('.zst');
}
if (!has7z) {
exts.push('.7z');
}
// if (!hasRar) {
// exts.push('.rar');
// }
// exts.push('.tar.bz2');
// exts.push('.bz2');
bc._allFormats[id] = exts;
return exts;
};
bc.selectPackage = function (packages, formats) {
if (packages.length === 1) {
return packages[0];
}
let exts = bc.getSortedFormats(formats);
for (let ext of exts) {
for (let build of packages) {
if (build.ext === ext) {
return build;
}
}
}
return packages[0];
};
/**
* @param {ProjectInfo} projInfo
* @param {HostTarget} hostTarget
*/
bc.enumerateLatestVersions = function (projInfo, hostTarget) {
let lexPrefix = '';
let matchInfo = Lexver.matchSorted(projInfo.lexvers, lexPrefix);
let verInfo = {
default: projInfo.lexversMap[matchInfo.default],
previous: projInfo.lexversMap[matchInfo.previous],
stable: projInfo.lexversMap[matchInfo.stable],
latest: projInfo.lexversMap[matchInfo.latest],
};
return verInfo;
};
/**
* @param {ProjectInfo} projInfo
* @param {HostTarget} hostTarget
* @param {VersionTarget} verTarget
*/
bc.findMatchingPackages = function (projInfo, hostTarget, verTarget) {
let matchInfo = bc._enumerateVersions(projInfo, verTarget.version);
let triplets = bc._enumerateTriplets(hostTarget);
//console.log('dbg: matchInfo', matchInfo);
if (matchInfo) {
for (let _triplet of triplets) {
let targetReleases = projInfo.releasesByTriplet[_triplet];
if (!targetReleases) {
continue;
}
// 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 matchInfo.matches) {
let ver = projInfo.lexversMap[matchver] || matchver;
let packages = targetReleases[ver];
if (!packages) {
continue;
}
let match = {
triplet: _triplet,
packages: packages,
latest: projInfo.versions[0],
version: ver,
versions: matchInfo,
};
return match;
}
}
return null;
}
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;
}
let pkg = packages[0];
if (verTarget.lts) {
if (!pkg.lts) {
continue;
}
let match = {
triplet: _triplet,
packages: packages,
latest: projInfo.versions[0],
version: ver,
versions: matchInfo,
};
return match;
}
let wantChannel = verTarget.channel || 'stable';
let isChannel = pkg.channel || 'stable';
if (wantChannel === 'stable') {
if (isChannel !== 'stable') {
continue;
}
}
// latest, beta, alpha, rc, preview
let match = {
triplet: _triplet,
packages: packages,
latest: projInfo.versions[0],
version: ver,
versions: matchInfo,
};
return match;
}
}
return null;
};
bc._enumerateTriplets = function (hostTarget) {
let id = [hostTarget.os, hostTarget.arch, hostTarget.libc].join(',');
let triplets = bc._allTriplets[id] || [];
if (triplets.length > 0) {
return triplets;
}
let oses = [];
if (hostTarget.os === 'windows') {
oses = ['ANYOS', 'windows'];
} else if (hostTarget.os === 'android') {
oses = ['ANYOS', 'posix_2017', 'android', 'linux'];
} else {
oses = ['ANYOS', 'posix_2017', hostTarget.os];
}
let waterfall = HostTargets.WATERFALL[hostTarget.os] || {};
let arches = waterfall[hostTarget.arch] ||
HostTargets.WATERFALL.ANYOS[hostTarget.arch] || [hostTarget.arch];
arches = ['ANYARCH'].concat(arches);
let libcs = waterfall[hostTarget.libc] ||
HostTargets.WATERFALL.ANYOS[hostTarget.libc] || [hostTarget.libc];
for (let os of oses) {
for (let arch of arches) {
for (let libc of libcs) {
let triplet = `${os}-${arch}-${libc}`;
triplets.push(triplet);
}
}
}
bc._allTriplets[id] = triplets;
return triplets;
};
bc._enumerateVersions = function (projInfo, ver) {
if (!ver) {
return null;
}
let lexPrefix = Lexver.parsePrefix(ver);
let matchInfo = Lexver.matchSorted(projInfo.lexvers, lexPrefix);
return matchInfo;
};
return bc;
};
BuildsCacher._classify = function (bc, projInfo, build) {
/* jshint maxcomplexity: 25 */
let maybeInstallable = Triplet.maybeInstallable(projInfo, build);
if (!maybeInstallable) {
return null;
}
if (LEGACY_OS_MAP[build.os]) {
build.os = LEGACY_OS_MAP[build.os];
}
if (LEGACY_ARCH_MAP[build.arch]) {
build.arch = LEGACY_ARCH_MAP[build.arch];
}
// because some packages are shimmed to match a single download against
let preTarget = Object.assign({ os: '', arch: '', libc: '' }, build);
let targetId = `${preTarget.os}:${preTarget.arch}:${preTarget.libc}`;
let buildId = `${projInfo.name}:${targetId}@${build.download}`;
//console.log(`dbg: buildId`, buildId);
let target = bc._targetsByBuildIdCache[buildId];
if (target) {
Object.assign(build, { target: target, triplet: target.triplet });
return target;
}
let pattern = Triplet.toPattern(projInfo, build);
//console.log(`dbg: pattern`, pattern);
if (!pattern) {
let err = new Error(`no pattern generated for ${projInfo.name}`);
err.code = 'E_BUILD_NO_PATTERN';
target = { error: err };
bc._targetsByBuildIdCache[buildId] = target;
return target;
}
let rawTerms = pattern.split(/[_\{\}\/\.\-]+/g);
//console.log(`dbg: rawTerms`, rawTerms);
for (let term of rawTerms) {
delete bc.orphanTerms[term];
bc.usedTerms[term] = true;
}
// {NAME}/{NAME}-{VER}-Windows-x86_64_v2-musl.exe =>
// {NAME}.windows.x86_64v2.musl.exe
let terms = Triplet.patternToTerms(pattern);
//console.log(`dbg: terms`, terms);
if (!terms.length) {
let err = new Error(`'${terms}' was trimmed to ''`);
target = { error: err };
bc._targetsByBuildIdCache[buildId] = target;
return target;
}
for (let term of terms) {
if (!term) {
continue;
}
if (bc.ALL_TERMS[term]) {
delete bc.orphanTerms[term];
bc.usedTerms[term] = true;
continue;
}
bc.unknownTerms[term] = true;
}
// {NAME}.windows.x86_64v2.musl.exe
// windows-x86_64_v2-musl
target = { triplet: '' };
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}`;
{
// TODO I don't love this hidden behavior
// perhaps classify should just happen when the package is loaded
// (and the sanity error should be removed, or thrown after the loop is complete)
let hasTriplet = projInfo.triplets.includes(target.triplet);
if (!hasTriplet) {
projInfo.triplets.push(target.triplet);
}
let hasOs = projInfo.oses.includes(target.os);
if (!hasOs) {
projInfo.oses.push(target.os);
}
let hasArch = projInfo.arches.includes(target.arch);
if (!hasArch) {
projInfo.arches.push(target.arch);
}
let hasLibc = projInfo.libcs.includes(target.libc);
if (!hasLibc) {
projInfo.libcs.push(target.libc);
}
if (!build.ext) {
build.ext = Triplet.buildToPackageType(build);
}
if (build.ext) {
if (!build.ext.startsWith('.')) {
build.ext = `.${build.ext}`;
}
}
let hasExt = projInfo.formats.includes(build.ext);
if (!hasExt) {
projInfo.formats.push(build.ext);
}
let hasGlobalExt = bc.formats.includes(build.ext);
if (!hasGlobalExt) {
bc.formats.push(build.ext);
}
}
bc._triplets[target.triplet] = true;
bc._targetsByBuildIdCache[buildId] = target;
let triple = [target.arch, target.vendor, target.os, target.libc];
for (let term of triple) {
if (!bc.ALL_TERMS[term]) {
throw new Error(
`[SANITY FAIL] '${projInfo.name}' '${target.triplet}' generated unknown term '${term}'`,
);
}
delete bc.orphanTerms[term];
bc.usedTerms[term] = true;
}
return target;
};
BuildsCacher.transformAndUpdate = function (name, projInfo, meta, date, bc) {
meta.packages = [];
let updated = date.valueOf();
Object.assign(projInfo, { name, updated }, meta);
for (let build of projInfo.releases) {
let buildTarget = BuildsCacher._classify(bc, projInfo, build);
if (!buildTarget) {
// ignore known, non-package extensions
continue;
}
if (buildTarget.error) {
let err = buildTarget.error;
let code = err.code || '';
console.error(`[ERROR]: ${code} ${projInfo.name}: ${build.name}`);
console.error(`>>> ${err.message} <<<`);
console.error(projInfo);
console.error(build);
console.error(`^^^ ${err.message} ^^^`);
console.error(err.stack);
continue;
}
if (!build.name) {
build.name = build.download.replace(/.*\//, '');
}
build.target = buildTarget;
meta.packages.push(build);
}
BuildsCacher.updateReleasesByTriplet(meta);
BuildsCacher.updateAndSortVersions(projInfo, meta);
Object.assign(projInfo, { name, updated }, meta);
return projInfo;
};
// TODO
// - tag channels
BuildsCacher.updateAndSortVersions = function (projInfo, meta) {
for (let build of projInfo.packages) {
let hasVersion = meta.versions.includes(build.version);
if (!hasVersion) {
build.lexver = Lexver.parseVersion(build.version);
meta.lexversMap[build.lexver] = build.version;
}
}
meta.lexvers = Object.keys(meta.lexversMap);
meta.lexvers.sort();
meta.lexvers.reverse();
meta.versions = [];
for (let lexver of meta.lexvers) {
let version = meta.lexversMap[lexver];
meta.versions.push(version);
}
projInfo.packages.sort(function (a, b) {
if (a.lexver > b.lexver) {
return -1;
}
if (a.lexver < b.lexver) {
return 1;
}
return 0;
});
};
BuildsCacher.updateReleasesByTriplet = function (meta) {
for (let build of meta.packages) {
let target = build.target;
let triplet = `${target.os}-${target.arch}-${target.libc}`;
if (!meta.releasesByTriplet[triplet]) {
meta.releasesByTriplet[triplet] = {};
}
let buildsByRelease = meta.releasesByTriplet[triplet];
if (!buildsByRelease[build.version]) {
buildsByRelease[build.version] = [];
}
let packages = buildsByRelease[build.version];
packages.push(build);
}
};

View File

@@ -1,41 +0,0 @@
'use strict';
let Builds = module.exports;
let Path = require('node:path');
let BuildsCacher = require('./builds-cacher.js');
// let HostTargets = require('./build-classifier/host-targets.js');
let Parallel = require('./parallel.js');
var INSTALLERS_DIR = Path.join(__dirname, '..');
var CACHE_DIR = Path.join(__dirname, '../_cache');
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);
let parallel = 25;
await Parallel.run(parallel, projNames, getAll);
async function getAll(name) {
void (await bc.getPackages({
//Releases: Releases,
name: name,
date: new Date(),
}));
}
};
Builds.enumerateLatestVersions = bc.enumerateLatestVersions;
Builds.findMatchingPackages = bc.findMatchingPackages;
Builds.getPackage = bc.getPackages;
Builds.getProjectType = bc.getProjectType;
Builds.selectPackage = bc.selectPackage;

View File

@@ -1,92 +0,0 @@
'use strict';
let Path = require('node:path');
// let Builds = require('./builds.js');
let BuildsCacher = require('./builds-cacher.js');
let Triplet = require('./build-classifier/triplet.js');
let request = require('@root/request');
async function main() {
let projName = process.argv[2];
if (!projName) {
console.error(``);
console.error(`USAGE`);
console.error(``);
console.error(` classify-one <project-name>`);
console.error(``);
console.error(`EXAMPLE`);
console.error(``);
console.error(` classify-one caddy`);
console.error(``);
return;
}
let tsDate = new Date(0);
let meta = {
// version info
versions: [],
lexvers: [],
lexversMap: {},
// culled release assets
packages: [],
releasesByTriplet: {},
// target info
triplets: [],
oses: [],
arches: [],
libcs: [],
formats: [],
// TODO channels: [],
};
let installersDir = Path.join(__dirname, '..');
let Releases = require(`${installersDir}/${projName}/releases.js`);
if (!Releases.latest) {
Releases.latest = Releases;
}
let projInfo = await Releases.latest(request);
// let packages = await Builds.getPackage({ name: projName });
// console.log(packages);
let bc = {};
bc.ALL_TERMS = Triplet.TERMS_PRIMARY_MAP;
bc.orphanTerms = Object.assign({}, bc.ALL_TERMS);
bc.unknownTerms = {};
bc.usedTerms = {};
bc.formats = [];
bc._targetsByBuildIdCache = {};
bc._triplets = {};
let transformed = BuildsCacher.transformAndUpdate(
projName,
projInfo,
meta,
tsDate,
bc,
);
console.log(`[DEBUG] transformed`);
let sample = transformed.packages.slice(0, 20);
console.log('packages:', sample, ':packages');
console.log(
'releasesByTriplet:',
transformed.releasesByTriplet['linux-x86_64-none'][transformed.versions[0]],
':releasesByTriplet',
);
console.log('versions:', transformed.versions, ':versions');
console.log('triplets:', transformed.triplets, ':triplets');
console.log('oses:', transformed.oses, ':oses');
console.log('arches:', transformed.arches, ':arches');
console.log('libcs:', transformed.libcs, ':libcs');
console.log('formats:', transformed.formats, ':formats');
console.log(Object.keys(transformed));
}
main().catch(function (err) {
console.error('Error:');
console.error(err);
});

View File

@@ -1,349 +0,0 @@
#!/bin/sh
#<pre>
############################################################
# <h1>Cheat Sheet at CHEATSHEET_URL</h1>
# <meta http-equiv="refresh" content="3; URL='CHEATSHEET_URL'" />
############################################################
export WEBI_PKG=webi
export WEBI_HOST=https://webinstall.dev
export WEBI_CHECKSUM=06a7fb9f
#########################################
# #
# Display Debug Info in Case of Failure #
# #
#########################################
fn_show_welcome() { (
echo ""
echo ""
# invert t_task and t_pkg for top-level welcome message
printf -- ">>> %s %s <<<\n" \
"$(t_pkg 'Welcome to') $(t_task 'Webi')$(t_pkg '!')" \
"$(t_dim "- modern tools, instant installs.")"
echo " We expect your experience to be $(t_em 'absolutely perfect')!"
echo ""
echo " $(t_attn 'Success')? Star it! $(t_url 'https://github.com/webinstall/webi-installers')"
echo " $(t_attn 'Problem')? Report it: $(t_url 'https://github.com/webinstall/webi-installers/issues')"
echo " $(t_dim "(your system is") $(t_host "$(fn_get_os)")/$(t_host "$(uname -m)") $(t_dim "with") $(t_host "$(fn_get_libc)") $(t_dim "&") $(t_host "$(fn_get_http_client_name)")$(t_dim ")")"
sleep 0.2
); }
fn_get_os() { (
# Ex:
# GNU/Linux
# Android
# Linux (often Alpine, musl)
# Darwin
b_os="$(uname -o 2> /dev/null || echo '')"
b_sys="$(uname -s)"
if test -z "${b_os}" || test "${b_os}" = "${b_sys}"; then
# ex: 'Darwin' (and plain, non-GNU 'Linux')
echo "${b_sys}"
return 0
fi
if echo "${b_os}" | grep -q "${b_sys}"; then
# ex: 'GNU/Linux'
echo "${b_os}"
return 0
fi
# ex: 'Android/Linux'
echo "${b_os}/${b_sys}"
); }
fn_get_libc() { (
# Ex:
# musl
# libc
if ldd /bin/ls 2> /dev/null | grep -q 'musl' 2> /dev/null; then
echo 'musl'
elif fn_get_os | grep -q 'GNU|Linux'; then
echo 'gnu'
else
echo 'libc'
fi
); }
fn_get_http_client_name() { (
# Ex:
# curl
# curl+wget
b_client=""
if command -v curl > /dev/null; then
b_client="curl"
fi
if command -v wget > /dev/null; then
if test -z "${b_client}"; then
b_client="wget"
else
b_client="curl+wget"
fi
fi
echo "${b_client}"
); }
#########################################
# #
# For Making the Display Nice #
# #
#########################################
# Term Types
t_cmd() { (fn_printf '\e[2m\e[35m%s\e[39m\e[22m' "${1}"); }
t_host() { (fn_printf '\e[2m\e[33m%s\e[39m\e[22m' "${1}"); }
t_link() { (fn_printf '\e[1m\e[36m%s\e[39m\e[22m' "${1}"); }
t_path() { (fn_printf '\e[2m\e[32m%s\e[39m\e[22m' "${1}"); }
t_pkg() { (fn_printf '\e[1m\e[32m%s\e[39m\e[22m' "${1}"); }
t_task() { (fn_printf '\e[36m%s\e[39m' "${1}"); }
t_url() { (fn_printf '\e[2m%s\e[22m' "${1}"); }
# Levels
t_info() { (fn_printf '\e[1m\e[36m%s\e[39m\e[22m' "${1}"); }
t_attn() { (fn_printf '\e[1m\e[33m%s\e[39m\e[22m' "${1}"); }
t_warn() { (fn_printf '\e[1m\e[33m%s\e[39m\e[22m' "${1}"); }
t_err() { (fn_printf '\e[31m%s\e[39m' "${1}"); }
# Styles
t_bold() { (fn_printf '\e[1m%s\e[22m' "${1}"); }
t_dim() { (fn_printf '\e[2m%s\e[22m' "${1}"); }
t_em() { (fn_printf '\e[3m%s\e[23m' "${1}"); }
t_under() { (fn_printf '\e[4m%s\e[24m' "${1}"); }
# FG Colors
t_cyan() { (fn_printf '\e[36m%s\e[39m' "${1}"); }
t_green() { (fn_printf '\e[32m%s\e[39m' "${1}"); }
t_magenta() { (fn_printf '\e[35m%s\e[39m' "${1}"); }
t_yellow() { (fn_printf '\e[33m%s\e[39m' "${1}"); }
fn_printf() { (
a_style="${1}"
a_text="${2}"
if fn_is_tty; then
#shellcheck disable=SC2059
printf -- "${a_style}" "${a_text}"
else
printf -- '%s' "${a_text}"
fi
); }
fn_sub_home() { (
my_rel=${HOME}
my_abs=${1}
echo "${my_abs}" | sed "s:^${my_rel}:~:"
); }
###################################
# #
# Detect HTTP Client #
# #
###################################
fn_wget() { (
# Doc:
# Downloads the file at the given url to the given path
a_url="${1}"
a_path="${2}"
cmd_wget="wget -c -q --user-agent"
if fn_is_tty; then
cmd_wget="wget -c -q --show-progress --user-agent"
fi
# busybox wget doesn't support --show-progress
# See <https://github.com/webinstall/webi-installers/pull/772>
if readlink "$(command -v wget)" | grep -q busybox; then
cmd_wget="wget --user-agent"
fi
b_triple_ua="$(fn_get_target_triple_user_agent)"
b_agent="webi/wget ${b_triple_ua}"
if command -v curl > /dev/null; then
b_agent="webi/wget+curl ${b_triple_ua}"
fi
if ! $cmd_wget "${b_agent}" "${a_url}" -O "${a_path}"; then
echo >&2 " $(t_err "failed to download (wget)") '$(t_url "${a_url}")'"
echo >&2 " $cmd_wget '${b_agent}' '${a_url}' -O '${a_path}'"
echo >&2 " $(wget -V)"
return 1
fi
); }
fn_curl() { (
# Doc:
# Downloads the file at the given url to the given path
a_url="${1}"
a_path="${2}"
cmd_curl="curl -f -sSL -#"
if fn_is_tty; then
cmd_curl="curl -f -sSL"
fi
b_triple_ua="$(fn_get_target_triple_user_agent)"
b_agent="webi/curl ${b_triple_ua}"
if command -v wget > /dev/null; then
b_agent="webi/curl+wget ${b_triple_ua}"
fi
if ! $cmd_curl -A "${b_agent}" "${a_url}" -o "${a_path}"; then
echo >&2 " $(t_err "failed to download (curl)") '$(t_url "${a_url}")'"
echo >&2 " $cmd_curl -A '${b_agent}' '${a_url}' -o '${a_path}'"
echo >&2 " $(curl -V)"
return 1
fi
); }
fn_get_target_triple_user_agent() { (
# Ex:
# x86_64/unknown GNU/Linux/5.15.107-2-pve gnu
# arm64/unknown Darwin/22.6.0 libc
echo "$(uname -m)/unknown $(fn_get_os)/$(uname -r) $(fn_get_libc)"
); }
fn_download_to_path() { (
a_url="${1}"
a_path="${2}"
mkdir -p "$(dirname "${a_path}")"
if command -v curl > /dev/null; then
fn_curl "${a_url}" "${a_path}.part"
elif command -v wget > /dev/null; then
fn_wget "${a_url}" "${a_path}.part"
else
echo >&2 " $(t_err "failed to detect HTTP client (curl, wget)")"
return 1
fi
mv "${a_path}.part" "${a_path}"
); }
##############################################
# #
# Install or Update Webi and Install Package #
# #
##############################################
webi_bootstrap() { (
a_path="${1}"
echo ""
echo "$(t_task 'Bootstrapping') $(t_pkg 'Webi')"
b_path_rel="$(fn_sub_home "${a_path}")"
b_checksum=""
if test -r "${a_path}"; then
b_checksum="$(fn_checksum "${a_path}")"
fi
if test "$b_checksum" = "${WEBI_CHECKSUM}"; then
echo " $(t_dim 'Found') $(t_path "${b_path_rel}")"
sleep 0.1
return 0
fi
b_webi_file_url="${WEBI_HOST}/packages/webi/webi.sh"
b_tmp=''
if test -r "${a_path}"; then
b_ts="$(date -u '+%s')"
b_tmp="${a_path}.${b_ts}.bak"
mv "${a_path}" "${b_tmp}"
echo " Updating $(t_path "${b_path_rel}")"
fi
echo " Downloading $(t_url "${b_webi_file_url}")"
echo " to $(t_path "${b_path_rel}")"
fn_download_to_path "${b_webi_file_url}" "${a_path}"
chmod u+x "${a_path}"
if test -r "${b_tmp}"; then
rm -f "${b_tmp}"
fi
); }
fn_checksum() {
a_filepath="${1}"
if command -v sha1sum > /dev/null; then
sha1sum "${a_filepath}" | cut -d' ' -f1 | cut -c 1-8
return 0
fi
if command -v shasum > /dev/null; then
shasum "${a_filepath}" | cut -d' ' -f1 | cut -c 1-8
return 0
fi
if command -v sha1 > /dev/null; then
sha1 "${a_filepath}" | cut -d'=' -f2 | cut -c 2-9
return 0
fi
echo >&2 " warn: no sha1 sum program"
date '+%F %H:%M'
}
##############################################
# #
# Detect TTY and run main #
# #
##############################################
fn_is_tty() {
if test "${WEBI_TTY}" = 'tty'; then
return 0
fi
return 1
}
fn_detect_tty() { (
# stdin will NOT be a tty if it's being piped
# stdout & stderr WILL be a tty even when piped
# they are not a tty if being captured or redirected
# 'set -i' is NOT available in sh
if test -t 1 && test -t 2; then
return 0
fi
return 1
); }
main() { (
set -e
set -u
WEBI_TTY="${WEBI_TTY:-}"
if test -z "${WEBI_TTY}"; then
if fn_detect_tty; then
WEBI_TTY="tty"
fi
export WEBI_TTY
fi
if test -z "${WEBI_WELCOME:-}"; then
fn_show_welcome
fi
export WEBI_WELCOME='shown'
# note: we may support custom locations in the future
export WEBI_HOME="${HOME}/.local"
b_home="$(fn_sub_home "${WEBI_HOME}")"
b_webi_path="${WEBI_HOME}/bin/webi"
b_webi_path_rel="${b_home}/bin/webi"
WEBI_CURRENT="${WEBI_CURRENT:-}"
if test "${WEBI_CURRENT}" != "${WEBI_CHECKSUM}"; then
webi_bootstrap "${b_webi_path}"
export WEBI_CURRENT="${WEBI_CHECKSUM}"
fi
echo " Running $(t_cmd "${b_webi_path_rel} ${WEBI_PKG}")"
echo ""
"${b_webi_path}" "${WEBI_PKG}"
); }
main

View File

@@ -0,0 +1,19 @@
#!/bin/sh
set -e
set -u
# For installing from the extracted package tmp directory
pkg_install() {
(
cd "$WEBI_TMP"
if command -v rsync 2> /dev/null | grep -q rsync; then
rsync -Krl ./xmpl*/ "$pkg_src/" 2> /dev/null
else
cp -Hr ./xmpl*/* "$pkg_src/" 2> /dev/null
cp -Hr ./xmpl*/.* "$pkg_src/" 2> /dev/null
fi
rm -rf ./xmpl*
)
}

View File

@@ -1,232 +0,0 @@
'use strict';
var Installers = module.exports;
var Crypto = require('crypto');
var Fs = require('node:fs/promises');
var path = require('node:path');
var reInstallTpl = /\s*#?\s*{{ installer }}/;
function padScript(txt) {
return txt.replace(/^/g, ' ');
}
var BAD_SH_RE = /[<>'"`$\\]/;
Installers.renderBash = async function (
pkgdir,
rel,
{ baseurl, pkg, tag, ver, os = '', arch = '', libc = '', formats, latest },
) {
if (!Array.isArray(formats)) {
formats = [];
}
if (!tag) {
tag = '';
}
let installTxt = await Fs.readFile(path.join(pkgdir, 'install.sh'), 'utf8');
installTxt = padScript(installTxt);
var vers = rel.version.split('.');
var v = {
major: vers.shift() || '',
minor: vers.shift() || '',
patch: vers.join('.').replace(/[+\-].*/, ''),
build: vers
.join('.')
.replace(/[^+\-]*/, '')
.replace(/^-/, ''),
};
var pkgFile = rel.filename || rel.name;
let tplTxt = await Fs.readFile(
path.join(__dirname, 'package-install.tpl.sh'),
'utf8',
);
// ex: 'node@lts' or 'node'
var webiPkg = pkg;
if (ver) {
webiPkg += `@${ver}`;
}
let releaseParams = new URLSearchParams({
os: rel.os,
arch: rel.arch,
libc: rel.libc,
formats: formats.join(','),
pretty: true,
});
let releaseSearch = releaseParams.toString();
releaseSearch = releaseSearch.replace(/%2C/g, ',');
let releaseUrl = `/api/releases/${pkg}@${tag}.tab?${releaseSearch}`;
let releaseCsv = [
rel.version,
rel.lts,
rel.channel,
rel.date,
rel.os,
rel.arch,
rel.ext,
'-',
rel.download,
rel.name,
rel.comment || '',
]
.join(',')
.replace(/'/g, '');
let webiChecksum = await Installers.getWebiShChecksum();
let envReplacements = [
['WEBI_CHECKSUM', webiChecksum],
['WEBI_PKG', webiPkg],
['WEBI_HOST', baseurl],
['WEBI_OS', os],
['WEBI_ARCH', arch],
['WEBI_LIBC', libc],
['WEBI_TAG', tag],
['WEBI_RELEASES', `${baseurl}${releaseUrl}`],
['WEBI_CSV', releaseCsv],
['WEBI_VERSION', rel.version],
['WEBI_MAJOR', v.major],
['WEBI_MINOR', v.minor],
['WEBI_PATCH', v.patch],
['WEBI_BUILD', v.build],
['WEBI_GIT_BRANCH', rel.git_branch || rel.git_tag],
['WEBI_GIT_TAG', rel.git_tag], // TODO replace with branch
['WEBI_LTS', rel.lts],
['WEBI_CHANNEL', rel.channel],
['WEBI_EXT', rel.ext],
['WEBI_FORMATS', formats.join(',')],
['WEBI_PKG_URL', rel.download],
['WEBI_PKG_PATHNAME', pkgFile],
['WEBI_PKG_FILE', pkgFile], // TODO replace with pathname
['PKG_NAME', pkg],
['PKG_OSES', (rel.oses || []).join(' ')],
['PKG_ARCHES', (rel.arches || []).join(' ')],
['PKG_LIBCS', (rel.libcs || []).join(' ')],
['PKG_FORMATS', (rel.formats || []).join(' ')],
['PKG_LATEST', latest],
];
for (let env of envReplacements) {
let name = env[0];
let value = env[1];
// Ex:
// #export WEBI_FOO=xyz => export WEBI_FOO='123'
// export WEBI_FOO= => export WEBI_FOO='123'
let envRe = new RegExp(
`^[ \\t]*#?[ \\t]*(export[ \\t])?[ \\t]*(${name})=.*`,
'm',
);
if (BAD_SH_RE.test(value)) {
throw new Error(`key '${name}' has invalid value '${value}'`);
}
tplTxt = tplTxt.replace(envRe, `$1$2='${value}'`);
}
tplTxt = tplTxt
.replace(/CHEATSHEET_URL/g, `${baseurl}/${pkg}`)
// $', $0, ... $9, $`, $&, and $_ all have special meaning
// (see https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/RegExp)
// However, it can be escaped with $$ (which must be escaped with $$)
.replace(reInstallTpl, '\n' + installTxt.replace(/\$/g, '$$$$'));
return tplTxt;
};
Installers.renderPowerShell = async function (
pkgdir,
rel,
{ baseurl, pkg, tag, ver, os, arch, libc = '', formats },
) {
if (!Array.isArray(formats)) {
formats = [];
}
if (!tag) {
tag = '';
}
let installTxt = await Fs.readFile(path.join(pkgdir, 'install.ps1'), 'utf8');
installTxt = padScript(installTxt);
/*
var vers = rel.version.split('.');
var v = {
major: vers.shift() || '',
minor: vers.shift() || '',
patch: vers.join('.').replace(/[+\-].*$/, ''),
build: vers
.join('.')
.replace(/[^+\-]*()/, '')
.replace(/^-/, '')
};
*/
let tplTxt = await Fs.readFile(
path.join(__dirname, 'package-install.tpl.ps1'),
'utf8',
);
var pkgver = pkg + '@' + ver;
return (
tplTxt
.replace(
/^(#)?\$Env:WEBI_LIBC\s*=.*/im,
"$Env:WEBI_LIBC = '" + libc + "'",
)
.replace(
/^(#)?\$Env:WEBI_HOST\s*=.*/im,
"$Env:WEBI_HOST = '" + baseurl + "'",
)
.replace(
/^(#)?\$Env:WEBI_PKG\s*=.*/im,
"$Env:WEBI_PKG = '" + pkgver + "'",
)
.replace(/^(#)?\$Env:PKG_NAME\s*=.*/im, "$Env:PKG_NAME = '" + pkg + "'")
.replace(
/^(#)?\$Env:WEBI_VERSION\s*=.*/im,
"$Env:WEBI_VERSION = '" + rel.version + "'",
)
.replace(
/^(#)?\$Env:WEBI_GIT_TAG\s*=.*/im,
"$Env:WEBI_GIT_TAG = '" + rel.git_tag + "'",
)
.replace(
/^(#)?\$Env:WEBI_PKG_URL\s*=.*/im,
"$Env:WEBI_PKG_URL = '" + rel.download + "'",
)
// TODO replace WEBI_PKG_FILE (which is sometimes a dir)
.replace(
/^(#)?\$Env:WEBI_PKG_PATHNAME\s*=.*/im,
"$Env:WEBI_PKG_PATHNAME = '" + rel.name + "'",
)
// TODO deprecate
.replace(
/^(#)?\$Env:WEBI_PKG_FILE\s*=.*/im,
"$Env:WEBI_PKG_FILE = '" + rel.name + "'",
)
.replace(reInstallTpl, '\n' + installTxt)
);
};
var _webiShMeta = {
stale: 10 * 1000,
updated_at: 0,
checksum: '',
mtime: 0,
};
Installers.getWebiShChecksum = async function () {
let now = Date.now();
let ago = now - _webiShMeta.updated_at;
if (ago <= _webiShMeta.stale) {
return _webiShMeta.checksum;
}
let webiPath = path.join(__dirname, '../webi/webi.sh');
let stat = await Fs.stat(webiPath);
if (stat.mtimeMs === _webiShMeta.mtime) {
return _webiShMeta.checksum;
}
let webiBuf = await Fs.readFile(webiPath, null);
let webiHash = Crypto.createHash('sha1').update(webiBuf).digest('hex');
let webiChecksum = webiHash.slice(0, 8);
_webiShMeta.mtime = stat.mtimeMs;
_webiShMeta.updated_at = now;
_webiShMeta.checksum = webiChecksum;
return _webiShMeta.checksum;
};

View File

@@ -1,338 +0,0 @@
#!/usr/bin/env node
'use strict';
let Fs = require('node:fs/promises');
let Path = require('node:path');
let BuildsCacher = require('./builds-cacher.js');
let HostTargets = require('./build-classifier/host-targets.js');
let Parallel = require('./parallel.js');
var INSTALLERS_DIR = Path.join(__dirname, '..');
var CACHE_DIR = Path.join(__dirname, '../_cache');
let UserAgentsMap = require('./build-classifier/uas.json');
let uas = Object.keys(UserAgentsMap);
let uaTargetsMap = {};
for (let ua of uas) {
let terms = ua.split(/[\s\/]+/g);
let target = {};
void HostTargets.termsToTarget(target, terms);
if (!target) {
continue;
}
if (target.errors.length) {
throw target.errors[0];
}
if (!target.os) {
// TODO make target null, or create error for this
console.warn(`no os for terms: ${terms}`);
//throw new Error(`terms: ${terms}`);
continue;
}
if (!target.arch) {
// TODO make target null, or create error for this
console.warn(`no arch for terms: ${terms}`);
//throw new Error(`terms: ${terms}`);
continue;
}
if (!target.libc) {
// TODO make target null, or create error for this
console.warn(`no libc for terms: ${terms}`);
//throw new Error(`terms: ${terms}`);
continue;
}
let triplet = `${target.os}-${target.arch}-${target.libc}`;
uaTargetsMap[triplet] = target;
}
let uaTargets = [];
let triplets = Object.keys(uaTargetsMap);
for (let triplet of triplets) {
let target = uaTargetsMap[triplet];
uaTargets.push(target);
}
function showDirs(dirs) {
{
let errors = Object.keys(dirs.errors);
console.error('');
console.error(`Errors: ${errors.length}`);
for (let name of errors) {
let err = dirs.errors[name];
console.error(`${name}/: ${err.message}`);
}
}
{
let hidden = Object.keys(dirs.hidden);
console.debug('');
console.debug(`Hidden: ${hidden.length}`);
for (let name of hidden) {
let kind = dirs.hidden[name];
if (kind === '!directory') {
console.debug(` ${name}`);
} else {
console.debug(` ${name}/`);
}
}
}
{
let alias = Object.keys(dirs.alias);
console.debug('');
console.debug(`Alias: ${alias.length}`);
for (let name of alias) {
let kind = dirs.alias[name];
if (kind === 'symlink') {
console.debug(` ${name} => ...`);
} else {
console.debug(` ${name}/`);
}
}
}
{
let invalids = Object.keys(dirs.invalid);
console.warn('');
console.warn(`Invalid: ${invalids.length}`);
for (let name of invalids) {
console.warn(` ${name}/`);
}
}
{
let selfhosted = Object.keys(dirs.selfhosted);
console.info('');
console.info(`Self-Hosted: ${selfhosted.length}`);
for (let name of selfhosted) {
console.info(` ${name}/`);
}
}
{
let valids = Object.keys(dirs.valid);
console.info('');
console.info(`Found: ${valids.length}`);
for (let name of valids) {
console.info(` ${name}/`);
}
}
}
let bc = BuildsCacher.create({
caches: CACHE_DIR,
installers: INSTALLERS_DIR,
});
async function main() {
/* jshint maxcomplexity: 25 */
// TODO
// node ./_webi/lint-builds.js caddy@beta 'x86_64/unknown Darwin libc'
//
//let [projName, userAgent] = process.argv.slice(2);
let projName = process.argv[2];
// create test case for zoxide, goreleaser, go, yq, caddy, rg
let dirs = await bc.getProjectsByType();
if (!projName) {
showDirs(dirs);
console.info('');
}
bc.freshenRandomPackage(600 * 1000);
let rows = [];
let triples = [];
let valids = Object.keys(dirs.valid);
if (projName) {
if (!valids.includes(projName)) {
throw new Error(`'${projName}' is not a valid installable project`);
}
valids = [projName];
}
//valids = ['atomicparsley', 'caddy', 'macos'];
//valids = ['atomicparsley'];
console.info('');
console.info(`Fetching project release assets`);
let parallel = 25;
let projects = [];
await Parallel.run(parallel, valids, getAll);
async function getAll(name, i) {
console.info(` ${name}`);
let projInfo = await bc.getPackages({
//Releases: Releases,
name: name,
date: new Date(),
});
projects[i] = projInfo;
}
console.info(`Classifying build assets for...`);
for (let projInfo of projects) {
console.info(` ${projInfo.name}`);
let nStr = projInfo.releases.length.toString();
let n = nStr.padStart(5, ' ');
let row = `##### ${n}\t${projInfo.name}\tv`;
rows.push(row);
// ignore known, non-package extensions
for (let build of projInfo.releases) {
let target = bc.classify(projInfo, build);
if (!target) {
// non-build file
continue;
}
if (target.error) {
let e = target.error;
if (e.code === 'E_BUILD_NO_PATTERN') {
console.warn(`>>> ${e.message} <<<`);
console.warn(projInfo);
console.warn(build);
console.warn(`^^^ ${e.message} ^^^`);
}
throw e;
}
if (target.unknownTerms?.length) {
let msg = `${projInfo.name}: unrecognized term(s) '${target.unknownTerms}' in '${build.download}'`;
let err = new Error(msg);
throw err;
}
triples.push(target.triplet);
// if (!build.version) {
// throw new Error(`no version for ${pkg.name} ${build.name}`);
// }
// // For debug printing versions
// console.error(build.version);
rows.push(`${target.triplet}\t${projInfo.name}\t${build.version}`);
}
}
console.info(`Fetching builds for`);
for (let projInfo of projects) {
console.info('');
console.info('');
console.info(` ${projInfo.name}`);
for (let target of uaTargets) {
let libc = target.libc || 'libc';
let hostTriplet = `${target.os}-${target.arch}-${libc}`;
console.info('');
console.info(` target: ${hostTriplet}`);
let match = bc.findMatchingPackages(projInfo, target, {
ver: '',
});
if (!match) {
console.info(
` project: ${projInfo.name}: missing build for os '${target.os}'`,
);
continue;
}
if (!match.releases) {
console.info(
` project: ${projInfo.name}: missing build for os '${target.os}-${target.arch}-${libc}'`,
);
} else if (match.triplet === hostTriplet) {
let releaseNames = Object.keys(match.releases);
console.info(` selected ${releaseNames.length}`);
} else {
let releaseNames = Object.keys(match.releases);
console.info(
` selected ${releaseNames.length} (${match.triplet} fallback)`,
);
}
}
}
let tsv = rows.join('\n');
console.info('');
console.info('#rows', rows.length);
await Fs.writeFile('builds.tsv', tsv, 'utf8');
console.info('');
console.info('Triplets Detected:');
let triplets = Object.keys(bc._triplets);
if (triplets.length) {
triplets.sort();
console.info(' ', triplets.join('\n '));
} else {
console.info(' (none)');
}
console.info('');
console.info('New / Unknown Terms:');
let unknowns = Object.keys(bc.unknownTerms);
if (unknowns.length) {
unknowns.sort();
console.warn(' ', unknowns.join('\n '));
} else {
console.info(' (none)');
}
console.info('');
console.info('Unused Terms:');
let unuseds = Object.keys(bc.orphanTerms);
if (unuseds.length) {
unuseds.sort();
console.warn(' ', unuseds.join('\n '));
} else {
console.info(' (none)');
}
console.info('');
console.info('Formats:');
if (bc.formats.length) {
let formats = bc.formats.slice();
formats.sort();
if (!formats[0]) {
formats[0] = '(bin)';
}
console.warn(' ', formats.join('\n '));
} else {
console.info(' (none)');
}
// sort -u -k1 builds.tsv | rg -v '^#|^https?:' | rg -i arm
// cut -f1 builds.tsv | sort -u -k1 | rg -v '^#|^https?:' | rg -i arm
}
if (module === require.main) {
let times = [];
let now = Date.now();
main()
.then(async function () {
let then = Date.now();
let delta = then - now;
times.push(delta);
now = then;
await main();
then = Date.now();
delta = then - now;
times.push(delta);
})
.then(function () {
console.info('');
console.info('Run times');
for (let delta of times) {
let s = delta / 1000;
console.info(` ${s}`);
}
function forceExit() {
console.warn(`warn: dangling event loop reference`);
process.exit(0);
}
let exitTimeout = setTimeout(forceExit, 250);
exitTimeout.unref();
})
.catch(function (err) {
console.error(err.stack || err);
process.exit(1);
});
}

View File

@@ -13,7 +13,6 @@ var osMap = {
var maps = {
oses: {},
arches: {},
libcs: {},
formats: {},
};
@@ -21,7 +20,7 @@ Object.keys(osMap).forEach(function (name) {
maps.oses[name] = true;
});
var formats = ['zip', 'xz', 'tar', 'pkg', 'msi', 'git', 'exe', 'dmg', 'git'];
var formats = ['zip', 'xz', 'tar', 'pkg', 'msi', 'git', 'exe', 'dmg'];
formats.forEach(function (name) {
maps.formats[name] = true;
});
@@ -29,14 +28,14 @@ formats.forEach(function (name) {
// 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
// arm 7 cannot be confused with arm64
'armv7l',
// arm6 can run on armv7
'armv6l',
// amd64 is more likely and less often specified than arm64
// amd64 is more likely than arm64
'amd64',
// arm6 has the same prefix as arm64
'armv6l',
// arm64 is more likely than arm6, and should be the default
'arm64',
'x86',
'ppc64le',
'ppc64',
@@ -50,13 +49,13 @@ var arches = [
// 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,
armv7l: /(\b|_)(armv?7l?)/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,
armv6l: /(\b|_)(aarch32|armv?6l?)(\b|_)/i,
arm64: /(\b|_)((aarch|arm)64|arm)/i,
x86: /(\b|_|amd|(dar)?win(dows)?|mac(os)?|linux|osx|x)(86|32)([_\-]?bit)(\b|_)/i,
ppc64le: /(\b|_)(ppc64le)/i,
ppc64: /(\b|_)(ppc64)(\b|_)/i,
@@ -66,39 +65,31 @@ 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) {
all.releases.forEach(function (rel) {
/* jshint maxcomplexity:25 */
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;
rel.os =
Object.keys(osMap).find(function (regKey) {
return osMap[regKey].test(rel.name || rel.download);
}) || 'unknown';
}
// Hacky-doo for musl
// TODO some sort of glibc vs musl tag?
if (!rel._musl_native) {
if (!rel._musl) {
if (/(\b|\.|_|-)(musl)(\b|\.|_|-)/.test(rel.download)) {
rel._musl = true;
}
}
}
@@ -121,60 +112,6 @@ function normalize(all) {
}
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']
@@ -202,28 +139,10 @@ function normalize(all) {
}
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|_)(preview|rc|beta|alpha)(\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];
@@ -231,9 +150,6 @@ function normalize(all) {
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];
});
@@ -254,5 +170,4 @@ module.exports._debug = function (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

@@ -1,155 +0,0 @@
#!/usr/bin/env pwsh
#350 check if windows user run as admin
# this allows us to call ps1 files, which allows us to have spaces in filenames
# ('powershell "$Env:USERPROFILE\test.ps1" foo' will fail if it has a space in
# the path but '& "$Env:USERPROFILE\test.ps1" foo' will work even with a space)
Set-ExecutionPolicy -Scope Process Bypass
# If a command returns an error, halt the script.
$ErrorActionPreference = 'Stop'
# Ignore progress events from cmdlets so Invoke-WebRequest is not painfully slow
$ProgressPreference = 'SilentlyContinue'
$Env:WEBI_HOST = 'https://webinstall.dev'
#$Env:WEBI_PKG = 'node@lts'
#$Env:PKG_NAME = node
#$Env:WEBI_VERSION = v12.16.2
#$Env:WEBI_GIT_TAG = 12.16.2
#$Env:WEBI_PKG_URL = "https://.../node-....zip"
#$Env:WEBI_PKG_FILE = "node-v12.16.2-win-x64.zip"
#$Env:WEBI_PKG_PATHNAME = "node-v12.16.2-win-x64.zip"
# Switch to userprofile
Push-Location $Env:USERPROFILE
# Make paths
New-Item -Path "$Env:USERPROFILE\Downloads" -ItemType Directory -Force | Out-Null
New-Item -Path "$Env:USERPROFILE\.local\bin" -ItemType Directory -Force | Out-Null
New-Item -Path "$Env:USERPROFILE\.local\opt" -ItemType Directory -Force | Out-Null
# {{ baseurl }}
# {{ version }}
# See
# - <https://superuser.com/q/1264444>
# - <https://stackoverflow.com/a/60572643/151312>
$Esc = [char]27
$TTask = "${Esc}[36m"
$TName = "${Esc}[1m${Esc}[32m"
$TUrl = "${Esc}[2m"
$TPath = "${Esc}[2m${Esc}[32m"
$TCmd = "${Esc}[2m${Esc}[35m"
$TDim = "${Esc}[2m"
$TReset = "${Esc}[0m"
function Invoke-DownloadUrl {
Param (
[string]$URL,
[string]$Params,
[string]$Path,
[switch]$Force
)
IF (Test-Path -Path "$Path") {
IF (-Not $Force.IsPresent) {
Write-Host " ${TDim}Found${TReset} $Path"
return
}
Write-Host " Updating ${TDim}${Path}${TDim}"
}
$TmpPath = "${Path}.part"
Remove-Item -Path $TmpPath -Force -ErrorAction Ignore
Write-Host " Downloading ${TDim}from${TReset}"
Write-Host " ${TDim}${URL}${TReset}"
IF ($Params.Length -ne 0) {
Write-Host " ?$Params"
$URL = "${URL}?${Params}"
}
curl.exe '-#' -f -sS -A $Env:WEBI_UA -L $URL | Out-File $TmpPath
Remove-Item -Path $Path -Force -ErrorAction Ignore
Move-Item $TmpPath $Path
Write-Host " Saved ${TPath}${Path}${TReset}"
}
function Get-UserAgent {
# This is the canonical CPU arch when the process is emulated
$my_arch = "$Env:PROCESSOR_ARCHITEW6432"
IF ($my_arch -eq $null -or $my_arch -eq "") {
# This is the canonical CPU arch when the process is native
$my_arch = "$Env:PROCESSOR_ARCHITECTURE"
}
IF ($my_arch -eq "AMD64") {
# Because PowerShell is sometimes AMD64 on Windows 10 ARM
# See https://oofhours.com/2020/02/04/powershell-on-windows-10-arm64/
$my_os_arch = wmic os get osarchitecture
# Using -clike because of the trailing newline
IF ($my_os_arch -clike "ARM 64*") {
$my_arch = "ARM64"
}
}
"PowerShell+curl Windows/10+ $my_arch msvc"
}
function webi_path_add($pathname) {
# C:\Users\me => C:/Users/me
$my_home = $Env:UserProfile
$my_home = $my_home.replace('\\', '/')
$my_home_re = [regex]::escape($my_home)
# ~/bin => %USERPROFILE%/bin
$pathname = $pathname.replace('~/', "$Env:UserProfile/")
# C:\Users\me\bin => %USERPROFILE%/bin
$my_pathname = $pathname.replace('\\', '/')
$my_pathname = $my_pathname -ireplace $my_home_re, "%USERPROFILE%"
$all_user_paths = [Environment]::GetEnvironmentVariable("Path", "User")
$user_paths = "${all_user_paths}".Trim(';').Split(';')
$exists_in_path = $false
foreach ($user_path in $user_paths) {
# C:\Users\me\bin => %USERPROFILE%/bin
$my_user_path = $user_path.replace('\\', '/')
$my_user_path = $my_user_path -ireplace $my_home_re, "%USERPROFILE%"
if ($my_user_path -ieq $my_pathname) {
$exists_in_path = $true
}
}
if (-Not $exists_in_path) {
$all_user_paths = "${pathname};${all_user_paths}".Trim(';')
[Environment]::SetEnvironmentVariable("Path", $all_user_paths, "User")
$null = Sync-EnvPath
}
}
function Sync-EnvPath {
$UserPath = [Environment]::GetEnvironmentVariable("Path", "User").Trim(';')
$MachinePath = [Environment]::GetEnvironmentVariable("Path", "Machine").Trim(';')
$Env:Path = "${UserPath};${MachinePath}"
[Environment]::SetEnvironmentVariable("Path", $Env:Path)
$Env:Path
}
$Env:WEBI_UA = Get-UserAgent
#$has_local_bin = echo "$Env:PATH" | Select-String -Pattern '\.local.bin'
#if (!$has_local_bin)
#{
webi_path_add ~/.local/bin
#}
# {{ installer }}
webi_path_add ~/.local/bin
# Done
Pop-Location

File diff suppressed because it is too large Load Diff

View File

@@ -1 +0,0 @@
projects.js

96
_webi/packages.js Normal file
View File

@@ -0,0 +1,96 @@
'use strict';
var frontmarker = require('./frontmarker.js');
var fs = require('fs');
var path = require('path');
var pkgs = module.exports;
pkgs.create = function (Pkgs, basepath) {
if (!Pkgs) {
Pkgs = {};
}
if (!basepath) {
basepath = path.join(__dirname, '../');
}
Pkgs.all = function () {
return fs.promises.readdir(basepath).then(function (nodes) {
var items = [];
return nodes
.reduce(function (p, node) {
return p.then(function () {
return pkgs.get(node).then(function (meta) {
if (meta && '_' !== node[0]) {
meta.name = node;
items.push(meta);
}
});
});
}, Promise.resolve())
.then(function () {
return items;
});
});
};
Pkgs.get = function (node) {
return fs.promises.access(path.join(basepath, node)).then(function () {
return Pkgs._get(node);
});
};
Pkgs._get = function (node) {
var curlbash = path.join(basepath, node, 'install.sh');
var readme = path.join(basepath, node, 'README.md');
var winstall = path.join(basepath, node, 'install.ps1');
return Promise.all([
fs.promises
.readFile(readme, 'utf-8')
.then(function (txt) {
// TODO
return frontmarker.parse(txt);
})
.catch(function (e) {
if ('ENOENT' !== e.code && 'ENOTDIR' !== e.code) {
console.error("failed to read '" + node + "/README.md'");
console.error(e);
}
}),
fs.promises.access(curlbash).catch(function (e) {
// no *nix installer
curlbash = '';
if ('ENOENT' !== e.code && 'ENOTDIR' !== e.code) {
console.error("failed to parse '" + node + "/install.sh'");
console.error(e);
}
}),
fs.promises.access(winstall).catch(function (e) {
// no winstaller
winstall = '';
if ('ENOENT' !== e.code && 'ENOTDIR' !== e.code) {
console.error("failed to read '" + node + "/install.ps1'");
console.error(e);
}
}),
]).then(function (items) {
var meta = items[0] || items[1];
if (!meta) {
// doesn't exist
return;
}
meta.windows = !!winstall;
meta.bash = !!curlbash;
return meta;
});
};
return Pkgs;
};
pkgs.create(pkgs);
if (module === require.main) {
pkgs.all().then(function (data) {
console.info('package info:');
console.info(data);
});
}

View File

@@ -1,44 +0,0 @@
'use strict';
var Parallel = module.exports;
Parallel.run = async function (limit, arr, fn) {
let index = 0;
let actives = [];
let results = [];
limit = Math.min(limit, arr.length);
function launch() {
let _index = index;
let p = fn(arr[_index], _index, arr);
// some tasks may be synchronous
// so we must push before removing
actives.push(p);
p.then(function _resolve(result) {
let i = actives.indexOf(p);
actives.splice(i, 1);
results[_index] = result;
});
index += 1;
}
// start tasks in parallel, up to limit
for (; actives.length < limit; ) {
launch();
}
// keep the task queue full
for (; index < arr.length; ) {
// wait for one task to complete
await Promise.race(actives);
// add one task again
launch();
}
// wait for all remaining tasks
await Promise.all(actives);
return results;
};

View File

@@ -1,96 +0,0 @@
'use strict';
var frontmarker = require('./frontmarker.js');
var fs = require('fs');
var path = require('path');
var pkgs = module.exports;
pkgs.create = function (Pkgs, basepath) {
if (!Pkgs) {
Pkgs = {};
}
if (!basepath) {
basepath = path.join(__dirname, '../');
}
Pkgs.all = function () {
return fs.promises.readdir(basepath).then(function (nodes) {
var items = [];
return nodes
.reduce(function (p, node) {
return p.then(function () {
return pkgs.get(node).then(function (meta) {
if (meta && '_' !== node[0]) {
meta.name = node;
items.push(meta);
}
});
});
}, Promise.resolve())
.then(function () {
return items;
});
});
};
Pkgs.get = function (node) {
return fs.promises.access(path.join(basepath, node)).then(function () {
return Pkgs._get(node);
});
};
Pkgs._get = function (node) {
var curlbash = path.join(basepath, node, 'install.sh');
var readme = path.join(basepath, node, 'README.md');
var winstall = path.join(basepath, node, 'install.ps1');
return Promise.all([
fs.promises
.readFile(readme, 'utf-8')
.then(function (txt) {
// TODO
return frontmarker.parse(txt);
})
.catch(function (e) {
if ('ENOENT' !== e.code && 'ENOTDIR' !== e.code) {
console.error("failed to read '" + node + "/README.md'");
console.error(e);
}
}),
fs.promises.access(curlbash).catch(function (e) {
// no *nix installer
curlbash = '';
if ('ENOENT' !== e.code && 'ENOTDIR' !== e.code) {
console.error("failed to parse '" + node + "/install.sh'");
console.error(e);
}
}),
fs.promises.access(winstall).catch(function (e) {
// no winstaller
winstall = '';
if ('ENOENT' !== e.code && 'ENOTDIR' !== e.code) {
console.error("failed to read '" + node + "/install.ps1'");
console.error(e);
}
}),
]).then(function (items) {
var meta = items[0] || items[1];
if (!meta) {
// doesn't exist
return;
}
meta.windows = !!winstall;
meta.bash = !!curlbash;
return meta;
});
};
return Pkgs;
};
pkgs.create(pkgs);
if (module === require.main) {
pkgs.all().then(function (data) {
console.info('package info:');
console.info(data);
});
}

260
_webi/releases.js Normal file
View File

@@ -0,0 +1,260 @@
'use strict';
var fs = require('node:fs');
var path = require('node:path');
var request = require('@root/request');
var _normalize = require('../_webi/normalize.js');
// PowerShell: { { installer } }
// POSIX: {{ installer }}
var reInstallTpl = /\s*#?\s*{{ installer }}/;
var Releases = module.exports;
Releases.get = async function (pkgdir) {
let get;
try {
get = require(path.join(pkgdir, 'releases.js'));
} catch (e) {
let err = new Error('no releases.js for', pkgdir.split(/[\/\\]+/).pop());
err.code = 'E_NO_RELEASE';
throw err;
}
let all = await get(request);
return _normalize(all);
};
function padScript(txt) {
return txt.replace(/^/g, ' ');
}
Releases.renderBash = async function (
pkgdir,
rel,
{ baseurl, pkg, tag, ver, os = '', arch = '', formats },
) {
if (!Array.isArray(formats)) {
formats = [];
}
if (!tag) {
tag = '';
}
return fs.promises
.readFile(path.join(pkgdir, 'install.sh'), 'utf8')
.then(function (installTxt) {
installTxt = padScript(installTxt);
var vers = rel.version.split('.');
var v = {
major: vers.shift() || '',
minor: vers.shift() || '',
patch: vers.join('.').replace(/[+\-].*/, ''),
build: vers
.join('.')
.replace(/[^+\-]*/, '')
.replace(/^-/, ''),
};
var pkgFile = rel.filename || rel.name;
return fs.promises
.readFile(path.join(__dirname, 'template.sh'), 'utf8')
.then(function (tplTxt) {
// ex: 'node@lts' or 'node'
var webiPkg = pkg;
if (ver) {
webiPkg += `@${ver}`;
}
return (
tplTxt
.replace(/CHEATSHEET_URL/g, `${baseurl}/${pkg}`)
.replace(/^\s*#?WEBI_PKG=.*/m, `WEBI_PKG='${webiPkg}'`)
.replace(/^\s*#?WEBI_HOST=.*/m, `WEBI_HOST='${baseurl}'`)
.replace(/^\s*#?WEBI_OS=.*/m, `WEBI_OS='${os}'`)
.replace(/^\s*#?WEBI_ARCH=.*/m, `WEBI_ARCH='${arch}'`)
.replace(/^\s*#?WEBI_TAG=.*/m, `WEBI_TAG='${tag}'`)
.replace(
/^\s*#?WEBI_RELEASES=.*/m,
"WEBI_RELEASES='" +
baseurl +
'/api/releases/' +
pkg +
'@' +
tag +
'.tab?os=' +
rel.os +
'&arch=' +
rel.arch +
'&formats=' +
formats.join(',') +
'&pretty=true' +
"'",
)
.replace(
/^\s*#?WEBI_CSV=.*/m,
"WEBI_CSV='" +
[
rel.version,
rel.lts,
rel.channel,
rel.date,
rel.os,
rel.arch,
rel.ext,
'-',
rel.download,
rel.name,
rel.comment || '',
]
.join(',')
.replace(/'/g, '') +
"'",
)
.replace(
/^\s*#?WEBI_VERSION=.*/m,
'WEBI_VERSION=' + JSON.stringify(rel.version),
)
.replace(/^\s*#?WEBI_MAJOR=.*/m, 'WEBI_MAJOR=' + v.major)
.replace(/^\s*#?WEBI_MINOR=.*/m, 'WEBI_MINOR=' + v.minor)
.replace(/^\s*#?WEBI_PATCH=.*/m, 'WEBI_PATCH=' + v.patch)
.replace(/^\s*#?WEBI_BUILD=.*/m, 'WEBI_BUILD=' + v.build)
.replace(/^\s*#?WEBI_LTS=.*/m, 'WEBI_LTS=' + rel.lts)
.replace(/^\s*#?WEBI_CHANNEL=.*/m, 'WEBI_CHANNEL=' + rel.channel)
.replace(
/^\s*#?WEBI_EXT=.*/m,
'WEBI_EXT=' + rel.ext.replace(/tar.*/, 'tar'),
)
.replace(
/^\s*#?WEBI_FORMATS=.*/m,
"WEBI_FORMATS='" + formats.join(',') + "'",
)
.replace(
/^\s*#?WEBI_PKG_URL=.*/m,
"WEBI_PKG_URL='" + rel.download + "'",
)
.replace(
/^\s*#?WEBI_PKG_FILE=.*/m,
"WEBI_PKG_FILE='" + pkgFile + "'",
)
// PKG details
.replace(/^\s*#?PKG_NAME=.*/m, "PKG_NAME='" + pkg + "'")
.replace(
/^\s*#?PKG_OSES=.*/m,
"PKG_OSES='" + ((rel && rel.oses) || []).join(',') + "'",
)
.replace(
/^\s*#?PKG_ARCHES=.*/m,
"PKG_ARCHES='" + ((rel && rel.arches) || []).join(',') + "'",
)
.replace(
/^\s*#?PKG_FORMATS=.*/m,
"PKG_FORMATS='" + ((rel && rel.formats) || []).join(',') + "'",
)
// $', $0, ... $9, $`, $&, and $_ all have special meaning
// (see https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/RegExp)
// However, it can be escaped with $$ (which must be escaped with $$)
.replace(reInstallTpl, '\n' + installTxt.replace(/\$/g, '$$$$'))
);
});
});
};
Releases.renderBatch = async function (
pkgdir,
rel,
{ baseurl, pkg, tag, ver, os, arch, formats },
) {
if (!Array.isArray(formats)) {
formats = [];
}
if (!tag) {
tag = '';
}
return fs.promises
.readFile(path.join(pkgdir, 'install.bat'), 'utf8')
.then(function (installTxt) {
installTxt = padScript(installTxt);
/*
var vers = rel.version.split('.');
var v = {
major: vers.shift() || '',
minor: vers.shift() || '',
patch: vers.join('.').replace(/[+\-].*$/, ''),
build: vers
.join('.')
.replace(/[^+\-]*()/, '')
.replace(/^-/, '')
};
*/
return fs.promises
.readFile(path.join(__dirname, 'template.bat'), 'utf8')
.then(function (tplTxt) {
return tplTxt
.replace(
/^(REM )?WEBI_PKG=.*/im,
"WEBI_PKG='" + pkg + '@' + ver + "'",
)
.replace(reInstallTpl, '\n' + installTxt);
});
});
};
Releases.renderPowerShell = async function (
pkgdir,
rel,
{ baseurl, pkg, tag, ver, os, arch, formats },
) {
if (!Array.isArray(formats)) {
formats = [];
}
if (!tag) {
tag = '';
}
return fs.promises
.readFile(path.join(pkgdir, 'install.ps1'), 'utf8')
.then(function (installTxt) {
installTxt = padScript(installTxt);
/*
var vers = rel.version.split('.');
var v = {
major: vers.shift() || '',
minor: vers.shift() || '',
patch: vers.join('.').replace(/[+\-].*$/, ''),
build: vers
.join('.')
.replace(/[^+\-]*()/, '')
.replace(/^-/, '')
};
*/
return fs.promises
.readFile(path.join(__dirname, 'template.ps1'), 'utf8')
.then(function (tplTxt) {
var pkgver = pkg + '@' + ver;
return tplTxt
.replace(
/^(#)?\$Env:WEBI_HOST\s*=.*/im,
"$Env:WEBI_HOST = '" + baseurl + "'",
)
.replace(
/^(#)?\$Env:WEBI_PKG\s*=.*/im,
"$Env:WEBI_PKG = '" + pkgver + "'",
)
.replace(
/^(#)?\$Env:PKG_NAME\s*=.*/im,
"$Env:PKG_NAME = '" + pkg + "'",
)
.replace(
/^(#)?\$Env:WEBI_VERSION\s*=.*/im,
"$Env:WEBI_VERSION = '" + rel.version + "'",
)
.replace(
/^(#)?\$Env:WEBI_PKG_URL\s*=.*/im,
"$Env:WEBI_PKG_URL = '" + rel.download + "'",
)
.replace(
/^(#)?\$Env:WEBI_PKG_FILE\s*=.*/im,
"$Env:WEBI_PKG_FILE = '" + rel.name + "'",
)
.replace(reInstallTpl, '\n' + installTxt);
});
});
};

View File

@@ -1,288 +1,89 @@
'use strict';
var InstallerServer = module.exports;
var path = require('path');
let Fs = require('fs/promises');
let Path = require('path');
var uaDetect = require('./ua-detect.js');
var packages = require('./packages.js');
var Releases = require('./releases.js');
let HostTargets = require('./build-classifier/host-targets.js');
let Builds = require('./builds.js');
let Installers = require('./installers.js');
// handlers caching and transformation, probably should be broken down
var getReleases = require('./transform-releases.js');
InstallerServer.INSTALLERS_DIR = Path.join(__dirname, '..');
InstallerServer.serveInstaller = async function (
baseurl,
ua,
pkg,
tag,
ext,
formats,
libc,
) {
let unameAgent = ua;
let projectName = pkg;
let [rel, tmplParams] = await InstallerServer.helper({
unameAgent,
projectName,
tag,
var installersDir = path.join(__dirname, '..');
serveInstaller.serveInstaller = serveInstaller;
module.exports = serveInstaller;
async function serveInstaller(baseurl, ua, pkg, tag, ext, formats) {
// TODO put some of this in a middleware? or common function?
var ver = tag.replace(/^v/, '');
var lts;
var channel;
switch (ver) {
case 'latest':
ver = '';
channel = 'stable';
break;
case 'lts':
lts = true;
channel = 'stable';
ver = '';
break;
case 'stable':
channel = 'stable';
ver = '';
break;
case 'beta':
channel = 'beta';
ver = '';
break;
case 'dev':
channel = 'dev';
ver = '';
break;
}
// TODO maybe move package/version/lts/channel detection into getReleases
var myOs = uaDetect.os(ua);
var myArch = uaDetect.arch(ua);
var myLibc = uaDetect.libc(ua);
let cfg = await packages.get(pkg);
let rels = await getReleases({
pkg: cfg.alias || pkg,
ver,
os: myOs,
arch: myArch,
libc: myLibc,
lts,
channel,
formats,
libc,
limit: 1,
});
Object.assign(tmplParams, {
var rel = rels.releases[0];
var pkgdir = path.join(installersDir, pkg);
var opts = {
baseurl,
});
var pkgdir = Path.join(InstallerServer.INSTALLERS_DIR, projectName);
if ('ps1' === ext) {
return Installers.renderPowerShell(pkgdir, rel, tmplParams);
}
return Installers.renderBash(pkgdir, rel, tmplParams);
};
// TODO put some of this in a middleware? or common function?
// TODO maybe move package/version/lts/channel detection into getReleases
InstallerServer.helper = async function ({
unameAgent,
projectName,
tag,
formats,
libc,
}) {
console.log(`dbg: Installer User-Agent: ${unameAgent}`);
let releaseTarget = toReleaseTarget(tag);
let hostFormats = formats;
let terms = unameAgent.split(/[\s\/]+/g);
let hostTarget = {};
try {
void HostTargets.termsToTarget(hostTarget, terms);
} catch (e) {
// if we can't guarantee the results...
// "in the face of ambiguity, refuse the temptation to guess"
throw e;
}
console.log(`dbg: Installer Host Target:`);
console.log(hostTarget);
if (!hostTarget.os) {
throw new Error(`OS could not be identified by User-Agent '${unameAgent}'`);
}
console.log(`dbg: Get Project Installer Type for '${projectName}':`);
let proj = await Builds.getProjectType(projectName);
console.log(proj);
let validTypes = ['alias', 'selfhosted', 'valid'];
if (!validTypes.includes(proj.type)) {
let msg = `'${projectName}' doesn't have an installer: '${proj.type}': '${proj.detail}'`;
let err = new Error(msg);
err.code = 'ENOENT';
throw err;
}
if (proj.type === 'alias') {
projectName = proj.detail;
}
let tmplParams = {
pkg: projectName,
tag: tag,
os: hostTarget.os,
arch: hostTarget.arch,
libc: hostTarget.libc,
formats: hostFormats,
pkg: cfg.alias || pkg,
ver,
tag,
os: myOs,
arch: myArch,
lts,
channel,
formats,
limit: 1,
};
Object.assign(tmplParams, releaseTarget);
console.log('tmplParams', tmplParams);
rel.oses = rels.oses;
rel.arches = rels.arches;
rel.formats = rels.formats;
let errPackage = {
name: 'doesntexist.ext',
version: '0.0.0',
lts: '-',
channel: 'error',
date: '1970-01-01',
os: hostTarget.os || '-',
arch: hostTarget.arch || '-',
libc: hostTarget.libc || '-',
ext: 'err',
download: 'https://example.com/doesntexist.ext',
comment:
'No matches found. Could be bad or missing version info' +
',' +
"Check query parameters. Should be something like '/api/releases/{package}@{version}.tab?os={macos|linux|windows|-}&arch={amd64|x86|aarch64|arm64|armv7l|-}&libc={musl|gnu|msvc|libc|static}&limit=10'",
};
if (proj.type === 'selfhosted') {
return [errPackage, tmplParams];
if ('bat' === ext) {
return Releases.renderBatch(pkgdir, rel, opts);
}
let projInfo = await Builds.getPackage({
name: projectName,
date: new Date(),
});
let latest = projInfo.versions[0];
Object.assign(tmplParams, { latest });
//console.log('projInfo', projInfo);
let buildTargetInfo = {
triplets: projInfo.triplets,
oses: projInfo.oses,
arches: projInfo.arches,
libcs: projInfo.libcs,
formats: projInfo.formats,
};
let hasOs = projInfo.oses.includes(hostTarget.os);
if (!hasOs) {
hasOs = projInfo.oses.includes('ANYOS');
if ('ps1' === ext) {
return Releases.renderPowerShell(pkgdir, rel, opts);
}
if (!hasOs) {
let pkg1 = Object.assign(buildTargetInfo, errPackage);
return [pkg1, tmplParams];
}
let targetRelease = Builds.findMatchingPackages(
projInfo,
hostTarget,
releaseTarget,
);
// { triplet: `${os}-${arch}-${libc}`, packages: targetPackages
// , latest: projInfo.versions[0], versions: matchInfo
// }
if (!targetRelease?.packages) {
let pkg1 = Object.assign(buildTargetInfo, errPackage);
return [pkg1, tmplParams];
}
let buildPkg = Builds.selectPackage(targetRelease.packages, hostFormats);
let ext = buildPkg.ext || '.exe';
if (ext.startsWith('.')) {
ext = ext.slice(1);
}
let version = targetRelease.version;
if (version.startsWith('v')) {
version = version.slice(1);
}
buildPkg = Object.assign(buildTargetInfo, buildPkg, { ext, version });
console.log('dbg: buildPkg', buildPkg);
console.log('dbg: tmplParams', tmplParams);
return [buildPkg, tmplParams];
};
let channelNames = [
'stable',
// 'hotfix',
'latest',
'rc',
'preview',
'pre',
'dev',
'beta',
'alpha',
];
function toReleaseTarget(tag) {
tag = tag.replace(/^v/, '');
let releaseTarget = {
channel: '',
lts: false,
version: '',
};
if (tag === 'lts') {
releaseTarget.lts = true;
releaseTarget.channel = 'stable';
} else if (channelNames.includes(tag)) {
releaseTarget.channel = tag;
} else {
releaseTarget.version = tag;
}
return releaseTarget;
return Releases.renderBash(pkgdir, rel, opts);
}
var CURL_PIPE_PS1_BOOT = Path.join(__dirname, 'curl-pipe-bootstrap.tpl.ps1');
var CURL_PIPE_SH_BOOT = Path.join(__dirname, 'curl-pipe-bootstrap.tpl.sh');
var BAD_SH_RE = /[<>'"`$\\]/;
InstallerServer.getPosixCurlPipeBootstrap = async function ({
baseurl,
pkg,
ver,
}) {
let bootTxt = await Fs.readFile(CURL_PIPE_SH_BOOT, 'utf8');
var webiPkg = [pkg, ver].filter(Boolean).join('@');
var webiChecksum = await Installers.getWebiShChecksum();
var envReplacements = [
['WEBI_PKG', webiPkg],
['WEBI_HOST', baseurl],
['WEBI_CHECKSUM', webiChecksum],
];
for (let env of envReplacements) {
let name = env[0];
let value = env[1];
let envRe = new RegExp(
`^[ \\t]*#?[ \\t]*(export[ \\t])?[ \\t]*(${name})=.*`,
'm',
);
if (BAD_SH_RE.test(value)) {
throw new Error(`key '${name}' has invalid value '${value}'`);
}
bootTxt = bootTxt.replace(envRe, `$1$2='${value}'`);
}
// TODO init config here
//bootTxt.replace(/CHEATSHEET_URL/g, `${Config.cheatUrl}/${pkg}`);
return bootTxt;
};
InstallerServer.getPwshCurlPipeBootstrap = async function ({
baseurl,
pkg,
ver,
exename,
}) {
let bootTxt = await Fs.readFile(CURL_PIPE_PS1_BOOT, 'utf8');
var webiPkg = [pkg, ver].filter(Boolean).join('@');
//var webiChecksum = await InstallerServer.getWebiPs1Checksum();
var envReplacements = [
['Env:WEBI_PKG', webiPkg],
['Env:WEBI_HOST', baseurl],
//['Env:WEBI_CHECKSUM', webiChecksum],
['baseurl', baseurl],
['exename', exename],
['version', ver],
];
for (let env of envReplacements) {
let name = env[0];
let value = env[1];
if (BAD_SH_RE.test(value)) {
throw new Error(`key '${name}' has invalid value '${value}'`);
}
let tplRe = new RegExp(`{{ (${name}) }}`, 'g');
bootTxt = bootTxt.replace(tplRe, `${value}`);
let setRe = new RegExp(
`(#[ \\t]*)?(\\$${name})[ \\t]*=[ \\t]['"].*['"][ \\t]`,
'im',
);
bootTxt = bootTxt.replace(setRe, `$$${name} = '${value}'`);
}
// TODO init config here
//bootTxt.replace(/CHEATSHEET_URL/g, `${Config.cheatUrl}/${pkg}`);
return bootTxt;
};

5
_webi/template.bat Normal file
View File

@@ -0,0 +1,5 @@
REM REM debug
REM WEBI_PKG=
{{ installer }}

121
_webi/template.ps1 Normal file
View File

@@ -0,0 +1,121 @@
#!/usr/bin/env pwsh
#350 check if windows user run as admin
function Confirm-IsElevated {
$id = [System.Security.Principal.WindowsIdentity]::GetCurrent()
$p = New-Object System.Security.Principal.WindowsPrincipal($id)
if ($p.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator))
{ Write-Output $true }
else
{ Write-Output $false }
}
if (Confirm-IsElevated)
{ throw "Webi MUST NOT be run with elevated privileges. Please run again as a normal user, NOT as administrator." }
# this allows us to call ps1 files, which allows us to have spaces in filenames
# ('powershell "$Env:USERPROFILE\test.ps1" foo' will fail if it has a space in
# the path but '& "$Env:USERPROFILE\test.ps1" foo' will work even with a space)
Set-ExecutionPolicy -Scope Process Bypass
# If a command returns an error, halt the script.
$ErrorActionPreference = 'Stop'
# Ignore progress events from cmdlets so Invoke-WebRequest is not painfully slow
$ProgressPreference = 'SilentlyContinue'
$Env:WEBI_HOST = 'https://webinstall.dev'
#$Env:WEBI_PKG = 'node@lts'
#$Env:PKG_NAME = node
#$Env:WEBI_VERSION = v12.16.2
#$Env:WEBI_PKG_URL = "https://.../node-....zip"
#$Env:WEBI_PKG_FILE = "node-v12.16.2-win-x64.zip"
# Switch to userprofile
Push-Location $Env:USERPROFILE
# Make paths
New-Item -Path Downloads -ItemType Directory -Force | Out-Null
New-Item -Path .local\bin -ItemType Directory -Force | Out-Null
New-Item -Path .local\opt -ItemType Directory -Force | Out-Null
# {{ baseurl }}
# {{ version }}
function webi_path_add($pathname) {
# C:\Users\me => C:/Users/me
$my_home = $Env:UserProfile
$my_home = $my_home.replace('\\', '/')
$my_home_re = [regex]::escape($my_home)
# ~/bin => %USERPROFILE%/bin
$pathname = $pathname.replace('~/', "$Env:UserProfile/")
# C:\Users\me\bin => %USERPROFILE%/bin
$my_pathname = $pathname.replace('\\', '/')
$my_pathname = $my_pathname -ireplace $my_home_re, "%USERPROFILE%"
$all_user_paths = [Environment]::GetEnvironmentVariable("Path", "User")
$user_paths = $all_user_paths -Split (';')
$exists_in_path = $false
foreach ($user_path in $user_paths) {
# C:\Users\me\bin => %USERPROFILE%/bin
$my_user_path = $user_path.replace('\\', '/')
$my_user_path = $my_user_path -ireplace $my_home_re, "%USERPROFILE%"
if ($my_user_path -ieq $my_pathname) {
$exists_in_path = $true
}
}
if (-Not $exists_in_path) {
$all_user_paths = $pathname + ";" + $all_user_paths
[Environment]::SetEnvironmentVariable("Path", $all_user_paths, "User")
}
$session_paths = $Env:Path -Split (';')
$in_session_path = $false
foreach ($session_path in $session_paths) {
# C:\Users\me\bin => %USERPROFILE%/bin
$my_session_path = $session_path.replace('\\', '/')
$my_session_path = $my_session_path -ireplace $my_home_re, "%USERPROFILE%"
if ($my_session_path -ieq $my_pathname) {
$in_session_path = $true
}
}
if (-Not $in_session_path) {
$my_cmd = 'PATH ' + "$pathname" + ';%PATH%'
$my_pwsh = '$Env:Path = "' + "$pathname" + ';$Env:Path"'
Write-Host ''
Write-Host '**********************************' -ForegroundColor red -BackgroundColor white
Write-Host '* IMPORTANT -- READ ME *' -ForegroundColor red -BackgroundColor white
Write-Host '* (run the PATH command below) *' -ForegroundColor red -BackgroundColor white
Write-Host '**********************************' -ForegroundColor red -BackgroundColor white
Write-Host ''
Write-Output ""
Write-Output "Copy, paste, and run the appropriate command to update your PATH:"
Write-Output "(or close and reopen the terminal, or reboot)"
Write-Output ""
Write-Output "cmd.exe:"
Write-Output " $my_cmd"
Write-Output ""
Write-Output "PowerShell:"
Write-Output " $my_pwsh"
Write-Output ""
}
}
#$has_local_bin = echo "$Env:PATH" | Select-String -Pattern '\.local.bin'
#if (!$has_local_bin)
#{
webi_path_add ~/.local/bin
#}
# {{ installer }}
webi_path_add ~/.local/bin
# Done
Pop-Location

625
_webi/template.sh Normal file
View File

@@ -0,0 +1,625 @@
#!/bin/sh
__bootstrap_webi() {
set -e
set -u
#set -x
my_libc=''
if ldd /bin/ls 2> /dev/null | grep -q 'musl' 2> /dev/null; then
my_libc=' musl-native'
fi
#WEBI_PKG=
#PKG_NAME=
# TODO should this be BASEURL instead?
#WEBI_OS=
#WEBI_ARCH=
#WEBI_HOST=
#WEBI_RELEASES=
#WEBI_CSV=
#WEBI_TAG=
#WEBI_VERSION=
#WEBI_MAJOR=
#WEBI_MINOR=
#WEBI_PATCH=
# TODO not sure if BUILD is the best name for this
#WEBI_BUILD=
#WEBI_LTS=
#WEBI_CHANNEL=
#WEBI_EXT=
#WEBI_FORMATS=
#WEBI_PKG_URL=
#WEBI_PKG_FILE=
#PKG_OSES=
#PKG_ARCHES=
#PKG_FORMATS=
WEBI_UA="$(uname -s)/$(uname -r) $(uname -m)/unknown${my_libc}"
WEBI_PKG_DOWNLOAD=""
WEBI_DOWNLOAD_DIR="${HOME}/Downloads"
if command -v xdg-user-dir > /dev/null; then
WEBI_DOWNLOAD_DIR="$(xdg-user-dir DOWNLOAD)"
if [ "${WEBI_DOWNLOAD_DIR}" = "${HOME}" ]; then
WEBI_DOWNLOAD_DIR="${HOME}/Downloads"
fi
fi
WEBI_PKG_PATH="${WEBI_DOWNLOAD_DIR}/webi/${PKG_NAME:-error}/${WEBI_VERSION:-latest}"
export WEBI_HOST
##
## Set up tmp, download, and install directories
##
WEBI_TMP=${WEBI_TMP:-"$(mktemp -d -t webinstall-"${WEBI_PKG-}".XXXXXXXX)"}
export _webi_tmp="${_webi_tmp:-"$HOME/.local/opt/webi-tmp.d"}"
mkdir -p "${WEBI_PKG_PATH}"
mkdir -p "$HOME/.local/bin"
mkdir -p "$HOME/.local/opt"
##
## Detect http client
##
set +e
WEBI_CURL="$(command -v curl)"
export WEBI_CURL
WEBI_WGET="$(command -v wget)"
export WEBI_WGET
set -e
# get the special formatted version (i.e. "go is go1.14" while node is "node v12.10.8")
my_versioned_name=""
_webi_canonical_name() {
if [ -n "$my_versioned_name" ]; then
echo "$my_versioned_name"
return 0
fi
if [ -n "$(command -v pkg_format_cmd_version)" ]; then
my_versioned_name="'$(pkg_format_cmd_version "$WEBI_VERSION")'"
else
my_versioned_name="'$pkg_cmd_name v$WEBI_VERSION'"
fi
echo "$my_versioned_name"
}
# update symlinks according to $HOME/.local/opt and $HOME/.local/bin install paths.
# shellcheck disable=2120
# webi_link may be used in the templated install script
webi_link() {
if [ -n "$(command -v pkg_link)" ]; then
pkg_link
return 0
fi
if [ -n "$WEBI_SINGLE" ] || [ "single" = "${1-}" ]; then
rm -rf "$pkg_dst_cmd"
ln -s "$pkg_src_cmd" "$pkg_dst_cmd"
else
# 'pkg_dst' will default to $HOME/.local/opt/<pkg>
# 'pkg_src' will be the installed version, such as to $HOME/.local/opt/<pkg>-<version>
rm -rf "$pkg_dst"
ln -s "$pkg_src" "$pkg_dst"
fi
}
# detect if this program is already installed or if an installed version may cause conflict
webi_check() {
# Test for existing version
set +e
my_path="$PATH"
PATH="$(dirname "$pkg_dst_cmd"):$PATH"
export PATH
my_current_cmd="$(command -v "$pkg_cmd_name")"
set -e
if [ -n "$my_current_cmd" ]; then
my_canonical_name="$(_webi_canonical_name)"
if [ "$my_current_cmd" != "$pkg_dst_cmd" ]; then
echo >&2 "WARN: possible PATH conflict between $my_canonical_name and currently installed version"
echo >&2 " ${pkg_dst_cmd} (new)"
echo >&2 " ${my_current_cmd} (existing)"
#my_current_version=false
fi
# 'readlink' can't read links in paths on macOS 🤦
# but that's okay, 'cmp -s' is good enough for us
if cmp -s "${pkg_src_cmd}" "${my_current_cmd}"; then
echo "${my_canonical_name} already installed:"
printf " %s" "${pkg_dst}"
if [ "${pkg_src_cmd}" != "${my_current_cmd}" ]; then
printf " => %s" "${pkg_src}"
fi
echo ""
exit 0
fi
if [ -x "$pkg_src_cmd" ]; then
# shellcheck disable=2119
# this function takes no args
webi_link
echo "switched to $my_canonical_name:"
echo " ${pkg_dst} => ${pkg_src}"
exit 0
fi
fi
export PATH="$my_path"
}
is_interactive_shell() {
# $- shows shell flags (error,unset,interactive,etc)
case $- in
*i*)
# true
return 0
;;
*)
# false
return 1
;;
esac
}
# detect if file is downloaded, and how to download it
webi_download() {
# determine the url to download
if [ -n "${1-}" ]; then
my_url="${1}"
else
if [ "error" = "$WEBI_CHANNEL" ]; then
# TODO pass back requested OS / Arch / Version
echo >&2 "Error: no '$PKG_NAME' release for '${WEBI_OS-}' on '$WEBI_ARCH' as one of '$WEBI_FORMATS' by the tag '${WEBI_TAG-}'"
echo >&2 " '$PKG_NAME' is available for '$PKG_OSES' on '$PKG_ARCHES' as one of '$PKG_FORMATS'"
echo >&2 " (check that the package name and version are correct)"
echo >&2 ""
my_release_url="$(
echo "$WEBI_RELEASES" |
sed 's:\?.*::'
)"
echo >&2 " Double check at ${my_release_url}"
echo >&2 ""
exit 1
fi
my_url="$WEBI_PKG_URL"
fi
# determine the location to download to
if [ -n "${2-}" ]; then
my_dl="${2}"
else
my_dl="${WEBI_PKG_PATH}/$WEBI_PKG_FILE"
fi
if [ -n "${3-}" ]; then
my_dl_name="${3}"
else
my_dl_name="${PKG_NAME}"
fi
WEBI_PKG_DOWNLOAD="${my_dl}"
export WEBI_PKG_DOWNLOAD
if [ -e "$my_dl" ]; then
echo "Found $my_dl"
return 0
fi
echo "Downloading ${my_dl_name} from"
echo "$my_url"
# It's only 2020, we can't expect to have reliable CLI tools
# to tell us the size of a file as part of a base system...
if [ -n "$WEBI_WGET" ]; then
# wget has resumable downloads
# TODO wget -c --content-disposition "$my_url"
set +e
my_show_progress=""
if is_interactive_shell; then
my_show_progress="--show-progress"
fi
if ! wget -q $my_show_progress --user-agent="wget $WEBI_UA" -c "$my_url" -O "$my_dl.part"; then
echo >&2 "failed to download from $WEBI_PKG_URL"
exit 1
fi
set -e
else
# Neither GNU nor BSD curl have sane resume download options, hence we don't bother
# TODO curl -fsSL --remote-name --remote-header-name --write-out "$my_url"
my_show_progress="-#"
if is_interactive_shell; then
my_show_progress=""
fi
# shellcheck disable=SC2086
# we want the flags to be split
curl -fSL $my_show_progress -H "User-Agent: curl $WEBI_UA" "$my_url" -o "$my_dl.part"
fi
mv "$my_dl.part" "$my_dl"
echo ""
echo "Saved as $my_dl"
}
# detect which archives can be used
webi_extract() {
(
cd "$WEBI_TMP"
if [ "tar" = "$WEBI_EXT" ]; then
echo "Extracting ${WEBI_PKG_PATH}/$WEBI_PKG_FILE"
tar xf "${WEBI_PKG_PATH}/$WEBI_PKG_FILE"
elif [ "zip" = "$WEBI_EXT" ]; then
echo "Extracting ${WEBI_PKG_PATH}/$WEBI_PKG_FILE"
unzip "${WEBI_PKG_PATH}/$WEBI_PKG_FILE" > __unzip__.log
elif [ "exe" = "$WEBI_EXT" ]; then
echo "Moving ${WEBI_PKG_PATH}/$WEBI_PKG_FILE"
mv "${WEBI_PKG_PATH}/$WEBI_PKG_FILE" .
elif [ "xz" = "$WEBI_EXT" ]; then
echo "Inflating ${WEBI_PKG_PATH}/$WEBI_PKG_FILE"
unxz -c "${WEBI_PKG_PATH}/$WEBI_PKG_FILE" > "$(basename "$WEBI_PKG_FILE")"
else
# do nothing
echo "Failed to extract ${WEBI_PKG_PATH}/$WEBI_PKG_FILE"
exit 1
fi
)
}
# use 'pathman' to update $HOME/.config/envman/PATH.env
webi_path_add() {
my_path="${1}"
fn_envman_init
# \v was chosen as it is extremely unlikely for a filename
# \1 could be an even better choice, but needs more testing.
# (currently tested working on: linux & mac)
# "\0001" should also work
my_delim="$(
printf '\v'
)"
my_path_expanded="$(
echo "${my_path}" |
sed -e "s${my_delim}\$HOME${my_delim}$HOME${my_delim}g" \
-e "s${my_delim}\${HOME}${my_delim}$HOME${my_delim}g" \
-e "s${my_delim}^~/${my_delim}$HOME/${my_delim}g"
)"
# A gift for @adamcstephens.
# See https://github.com/webinstall/webi-installers/issues/322
case "${PATH}" in
# matches whether the first, a middle, the last, or the only PATH entry
"${my_path_expanded}":* | \
*:"${my_path_expanded}":* | \
*:"${my_path_expanded}" | \
"${my_path_expanded}")
if fn_is_defined_in_all_shells "${my_path}"; then
return 0
fi
;;
*) ;;
esac
my_path_export="$(
echo "${my_path}" |
sed -e "s${my_delim}${HOME}${my_delim}\$HOME${my_delim}g" \
-e "s${my_delim}\${HOME}${my_delim}\$HOME${my_delim}g" \
-e "s${my_delim}^~/${my_delim}\$HOME/${my_delim}g"
)"
my_export="export PATH=\"$my_path_export:\$PATH\""
if grep -q -F "${my_export}" ~/.config/envman/PATH.env; then
return 0
fi
echo "${my_export}" >> ~/.config/envman/PATH.env
mkdir -p "$_webi_tmp"
my_path_tilde="$(
echo "${my_path}" |
sed -e "s${my_delim}${HOME}${my_delim}~${my_delim}g"
)"
if ! test -f "$_webi_tmp/.PATH.env" ||
! grep -q -F "${my_path_tilde}" "$_webi_tmp/.PATH.env"; then
echo "${my_path_tilde}" >> "$_webi_tmp/.PATH.env"
fi
}
fn_envman_init() {
mkdir -p ~/.config/envman/
if ! test -e ~/.config/envman/PATH.env; then
touch ~/.config/envman/PATH.env
fi
if ! test -e ~/.config/envman/load.sh; then
# shellcheck disable=SC2016
{
echo '# Generated for envman. Do not edit.'
echo 'for x in ~/.config/envman/*.env; do'
echo ' my_basename="$(basename "${x}")"'
echo ' if [ "*.env" = "${my_basename}" ]; then'
echo ' continue'
echo ' fi'
echo ''
echo ' # shellcheck source=/dev/null'
echo ' . "${x}"'
echo 'done'
} > ~/.config/envman/load.sh
fi
if command -v sh > /dev/null; then
if test -e ~/.profile; then
if ! grep -q -F '/.config/envman/load.sh' ~/.profile; then
fn_echo_load_sh >> ~/.profile
fi
fi
fi
if command -v bash > /dev/null; then
if test -e ~/.bashrc; then
if ! grep -q -F '/.config/envman/load.sh' ~/.bashrc; then
fn_echo_load_sh >> ~/.bashrc
fi
fi
fi
if command -v zsh > /dev/null; then
if test -e ~/.zshrc; then
if ! grep -q -F '/.config/envman/load.sh' ~/.zshrc; then
fn_echo_load_sh >> ~/.zshrc
fi
fi
fi
if command -v fish > /dev/null; then
if test ! -e ~/.config/envman/load.fish; then
# shellcheck disable=SC2016
{
echo '# Generated for envman. Do not edit.'
echo 'for x in ~/.config/envman/*.env'
echo ' source "$x"'
echo 'end'
} > ~/.config/envman/load.fish
fi
mkdir -p ~/.config/fish
if test -e ~/.config/fish/config.fish; then
touch ~/.config/fish/config.fish
fi
if ! grep -q -F '/.config/envman/load.fish' ~/.config/fish/config.fish; then
fn_echo_load_fish >> ~/.config/fish/config.fish
fi
fi
}
fn_echo_load_fish() {
echo ''
echo '# Generated for envman. Do not edit.'
# shellcheck disable=SC2016
echo 'test -s "$HOME/.config/envman/load.fish"; and source "$HOME/.config/envman/load.fish"'
}
fn_echo_load_sh() {
echo ''
echo '# Generated for envman. Do not edit.'
# shellcheck disable=SC2016
echo '[ -s "$HOME/.config/envman/load.sh" ] && source "$HOME/.config/envman/load.sh"'
}
fn_is_defined_in_all_shells() {
my_path="${1}"
my_path_expanded="$(
echo "${my_path}" |
sed -e "s${my_delim}\$HOME|${my_delim}$HOME${my_delim}g" \
-e "s${my_delim}\${HOME}${my_delim}$HOME${my_delim}g" \
-e "s${my_delim}^~/${my_delim}$HOME/${my_delim}g"
)"
my_paths="$(
echo "${my_path_expanded}"
# $HOME/foo
echo "${my_path_expanded}" |
sed "s${my_delim}${HOME}${my_delim}\$HOME${my_delim}g"
# ${HOME}/foo
echo "${my_path_expanded}" |
sed "s${my_delim}${HOME}${my_delim}\${HOME}${my_delim}g"
echo "${my_path}"
)"
my_confs="$(
echo "${HOME}/.profile"
echo "${HOME}/.bashrc"
echo "${HOME}/.zshrc"
echo "${HOME}/.config/fish/config.fish"
)"
for my_conf in $my_confs; do
if test -e "${my_conf}"; then
if ! grep -q -F "${my_paths}" "${my_conf}"; then
return 1
fi
fi
done
}
# group common pre-install tasks as default
webi_pre_install() {
webi_check
webi_download
webi_extract
}
# move commands from the extracted archive directory to $HOME/.local/opt or $HOME/.local/bin
# shellcheck disable=2120
# webi_install may be sourced and used elsewhere
webi_install() {
if [ -n "$WEBI_SINGLE" ] || [ "single" = "${1-}" ]; then
mkdir -p "$(dirname "$pkg_src_cmd")"
mv ./"$pkg_cmd_name"* "$pkg_src_cmd"
else
rm -rf "$pkg_src"
mv ./"$pkg_cmd_name"* "$pkg_src"
fi
}
# run post-install functions - just updating PATH by default
webi_post_install() {
webi_path_add "$(dirname "$pkg_dst_cmd")"
}
_webi_enable_exec() {
if [ -n "$(command -v spctl)" ] && [ -n "$(command -v xattr)" ]; then
# note: some packages contain files that cannot be affected by xattr
xattr -r -d com.apple.quarantine "$pkg_src" || true
return 0
fi
# TODO need to test that the above actually worked
# (and proceed to this below if it did not)
if [ -n "$(command -v spctl)" ]; then
echo "Checking permission to execute '$pkg_cmd_name' on macOS 11+"
set +e
is_allowed="$(spctl -a "$pkg_src_cmd" 2>&1 | grep valid)"
set -e
if [ -z "$is_allowed" ]; then
echo ""
echo "##########################################"
echo "# IMPORTANT: Permission Grant Required #"
echo "##########################################"
echo ""
echo "Requesting permission to execute '$pkg_cmd_name' on macOS 10.14+"
echo ""
sleep 3
spctl --add "$pkg_src_cmd"
fi
fi
}
# a friendly message when all is well, showing the final install path in $HOME/.local
_webi_done_message() {
echo "Installed $(_webi_canonical_name) as $pkg_dst_cmd"
}
##
##
## BEGIN custom override functions from <package>/install.sh
##
##
WEBI_SINGLE=
if [ -z "${WEBI_WELCOME-}" ]; then
echo ""
printf "Thanks for using webi to install '\e[32m%s\e[0m' on '\e[33m%s/%s\e[0m'.\n" "${WEBI_PKG-}" "$(uname -s)" "$(uname -m)"
echo "Have a problem? Experience a bug? Please let us know:"
printf " \e[2m\e[36mhttps://github.com/webinstall/webi-installers/issues\e[0m\n"
echo ""
printf "\e[35mLovin'\e[0m it? Say thanks with a \e[1m\e[33mStar on GitHub\e[0m:\n"
printf " \e[36mhttps://github.com/webinstall/webi-installers\e[0m\n"
echo ""
fi
__init_installer() {
# do nothing - to satisfy parser prior to templating
printf ""
# {{ installer }}
}
__init_installer
##
##
## END custom override functions
##
##
# run everything with defaults or overrides as needed
if command -v pkg_install > /dev/null ||
command -v pkg_link > /dev/null ||
command -v pkg_post_install > /dev/null ||
command -v pkg_done_message > /dev/null ||
command -v pkg_format_cmd_version > /dev/null ||
[ -n "${WEBI_SINGLE-}" ] ||
[ -n "${pkg_cmd_name-}" ] ||
[ -n "${pkg_dst_cmd-}" ] ||
[ -n "${pkg_dst_dir-}" ] ||
[ -n "${pkg_dst-}" ] ||
[ -n "${pkg_src_cmd-}" ] ||
[ -n "${pkg_src_dir-}" ] ||
[ -n "${pkg_src-}" ]; then
pkg_cmd_name="${pkg_cmd_name:-$PKG_NAME}"
if [ -n "$WEBI_SINGLE" ]; then
pkg_dst_cmd="${pkg_dst_cmd:-$HOME/.local/bin/$pkg_cmd_name}"
pkg_dst="$pkg_dst_cmd" # "$(dirname "$(dirname $pkg_dst_cmd)")"
#pkg_src_cmd="${pkg_src_cmd:-$HOME/.local/opt/$pkg_cmd_name-v$WEBI_VERSION/bin/$pkg_cmd_name-v$WEBI_VERSION}"
pkg_src_cmd="${pkg_src_cmd:-$HOME/.local/opt/$pkg_cmd_name-v$WEBI_VERSION/bin/$pkg_cmd_name}"
pkg_src="$pkg_src_cmd" # "$(dirname "$(dirname $pkg_src_cmd)")"
else
pkg_dst="${pkg_dst:-$HOME/.local/opt/$pkg_cmd_name}"
pkg_dst_cmd="${pkg_dst_cmd:-$pkg_dst/bin/$pkg_cmd_name}"
pkg_src="${pkg_src:-$HOME/.local/opt/$pkg_cmd_name-v$WEBI_VERSION}"
pkg_src_cmd="${pkg_src_cmd:-$pkg_src/bin/$pkg_cmd_name}"
fi
# this script is templated and these are used elsewhere
# shellcheck disable=SC2034
pkg_src_bin="$(dirname "$pkg_src_cmd")"
# shellcheck disable=SC2034
pkg_dst_bin="$(dirname "$pkg_dst_cmd")"
if [ -n "$(command -v pkg_pre_install)" ]; then pkg_pre_install; else webi_pre_install; fi
(
cd "$WEBI_TMP"
echo "Installing to $pkg_src_cmd"
if [ -n "$(command -v pkg_install)" ]; then pkg_install; else webi_install; fi
chmod a+x "$pkg_src"
chmod a+x "$pkg_src_cmd"
)
webi_link
_webi_enable_exec
(
cd "$WEBI_TMP"
if [ -n "$(command -v pkg_post_install)" ]; then pkg_post_install; else webi_post_install; fi
)
(
cd "$WEBI_TMP"
if [ -n "$(command -v pkg_done_message)" ]; then pkg_done_message; else _webi_done_message; fi
)
echo ""
fi
webi_path_add "$HOME/.local/bin"
if [ -z "${_WEBI_CHILD-}" ] && [ -f "$_webi_tmp/.PATH.env" ]; then
if [ -n "$(cat "$_webi_tmp/.PATH.env")" ]; then
printf 'PATH.env updated with:\n'
sort -u "$_webi_tmp/.PATH.env" | while read -r my_new_path; do
echo " ${my_new_path}"
done
printf "\n"
rm -f "$_webi_tmp/.PATH.env"
printf "\e[1m\e[35mTO FINISH\e[0m: copy, paste & run the following command:\n"
printf "\n"
printf " \e[1m\e[32msource ~/.config/envman/PATH.env\e[0m\n"
printf " (newly opened terminal windows will update automatically)\n"
fi
fi
# cleanup the temp directory
rm -rf "$WEBI_TMP"
# See? No magic. Just downloading and moving files.
}
__bootstrap_webi

View File

@@ -32,10 +32,8 @@ if (/\b-?-h(elp)?\b/.test(process.argv.join(' '))) {
var os = require('os');
var fs = require('fs');
var path = require('path');
var Builds = require('./builds.js');
var Installers = require('./installers.js');
var ServeInstaller = require('./serve-installer.js');
var Releases = require('./releases.js');
var uaDetect = require('./ua-detect.js');
var pkg = process.argv[2].split('@');
var pkgdir = pkg[0];
var pkgtag = pkg[1] || '';
@@ -44,7 +42,6 @@ var nodes = fs.readdirSync(pkgdir);
nodes.forEach(function (node) {
nodesMap[node] = true;
});
var baseurl = 'https://webinstall.dev';
var maxLen = 0;
console.info('');
@@ -65,49 +62,31 @@ console.info('Has the necessary files?');
});
console.info('');
let projName = pkgdir.split('/').filter(Boolean).pop();
Builds.getPackage({ name: projName }).then(async function (/*projInfo*/) {
Releases.get(path.join(process.cwd(), pkgdir)).then(function (all) {
var pkgname = path.basename(pkgdir.replace(/\/$/, ''));
var nodeOs = os.platform();
var nodeOsRelease = os.release();
var nodeArch = os.arch();
var nodeLibc = 'libc';
if (process.platform === 'linux') {
nodeLibc = 'gnu';
let isUnofficial =
process.config.variables.node_release_urlbase.includes('unofficial');
if (isUnofficial) {
nodeLibc = 'musl';
}
}
var formats = ['exe', 'xz', 'tar', 'zip', 'git'];
var osrel = os.platform() + '-' + os.release();
var arch = os.arch();
var formats = ['exe', 'xz', 'tar', 'zip'];
let [rel, opts] = await ServeInstaller.helper({
unameAgent: `${nodeOs}/${nodeOsRelease} ${nodeArch}/unknown ${nodeLibc}`,
projectName: pkgname,
tag: pkgtag || '',
formats: formats,
libc: nodeLibc,
});
Object.assign(
{
ver: '',
lts: null,
channel: '',
os: '',
arch: '',
limit: 0,
},
opts,
{
baseurl,
},
);
var rel = all.releases.filter(function (rel) {
return (
formats.filter(function (ext) {
return rel.ext.match(ext);
})[0] &&
'stable' === rel.channel &&
rel.os === uaDetect.os(osrel) &&
rel.arch === uaDetect.arch(arch) &&
(!pkgtag ||
rel.tag === pkgtag ||
new RegExp('^' + pkgtag).test(rel.version))
);
})[0];
rel.oses = all.oses;
rel.arches = all.arches;
rel.formats = all.formats;
if (!rel) {
console.error(
`Error: ❌ no release found for @${pkgtag}?os=${nodeOs}&arch=${nodeArch}&libc=${nodeLibc}&formats=${formats}`,
);
console.error('Error: ❌ no release found for current os, arch, and tag');
process.exit(1);
return;
}
@@ -118,8 +97,24 @@ Builds.getPackage({ name: projName }).then(async function (/*projInfo*/) {
console.info('');
return Promise.all([
Installers.renderBash(pkgdir, rel, opts).catch(function () {}),
Installers.renderPowerShell(pkgdir, rel, opts).catch(function () {}),
Releases.renderBash(pkgdir, rel, {
baseurl: 'https://webinstall.dev',
pkg: pkgname,
tag: pkgtag || '',
ver: '',
os: osrel,
arch,
formats: formats,
}).catch(function () {}),
Releases.renderPowerShell(pkgdir, rel, {
baseurl: 'https://webinstall.dev',
pkg: pkgname,
tag: pkgtag || '',
ver: '',
os: osrel,
arch,
formats: formats,
}).catch(function () {}),
]).then(function (scripts) {
var bashTxt = scripts[0];
var ps1Txt = scripts[1];
@@ -143,9 +138,5 @@ Builds.getPackage({ name: projName }).then(async function (/*projInfo*/) {
console.info('\tNEEDS MANUAL TEST: powershell.exe %s', ps1File);
}
console.info('');
setTimeout(function () {
console.warn(`[warn] dangling event loop handle`);
process.exit(0);
}, 300).unref();
});
});

View File

@@ -1,10 +1,8 @@
'use strict';
var Releases = module.exports;
var path = require('path');
var request = require('@root/request');
var _normalize = require('./normalize.js');
var Releases = require('./releases.js');
var uaDetect = require('./ua-detect.js');
var cache = {};
//var staleAge = 5 * 1000;
@@ -14,21 +12,6 @@ var expiredAge = 15 * 60 * 1000;
let installerDir = path.join(__dirname, '..');
Releases.get = async function (pkgdir) {
let get;
try {
get = require(path.join(pkgdir, 'releases.js'));
} catch (e) {
let err = new Error('no releases.js for', pkgdir.split(/[\/\\]+/).pop());
err.code = 'E_NO_RELEASE';
throw err;
}
let all = await get(request);
return _normalize(all);
};
// TODO needs a proper test, and more accurate (though perhaps far less simple) code
function createFormatsSorter(formats) {
return function sortByVerExt(a, b) {
@@ -71,14 +54,21 @@ function createFormatsSorter(formats) {
return 1;
}
// rank builds that don't depend on any form of libc first
if (a.libc === 'none' && b.libc !== 'none') {
return -1;
}
if (a.libc !== 'none' && b.libc === 'none') {
// Hacky-doo for musl-native: prefer non-musl
if (a._musl_native && !b._musl_native) {
return 1;
}
if (!a._musl_native && b._musl_native) {
return -1;
}
// Hacky-doo for linux: prefer musl
if (a._musl && !b._musl) {
return -1;
}
if (!a._musl && b._musl) {
return 1;
}
return 0;
};
}
@@ -140,7 +130,7 @@ async function getCachedReleases(pkg) {
cache[pkg].all = all;
complete = true;
}),
sleep(15000).then(function () {
sleep(5000).then(function () {
if (complete) {
return;
}
@@ -192,35 +182,24 @@ async function filterReleases(
function selectMatches(rel) {
if (os) {
if (rel.os !== '*') {
if (rel.os !== os) {
return false;
}
if (rel.os !== os) {
return false;
}
}
if (arch) {
if (rel.arch !== '*') {
if (rel.arch !== arch) {
return false;
}
if (rel.arch !== arch) {
return false;
}
}
if (rel.libc !== 'none') {
let releaseRequiresMusl = rel.libc === 'musl';
// goal: handle non-glibc (Alpine / Docker / musl)
let osHasMusl = libc === 'musl';
if (osHasMusl) {
// goal: fail if dependent on libc
let releaseRequiresLibc = rel.libc === 'gnu';
if (releaseRequiresLibc) {
return false;
}
} else if (releaseRequiresMusl) {
// goal: don't use musl++ on glibc (Ubuntu, GNU, etc)
// Hacky-doo for linux musl
if (libc === uaDetect.MUSL_NATIVE) {
if (!rel._musl && !rel._musl_native) {
return false;
}
} else if (rel._musl_native) {
return false;
}
if (lts) {
@@ -260,7 +239,7 @@ async function filterReleases(
return sortedRels.slice(0, limit || 1000);
}
Releases.getReleases = function ({
module.exports = function getReleases({
_count,
pkg,
ver,
@@ -296,19 +275,10 @@ Releases.getReleases = function ({
console.error(err);
})
.then(function (releases) {
if (releases.length) {
return {
oses: all.oses,
arches: all.arches,
libcs: all.libcs,
formats: all.formats,
releases: releases,
};
}
if (_count < 1) {
if (!releases.length) {
// Apple Silicon M1 hacky-do workaround fix
if ('macos' === os && 'arm64' === arch) {
return Releases.getReleases({
return getReleases({
pkg,
ver,
os,
@@ -322,7 +292,7 @@ Releases.getReleases = function ({
}
// Windows ARM hacky-do workaround fix
if ('windows' === os && 'arm64' === arch) {
return Releases.getReleases({
return getReleases({
pkg,
ver,
os,
@@ -334,42 +304,9 @@ Releases.getReleases = function ({
limit,
});
}
// Raspberry Pi 3+ on Ubuntu arm64 (via Bionic?)
if ('linux' === os && 'arm64' === arch) {
return Releases.getReleases({
_count: _count + 1,
pkg,
ver,
os,
arch: 'armv7l',
libc,
lts,
channel,
formats,
limit,
});
}
// armv7 can run armv6
if ('linux' === os && 'armv7l' === arch) {
return Releases.getReleases({
_count: _count + 1,
pkg,
ver,
os,
arch: 'armv6l',
libc,
lts,
channel,
formats,
limit,
});
}
}
if (_count < 2) {
// Raspberry Pi 3+ on Raspbian arm7 (not Ubuntu arm64)
// hail mary
if ('linux' === os && 'armv7l' === arch) {
return Releases.getReleases({
// Raspberry Pi 3+ on Raspbian x86 (not Ubuntu arm64)
if (!_count && 'linux' === os && 'armv7l' === arch) {
return getReleases({
_count: _count + 1,
pkg,
ver,
@@ -382,29 +319,60 @@ Releases.getReleases = function ({
limit,
});
}
// Raspberry Pi 3+ on Ubuntu arm64 (via Bionic?)
// (this may be the same as the prior search, that's okay)
if ('linux' === os && 'arm64' === arch) {
return getReleases({
_count: _count + 1,
pkg,
ver,
os,
arch: 'armv7l',
libc,
lts,
channel,
formats,
limit,
});
}
// Raspberry Pi 3+ on Ubuntu arm64 (via Bionic?)
if ('linux' === os && 'armv7l' === arch) {
return getReleases({
_count: _count + 1,
pkg,
ver,
os,
arch: 'armv6l',
libc,
lts,
channel,
formats,
limit,
});
}
releases = [
{
name: 'doesntexist.ext',
version: '0.0.0',
lts: '-',
channel: 'error',
date: '1970-01-01',
os: os || '-',
arch: arch || '-',
_musl: undefined,
_musl_native: undefined,
ext: 'err',
download: 'https://example.com/doesntexist.ext',
comment:
'No matches found. Could be bad or missing version info' +
',' +
"Check query parameters. Should be something like '/api/releases/{package}@{version}.tab?os={macos|linux|windows|-}&arch={amd64|x86|aarch64|arm64|armv7l|-}&limit=100'",
},
];
}
releases = [
{
name: 'doesntexist.ext',
version: '0.0.0',
lts: '-',
channel: 'error',
date: '1970-01-01',
os: os || '-',
arch: arch || '-',
libc: libc || '-',
ext: 'err',
download: 'https://example.com/doesntexist.ext',
comment:
'No matches found. Could be bad or missing version info' +
',' +
"Check query parameters. Should be something like '/api/releases/{package}@{version}.tab?os={macos|linux|windows|-}&arch={amd64|x86|aarch64|arm64|armv7l|-}&libc={musl|gnu|msvc|libc|static}&limit=10'",
},
];
return {
oses: all.oses,
arches: all.arches,
libcs: all.libcs,
formats: all.formats,
releases: releases,
};
@@ -420,7 +388,7 @@ if (require.main === module) {
os: 'macos',
arch: 'amd64',
lts: true,
libc: 'libc',
libc: '',
channel: 'stable',
formats: ['tar', 'exe', 'zip', 'xz', 'dmg', 'pkg'],
limit: 10,

View File

@@ -2,6 +2,10 @@
var uaDetect = module.exports;
const MUSL_NATIVE = 'musl-native';
uaDetect.MUSL_NATIVE = MUSL_NATIVE;
function getRequest(req) {
var ua = req.headers['user-agent'] || '';
var os = req.query.os;
@@ -67,9 +71,9 @@ function getArch(ua) {
if (/aarch64|arm64|arm8|armv8/i.test(ua)) {
return 'arm64';
} else if (/aarch|arm7|armv7|arm32/i.test(ua)) {
} else if (/aarch|arm7|armv7/i.test(ua)) {
return 'armv7l';
} else if (/arm6|armv6|arm(\b|_)/i.test(ua)) {
} else if (/arm6|armv6/i.test(ua)) {
return 'armv6l';
} else if (/ppc64le/i.test(ua)) {
return 'ppc64le';
@@ -96,55 +100,19 @@ function getLibc(ua) {
return '-';
}
// How to see a bunch of target host quadruples:
// go tool dist list
// rustup target list
// zig targets | jq -r '.libc[]' | sort -
// Use native 'libc' information, if provided
//
// Generally, we prefer 'musl' builds because they DO work on glibc systems (Ubuntu),
// but 'glibc' builds will NOT work on musl systems (Alpine / Docker).
//
// However, there are a few instances (ex: Node.js), where the 'musl' builds
// DO NOT work on glibc systems.
{
let muslRe = /(\b|_)(musl)(\b|_)/i;
if (muslRe.test(ua)) {
return 'musl';
}
if (ua.match(MUSL_NATIVE)) {
return MUSL_NATIVE;
}
{
let msvcRe = /(\b|_)(msvc|windows|microsoft)(\b|_)/i;
if (msvcRe.test(ua)) {
return 'msvc';
}
}
{
let gnuRe = /(\b|_)(gnu|glibc|linux)(\b|_)/i;
if (gnuRe.test(ua)) {
return 'gnu';
}
}
{
let libcRe = /(\b|_)(libc)(\b|_)/i;
if (libcRe.test(ua)) {
return 'libc';
}
}
{
let darwinRe = /(\b|_)(darwin)(\b|_)/i;
if (darwinRe.test(ua)) {
// not sure whether this should be "darwin" or "libc" or "none"
// https://opensource.apple.com/source/Libc/
return 'libc';
}
}
return 'libc';
// TODO handle explicit invalid different
return '';
}
uaDetect.os = getOs;

112
_webi/webi-pwsh.ps1 Normal file
View File

@@ -0,0 +1,112 @@
#!/usr/bin/env pwsh
# this allows us to call ps1 files, which allows us to have spaces in filenames
# ('powershell "$Env:USERPROFILE\test.ps1" foo' will fail if it has a space in
# the path but '& "$Env:USERPROFILE\test.ps1" foo' will work even with a space)
Set-ExecutionPolicy -Scope Process Bypass
# If a command returns an error, halt the script.
$ErrorActionPreference = 'Stop'
# Ignore progress events from cmdlets so Invoke-WebRequest is not painfully slow
$ProgressPreference = 'SilentlyContinue'
# This is the canonical CPU arch when the process is emulated
$my_arch = "$Env:PROCESSOR_ARCHITEW6432"
IF ($my_arch -eq $null -or $my_arch -eq "") {
# This is the canonical CPU arch when the process is native
$my_arch = "$Env:PROCESSOR_ARCHITECTURE"
}
IF ($my_arch -eq "AMD64") {
# Because PowerShell isn't ARM yet.
# See https://oofhours.com/2020/02/04/powershell-on-windows-10-arm64/
$my_os_arch = wmic os get osarchitecture
# Using -clike because of the trailing newline
IF ($my_os_arch -clike "ARM 64*") {
$my_arch = "ARM64"
}
}
$Env:WEBI_UA = "Windows/10 $my_arch"
$exename = $args[0]
# Switch to userprofile
Push-Location $Env:USERPROFILE
# Make paths if needed
New-Item -Path .local\bin -ItemType Directory -Force | Out-Null
# TODO replace all xbin with opt\bin\
New-Item -Path .local\xbin -ItemType Directory -Force | Out-Null
# See note on Set-ExecutionPolicy above
Set-Content -Path .local\bin\webi.bat -Value "@echo off`r`npushd %USERPROFILE%`r`npowershell -ExecutionPolicy Bypass .local\bin\webi-pwsh.ps1 %1`r`npopd"
# Backwards-compat bugfix: remove old webi-pwsh.ps1 location
Remove-Item -Path .local\bin\webi.ps1 -Recurse -ErrorAction Ignore
if (!(Test-Path -Path .local\opt)) {
New-Item -Path .local\opt -ItemType Directory -Force | Out-Null
}
# TODO windows version of mktemp -d
if (!(Test-Path -Path .local\tmp)) {
New-Item -Path .local\tmp -ItemType Directory -Force | Out-Null
}
# TODO SetStrictMode
# TODO Test-Path variable:global:Env:WEBI_HOST ???
IF ($null -eq $Env:WEBI_HOST -or $Env:WEBI_HOST -eq "") {
$Env:WEBI_HOST = "https://webinstall.dev"
}
# {{ baseurl }}
# {{ version }}
$my_version = 'v1.1.15'
## show help if no params given or help flags are used
if ($null -eq $exename -or $exename -eq "-h" -or $exename -eq "--help" -or $exename -eq "help" -or $exename -eq "/?") {
Write-Host "webi " -ForegroundColor Green -NoNewline; Write-Host "$my_version " -ForegroundColor Red -NoNewline; Write-Host "Copyright 2020+ AJ ONeal"
Write-Host " https://webinstall.dev/webi" -ForegroundColor blue
Write-Output ""
Write-Output "SUMMARY"
Write-Output " Webi is the best way to install the modern developer tools you love."
Write-Output " It's fast, easy-to-remember, and conflict free."
Write-Output ""
Write-Output "USAGE"
Write-Output " webi <thing1>[@version] [thing2] ..."
Write-Output ""
Write-Output "UNINSTALL"
Write-Output " Almost everything that is installed with webi is scoped to"
Write-Output " ~/.local/opt/<thing1>, so you can remove it like so:"
Write-Output ""
Write-Output " rmdir /s %USERPROFILE%\.local\opt\<thing1>"
Write-Output " del %USERPROFILE%\.local\bin\<thing1>"
Write-Output ""
Write-Output " Some packages have special uninstall instructions, check"
Write-Output " https://webinstall.dev/<thing1> to be sure."
Write-Output ""
Write-Output "FAQ"
Write-Host " See " -NoNewline; Write-Host "https://webinstall.dev/faq" -ForegroundColor blue
Write-Output ""
Write-Output "ALWAYS REMEMBER"
Write-Output " Friends don't let friends use brew for simple, modern tools that don't need it."
exit 0
}
if ($exename -eq "-V" -or $exename -eq "--version" -or $exename -eq "version" -or $exename -eq "/v") {
Write-Host "webi " -ForegroundColor Green -NoNewline; Write-Host "$my_version " -ForegroundColor Red -NoNewline; Write-Host "Copyright 2020+ AJ ONeal"
Write-Host " https://webinstall.dev/webi" -ForegroundColor blue
exit 0
}
# Fetch <whatever>.ps1
# TODO detect formats
$PKG_URL = "$Env:WEBI_HOST/api/installers/$exename.ps1?formats=zip,exe,tar"
Write-Output "Downloading $PKG_URL"
# Invoke-WebRequest -UserAgent "Windows amd64" "$PKG_URL" -OutFile ".\.local\tmp\$exename.install.ps1"
& curl.exe -fsSL -A "$Env:WEBI_UA" "$PKG_URL" -o .\.local\tmp\$exename.install.ps1
# Run <whatever>.ps1
powershell .\.local\tmp\$exename.install.ps1
# Done
Pop-Location

27
_webi/webi.bat Normal file
View File

@@ -0,0 +1,27 @@
@echo off
pushd "%userprofile%" || goto :error
IF NOT EXIST .local (
mkdir .local || goto :error
)
IF NOT EXIST .local\bin (
mkdir .local\bin || goto :error
)
IF NOT EXIST .local\opt (
mkdir .local\opt || goto :error
)
echo Downloading and installing %1
powershell $ProgressPreference = 'SilentlyContinue'; Invoke-WebRequest https://webinstall.dev/packages/%1/install.ps1 -OutFile %1-webinstall.bat || goto :error
rem TODO only add if it's not in there already
PATH .local\bin;%PATH%
call %1-webinstall.bat || goto :error
del %1-webinstall.bat || goto :error
popd
goto :EOF
:error
echo Failed with error #%errorlevel%.
exit /b %errorlevel%

1
_webi/webi.ps1.bat Normal file
View File

@@ -0,0 +1 @@
powershell -ExecutionPolicy Bypass .\.local\bin\webi-pwsh.ps1 %1

View File

@@ -17,7 +17,6 @@ module.exports = function (request) {
];
let oses = ['freebsd', 'linux', 'macos', 'posix'];
return githubSource(request, owner, repo, oses, arches).then(function (all) {
all._names = ['aliasman', 'legacy'];
return all;
});
};

View File

@@ -6,7 +6,6 @@ var repo = 'archiver';
module.exports = function (request) {
return github(request, owner, repo).then(function (all) {
all._names = ['archiver', 'arc'];
return all;
});
};

View File

@@ -2,4 +2,4 @@
Write-Output "'archiver@$Env:WEBI_TAG' is an alias for 'arc@$Env:WEBI_VERSION'"
IF ($null -eq $Env:WEBI_HOST -or $Env:WEBI_HOST -eq "") { $Env:WEBI_HOST = "https://webinstall.dev" }
curl.exe -A MS -fsSL "$Env:WEBI_HOST/arc@$Env:WEBI_VERSION" | powershell
curl.exe -fsSL "$Env:WEBI_HOST/arc@$Env:WEBI_VERSION" | powershell

View File

@@ -8,13 +8,10 @@ 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',
@@ -23,12 +20,11 @@ let targets = {
x64lin: {
os: 'linux',
arch: 'amd64',
libc: 'gnu',
},
x64musl: {
os: 'linux',
arch: 'amd64',
libc: 'musl',
_musl_native: true,
},
};
@@ -65,7 +61,6 @@ module.exports = function (request) {
continue;
}
}
all._names = ['AtomicParsley', 'atomicparsley'];
return all;
});
};

View File

@@ -15,7 +15,7 @@ IF (-not (Test-Path "\Windows\System32\vcruntime140.dll")) {
& "$Env:USERPROFILE\.local\bin\webi-pwsh.ps1" vcruntime
}
IF (!(Test-Path -Path "$Env:USERPROFILE\.local\bin\$VERNAME")) {
IF (!(Test-Path -Path "$Env:USERPROFILE\.local\xbin\$VERNAME")) {
Write-Output "Installing $Env:PKG_NAME"
# TODO: temp directory
@@ -35,13 +35,13 @@ IF (!(Test-Path -Path "$Env:USERPROFILE\.local\bin\$VERNAME")) {
# Settle unpacked archive into place
Write-Output "New Name: $VERNAME"
Write-Output "New Location: $Env:USERPROFILE\.local\bin\$VERNAME"
Move-Item -Path "$VERNAME" -Destination "$Env:USERPROFILE\.local\bin"
Write-Output "New Location: $Env:USERPROFILE\.local\xbin\$VERNAME"
Move-Item -Path "$VERNAME" -Destination "$Env:USERPROFILE\.local\xbin"
# Exit tmp
Pop-Location
}
Write-Output "Copying into '$Env:USERPROFILE\.local\bin\$EXENAME' from '$Env:USERPROFILE\.local\bin\$VERNAME'"
Write-Output "Copying into '$Env:USERPROFILE\.local\bin\$EXENAME' from '$Env:USERPROFILE\.local\xbin\$VERNAME'"
Remove-Item -Path "$Env:USERPROFILE\.local\bin\$EXENAME" -Recurse -ErrorAction Ignore
Copy-Item -Path "$Env:USERPROFILE\.local\bin\$VERNAME" -Destination "$Env:USERPROFILE\.local\bin\$EXENAME" -Recurse
Copy-Item -Path "$Env:USERPROFILE\.local\xbin\$VERNAME" -Destination "$Env:USERPROFILE\.local\bin\$EXENAME" -Recurse

View File

@@ -19,7 +19,7 @@ __init_bat() {
}
pkg_install() {
# ~/.local/bin
# ~/.local/xbin
mkdir -p "$pkg_src_bin"
# mv ./bat-*/bat ~/.local/opt/bat-v0.15.4/bin/bat

View File

@@ -6,23 +6,14 @@ main() { (
sed '1,/^#~\/.local\/bin\/brew-updater/d' "${0}" > ~/.local/bin/brew-update-hourly
chmod a+x ~/.local/bin/brew-update-hourly
echo "Checking for serviceman..."
if ! command -v serviceman > /dev/null; then
"$HOME/.local/bin/webi" serviceman
export PATH="$HOME/.local/bin:$PATH"
serviceman --version
fi
env PATH="$PATH" serviceman add --user \
--workdir ~/.local/opt/brew/ \
--name sh.brew.updater -- \
~/.local/bin/brew-update-hourly
); }
if ! main; then
exit 1
if main; then
exit 0
fi
exit 0
#~/.local/bin/brew-updater
#!/bin/sh

View File

@@ -8,17 +8,10 @@ module.exports = function (request) {
return github(request, owner, repo).then(function (all) {
all.releases = all.releases
.filter(function (r) {
let isDebug = r.name.includes('-profile');
if (isDebug) {
return false;
let isDebug = /-profile/.test(r.name);
if (!isDebug) {
return true;
}
let isAncient = r.name.includes('-baseline');
if (isAncient) {
return false;
}
return true;
})
.map(function (r) {
// bun-v0.5.1 => v0.5.1

File diff suppressed because it is too large Load Diff

View File

@@ -8,12 +8,7 @@ module.exports = function (request) {
return github(request, 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 !/(\.txt)|(\.deb)$/i.test(rel.name);
});
return all;
});

View File

@@ -20,28 +20,20 @@ __init_chromedriver() {
# pkg_install must be defined by every package
pkg_install() {
# ~/.local/opt/chromedriver-v121.0.6130.0/bin
# ~/.local/opt/chromedriver-v88.0.4324.96/bin
mkdir -p "$(dirname "$pkg_src_cmd")"
# mv ./chromedriver-macos-arm64/chromedriver \
# ~/.local/opt/chromedriver-v121.0.6130.0/bin/chromedriver
mv ./chromedriver-*/chromedriver "$pkg_src_cmd"
echo ""
echo " $(t_warn 'MANUAL STEPS TO FINISH:') you may need to install libnss3:"
echo " $(t_cmd 'sudo apt install -y') $(t_warn 'libnss3')"
# mv ./chromedriver-*/chromedriver ~/.local/opt/chromedriver-v88.0.4324.96/bin/chromedriver
mv ./chromedriver* "$pkg_src_cmd"
}
# pkg_get_current_version is recommended, but (soon) not required
pkg_get_current_version() {
# 'chromedriver --version' has output in this format:
# ChromeDriver 121.0.6130.0 (5fc19c7ab5e88bb674c2efc65db4b7890a52a4ec-refs/branch-heads/6130@{#1})
# ChromeDriver 88.0.4324.96 (68dba2d8a0b149a1d3afac56fa74648032bcf46b-refs/branch-heads/4324@{#1784})
# This trims it down to just the version number:
# 121.0.6130.0
chromedriver --version 2> /dev/null |
head -n 1 |
tr -s ' ' |
cut -d' ' -f2
# 88.0.4324.96
chromedriver --version 2> /dev/null | head -n 1 | cut -d ' ' -f 2
}
}

View File

@@ -1,77 +1,81 @@
'use strict';
// See <https://googlechromelabs.github.io/chrome-for-testing/>
var releaseApiUrl =
'https://googlechromelabs.github.io/chrome-for-testing/known-good-versions-with-downloads.json';
var matchers = {
key: /.*Key>(.*)<\/Key.*/,
generation: /.*Generation>(.*)<\/Generation.*/,
metaGeneration: /.*MetaGeneration>(.*)<\/MetaGeneration.*/,
lastModified: /.*LastModified>(.*)<\/LastModified.*/,
etag: /.*ETag>(.*)<\/ETag.*/,
size: /.*Size>(.*)<\/Size.*/,
};
var baseUrl = 'https://chromedriver.storage.googleapis.com';
// {
// "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 (request) {
let resp = await request({
url: releaseApiUrl,
json: true,
});
let builds = [];
for (let release of resp.body.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 Apline
libc: 'none',
};
builds.push(build);
}
}
let all = {
module.exports = function (request) {
var all = {
download: '',
releases: builds,
releases: [],
};
return all;
// XML
return request({
url: 'https://chromedriver.storage.googleapis.com/',
json: false,
})
.then(function (resp) {
var body = resp.body;
var groups = body.split(/<\/?Contents>/g);
// get rid of leading and trailing junk
groups.shift();
groups.pop();
var metas = groups.map(function (group) {
return {
key: group.replace(matchers.key, '$1'),
//generation: group.replace(matchers.generation, '$1'),
//metaGeneration: group.replace(matchers.metaGeneration, '$1'),
lastModified: group.replace(matchers.lastModified, '$1'),
//etag: group.replace(matchers.etag, '$1'),
//size: group.replace(matchers.size, '$1')
};
});
all.download = baseUrl + '/{{ download }}';
metas.forEach(function (asset) {
if (!asset.key.includes('chromedriver')) {
// skip the indexes, images, etc
return null;
}
var osname = asset.key.replace(/.*(win|mac|linux)/, '$1');
var arch;
if (asset.key.includes('linux')) {
osname = 'linux';
} else if (asset.key.includes('mac64')) {
osname = 'macos';
if (asset.key.includes('_m1.')) {
arch = 'arm64';
}
} else if (asset.key.includes('win')) {
osname = 'windows';
arch = 'amd64';
}
all.releases.push({
// 87.0.4280.88/chromedriver_win32.zip => 87.0.4280.88
version: asset.key.replace(/(.*)\/.*/, '$1'),
lts: false,
channel: 'stable',
date: asset.lastModified.replace(/T.*/, '$1'),
os: osname,
arch: arch,
hash: '-', // not sure about including etag as hash yet
download: asset.key,
});
});
})
.then(function () {
all.releases.sort(function (a, b) {
return new Date(b.date).valueOf() - new Date(a.date).valueOf();
});
return all;
});
};
if (module === require.main) {

View File

@@ -1,47 +0,0 @@
---
title: cilium
homepage: https://github.com/cilium/cilium-cli
tagline: |
cilium: manage & troubleshoot Kubernetes clusters running Cilium
---
To update or switch versions, run `webi cilium@stable` (or `@v2`, `@beta`,etc).
### Files
These are the files / directories that are created and/or modified with this
install:
```text
~/.config/envman/PATH.env
~/.local/bin/cilium
~/.local/opt/cilium/
```
## Cheat Sheet
> Cilium is an open source, cloud native solution for providing, securing, and
> observing network connectivity between workloads, fueled by the revolutionary
> Kernel technology eBPF.
Quick Start User Guide:
<https://docs.cilium.io/en/stable/gettingstarted/k8s-install-default/#k8s-install-quick>
To install the default version of the Cilium image:
```sh
cilium install
```
To upgrade to a specific version of the Cilium image:
```sh
cilium upgrade --version v1.15.3
```
To check the status of the current Cilium deployment:
```sh
cilium status
```

View File

@@ -1,45 +0,0 @@
#!/usr/bin/env pwsh
##################
# Install cilium #
##################
$pkg_cmd_name = "cilium"
$pkg_dst_cmd = "$Env:USERPROFILE\.local\bin\cilium.exe"
$pkg_dst = "$pkg_dst_cmd"
$pkg_src_cmd = "$Env:USERPROFILE\.local\opt\cilium-v$Env:WEBI_VERSION\bin\cilium.exe"
$pkg_src_bin = "$Env:USERPROFILE\.local\opt\cilium-v$Env:WEBI_VERSION\bin"
$pkg_src_dir = "$Env:USERPROFILE\.local\opt\cilium-v$Env:WEBI_VERSION"
$pkg_src = "$pkg_src_cmd"
New-Item "$Env:USERPROFILE\Downloads\webi" -ItemType Directory -Force | Out-Null
$pkg_download = "$Env:USERPROFILE\Downloads\webi\$Env:WEBI_PKG_FILE"
IF (!(Test-Path -Path "$Env:USERPROFILE\Downloads\webi\$Env:WEBI_PKG_FILE")) {
Write-Output "Downloading cilium from $Env:WEBI_PKG_URL to $pkg_download"
& curl.exe -A "$Env:WEBI_UA" -fsSL "$Env:WEBI_PKG_URL" -o "$pkg_download.part"
& Move-Item "$pkg_download.part" "$pkg_download"
}
IF (!(Test-Path -Path "$pkg_src_cmd")) {
Write-Output "Installing cilium"
Push-Location .local\tmp
Remove-Item -Path ".\cilium-v*" -Recurse -ErrorAction Ignore
Remove-Item -Path ".\cilium.exe" -Recurse -ErrorAction Ignore
Write-Output "Unpacking $pkg_download"
& tar xf "$pkg_download"
Write-Output "Install Location: $pkg_src_cmd"
New-Item "$pkg_src_bin" -ItemType Directory -Force
Move-Item -Path ".\cilium-*\cilium.exe" -Destination "$pkg_src_bin"
Pop-Location
}
Write-Output "Copying into '$pkg_dst_cmd' from '$pkg_src_cmd'"
Remove-Item -Path "$pkg_dst_cmd" -Recurse -ErrorAction Ignore
Copy-Item -Path "$pkg_src" -Destination "$pkg_dst" -Recurse

View File

@@ -1,39 +0,0 @@
#!/bin/sh
__init_cilium() {
set -e
set -u
##################
# Install cilium #
##################
pkg_cmd_name="cilium"
pkg_dst_cmd="$HOME/.local/bin/cilium"
pkg_dst="$pkg_dst_cmd"
pkg_src_cmd="$HOME/.local/opt/cilium-v$WEBI_VERSION/bin/cilium"
pkg_src_dir="$HOME/.local/opt/cilium-v$WEBI_VERSION"
pkg_src="$pkg_src_cmd"
WEBI_SINGLE=true
# pkg_install must be defined by every package
pkg_install() {
# ~/.local/opt/cilium-v0.16.16/bin
mkdir -p "$(dirname "${pkg_src_cmd}")"
# mv ./hugo ~/.local/opt/cilium-v0.16.16/bin/
mv ./cilium "${pkg_src_cmd}"
}
pkg_get_current_version() {
cilium version 2> /dev/null |
head -n 1 |
cut -d ' ' -f 2
}
}
__init_cilium

View File

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

View File

@@ -8,19 +8,19 @@
$pkg_cmd_name = "cmake"
$pkg_dst_cmd = "$Env:USERPROFILE\.local\opt\cmake\bin\cmake.exe"
$pkg_dst_bin = "$Env:USERPROFILE\.local\opt\cmake\bin"
$pkg_dst_dir = "$Env:USERPROFILE\.local\opt\cmake"
$pkg_dst = "$pkg_dst_dir"
$pkg_dst = "$pkg_dst_cmd"
$pkg_src_cmd = "$Env:USERPROFILE\.local\opt\cmake-v$Env:WEBI_VERSION\bin\cmake.exe"
$pkg_src_bin = "$Env:USERPROFILE\.local\opt\cmake-v$Env:WEBI_VERSION\bin"
$pkg_src_dir = "$Env:USERPROFILE\.local\opt\cmake-v$Env:WEBI_VERSION"
$pkg_src = "$pkg_src_dir"
$pkg_src = "$pkg_src_cmd"
New-Item "$Env:USERPROFILE\Downloads\webi" -ItemType Directory -Force | Out-Null
$pkg_download = "$Env:USERPROFILE\Downloads\webi\$Env:WEBI_PKG_FILE"
# Fetch archive
IF (!(Test-Path -Path "$pkg_download")) {
IF (!(Test-Path -Path "$Env:USERPROFILE\Downloads\webi\$Env:WEBI_PKG_FILE")) {
Write-Output "Downloading cmake from $Env:WEBI_PKG_URL to $pkg_download"
& curl.exe -A "$Env:WEBI_UA" -fsSL "$Env:WEBI_PKG_URL" -o "$pkg_download.part"
& Move-Item "$pkg_download.part" "$pkg_download"
@@ -49,8 +49,6 @@ IF (!(Test-Path -Path "$pkg_src_dir")) {
Pop-Location
}
Write-Output "Copying into '$pkg_dst' from '$pkg_src'"
Remove-Item -Path "$pkg_dst" -Recurse -ErrorAction Ignore | Out-Null
Write-Output "Copying into '$pkg_dst_cmd' from '$pkg_src_cmd'"
Remove-Item -Path "$pkg_dst_cmd" -Recurse -ErrorAction Ignore | Out-Null
Copy-Item -Path "$pkg_src" -Destination "$pkg_dst" -Recurse
webi_path_add "$pkg_dst_bin"
& "$pkg_dst_cmd" --version

View File

@@ -28,11 +28,7 @@ __init_cmake() {
mkdir -p "$(dirname "${pkg_src_dir}")"
# mv ./cmake-*/ ~/.local/opt/cmake-v3.27.0/
if test -e ./cmake-*/CMake.app/Contents/; then
mv ./cmake-*/CMake.app/Contents/ "${pkg_src_dir}"
else
mv ./cmake-*/ "${pkg_src_dir}"
fi
mv ./cmake-*/ "${pkg_src_dir}"
}
# pkg_get_current_version is recommended, but not required

View File

@@ -6,39 +6,6 @@ var repo = 'CMake';
module.exports = function (request) {
return github(request, 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;
});
};

View File

@@ -1,55 +0,0 @@
---
title: XCode Command Line Tools
homepage: https://webinstall.dev/commandlinetools
tagline: |
The XCode Command Line Tools include git, swift, make, clang, and other developer tools
---
## Cheat Sheet
> The developer tools provided by Apple for macOS.
- git
- swift
- make
- clang
- etc
This is also part of [webi-essentials](../webi-essentials/).
## Table of Contents
- Files
- Manual Install
- macOS
- Linux
- Alpine
- Windows
### Files
These are the files / directories that are created and/or modified with this
install:
```sh
/Library/Developer/CommandLineTools/
```
### How to Install Manually
It's very easy to start the installer:
```sh
xcode-select --install
```
The trick is to also have a mechanism to know when it has finished:
```sh
while ! test -x /Library/Developer/CommandLineTools/usr/bin/git ||
! test -x /Library/Developer/CommandLineTools/usr/bin/make; do
sleep 0.25
done
echo "Command Line Tools Installed"
```

View File

@@ -1,62 +0,0 @@
#!/bin/sh
set -e
set -u
fn_install_xcode_commandlinetools() { (
b_os="$(uname -s)"
if test "${b_os}" != 'Darwin'; then
echo >&2 'XCode Command Line Tools are for macOS only'
return 1
fi
# streamline the output to be pretty
fn_check_pkg '/Library/Developer/CommandLineTools/usr/bin/clang' 'clang'
fn_check_pkg '/Library/Developer/CommandLineTools/usr/bin/git' 'git'
fn_check_pkg '/Library/Developer/CommandLineTools/usr/bin/make' 'make'
echo >&2 ""
# git
if xcode-select -p > /dev/null 2> /dev/null; then
echo ""
return 0
fi
cmd_xcode_cli_tools_install="xcode-select --install"
echo " Running $(t_cmd "${cmd_xcode_cli_tools_install}")"
$cmd_xcode_cli_tools_install 2> /dev/null
echo ""
echo ">>> $(t_attn 'ACTION REQUIRED') <<<"
echo ""
echo " $(t_attn "Click") '$(t_bold 'Install')' $(t_attn "in the pop-up")"
echo " (it may appear $(t_em 'under') this window)"
echo ""
echo "^^^ $(t_attn 'ACTION REQUIRED') ^^^"
echo ""
printf " waiting %s to finish installing Command Line Developer Tools ..." "$(t_em 'for you')"
while ! test -x /Library/Developer/CommandLineTools/usr/bin/git ||
! test -x /Library/Developer/CommandLineTools/usr/bin/make; do
sleep 0.25
done
echo " $(t_info 'OK')"
echo " Installed to $(t_path '/Library/Developer/CommandLineTools/')"
sleep 1
); }
fn_check_pkg() { (
a_pkg="${1}"
a_pkgname="${2:-$a_pkg}"
printf >&2 ' %s %s %s' \
"$(t_dim "Checking for")" \
"$(t_pkg "${a_pkgname}")" \
"$(t_dim "...")"
if command -v "${a_pkg}" > /dev/null; then
echo >&2 " $(t_dim 'OK')"
return 0
fi
echo >&2 ' missing'
); }
fn_install_xcode_commandlinetools

View File

@@ -4,28 +4,8 @@ var github = require('../_common/github.js');
var owner = 'kivikakk';
var repo = 'comrak';
var ODDITIES = ['-musleabihf.1-'];
module.exports = function (request) {
return github(request, 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;
});
};

View File

@@ -1,116 +0,0 @@
---
title: Crabz
homepage: https://github.com/sstadick/crabz
tagline: |
crabz: multi-threaded gzip (like pigz, but in Rust)
---
To update or switch versions, run `webi crabz@stable` (or `@0.8`, `@beta`, etc).
## Cheat Sheet
> `crabz` brings the power of multi-core compression to gzip and deflate. \
> (and a few other formats + other useful features)
gzip, faster.
```sh
crabz -I ./example.json
crabz -d -I ./example.json.gz
```
```text
Compressing (gzip) with 8 threads at compression level 6.
Decompressing (gzip) with 8 threads available.
```
## Table of Contents
- Files
- Tar
- Other Formats
### Files
These are the files/directories that are created and/or modified with this
install:
```text
~/.config/envman/PATH.env
~/.local/bin/crabz
```
#### How to Optimize
| Flag | Value | Comments |
| ----------------------------- | ----- | ------------------------------------------ |
| `-l`, `--compression-level` | 1-9 | higher is slower |
| `-p`, `--compression-threads` | 8 | set to the number of available cores |
| | | (but no more than 4 for decompression) |
| `-P`, `--pin-at` | 0 | pin to physical cores, starting at N |
| | | (so 4 threads starting at 0 is 0, 1, 2, 3) |
```sh
crabz -l 9 -p 8 -I ./example.tar
crabz -d -p 4 -I ./example.tar.gz
```
#### How to use with Tar
Tar and then compress:
```sh
tar cv ./example/ | crabz -o ./example.tar.gz
```
Or decompress and then untar:
```sh
crabz -d ./example.tar.gz | tar xv
```
#### How to use with other formats
`crabz` supports most of the _LZ77 with Huffman coding_ compression formats:
| Format | Extension | Notes |
| ---------------- | --------- | ------------------------------------ |
| `gzip` | `.gz` | of GNU fame |
| [`bgzf`][bgzf] | `.gz` | supports random-access decompression |
| [`mgzip`][mgzip] | `.gz` | of python fame |
| `zlib` | `.zz` | of PNG fame, also `.z` |
| [`snap`][snap] | `.sz` | of LevelDB and MongoDB fame |
| `deflate` | `.gz` | the O.G. LZ77 |
```sh
crabz --format mgzip -I ./example.tar
```
```sh
# DO NOT decompress in-place
crabz --format mgzip -d ./example.tar.gz -o ./example.tar
# verify before removing the original
tar tf ./example.tar
```
⚠️ **Warnings**:
- DO NOT deflate in-place with non-standard formats: \
Although `gunzip` will work correctly on files compressed with `mgzip` or
`bgzf`, some combinations (ex: decompressing from `mgzip` with `bgzf`) could
result in corruption!
- `tar xvf` and `gzip -l` may report incorrect information, even though `gunzip`
will work
See also:
- https://dev.to/biellls/compression-clearing-the-confusion-on-zip-gzip-zlib-and-deflate-15g1
(p.s. `zip` isn't in the list because it's a container format like `tar`, not a
zip format)
[snap]: https://github.com/google/snappy/blob/main/format_description.txt
[bgzf]: https://samtools.github.io/hts-specs/SAMv1.pdf
[mgzip]: https://pypi.org/project/mgzip/

View File

@@ -1,48 +0,0 @@
#!/usr/bin/env pwsh
#################
# Install crabz #
#################
# Every package should define these variables
$pkg_cmd_name = "crabz"
$pkg_dst_cmd = "$Env:USERPROFILE\.local\bin\crabz.exe"
$pkg_dst_bin = "$Env:USERPROFILE\.local\bin"
$pkg_dst = "$pkg_dst_cmd"
$pkg_src_cmd = "$Env:USERPROFILE\.local\opt\crabz-v$Env:WEBI_VERSION\bin\crabz.exe"
$pkg_src_bin = "$Env:USERPROFILE\.local\opt\crabz-v$Env:WEBI_VERSION\bin"
$pkg_src_dir = "$Env:USERPROFILE\.local\opt\crabz-v$Env:WEBI_VERSION"
$pkg_src = "$pkg_src_cmd"
New-Item "$Env:USERPROFILE\Downloads\webi" -ItemType Directory -Force | Out-Null
$pkg_download = "$Env:USERPROFILE\Downloads\webi\$Env:WEBI_PKG_FILE"
# Fetch archive
IF (!(Test-Path -Path "$pkg_download")) {
Write-Output "Downloading crabz from $Env:WEBI_PKG_URL to $pkg_download"
& curl.exe -A "$Env:WEBI_UA" -fsSL "$Env:WEBI_PKG_URL" -o "$pkg_download.part"
& Move-Item "$pkg_download.part" "$pkg_download"
}
IF (!(Test-Path -Path "$pkg_src_cmd")) {
Write-Output "Installing crabz"
# TODO: create package-specific temp directory
# Enter tmp
Push-Location .local\tmp
# Settle unpacked archive into place
Write-Output "Install Location: $pkg_src_cmd"
New-Item "$pkg_src_bin" -ItemType Directory -Force | Out-Null
Copy-Item -Path "$pkg_download" -Destination "$pkg_src_cmd"
# Exit tmp
Pop-Location
}
Write-Output "Copying into '$pkg_dst_cmd' from '$pkg_src_cmd'"
Remove-Item -Path "$pkg_dst_cmd" -Recurse -ErrorAction Ignore | Out-Null
New-Item "$pkg_dst_bin" -ItemType Directory -Force | Out-Null
Copy-Item -Path "$pkg_src" -Destination "$pkg_dst" -Recurse

View File

@@ -1,49 +0,0 @@
#!/bin/sh
# The generic functions - version checks, download, extract, etc - are here:
# - https://github.com/webinstall/packages/branches/master/_webi/template.sh
set -e
set -u
pkg_cmd_name="crabz"
# IMPORTANT: this let's other functions know to expect this to be a single file
WEBI_SINGLE=true
# Every package should define these 6 variables
pkg_cmd_name="crabz"
pkg_dst_cmd="$HOME/.local/bin/crabz"
#pkg_dst="$pkg_dst_cmd"
pkg_src_cmd="$HOME/.local/opt/crabz-v$WEBI_VERSION/bin/crabz"
#pkg_src_dir="$HOME/.local/opt/crabz-v$WEBI_VERSION/bin"
#pkg_src="$pkg_src_cmd"
pkg_get_current_version() {
# 'crabz version' has output in this format:
# crabz git:xxxxxxx
# Since that's not sortable, this prints v0.0.0
# v0.0.0
echo "v0.0.0"
}
pkg_install() {
# $HOME/.local/opt/crabz-v0.3.5/bin
mkdir -p "${pkg_src_bin}"
# mv ./crabz* "$HOME/.local/opt/crabz-v0.3.5/bin/crabz"
mv ./"${pkg_cmd_name}"* "${pkg_src_cmd}"
# chmod a+x "$HOME/.local/opt/crabz-v0.3.5/bin/crabz"
chmod a+x "${pkg_src_cmd}"
}
pkg_link() {
# rm -f "$HOME/.local/bin/crabz"
rm -f "${pkg_dst_cmd}"
# ln -s "$HOME/.local/opt/crabz-v0.3.5/bin/crabz" "$HOME/.local/bin/crabz"
ln -s "${pkg_src_cmd}" "${pkg_dst_cmd}"
}

View File

@@ -1,31 +0,0 @@
'use strict';
var github = require('../_common/github.js');
var owner = 'sstadick';
var repo = 'crabz';
module.exports = async function (request) {
let all = await github(request, 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(require('@root/request')).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

@@ -9,7 +9,7 @@ IF (!(Test-Path -Path "$Env:USERPROFILE\Downloads\webi\$Env:WEBI_PKG_FILE")) {
& Move-Item "$Env:USERPROFILE\Downloads\webi\$Env:WEBI_PKG_FILE.part" "$Env:USERPROFILE\Downloads\webi\$Env:WEBI_PKG_FILE"
}
IF (!(Test-Path -Path "$Env:USERPROFILE\.local\bin\$VERNAME")) {
IF (!(Test-Path -Path "$Env:USERPROFILE\.local\xbin\$VERNAME")) {
Write-Output "Installing $Env:PKG_NAME"
# TODO: temp directory
@@ -29,13 +29,13 @@ IF (!(Test-Path -Path "$Env:USERPROFILE\.local\bin\$VERNAME")) {
# Settle unpacked archive into place
Write-Output "New Name: $VERNAME"
Write-Output "New Location: $Env:USERPROFILE\.local\bin\$VERNAME"
Move-Item -Path "$VERNAME" -Destination "$Env:USERPROFILE\.local\bin"
Write-Output "New Location: $Env:USERPROFILE\.local\xbin\$VERNAME"
Move-Item -Path "$VERNAME" -Destination "$Env:USERPROFILE\.local\xbin"
# Exit tmp
Pop-Location
}
Write-Output "Copying into '$Env:USERPROFILE\.local\bin\$EXENAME' from '$Env:USERPROFILE\.local\bin\$VERNAME'"
Write-Output "Copying into '$Env:USERPROFILE\.local\bin\$EXENAME' from '$Env:USERPROFILE\.local\xbin\$VERNAME'"
Remove-Item -Path "$Env:USERPROFILE\.local\bin\$EXENAME" -Recurse -ErrorAction Ignore
Copy-Item -Path "$Env:USERPROFILE\.local\bin\$VERNAME" -Destination "$Env:USERPROFILE\.local\bin\$EXENAME" -Recurse
Copy-Item -Path "$Env:USERPROFILE\.local\xbin\$VERNAME" -Destination "$Env:USERPROFILE\.local\bin\$EXENAME" -Recurse

View File

@@ -19,7 +19,7 @@ __init_curlie() {
}
pkg_install() {
# $HOME/.local/bin
# $HOME/.local/xbin
mkdir -p "$pkg_src_bin"
# mv ./curlie* "$HOME/.local/opt/curlie-v1.3.1/bin/curlie"

View File

@@ -6,7 +6,6 @@ var repo = 'curlie';
module.exports = function (request) {
return github(request, owner, repo).then(function (all) {
all._names = ['curlie', 'curl-httpie'];
return all;
});
};

View File

@@ -1,14 +1,7 @@
# for exposing RPCs, building APIs
txindex=1
addressindex=1
timestampindex=1
spentindex=1
# listen as server (explicit default)
listen=1
# because its already run as a service (systemd, openrc)
daemon=0
# for evonodes
#server=1
[main]
rpcuser=RPCUSER_MAIN

View File

@@ -17,7 +17,7 @@ fn_usage() { (
); }
fn_datadir_help() { (
my_vol="${1}"
my_vol="${1:-}"
my_user="$(
id -n -u
)"
@@ -25,7 +25,6 @@ fn_datadir_help() { (
id -n -g
)"
my_mount="$(dirname "${my_vol}")"
echo >&2 ""
echo >&2 "ERROR"
echo >&2 " '${my_vol}' is not writable"
@@ -33,8 +32,8 @@ fn_datadir_help() { (
echo >&2 "SOLUTION"
echo >&2 " 1. Mount a large (50gb+) volume"
echo >&2 ""
echo >&2 " sudo mkdir -p ${my_mount}"
echo >&2 " sudo mount /dev/sdx1 ${my_mount}"
echo >&2 " sudo mkdir -p /mnt/EXAMPLE"
echo >&2 " sudo mount /dev/sdx1 /mnt/EXAMPLE"
echo >&2 ""
echo >&2 " 2. Create a 'dashcore' inside of it"
echo >&2 ""
@@ -112,7 +111,7 @@ fn_srv_install() { (
echo "Installing 'serviceman'..."
echo ""
{
"$HOME/.local/bin/webi" serviceman
curl -fsSL "${WEBI_HOST}/serviceman" | sh
} > /dev/null
# shellcheck disable=SC1090

View File

@@ -5,38 +5,32 @@ set -u
__install_dashcore_utils() {
webi_download \
"$WEBI_HOST/packages/dashcore-utils/dash-qt-hd" \
"$HOME/.local/bin/dash-qt-hd" \
"dash-qt-hd"
"$HOME/.local/bin/dash-qt-hd"
chmod a+x "$HOME/.local/bin/dash-qt-hd"
webi_download \
"$WEBI_HOST/packages/dashcore-utils/dash-qt-testnet" \
"$HOME/.local/bin/dash-qt-testnet" \
"dash-qt-testnet"
"$HOME/.local/bin/dash-qt-testnet"
chmod a+x "$HOME/.local/bin/dash-qt-testnet"
webi_download \
"$WEBI_HOST/packages/dashcore-utils/dashd-hd" \
"$HOME/.local/bin/dashd-hd" \
"dashd-hd"
"$HOME/.local/bin/dashd-hd"
chmod a+x "$HOME/.local/bin/dashd-hd"
webi_download \
"$WEBI_HOST/packages/dashcore-utils/dashd-testnet" \
"$HOME/.local/bin/dashd-testnet" \
"dashd-testnet"
"$HOME/.local/bin/dashd-testnet"
chmod a+x "$HOME/.local/bin/dashd-testnet"
webi_download \
"$WEBI_HOST/packages/dashcore-utils/dashd-hd-service-install" \
"$HOME/.local/bin/dashd-hd-service-install" \
"dashd-hd-service-install"
"$HOME/.local/bin/dashd-hd-service-install"
chmod a+x "$HOME/.local/bin/dashd-hd-service-install"
webi_download \
"$WEBI_HOST/packages/dashcore-utils/dashd-testnet-service-install" \
"$HOME/.local/bin/dashd-testnet-service-install" \
"dashd-testnet-service-install"
"$HOME/.local/bin/dashd-testnet-service-install"
chmod a+x "$HOME/.local/bin/dashd-testnet-service-install"
if ! test -e "${HOME}/.dashcore"; then
@@ -50,8 +44,7 @@ __install_dashcore_utils() {
webi_download \
"$WEBI_HOST/packages/dashcore-utils/dash.example.conf" \
"$HOME/.dashcore/dash.example.conf" \
"dash.example.conf"
"$HOME/.dashcore/dash.example.conf"
if ! grep -q rpcuser ~/.dashcore/dash.conf; then
cat ~/.dashcore/dash.example.conf >> ~/.dashcore/dash.conf

View File

@@ -6,17 +6,14 @@ var repo = 'dash';
module.exports = function (request) {
return github(request, owner, repo).then(function (all) {
all.releases = all.releases.filter(function (rel) {
return !rel.name.endsWith('.asc');
});
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;
});
};

View File

@@ -24,7 +24,7 @@ install:
~/.config/envman/PATH.env
~/.dashcore/dash.conf
~/.dashcore/wallets/
~/.local/bin/dashd-hd-service-install
~/.local/bin/bin/dashd-hd-service-install
~/.local/opt/dashcore/
/mnt/<BLK_VOL>/dashcore/
```

View File

@@ -23,13 +23,13 @@ pkg_get_current_version() {
}
pkg_install() {
# $HOME/.local/bin
# $HOME/.local/xbin
mkdir -p "$pkg_src_bin"
# mv ./deno* "$HOME/.local/bin/deno-v1.1.0"
# mv ./deno* "$HOME/.local/xbin/deno-v1.1.0"
mv ./"$pkg_cmd_name"* "$pkg_src_cmd"
# chmod a+x "$HOME/.local/bin/deno-v1.1.0"
# chmod a+x "$HOME/.local/xbin/deno-v1.1.0"
chmod a+x "$pkg_src_cmd"
}
@@ -37,6 +37,6 @@ pkg_link() {
# rm -f "$HOME/.local/bin/deno"
rm -f "$pkg_dst_cmd"
# ln -s "$HOME/.local/bin/deno-v1.1.0" "$HOME/.local/bin/deno"
# ln -s "$HOME/.local/xbin/deno-v1.1.0" "$HOME/.local/bin/deno"
ln -s "$pkg_src_cmd" "$pkg_dst_cmd"
}

View File

@@ -11,12 +11,7 @@ module.exports = function (request) {
// remove checksums and .deb
all.releases = all.releases
.filter(function (rel) {
let isMeta = rel.name.endsWith('.d.ts');
if (isMeta) {
return false;
}
return true;
return !/(\.txt)|(\.deb)$/i.test(rel.name);
})
.map(function (rel) {
var ext;

View File

@@ -17,7 +17,6 @@ module.exports = function (request) {
];
let oses = ['freebsd', 'linux', 'macos', 'posix'];
return githubSource(request, owner, repo, oses, arches).then(function (all) {
all._names = ['DuckDNS.sh', 'duckdns.sh', 'legacy'];
return all;
});
};

View File

@@ -9,7 +9,7 @@ IF (!(Test-Path -Path "$Env:USERPROFILE\Downloads\webi\$Env:WEBI_PKG_FILE")) {
& Move-Item "$Env:USERPROFILE\Downloads\webi\$Env:WEBI_PKG_FILE.part" "$Env:USERPROFILE\Downloads\webi\$Env:WEBI_PKG_FILE"
}
IF (!(Test-Path -Path "$Env:USERPROFILE\.local\bin\$VERNAME")) {
IF (!(Test-Path -Path "$Env:USERPROFILE\.local\xbin\$VERNAME")) {
Write-Output "Installing $Env:PKG_NAME"
# TODO: temp directory
@@ -29,13 +29,13 @@ IF (!(Test-Path -Path "$Env:USERPROFILE\.local\bin\$VERNAME")) {
# Settle unpacked archive into place
Write-Output "New Name: $VERNAME"
Write-Output "New Location: $Env:USERPROFILE\.local\bin\$VERNAME"
Move-Item -Path "$VERNAME" -Destination "$Env:USERPROFILE\.local\bin"
Write-Output "New Location: $Env:USERPROFILE\.local\xbin\$VERNAME"
Move-Item -Path "$VERNAME" -Destination "$Env:USERPROFILE\.local\xbin"
# Exit tmp
Pop-Location
}
Write-Output "Copying into '$Env:USERPROFILE\.local\bin\$EXENAME' from '$Env:USERPROFILE\.local\bin\$VERNAME'"
Write-Output "Copying into '$Env:USERPROFILE\.local\bin\$EXENAME' from '$Env:USERPROFILE\.local\xbin\$VERNAME'"
Remove-Item -Path "$Env:USERPROFILE\.local\bin\$EXENAME" -Recurse -ErrorAction Ignore
Copy-Item -Path "$Env:USERPROFILE\.local\bin\$VERNAME" -Destination "$Env:USERPROFILE\.local\bin\$EXENAME" -Recurse
Copy-Item -Path "$Env:USERPROFILE\.local\xbin\$VERNAME" -Destination "$Env:USERPROFILE\.local\bin\$EXENAME" -Recurse

View File

@@ -6,17 +6,6 @@ var repo = 'fd';
module.exports = function (request) {
return github(request, 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;
});
};

View File

@@ -10,11 +10,6 @@ module.exports = function (request) {
return github(request, 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));
})

View File

@@ -4,19 +4,10 @@ var github = require('../_common/github.js');
var owner = 'fish-shell';
var repo = 'fish-shell';
var ODDITIES = ['bundledpcre'];
module.exports = function (request) {
return github(request, 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';

View File

@@ -1,99 +1,59 @@
'use strict';
var FLUTTER_OSES = ['macos', 'linux', 'windows'];
var map = {};
// stable, beta, dev
var 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"
// }
// ]
// }
module.exports = async function (request) {
let all = {
module.exports = function (request) {
var all = {
download: '',
releases: [],
channels: [],
};
for (let osname of FLUTTER_OSES) {
let resp = await request({
url: `https://storage.googleapis.com/flutter_infra_release/releases/releases_${osname}.json`,
json: true,
});
let osBaseUrl = resp.body.base_url;
let osReleases = resp.body.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.*/, ''),
//sha256: asset.sha256,
download: `${osBaseUrl}/${asset.archive}`,
_filename: asset.archive,
return Promise.all(
['macos', 'linux', 'windows'].map(function (osname) {
return request({
url:
'https://storage.googleapis.com/flutter_infra/releases/releases_' +
osname +
'.json',
json: true,
}).then(function (resp) {
var body = resp.body;
all.download = body.base_url + '/{{ download }}';
body.releases.forEach(function (asset) {
if (!map[asset.channel]) {
map[asset.channel] = true;
}
all.releases.push({
// nix leading 'v'
version: asset.version.replace(/v/, ''),
lts: false,
channel: asset.channel,
date: asset.release_date.replace(/T.*/, ''),
os: osname,
arch: 'amd64',
hash: '-', // not sure about including hash / sha256 yet
download: 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;
}),
).then(function () {
all.releases.sort(function (a, b) {
if ('stable' === a.channel && a.channel !== b.channel) {
return -1;
}
if ('stable' === b.channel && a.channel !== b.channel) {
return 1;
}
if ('beta' === a.channel && a.channel !== b.channel) {
return -1;
}
if ('beta' === b.channel && a.channel !== b.channel) {
return 1;
}
return new Date(b.date).valueOf() - new Date(a.date).valueOf();
});
return all;
});
};
if (module === require.main) {

View File

@@ -9,7 +9,7 @@ IF (!(Test-Path -Path "$Env:USERPROFILE\Downloads\webi\$Env:WEBI_PKG_FILE")) {
& Move-Item "$Env:USERPROFILE\Downloads\webi\$Env:WEBI_PKG_FILE.part" "$Env:USERPROFILE\Downloads\webi\$Env:WEBI_PKG_FILE"
}
IF (!(Test-Path -Path "$Env:USERPROFILE\.local\bin\$VERNAME")) {
IF (!(Test-Path -Path "$Env:USERPROFILE\.local\xbin\$VERNAME")) {
Write-Output "Installing $Env:PKG_NAME"
# TODO: temp directory
@@ -29,13 +29,13 @@ IF (!(Test-Path -Path "$Env:USERPROFILE\.local\bin\$VERNAME")) {
# Settle unpacked archive into place
Write-Output "New Name: $VERNAME"
Write-Output "New Location: $Env:USERPROFILE\.local\bin\$VERNAME"
Move-Item -Path "$VERNAME" -Destination "$Env:USERPROFILE\.local\bin"
Write-Output "New Location: $Env:USERPROFILE\.local\xbin\$VERNAME"
Move-Item -Path "$VERNAME" -Destination "$Env:USERPROFILE\.local\xbin"
# Exit tmp
Pop-Location
}
Write-Output "Copying into '$Env:USERPROFILE\.local\bin\$EXENAME' from '$Env:USERPROFILE\.local\bin\$VERNAME'"
Write-Output "Copying into '$Env:USERPROFILE\.local\bin\$EXENAME' from '$Env:USERPROFILE\.local\xbin\$VERNAME'"
Remove-Item -Path "$Env:USERPROFILE\.local\bin\$EXENAME" -Recurse -ErrorAction Ignore
Copy-Item -Path "$Env:USERPROFILE\.local\bin\$VERNAME" -Destination "$Env:USERPROFILE\.local\bin\$EXENAME" -Recurse
Copy-Item -Path "$Env:USERPROFILE\.local\xbin\$VERNAME" -Destination "$Env:USERPROFILE\.local\bin\$EXENAME" -Recurse

View File

@@ -7,17 +7,6 @@ tagline: |
To update or switch versions, run `webi gh@stable` (or `@v1`, `@beta`, etc).
### Files
These are the files / directories that are created and/or modified with this
install:
```text
~/.config/envman/PATH.env
~/.local/bin/gh
~/.config/gh/
```
## Cheat Sheet
> `gh` is cross-platform Github command-line. You can perform pull requests

View File

@@ -6,9 +6,7 @@ __install_git_gpg_init() {
MY_CMD="git-config-gpg"
rm -f "$HOME/.local/bin/$MY_CMD"
webi_download \
"$WEBI_HOST/packages/$MY_CMD/$MY_CMD.sh" \
"$HOME/.local/bin/$MY_CMD"
webi_download "$WEBI_HOST/packages/$MY_CMD/$MY_CMD.sh" "$HOME/.local/bin/$MY_CMD"
chmod a+x "$HOME/.local/bin/$MY_CMD"
}

View File

@@ -2,4 +2,4 @@
Write-Output "'git-gpg-init@$Env:WEBI_TAG' is an alias for 'git-config-gpg@$Env:WEBI_VERSION'"
IF ($null -eq $Env:WEBI_HOST -or $Env:WEBI_HOST -eq "") { $Env:WEBI_HOST = "https://webinstall.dev" }
curl.exe -A MS -fsSL "$Env:WEBI_HOST/git-config-gpg@$Env:WEBI_VERSION" | powershell
curl.exe -fsSL "$Env:WEBI_HOST/git-config-gpg@$Env:WEBI_VERSION" | powershell

View File

@@ -16,28 +16,6 @@ To update or switch versions, run `webi git@stable` (or `@v2.30`, `@beta`, etc).
Github's [Git 'Hello World'](https://guides.github.com/activities/hello-world/)
is a good place to get started if you're new to git.
## Table of Contents
- Files
- Commit Files
- Ignore Files
- Create Branch
- Rebase by Default
- Rebase
- Auth via Token
### Files
These are the files / directories that are created and/or modified with this
install:
```text
~/.config/envman/PATH.env
~/.gitconfig
~/.local/opt/git/ # Windows
/Library/Developer/CommandLineTools/ # macOS
```
### How to commit files
```sh
@@ -63,7 +41,7 @@ ignore
This will branch from the branch you're currently on.
```sh
git switch -c my-branch-name
git checkout -b my-branch-name
```
### How to rebase by default
@@ -88,11 +66,11 @@ Rebase a feature branch from master before a merge
```sh
# update master
git fetch
git switch master
git checkout master
git pull
# go back to your feature branch
git switch my-feature
git checkout my-feature
# start the rebase
git rebase master
@@ -119,14 +97,14 @@ git config --global url."https://git@github.com/".insteadOf "git@github.com:"
Next, create a `.git-askpass`:
```sh
echo 'echo "${MY_GIT_TOKEN}"' > ~/.git-askpass
chmod +x ~/.git-askpass
echo 'echo $MY_GIT_TOKEN' > $HOME/.git-askpass
chmod +x $HOME/.git-askpass
```
Finally, add the following ENVs to your deployment environment:
```sh
GIT_ASKPASS="${HOME}/.git-askpass"
GIT_ASKPASS=$HOME/.git-askpass
# Relpace xxxx... with your deploy token
MY_GIT_TOKEN=xxxxxxxxxxxxxxxx

View File

@@ -4,21 +4,20 @@ set -u
__init_git() {
if command -v git > /dev/null; then
echo "'git' already installed"
return 0
fi
if [ "Darwin" = "$(uname -s)" ]; then
echo >&2 "Error: 'git' not found. You may have to re-install 'git' on Mac after every major update."
echo >&2 " for example, try: xcode-select --install"
# sudo xcodebuild -license accept
if [ -z "$(command -v git)" ]; then
if [ "Darwin" = "$(uname -s)" ]; then
echo >&2 "Error: 'git' not found. You may have to re-install 'git' on Mac after every major update."
echo >&2 " for example, try: xcode-select --install"
# sudo xcodebuild -license accept
else
echo >&2 "Error: to install 'git' on Linux use the built-in package manager."
echo >&2 " for example, try: sudo apt install -y git"
fi
exit 1
else
echo >&2 "Error: to install 'git' on Linux use the built-in package manager."
echo >&2 " for example, try: sudo apt install -y git"
echo "'git' already installed"
fi
exit 1
}
__init_git

View File

@@ -12,15 +12,12 @@ module.exports = function (request) {
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;
});
};

View File

@@ -7,20 +7,6 @@ tagline: |
To update or switch versions, run `webi gitdeploy@stable`.
### Files
These are the files / directories that are created and/or modified with this
install:
```text
~/.config/envman/PATH.env
~/.local/bin/gitdeploy
~/.gitconfig
<PROJECT-DIR>/.env
<PROJECT-DIR>/.gitdeploy/
<PROJECT-DIR>/scripts/
```
## Cheat Sheet
> gitdeploy makes it easy to build and deploy static sites (or anything else)

View File

@@ -10,16 +10,6 @@ etc).
**Windows users**: You must [install git](/git) first.
### Files
These are the files / directories that are created and/or modified with this
install:
```text
~/.config/envman/PATH.env
~/.local/opt/gitea/
```
## Cheat Sheet
> Gitea is a clean, lightweight self-hosted Github clone. It only uses a few

View File

@@ -18,12 +18,6 @@ $pkg_src = "$pkg_src_cmd"
New-Item "$Env:USERPROFILE\Downloads\webi" -ItemType Directory -Force | Out-Null
$pkg_download = "$Env:USERPROFILE\Downloads\webi\$Env:WEBI_PKG_FILE"
Write-Output "Checking for Git..."
IF (-Not (Get-Command -Name "git" -ErrorAction Silent)) {
& "$HOME\.local\bin\webi-pwsh.ps1" git
$null = Sync-EnvPath
}
# Fetch archive
IF (!(Test-Path -Path "$Env:USERPROFILE\Downloads\webi\$Env:WEBI_PKG_FILE")) {
Write-Output "Downloading gitea from $Env:WEBI_PKG_URL to $pkg_download"

View File

@@ -4,24 +4,12 @@ var github = require('../_common/github.js');
var owner = 'go-gitea';
var repo = 'gitea';
var ODDITIES = ['-gogit-', '-docs-'];
module.exports = function (request) {
return github(request, 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;
return !/(\.txt)|(\.deb)|(\.asc)|(\.sha256)$/i.test(rel.name);
});
// "windows-4.0" as a nod to Windows NT ¯\_(ツ)_/¯
all._names = ['gitea', '-4.0-'];
return all;
});
};

View File

@@ -2,7 +2,8 @@
if (!(Get-Command "go.exe" -ErrorAction SilentlyContinue)) {
& "$Env:USERPROFILE\.local\bin\webi-pwsh.ps1" go
$null = Sync-EnvPath
# because we need git.exe to be available to golang immediately
$Env:PATH = "$Env:USERPROFILE\go\bin;$Env:USERPROFILE\.local\opt\go\bin;$Env:PATH"
}
# Special to go: re-run all go tooling builds

View File

@@ -3,7 +3,7 @@ set -e
set -u
__run_go_essentials() {
if ! command -v go > /dev/null; then
if ! command -v go 2> /dev/null; then
"$HOME/.local/bin/webi" "go@${WEBI_TAG}"
fi

View File

@@ -12,7 +12,8 @@ $pkg_dst_bin = "$pkg_dst\bin"
if (!(Get-Command "git.exe" -ErrorAction SilentlyContinue)) {
& "$Env:USERPROFILE\.local\bin\webi-pwsh.ps1" git
$null = Sync-EnvPath
# because we need git.exe to be available to golang immediately
$Env:PATH = "$Env:USERPROFILE\.local\opt\git\cmd;$Env:PATH"
}
# Fetch archive

View File

@@ -7,17 +7,6 @@ var archMap = {
386: 'x86',
};
let ODDITIES = ['bootstrap', '-arm6.'];
function isOdd(filename) {
for (let oddity of ODDITIES) {
let isOddity = filename.includes(oddity);
if (isOddity) {
return true;
}
}
}
function getAllReleases(request) {
/*
{
@@ -44,7 +33,7 @@ function getAllReleases(request) {
var goReleases = resp.body;
var all = {
releases: [],
download: '',
download: 'https://dl.google.com/go/{{ download }}',
};
goReleases.forEach((release) => {
@@ -54,21 +43,13 @@ function getAllReleases(request) {
parts.push('0');
}
var version = parts.join('.');
// nix 'go' prefix
var fileversion = release.version.slice(2);
release.files.forEach((asset) => {
let odd = isOdd(asset.filename);
if (odd) {
return;
}
var filename = asset.filename;
var os = osMap[asset.os] || asset.os || '-';
var arch = archMap[asset.arch] || asset.arch || '-';
all.releases.push({
version: version,
_version: fileversion,
// all go versions >= 1.0.0 are effectively LTS
lts: (parts[0] > 0 && release.stable) || false,
channel: (release.stable && 'stable') || 'beta',
@@ -77,7 +58,7 @@ function getAllReleases(request) {
arch: arch,
ext: '', // let normalize run the split/test/join
hash: '-', // not ready to standardize this yet
download: `https://dl.google.com/go/${filename}`,
download: filename,
});
});
});

View File

@@ -12,7 +12,8 @@ $pkg_dst_bin = "$pkg_dst\bin"
if (!(Get-Command "git.exe" -ErrorAction SilentlyContinue)) {
& "$Env:USERPROFILE\.local\bin\webi-pwsh.ps1" git
$null = Sync-EnvPath
# because we need git.exe to be available to golang immediately
$Env:PATH = "$Env:USERPROFILE\.local\opt\git\cmd;$Env:PATH"
}
# Fetch archive

View File

@@ -17,7 +17,6 @@ To update or switch versions, run `webi goreleaser@stable` (or `@v0.174`,
There's a lot that you can do with GoReleaser. These are the things that we've
found the most useful for the majority of projects:
- Files
- Basic Usage & Versioning
- Publishing Builds to GitHub
- Publishing to Gitea and Gitlab
@@ -26,32 +25,18 @@ found the most useful for the majority of projects:
- Cross-Compiling with cgo
- Full `.goreleaser.yml` example
### Files
These are the files / directories that are created and/or modified with this
install:
```text
~/.config/envman/PATH.env
~/.config/goreleaser/github_token
~/.local/bin/goreleaser
<PROJECT-DIR>/.goreleaser.yaml
```
## Basic Usage & Versioning
To create an example `.goreleaser.yaml` file, and test the configuration:
```sh
goreleaser init
# dry run
goreleaser --snapshot --skip=publish --clean
goreleaser --snapshot --skip-publish --rm-dist
```
- `--snapshot` allows "dirty" builds (when the repo has uncommitted changes)
- `--skip=publish` will NOT publish to GitHub, etc
- `--clean` will automatically remove the `./dist/` directory
- `--skip-publish` will NOT publish to GitHub, etc
- `--rm-dist` will automatically remove the `./dist/` directory
The default `.goreleaser.yml` works well for projects for which `package main`
is at the root.
@@ -64,17 +49,14 @@ package main
var (
// these will be replaced by goreleaser
version = "0.0.0"
version = "v0.0.0"
date = "0001-01-01T00:00:00Z"
commit = "0000000"
)
func main() {
if len(os.Args) >= 2 {
if os.Args[1] == "-V" || strings.TrimPrefix(os.Args[1]) == "version" {
fmt.Printf("CHANGE_ME v%s %s (%s)\n", version, commit[:7], date)
os.Exit(0)
}
if len(os.Args) >= 2 && "version" == strings.TrimPrefix(os.Args[1]) {
fmt.Printf("YOUR_CLI_NAME v%s %s (%s)\n", version, commit[:7], date)
}
// ...
@@ -114,10 +96,10 @@ git tag -a v1.0.0 -m "First release"
git push origin v1.0.0
```
Running GoReleaser without `--skip=publish` will publish the builds:
Running GoReleaser without `--skip-publish` will publish the builds:
```sh
goreleaser --clean
goreleaser --rm-dist
```
Check the console output to make sure that there are no messages about a failed
@@ -337,19 +319,21 @@ builds:
- linux
- windows
- darwin
- freebsd
- js
goarch:
- 386
- amd64
- arm
- arm64
- wasm
goarm:
- 6
- 7
archives:
- id: my-binary
format: tar.xz
- replacements:
darwin: Darwin
linux: Linux
windows: Windows
386: i386
amd64: x86_64
format_overrides:
- goos: windows
format: zip

View File

@@ -20,40 +20,20 @@ __init_goreleaser() {
# pkg_install must be defined by every package
pkg_install() {
# ~/.local/opt/goreleaser-v1.21.2/bin
# ~/.local/opt/goreleaser-v0.99.9/bin
mkdir -p "$(dirname "$pkg_src_cmd")"
# mv ./goreleaser-*/goreleaser ~/.local/opt/goreleaser-v1.21.2/bin/goreleaser
# mv ./goreleaser-*/goreleaser ~/.local/opt/goreleaser-v0.99.9/bin/goreleaser
mv ./goreleaser "$pkg_src_cmd"
}
# pkg_get_current_version is recommended, but (soon) not required
pkg_get_current_version() {
# 'goreleaser --version' has output in this format:
# ____ ____ _
# / ___| ___ | _ \ ___| | ___ __ _ ___ ___ _ __
# | | _ / _ \| |_) / _ \ |/ _ \/ _` / __|/ _ \ '__|
# | |_| | (_) | _ < __/ | __/ (_| \__ \ __/ |
# \____|\___/|_| \_\___|_|\___|\__,_|___/\___|_|
# goreleaser: Deliver Go Binaries as fast and easily as possible
# https://goreleaser.com
#
# GitVersion: 1.21.2
# GitCommit: 26fed97a0defe4e73e3094cb903225d5445e5f0d
# GitTreeState: false
# BuildDate: 2023-09-26T11:20:15Z
# BuiltBy: goreleaser
# GoVersion: go1.21.1
# Compiler: gc
# ModuleSum: h1:dgYtIS7aZlQuRMUMLCjDCOs4lWss85Oh60RDSO0rbWU=
# Platform: darwin/arm64
# goreleaser 0.99.9 (rev abcdef0123)
# This trims it down to just the version number:
# 1.21.2
# shellcheck disable=SC2046,SC2005 # unquoted echo trims whitespace
goreleaser --version 2> /dev/null |
grep 'GitVersion:' |
cut -d':' -f2 |
tr -d ' '
# 0.99.9
goreleaser --version 2> /dev/null | head -n 1 | cut -d ' ' -f 2
}
}

View File

@@ -6,7 +6,6 @@ var repo = 'goreleaser';
module.exports = function (request) {
return github(request, owner, repo).then(function (all) {
all._names = ['goreleaser', '1'];
return all;
});
};

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