Skip to content

Commit

Permalink
Automatic version switching in bash
Browse files Browse the repository at this point in the history
  • Loading branch information
jasongin committed Nov 8, 2016
1 parent 3a8cc83 commit b29febb
Show file tree
Hide file tree
Showing 7 changed files with 99 additions and 26 deletions.
4 changes: 4 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/nvs text eol=lf
/nvs.sh text eol=lf
/nvs.cmd text eol=crlf
/nvs.ps1 text eol=crlf
27 changes: 23 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ A Windows Installer (MSI) package is available from the [NVS releases page on Gi

### Mac, Linux
Specify the installation path, clone the repo, and *source* the `install` command:
```sh
```
export NVS_HOME="$HOME/.nvs"
git clone https://github.com/jasongin/nvs "$NVS_HOME"
. "$NVS_HOME/nvs.sh" install
Expand All @@ -40,6 +40,7 @@ Command | Description
`nvs rm <version>` | Remove a node version
`nvs migrate <fromver> [tover]` | Migrate global modules
`nvs use <version>` | Use a node version in the current shell
`nvs auto [on/off]` | Automatically switch based on cwd
`nvs run <ver> <js> [args...]` | Run a script using a node version
`nvs exec <ver> <exe> [args...]` | Run an executable using a node version
`nvs which [version]` | Show the path to a node version binary
Expand Down Expand Up @@ -68,9 +69,10 @@ $ nvs remote
default node
nightly https://nodejs.org/download/nightly/
node https://nodejs.org/dist/
$ nvs lsr nightly
7.0.0-nightly20161006bd0bedb86a ...
$ nvs add nightly/7.0.0-nightly20161006bd0bedb86a
$ nvs lsr nightly 7
7.0.1-nightly2016102527e1749dcb
...
$ nvs add nightly/7.0.1-nightly2016102527e1749dcb
```
## Aliases
An alias refers to a combination of a remote name and a semantic version. (Processor architectures are not aliased.) When setting an alias, the remote name may be omitted, in which case the alias refers to the default remote. An alias may be used in place of a version string in any of the other commands.
Expand All @@ -85,6 +87,23 @@ $ nvs which myalias
$ nvs which myalias/32
~/.nvs/node/6.7.0/x86/bin/node
```
## Automatic switching per directory
In either Bash or PowerShell, NVS can automatically switch the node version in the current shell as you change directories. This function is disabled by default; to enable it run `nvs auto on`. Afterward, whenver you `cd` or `pushd` under a directory containing a `.node-version` file then NVS will automatically switch the node version accordingly, downloading a new version if necessary. When you `cd` out to a directory with no `.node-version` file anywhere above it, then the default (linked) version is restored, if any.
```
~$ nvs link 6.9.1
~/.nvs/default -> ~/.nvs/node/6.9.1/x64
~$ nvs use
PATH += ~/.nvs/default/bin
~$ nvs auto on
~$ cd myproject
PATH -= ~/.nvs/default/bin
PATH += ~/.nvs/node/4.6.1/x64/bin
~/myproject$ cd ..
PATH -= ~/.nvs/node/4.6.1/x64/bin
PATH += ~/.nvs/default/bin
```
*This feature is not available in Windows Command Prompt. Use PowerShell instead.*

# How it works

## Bootstrapping node
Expand Down
55 changes: 37 additions & 18 deletions lib/auto.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,25 +69,44 @@ function enableAutoSwitch(enable) {
}

let psScriptFile = path.join(path.resolve(__dirname, '..'), 'nvs.ps1');
// let shScriptFile = path.join(path.resolve(__dirname, '..'), 'nvs.sh');

require('./postScript').generate(null, {
'.PS1': [
// Patch the function that is invoked every time PowerShell shows a prompt.
// Export the function from the script using a dynamic module; this
// does NOT require the script to be sourced.
'if (-not $env:NVS_ORIGINAL_PROMPT) { ',
' $env:NVS_ORIGINAL_PROMPT = $(Get-Content function:\\prompt)',
'}',
'New-Module -Script {',
enable
? 'function prompt { . "' + psScriptFile + '" "prompt" }'
: 'function prompt { Invoke-Expression $env:NVS_ORIGINAL_PROMPT }',
'Export-ModuleMember -Function prompt',
'} > $null',
],
'.SH': null, // TODO: define cd(), pushd(), popd() functions
});
if (enable) {
require('./postScript').generate(null, {
'.PS1': [
// Patch the function that is invoked every time PowerShell shows a prompt.
// Export the function from the script using a dynamic module; this
// does NOT require the script to be sourced.
'if (-not $env:NVS_ORIGINAL_PROMPT) { ',
' $env:NVS_ORIGINAL_PROMPT = $(Get-Content function:\\prompt)',
'}',
'New-Module -Script {',
' function prompt { . "' + psScriptFile + '" "prompt" }',
' Export-ModuleMember -Function prompt',
'} > $null',
],
'.SH': [
'cd () { builtin cd "$@" && nvs cd; }',
'pushd () { builtin pushd "$@" && nvs cd; }',
'popd () { builtin popd "$@" && nvs cd; }',
],
});
} else {
require('./postScript').generate(null, {
'.PS1': [
'if ($env:NVS_ORIGINAL_PROMPT) { ',
' New-Module -Script {',
' function prompt { Invoke-Expression $env:NVS_ORIGINAL_PROMPT }',
' Export-ModuleMember -Function prompt',
' } > $null',
'}',
],
'.SH': [
'cd () { builtin cd "$@"; }',
'pushd () { builtin pushd "$@"; }',
'popd () { builtin popd "$@"; }',
],
});
}
}

