diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2017 Profpatsch <mail@profpatsch.de>
+MIT LICENSE
+
+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/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/src/Yarn/Lock.hs b/src/Yarn/Lock.hs
new file mode 100644
--- /dev/null
+++ b/src/Yarn/Lock.hs
@@ -0,0 +1,199 @@
+{-# LANGUAGE NoImplicitPrelude #-}
+{-|
+Module : Yarn.Lock
+Description : Parser & Types for yarn.lock files
+Maintainer : Profpatsch
+Stability : experimental
+
+The <https://yarnpkg.com/ Yarn package manager> improves on npm
+in that it writes @yarn.lock@ files that contain a complete
+version resolution of all dependencies. This way a deterministic
+deployment can be guaranteed.
+
+This module provides a parser for @yarn.lock@ files.
+-}
+module Yarn.Lock
+( Lockfile, PackageKey(..), Package(..), RemoteFile(..)
+, PackageEntry, PackageList
+, Yarn.Lock.parse
+-- | = Parsers
+, lockfile
+, packageListToLockfile, packageList
+, packageEntry, packageKeys, packageKey, package
+) where
+
+import Protolude hiding (try)
+import Data.String (String)
+import Text.Megaparsec as MP
+import Text.Megaparsec.Text
+import qualified Data.Text as T
+
+import qualified Data.Map.Strict as M
+
+-- | Yarn lockfile.
+type Lockfile = M.Map PackageKey Package
+
+-- | Key that indexes package for a specific version.
+data PackageKey = PackageKey
+  { name      :: Text -- ^ package name
+  , npmSemver :: Text -- ^ semver string
+  } deriving (Show, Eq, Ord)
+
+-- | The actual package with dependencies and download link.
+data Package = Package
+  { version              :: Text         -- ^ resolved, specific version
+  , resolved             :: RemoteFile   -- ^ download link w/ hash
+  , dependencies         :: [PackageKey] -- ^ list of dependencies
+  , optionalDependencies :: [PackageKey] -- ^ list of optional dependencies
+  } deriving (Eq, Show)
+
+-- | A package download link.
+data RemoteFile = RemoteFile
+  { url     :: Text
+  , sha1sum :: Text }
+  deriving (Eq, Show)
+
+-- | A entry as it appears in the yarn.lock representation.
+type PackageEntry = ([PackageKey], Package)
+-- | Convenience alias.
+type PackageList = [PackageEntry]
+
+-- | Convenience function that converts errors to Text.
+--
+-- The actual parsers are below.
+parse :: Text -- ^ name of source file
+      -> Text -- ^ input for parser
+      -> Either Text Lockfile
+parse src inp = first (T.pack . parseErrorPretty)
+              $ MP.parse lockfile (T.unpack src) inp
+
+-- TODO: actually use somehow (apart from manual testing)
+-- The yarn.lock file should resolve each packageKey exactly once.
+--
+-- No pkgname/semver combination should appear twice. That means
+-- the lengths of the converted map and the list lists need to match.
+-- prop_LockfileSameAmountOfKeys :: PackageList -> Bool
+-- prop_LockfileSameAmountOfKeys pl = length (packageListToLockfile pl)
+--                                    == length (concatMap fst pl)
+
+
+-- HALP, I don’t know how to parser.
+-- It appears to be a more general format which somewhat resembles yaml.
+-- The code below conflates the format & the semantics of yarn.lock files.
+-- It should be separated sometime, to make parsing easier.
+
+-- | Convenience function that applies @packageListToLockfile@.
+lockfile :: Parser Lockfile
+lockfile = packageListToLockfile <$> packageList
+
+-- | The yarn.lock file is basically a hashmap with multi-keyed entries.
+--
+-- This should press it into our Lockfile Map.
+packageListToLockfile :: PackageList -> Lockfile
+packageListToLockfile = foldl' go mempty
+  where go lf (keys, pkg) = foldl' (\lf' key' -> M.insert key' pkg lf') lf keys
+
+-- | Parse a complete yarn.lock into exaclty the same representation.
+--
+-- You can apply @packageListToLockfile@ to make it usable.
+packageList :: Parser PackageList
+packageList = many $ (skipMany (comment <|> eol)) *> packageEntry
+                where comment = char '#' *> manyTill anyChar eol
+
+-- | A single PackageEntry.
+--
+-- @
+-- handlebars@^4.0.4:
+--   version "4.0.6"
+--   resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.6.tgz#2ce4484850537f9c97a8026d5399b935c4ed4ed7"
+--   dependencies:
+--     async "^1.4.0"
+--     optimist "^0.6.1"
+--     source-map "^0.4.4"
+--   optionalDependencies:
+--     uglify-js "^2.6"
+-- @
+packageEntry :: Parser PackageEntry
+packageEntry = (,) <$> packageKeys <*> package <?> "package entry"
+
+-- | The list of PackageKeys that index the same Package
+--
+-- @
+-- align-text@^0.1.1, align-text@^0.1.3:\\n
+-- @
+packageKeys :: Parser [PackageKey]
+packageKeys = sepBy1 packageKey (string ", ") <* (char ':') <* eol <?> "package keys"
+
+-- | A packageKey is @\<package-name\>\@\<semver\>@;
+--
+-- If the semver contains spaces, it is also quoted with @"@.
+packageKey :: Parser PackageKey
+packageKey = label "package key" $ inString pkgKey <|> pkgKey
+  where
+    pkgKey = PackageKey
+      -- everything until the version, sep is @
+      <$> (someTextUntilSep '@' <?> "package name part of package key")
+      -- a version is anything but the , (used for seperating package keys)
+      -- or : (used to close the packageKeys line)
+      -- could be more specific (version is semver), but I’m lazy
+      <*> (someText (noneOf "\",:") <?> "semver part of package key")
+
+-- | Parses the content fields of a package.
+package :: Parser Package
+package = Package
+-- TODO: order shouldn’t matter, horrible indentation scheme
+  <$> (indent 2 $ key "version" stringText)
+  <*> (indent 2 $ key "resolved" remoteFile)
+  <*> (maybe [] identity <$> optional (indent 2 $ dependencyEntries "dependencies"))
+  <*> (maybe [] identity <$> optional (indent 2 $ dependencyEntries "optionalDependencies"))
+
+
+-- internal parsers
+
+-- | the “resolved”-field contains the link and the hash
+remoteFile :: Parser RemoteFile
+remoteFile = label "file link with hash" $ RemoteFile
+  <$> someTextUntilSep '#'
+  <*> stringText
+
+-- | dependency field of a package
+dependencyEntries :: String -> Parser [PackageKey]
+dependencyEntries key' = label (key' <>" field") $ do
+  _ <- string (key' <>":") <* eol
+  -- TODO: cool indentation handling
+  some (indent 4 dep)
+  where
+    -- It’s a bit like a key below, but the value of the key is not known.
+    -- Here’s where the format should get its own AST, but I’m too lazy right now.
+    dep = PackageKey <$> someTextUntilSep ' ' <*> inString stringText <* eol <?> "a dependency entry"
+
+-- | A key-value pair, separated by space. The value is enclosed in "".
+--
+-- The given parser is used to parse the value and should not parse ".
+key :: String -> Parser a -> Parser a
+key name' val = label ("key " <> name') $
+  string name' *> char ' ' *> inString val  <* eol
+
+
+-- text versions of parsers & helpers
+
+someText :: Parser Char -> Parser Text
+someText c = T.pack <$> some c
+
+-- | parse everything as inside a string
+-- TODO: this breaks the 'between' abstraction, can it be avoided somehow?
+inString :: Parser a -> Parser a
+inString = between (char '"') (char '"')
+
+-- | function to annotate text inside strings (which should never parse ")
+--   symptom of the broken 'between' abstraction
+stringText :: Parser Text
+stringText = someText (noneOf "\"") <?> "non-empty text without \""
+
+-- | parse some text until seperator is reached
+someTextUntilSep :: Char -> Parser Text
+someTextUntilSep sep = T.pack <$> someTill anyChar (char sep)
+
+-- | intend by @i@ spaces
+indent :: Int -> Parser a -> Parser a
+indent i p = try $ count i (char ' ') *> p
diff --git a/yarn-lock.cabal b/yarn-lock.cabal
new file mode 100644
--- /dev/null
+++ b/yarn-lock.cabal
@@ -0,0 +1,35 @@
+-- This file has been generated from package.yaml by hpack version 0.17.0.
+--
+-- see: https://github.com/sol/hpack
+
+name:           yarn-lock
+version:        0.1.0
+synopsis:       Represent and parse yarn.lock files
+description:    Types and parser for the lock file format of the npm successor yarn.
+category:       Data
+homepage:       https://github.com/Profpatsch/yaml-lock#readme
+bug-reports:    https://github.com/Profpatsch/yaml-lock/issues
+author:         Profpatsch
+maintainer:     mail@profpatsch.de
+license:        MIT
+license-file:   LICENSE
+build-type:     Simple
+cabal-version:  >= 1.10
+
+source-repository head
+  type: git
+  location: https://github.com/Profpatsch/yaml-lock
+
+library
+  hs-source-dirs:
+      src
+  ghc-options: -Wall
+  build-depends:
+      base == 4.*
+    , containers
+    , text
+    , megaparsec == 5.*
+    , protolude >= 0.1
+  exposed-modules:
+      Yarn.Lock
+  default-language: Haskell2010
