Skip to content

Commit

Permalink
Add lamdera update command
Browse files Browse the repository at this point in the history
  • Loading branch information
supermario committed Sep 21, 2024
1 parent 29493f7 commit 5770d40
Show file tree
Hide file tree
Showing 9 changed files with 226 additions and 19 deletions.
1 change: 1 addition & 0 deletions elm.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ Executable lamdera
Lamdera.CLI.Check
Lamdera.CLI.Deploy
Lamdera.CLI.Reset
Lamdera.CLI.Update
-- CLI Experimental
Lamdera.CLI.Annotate
Lamdera.CLI.Interpreter
Expand Down
11 changes: 7 additions & 4 deletions extra/Lamdera.hs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE BangPatterns #-}

module Lamdera
Expand Down Expand Up @@ -61,6 +60,7 @@ module Lamdera
, textContains
, textHasPrefix
, stringContains
, stringHasPrefix
, fileContains
, formatHaskellValue
, hindent
Expand Down Expand Up @@ -530,13 +530,16 @@ onlyWith filepath io = do


textContains :: Text -> Text -> Bool
textContains needle haystack = T.isInfixOf needle haystack
textContains = T.isInfixOf

textHasPrefix :: Text -> Text -> Bool
textHasPrefix needle haystack = T.isPrefixOf needle haystack
textHasPrefix = T.isPrefixOf

stringContains :: String -> String -> Bool
stringContains needle haystack = List.isInfixOf needle haystack
stringContains = List.isInfixOf

stringHasPrefix :: String -> String -> Bool
stringHasPrefix = List.isPrefixOf