module.exports = {
Expand Down
3 changes: 3 additions & 0 deletions lib/install.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const Error = require('./error');
let nvsUse = require('./use');
let nvsLink = require('./link');
let nvsPostScript = require('./postScript');
let nvsAuto = require('./auto');

const isWindows = process.platform === 'win32';

Expand Down Expand Up @@ -41,6 +42,8 @@ function install() {

function uninstall() {
let result = [];

nvsAuto.enableAutoSwitch(false);
result = result.concat(nvsUse.use(null));
result = result.concat(nvsLink.unlink());

Expand Down
4 changes: 3 additions & 1 deletion lib/postScript.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ const os = require('os');

const canUpdateEnv = !process.env['NVS_EXECUTE'];

// Lines may be accumulated across multiple calls to generate().
let postScriptLines = [];

/**
* Generates a shell script to be invoked after the main (Node.js) script finishes.
* The NVS shim shell scripts take care of invoking the generated script and
Expand All @@ -18,7 +21,6 @@ function generate(exportVars, additionalLines) {
}

let envVars = Object.keys(exportVars || {});
let postScriptLines = [];
let postScriptFile = process.env['NVS_POSTSCRIPT'];
if (!postScriptFile) {
throw new Error('NVS_POSTSCRIPT environment variable not set.');
Expand Down
3 changes: 3 additions & 0 deletions lib/use.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,9 @@ function use(version, skipUpdateShellEnv) {
version = null;
} else {
pathEntries.splice(i--, 1);
if (!isWindows) {
pathEntry = path.join(pathEntry, 'bin');
}
result.push('PATH -= ' + homePath(pathEntry));
saveChanges = true;
}
Expand Down
29 changes: 26 additions & 3 deletions nvs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,32 @@ nvs() {
echo ""
fi

# Forward args to the main JavaScript file.
command "${BOOTSTRAP_NODE_PATH}" "${NVS_ROOT}/lib/main.js" "$@"
local EXIT_CODE=$?
local EXIT_CODE

# Check if invoked as a CD function that enables auto-switching.
if [[ "$@" == "cd" ]]; then
# Find the nearest .node-version file in current or parent directories
local DIR=$PWD
while [[ "$DIR" != "" && ! -e "$DIR/.node-version" ]]; do
if [[ "$DIR" == "/" ]]; then
DIR=
else
DIR=$(dirname "$DIR")
fi
done

# If it's different from the last auto-switched directory, then switch.
if [[ "$DIR" != "$NVS_AUTO_DIRECTORY" ]]; then
command "${BOOTSTRAP_NODE_PATH}" "${NVS_ROOT}/lib/main.js" auto
EXIT_CODE=$?
fi

export NVS_AUTO_DIRECTORY=$DIR
else
# Forward args to the main JavaScript file.
command "${BOOTSTRAP_NODE_PATH}" "${NVS_ROOT}/lib/main.js" "$@"
EXIT_CODE=$?
fi

# Call the post-invocation script if it is present, then delete it.
# This allows the invocation to potentially modify the caller's environment (e.g. PATH)
Expand Down

0 comments on commit b29febb

Please sign in to comment.