diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Allen Nelson
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/nix-libs/nodeLib/buildNodePackage.nix b/nix-libs/nodeLib/buildNodePackage.nix
new file mode 100644
--- /dev/null
+++ b/nix-libs/nodeLib/buildNodePackage.nix
@@ -0,0 +1,521 @@
+{
+  # System packages.
+  pkgs,
+  # Derivation for nodejs and npm.
+  nodejs,
+  # Which version of npm to use.
+  npm ? nodejs,
+  # List of required native build inputs.
+  neededNatives,
+  # Self-reference for overriding purposes.
+  buildNodePackage,
+}:
+
+let
+  inherit (pkgs) stdenv;
+  inherit (pkgs.lib) showVal;
+  # This expression builds the raw C headers and source files for the base
+  # node.js installation. Node packages which use the C API for node need to
+  # link against these files and use the headers.
+  nodejsSources = pkgs.runCommand "node-sources" {} ''
+    tar --no-same-owner --no-same-permissions -xf ${nodejs.src}
+    mv $(find . -type d -mindepth 1 -maxdepth 1) $out
+  '';
+
+  # The path within $out/lib to find a package. If the package does not
+  # have a namespace, it will simply be in `node_modules`, and otherwise it
+  # will appear in `node_modules/@namespace`.
+  modulePath = pkg:
+    if !(builtins.hasAttr "namespace" pkg)
+    then throw ''
+      Package dependency does not appear to be a node package: ${showVal pkg}.
+      Use "buildInputs" or "propagatedBuildInputs" for non-node dependencies,
+      instead of "deps".
+    ''
+    else if pkg.namespace == null then "node_modules"
+    else "node_modules/@${pkg.namespace}";
+
+  # The path to the package within its modulePath. Just appending the name
+  # of the package.
+  pathInModulePath = pkg: "${modulePath pkg}/${pkg.basicName}";
+
+  # By default, when checking we'll run npm test.
+  defaultCheckPhase = ''
+    runHook preCheck
+    npm test
+    runHook postCheck
+  '';
+in
+
+{
+  # The name of the package. If it's a private package with a namespace,
+  # this should not contain the namespace.
+  name,
+
+  # Used for private packages. Indicated in the name field of the
+  # package.json, e.g. if the name in that file is "@mynamespace/mypackage",
+  # then the namespace is 'mynamespace' and the name is 'mypackage'. Public
+  # packages will not need this.
+  namespace ? null,
+
+  # Version of the package. This should follow the semver standard, although
+  # we don't explicitly enforce that in this function.
+  version,
+
+  # Source of the package; can be a tarball or a folder on the filesystem.
+  src,
+
+  # The suffix to the name as it appears in `nix-env -q` and the nix store. By
+  # default, the name of nodejs interpreter e.g:
+  # "<package name>-<package version>-nodejs-<nodejs version>"
+  nameSuffix ? "-${nodejs.name}",
+
+  # If there's a namespace, by default it will be prepended to the package
+  # name. Otherwise, a prefix can be given explicitly.
+  namePrefix ? (if namespace == null then "" else "${namespace}-"),
+
+  # List or attribute set of (runtime) dependencies.
+  deps ? {},
+
+  # List or attribute set of peer dependencies. See:
+  # https://nodejs.org/en/blog/npm/peer-dependencies/
+  peerDependencies ? {},
+
+  # List or attribute set of optional dependencies.
+  optionalDependencies ? {},
+
+  # List of optional dependencies to skip. List of strings, where a string
+  # should contain the `name` of the derivation to skip (not a version or
+  # namespace).
+  skipOptionalDependencies ? [],
+
+  # List or set of development dependencies (or null). These will only be
+  # installed when `installDevDependencies` is true, which is provided by
+  # the `.env` attribute.
+  devDependencies ? null,
+
+  # If true and devDependencies are defined, the package will only be
+  # installed contingent on successfully running tests.
+  doCheck ? false,
+
+  # Test command.
+  checkPhase ? defaultCheckPhase,
+
+  # Additional flags passed to npm install. A list of strings.
+  extraNpmFlags ? [],
+
+  # Same as https://docs.npmjs.com/files/package.json#os
+  os ? [],
+
+  # Attribute set of already resolved dependencies, for avoiding infinite
+  # recursion. Used internally (don't use this argument explicitly).
+  resolvedDeps ? {},
+
+  # Build inputs to propagate in addition to nodejs and non-dev dependencies.
+  propagatedBuildInputs ? [],
+
+  # Build inputs in addition to npm and dev dependencies.
+  buildInputs ? [],
+
+  # Whether to strip debugging symbols from binaries and patch ELF executables.
+  # These should both probably be true but can be overridden.
+  # Doc for details: https://nixos.org/wiki/NixPkgs_Standard_Environment.
+  dontStrip ? true, dontPatchELF ? true,
+
+  # Optional attributes to pass through to downstream derivations.
+  passthru ? {},
+
+  # A set of dependencies to patch.
+  patchDependencies ? {},
+
+  # Any remaining flags are passed through to mkDerivation.
+  ...
+} @ args:
+
+let
+  # The package name as it appears in the package.json. This contains a
+  # namespace if there is one, so it will be a distinct identifier for
+  # different packages.
+  packageJsonName = if namespace == null then name
+                    else "@${namespace}/${name}";
+
+  # The package name with a version appended. This should be unique amongst
+  # all packages.
+  uniqueName = "${packageJsonName}@${version}";
+
+in
+
+if doCheck && (devDependencies == null)
+then throw ("${uniqueName}: Can't run tests because devDependencies have " +
+            "not been defined.")
+else
+
+let
+  inherit (stdenv.lib) fold removePrefix hasPrefix subtractLists isList flip
+                       intersectLists isAttrs listToAttrs nameValuePair
+                       mapAttrs filterAttrs attrNames elem concatMapStrings
+                       attrValues getVersion flatten remove concatStringsSep;
+
+  dependencyTypes = ["dependencies" "devDependencies" "peerDependencies"
+                     "optionalDependencies"];
+
+
+  # These arguments are intended as directives to this function and not
+  # to be passed through to mkDerivation. They are removed below.
+  attrsToRemove = ["deps" "resolvedDeps" "flags" "os" "skipOptionalDependencies"
+                   "passthru" "doCheck" "installDevDependencies" "version"
+                   "namespace" "patchDependencies"] ++ dependencyTypes;
+
+  # We create a `self` object for self-referential expressions. It
+  # bottoms out in a call to `mkDerivation` at the end.
+  self = let
+    platforms = if os == [] then nodejs.meta.platforms else
+      fold (entry: platforms:
+        let
+          filterPlatforms =
+            stdenv.lib.platforms.${removePrefix "!" entry} or [];
+        in
+          # Ignore unknown platforms
+          if filterPlatforms == [] then (if platforms == [] then nodejs.meta.platforms else platforms)
+          else
+            if hasPrefix "!" entry then
+              subtractLists (intersectLists filterPlatforms nodejs.meta.platforms) platforms
+            else
+              platforms ++ (intersectLists filterPlatforms nodejs.meta.platforms)
+      ) [] os;
+
+
+    strict = x: builtins.seq x x;
+
+    toAttrSet = obj: if isAttrs obj then obj else
+      (listToAttrs (map (x: nameValuePair x.name x) obj));
+
+    mapDependencies = deps: filterFunc: let
+        attrDeps = toAttrSet deps;
+      in rec {
+        # All required node modules, without already resolved dependencies
+        # Also override with already resolved dependencies
+        requiredDeps = strict (mapAttrs (name: dep:
+          dep.overrideNodePackage {
+            resolvedDeps = resolvedDeps // {"${name}" = self;};
+          }
+        ) (filterAttrs filterFunc
+            (removeAttrs attrDeps (attrNames resolvedDeps))));
+
+        # Recursive dependencies that we want to avoid with shim creation
+        recursiveDeps = strict (filterAttrs filterFunc
+                          (removeAttrs attrDeps (attrNames requiredDeps)));
+      };
+
+    # Filter out self-referential dependencies.
+    _dependencies = strict (mapDependencies deps (name: dep: dep.uniqueName != uniqueName));
+
+    # Filter out self-referential peer dependencies.
+    _peerDependencies = strict (mapDependencies peerDependencies (name: dep:
+      dep.uniqueName != uniqueName));
+
+    # Filter out any optional dependencies which don't build correctly.
+    _optionalDependencies = strict (mapDependencies optionalDependencies (name: dep:
+      (builtins.tryEval dep).success &&
+      !(elem dep.basicName skipOptionalDependencies)
+    ));
+
+    # Grab development dependencies if doCheck is true.
+    _devDependencies = let
+        filterFunc = name: dep: dep.uniqueName != uniqueName;
+        depSet = if doCheck then devDependencies else [];
+      in
+      strict (mapDependencies depSet filterFunc);
+
+    # Depencencies we need to propagate (all except devDependencies)
+    propagatedDependencies = strict (
+      _dependencies.requiredDeps //
+      _optionalDependencies.requiredDeps //
+      _peerDependencies.requiredDeps);
+
+    # Required dependencies are those that we haven't filtered yet.
+    requiredDependencies =
+      strict (_devDependencies.requiredDeps // propagatedDependencies);
+
+    # Recursive dependencies. These are turned into "shims" or fake packages,
+    # which allows us to have dependency cycles, something npm allows.
+    recursiveDependencies = strict (
+      _devDependencies.recursiveDeps //
+      _dependencies.recursiveDeps //
+      _optionalDependencies.recursiveDeps //
+      _peerDependencies.recursiveDeps);
+
+    # Flags that we will pass to `npm install`.
+    npmFlags = concatStringsSep " " ([
+      # We point the registry at something that doesn't exist. This will
+      # mean that NPM will fail if any of the dependencies aren't met, as it
+      # will attempt to hit this registry for the missing dependency.
+      "--registry=http://notaregistry.$UNIQNAME.derp"
+      # These flags make failure fast, as otherwise NPM will spin for a while.
+      "--fetch-retry-mintimeout=0" "--fetch-retry-maxtimeout=10" "--fetch-retries=0"
+      # This will disable any user-level npm configuration.
+      "--userconfig=/dev/null"
+      # This flag is used for packages which link against the node headers.
+      "--nodedir=${nodejsSources}"
+      # This will tell npm not to run pre/post publish hooks
+      # "--ignore-scripts"
+      ] ++
+      # Use the --production flag if we're not running tests; this will make
+      # npm skip the dev dependencies.
+      (if !doCheck then ["--production"] else []) ++
+      # Add any extra headers that the user has passed in.
+      extraNpmFlags);
+
+    # A bit of bash to check that variables are set.
+    checkSet = vars: concatStringsSep "\n" (flip map vars (var: ''
+      [[ -z $${var} ]] && { echo "${var} is not set."; exit 1; }
+    ''));
+
+    patchPhase = ''
+      runHook prePatch
+      patchShebangs $PWD
+
+      # Ensure that the package name matches what is in the package.json.
+      node ${./checkPackageJson.js} ${packageJsonName}
+
+      # Remove any impure dependencies from the package.json (see script
+      # for details)
+      node ${./removeImpureDependencies.js}
+
+      # We do not handle shrinkwraps yet
+      rm npm-shrinkwrap.json 2>/dev/null || true
+
+      ${if patchDependencies == {} then "" else ''
+        cat <<EOF | python
+        import json
+        with open("package.json") as f:
+            package_json = json.load(f)
+        ${flip concatMapStrings (attrNames patchDependencies) (name: let
+            version = patchDependencies.${name};
+          in flip concatMapStrings dependencyTypes (depType: ''
+            if "${name}" in package_json.setdefault("${depType}", {}):
+                ${if version == null then ''
+                    print("removing ${name} from ${depType}")
+                    package_json["${depType}"].pop("${name}", None)
+                  '' else ''
+                    print("Patching ${depType} ${name} to version ${version}")
+                    package_json["dependencies"]["${name}"] = "${version}"
+                  ''}
+            ''))}
+        with open("package.json", "w") as f:
+            f.write(json.dumps(package_json))
+        EOF
+      ''}
+
+      runHook postPatch
+    '';
+
+    configurePhase = ''
+      runHook preConfigure
+      # Symlink or copy dependencies for node modules
+      # copy is needed if dependency has recursive dependencies,
+      # because node can't follow symlinks while resolving recursive deps.
+      ${
+        let
+          linkCmd = p: if p.recursiveDeps == [] then "ln -sv" else "cp -r";
+          link = dep: ''
+            if ! [[ -e ${pathInModulePath dep} ]]; then
+              ${linkCmd dep} ${dep}/lib/${pathInModulePath dep} ${modulePath dep}
+              if [[ -d ${dep}/bin ]]; then
+                find -L ${dep}/bin -maxdepth 1 -type f -executable \
+                  | while read exec_file; do
+                    echo "Symlinking $exec_file binary to node_modules/.bin"
+                    mkdir -p node_modules/.bin
+                    ln -s $exec_file node_modules/.bin/$(basename $exec_file)
+                done
+              fi
+            fi
+          '';
+        in
+        flip concatMapStrings (attrValues requiredDependencies) (dep:
+        # Create symlinks (or copies) of all of the required dependencies.
+        ''
+          mkdir -p ${modulePath dep}
+          ${link dep}
+          ${concatMapStrings link (attrValues dep.peerDependencies)}
+        '')}
+
+      # Remove recursive dependencies from package.json
+      ${if recursiveDependencies == {} then "" else ''
+        python <<EOF
+        import json
+        with open("package.json") as f:
+            package_json = json.load(f)
+        dep_names = [${concatStringsSep ", "
+                       (map (d: "'${d.packageJsonName}'")
+                         (attrValues recursiveDependencies))}]
+        for name in dep_names:
+            print("Removing recursive dependency on {} from package.json"
+                  .format(name))
+            for k in ("dependencies", "devDependencies", "peerDependencies",
+                      "optionalDependencies"):
+                package_json[k] = package_json.setdefault(k, {})
+                package_json[k].pop(name, None)
+        with open("package.json", "w") as f:
+            f.write(json.dumps(package_json))
+        EOF
+        ''}
+
+      runHook postConfigure
+    '';
+
+    buildPhase = ''
+      runHook preBuild
+      (
+        # NPM reads the `HOME` environment variable and fails if it doesn't
+        # exist, so set it here.
+        export HOME=$PWD
+        echo npm install $npmFlags
+
+        npm install $npmFlags || {
+          echo "NPM installation of ${name}@${version} failed!"
+          echo "Rerunning with verbose logging:"
+          npm install . $npmFlags --loglevel=verbose
+          if [[ -d node_modules ]]; then
+            echo "node_modules contains these files:"
+            ls -l node_modules
+          fi
+          exit 1
+        }
+      )
+      runHook postBuild
+    '';
+
+    installPhase = ''
+      runHook preInstall
+
+      # Install the package that we just built.
+      mkdir -p $out/lib/${modulePath self}
+
+      # Copy the folder that was created for this path to $out/lib.
+      cp -r $PWD $out/lib/${pathInModulePath self}
+
+      # Remove the node_modules subfolder from there, and instead put things
+      # in $PWD/node_modules into that folder.
+      if [ -e "$out/lib/${pathInModulePath self}/man" ]; then
+        echo "Linking manpages..."
+        mkdir -p $out/share
+        for dir in $out/lib/${pathInModulePath self}/man/*; do          #*/
+          mkdir -p $out/share/man/$(basename "$dir")
+          for page in $dir/*; do                                        #*/
+            ln -sv $page $out/share/man/$(basename "$dir")
+          done
+        done
+      fi
+
+      # Move peer dependencies to node_modules
+      ${concatMapStrings (dep: ''
+        mkdir -p ${modulePath dep}
+        mv ${pathInModulePath dep} $out/lib/${modulePath dep}
+      '') (attrValues _peerDependencies.requiredDeps)}
+
+      # Install binaries using the `bin` object in the package.json
+      python ${./installBinaries.py}
+
+      runHook postInstall
+    '';
+
+    # These are the arguments that we will pass to `stdenv.mkDerivation`.
+    mkDerivationArgs = {
+      inherit
+        src
+        patchPhase
+        configurePhase
+        buildPhase
+        checkPhase
+        installPhase
+        doCheck;
+
+      # Tell mkDerivation to run `setVariables` prior to other phases.
+      prePhases = ["setVariables"];
+
+      # Define some environment variables that we will use in the build.
+      setVariables = ''
+        export HASHEDNAME=$(echo "$propagatedNativeBuildInputs $name" \
+                          | md5sum | awk '{print $1}')
+        export UNIQNAME="''${HASHEDNAME:0:10}-${name}-${version}"
+        export BUILD_DIR=$TMPDIR/$UNIQNAME-build
+        export npmFlags="${npmFlags}"
+      '';
+
+      shellHook = ''
+        runHook preShellHook
+        runHook setVariables
+        export PATH=${npm}/bin:${nodejs}/bin:$(pwd)/node_modules/.bin:$PATH
+        mkdir -p $TMPDIR/$UNIQNAME
+        (
+          cd $TMPDIR/$UNIQNAME
+          eval "$configurePhase"
+        )
+        echo "Installed dependencies in $TMPDIR/$UNIQNAME."
+        export PATH=$TMPDIR/$UNIQNAME/node_modules/.bin:$PATH
+        export NODE_PATH=$TMPDIR/$UNIQNAME/node_modules
+        runHook postShellHook
+      '';
+
+      inherit dontStrip dontPatchELF;
+
+      meta = {
+        inherit platforms;
+        maintainers = [ stdenv.lib.maintainers.offline ];
+      };
+
+      # Propagate pieces of information about the package so that downstream
+      # packages can reflect on them.
+      passthru = (passthru // {
+        inherit uniqueName packageJsonName namespace version;
+        # The basic name is the name without namespace or version.
+        basicName = name;
+        peerDependencies = _peerDependencies.requiredDeps;
+
+        # Expose a list of recursive dependencies to upstream packages, so that
+        # they can be shimmed out.
+        recursiveDeps = let
+          required = attrValues requiredDependencies;
+          recursive = attrNames recursiveDependencies;
+        in
+          flatten (map (dep: remove name dep.recursiveDeps) required) ++
+          recursive;
+
+        # The `env` attribute is meant to be used with `nix-shell` (although
+        # that's not required). It will build the package with its dev
+        # dependencies. This means that the package must have dev dependencies
+        # defined, or it will error.
+        env = buildNodePackage (args // {installDevDependencies = true;});
+
+        # An 'overrideNodePackage' attribute, which will call
+        # `buildNodePackage` with the given arguments overridden.
+        # We don't use the name `override` because this will get stomped on
+        # if the derivation is the result of a `callPackage` application.
+        overrideNodePackage = newArgs: buildNodePackage (args // newArgs);
+      });
+    } // (removeAttrs args attrsToRemove) // {
+      name = "${namePrefix}${name}-${version}${nameSuffix}";
+
+      # Pass the required dependencies and
+      propagatedBuildInputs = propagatedBuildInputs ++
+                              attrValues propagatedDependencies ++
+                              [nodejs pkgs.tree];
+
+
+      buildInputs = [npm] ++ buildInputs ++
+                    attrValues (_devDependencies.requiredDeps) ++
+                    neededNatives;
+
+      # Expose list of recursive dependencies upstream, up to the package that
+      # caused recursive dependency
+      recursiveDeps =
+        (flatten (
+          map (dep: remove name dep.recursiveDeps) (attrValues requiredDependencies)
+        )) ++
+        (attrNames recursiveDependencies);
+    };
+
+    in stdenv.mkDerivation mkDerivationArgs;
+
+in self
diff --git a/nix-libs/nodeLib/checkPackageJson.js b/nix-libs/nodeLib/checkPackageJson.js
new file mode 100644
--- /dev/null
+++ b/nix-libs/nodeLib/checkPackageJson.js
@@ -0,0 +1,18 @@
+// Given an expected name of a package, checks that this matches the
+// name declared in the package.json file.
+
+// Pull the expected name out of the arguments. For example, the expected
+// name might be 'foo' or '@foo/bar'.
+var fs = require('fs');
+var expectedName = process.argv[2];
+
+// Load up the package object.
+var packageObj = JSON.parse(fs.readFileSync('./package.json'));
+
+// Ensure that they match.
+if (expectedName !== packageObj['name']) {
+  console.error("Package name declared in package.json ("
+                + packageObj['name'] + ") does not match expected name ("
+                + expectedName + ")");
+  process.exit(1);
+}
diff --git a/nix-libs/nodeLib/default.nix b/nix-libs/nodeLib/default.nix
new file mode 100644
--- /dev/null
+++ b/nix-libs/nodeLib/default.nix
@@ -0,0 +1,152 @@
+/*
+A set of tools for generating node packages, such as to be imported by
+default.nix files generated by nixfromnpm.
+*/
+
+{
+  # Self-reference so that we can pass through to downstream libraries
+  self
+}:
+
+{
+  # Base set of packages, i.e. nixpkgs.
+  pkgs,
+  # nodejs derivation.
+  nodejs ? pkgs.nodejs-4_x,
+  # Whether to use npm3 (requires a prebuilt tarball of npm3).
+  npm3 ? true
+} @ args:
+
+let
+  # Function to replace dots with something
+  replaceDots = c: replaceChars ["."] [c];
+  inherit (builtins) readDir removeAttrs length getEnv elemAt hasAttr;
+  inherit (pkgs.lib) attrNames attrValues filterAttrs flip foldl
+                     hasSuffix hasPrefix removeSuffix replaceChars
+                     optional optionals stringToCharacters
+                     concatStrings tail splitString;
+  inherit (pkgs.stdenv) isLinux;
+
+  # Function to remove the first character of a string.
+  dropFirstChar = str: concatStrings (tail (stringToCharacters str));
+
+  # Concatenate a list of sets.
+  joinSets = foldl (a: b: a // b) {};
+
+  # Builds the extracted nix file. Since of course it can't use npm3,
+  # being that it hasn't been built yet, we disable npm3 for this.
+  _npm3 = ((self (args // {npm3 = false;}))
+          .generatePackages {nodePackagesPath = ../nodePackages;})
+          .nodePackages.npm;
+
+  # Parse the `NPM_AUTH_TOKENS` environment variable to discover
+  # namespace-token associations and turn them into an attribute set
+  # which we can use as an input to the fetchPrivateNpm function.
+  # Split the variable on ':', then turn each k=v element in
+  # the list into an attribute set and join all of those sets.
+  namespaceTokens = joinSets (
+    flip map (splitString ":" (getEnv "NPM_AUTH_TOKENS")) (kvPair:
+      let kv = splitString "=" kvPair; in
+      if length kv != 2 then {}
+      else {"${elemAt kv 0}" = elemAt kv 1;}));
+
+  # A function similar to fetchUrl but allows setting of custom headers.
+  fetchUrlWithHeaders = pkgs.callPackage ./fetchUrlWithHeaders.nix {};
+
+  # Uses the parsed namespace tokens to create a function that can
+  # fetch a private package from an npm repo.
+  fetchPrivateNpm = {namespace, headers ? {}, ...}@args:
+    if !(hasAttr namespace namespaceTokens)
+    then throw "NPM_AUTH_TOKENS does not contain namespace ${namespace}"
+    else let
+      Authorization = "Bearer ${namespaceTokens.${namespace}}";
+      headers = {inherit Authorization;} // headers;
+    in
+    fetchUrlWithHeaders (removeAttrs args ["namespace"] // {inherit headers;});
+in
+
+rec {
+  inherit nodejs;
+
+  buildNodePackage = import ./buildNodePackage.nix ({
+    inherit pkgs nodejs buildNodePackage;
+    neededNatives = [pkgs.python] ++ optionals isLinux [pkgs.utillinux];
+  } // (if npm3 then {npm = _npm3;} else {}));
+  # A generic package that will fail to build. This is used to indicate
+  # packages that are broken, without failing the entire generation of
+  # a package expression.
+  brokenPackage = {name, reason}:
+    let
+     deriv = pkgs.stdenv.mkDerivation {
+        name = "BROKEN-${name}";
+        buildCommand = ''
+          echo "Package ${name} is broken: ${reason}"
+          exit 1
+        '';
+        passthru.withoutTests = deriv;
+        passthru.pkgName = name;
+        passthru.basicName = "BROKEN";
+        passthru.uniqueName = "BROKEN";
+        passthru.overrideNodePackage = (_: deriv);
+        passthru.namespace = null;
+        passthru.version = "BROKEN";
+        passthru.override = _: deriv;
+        passthru.recursiveDeps = [];
+        passthru.peerDependencies = {};
+      };
+    in
+    deriv;
+
+  # The function that a default.nix can call into which will scan its
+  # directory for all of the package files and generate a big attribute set
+  # for all of them. Re-exports the `callPackage` function and all of the
+  # attribute sets, as well as the nodeLib.
+  generatePackages = {
+    # Path to find node packages in.
+    nodePackagesPath,
+    # Extensions are other node libraries which will be folded into the
+    # generated one.
+    extensions ? [],
+    # If any additional arguments should be made available to callPackage
+    # (for example for packages which require additional arguments), they
+    # can be passed in here. Those packages can declare an `extras` argument
+    # which will contain whatever is passed in here.
+    extras ? {}
+  }:
+    let
+      callPackageWith = pkgSet: path: overridingArgs: let
+        inherit (builtins) intersectAttrs functionArgs;
+        inherit (pkgs.lib) filterAttrs;
+        # The path must be a function; import it here.
+        func = import path;
+        # Get the arguments to the function; e.g. "{a=false; b=true;}", where
+        # a false value is an argument that has no default.
+        funcArgs = functionArgs func;
+        # Take only the arguments that don't have a default.
+        noDefaults = filterAttrs (_: v: v == false) funcArgs;
+        # Intersect this set with the package set to create the arguments to
+        # the function.
+        satisfyingArgs = intersectAttrs noDefaults pkgSet;
+        # Override these arguments with whatever's passed in.
+        actualArgs = satisfyingArgs // overridingArgs;
+        # Call the function with these args to get a derivation.
+        deriv = func actualArgs;
+        in deriv;
+
+      nodePackages = joinSets (map (e: e.nodePackages) extensions) //
+                     (import nodePackagesPath {inherit callPackage;});
+
+      callPackage = callPackageWith {
+        inherit fetchUrlWithHeaders namespaceTokens;
+        inherit pkgs nodePackages buildNodePackage brokenPackage;
+        inherit extras;
+        inherit (nodePackages) namespaces;
+      };
+    in
+    {
+      inherit callPackage namespaceTokens pkgs;
+      nodePackages = nodePackages // {inherit nodejs;};
+      nodeLib = self args;
+      npm3 = _npm3;
+    };
+}
diff --git a/nix-libs/nodeLib/fetch.py b/nix-libs/nodeLib/fetch.py
new file mode 100644
--- /dev/null
+++ b/nix-libs/nodeLib/fetch.py
@@ -0,0 +1,21 @@
+import os
+import requests
+out = os.environ['out']
+url = os.environ['url']
+headers = {"User-Agent": "nix-fetchurl"}
+header_names = os.environ.get("headerNames", "")
+for name in header_names.split():
+    if "__HTTP_HEADER_{}".format(name) not in os.environ:
+        exit("FATAL: no corresponding value set for header {}"
+             .format(name))
+    headers[name] = os.environ["__HTTP_HEADER_{}".format(name)]
+print('GET {} with headers {}'.format(url, headers))
+response = requests.get(url, headers=headers)
+if response.status_code != 200:
+    exit("Received a {} response. :(\nContent: {}"
+         .format(response.status_code, response.content))
+else:
+    print('Response: {} ({} bytes)'
+          .format(response.status_code, len(response.content)))
+with open(out, 'wb') as f:
+    f.write(response.content)
diff --git a/nix-libs/nodeLib/fetchUrlWithHeaders.nix b/nix-libs/nodeLib/fetchUrlWithHeaders.nix
new file mode 100644
--- /dev/null
+++ b/nix-libs/nodeLib/fetchUrlWithHeaders.nix
@@ -0,0 +1,71 @@
+# A python-based fetchurl function, allowing the passage of custom headers.
+# Just calls into `requests` under the hood.
+{
+  pythonPackages, stdenv
+}:
+
+
+{ # URL to fetch.
+  url ? ""
+
+, # Additional curl options needed for the download to succeed.
+  curlOpts ? ""
+
+, # Name of the file.  If empty, use the basename of `url' (or of the
+  # first element of `urls').
+  name ? ""
+
+  # Different ways of specifying the hash.
+, outputHash ? ""
+, outputHashAlgo ? ""
+, md5 ? ""
+, sha1 ? ""
+, sha256 ? ""
+
+, # Meta information, if any.
+  meta ? {}
+
+  # Headers to set, if any.
+, headers ? {}
+}:
+
+let
+  inherit (stdenv.lib) flip mapAttrs' nameValuePair;
+  hasHash = (outputHash != "" && outputHashAlgo != "")
+            || md5 != "" || sha1 != "" || sha256 != "";
+
+  # Create an attribute set translating each header name and value into
+  # the header name prefixed with __HTTP_HEADER. When the derivation is
+  # evaluated, the script will pick up these environment variables and use
+  # them to produce the actual headers.
+  headerValues = flip mapAttrs' headers (headerName: headerValue:
+    nameValuePair "__HTTP_HEADER_${headerName}" headerValue);
+in
+
+if !hasHash
+then throw "You must specify the output hash for ${url}"
+else
+
+stdenv.mkDerivation ({
+  inherit url;
+  name = if name != "" then name else baseNameOf (toString url);
+
+  outputHashAlgo = if outputHashAlgo != "" then outputHashAlgo else
+      if sha256 != "" then "sha256" else if sha1 != "" then "sha1" else "md5";
+  outputHash = if outputHash != "" then outputHash else
+      if sha256 != "" then sha256 else if sha1 != "" then sha1 else md5;
+
+  # Only flat hashing, which is the normal mode if you're fetching a file.
+  outputHashMode = "flat";
+
+  # Doing the download on a remote machine just duplicates network
+  # traffic, so don't do that.
+  preferLocalBuild = true;
+
+  headerNames = builtins.attrNames headers;
+
+  buildInputs = with pythonPackages; [python requests2];
+  buildCommand = ''
+    python ${./fetch.py}
+  '';
+} // headerValues)
diff --git a/nix-libs/nodeLib/installBinaries.py b/nix-libs/nodeLib/installBinaries.py
new file mode 100644
--- /dev/null
+++ b/nix-libs/nodeLib/installBinaries.py
@@ -0,0 +1,41 @@
+# This script will read the package.json file and create a symlink for each
+# item under the `bin` key. The bin can be a string or a dictionary.
+# See: https://docs.npmjs.com/files/package.json#bin
+import json
+import os
+from os.path import join, isdir, normpath
+import sys
+
+with open('package.json', 'r') as f:
+    package = json.load(f)
+
+if 'bin' not in package:
+    sys.exit(0)
+
+_bin = package['bin']
+
+if isinstance(_bin, basestring):
+    # This is equivalent to a singleton dictionary where the key is the
+    # name of the package.
+    _bin = {package['name']: _bin}
+elif not isinstance(_bin, dict):
+    # Otherwise it must be a dictionary.
+    sys.exit("Expected `bin` key in package.json to point to a string "
+             "or a dict, but it is '{}', of type '{}'"
+             .format(_bin, type(_bin).__name__))
+
+out_dir = os.environ['out']
+
+# Create the .bin folder
+bin_folder = join(out_dir, 'bin')
+if not isdir(bin_folder):
+    os.makedirs(bin_folder)
+
+print("Creating binaries in {}".format(bin_folder))
+
+for bin_name, bin_path in _bin.items():
+    # Get the absolute path of the script being pointed to.
+    bin_abs_path = normpath(join(out_dir, 'lib', 'node_modules',
+                            package['name'], bin_path))
+    print("Linking binary {} to {}".format(bin_name, bin_abs_path))
+    os.symlink(bin_abs_path, join(bin_folder, bin_name))
diff --git a/nix-libs/nodeLib/npm3.tar.gz b/nix-libs/nodeLib/npm3.tar.gz
new file mode 100644
Binary files /dev/null and b/nix-libs/nodeLib/npm3.tar.gz differ
diff --git a/nix-libs/nodeLib/removeImpureDependencies.js b/nix-libs/nodeLib/removeImpureDependencies.js
new file mode 100644
--- /dev/null
+++ b/nix-libs/nodeLib/removeImpureDependencies.js
@@ -0,0 +1,49 @@
+// These packages come packaged with nodejs.
+var fs = require('fs');
+var url = require('url');
+
+function versionSpecIsImpure(versionSpec) {
+  // Returns true if a version spec is impure.
+  return (versionSpec == "latest" || versionSpec == "unstable" ||
+          // file path references
+          versionSpec.substr(0, 2) == ".." ||
+          versionSpec.substr(0, 2) == "./" ||
+          versionSpec.substr(0, 2) == "~/" ||
+          versionSpec.substr(0, 1) == '/' ||
+          // github owner/repo references
+          /^[^/]+\/[^/]+(#.*)?$/.test(versionSpec) ||
+          // is a URL
+          url.parse(versionSpec).protocol);
+}
+
+// Load up the package object.
+var packageObj = JSON.parse(fs.readFileSync('./package.json'));
+
+// Purify dependencies.
+var depTypes = ['dependencies', 'devDependencies', 'optionalDependencies'];
+for (var i in depTypes) {
+  var depType = depTypes[i];
+  var depSet = packageObj[depType];
+  if (depSet !== undefined) {
+    for (var depName in depSet) {
+      var versionSpec = depSet[depName];
+      if (versionSpecIsImpure(versionSpec)) {
+        console.log("Replacing impure version spec " + versionSpec +
+                    " for dependency " + depName + " with '*'");
+        depSet[depName] = '*';
+      }
+    }
+  }
+}
+
+/* Remove peer dependencies */
+if (process.env.removePeerDependencies && packageObj.peerDependencies) {
+  console.log("WARNING: removing the following peer dependencies:");
+  for (key in packageObj.peerDependencies) {
+    console.log("  " + key + ": " + packageObj.peerDependencies[key]);
+  }
+  delete packageObj.peerDependencies;
+}
+
+/* Write the fixed JSON file */
+fs.writeFileSync("package.json", JSON.stringify(packageObj));
diff --git a/nixfromnpm.cabal b/nixfromnpm.cabal
--- a/nixfromnpm.cabal
+++ b/nixfromnpm.cabal
@@ -1,5 +1,5 @@
 name:                nixfromnpm
-version:             0.2.1
+version:             0.10.1
 synopsis:            Generate nix expressions from npm packages.
 description:
   Given an npm package name and one or more npm repositories, will dump out a
@@ -9,62 +9,47 @@
   using the same target directory will result in re-use of the existing files,
   to avoid unnecessary duplication.
 license:             MIT
+license-file:        LICENSE
 author:              Allen Nelson
 maintainer:          anelson@narrativescience.com
 build-type:          Simple
 cabal-version:       >=1.10
 bug-reports:         https://github.com/adnelson/nixfromnpm/issues
+Category:            Tools, Nix
 
+data-files:   nix-libs/nodeLib/default.nix
+            , nix-libs/nodeLib/buildNodePackage.nix
+            , nix-libs/nodeLib/removeImpureDependencies.js
+            , nix-libs/nodeLib/checkPackageJson.js
+            , nix-libs/nodeLib/fetchUrlWithHeaders.nix
+            , nix-libs/nodeLib/installBinaries.py
+            , nix-libs/nodeLib/fetch.py
+            , nix-libs/nodeLib/npm3.tar.gz
+            , top_packages.txt
+
 source-repository head
   type:     git
   location: git://github.com/adnelson/nixfromnpm.git
 
-library
-  exposed-modules:     NixFromNpm
-  other-modules:       NixFromNpm.Common
-                     , NixFromNpm.ConvertToNix
-                     , NixFromNpm.NpmLookup
-                     , NixFromNpm.NpmTypes
-                     , NixFromNpm.NpmVersion
-                     , NixFromNpm.Options
-                     , NixFromNpm.SemVer
-                     , NixFromNpm.Parsers.Common
-                     , NixFromNpm.Parsers.NpmVersion
-                     , NixFromNpm.Parsers.SemVer
-  build-depends:       base >=4.8 && <4.9
-                     , classy-prelude
-                     , text
-                     , mtl
-                     , bytestring
-                     , unordered-containers
-                     , containers
-                     , parsec
-                     , aeson
-                     , data-default
-                     , shelly
-                     , MissingH
-                     , error-list
-                     , text-render
-                     , system-filepath
-                     , filepath
-                     , network-uri
-                     , directory
-                     , hnix
-                     , optparse-applicative
-  hs-source-dirs:      src
-  default-language:    Haskell2010
-
-
-
 executable nixfromnpm
   main-is:             Main.hs
-  -- other-modules:
-  other-extensions:    NoImplicitPrelude, OverloadedStrings
+  other-extensions:    FlexibleContexts
+                     , FlexibleInstances
+                     , LambdaCase
+                     , NoImplicitPrelude
+                     , NoMonomorphismRestriction
+                     , OverloadedStrings
+                     , QuasiQuotes
+                     , RecordWildCards
+                     , ScopedTypeVariables
+                     , TypeFamilies
+                     , TypeSynonymInstances
+                     , ViewPatterns
   build-depends:       base >=4.8 && <4.9
                      , classy-prelude
                      , text
-                     , mtl
                      , bytestring
+                     , mtl
                      , unordered-containers
                      , containers
                      , parsec
@@ -72,47 +57,21 @@
                      , data-default
                      , shelly
                      , MissingH
-                     , error-list
                      , text-render
                      , system-filepath
-                     , filepath
                      , network-uri
                      , directory
-                     , hnix
-                     , nixfromnpm
+                     , hnix >=0.2.3
                      , optparse-applicative
-  -- extra-libraries:     curl
+                     , curl
+                     , temporary
+                     , SHA
+                     , monad-control
+                     , lifted-base
+                     , transformers
+                     , unix
+                     , ansi-terminal
+                     , semver-range
+                     , data-fix
   hs-source-dirs:      src
   default-language:    Haskell2010
-
-Test-Suite spec
-  Type:                exitcode-stdio-1.0
-  Default-Language:    Haskell2010
-  Hs-Source-Dirs:      src
-                     , test
-  Main-Is:             Spec.hs
-  Build-Depends:       base >=4.8 && <4.9
-                     , classy-prelude
-                     , mtl
-                     , text
-                     , bytestring
-                     , unordered-containers
-                     , containers
-                     , parsec
-                     , io-streams
-                     , http-client-streams
-                     , aeson
-                     , data-default
-                     , shelly
-                     , MissingH
-                     , github
-                     , hspec
-                     , hspec-expectations
-                     , error-list
-                     , text-render
-                     , system-filepath
-                     , filepath
-                     , network-uri
-                     , directory
-                     , hnix
-                     , optparse-applicative
diff --git a/src/Main.hs b/src/Main.hs
--- a/src/Main.hs
+++ b/src/Main.hs
@@ -3,18 +3,23 @@
 {-# LANGUAGE OverloadedStrings #-}
 module Main where
 
+import qualified Data.Text.Encoding as T
 import Options.Applicative
-
-import NixFromNpm hiding (getArgs, (<>))
+import System.Exit
 
+import NixFromNpm.Common hiding (getArgs, (<>))
+import NixFromNpm.Options (NixFromNpmOptions, parseOptions,
+                           validateOptions)
+import NixFromNpm.Conversion.ToDisk (dumpPkgFromOptions)
+import NixFromNpm.Merge (mergeInto, MergeType(..), Source(..), Dest(..))
 
 main :: IO ()
 main = do
-  maybeToken <- getEnv "GITHUB_TOKEN"
-  let opts = info (helper <*> pOptions maybeToken)
+  let opts = info (helper <*> parseOptions)
              (fullDesc <> progDesc description <> header headerText)
   parsedOpts <- execParser opts
-  dumpPkgFromOptions parsedOpts
+  validatedOpts <- validateOptions parsedOpts
+  exitWith =<< dumpPkgFromOptions validatedOpts
   where
     description = concat ["nixfromnpm allows you to generate nix expressions ",
                           "automatically from npm packages. It provides ",
diff --git a/src/NixFromNpm.hs b/src/NixFromNpm.hs
deleted file mode 100644
--- a/src/NixFromNpm.hs
+++ /dev/null
@@ -1,21 +0,0 @@
-{-# LANGUAGE NoImplicitPrelude #-}
-{-# LANGUAGE LambdaCase #-}
-module NixFromNpm (module NixFromNpm.Common,
-                   module NixFromNpm.Options,
-                   module NixFromNpm.SemVer,
-                   module NixFromNpm.Parsers.SemVer,
-                   module NixFromNpm.NpmVersion,
-                   module NixFromNpm.NpmTypes,
-                   module NixFromNpm.Parsers.NpmVersion,
-                   module NixFromNpm.NpmLookup,
-                   module NixFromNpm.ConvertToNix) where
-
-import NixFromNpm.Common
-import NixFromNpm.Options
-import NixFromNpm.SemVer
-import NixFromNpm.Parsers.SemVer
-import NixFromNpm.NpmVersion
-import NixFromNpm.NpmTypes
-import NixFromNpm.Parsers.NpmVersion
-import NixFromNpm.NpmLookup
-import NixFromNpm.ConvertToNix
diff --git a/src/NixFromNpm/Common.hs b/src/NixFromNpm/Common.hs
deleted file mode 100644
--- a/src/NixFromNpm/Common.hs
+++ /dev/null
@@ -1,177 +0,0 @@
-{-# LANGUAGE LambdaCase #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE NoMonomorphismRestriction #-}
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE TypeSynonymInstances #-}
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE ViewPatterns #-}
-{-# LANGUAGE NoImplicitPrelude #-}
-{-# LANGUAGE TypeFamilies #-}
-module NixFromNpm.Common (
-    module ClassyPrelude,
-    module Control.Applicative,
-    module Control.Exception,
-    module Control.Exception.ErrorList,
-    module Control.Monad,
-    module Control.Monad.Except,
-    module Control.Monad.Identity,
-    module Control.Monad.State.Strict,
-    module Control.Monad.Reader,
-    module Control.Monad.Trans,
-    module Data.Char,
-    module Data.Default,
-    module Data.HashMap.Strict,
-    module Data.Either,
-    module Data.Maybe,
-    module Data.List,
-    module Data.String.Utils,
-    module GHC.Exts,
-    module Filesystem.Path.CurrentOS,
-    module Network.URI,
-    module GHC.IO.Exception,
-    module System.Directory,
-    module Text.Render,
-    module System.FilePath.Posix,
-    Name, Record, Path,
-    tuple, tuple3, fromRight, cerror, cerror', uriToText, uriToString, slash,
-    putStrsLn, pathToText, putStrs, dropSuffix, maybeIf, grab, withDir,
-    pathToString, joinBy, mapJoinBy, getEnv, modifyMap
-  ) where
-
-import ClassyPrelude hiding (assert, asList, find, FilePath, bracket,
-                             maximum, maximumBy, try)
-import qualified Prelude as P
-import Control.Monad (when)
-import Control.Monad.Trans (MonadIO(..), lift)
-import Control.Monad.Reader (ReaderT(..), MonadReader(..), (<=<), (>=>), ask,
-                             asks)
-import Control.Monad.State.Strict (MonadState, StateT, State, get, gets,
-                                   modify, put, liftM, liftIO, runState,
-                                   runStateT, execState, execStateT,
-                                   evalState, evalStateT)
-import Control.Monad.Except (ExceptT, MonadError(..), throwError, runExceptT)
-import Control.Exception.ErrorList
-import Control.Monad.Identity (Identity(..))
-import Control.Applicative hiding (empty, optional)
-import Data.Char (isDigit, isAlpha)
-import Data.Default
-import Data.List (maximum, maximumBy)
-import Data.HashMap.Strict (HashMap, (!))
-import qualified Data.HashMap.Strict as H
-import Data.Maybe (fromJust, isJust, isNothing)
-import Data.Either (isRight, isLeft)
-import Data.String.Utils hiding (join)
-import qualified Data.Text as T
-import Filesystem.Path.CurrentOS (FilePath, fromText, toText, collapse)
-import GHC.Exts (IsList)
-import GHC.IO.Exception
-import Control.Exception (bracket)
-import Text.Render hiding (renderParens)
-import Network.URI (URI(..), parseURI, parseAbsoluteURI,
-                    parseRelativeReference, relativeTo)
-import qualified Network.URI as NU
-import Shelly hiding (get, relativeTo)
-import System.Directory
-import System.FilePath.Posix hiding (FilePath)
-
--- | Indicates that the text is some identifier.
-type Name = Text
-
--- | Indicates that the text is some path.
-type Path = Text
-
--- | A record is a lookup table with string keys.
-type Record = HashMap Name
-
--- | Takes two applicative actions and returns their result as a 2-tuple.
-tuple :: Applicative f => f a -> f b -> f (a, b)
-tuple action1 action2 = (,) <$> action1 <*> action2
-
--- | Takes three applicative actions and returns their result as a 3-tuple.
-tuple3 :: Applicative f => f a -> f b -> f c -> f (a, b, c)
-tuple3 action1 action2 action3 = (,,) <$> action1 <*> action2 <*> action3
-
--- | Creates a new hashmap by applying a function to every key in it.
-alterKeys :: (Eq k, Hashable k, Eq k', Hashable k') =>
-             (k -> k') -> HashMap k v -> HashMap k' v
-alterKeys f mp = do
-  let pairs = H.toList mp
-  let newPairs = P.map (\(k, v) -> (f k, v)) pairs
-  let newMap = H.fromList newPairs
-  newMap
-
--- | Create a hashmap by applying a test to everything in the existing
--- map.
-modifyMap :: (Eq k, Hashable k) => (a -> Maybe b) -> HashMap k a -> HashMap k b
-modifyMap test inputMap = foldl' step mempty $ H.toList inputMap where
-  step result (k, elem) = case test elem of
-    Nothing -> result
-    Just newElem -> H.insert k newElem result
-
-cerror :: [String] -> a
-cerror = error . concat
-
-cerror' :: [Text] -> a
-cerror' = cerror . map unpack
-
-fromRight :: Either a b -> b
-fromRight (Right x) = x
-fromRight (Left err) = error "Expected `Right` value"
-
-uriToText :: URI -> Text
-uriToText = pack . uriToString
-
-uriToString :: URI -> String
-uriToString uri = NU.uriToString id uri ""
-
--- | Appends text to URI with a slash. Ex: foo.com `slash` bar == foo.com/bar.
-slash :: URI -> Text -> URI
-slash uri txt = case parseRelativeReference (unpack txt) of
-  Nothing -> error ("Invalid appending URI: " <> show txt)
-  Just uri' -> uri' `relativeTo` uri
-
-infixl 6 `slash`
-
-putStrsLn :: MonadIO m => [Text] -> m ()
-putStrsLn = putStrLn . concat
-
-putStrs :: MonadIO m => [Text] -> m ()
-putStrs = putStr . concat
-
-pathToText :: FilePath -> Text
-pathToText pth = case toText pth of
-  Left p -> p
-  Right p -> p
-
-dropSuffix :: String -> String -> String
-dropSuffix suffix s | s == suffix = ""
-dropSuffix suffix (c:cs) = c : dropSuffix suffix cs
-dropSuffix suffix "" = ""
-
-maybeIf :: Bool -> a -> Maybe a
-maybeIf True x = Just x
-maybeIf False _ = Nothing
-
-grab :: (Hashable k, Eq k) => k -> HashMap k v -> v
-grab k = fromJust . H.lookup k
-
-withDir :: String -> IO a -> IO a
-withDir d action = do
-  cur <- getCurrentDirectory
-  result <- bracket (setCurrentDirectory d)
-                    (\_ -> setCurrentDirectory cur)
-                    (\_ -> action)
-  return result
-
-pathToString :: FilePath -> String
-pathToString = unpack . pathToText
-
-joinBy :: Text -> [Text] -> Text
-joinBy = T.intercalate
-
-mapJoinBy :: Text -> (a -> Text) -> [a] -> Text
-mapJoinBy sep func = joinBy sep . map func
-
--- | Reads an environment variable.
-getEnv :: Text -> IO (Maybe Text)
-getEnv = shelly . silently . get_env
diff --git a/src/NixFromNpm/ConvertToNix.hs b/src/NixFromNpm/ConvertToNix.hs
deleted file mode 100644
--- a/src/NixFromNpm/ConvertToNix.hs
+++ /dev/null
@@ -1,295 +0,0 @@
-{-# LANGUAGE NoImplicitPrelude #-}
-{-# LANGUAGE LambdaCase #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE RecordWildCards #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-module NixFromNpm.ConvertToNix where
-
-import Data.HashMap.Strict (HashMap)
-import qualified Data.HashMap.Strict as H
-import Data.Map (Map)
-import qualified Data.Map as M
-import Data.Text (Text)
-import qualified Data.Text as T
-
-import NixFromNpm.Common
-import Nix.Types
-import Nix.Parser
-import Nix.Pretty (prettyNix)
-import NixFromNpm.Options
-import NixFromNpm.NpmTypes
-import NixFromNpm.SemVer
-import NixFromNpm.Parsers.SemVer
-import NixFromNpm.NpmLookup (getPkg, FullyDefinedPackage(..), concatDots,
-                             PackageMap, mapPM, PreExistingPackage(..))
-
-_startingSrc :: String
-_startingSrc = "\
-  \{nixpkgs ? import <nixpkgs> {}}:                                  \
-  \let                                                               \
-  \  inherit (nixpkgs.lib) attrValues foldl;                         \
-  \  joinSets = foldl (a: b: a // b) {};                             \
-  \  joinedExtensions = joinSets (attrValues extensions);            \
-  \  allPkgs = nixpkgs // nodePkgs // joinedExtensions //            \
-  \   {inherit (nixpkgs.nodePackages)buildNodePackage;};             \
-  \  callPackage = nixpkgs.lib.callPackageWith allPkgs;              \
-  \  nodePkgs = joinedExtensions // byVersion // defaults;           \
-  \in                                                                \
-  \nodePkgs"
-
-_startingExpr :: NExpr
-_startingExpr = case parseNixString _startingSrc of
-  Success e -> e
-  Failure e -> error $ unlines ["FATAL: Starting source failed to parse:",
-                                show e]
-
-callPackage :: NExpr -> NExpr
-callPackage = callPackageWith []
-
-callPackageWith :: [Binding NExpr] -> NExpr -> NExpr
-callPackageWith args e = mkApp (mkApp (mkSym "callPackage") e)
-                               (mkNonRecSet args)
-
-callPackageWithRec :: [Binding NExpr] -> NExpr -> NExpr
-callPackageWithRec args e = mkApp (mkApp (mkSym "callPackage") e)
-                                  (mkRecSet args)
-
--- | Turns a string into one that can be used as an identifier.
-fixName :: Name -> Name
-fixName = T.replace "." "-"
-
--- | Converts a package name and semver into an identifier.
-toDepName :: Name -> SemVer -> Name
-toDepName name (a, b, c) = concat [fixName name, "_", pack $
-                                   intercalate "-" $ map show [a, b, c]]
--- | Gets the .nix filename of a semver. E.g. (0, 1, 2) -> 0.1.2.nix
-toDotNix :: SemVer -> Text
-toDotNix v = concatDots v <> ".nix"
-
--- | Creates a doublequoted string from some text.
-str :: Text -> NExpr
-str = mkStr DoubleQuoted
-
--- | Converts distinfo into a nix fetchurl call.
-distInfoToNix :: DistInfo -> NExpr
-distInfoToNix DistInfo{..} = mkApp (mkSym "fetchurl") $ mkNonRecSet
-  [ "url" `bindTo` str diUrl,
-    "sha1" `bindTo`  str diShasum ]
-
--- | Tests if there is information in the package meta.
-metaNotEmpty :: PackageMeta -> Bool
-metaNotEmpty PackageMeta{..} = isJust pmDescription
-
--- | Converts package meta to a nix expression.
-metaToNix :: PackageMeta -> NExpr
-metaToNix PackageMeta{..} = case pmDescription of
-  Nothing -> mkNonRecSet []
-  Just d -> mkNonRecSet ["description" `bindTo` str d]
-
--- | Converts a resolved package object into a nix expression. The expresion
--- will be a function where the arguments are its dependencies, and its result
--- is a call to `buildNodePackage`.
-resolvedPkgToNix :: ResolvedPkg -> NExpr
-resolvedPkgToNix ResolvedPkg{..} = do
-  let -- Get a string representation of each dependency in name-version format.
-      deps = map (mkSym . uncurry toDepName) $ H.toList rpDependencies
-      -- Get the parameters of the package function (deps + utility functions).
-      _funcParams = map (uncurry toDepName) (H.toList rpDependencies)
-                    <> ["buildNodePackage", "fetchurl"]
-      -- None of these have defaults, so put them into pairs with Nothing.
-      funcParams = mkFormalSet $ map (\x -> (x, Nothing)) _funcParams
-  let args = mkNonRecSet $ catMaybes [
-        Just $ "name" `bindTo` str rpName,
-        Just $ "version" `bindTo` (str $ renderSV rpVersion),
-        Just $ "src" `bindTo` distInfoToNix rpDistInfo,
-        maybeIf (length deps > 0) $ "deps" `bindTo` mkList deps,
-        maybeIf (metaNotEmpty rpMeta) $ "meta" `bindTo` metaToNix rpMeta
-        ]
-  mkFunction funcParams $ mkApp (mkSym "buildNodePackage") args
-
--- | Creates the `default.nix` file that is the top-level expression we are
--- generating.
-mkDefaultNix :: Record [SemVer] -- ^ Map of names to versions of packages that
-                                --   exist in this library.
-             -> Record Path -- ^ Map of extensions being included.
-             -> NExpr -- ^ A generated nix expression.
-mkDefaultNix versionMap extensionMap = do
-  let mkPath' = mkPath False . unpack
-      toPath name ver = mkPath' $ concat ["./", name, "/", toDotNix ver]
-      -- Make a set of all of the extensions
-      extensionsSet = mkNonRecSet $
-        -- Map over the expression map, creating a binding for each pair.
-        flip map (H.toList extensionMap) $ \(name, path) ->
-          name `bindTo` (mkApp
-                          (mkApp (mkSym "import") (mkPath False (unpack path)))
-                          (mkNonRecSet [Inherit Nothing [mkSelector "nixpkgs"]]))
-      mkBinding name ver = toDepName name ver
-                            `bindTo` callPackage (toPath name ver)
-      mkBindings name vers = map (mkBinding name) vers
-      mkDefVer name vers = case vers of
-        [] -> errorC ["FATAL: no versions generated for package ", name]
-        _  -> fixName name `bindTo` mkSym (toDepName name $ maximum vers)
-      -- This bit of map gymnastics will create a list of pairs of names
-      -- with all of the versions of that name that we have.
-      versOnly = sortOn fst $ H.toList versionMap
-      byVersion = mkNonRecSet $ concatMap (uncurry mkBindings) versOnly
-      defaults = mkWith (mkSym "byVersion") $
-        mkNonRecSet $ map (uncurry mkDefVer) versOnly
-      newBindings = ["extensions" `bindTo` extensionsSet,
-                     "byVersion" `bindTo` byVersion,
-                     "defaults" `bindTo` defaults]
-  modifyFunctionBody (appendBindings newBindings) _startingExpr
-
--- | The npm lookup utilities will produce a bunch of fully defined packages.
---   However, the only packages that we want to write are the new ones; that
---   is, the ones that we've discovered and the ones that already exist. This
---   will perform the appropriate filter.
-takeNewPackages :: PackageMap FullyDefinedPackage
-                -> (PackageMap ResolvedPkg, PackageMap NExpr)
-takeNewPackages startingRec = do
-  let isNew (NewPackage rpkg) = Just rpkg
-      isNew _ = Nothing
-      exists (FromExistingInOutput expr) = Just expr
-      exists _ = Nothing
-      newPkgs = H.map (modifyMap isNew) startingRec
-      existingPkgs = H.map (modifyMap exists) startingRec
-      removeEmpties = H.filter (not . H.null)
-  (removeEmpties newPkgs, removeEmpties existingPkgs)
-
--- | Actually writes the packages to disk. Takes in the new packages to write,
---   and the names/paths to the libraries being extended.
-dumpPkgs :: MonadIO m
-         => String                 -- ^ Path to output directory.
-         -> PackageMap ResolvedPkg -- ^ New packages being written.
-         -> PackageMap NExpr       -- ^ Existing packages to be included
-                                   --   in the generated default.nix.
-         -> Record Path            -- ^ Libraries being extended.
-         -> m ()
-dumpPkgs path newPackages existingPackages extensions = liftIO $ do
-  let _path = pack path
-  -- If there aren't any new packages, we can stop here.
-  if H.null newPackages
-  then putStrLn "No new packages created." >> return ()
-  else do
-    putStrsLn ["Creating new packages at ", _path]
-    createDirectoryIfMissing True path
-    withDir path $ do
-      -- Write the .nix file for each version of this package.
-      forM_ (H.toList newPackages) $ \(pkgName, pkgVers) -> do
-        let subdir = path </> unpack pkgName
-        createDirectoryIfMissing False subdir
-        withDir subdir $ forM_ (H.toList pkgVers) $ \(ver, rpkg) -> do
-          let expr = resolvedPkgToNix rpkg
-              fullPath = subdir </> unpack (toDotNix ver)
-          putStrsLn ["Writing package file at ", pack fullPath]
-          writeFile (unpack $ toDotNix ver) $ show $ prettyNix expr
-      -- Write the default.nix file for the library.
-      -- We need to build up a record mapping package names to the list of
-      -- versions being defined in this library.
-      let versionMap = map H.keys newPackages <> map H.keys existingPackages
-          defaultNix = mkDefaultNix versionMap extensions
-      writeFile "default.nix" $ show $ prettyNix defaultNix
-
--- | Given the path to a package, finds all of the .nix files which parse
---   correctly.
-parseVersion :: Name -> Path -> IO (Maybe (SemVer, NExpr))
-parseVersion pkgName path = do
-  let pth = unpack path
-      versionTxt = pack $ dropSuffix ".nix" $ takeBaseName pth
-  case parseSemVer versionTxt of
-    Left _ -> return Nothing -- not a version file
-    Right version -> parseNixString . pack <$> readFile pth >>= \case
-      Failure err -> do
-        putStrsLn ["Warning: expression for ", pkgName, " version ",
-                   versionTxt, " failed to parse:\n", pack $ show err]
-        return Nothing -- invalid nix, should overwrite
-      Success expr -> return $ Just (version, expr)
-
--- | Given the path to a file possibly containing nix expressions, finds all
---   expressions findable at that path and returns a map of them.
-findExisting :: Maybe Name -- ^ Is `Just` if this is an extension.
-             -> Path       -- ^ The path to search.
-             -> IO (PackageMap PreExistingPackage) -- ^ Mapping of package
-                                                   --   names to maps of
-                                                   --   versions to nix
-                                                   --   expressions.
-findExisting maybeName path = do
-  doesDirectoryExist (unpack path) >>= \case
-    False -> case maybeName of
-               Just name -> errorC ["Extension ", name, " at path ", path,
-                                    " does not exist."]
-               Nothing -> return mempty
-    True -> withDir (unpack path) $ do
-      let wrapper :: NExpr -> PreExistingPackage
-          wrapper = case maybeName of Nothing -> FromOutput
-                                      Just name -> FromExtension name
-      putStrsLn ["Searching for existing expressions in ", path, "..."]
-      contents <- getDirectoryContents "."
-      verMaps <- forM contents $ \dir -> do
-        exprs <- doesDirectoryExist dir >>= \case
-          True -> withDir dir $ do
-            contents <- getDirectoryContents "."
-            let files = pack <$> filter (endswith ".nix") contents
-            catMaybes <$> mapM (parseVersion $ pack dir) files
-          False -> do
-            return mempty -- not a directory
-        case exprs of
-          [] -> return Nothing
-          vs -> return $ Just (pack dir, H.map wrapper $ H.fromList exprs)
-      let total = sum $ map (H.size . snd) $ catMaybes verMaps
-      putStrsLn ["Found ", render total, " existing expressions"]
-      return $ H.fromList $ catMaybes verMaps
-
--- | Given the output directory and any number of extensions to load,
--- finds any existing packages.
-preloadPackages :: Bool        -- ^ Whether to skip the existence check.
-                -> Path        -- ^ Output path to search for existing packages.
-                -> Record Path -- ^ Mapping of names of libraries to extend,
-                               --   and paths to those libraries.
-                -> IO (PackageMap PreExistingPackage)
-preloadPackages noExistCheck path toExtend = do
-  existing <- if noExistCheck then pure mempty
-              else findExisting Nothing path
-  libraries <- fmap concat $ forM (H.toList toExtend) $ \(name, path) -> do
-    findExisting (Just name) path
-  return (existing <> libraries)
-
--- | Given the name of a package and a place to dump expressions to, generates
---   the expressions needed to build that package.
-dumpPkgNamed :: Text        -- ^ The name of the package to fetch.
-             -> Path        -- ^ The path to output to.
-             -> PackageMap PreExistingPackage  -- ^ Set of existing packages.
-             -> Record Path -- ^ Names -> paths of extensions.
-             -> Maybe Text  -- ^ Optional github token.
-             -> IO ()       -- ^ Writes files to a folder.
-dumpPkgNamed name path existing extensions token = do
-  pwd <- getCurrentDirectory
-  packages <- getPkg name existing token
-  let (new, existing) = takeNewPackages packages
-  dumpPkgs (pwd </> unpack path) new existing extensions
-
--- | Parse the NAME=PATH extension directives.
-getExtensions :: [Text] -> Record Path
-getExtensions = foldl' step mempty where
-  step :: Record Path -> Text -> Record Path
-  step exts nameEqPath = case T.split (== '=') nameEqPath of
-    [name, path] -> append name path
-    [path] -> append (pack $ takeBaseName (unpack path)) path
-    _ -> errorC ["Extensions must be of the form NAME=PATH (in argument ",
-                 nameEqPath, ")"]
-    where
-      append name path = case H.lookup name exts of
-        Nothing -> H.insert name path exts
-        Just path' -> errorC ["Extension ", name, " is mapped to both path ",
-                              path, " and path ", path']
-
--- displayExisting :: PackageMap -> IO ()
--- displayExisting pmap = forM_
-
-dumpPkgFromOptions :: NixFromNpmOptions -> IO ()
-dumpPkgFromOptions NixFromNpmOptions{..} = do
-  forM_ nfnoPkgNames $ \name -> do
-    let extensions = getExtensions nfnoExtendPaths
-    existing <- preloadPackages nfnoNoCache nfnoOutputPath extensions
-    -- displayExisting existing
-    dumpPkgNamed name nfnoOutputPath existing extensions nfnoGithubToken
diff --git a/src/NixFromNpm/NpmLookup.hs b/src/NixFromNpm/NpmLookup.hs
deleted file mode 100644
--- a/src/NixFromNpm/NpmLookup.hs
+++ /dev/null
@@ -1,478 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE LambdaCase #-}
-{-# LANGUAGE NoImplicitPrelude #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-module NixFromNpm.NpmLookup where
-
---------------------------------------------------------------------------
-import qualified Prelude as P
-import qualified Data.List as L
-import Data.Text (Text)
-import qualified Data.Text as T
-import qualified Data.Text.Encoding as T
-import Data.HashMap.Strict (HashMap)
-import qualified Data.HashMap.Strict as H
-import Data.HashSet (HashSet)
-import qualified Data.HashSet as HS
-
-import qualified Data.ByteString.Lazy.Char8 as BL8
-import Data.Aeson.Parser
-import Data.Aeson
-import Data.Aeson.Types (Parser, typeMismatch)
-import qualified Text.Parsec as Parsec
-import Shelly hiding (get)
-
-import NixFromNpm.Common
-import NixFromNpm.NpmTypes
-import NixFromNpm.SemVer
-import NixFromNpm.Parsers.Common hiding (Parser, Error, lines)
-import NixFromNpm.Parsers.SemVer
-import NixFromNpm.NpmVersion
-import NixFromNpm.Parsers.NpmVersion
-import Nix.Types
---------------------------------------------------------------------------
-
--- | Things which can be converted into nix expressions: either they
---   are actual nix expressions themselves (which can be either
---   existing in the output, or existing in an extension), or they are
---   new packages which we have discovered.
-data FullyDefinedPackage
-  = NewPackage ResolvedPkg
-  | FromExistingInOutput NExpr
-  | FromExistingInExtension Name NExpr
-  deriving (Show, Eq)
-
--- | The type of pre-existing packages, which can either come from the
--- output path, or come from an extension
-data PreExistingPackage
-  = FromOutput NExpr
-  | FromExtension Name NExpr
-  deriving (Show, Eq)
-
-toFullyDefined :: PreExistingPackage -> FullyDefinedPackage
-toFullyDefined (FromOutput expr) = FromExistingInOutput expr
-toFullyDefined (FromExtension name expr) = FromExistingInExtension name expr
-
--- | We use this data structure a lot: a mapping of package names to
---   a mapping of versions to fully defined packages.
-type PackageMap pkg = Record (HashMap SemVer pkg)
-
--- | Map a function across a PackageMap.
-mapPM :: (a -> b) -> PackageMap a -> PackageMap b
-mapPM f = H.map (H.map f)
-
--- | The state of the NPM fetcher.
-data NpmFetcherState = NpmFetcherState {
-  registries :: [URI],
-  githubAuthToken :: Maybe Text,
-  resolved :: PackageMap FullyDefinedPackage,
-  pkgInfos :: Record PackageInfo,
-  -- For cycle detection.
-  currentlyResolving :: HashSet (Name, SemVer),
-  indentLevel :: Int,
-  knownProblematicPackages :: HashSet Name,
-  getDevDeps :: Bool
-} deriving (Show, Eq)
-
-type NpmFetcher = ExceptT EList (StateT NpmFetcherState IO)
-
-concatDots :: SemVer -> Text
-concatDots (a, b, c) = pack $ intercalate "." (map show [a,b,c])
-
-indent :: Text -> NpmFetcher Text
-indent txt = do
-  ind <- gets indentLevel
-  return $ concat [txt, " (depth of ", pack $ show ind, ")"]
-
-putStrLnI :: Text -> NpmFetcher ()
-putStrLnI = indent >=> putStrLn
-
-putStrsLnI :: [Text] -> NpmFetcher ()
-putStrsLnI = putStrLnI . concat
-
-putStrI :: Text -> NpmFetcher ()
-putStrI = indent >=> putStr
-
-putStrsI :: [Text] -> NpmFetcher ()
-putStrsI = putStrI . concat
-
-addResolvedPkg :: Name -> SemVer -> ResolvedPkg -> NpmFetcher ()
-addResolvedPkg name version _rpkg = do
-  let rpkg = NewPackage _rpkg
-  pkgSet <- H.lookupDefault mempty name <$> gets resolved
-  modify $ \s -> s {
-    resolved = H.insert name (H.insert version rpkg pkgSet) (resolved s)
-    }
-
--- | Performs a curl query and returns whatever that query returns.
-curl :: [Text] -> NpmFetcher Text
-curl args = shell $ print_stdout False $ run "curl" (["-L", "--fail"] <> args)
-
--- | Queries NPM for package information.
-_getPackageInfo :: Name -> URI -> NpmFetcher PackageInfo
-_getPackageInfo pkgName registryUri = do
-  let uri = uriToText $ registryUri `slash` pkgName
-  putStrsLn ["Querying ", uriToText registryUri,
-             " for package ", pkgName, "..."]
-  jsonStr <- curl [uri]
-  case eitherDecode $ BL8.fromChunks [T.encodeUtf8 jsonStr] of
-    Left err -> throwErrorC ["couldn't parse JSON from NPM: ", pack err]
-    Right info -> return info
-
--- | Same as _getPackageInfo, but caches results for speed.
-getPackageInfo :: Name -> NpmFetcher PackageInfo
-getPackageInfo name = lookup name . pkgInfos <$> get >>= \case
-  Just info -> return info
-  Nothing -> inContext ctx $ do
-    regs <- gets registries
-    info <- firstSuccess "No repos contained package" $
-              map (_getPackageInfo name) regs
-    storePackageInfo name info
-    return info
-  where ctx = pack $ "When querying NPM registry for package " <> show name
-
-storePackageInfo :: Name -> PackageInfo -> NpmFetcher ()
-storePackageInfo name info = do
-  infos <- gets pkgInfos
-  let existingInfo = H.lookupDefault mempty name infos
-      newInfo = existingInfo <> info
-  modify $ \s -> s {pkgInfos = H.insert name newInfo (pkgInfos s)}
-
-
-toSemVerList :: Record a -> NpmFetcher [(SemVer, a)]
-toSemVerList rec = do
-  -- Pairings of parsed semvers (or errors) to values.
-  let parsePair (k, v) = (parseSemVer k, v)
-      pairs = map parsePair $ H.toList rec
-  case filter (\(k, _) -> isRight k) pairs of
-    [] -> throwError1 "No correctly-formatted versions strings found"
-    okPairs -> return $ map (\(Right k, v) -> (k, v)) okPairs
-
-bestMatchFromRecord :: SemVerRange -> Record a -> NpmFetcher a
-bestMatchFromRecord range rec = do
-  pairs <- toSemVerList rec
-  case filter (matches range . fst) pairs of
-    [] -> throwError1 "No versions satisfy given range"
-    matches -> return $ snd $ maximumBy (compare `on` fst) matches
-
-shell :: Sh Text -> NpmFetcher Text
-shell action = do
-  (code, out, err) <- shelly $ errExit False $ do
-    out <- action
-    code <- lastExitCode
-    err <- lastStderr
-    return (code, out, err)
-  case code of
-    0 -> return out
-    n -> do
-      throwErrorC $ catMaybes [
-                      Just "Shell command returned an error.",
-                      maybeIf (out /= "") $ "\nstdout:\n" <> out,
-                      Just $ "\nstderr:\n" <> err]
-
-silentShell :: Sh Text -> NpmFetcher Text
-silentShell = shell . silently
-
--- | Returns the SHA1 hash of the result of fetching the URI, and the path
--- in which the tarball is stored.
-nixPrefetchSha1 :: URI -> NpmFetcher (Text, FilePath)
-nixPrefetchSha1 uri = do
-  hashAndPath <- silentShell $ do
-    setenv "PRINT_PATH" "1"
-    run "nix-prefetch-url" ["--type", "sha1", uriToText uri]
-  if length (lines hashAndPath) /= 2
-  then error "Expected two lines from nix-prefetch-url"
-  else do
-    let [hashBase32, path] = lines hashAndPath
-    -- Convert the hash to base16, which is the format NPM uses.
-    hash <- silentShell $ do
-       run "nix-hash" ["--type", "sha1", "--to-base16", hashBase32]
-    return (T.strip hash, fromString $ unpack path)
-
-extractVersionInfo :: FilePath -> Text -> NpmFetcher VersionInfo
-extractVersionInfo tarballPath subpath = do
-  ind <- gets indentLevel
-  pkJson <- silentShell $ withTmpDir $ \dir -> do
-    chdir dir $ do
-      putStrs ["Extracting ", pathToText tarballPath, " to tempdir"]
-      putStrsLn [" (depth of ", pack $ show ind, ")"]
-      run_ "tar" ["-xf", pathToText tarballPath]
-      curdir <- pathToText <$> pwd
-      pth <- fmap T.strip $ run "find" [curdir, "-name", "package.json"]
-                           -|- run "head" ["-n", "1"]
-      when (pth == "") $ error "No package.json found"
-      map decodeUtf8 $ readBinary $ fromString $ unpack $ pth
-  case eitherDecode $ BL8.fromChunks [encodeUtf8 pkJson] of
-    Left err -> error $ "couldn't parse JSON as VersionInfo: " <> err
-    Right info -> return info
-
--- | Fetch a package over HTTP. Return the version of the fetched package,
--- and store the hash.
-fetchHttp :: Text -- ^ Subpath in which to find the package.json.
-          -> URI -- ^ The URI to fetch.
-          -> NpmFetcher SemVer -- ^ The version of the package at that URI.
-fetchHttp subpath uri = do
-  -- Use nix-fetch to download and hash the tarball.
-  (hash, tarballPath) <- nixPrefetchSha1 uri
-  -- Extract the tarball to a temp directory and parse the package.json.
-  versionInfo <- extractVersionInfo tarballPath subpath
-  -- Create the DistInfo.
-  let dist = DistInfo {diUrl = uriToText uri, diShasum = hash}
-  -- Add the dist information to the version info and resolve it.
-  resolveVersionInfo $ versionInfo {viDist = Just dist}
-
-githubCurl :: Text -> NpmFetcher Value
-githubCurl uri = do
-  -- Add in the github auth token if it is provided.
-  extraCurlArgs <- gets githubAuthToken >>= \case
-    Nothing -> return []
-    Just token -> return ["-H", "Authorization: token " <> token]
-  let curlArgs = extraCurlArgs <> [
-        -- This accept header tells github to allow redirects.
-        "-H", "Accept: application/vnd.github.quicksilver-preview+json",
-        uri
-        ]
-  -- putStrsLn $ ["calling curl with args: ", T.intercalate " " curlArgs]
-  jsonStr <- curl curlArgs
-  case eitherDecode $ BL8.fromChunks [T.encodeUtf8 jsonStr] of
-    Left err -> throwErrorC ["couldn't parse JSON from github: ", pack err]
-    Right info -> return info
-
--- | Queries NPM for package information.
-getDefaultBranch :: Name -> Name -> NpmFetcher Name
-getDefaultBranch owner repo = do
-  let rpath = "/" <> owner <> "/" <> repo
-  let uri = concat ["https://api.github.com/repos", rpath]
-  putStrs ["Querying github for default branch of ", rpath, "..."]
-  githubCurl uri >>= \case
-    Object o -> case H.lookup "default_branch" o of
-      Just (String b) -> putStr " OK. " >> return b
-      Nothing -> putStrLn "" >> error "No default branch, or not a string"
-    _ -> error "Expected an object back from github"
-
--- | Given a github repo and a branch, gets the SHA of the head of that
--- branch
-getShaOfBranch :: Name -- ^ Repo owner
-               -> Name -- ^ Repo name
-               -> Name -- ^ Name of the branch to get
-               -> NpmFetcher Text -- ^ The hash of the branch
-getShaOfBranch owner repo branchName = do
-  let rpath = "/" <> owner <> "/" <> repo
-  let uri = concat ["https://api.github.com/repos", rpath,
-                    "/branches/", branchName]
-  putStrs ["Querying github for sha of ", rpath, "/", branchName, "..."]
-  githubCurl uri >>= \case
-    Object o -> case H.lookup "commit" o of
-      Just (Object o') -> case H.lookup "sha" o' of
-        Just (String sha) -> return sha
-        Nothing -> error "No sha in commit info"
-      Nothing -> error "No commit info"
-    _ -> error "Didn't get an object back"
-
--- | Fetch a package from git.
-fetchGithub :: URI -> NpmFetcher SemVer
-fetchGithub uri = do
-  (owner, repo) <- case split "/" $ uriPath uri of
-    [_, owner, repo] -> return (pack owner, pack $ dropSuffix ".git" repo)
-    _ -> throwErrorC ["Invalid repo path: ", pack $ uriPath uri]
-  hash <- case uriFragment uri of
-    -- if there isn't a ref or a tag, use the default branch.
-    "" -> do
-      branch <- getDefaultBranch owner repo
-      putStrLn $ " Branch is " <> branch
-      sha <- getShaOfBranch owner repo branch
-      putStrLn $ " Hash is " <> sha
-      return sha
-    -- otherwise, use that as a tag.
-    '#':frag -> return $ pack frag
-    frag -> throwErrorC ["Invalid fragment '", pack frag, "'"]
-  -- Use the hash to pull down a zip.
-  let uri = concat ["https://github.com/", owner, "/", repo, "/archive/",
-                    hash, ".tar.gz"]
-  fetchHttp (repo <> "-" <> hash) (fromJust $ parseURI $ unpack uri)
-
-resolveNpmVersionRange :: Name -> NpmVersionRange -> NpmFetcher SemVer
-resolveNpmVersionRange name range = case range of
-  SemVerRange svr -> resolveDep name svr
-  NpmUri uri -> case uriScheme uri of
-    "git:" -> fetchGithub uri
-    "git+https:" -> fetchGithub uri
-    "http:" -> fetchHttp "package" uri
-    "https:" -> fetchHttp  "package" uri
-    scheme -> throwErrorC ["Unknown uri scheme ", pack scheme]
-  GitId src owner repo rev -> case src of
-    Github -> do
-      let frag = case rev of
-            Nothing -> ""
-            Just r -> "#" <> r
-      let uri = concat ["https://github.com/", owner, "/", repo, frag]
-      fetchGithub $ fromJust $ parseURI $ unpack uri
-    _ -> throwErrorC ["Can't handle git source ", pack $ show src]
-  Tag tag -> resolveByTag tag name
-  vr -> throwErrorC ["Don't know how to resolve dependency '",
-                     pack $ show vr, "'"]
-
--- | Uses the set of downloaded packages as a cache to avoid unnecessary
--- duplication.
-resolveDep :: Name -> SemVerRange -> NpmFetcher SemVer
-resolveDep name range = H.lookup name <$> gets resolved >>= \case
-  -- We've alread defined some versions of this package.
-  Just versions -> case filter (matches range) (H.keys versions) of
-    [] -> _resolveDep name range -- No matching versions, need to fetch.
-    vs -> do
-      let bestVersion = maximum vs
-          versionDots = concatDots bestVersion
-          package = fromJust $ H.lookup bestVersion versions
-      putStrsLn ["Requirement ", name, " version ", pack $ show range,
-                 " already satisfied:"]
-      putStrsLn $ case package of
-        NewPackage _ -> ["Fetched package version ", versionDots]
-        FromExistingInOutput _ -> ["Already had version ", versionDots,
-                                   " in output directory (use --no-cache",
-                                   " to override)"]
-        FromExistingInExtension name _ -> ["Version ", versionDots,
-                                           " provided by extension ", name]
-      return bestVersion
-  -- We haven't yet found any versions of this package.
-  Nothing -> _resolveDep name range
-
-startResolving :: Name -> SemVer -> NpmFetcher ()
-startResolving name ver = do
-  putStrsLn ["Resolving ", name, " version ", renderSV ver]
-  modify $ \s ->
-    s {currentlyResolving = HS.insert (name, ver) $ currentlyResolving s}
-
-finishResolving :: Name -> SemVer -> NpmFetcher ()
-finishResolving name ver = do
-  modify $ \s ->
-    s {currentlyResolving = HS.delete (name, ver) $ currentlyResolving s}
-  putStrsLn ["Finished resolving ", name, " ", renderSV ver]
-
-resolveVersionInfo :: VersionInfo -> NpmFetcher SemVer
-resolveVersionInfo versionInfo = do
-  let name = viName versionInfo
-      version = viVersion versionInfo
-      ctx = concat ["When resolving package ", name, ", version ", version]
-  inContext ctx $ do
-    version <- case parseSemVer $ viVersion versionInfo of
-      Left err -> throwErrorC ["Invalid semver in versionInfo object ",
-                                   viVersion versionInfo,
-                                   " Due to: ", pack $ show err]
-      Right v -> return v
-    HS.member (name, version) <$> gets currentlyResolving >>= \case
-      True -> do putStrsLn ["Warning: cycle detected"]
-                 return version
-      False -> do
-        let recurOn deptype deps = map H.fromList $ do
-              let depList = H.toList $ deps versionInfo
-              when (length depList > 0) $
-                putStrsLn ["Found ", deptype, ": ", pack (show depList)]
-              res <- map catMaybes $ forM depList $ \(depName, depRange) -> do
-                HS.member depName <$> gets knownProblematicPackages >>= \case
-                  True -> do
-                    putStrsLn ["WARNING: ", name, " is a broken package"]
-                    return Nothing
-                  False -> do
-                    depVersion <- resolveNpmVersionRange depName depRange
-                    return $ Just (depName, depVersion)
-              return res
-        -- We need to recur into the package's dependencies.
-        -- To prevent the cycles, we store which packages we're currently
-        -- resolving.
-        startResolving name version
-        deps <- recurOn "Dependencies" viDependencies
-        devDeps <- gets getDevDeps >>= \case
-          True -> recurOn "Dev dependencies" viDevDependencies
-          False -> return mempty
-        finishResolving name version
-        let dist = case viDist versionInfo of
-              Nothing -> error "Version information did not include dist"
-              Just d -> d
-        -- Store this version's info.
-        addResolvedPkg name version $ ResolvedPkg {
-            rpName = name,
-            rpVersion = version,
-            rpDistInfo = dist,
-            rpMeta = viMeta versionInfo,
-            rpDependencies = deps,
-            rpDevDependencies = devDeps
-          }
-        return version
-
--- | Resolves a dependency given a name and version range.
-_resolveDep :: Name -> SemVerRange -> NpmFetcher SemVer
-_resolveDep name range = do
-  let ctx = concat ["When resolving dependency ", name, " (",
-                    pack $ show range, ")"]
-  inContext ctx $ do
-    pInfo <- getPackageInfo name
-    versionInfo <- bestMatchFromRecord range $ piVersions pInfo
-    resolveVersionInfo versionInfo
-
-resolveByTag :: Name -> Name -> NpmFetcher SemVer
-resolveByTag tag pkgName = do
-  pInfo <- getPackageInfo pkgName
-  case H.lookup tag $ piTags pInfo of
-    Nothing -> throwErrorC ["Package ", pkgName, " has no tag '", tag, "'"]
-    Just version -> case H.lookup version $ piVersions pInfo of
-      Nothing -> throwErrorC ["Tag '", tag, "' refers to version '", version,
-                              "' but no such version exists for package ",
-                              pack $ show pkgName]
-      Just versionInfo -> resolveVersionInfo versionInfo
-
-parseURIs :: [Text] -> [URI]
-parseURIs rawUris = map p $! rawUris where
-  p txt = case parseURI $ unpack txt of
-            Nothing -> errorC ["Invalid URI: ", txt]
-            Just uri -> uri
-
-startState :: PackageMap PreExistingPackage
-           -> [Text]
-           -> Maybe Text
-           -> NpmFetcherState
-startState existing registries token = do
-  NpmFetcherState {
-      registries = parseURIs registries,
-      githubAuthToken = token,
-      resolved = mapPM toFullyDefined existing,
-      pkgInfos = mempty,
-      currentlyResolving = mempty,
-      knownProblematicPackages = HS.fromList ["websocket-server"],
-      indentLevel = 0,
-      getDevDeps = False
-    }
-
--- | Read NPM registry from env or use default.
-getRegistries :: IO [Text]
-getRegistries = do
-  let npmreg = "https://registry.npmjs.org/"
-  others <- shelly $ silently $ do
-    get_env "ADDITIONAL_NPM_REGISTRIES" >>= \case
-      Nothing -> return []
-      Just regs -> return $ T.words regs
-  return (others `snoc` npmreg)
-
--- | Read github auth token from env or use none.
-getToken :: IO (Maybe Text)
-getToken = shelly $ silently $ get_env "GITHUB_TOKEN"
-
-runIt :: NpmFetcher a -> IO (a, NpmFetcherState)
-runIt x = do
-  state <- startState mempty <$> getRegistries <*> getToken
-  runItWith state x
-
-runItWith :: NpmFetcherState -> NpmFetcher a -> IO (a, NpmFetcherState)
-runItWith state x = do
-  runStateT (runExceptT x) state >>= \case
-    (Left elist, _) -> error $ "\n" <> (unpack $ render elist)
-    (Right x, state) -> return (x, state)
-
-getPkg :: Name -- ^ Name of package to get.
-       -> PackageMap PreExistingPackage -- ^ Set of pre-existing packages.
-       -> Maybe Text -- ^ A possible github token.
-       -> IO (PackageMap FullyDefinedPackage) -- ^ Set of fully defined packages.
-getPkg name existing token = do
-  let range = Gt (0, 0, 0)
-  state <- startState existing <$> getRegistries <*> pure token
-  (_, finalState) <- runItWith state (resolveDep name range)
-  return (resolved finalState)
diff --git a/src/NixFromNpm/NpmTypes.hs b/src/NixFromNpm/NpmTypes.hs
deleted file mode 100644
--- a/src/NixFromNpm/NpmTypes.hs
+++ /dev/null
@@ -1,108 +0,0 @@
-{-# LANGUAGE NoImplicitPrelude #-}
-{-# LANGUAGE LambdaCase #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-module NixFromNpm.NpmTypes where
-
-import Data.Aeson
-import Data.Aeson.Types (Parser, typeMismatch)
-import Data.HashMap.Strict (HashMap)
-import qualified Data.HashMap.Strict as H
-
-import NixFromNpm.Common
-import NixFromNpm.SemVer
-import NixFromNpm.NpmVersion
-import NixFromNpm.Parsers.NpmVersion
-import NixFromNpm.Parsers.SemVer
-
-data PackageInfo = PackageInfo {
-  piVersions :: Record VersionInfo,
-  piTags :: Record Name
-} deriving (Show, Eq)
-
-data PackageMeta = PackageMeta {
-  pmDescription :: Maybe Text
-} deriving (Show, Eq)
-
-data VersionInfo = VersionInfo {
-  viDependencies :: Record NpmVersionRange,
-  viDevDependencies :: Record NpmVersionRange,
-  viDist :: Maybe DistInfo, -- not present if in a package.json file.
-  viMain :: Maybe Text,
-  viName :: Text,
-  viHasTest :: Bool,
-  viMeta :: PackageMeta,
-  viVersion :: Text
-} deriving (Show, Eq)
-
--- | Distribution info from NPM. Tells us the URL and hash of a tarball.
-data DistInfo = DistInfo {
-  diUrl :: Text,
-  diShasum :: Text
-} deriving (Show, Eq)
-
-data ResolvedPkg = ResolvedPkg {
-  rpName :: Name,
-  rpVersion :: SemVer,
-  rpDistInfo :: DistInfo,
-  rpMeta :: PackageMeta,
-  rpDependencies :: Record SemVer,
-  rpDevDependencies :: Record SemVer
-} deriving (Show, Eq)
-
-instance Semigroup PackageInfo where
-  PackageInfo vs ts <> PackageInfo vs' ts' =
-    PackageInfo (vs <> vs') (ts <> ts')
-
-instance Monoid PackageInfo where
-  mempty = PackageInfo mempty mempty
-  mappend = (<>)
-
-instance FromJSON VersionInfo where
-  parseJSON = getObject "version info" >=> \o -> do
-    dependencies <- getDict "dependencies" o
-    devDependencies <- getDict "devDependencies" o
-    dist <- o .:? "dist"
-    name <- o .: "name"
-    main <- o .:? "main"
-    version <- o .: "version"
-    packageMeta <- fmap PackageMeta $ o .:? "description"
-    scripts :: Record Value <- getDict "scripts" o
-    return $ VersionInfo {
-      viDependencies = dependencies,
-      viDevDependencies = devDependencies,
-      viDist = dist,
-      viMain = main,
-      viName = name,
-      viHasTest = H.member "test" scripts,
-      viMeta = packageMeta,
-      viVersion = version
-    }
-
-instance FromJSON SemVerRange where
-  parseJSON v = case v of
-    String s -> case parseSemVerRange s of
-      Left err -> typeMismatch ("valid semantic version (got " <> show v <> ")") v
-      Right range -> return range
-    _ -> typeMismatch "string" v
-
-instance FromJSON PackageInfo where
-  parseJSON = getObject "package info" >=> \o -> do
-    vs <- getDict "versions" o
-    tags <- getDict "dist-tags" o
-    return $ PackageInfo vs tags
-
-instance FromJSON DistInfo where
-  parseJSON = getObject "dist info" >=> \o -> do
-    tarball <- o .: "tarball"
-    shasum <- o .: "shasum"
-    return $ DistInfo tarball shasum
-
--- | Gets a hashmap from an object, or otherwise returns an empty hashmap.
-getDict :: (FromJSON a) => Text -> Object -> Parser (HashMap Text a)
-getDict key o = mapM parseJSON =<< (o .:? key .!= mempty)
-
-getObject :: String -> Value -> Parser (HashMap Text Value)
-getObject _ (Object o) = return o
-getObject msg v =
-  typeMismatch ("object (got " <> show v <> ", message " <> msg <> ")") v
diff --git a/src/NixFromNpm/NpmVersion.hs b/src/NixFromNpm/NpmVersion.hs
deleted file mode 100644
--- a/src/NixFromNpm/NpmVersion.hs
+++ /dev/null
@@ -1,17 +0,0 @@
-{-# LANGUAGE NoImplicitPrelude #-}
-{-# LANGUAGE LambdaCase #-}
-{-# LANGUAGE OverloadedStrings #-}
-module NixFromNpm.NpmVersion where
-
-import NixFromNpm.Common
-import NixFromNpm.SemVer
-
-data GitSource = Github | Bitbucket | Gist | GitLab deriving (Show, Eq)
-
-data NpmVersionRange
-  = SemVerRange SemVerRange
-  | Tag Name
-  | NpmUri URI
-  | GitId GitSource Name Name (Maybe Name)
-  | LocalPath FilePath
-  deriving (Show, Eq)
diff --git a/src/NixFromNpm/Options.hs b/src/NixFromNpm/Options.hs
deleted file mode 100644
--- a/src/NixFromNpm/Options.hs
+++ /dev/null
@@ -1,73 +0,0 @@
-{-# LANGUAGE NoImplicitPrelude #-}
-{-# LANGUAGE OverloadedStrings #-}
-module NixFromNpm.Options where
-
-import Options.Applicative
-
-import NixFromNpm.Common hiding ((<>))
-
--- | Various options we have available for nixfromnpm. As of right now,
--- most of these are unimplemented.
-data NixFromNpmOptions = NixFromNpmOptions {
-  nfnoPkgNames :: [Name],       -- ^ Names of packages to build.
-  nfnoPkgPaths :: [Text],       -- ^ Paths to package.jsons to build.
-  nfnoOutputPath :: Text,       -- ^ Path to output built expressions to.
-  nfnoNoCache :: Bool,          -- ^ Build all expressions from scratch.
-  nfnoExtendPaths :: [Text],    -- ^ Extend existing expressions.
-  nfnoTest :: Bool,             -- ^ Fetch only; don't write expressions.
-  nfnoRegistries :: [Text],     -- ^ List of registries to query.
-  nfnoTimeout :: Int,           -- ^ Number of seconds after which to timeout.
-  nfnoGithubToken :: Maybe Text -- ^ Github authentication token.
-} deriving (Show, Eq)
-
-textOption :: Mod OptionFields String -> Parser Text
-textOption opts = pack <$> strOption opts
-
-pOptions :: Maybe Text -> Parser NixFromNpmOptions
-pOptions githubToken = NixFromNpmOptions
-    <$> many (textOption packageName)
-    <*> many (textOption packageFile)
-    <*> textOption outputDir
-    <*> noCache
-    <*> extendPaths
-    <*> isTest
-    <*> liftA2 snoc registries (pure "https://registry.npmjs.org")
-    <*> timeout
-    <*> token
-  where
-    packageName = (short 'p'
-                   <> long "package"
-                   <> metavar "PACKAGENAME"
-                   <> help "Package to generate expression for")
-    packageFile = (short 'f'
-                   <> long "file"
-                   <> metavar "PACKAGEFILE"
-                   <> help "Path to package.json to generate expression for")
-    outputDir = (short 'o'
-                 <> long "output"
-                 <> metavar "DIRECTORY"
-                 <> help "Directory to output expressions to")
-    noCache = switch (long "no-cache"
-                      <> help "Build all expressions from scratch")
-    extendHelp = "Use expressions at PATH called NAME"
-    extendPaths = many (textOption (long "extend"
-                                    <> short 'E'
-                                    <> metavar "NAME=PATH"
-                                    <> help extendHelp))
-    isTest = switch (long "test"
-                     <> help "Don't write expressions; just test")
-    timeout = option auto (long "timeout"
-                           <> metavar "SECONDS"
-                           <> help "Time requests out after SECONDS seconds"
-                           <> value 10)
-    registries :: Parser [Text]
-    registries = many $ textOption (long "registry"
-                                    <> short 'R'
-                                    <> metavar "REGISTRY"
-                                    <> help "NPM registry to query")
-    tokenHelp = ("Token to use for github access (also can be set with " <>
-                 "GITHUB_TOKEN environment variable)")
-    token = (Just <$> textOption (long "github-token"
-                                  <> metavar "TOKEN"
-                                  <> help tokenHelp))
-            <|> pure githubToken
diff --git a/src/NixFromNpm/Parsers/Common.hs b/src/NixFromNpm/Parsers/Common.hs
deleted file mode 100644
--- a/src/NixFromNpm/Parsers/Common.hs
+++ /dev/null
@@ -1,49 +0,0 @@
-{-# LANGUAGE NoImplicitPrelude #-}
-{-# LANGUAGE LambdaCase #-}
-{-# LANGUAGE OverloadedStrings #-}
-module NixFromNpm.Parsers.Common (
-    module Text.Parsec,
-    module NixFromNpm.Common,
-    Parser,
-    parse, parseFull, spaces, spaces1, sstring, schar, lexeme, pInt
-  ) where
-
-import qualified Prelude as P
-import Text.Parsec hiding (many, (<|>), spaces, parse, State, uncons)
-import qualified Text.Parsec as Parsec
-
-import NixFromNpm.Common hiding (try)
-import NixFromNpm.SemVer
-
-type Parser = ParsecT String () Identity
-
--- | Given a parser and a string, attempts to parse the string.
-parse :: Parser a -> Text -> Either ParseError a
-parse p = Parsec.parse p "" . unpack
-
-parseFull :: Parser a -> Text -> Either ParseError a
-parseFull p = Parsec.parse (p <* eof) "" . unpack
-
--- | Consumes any spaces (not other whitespace).
-spaces :: Parser String
-spaces = many $ char ' '
-
--- | Consumes at least one space (not other whitespace).
-spaces1 :: Parser String
-spaces1 = many1 $ char ' '
-
--- | Parses the given string and any trailing spaces.
-sstring :: String -> Parser String
-sstring = lexeme . string
-
--- | Parses the given character and any trailing spaces.
-schar :: Char -> Parser Char
-schar = lexeme . char
-
--- | Parses `p` and any trailing spaces.
-lexeme :: Parser a -> Parser a
-lexeme p = p <* spaces
-
--- | Parses an integer.
-pInt :: Parser Int
-pInt = lexeme $ P.read <$> many1 digit
diff --git a/src/NixFromNpm/Parsers/NpmVersion.hs b/src/NixFromNpm/Parsers/NpmVersion.hs
deleted file mode 100644
--- a/src/NixFromNpm/Parsers/NpmVersion.hs
+++ /dev/null
@@ -1,79 +0,0 @@
-{-# LANGUAGE LambdaCase #-}
-{-# LANGUAGE NoImplicitPrelude #-}
-{-# LANGUAGE OverloadedStrings #-}
--- | Tools for parsing NPM version range indicators.
-module NixFromNpm.Parsers.NpmVersion where
-
-import Data.Aeson
-import qualified Data.Aeson.Types as DAT
-
-import NixFromNpm.SemVer
-import NixFromNpm.Parsers.Common
-import NixFromNpm.Parsers.SemVer
-import NixFromNpm.NpmVersion
-
-pUri :: Parser NpmVersionRange
-pUri = try $ fmap NpmUri $ do
-  parseURI <$> many anyChar >>= \case
-    Nothing -> unexpected "Not a valid URI"
-    Just uri -> case uriScheme uri of
-      "git:" -> return uri
-      "git+http:" -> return uri
-      "git+https:" -> return uri
-      "http:" -> return uri
-      "https:" -> return uri
-      scheme -> unexpected ("Unknown URI scheme " <> scheme)
-
-pGitId :: Parser NpmVersionRange
-pGitId = try $ do
-  let sources = choice $ map (try . sstring) ["github", "gitlab", "gist",
-                                              "bitbucket"]
-  source <- optionMaybe (sources <* char ':') >>= \case
-    Just "github" -> return Github
-    Just "gitlab" -> return GitLab
-    Just "bitbucket" -> return Bitbucket
-    Just "gist" -> return Gist
-    Nothing -> return Github
-  account <- many1 $ noneOf ":/"
-  char '/'
-  repo <- many1 $ noneOf "#"
-  ref <- optionMaybe $ char '#' *> (pack <$> many1 anyChar)
-  return $ GitId source (pack account) (pack repo) ref
-
-pLocalPath :: Parser NpmVersionRange
-pLocalPath = LocalPath . fromText . pack <$> do
-  -- The string must start with one of these prefixes.
-  lookAhead $ choice $ map string ["/", "./", "../", "~/"]
-  many anyChar
-
-pEmptyString :: Parser NpmVersionRange
-pEmptyString = try $ do
-  filter (/= ' ') <$> many anyChar >>= \case
-    [] -> return $ SemVerRange $ Geq (0, 0, 0)
-    _ -> unexpected "Not an empty string"
-
-pTag :: Parser NpmVersionRange
-pTag = do
-  filter (/= ' ') <$> many anyChar >>= \case
-    [] -> unexpected "empty string, not a tag"
-    tag -> return $ Tag $ pack tag
-
-pNpmVersionRange :: Parser NpmVersionRange
-pNpmVersionRange = choice [pEmptyString,
-                           SemVerRange <$> pSemVerRange,
-                           pUri,
-                           pGitId,
-                           pLocalPath,
-                           pTag]
-
-parseNpmVersionRange :: Text -> Either ParseError NpmVersionRange
-parseNpmVersionRange = parse pNpmVersionRange
-
-instance FromJSON NpmVersionRange where
-  parseJSON v = case v of
-    String s -> case parseNpmVersionRange s of
-      Left err -> DAT.typeMismatch
-                    ("valid NPM version (got " <> show v <> ")"
-                     <> " Error: " <> show err) v
-      Right range -> return range
-    _ -> DAT.typeMismatch "string" v
diff --git a/src/NixFromNpm/Parsers/SemVer.hs b/src/NixFromNpm/Parsers/SemVer.hs
deleted file mode 100644
--- a/src/NixFromNpm/Parsers/SemVer.hs
+++ /dev/null
@@ -1,87 +0,0 @@
-{-# LANGUAGE NoImplicitPrelude #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE LambdaCase #-}
-module NixFromNpm.Parsers.SemVer (
-    parseSemVer, parseSemVerRange, pSemVerRange, pSemVer
-  ) where
-
-import qualified Prelude as P
-import NixFromNpm.Parsers.Common
-import NixFromNpm.SemVer
-
--- | Parse a string as a version range, or return an error.
-parseSemVerRange :: Text -> Either ParseError SemVerRange
-parseSemVerRange = parse pSemVerRange
-
--- | Parse a string as an explicit version, or return an error.
-parseSemVer :: Text -> Either ParseError SemVer
-parseSemVer = parse pSemVer
-
--- | Parses a semantic version.
-pSemVer :: Parser SemVer
-pSemVer = wildcardToSemver <$> pWildCard
-
-pVersionComp :: Parser SemVerRange
-pVersionComp = do
-  comparator <- cmp
-  ver <- pSemVer
-  let func = case comparator of {"=" -> Eq; ">" -> Gt; "<" -> Lt;
-                                 ">=" -> Geq; "<=" -> Leq; "==" -> Eq}
-  return $ func ver
-
--- | Parses a comparison operator.
-cmp :: Parser String
-cmp = choice $ fmap (try . sstring) [">=", "<=", ">", "<", "==", "="]
-
--- | Parses versions with an explicit range qualifier (gt, lt, etc).
-pSemVerRangeSingle :: Parser SemVerRange
-pSemVerRangeSingle = choice [
-    wildcardToRange <$> pWildCard,
-    tildeToRange <$> pTildeRange,
-    caratToRange <$> pCaratRange,
-    pVersionComp
-  ]
-
--- | Parses semantic version ranges joined with Ands and Ors.
-pJoinedSemVerRange :: Parser SemVerRange
-pJoinedSemVerRange = do
-  first <- pSemVerRangeSingle
-  option first $ do
-    lookAhead (sstring "||" <|> cmp) >>= \case
-      "||" -> Or first <$> (sstring "||" *> pJoinedSemVerRange)
-      _ -> And first <$> pJoinedSemVerRange
-
--- | Parses a hyphenated range.
-pHyphen :: Parser SemVerRange
-pHyphen = hyphenatedRange <$> pWildCard <*> (sstring "-" *> pWildCard)
-
--- | Parses a "wildcard" (which is a possibly partial semantic version).
-pWildCard :: Parser Wildcard
-pWildCard = try $ do
-  let seps = choice $ map sstring ["x", "X", "*"]
-  let bound = choice [seps *> pure Nothing, Just <$> pInt]
-  let stripNothings [Nothing] = []
-      stripNothings (Just x:xs) = x : stripNothings xs
-  takeWhile isJust <$> sepBy1 bound (sstring ".") >>= \case
-    [] -> return Any
-    [Just n] -> return $ One n
-    [Just n, Just m] -> return $ Two n m
-    [Just n, Just m, Just o] -> return $ Three n m o
-    w -> unexpected ("Invalid version " ++ show w)
-
--- | Parses a tilde range (~1.2.3).
-pTildeRange :: Parser Wildcard
-pTildeRange = do
-  sstring "~"
-  -- For some reason, including the following operators after
-  -- a tilde is valid, but seems to have no effect.
-  optional $ choice [try $ sstring ">=", sstring ">", sstring "="]
-  pWildCard
-
--- | Parses a carat range (^1.2.3).
-pCaratRange :: Parser Wildcard
-pCaratRange = sstring "^" *> pWildCard
-
--- | Top-level parser. Parses a semantic version range.
-pSemVerRange :: Parser SemVerRange
-pSemVerRange = try pHyphen <|> pJoinedSemVerRange
diff --git a/src/NixFromNpm/SemVer.hs b/src/NixFromNpm/SemVer.hs
deleted file mode 100644
--- a/src/NixFromNpm/SemVer.hs
+++ /dev/null
@@ -1,117 +0,0 @@
-{-# LANGUAGE NoImplicitPrelude #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE LambdaCase #-}
-module NixFromNpm.SemVer where
-
-import qualified Prelude as P
-import Data.Text (Text)
-import qualified Data.Text as T
-import Data.Aeson.Parser
-import Data.Aeson
-import Data.Aeson.Types (typeMismatch)
-
-import NixFromNpm.Common
-
-type SemVer = (Int, Int, Int)
-
--- | A partially specified semantic version. Implicitly defines
--- a range of acceptable versions, as seen in @wildcardToRange@.
-data Wildcard = Any
-              | One Int
-              | Two Int Int
-              | Three Int Int Int
-              deriving (Show, Eq)
-
-data SemVerRange
-  = Eq SemVer
-  | Gt SemVer
-  | Lt SemVer
-  | Geq SemVer
-  | Leq SemVer
-  | And SemVerRange SemVerRange
-  | Or SemVerRange SemVerRange
-  deriving (Eq)
-
-renderSV :: SemVer -> Text
-renderSV (x, y, z) = pack (renderSV' (x, y, z))
-
-renderSV' :: SemVer -> String
-renderSV' (x, y, z) = show x <> "." <> show y <> "." <> show z
-
-instance Show SemVerRange where
-  show = \case
-    Eq sv -> "=" <> renderSV' sv
-    Gt sv -> ">" <> renderSV' sv
-    Lt sv -> "<" <> renderSV' sv
-    Geq sv -> ">=" <> renderSV' sv
-    Leq sv -> "<=" <> renderSV' sv
-    And svr1 svr2 -> show svr1 <> " " <> show svr2
-    Or svr1 svr2 -> show svr1 <> " || " <> show svr2
-
--- | Returns whether a given semantic version matches a range.
-matches :: SemVerRange -> SemVer -> Bool
-matches range ver = case range of
-  Eq sv -> ver == sv
-  Gt sv -> ver > sv
-  Lt sv -> ver < sv
-  Geq sv -> ver >= sv
-  Leq sv -> ver <= sv
-  And sv1 sv2 -> matches sv1 ver && matches sv2 ver
-  Or sv1 sv2 -> matches sv1 ver || matches sv2 ver
-
--- | Gets the highest-matching semver in a range.
-bestMatch :: SemVerRange -> [SemVer] -> Either String SemVer
-bestMatch range vs = case filter (matches range) vs of
-  [] -> Left "No matching versions"
-  vs -> Right $ maximum vs
-
--- | Fills in zeros in a wildcard.
-wildcardToSemver :: Wildcard -> SemVer
-wildcardToSemver Any = (0, 0, 0)
-wildcardToSemver (One n) = (n, 0, 0)
-wildcardToSemver (Two n m) = (n, m, 0)
-wildcardToSemver (Three n m o) = (n, m, o)
-
--- | Translates a wildcard (partially specified version) to a range.
--- Ex: 2 := >=2.0.0 <3.0.0
--- Ex: 1.2.x := 1.2 := >=1.2.0 <1.3.0
-wildcardToRange :: Wildcard -> SemVerRange
-wildcardToRange = \case
-  Any -> Geq (0, 0, 0)
-  One n -> Geq (n, 0, 0) `And` Lt (n+1, 0, 0)
-  Two n m -> Geq (n, m, 0) `And` Lt (n, m + 1, 0)
-  Three n m o -> Eq (n, m, o)
-
--- | Translates a ~wildcard to a range.
--- Ex: ~1.2.3 := >=1.2.3 <1.(2+1).0 := >=1.2.3 <1.3.0
-tildeToRange :: Wildcard -> SemVerRange
-tildeToRange = \case
-  Any -> tildeToRange (Three 0 0 0)
-  One n -> tildeToRange (Three n 0 0)
-  Two n m -> tildeToRange (Three n m 0)
-  Three n m o -> And (Geq (n, m, o)) (Lt (n, m + 1, 0))
-
--- | Translates a ^wildcard to a range.
--- Ex: ^1.2.x := >=1.2.0 <2.0.0
-caratToRange :: Wildcard -> SemVerRange
-caratToRange = \case
-  One n -> And (Geq (n, 0, 0)) (Lt (n+1, 0, 0))
-  Two n m -> And (Geq (n, m, 0)) (Lt (n+1, 0, 0))
-  Three 0 0 n -> Eq (0, 0, n)
-  Three 0 n m -> And (Geq (0, n, m)) (Lt (0, n + 1, 0))
-  Three n m o -> And (Geq (n, m, o)) (Lt (n+1, 0, 0))
-
--- | Translates two hyphenated wildcards to an actual range.
--- Ex: 1.2.3 - 2.3.4 := >=1.2.3 <=2.3.4
--- Ex: 1.2 - 2.3.4 := >=1.2.0 <=2.3.4
--- Ex: 1.2.3 - 2 := >=1.2.3 <3.0.0
-hyphenatedRange :: Wildcard -> Wildcard -> SemVerRange
-hyphenatedRange wc1 wc2 = And sv1 sv2 where
-  sv1 = case wc1 of Any -> Geq (0, 0, 0)
-                    One n -> Geq (n, 0, 0)
-                    Two n m -> Geq (n, m, 0)
-                    Three n m o -> Geq (n, m, o)
-  sv2 = case wc2 of Any -> Geq (0, 0, 0) -- Refers to "any version"
-                    One n -> Lt (n+1, 0, 0)
-                    Two n m -> Lt (n, m + 1, 0)
-                    Three n m o -> Leq (n, m, o)
diff --git a/test/Spec.hs b/test/Spec.hs
deleted file mode 100644
--- a/test/Spec.hs
+++ /dev/null
@@ -1,1 +0,0 @@
-{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
diff --git a/top_packages.txt b/top_packages.txt
new file mode 100644
--- /dev/null
+++ b/top_packages.txt
@@ -0,0 +1,108 @@
+lodash
+async
+request
+underscore
+express
+commander
+debug
+chalk
+q
+bluebird
+mkdirp
+colors
+through2
+coffee-script
+moment
+yeoman-generator
+glob
+gulp-util
+optimist
+minimist
+cheerio
+node-uuid
+fs-extra
+body-parser
+react
+jade
+jquery
+socket.io
+redis
+winston
+yosay
+uglify-js
+handlebars
+rimraf
+semver
+gulp
+yargs
+extend
+through
+mongodb
+mime
+underscore.string
+mongoose
+xml2js
+grunt
+ejs
+superagent
+mocha
+marked
+js-yaml
+connect
+shelljs
+object-assign
+xtend
+browserify
+inquirer
+ember-cli-babel
+aws-sdk
+promise
+minimatch
+ws
+prompt
+mysql
+less
+morgan
+cookie-parser
+uuid
+chai
+inherits
+event-stream
+babel-runtime
+when
+readable-stream
+babel
+concat-stream
+esprima
+es6-promise
+jsdom
+nan
+co
+qs
+stylus
+joi
+open
+gulp-rename
+nodemailer
+backbone
+path
+validator
+chokidar
+npm
+wrench
+cli-color
+bunyan
+nconf
+socket.io-client
+nopt
+mustache
+classnames
+pkginfo
+clone
+postcss
+express-session
+hoek
+ncp
+pg
+cli-table
+iconv-lite
