Skip to content

Commit 05b797a

Browse files
Support pre-release versions for PureScript tool (#27)
Enables `unstable` version, which will use the latest release found, whether it has pre-release identifiers or build metadata or not. Also `main` now works on the `versions-v2.json` file to support the `unstable` version. The `versions.json` file is unchanged so that users of this library who do not depend on the `main` branch directly won't have their CI suddenly break because the schema was changed. * Run constructBuildPlan in Aff * Enable fFGHR to use first pre-release found * Dedent 'where' functions and add type sigs * Export fFGHR and use in getVersionField * Make unstable get latest release (pre-release or not) * Update readme about 'unstable' version * Duplicate logic for unstable; print different info msg * Parse desired label's version This JSON encoding doesn't yet produce the JSON that would decode properly here. * Use 1 API call run to get latest and unstable Note: for tools using fetchFromGitHubTags, latest is the same as unstable * Use env to determine which versions file to use in CI * Add v2 file; encode to all versions, decode from v2 * Run npm run build in nix-shell * Run node update.js
1 parent 5f2c1f6 commit 05b797a

File tree

11 files changed

+378
-148
lines changed

11 files changed

+378
-148
lines changed

.github/workflows/integration.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ jobs:
1313
strategy:
1414
matrix:
1515
os: [ubuntu-latest, macos-latest, windows-latest]
16+
env:
17+
USE_LOCAL_VERSIONS_JSON: 1
1618

1719
steps:
1820
- uses: actions/checkout@v2

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,12 @@ Other tools are not enabled by default, but you can enable them by specifying th
3636
3737
### Specify Versions
3838
39-
Each tool can accept a semantic version (only exact versions currently supported) or the string `"latest"`. Tools that are not installed by default must be specified this way to be included in the toolchain.
39+
Each tool can accept one of the following:
40+
- a semantic version (only exact versions currently supported)
41+
- the string `"latest"`, which represents the latest version that uses major, minor, and patch, but will omit versions using pre-release identifiers or build metadata
42+
- the string `"unstable"`, which represents the latest version no matter what it is (i.e. pre-release identifiers and build metadata are not considered).
43+
44+
Tools that are not installed by default must be specified this way to be included in the toolchain.
4045
4146
```yaml
4247
steps:

dist/index.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/update.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/version-v2.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"purs": {
3+
"unstable": "0.14.7",
4+
"latest": "0.14.7"
5+
},
6+
"spago": {
7+
"unstable": "0.20.7",
8+
"latest": "0.20.7"
9+
},
10+
"psa": {
11+
"unstable": "0.8.2",
12+
"latest": "0.8.2"
13+
},
14+
"purs-tidy": {
15+
"unstable": "0.7.0",
16+
"latest": "0.7.0"
17+
},
18+
"zephyr": {
19+
"unstable": "0.5.0-wip2",
20+
"latest": "0.3.2"
21+
}
22+
}

spago.dhall

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,25 @@
77
, "argonaut-core"
88
, "arrays"
99
, "bifunctors"
10+
, "control"
1011
, "effect"
1112
, "either"
1213
, "enums"
1314
, "exceptions"
1415
, "foldable-traversable"
16+
, "foreign-object"
1517
, "github-actions-toolkit"
1618
, "integers"
19+
, "lists"
1720
, "math"
1821
, "maybe"
22+
, "newtype"
1923
, "node-buffer"
2024
, "node-fs"
25+
, "node-fs-aff"
2126
, "node-path"
2227
, "node-process"
28+
, "ordered-collections"
2329
, "parsing"
2430
, "partial"
2531
, "prelude"

src/Main.purs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,28 +5,40 @@ import Prelude
55
import Affjax (printError)
66
import Affjax as AX
77
import Affjax.ResponseFormat as RF
8-
import Control.Monad.Except.Trans (ExceptT(..), mapExceptT, runExceptT)
9-
import Data.Bifunctor (lmap)
8+
import Control.Monad.Except.Trans (ExceptT(..), runExceptT)
9+
import Data.Argonaut.Parser (jsonParser)
10+
import Data.Bifunctor (bimap, lmap)
1011
import Data.Either (Either(..))
1112
import Data.Foldable (traverse_)
13+
import Data.Maybe (isJust)
1214
import Effect (Effect)
1315
import Effect.Aff (error, launchAff_, runAff_)
1416
import Effect.Class (liftEffect)
1517
import Effect.Exception (message)
1618
import GitHub.Actions.Core as Core
19+
import Node.Encoding (Encoding(..))
20+
import Node.FS.Aff as FSA
21+
import Node.Process as Process
1722
import Setup.BuildPlan (constructBuildPlan)
23+
import Setup.Data.VersionFiles (V2FileSchema(..), latestVersion)
1824
import Setup.GetTool (getTool)
1925
import Setup.UpdateVersions (updateVersions)
2026

2127
main :: Effect Unit
2228
main = runAff_ go $ runExceptT do
23-
versionsJson <- ExceptT $ map (lmap (error <<< printError)) $ AX.get RF.json versionsFile
24-
tools <- mapExceptT liftEffect $ constructBuildPlan versionsJson.body
29+
versionsJson <- getVersionsFile
30+
tools <- constructBuildPlan versionsJson
2531
liftEffect $ Core.info "Constructed build plan."
2632
traverse_ getTool tools
2733
liftEffect $ Core.info "Fetched tools."
2834
where
29-
versionsFile = "https://raw.githubusercontent.com/purescript-contrib/setup-purescript/main/dist/versions.json"
35+
getVersionsFile = ExceptT do
36+
let V2FileSchema { localFile, fileUrl } = latestVersion
37+
mb <- liftEffect $ Process.lookupEnv "USE_LOCAL_VERSIONS_JSON"
38+
if isJust mb then do
39+
map (lmap error <<< jsonParser) $ FSA.readTextFile UTF8 localFile
40+
else do
41+
map (bimap (error <<< printError) _.body) $ AX.get RF.json fileUrl
3042

3143
go res = case join res of
3244
Left err -> Core.setFailed (message err)

src/Setup/BuildPlan.purs

Lines changed: 45 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,48 +2,54 @@ module Setup.BuildPlan (constructBuildPlan, BuildPlan) where
22

33
import Prelude
44

5-
import Control.Monad.Except.Trans (ExceptT)
5+
import Control.Monad.Except.Trans (ExceptT, mapExceptT)
66
import Data.Argonaut.Core (Json)
7-
import Data.Argonaut.Decode (decodeJson, printJsonDecodeError, (.:))
87
import Data.Array as Array
9-
import Data.Bifunctor (lmap)
108
import Data.Either (Either(..))
119
import Data.Foldable (fold)
10+
import Data.Map as Map
1211
import Data.Maybe (Maybe(..))
1312
import Data.Traversable (traverse)
1413
import Data.Version (Version)
1514
import Data.Version as Version
16-
import Effect (Effect)
17-
import Effect.Aff (error, throwError)
15+
import Effect.Aff (Aff, error, throwError)
1816
import Effect.Class (liftEffect)
1917
import Effect.Exception (Error)
2018
import GitHub.Actions.Core as Core
2119
import Setup.Data.Key (Key)
2220
import Setup.Data.Key as Key
2321
import Setup.Data.Tool (Tool)
2422
import Setup.Data.Tool as Tool
25-
import Text.Parsing.Parser (parseErrorMessage)
23+
import Setup.Data.VersionFiles (V2FileSchema(..), latestVersion, printV2FileError)
2624
import Text.Parsing.Parser as ParseError
2725

2826
-- | The list of tools that should be downloaded and cached by the action
2927
type BuildPlan = Array { tool :: Tool, version :: Version }
3028

3129
-- | Construct the list of tools that sholud be downloaded and cached by the action
32-
constructBuildPlan :: Json -> ExceptT Error Effect BuildPlan
30+
constructBuildPlan :: Json -> ExceptT Error Aff BuildPlan
3331
constructBuildPlan json = map Array.catMaybes $ traverse (resolve json) Tool.allTools
3432

3533
-- | The parsed value of an input field that specifies a version
36-
data VersionField = Latest | Exact Version
34+
data VersionField
35+
-- | Lookup the latest release, pre-release or not
36+
= Unstable
37+
-- | Lookup the latest release that is not a pre-release
38+
| Latest
39+
-- | Use the given version
40+
| Exact Version
3741

3842
-- | Attempt to read the value of an input specifying a tool version
39-
getVersionField :: Key -> ExceptT Error Effect (Maybe VersionField)
43+
getVersionField :: Key -> ExceptT Error Aff (Maybe VersionField)
4044
getVersionField key = do
41-
value <- Core.getInput' (Key.toString key)
45+
value <- mapExceptT liftEffect $ Core.getInput' (Key.toString key)
4246
case value of
4347
"" ->
4448
pure Nothing
4549
"latest" ->
4650
pure (pure Latest)
51+
"unstable" ->
52+
pure (pure Unstable)
4753
val -> case Version.parseVersion val of
4854
Left msg -> do
4955
liftEffect $ Core.error $ fold [ "Failed to parse version ", val ]
@@ -53,7 +59,7 @@ getVersionField key = do
5359

5460
-- | Resolve the exact version to provide for a tool in the environment, based
5561
-- | on the action.yml file.
56-
resolve :: Json -> Tool -> ExceptT Error Effect (Maybe { tool :: Tool, version :: Version })
62+
resolve :: Json -> Tool -> ExceptT Error Aff (Maybe { tool :: Tool, version :: Version })
5763
resolve versionsContents tool = do
5864
let key = Key.fromTool tool
5965
field <- getVersionField key
@@ -65,16 +71,33 @@ resolve versionsContents tool = do
6571
pure (pure { tool, version: v })
6672

6773
Just Latest -> liftEffect do
68-
Core.info $ fold [ "Fetching latest tag for ", Tool.name tool ]
74+
Core.info $ fold [ "Fetching latest stable tag for ", Tool.name tool ]
75+
readVersionFromFile "latest" _.latest
6976

70-
let
71-
version = lmap printJsonDecodeError $ (_ .: Tool.name tool) =<< decodeJson versionsContents
72-
parse = lmap parseErrorMessage <<< Version.parseVersion
77+
Just Unstable -> liftEffect do
78+
Core.info $ fold [ "Fetching latest tag (pre-release or not) for ", Tool.name tool ]
79+
readVersionFromFile "unstable" _.unstable
80+
where
81+
readVersionFromFile fieldName fieldSelector = do
82+
let V2FileSchema { decode } = latestVersion
83+
case decode versionsContents of
84+
Left err -> do
85+
Core.setFailed $ fold
86+
[ "Unable to parse version for field '"
87+
, fieldName
88+
, "': "
89+
, printV2FileError err
90+
]
91+
throwError $ error "Unable to complete fetching version."
7392

74-
case parse =<< version of
75-
Left e -> do
76-
Core.setFailed $ fold [ "Unable to parse version: ", e ]
77-
throwError $ error "Unable to complete fetching version."
78-
79-
Right v -> do
80-
pure (pure { tool, version: v })
93+
Right toolMap
94+
| Just v <- Map.lookup tool toolMap -> do
95+
pure (pure { tool, version: fieldSelector v })
96+
| otherwise -> do
97+
Core.setFailed $ fold
98+
[ "Unable to find version for tool '"
99+
, Tool.name tool
100+
, "'. Tools found were: "
101+
, show $ map Tool.name $ Array.fromFoldable $ Map.keys toolMap
102+
]
103+
throwError $ error "Unable to complete fetching version."

src/Setup/Data/Tool.purs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ module Setup.Data.Tool where
33
import Prelude
44

55
import Affjax (URL)
6+
import Data.Bounded.Generic (genericBottom, genericTop)
67
import Data.Either (fromRight')
78
import Data.Enum (class Enum, upFromIncluding)
9+
import Data.Enum.Generic (genericPred, genericSucc)
810
import Data.Foldable (elem, fold)
911
import Data.Generic.Rep (class Generic)
10-
import Data.Bounded.Generic (genericBottom, genericTop)
11-
import Data.Enum.Generic (genericPred, genericSucc)
1212
import Data.Version (Version, parseVersion)
1313
import Data.Version as Version
1414
import Node.Path (FilePath)

src/Setup/Data/VersionFiles.purs

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
module Setup.Data.VersionFiles where
2+
3+
import Prelude
4+
5+
import Data.Argonaut.Core (Json, jsonEmptyObject)
6+
import Data.Argonaut.Decode (JsonDecodeError(..), decodeJson, printJsonDecodeError)
7+
import Data.Argonaut.Encode (encodeJson, (:=), (~>))
8+
import Data.Array (fold)
9+
import Data.Bifunctor (lmap)
10+
import Data.Either (Either(..))
11+
import Data.FoldableWithIndex (foldlWithIndex)
12+
import Data.Map (Map)
13+
import Data.Map as Map
14+
import Data.Maybe (Maybe(..))
15+
import Data.Newtype (class Newtype)
16+
import Data.TraversableWithIndex (forWithIndex)
17+
import Data.Tuple (Tuple(..))
18+
import Data.Version (Version)
19+
import Data.Version as Version
20+
import Foreign.Object (Object)
21+
import Setup.Data.Tool (Tool(..))
22+
import Text.Parsing.Parser (ParseError)
23+
24+
latestVersion :: V2FileSchema
25+
latestVersion = version2
26+
27+
data V2FileError
28+
= JsonCodecError JsonDecodeError
29+
| VersionParseError String ParseError
30+
| ToolNameError JsonDecodeError
31+
32+
printV2FileError :: V2FileError -> String
33+
printV2FileError = case _ of
34+
JsonCodecError e -> printJsonDecodeError e
35+
VersionParseError field e -> fold
36+
[ "Version parse failure for key, "
37+
, field
38+
, "': "
39+
, show e
40+
]
41+
ToolNameError e -> fold
42+
[ "Unable to convert String into Tool. "
43+
, printJsonDecodeError e
44+
]
45+
46+
type LatestUnstable a =
47+
{ latest :: a
48+
, unstable :: a
49+
}
50+
51+
newtype V2FileSchema = V2FileSchema
52+
{ fileUrl :: String
53+
, localFile :: String
54+
, encode :: Map Tool (LatestUnstable Version) -> Json
55+
, decode :: Json -> Either V2FileError (Map Tool (LatestUnstable Version))
56+
}
57+
58+
derive instance Newtype V2FileSchema _
59+
60+
version2 :: V2FileSchema
61+
version2 = V2FileSchema
62+
{ fileUrl: "https://raw.githubusercontent.com/purescript-contrib/setup-purescript/main/dist/versions" <> vSuffix <> ".json"
63+
, localFile: "./dist/version" <> vSuffix <> ".json"
64+
, encode: foldlWithIndex encodeFoldFn jsonEmptyObject
65+
, decode: \j -> do
66+
obj :: Object Json <- lmap JsonCodecError $ decodeJson j
67+
keyVals <- forWithIndex obj \key val -> do
68+
tool <- strToTool key
69+
{ latest
70+
, unstable
71+
} :: LatestUnstable String <- lmap JsonCodecError $ decodeJson val
72+
latest' <- lmap (VersionParseError (key <> ".latest")) $ Version.parseVersion latest
73+
unstable' <- lmap (VersionParseError (key <> ".unstable")) $ Version.parseVersion unstable
74+
pure $ Tuple tool { latest: latest', unstable: unstable' }
75+
pure $ Map.fromFoldable keyVals
76+
}
77+
where
78+
vSuffix = "-v2"
79+
encodeFoldFn tool acc { latest, unstable }
80+
| Just toolStr <- toolToMbString tool = do
81+
let rec = { latest: Version.showVersion latest, unstable: Version.showVersion unstable }
82+
toolStr := rec ~> acc
83+
| otherwise =
84+
acc
85+
86+
-- in case we add support for other tools in the future...
87+
toolToMbString = case _ of
88+
PureScript -> Just "purs"
89+
Spago -> Just "spago"
90+
Psa -> Just "psa"
91+
PursTidy -> Just "purs-tidy"
92+
Zephyr -> Just "zephyr"
93+
94+
strToTool = case _ of
95+
"purs" -> Right PureScript
96+
"spago" -> Right Spago
97+
"psa" -> Right Psa
98+
"purs-tidy" -> Right PursTidy
99+
"zephyr" -> Right Zephyr
100+
str -> Left $ ToolNameError $ UnexpectedValue $ encodeJson str
101+
102+
newtype V1FileSchema = V1FileSchema
103+
{ localFile :: String
104+
, encode :: Map Tool Version -> Json
105+
}
106+
107+
derive instance Newtype V1FileSchema _
108+
109+
version1 :: V1FileSchema
110+
version1 = V1FileSchema
111+
{ localFile: "./dist/versions.json"
112+
, encode: foldlWithIndex encodeFoldFn jsonEmptyObject
113+
}
114+
where
115+
encodeFoldFn tool acc version
116+
| Just toolStr <- printTool tool =
117+
toolStr := Version.showVersion version ~> acc
118+
| otherwise =
119+
acc
120+
121+
-- We preserve the set of tools that existed at the time this version format was produced;
122+
-- if more tools are added, they should map to `Nothing`
123+
printTool = case _ of
124+
PureScript -> Just "purs"
125+
Spago -> Just "spago"
126+
Psa -> Just "psa"
127+
PursTidy -> Just "purs-tidy"
128+
Zephyr -> Just "zephyr"

0 commit comments

Comments
 (0)