fileContains :: FilePath -> Text -> IO Bool
fileContains filename needle = do
Expand Down
22 changes: 21 additions & 1 deletion extra/Lamdera/CLI.hs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{-# LANGUAGE OverloadedStrings #-}

module Lamdera.CLI (live, login, check, deploy, reset, annotate, eval) where
module Lamdera.CLI (live, login, check, deploy, reset, update, annotate, eval) where

import Text.Read (readMaybe)
import qualified Text.PrettyPrint.ANSI.Leijen as P
Expand All @@ -13,6 +13,7 @@ import qualified Lamdera.CLI.Login
import qualified Lamdera.CLI.Check
import qualified Lamdera.CLI.Deploy
import qualified Lamdera.CLI.Reset
import qualified Lamdera.CLI.Update
import qualified Lamdera.CLI.Annotate
import qualified Lamdera.CLI.Interpreter

Expand Down Expand Up @@ -128,6 +129,25 @@ reset =
Terminal.Command "reset" (Common summary) details example noArgs noFlags Lamdera.CLI.Reset.run


update :: Terminal.Command
update =
let
summary =
"Update the Lamdera compiler to the latest version if out of date."

details =
"The latest versions of the Lamdera compiler are available at https://dashboard.lamdera.app/docs/download"

example =
reflow
"It will find the latest lamdera binary version, download it, and replace itself."

updateFlags =
flags Lamdera.CLI.Update.Flags
|-- onOff "force" "Force update to the latest published version, regardless of what version is installed currently."
in
Terminal.Command "update" (Common summary) details example noArgs updateFlags Lamdera.CLI.Update.run


annotate :: Terminal.Command
annotate =
Expand Down
4 changes: 2 additions & 2 deletions extra/Lamdera/CLI/Check.hs
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,7 @@ getNextVersionInfo_ nextVersion prodVersion isHoistRebuild localTypesChangedFrom


checkForLatestBinaryVersion inDebug = do
progressPointer "Checking version..."
latestVersionText_ <- Lamdera.Update.fetchCurrentVersion
case latestVersionText_ of
Right latestVersionText -> do
Expand Down Expand Up @@ -551,12 +552,11 @@ checkForLatestBinaryVersion inDebug = do
debug $ "comparing remote:" <> show latestVersion <> " local:" <> show localVersion

onlyWhen (latestVersionText /= "skip" && latestVersion > localVersion) $ do
progressPointer "Checking version..."
progressDoc $ D.stack
[ D.red $ D.reflow $ "NOTE: There is a new lamdera version, please upgrade before you deploy."
, D.reflow $ "Current: " <> Lamdera.Version.short
, D.reflow $ "New: " <> T.unpack latestVersionText
, D.reflow $ "You can download it here: <https://dashboard.lamdera.app/docs/download>"
, D.reflow $ "Run `lamdera upgade`, or download it here: <https://dashboard.lamdera.app/docs/download>"
]

onlyWhen (latestVersion < localVersion) $ do
Expand Down
121 changes: 121 additions & 0 deletions extra/Lamdera/CLI/Update.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
{-# LANGUAGE OverloadedStrings #-}

-- lamdera update – to update the lamdera binary
-- lamdera ugprade namespace left for potential future command to mirror elm-json upgrade

module Lamdera.CLI.Update where

import Control.Exception (bracket_)
import Data.List (isPrefixOf)
import Network.HTTP.Client
import qualified Data.Text as T
import qualified System.Info
import qualified Text.Read
import System.Directory (renameFile, getPermissions, setPermissions, executable)
import System.Environment (getExecutablePath, getProgName)
import System.FilePath ((</>))
import System.IO.Temp (withSystemTempDirectory)
import System.Process (callProcess)

import qualified Http
import qualified Reporting
import qualified Reporting.Doc as D

import Lamdera
import Lamdera.Version (Version)
import qualified Lamdera.Http
import qualified Lamdera.Update
import qualified Lamdera.Version


newtype Flags =
Flags
{ _force :: Bool
}


run :: () -> Flags -> IO ()
run _ flags@(Flags force) = do
binaryPath <- getExecutablePath

onlyWhen (isInNixStore binaryPath) $ error $
"Oops! Looks like lamdera is installed via Nix (" <> binaryPath <> ").\n" <>
"Skipping self-upgrade, please bump your Nix derivation manually."

onlyWhen (isRunningInGHCi binaryPath) $ error $
"Oops! Can't self-update in GHCi (detected by current path " <> binaryPath <> ")"

latest <- Lamdera.Update.getLatestVersion

case latest of
Left err -> do
atomicPutStrLn $ "Error fetching latest version: " <> show err

Right latestVersion -> do
if not (Lamdera.Update.isLatest latestVersion) || force
then do
updateApproved <- do
Reporting.ask $ D.stack [ D.reflow $
"Replace " <> binaryPath <> " with version " <> Lamdera.Version.rawToString latestVersion <> "? [Y/n]: "
]

onlyWhen updateApproved $ do
atomicPutStrLn $ "Downloading Lamdera from " <> downloadUrl latestVersion
withSystemTempDirectory "lamdera-upgrade" $ \tempDir -> do
let tempFilePath = tempDir </> "lamdera-new"
download <- Lamdera.Http.downloadToFile (downloadUrl latestVersion) tempFilePath
case download of
Left err -> do
atomicPutStrLn $ "Error downloading latest version: " <> show err
Right () -> do
overwriteBinary tempFilePath binaryPath
execNewBinary binaryPath

else
atomicPutStrLn $ "No updates available, version " <> Lamdera.Version.rawToString latestVersion <> " is the latest."


downloadUrl :: Version -> String
downloadUrl version =
let
base = "https://static.lamdera.com/bin/lamdera"

architecture =
case System.Info.arch of
"aarch64" -> "arm64"
"x86_64" -> "x86_64"
_ -> error "Oops! Unsupported architecture: " <> System.Info.arch <> ", please report this."
in
case ostype of
MacOS -> do
base <> "-" <> Lamdera.Version.rawToString version <> "-" <> "macos" <> "-" <> architecture

Linux -> do
base <> "-" <> Lamdera.Version.rawToString version <> "-" <> "linux" <> "-" <> architecture

Windows -> do
error "Oops, I can't auto-update on Windows, please download manually from https://dashboard.lamdera.app/docs/download"

UnknownOS name -> do
error $ "Oops, I couldn't figure out the OS to upgrade for. Please report unknown OSTYPE: " <> show name


overwriteBinary :: FilePath -> FilePath -> IO ()
overwriteBinary tempFilePath destFilePath = do
renameFile tempFilePath destFilePath
permissions <- getPermissions destFilePath
setPermissions destFilePath (permissions { executable = True })


execNewBinary :: FilePath -> IO ()
execNewBinary binaryPath = do
callProcess binaryPath []


isInNixStore :: FilePath -> Bool
isInNixStore path = path & stringHasPrefix "/nix/store"


isRunningInGHCi :: String -> Bool
isRunningInGHCi binaryPath =
binaryPath & stringContains "/ghc/"
36 changes: 25 additions & 11 deletions extra/Lamdera/Http.hs
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE StandaloneDeriving #-}

module Lamdera.Http where

{- HTTP helpers and wrapper
-}

import qualified Data.ByteString.Char8 as BS
import qualified Network.HTTP.Client as HTTP
import qualified Network.HTTP.Types.Header as Http

import qualified Http
import qualified Json.Decode as D
import qualified Json.Encode as E
import qualified Network.HTTP.Types.Header as Http
import Reporting.Exit
import qualified Http
import qualified Data.ByteString.Char8 as BS
import qualified Network.HTTP.Client as HTTP

import Lamdera
import qualified Lamdera.Version
import Lamdera.Progress
import qualified Lamdera.Version
import StandaloneInstances


Expand All @@ -26,15 +26,21 @@ data WithErrorField a
deriving (Show)


jsonHeaders :: [Http.Header]
jsonHeaders =
defaultHeaders :: [Http.Header]
defaultHeaders =
[ ( Http.hUserAgent, "lamdera-" <> BS.pack Lamdera.Version.short )
, ( Http.hContentType, "application/json" )
, ( Http.hAccept, "application/json" )
, ( Http.hAcceptEncoding, "gzip")
]


jsonHeaders :: [Http.Header]
jsonHeaders =
defaultHeaders ++
[ ( Http.hContentType, "application/json" )
, ( Http.hAccept, "application/json" )
]


normalJson :: (Show a) => String -> String -> D.Decoder () a -> IO (Either Error a)
normalJson debugIdentifier url decoder = do
manager <- Http.getManager
Expand Down Expand Up @@ -69,10 +75,18 @@ normalRpcJson debugIdentifier body url decoder = do
return $ Left (JsonError url problem)


downloadToFile :: String -> FilePath -> IO (Either Error ())
downloadToFile url path = do
manager <- Http.getManager
Http.get manager url [] HttpError $ \body -> do
BS.writeFile path body
pure $ Right ()


printHttpError :: Error -> String -> IO ()
printHttpError error reason =
case error of
JsonError string dError -> putStrLn $ show error
JsonError string dError -> atomicPutStrLn $ show error

HttpError httpError ->
throw $ toHttpErrorReport "HTTP PROBLEM" httpError reason
Expand Down
47 changes: 46 additions & 1 deletion extra/Lamdera/Update.hs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,13 @@ module Lamdera.Update where
import Data.Text (Text)
import qualified Data.Text as T
import qualified Json.Decode as D
import qualified Lamdera.Http
import qualified Text.Read

import qualified Json.String

import Lamdera
import qualified Lamdera.Http
import qualified Lamdera.Version

fetchCurrentVersion :: IO (Either Lamdera.Http.Error Text)
fetchCurrentVersion = do
Expand All @@ -22,3 +26,44 @@ fetchCurrentVersion = do
D.string

fmap (T.pack . Json.String.toChars) <$> Lamdera.Http.normalJson "fetchCurrentVersion" endpoint decoder

getLatestVersion :: IO (Either String (Int, Int, Int))
getLatestVersion = do
latestVersionText_ <- fetchCurrentVersion
case latestVersionText_ of
Right latestVersionText -> do
let
toIntCertain :: Text -> Int
toIntCertain t =
t & T.unpack
& Text.Read.readMaybe
& withDefault 0

latestVersion =
latestVersionText
& T.splitOn "-"
& (\parts ->
case parts of
ev:lv:_ ->
lv
& T.splitOn "."
& fmap toIntCertain
& (\parts_ ->
case parts_ of
[v1, v2, v3] ->
Right ( v1, v2, v3 )

_ ->
Left "Got an invalid lamdera version format"
)

_ -> Left "Got an invalid full version format"
)
pure latestVersion
Left err ->
pure $ Left $ show err


isLatest :: Lamdera.Version.Version -> Bool
isLatest version =
version >= Lamdera.Version.raw
2 changes: 2 additions & 0 deletions extra/Lamdera/Version.hs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import qualified Ext.Common
import qualified Elm.Version as V


type Version = (Int, Int, Int)

raw :: (Int, Int, Int)
raw = (1,2,2)

Expand Down
1 change: 1 addition & 0 deletions terminal/src/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ main =
, make
, repl
, Lamdera.CLI.reset
, Lamdera.CLI.update
, Lamdera.CLI.annotate
, Lamdera.CLI.eval
-- , reactor
Expand Down

0 comments on commit 5770d40

Please sign in to comment.