packages feed

yarn2nix 0.5.0 → 0.7.0

raw patch · 19 files changed

+706/−411 lines, 19 filesdep +transformersdep −eitherdep ~data-fixdep ~hnixdep ~optparse-applicativenew-component:exe:node-package-toolPVP ok

version bump matches the API change (PVP)

Dependencies added: transformers

Dependencies removed: either

Dependency ranges changed: data-fix, hnix, optparse-applicative, process, protolude, tasty, tasty-hunit, tasty-quickcheck, yarn-lock

API changes (from Hackage documentation)

+ Distribution.Nixpkgs.Nodejs.Utils: packageKeyNameToSymbol :: PackageKeyName -> Text
+ Nix.Expr.Additions: inheritStatic :: [Text] -> Binding e
- Distribution.Nodejs.Package: WrongType :: Text -> Text -> Warning
+ Distribution.Nodejs.Package: WrongType :: Text -> Maybe Text -> Warning
- Distribution.Nodejs.Package: [wrongTypeDefault] :: Warning -> Text
+ Distribution.Nodejs.Package: [wrongTypeDefault] :: Warning -> Maybe Text

Files

+ NodePackageTool.hs view
@@ -0,0 +1,170 @@+{-# LANGUAGE RecordWildCards, NoImplicitPrelude, LambdaCase, FlexibleContexts, NamedFieldPuns, OverloadedStrings #-}+import Protolude+import qualified Data.Text.IO as TIO+import qualified Control.Monad.Except as ExcT+import qualified Control.Exception as Exc+import qualified System.IO.Error as IOErr+import qualified Data.ByteString.Lazy as BL+import qualified Data.HashMap.Lazy as HML+import qualified System.FilePath as FP+import qualified System.Directory as Dir+import qualified System.Posix.Files as PosixFiles+import Options.Applicative++import qualified Distribution.Nodejs.Package as NP++data Args+  = Args+    { argsPackageDir :: FilePath+    , argsMode :: Mode }++data Mode+  = LinkBin+    { linkBinTargetDir :: FilePath }+  | SetBinExecFlag++args :: Parser Args+args = subparser+    (  command "link-bin"+       (info (modeCommands linkBinSubcommands)+         (progDesc "link package dependecies’ bin files"))+    <> command "set-bin-exec-flag"+       (info (modeCommands setBinExecFlagSubcommands)+         (progDesc "make all bin scripts executable")) )+  where+    modeCommands modeSubcommands = Args+      <$> strOption+        ( long "package"+        <> metavar "FOLDER"+        <> help "folder with the node package" )+      <*> modeSubcommands+    linkBinSubcommands = LinkBin+      <$> strOption+        ( long "to"+        <> metavar "LINK_TARGET"+        <> help "folder to link to (absolute or relative from package folder)" )+    setBinExecFlagSubcommands = pure SetBinExecFlag+  ++type ErrorLogger = ExceptT [Char] IO++-- | Print a warning to stdout.+warn :: [Text] -> ErrorLogger ()+warn ws = for_ ws $ \w -> liftIO $ TIO.hPutStrLn stderr ("Warning: " <> w)++-- | On Exception rethrow with annotation.+tryIOMsg :: ([Char] -> [Char]) -> IO a -> ErrorLogger a+tryIOMsg errAnn = ExcT.withExceptT (errAnn . Exc.displayException) . tryIO+++main :: IO ()+main = execParser (info (args <**> helper)+                    (progDesc "Tool for various node package maintenance tasks"))+       >>= realMain++realMain :: Args -> IO ()+realMain Args{..} = do+  -- basic sanity checks+  let packageJsonPath = argsPackageDir FP.</> "package.json"+  unlessM (Dir.doesFileExist packageJsonPath)+    $ die $ toS $ packageJsonPath <> " does not exist."+  case argsMode of+    LinkBin{..} -> do+      unlessM (Dir.doesDirectoryExist linkBinTargetDir)+        $ die $ toS $ linkBinTargetDir <> " is not a directory."+    _ -> pass++  -- parse & decode & run logic+  runExceptT+    (tryRead packageJsonPath >>= tryDecode packageJsonPath >>= go)+    >>= \case+      (Left err) -> die $ toS $ "ERROR: " <> err+      (Right _) -> pass++  where+    tryRead :: FilePath -> ErrorLogger BL.ByteString+    tryRead fp = tryIOMsg exc $ BL.readFile fp+      where exc e = fp <> " cannot be read:\n" <> e+    tryDecode :: FilePath -> BL.ByteString -> ExceptT [Char] IO NP.Package+    tryDecode fp fileBs = do+      (pkg, warnings) <- NP.unLoggingPackage+                      <$> ExcT.ExceptT (pure $ first exc $ NP.decode fileBs)+      warn $ fmap ((\w -> toS fp <> ": " <> w) . NP.formatWarning) warnings+      pure pkg+      where exc e = fp <> " cannot be decoded\n" <> toS e++    tryAccess :: IO a -> IO (Maybe a)+    tryAccess io =+      hush <$> tryJust+        (\e -> guard (IOErr.isDoesNotExistError e ||+                      IOErr.isPermissionError e))+          io++    qte s = "\"" <> s <> "\""++    go :: NP.Package -> ErrorLogger ()+    go NP.Package{bin} = do+     binFiles <- readBinFiles bin+     for_ binFiles $ case argsMode of+        -- Link all dependency binaries to their target folder+        LinkBin{..} -> linkBin linkBinTargetDir+        -- Set the executable flag of all package binaries+        SetBinExecFlag -> setBinExecFlag . snd++    -- | Read the binary files and return their names & paths.+    readBinFiles :: NP.Bin -> ErrorLogger [(Text, FilePath)]+    readBinFiles bin = case bin of+      -- files with names how they should be linked+      (NP.BinFiles bs) -> pure $ HML.toList bs+      -- a whole folder where everything should be linked+      (NP.BinFolder bf) -> do+        dirM <- liftIO $ tryAccess (Dir.listDirectory bf)+        case dirM of+          Nothing -> do+            warn ["Binary folder " <> toS (qte bf) <> " could not be accessed."]+            pure []+          (Just dir) ->+            pure $ fmap (\f -> (toS f, bf FP.</> f)) dir++    -- | Canonicalize the path.+    canon :: FilePath -> ErrorLogger FilePath+    canon fp = tryIOMsg+      (\e -> "Couldn’t canonicalize path " <> qte fp <> ": " <> e)+      (Dir.canonicalizePath fp)++    -- | Canonicalize relative to our package+    -- and ensure that the relative path is not outside.+    canonPkg :: FilePath -> ErrorLogger FilePath+    canonPkg relPath = do+      pkgDir <- canon argsPackageDir+      resPath <- canon $ argsPackageDir FP.</> relPath+      when (not $ pkgDir `isPrefixOf` resPath)+        $ throwError $ mconcat+          [ "The link to executable file "+          , qte relPath+          , " lies outside of the package folder!\n"+          , "That’s a security risk, aborting." ]+      pure resPath++    -- | Link a binary file to @targetDir/name@.+    -- @relBinPath@ is relative from the package dir.+    linkBin :: FilePath -> (Text, FilePath) -> ErrorLogger ()+    linkBin targetDir_ (name, relBinPath) = do+      binPath <- canonPkg relBinPath+      targetDir <- canon targetDir_+      tryIOMsg+        (\e -> "Symlink could not be created: " <> e)+        (PosixFiles.createSymbolicLink binPath $ targetDir FP.</> toS name)++    -- | Set executable flag of the file.+    setBinExecFlag :: FilePath -> ErrorLogger ()+    setBinExecFlag file_ = do+      file <- canonPkg file_+      res <- liftIO $ tryAccess $ do+        perm <- Dir.getPermissions file+        Dir.setPermissions file+          $ Dir.setOwnerExecutable True perm+      case res of+        Nothing ->+          warn ["Cannot set executable bit on " <> toS file]+        Just () -> pass
README.md view
@@ -19,6 +19,7 @@ - Extremely fast. - Nice code that can be easily extended, new repositories introduced, adapt to new versions of the `yarn.lock` format. - Comes with a [nix library][nix-lib] that uses the power of overlays to make overriding dependencies possible.+- POWERED BY [HNIX](https://github.com/haskell-nix/hnix)™ since before it was cool.  Probably a few more. @@ -55,12 +56,50 @@  [nix-lib]: ./nix-lib/default.nix -## Building+## Building `yarn2nix`  ``` $ nix-build $ result/bin/yarn2nix ```++## Using the generated nix files to build a project++**Note:** This is a temporary interface. Ideally, the library will be in nixpkgs+and yarn2nix will be callable from inside the build (so the resulting nix files+don’t have to be checked in).++Once you have the `yarn2nix` binary, use it to generate nix files for the+`yarn.lock` file and the `package.json`:++```shell+$ yarn2nix ./jsprotect/yarn.lock > npm-deps.nix+$ yarn2nix --template ./jsproject/package.json > npm-package.nix+```++Then use the library to assemble the generated files in a `default.nix`:++```nix+let+  nixpkgsPath = <nixpkgs>;+  pkgs = import nixpkgsPath {};+  nixLib = pkgs.callPackage /path/to/yarn2nix/nix-lib {+    # WARNING (TODO): for now you need to use this checked out yarn2nix+    # because the upstream package (in haskellPackages) might have+    # broken dependencies (yarn-lock and yarn2nix are not in stackage)+    yarn2nix = import /path/to/yarn2nix { inherit nixpkgsPath; };+  };++in+  nixLib.buildNodePackage+    ( { src = nixLib.removePrefixes [ "node_modules" ] ./.; } //+      nixLib.callTemplate ./npm-package.nix+        (nixLib.buildNodeDeps (pkgs.callPackage ./npm-deps.nix {}))+```++Finally, run `nix-build`, and voilà, in `./result/` you find the project with+all its dependencies correctly linked to their corresponding `node_modules`+folder, recursively.  ## Development 
− SetupNodePackagePaths.hs
@@ -1,116 +0,0 @@-{-# LANGUAGE RecordWildCards, NoImplicitPrelude, LambdaCase, FlexibleContexts, NamedFieldPuns, OverloadedStrings #-}-import Protolude-import qualified Data.Text.IO as TIO-import qualified Control.Monad.Except as ExcT-import qualified Control.Exception as Exc-import qualified System.IO.Error as IOErr-import qualified Data.ByteString.Lazy as BL-import qualified Data.HashMap.Lazy as HML-import qualified System.FilePath as FP-import qualified System.Directory as Dir-import qualified System.Posix.Files as PosixFiles-import Options.Applicative--import qualified Distribution.Nodejs.Package as NP--data Args-  = Args { argMode :: Mode-         , argTargetDir :: FilePath-         , argPackageDir :: FilePath }--data Mode = BinMode--args :: Parser Args-args = subparser-    ( command "bin" (info (modeCommands BinMode)-                    (progDesc "link package bin files")) )-  where-    modeCommands mode = Args-      <$> pure mode-      <*> strOption-            ( long "to"-           <> metavar "LINK_TARGET"-           <> help "folder to link to (absolute or relative from package folder)" )-      <*> strOption-            ( long "package"-           <> metavar "FOLDER"-           <> help "folder with the node package" )-  --main :: IO ()-main = execParser (info (args <**> helper)-                    (progDesc "Link various files from npm packages to folders"))-       >>= realMain--type ErrorLogger = ExceptT [Char] IO--realMain :: Args -> IO ()-realMain Args{..} = do-  let packageJsonPath = argPackageDir FP.</> "package.json"-  unlessM (Dir.doesDirectoryExist argTargetDir)-    $ die $ argTargetDir <> " is not a directory."-  unlessM (Dir.doesFileExist packageJsonPath)-    $ die $ packageJsonPath <> " does not exist."--  runExceptT-    (tryRead packageJsonPath >>= tryDecode packageJsonPath >>= go)-    >>= \case-      (Left err) -> die $ "ERROR: " <> err-      (Right _) -> pass--  where-    tryIOMsg :: ([Char] -> [Char]) -> IO a -> ErrorLogger a-    tryIOMsg errAnn = ExcT.withExceptT (errAnn . Exc.displayException) . tryIO--    tryRead :: FilePath -> ErrorLogger BL.ByteString-    tryRead fp = tryIOMsg exc $ BL.readFile fp-      where exc e = fp <> " cannot be read:\n" <> e-    tryDecode :: FilePath -> BL.ByteString -> ExceptT [Char] IO NP.Package-    tryDecode fp fileBs = do-      (pkg, warnings) <- NP.unLoggingPackage-                      <$> ExcT.ExceptT (pure $ first exc $ NP.decode fileBs)-      warn $ fmap NP.formatWarning warnings-      pure pkg-      where exc e = fp <> " cannot be decoded\n" <> toS e--    qte s = "\"" <> s <> "\""-    warn :: [Text] -> ErrorLogger ()-    warn ws = for_ ws $ liftIO . TIO.hPutStrLn stderr--    go :: NP.Package -> ErrorLogger ()-    go NP.Package{bin} = case argMode of-      BinMode -> traverse_ linkBin =<< case bin of-        -- files with names how they should be linked-        (NP.BinFiles bs) -> pure $ HML.toList bs-        -- a whole folder where everything should be linked-        (NP.BinFolder bf) -> do-          dirE <- liftIO $ tryJust-            (\e -> guard (IOErr.isDoesNotExistError e ||-                          IOErr.isPermissionError e))-            (Dir.listDirectory bf)-          case dirE of-            (Left _) -> do-              warn ["Binary folder " <> toS (qte bf) <> " could not be accessed."]-              pure []-            (Right dir) ->-              pure $ fmap (\f -> (toS f, bf FP.</> f)) dir--    -- | Link a binary file to @targetDir/name@.-    -- @relBinPath@ is relative from the package dir.-    linkBin :: (Text, FilePath) -> ErrorLogger ()-    linkBin (name, relBinPath) = do-      let canon fp = tryIOMsg-            (\e -> "Couldn’t canonicalize path " <> qte fp <> ": " <> e)-            (Dir.canonicalizePath fp)-      pkgDir <- canon argPackageDir-      binPath <- canon $ argPackageDir FP.</> relBinPath-      targetDir <- canon argTargetDir-      when (not $ pkgDir `isPrefixOf` binPath)-        $ throwError $ mconcat-          [ "The link to executable file "-          , qte relBinPath-          , " lies outside of the package folder!\n"-          , "That’s a security risk, aborting." ]-      tryIOMsg-        (\e -> "Symlink could not be created: " <> e)-        (PosixFiles.createSymbolicLink binPath $ targetDir FP.</> toS name)
default.nix view
@@ -1,16 +1,31 @@-(import <nixpkgs> {+{ nixpkgsPath ? ./nixpkgs-pinned.nix }:+(import nixpkgsPath {   overlays = [(pkgs: oldpkgs: {     haskellPackages =       let nix-lib = pkgs.callPackage ./nix-lib {};       in oldpkgs.haskellPackages.override {-        overrides = self: super: {-          inherit (pkgs.callPackage ./nix-lib/yarn-lock.nix {} self super) yarn-lock;-          yarn2nix =-            let pkg = self.callPackage ./yarn2nix.nix {};-            in oldpkgs.haskell.lib.overrideCabal pkg (old: {-              src = nix-lib.removePrefixes [ "nix-lib" "dist" "result" ] ./.;-            });-        };+        overrides = oldpkgs.lib.composeExtensions+          (pkgs.callPackage ./nix-lib/old-version-dependencies.nix {})+          (self: super: {+            yarn2nix =+              let+                pkg = oldpkgs.haskell.lib.overrideCabal+                  (self.callPackage ./yarn2nix.nix {})+                  (old: {+                    prePatch = old.prePatch or "" + ''+                      ${oldpkgs.lib.getBin self.hpack}/bin/hpack+                      # we depend on the git prefetcher+                      substituteInPlace \+                        src/Distribution/Nixpkgs/Nodejs/ResolveLockfile.hs \+                        --replace '"nix-prefetch-git"' \+                          '"${pkgs.nix-prefetch-scripts}/bin/nix-prefetch-git"'+                    '';+                  });+              in oldpkgs.haskell.lib.overrideCabal pkg (old: {+                src = nix-lib.removePrefixes+                  [ "nix-lib" "dist" "result" "tests/nix-tests" ] ./.;+              });+          });       };   })]; 
nix-lib/buildNodePackage.nix view
@@ -1,8 +1,8 @@-{ stdenv, linkNodeDeps, nodejs }:-{ name # String+{ stdenv, linkNodeDeps, nodejs, yarn2nix }:+{ key # { scope: String, name: String } , version # String , src # Drv-, nodeBuildInputs # Listof { name : String, drv : Drv }+, nodeBuildInputs # Listof { key: { scope: String, name: String }, drv : Drv } , ... }@args:  # since we skip the build phase, pre and post will not work@@ -13,8 +13,15 @@  with stdenv.lib; -stdenv.mkDerivation ((removeAttrs args [ "nodeBuildInputs" ]) // {-  name = "${name}-${version}";+let+  # TODO: scope should be more structured somehow. :(+  packageName =+    if key.scope == ""+    then "${key.name}-${version}"+    else "${key.scope}-${key.name}-${version}";++in stdenv.mkDerivation ((removeAttrs args [ "key" "nodeBuildInputs" ]) // {+  name = packageName;   inherit version src;    buildInputs = [ nodejs ];@@ -33,11 +40,19 @@     # a npm package is just the tarball extracted to $out     cp -r . $out +    # the binaries should be executable (TODO: always on?)+    ${yarn2nix}/bin/node-package-tool \+      set-bin-exec-flag \+      --package $out+     # then a node_modules folder is created for all its dependencies     ${if nodeBuildInputs != []       then ''         rm -rf $out/node_modules-        ln -sT "${linkNodeDeps name nodeBuildInputs}" $out/node_modules+        ln -sT "${linkNodeDeps {+            name = packageName;+            dependencies = nodeBuildInputs;+          }}" $out/node_modules       '' else ""}      runHook postInstall
nix-lib/default.nix view
@@ -1,21 +1,49 @@-{ lib, pkgs }:+{ lib, pkgs+# TODO: temporary, to make overwriting yarn2nix easy+# TODO: remove static building once RPATHs are fixed+, yarn2nix ? pkgs.haskell.lib.justStaticExecutables+               pkgs.haskellPackages.yarn2nix+}:  let   # Build an attrset of node dependencies suitable for the `nodeBuildInputs`-  # argument of `buildNodePackage`. The input is the path of a dependency-  # file generated by the `yarn2nix` utility from a `yarn.lock` file,-  # in turn the output of the `yarn` tool for an npm package.-  buildNodeDeps = lockDotNix: lib.fix+  # argument of `buildNodePackage`. The input is an overlay+  # of node packages that call `_buildNodePackage`, like in the+  # files generated by `yarn2nix`.+  # It is possible to just call it with a generated file, like so:+  # `buildNodeDeps (pkgs.callPackage ./npm-deps.nix {})`+  # You can also use `lib.composeExtensions` to override packages+  # in the set:+  # ```+  # buildNodeDeps (lib.composeExtensions+  #   (pkgs.callPackage ./npm-deps.nix {})+  #   (self: super: { pkg = super.pkg.override {…}; }))+  # ```+  # TODO: should _buildNodePackage be fixed in here?+  buildNodeDeps = nodeDeps: lib.fix     (lib.extends-      (import lockDotNix { inherit (pkgs) fetchurl fetchgit; })+      nodeDeps       (self: {-        # wrap the invocation in the fix point, to construct the-        # list of { name, drv } needed by buildNodePackage+        # The actual function building our packages.+        # type: { key: String | { scope: String, name: String }+        #       , <other arguments of ./buildNodePackage.nix> }+        # Wraps the invocation in the fix point, to construct the+        # list of { key, drv } needed by buildNodePackage         # from the templates.         # It is basically a manual paramorphism, carrying parts of the-        # information of the previous layer (the original package name).-        _buildNodePackage = { name, ... }@args:-          { inherit name; drv = buildNodePackage args; };+        # information of the previous layer (the original package key).+        # TODO: move that function out of the package set+        #       and get nice self/super scoping right+        _buildNodePackage = { key, ... }@args:+          # To keep the generated files shorter, we allow keys to+          # be represented as strings if they have no scopes.+          # This is the only place where this is accepted,+          # but hacky nonetheless. Probably fix with above TODO.+          let key' = if builtins.isString key+                     then { scope = ""; name = key; }+                     else key;+          in { key = key';+               drv = buildNodePackage (args // { key = key'; }); };       }));    # Build a package template generated by the `yarn2nix --template`@@ -23,13 +51,11 @@   # template nix file, the second input is all node dependencies   # needed by the template, in the form generated by `buildNodeDeps`.   callTemplate = yarn2nixTemplate: allDeps:-    pkgs.callPackage yarn2nixTemplate {-      inherit buildNodePackage removePrefixes;-    } allDeps;+    pkgs.callPackage yarn2nixTemplate {} allDeps;     buildNodePackage = import ./buildNodePackage.nix {-    inherit linkNodeDeps;+    inherit linkNodeDeps yarn2nix;     inherit (pkgs) stdenv nodejs;   }; @@ -37,22 +63,40 @@   # by npm’s module system to call dependencies.   # Also link executables of all dependencies into `.bin`.   # TODO: copy manpages & docs as well-  # type: String -> ListOf { name: String, drv : Drv } -> Drv-  linkNodeDeps = name: packageDeps:-    pkgs.runCommand (name + "-node_modules") {} ''+  # type: { name: String+  #       , dependencies: ListOf { key: { scope: String, name: String }+  #                              , drv : Drv } }+  #       -> Drv+  linkNodeDeps = {name, dependencies}:+    pkgs.runCommand ("${name}-node_modules") {+      # This just creates a simple link farm, which should be pretty fast,+      # saving us from additional hydra requests for potentially hundreds+      # of packages.+      allowSubstitutes = false;+      # Also tell Hydra it’s not worth copying to a builder.+      preferLocalBuild = true;+    } ''       mkdir -p $out/.bin       ${lib.concatMapStringsSep "\n"-        (dep: ''-          echo "linking node dependency ${dep.name}"-          ln -sT ${dep.drv} "$out/${dep.name}"-          ${ # TODO: remove static building once RPATHs are fixed-             pkgs.haskell.lib.justStaticExecutables-               pkgs.haskellPackages.yarn2nix}/bin/setup-node-package-paths \-            bin \-            --to=$out/.bin \-            --package=$out/${dep.name}-        '')-        packageDeps}+        (dep:+          let+            hasScope = dep.key.scope != "";+            # scoped packages get another subdirectory for their scope (`@scope/`)+            parentfolder = if hasScope+              then "$out/@${dep.key.scope}"+              else "$out";+            subfolder = "${parentfolder}/${dep.key.name}";+          in ''+            echo "linking node dependency ${formatKey dep.key}"+            ${ # we need to create the scope folder, otherwise ln fails+               lib.optionalString hasScope ''mkdir "${parentfolder}"'' }+            ln -sT ${dep.drv} "${subfolder}"+            ${yarn2nix}/bin/node-package-tool \+              link-bin \+              --to=$out/.bin \+              --package=${subfolder}+          '')+        dependencies}     '';    # Filter out files/directories with one of the given prefix names@@ -65,6 +109,13 @@     in       builtins.filterSource (file: _: ! (hasAnyPrefix file)) path; +  # format a package key of { scope: String, name: String }+  formatKey = { scope, name }:+    if scope == ""+    then name+    else "@${scope}/${name}";+ in {-  inherit buildNodeDeps callTemplate removePrefixes;+  inherit buildNodeDeps linkNodeDeps buildNodePackage+          callTemplate removePrefixes; }
+ nix-lib/old-version-dependencies.nix view
@@ -0,0 +1,20 @@+{ lib, pkgs }:+self: super: {+  yarn-lock =+    super.mkDerivation rec {+      pname = "yarn-lock";+      version = "0.5.0";+      # src = ../../../haskell/yarn-lock;+      src = pkgs.fetchFromGitHub {+        owner = "Profpatsch";+        repo = "yarn-lock";+        rev = version;+        # rev = "61cc65a858db92e7c0bea861bf279f286c34bb81";+        sha256 = "007a7n1qqa7cp85bbw00yvkg2ikg1yzj1k8i9bav1bnbk6n7xp7s";+      };+      license = lib.licenses.mit;+      buildDepends = with self; [ megaparsec protolude tasty-hunit tasty-th either neat-interpolation tasty-quickcheck quickcheck-instances ];+      buildTools = [ self.hpack ];+      prePatch = ''hpack'';+    };+}
− nix-lib/yarn-lock.nix
@@ -1,19 +0,0 @@-{ lib, pkgs }:-self: super: {-  yarn-lock =-    super.mkDerivation rec {-      pname = "yarn-lock";-      version = "0.4.0";-      # src = ../../haskell/yarn-lock;-      src = pkgs.fetchFromGitHub {-        owner = "Profpatsch";-        repo = "yarn-lock";-        rev = version;-        sha256 = "0r5ipyfpdmg9n9zv7q1knrlypv9q2l1x24j1sgibxy411b7r62wv";-      };-      license = lib.licenses.mit;-      buildDepends = with self; [ megaparsec protolude tasty-hunit tasty-th either neat-interpolation tasty-quickcheck ];-      buildTools = [ self.hpack ];-      preConfigure = ''hpack'';-    };-}
shell.nix view
@@ -1,40 +1,39 @@ with import <nixpkgs> {}; (haskellPackages.override {-  overrides = (self: super: {-    inherit (pkgs.callPackage ./nix-lib/yarn-lock.nix {} self super)-      yarn-lock;--    my-pkg = let-      buildDepends = with self; [-        protolude-        yarn-lock-        hnix-        hpack-        aeson-        async-pool-        ansi-wl-pprint-        regex-tdfa-        regex-tdfa-text-        neat-interpolation-        tasty-th-        tasty-quickcheck-        tasty-hunit-        http-client-tls-        monad-par-      ];-      in super.mkDerivation {-        pname = "pkg-env";-        src = "/dev/null";-        version = "none";-        license = "none";-        inherit buildDepends;-        buildTools = with self; [-          ghcid-          cabal-install-          (hoogleLocal {-            packages = buildDepends;-          })+  overrides = lib.composeExtensions+    (pkgs.callPackage ./nix-lib/old-version-dependencies.nix {})+    (self: super: {+      my-pkg = let+        buildDepends = with self; [+          protolude+          yarn-lock+          hnix+          hpack+          aeson+          async-pool+          ansi-wl-pprint+          regex-tdfa+          regex-tdfa-text+          neat-interpolation+          tasty-th+          tasty-quickcheck+          tasty-hunit+          http-client-tls+          monad-par         ];-      };-    });+        in super.mkDerivation {+          pname = "pkg-env";+          src = "/dev/null";+          version = "none";+          license = "none";+          inherit buildDepends;+          buildTools = with self; [+            ghcid+            cabal-install+            (hoogleLocal {+              packages = buildDepends;+            })+          ];+        };+      }); }).my-pkg.env
src/Distribution/Nixpkgs/Nodejs/Cli.hs view
@@ -77,7 +77,7 @@         Left err -> die' ("Could not parse " <> toS path <> ":\n" <> show err)  die' :: Text -> IO a-die' err = putText err *> exitFailure+die' err = putErrText err *> exitFailure dieWithUsage :: Text -> IO () dieWithUsage err = die' (err <> "\n" <> usage) 
src/Distribution/Nixpkgs/Nodejs/FromPackage.hs view
@@ -18,21 +18,29 @@   depsToPkgKeys :: NP.Dependencies -> [YLT.PackageKey]-depsToPkgKeys = map (\(k, v) -> YLT.PackageKey k v) . HML.toList+depsToPkgKeys = map toPkgKey . HML.toList+  where+    toPkgKey (k, v) =+      YLT.PackageKey (parsePackageKeyName k) v +parsePackageKeyName :: Text -> YLT.PackageKeyName+parsePackageKeyName k =+  -- we don’t crash on a “wrong” package key to keep this+  -- code pure, but assume it’s a simple key instead.+  maybe (YLT.SimplePackageKey k) identity+    $ YLT.parsePackageKeyName k+ -- | generate a nix expression that translates your package.nix -- -- and can serve as template for manual adjustments genTemplate :: NP.Package -> NExpr genTemplate NP.Package{..} =-  simpleParamSet ["stdenv", "buildNodePackage", "filterSourcePrefixes"]+  -- reserved for possible future arguments (to prevent breakage)+  simpleParamSet []   ==> Param nodeDepsSym-  -- TODO: devDeps-  ==> ("buildNodePackage" @@ mkRecSet+  ==> (mkNonRecSet         [ "name" $= nameStr         , "version" $= mkStr version-        , "src" $= ("filterSourcePrefixes"-                     @@ mkList [ mkStr "node_modules" ] @@ "./.")         , "nodeBuildInputs"  $= (letE "a" (mkSym nodeDepsSym)                                   $ mkList (map (pkgDep "a") depPkgKeys))         , "meta"      $= (mkNonRecSet@@ -41,8 +49,16 @@           <> may "homepage" homepage)         ])   where-    depPkgKeys = depsToPkgKeys dependencies+    -- TODO: The devDependencies are only needed for the build+    -- and probably also only from packages not stemming from+    -- a npm registry (e.g. a git package). It would be cool+    -- if these dependencies were gone in the final output.+    -- See https://github.com/Profpatsch/yarn2nix/issues/5+    depPkgKeys = depsToPkgKeys (dependencies <> devDependencies)     pkgDep depsSym pk = mkSym depsSym !!. packageKeyToSymbol pk     nodeDepsSym = "allDeps"-    nameStr = mkStrQ [StrQ name, "-", AntiQ "version"]+    nameStr = mkStrQ [StrQ+      $ pkgKeyToName $ parsePackageKeyName name]+    pkgKeyToName (YLT.SimplePackageKey n) = n+    pkgKeyToName (YLT.ScopedPackageKey s n) = s <> "-" <> n     may k v = [k $= mkStr (fromMaybe mempty v)]
src/Distribution/Nixpkgs/Nodejs/OptimizedNixOutput.hs view
@@ -31,8 +31,8 @@ import qualified Data.List as List import qualified Data.List.NonEmpty as NE -import Nix.Expr (NExpr, ($=), (==>), (!.), (@@))-import Nix.Expr.Additions (($$=), (!!.))+import Nix.Expr (NExpr, ($=), (==>), (@@))+import Nix.Expr.Additions (($$=), (!!.), inheritStatic) import qualified Nix.Expr as N import qualified Nix.Expr.Additions as NA @@ -87,38 +87,53 @@  -- | Package definition needed for calling the build function. data PkgData a = PkgData-  { pkgDataName :: Text           -- ^ package name-  , pkgDataVersion :: Text        -- ^ package version-  , pkgDataUpstream :: a          -- ^ points to upstream-  , pkgDataHashSum :: Text        -- ^ the hash sum of the package-  , pkgDataDependencies :: [Text] -- ^ list of dependencies (as resolved nix symbols)+  { pkgDataName :: YLT.PackageKeyName -- ^ package name+  , pkgDataVersion :: Text            -- ^ package version+  , pkgDataUpstream :: a              -- ^ points to upstream+  , pkgDataHashSum :: Text            -- ^ the hash sum of the package+  , pkgDataDependencies :: [Text]     -- ^ list of dependencies (as resolved nix symbols)   } -+-- | Tuples of prefix string to registry registries :: [(Text, Registry)] registries =   [ ( yarnP     , Registry "yarn"         $ \n v -> [T yarnP, V n, T "/-/", V n, T "-", V v, T ".tgz"] )+  , ( npmjsP+    , Registry "npm"+        $ \n v -> [T npmjsP, V n, T "/-/", V n, T "-", V v, T ".tgz"] )+   ]   where     yarnP = "https://registry.yarnpkg.com/"+    npmjsP = "https://registry.npmjs.org/"  shortcuts :: M.Map [NSym] NSym shortcuts = M.fromList   [ (["self"], "s")   , (["registries", "yarn"], "y")+  , (["registries", "npm"], "n")   , (["nodeFilePackage"], "f")   , (["nodeGitPackage"], "g")   , (["identityRegistry"], "ir")+  , (["scopedName"], "sc")   ]  -- | Find out which registry the given 'YLT.Remote' shortens to.-recognizeRegistry :: Text -> Maybe Registry-recognizeRegistry fileUrl = snd <$> filterRegistry fileUrl+recognizeRegistry :: YLT.PackageKeyName -- ^ package name+                  -> Text -- ^ url to file+                  -> Maybe Registry+-- We don’t shorten scoped key names, because+-- they are handled specially by npm registries and+-- the URLs differ from other packages+recognizeRegistry (YLT.ScopedPackageKey{}) _ = Nothing+recognizeRegistry _ fileUrl = snd <$> foundRegistry   where     -- | Get registry by the prefix of the registry’s URL.-    filterRegistry url = find (\reg -> fst reg `T.isPrefixOf` url) registries+    foundRegistry = find predicate registries+    predicate :: (Text, Registry) -> Bool+    predicate reg = fst reg `T.isPrefixOf` fileUrl   -- | Convert a 'Res.ResolvedLockfile' to its final, nix-ready form.@@ -147,7 +162,8 @@         }       def = case YLT.remote pkg of         YLT.FileRemote{fileUrl} ->-          PkgDefFile $ pkgDataGeneric $ note fileUrl $ recognizeRegistry fileUrl+          PkgDefFile $ pkgDataGeneric $ note fileUrl+            $ recognizeRegistry defName fileUrl         YLT.GitRemote{gitRepoUrl, gitRev} ->           PkgDefGit $ pkgDataGeneric $ Git gitRepoUrl gitRev                  -- we don’t need another ref indirection@@ -169,8 +185,6 @@     yarn = n: v: "https://registry.yarnpkg.com/${n}/-/${n}-${v}.tgz";   }; -  sanitizePackageName = builtins.replaceStrings ["@" "/"] ["-" "-"];-   # We want each package definition to be one line, by putting   # the boilerplate into these functions for different remotes.   nodeFilePackage = …@@ -179,18 +193,23 @@   # an identity function for e.g. git repos or unknown registries   identityRegistry = url: _: _: url; +  # a way to pass through scoped package names+  scopedName = scope: name: { inherit scope name; }+   # shortcut section   s = self;   ir = identityRegistry;   f = nodeFilePackage;   g = nodeGitPackage;   y = registries.yarnpkg;+  sc = scopedName;  # the actual package definitions in {   "accepts@~1.3.3" = s."accepts@1.3.3";   "accepts@1.3.3" = f "accepts" "1.3.3" y "sha" [];+  "@types/accepts@1.3.3" = f (sc "types" "accepts") "1.3.3" y "sha" [];   "babel-core@^6.14.0" = s."babel-core@6.24.1";   "babel-core@6.24.1" = f "babel-core" "6.24.1" y "a0e457c58ebdbae575c9f8cd75127e93756435d8" [     s."accepts@~1.3.3"@@ -210,10 +229,13 @@     ==> N.Param "self" ==> N.Param "super"     ==> N.mkLets         (  [ "registries" $= N.mkNonRecSet (fmap (mkRegistry . snd) registries)-           , "sanitizePackageName" $= sanitizePackageName            , "nodeFilePackage" $= buildPkgFn            , "nodeGitPackage" $= buildPkgGitFn-           , "identityRegistry" $= NA.multiParam ["url", "_", "_"] "url" ]+           , "identityRegistry" $= NA.multiParam ["url", "_", "_"] "url"+           , "scopedName" $=+               (NA.multiParam ["scope", "name"]+                 $ N.mkNonRecSet [ inheritStatic ["scope", "name"] ])+           ]         <> fmap mkShortcut (M.toList shortcuts) )         (N.mkNonRecSet (map mkPkg $ M.toAscList packages))   where@@ -222,26 +244,18 @@      concatNSyms :: [NSym] -> NExpr     concatNSyms [] = panic "non-empty shortcut syms!"-    concatNSyms (l:ls) = foldl (!.) (N.mkSym $ unNSym l) (fmap unNSym ls)+    concatNSyms (l:ls) = foldl (!!.) (N.mkSym $ unNSym l) (fmap unNSym ls)     mkShortcut :: ([NSym], NSym) -> N.Binding NExpr     mkShortcut (nSyms, short) = unNSym short $= concatNSyms nSyms     -- | Try to shorten sym, otherwise use input.     shorten :: [NSym] -> NExpr     shorten s = maybe (concatNSyms s) (N.mkSym . unNSym) $ M.lookup s shortcuts--    -- | remove symbols not allowed in nix derivation names-    sanitizePackageName :: NExpr-    sanitizePackageName = "builtins" !. "replaceStrings"-                            @@ N.mkList [N.mkStr "@", N.mkStr "/"]-                            @@ N.mkList [N.mkStr "-", N.mkStr "-"]-     -- | Build function boilerplate the build functions share in common.     buildPkgFnGeneric :: [Text] -> NExpr -> NExpr     buildPkgFnGeneric additionalArguments srcNExpr =-      NA.multiParam (["name", "version"] <> additionalArguments <> ["deps"])+      NA.multiParam (["key", "version"] <> additionalArguments <> ["deps"])         $ ("super" !!. "_buildNodePackage") @@ N.mkNonRecSet-          [ "name" $= ("sanitizePackageName" @@ "name")-          , N.inherit $ map N.StaticKey ["version"]+          [ inheritStatic ["key", "version"]           , "src" $= srcNExpr           , "nodeBuildInputs" $= "deps" ]     -- | Building a 'YLT.FileRemote' package.@@ -249,20 +263,22 @@     buildPkgFn =       buildPkgFnGeneric ["registry", "sha1"]         ("fetchurl" @@ N.mkNonRecSet-          [ "url" $= ("registry" @@ "name" @@ "version")-          , N.inherit $ [N.StaticKey "sha1"] ])+          [ "url" $= ("registry" @@ "key" @@ "version")+          , inheritStatic ["sha1"] ])     -- | Building a 'YLT.GitRemote' package.     buildPkgGitFn :: NExpr     buildPkgGitFn =       buildPkgFnGeneric ["url", "rev", "sha256"]         ("fetchgit" @@ N.mkNonRecSet-          [ N.inherit $ map N.StaticKey ["url", "rev", "sha256"] ])+          [ inheritStatic ["url", "rev", "sha256"] ])      mkDefGeneric :: PkgData a -> NSym -> [NExpr] -> NExpr     mkDefGeneric PkgData{..} buildFnSym additionalArguments =       foldl' (@@) (shorten [buildFnSym])-        $ [  N.mkStr pkgDataName-          ,  N.mkStr pkgDataVersion ]+        $ [ case pkgDataName of+              YLT.SimplePackageKey n -> N.mkStr n+              YLT.ScopedPackageKey s n -> "sc" @@ N.mkStr s @@ N.mkStr n+          , N.mkStr pkgDataVersion ]           <> additionalArguments <>           [ N.mkList $ map (N.mkSym selfSym !!.) pkgDataDependencies ] 
src/Distribution/Nixpkgs/Nodejs/ResolveLockfile.hs view
@@ -11,7 +11,7 @@ ) where  import Protolude-import qualified Control.Monad.Trans.Either as E+import qualified Control.Monad.Trans.Except as E import qualified Data.List.NonEmpty as NE import qualified Data.MultiKeyedMap as MKM import qualified Data.Aeson as Aeson@@ -23,6 +23,9 @@  import qualified Yarn.Lock.Types as YLT +nixPrefetchGitPath :: FilePath+nixPrefetchGitPath = "nix-prefetch-git"+ maxFetchers :: Int maxFetchers = 5 @@ -42,7 +45,7 @@                       -> IO (Either (NE.NonEmpty Text) ResolvedLockfile) resolveLockfileStatus msgChan lf = Async.withTaskGroup maxFetchers $ \taskGroup -> do   job <- STM.atomically $ Async.mapReduce taskGroup-           $ fmap (\(ks, pkg) -> (:[]) <$> (E.runEitherT $ do+           $ fmap (\(ks, pkg) -> (:[]) <$> (E.runExceptT $ do                         liftIO $ writeChan msgChan (YLT.remote pkg)                         res <- resolve pkg                         pure (ks, res)))@@ -53,20 +56,20 @@     (_   , ys) -> pure $ Right $ MKM.fromList YLT.lockfileIkProxy ys    where-    resolve :: YLT.Package -> E.EitherT Text IO (Resolved YLT.Package)+    resolve :: YLT.Package -> E.ExceptT Text IO (Resolved YLT.Package)     resolve pkg = case YLT.remote pkg of       YLT.FileRemote{..} -> pure $ r fileSha1       YLT.GitRemote{..}  -> r <$> fetchFromGit gitRepoUrl gitRev       where         r sha = Resolved { hashSum = sha, resolved = pkg } -    fetchFromGit :: Text -> Text -> E.EitherT Text IO Text+    fetchFromGit :: Text -> Text -> E.ExceptT Text IO Text     fetchFromGit repo rev = do-      res <- liftIO $ Process.readProcessWithExitCode "nix-prefetch-git"+      res <- liftIO $ Process.readProcessWithExitCode nixPrefetchGitPath                ["--url", toS repo, "--rev", toS rev, "--hash", "sha256"] ""       case res of-        ((ExitFailure _), _, err) -> E.left $ toS err-        (ExitSuccess, out, _) -> E.hoistEither+        ((ExitFailure _), _, err) -> E.throwE $ toS err+        (ExitSuccess, out, _) -> E.ExceptT . pure           $ first (\decErr -> "parsing json output failed:\n"                     <> toS decErr <> "\nThe output was:\n" <> toS out)             $ do val <- Aeson.eitherDecode' (toS out)
src/Distribution/Nixpkgs/Nodejs/Utils.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE NoImplicitPrelude, OverloadedStrings, RecordWildCards #-}+{-# LANGUAGE NoImplicitPrelude, OverloadedStrings, RecordWildCards, LambdaCase #-} {-| Description: Misc utils -}@@ -8,4 +8,14 @@  -- | Representation of a PackageKey as nix attribute name. packageKeyToSymbol :: YLT.PackageKey -> Text-packageKeyToSymbol (YLT.PackageKey{..}) = name <> "@" <> npmVersionSpec+packageKeyToSymbol (YLT.PackageKey{..}) =+  packageKeyNameToSymbol name <> "@" <> npmVersionSpec+{-# INLINABLE packageKeyToSymbol #-}++-- | Representation of a PackageKeyName as nix attribute name.+packageKeyNameToSymbol :: YLT.PackageKeyName -> Text+packageKeyNameToSymbol = \case+  YLT.SimplePackageKey n -> n+  YLT.ScopedPackageKey scope n -> "@" <> scope <> "/" <> n+{-# INLINABLE packageKeyNameToSymbol #-}+
src/Distribution/Nodejs/Package.hs view
@@ -13,7 +13,7 @@ , Bin(..), Man(..), Dependencies ) where -import Protolude+import Protolude hiding (packageName) import Control.Monad (fail) import qualified Control.Monad.Writer.Lazy as WL import qualified Data.ByteString.Lazy as BL@@ -50,8 +50,9 @@ -- | Possible warnings from parsing. data Warning   = WrongType-  { wrongTypeField :: Text-  , wrongTypeDefault :: Text }+  { wrongTypeField :: Text -- ^ the field which has a wrong type+  , wrongTypeDefault :: Maybe Text -- ^ the default value, if used+  }   | PlainWarning Text  -- | The package’s executable files.@@ -71,23 +72,29 @@ -- | Dependencies of a package. type Dependencies = HML.HashMap Text Text +type Warn = WL.WriterT [Warning] AT.Parser+putWarning :: a -> Warning -> Warn a+putWarning a w = WL.writer (a, [w])+ -- | See https://github.com/npm/normalize-package-data for -- normalization steps used by npm itself. instance A.FromJSON LoggingPackage where   parseJSON = A.withObject "Package" $ \v -> fmap LoggingPackage . WL.runWriterT $ do     let-      l :: AT.Parser a -> WL.WriterT [Warning] AT.Parser a+      l :: AT.Parser a -> Warn a       l = WL.WriterT . fmap (\a -> (a, []))       tryWarn :: (AT.FromJSON a, Show a)-              => Text -> a -> WL.WriterT [Warning] AT.Parser a-      tryWarn field def = lift (v .:? field .!= def)-                          <|> WL.writer (def, [WrongType field (show def)])+              => Text -> a -> Warn a+      tryWarn field def =+        lift (v .:? field .!= def)+        <|> putWarning def (WrongType { wrongTypeField = field+                                       , wrongTypeDefault = Just (show def) })     name            <- l $ v .:  "name"     version         <- l $ v .:  "version"     description     <- tryWarn "description" Nothing     homepage        <- tryWarn "homepage" Nothing     private         <- tryWarn "private" False-    scripts         <- l $ v .:? "scripts" .!= mempty+    scripts         <- (parseMapText "scripts" =<< (tryWarn "scripts" mempty))     bin             <- parseBin name v     man             <- l $ parseMan name v     license         <- tryWarn "license" Nothing@@ -96,7 +103,17 @@     pure Package{..}     where -      parseBin :: Text -> AT.Object -> WL.WriterT [Warning] AT.Parser Bin+      parseMapText :: Text -> HML.HashMap Text AT.Value+                   -> Warn (HML.HashMap Text Text)+      parseMapText fieldPath val =+        HML.mapMaybe identity <$> HML.traverseWithKey tryParse val+        where+          tryParse :: Text -> A.Value -> Warn (Maybe Text)+          tryParse key el = lift (Just <$> AT.parseJSON el)+            <|> putWarning Nothing+                  (WrongType { wrongTypeField = fieldPath <> "." <> key+                             , wrongTypeDefault = Nothing })+      parseBin :: Text -> AT.Object -> Warn Bin       parseBin packageName v = do         -- check for existence of these fields         binVal <- lift $ optional $ v .: "bin"@@ -105,8 +122,8 @@         -- see npm documentation for more         case (binVal, dirBinVal) of           (Just _              , Just _) ->-            WL.writer (BinFiles mempty, [PlainWarning-              "`bin` and `directories.bin` must not exist at the same time."])+            putWarning (BinFiles mempty) $ PlainWarning+              "`bin` and `directories.bin` must not exist at the same time, skipping."           -- either "bin" is a direct path, then it’s linked to the package name           (Just (A.String path),      _) -> pure $ BinFiles             $ HML.singleton packageName (toS path)@@ -141,7 +158,13 @@  -- | Convert a @package.json@ parsing warning to plain text. formatWarning :: Warning -> Text-formatWarning = ("Warning: " <>) . \case-  (WrongType field def) ->-    "Field \"" <> field <> "\" has the wrong type. Defaulting to " <> def <> "."+formatWarning = \case+  WrongType{..} ->+       "Field \""+    <> wrongTypeField+    <> "\" has the wrong type. "+    <> (case wrongTypeDefault of+         Just def -> "Defaulting to " <> def+         Nothing  -> "Leaving it out")+    <> "."   (PlainWarning t) -> t
src/Nix/Expr/Additions.hs view
@@ -5,7 +5,7 @@ Nix generation helpers. No guarantee of stability (internal!). -} module Nix.Expr.Additions-( stringKey, ($$=), dynamicKey+( stringKey, ($$=), dynamicKey, inheritStatic , simpleParamSet, multiParam , (!!.) , StrQ(..), mkStrQ, mkStrQI@@ -24,7 +24,7 @@  -- | Make a binding, but have the key be a string, not symbol. stringKey :: Text -> NExpr -> Binding NExpr-stringKey k v = NamedVar [dynamicKey k] v+stringKey k v = NamedVar (pure $ dynamicKey k) v nullPos -- | Infix version of 'stringKey'. ($$=) :: Text -> NExpr -> Binding NExpr ($$=) = stringKey@@ -34,9 +34,13 @@ dynamicKey :: Text -> NKeyName NExpr dynamicKey k = DynamicKey $ Plain $ DoubleQuoted [Plain k] +-- | Inherit the given list of symbols.+inheritStatic :: [Text] -> Binding e+inheritStatic names = inherit (map StaticKey names) nullPos+ -- | shortcut to create a list of closed params, like @{ foo, bar, baz }:@ simpleParamSet :: [Text] -> Params NExpr-simpleParamSet = mkParamset . fmap (, Nothing)+simpleParamSet prms = mkParamset (fmap (, Nothing) prms) False  -- | shortcut to create a list of multiple params, like @a: b: c:@ multiParam :: [Text] -> NExpr -> NExpr@@ -49,7 +53,7 @@ (!!.) :: NExpr -> Text -> NExpr aset !!. k = Fix   $ NSelect aset-      [(if isPlainSymbol k then StaticKey else dynamicKey) k] Nothing+      (pure $ (if isPlainSymbol k then StaticKey else dynamicKey) k) Nothing   where     -- the nix lexer regex for IDs (symbols) is      -- [a-zA-Z\_][a-zA-Z0-9\_\'\-]*@@ -73,4 +77,4 @@ -- | Create a double-quoted string from a list of antiquotes/plain strings. mkStrQ = mkStrQtmpl DoubleQuoted -- | Create a single-quoted string from a list of antiquotes/plain strings.-mkStrQI = mkStrQtmpl Indented+mkStrQI = mkStrQtmpl (Indented 2)
tests/TestNpmjsPackage.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE OverloadedStrings, TemplateHaskell, QuasiQuotes, NoImplicitPrelude #-}+{-# LANGUAGE OverloadedStrings, TemplateHaskell, QuasiQuotes, NoImplicitPrelude, LambdaCase #-} module TestNpmjsPackage (tests) where  import Protolude@@ -21,34 +21,77 @@  case_binPaths :: Assertion case_binPaths = do-  parseSuccess bin (baseAnd [ ("bin", "./abc") ])-    >>= assertEqual "bin path" (NP.BinFiles $ HML.fromList-                           [ ("foopkg", "./abc") ])-  parseSuccess bin (baseAnd [ ("directories", A.object [("bin", "./abc")]) ])-    >>= assertEqual "bin path" (NP.BinFolder "./abc")-  parseSuccess bin (baseAnd [ ("bin", A.object-                           [ ("one", "./bin/one")-                           , ("two", "imhere") ]) ])-    >>= assertEqual "bin multiple" (NP.BinFiles $ HML.fromList-                           [ ("one", "./bin/one")-                           , ("two", "imhere") ])-  -- TODO: succeeds with warning; test whether the correct warnings are generated-  -- parseFailure bin (baseAnd [ ("bin", "foo")-  --                           , ("directories", A.object [("bin", "foo")]) ])-  parseSuccess bin (baseAnd [])-    >>= assertEqual "no bins" (NP.BinFiles mempty)-  where-    bin = fmap (NP.bin . fst . NP.unLoggingPackage) . A.parseJSON+  let+    parseWithWarningsZoom :: (Eq a, Show a)+                          => Text -> A.Value -> (NP.Package -> a) -> a+                          -> ([NP.Warning] -> Assertion)+                          -> Assertion+    parseWithWarningsZoom name got zoom want warningPred =+      NP.unLoggingPackage <$> parseSuccess got+      >>= \(val, warnings) -> do+              assertEqual (toS name) want (zoom val)+              warningPred warnings+    parseZoom name got zoom want =+      parseWithWarningsZoom name got zoom want (const $ pure ()) -parseSuccess :: (A.Value -> AT.Parser a) -> A.Value -> IO a-parseSuccess p v = case AT.parse p v of+    hasWarning :: (NP.Warning -> Bool) -> [NP.Warning] -> Assertion+    hasWarning warningPred = assertBool "no such warning!" . any warningPred+    wrongType field def = \case+      (NP.WrongType f d) -> field == f && def == d+      _ -> False+    plainWarning = \case+      (NP.PlainWarning _) -> True+      _ -> False++  parseZoom ".bin exists with files"+            (baseAnd [ ("bin", "./abc") ])+            NP.bin+            (NP.BinFiles $ HML.fromList [ ("foopkg", "./abc") ])++  parseZoom ".directories.bin exists with path"+            (baseAnd [ ("directories", A.object [("bin", "./abc")]) ])+            NP.bin+            (NP.BinFolder "./abc")++  parseZoom "multiple .bin files are parsed"+            (baseAnd [ ("bin", A.object+                       [ ("one", "./bin/one")+                       , ("two", "imhere") ]) ])+            NP.bin+            (NP.BinFiles $ HML.fromList+              [ ("one", "./bin/one")+              , ("two", "imhere") ])++  parseWithWarningsZoom "bin and directories.bin both exist"+                        (baseAnd [ ("bin", "foo")+                                 , ("directories", A.object+                                     [ ("bin", "foo") ]) ])+                        NP.bin+                        (NP.BinFiles mempty)+                        (hasWarning plainWarning)++  parseZoom "neither .bin nor .directories.bin exis"+            (baseAnd [])+            NP.bin+            (NP.BinFiles mempty)++  parseWithWarningsZoom ".scripts field has a wrong type"+                        (baseAnd [ ("scripts", A.object+                                   [ ("foo", A.object [])+                                   , ("bar", "imascript") ]) ])+                        NP.scripts+                        (HML.fromList [ ("bar", "imascript") ])+                        (hasWarning (wrongType "scripts.foo" Nothing))++parseSuccess :: (A.FromJSON a) => A.Value -> IO a+parseSuccess v = case A.fromJSON v of   (AT.Error err) -> assertFailure err >> panic "not reached"   (AT.Success a) -> pure a   -parseFailure :: Show a => (A.Value -> AT.Parser a) -> A.Value -> IO ()-parseFailure p v = case AT.parse p v of-  (AT.Error _) -> pass-  (AT.Success a) -> assertFailure $ "no parse" <> show a+-- parseFailure :: Show a => (A.Value -> AT.Parser a) -> A.Value -> IO ()+-- parseFailure p v = case AT.parse p v of+--   (AT.Error _) -> pass+--   (AT.Success a) -> assertFailure $ "no parse" <> show a  tests :: TestTree tests = $(testGroupGenerator)
yarn2nix.cabal view
@@ -1,9 +1,11 @@--- This file has been generated from package.yaml by hpack version 0.18.1.+-- This file has been generated from package.yaml by hpack version 0.27.0. -- -- see: https://github.com/sol/hpack+--+-- hash: 9f5e62ecebb812a5457199df157d56a8529dd64b80a8ea3c7fbbbf22d86627f3  name:           yarn2nix-version:        0.5.0+version:        0.7.0 synopsis:       Convert yarn.lock files to nix expressions description:    Convert @yarn.lock@ files to nix expressions. See @yarn2nix@ executable. Contains a nix library to call the generated nix files in @nix-lib/@. Library functions and module names might be restructured in the future. category:       Distribution, Nix@@ -21,7 +23,7 @@     LICENSE     nix-lib/buildNodePackage.nix     nix-lib/default.nix-    nix-lib/yarn-lock.nix+    nix-lib/old-version-dependencies.nix     README.md     shell.nix     yarn2nix.nix@@ -31,30 +33,6 @@   location: https://github.com/Profpatsch/yarn2nix  library-  hs-source-dirs:-      src-  ghc-options: -Wall-  build-depends:-      aeson >= 1.0 && < 1.3-    , ansi-wl-pprint == 0.6.*-    , async-pool == 0.9.*-    , base == 4.*-    , bytestring == 0.10.*-    , containers >= 0.5 && < 0.7-    , data-fix >= 0.0.7 && < 0.1-    , directory == 1.3.*-    , either == 4.4.*-    , filepath == 1.4.*-    , hnix == 0.3.*-    , mtl == 2.2.*-    , process == 1.4.*-    , protolude == 0.1.*-    , regex-tdfa == 1.2.*-    , regex-tdfa-text == 1.0.0.*-    , stm == 2.4.*-    , text == 1.2.*-    , unordered-containers == 0.2.*-    , yarn-lock == 0.4.*   exposed-modules:       Distribution.Nixpkgs.Nodejs.Cli       Distribution.Nixpkgs.Nodejs.FromPackage@@ -65,98 +43,126 @@       Nix.Expr.Additions   other-modules:       Paths_yarn2nix+  hs-source-dirs:+      src+  ghc-options: -Wall+  build-depends:+      aeson >=1.0 && <1.3+    , ansi-wl-pprint ==0.6.*+    , async-pool ==0.9.*+    , base ==4.*+    , bytestring ==0.10.*+    , containers >=0.5 && <0.7+    , data-fix ==0.0.7 || ==0.2.0+    , directory ==1.3.*+    , filepath ==1.4.*+    , hnix ==0.5.*+    , mtl ==2.2.*+    , process >=1.4+    , protolude ==0.2.*+    , regex-tdfa ==1.2.*+    , regex-tdfa-text ==1.0.0.*+    , stm ==2.4.*+    , text ==1.2.*+    , transformers ==0.5.*+    , unordered-containers ==0.2.*+    , yarn-lock ==0.5.*   default-language: Haskell2010 -executable setup-node-package-paths-  main-is: SetupNodePackagePaths.hs+executable node-package-tool+  main-is: NodePackageTool.hs+  other-modules:+      Paths_yarn2nix   ghc-options: -Wall   build-depends:-      aeson >= 1.0 && < 1.3-    , ansi-wl-pprint == 0.6.*-    , async-pool == 0.9.*-    , base == 4.*-    , bytestring == 0.10.*-    , containers >= 0.5 && < 0.7-    , data-fix >= 0.0.7 && < 0.1-    , directory == 1.3.*-    , either == 4.4.*-    , filepath == 1.4.*-    , hnix == 0.3.*-    , mtl == 2.2.*-    , process == 1.4.*-    , protolude == 0.1.*-    , regex-tdfa == 1.2.*-    , regex-tdfa-text == 1.0.0.*-    , stm == 2.4.*-    , text == 1.2.*-    , unordered-containers == 0.2.*-    , yarn-lock == 0.4.*-    , optparse-applicative == 0.13.*-    , unix == 2.7.*+      aeson >=1.0 && <1.3+    , ansi-wl-pprint ==0.6.*+    , async-pool ==0.9.*+    , base ==4.*+    , bytestring ==0.10.*+    , containers >=0.5 && <0.7+    , data-fix ==0.0.7 || ==0.2.0+    , directory ==1.3.*+    , filepath ==1.4.*+    , hnix ==0.5.*+    , mtl ==2.2.*+    , optparse-applicative >=0.13 && <0.15+    , process >=1.4+    , protolude ==0.2.*+    , regex-tdfa ==1.2.*+    , regex-tdfa-text ==1.0.0.*+    , stm ==2.4.*+    , text ==1.2.*+    , transformers ==0.5.*+    , unix ==2.7.*+    , unordered-containers ==0.2.*+    , yarn-lock ==0.5.*     , yarn2nix   default-language: Haskell2010  executable yarn2nix   main-is: Main.hs+  other-modules:+      Paths_yarn2nix   ghc-options: -Wall   build-depends:-      aeson >= 1.0 && < 1.3-    , ansi-wl-pprint == 0.6.*-    , async-pool == 0.9.*-    , base == 4.*-    , bytestring == 0.10.*-    , containers >= 0.5 && < 0.7-    , data-fix >= 0.0.7 && < 0.1-    , directory == 1.3.*-    , either == 4.4.*-    , filepath == 1.4.*-    , hnix == 0.3.*-    , mtl == 2.2.*-    , process == 1.4.*-    , protolude == 0.1.*-    , regex-tdfa == 1.2.*-    , regex-tdfa-text == 1.0.0.*-    , stm == 2.4.*-    , text == 1.2.*-    , unordered-containers == 0.2.*-    , yarn-lock == 0.4.*+      aeson >=1.0 && <1.3+    , ansi-wl-pprint ==0.6.*+    , async-pool ==0.9.*+    , base ==4.*+    , bytestring ==0.10.*+    , containers >=0.5 && <0.7+    , data-fix ==0.0.7 || ==0.2.0+    , directory ==1.3.*+    , filepath ==1.4.*+    , hnix ==0.5.*+    , mtl ==2.2.*+    , process >=1.4+    , protolude ==0.2.*+    , regex-tdfa ==1.2.*+    , regex-tdfa-text ==1.0.0.*+    , stm ==2.4.*+    , text ==1.2.*+    , transformers ==0.5.*+    , unordered-containers ==0.2.*+    , yarn-lock ==0.5.*     , yarn2nix   default-language: Haskell2010  test-suite yarn2nix-tests   type: exitcode-stdio-1.0   main-is: Test.hs+  other-modules:+      TestNpmjsPackage+      Paths_yarn2nix   hs-source-dirs:       tests   ghc-options: -Wall   build-depends:-      aeson >= 1.0 && < 1.3-    , ansi-wl-pprint == 0.6.*-    , async-pool == 0.9.*-    , base == 4.*-    , bytestring == 0.10.*-    , containers >= 0.5 && < 0.7-    , data-fix >= 0.0.7 && < 0.1-    , directory == 1.3.*-    , either == 4.4.*-    , filepath == 1.4.*-    , hnix == 0.3.*-    , mtl == 2.2.*-    , process == 1.4.*-    , protolude == 0.1.*-    , regex-tdfa == 1.2.*-    , regex-tdfa-text == 1.0.0.*-    , stm == 2.4.*-    , text == 1.2.*-    , unordered-containers == 0.2.*-    , yarn-lock == 0.4.*-    , neat-interpolation == 0.3.*-    , protolude == 0.1.*-    , tasty == 0.11.*-    , tasty-hunit == 0.9.*-    , tasty-quickcheck == 0.8.*-    , tasty-th == 0.1.7.*+      aeson >=1.0 && <1.3+    , ansi-wl-pprint ==0.6.*+    , async-pool ==0.9.*+    , base ==4.*+    , bytestring ==0.10.*+    , containers >=0.5 && <0.7+    , data-fix ==0.0.7 || ==0.2.0+    , directory ==1.3.*+    , filepath ==1.4.*+    , hnix ==0.5.*+    , mtl ==2.2.*+    , neat-interpolation ==0.3.*+    , process >=1.4+    , protolude ==0.2.*+    , regex-tdfa ==1.2.*+    , regex-tdfa-text ==1.0.0.*+    , stm ==2.4.*+    , tasty >=0.11 && <1.2+    , tasty-hunit >=0.9 && <0.11+    , tasty-quickcheck ==0.8.* || ==0.9.*+    , tasty-th ==0.1.7.*+    , text ==1.2.*+    , transformers ==0.5.*+    , unordered-containers ==0.2.*+    , yarn-lock ==0.5.*     , yarn2nix-  other-modules:-      TestNpmjsPackage   default-language: Haskell2010
yarn2nix.nix view
@@ -8,7 +8,7 @@ }: mkDerivation {   pname = "yarn2nix";-  version = "0.1.0";+  version = "0.6.0";   src = ./.;   isLibrary = true;   isExecutable = true;