diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1706d6c..e2fb684 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,7 +35,7 @@ jobs: key: ${{ runner.os }}-stack-${{ hashFiles('stack.yaml.lock') }}-${{ hashFiles('trypurescript.cabal') }} - name: Build server code - run: stack --no-terminal -j1 build + run: stack --no-terminal build - name: Build server assets if: github.event_name == 'release' diff --git a/CHANGELOG.md b/CHANGELOG.md index c2dcb65..d2a4149 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ New features: Bugfixes: Other improvements: +- Bump the Stackage resolver from `lts-20.9` to `lts-20.26` (GHC 9.2.5 → 9.2.8), the newest snapshot compatible with `purescript-0.15.15` (#322 by @thomashoneyman) +- Modernize the server build: `cabal-version: 2.4`, build with `-Wall -Werror` (with attendant cleanup in `server/Main.hs`), remove unused dependencies and the redundant `Setup.hs` (#322 by @thomashoneyman) +- Speed up CI server builds by removing the `-j1` limit; runners have had 4 vCPUs and 16GB of RAM for years (#322 by @thomashoneyman) +- Serve the site and the compiled `/output` modules with `Cache-Control: no-cache` so browsers revalidate via ETag instead of heuristically caching a stale client for months after a deploy (#322 by @thomashoneyman) ## [v2026-07-05.1](https://github.com/purescript/trypurescript/releases/tag/v2026-07-05.1) diff --git a/Setup.hs b/Setup.hs deleted file mode 100644 index 9a994af..0000000 --- a/Setup.hs +++ /dev/null @@ -1,2 +0,0 @@ -import Distribution.Simple -main = defaultMain diff --git a/deploy/nginx.conf b/deploy/nginx.conf index 606f317..778fc36 100644 --- a/deploy/nginx.conf +++ b/deploy/nginx.conf @@ -44,6 +44,10 @@ server { location / { root /var/www/trypurescript/public; + # Always revalidate (cheap ETag 304s): the HTML references the bundle by + # a fixed name, so without this browsers heuristically cache the site + # and can serve a stale client for months after a deploy. + add_header Cache-Control "no-cache"; } } @@ -86,6 +90,10 @@ server { # match to ensure that we only serve JS files. location ~ ^/output/(.+\.js)$ { add_header Access-Control-Allow-Origin *; + # Module URLs are generated by the compiler and change content when the + # package set or compiler changes; revalidate so browsers never mix + # modules from different deploys. + add_header Cache-Control "no-cache"; alias /var/www/trypurescript/staging/.psci_modules/$1; } } diff --git a/server/Main.hs b/server/Main.hs index fdbbb74..a87da6b 100644 --- a/server/Main.hs +++ b/server/Main.hs @@ -6,18 +6,15 @@ module Main (main) where -import Control.Monad (unless, foldM) -import Control.Monad.Error.Class (throwError) +import Control.Monad (foldM) import Control.Monad.IO.Class (liftIO) -import Control.Monad.Logger (runLogger') import qualified Control.Monad.State as State import Control.Monad.Trans (lift) import Control.Monad.Trans.Except (ExceptT(..), runExceptT) -import Control.Monad.Trans.Reader (runReaderT) import Control.Monad.Writer.Strict (runWriterT) import qualified Data.Aeson as A import Data.Aeson ((.=)) -import Data.Bifunctor (first, second, bimap) +import Data.Bifunctor (second, bimap) import qualified Data.ByteString.Lazy as BL import Data.Default (def) import Data.Function (on) @@ -25,7 +22,6 @@ import qualified Data.IORef as IORef import Data.List (nubBy) import qualified Data.List.NonEmpty as NE import qualified Data.Map as M -import Data.Set (Set) import qualified Data.Set as Set import Data.Text (Text) import qualified Data.Text as T @@ -50,6 +46,7 @@ import System.Environment (getArgs) import System.Exit (exitFailure) import System.FilePath.Glob (glob) import qualified System.IO as IO +import Text.Read (readMaybe) import Web.Scotty import qualified Web.Scotty as Scotty @@ -204,9 +201,9 @@ lookupAllConstructors env = P.everywhereOnTypesM $ \case other -> pure other where lookupConstructor :: P.Environment -> P.ProperName 'P.TypeName -> [P.Qualified (P.ProperName 'P.TypeName)] - lookupConstructor env nm = + lookupConstructor env' nm = [ q - | (q@(P.Qualified (N.ByModuleName _) thisNm), _) <- M.toList (P.types env) + | (q@(P.Qualified (N.ByModuleName _) thisNm), _) <- M.toList (P.types env') , thisNm == nm ] @@ -216,15 +213,15 @@ lookupAllConstructors env = P.everywhereOnTypesM $ \case replaceTypeVariablesAndDesugar :: (Text -> Int -> P.SourceType) -> P.SourceType -> P.SourceType replaceTypeVariablesAndDesugar f ty = State.evalState (P.everywhereOnTypesM go ty) (0, M.empty) where go = \case - P.ParensInType _ ty -> pure ty + P.ParensInType _ inner -> pure inner P.TypeVar _ s -> do - (next, m) <- State.get + (n, m) <- State.get case M.lookup s m of Nothing -> do - let ty = f s next - State.put (next + 1, M.insert s ty m) - pure ty - Just ty -> pure ty + let unknown = f s n + State.put (n + 1, M.insert s unknown m) + pure unknown + Just unknown -> pure unknown other -> pure other tryParseType :: Text -> Maybe P.SourceType @@ -242,16 +239,24 @@ main :: IO () main = do -- Stop mangled "Compiling ModuleName" text IO.hSetBuffering IO.stderr IO.LineBuffering - (portString : inputGlobs) <- getArgs - let port = read portString - inputFiles <- concat <$> traverse glob inputGlobs - e <- runExceptT $ do - modules <- ExceptT $ I.loadAllModules inputFiles - (exts, env) <- ExceptT . I.runMake . I.make $ map (second CST.pureResult) modules - namesEnv <- fmap fst . runWriterT $ foldM P.externsEnv P.primEnv exts - let - allModuleNames = fmap (P.getModuleName . snd) modules - pure (allModuleNames, exts, namesEnv, env) - case e of - Left err -> print err >> exitFailure - Right (allModuleNames, exts, namesEnv, env) -> server allModuleNames exts namesEnv env port + args <- getArgs + case args of + portString : inputGlobs + | Just port <- readMaybe portString -> run port inputGlobs + _ -> do + IO.hPutStrLn IO.stderr "Usage: trypurescript PORT INPUT_GLOB..." + exitFailure + where + run :: Int -> [String] -> IO () + run port inputGlobs = do + inputFiles <- concat <$> traverse glob inputGlobs + e <- runExceptT $ do + modules <- ExceptT $ I.loadAllModules inputFiles + (exts, env) <- ExceptT . I.runMake . I.make $ map (second CST.pureResult) modules + namesEnv <- fmap fst . runWriterT $ foldM P.externsEnv P.primEnv exts + let + allModuleNames = fmap (P.getModuleName . snd) modules + pure (allModuleNames, exts, namesEnv, env) + case e of + Left err -> print err >> exitFailure + Right (allModuleNames, exts, namesEnv, env) -> server allModuleNames exts namesEnv env port diff --git a/stack.yaml b/stack.yaml index 207ddd5..a8de960 100644 --- a/stack.yaml +++ b/stack.yaml @@ -1,4 +1,4 @@ -resolver: lts-20.9 +resolver: lts-20.26 packages: - "." @@ -8,8 +8,11 @@ extra-deps: - process-1.6.13.1 # The Cabal library is not in Stackage - Cabal-3.6.3.0 - # Protolude is not yet in resolver snapshot - - protolude-0.3.1 + # Protolude is not in the resolver snapshot; 0.3.3 (not 0.3.1) is needed + # for compatibility with the bytestring version in lts-20.26 + - protolude-0.3.3 + # purescript pins happy ==1.20.0, but lts-20.26 ships happy-1.20.1.1 + - happy-1.20.0 flags: these: diff --git a/stack.yaml.lock b/stack.yaml.lock index 795f79c..1e49384 100644 --- a/stack.yaml.lock +++ b/stack.yaml.lock @@ -1,7 +1,7 @@ # This file was autogenerated by Stack. # You should not edit this file by hand. # For more information, please see the documentation at: -# https://docs.haskellstack.org/en/stable/lock_files +# https://docs.haskellstack.org/en/stable/topics/lock_files packages: - completed: @@ -33,15 +33,22 @@ packages: original: hackage: Cabal-3.6.3.0 - completed: - hackage: protolude-0.3.1@sha256:1cc9e5a5c26c33a43c52b554443dd9779fef13974eaa0beec7ca6d2551b400da,2647 + hackage: protolude-0.3.3@sha256:0106615f6fe08de16fe512852c728797db17374d36b4a4ec8187563e924b2439,2261 pantry-tree: - sha256: 6452a6ca8d395f7d810139779bb0fd16fc1dbb00f1862630bc08ef5a100430f9 - size: 1645 + sha256: 174c107f3c3af1dc23330ff4431445830aac4b4fdba326e3e654eb491d6e9ca6 + size: 1594 original: - hackage: protolude-0.3.1 + hackage: protolude-0.3.3 +- completed: + hackage: happy-1.20.0@sha256:5d47dc221a9fe964e36aaaa2e1ab7e8f085a225fd6528d6eff310b92360bbe99,5732 + pantry-tree: + sha256: ff2e7df7cfe8746eedc6f69c225fa8c899c26d28f1c4964eeb00a5d757d30351 + size: 8749 + original: + hackage: happy-1.20.0 snapshots: - completed: - sha256: c11fcbeb1aa12761044755b1109d16952ede2cb6147ebde777dd5cb38f784501 - size: 649333 - url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/20/9.yaml - original: lts-20.9 + sha256: 5a59b2a405b3aba3c00188453be172b85893cab8ebc352b1ef58b0eae5d248a2 + size: 650475 + url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/20/26.yaml + original: lts-20.26 diff --git a/trypurescript.cabal b/trypurescript.cabal index 0e54ac3..c19ee3c 100644 --- a/trypurescript.cabal +++ b/trypurescript.cabal @@ -1,8 +1,8 @@ +cabal-version: 2.4 name: trypurescript version: 1.0.0 -cabal-version: >=1.8 build-type: Simple -license: BSD3 +license: BSD-3-Clause license-file: LICENSE copyright: (c) Phil Freeman 2013 maintainer: paf31@cantab.net @@ -10,32 +10,27 @@ synopsis: Interactive PureScript in the Browser description: A simple web app for trying out the PureScript compiler category: Web author: Phil Freeman -data-dir: "" executable trypurescript -- Since no one depends on this project as a library, - -- packages below are `-any` because their versions are determined - -- by the `stack.yml` file. Versions correspond to the ones - -- specified in the resolver (i.e. the package set) + -- packages below are unbounded because their versions are + -- determined by the `stack.yaml` file. Versions correspond to the + -- ones specified in the resolver (i.e. the package set) -- unless it is a versioned library added via `extra-deps` field. - build-depends: base -any, - aeson -any, - bytestring -any, - data-default -any, - directory -any, - filepath -any, - Glob -any, - scotty -any, + build-depends: base, + aeson, + bytestring, + containers, + data-default, + Glob, + mtl, purescript, - containers -any, - http-types -any, - transformers -any, - mtl -any, - text -any, - time -any, - warp -any + scotty, + text, + time, + transformers, + warp hs-source-dirs: server main-is: Main.hs - buildable: True - other-modules: Main - ghc-options: -Werror -O2 -threaded -rtsopts + default-language: Haskell2010 + ghc-options: -Wall -Werror -O2 -threaded -rtsopts