diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -169,6 +169,7 @@
 | [@xgrommx](https://github.com/xgrommx) | Denis Stoyanov | [MIT license] |
 | [@zudov](https://github.com/zudov) | Konstantin Zudov | [MIT license] |
 | [@roryc89](https://github.com/roryc89) | Rory Campbell | [MIT license] |
+| [@drathier](https://github.com/drathier) | Drathier | [MIT license] |
 
 
 ### Contributors using Modified Terms
diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -69,7 +69,7 @@
   constraints
   containers
   contravariant
-  cryptonite
+  crypton
   css-text
   data-default
   data-default-class
@@ -1580,7 +1580,7 @@
   ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   POSSIBILITY OF SUCH DAMAGE.
 
-cryptonite LICENSE file:
+crypton LICENSE file:
 
   Copyright (c) 2006-2015 Vincent Hanquez <vincent@snarc.org>
 
diff --git a/purescript.cabal b/purescript.cabal
--- a/purescript.cabal
+++ b/purescript.cabal
@@ -1,6 +1,6 @@
 cabal-version:      2.4
 name:               purescript
-version:            0.15.16
+version:            0.15.17
 license:            BSD-3-Clause
 license-file:       LICENSE
 copyright:
@@ -32,6 +32,7 @@
     tests/json-compat/**/*.json
     tests/support/*.json
     tests/support/checkSourceMapValidity.js
+    tests/support/spago.yaml
     tests/support/psci/**/*.purs
     tests/support/psci/**/*.edit
     tests/support/pscide/src/**/*.purs
@@ -286,7 +287,7 @@
         cheapskate >=0.1.1.2 && <0.2,
         clock >=0.8.4 && <0.9,
         containers >=0.6.8 && <0.7,
-        cryptonite >=0.30 && <0.31,
+        crypton >=1.0.4 && <1.1,
         data-ordlist >=0.4.7.0 && <0.5,
         deepseq >=1.5.1.0 && <1.6,
         directory >=1.3.8.5 && <1.4,
@@ -385,7 +386,7 @@
         cheapskate >=0.1.1.2 && <0.2,
         clock >=0.8.4 && <0.9,
         containers >=0.6.8 && <0.7,
-        cryptonite >=0.30 && <0.31,
+        crypton >=1.0.4 && <1.1,
         data-ordlist >=0.4.7.0 && <0.5,
         deepseq >=1.5.1.0 && <1.6,
         directory >=1.3.8.5 && <1.4,
@@ -518,7 +519,7 @@
         cheapskate >=0.1.1.2 && <0.2,
         clock >=0.8.4 && <0.9,
         containers >=0.6.8 && <0.7,
-        cryptonite >=0.30 && <0.31,
+        crypton >=1.0.4 && <1.1,
         data-ordlist >=0.4.7.0 && <0.5,
         deepseq >=1.5.1.0 && <1.6,
         directory >=1.3.8.5 && <1.4,
diff --git a/src/Language/PureScript/CST/Convert.hs b/src/Language/PureScript/CST/Convert.hs
--- a/src/Language/PureScript/CST/Convert.hs
+++ b/src/Language/PureScript/CST/Convert.hs
@@ -445,7 +445,7 @@
 
 convertDeclaration :: String -> Declaration a -> [AST.Declaration]
 convertDeclaration fileName decl = case decl of
-  DeclData _ (DataHead _ a vars) bd -> do
+  DeclData _ (DataHead _ a vars) bd deriveClauses -> do
     let
       ctrs :: SourceToken -> DataCtor b -> [(SourceToken, DataCtor b)] -> [AST.DataConstructorDeclaration]
       ctrs st (DataCtor _ name fields) tl
@@ -454,15 +454,17 @@
             [] -> []
             (st', ctor) : tl' -> ctrs st' ctor tl'
           )
-    pure $ AST.DataDeclaration ann Env.Data (nameValue a) (goTypeVar <$> vars) (maybe [] (\(st, Separated hd tl) -> ctrs st hd tl) bd)
+    AST.DataDeclaration ann Env.Data (nameValue a) (goTypeVar <$> vars) (maybe [] (\(st, Separated hd tl) -> ctrs st hd tl) bd)
+      : convertDeriveClauses fileName (nameValue a) deriveClauses
   DeclType _ (DataHead _ a vars) _ bd ->
     pure $ AST.TypeSynonymDeclaration ann
       (nameValue a)
       (goTypeVar <$> vars)
       (convertType fileName bd)
-  DeclNewtype _ (DataHead _ a vars) st x ys -> do
+  DeclNewtype _ (DataHead _ a vars) st x ys deriveClauses -> do
     let ctrs = [AST.DataConstructorDeclaration (sourceAnnCommented fileName st (snd $ declRange decl)) (nameValue x) [(headDef (internalError "No constructor name") ctrFields, convertType fileName ys)]]
-    pure $ AST.DataDeclaration ann Env.Newtype (nameValue a) (goTypeVar <$> vars) ctrs
+    AST.DataDeclaration ann Env.Newtype (nameValue a) (goTypeVar <$> vars) ctrs
+      : convertDeriveClauses fileName (nameValue a) deriveClauses
   DeclClass _ (ClassHead _ sup name vars fdeps) bd -> do
     let
       goTyVar (TypeVarKinded (Wrapped _ (Labeled (_, a) _ _) _)) = nameValue a
@@ -553,25 +555,8 @@
 
   mkPartialInstanceName :: Maybe (Name Ident, SourceToken) -> QualifiedName (N.ProperName 'N.ClassName) -> [Type a] -> Either Text.Text N.Ident
   mkPartialInstanceName nameSep cls args =
-    maybe (Left genName) (Right . ident . nameValue . fst) nameSep
+    maybe (Left (genInstanceName cls (foldMap argName args))) (Right . ident . nameValue . fst) nameSep
     where
-      -- truncate to 25 chars to reduce verbosity
-      -- of name and still keep it readable
-      -- name will be used to create a GenIdent
-      -- in desugaring process
-      genName :: Text.Text
-      genName = Text.take 25 (className <> typeArgs)
-
-      className :: Text.Text
-      className
-        = foldMap (uncurry Text.cons . first toLower)
-        . Text.uncons
-        . N.runProperName
-        $ qualName cls
-
-      typeArgs :: Text.Text
-      typeArgs = foldMap argName args
-
       argName :: Type a -> Text.Text
       argName = \case
         -- These are only useful to disambiguate between overlapping instances
@@ -618,6 +603,36 @@
       qualRange cls
     else
       (fst $ qualRange cls, snd $ typeRange $ last args)
+
+convertDeriveClauses
+  :: String
+  -> N.ProperName 'N.TypeName
+  -> [DeriveClause]
+  -> [AST.Declaration]
+convertDeriveClauses fileName tyName = concatMap go
+  where
+  go (DeriveClause _ (Wrapped _ classes _)) = map convertClass (toList classes)
+  convertClass (DeriveClass cls) =
+    AST.TypeInstanceDeclaration clsAnn clsAnn chainId 0 (Left genName)
+      []
+      (qualified cls)
+      [tyCon]
+      AST.DerivedInstance
+    where
+    clsAnn = uncurry (sourceAnnCommented fileName) (qualRange cls)
+    chainId = mkChainId fileName (Pos.spanStart (fst clsAnn))
+    tyCon = T.TypeConstructor clsAnn (N.Qualified N.ByNullSourcePos tyName)
+    genName = genInstanceName cls (N.runProperName tyName)
+
+genInstanceName :: QualifiedName (N.ProperName 'N.ClassName) -> Text.Text -> Text.Text
+genInstanceName cls typeArgs = Text.take 25 (className <> typeArgs)
+  where
+  className :: Text.Text
+  className
+    = foldMap (uncurry Text.cons . first toLower)
+    . Text.uncons
+    . N.runProperName
+    $ qualName cls
 
 convertSignature :: String -> Labeled (Name Ident) (Type a) -> AST.Declaration
 convertSignature fileName (Labeled a _ b) = do
diff --git a/src/Language/PureScript/CST/Flatten.hs b/src/Language/PureScript/CST/Flatten.hs
--- a/src/Language/PureScript/CST/Flatten.hs
+++ b/src/Language/PureScript/CST/Flatten.hs
@@ -205,11 +205,12 @@
 
 flattenDeclaration :: Declaration a -> DList SourceToken
 flattenDeclaration = \case
-  DeclData _ a b ->
+  DeclData _ a b drvs ->
     flattenDataHead a <>
-    foldMap (\(t, cs) -> pure t <> flattenSeparated flattenDataCtor cs) b
-  DeclType _ a b c ->flattenDataHead a <> pure b <> flattenType c
-  DeclNewtype _ a b c d -> flattenDataHead a <> pure b <> flattenName c <> flattenType d
+    foldMap (\(t, ctrs) -> pure t <> flattenSeparated flattenDataCtor ctrs) b <>
+    foldMap flattenDeriveClause drvs
+  DeclType _ a b c -> flattenDataHead a <> pure b <> flattenType c
+  DeclNewtype _ a b c d drvs -> flattenDataHead a <> pure b <> flattenName c <> flattenType d <> foldMap flattenDeriveClause drvs
   DeclClass _ a b ->
     flattenClassHead a <>
     foldMap (\(c, d) -> pure c <> foldMap (flattenLabeled flattenName flattenType) d) b
@@ -221,6 +222,16 @@
   DeclForeign _ a b c -> pure a <> pure b <> flattenForeign c
   DeclRole _ a b c d -> pure a <> pure b <> flattenName c <> foldMap flattenRole d
   DeclValue _ a -> flattenValueBindingFields a
+
+  where
+  flattenDeriveClass :: DeriveClass -> DList SourceToken
+  flattenDeriveClass (DeriveClass cls) =
+    flattenQualifiedName cls
+
+  flattenDeriveClause :: DeriveClause -> DList SourceToken
+  flattenDeriveClause (DeriveClause kw classes) =
+    pure kw <>
+    flattenWrapped (flattenSeparated flattenDeriveClass) classes
 
 flattenQualifiedName :: QualifiedName a -> DList SourceToken
 flattenQualifiedName = pure . qualTok
diff --git a/src/Language/PureScript/CST/Parser.y b/src/Language/PureScript/CST/Parser.y
--- a/src/Language/PureScript/CST/Parser.y
+++ b/src/Language/PureScript/CST/Parser.y
@@ -661,10 +661,10 @@
   | 'class' properName { ImportClass () $1 (getProperName $2) }
 
 decl :: { Declaration () }
-  : dataHead { DeclData () $1 Nothing }
-  | dataHead '=' sep(dataCtor, '|') { DeclData () $1 (Just ($2, $3)) }
+  : dataHead manyOrEmpty(deriveClause) { DeclData () $1 Nothing $2 }
+  | dataHead '=' sep(dataCtor, '|') manyOrEmpty(deriveClause) { DeclData () $1 (Just ($2, $3)) $4 }
   | typeHead '=' type {% checkNoWildcards $3 *> pure (DeclType () $1 $2 $3) }
-  | newtypeHead '=' properName typeAtom {% checkNoWildcards $4 *> pure (DeclNewtype () $1 $2 (getProperName $3) $4) }
+  | newtypeHead '=' properName typeAtom manyOrEmpty(deriveClause) {% checkNoWildcards $4 *> pure (DeclNewtype () $1 $2 (getProperName $3) $4 $5) }
   | classHead { either id (\h -> DeclClass () h Nothing) $1 }
   | classHead 'where' '\{' manySep(classMember, '\;') '\}' {% either (const (parseError $2)) (\h -> pure $ DeclClass () h (Just ($2, $4))) $1 }
   | instHead { DeclInstanceChain () (Separated (Instance $1 Nothing) []) }
@@ -680,6 +680,12 @@
   | 'foreign' 'import' ident '::' type {% when (isConstrained $5) (addFailure ([$1, $2, nameTok $3, $4] <> toList (flattenType $5)) ErrConstraintInForeignImportSyntax) *> pure (DeclForeign () $1 $2 (ForeignValue (Labeled $3 $4 $5))) }
   | 'foreign' 'import' 'data' properName '::' type { DeclForeign () $1 $2 (ForeignData $3 (Labeled (getProperName $4) $5 $6)) }
   | 'type' 'role' properName many(role) { DeclRole () $1 $2 (getProperName $3) $4 }
+
+deriveClause :: { DeriveClause }
+  : 'derive' '(' sep(deriveClass, ',') ')' { DeriveClause $1 (Wrapped $2 $3 $4) }
+
+deriveClass :: { DeriveClass }
+  : qualProperName { DeriveClass (getQualifiedProperName $1) }
 
 dataHead :: { DataHead () }
   : 'data' properName manyOrEmpty(typeVarBindingPlain) { DataHead $1 (getProperName $2) $3 }
diff --git a/src/Language/PureScript/CST/Positions.hs b/src/Language/PureScript/CST/Positions.hs
--- a/src/Language/PureScript/CST/Positions.hs
+++ b/src/Language/PureScript/CST/Positions.hs
@@ -157,14 +157,21 @@
   DataAll _ a -> (a, a)
   DataEnumerated _ (Wrapped a _ b) -> (a, b)
 
+deriveClauseRange :: DeriveClause -> TokenRange
+deriveClauseRange (DeriveClause kw classes) = (kw, wrpClose classes)
+
 declRange :: Declaration a -> TokenRange
 declRange = \case
-  DeclData _ hd ctors
+  DeclData _ hd ctors drvs
+    | _:_ <- drvs -> (fst start, snd . deriveClauseRange $ last drvs)
     | Just (_, cs) <- ctors -> (fst start, snd . dataCtorRange $ sepLast cs)
     | otherwise -> start
     where start = dataHeadRange hd
   DeclType _ a _ b -> (fst $ dataHeadRange a,  snd $ typeRange b)
-  DeclNewtype _ a _ _ b -> (fst $ dataHeadRange a, snd $ typeRange b)
+  DeclNewtype _ a _ _ b drvs
+    | _:_ <- drvs -> (fst start, snd . deriveClauseRange $ last drvs)
+    | otherwise -> start
+    where start = (fst $ dataHeadRange a, snd $ typeRange b)
   DeclClass _ hd body
     | Just (_, ts) <- body -> (fst start, snd . typeRange . lblValue $ NE.last ts)
     | otherwise -> start
diff --git a/src/Language/PureScript/CST/Types.hs b/src/Language/PureScript/CST/Types.hs
--- a/src/Language/PureScript/CST/Types.hs
+++ b/src/Language/PureScript/CST/Types.hs
@@ -194,10 +194,19 @@
   | DataEnumerated a (Delimited (Name (N.ProperName 'N.ConstructorName)))
   deriving (Show, Eq, Ord, Functor, Foldable, Traversable, Generic)
 
+data DeriveClass = DeriveClass
+  { dcClass :: QualifiedName (N.ProperName 'N.ClassName)
+  } deriving (Show, Eq, Ord, Generic)
+
+data DeriveClause = DeriveClause
+  { dclKeyword :: SourceToken
+  , dclClasses :: Wrapped (Separated DeriveClass)
+  } deriving (Show, Eq, Ord, Generic)
+
 data Declaration a
-  = DeclData a (DataHead a) (Maybe (SourceToken, Separated (DataCtor a)))
+  = DeclData a (DataHead a) (Maybe (SourceToken, Separated (DataCtor a))) [DeriveClause]
   | DeclType a (DataHead a) SourceToken (Type a)
-  | DeclNewtype a (DataHead a) SourceToken (Name (N.ProperName 'N.ConstructorName)) (Type a)
+  | DeclNewtype a (DataHead a) SourceToken (Name (N.ProperName 'N.ConstructorName)) (Type a) [DeriveClause]
   | DeclClass a (ClassHead a) (Maybe (SourceToken, NonEmpty (Labeled (Name Ident) (Type a))))
   | DeclInstanceChain a (Separated (Instance a))
   | DeclDerive a SourceToken (Maybe SourceToken) (InstanceHead a)
diff --git a/src/Language/PureScript/CoreFn/Traversals.hs b/src/Language/PureScript/CoreFn/Traversals.hs
--- a/src/Language/PureScript/CoreFn/Traversals.hs
+++ b/src/Language/PureScript/CoreFn/Traversals.hs
@@ -70,7 +70,7 @@
   g' (Abs ann name e) = Abs ann name <$> g e
   g' (App ann v1 v2) = App ann <$> g v1 <*> g v2
   g' (Case ann vs alts) = Case ann <$> traverse g vs <*> traverse i alts
-  g' (Let ann ds e) = Let ann <$> traverse f ds <*> g' e
+  g' (Let ann ds e) = Let ann <$> traverse f ds <*> g e
   g' e = pure e
 
   h' (LiteralBinder a b) = LiteralBinder a <$> handleLiteral h b
diff --git a/src/Language/PureScript/CoreImp/Optimizer/Inliner.hs b/src/Language/PureScript/CoreImp/Optimizer/Inliner.hs
--- a/src/Language/PureScript/CoreImp/Optimizer/Inliner.hs
+++ b/src/Language/PureScript/CoreImp/Optimizer/Inliner.hs
@@ -155,7 +155,7 @@
   , binary' C.P_xor BitwiseXor
   , binary' C.P_shl ShiftLeft
   , binary' C.P_shr ShiftRight
-  , binary' C.P_zshr ZeroFillShiftRight
+  , binaryToInt' C.P_zshr ZeroFillShiftRight
   , unary'  C.P_complement BitwiseNot
 
   , inlineNonClassFunction (isModFnWithDict C.P_unsafeIndex) $ flip (Indexer Nothing)
@@ -174,6 +174,12 @@
   binary' fn op = convert where
     convert :: AST -> AST
     convert (App ss (App _ (Ref fn') [x]) [y]) | fn == fn' = Binary ss op x y
+    convert other = other
+  binaryToInt' :: (ModuleName, PSString) -> BinaryOperator -> AST -> AST
+  binaryToInt' fn op = convert where
+    convert :: AST -> AST
+    convert (App ss (App _ (Ref fn') [x]) [y]) | fn == fn' =
+      Binary ss BitwiseOr (Binary ss op x y) (NumericLiteral ss (Left 0))
     convert other = other
   unary :: (ModuleName, PSString) -> (ModuleName, PSString) -> UnaryOperator -> AST -> AST
   unary dict fn op = convert where
diff --git a/src/Language/PureScript/Sugar/TypeClasses/Deriving.hs b/src/Language/PureScript/Sugar/TypeClasses/Deriving.hs
--- a/src/Language/PureScript/Sugar/TypeClasses/Deriving.hs
+++ b/src/Language/PureScript/Sugar/TypeClasses/Deriving.hs
@@ -15,7 +15,7 @@
 import Language.PureScript.Errors (MultipleErrors, SimpleErrorMessage(..), errorMessage')
 import Language.PureScript.Names (pattern ByNullSourcePos, Ident(..), ModuleName, ProperName(..), ProperNameType(..), Qualified(..), QualifiedBy(..), freshIdent)
 import Language.PureScript.PSString (mkString)
-import Language.PureScript.Types (SourceType, Type(..), WildcardData(..), replaceAllTypeVars, srcTypeApp, srcTypeConstructor, srcTypeLevelString)
+import Language.PureScript.Types (SourceType, Type(..), WildcardData(..), replaceAllTypeVars, srcTypeApp, srcTypeConstructor, srcTypeLevelString, srcTypeVar)
 import Language.PureScript.TypeChecker (checkNewtype)
 
 -- | Elaborates deriving instance declarations by code generation.
@@ -46,16 +46,32 @@
 deriveInstance mn ds decl =
   case decl of
     TypeInstanceDeclaration sa@(ss, _) na ch idx nm deps className tys DerivedInstance -> let
+      -- Attached `derive (Generic)` / `derive (Newtype)` produces `[T]`.
+      -- These two classes need the fully-applied type plus a trailing
+      -- wildcard, so pad the args before falling into the standard handler.
+      paddedTys = case tys of
+        [bareTy]
+          | className == Libs.Generic || className == Libs.Newtype
+          , Just utc <- unwrapTypeConstructor bareTy
+          , mn == utcModuleName utc
+          , null (utcArgs utc)
+          , Just (DataDeclaration _ _ _ tyVars _) <- find (matchesTyName (utcTyCon utc)) ds ->
+              let applied = foldl srcTypeApp bareTy (map (srcTypeVar . fst) tyVars)
+              in [applied, TypeWildcard sa UnnamedWildcard]
+        _ -> tys
+      matchesTyName n (DataDeclaration _ _ n' _ _) = n == n'
+      matchesTyName _ _ = False
+
       binaryWildcardClass :: (Declaration -> [SourceType] -> m ([Declaration], SourceType)) -> m Declaration
-      binaryWildcardClass f = case tys of
+      binaryWildcardClass f = case paddedTys of
         [ty1, ty2] -> case unwrapTypeConstructor ty1 of
           Just UnwrappedTypeConstructor{..} | mn == utcModuleName -> do
             checkIsWildcard ss utcTyCon ty2
             tyConDecl <- findTypeDecl ss utcTyCon ds
             (members, ty2') <- f tyConDecl utcArgs
             pure $ TypeInstanceDeclaration sa na ch idx nm deps className [ty1, ty2'] (ExplicitInstance members)
-          _ -> throwError . errorMessage' ss $ ExpectedTypeConstructor className tys ty1
-        _ -> throwError . errorMessage' ss $ InvalidDerivedInstance className tys 2
+          _ -> throwError . errorMessage' ss $ ExpectedTypeConstructor className paddedTys ty1
+        _ -> throwError . errorMessage' ss $ InvalidDerivedInstance className paddedTys 2
 
       in case className of
         Libs.Generic -> binaryWildcardClass (deriveGenericRep ss mn)
diff --git a/stack.yaml b/stack.yaml
--- a/stack.yaml
+++ b/stack.yaml
@@ -26,7 +26,6 @@
   # Test dependencies
   - nodejs
   - nodePackages.npm
-  - nodePackages.bower
 flags:
   aeson-pretty:
     lib-only: true
diff --git a/tests/TestPscPublish.hs b/tests/TestPscPublish.hs
--- a/tests/TestPscPublish.hs
+++ b/tests/TestPscPublish.hs
@@ -26,11 +26,12 @@
 spec :: Spec
 spec = do
   context "preparePackage with json roundtrips" $ do
-    it "purescript-prelude" $ do
-      testPackage
-        "tests/support/bower_components/purescript-prelude"
-        "bower.json"
-        "../../prelude-resolutions.json"
+    forM_ [("prelude", "6.0.3"), ("effect", "4.0.1")] $ \(name, version) ->
+      it (name ++ " (registry purs.json)") $
+        testPackage
+          ("tests/support/.spago/p" </> name ++ "-" ++ version)
+          "purs.json"
+          ("../../../" ++ name ++ "-resolutions.json")
 
     it "basic example (bower.json)" $ do
       testPackage
diff --git a/tests/TestPsci/TestEnv.hs b/tests/TestPsci/TestEnv.hs
--- a/tests/TestPsci/TestEnv.hs
+++ b/tests/TestPsci/TestEnv.hs
@@ -28,7 +28,7 @@
   cwd <- getCurrentDirectory
   let supportDir = cwd </> "tests" </> "support"
   psciFiles <- Glob.globDir1 (Glob.compile "**/*.purs") (supportDir </> "psci")
-  libraries <- Glob.globDir1 (Glob.compile "purescript-*/src/**/*.purs") (supportDir </> "bower_components")
+  libraries <- Glob.globDir1 (Glob.compile "*/src/**/*.purs") (supportDir </> ".spago" </> "p")
   let pursFiles = psciFiles ++ libraries
   modulesOrError <- loadAllModules pursFiles
   case modulesOrError of
diff --git a/tests/TestUtils.hs b/tests/TestUtils.hs
--- a/tests/TestUtils.hs
+++ b/tests/TestUtils.hs
@@ -26,7 +26,7 @@
 import Data.Text.Encoding qualified as T
 import Data.Time.Clock (UTCTime(), diffUTCTime, getCurrentTime, nominalDay)
 import Data.Tuple (swap)
-import System.Directory (createDirectoryIfMissing, doesDirectoryExist, getCurrentDirectory, getModificationTime, getTemporaryDirectory, listDirectory, setCurrentDirectory, withCurrentDirectory)
+import System.Directory (createDirectoryIfMissing, doesDirectoryExist, getCurrentDirectory, getModificationTime, getTemporaryDirectory, listDirectory, removePathForcibly, setCurrentDirectory, withCurrentDirectory)
 import System.Exit (exitFailure)
 import System.Environment (lookupEnv)
 import System.FilePath (dropExtensions, makeRelative, takeDirectory, takeExtensions, takeFileName, (</>))
@@ -38,12 +38,7 @@
 import Test.Hspec (Expectation, HasCallStack, expectationFailure, pendingWith)
 
 -- |
--- Fetches code necessary to run the tests with. The resulting support code
--- should then be checked in, so that npm/bower etc is not required to run the
--- tests.
---
--- Simply rerun this (via ghci is probably easiest) when the support code needs
--- updating.
+-- Fetches the JavaScript tools and pinned registry packages used by the tests.
 --
 updateSupportCode :: IO ()
 updateSupportCode = withCurrentDirectory "tests/support" $ do
@@ -57,12 +52,11 @@
     now <- lift getCurrentTime
     guard $ now `diffUTCTime` lastUpdated < nominalDay
 
-    -- ... and the needed directories exist,
+    -- ... and the JavaScript tools are installed,
     contents <- lift $ listDirectory "."
-    guard $ "node_modules" `elem` contents && "bower_components" `elem` contents
+    guard $ all (`elem` contents) ["node_modules", ".spago"]
 
-    -- ... and everything else in `tests/support` is at least as old as
-    -- `.last_updated`.
+    -- ... and the support files are at least as old as `.last_updated`.
     modTimes <- lift $ traverse getModificationTime . filter (/= lastUpdatedFile) $ contents
     guard $ all (<= lastUpdated) modTimes
 
@@ -71,12 +65,11 @@
   unless skipUpdate $ do
     heading "Updating support code"
     callCommand "npm install"
-    -- bower uses shebang "/usr/bin/env node", but we might have nodejs
+    removePathForcibly ".spago"
     node <- either cannotFindNode pure =<< findNodeProcess
-    -- Sometimes we run as a root (e.g. in simple docker containers)
-    -- And we are non-interactive: https://github.com/bower/bower/issues/1162
-    callProcess node ["node_modules/bower/bin/bower", "--allow-root", "install", "--config.interactive=false"]
+    callProcess node ["node_modules/spago/bin/bundle.js", "fetch"]
     writeFile lastUpdatedFile ""
+
   where
   cannotFindNode :: String -> IO a
   cannotFindNode message = do
@@ -109,7 +102,7 @@
   cd <- getCurrentDirectory
   let supportDir = cd </> "tests" </> "support"
   psciFiles <- Glob.globDir1 (Glob.compile "**/*.purs") (supportDir </> "psci")
-  libraries <- Glob.globDir1 (Glob.compile "purescript-*/src/**/*.purs") (supportDir </> "bower_components")
+  libraries <- Glob.globDir1 (Glob.compile "*/src/**/*.purs") (supportDir </> ".spago" </> "p")
   let pursFiles = psciFiles ++ libraries
   fileContents <- readInput pursFiles
   modules <- runExceptT $ ExceptT . return $ CST.parseFromFiles id fileContents
diff --git a/tests/purs/failing/DeriveClauseCannotDerive.out b/tests/purs/failing/DeriveClauseCannotDerive.out
new file mode 100644
--- /dev/null
+++ b/tests/purs/failing/DeriveClauseCannotDerive.out
@@ -0,0 +1,14 @@
+Error found:
+in module [33mMain[0m
+at tests/purs/failing/DeriveClauseCannotDerive.purs:7:11 - 7:18 (line 7, column 11 - line 7, column 18)
+
+  Cannot derive a type class instance for
+  [33m                  [0m
+  [33m  Main.MyClass Foo[0m
+  [33m                  [0m
+  since instances of this type class are not derivable.
+
+
+See https://github.com/purescript/documentation/blob/master/errors/CannotDerive.md for more information,
+or to contribute content related to this error.
+
diff --git a/tests/purs/failing/DeriveClauseCannotDerive.purs b/tests/purs/failing/DeriveClauseCannotDerive.purs
new file mode 100644
--- /dev/null
+++ b/tests/purs/failing/DeriveClauseCannotDerive.purs
@@ -0,0 +1,7 @@
+-- @shouldFailWith CannotDerive
+module Main where
+
+class MyClass a
+
+data Foo a = Foo a
+  derive (MyClass)
diff --git a/tests/purs/failing/DeriveClauseEither2.out b/tests/purs/failing/DeriveClauseEither2.out
new file mode 100644
--- /dev/null
+++ b/tests/purs/failing/DeriveClauseEither2.out
@@ -0,0 +1,24 @@
+Error found:
+in module [33mMain[0m
+at tests/purs/failing/DeriveClauseEither2.purs:7:11 - 7:13 (line 7, column 11 - line 7, column 13)
+
+  Could not match kind
+  [33m                      [0m
+  [33m  Type -> Type -> Type[0m
+  [33m                      [0m
+  with kind
+  [33m      [0m
+  [33m  Type[0m
+  [33m      [0m
+
+while checking that type [33mEither2[0m
+  has kind [33mType[0m
+while inferring the kind of [33mEq Either2[0m
+in type class instance
+[33m                    [0m
+[33m  Data.Eq.Eq Either2[0m
+[33m                    [0m
+
+See https://github.com/purescript/documentation/blob/master/errors/KindsDoNotUnify.md for more information,
+or to contribute content related to this error.
+
diff --git a/tests/purs/failing/DeriveClauseEither2.purs b/tests/purs/failing/DeriveClauseEither2.purs
new file mode 100644
--- /dev/null
+++ b/tests/purs/failing/DeriveClauseEither2.purs
@@ -0,0 +1,7 @@
+-- @shouldFailWith KindsDoNotUnify
+module Main where
+
+import Prelude
+
+data Either2 a b = Left2 a | Right2 b
+  derive (Eq)
diff --git a/tests/purs/failing/DeriveClauseKindMismatch.out b/tests/purs/failing/DeriveClauseKindMismatch.out
new file mode 100644
--- /dev/null
+++ b/tests/purs/failing/DeriveClauseKindMismatch.out
@@ -0,0 +1,24 @@
+Error found:
+in module [33mMain[0m
+at tests/purs/failing/DeriveClauseKindMismatch.purs:7:11 - 7:13 (line 7, column 11 - line 7, column 13)
+
+  Could not match kind
+  [33m              [0m
+  [33m  Type -> Type[0m
+  [33m              [0m
+  with kind
+  [33m      [0m
+  [33m  Type[0m
+  [33m      [0m
+
+while checking that type [33mBox[0m
+  has kind [33mType[0m
+while inferring the kind of [33mEq Box[0m
+in type class instance
+[33m                [0m
+[33m  Data.Eq.Eq Box[0m
+[33m                [0m
+
+See https://github.com/purescript/documentation/blob/master/errors/KindsDoNotUnify.md for more information,
+or to contribute content related to this error.
+
diff --git a/tests/purs/failing/DeriveClauseKindMismatch.purs b/tests/purs/failing/DeriveClauseKindMismatch.purs
new file mode 100644
--- /dev/null
+++ b/tests/purs/failing/DeriveClauseKindMismatch.purs
@@ -0,0 +1,7 @@
+-- @shouldFailWith KindsDoNotUnify
+module Main where
+
+import Prelude
+
+data Box a = Box a
+  derive (Eq)
diff --git a/tests/purs/failing/DeriveClauseNewtypeOverlap.out b/tests/purs/failing/DeriveClauseNewtypeOverlap.out
new file mode 100644
--- /dev/null
+++ b/tests/purs/failing/DeriveClauseNewtypeOverlap.out
@@ -0,0 +1,24 @@
+Error found:
+in module [33mMain[0m
+at tests/purs/failing/DeriveClauseNewtypeOverlap.purs:10:1 - 10:34 (line 10, column 1 - line 10, column 34)
+
+  Overlapping type class instances found for
+  [33m                              [0m
+  [33m  Data.Newtype.Newtype Wrapper[0m
+  [33m                       String [0m
+  [33m                              [0m
+  The following instances were found:
+
+    instance in module [33mMain[0m with type [33mNewtype Wrapper String[0m (line 8, column 11 - line 8, column 18)
+    instance in module [33mMain[0m with type [33mNewtype Wrapper String[0m (line 10, column 1 - line 10, column 34)
+
+
+in type class instance
+[33m                              [0m
+[33m  Data.Newtype.Newtype Wrapper[0m
+[33m                       String [0m
+[33m                              [0m
+
+See https://github.com/purescript/documentation/blob/master/errors/OverlappingInstances.md for more information,
+or to contribute content related to this error.
+
diff --git a/tests/purs/failing/DeriveClauseNewtypeOverlap.purs b/tests/purs/failing/DeriveClauseNewtypeOverlap.purs
new file mode 100644
--- /dev/null
+++ b/tests/purs/failing/DeriveClauseNewtypeOverlap.purs
@@ -0,0 +1,13 @@
+-- @shouldFailWith OverlappingInstances
+module Main where
+
+import Prelude
+import Data.Newtype (class Newtype, unwrap)
+
+newtype Wrapper = Wrapper String
+  derive (Newtype)
+
+derive instance Newtype Wrapper _
+
+value :: String
+value = unwrap (Wrapper "hi")
diff --git a/tests/purs/optimize/ZeroFillShiftRight.out.js b/tests/purs/optimize/ZeroFillShiftRight.out.js
new file mode 100644
--- /dev/null
+++ b/tests/purs/optimize/ZeroFillShiftRight.out.js
@@ -0,0 +1,8 @@
+var shift = function (x) {
+    return function (count) {
+        return x >>> count | 0;
+    };
+};
+export {
+    shift
+};
diff --git a/tests/purs/optimize/ZeroFillShiftRight.purs b/tests/purs/optimize/ZeroFillShiftRight.purs
new file mode 100644
--- /dev/null
+++ b/tests/purs/optimize/ZeroFillShiftRight.purs
@@ -0,0 +1,6 @@
+module Main where
+
+import Data.Int.Bits (zshr)
+
+shift :: Int -> Int -> Int
+shift x count = zshr x count
diff --git a/tests/purs/passing/DerivingClause.purs b/tests/purs/passing/DerivingClause.purs
new file mode 100644
--- /dev/null
+++ b/tests/purs/passing/DerivingClause.purs
@@ -0,0 +1,53 @@
+module Main where
+
+import Prelude
+
+import Data.Bifunctor (class Bifunctor, bimap)
+import Data.Generic.Rep (class Generic)
+import Data.Newtype (class Newtype)
+import Data.Foldable (class Foldable, foldMap)
+import Data.Traversable (class Traversable)
+import Effect (Effect)
+import Effect.Console (log)
+import Test.Assert
+
+data Color = Red | Green | Blue
+  derive (Eq, Ord)
+
+newtype Name = Name String
+  derive (Eq, Ord)
+
+data List a = Nil | Cons a (List a)
+  derive (Functor, Foldable, Traversable)
+
+data Either2 a b = Left2 a | Right2 b
+  derive (Bifunctor)
+
+derive instance Eq a => Eq (Either2 a a)
+
+data Direction = North | South | East | West
+  derive (Generic)
+
+newtype Wrapper = Wrapper String
+  derive (Newtype)
+
+data Pair a = Pair a a
+  derive (Functor)
+
+data Box a = Empty | Full a
+  derive (Functor)
+
+derive instance Eq a => Eq (Box a)
+
+main :: Effect Unit
+main = do
+  assert $ Red == Red
+  assert $ Red < Green
+  assert $ Name "Alice" == Name "Alice"
+  assert $ foldMap show (Cons 1 (Cons 2 Nil)) == "12"
+  assert $ bimap (_ + 1) (_ * 2) (Left2 3) == Left2 4
+  assert $ map (_ + 1) (Full 1) == Full 2
+  assert $ case map (_ + 1) (Pair 1 2) of
+    Pair 2 3 -> true
+    _ -> false
+  log "Done"
diff --git a/tests/purs/passing/ZeroFillShiftRight.purs b/tests/purs/passing/ZeroFillShiftRight.purs
new file mode 100644
--- /dev/null
+++ b/tests/purs/passing/ZeroFillShiftRight.purs
@@ -0,0 +1,20 @@
+module Main where
+
+import Prelude
+
+import Data.Int.Bits (zshr)
+import Effect (Effect)
+import Effect.Console (log)
+import Test.Assert (assertEqual)
+
+main :: Effect Unit
+main = do
+  assertEqual { actual: zshr (-8) 0, expected: -8 }
+  assertEqual { actual: zshr (-8) 32, expected: -8 }
+  assertEqual { actual: zshr (-8) (-32), expected: -8 }
+  assertEqual { actual: zshr (-1) 1, expected: 2147483647 }
+  assertEqual { actual: zshr bottom 1, expected: 1073741824 }
+  assertEqual { actual: zshr (-8) 33, expected: 2147483644 }
+  assertEqual { actual: zshr (-8) (-1), expected: 1 }
+  assertEqual { actual: zshr 8 1, expected: 4 }
+  log "Done"
diff --git a/tests/purs/publish/basic-example/resolutions.json b/tests/purs/publish/basic-example/resolutions.json
--- a/tests/purs/publish/basic-example/resolutions.json
+++ b/tests/purs/publish/basic-example/resolutions.json
@@ -1,23 +1,23 @@
 {
   "purescript-console": {
     "version": "1.0.0",
-    "path": "../../../support/bower_components/purescript-console"
+    "path": "../../../support/.spago/p/console-6.1.1"
   },
   "purescript-effect": {
     "version": "1.0.0",
-    "path": "../../../support/bower_components/purescript-effect"
+    "path": "../../../support/.spago/p/effect-4.0.1"
   },
   "purescript-prelude": {
     "version": "1.0.0",
-    "path": "../../../support/bower_components/purescript-prelude"
+    "path": "../../../support/.spago/p/prelude-6.0.3"
   },
   "purescript-newtype": {
-    "path": "../../../support/bower_components/purescript-newtype"
+    "path": "../../../support/.spago/p/newtype-5.0.1"
   },
   "purescript-safe-coerce": {
-    "path": "../../../support/bower_components/purescript-safe-coerce"
+    "path": "../../../support/.spago/p/safe-coerce-2.0.1"
   },
   "purescript-unsafe-coerce": {
-    "path": "../../../support/bower_components/purescript-unsafe-coerce"
+    "path": "../../../support/.spago/p/unsafe-coerce-6.0.1"
   }
 }
diff --git a/tests/support/bower.json b/tests/support/bower.json
deleted file mode 100644
--- a/tests/support/bower.json
+++ /dev/null
@@ -1,39 +0,0 @@
-{
-  "name": "purescript-test-suite-support",
-  "dependencies": {
-    "purescript-arrays": "^7.0.0",
-    "purescript-assert": "^6.0.0",
-    "purescript-bifunctors": "^6.0.0",
-    "purescript-console": "^6.0.0",
-    "purescript-control": "^6.0.0",
-    "purescript-distributive": "^6.0.0",
-    "purescript-effect": "^4.0.0",
-    "purescript-either": "^6.1.0",
-    "purescript-enums": "^6.0.0",
-    "purescript-foldable-traversable": "^6.0.0",
-    "purescript-functions": "^6.0.0",
-    "purescript-gen": "^4.0.0",
-    "purescript-identity": "^6.0.0",
-    "purescript-integers": "^6.0.0",
-    "purescript-invariant": "^6.0.0",
-    "purescript-lazy": "^6.0.0",
-    "purescript-lists": "^7.0.0",
-    "purescript-maybe": "^6.0.0",
-    "purescript-newtype": "^5.0.0",
-    "purescript-nonempty": "^7.0.0",
-    "purescript-numbers": "^9.0.0",
-    "purescript-partial": "^4.0.0",
-    "purescript-prelude": "^6.0.0",
-    "purescript-psci-support": "^6.0.0",
-    "purescript-refs": "^6.0.0",
-    "purescript-safe-coerce": "^2.0.0",
-    "purescript-st": "^6.1.0",
-    "purescript-strings": "^6.0.0",
-    "purescript-tailrec": "^6.1.0",
-    "purescript-tuples": "^7.0.0",
-    "purescript-type-equality": "^4.0.1",
-    "purescript-typelevel-prelude": "^7.0.0",
-    "purescript-unfoldable": "^6.0.0",
-    "purescript-unsafe-coerce": "^6.0.0"
-  }
-}
diff --git a/tests/support/effect-resolutions.json b/tests/support/effect-resolutions.json
new file mode 100644
--- /dev/null
+++ b/tests/support/effect-resolutions.json
@@ -0,0 +1,3 @@
+{
+  "purescript-prelude": { "version": "6.0.3", "path": "../prelude-6.0.3" }
+}
diff --git a/tests/support/package.json b/tests/support/package.json
--- a/tests/support/package.json
+++ b/tests/support/package.json
@@ -1,9 +1,12 @@
 {
   "private": true,
+  "engines": {
+    "node": "^22.13.0 || >=23.4.0"
+  },
   "dependencies": {
-    "bower": "^1.8.8",
     "glob": "^5.0.14",
     "rimraf": "^2.5.2",
-    "source-map": "^0.7.3"
+    "source-map": "^0.7.3",
+    "spago": "1.0.5"
   }
 }
diff --git a/tests/support/spago.yaml b/tests/support/spago.yaml
new file mode 100644
--- /dev/null
+++ b/tests/support/spago.yaml
@@ -0,0 +1,40 @@
+package:
+  name: test-suite-support
+  dependencies:
+    - arrays
+    - assert
+    - bifunctors
+    - console
+    - control
+    - distributive
+    - effect
+    - either
+    - enums
+    - foldable-traversable
+    - functions
+    - gen
+    - identity
+    - integers
+    - invariant
+    - lazy
+    - lists
+    - maybe
+    - newtype
+    - nonempty
+    - numbers
+    - partial
+    - prelude
+    - psci-support
+    - refs
+    - safe-coerce
+    - st
+    - strings
+    - tailrec
+    - tuples
+    - type-equality
+    - typelevel-prelude
+    - unfoldable
+    - unsafe-coerce
+workspace:
+  packageSet:
+    registry: 81.11.4
