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
181 changed files with 1483 additions and 7795 deletions

View File

@@ -10,7 +10,7 @@ jobs:
strategy: strategy:
matrix: matrix:
node-version: node-version:
#- 20.x - 20.x
- latest - latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3

View File

@@ -1,196 +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 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`;
await exec(`git clone --bare --filter=tree:0 ${gitUrl} ${tmpPath}`);
await Fs.rename(tmpPath, repoPath);
};
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

@@ -3,7 +3,8 @@
require('dotenv').config(); 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 request * @param request
* @param {string} owner * @param {string} owner
@@ -60,32 +61,20 @@ async function getAllReleases(
} }
function transformReleases(release) { function transformReleases(release) {
for (let asset of release['assets']) { release['assets'].forEach(function (asset) {
let name = asset['name']; 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({ all.releases.push({
name: name, name: name,
version: version, version: release['tag_name'], // TODO tags aren't always semver / sensical
lts: lts, lts: /(\b|_)(lts)(\b|_)/.test(release['tag_name']),
channel: channel, channel: !release['prerelease'] ? 'stable' : 'beta',
date: date, date: (release['published_at'] || '').replace(/T.*/, ''),
os: '', // will be guessed by download filename os: '', // will be guessed by download filename
arch: '', // will be guessed by download filename arch: '', // will be guessed by download filename
ext: '', // will be normalized ext: '', // will be normalized
download: download, download: asset['browser_download_url'],
}); });
} });
} }
return all; return all;

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\Downloads\webi" -ItemType Directory -Force | Out-Null
New-Item -Path "$Env:USERPROFILE\.local\bin" -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" } 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 Set-ExecutionPolicy -Scope Process Bypass
& "$Env:USERPROFILE\.local\bin\webi-pwsh.ps1" "{{ exename }}" & "$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,243 +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 #
# #
#########################################
# shellcheck disable=SC2005
fn_show_welcome() { (
echo ""
printf "%s %s\n" \
"$(t_strong 'Welcome to') $(t_stronger 'Webi')$(t_strong '!')" \
"$(t_dim "- Modern tools, instant installs.")"
echo "We expect your experience to be $(t_em 'absolutely perfect')!"
echo ""
echo " $(t_yellow 'Have a problem?') Please $(t_em 'let us know'):"
echo " $(t_url 'https://github.com/webinstall/webi-installers/issues')"
echo " $(t_dim "(your system is $(t_yellow "$(uname -s)")/$(t_yellow "$(uname -m)") with $(t_yellow "$(fn_get_libc)") & $(t_yellow "$(fn_get_http)"))")"
echo ""
echo " $(t_yellow 'Love it?') Star it!"
echo " $(t_url 'https://github.com/webinstall/webi-installers')"
sleep 0.2
); }
t_strong() { (printf '\e[36m%s\e[39m' "${1}"); }
t_stronger() { (printf '\e[1m\e[32m%s\e[39m\e[22m' "${1}"); }
t_url() { (printf '\e[2m%s\e[22m' "${1}"); }
t_path() { (printf '\e[2m\e[32m%s\e[39m\e[22m' "${1}"); }
t_cmd() { (printf '\e[2m\e[35m%s\e[39m\e[22m' "${1}"); }
t_err() { (printf '\e[31m%s\e[39m' "${1}"); }
t_bold() { (printf '\e[1m%s\e[22m' "${1}"); }
t_dim() { (printf '\e[2m%s\e[22m' "${1}"); }
t_em() { (printf '\e[3m%s\e[23m' "${1}"); }
t_under() { (printf '\e[4m%s\e[24m' "${1}"); }
t_green() { (printf '\e[32m%s\e[39m' "${1}"); }
t_yellow() { (printf '\e[33m%s\e[39m' "${1}"); }
t_magenta() { (printf '\e[35m%s\e[39m' "${1}"); }
t_cyan() { (printf '\e[36m%s\e[39m' "${1}"); }
fn_get_libc() { (
# Ex:
# musl
# libc
if ldd /bin/ls 2> /dev/null | grep -q 'musl' 2> /dev/null; then
echo 'musl'
elif uname -o | grep -q 'GNU' || uname -s | grep -q 'Linux'; then
echo 'gnu'
else
echo 'libc'
fi
); }
fn_get_http() { (
# 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}"
); }
###################################
# #
# 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 -q --user-agent"
if fn_is_interactive; then
cmd_wget="wget -q --show-progress --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}" -c "${a_url}" -O "${a_path}"; then
echo >&2 " $(t_err "failed to download (wget)") '$(t_url "${a_url}")'"
echo >&2 " $cmd_wget '${b_agent}' -c '${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 --fail-with-body -sSL -#"
if fn_is_interactive; then
cmd_curl="curl --fail-with-body 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_is_interactive() {
# Ex:
# himBH
# hBc
case $- in
*i*) return 0 ;;
*) return 1 ;;
esac
}
fn_get_target_triple_user_agent() { (
# Ex:
# x86_64/unknown Linux/5.15.107-2-pve gnu
# arm64/unknown Darwin/22.6.0 libc
echo "$(uname -m)/unknown $(uname -s)/$(uname -r) $(fn_get_libc)"
); }
fn_download_to_path() { (
a_url="${1}"
a_path="${2}"
mkdir -p "$(dirname "${a_path}")"
if command -v wget > /dev/null; then
fn_wget "${a_url}" "${a_path}.part"
elif command -v curl > /dev/null; then
fn_curl "${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_upgrade() { (
a_path="${1}"
echo ""
echo "$(t_strong 'Bootstrapping') $(t_stronger 'Webi')"
b_path_rel="$(fn_sub_home "${a_path}")"
b_checksum=""
if test -r "${a_path}"; then
echo " $(t_dim 'Found') $(t_path "${b_path_rel}")"
b_checksum="$(fn_checksum "${a_path}")"
fi
if test "$b_checksum" = "${WEBI_CHECKSUM}"; then
sleep 0.1
return 0
fi
b_webi_file_url="${WEBI_HOST}/packages/webi/webi.sh"
if test -r "${a_path}"; then
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}"
); }
fn_checksum() {
a_filepath="${1}"
cmd_shasum='sha1sum'
if command -v shasum > /dev/null; then
cmd_shasum='shasum'
fi
$cmd_shasum "${a_filepath}" | cut -d' ' -f1 | cut -c 1-8
}
fn_sub_home() { (
my_rel=${HOME}
my_abs=${1}
echo "${my_abs}" | sed "s:^${my_rel}:~:"
); }
main() { (
fn_show_welcome
export WEBI_WELCOME=true
# 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_upgrade "${b_webi_path}"
echo ""
echo "$(t_strong 'Installing') $(t_stronger "${WEBI_PKG}") $(t_strong '...')"
echo " Running $(t_cmd "${b_webi_path_rel} ${WEBI_PKG}")"
"${b_webi_path}" "${WEBI_PKG}"
); }
set -e
set -u
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,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 '-#' --fail-with-body -sS -A $Env:WEBI_UA $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

View File

@@ -13,7 +13,6 @@ var osMap = {
var maps = { var maps = {
oses: {}, oses: {},
arches: {}, arches: {},
libcs: {},
formats: {}, formats: {},
}; };
@@ -21,7 +20,7 @@ Object.keys(osMap).forEach(function (name) {
maps.oses[name] = true; 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) { formats.forEach(function (name) {
maps.formats[name] = true; maps.formats[name] = true;
}); });
@@ -50,7 +49,7 @@ var arches = [
// https://git.com/org/foo/releases/v0.7.9/foo-x86_64-linux-musl.tar.gz // https://git.com/org/foo/releases/v0.7.9/foo-x86_64-linux-musl.tar.gz
// //
var archMap = { var archMap = {
armv7l: /(\b|_)(arm32|armv?7l?)/i, armv7l: /(\b|_)(armv?7l?)/i,
//amd64: /(amd.?64|x64|[_\-]64)/i, //amd64: /(amd.?64|x64|[_\-]64)/i,
amd64: amd64:
/(\b|_|amd|(dar)?win(dows)?|mac(os)?|linux|osx|x)64([_\-]?bit)?(\b|_)/i, /(\b|_|amd|(dar)?win(dows)?|mac(os)?|linux|osx|x)64([_\-]?bit)?(\b|_)/i,
@@ -66,39 +65,31 @@ arches.forEach(function (name) {
maps.arches[name] = true; maps.arches[name] = true;
}); });
var libcs = ['none', 'musl', 'gnu', 'msvc', 'libc'];
libcs.forEach(function (name) {
maps.libcs[name] = true;
});
function normalize(all) { function normalize(all) {
/* jshint maxcomplexity:50 */
/* jshint maxdepth:10 */
var supported = { var supported = {
oses: {}, oses: {},
arches: {}, arches: {},
libcs: {},
formats: {}, formats: {},
}; };
for (let rel of all.releases) { all.releases.forEach(function (rel) {
/* jshint maxcomplexity:25 */
rel.version = rel.version.replace(/^v/i, ''); rel.version = rel.version.replace(/^v/i, '');
if (!rel.name) { if (!rel.name) {
rel.name = rel.download.replace(/.*\//, ''); rel.name = rel.download.replace(/.*\//, '');
} }
if (!rel.os) { if (!rel.os) {
rel.os = 'unknown'; rel.os =
Object.keys(osMap).find(function (regKey) {
let osNames = Object.keys(osMap); return osMap[regKey].test(rel.name || rel.download);
for (let osName of osNames) { }) || 'unknown';
let relName = rel.name || rel.download; }
let osRegExp = osMap[osName]; // Hacky-doo for musl
let matches = osRegExp.test(relName); // TODO some sort of glibc vs musl tag?
if (matches) { if (!rel._musl_native) {
rel.os = osName; if (!rel._musl) {
break; if (/(\b|\.|_|-)(musl)(\b|\.|_|-)/.test(rel.download)) {
rel._musl = true;
} }
} }
} }
@@ -121,60 +112,6 @@ function normalize(all) {
} }
supported.arches[rel.arch] = true; 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; var tarExt;
if (!rel.ext) { if (!rel.ext) {
// pkg-v1.0.tar.gz => ['gz', 'tar', '0', 'pkg-v1'] // pkg-v1.0.tar.gz => ['gz', 'tar', '0', 'pkg-v1']
@@ -202,28 +139,10 @@ function normalize(all) {
} }
supported.formats[tarExt || rel.ext] = true; 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) { if (all.download) {
rel.download = all.download.replace(/{{ download }}/, rel.download); rel.download = all.download.replace(/{{ download }}/, rel.download);
} }
} });
all.oses = Object.keys(supported.oses).filter(function (name) { all.oses = Object.keys(supported.oses).filter(function (name) {
return maps.oses[name]; return maps.oses[name];
@@ -231,9 +150,6 @@ function normalize(all) {
all.arches = Object.keys(supported.arches).filter(function (name) { all.arches = Object.keys(supported.arches).filter(function (name) {
return maps.arches[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) { all.formats = Object.keys(supported.formats).filter(function (name) {
return maps.formats[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, ...) // NOT in order of priority (which would be tar, xz, zip, ...)
module.exports.formats = formats; module.exports.formats = formats;
module.exports.arches = arches; module.exports.arches = arches;
module.exports.libcs = libcs;
module.exports.formatsMap = maps.formats; module.exports.formatsMap = maps.formats;

View File

@@ -5,6 +5,8 @@ var path = require('node:path');
var request = require('@root/request'); var request = require('@root/request');
var _normalize = require('../_webi/normalize.js'); var _normalize = require('../_webi/normalize.js');
// PowerShell: { { installer } }
// POSIX: {{ installer }}
var reInstallTpl = /\s*#?\s*{{ installer }}/; var reInstallTpl = /\s*#?\s*{{ installer }}/;
var Releases = module.exports; var Releases = module.exports;
@@ -27,11 +29,10 @@ function padScript(txt) {
return txt.replace(/^/g, ' '); return txt.replace(/^/g, ' ');
} }
var BAD_SH_RE = /[<>'"`$\\]/;
Releases.renderBash = async function ( Releases.renderBash = async function (
pkgdir, pkgdir,
rel, rel,
{ baseurl, pkg, tag, ver, os = '', arch = '', libc = '', formats }, { baseurl, pkg, tag, ver, os = '', arch = '', formats },
) { ) {
if (!Array.isArray(formats)) { if (!Array.isArray(formats)) {
formats = []; formats = [];
@@ -39,116 +40,168 @@ Releases.renderBash = async function (
if (!tag) { if (!tag) {
tag = ''; tag = '';
} }
let installTxt = await fs.promises.readFile( return fs.promises
path.join(pkgdir, 'install.sh'), .readFile(path.join(pkgdir, 'install.sh'), 'utf8')
'utf8', .then(function (installTxt) {
); installTxt = padScript(installTxt);
installTxt = padScript(installTxt); var vers = rel.version.split('.');
var vers = rel.version.split('.'); var v = {
var v = { major: vers.shift() || '',
major: vers.shift() || '', minor: vers.shift() || '',
minor: vers.shift() || '', patch: vers.join('.').replace(/[+\-].*/, ''),
patch: vers.join('.').replace(/[+\-].*/, ''), build: vers
build: vers .join('.')
.join('.') .replace(/[^+\-]*/, '')
.replace(/[^+\-]*/, '') .replace(/^-/, ''),
.replace(/^-/, ''), };
}; var pkgFile = rel.filename || rel.name;
var pkgFile = rel.filename || rel.name; return fs.promises
let tplTxt = await fs.promises.readFile( .readFile(path.join(__dirname, 'template.sh'), 'utf8')
path.join(__dirname, 'install-package.tpl.sh'), .then(function (tplTxt) {
'utf8', // ex: 'node@lts' or 'node'
); var webiPkg = pkg;
// ex: 'node@lts' or 'node' if (ver) {
var webiPkg = pkg; webiPkg += `@${ver}`;
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) {
let releaseParams = new URLSearchParams({ tag = '';
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 envReplacements = [
['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.replace(/tar.*/, 'tar')],
['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],
['PKG_ARCHES', rel.arches],
['PKG_LIBCS', rel.libcs],
['PKG_FORMATS', (rel.formats || []).join(',')],
];
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\\s)?[ \\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}'`);
} }
return fs.promises
tplTxt = tplTxt .readFile(path.join(pkgdir, 'install.bat'), 'utf8')
.replace(/CHEATSHEET_URL/g, `${baseurl}/${pkg}`) .then(function (installTxt) {
// $', $0, ... $9, $`, $&, and $_ all have special meaning installTxt = padScript(installTxt);
// (see https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/RegExp) /*
// However, it can be escaped with $$ (which must be escaped with $$) var vers = rel.version.split('.');
.replace(reInstallTpl, '\n' + installTxt.replace(/\$/g, '$$$$')); var v = {
return tplTxt; 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 ( Releases.renderPowerShell = async function (
pkgdir, pkgdir,
rel, rel,
{ baseurl, pkg, tag, ver, os, arch, libc = '', formats }, { baseurl, pkg, tag, ver, os, arch, formats },
) { ) {
if (!Array.isArray(formats)) { if (!Array.isArray(formats)) {
formats = []; formats = [];
@@ -156,65 +209,52 @@ Releases.renderPowerShell = async function (
if (!tag) { if (!tag) {
tag = ''; tag = '';
} }
let installTxt = await fs.promises.readFile( return fs.promises
path.join(pkgdir, 'install.ps1'), .readFile(path.join(pkgdir, 'install.ps1'), 'utf8')
'utf8', .then(function (installTxt) {
); installTxt = padScript(installTxt);
installTxt = padScript(installTxt); /*
/* var vers = rel.version.split('.');
var vers = rel.version.split('.'); var v = {
var v = { major: vers.shift() || '',
major: vers.shift() || '', minor: vers.shift() || '',
minor: vers.shift() || '', patch: vers.join('.').replace(/[+\-].*$/, ''),
patch: vers.join('.').replace(/[+\-].*$/, ''), build: vers
build: vers .join('.')
.join('.') .replace(/[^+\-]*()/, '')
.replace(/[^+\-]*()/, '') .replace(/^-/, '')
.replace(/^-/, '') };
}; */
*/ return fs.promises
let tplTxt = await fs.promises.readFile( .readFile(path.join(__dirname, 'template.ps1'), 'utf8')
path.join(__dirname, 'install-package.tpl.ps1'), .then(function (tplTxt) {
'utf8', var pkgver = pkg + '@' + ver;
); return tplTxt
var pkgver = pkg + '@' + ver; .replace(
return ( /^(#)?\$Env:WEBI_HOST\s*=.*/im,
tplTxt "$Env:WEBI_HOST = '" + baseurl + "'",
.replace( )
/^(#)?\$Env:WEBI_LIBC\s*=.*/im, .replace(
"$Env:WEBI_LIBC = '" + libc + "'", /^(#)?\$Env:WEBI_PKG\s*=.*/im,
) "$Env:WEBI_PKG = '" + pkgver + "'",
.replace( )
/^(#)?\$Env:WEBI_HOST\s*=.*/im, .replace(
"$Env:WEBI_HOST = '" + baseurl + "'", /^(#)?\$Env:PKG_NAME\s*=.*/im,
) "$Env:PKG_NAME = '" + pkg + "'",
.replace( )
/^(#)?\$Env:WEBI_PKG\s*=.*/im, .replace(
"$Env:WEBI_PKG = '" + pkgver + "'", /^(#)?\$Env:WEBI_VERSION\s*=.*/im,
) "$Env:WEBI_VERSION = '" + rel.version + "'",
.replace(/^(#)?\$Env:PKG_NAME\s*=.*/im, "$Env:PKG_NAME = '" + pkg + "'") )
.replace( .replace(
/^(#)?\$Env:WEBI_VERSION\s*=.*/im, /^(#)?\$Env:WEBI_PKG_URL\s*=.*/im,
"$Env:WEBI_VERSION = '" + rel.version + "'", "$Env:WEBI_PKG_URL = '" + rel.download + "'",
) )
.replace( .replace(
/^(#)?\$Env:WEBI_GIT_TAG\s*=.*/im, /^(#)?\$Env:WEBI_PKG_FILE\s*=.*/im,
"$Env:WEBI_GIT_TAG = '" + rel.git_tag + "'", "$Env:WEBI_PKG_FILE = '" + rel.name + "'",
) )
.replace( .replace(reInstallTpl, '\n' + installTxt);
/^(#)?\$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)
);
}; };

View File

@@ -1,9 +1,5 @@
'use strict'; 'use strict';
var Installers = module.exports;
var Crypto = require('crypto');
var Fs = require('fs/promises');
var path = require('path'); var path = require('path');
var uaDetect = require('./ua-detect.js'); var uaDetect = require('./ua-detect.js');
@@ -13,37 +9,13 @@ var Releases = require('./releases.js');
// handlers caching and transformation, probably should be broken down // handlers caching and transformation, probably should be broken down
var getReleases = require('./transform-releases.js'); var getReleases = require('./transform-releases.js');
Installers.INSTALLERS_DIR = path.join(__dirname, '..'); var installersDir = path.join(__dirname, '..');
Installers.serveInstaller = async function (
baseurl,
ua,
pkg,
tag,
ext,
formats,
libc,
) {
let [rel, opts] = await Installers.helper({
ua,
pkg,
tag,
formats,
libc,
});
Object.assign(opts, {
baseurl,
});
var pkgdir = path.join(Installers.INSTALLERS_DIR, pkg); serveInstaller.serveInstaller = serveInstaller;
if ('ps1' === ext) { module.exports = serveInstaller;
return Releases.renderPowerShell(pkgdir, rel, opts); async function serveInstaller(baseurl, ua, pkg, tag, ext, formats) {
}
return Releases.renderBash(pkgdir, rel, opts);
};
Installers.helper = async function ({ ua, pkg, tag, formats, libc }) {
// TODO put some of this in a middleware? or common function? // TODO put some of this in a middleware? or common function?
// TODO maybe move package/version/lts/channel detection into getReleases
var ver = tag.replace(/^v/, ''); var ver = tag.replace(/^v/, '');
var lts; var lts;
var channel; var channel;
@@ -72,21 +44,12 @@ Installers.helper = async function ({ ua, pkg, tag, formats, libc }) {
break; break;
} }
// TODO maybe move package/version/lts/channel detection into getReleases
var myOs = uaDetect.os(ua); var myOs = uaDetect.os(ua);
var myArch = uaDetect.arch(ua); var myArch = uaDetect.arch(ua);
var myLibc; var myLibc = uaDetect.libc(ua);
if (libc) {
myLibc = uaDetect.libc(libc);
}
if (!myLibc) {
myLibc = uaDetect.libc(ua);
}
if (!myLibc) {
myLibc = 'libc';
}
let cfg = await packages.get(pkg); let cfg = await packages.get(pkg);
let releaseQuery = { let rels = await getReleases({
pkg: cfg.alias || pkg, pkg: cfg.alias || pkg,
ver, ver,
os: myOs, os: myOs,
@@ -94,147 +57,33 @@ Installers.helper = async function ({ ua, pkg, tag, formats, libc }) {
libc: myLibc, libc: myLibc,
lts, lts,
channel, channel,
// TODO use formats for sorting, not exclusion
// (it's better to install xz or report an error to install zip)
formats, formats,
limit: 1, limit: 1,
}; });
let rels = await getReleases(releaseQuery);
var rel = rels.releases[0]; var rel = rels.releases[0];
var pkgdir = path.join(installersDir, pkg);
var opts = { var opts = {
baseurl,
pkg: cfg.alias || pkg, pkg: cfg.alias || pkg,
ver, ver,
tag, tag,
os: myOs, os: myOs,
arch: myArch, arch: myArch,
libc: myLibc,
lts, lts,
channel, channel,
formats, formats,
limit: 1, limit: 1,
}; };
rel = Object.assign( rel.oses = rels.oses;
{ rel.arches = rels.arches;
oses: rels.oses, rel.formats = rels.formats;
arches: rels.arches,
libcs: rels.libcs,
formats: rels.formats,
},
rel,
);
return [rel, opts]; if ('bat' === ext) {
}; return Releases.renderBatch(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 = /[<>'"`$\\]/;
Installers.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];
// TODO create REs once, in higher scope
let envRe = new RegExp(
`^[ \\t]*#?[ \\t]*(export\\s)?[ \\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 if ('ps1' === ext) {
//bootTxt.replace(/CHEATSHEET_URL/g, `${Config.cheatUrl}/${pkg}`); return Releases.renderPowerShell(pkgdir, rel, opts);
return bootTxt;
};
Installers.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 Installers.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 envRe = new RegExp(`^[ \\t]*#?[ \\t]*($$${name})[ \\t]*=.*`, 'im');
// bootTxt = bootTxt.replace(envRe, `$$${name} = '${value}'`);
let setRe = new RegExp(
`(#[ \\t]*)?(\\$${name})[ \\t]*=[ \\t]['"].*['"][ \\t]`,
'im',
);
bootTxt = bootTxt.replace(setRe, `$$${name} = '${value}'`);
} }
// TODO init config here return Releases.renderBash(pkgdir, rel, opts);
//bootTxt.replace(/CHEATSHEET_URL/g, `${Config.cheatUrl}/${pkg}`); }
return bootTxt;
};
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;
};

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

View File

@@ -8,19 +8,14 @@ __bootstrap_webi() {
my_libc='' my_libc=''
if ldd /bin/ls 2> /dev/null | grep -q 'musl' 2> /dev/null; then if ldd /bin/ls 2> /dev/null | grep -q 'musl' 2> /dev/null; then
my_libc='musl' my_libc=' musl-native'
elif uname -o | grep -q 'GNU' || uname -s | grep -q 'Linux'; then
my_libc='gnu'
else
my_libc='libc'
fi fi
WEBI_UA="$(uname -s)/$(uname -r) $(uname -m)/unknown ${my_libc}"
#WEBI_PKG= #WEBI_PKG=
#PKG_NAME= #PKG_NAME=
# TODO should this be BASEURL instead?
#WEBI_OS= #WEBI_OS=
#WEBI_ARCH= #WEBI_ARCH=
#WEBI_LIBC=
#WEBI_HOST= #WEBI_HOST=
#WEBI_RELEASES= #WEBI_RELEASES=
#WEBI_CSV= #WEBI_CSV=
@@ -31,19 +26,16 @@ __bootstrap_webi() {
#WEBI_PATCH= #WEBI_PATCH=
# TODO not sure if BUILD is the best name for this # TODO not sure if BUILD is the best name for this
#WEBI_BUILD= #WEBI_BUILD=
#WEBI_GIT_TAG=
#WEBI_LTS= #WEBI_LTS=
#WEBI_CHANNEL= #WEBI_CHANNEL=
#WEBI_EXT= #WEBI_EXT=
#WEBI_FORMATS= #WEBI_FORMATS=
#WEBI_PKG_URL= #WEBI_PKG_URL=
#WEBI_PKG_FILE= #WEBI_PKG_FILE=
#WEBI_PKG_PATHNAME=
#PKG_OSES= #PKG_OSES=
#PKG_ARCHES= #PKG_ARCHES=
#PKG_LIBCS=
#PKG_FORMATS= #PKG_FORMATS=
WEBI_UA="$(uname -s)/$(uname -r) $(uname -m)/unknown ${my_libc}" WEBI_UA="$(uname -s)/$(uname -r) $(uname -m)/unknown${my_libc}"
WEBI_PKG_DOWNLOAD="" WEBI_PKG_DOWNLOAD=""
WEBI_DOWNLOAD_DIR="${HOME}/Downloads" WEBI_DOWNLOAD_DIR="${HOME}/Downloads"
if command -v xdg-user-dir > /dev/null; then if command -v xdg-user-dir > /dev/null; then
@@ -68,20 +60,6 @@ __bootstrap_webi() {
mkdir -p "$HOME/.local/bin" mkdir -p "$HOME/.local/bin"
mkdir -p "$HOME/.local/opt" mkdir -p "$HOME/.local/opt"
if test -e ~/.local/bin; then
echo "Found ~/.local/bin"
else
echo "Creating ~/.local/bin"
mkdir -p "$HOME/.local/bin"
fi
if test -e ~/.local/bin/webi; then
echo "Found ~/.local/bin/webi"
else
echo "Creating ~/.local/bin"
mkdir -p "$HOME/.local/bin"
fi
## ##
## Detect http client ## Detect http client
## ##
@@ -92,8 +70,7 @@ __bootstrap_webi() {
export WEBI_WGET export WEBI_WGET
set -e set -e
# get the special formatted version # get the special formatted version (i.e. "go is go1.14" while node is "node v12.10.8")
# (i.e. "go is go1.14" while node is "node v12.10.8")
my_versioned_name="" my_versioned_name=""
_webi_canonical_name() { _webi_canonical_name() {
if [ -n "$my_versioned_name" ]; then if [ -n "$my_versioned_name" ]; then
@@ -101,7 +78,7 @@ __bootstrap_webi() {
return 0 return 0
fi fi
if command -v pkg_format_cmd_version > /dev/null; then if [ -n "$(command -v pkg_format_cmd_version)" ]; then
my_versioned_name="'$(pkg_format_cmd_version "$WEBI_VERSION")'" my_versioned_name="'$(pkg_format_cmd_version "$WEBI_VERSION")'"
else else
my_versioned_name="'$pkg_cmd_name v$WEBI_VERSION'" my_versioned_name="'$pkg_cmd_name v$WEBI_VERSION'"
@@ -110,28 +87,28 @@ __bootstrap_webi() {
echo "$my_versioned_name" echo "$my_versioned_name"
} }
# Update symlinks as per $HOME/.local/opt and $HOME/.local/bin install paths. # 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() { webi_link() {
if command -v pkg_link > /dev/null; then if [ -n "$(command -v pkg_link)" ]; then
pkg_link pkg_link
return 0 return 0
fi fi
if test -n "${WEBI_SINGLE}"; then if [ -n "$WEBI_SINGLE" ] || [ "single" = "${1-}" ]; then
rm -rf "$pkg_dst_cmd" rm -rf "$pkg_dst_cmd"
ln -s "$pkg_src_cmd" "$pkg_dst_cmd" ln -s "$pkg_src_cmd" "$pkg_dst_cmd"
else else
# 'pkg_dst' will default to $HOME/.local/opt/<pkg> # 'pkg_dst' will default to $HOME/.local/opt/<pkg>
# 'pkg_src' will be the installed version, # 'pkg_src' will be the installed version, such as to $HOME/.local/opt/<pkg>-<version>
# such as to $HOME/.local/opt/<pkg>-<version>
rm -rf "$pkg_dst" rm -rf "$pkg_dst"
ln -s "$pkg_src" "$pkg_dst" ln -s "$pkg_src" "$pkg_dst"
fi fi
} }
# detect if this program is already installed # detect if this program is already installed or if an installed version may cause conflict
# or if an installed version may cause conflict webi_check() {
webi_check_installed() {
# Test for existing version # Test for existing version
set +e set +e
my_path="$PATH" my_path="$PATH"
@@ -151,88 +128,80 @@ __bootstrap_webi() {
# but that's okay, 'cmp -s' is good enough for us # but that's okay, 'cmp -s' is good enough for us
if cmp -s "${pkg_src_cmd}" "${my_current_cmd}"; then if cmp -s "${pkg_src_cmd}" "${my_current_cmd}"; then
echo "${my_canonical_name} already installed:" echo "${my_canonical_name} already installed:"
my_dst_rel="$( printf " %s" "${pkg_dst}"
webi_sub_home "${pkg_dst}"
)"
printf " %s" "${my_dst_rel}"
if [ "${pkg_src_cmd}" != "${my_current_cmd}" ]; then if [ "${pkg_src_cmd}" != "${my_current_cmd}" ]; then
my_src_rel="$( printf " => %s" "${pkg_src}"
webi_sub_home "${pkg_src}"
)"
printf " => %s" "${my_src_rel}"
fi fi
echo "" echo ""
exit 0 exit 0
fi fi
if [ -x "$pkg_src_cmd" ]; then if [ -x "$pkg_src_cmd" ]; then
# shellcheck disable=2119
# this function takes no args
webi_link webi_link
echo "switched to $my_canonical_name:" echo "switched to $my_canonical_name:"
my_src_rel="$( echo " ${pkg_dst} => ${pkg_src}"
webi_sub_home "${pkg_src}"
)"
my_dst_rel="$(
webi_sub_home "${pkg_dst}"
)"
echo " ${my_dst_rel} => ${my_src_rel}"
exit 0 exit 0
fi fi
fi fi
export PATH="$my_path" export PATH="$my_path"
} }
webi_check_available() {
if test "$WEBI_CHANNEL" != "error"; then
return 0
fi
echo >&2 "Error: no '${PKG_NAME:-"Unknown Package"}@${WEBI_TAG:-"Unknown Tag"}' release for '${WEBI_OS:-"Unknown OS"}' (${WEBI_LIBC:-"Unknown Libc"}) on '${WEBI_ARCH:-"Unknown CPU"}' as one of '${WEBI_FORMATS:-"Unknown File Type"}'"
echo >&2 " '$PKG_NAME' is available for '$PKG_OSES' ($PKG_LIBCS) 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:?.*::'
)"
my_release_params="$(
echo "$WEBI_RELEASES" |
sed 's:.*?:?:'
)"
echo >&2 " Double check at ${my_release_url}"
echo >&2 " ${my_release_params}"
echo >&2 ""
exit 1
}
is_interactive_shell() { is_interactive_shell() {
# $- shows shell flags (error,unset,interactive,etc) # $- shows shell flags (error,unset,interactive,etc)
case $- in case $- in
*i*) return 0 ;; *i*)
*) return 1 ;; # true
return 0
;;
*)
# false
return 1
;;
esac esac
} }
webi_sub_home() { (
my_rel=${HOME}
my_abs=${1}
echo "${my_abs}" | sed "s:^${my_rel}:~:"
); }
# detect if file is downloaded, and how to download it # detect if file is downloaded, and how to download it
webi_download() { webi_download() {
my_url="${1}" # determine the url to download
my_dl="${2}" if [ -n "${1-}" ]; then
my_dl_name="${3:-${PKG_NAME}}" 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
my_dl_rel="$( # determine the location to download to
webi_sub_home "${my_dl}" 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}" WEBI_PKG_DOWNLOAD="${my_dl}"
export WEBI_PKG_DOWNLOAD export WEBI_PKG_DOWNLOAD
if [ -e "${my_dl}" ]; then if [ -e "$my_dl" ]; then
echo "Found ${my_dl_rel}" echo "Found $my_dl"
return 0 return 0
fi fi
@@ -256,6 +225,7 @@ __bootstrap_webi() {
set -e set -e
else else
# Neither GNU nor BSD curl have sane resume download options, hence we don't bother # 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="-#" my_show_progress="-#"
if is_interactive_shell; then if is_interactive_shell; then
my_show_progress="" my_show_progress=""
@@ -267,59 +237,27 @@ __bootstrap_webi() {
mv "$my_dl.part" "$my_dl" mv "$my_dl.part" "$my_dl"
echo "" echo ""
echo "Saved as ${my_dl_rel}" echo "Saved as $my_dl"
} }
webi_git_clone() { (
my_url="${1}"
my_dl="${2}"
my_dl_rel="$(
webi_sub_home "${my_dl}"
)"
if [ -e "${my_dl}" ]; then
echo "Found ${my_dl_rel}"
cp -RPp "${my_dl}" "${WEBI_TMP}/${WEBI_PKG_FILE}/"
return 0
fi
echo "Cloning ${my_url}"
cmd_git="git clone --config advice.detachedHead=false --quiet --depth=1 --single-branch"
rm -rf "${my_dl}.part"
if ! $cmd_git "${my_url}" --branch "${WEBI_GIT_TAG}" "${my_dl}.part"; then
echo >&2 "failed to git clone ${WEBI_PKG_URL}"
exit 1
fi
mv "${my_dl}.part" "${my_dl}"
cp -RPp "${my_dl}" "${WEBI_TMP}/${WEBI_PKG_FILE}/"
); }
# detect which archives can be used # detect which archives can be used
webi_extract() { webi_extract() {
( (
cd "$WEBI_TMP" cd "$WEBI_TMP"
my_dl_rel="$(
webi_sub_home "${WEBI_PKG_PATH}/${WEBI_PKG_FILE}"
)"
if [ "tar" = "$WEBI_EXT" ]; then if [ "tar" = "$WEBI_EXT" ]; then
echo "Extracting ${my_dl_rel}" echo "Extracting ${WEBI_PKG_PATH}/$WEBI_PKG_FILE"
tar xf "${WEBI_PKG_PATH}/$WEBI_PKG_FILE" tar xf "${WEBI_PKG_PATH}/$WEBI_PKG_FILE"
elif [ "zip" = "$WEBI_EXT" ]; then elif [ "zip" = "$WEBI_EXT" ]; then
echo "Extracting ${my_dl_rel}" echo "Extracting ${WEBI_PKG_PATH}/$WEBI_PKG_FILE"
unzip "${WEBI_PKG_PATH}/$WEBI_PKG_FILE" > __unzip__.log unzip "${WEBI_PKG_PATH}/$WEBI_PKG_FILE" > __unzip__.log
elif [ "exe" = "$WEBI_EXT" ]; then elif [ "exe" = "$WEBI_EXT" ]; then
echo "Moving ${my_dl_rel}" echo "Moving ${WEBI_PKG_PATH}/$WEBI_PKG_FILE"
mv "${WEBI_PKG_PATH}/$WEBI_PKG_FILE" .
elif [ "git" = "$WEBI_EXT" ]; then
echo "Moving ${my_dl_rel}"
mv "${WEBI_PKG_PATH}/$WEBI_PKG_FILE" . mv "${WEBI_PKG_PATH}/$WEBI_PKG_FILE" .
elif [ "xz" = "$WEBI_EXT" ]; then elif [ "xz" = "$WEBI_EXT" ]; then
echo "Inflating ${my_dl_rel}" echo "Inflating ${WEBI_PKG_PATH}/$WEBI_PKG_FILE"
unxz -c "${WEBI_PKG_PATH}/$WEBI_PKG_FILE" > "$(basename "$WEBI_PKG_FILE")" unxz -c "${WEBI_PKG_PATH}/$WEBI_PKG_FILE" > "$(basename "$WEBI_PKG_FILE")"
else else
# do nothing
echo "Failed to extract ${WEBI_PKG_PATH}/$WEBI_PKG_FILE" echo "Failed to extract ${WEBI_PKG_PATH}/$WEBI_PKG_FILE"
exit 1 exit 1
fi fi
@@ -505,24 +443,16 @@ __bootstrap_webi() {
# group common pre-install tasks as default # group common pre-install tasks as default
webi_pre_install() { webi_pre_install() {
webi_check_installed webi_check
webi_check_available webi_download
if test "git" = "${WEBI_EXT}"; then
webi_git_clone \
"${WEBI_PKG_URL}" \
"${WEBI_PKG_PATH}/${WEBI_PKG_FILE}"
return 0
fi
webi_download \
"${WEBI_PKG_URL}" \
"${WEBI_PKG_PATH}/${WEBI_PKG_FILE}"
webi_extract webi_extract
} }
# move commands from the extracted archive directory # move commands from the extracted archive directory to $HOME/.local/opt or $HOME/.local/bin
# to $HOME/.local/opt or $HOME/.local/bin # shellcheck disable=2120
# webi_install may be sourced and used elsewhere
webi_install() { webi_install() {
if test -n "${WEBI_SINGLE}"; then if [ -n "$WEBI_SINGLE" ] || [ "single" = "${1-}" ]; then
mkdir -p "$(dirname "$pkg_src_cmd")" mkdir -p "$(dirname "$pkg_src_cmd")"
mv ./"$pkg_cmd_name"* "$pkg_src_cmd" mv ./"$pkg_cmd_name"* "$pkg_src_cmd"
else else
@@ -533,29 +463,39 @@ __bootstrap_webi() {
# run post-install functions - just updating PATH by default # run post-install functions - just updating PATH by default
webi_post_install() { webi_post_install() {
if test -n "${pkg_no_exec}"; then
return 0
fi
webi_path_add "$(dirname "$pkg_dst_cmd")" webi_path_add "$(dirname "$pkg_dst_cmd")"
} }
_webi_enable_exec() { _webi_enable_exec() {
if command -v spctl > /dev/null && command -v xattr > /dev/null; then if [ -n "$(command -v spctl)" ] && [ -n "$(command -v xattr)" ]; then
# note: some packages contain files that cannot be affected by xattr # note: some packages contain files that cannot be affected by xattr
xattr -r -d com.apple.quarantine "$pkg_src" || true xattr -r -d com.apple.quarantine "$pkg_src" || true
return 0 return 0
fi 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() { _webi_done_message() {
my_dst_rel="$( echo "Installed $(_webi_canonical_name) as $pkg_dst_cmd"
webi_sub_home "${pkg_dst_cmd}"
)"
my_canonical_name="$(
_webi_canonical_name
)"
echo "Installed ${my_canonical_name} as ${my_dst_rel}"
} }
## ##
@@ -568,7 +508,7 @@ __bootstrap_webi() {
if [ -z "${WEBI_WELCOME-}" ]; then if [ -z "${WEBI_WELCOME-}" ]; then
echo "" echo ""
printf "Thanks for using webi to install '\e[32m%s\e[0m' on '\e[33m%s (%s) %s\e[0m'.\n" "${WEBI_PKG:-"Unknown Package"}" "$(uname -s)" "${WEBI_LIBC:-"Unknown Libc"}" "$(uname -m)" 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:" 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" printf " \e[2m\e[36mhttps://github.com/webinstall/webi-installers/issues\e[0m\n"
echo "" echo ""
@@ -577,14 +517,13 @@ __bootstrap_webi() {
echo "" echo ""
fi fi
WEBI_WELCOME=true
export WEBI_WELCOME
__init_installer() { __init_installer() {
# the installer will be injected here
# do nothing - to satisfy parser prior to templating
printf ""
# {{ installer }} # {{ installer }}
return 0
} }
__init_installer __init_installer
@@ -612,19 +551,13 @@ __bootstrap_webi() {
pkg_cmd_name="${pkg_cmd_name:-$PKG_NAME}" pkg_cmd_name="${pkg_cmd_name:-$PKG_NAME}"
pkg_no_exec="${pkg_no_exec:-}" if [ -n "$WEBI_SINGLE" ]; then
if [ -n "${pkg_no_exec}" ]; then
pkg_dst_cmd="${pkg_dst}"
pkg_src="${pkg_dst}"
pkg_src_cmd="${pkg_dst}"
elif [ -n "${WEBI_SINGLE}" ]; then
pkg_dst_cmd="${pkg_dst_cmd:-$HOME/.local/bin/$pkg_cmd_name}" pkg_dst_cmd="${pkg_dst_cmd:-$HOME/.local/bin/$pkg_cmd_name}"
pkg_dst="$pkg_dst_cmd" 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-v$WEBI_VERSION}"
pkg_src_cmd="${pkg_src_cmd:-$HOME/.local/opt/$pkg_cmd_name-v$WEBI_VERSION/bin/$pkg_cmd_name}" pkg_src_cmd="${pkg_src_cmd:-$HOME/.local/opt/$pkg_cmd_name-v$WEBI_VERSION/bin/$pkg_cmd_name}"
pkg_src="$pkg_src_cmd" pkg_src="$pkg_src_cmd" # "$(dirname "$(dirname $pkg_src_cmd)")"
else else
pkg_dst="${pkg_dst:-$HOME/.local/opt/$pkg_cmd_name}" pkg_dst="${pkg_dst:-$HOME/.local/opt/$pkg_cmd_name}"
pkg_dst_cmd="${pkg_dst_cmd:-$pkg_dst/bin/$pkg_cmd_name}" pkg_dst_cmd="${pkg_dst_cmd:-$pkg_dst/bin/$pkg_cmd_name}"
@@ -632,40 +565,33 @@ __bootstrap_webi() {
pkg_src="${pkg_src:-$HOME/.local/opt/$pkg_cmd_name-v$WEBI_VERSION}" 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}" pkg_src_cmd="${pkg_src_cmd:-$pkg_src/bin/$pkg_cmd_name}"
fi fi
# shellcheck disable=SC2034 # used in ${WEBI_PKG}/install.sh # this script is templated and these are used elsewhere
# shellcheck disable=SC2034
pkg_src_bin="$(dirname "$pkg_src_cmd")" pkg_src_bin="$(dirname "$pkg_src_cmd")"
# shellcheck disable=SC2034 # used in ${WEBI_PKG}/install.sh # shellcheck disable=SC2034
pkg_dst_bin="$(dirname "$pkg_dst_cmd")" pkg_dst_bin="$(dirname "$pkg_dst_cmd")"
if command -v pkg_pre_install > /dev/null; then pkg_pre_install; else webi_pre_install; fi if [ -n "$(command -v pkg_pre_install)" ]; then pkg_pre_install; else webi_pre_install; fi
( (
cd "$WEBI_TMP" cd "$WEBI_TMP"
my_src_rel="$( echo "Installing to $pkg_src_cmd"
webi_sub_home "${pkg_src_cmd}" if [ -n "$(command -v pkg_install)" ]; then pkg_install; else webi_install; fi
)" chmod a+x "$pkg_src"
if test -e "${pkg_src_cmd}"; then chmod a+x "$pkg_src_cmd"
echo "Found ${my_src_rel} (remove to force reinstall)"
else
echo "Installing to ${my_src_rel}"
if command -v pkg_install > /dev/null; then pkg_install; else webi_install; fi
chmod a+x "$pkg_src"
chmod a+x "$pkg_src_cmd"
fi
) )
if test -z "${pkg_no_exec}"; then webi_link
webi_link
_webi_enable_exec _webi_enable_exec
fi
( (
cd "$WEBI_TMP" cd "$WEBI_TMP"
if command -v pkg_post_install > /dev/null; then pkg_post_install; else webi_post_install; fi if [ -n "$(command -v pkg_post_install)" ]; then pkg_post_install; else webi_post_install; fi
) )
( (
cd "$WEBI_TMP" cd "$WEBI_TMP"
if command -v pkg_done_message > /dev/null; then pkg_done_message; else _webi_done_message; fi if [ -n "$(command -v pkg_done_message)" ]; then pkg_done_message; else _webi_done_message; fi
) )
echo "" echo ""
@@ -673,7 +599,7 @@ __bootstrap_webi() {
webi_path_add "$HOME/.local/bin" webi_path_add "$HOME/.local/bin"
if [ -z "${_WEBI_CHILD-}" ] && [ -f "$_webi_tmp/.PATH.env" ]; then if [ -z "${_WEBI_CHILD-}" ] && [ -f "$_webi_tmp/.PATH.env" ]; then
if test -s "$_webi_tmp/.PATH.env"; then if [ -n "$(cat "$_webi_tmp/.PATH.env")" ]; then
printf 'PATH.env updated with:\n' printf 'PATH.env updated with:\n'
sort -u "$_webi_tmp/.PATH.env" | while read -r my_new_path; do sort -u "$_webi_tmp/.PATH.env" | while read -r my_new_path; do
echo " ${my_new_path}" echo " ${my_new_path}"
@@ -689,7 +615,11 @@ __bootstrap_webi() {
fi fi
fi fi
# cleanup the temp directory
rm -rf "$WEBI_TMP" rm -rf "$WEBI_TMP"
# See? No magic. Just downloading and moving files.
} }
__bootstrap_webi __bootstrap_webi

View File

@@ -33,8 +33,7 @@ var os = require('os');
var fs = require('fs'); var fs = require('fs');
var path = require('path'); var path = require('path');
var Releases = require('./releases.js'); var Releases = require('./releases.js');
var ServeInstaller = require('./serve-installer.js'); var uaDetect = require('./ua-detect.js');
var pkg = process.argv[2].split('@'); var pkg = process.argv[2].split('@');
var pkgdir = pkg[0]; var pkgdir = pkg[0];
var pkgtag = pkg[1] || ''; var pkgtag = pkg[1] || '';
@@ -43,7 +42,6 @@ var nodes = fs.readdirSync(pkgdir);
nodes.forEach(function (node) { nodes.forEach(function (node) {
nodesMap[node] = true; nodesMap[node] = true;
}); });
var baseurl = 'https://webinstall.dev';
var maxLen = 0; var maxLen = 0;
console.info(''); console.info('');
@@ -64,48 +62,31 @@ console.info('Has the necessary files?');
}); });
console.info(''); console.info('');
Releases.get(path.join(process.cwd(), pkgdir)).then(async function (all) { Releases.get(path.join(process.cwd(), pkgdir)).then(function (all) {
var pkgname = path.basename(pkgdir.replace(/\/$/, '')); var pkgname = path.basename(pkgdir.replace(/\/$/, ''));
var nodeOs = os.platform(); var osrel = os.platform() + '-' + os.release();
var nodeOsRelease = os.release(); var arch = os.arch();
var nodeArch = os.arch(); var formats = ['exe', 'xz', 'tar', 'zip'];
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'];
let [rel, opts] = await ServeInstaller.helper({ var rel = all.releases.filter(function (rel) {
ua: `${nodeOs}/${nodeOsRelease} ${nodeArch}/unknown ${nodeLibc}`, return (
pkg: pkgname, formats.filter(function (ext) {
tag: pkgtag || '', return rel.ext.match(ext);
formats: formats, })[0] &&
libc: nodeLibc, 'stable' === rel.channel &&
}); rel.os === uaDetect.os(osrel) &&
Object.assign( rel.arch === uaDetect.arch(arch) &&
{ (!pkgtag ||
ver: '', rel.tag === pkgtag ||
lts: null, new RegExp('^' + pkgtag).test(rel.version))
channel: '', );
os: '', })[0];
arch: '', rel.oses = all.oses;
limit: 0, rel.arches = all.arches;
}, rel.formats = all.formats;
opts,
{
baseurl,
},
);
if (!rel) { if (!rel) {
console.error( console.error('Error: ❌ no release found for current os, arch, and tag');
`Error: ❌ no release found for @${pkgtag}?os=${nodeOs}&arch=${nodeArch}&libc=${nodeLibc}&formats=${formats}`,
);
process.exit(1); process.exit(1);
return; return;
} }
@@ -116,8 +97,24 @@ Releases.get(path.join(process.cwd(), pkgdir)).then(async function (all) {
console.info(''); console.info('');
return Promise.all([ return Promise.all([
Releases.renderBash(pkgdir, rel, opts).catch(function () {}), Releases.renderBash(pkgdir, rel, {
Releases.renderPowerShell(pkgdir, rel, opts).catch(function () {}), 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) { ]).then(function (scripts) {
var bashTxt = scripts[0]; var bashTxt = scripts[0];
var ps1Txt = scripts[1]; var ps1Txt = scripts[1];
@@ -141,9 +138,5 @@ Releases.get(path.join(process.cwd(), pkgdir)).then(async function (all) {
console.info('\tNEEDS MANUAL TEST: powershell.exe %s', ps1File); console.info('\tNEEDS MANUAL TEST: powershell.exe %s', ps1File);
} }
console.info(''); console.info('');
setTimeout(function () {
console.warn(`[warn] dangling event loop handle`);
process.exit(0);
}, 300).unref();
}); });
}); });

View File

@@ -2,6 +2,7 @@
var path = require('path'); var path = require('path');
var Releases = require('./releases.js'); var Releases = require('./releases.js');
var uaDetect = require('./ua-detect.js');
var cache = {}; var cache = {};
//var staleAge = 5 * 1000; //var staleAge = 5 * 1000;
@@ -53,14 +54,21 @@ function createFormatsSorter(formats) {
return 1; return 1;
} }
// rank builds that don't depend on any form of libc first // Hacky-doo for musl-native: prefer non-musl
if (a.libc === 'none' && b.libc !== 'none') { if (a._musl_native && !b._musl_native) {
return -1;
}
if (a.libc !== 'none' && b.libc === 'none') {
return 1; 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; return 0;
}; };
} }
@@ -174,35 +182,24 @@ async function filterReleases(
function selectMatches(rel) { function selectMatches(rel) {
if (os) { if (os) {
if (rel.os !== '*') { if (rel.os !== os) {
if (rel.os !== os) { return false;
return false;
}
} }
} }
if (arch) { if (arch) {
if (rel.arch !== '*') { if (rel.arch !== arch) {
if (rel.arch !== arch) { return false;
return false;
}
} }
} }
if (rel.libc !== 'none') { // Hacky-doo for linux musl
let releaseRequiresMusl = rel.libc === 'musl'; if (libc === uaDetect.MUSL_NATIVE) {
// goal: handle non-glibc (Alpine / Docker / musl) if (!rel._musl && !rel._musl_native) {
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)
return false; return false;
} }
} else if (rel._musl_native) {
return false;
} }
if (lts) { if (lts) {
@@ -362,20 +359,20 @@ module.exports = function getReleases({
date: '1970-01-01', date: '1970-01-01',
os: os || '-', os: os || '-',
arch: arch || '-', arch: arch || '-',
libc: libc || '-', _musl: undefined,
_musl_native: undefined,
ext: 'err', ext: 'err',
download: 'https://example.com/doesntexist.ext', download: 'https://example.com/doesntexist.ext',
comment: comment:
'No matches found. Could be bad or missing version info' + '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'", "Check query parameters. Should be something like '/api/releases/{package}@{version}.tab?os={macos|linux|windows|-}&arch={amd64|x86|aarch64|arm64|armv7l|-}&limit=100'",
}, },
]; ];
} }
return { return {
oses: all.oses, oses: all.oses,
arches: all.arches, arches: all.arches,
libcs: all.libcs,
formats: all.formats, formats: all.formats,
releases: releases, releases: releases,
}; };
@@ -391,7 +388,7 @@ if (require.main === module) {
os: 'macos', os: 'macos',
arch: 'amd64', arch: 'amd64',
lts: true, lts: true,
libc: 'libc', libc: '',
channel: 'stable', channel: 'stable',
formats: ['tar', 'exe', 'zip', 'xz', 'dmg', 'pkg'], formats: ['tar', 'exe', 'zip', 'xz', 'dmg', 'pkg'],
limit: 10, limit: 10,

View File

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

@@ -2,4 +2,4 @@
Write-Output "'archiver@$Env:WEBI_TAG' is an alias for 'arc@$Env:WEBI_VERSION'" 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" } 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: { x86win: {
os: 'windows', os: 'windows',
arch: 'x86', arch: 'x86',
libc: 'msvc',
}, },
x64win: { x64win: {
os: 'windows', os: 'windows',
arch: 'amd64', arch: 'amd64',
// https://github.com/wez/atomicparsley/issues/6#issuecomment-1364523028
libc: 'msvc',
}, },
x64mac: { x64mac: {
os: 'macos', os: 'macos',
@@ -23,12 +20,11 @@ let targets = {
x64lin: { x64lin: {
os: 'linux', os: 'linux',
arch: 'amd64', arch: 'amd64',
libc: 'gnu',
}, },
x64musl: { x64musl: {
os: 'linux', os: 'linux',
arch: 'amd64', arch: 'amd64',
libc: 'musl', _musl_native: true,
}, },
}; };

View File

@@ -15,7 +15,7 @@ IF (-not (Test-Path "\Windows\System32\vcruntime140.dll")) {
& "$Env:USERPROFILE\.local\bin\webi-pwsh.ps1" vcruntime & "$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" Write-Output "Installing $Env:PKG_NAME"
# TODO: temp directory # TODO: temp directory
@@ -35,13 +35,13 @@ IF (!(Test-Path -Path "$Env:USERPROFILE\.local\bin\$VERNAME")) {
# Settle unpacked archive into place # Settle unpacked archive into place
Write-Output "New Name: $VERNAME" Write-Output "New Name: $VERNAME"
Write-Output "New Location: $Env:USERPROFILE\.local\bin\$VERNAME" Write-Output "New Location: $Env:USERPROFILE\.local\xbin\$VERNAME"
Move-Item -Path "$VERNAME" -Destination "$Env:USERPROFILE\.local\bin" Move-Item -Path "$VERNAME" -Destination "$Env:USERPROFILE\.local\xbin"
# Exit tmp # Exit tmp
Pop-Location 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 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() { pkg_install() {
# ~/.local/bin # ~/.local/xbin
mkdir -p "$pkg_src_bin" mkdir -p "$pkg_src_bin"
# mv ./bat-*/bat ~/.local/opt/bat-v0.15.4/bin/bat # mv ./bat-*/bat ~/.local/opt/bat-v0.15.4/bin/bat

View File

@@ -7,7 +7,6 @@ main() { (
chmod a+x ~/.local/bin/brew-update-hourly chmod a+x ~/.local/bin/brew-update-hourly
env PATH="$PATH" serviceman add --user \ env PATH="$PATH" serviceman add --user \
--workdir ~/.local/opt/brew/ \
--name sh.brew.updater -- \ --name sh.brew.updater -- \
~/.local/bin/brew-update-hourly ~/.local/bin/brew-update-hourly
); } ); }

File diff suppressed because it is too large Load Diff

View File

@@ -8,19 +8,19 @@
$pkg_cmd_name = "cmake" $pkg_cmd_name = "cmake"
$pkg_dst_cmd = "$Env:USERPROFILE\.local\opt\cmake\bin\cmake.exe" $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_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_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_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 New-Item "$Env:USERPROFILE\Downloads\webi" -ItemType Directory -Force | Out-Null
$pkg_download = "$Env:USERPROFILE\Downloads\webi\$Env:WEBI_PKG_FILE" $pkg_download = "$Env:USERPROFILE\Downloads\webi\$Env:WEBI_PKG_FILE"
# Fetch archive # 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" 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" & curl.exe -A "$Env:WEBI_UA" -fsSL "$Env:WEBI_PKG_URL" -o "$pkg_download.part"
& Move-Item "$pkg_download.part" "$pkg_download" & Move-Item "$pkg_download.part" "$pkg_download"
@@ -49,8 +49,6 @@ IF (!(Test-Path -Path "$pkg_src_dir")) {
Pop-Location Pop-Location
} }
Write-Output "Copying into '$pkg_dst' from '$pkg_src'" Write-Output "Copying into '$pkg_dst_cmd' from '$pkg_src_cmd'"
Remove-Item -Path "$pkg_dst" -Recurse -ErrorAction Ignore | Out-Null Remove-Item -Path "$pkg_dst_cmd" -Recurse -ErrorAction Ignore | Out-Null
Copy-Item -Path "$pkg_src" -Destination "$pkg_dst" -Recurse 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}")" mkdir -p "$(dirname "${pkg_src_dir}")"
# mv ./cmake-*/ ~/.local/opt/cmake-v3.27.0/ # mv ./cmake-*/ ~/.local/opt/cmake-v3.27.0/
if test -e ./cmake-*/CMake.app/Contents/; then mv ./cmake-*/ "${pkg_src_dir}"
mv ./cmake-*/CMake.app/Contents/ "${pkg_src_dir}"
else
mv ./cmake-*/ "${pkg_src_dir}"
fi
} }
# pkg_get_current_version is recommended, but not required # pkg_get_current_version is recommended, but not required

View File

@@ -6,35 +6,6 @@ var repo = 'CMake';
module.exports = function (request) { module.exports = function (request) {
return github(request, owner, repo).then(function (all) { return github(request, owner, repo).then(function (all) {
for (let rel of all.releases) {
{
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; 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" & 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" Write-Output "Installing $Env:PKG_NAME"
# TODO: temp directory # TODO: temp directory
@@ -29,13 +29,13 @@ IF (!(Test-Path -Path "$Env:USERPROFILE\.local\bin\$VERNAME")) {
# Settle unpacked archive into place # Settle unpacked archive into place
Write-Output "New Name: $VERNAME" Write-Output "New Name: $VERNAME"
Write-Output "New Location: $Env:USERPROFILE\.local\bin\$VERNAME" Write-Output "New Location: $Env:USERPROFILE\.local\xbin\$VERNAME"
Move-Item -Path "$VERNAME" -Destination "$Env:USERPROFILE\.local\bin" Move-Item -Path "$VERNAME" -Destination "$Env:USERPROFILE\.local\xbin"
# Exit tmp # Exit tmp
Pop-Location 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 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() { pkg_install() {
# $HOME/.local/bin # $HOME/.local/xbin
mkdir -p "$pkg_src_bin" mkdir -p "$pkg_src_bin"
# mv ./curlie* "$HOME/.local/opt/curlie-v1.3.1/bin/curlie" # mv ./curlie* "$HOME/.local/opt/curlie-v1.3.1/bin/curlie"

View File

@@ -23,13 +23,13 @@ pkg_get_current_version() {
} }
pkg_install() { pkg_install() {
# $HOME/.local/bin # $HOME/.local/xbin
mkdir -p "$pkg_src_bin" 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" 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" chmod a+x "$pkg_src_cmd"
} }
@@ -37,6 +37,6 @@ pkg_link() {
# rm -f "$HOME/.local/bin/deno" # rm -f "$HOME/.local/bin/deno"
rm -f "$pkg_dst_cmd" 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" ln -s "$pkg_src_cmd" "$pkg_dst_cmd"
} }

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" & 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" Write-Output "Installing $Env:PKG_NAME"
# TODO: temp directory # TODO: temp directory
@@ -29,13 +29,13 @@ IF (!(Test-Path -Path "$Env:USERPROFILE\.local\bin\$VERNAME")) {
# Settle unpacked archive into place # Settle unpacked archive into place
Write-Output "New Name: $VERNAME" Write-Output "New Name: $VERNAME"
Write-Output "New Location: $Env:USERPROFILE\.local\bin\$VERNAME" Write-Output "New Location: $Env:USERPROFILE\.local\xbin\$VERNAME"
Move-Item -Path "$VERNAME" -Destination "$Env:USERPROFILE\.local\bin" Move-Item -Path "$VERNAME" -Destination "$Env:USERPROFILE\.local\xbin"
# Exit tmp # Exit tmp
Pop-Location 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 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

@@ -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" & 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" Write-Output "Installing $Env:PKG_NAME"
# TODO: temp directory # TODO: temp directory
@@ -29,13 +29,13 @@ IF (!(Test-Path -Path "$Env:USERPROFILE\.local\bin\$VERNAME")) {
# Settle unpacked archive into place # Settle unpacked archive into place
Write-Output "New Name: $VERNAME" Write-Output "New Name: $VERNAME"
Write-Output "New Location: $Env:USERPROFILE\.local\bin\$VERNAME" Write-Output "New Location: $Env:USERPROFILE\.local\xbin\$VERNAME"
Move-Item -Path "$VERNAME" -Destination "$Env:USERPROFILE\.local\bin" Move-Item -Path "$VERNAME" -Destination "$Env:USERPROFILE\.local\xbin"
# Exit tmp # Exit tmp
Pop-Location 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 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). 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 ## Cheat Sheet
> `gh` is cross-platform Github command-line. You can perform pull requests > `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" MY_CMD="git-config-gpg"
rm -f "$HOME/.local/bin/$MY_CMD" rm -f "$HOME/.local/bin/$MY_CMD"
webi_download \ webi_download "$WEBI_HOST/packages/$MY_CMD/$MY_CMD.sh" "$HOME/.local/bin/$MY_CMD"
"$WEBI_HOST/packages/$MY_CMD/$MY_CMD.sh" \
"$HOME/.local/bin/$MY_CMD"
chmod a+x "$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'" 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" } 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/) 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. 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 ### How to commit files
```sh ```sh
@@ -63,7 +41,7 @@ ignore
This will branch from the branch you're currently on. This will branch from the branch you're currently on.
```sh ```sh
git switch -c my-branch-name git checkout -b my-branch-name
``` ```
### How to rebase by default ### How to rebase by default
@@ -88,11 +66,11 @@ Rebase a feature branch from master before a merge
```sh ```sh
# update master # update master
git fetch git fetch
git switch master git checkout master
git pull git pull
# go back to your feature branch # go back to your feature branch
git switch my-feature git checkout my-feature
# start the rebase # start the rebase
git rebase master git rebase master
@@ -119,14 +97,14 @@ git config --global url."https://git@github.com/".insteadOf "git@github.com:"
Next, create a `.git-askpass`: Next, create a `.git-askpass`:
```sh ```sh
echo 'echo "${MY_GIT_TOKEN}"' > ~/.git-askpass echo 'echo $MY_GIT_TOKEN' > $HOME/.git-askpass
chmod +x ~/.git-askpass chmod +x $HOME/.git-askpass
``` ```
Finally, add the following ENVs to your deployment environment: Finally, add the following ENVs to your deployment environment:
```sh ```sh
GIT_ASKPASS="${HOME}/.git-askpass" GIT_ASKPASS=$HOME/.git-askpass
# Relpace xxxx... with your deploy token # Relpace xxxx... with your deploy token
MY_GIT_TOKEN=xxxxxxxxxxxxxxxx MY_GIT_TOKEN=xxxxxxxxxxxxxxxx

View File

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

View File

@@ -7,20 +7,6 @@ tagline: |
To update or switch versions, run `webi gitdeploy@stable`. 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 ## Cheat Sheet
> gitdeploy makes it easy to build and deploy static sites (or anything else) > 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. **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 ## Cheat Sheet
> Gitea is a clean, lightweight self-hosted Github clone. It only uses a few > Gitea is a clean, lightweight self-hosted Github clone. It only uses a few

View File

@@ -2,7 +2,8 @@
if (!(Get-Command "go.exe" -ErrorAction SilentlyContinue)) { if (!(Get-Command "go.exe" -ErrorAction SilentlyContinue)) {
& "$Env:USERPROFILE\.local\bin\webi-pwsh.ps1" go & "$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 # Special to go: re-run all go tooling builds

View File

@@ -12,7 +12,8 @@ $pkg_dst_bin = "$pkg_dst\bin"
if (!(Get-Command "git.exe" -ErrorAction SilentlyContinue)) { if (!(Get-Command "git.exe" -ErrorAction SilentlyContinue)) {
& "$Env:USERPROFILE\.local\bin\webi-pwsh.ps1" git & "$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 # Fetch archive

View File

@@ -12,7 +12,8 @@ $pkg_dst_bin = "$pkg_dst\bin"
if (!(Get-Command "git.exe" -ErrorAction SilentlyContinue)) { if (!(Get-Command "git.exe" -ErrorAction SilentlyContinue)) {
& "$Env:USERPROFILE\.local\bin\webi-pwsh.ps1" git & "$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 # 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 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: found the most useful for the majority of projects:
- Files
- Basic Usage & Versioning - Basic Usage & Versioning
- Publishing Builds to GitHub - Publishing Builds to GitHub
- Publishing to Gitea and Gitlab - Publishing to Gitea and Gitlab
@@ -26,32 +25,18 @@ found the most useful for the majority of projects:
- Cross-Compiling with cgo - Cross-Compiling with cgo
- Full `.goreleaser.yml` example - 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 ## Basic Usage & Versioning
To create an example `.goreleaser.yaml` file, and test the configuration: To create an example `.goreleaser.yaml` file, and test the configuration:
```sh ```sh
goreleaser init goreleaser init
goreleaser --snapshot --skip-publish --rm-dist
# dry run
goreleaser --snapshot --skip=publish --clean
``` ```
- `--snapshot` allows "dirty" builds (when the repo has uncommitted changes) - `--snapshot` allows "dirty" builds (when the repo has uncommitted changes)
- `--skip=publish` will NOT publish to GitHub, etc - `--skip-publish` will NOT publish to GitHub, etc
- `--clean` will automatically remove the `./dist/` directory - `--rm-dist` will automatically remove the `./dist/` directory
The default `.goreleaser.yml` works well for projects for which `package main` The default `.goreleaser.yml` works well for projects for which `package main`
is at the root. is at the root.
@@ -64,17 +49,14 @@ package main
var ( var (
// these will be replaced by goreleaser // these will be replaced by goreleaser
version = "0.0.0" version = "v0.0.0"
date = "0001-01-01T00:00:00Z" date = "0001-01-01T00:00:00Z"
commit = "0000000" commit = "0000000"
) )
func main() { func main() {
if len(os.Args) >= 2 { if len(os.Args) >= 2 && "version" == strings.TrimPrefix(os.Args[1]) {
if os.Args[1] == "-V" || strings.TrimPrefix(os.Args[1]) == "version" { fmt.Printf("YOUR_CLI_NAME v%s %s (%s)\n", version, commit[:7], date)
fmt.Printf("CHANGE_ME v%s %s (%s)\n", version, commit[:7], date)
os.Exit(0)
}
} }
// ... // ...
@@ -114,10 +96,10 @@ git tag -a v1.0.0 -m "First release"
git push origin v1.0.0 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 ```sh
goreleaser --clean goreleaser --rm-dist
``` ```
Check the console output to make sure that there are no messages about a failed Check the console output to make sure that there are no messages about a failed
@@ -337,19 +319,21 @@ builds:
- linux - linux
- windows - windows
- darwin - darwin
- freebsd
- js
goarch: goarch:
- 386 - 386
- amd64 - amd64
- arm - arm
- arm64 - arm64
- wasm
goarm: goarm:
- 6
- 7 - 7
archives: archives:
- id: my-binary - replacements:
format: tar.xz darwin: Darwin
linux: Linux
windows: Windows
386: i386
amd64: x86_64
format_overrides: format_overrides:
- goos: windows - goos: windows
format: zip format: zip

View File

@@ -20,40 +20,20 @@ __init_goreleaser() {
# pkg_install must be defined by every package # pkg_install must be defined by every package
pkg_install() { pkg_install() {
# ~/.local/opt/goreleaser-v1.21.2/bin # ~/.local/opt/goreleaser-v0.99.9/bin
mkdir -p "$(dirname "$pkg_src_cmd")" 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" mv ./goreleaser "$pkg_src_cmd"
} }
# pkg_get_current_version is recommended, but (soon) not required # pkg_get_current_version is recommended, but (soon) not required
pkg_get_current_version() { pkg_get_current_version() {
# 'goreleaser --version' has output in this format: # 'goreleaser --version' has output in this format:
# ____ ____ _ # goreleaser 0.99.9 (rev abcdef0123)
# / ___| ___ | _ \ ___| | ___ __ _ ___ ___ _ __
# | | _ / _ \| |_) / _ \ |/ _ \/ _` / __|/ _ \ '__|
# | |_| | (_) | _ < __/ | __/ (_| \__ \ __/ |
# \____|\___/|_| \_\___|_|\___|\__,_|___/\___|_|
# 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
# This trims it down to just the version number: # This trims it down to just the version number:
# 1.21.2 # 0.99.9
# shellcheck disable=SC2046,SC2005 # unquoted echo trims whitespace goreleaser --version 2> /dev/null | head -n 1 | cut -d ' ' -f 2
goreleaser --version 2> /dev/null |
grep 'GitVersion:' |
cut -d':' -f2 |
tr -d ' '
} }
} }

View File

@@ -1,43 +1,37 @@
#!/usr/bin/env pwsh #!/usr/bin/env pwsh
$ErrorActionPreference = 'stop' #
# gpg-pubkey-id
#
$MY_CMD = "gpg-pubkey"
$MY_SUBCMD = "gpg-pubkey-id"
function Install-WebiHostedScript () { & curl.exe -A "$Env:WEBI_UA" -fsSL "$Env:WEBI_HOST/packages/$MY_CMD/$MY_SUBCMD.ps1" -o "$Env:USERPROFILE\.local\bin\$MY_SUBCMD.ps1.part"
Param( Remove-Item -Path "$Env:USERPROFILE\.local\bin\$MY_SUBCMD.ps1" -Recurse -ErrorAction Ignore
[string]$Package, & Move-Item "$Env:USERPROFILE\.local\bin\$MY_SUBCMD.ps1.part" "$Env:USERPROFILE\.local\bin\$MY_SUBCMD.ps1"
[string]$ScriptName Set-Content -Path "$Env:USERPROFILE\.local\bin\$MY_SUBCMD.bat" -Value "@echo off`r`npushd %USERPROFILE%`r`npowershell -ExecutionPolicy Bypass .local\bin\$MY_SUBCMD.ps1 %1`r`npopd"
)
$PwshName = "_${ScriptName}.ps1"
$PwshUrl = "${Env:WEBI_HOST}/packages/${Package}/${ScriptName}.ps1"
$PwshPath = "$HOME\.local\bin\${PwshName}"
$OldPath = "$HOME\.local\bin\${ScriptName}.ps1"
$BatPath = "$HOME\.local\bin\${ScriptName}.bat" #
$PwshExec = "powershell -ExecutionPolicy Bypass" # gpg-pubkey
$Bat = "@echo off`r`n$PwshExec %USERPROFILE%\.local\bin\${PwshName} %*" #
$MY_CMD = "gpg-pubkey"
Invoke-DownloadUrl -Force -URL $PwshUrl -Path $PwshPath & curl.exe -A "$Env:WEBI_UA" -fsSL "$Env:WEBI_HOST/packages/$MY_CMD/$MY_CMD.ps1" -o "$Env:USERPROFILE\.local\bin\$MY_CMD.ps1.part"
Set-Content -Path $BatPath -Value $Bat Remove-Item -Path "$Env:USERPROFILE\.local\bin\$MY_CMD.ps1" -Recurse -ErrorAction Ignore
Write-Host " Created alias ${BatPath}" & Move-Item "$Env:USERPROFILE\.local\bin\$MY_CMD.ps1.part" "$Env:USERPROFILE\.local\bin\$MY_CMD.ps1"
Write-Host " to run ${PwshPath}" Set-Content -Path "$Env:USERPROFILE\.local\bin\$MY_CMD.bat" -Value "@echo off`r`npushd %USERPROFILE%`r`npowershell -ExecutionPolicy Bypass .local\bin\$MY_CMD.ps1 %1`r`npopd"
# fix for old installs
Remove-Item -Path $OldPath -Force -ErrorAction Ignore
}
Install-WebiHostedScript -Package "gpg-pubkey" -ScriptName "gpg-pubkey-id"
Install-WebiHostedScript -Package "gpg-pubkey" -ScriptName "gpg-pubkey"
# #
# Check the gpg exists # Check the gpg exists
# #
IF (-Not (Get-Command -Name "gpg" -ErrorAction SilentlyContinue)) {
& "${Env:USERPROFILE}\.local\bin\webi-pwsh.ps1" gpg
$Env:Path = [Environment]::GetEnvironmentVariable("Path", "User")
IF (-Not (Get-Command -Name "gpg" -ErrorAction SilentlyContinue)) { $gpg_exists = Get-Command gpg 2> $null
if (!$gpg_exists) {
curl.exe "$Env:WEBI_HOST/gpg" | powershell
$gpg_exists = Get-Command gpg 2> $null
if (!$gpg_exists) {
Write-Output "" Write-Output ""
Write-Output "(exited because gpg is not installed)" Write-Output "(exited because gpg is not existalled)"
Write-Output "" Write-Output ""
Exit 1 Exit 1
} }
@@ -46,4 +40,4 @@ IF (-Not (Get-Command -Name "gpg" -ErrorAction SilentlyContinue)) {
# #
# run gpg-pubkey # run gpg-pubkey
# #
& "$Env:USERPROFILE\.local\bin\gpg-pubkey.bat" & "$Env:USERPROFILE\.local\bin\$MY_CMD.bat"

View File

@@ -6,9 +6,7 @@ __install_gpg_pubkey() {
MY_CMD="gpg-pubkey" MY_CMD="gpg-pubkey"
rm -f "$HOME/.local/bin/$MY_CMD" rm -f "$HOME/.local/bin/$MY_CMD"
webi_download \ webi_download "$WEBI_HOST/packages/$MY_CMD/$MY_CMD.sh" "$HOME/.local/bin/$MY_CMD"
"$WEBI_HOST/packages/$MY_CMD/$MY_CMD.sh" \
"$HOME/.local/bin/$MY_CMD"
chmod a+x "$HOME/.local/bin/$MY_CMD" chmod a+x "$HOME/.local/bin/$MY_CMD"
} }
@@ -17,9 +15,7 @@ __install_gpg_pubkey_id() {
MY_SUBCMD="gpg-pubkey-id" MY_SUBCMD="gpg-pubkey-id"
rm -f "$HOME/.local/bin/$MY_SUBCMD" rm -f "$HOME/.local/bin/$MY_SUBCMD"
webi_download \ webi_download "$WEBI_HOST/packages/$MY_CMD/$MY_SUBCMD.sh" "$HOME/.local/bin/$MY_SUBCMD"
"$WEBI_HOST/packages/$MY_CMD/$MY_SUBCMD.sh" \
"$HOME/.local/bin/$MY_SUBCMD"
chmod a+x "$HOME/.local/bin/$MY_SUBCMD" chmod a+x "$HOME/.local/bin/$MY_SUBCMD"
} }

View File

@@ -12,9 +12,7 @@ _install_gpg() {
# Download the latest LTS # Download the latest LTS
#curl -fsSL -o ~/Downloads/webi/GnuPG-2.2.32.dmg 'https://sourceforge.net/projects/gpgosx/files/GnuPG-2.2.32.dmg/download' #curl -fsSL -o ~/Downloads/webi/GnuPG-2.2.32.dmg 'https://sourceforge.net/projects/gpgosx/files/GnuPG-2.2.32.dmg/download'
webi_download \ webi_download
"${WEBI_PKG_URL}" \
"${WEBI_PKG_PATH}/${WEBI_PKG_FILE}"
chmod a-w "${WEBI_PKG_DOWNLOAD}" chmod a-w "${WEBI_PKG_DOWNLOAD}"
# Mount the DMG in /Volumes # Mount the DMG in /Volumes
@@ -36,7 +34,7 @@ _install_gpg() {
rm -rf ~/.local/opt/gnupg rm -rf ~/.local/opt/gnupg
ln -s gnupg-"${WEBI_VERSION}" ~/.local/opt/gnupg ln -s gnupg-"${WEBI_VERSION}" ~/.local/opt/gnupg
webi_path_add ~/.local/opt/gnupg/bin pathman add ~/.local/opt/gnupg/bin
export PATH="$HOME/.local/opt/gnupg/bin:$PATH" export PATH="$HOME/.local/opt/gnupg/bin:$PATH"
export PATH="$HOME/.local/opt/gnupg/bin/pinentry-mac.app/Contents/MacOS:$PATH" export PATH="$HOME/.local/opt/gnupg/bin/pinentry-mac.app/Contents/MacOS:$PATH"

View File

@@ -9,16 +9,6 @@ tagline: |
`webi gprox` `webi gprox`
### Files
These are the files / directories that are created and/or modified with this
install:
```text
~/.config/envman/PATH.env
~/.local/bin/gprox
```
## Cheat Sheet ## Cheat Sheet
`gprox` was built to be a no-dependency development tool for simply proxying `gprox` was built to be a no-dependency development tool for simply proxying

View File

@@ -8,16 +8,6 @@ tagline: |
To update or switch versions, run `webi hexyl@stable` (or `@v0.9`, `@beta`, To update or switch versions, run `webi hexyl@stable` (or `@v0.9`, `@beta`,
etc). etc).
### Files
These are the files / directories that are created and/or modified with this
install:
```text
~/.config/envman/PATH.env
~/.local/bin/hexyl
```
## Cheat Sheet ## Cheat Sheet
![](https://camo.githubusercontent.com/1f71ee7031e1962b23f21c8cc89cb837e1201238/68747470733a2f2f692e696d6775722e636f6d2f4d574f3975534c2e706e67) ![](https://camo.githubusercontent.com/1f71ee7031e1962b23f21c8cc89cb837e1201238/68747470733a2f2f692e696d6775722e636f6d2f4d574f3975534c2e706e67)

View File

@@ -4,8 +4,6 @@ set -u
__iterm2_utils() { __iterm2_utils() {
curl -fsSL https://iterm2.com/shell_integration/install_shell_integration_and_utilities.sh | bash curl -fsSL https://iterm2.com/shell_integration/install_shell_integration_and_utilities.sh | bash
webi_path_add ~/.iterm2/
} }
__iterm2_utils __iterm2_utils

View File

@@ -16,9 +16,7 @@ _install_iterm2() {
exit 1 exit 1
fi fi
webi_download \ webi_download
"${WEBI_PKG_URL}" \
"${WEBI_PKG_PATH}/${WEBI_PKG_FILE}"
webi_extract webi_extract
if [ ! -d "${WEBI_TMP}/iTerm.app" ]; then if [ ! -d "${WEBI_TMP}/iTerm.app" ]; then

View File

@@ -7,16 +7,6 @@ tagline: |
To update or switch versions, run `webi jq@stable` (or `@v1.6`, `@beta`, etc). To update or switch versions, run `webi jq@stable` (or `@v1.6`, `@beta`, etc).
### Files
These are the files / directories that are created and/or modified with this
install:
```text
~/.config/envman/PATH.env
~/.local/bin/jq
```
## Cheat Sheet ## Cheat Sheet
> `jq` is like `sed` for JSON data - you can use it to slice and filter and map > `jq` is like `sed` for JSON data - you can use it to slice and filter and map

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" & 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" Write-Output "Installing $Env:PKG_NAME"
# TODO: temp directory # TODO: temp directory
@@ -26,13 +26,13 @@ IF (!(Test-Path -Path "$Env:USERPROFILE\.local\bin\$VERNAME")) {
# Settle unpacked archive into place # Settle unpacked archive into place
Write-Output "New Name: $VERNAME" Write-Output "New Name: $VERNAME"
Get-ChildItem "$Env:PKG_NAME-v*" | Select-Object -f 1 | Rename-Item -NewName "$VERNAME" Get-ChildItem "$Env:PKG_NAME-v*" | Select-Object -f 1 | Rename-Item -NewName "$VERNAME"
Write-Output "New Location: $Env:USERPROFILE\.local\bin\$VERNAME" Write-Output "New Location: $Env:USERPROFILE\.local\xbin\$VERNAME"
Move-Item -Path "$VERNAME" -Destination "$Env:USERPROFILE\.local\bin" Move-Item -Path "$VERNAME" -Destination "$Env:USERPROFILE\.local\xbin"
# Exit tmp # Exit tmp
Pop-Location 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 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,16 +7,6 @@ tagline: |
To update or switch versions, run `webi keypairs@stable`. To update or switch versions, run `webi keypairs@stable`.
### Files
These are the files / directories that are created and/or modified with this
install:
```text
~/.config/envman/PATH.env
~/.local/bin/keypairs
```
## Cheat Sheet ## Cheat Sheet
> keypairs is like JWT.io, at your fingertips. > keypairs is like JWT.io, at your fingertips.

View File

@@ -7,17 +7,6 @@ tagline: |
To update or switch versions, run `webi kind@stable` (or `@v2`, `@beta`,etc). To update or switch versions, run `webi kind@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/kind
<PROJECT-DIR>/kind-config.yaml
```
## Cheat Sheet ## Cheat Sheet
> Kind uses a single docker container to run a lightweight kubernetes cluster > Kind uses a single docker container to run a lightweight kubernetes cluster

View File

@@ -8,16 +8,6 @@ tagline: |
To update or switch versions, run `webi kubectx@stable` (or `@v0.9`, `@beta`, To update or switch versions, run `webi kubectx@stable` (or `@v0.9`, `@beta`,
etc). etc).
### Files
These are the files / directories that are created and/or modified with this
install:
```text
~/.config/envman/PATH.env
~/.local/bin/kubectx
```
## Cheat Sheet ## Cheat Sheet
> `kubectx` kubectx helps you switch between Kubernetes clusters back and forth > `kubectx` kubectx helps you switch between Kubernetes clusters back and forth

View File

@@ -8,16 +8,6 @@ tagline: |
To update or switch versions, run `webi kubens@stable` (or `@v0.9`, `@beta`, To update or switch versions, run `webi kubens@stable` (or `@v0.9`, `@beta`,
etc). etc).
### Files
These are the files / directories that are created and/or modified with this
install:
```text
~/.config/envman/PATH.env
~/.local/bin/kubens
```
## Cheat Sheet ## Cheat Sheet
> `kubens` kubens helps you switch between Kubernetes namespaces > `kubens` kubens helps you switch between Kubernetes namespaces

View File

@@ -3,9 +3,7 @@ set -e
set -u set -u
main() { main() {
webi_download \ webi_download
"${WEBI_PKG_URL}" \
"${WEBI_PKG_PATH}/${WEBI_PKG_FILE}"
( (
cd ~/Downloads/webi cd ~/Downloads/webi

View File

@@ -8,17 +8,6 @@ tagline: |
To update or switch versions, run `webi mutagen@stable` (or `@v2`, `@beta`, To update or switch versions, run `webi mutagen@stable` (or `@v2`, `@beta`,
etc). etc).
### Files
These are the files / directories that are created and/or modified with this
install:
```text
~/.config/envman/PATH.env
~/.local/bin/mutagen
~/.mutagen.yml
```
## Cheat Sheet ## Cheat Sheet
> Mutagen is a new kind of remote development tool that enables your existing > Mutagen is a new kind of remote development tool that enables your existing

View File

@@ -11,16 +11,6 @@ tagline: |
webi myip webi myip
``` ```
### Files
These are the files / directories that are created and/or modified with this
install:
```text
~/.config/envman/PATH.env
~/.local/bin/myip
```
## Cheat Sheet ## Cheat Sheet
`myip` will contact ipify.org to tell you your IP addresses: `myip` will contact ipify.org to tell you your IP addresses:

View File

@@ -1,28 +1,7 @@
#!/usr/bin/env pwsh #!/usr/bin/env pwsh
$ErrorActionPreference = 'stop' & curl.exe -A "$Env:WEBI_UA" -fsSL "$Env:WEBI_HOST/packages/myip/myip.ps1" -o "$Env:USERPROFILE\.local\bin\myip.ps1.part"
Remove-Item -Path "$Env:USERPROFILE\.local\bin\myip.ps1" -Recurse -ErrorAction Ignore
function Install-WebiHostedScript () { & Move-Item "$Env:USERPROFILE\.local\bin\myip.ps1.part" "$Env:USERPROFILE\.local\bin\myip.ps1"
Param( Set-Content -Path "$Env:USERPROFILE\.local\bin\myip.bat" -Value "@echo off`r`npushd %USERPROFILE%`r`npowershell -ExecutionPolicy Bypass .local\bin\myip.ps1 %1`r`npopd"
[string]$Package, & "$Env:USERPROFILE\.local\bin\myip.bat"
[string]$ScriptName
)
$PwshName = "_${ScriptName}.ps1"
$PwshUrl = "${Env:WEBI_HOST}/packages/${Package}/${ScriptName}.ps1"
$PwshPath = "$HOME\.local\bin\${PwshName}"
$OldPath = "$HOME\.local\bin\${ScriptName}.ps1"
$BatPath = "$HOME\.local\bin\${ScriptName}.bat"
$PwshExec = "powershell -ExecutionPolicy Bypass"
$Bat = "@echo off`r`n$PwshExec %USERPROFILE%\.local\bin\${PwshName} %*"
Invoke-DownloadUrl -Force -URL $PwshUrl -Path $PwshPath
Set-Content -Path $BatPath -Value $Bat
Write-Host " Created alias ${BatPath}"
Write-Host " to run ${PwshPath}"
# fix for old installs
Remove-Item -Path $OldPath -Force -ErrorAction Ignore
}
Install-WebiHostedScript -Package "myip" -ScriptName "myip"

View File

@@ -5,9 +5,7 @@ set -u
__init_myip() { __init_myip() {
rm -f "$HOME/.local/bin/myip" rm -f "$HOME/.local/bin/myip"
webi_download \ webi_download "$WEBI_HOST/packages/myip/myip.sh" "$HOME/.local/bin/myip"
"$WEBI_HOST/packages/myip/myip.sh" \
"$HOME/.local/bin/myip"
chmod a+x "$HOME/.local/bin/myip" chmod a+x "$HOME/.local/bin/myip"
"$HOME/.local/bin/myip" "$HOME/.local/bin/myip"

View File

@@ -41,35 +41,6 @@ node -e 'console.log("Hello, World!")'
> Hello, World! > Hello, World!
``` ```
### How to Lint and Fmt Node Code
Just by installing these alone, most code editors (vim, VS Code, etc) can
automatically use them for JavaScript:
```sh
npm install --location=global fixjson@1 jshint@2 prettier@3
```
To run them manually on your code;
- prettier (fmt)
```sh
touch .prettierrc.json .prettierignore
prettier -w '**/*.{md,js,jsx,html}'
```
- jshintrc (lint)
```sh
touch .jshintrc .jshintignore
jhint -c ./.jshintrc *.js */*.js
```
- fixjson \
(turns JavaScript Objects with comments, trailing commas, etc into actual json)
```sh
fixjson -i 2 -w ./package.json
```
To run with **GitHub Actions on PRs** see "Fmt & Lint Automatically" below.
### A Simple Web Server ### A Simple Web Server
`server.js`: `server.js`:
@@ -143,224 +114,3 @@ http.createServer(app).listen(8080, function () {
```sh ```sh
npm start npm start
``` ```
### How to Reverse Proxy to Node
You can use [caddy](../caddy/):
`./Caddyfile`:
```Caddyfile
localhost {
# Reverse Proxy to your Node app's API
handle /api/* {
reverse_proxy localhost:3000
}
# Handle static files directly with Caddy
handle /* {
root * ./public/
file_server
}
}
```
```sh
caddy run --config ./Caddyfile
```
See the [Caddy Cheat Sheet](../caddy/) for more info, such as how to use
`X-SendFile` to .
### How to Allow Node to bind on 80 & 443
You can... but should you?
#### Are You Sure?
Typically you should use [`caddy`](../caddy/) as a _Reverse Proxy_ (see above).
#### Yes
On macOS all programs have permissions to use privileged ports by default.
On Linux there are several ways to add network _capabilities_ for privileged
ports:
1. Use `setcap-netbind`
```sh
webi setcap-netbind
setcap-netbind caddy
```
2. Use `setcap` directly
```sh
my_caddy_path="$( command -v caddy )"
my_caddy_absolute="$( readlink -f "${my_caddy_path}" )"
sudo setcap cap_net_bind_service=+ep "${my_caddy_absolute}"
```
3. Add the `--set-cap-net-bind` option to [`serviceman`](../serviceman/) (see
below)
4. Update the `systemd` config directly:
```sh
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
```
### How to run a Node app as a User Service
Also called "Login Item", "Startup Item" or "User Unit", this is how you run a
Node app as a Non-System (Unprivileged) Service on Mac, Windows, and Linux:
1. Install [`serviceman`](../serviceman/)
```sh
webi serviceman
```
2. Use Serviceman to create a _Launch Agent_ (macOS), _Startup Item_ (Windows),
or _User Unit_ (Linux):
```sh
my_username="$( id -u -n )"
serviceman add --user --name my-node-project -- \
caddy run --config ./Caddyfile --envfile ~/.config/caddy/env
```
3. Manage the service
- On macOS
```sh
# Manage (-w to disable/enable on login)
launchctl unload -w ~/Library/LaunchAgents/my-node-project.plist
launchctl load -w ~/Library/LaunchAgents/my-node-project.plist
# View Logs
tail -f ~/.local/share/my-node-project/var/log/my-node-project.log
```
- On Windows
```sh
# Manage
serviceman stop caddy
serviceman start caddy
# View Logs
type ~/.local/share/my-node-project/var/log/my-node-project.log | more
```
- On Linux
```sh
# Manage
systemctl --user disable my-node-project
systemctl --user stop my-node-project
systemctl --user enable my-node-project
systemctl --user start my-node-project
# View Logs
journalctl --user -xef -u my-node-project
```
### How to run a Node app as a System Service
```sh
pushd ./my-node-project/
my_username="$( id -u -n )"
sudo env PATH="$PATH" \
serviceman add --system --path "$PATH" --cap-net-bind \
--name my-node-project --username "${my_username}" -- \
npm run start
```
#### ... with auto-reload in Dev
```sh
pushd ./my-node-project/
sudo env PATH="$PATH" \
serviceman add --system --path "$PATH" --cap-net-bind \
--name my-node-project --username "$(id -u -n)" -- \
npx -p nodemon@3 -- nodemon ./server.js
```
#### View Logs & Restart
```sh
sudo journalctl -xef -u my-node-project
sudo systemctl restart my-node-project
```
### How to Fmt & Lint Automatically
Here are some useful scripts to have in your `package.json`, and a sample file
to run them with GitHub Actions (Workflows):
```sh
npm run
fmt
lint
bump <major|minor|patch|prerelease>
prepublish # also runs after npm install
```
```sh
# bump
npm pkg set scripts.bump='npm version -m "chore(release): bump to v%s"'
# fmt
npm pkg set scripts.fmt='npm run fixjson && npm run prettier'
npm pkg set scripts.prettier="npx -p prettier@2 -- prettier --write '**/*.{md,js,jsx,json,css,html,vue}'"
npm pkg set scripts.fixjson="npx -p fixjson@1 -- fixjson -i 2 -w '*.json' '*/*.json'"
echo 'node_modules' >> .prettierignore
# lint
npm pkg set scripts.lint='npm run jshint'
npm pkg set scripts.jshint="npx -p jshint@2 -- jshint -c ./.jshintrc ./*.js ./*/*.json"
echo 'node_modules' >> .jshintignore
# prepublish
npm pkg set scripts.prepublish='npm run lint && npm run fmt'
```
To run these automatically for all PRs on GitHub:
`.github/workflows/node.js.yml`:
```sh
name: Node.js CI
on:
push:
branches: ['main']
pull_request:
jobs:
build:
name: "Fmt, Lint, & Test"
runs-on: ubuntu-latest
strategy:
matrix:
node-version:
- 20.x
- latest
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: "Webi: Install 'shfmt' and 'shellcheck', and update PATH"
run: |
#sh ./_scripts/install-ci-deps
echo "${HOME}/.local/bin" >> $GITHUB_PATH
- run: node --version
- run: npm run fmt
- run: npm clean-install
- run: npm run lint
- run: npm run test
```

23
node/install.bat Normal file
View File

@@ -0,0 +1,23 @@
@echo off
setlocal
pushd "%userprofile%" || goto :error
pushd "%userprofile%\.local\opt" || goto :error
powershell $ProgressPreference = 'SilentlyContinue'; Invoke-WebRequest https://nodejs.org/dist/v12.16.2/node-v12.16.2-win-x64.zip -OutFile node-v12.16.2-win-x64.zip || goto :error
rem Windows BSD-tar handles zip. Imagine that.
tar xf node-v12.16.2-win-x64.zip || goto :error
dir
rename node-v12.16.2-win-x64 node-v12.16.2 || goto :error
rmdir node-v12.16.2-win-x64
del node-v12.16.2-win-x64.zip || goto :error
popd || goto :error
rem make npm not act stupid about which node to use... ugh (this should be the default)
.\.local\opt\node-v12.16.2\npm.cmd" --scripts-prepend-node-path=true config set scripts-prepend-node-path true || goto :error
pathman add .local\opt\node-v12.16.2 || goto :error
popd || goto :error
goto :EOF
:error
echo Failed with error #%errorlevel%.
exit /b %errorlevel%

View File

@@ -119,7 +119,6 @@ async function getAllReleases(request) {
let os = osMap[osPart]; let os = osMap[osPart];
let archPart = fileParts[1]; let archPart = fileParts[1];
let arch = archMap[archPart]; let arch = archMap[archPart];
let libc = '';
let pkgPart = fileParts[2]; let pkgPart = fileParts[2];
let pkgs = pkgMap[pkgPart]; let pkgs = pkgMap[pkgPart];
if (!pkgPart) { if (!pkgPart) {
@@ -134,16 +133,13 @@ async function getAllReleases(request) {
if (fileParts[2] === 'musl') { if (fileParts[2] === 'musl') {
extra = '-musl'; extra = '-musl';
muslNative = true; muslNative = true;
libc = 'musl';
} else if (os === 'linux') {
libc = 'gnu';
}
if (osPart === 'osx') {
osPart = 'darwin';
} }
pkgs.forEach(function (pkg) { pkgs.forEach(function (pkg) {
if (osPart === 'osx') {
osPart = 'darwin';
}
let filename = `node-${build.version}-${osPart}-${archPart}${extra}.${pkg}`; let filename = `node-${build.version}-${osPart}-${archPart}${extra}.${pkg}`;
if ('msi' === pkg) { if ('msi' === pkg) {
filename = `node-${build.version}-${archPart}${extra}.${pkg}`; filename = `node-${build.version}-${archPart}${extra}.${pkg}`;
@@ -160,7 +156,7 @@ async function getAllReleases(request) {
arch: arch, arch: arch,
ext: pkg, ext: pkg,
download: downloadUrl, download: downloadUrl,
libc: libc, _musl_native: muslNative,
}; };
all.releases.push(release); all.releases.push(release);

View File

@@ -1,127 +0,0 @@
---
title: ollama
homepage: https://github.com/jmorganca/ollama
tagline: |
ollama is a golang LLM server built for ease of use.
---
To update or switch versions, run `webi ollama@stable` (or `@v0.1.5`, etc).
## Cheat Sheet
> `ollama` is an LLM serving platform written in golang. It makes LLMs built on
> Llama standards easy to run with an API.
To get started quickly with the open source LLM Mistral-7b as an example is two
commands.
1. Open **TWO Terminals**
2. In the **first**, start the `ollama` server
```sh
OLLAMA_ORIGINS='*' OLLAMA_HOST=localhost:11434 ollama serve
```
3. In the **second**, run the `ollama` CLI (using the Mistral-7b model)
```sh
ollama pull mistral
ollama run mistral
```
![](https://user-images.githubusercontent.com/122831/278840139-eb3987b3-aeda-45ee-8cd2-06045c632386.png)
## Table of Contents
- Files
- ChatGPT-style Web UI
- System Notes
- Models to Try
- As a Network API
### Files
These are the files / directories that are created and/or modified with this
install:
```text
~/.config/envman/PATH.env
~/.local/bin/ollama
~/.ollama/models/
```
### How to Use a ChatGPT-style Web Interface
There are [many Ollama UIs][ollama-uis] to choose from, but `ollama-webui` is
easy to start with (and can be built as a static page):
![](https://user-images.githubusercontent.com/122831/278840488-684ffdb9-c527-4671-9072-8f5c5e823437.png)
[ollama-uis]:
https://github.com/jmorganca/ollama/issues/875#issuecomment-1778045758
1. Install [`node`](../node/)
```sh
webi node@lts
source ~/.config/envman/PATH.env
```
2. Clone and enter the `ollama-webui` repo
```sh
git clone https://github.com/ollama-webui/ollama-webui.git ./ollama-webui/
pushd ./ollama-webui/
```
3. Install and start the project
```sh
cp -RPp ./example.env ./.env
npm clean-install
npm run dev
```
4. Enjoy!
- <http://localhost:5173/>
Note: Be sure to run `ollama` with CORS enabled:
```sh
OLLAMA_ORIGINS='*' OLLAMA_HOST=localhost:11434 ollama serve
```
## System Notes
You'll need a fairly modern computer. An Apple M1 Air works great.
- 8GB+ RAM
- 4GB+ Storage
- Models range between 3GB and 30GB+ \
(they can take a while to download, and _several_ seconds to initialize)
### How to Downloads Other Models
See the list at <https://ollama.ai/library>.
For example, we could try `sqlcoder`, or `orca-mini` (because it's small):
```sh
ollama pull sqlcoder
ollama run sqlcoder
```
```sh
ollama pull orca-mini
ollama run orca-mini
```
### How to Use as an API on a Network
If you'd like `ollama` to be accessible beyond `localhost` (`127.0.0.1`):
- set the host to `0.0.0.0`, which makes it accessible to _ALL_ networks
- you may wish to **limit origins**
```sh
# fully open to all
OLLAMA_ORIGINS='*' OLLAMA_HOST=0.0.0.0:11435 ollama serve
# restrict browsers (not APIs) to requests from https://example.com
OLLAMA_ORIGINS='https://example.com' OLLAMA_HOST=0.0.0.0:11435 ollama serve
```
See also:
- API Docs: <https://github.com/jmorganca/ollama/blob/main/docs/api.md>

View File

@@ -1,5 +0,0 @@
#!/usr/bin/env pwsh
Write-Host "Error: ollama doesn't have Windows support yet:"
Write-Host "See https://github.com/jmorganca/ollama/issues/403"
exit 1

View File

@@ -1,22 +0,0 @@
#!/bin/sh
set -e
set -u
__init_ollama() {
##############
# Install ollama #
##############
WEBI_SINGLE=true
pkg_get_current_version() {
# 'ollama --version' has output in this format:
# ollama version 0.1.3
# This trims it down to just the version number:
# 0.1.3
ollama --version 2> /dev/null | head -n 1 | sed 's:^ollama version ::'
}
}
__init_ollama

View File

@@ -1,31 +0,0 @@
'use strict';
var github = require('../_common/github.js');
var owner = 'jmorganca';
var repo = 'ollama';
module.exports = async function (request) {
let all = await github(request, owner, repo);
// TODO why are the 0.0.x releases sorting so high?
let releases = [];
for (let rel of all.releases) {
let isLow = rel.version.startsWith('v0.0.');
if (isLow) {
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);
console.info(JSON.stringify(all));
//console.info(JSON.stringify(all, null, 2));
});
}

View File

@@ -8,16 +8,6 @@ tagline: |
To update or switch versions, run `webi pandoc@stable` (or `@v2.14`, `@beta`, To update or switch versions, run `webi pandoc@stable` (or `@v2.14`, `@beta`,
etc). etc).
### Files
These are the files / directories that are created and/or modified with this
install:
```text
~/.config/envman/PATH.env
~/.local/bin/pandoc
```
## Cheat Sheet ## Cheat Sheet
> Pandoc is a Haskell library for converting from one markup format to another, > Pandoc is a Haskell library for converting from one markup format to another,

View File

@@ -5,61 +5,26 @@ tagline: |
Pathman: cross-platform PATH management for bash, zsh, fish, cmd.exe, and PowerShell. Pathman: cross-platform PATH management for bash, zsh, fish, cmd.exe, and PowerShell.
--- ---
To update or switch versions, run `webi pathman@stable` (or `@v0.5`, `@beta`, ## Updating `pathman`
etc).
```sh
webi pathman
```
## Cheat Sheet ## Cheat Sheet
Manages PATH on various OSes and shells Manages PATH on various OSes and shells
- Mac, Windows, Linux - Mac, Windows, Linux
- POSIX Shell, Bash, Zsh, Fish - Bash, Zsh, Fish
- Command, Powershell - Command, Powershell
```sh
pathman add ~/bin/
```
<pre>
Saved changes to <code>~/.config/envman/PATH.env</code>
Copy, paste, and run the following command:
<strong><em><code>PATH="$HOME/bin:$PATH"</code></em></strong>
(newly opened terminal windows will have the updated PATH)
</pre>
**Windows Users**: use POSIX-style `/` (for paths) and `~` (for
`%USERPROFILE%`) - they'll be adjusted automatically.
## Table of Contents
- Files
- Add, Remove, List, etc
### Files
These are the files / directories that are created and/or modified with this
install:
```text
~/.config/envman/PATH.env
~/.local/bin/pathman
# 'source ~/.config/envman/PATH.env' will be added to
~/.profile
~/.bashrc
~/.zshrc
~/.config/fish/config.fish
```
### Usage
```sh ```sh
pathman help pathman help
``` ```
### Usage
```sh ```sh
pathman add ~/.local/bin pathman add ~/.local/bin
``` ```
@@ -68,6 +33,9 @@ pathman add ~/.local/bin
pathman remove ~/.local/bin pathman remove ~/.local/bin
``` ```
Note: Even on Windows it is best to use Unix-style `/` paths and `~` for
`%USERPROFILE%`.
```sh ```sh
pathman list pathman list
``` ```

View File

@@ -8,17 +8,6 @@ tagline: |
To update or switch versions, run `webi postgres@stable` (or `@v10`, `@beta`, To update or switch versions, run `webi postgres@stable` (or `@v10`, `@beta`,
etc). etc).
### Files
These are the files / directories that are created and/or modified with this
install:
```text
~/.config/envman/PATH.env
~/.local/opt/postgres/
~/.local/share/postgres/var/postgresql.conf
```
## Cheat Sheet ## Cheat Sheet
> Postgres is the all-in-one database for beginners and experts alike. It > Postgres is the all-in-one database for beginners and experts alike. It

View File

@@ -7,26 +7,6 @@ tagline: |
To update or switch versions, run `webi pwsh@stable` (or `@v7.4`, `@beta`, etc). To update or switch versions, run `webi pwsh@stable` (or `@v7.4`, `@beta`, etc).
## Cheat Sheet
> The core benefit of running `pwsh` on Mac or Linux is that you get a way to
> debug Windows scripts without having to boot up Windows.
>
> The core benefit of running `pwsh` on Windows is that it's years ahead of
> pre-installed version.
For example, if you want to create a `curl.exe | powershell` script for Windows
(as we do), it's helpful to be able to do some level of debugging on other
platforms.
## Table of Contents
- Files
- ProTips
- vim
- lint
- fmt
### Files ### Files
These are the files / directories that are created and/or modified with this These are the files / directories that are created and/or modified with this
@@ -35,29 +15,17 @@ install:
```text ```text
~/.config/envman/PATH.env ~/.config/envman/PATH.env
~/.local/opt/pwsh/ ~/.local/opt/pwsh/
~/.local/share/powershell/Modules/ ~/.local/share/powershell/Modules
~/.local/opt/pwsh/Modules/
``` ```
### ProTip: pwsh-essentials ## Cheat Sheet
Friends don't let friends PowerShell without > The core benefit of running `pwsh` on Mac or Linux is that you get a way to
[pwsh-essentials](../pwsh-essentials/): > debug Windows scripts without having to boot up Windows.
- [PSScriptAnalyzer](../psscriptanalyzer/) For example, if you want to create a `curl.exe -A "windows" | powershell` script
- [pwsh-fmt](../pwsh-essentials/) for Windows (as we do), it's helpful to be able to do some level of debugging on
- [pwsh-lint](../pwsh-essentials/) other platforms.
- [pwsh-fix](../pwsh-essentials/)
- [pwsh-run](../pwsh-essentials/)
Plus, important information for anyone **Getting Started with PowerShell**:
- Case-Sensitivity
- Returns vs Pipeline Streams
- Strict, Trace, & Verbose Modes
- The Call Operator "&"
- curl vs curl.exe
- Script Policies & Preferences
### How to Use PowerShell with Vim ### How to Use PowerShell with Vim
@@ -67,21 +35,20 @@ Assuming you have [vim-ale](../vim-ale/) installed - which is included with
See the "Lint & Fmt" section below. See the "Lint & Fmt" section below.
### How to Use PowerShell with VSCode ### How to Lint & Fmt ps1 Files
_VS Code_ should also automatically recognize and use `PSScriptAnalyzer`. You must install `PSScriptAnalyzer`. Then you can use `Invoke-ScriptAnalyzer`
and `Invoke-Formatter`
### How to Lint, Fmt, & Fix ps1 Files
See [pwsh-essentials](../pwsh-essentials/) for more info but, in short:
```sh ```sh
pwsh -Command "Install-Module -Name PSScriptAnalyzer -Scope CurrentUser -AllowClobber" pwsh -Command "Install-Module -Name PSScriptAnalyzer -Scope CurrentUser -AllowClobber"
``` ```
To lint:
```sh ```sh
my_ps1='./my-file.ps1' my_ps1='./my-file.ps1'
pwsh -Command "Invoke-ScriptAnalyzer -Fix -ExcludeRule PSAvoidUsingWriteHost -Path '$my_ps1'" pwsh -Command "Invoke-ScriptAnalyzer -Fix -ExcludeRule PSAvoidUsingWriteHost, PSUseDeclaredVarsMoreThanAssignment -Path \"$my_ps1\""
``` ```
To fmt: To fmt:
@@ -89,7 +56,7 @@ To fmt:
```sh ```sh
my_ps1='./my-file.ps1' my_ps1='./my-file.ps1'
my_text="$( my_text="$(
pwsh -Command "Invoke-Formatter -ScriptDefinition (Get-Content -Path '$my_ps1' -Raw)" pwsh -Command "Invoke-Formatter -ScriptDefinition (Get-Content -Path \"$my_ps1\" -Raw)"
)" )"
printf '%s\n' "${my_text}" > "${my_ps1}" printf '%s\n' "${my_text}" > "${my_ps1}"
``` ```

View File

@@ -1,56 +0,0 @@
#!/usr/bin/env pwsh
#################
# Install pwsh #
#################
# Every package should define these variables
$pkg_cmd_name = "pwsh"
$pkg_dst_cmd = "$HOME\.local\opt\pwsh\${pkg_cmd_name}.exe"
$pkg_dst_bin = "$HOME\.local\opt\pwsh"
$pkg_dst_dir = "$HOME\.local\opt\pwsh"
$pkg_dst = "$pkg_dst_dir"
$pkg_src_cmd = "$HOME\.local\opt\pwsh-v$Env:WEBI_VERSION\${pkg_cmd_name}.exe"
$pkg_src_dir = "$HOME\.local\opt\pwsh-v$Env:WEBI_VERSION"
$pkg_src = "$pkg_src_dir"
New-Item "$HOME\Downloads\webi" -ItemType Directory -Force | Out-Null
$pkg_download = "$HOME\Downloads\webi\$Env:WEBI_PKG_FILE"
# Fetch archive
Invoke-DownloadURL -URL $Env:WEBI_PKG_URL -Path $pkg_download
IF (!(Test-Path -Path "$pkg_src_cmd")) {
Push-Location "$HOME\.local\tmp"
# Remove any leftover tmp cruft
Remove-Item -Path "$pkg_src_dir" -Recurse -ErrorAction Ignore | Out-Null
Remove-Item -Path "$HOME\.local\tmp\pwsh*" -Recurse -ErrorAction Ignore
# Unpack archive file into this temporary directory
# Windows BSD-tar handles zip. Imagine that.
New-Item ".\pwsh-v$Env:WEBI_VERSION" -ItemType Directory -Force | Out-Null
Push-Location ".\pwsh-v$Env:WEBI_VERSION"
Write-Output " Unpacking $pkg_download"
& tar xf "$pkg_download"
Pop-Location
# Settle unpacked archive into place
# TODO if .\pwsh-v$Env:WEBI_VERSION\pwsh*\ exists,
# then the nesting of the archive has changed
# so move either .\pwsh-v$Env:WEBI_VERSION\pwsh*\
# or .\pwsh-v$Env:WEBI_VERSION\ accordingly
Move-Item -Path ".\pwsh-v$Env:WEBI_VERSION" -Destination "$pkg_src_dir"
Write-Output " into ${TPath}${pkg_src_dir}${TReset}"
Pop-Location
}
Write-Output " Copying ${TDim}${pkg_src}${TReset}"
Write-Output " into ${TPath}${pkg_dst}${TReset}"
Remove-Item -Path "$pkg_dst" -Recurse -ErrorAction Ignore | Out-Null
Copy-Item -Path "$pkg_src" -Destination "$pkg_dst" -Recurse
webi_path_add "$pkg_dst_bin"
& "$pkg_dst_cmd" -V

View File

@@ -39,7 +39,6 @@ __init_powershell() {
pkg_done_message() { pkg_done_message() {
echo "Installed 'pwsh' at $pkg_dst" echo "Installed 'pwsh' at $pkg_dst"
pwsh -V
} }
} }

View File

@@ -1,60 +0,0 @@
#!/usr/bin/env pwsh
####################
# Install prettier #
####################
function command-v-silent($cmdname) {
$my_cmd = Get-Command $command -ErrorAction SilentlyContinue
# $my_path = $my_cmd | Select-Object -ExpandProperty Definition
if ($my_cmd) {
return True
}
return False
}
function mktemp-d-t() {
# create random suffix for dirname
$my_bytes = New-Object byte[] 4
$my_rng = [Security.Cryptography.RNGCryptoServiceProvider]::Create()
$my_rng.GetBytes($bytes)
$my_hex_delimited = [BitConverter]::ToString($bytes)
$my_hex = $my_hex_delimited -replace "-", ""
# create random directory
$my_systmpdir = [System.IO.Path]::GetTempPath()
$my_tmpdir = Join-Path "$my_systmpdir" "$my_hex"
New-Item -ItemType Directory -Path "$my_tmpdir"
return "$my_tmpdir"
}
function npm-install-global($pkgname) {
# Fetch npm package manager
Write-Output "Checking for npm..."
if (-Not (command-v-silent("npm"))) {
& "$Env:USERPROFILE\.local\bin\webi-pwsh.ps1" node
}
if (command-v-silent($pkgname)) {
$my_cmd = Get-Command $pkgname
$my_cmd Select-Object -ExpandProperty Definition
Write-Host "Found '$my_cmd'"
return
}
$my_tmpdir = mktemp-d-t()
# npm install works best from a directory with no package.json
Push-Location "$my_tmpdir"
if (command-v-silent("npm")) {
& npm --location-golbal prettier
}
else {
& "$Env:USERPROFILE\.local\opt\node\npm" --location-golbal "$pkgname"
}
Pop-Location
}
npm-install-global prettier

View File

@@ -1,210 +0,0 @@
---
title: PSScriptAnalyzer
homepage: https://github.com/PowerShell/PSScriptAnalyzer
tagline: |
PSScriptAnalyzer is Formatter & Linter for PowerShell.
---
To update or switch versions, run `webi psscriptanalyzer`.
## Cheat Sheet
> It's dangerous to go alone! Take _PSScriptAnalyzer_! \
> (nothing crazy, just the standard fmt & lint tool you'd expect)
You'll probably want [pwsh-essentials](../pwsh-essentials) as well.
### Table of Contents
- Files
- Manual Install
- Format
- Lint
- Vim Config
- Beware the BOM!
- Check Version
### Files
These are the files / directories that are created and/or modified with this
install:
```text
~/.local/share/powershell/Modules/PSScriptAnalyzer/
```
### How to Install PSScriptAnalyzer Manually
It's just a one liner, but... a little harder to remember than the webi version:
```sh
pwsh -Command "Install-Module -Name PSScriptAnalyzer -Scope CurrentUser -AllowClobber"
```
### How to Run the PowerShell Formatter
**`pwsh-fmt`** from ([pwsh-essentials](../pwsh-essentials/)) is the **easiest
way** to run the formatter on a file or directory.
```sh
pwsh-fmt ./script.ps1
```
There is **no built-in one-liner** to do so. You have to do **something like
this instead**:
```pwsh
function Format-File($Path) {
$WasDirty = $false
$Original = Get-Content -Path $Path -Raw
$Formatted = Invoke-Formatter -ScriptDefinition $Original
IF ($Original -eq $Formatted) {
return $WasDirty
}
$WasDirty = $true
# By default Set-Content unconditionally adds an *extra* newline every time
# See <https://stackoverflow.com/a/45266681/151312>
Set-Content -Path $Path $Formatted -Encoding utf8NoBom -NoNewline
return $WasDirty
}
```
```pwsh
$WasDirty = Format-File -Path "./script.ps1"
```
### How to Run the PowerShell Linter
**`pwsh-lint`** from ([pwsh-essentials](../pwsh-essentials/)) is the **easiest
way** to run the linter on a file or directory.
```sh
pwsh-lint ./script.ps1
```
However, this one does have a nice onesliner:
```pwsh
Invoke-ScriptAnalyzer -Path "./script.ps1" -ExcludeRule PSAvoidUsingWriteHost
```
You can also make the output much more readable by using `Format-List`:
```pwsh
$Diags = Invoke-ScriptAnalyzer -Path "./script.ps1" -ExcludeRule PSAvoidUsingWriteHost
Write-Host ($Diags | Format-List | Out-String)
```
### How to Run the PowerShell Fixer
**`pwsh-fix`** from ([pwsh-essentials](../pwsh-essentials/)) is the **easiest
way** to run the fixer on a file or directory.
At first blush the _Fixer_ seems simple - just at `-Fix` to the _Linter_.
However, it's tricky because it will output a Byte-Order-Marker, which
**requires running the _Formatter_** to remove.
That would look something like this:
```pwsh
function Repair-File($Path) {
$WasDirty = $false
$Original = Get-Content -Path $Path -Raw
$Formatted = Invoke-Formatter -ScriptDefinition $Original
IF ($Original -eq $Formatted) {
return $WasDirty
}
$WasDirty = $true
# By default Set-Content unconditionally adds an *extra* newline every time
# See <https://stackoverflow.com/a/45266681/151312>
Set-Content -Path $Path $Formatted -Encoding utf8NoBom -NoNewline
return $WasDirty
}
```
You'll notice this is very similar to the Formatter solution above, with just
these changes:
```diff
- $Formatted = Invoke-Formatter -ScriptDefinition $Original
+
+ Invoke-ScriptAnalyzer -Fix -Path $Path -ExcludeRule PSAvoidUsingWriteHost
+ $Fixed = Get-Content -Path $Path -Raw
+ $Formatted = Invoke-Formatter -ScriptDefinition $Fixed
```
### How to Configure PSScriptAnalyzer
There is no standard config location for projects or globally. \
(not at the time of this writing at least)
Instead you'll need to change the config in your editor.
### How to Configure PSScriptAnalyzer for Vim
1. You'll need to install [vim-ale](../vim-ale/)
```sh
webi vim-ale
```
2. For **Per-User** config, edit `~/.vimrc` and flavor to taste: `~/.vimrc:`
```vim
" PowerShell settings
let g:ale_powershell_psscriptanalyzer_exclusions = "PSAvoidUsingWriteHost"
```
3. For **Per-Project** config you'll need to edit `~/.vimrc` _AND_
`<PROJECT-DIR>/.vimrc`: `~/.vimrc`:
```vim
" Place these 2 lines at the very END of ~/.vimrc
" (this will enable per-directory .vimrc loading)
set secure
set exrc
```
`~/PROJECT-NAME/.vimrc`:
```sh
" PowerShell settings
let g:ale_powershell_psscriptanalyzer_exclusions = "PSAvoidUsingWriteHost"
```
### How to Fix Byte-Order-Marker (BOM)
When you run the _Linter_ with `-Fix` it will sometimes output a UTF
Byte-Order-Marker to the file.
The good news is that the _Formatter_ will remove this.
So always run the formatter after the linter. 🤷‍♂️
```pwsh
$Fixed = Get-Content -Path $Path -Raw
$Formatted = Invoke-Formatter -ScriptDefinition $Fixed
# By default Set-Content unconditionally adds an *extra* newline every time
# See <https://stackoverflow.com/a/45266681/151312>
Set-Content -Path $Path $Formatted -Encoding utf8NoBom -NoNewline
```
## Check the Installed Version
This will output the module and version, or `$null`.
```pwsh
Get-InstalledModule PSScriptAnalyzer `
| Select-Object -Property Name, Version `
| Format-List
```
```text
Name : PSScriptAnalyzer
Version : 1.21.0
```

View File

@@ -1,41 +0,0 @@
#!/usr/bin/env pwsh
function Repair-MissingCommand {
Param(
[string]$Name,
[string]$Package,
[string]$Command
)
Write-Host " Checking for $Name ..."
$HasCommand = Get-Command -Name $Command -ErrorAction Silent
IF ($HasCommand) {
Return
}
& $HOME\.local\bin\webi-pwsh.ps1 $Package
$null = Sync-EnvPath
}
IF ($null -eq $Env:WEBI_HOST -or "" -eq $Env:WEBI_HOST) {
$Env:WEBI_HOST = "https://webinstall.dev"
}
function Install-PSScriptAnalyzer {
# Fetch PowerShell Core
Repair-MissingCommand -Name "PowerShell Core" -Package "pwsh" -Command "pwsh"
$NeedsTrust = pwsh -Command "Get-PSRepository -Name 'PSGallery' | Where-Object -Property InstallationPolicy -eq 'Untrusted'"
IF ($NeedsTrust) {
Write-Host " Trusting PSRepository 'PSGallery' ..."
pwsh -Command "Set-PSRepository -Name 'PSGallery' -InstallationPolicy Trusted"
}
# TODO use arguments array instead
Write-Host " Running 'Install-Module -Name PSScriptAnalyzer' ..."
pwsh -Command "Install-Module -Name PSScriptAnalyzer -Repository PSGallery -Scope CurrentUser -AllowClobber"
pwsh -Command "Get-InstalledModule PSScriptAnalyzer | Select-Object -Property Name, Version"
}
Install-PSScriptAnalyzer

View File

@@ -1,17 +0,0 @@
#!/bin/sh
set -e
set -u
__install_psscriptanalyzer() {
echo "Checking for pwsh (PowerShell Core)..."
if ! command -v pwsh > /dev/null; then
"$HOME/.local/bin/webi" pwsh
export PATH="$HOME/.local/opt/pwsh:$PATH"
pwsh -V
fi
pwsh -Command "Install-Module -Name PSScriptAnalyzer -Scope CurrentUser -AllowClobber"
pwsh -Command 'Get-InstalledModule -Name PSScriptAnalyzer | Select-Object -Property "Name", "Version" | Format-List'
}
__install_psscriptanalyzer

View File

@@ -1,221 +0,0 @@
---
title: pwsh-essentials
homepage: https://webinstall.dev/pwsh-essentials
tagline: |
meta package for our recommended PowerShell plugins and settings
---
To update (replacing the current version) run `webi pwsh-essentials`.
## Cheat Sheet
> The tools you need to write PowerShell effectively.
This meta package will install the full set of plugins and settings we
recommended.
## Table of Contents
- Files
- Mostly Case-Insensitive
- Returns vs Pipeline Streams
- Strict, Trace, & Verbose Modes
- The Call Operator "&"
- curl vs curl.exe
- Script Policies & Preferences
### Files
These are the files / directories that are created and/or modified with this
install:
```text
~/.config/envman/PATH.env
~/.local/bin/pwsh-fix.ps1
~/.local/bin/pwsh-fmt.ps1
~/.local/bin/pwsh-lint.ps1
~/.local/opt/pwsh/
~/.local/share/powershell/Modules/PSScriptAnalyzer/
```
### Things You MUST Know
There are a few key differences to PowerShell from other scripting and
programming languages you may have used in the past.
Knowing these from the start can save you a _tonne_ of headache.
### 0. PowerShell is Case-Insenstive, Usually
Function and Commandlet names, file paths, and boolean operators are all
case-insensitive:
```pwsh
write-host "Hello, World!"
WRITE-HOST "Hello, World!"
Write-Host "HELLO, WORLD!" -eq "hello, world!"
# True
```
However, raw string and byte functions are case sensitive unless
'CurrentCultureIgnoreCase' is used:
```pwsh
"https://webi.sh".StartsWith("HTTPS://")
# False
"https://webi.sh".StartsWith("HTTPS://", 'CurrentCultureIgnoreCase')
# True
```
### 1. PowerShell Doesn't Have Return Values
There's not so much concept of a "return" as a "pipeline stream".
All values which are not assigned to a variable, such as `$Foobar` - or
`$null` - are pipelined.
- There are no _Return Values_
- The `Return` keyword only serves to exit early.
- The result of EVERY command is put into a _Pipeline_
- You can pick which _Pipeline_ a result is written to:
- `... | Write-Output` (the default)
- `... | Write-Debug`
- `... | Write-Verbose`
- `... | Write-Information`
- `... | Write-Warning`
- `... | Write-Error`
- `... | Write-Host` (forced console output, no Pipeline) \
(also, [considered "evil"](https://stackoverflow.com/a/38527767/151312))
- Results captured with a variable DO NOT enter a pipeline
- `$Foobar = ...` (captured to variable, no Pipeline)
- `$null = ...` (no Pipleline) \
(the same as `... | Out-Null`, but easier to read and _much_ faster)
#### Example
```pwsh
function Get-LotsInThePipeline ($Thing1, $Thing2) {
Write-Output "a"
1
IF ($Thing) {
$null = Write-Output "b"
$null = 2
$Thing2
Return
}
Write-Output "c"
3
}
Get-LotsInThePipeline $true 'red'
Get-LotsInThePipeline $false 'blue'
```
There are two possible outputs for this program:
```text
a, 1, "red"
a, 1, c, 3
```
When you need a limited set of pipeline values, you'll need to `$null = xxxx`...
a lot.
See <https://stackoverflow.com/q/29556437/151312>
### 2. Strict, Trace, & Verbose Modes
```pwsh
# set -e
$ErrorActionPreference = "Stop"
# set -x
Set-PSDebug -Trace 2
# DEBUG='true'
$VerbosePreference="Continue"
# if test -n "$DEBUG"; then echo "Debug 123"; fi
Write-Verbose "Debug: 123"
```
### 3. The Call Operator (`&`)
Normally you can run a command the same as a commandlet:
```pwsh
# Built-in Commandlet
Write-Host "Unpacking the tarball..."
# External Command
tar xvf foobar.tar.gz
```
However, if the command is any of:
- a script (particular that will **change its parent's variables**)
- accessed directly by it's path (i.e. it's not in `$Env:Path`)
- in a path with a space
you must use the call operator:
```pwsh
# can't write parent's variables
pwsh -ExecutionPolicy Bypass $HOME\.local\bin\_webi.ps1 xz
# can write parent's variables (same process)
& $HOME\.local\bin\_webi.ps1 xz
# not in PATH, and has space in its path
& ".\Foo Bar\foobar.exe" -baz
```
To use it with multiple arguments:
```pwsh
# this can be just the name, or the full path
$Cmd = 'curl.exe'
# mind the leading ,
$CmdArguments = , '--fail-with-body', '-sS'
& $Cmd $CmdArguments -o example.html 'https://example.com'
```
As you can see, the array of options and arguments is flattened, which makes
constructing commands with complex arrangements much easier than...
<small>posix</small> (but don't tell my friends I said that).
### 4. PowerShell's curl vs Windows' curl.exe
PowerShell has a built-in `curl` which is an alias for the `Invoke-WebRequest`
commandlet.
Never use `curl`. Always use `curl.exe`.
### 5. PowerShell Preferences and Policies
- _Execution Policy_ must be set to allow PowerShell to run scripts:
```pwsh
# from a shell
powershell -ExecutionPolicy Bypass .\foobar.ps1
```
```pwsh
# for a script, itself
Set-ExecutionPolicy Bypass -Scope Process
& .\foobar.ps1
```
- Set _Strict Mode_ (like `set -e` in Bash, etc)
```sh
$ErrorActionPreference = 'Stop'
```
- Eliminate _Progress Bars_ (they _really_ slow Windows down) \
```sh
$ProgressPreference = 'SilentlyContinue'
```
The number of progress created by `Invoke-WebRequest` during a download is so
many that it literally can't download files over a few kilobytes in a
reasonable amount of time unless this is turned off.

View File

@@ -1,65 +0,0 @@
#!/usr/bin/env pwsh
$PkgName = "pwsh-essentials"
function Repair-MissingCommand {
Param(
[string]$Name,
[string]$Package,
[string]$Command
)
Write-Host " Checking for $Name ..."
$HasCommand = Get-Command -Name $Command -ErrorAction Silent
IF ($HasCommand) {
Return
}
& $HOME\.local\bin\webi-pwsh.ps1 $Package
$null = Sync-EnvPath
}
function Install-WebiHostedPSCoreScript () {
Param(
[string]$Package,
[string]$ScriptName
)
$PwshName = "_${ScriptName}.ps1"
$PwshUrl = "${Env:WEBI_HOST}/packages/${Package}/${ScriptName}.ps1"
$PwshPath = "$HOME\.local\bin\${PwshName}"
$OldPath = "$HOME\.local\bin\${ScriptName}.ps1"
$BatPath = "$HOME\.local\bin\${ScriptName}.bat"
$PwshExec = "pwsh -ExecutionPolicy Bypass"
$Bat = "@echo off`r`n$PwshExec %USERPROFILE%\.local\bin\${PwshName} %*"
Invoke-DownloadUrl -Force -URL $PwshUrl -Path $PwshPath
Set-Content -Path $BatPath -Value $Bat
Write-Host " Created alias ${BatPath}"
Write-Host " to run ${PwshPath}"
# fix for old installs
Remove-Item -Path $OldPath -Force -ErrorAction Ignore
}
function Install-PwshEssential {
# Fetch PowerShell Core
Repair-MissingCommand -Name "PowerShell Core" -Package "pwsh" -Command "pwsh"
# Fetch PSScriptAnalyzer (fmt, lint, fix)
& $HOME\.local\bin\webi-pwsh.ps1 psscriptanalyzer
# Fetch shorthand commands to fmt, lint, & fix
$ScriptNames = , "pwsh-fmt", "pwsh-fix", "pwsh-lint"
foreach ($ScriptName in $ScriptNames) {
Write-Host ""
Write-Host "${TTask}Installing${TReset} ${TName}$ScriptName${TReset}"
Install-WebiHostedPSCoreScript -Package $PkgName -ScriptName $ScriptName
}
$PwshRunUrl = "${Env:WEBI_HOST}/packages/${PkgName}/pwsh-run.bat"
$PwshRunPath = "$HOME\.local\bin\pwsh-run.bat"
Invoke-DownloadUrl -Force -URL $PwshRunUrl -Path $PwshRunPath
}
Install-PwshEssential

View File

@@ -1,28 +0,0 @@
#!/bin/sh
set -e
set -u
__run_pwsh_essentials() {
# PowerShell Core
if ! command -v pwsh > /dev/null; then
"$HOME/.local/bin/webi" "pwsh@${WEBI_TAG}"
fi
export PATH="$HOME/.local/opt/pwsh:$PATH"
# PSScriptAnalyzer (Linter, Fixer, & Formatter)
"$HOME/.local/bin/webi" "psscriptanalyzer"
# pwsh-fmt, pwsh-fix, pwsh-lint
for b_file in pwsh-fmt.ps1 pwsh-fix.ps1 pwsh-lint.ps1; do
rm -f ~/.local/bin/"${b_file}"
webi_download \
"${WEBI_HOST}/packages/${PKG_NAME}/${b_file}" \
~/.local/bin/"${b_file}" \
"${b_file}"
chmod a+x ~/.local/bin/"${b_file}"
~/.local/bin/"${b_file}" ~/.local/bin/"${b_file}"
done
echo ""
}
__run_pwsh_essentials

View File

@@ -1,93 +0,0 @@
#!/usr/bin/env pwsh
$ErrorActionPreference = "Stop"
# See
# - <https://superuser.com/q/1264444>
# - <https://stackoverflow.com/a/60572643/151312>
$Esc = [char]27
$Warn = "${Esc}[33m"
$Bold = "${Esc}[1m"
$Dim = "${Esc}[2m"
$ResetWeight = "${Esc}[22m"
$ResetColor = "${Esc}[39m"
$PwshExts = "*.ps1", "*.psm1"
function Repair-All($Root) {
$WereDirty = $false
$Children = Get-ChildItem -Path $Root -File -Recurse -Include $PwshExts
foreach ($Child in $Children) {
$Style = $Dim
$WasDirty = Repair-File -Filepath $Child
IF ($WasDirty) {
$WereDirty = $true
$Style = $Bold
}
$RelPath = [System.IO.Path]::GetRelativePath($Root, $Child)
IF (Test-Path -PathType Leaf -Path $Root) {
$RelPath = $Root
}
Write-Host " ${Style}${RelPath}${ResetWeight}"
}
return $WereDirty
}
function Repair-File {
Param (
[string]$Filepath
)
$WasDirty = $false
$Original = Get-Content -Path $Filepath -Raw
Invoke-ScriptAnalyzer -Fix -Path $Filepath -ExcludeRule PSAvoidUsingWriteHost
$Fixed = Get-Content -Path $Filepath -Raw
$Formatted = Invoke-Formatter -ScriptDefinition $Fixed
IF ($Original -eq $Formatted) {
$WasDirty = $false
return $WasDirty
}
$WasDirty = $true
# By default Set-Content unconditionally adds an *extra* newline every time
# See
# - <https://stackoverflow.com/a/45266681/151312>
# - <https://learn.microsoft.com/powershell/module/microsoft.powershell.management/set-content>
Set-Content -Path $Filepath $Formatted -Encoding utf8NoBom -NoNewline
return $WasDirty
}
function Repair-Recursively($Paths) {
$Dirty = $false
IF ($Paths.Length -lt 1) {
$Paths = , (Get-Location)
}
foreach ($Root in $Paths) {
Write-Host "Fixing ${Root}"
$Entry = Get-Item $Root
IF (-Not ((Test-Path -PathType Container -Path $Entry) -Or (Test-Path -PathType Leaf -Path $Entry))) {
$RelPath = [System.IO.Path]::GetRelativePath($Root, $Entry.FullName)
Write-Host " ${Warn}SKIP${ResetColor} ${RelPath} (${Warn}not a regular file or directory${ResetColor})"
exit 1
}
$WereDirty = Repair-All $Root $Entry
IF ($WereDirty) {
$Dirty = $true
}
}
if ($Dirty) {
exit 1
}
}
Repair-Recursively $Args

View File

@@ -1,90 +0,0 @@
#!/usr/bin/env pwsh
$ErrorActionPreference = "Stop"
# See
# - <https://superuser.com/q/1264444>
# - <https://stackoverflow.com/a/60572643/151312>
$Esc = [char]27
$Warn = "${Esc}[33m"
$Bold = "${Esc}[1m"
$Dim = "${Esc}[2m"
$ResetWeight = "${Esc}[22m"
$ResetColor = "${Esc}[39m"
$PwshExts = "*.ps1", "*.psm1"
function Format-All($Root) {
$WereDirty = $false
$Children = Get-ChildItem -Path $Root -File -Recurse -Include $PwshExts
foreach ($Child in $Children) {
$Style = $Dim
$WasDirty = Format-File -Filepath $Child
IF ($WasDirty) {
$WereDirty = $true
$Style = $Bold
}
$RelPath = [System.IO.Path]::GetRelativePath($Root, $Child)
IF (Test-Path -PathType Leaf -Path $Root) {
$RelPath = $Root
}
Write-Host " ${Style}${RelPath}${ResetWeight}"
}
return $WereDirty
}
function Format-File {
Param (
[string]$Filepath
)
$WasDirty = $false
$Original = Get-Content -Path $Filepath -Raw
$Formatted = Invoke-Formatter -ScriptDefinition $Original
IF ($Original -eq $Formatted) {
$WasDirty = $false
return $WasDirty
}
$WasDirty = $true
# By default Set-Content unconditionally adds an *extra* newline every time
# See
# - <https://stackoverflow.com/a/45266681/151312>
# - <https://learn.microsoft.com/powershell/module/microsoft.powershell.management/set-content>
Set-Content -Path $Filepath $Formatted -Encoding utf8NoBom -NoNewline
return $WasDirty
}
function Format-Recursively($Paths) {
$Dirty = $false
IF ($Paths.Length -lt 1) {
$Paths = , (Get-Location)
}
foreach ($Root in $Paths) {
Write-Host "Formatting ${Root}"
$Entry = Get-Item $Root
IF (-Not ((Test-Path -PathType Container -Path $Entry) -Or (Test-Path -PathType Leaf -Path $Entry))) {
$RelPath = [System.IO.Path]::GetRelativePath($Root, $Entry.FullName)
Write-Host " ${Warn}SKIP${ResetColor} ${RelPath} (${Warn}not a regular file or directory${ResetColor})"
exit 1
}
$WereDirty = Format-All $Root $Entry
IF ($WereDirty) {
$Dirty = $true
}
}
if ($Dirty) {
exit 1
}
}
Format-Recursively $Args

View File

@@ -1,70 +0,0 @@
#!/usr/bin/env pwsh
$ErrorActionPreference = "Stop"
# See
# - <https://superuser.com/q/1264444>
# - <https://stackoverflow.com/a/60572643/151312>
$Esc = [char]27
$Warn = "${Esc}[33m"
$Bold = "${Esc}[1m"
$Dim = "${Esc}[2m"
$ResetWeight = "${Esc}[22m"
$ResetColor = "${Esc}[39m"
$PwshExts = "*.ps1", "*.psm1"
function Debug-All($Root) {
$AreDirty = $false
$Children = Get-ChildItem -Path $Root -File -Recurse -Include $PwshExts
foreach ($Child in $Children) {
$Style = $Dim
$Diags = Invoke-ScriptAnalyzer -Fix -Path $Child -ExcludeRule PSAvoidUsingWriteHost
IF ($Diags.Length -gt 0) {
$AreDirty = $true
$Style = $Bold
}
$RelPath = [System.IO.Path]::GetRelativePath($Root, $Child)
IF (Test-Path -PathType Leaf -Path $Root) {
$RelPath = $Root
}
Write-Host " ${Style}${RelPath}${ResetWeight}"
IF ($Diags.Length -gt 0) {
Write-Host ($Diags | Format-List | Out-String)
}
}
return $AreDirty
}
function Debug-Recursively($Paths) {
$Dirty = $false
IF ($Paths.Length -lt 1) {
$Paths = , (Get-Location)
}
foreach ($Root in $Paths) {
Write-Host "Linting ${Root}"
$Entry = Get-Item $Root
IF (-Not ((Test-Path -PathType Container -Path $Entry) -Or (Test-Path -PathType Leaf -Path $Entry))) {
$RelPath = [System.IO.Path]::GetRelativePath($Root, $Entry.FullName)
Write-Host " ${Warn}SKIP${ResetColor} ${RelPath} (${Warn}not a regular file or directory${ResetColor})"
exit 1
}
$AreDirty = Debug-All $Root $Entry
IF ($AreDirty) {
$Dirty = $true
}
}
if ($Dirty) {
exit 1
}
}
Debug-Recursively $Args

View File

@@ -1,2 +0,0 @@
@echo off
powershell -ExecutionPolicy Bypass %*

View File

@@ -1,8 +0,0 @@
#!/bin/pwsh
Write-Output "'pwsh@$Env:WEBI_TAG' is an alias for 'powershell@$Env:WEBI_VERSION'"
$PwshBootUrl = "$Env:WEBI_HOST/powershell@$Env:WEBI_VERSION"
$PwshBootScript = "$HOME\.local\tmp\install-pwsh-boot.ps1"
Invoke-DownloadUrl -Force -URL $PwshBootUrl -Path $PwshBootScript
powershell -ExecutionPolicy Bypass $PwshBootScript

View File

@@ -7,25 +7,9 @@ tagline: |
To update run `pyenv update`. To update run `pyenv update`.
### Files
These are the files / directories that are created and/or modified with this
install:
```sh
~/.config/envman/PATH.env
~/.pyenv/bin/
~/.pyenv/shims/
# pyenv also loads shell hooks via
~/.bashrc
~/.config/fish/config.fish
~/.zshrc
```
### How to Install pyenv on macOS ### How to Install pyenv on macOS
Install Xcode tools first: Make sure that you already have Xcode tools installed:
```sh ```sh
xcode-select --install xcode-select --install
@@ -41,8 +25,7 @@ sudo apt update
sudo apt install -y build-essential zlib1g-dev libssl-dev sudo apt install -y build-essential zlib1g-dev libssl-dev
# recommended # recommended
sudo apt install -y libreadline-dev libsqlite3-dev \ sudo apt install -y libreadline-dev libbz2-dev libsqlite3-dev libffi-dev
libffi-dev libbz2-dev liblzma-dev
``` ```
## Cheat Sheet ## Cheat Sheet

View File

@@ -17,7 +17,7 @@ __init_pyenv() {
} >> ~/.bashrc } >> ~/.bashrc
fi fi
if command -v zsh > /dev/null; then if [ -n "$(command -v zsh)" ]; then
touch ~/.zshrc touch ~/.zshrc
if ! grep -q 'pyenv init' ~/.zshrc; then if ! grep -q 'pyenv init' ~/.zshrc; then
{ {
@@ -31,7 +31,7 @@ __init_pyenv() {
fi fi
fi fi
if command -v fish > /dev/null; then if [ -n "$(command -v fish)" ]; then
mkdir -p ~/.config/fish mkdir -p ~/.config/fish
touch ~/.config/fish/config.fish touch ~/.config/fish/config.fish
if ! grep -q 'pyenv init' ~/.config/fish/config.fish; then if ! grep -q 'pyenv init' ~/.config/fish/config.fish; then
@@ -45,10 +45,10 @@ __init_pyenv() {
fi fi
mkdir -p ~/.pyenv/bin mkdir -p ~/.pyenv/bin
webi_path_add ~/.pyenv/bin pathman add ~/.pyenv/bin
mkdir -p ~/.pyenv/shims mkdir -p ~/.pyenv/shims
webi_path_add ~/.pyenv/shims pathman add ~/.pyenv/shims
echo "NOTE: You may also need to CLOSE and RE-OPEN your terminal for pyenv to take effect." echo "NOTE: You may also need to CLOSE and RE-OPEN your terminal for pyenv to take effect."
} }

View File

@@ -7,30 +7,16 @@ tagline: |
To update or switch versions, run `pyenv install -v 3` (or `3.10`, etc). To update or switch versions, run `pyenv install -v 3` (or `3.10`, etc).
### Files
These are the files / directories that are created and/or modified with this
install:
```sh
~/.config/envman/PATH.env
~/.pyenv/bin/
~/.pyenv/shims/
# pyenv also loads shell hooks via
~/.bashrc
~/.config/fish/config.fish
~/.zshrc
```
### How to Install python3 on macOS ### How to Install python3 on macOS
Install Xcode tools first: Make sure that you already have Xcode tools installed:
```sh ```sh
xcode-select --install xcode-select --install
``` ```
You may also need to install Xcode proper from the App Store.
### How to Install python3 on Linux ### How to Install python3 on Linux
Make sure that you already have the necessary build tools installed: Make sure that you already have the necessary build tools installed:
@@ -41,8 +27,18 @@ sudo apt update
sudo apt install -y build-essential zlib1g-dev libssl-dev sudo apt install -y build-essential zlib1g-dev libssl-dev
# recommended # recommended
sudo apt install -y libreadline-dev libsqlite3-dev \ sudo apt install -y libreadline-dev libbz2-dev libsqlite3-dev
libffi-dev libbz2-dev liblzma-dev ```
### Files
These are the files / directories that are created and/or modified with this
install:
```text
~/.bashrc (or your shell's equivalent)
~/.config/envman/PATH.env
~/.pyenv
``` ```
## Cheat Sheet ## Cheat Sheet

View File

@@ -7,30 +7,16 @@ tagline: |
To update or switch versions, run `pyenv install -v 2` (or `2.6`, etc). To update or switch versions, run `pyenv install -v 2` (or `2.6`, etc).
### Files
These are the files / directories that are created and/or modified with this
install:
```sh
~/.config/envman/PATH.env
~/.pyenv/bin/
~/.pyenv/shims/
# pyenv also loads shell hooks via
~/.bashrc
~/.config/fish/config.fish
~/.zshrc
```
### How to Install python2 on macOS ### How to Install python2 on macOS
Install Xcode tools first: Make sure that you already have Xcode tools installed:
```sh ```sh
xcode-select --install xcode-select --install
``` ```
You may also need to install Xcode proper from the App Store.
### How to Install python2 on Linux ### How to Install python2 on Linux
Make sure that you already have the necessary build tools installed: Make sure that you already have the necessary build tools installed:
@@ -41,8 +27,18 @@ sudo apt update
sudo apt install -y build-essential zlib1g-dev libssl-dev sudo apt install -y build-essential zlib1g-dev libssl-dev
# recommended # recommended
sudo apt install -y libreadline-dev libsqlite3-dev \ sudo apt install -y libreadline-dev libbz2-dev libsqlite3-dev
libffi-dev libbz2-dev liblzma-dev ```
### Files
These are the files / directories that are created and/or modified with this
install:
```text
~/.bashrc (or your shell's equivalent)
~/.config/envman/PATH.env
~/.pyenv
``` ```
## Cheat Sheet ## Cheat Sheet

View File

@@ -8,4 +8,4 @@ description: |
See https://webinstall.dev/python See https://webinstall.dev/python
--- ---
Alias for [python](../python/). Alias for https://webinstall.dev/python

View File

@@ -1,48 +0,0 @@
---
title: Redis Commander
homepage: https://joeferner.github.io/redis-commander/
tagline: |
Redis-Commander is a node.js web application used to view, edit, and manage a Redis Database.
---
To update or switch versions, run `npm install -g redis-commander@latest`.
### Files
These are the files / directories that are created and/or modified with this
install:
```text
~/.config/envman/PATH.env
~/.local/opt/node/bin/redis-commander
```
If [`node`](../node/) is not found, it will also be installed.
## Cheat Sheet
> Web-UI to display and edit data within multiple different Redis servers. It
> has support for the following data types to view, add, update and delete data:
- Strings
- Lists
- Sets
- Sorted Set
- Streams (Basic support based on HFXBus project from
<https://github.com/exocet-engineering/hfx-bus>, only view/add/delete data)
- ReJSON documents (Basic support, only for viewing values of ReJSON type keys)
List available commands:
```sh
redis-commander --help
```
Start redis commander with default settings:
```sh
redis-commander
```
This will open up web app at <http://127.0.0.1:8081> and will be connected to
local redis server at default port!

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