-
Notifications
You must be signed in to change notification settings - Fork 7
Add support for Yarn v2 projects #244
Changes from 25 commits
1cdef17
2c8311f
5abb9cd
12a3b26
342b503
2bc3d06
41ac122
653e59e
3bcac7b
3d75f50
96f0118
23031f7
2ba18c6
aa4070d
eac91a2
4c928cf
72dc874
b329091
bff5f1b
915dcf3
41c61f3
4e17b1a
316f5ee
ef4c432
8a78159
9195ffc
0b8c7fd
e3e7303
f858a6b
052d18d
878fc2e
5a0340e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
|
||
* 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. | ||
|
||
*** 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. | ||
|
||
*** 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. | ||
|
||
*** 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]]. | ||
** 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 identically from a package's =dependencies= and =devDependencies= fields in =package.json= | ||
|
||
Full dependency descriptors 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 |
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 | ||
|
@@ -238,7 +239,6 @@ library | |
Strategy.Node.NpmList | ||
Strategy.Node.NpmLock | ||
Strategy.Node.PackageJson | ||
Strategy.Node.YarnLock | ||
Strategy.Npm | ||
Strategy.NuGet.Nuspec | ||
Strategy.NuGet.PackageReference | ||
|
@@ -251,13 +251,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 | ||
|
@@ -288,9 +292,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 | ||
|
@@ -311,15 +315,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 | ||
|
@@ -334,11 +337,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 |
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
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -158,19 +158,23 @@ instance Show FailureBundle where | |
show = show . renderFailureBundle | ||
|
||
renderFailureBundle :: FailureBundle -> Doc AnsiStyle | ||
renderFailureBundle FailureBundle {..} = | ||
vsep | ||
[ annotate (color Yellow) "----------", | ||
annotate (color Yellow) "An error occurred:", | ||
"", | ||
indent 4 (renderSomeDiagnostic failureCause), | ||
"", | ||
">>>", | ||
"", | ||
indent 2 (annotate (color Yellow) "Relevant warnings include:"), | ||
"", | ||
indent 4 (renderWarnings failureWarnings) | ||
renderFailureBundle FailureBundle{..} = | ||
vsep $ | ||
[ annotate (color Yellow) "----------" | ||
, annotate (color Yellow) "An error occurred:" | ||
, "" | ||
, indent 4 (renderSomeDiagnostic failureCause) | ||
, "" | ||
] | ||
++ if null failureWarnings | ||
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. This change is unrelated to adding yarn support. It excludes the "Relevant warnings include" addendum to error messages if there are no relevant warnings |
||
then [] | ||
else | ||
[ ">>>" | ||
, "" | ||
, indent 2 (annotate (color Yellow) "Relevant warnings include:") | ||
, "" | ||
, indent 4 (renderWarnings failureWarnings) | ||
] | ||
|
||
renderSomeDiagnostic :: SomeDiagnostic -> Doc AnsiStyle | ||
renderSomeDiagnostic (SomeDiagnostic stack cause) = | ||
|
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