This repository has been archived by the owner on Apr 1, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 7
Add support for Yarn v2 projects #244
Merged
Changes from all commits
Commits
Show all changes
32 commits
Select commit
Hold shift + click to select a range
1cdef17
WIP - add parsers for locator and descriptor
cnr 2c8311f
WIP - define json/yaml parsers
cnr 5abb9cd
Use the best parser
cnr 12a3b26
Add LockfileV2 and Resolvers modules
cnr 342b503
Add resolvers and partial analysis logic
cnr 2bc3d06
Use real diagnostics
cnr 41ac122
Implement graph-building logic
cnr 653e59e
Add tar resolver
cnr 3bcac7b
use better message for unsupported locator
cnr 3d75f50
Add remaining unhandled resolvers
cnr 96f0118
format cabal file
cnr 23031f7
Add devdocs for yarn v2
cnr 2ba18c6
Add a couple of links to yarnv2 devdocs
cnr aa4070d
Add docs to LockfileV2
cnr eac91a2
Add docs to Resolvers
cnr 4c928cf
Mention hack for npm: protocol lookup
cnr 72dc874
Reorganize yarn strategy module layout
cnr b329091
Strategy.Npm: ignore directories and subdirectories when yarn.lock is…
cnr bff5f1b
Yarn: try V1 lockfile then V2 lockfile
cnr 915dcf3
Factor out duplication in unsupported resolvers
cnr 41c61f3
Copy editing
cnr 4e17b1a
Add tests for resolvers
cnr 316f5ee
Kill redundant resolver names
cnr ef4c432
Add end-to-end test for the example lockfile
cnr 8a78159
Update changelog
cnr 9195ffc
Add more links to yarnv2 devdocs
cnr 0b8c7fd
Kill scary comment at the top of test yarn.lock
cnr e3e7303
Add tests for Data.Text.Extra.dropPrefix
cnr f858a6b
Allow dependency versions as numbers
cnr 052d18d
Strip selectors from npm locators
cnr 878fc2e
Add workaround for semver range coalescing
cnr 5a0340e
Merge remote-tracking branch 'origin/master' into yarn-v2
cnr File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
|
||
* Overview | ||
Yarn is a buildtool primarily used for building and managing javascript projects. It's functionally a superset of the =npm= cli. | ||
|
||
Yarn uses the same package manifest file as =npm= -- =package.json= -- but uses a novel lockfile format to pin dependencies, saved as =yarn.lock=. | ||
|
||
For dependency analysis, we focus exclusively on the lockfile. | ||
|
||
* Lockfile (=yarn.lock=) | ||
As part of the update from Yarn v1 to Yarn v2, some major changes were made to the lockfile. Most notably: | ||
|
||
+ The lockfile [[https://dev.to/arcanis/introducing-yarn-2-4eh1#new-lockfile-format][is now real yaml]]. Yarn v1 used an almost-but-not-quite pseudo-yaml format. | ||
+ While Yarn v1's lockfile contained information only about dependencies of user projects, Yarn v2's lockfile is much more information-rich. It contains information about first-party user projects ("workspaces"), the version ranges specified in =package.json= for dependencies ("descriptors"), and the resolved version for each dependency ("locators") | ||
|
||
** Concepts | ||
*** Workspaces | ||
Workspaces are first-party package directories (directories that contain =package.json=). Workspaces are always available locally on disk, and are specified by a relative reference to a directory (e.g., =.= or =./foo/bar= or =../baz=) | ||
|
||
A yarn project can have several workspaces, where workspaces may (but *are not required to*) depend on each other in a DAG. This is similar to "multi-module projects" in other buildtools like maven or gomodules. | ||
|
||
Every yarn project will contain at least one workspace. | ||
|
||
See: [[https://yarnpkg.com/features/workspaces]] | ||
|
||
*** Locators | ||
/Somewhat/ similar to fossa locators, a yarn locator is an unambiguous reference to a specific version of a package and where to find it. | ||
|
||
Locators have three components: | ||
+ Package scope (optional) -- like =@babel= -- a scope on npm. | ||
+ Package name -- like =underscore=. | ||
+ Package reference -- which can vary in shape depending on where the package is coming from. For example, this could be a pointer to specific package version on the npm registry, a pointer to a git repo at a specific commit, or a link to a tarball. | ||
|
||
Package scope and name in a locator, for the purposes of dependency resolution, *are unused*. Only the package reference matters. | ||
|
||
Yarn supports a handful of reference types by default, and plugins can be added to support new reference types. See the =Resolvers= section below. | ||
|
||
See: https://yarnpkg.com/advanced/lexicon#locator | ||
|
||
*** Descriptors | ||
Descriptors are similar to locators, but may point to a /range/ of package versions. For the purposes of dependency analysis, we don't care much about the shape and content of descriptors. | ||
|
||
Descriptors have three components: | ||
+ Package scope (optional) -- like =@babel= -- a scope on npm | ||
+ Package name -- like =underscore= | ||
+ Package range -- which can vary in shape depending on where the package is coming from. For example, this could be a semver for a package on the npm registry, a pointer to a git repo on a branch, or a link to a tarball | ||
|
||
All locators are valid descriptors; not all descriptors are valid locators. | ||
|
||
See: https://yarnpkg.com/advanced/lexicon#descriptor | ||
|
||
*** Resolvers | ||
Plugins are the yarn v2 mechanism used to add support for, among other things, new types of locators. | ||
|
||
A plugin can export zero or more "Resolvers", each of which can add support for new types of locator. Yarn itself implements support for "built-in" locator types (npm dependencies, git dependencies, etc) as resolvers in bundled plugins. | ||
|
||
For dependency analysis, we support locators produced by [[https://github.com/yarnpkg/berry/blob/8afcaa2a954e196d6cd997f8ba506f776df83b1f/packages/yarnpkg-cli/package.json#L68-L82][all of the built-in plugins]]. | ||
|
||
See: https://yarnpkg.com/advanced/lexicon#resolver | ||
** Format | ||
#+BEGIN_SRC yaml | ||
# This file is generated by running "yarn install" inside your project. | ||
# Manual changes might be lost - proceed with caution! | ||
|
||
__metadata: | ||
version: 4 | ||
cacheKey: 7 | ||
|
||
"bar@workspace:bar": | ||
version: 0.0.0-use.local | ||
resolution: "bar@workspace:bar" | ||
dependencies: | ||
underscore: 1.13.1 | ||
languageName: unknown | ||
linkType: soft | ||
|
||
"foo@workspace:foo": | ||
version: 0.0.0-use.local | ||
resolution: "foo@workspace:foo" | ||
dependencies: | ||
underscore: ^1.13.0 | ||
languageName: unknown | ||
linkType: soft | ||
|
||
"quux@workspace:quux": | ||
version: 0.0.0-use.local | ||
resolution: "quux@workspace:quux" | ||
dependencies: | ||
underscore: "jashkenas/underscore#tag=1.13.1" | ||
languageName: unknown | ||
linkType: soft | ||
|
||
"toplevel@workspace:.": | ||
version: 0.0.0-use.local | ||
resolution: "toplevel@workspace:." | ||
languageName: unknown | ||
linkType: soft | ||
|
||
"underscore@jashkenas/underscore#tag=1.13.1": | ||
version: 1.13.1 | ||
resolution: "underscore@https://github.com/jashkenas/underscore.git#commit=cbb48b79fc1205aa04feb03dbc055cdd28a12652" | ||
checksum: 560609fdb4ba2c30e79db95ea37269982d1a2788d49b78f0de4f391da711bc2495d5fbddd6d24e7716fccf69959e445916af83eb5de1ad137b215777e2d32e4d | ||
languageName: node | ||
linkType: hard | ||
|
||
"underscore@npm:1.13.1, underscore@npm:^1.13.0": | ||
version: 1.13.1 | ||
resolution: "underscore@npm:1.13.1" | ||
checksum: 19527b2db3d34f783c3f2db9716a2c1221fef2958866925545697c46f430f59d1b384b8105cc7e7c809bdf0dc9075f2bfff90b8fb270b9d3a6c58347de2dd79d | ||
languageName: node | ||
linkType: hard | ||
|
||
#+END_SRC | ||
|
||
Ignoring the =__metadata= field, the yarn lockfile is a mapping from =a comma-separated list of descriptors= to a =package description=. | ||
|
||
*** Package description fields | ||
|
||
Of a package's fields, we only care about =resolution= and =dependencies= | ||
|
||
**** =resolution= | ||
The locator used for this package | ||
|
||
**** =dependencies= | ||
An optional field containing =package: descriptor-range= mappings for each dependency of the package. *This includes dev dependencies* if they were included when running =yarn install=. | ||
|
||
This field is copied functionally identically from a package's =dependencies= and =devDependencies= fields in =package.json=. The code that parses a =Package description= [[https://github.com/yarnpkg/berry/blob/0d9834036d6a3747d6c0dbb5c11e27568f7194dc/packages/yarnpkg-core/sources/Project.ts#L284][is the same code]] that parses dependencies in a =package.json= file | ||
|
||
Full dependency descriptors [[https://github.com/yarnpkg/berry/blob/0d9834036d6a3747d6c0dbb5c11e27568f7194dc/packages/yarnpkg-core/sources/Manifest.ts#L307-L326][can be reconstructed]] by joining key-value pairs on =@=: =underscore: ^1.13.0= is =underscore@^1.13.0=. Each dependency's descriptor is a key for a package at the top level of the yarn lockfile | ||
|
||
#+BEGIN_QUOTE | ||
*NOTE*: a fun note about dependency descriptors | ||
|
||
A keen eye may notice that in the lockfile above, some descriptor keys contain =npm:= at the top-level. For example, there's =underscore@npm:1.13.1= -- but that descriptor isn't used anywhere as a dependency. The closest is =underscore@1.13.1=, a dependency of the =bar= workspace. | ||
|
||
In an interesting design decision, yarn makes the default resolver for packages configurable. When a user provides a raw version (e.g., =1.13.1=) or semver (=^1.13.1=) for a dependency in =package.json=, a "default protocol" string is prepended to the descriptor range. This option [[https://next.yarnpkg.com/configuration/yarnrc#defaultProtocol][is configured]] as =defaultProtocol=, which defaults to =npm:=. | ||
|
||
As a workaround, when using a descriptor =name@range= to look up a package in the lockfile, we must also try =name@npm:range= | ||
#+END_QUOTE | ||
|
||
*** Lockfile sources | ||
The above lockfile was generated from the following files | ||
|
||
=package.json= | ||
#+BEGIN_SRC json | ||
{ | ||
"name": "toplevel", | ||
"private": true, | ||
"workspaces": [ | ||
"foo", | ||
"bar", | ||
"quux" | ||
] | ||
} | ||
#+END_SRC | ||
|
||
=foo/package.json= | ||
#+BEGIN_SRC json | ||
{ | ||
"name": "foo", | ||
"version": "1.0.0", | ||
"dependencies": { | ||
"underscore": "^1.13.0" | ||
} | ||
} | ||
#+END_SRC | ||
|
||
=bar/package.json= | ||
#+BEGIN_SRC json | ||
{ | ||
"name": "bar", | ||
"version": "1.0.0", | ||
"dependencies": { | ||
"underscore": "1.13.1" | ||
} | ||
} | ||
#+END_SRC | ||
|
||
=quux/package.json= | ||
|
||
Note that =name/repo= is implicitly treated as a github repo reference | ||
#+BEGIN_SRC json | ||
{ | ||
"name": "quux", | ||
"version": "1.0.0", | ||
"dependencies": { | ||
"underscore": "jashkenas/underscore#tag=1.13.1" | ||
} | ||
} | ||
#+END_SRC |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -33,7 +33,6 @@ common lang | |
GADTSyntax | ||
GeneralizedNewtypeDeriving | ||
HexFloatLiterals | ||
ImportQualifiedPost | ||
InstanceSigs | ||
KindSignatures | ||
MultiParamTypeClasses | ||
|
@@ -46,12 +45,13 @@ common lang | |
RankNTypes | ||
ScopedTypeVariables | ||
StandaloneDeriving | ||
StandaloneKindSignatures | ||
StrictData | ||
TupleSections | ||
TypeApplications | ||
TypeOperators | ||
TypeSynonymInstances | ||
ImportQualifiedPost | ||
StandaloneKindSignatures | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [nit]: did cabal-fmt put these out-of-order? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah, I'm not sure why it's doing that 😕 |
||
|
||
ghc-options: | ||
-Wall -Wincomplete-uni-patterns -Wcompat | ||
|
@@ -123,14 +123,15 @@ library | |
|
||
-- cabal-fmt: expand src | ||
exposed-modules: | ||
Algebra.Graph.AdjacencyMap.Extra | ||
App.Fossa.API.BuildLink | ||
App.Fossa.API.BuildWait | ||
App.Fossa.Analyze | ||
App.Fossa.Analyze.Graph | ||
App.Fossa.Analyze.GraphBuilder | ||
App.Fossa.Analyze.GraphMangler | ||
App.Fossa.Analyze.Project | ||
App.Fossa.Analyze.Record | ||
App.Fossa.API.BuildLink | ||
App.Fossa.API.BuildWait | ||
App.Fossa.Compatibility | ||
App.Fossa.Configuration | ||
App.Fossa.Container | ||
|
@@ -161,20 +162,20 @@ library | |
App.Version | ||
App.Version.TH | ||
Console.Sticky | ||
Control.Carrier.AtomicCounter | ||
Control.Carrier.AtomicState | ||
Control.Carrier.Diagnostics | ||
Control.Carrier.Diagnostics.StickyContext | ||
Control.Carrier.Finally | ||
Control.Carrier.AtomicCounter | ||
Control.Carrier.Output.IO | ||
Control.Carrier.StickyLogger | ||
Control.Carrier.TaskPool | ||
Control.Carrier.Threaded | ||
Control.Effect.AtomicCounter | ||
Control.Effect.AtomicState | ||
Control.Effect.ConsoleRegion | ||
Control.Effect.Diagnostics | ||
Control.Effect.Finally | ||
Control.Effect.AtomicCounter | ||
Control.Effect.Output | ||
Control.Effect.Path | ||
Control.Effect.Record | ||
|
@@ -184,6 +185,7 @@ library | |
Control.Effect.StickyLogger | ||
Control.Effect.TaskPool | ||
Control.Exception.Extra | ||
Data.Aeson.Extra | ||
Data.FileEmbed.Extra | ||
Data.Flag | ||
Data.Functor.Extra | ||
|
@@ -242,7 +244,6 @@ library | |
Strategy.Node.NpmList | ||
Strategy.Node.NpmLock | ||
Strategy.Node.PackageJson | ||
Strategy.Node.YarnLock | ||
Strategy.Npm | ||
Strategy.NuGet.Nuspec | ||
Strategy.NuGet.PackageReference | ||
|
@@ -255,13 +256,17 @@ library | |
Strategy.Python.SetupPy | ||
Strategy.Python.Setuptools | ||
Strategy.Python.Util | ||
Strategy.Rebar3 | ||
Strategy.RPM | ||
Strategy.Rebar3 | ||
Strategy.Ruby.BundleShow | ||
Strategy.Ruby.GemfileLock | ||
Strategy.Scala | ||
Strategy.Yarn | ||
Strategy.UserSpecified.YamlDependencies | ||
Strategy.Yarn | ||
Strategy.Yarn.V1.YarnLock | ||
Strategy.Yarn.V2.Lockfile | ||
Strategy.Yarn.V2.Resolvers | ||
Strategy.Yarn.V2.YarnLock | ||
Text.URI.Builder | ||
Types | ||
VCS.Git | ||
|
@@ -292,9 +297,9 @@ test-suite unit-tests | |
-- cabal-fmt: expand test | ||
other-modules: | ||
App.Fossa.API.BuildLinkSpec | ||
App.Fossa.Configuration.ConfigurationSpec | ||
App.Fossa.Report.AttributionSpec | ||
App.Fossa.VPS.NinjaGraphSpec | ||
App.Fossa.Configuration.ConfigurationSpec | ||
Cargo.MetadataSpec | ||
Carthage.CarthageSpec | ||
Clojure.ClojureSpec | ||
|
@@ -317,15 +322,14 @@ test-suite unit-tests | |
Go.TransitiveSpec | ||
Googlesource.RepoManifestSpec | ||
Gradle.GradleSpec | ||
GraphingSpec | ||
GraphUtil | ||
GraphingSpec | ||
Haskell.CabalSpec | ||
Haskell.StackSpec | ||
Maven.PluginStrategySpec | ||
Maven.PomStrategySpec | ||
Node.NpmLockSpec | ||
Node.PackageJsonSpec | ||
Node.YarnLockSpec | ||
NuGet.NuspecSpec | ||
NuGet.PackageReferenceSpec | ||
NuGet.PackagesConfigSpec | ||
|
@@ -340,11 +344,15 @@ test-suite unit-tests | |
Ruby.BundleShowSpec | ||
Ruby.GemfileLockSpec | ||
UserSpecified.YamlDependenciesSpec | ||
Yarn.V2.LockfileSpec | ||
Yarn.V2.ResolversSpec | ||
Yarn.YarnLockV1Spec | ||
|
||
build-tool-depends: hspec-discover:hspec-discover ^>=2.7.1 | ||
build-depends: | ||
, hedgehog ^>=1.0.2 | ||
, hspec ^>=2.7.1 | ||
, hspec-hedgehog ^>=0.0.1.2 | ||
, hspec-megaparsec ^>=2.1 | ||
, hedgehog ^>=1.0.2 | ||
, hspec ^>=2.7.1 | ||
, hspec-expectations-pretty-diff ^>=0.7.2.5 | ||
, hspec-hedgehog ^>=0.0.1.2 | ||
, hspec-megaparsec ^>=2.1 | ||
, spectrometer |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change | ||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,19 @@ | ||||||||||||||||||||
module Algebra.Graph.AdjacencyMap.Extra ( | ||||||||||||||||||||
gtraverse, | ||||||||||||||||||||
) where | ||||||||||||||||||||
|
||||||||||||||||||||
import Algebra.Graph.AdjacencyMap qualified as AM | ||||||||||||||||||||
import Data.Set qualified as S | ||||||||||||||||||||
|
||||||||||||||||||||
-- | It's 'traverse', but for graphs | ||||||||||||||||||||
-- | ||||||||||||||||||||
-- It's also unlawful. 'f' might be called several times for each node in the graph | ||||||||||||||||||||
gtraverse :: | ||||||||||||||||||||
(Applicative f, Ord b) => | ||||||||||||||||||||
(a -> f b) -> | ||||||||||||||||||||
AM.AdjacencyMap a -> | ||||||||||||||||||||
f (AM.AdjacencyMap b) | ||||||||||||||||||||
gtraverse f = fmap mkAdjacencyMap . traverse (\(a, xs) -> (,) <$> f a <*> traverse f xs) . AM.adjacencyList | ||||||||||||||||||||
where | ||||||||||||||||||||
mkAdjacencyMap :: Ord c => [(c, [c])] -> AM.AdjacencyMap c | ||||||||||||||||||||
mkAdjacencyMap = AM.fromAdjacencySets . fmap (fmap S.fromList) | ||||||||||||||||||||
Comment on lines
+16
to
+19
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [optional, readability]: I hate the
Suggested change
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file is currently formatted as an emacs org-mode document, because it's substantially easier for me to edit that way. I'll make sure to convert to markdown before merging