From 3478e8691986837c3c2fdc63fd3b68ce1e8f7ca3 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Fri, 9 Aug 2019 02:35:26 -0600 Subject: [PATCH] publish pathman to npm --- npm/.gitignore | 1 + npm/README.md | 41 +++++ npm/bin/pathman | 1 + npm/package-lock.json | 18 +++ npm/package.json | 39 +++++ npm/scripts/fetch-prebuilt.js | 288 ++++++++++++++++++++++++++++++++++ 6 files changed, 388 insertions(+) create mode 100644 npm/.gitignore create mode 100644 npm/README.md create mode 100644 npm/bin/pathman create mode 100644 npm/package-lock.json create mode 100644 npm/package.json create mode 100755 npm/scripts/fetch-prebuilt.js diff --git a/npm/.gitignore b/npm/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/npm/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/npm/README.md b/npm/README.md new file mode 100644 index 0000000..abac7a8 --- /dev/null +++ b/npm/README.md @@ -0,0 +1,41 @@ +# pathman + +A cross-platform PATH manager + +Manage PATH on Windows, Mac, and Linux with various Shells + +```bash +pathman list +pathman add ~/.local/bin +pathman remove ~/.local/bin +pathman version +pathman help +``` + +Windows: stores PATH in the registry. + +Mac & Linux: stores PATH in `~/.config/envman/PATH.sh` + +## Meta Package + +This is a meta-package to fetch and install the correction version of +[go-pathman](https://git.rootprojects.org/root/pathman) +for your architecture and platform. + +```bash +npm install -g @root/pathman +``` + +# Supported Shells + +In theory, anything with bourne-compatible exports. Specifically: + +- bash +- zsh +- fish + +On Windows, all shells inherit from the registry. + +- cmd.exe +- PowerShell +- Git Bash diff --git a/npm/bin/pathman b/npm/bin/pathman new file mode 100644 index 0000000..717a02c --- /dev/null +++ b/npm/bin/pathman @@ -0,0 +1 @@ +# this will be replaced by the postinstall script diff --git a/npm/package-lock.json b/npm/package-lock.json new file mode 100644 index 0000000..0c2a4ec --- /dev/null +++ b/npm/package-lock.json @@ -0,0 +1,18 @@ +{ + "name": "@root/pathman", + "version": "0.5.2-alpha.5", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@root/mkdirp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@root/mkdirp/-/mkdirp-1.0.0.tgz", + "integrity": "sha512-hxGAYUx5029VggfG+U9naAhQkoMSXtOeXtbql97m3Hi6/sQSRL/4khKZPyOF6w11glyCOU38WCNLu9nUcSjOfA==" + }, + "@root/request": { + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/@root/request/-/request-1.3.11.tgz", + "integrity": "sha512-3a4Eeghcjsfe6zh7EJ+ni1l8OK9Fz2wL1OjP4UCa0YdvtH39kdXB9RGWuzyNv7dZi0+Ffkc83KfH0WbPMiuJFw==" + } + } +} diff --git a/npm/package.json b/npm/package.json new file mode 100644 index 0000000..16efc79 --- /dev/null +++ b/npm/package.json @@ -0,0 +1,39 @@ +{ + "name": "@root/pathman", + "version": "0.5.2", + "description": "A cross-platform PATH manager", + "main": "index.js", + "homepage": "https://git.rootprojects.org/root/pathman/src/branch/master/npm", + "files": [ + "bin/", + "scripts/" + ], + "bin": { + "pathman": "bin/pathman" + }, + "scripts": { + "pathman": "pathman", + "postinstall": "node scripts/fetch-prebuilt.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "https://git.rootprojects.org/root/pathman.git" + }, + "keywords": [ + "launchd", + "systemd", + "winsvc", + "launchctl", + "systemctl", + "HKEY_CURRENT_USER", + "HKCU", + "Run" + ], + "author": "AJ ONeal (https://coolaj86.com/)", + "license": "MPL-2.0", + "dependencies": { + "@root/mkdirp": "^1.0.0", + "@root/request": "^1.3.11" + } +} diff --git a/npm/scripts/fetch-prebuilt.js b/npm/scripts/fetch-prebuilt.js new file mode 100755 index 0000000..1b3684c --- /dev/null +++ b/npm/scripts/fetch-prebuilt.js @@ -0,0 +1,288 @@ +#!/usr/bin/env node + +'use strict'; +var path = require('path'); +var os = require('os'); + +// https://nodejs.org/api/os.html#os_os_arch +// 'arm', 'arm64', 'ia32', 'mips', 'mipsel', 'ppc', 'ppc64', 's390', 's390x', 'x32', and 'x64' +var arch = os.arch(); // process.arch + +// https://nodejs.org/api/os.html#os_os_platform +// 'aix', 'darwin', 'freebsd', 'linux', 'openbsd', 'sunos', 'win32' +var platform = os.platform(); // process.platform +var ext = /^win/i.test(platform) ? '.exe' : ''; + +// This is _probably_ right. It's good enough for us +// https://github.com/nodejs/node/issues/13629 +if ('arm' === arch) { + arch += 'v' + process.config.variables.arm_version; +} + +var map = { + // arches + armv6: 'armv6', + armv7: 'armv7', + arm64: 'armv8', + ia32: '386', + x32: '386', + x64: 'amd64', + // platforms + darwin: 'darwin', + linux: 'linux', + win32: 'windows' +}; + +arch = map[arch]; +platform = map[platform]; + +if (!arch || !platform) { + console.error( + "'" + os.platform() + "' on '" + os.arch() + "' isn't supported yet." + ); + console.error( + 'Please open an issue at https://git.rootprojects.org/root/pathman/issues' + ); + process.exit(1); +} + +var pkg = require('../package.json'); +var newVer = pkg.version; +var fs = require('fs'); +var exec = require('child_process').exec; +var request = require('@root/request'); +var mkdirp = require('@root/mkdirp'); + +function needsUpdate(oldVer, newVer) { + // "v1.0.0-pre" is BEHIND "v1.0.0" + newVer = newVer + .replace(/^v/, '') + .split(/[\.\-\+]/) + .filter(Boolean); + oldVer = oldVer + .replace(/^v/, '') + .split(/[\.\-\+]/) + .filter(Boolean); + + if (!oldVer.length) { + return true; + } + + // ex: v1.0.0-pre vs v1.0.0 + if (newVer[3] && !oldVer[3]) { + // don't install beta over stable + return false; + } + + // ex: old is v1.0.0-pre + if (oldVer[3]) { + if (oldVer[2] > 0) { + oldVer[2] -= 1; + } else if (oldVer[1] > 0) { + oldVer[2] = 999; + oldVer[1] -= 1; + } else if (oldVer[0] > 0) { + oldVer[2] = 999; + oldVer[1] = 999; + oldVer[0] -= 1; + } else { + // v0.0.0 + return true; + } + } + + // ex: v1.0.1 vs v1.0.0-pre + if (newVer[3]) { + if (newVer[2] > 0) { + newVer[2] -= 1; + } else if (newVer[1] > 0) { + newVer[2] = 999; + newVer[1] -= 1; + } else if (newVer[0] > 0) { + newVer[2] = 999; + newVer[1] = 999; + newVer[0] -= 1; + } else { + // v0.0.0 + return false; + } + } + + // ex: v1.0.1 vs v1.0.0 + if (oldVer[0] > newVer[0]) { + return false; + } else if (oldVer[0] < newVer[0]) { + return true; + } else if (oldVer[1] > newVer[1]) { + return false; + } else if (oldVer[1] < newVer[1]) { + return true; + } else if (oldVer[2] > newVer[2]) { + return false; + } else if (oldVer[2] < newVer[2]) { + return true; + } else if (!oldVer[3] && newVer[3]) { + return false; + } else if (oldVer[3] && !newVer[3]) { + return true; + } else { + return false; + } +} + +/* +// Same version +console.log(false === needsUpdate('0.5.0', '0.5.0')); +// No previous version +console.log(true === needsUpdate('', '0.5.1')); +// The new version is slightly newer +console.log(true === needsUpdate('0.5.0', '0.5.1')); +console.log(true === needsUpdate('0.4.999-pre1', '0.5.0-pre1')); +// The new version is slightly older +console.log(false === needsUpdate('0.5.0', '0.5.0-pre1')); +console.log(false === needsUpdate('0.5.1', '0.5.0')); +*/ + +function install(name, bindirs, getVersion, parseVersion, urlTpl) { + exec(getVersion, { windowsHide: true }, function(err, stdout) { + var oldVer = parseVersion(stdout); + //console.log('old:', oldVer, 'new:', newVer); + if (!needsUpdate(oldVer, newVer)) { + console.info( + 'Current ' + name + ' version is new enough:', + oldVer, + newVer + ); + return; + //} else { + // console.info('Current version is older:', oldVer, newVer); + } + + var url = urlTpl + .replace(/{{ .Version }}/g, newVer) + .replace(/{{ .Platform }}/g, platform) + .replace(/{{ .Arch }}/g, arch) + .replace(/{{ .Ext }}/g, ext); + + console.info('Installing from', url); + return request({ uri: url, encoding: null }, function(err, resp) { + if (err) { + console.error(err); + return; + } + + //console.log(resp.body.byteLength); + //console.log(typeof resp.body); + var bin = name + ext; + function next() { + if (!bindirs.length) { + return; + } + var bindir = bindirs.pop(); + return mkdirp(bindir, function(err) { + if (err) { + console.error(err); + return; + } + + var localbin = path.join(bindir, bin); + return fs.writeFile(localbin, resp.body, function(err) { + next(); + if (err) { + console.error(err); + return; + } + fs.chmodSync(localbin, parseInt('0755', 8)); + console.info('Wrote', bin, 'to', bindir); + }); + }); + } + next(); + }); + }); +} + +function winstall(pkgname, name, bindir) { + var dd = /\//.test(pkgname) ? '../' : ''; + var pkgpath = pkgname.replace(/@/g, '\\@'); + try { + fs.writeFileSync( + path.join(bindir, name), + [ + '#!/usr/bin/env bash', + '"$(dirname "$0")/' + name + '.exe" "$@"', + 'exit $?' + ].join('\n') + ); + } catch (e) { + // ignore + } + + // because bugs in npm + git bash oddities, of course + // https://npm.community/t/globally-installed-package-does-not-execute-in-git-bash-on-windows/9394 + try { + console.log( + 'try local path', + path.join(__dirname, dd + '../../.bin', name) + ); + fs.writeFileSync( + path.join(__dirname, dd + '../../.bin', name), + [ + '#!/bin/sh', + '# manual bugfix patch for npm on windows', + 'basedir=$(dirname "$(echo "$0" | sed -e \'s,\\\\,/,g\')")', + '"$basedir/../' + pkgpath + '/bin/' + name + '" "$@"', + 'exit $?' + ].join('\n') + ); + } catch (e) { + // ignore + } + try { + console.log( + 'try global path', + path.join(__dirname, dd + '../../..', name) + ); + fs.writeFileSync( + path.join(__dirname, dd + '../../..', name), + [ + '#!/bin/sh', + '# manual bugfix patch for npm on windows', + 'basedir=$(dirname "$(echo "$0" | sed -e \'s,\\\\,/,g\')")', + '"$basedir/node_modules/' + + pkgname + + '/bin/' + + name + + '" "$@"', + 'exit $?' + ].join('\n') + ); + } catch (e) { + // ignore + } + // end bugfix +} + +function run() { + //var homedir = require('os').homedir(); + //var bindir = path.join(homedir, '.local', 'bin'); + var bindir = path.resolve(__dirname, '..', 'bin'); + var name = pkg.name.replace(/.*\//, ''); + if ('.exe' === ext) { + winstall(pkg.name, name, bindir); + } + + return install( + name, + [bindir], + 'pathman version', + function parseVersion(stdout) { + return (stdout || '').split(' ')[0]; + }, + 'https://rootprojects.org/pathman/dist/{{ .Platform }}/{{ .Arch }}/pathman{{ .Ext }}' + ); +} + +if (require.main === module) { + run(); +}