on master help sign in
Merge pull request #1 from obsidiansystems/km-obelisk-unset-lang-fix
5069112Ishaq Sloan
@@ -1,6 +1,7 @@
1# Frequently Asked Questions
1# Frequently Asked Questions
2
2
31. [How do I fix invalid entitlements? ](#how-do-i-fix-invalid-entitlements)
31. [How do I fix invalid entitlements? ](#how-do-i-fix-invalid-entitlements)
4+1. [`ob thunk update` or `ob deploy update` fails](#ob-thunk-update-or-ob-deploy-update-fails)
4
5
5### How do I fix invalid entitlements?
6### How do I fix invalid entitlements?
6
7
@@ -13,3 +14,33 @@ When this happens you'll see an error something like this:
13
14
14Fixing the value of `ios.bundleIdentifier` should fix the error.
15Fixing the value of `ios.bundleIdentifier` should fix the error.
15
16
17+### `ob thunk update` or `ob deploy update` fails
18+Whenever an `ob` command fails, try re-running it with `-v`.
19+
20+If you're using a private repo, and you get a failure in nix-prefetch-url, you may need to unpack and repack the thunk. Here's some example output that shows this issue:
21+
22+```
23+Starting Obelisk </nix/store/j8wls8a89xr6s1a47lg6g83gnbdrfd0l-obelisk-command-0.1/bin/.ob-wrapped> args=["deploy","update","-v"] logging-level=Debug
24+Creating process: 'nix-build' './.obelisk/impl' '-A' 'command' '--no-out-link'
25+✔ Built on ./.obelisk/impl [command]
26+Handing off to /nix/store/j8wls8a89xr6s1a47lg6g83gnbdrfd0l-obelisk-command-0.1/bin/ob
27+Starting Obelisk </nix/store/j8wls8a89xr6s1a47lg6g83gnbdrfd0l-obelisk-command-0.1/bin/.ob-wrapped> args=["--no-handoff","deploy","update","-v"] logging-level=Debug
28+Creating process: 'git' 'ls-remote' '--exit-code' '--symref' 'https://github.com/obsidiansystems/some-private-repo.git' 'refs/heads/master'
29+git ls-remote maps: (fromList [],fromList [(GitRef_Branch "master","8546dfc8be9653f20bb26214e0991dbb957cb290")])
30+Latest commit in branch master from remote repo https://github.com/obsidiansystems/some-private-repo.git is 8546dfc8be9653f20bb26214e0991dbb957cb290
31+Creating process: 'nix-prefetch-url' '--unpack' '--type' 'sha256' 'https://github.com/obsidiansystems/some-private-repo/archive/8546dfc8be9653f20bb26214e0991dbb957cb290.tar.gz'
32+error: unable to download 'https://github.com/obsidiansystems/some-private-repo/archive/8546dfc8be9653f20bb26214e0991dbb957cb290.tar.gz': HTTP error 404
33+Process exited with code 1; 'nix-prefetch-url' '--unpack' '--type' 'sha256' 'https://github.com/obsidiansystems/some-private-repo/archive/8546dfc8be9653f20bb26214e0991dbb957cb290.tar.gz'
34+nix-prefetch-url: Failed to determine sha256 hash of URL https://github.com/obsidiansystems/some-private-repo/archive/8546dfc8be9653f20bb26214e0991dbb957cb290.tar.gz
35+✖ Updating thunk ./src to latest
36+```
37+
38+And here's how you can fix it:
39+
40+```
41+ob thunk unpack $SOME_THUNK
42+ob thunk pack $SOME_THUNK
43+```
44+Substitute the directory of your thunk for `$SOME_THUNK`. In the case of `ob deploy update`, `$SOME_THUNK` will be `src` in the deployment directory.
45+
46+(Based on issue #351).
default.nix+1
@@ -206,6 +206,7 @@ in rec {
206 forceSSL = enableHttps;
206 forceSSL = enableHttps;
207 locations.${baseUrl} = {
207 locations.${baseUrl} = {
208 proxyPass = "http://localhost:" + toString internalPort;
208 proxyPass = "http://localhost:" + toString internalPort;
209+ proxyWebsockets = true;
209 };
210 };
210 };
211 };
211 };
212 };
dep/reflex-platform/github.json+2−2
@@ -2,6 +2,6 @@
2 "owner": "reflex-frp",
2 "owner": "reflex-frp",
3 "repo": "reflex-platform",
3 "repo": "reflex-platform",
4 "branch": "develop",
4 "branch": "develop",
5− "rev": "a7cd9a23e7faa9c2545a4c111229e28208e42351",
5+ "rev": "d5b662e220a73e9d711aff0133265b6aafda89d8",
6− "sha256": "0y52rfrhk4zszgprpyni51l0pgq18dg695k5bmpd62c3zxar5mvm"
6+ "sha256": "1ri7khnxid8365s3vkgyny5ncgk1as86fjyznx3qj157mxgh3f3n"
7}
7}
lib/backend/obelisk-backend.cabal−1
@@ -14,7 +14,6 @@ library
14 lens,
14 lens,
15 mtl,
15 mtl,
16 obelisk-asset-serve-snap,
16 obelisk-asset-serve-snap,
17− obelisk-executable-config-inject,
18 obelisk-frontend,
17 obelisk-frontend,
19 obelisk-route,
18 obelisk-route,
20 obelisk-snap-extras,
19 obelisk-snap-extras,
lib/backend/src/Obelisk/Backend.hs+2−4
@@ -37,7 +37,6 @@ import Data.Text (Text)
37import qualified Data.Text as T
37import qualified Data.Text as T
38import Data.Text.Encoding
38import Data.Text.Encoding
39import Obelisk.Asset.Serve.Snap (serveAsset)
39import Obelisk.Asset.Serve.Snap (serveAsset)
40−import Obelisk.ExecutableConfig.Inject (injectExecutableConfigs)
41import Obelisk.Frontend
40import Obelisk.Frontend
42import Obelisk.Route
41import Obelisk.Route
43import Obelisk.Snap.Extras (doNotCache, serveFileIfExistsAs)
42import Obelisk.Snap.Extras (doNotCache, serveFileIfExistsAs)
@@ -167,9 +166,8 @@ mkRouteToUrl validFullEncoder =
167
166
168renderGhcjsFrontend :: MonadIO m => (route -> Text) -> route -> Frontend route -> m ByteString
167renderGhcjsFrontend :: MonadIO m => (route -> Text) -> route -> Frontend route -> m ByteString
169renderGhcjsFrontend urlEnc route f = do
168renderGhcjsFrontend urlEnc route f = do
170− let baseTag = elAttr "base" ("href" =: "/") blank --TODO: Figure out the base URL from the routes
171− ghcjsPreload = elAttr "link" ("rel" =: "preload" <> "as" =: "script" <> "href" =: "ghcjs/all.js") blank
169+ let ghcjsPreload = elAttr "link" ("rel" =: "preload" <> "as" =: "script" <> "href" =: "ghcjs/all.js") blank
172 ghcjsScript = elAttr "script" ("language" =: "javascript" <> "src" =: "ghcjs/all.js" <> "defer" =: "defer") blank
170 ghcjsScript = elAttr "script" ("language" =: "javascript" <> "src" =: "ghcjs/all.js" <> "defer" =: "defer") blank
173 liftIO $ renderFrontendHtml urlEnc route
171 liftIO $ renderFrontendHtml urlEnc route
174− (_frontend_head f >> injectExecutableConfigs >> baseTag >> ghcjsPreload)
172+ (_frontend_head f >> ghcjsPreload)
175 (_frontend_body f >> ghcjsScript)
173 (_frontend_body f >> ghcjsScript)
lib/cliapp/obelisk-cliapp.cabal+1
@@ -29,5 +29,6 @@ library
29 Obelisk.CliApp.Process
29 Obelisk.CliApp.Process
30 Obelisk.CliApp.Spinner
30 Obelisk.CliApp.Spinner
31 Obelisk.CliApp.TerminalString
31 Obelisk.CliApp.TerminalString
32+ Obelisk.CliApp.Theme
32 Obelisk.CliApp.Types
33 Obelisk.CliApp.Types
33 ghc-options: -Wall -fobject-code
34 ghc-options: -Wall -fobject-code
lib/cliapp/src/Obelisk/CliApp/Logging.hs+25−2
@@ -39,17 +39,20 @@ import Control.Monad.Log (Severity (..), WithSeverity (..), logMessage, runLoggi
39import Control.Monad.Loops (iterateUntil)
39import Control.Monad.Loops (iterateUntil)
40import Control.Monad.Reader (MonadIO, ReaderT (..))
40import Control.Monad.Reader (MonadIO, ReaderT (..))
41import Data.IORef (atomicModifyIORef', newIORef, readIORef, writeIORef)
41import Data.IORef (atomicModifyIORef', newIORef, readIORef, writeIORef)
42+import Data.Maybe
42import Data.Semigroup ((<>))
43import Data.Semigroup ((<>))
43import Data.Text (Text)
44import Data.Text (Text)
44import qualified Data.Text as T
45import qualified Data.Text as T
45import qualified Data.Text.IO as T
46import qualified Data.Text.IO as T
47+import GHC.IO.Encoding.Types
46import System.Console.ANSI (Color (Red, Yellow), ColorIntensity (Vivid),
48import System.Console.ANSI (Color (Red, Yellow), ColorIntensity (Vivid),
47 ConsoleIntensity (FaintIntensity), ConsoleLayer (Foreground),
49 ConsoleIntensity (FaintIntensity), ConsoleLayer (Foreground),
48 SGR (SetColor, SetConsoleIntensity), clearLine)
50 SGR (SetColor, SetConsoleIntensity), clearLine)
49import System.Exit (ExitCode (..))
51import System.Exit (ExitCode (..))
50−import System.IO (BufferMode (NoBuffering), hFlush, hReady, hSetBuffering, stderr, stdin, stdout, hGetEncoding)
52+import System.IO
51
53
52import qualified Obelisk.CliApp.TerminalString as TS
54import qualified Obelisk.CliApp.TerminalString as TS
55+import Obelisk.CliApp.Theme
53import Obelisk.CliApp.Types
56import Obelisk.CliApp.Types
54
57
55newCliConfig
58newCliConfig
@@ -64,7 +67,10 @@ newCliConfig sev noColor noSpinner errorLogExitCode = do
64 tipDisplayed <- newIORef False
67 tipDisplayed <- newIORef False
65 stack <- newIORef ([], [])
68 stack <- newIORef ([], [])
66 textEncoding <- hGetEncoding stdout
69 textEncoding <- hGetEncoding stdout
70+ let theme = if fromMaybe False $ supportsUnicode <$> textEncoding
71+ then unicodeTheme
72+ else noUnicodeTheme
67− return $ CliConfig level noColor noSpinner lock tipDisplayed stack errorLogExitCode textEncoding
73+ return $ CliConfig level noColor noSpinner lock tipDisplayed stack errorLogExitCode theme
68
74
69runCli :: MonadIO m => CliConfig e -> CliT e m a -> m a
75runCli :: MonadIO m => CliConfig e -> CliT e m a -> m a
70runCli c =
76runCli c =
@@ -227,3 +233,20 @@ fork :: (HasCliConfig e m, MonadIO m) => CliT e IO () -> m ThreadId
227fork f = do
233fork f = do
228 c <- getCliConfig
234 c <- getCliConfig
229 liftIO $ forkIO $ runCli c f
235 liftIO $ forkIO $ runCli c f
236+
237+-- | Conservatively determines whether the encoding supports Unicode.
238+--
239+-- Currently this uses a whitelist of known-to-work encodings. In principle it
240+-- could test dynamically by opening a file with this encoding, but it doesn't
241+-- look like base exposes any way to determine this in a pure fashion.
242+supportsUnicode :: TextEncoding -> Bool
243+supportsUnicode enc = any ((textEncodingName enc ==) . textEncodingName)
244+ [ utf8
245+ , utf8_bom
246+ , utf16
247+ , utf16be
248+ , utf16le
249+ , utf32
250+ , utf32be
251+ , utf32le
252+ ]
lib/cliapp/src/Obelisk/CliApp/Process.hs+4−3
@@ -33,6 +33,7 @@ import Data.Monoid ((<>))
33import Data.Text (Text)
33import Data.Text (Text)
34import qualified Data.Text as T
34import qualified Data.Text as T
35import qualified Data.Text.Encoding as T
35import qualified Data.Text.Encoding as T
36+import Data.Text.Encoding.Error (lenientDecode)
36import System.Exit (ExitCode (..))
37import System.Exit (ExitCode (..))
37import System.IO (Handle)
38import System.IO (Handle)
38import System.IO.Streams (InputStream, handleToInputStream)
39import System.IO.Streams (InputStream, handleToInputStream)
@@ -64,7 +65,7 @@ readProcessAndLogStderr
64readProcessAndLogStderr sev process = do
65readProcessAndLogStderr sev process = do
65 (out, _err) <- withProcess process $ \_out err -> do
66 (out, _err) <- withProcess process $ \_out err -> do
66 streamToLog =<< liftIO (streamHandle sev err)
67 streamToLog =<< liftIO (streamHandle sev err)
67− liftIO $ T.decodeUtf8 <$> BS.hGetContents out
68+ liftIO $ T.decodeUtf8With lenientDecode <$> BS.hGetContents out
68
69
69readCreateProcessWithExitCode
70readCreateProcessWithExitCode
70 :: (MonadIO m, CliLog m, CliThrow e m, AsProcessFailure e)
71 :: (MonadIO m, CliLog m, CliThrow e m, AsProcessFailure e)
@@ -89,7 +90,7 @@ readProcessAndLogOutput (sev_out, sev_err) process = do
89
90
90 -- TODO interleave stdout and stderr in log correctly
91 -- TODO interleave stdout and stderr in log correctly
91 streamToLog =<< liftIO (streamHandle sev_err err)
92 streamToLog =<< liftIO (streamHandle sev_err err)
92− outText <- liftIO $ T.decodeUtf8 <$> BS.hGetContents out
93+ outText <- liftIO $ T.decodeUtf8With lenientDecode <$> BS.hGetContents out
93 putLogRaw sev_out outText
94 putLogRaw sev_out outText
94
95
95 liftIO (waitForProcess p) >>= \case
96 liftIO (waitForProcess p) >>= \case
@@ -173,7 +174,7 @@ streamToLog
173streamToLog stream = fix $ \loop -> do
174streamToLog stream = fix $ \loop -> do
174 liftIO (Streams.read stream) >>= \case
175 liftIO (Streams.read stream) >>= \case
175 Nothing -> return ()
176 Nothing -> return ()
176− Just (sev, line) -> putLogRaw sev (T.decodeUtf8 line) >> loop
177+ Just (sev, line) -> putLogRaw sev (T.decodeUtf8With lenientDecode line) >> loop
177
178
178-- | Pretty print a 'CmdSpec'
179-- | Pretty print a 'CmdSpec'
179reconstructCommand :: Process.CmdSpec -> Text
180reconstructCommand :: Process.CmdSpec -> Text
lib/cliapp/src/Obelisk/CliApp/Spinner.hs+23−67
@@ -21,10 +21,10 @@ import qualified Data.List as L
21import Data.Maybe (isNothing)
21import Data.Maybe (isNothing)
22import Data.Text (Text)
22import Data.Text (Text)
23import System.Console.ANSI (Color (Blue, Cyan, Green, Red))
23import System.Console.ANSI (Color (Blue, Cyan, Green, Red))
24−import System.IO (utf8, utf8_bom, utf16, utf16be, utf16le, utf32, utf32be, utf32le, TextEncoding)
25
24
26import Obelisk.CliApp.Logging (allowUserToMakeLoggingVerbose, fork, putLog)
25import Obelisk.CliApp.Logging (allowUserToMakeLoggingVerbose, fork, putLog)
27import Obelisk.CliApp.TerminalString (TerminalString (..), enquiryCode)
26import Obelisk.CliApp.TerminalString (TerminalString (..), enquiryCode)
27+import Obelisk.CliApp.Theme
28import Obelisk.CliApp.Types (CliLog, CliConfig (..), HasCliConfig, Output (..), getCliConfig)
28import Obelisk.CliApp.Types (CliLog, CliConfig (..), HasCliConfig, Output (..), getCliConfig)
29
29
30-- | Run an action with a CLI spinner.
30-- | Run an action with a CLI spinner.
@@ -54,44 +54,38 @@ withSpinner'
54withSpinner' msg mkTrail action = do
54withSpinner' msg mkTrail action = do
55 cliConf <- getCliConfig
55 cliConf <- getCliConfig
56 let noSpinner = _cliConfig_noSpinner cliConf
56 let noSpinner = _cliConfig_noSpinner cliConf
57− mTextEncoding = _cliConfig_textEncoding cliConf
58 if noSpinner
57 if noSpinner
59 then putLog Notice msg >> action
58 then putLog Notice msg >> action
60− else bracket' (run mTextEncoding) (cleanup mTextEncoding) $ const action
59+ else bracket' run cleanup $ const action
61 where
60 where
62− run mTextEncoding = do
61+ run = do
63 -- Add this log to the spinner stack, and start a spinner if it is top-level.
62 -- Add this log to the spinner stack, and start a spinner if it is top-level.
64 modifyStack pushSpinner >>= \case
63 modifyStack pushSpinner >>= \case
65 True -> do -- Top-level spinner; fork a thread to manage output of anything on the stack
64 True -> do -- Top-level spinner; fork a thread to manage output of anything on the stack
66 ctrleThread <- fork $ allowUserToMakeLoggingVerbose enquiryCode
65 ctrleThread <- fork $ allowUserToMakeLoggingVerbose enquiryCode
66+ cliConf <- getCliConfig
67+ let theme = _cliConfig_theme cliConf
68+ spinner = coloredSpinner $ _cliTheme_spinner theme
67− spinnerThread <- fork $ runSpinner (spinner mTextEncoding) $ \c -> do
69+ spinnerThread <- fork $ runSpinner spinner $ \c -> do
68− logs <- renderSpinnerStack mTextEncoding c . snd <$> readStack
70+ logs <- renderSpinnerStack theme c . snd <$> readStack
69 logMessage $ Output_Overwrite logs
71 logMessage $ Output_Overwrite logs
70 pure [ctrleThread, spinnerThread]
72 pure [ctrleThread, spinnerThread]
71 False -> -- Sub-spinner; nothing to do.
73 False -> -- Sub-spinner; nothing to do.
72 pure []
74 pure []
73− cleanup mTextEncoding tids resultM = do
75+ cleanup tids resultM = do
74 liftIO $ mapM_ killThread tids
76 liftIO $ mapM_ killThread tids
75 logMessage Output_ClearLine
77 logMessage Output_ClearLine
78+ cliConf <- getCliConfig
79+ let theme = _cliConfig_theme cliConf
76− logsM <- modifyStack $ (popSpinner mTextEncoding) $ case resultM of
80+ logsM <- modifyStack $ (popSpinner theme) $ case resultM of
77− Nothing -> case mTextEncoding of
81+ Nothing ->
78− Just enc | supportUnicode enc ->
79− ( TerminalString_Colorized Red "✖"
82+ ( TerminalString_Colorized Red $ _cliTheme_failed $ _cliConfig_theme cliConf
80− , Just msg -- Always display final message if there was an exception.
81− )
82− _ ->
83− ( TerminalString_Colorized Red "FAILED"
84− , Just msg -- Always display final message if there was an exception.
83+ , Just msg -- Always display final message if there was an exception.
85− )
84+ )
86− Just result -> case mTextEncoding of
85+ Just result ->
87− Just enc | supportUnicode enc ->
88− ( TerminalString_Colorized Green "✔"
86+ ( TerminalString_Colorized Green $ _cliTheme_done $ _cliConfig_theme cliConf
89− , mkTrail <*> pure result
90− )
91− _ ->
92− ( TerminalString_Colorized Green "DONE"
93− , mkTrail <*> pure result
87+ , mkTrail <*> pure result
94− )
88+ )
95 -- Last message, finish off with newline.
89 -- Last message, finish off with newline.
96 forM_ logsM $ logMessage . Output_Write
90 forM_ logsM $ logMessage . Output_Write
97 pushSpinner (flag, old) =
91 pushSpinner (flag, old) =
@@ -115,36 +109,19 @@ withSpinner' msg mkTrail action = do
115 =<< fmap _cliConfig_spinnerStack getCliConfig
109 =<< fmap _cliConfig_spinnerStack getCliConfig
116 modifyStack f = liftIO . flip atomicModifyIORef' f
110 modifyStack f = liftIO . flip atomicModifyIORef' f
117 =<< fmap _cliConfig_spinnerStack getCliConfig
111 =<< fmap _cliConfig_spinnerStack getCliConfig
118− spinner mTextEncoding = case mTextEncoding of
119− Just enc | supportUnicode enc -> coloredSpinner defaultSpinnerTheme
120− _ -> coloredSpinner minimalSpinnerTheme
121−
122−supportUnicode :: TextEncoding -> Bool
123−supportUnicode enc = any ((show enc ==) . show)
124− [ utf8
125− , utf8_bom
126− , utf16
127− , utf16be
128− , utf16le
129− , utf32
130− , utf32be
131− , utf32le
132− ]
133
112
134-- | How nested spinner logs should be displayed
113-- | How nested spinner logs should be displayed
135renderSpinnerStack
114renderSpinnerStack
136− :: Maybe TextEncoding
115+ :: CliTheme
137 -> TerminalString -- ^ That which comes before the final element in stack
116 -> TerminalString -- ^ That which comes before the final element in stack
138 -> [TerminalString] -- ^ Spinner elements in reverse order
117 -> [TerminalString] -- ^ Spinner elements in reverse order
139 -> [TerminalString]
118 -> [TerminalString]
140−renderSpinnerStack mTextEncoding mark = L.intersperse space . go . L.reverse
119+renderSpinnerStack theme mark = L.intersperse space . go . L.reverse
141 where
120 where
142 go [] = []
121 go [] = []
143 go (x:[]) = mark : [x]
122 go (x:[]) = mark : [x]
144 go (x:xs) = arrow : x : go xs
123 go (x:xs) = arrow : x : go xs
145− arrow = case mTextEncoding of
146− Just enc | supportUnicode enc -> TerminalString_Colorized Blue "⇾"
124+ arrow = TerminalString_Colorized Blue $ _cliTheme_arrow theme
147− _ -> TerminalString_Colorized Blue "->"
148 space = TerminalString_Normal " "
125 space = TerminalString_Normal " "
149
126
150-- | A spinner is simply an infinite list of strings that supplant each other in a delayed loop, creating the
127-- | A spinner is simply an infinite list of strings that supplant each other in a delayed loop, creating the
@@ -160,27 +137,6 @@ runSpinner spinner f = forM_ spinner $ f >=> const delay
160 where
137 where
161 delay = liftIO $ threadDelay 100000 -- A shorter delay ensures that we update promptly.
138 delay = liftIO $ threadDelay 100000 -- A shorter delay ensures that we update promptly.
162
139
163−type SpinnerTheme = [Text]
164−
165−-- Find more spinners at https://github.com/sindresorhus/cli-spinners/blob/master/spinners.json
166−_spinnerSticks :: SpinnerTheme
167−_spinnerSticks = ["|", "/", "-", "\\"]
168−
169−spinnerStick :: SpinnerTheme
170−spinnerStick = ["|", "/", "-", "\\"]
171−
172−_spinnerCircleHalves :: SpinnerTheme
173−_spinnerCircleHalves = ["◐", "◓", "◑", "◒"]
174−
175−spinnerMoon :: SpinnerTheme
176−spinnerMoon = ["🌑", "🌒", "🌓", "🌔", "🌕", "🌖", "🌗", "🌘"]
177−
178−minimalSpinnerTheme :: SpinnerTheme
179−minimalSpinnerTheme = spinnerStick
180−
181−defaultSpinnerTheme :: SpinnerTheme
182−defaultSpinnerTheme = spinnerMoon
183−
184-- | Like `bracket` but the `release` function can know whether an exception was raised
140-- | Like `bracket` but the `release` function can know whether an exception was raised
185bracket' :: MonadMask m => m a -> (a -> Maybe c -> m b) -> (a -> m c) -> m c
141bracket' :: MonadMask m => m a -> (a -> Maybe c -> m b) -> (a -> m c) -> m c
186bracket' acquire release use = mask $ \unmasked -> do
142bracket' acquire release use = mask $ \unmasked -> do
lib/cliapp/src/Obelisk/CliApp/Theme.hsnew file+29
@@ -0,0 +1,29 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Obelisk.CliApp.Theme where
3+
4+import Data.Text (Text)
5+
6+data CliTheme = CliTheme
7+ { _cliTheme_done :: Text
8+ , _cliTheme_failed :: Text
9+ , _cliTheme_arrow :: Text
10+ , _cliTheme_spinner :: SpinnerTheme
11+ }
12+
13+type SpinnerTheme = [Text]
14+
15+unicodeTheme :: CliTheme
16+unicodeTheme = CliTheme
17+ { _cliTheme_done = "✔"
18+ , _cliTheme_failed = "✖"
19+ , _cliTheme_arrow = "⇾"
20+ , _cliTheme_spinner = ["◐", "◓", "◑", "◒"]
21+ }
22+
23+noUnicodeTheme :: CliTheme
24+noUnicodeTheme = CliTheme
25+ { _cliTheme_done = "DONE"
26+ , _cliTheme_failed = "FAILED"
27+ , _cliTheme_arrow = "->"
28+ , _cliTheme_spinner = ["|", "/", "-", "\\"]
29+ }
lib/cliapp/src/Obelisk/CliApp/Types.hs+3−3
@@ -20,10 +20,10 @@ import Control.Monad.IO.Class (liftIO)
20import Control.Monad.Trans (MonadTrans, lift)
20import Control.Monad.Trans (MonadTrans, lift)
21import Data.IORef (IORef)
21import Data.IORef (IORef)
22import Data.Text (Text)
22import Data.Text (Text)
23−import System.IO (TextEncoding)
24import System.Exit (ExitCode (..), exitWith)
23import System.Exit (ExitCode (..), exitWith)
25
24
26import Obelisk.CliApp.TerminalString (TerminalString)
25import Obelisk.CliApp.TerminalString (TerminalString)
26+import Obelisk.CliApp.Theme (CliTheme)
27
27
28--------------------------------------------------------------------------------
28--------------------------------------------------------------------------------
29
29
@@ -89,8 +89,8 @@ data CliConfig e = CliConfig
89 _cliConfig_spinnerStack :: IORef ([Bool], [TerminalString])
89 _cliConfig_spinnerStack :: IORef ([Bool], [TerminalString])
90 , -- | Failure handler. How to log error and what exit status to use.
90 , -- | Failure handler. How to log error and what exit status to use.
91 _cliConfig_errorLogExitCode :: e -> (Text, Int)
91 _cliConfig_errorLogExitCode :: e -> (Text, Int)
92− , -- | Type of encoding allowed
92+ , -- | Theme strings for spinners
93− _cliConfig_textEncoding :: Maybe TextEncoding
93+ _cliConfig_theme :: CliTheme
94 }
94 }
95
95
96class Monad m => HasCliConfig e m | m -> e where
96class Monad m => HasCliConfig e m | m -> e where
lib/command/src/Obelisk/Command.hs+3−3
@@ -17,6 +17,7 @@ import Data.List
17import Data.Maybe (catMaybes)
17import Data.Maybe (catMaybes)
18import qualified Data.Text as T
18import qualified Data.Text as T
19import Data.Text.Encoding
19import Data.Text.Encoding
20+import Data.Text.Encoding.Error (lenientDecode)
20import GHC.StaticPtr
21import GHC.StaticPtr
21import Options.Applicative
22import Options.Applicative
22import System.Directory
23import System.Directory
@@ -330,8 +331,7 @@ ob = \case
330 route = _deployInitOpts_route deployOpts
331 route = _deployInitOpts_route deployOpts
331 adminEmail = _deployInitOpts_adminEmail deployOpts
332 adminEmail = _deployInitOpts_adminEmail deployOpts
332 enableHttps = _deployInitOpts_enableHttps deployOpts
333 enableHttps = _deployInitOpts_enableHttps deployOpts
333− deployInit thunkPtr (root </> "config") deployDir
334− sshKeyPath hostname route adminEmail enableHttps
334+ deployInit thunkPtr deployDir sshKeyPath hostname route adminEmail enableHttps
335 DeployCommand_Push remoteBuilder -> do
335 DeployCommand_Push remoteBuilder -> do
336 deployPath <- liftIO $ canonicalizePath "."
336 deployPath <- liftIO $ canonicalizePath "."
337 deployPush deployPath $ case remoteBuilder of
337 deployPush deployPath $ case remoteBuilder of
@@ -361,7 +361,7 @@ getArgsConfig :: IO ArgsConfig
361getArgsConfig = pure $ ArgsConfig { _argsConfig_enableVmBuilderByDefault = System.Info.os == "darwin" }
361getArgsConfig = pure $ ArgsConfig { _argsConfig_enableVmBuilderByDefault = System.Info.os == "darwin" }
362
362
363encodeStaticKey :: StaticKey -> String
363encodeStaticKey :: StaticKey -> String
364−encodeStaticKey = T.unpack . decodeUtf8 . Base16.encode . LBS.toStrict . Binary.encode
364+encodeStaticKey = T.unpack . decodeUtf8With lenientDecode . Base16.encode . LBS.toStrict . Binary.encode
365
365
366-- TODO: Use failWith in place of fail to be consistent.
366-- TODO: Use failWith in place of fail to be consistent.
367decodeStaticKey :: String -> Either String StaticKey
367decodeStaticKey :: String -> Either String StaticKey
lib/command/src/Obelisk/Command/Deploy.hs+15−10
@@ -47,33 +47,38 @@ deployInit
47 => ThunkPtr
47 => ThunkPtr
48 -> FilePath
48 -> FilePath
49 -> FilePath
49 -> FilePath
50− -> FilePath
51 -> [String] -- ^ hostnames
50 -> [String] -- ^ hostnames
52 -> String -- ^ route
51 -> String -- ^ route
53 -> String -- ^ admin email
52 -> String -- ^ admin email
54 -> Bool -- ^ enable https
53 -> Bool -- ^ enable https
55 -> m ()
54 -> m ()
56−deployInit thunkPtr configDir deployDir sshKeyPath hostnames route adminEmail enableHttps = do
55+deployInit thunkPtr deployDir sshKeyPath hostnames route adminEmail enableHttps = do
57− (hasConfigDir, localKey) <- withSpinner ("Preparing " <> T.pack deployDir) $ do
56+ localKey <- withSpinner ("Preparing " <> T.pack deployDir) $ do
58− hasConfigDir <- liftIO $ do
59− createDirectoryIfMissing True deployDir
60− doesDirectoryExist configDir
61 localKey <- liftIO (doesFileExist sshKeyPath) >>= \case
57 localKey <- liftIO (doesFileExist sshKeyPath) >>= \case
62 False -> failWith $ T.pack $ "ob deploy init: file does not exist: " <> sshKeyPath
58 False -> failWith $ T.pack $ "ob deploy init: file does not exist: " <> sshKeyPath
63 True -> pure $ deployDir </> "ssh_key"
59 True -> pure $ deployDir </> "ssh_key"
64 callProcessAndLogOutput (Notice, Error) $
60 callProcessAndLogOutput (Notice, Error) $
65 proc "cp" [sshKeyPath, localKey]
61 proc "cp" [sshKeyPath, localKey]
66 liftIO $ setFileMode localKey $ ownerReadMode .|. ownerWriteMode
62 liftIO $ setFileMode localKey $ ownerReadMode .|. ownerWriteMode
67− return $ (hasConfigDir, localKey)
63+ return localKey
68 withSpinner "Validating configuration" $ do
64 withSpinner "Validating configuration" $ do
69 void $ getHostFromRoute enableHttps route -- make sure that hostname is present
65 void $ getHostFromRoute enableHttps route -- make sure that hostname is present
70 forM_ hostnames $ \hostname -> do
66 forM_ hostnames $ \hostname -> do
71 putLog Notice $ "Verifying host keys (" <> T.pack hostname <> ")"
67 putLog Notice $ "Verifying host keys (" <> T.pack hostname <> ")"
72 -- Note: we can't use a spinner here as this function will prompt the user.
68 -- Note: we can't use a spinner here as this function will prompt the user.
73 verifyHostKey (deployDir </> "backend_known_hosts") localKey hostname
69 verifyHostKey (deployDir </> "backend_known_hosts") localKey hostname
70+ --IMPORTANT: We cannot copy config directory from the development project to
71+ --the deployment directory. If we do, it's very likely someone will
72+ --accidentally create a production deployment that uses development
73+ --credentials to connect to some resources. This could result in, e.g.,
74+ --production data backed up to a dev environment.
74− when hasConfigDir $ withSpinner "Importing project configuration" $ do
75+ withSpinner "Creating project configuration directories" $ do
75 callProcessAndLogOutput (Notice, Error) $
76 callProcessAndLogOutput (Notice, Error) $
77+ proc "mkdir" [ "-p"
78+ , deployDir </> "config" </> "backend"
79+ , deployDir </> "config" </> "common"
76− proc "cp" [ "-r" , "-T" , configDir , deployDir </> "config"]
80+ , deployDir </> "config" </> "frontend"
81+ ]
77 withSpinner "Writing deployment configuration" $ do
82 withSpinner "Writing deployment configuration" $ do
78 writeDeployConfig deployDir "backend_hosts" $ unlines hostnames
83 writeDeployConfig deployDir "backend_hosts" $ unlines hostnames
79 writeDeployConfig deployDir "enable_https" $ show enableHttps
84 writeDeployConfig deployDir "enable_https" $ show enableHttps
@@ -131,7 +136,7 @@ deployPush deployPath getNixBuilders = do
131 withSpinner "Uploading closures" $ ifor_ buildOutputByHost $ \host outputPath -> do
136 withSpinner "Uploading closures" $ ifor_ buildOutputByHost $ \host outputPath -> do
132 callProcess'
137 callProcess'
133 (Map.fromList [("NIX_SSHOPTS", unwords sshOpts)])
138 (Map.fromList [("NIX_SSHOPTS", unwords sshOpts)])
134− "nix-copy-closure" ["-v", "--to", "root@" <> host, "--gzip", outputPath]
139+ "nix-copy-closure" ["-v", "--to", "--use-substitutes", "root@" <> host, "--gzip", outputPath]
135 withSpinner "Uploading config" $ ifor_ buildOutputByHost $ \host _ -> do
140 withSpinner "Uploading config" $ ifor_ buildOutputByHost $ \host _ -> do
136 callProcessAndLogOutput (Notice, Warning) $
141 callProcessAndLogOutput (Notice, Warning) $
137 proc "rsync"
142 proc "rsync"
lib/command/src/Obelisk/Command/Run.hs+20−5
@@ -54,11 +54,26 @@ run = do
54 pkgs <- getLocalPkgs
54 pkgs <- getLocalPkgs
55 withGhciScript pkgs $ \dotGhciPath -> do
55 withGhciScript pkgs $ \dotGhciPath -> do
56 freePort <- getFreePort
56 freePort <- getFreePort
57− assets <- withProjectRoot "." $ \root ->
57+ assets <- withProjectRoot "." $ \root -> do
58+ let importableRoot = if "/" `isInfixOf` root
59+ then root
60+ else "./" <> root
61+ isDerivation <- readProcessAndLogStderr Debug $
62+ proc "nix"
63+ [ "eval"
64+ , "-f"
65+ , root
66+ , "(let a = import " <> importableRoot <> " {}; in toString (a.reflex.nixpkgs.lib.isDerivation a.passthru.staticFilesImpure))"
67+ , "--raw"
58− -- `--raw` is not available with old nix-instantiate. It drops quotation
68+ -- `--raw` is not available with old nix-instantiate. It drops quotation
59− -- marks and trailing newline, so is very convenient for shelling out.
69+ -- marks and trailing newline, so is very convenient for shelling out.
70+ ]
71+ -- Check whether the impure static files are a derivation (and so must be built)
72+ if isDerivation == "1"
60− readProcessAndLogStderr Debug $
73+ then fmap T.strip $ readProcessAndLogStderr Debug $ -- Strip whitespace here because nix-build has no --raw option
74+ proc "nix-build" ["-E", "(import " <> importableRoot <> "{}).passthru.staticFilesImpure"]
75+ else readProcessAndLogStderr Debug $
61− proc "nix" ["eval", "-f", root, "passthru.staticFilesImpure", "--raw"]
76+ proc "nix" ["eval", "-f", root, "passthru.staticFilesImpure", "--raw"]
62 putLog Debug $ "Assets impurely loaded from: " <> assets
77 putLog Debug $ "Assets impurely loaded from: " <> assets
63 runGhcid dotGhciPath $ Just $ unwords
78 runGhcid dotGhciPath $ Just $ unwords
64 [ "run"
79 [ "run"
lib/frontend/obelisk-frontend.cabal+1
@@ -11,6 +11,7 @@ library
11 ghcjs-dom,
11 ghcjs-dom,
12 jsaddle,
12 jsaddle,
13 lens,
13 lens,
14+ obelisk-executable-config-inject,
14 obelisk-route,
15 obelisk-route,
15 primitive,
16 primitive,
16 ref-tf,
17 ref-tf,
lib/frontend/src/Obelisk/Frontend.hs+5−1
@@ -42,6 +42,7 @@ import Obelisk.Route.Frontend
42import Reflex.Dom.Core
42import Reflex.Dom.Core
43import Reflex.Host.Class
43import Reflex.Host.Class
44import qualified Reflex.TriggerEvent.Base as TriggerEvent
44import qualified Reflex.TriggerEvent.Base as TriggerEvent
45+import Obelisk.ExecutableConfig.Inject (injectExecutableConfigs)
45
46
46makePrisms ''Sum
47makePrisms ''Sum
47
48
@@ -163,6 +164,9 @@ renderFrontendHtml urlEnc route headWidget bodyWidget = do
163 --TODO: We should probably have a "NullEventWriterT" or a frozen reflex timeline
164 --TODO: We should probably have a "NullEventWriterT" or a frozen reflex timeline
164 html <- fmap snd $ renderStatic $ fmap fst $ flip runRouteToUrlT urlEnc $ runSetRouteT $ flip runRoutedT (pure route) $
165 html <- fmap snd $ renderStatic $ fmap fst $ flip runRouteToUrlT urlEnc $ runSetRouteT $ flip runRoutedT (pure route) $
165 el "html" $ do
166 el "html" $ do
166− el "head" headWidget
167+ el "head" $ do
168+ let baseTag = elAttr "base" ("href" =: "/") blank --TODO: Figure out the base URL from the routes
169+ -- The order here is important - baseTag has to be before headWidget!
170+ baseTag >> injectExecutableConfigs >> headWidget
167 el "body" bodyWidget
171 el "body" bodyWidget
168 return $ "<!DOCTYPE html>" <> html
172 return $ "<!DOCTYPE html>" <> html
lib/route/src/Obelisk/Route.hs+55
@@ -19,6 +19,7 @@
19module Obelisk.Route
19module Obelisk.Route
20 ( R
20 ( R
21 , pattern (:/)
21 , pattern (:/)
22+ , hoistR
22 , PageName
23 , PageName
23 , PathQuery
24 , PathQuery
24 , Encoder
25 , Encoder
@@ -78,6 +79,12 @@ module Obelisk.Route
78 , void1Encoder
79 , void1Encoder
79 , pathSegmentsTextEncoder
80 , pathSegmentsTextEncoder
80 , queryParametersTextEncoder
81 , queryParametersTextEncoder
82+ , renderObeliskRoute
83+ , renderBackendRoute
84+ , renderFrontendRoute
85+ , readShowEncoder
86+ , integralEncoder
87+ , pathSegmentEncoder
81 ) where
88 ) where
82
89
83import Prelude hiding ((.), id)
90import Prelude hiding ((.), id)
@@ -114,6 +121,7 @@ import qualified Data.Text as T
114import Data.Text.Encoding
121import Data.Text.Encoding
115import Data.Universe
122import Data.Universe
116import Network.HTTP.Types.URI
123import Network.HTTP.Types.URI
124+import qualified Numeric.Lens
117import Obelisk.Route.TH
125import Obelisk.Route.TH
118import Text.Read (readMaybe)
126import Text.Read (readMaybe)
119
127
@@ -149,6 +157,9 @@ pattern a :/ b = a :=> Identity b
149mapSome :: (forall a. f a -> g a) -> Some f -> Some g
157mapSome :: (forall a. f a -> g a) -> Some f -> Some g
150mapSome f (Some.This a) = Some.This $ f a
158mapSome f (Some.This a) = Some.This $ f a
151
159
160+hoistR :: (forall x. f x -> g x) -> R f -> R g
161+hoistR f (x :=> Identity y) = f x :/ y
162+
152--------------------------------------------------------------------------------
163--------------------------------------------------------------------------------
153-- Encoder fundamentals
164-- Encoder fundamentals
154--------------------------------------------------------------------------------
165--------------------------------------------------------------------------------
@@ -824,4 +835,48 @@ makePrisms ''ObeliskRoute
824deriveGEq ''Void1
835deriveGEq ''Void1
825deriveGCompare ''Void1
836deriveGCompare ''Void1
826
837
838+-- | Given a backend route and a checked route encoder, render the route (path
839+-- and query string). See 'checkEncoder' for how to produce a checked encoder.
840+renderBackendRoute
841+ :: forall br a.
842+ Encoder Identity Identity (R (Sum br a)) PageName
843+ -> R br
844+ -> Text
845+renderBackendRoute enc = renderObeliskRoute enc . hoistR InL
846+
847+-- | Renders a frontend route with the supplied checked encoder
848+renderFrontendRoute
849+ :: forall a fr.
850+ Encoder Identity Identity (R (Sum a (ObeliskRoute fr))) PageName
851+ -> R fr
852+ -> Text
853+renderFrontendRoute enc = renderObeliskRoute enc . hoistR (InR . ObeliskRoute_App)
854+
855+-- | Renders a route of the form typically found in an Obelisk project
856+renderObeliskRoute
857+ :: forall a b.
858+ Encoder Identity Identity (R (Sum a b)) PageName
859+ -> R (Sum a b)
860+ -> Text
861+renderObeliskRoute e r =
862+ let enc :: Encoder Identity (Either Text) (R (Sum a b)) PathQuery
863+ enc = (pageNameEncoder . hoistParse (pure . runIdentity) e)
864+ in (T.pack . uncurry (<>)) $ encode enc r
865+
866+readShowEncoder :: (MonadError Text parse, Read a, Show a, Applicative check) => Encoder check parse a PageName
867+readShowEncoder = singlePathSegmentEncoder . unsafeTshowEncoder
868+
869+
870+integralEncoder :: (MonadError Text parse, Applicative check, Integral a) => Encoder check parse a Integer
871+integralEncoder = prismEncoder (Numeric.Lens.integral)
872+
873+pathSegmentEncoder :: (MonadError Text parse, Applicative check) =>
874+ Encoder check parse (Text, PageName) PageName
875+pathSegmentEncoder = unsafeMkEncoder EncoderImpl
876+ { _encoderImpl_encode = \(x, (y, z)) -> (x:y, z)
877+ , _encoderImpl_decode = \(xss, y) -> case xss of
878+ [] -> throwError "not enough path segments"
879+ (x:xs) -> pure (x, (xs, y))
880+ }
881+
827--TODO: decodeURIComponent as appropriate
882--TODO: decodeURIComponent as appropriate
lib/route/src/Obelisk/Route/Frontend.hs+1−1
@@ -171,7 +171,7 @@ instance (Monad m, SetRoute t r m) => SetRoute t r (QueryT t q m) where
171runRoutedT :: RoutedT t r m a -> Dynamic t r -> m a
171runRoutedT :: RoutedT t r m a -> Dynamic t r -> m a
172runRoutedT = runReaderT . unRoutedT
172runRoutedT = runReaderT . unRoutedT
173
173
174−mapRoutedT :: (m a -> n a) -> RoutedT t r m a -> RoutedT t r n a
174+mapRoutedT :: (m a -> n b) -> RoutedT t r m a -> RoutedT t r n b
175mapRoutedT f = RoutedT . mapReaderT f . unRoutedT
175mapRoutedT f = RoutedT . mapReaderT f . unRoutedT
176
176
177withRoutedT :: (Dynamic t r -> Dynamic t r') -> RoutedT t r' m a -> RoutedT t r m a
177withRoutedT :: (Dynamic t r -> Dynamic t r') -> RoutedT t r' m a -> RoutedT t r m a
lib/run/obelisk-run.cabal−1
@@ -25,7 +25,6 @@ library
25 , obelisk-asset-serve-snap
25 , obelisk-asset-serve-snap
26 , obelisk-backend
26 , obelisk-backend
27 , obelisk-executable-config
27 , obelisk-executable-config
28− , obelisk-executable-config-inject
29 , obelisk-frontend
28 , obelisk-frontend
30 , obelisk-route
29 , obelisk-route
31 , process
30 , process
lib/run/src/Obelisk/Run.hs+1−2
@@ -50,7 +50,6 @@ import Network.WebSockets (ConnectionOptions)
50import Network.WebSockets.Connection (defaultConnectionOptions)
50import Network.WebSockets.Connection (defaultConnectionOptions)
51import qualified Obelisk.Asset.Serve.Snap as Snap
51import qualified Obelisk.Asset.Serve.Snap as Snap
52import Obelisk.ExecutableConfig (get)
52import Obelisk.ExecutableConfig (get)
53−import Obelisk.ExecutableConfig.Inject (injectExecutableConfigs)
54import Obelisk.Frontend
53import Obelisk.Frontend
55import Obelisk.Route.Frontend
54import Obelisk.Route.Frontend
56import Reflex.Dom.Core
55import Reflex.Dom.Core
@@ -152,7 +151,7 @@ renderJsaddleFrontend :: (route -> Text) -> route -> Frontend route -> IO ByteSt
152renderJsaddleFrontend urlEnc r f =
151renderJsaddleFrontend urlEnc r f =
153 let jsaddleScript = elAttr "script" ("src" =: "/jsaddle/jsaddle.js") blank
152 let jsaddleScript = elAttr "script" ("src" =: "/jsaddle/jsaddle.js") blank
154 jsaddlePreload = elAttr "link" ("rel" =: "preload" <> "as" =: "script" <> "href" =: "/jsaddle/jsaddle.js") blank
153 jsaddlePreload = elAttr "link" ("rel" =: "preload" <> "as" =: "script" <> "href" =: "/jsaddle/jsaddle.js") blank
155− in renderFrontendHtml urlEnc r (_frontend_head f >> injectExecutableConfigs >> jsaddlePreload) (_frontend_body f >> jsaddleScript)
154+ in renderFrontendHtml urlEnc r (_frontend_head f >> jsaddlePreload) (_frontend_body f >> jsaddleScript)
156
155
157-- | like 'bindPortTCP' but reconnects on exception
156-- | like 'bindPortTCP' but reconnects on exception
158bindPortTCPRetry :: Settings
157bindPortTCPRetry :: Settings
skeleton/common/common.cabal+3−1
@@ -9,7 +9,9 @@ library
9 , obelisk-route
9 , obelisk-route
10 , mtl
10 , mtl
11 , text
11 , text
12−
12+ default-extensions:
13+ TypeFamilies
14+ PolyKinds
13 exposed-modules:
15 exposed-modules:
14 Common.Api
16 Common.Api
15 Common.Route
17 Common.Route