Jekyll2020-02-26T21:46:14+00:00https://cspotcode.com/feed.xmlcspotcode - Andrew BradleyPersonal site, blogs about programming, and open-source projects.Andrew Bradley[email protected]https://cspotcode.comConfiguring a basic TypeScript composite project2020-02-26T00:00:00+00:002020-02-26T00:00:00+00:00https://cspotcode.com/posts/tsconfig-composite-configuration<p>Sometimes I forget the cleanest way to configure a tsconfig composite project with separate tsconfigs for src and test. Here’s the latest template that I use. <code class="language-plaintext highlighter-rouge">tsc --showConfig</code> is helpful to validate that it’s doing the right thing.</p> <p>This configuration is pretty straightforward:</p> <ul> <li>source in <code class="language-plaintext highlighter-rouge">src</code> is emitted to <code class="language-plaintext highlighter-rouge">dist</code></li> <li>tests in <code class="language-plaintext highlighter-rouge">test</code> will be run via <code class="language-plaintext highlighter-rouge">ts-node</code> or similar, so they do not need to be emitted.</li> <li>anything else, such as ts-node dev scripts or an <code class="language-plaintext highlighter-rouge">./examples</code> directory, is handled by the root tsconfig file, and is not compiled.</li> </ul> <h3 id="tsconfigjson">tsconfig.json</h3> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{ // If in a monorepo, extend from the base config "extends": "../../tsconfig.json", "references": [ { "path": "src" }, { "path": "test" } ], // Exclude references and emitted output "exclude": ["src", "test", "dist"], "compilerOptions": { "composite": true, "rootDir": ".", // We can't pass "noEmit" in incremental mode, but this is close enough. "outDir": ".tmp/tsc/root", "emitDeclarationOnly": true } } </code></pre></div></div> <h3 id="srctsconfigjson">src/tsconfig.json</h3> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{ "extends": "../tsconfig.json", "exclude": [], "compilerOptions": { "rootDir": ".", "outDir": "../dist", "emitDeclarationOnly": false } } </code></pre></div></div> <h3 id="testtsconfigjson">test/tsconfig.json</h3> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{ "extends": "../tsconfig.json", "references": [ { "path": "../src" } ], "exclude": [], "compilerOptions": { "rootDir": ".", "outDir": "../.tmp/tsc/test" } } </code></pre></div></div>Andrew Bradley[email protected]https://cspotcode.comSometimes I forget the cleanest way to configure a tsconfig composite project with separate tsconfigs for src and test. Here’s the latest template that I use. tsc --showConfig is helpful to validate that it’s doing the right thing.Emulate JavaScript’s proposed pipeline operator with a helper function2020-02-12T00:00:00+00:002020-02-12T00:00:00+00:00https://cspotcode.com/posts/emulate-js-pipeline-operator-with-helper<p>JavaScript’s proposed pipeline operator has been stuck at stage 1 for a long time, and I’m skeptical it’ll ever be added to the language.</p> <p>It’s easy to emulate with a helper function. You have to type a few extra characters, but once the code’s written, it’s very readable and gets the job done.</p> <p><a href="https://www.typescriptlang.org/play/?ssl=1&amp;ssc=1&amp;pln=25&amp;pc=12#code/JYOwLgpgTgZghgYwgAgArAA7QDwBUB8yA3gLABQyly2ASvgBQIBGAXMvQG5u4CUyAvIRo826LFFr4A3OSrIOcADYBXCNxlkAvuRjKQCMMAD2IZBkwR6ItBYl6AJhBigI96Tr0Hjp81jwMFFTVkXlFbfw1dfUMTMwtOJVUAfjY4EABPazT04lkqKK9YhAALOFBGVmRsvlIKOSooCDBlKB945gSgnh4NOW06yhKykAA6QNUBeUSIXoamltMh0CqAZyqMjX6PaO8q+3t6ODYQZQBbJmgAGmRKk-PoGrzKRubWquQAahvN8kcERTgjWQBRiphWyiYYCgiDAh2OZwuUGutwRD1yA2QLwW7wAtN9yFsyAgTCswJjJr5LDwnuwAPoCQhwfb0ABM1wAzN0aZwGchwZDoQZONcWVyMWNplIgA">Playground link</a></p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>interface Pipeline&lt;T&gt; { &lt;R&gt;(cb: (v: T) =&gt; R): Pipeline&lt;R&gt;; value: T; } function pipe(): Pipeline&lt;undefined&gt;; function pipe&lt;T&gt;(value: T): Pipeline&lt;T&gt;; function pipe(value?: any): any { function chain(cb: any) { return pipe(cb(value)); } chain.value = value; return chain as any; } function add(a: number, b: number) { return a + b; } declare function subtract(a: number, b: number) { return a - b; } const r = pipe() (_ =&gt; add(2, 3)) (v =&gt; subtract(v, 2)) .value; </code></pre></div></div>Andrew Bradley[email protected]https://cspotcode.comJavaScript’s proposed pipeline operator has been stuck at stage 1 for a long time, and I’m skeptical it’ll ever be added to the language.Implement bash profile functions as external executables2020-01-29T00:00:00+00:002020-01-29T00:00:00+00:00https://cspotcode.com/posts/send-variables-to-bash-session<p>Sometimes a shell helper needs to set variables in your shell session, for example to modify your PATH. An external executable can’t do this, so these functions need to live inside your bash shell. Typically this means writing the functions in bash (gross) or dot-sourcing a large, external chunk of bash, which slows shell startup.</p> <p>One way around this is to write the helper function in a real language and put a tiny adapter in your shell profile.</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># In your shell profile</span> add-env-to-path<span class="o">()</span> <span class="o">{</span> <span class="o">{</span> <span class="nb">eval</span> <span class="s2">"</span><span class="si">$(</span> ~/scripts/add-env-to-path.ts <span class="s2">"</span><span class="nv">$@</span><span class="s2">"</span> 3&gt;&amp;1 1&gt;&amp;4 <span class="si">)</span><span class="s2">"</span> <span class="p">;</span> <span class="o">}</span> 4&gt;&amp;1 <span class="o">}</span> </code></pre></div></div> <p>This adapter will call an external executable but capture instructions from file descriptor 3. It’ll then eval them in the shell. The external executable can, at its discretion, write valid shell syntax to file descriptor 3, for example to modify the PATH.</p> <div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#!/usr/bin/env ts-script </span><span class="c1">// ~/scripts/add-env-to-path.ts</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Interact via stdout and stdin because those FDs are passed verbatim.</span><span class="dl">'</span><span class="p">);</span> <span class="kd">const</span> <span class="nx">environment</span> <span class="o">=</span> <span class="nx">prompt</span><span class="p">(</span><span class="dl">'</span><span class="s1">Which environment should be added to PATH?</span><span class="dl">'</span><span class="p">);</span> <span class="c1">// pseudocode, but you get the idea</span> <span class="nx">fs</span><span class="p">.</span><span class="nx">writeSync</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="s2">` PATH=</span><span class="p">${</span> <span class="nx">shellEscape</span><span class="p">(</span><span class="s2">`</span><span class="p">${</span> <span class="nx">Path</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="nx">os</span><span class="p">.</span><span class="nx">homedir</span><span class="p">(),</span> <span class="dl">'</span><span class="s1">.envs</span><span class="dl">'</span><span class="p">,</span> <span class="nx">environment</span><span class="p">)</span> <span class="p">}</span><span class="s2">:</span><span class="p">${</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">PATH</span> <span class="p">}</span><span class="s2">`</span><span class="p">)</span> <span class="p">}</span><span class="s2"> `</span><span class="p">);</span> </code></pre></div></div> <p>The magic is the switcheroo between file descriptors 3 and 1. Bash <code class="language-plaintext highlighter-rouge">$()</code> syntax passes a new stdout file descriptor, whose output is captured, to the child process. Instead, we want to capture the child process’s output to file descriptor 3, passing the original stdin, stdout, and stderr as file descriptors 0-2.</p>Andrew Bradley[email protected]https://cspotcode.comSometimes a shell helper needs to set variables in your shell session, for example to modify your PATH. An external executable can’t do this, so these functions need to live inside your bash shell. Typically this means writing the functions in bash (gross) or dot-sourcing a large, external chunk of bash, which slows shell startup.Enforce comprehensive keyof array in Typescript2019-06-24T00:00:00+00:002019-06-24T00:00:00+00:00https://cspotcode.com/posts/enforce-comprehensive-keyof-array-in-typescript<p>I had a situation where I wanted to ensure an array of <code class="language-plaintext highlighter-rouge">keyof T</code> included <em>all</em> keys of that interface. This could be useful in situations where you need to keep some runtime type information in sync with a compile-time interface.</p> <p>Here’s the exact code in question, but this trick could be applied to other situations. In this situation, I want to normalize a <code class="language-plaintext highlighter-rouge">notifier</code> object. It may or may not contain any number of callbacks; I need to replace all the missing ones with <code class="language-plaintext highlighter-rouge">noop</code> functions. Otherwise I’ll get runtime errors when I try to fire those callbacks elsewhere.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>function createNotifier&lt;T, Key extends keyof T&gt;(n: T | undefined): never; function createNotifier&lt;T, Key extends keyof T&gt;(n: T | undefined, ...keys: Array&lt;Key&gt;): keyof T extends Key ? Required&lt;NonNullable&lt;T&gt;&gt; : never; function createNotifier(n: any, ...keys: any) { const n2 = n ? n : {} as any; for(const key of keys) { if(!n2[key]) n2[key] = noop as any; } return n2 as any; } </code></pre></div></div> <p>The function accepts an array of <code class="language-plaintext highlighter-rouge">keyof T</code>. To <em>prove</em> that this array is comprehensive (doesn’t omit any properties) I use a conditional type in the return. Basically I flip the extends backwards: <code class="language-plaintext highlighter-rouge">keyof T extends Key</code> instead of <code class="language-plaintext highlighter-rouge">Key extends keyof T</code>. If that check fails, the function returns <code class="language-plaintext highlighter-rouge">never</code> which is sure to show up as an error elsewhere in my code. I suppose I could also return <code class="language-plaintext highlighter-rouge">"error": "HELPFUL ERROR MESSAGE"}</code></p> <p>Multiple function signatures are necessary to account for the case where zero <code class="language-plaintext highlighter-rouge">keys</code> are passed to the function. In that case, inference sets <code class="language-plaintext highlighter-rouge">Key</code> equal to <code class="language-plaintext highlighter-rouge">keyof T</code>, which allows our validation in the second function signature to erroneously pass. We avoid that by adding the first function signature. If zero <code class="language-plaintext highlighter-rouge">keys</code> are passed, the function returns <code class="language-plaintext highlighter-rouge">never</code>.</p>Andrew Bradley[email protected]https://cspotcode.comI had a situation where I wanted to ensure an array of keyof T included all keys of that interface. This could be useful in situations where you need to keep some runtime type information in sync with a compile-time interface.Hide Windows Updates Programmatically2019-06-09T00:00:00+00:002019-06-09T00:00:00+00:00https://cspotcode.com/posts/hide-windows-updates-programmatically<p>My company gives us Office 2010, but I have my personal copy of 2019 installed instead. For some reason, Windows Update keeps proposing Office 2010 updates which always fail to install.</p> <p><a href="https://www.powershellgallery.com/packages/PSWindowsUpdate/"><code class="language-plaintext highlighter-rouge">PSWindowsUpdate</code></a> is a PowerShell module for interacting with Windows Update interactively or programmatically. It allows hiding windows updates.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>install-module -scope currentuser pswindowsupdate hide-windowsupdate </code></pre></div></div> <p>This will query Windows Update for updates then prompt you to hide all of them one by one. I had to run it a bunch of times, since each batch of update hiding allowed Windows Update to find others, probably because at this point there are months of Office 2010 updates that don’t apply to me.</p>Andrew Bradley[email protected]https://cspotcode.comMy company gives us Office 2010, but I have my personal copy of 2019 installed instead. For some reason, Windows Update keeps proposing Office 2010 updates which always fail to install.Polyglot PowerShell and bash script2019-06-01T00:00:00+00:002019-06-01T00:00:00+00:00https://cspotcode.com/posts/polyglot-powershell-and-bash-script<p>I wanted to put some bash and some PowerShell into the same script file, so if I ran it in bash it would do one thing, and if I ran in PowerShell it would do something else. Here’s what I came up with:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#!/bin/bash echo `# &lt;#` # Bash goes here set -euo pipefail ls -al doing bash stuff exit #&gt; &gt; $null # PowerShell goes here $val = ( 'powershell', 'stuff', 'here' ) foreach($v in $val) { # yadda yadda } </code></pre></div></div> <p>The magic is here:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>echo `# &lt;#` # bash code exit #&gt; &gt; $null # powershell code </code></pre></div></div> <p>On bash, it executes what’s between the backticks, which is just a comment. So it succeeds silently and passes through to the bash script. We <code class="language-plaintext highlighter-rouge">exit</code> before it gets to any PowerShell syntax.</p> <p>On PowerShell the backtick is an escape character, so <code class="language-plaintext highlighter-rouge">`#</code> is parsed as the string <code class="language-plaintext highlighter-rouge">"#"</code>. It’s followed by a multiline comment delimited by <code class="language-plaintext highlighter-rouge">&lt;# #&gt;</code>, so PowerShell skips over all our bash code. We redirect to <code class="language-plaintext highlighter-rouge">$null</code> to suppress the echoed <code class="language-plaintext highlighter-rouge">#</code>.</p>Andrew Bradley[email protected]https://cspotcode.comI wanted to put some bash and some PowerShell into the same script file, so if I ran it in bash it would do one thing, and if I ran in PowerShell it would do something else. Here’s what I came up with:Attach VSCode to container from CLI2019-05-30T00:00:00+00:002019-05-30T00:00:00+00:00https://cspotcode.com/posts/attach-vscode-to-container-from-cli<p>VSCode’s new “Remote development” features let you attach the editor to a remote system, for example, WSL, an SSH server, or a docker container.</p> <p>The WSL integration lets you run <code class="language-plaintext highlighter-rouge">code-insiders .</code> from WSL, and it opens the current directory in a VSCode window automatically connected to WSL. However, it wasn’t obvious how to do the same for attaching to a docker container. After reading some of the code, I figured it out:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>auth="attached-container+$(node -p 'Buffer.from(process.argv[1]).toString("hex")' "$containerId")" code-insiders --remote="$auth" --folder-uri="vscode-remote://$auth/path/inside/container" </code></pre></div></div> <h2 id="how-it-works">How it works:</h2> <p>As far as I can tell, VSCode lets you specify a <code class="language-plaintext highlighter-rouge">--remote</code> flag of the form <code class="language-plaintext highlighter-rouge">&lt;authority&gt;+&lt;target&gt;</code></p> <p><code class="language-plaintext highlighter-rouge">&lt;authority&gt;</code> is one of <code class="language-plaintext highlighter-rouge">dev-container</code>, <code class="language-plaintext highlighter-rouge">attached-container</code>, or <code class="language-plaintext highlighter-rouge">wsl</code>. <code class="language-plaintext highlighter-rouge">&lt;target&gt;</code> depends on the type. For WSL, a target of <code class="language-plaintext highlighter-rouge">default</code> will attach to the <code class="language-plaintext highlighter-rouge">default</code> WSL distro. For attaching to a container, the target should be the hex-encoded container ID. We can compute this using a node one-liner.</p> <p>Technically, it looks like the <code class="language-plaintext highlighter-rouge">&lt;target&gt;</code> can also be a JSON object converted to hex, where the JSON object contains multiple fields. I think this is used for the <code class="language-plaintext highlighter-rouge">dev-container</code> authority to specify both a local directory and a docker host.</p> <p>To attach using a <code class="language-plaintext highlighter-rouge">devcontainer</code> configuration, the <code class="language-plaintext highlighter-rouge">&lt;authority&gt;</code> is <code class="language-plaintext highlighter-rouge">dev-container</code> and the <code class="language-plaintext highlighter-rouge">&lt;target&gt;</code> should be one of the following hex-encoded:</p> <ul> <li>path of the project on the host</li> <li>JSON of the form:</li> </ul> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{hostPath: "/home/me/whatever", dockerHost:"I assume a docker host domain name and port goes here"} </code></pre></div></div>Andrew Bradley[email protected]https://cspotcode.comVSCode’s new “Remote development” features let you attach the editor to a remote system, for example, WSL, an SSH server, or a docker container.Bash prompt zero-width spans2019-03-03T00:00:00+00:002019-03-03T00:00:00+00:00https://cspotcode.com/posts/bash-prompt-encoding<p>I had a lot of trouble setting up a bash prompt that renders within an external function, rather than inline within the PS1 string. Zero-width sequences from this external function were instead rendering <code class="language-plaintext highlighter-rouge">\[</code> and <code class="language-plaintext highlighter-rouge">\]</code> on the console.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PROMPT_COMMAND=prompt function prompt { status_info="&lt; generate a bunch of status info here&gt;" } PS1='$status_info' &gt; ' </code></pre></div></div> <p>Zero-width byte sequences in PS1 need to be wrapped with <code class="language-plaintext highlighter-rouge">\[</code> and <code class="language-plaintext highlighter-rouge">\]</code> so that bash can compute the correct visual width of the prompt.</p> <p>It wasn’t immediately clear that bash special-cases <code class="language-plaintext highlighter-rouge">\[</code> and <code class="language-plaintext highlighter-rouge">\]</code> character sequences within PS1, converting them into bytes 1 and 2. External variables and functions being interpolated into the PS1 string need to contain 1 and 2 byte values, not the 2-character <code class="language-plaintext highlighter-rouge">\[</code> <code class="language-plaintext highlighter-rouge">\]</code> sequences.</p> <p>https://unix.stackexchange.com/questions/504150/emit-zero-width-bash-prompt-sequence-from-external-binary</p>Andrew Bradley[email protected]https://cspotcode.comI had a lot of trouble setting up a bash prompt that renders within an external function, rather than inline within the PS1 string. Zero-width sequences from this external function were instead rendering \[ and \] on the console.Create an orphaned, empty tree branch in Git2019-03-03T00:00:00+00:002019-03-03T00:00:00+00:00https://cspotcode.com/posts/create-orphaned-empty-tree-commit<p>This blurb creates a git branch with a single, parent-less commit, with an empty tree. In other words, it’s an orphan with a commit but no files.</p> <p>You can achieve this by checking out an orphaned branch and then <code class="language-plaintext highlighter-rouge">commit --allow-empty</code>. However, the blurb below doesn’t require you to switch your worktree to a new branch.</p> <p>I’ve used this trick when I want to commit a subdirectory into a new, empty branch. I pass this branch to <code class="language-plaintext highlighter-rouge">git worktree add -b new-branch dir-name empty-orphan</code>.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Create and save an empty tree object</span> <span class="nv">treehash</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span> git hash-object <span class="nt">-w</span> <span class="nt">-t</span> tree /dev/null <span class="si">)</span><span class="s2">"</span> <span class="c"># Create a parent-less commit with empty tree</span> <span class="nv">commithash</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span> git commit-tree <span class="s2">"</span><span class="nv">$treehash</span><span class="s2">"</span> <span class="nt">-m</span> <span class="s2">"Empty tree"</span> <span class="si">)</span><span class="s2">"</span> <span class="c"># Create an orphan branch at the new commit</span> git branch empty-orphan <span class="s2">"</span><span class="nv">$commithash</span><span class="s2">"</span> </code></pre></div></div>Andrew Bradley[email protected]https://cspotcode.comThis blurb creates a git branch with a single, parent-less commit, with an empty tree. In other words, it’s an orphan with a commit but no files.Load nvm on-demand2018-08-16T00:00:00+00:002018-08-16T00:00:00+00:00https://cspotcode.com/posts/load-nvm-on-demand<p>nvm startup is slow on WSL. This is WSL’s fault, not nvm’s. All the process forking in nvm’s startup is slow. My work machine’s corporate virus scanner might be exacerbating the problem.</p> <p>Regardless, it’s easy to load nvm on-demand via bash profile. I rarely invoke the nvm command; I just need node and npm in my $PATH.</p> <p>Default nvm loader:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># nvm puts this in your .bashrc</span> <span class="nb">export </span><span class="nv">NVM_DIR</span><span class="o">=</span><span class="s2">"</span><span class="nv">$HOME</span><span class="s2">/.nvm"</span> <span class="o">[</span> <span class="nt">-s</span> <span class="s2">"</span><span class="nv">$NVM_DIR</span><span class="s2">/nvm.sh"</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="se">\.</span> <span class="s2">"</span><span class="nv">$NVM_DIR</span><span class="s2">/nvm.sh"</span> <span class="c"># This loads nvm</span> <span class="o">[</span> <span class="nt">-s</span> <span class="s2">"</span><span class="nv">$NVM_DIR</span><span class="s2">/bash_completion"</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="se">\.</span> <span class="s2">"</span><span class="nv">$NVM_DIR</span><span class="s2">/bash_completion"</span> <span class="c"># This loads nvm bash_completion</span> </code></pre></div></div> <p>Change it to this:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">export </span><span class="nv">NVM_DIR</span><span class="o">=</span><span class="s2">"</span><span class="nv">$HOME</span><span class="s2">/.nvm"</span> <span class="k">function </span>nvm <span class="o">{</span> <span class="o">[</span> <span class="nt">-s</span> <span class="s2">"</span><span class="nv">$NVM_DIR</span><span class="s2">/nvm.sh"</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="se">\.</span> <span class="s2">"</span><span class="nv">$NVM_DIR</span><span class="s2">/nvm.sh"</span> <span class="c"># This loads nvm</span> <span class="o">[</span> <span class="nt">-s</span> <span class="s2">"</span><span class="nv">$NVM_DIR</span><span class="s2">/bash_completion"</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="se">\.</span> <span class="s2">"</span><span class="nv">$NVM_DIR</span><span class="s2">/bash_completion"</span> <span class="c"># This loads nvm bash_completion</span> nvm <span class="s2">"</span><span class="nv">$@</span><span class="s2">"</span> <span class="o">}</span> <span class="nv">PATH</span><span class="o">=</span><span class="s2">"/home/abradley/.nvm/versions/node/</span><span class="si">$(</span> <span class="nb">cat</span> /home/abradley/.nvm/alias/default <span class="si">)</span><span class="s2">/bin:</span><span class="nv">$PATH</span><span class="s2">"</span> </code></pre></div></div> <p>There might be some corner-cases not handled when I set the PATH, but it works for me.</p>Andrew Bradley[email protected]https://cspotcode.comnvm startup is slow on WSL. This is WSL’s fault, not nvm’s. All the process forking in nvm’s startup is slow. My work machine’s corporate virus scanner might be exacerbating the problem. Regardless, it’s easy to load nvm on-demand via bash profile. I rarely invoke the nvm command; I just need node and npm in my $PATH. Default nvm loader: # nvm puts this in your .bashrc export NVM_DIR="$HOME/.nvm" [ -s "$NVM_DIR/nvm.sh" ] &amp;&amp; \. "$NVM_DIR/nvm.sh" # This loads nvm [ -s "$NVM_DIR/bash_completion" ] &amp;&amp; \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion Change it to this: export NVM_DIR="$HOME/.nvm" function nvm { [ -s "$NVM_DIR/nvm.sh" ] &amp;&amp; \. "$NVM_DIR/nvm.sh" # This loads nvm [ -s "$NVM_DIR/bash_completion" ] &amp;&amp; \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion nvm "$@" } PATH="/home/abradley/.nvm/versions/node/$( cat /home/abradley/.nvm/alias/default )/bin:$PATH" There might be some corner-cases not handled when I set the PATH, but it works for me.