packages feed

github-actions 0.1.0.0 → 0.1.1.0

raw patch · 11 files changed

+477/−125 lines, 11 filesdep ~basedep ~bytestringdep ~hedgehogPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependency ranges changed: base, bytestring, hedgehog, tasty-discover

API changes (from Hackage documentation)

+ Language.Github.Actions.Job.Needs: JobNeedsArray :: NonEmpty JobId -> JobNeeds
+ Language.Github.Actions.Job.Needs: JobNeedsString :: JobId -> JobNeeds
+ Language.Github.Actions.Job.Needs: data JobNeeds
+ Language.Github.Actions.Job.Needs: gen :: MonadGen m => m JobNeeds
+ Language.Github.Actions.Job.Needs: instance Data.Aeson.Types.FromJSON.FromJSON Language.Github.Actions.Job.Needs.JobNeeds
+ Language.Github.Actions.Job.Needs: instance Data.Aeson.Types.ToJSON.ToJSON Language.Github.Actions.Job.Needs.JobNeeds
+ Language.Github.Actions.Job.Needs: instance GHC.Classes.Eq Language.Github.Actions.Job.Needs.JobNeeds
+ Language.Github.Actions.Job.Needs: instance GHC.Classes.Ord Language.Github.Actions.Job.Needs.JobNeeds
+ Language.Github.Actions.Job.Needs: instance GHC.Generics.Generic Language.Github.Actions.Job.Needs.JobNeeds
+ Language.Github.Actions.Job.Needs: instance GHC.Show.Show Language.Github.Actions.Job.Needs.JobNeeds
+ Language.Github.Actions.RunIf: RunIfBool :: Bool -> RunIf
+ Language.Github.Actions.RunIf: RunIfString :: Text -> RunIf
+ Language.Github.Actions.RunIf: data RunIf
+ Language.Github.Actions.RunIf: gen :: MonadGen m => m RunIf
+ Language.Github.Actions.RunIf: instance Data.Aeson.Types.FromJSON.FromJSON Language.Github.Actions.RunIf.RunIf
+ Language.Github.Actions.RunIf: instance Data.Aeson.Types.ToJSON.ToJSON Language.Github.Actions.RunIf.RunIf
+ Language.Github.Actions.RunIf: instance GHC.Classes.Eq Language.Github.Actions.RunIf.RunIf
+ Language.Github.Actions.RunIf: instance GHC.Classes.Ord Language.Github.Actions.RunIf.RunIf
+ Language.Github.Actions.RunIf: instance GHC.Generics.Generic Language.Github.Actions.RunIf.RunIf
+ Language.Github.Actions.RunIf: instance GHC.Show.Show Language.Github.Actions.RunIf.RunIf
+ Language.Github.Actions.UnstructuredMap: UnstructuredMap :: Map Text UnstructuredValue -> UnstructuredMap
+ Language.Github.Actions.UnstructuredMap: UnstructuredValueBool :: Bool -> UnstructuredValue
+ Language.Github.Actions.UnstructuredMap: UnstructuredValueNumber :: Double -> UnstructuredValue
+ Language.Github.Actions.UnstructuredMap: UnstructuredValueString :: Text -> UnstructuredValue
+ Language.Github.Actions.UnstructuredMap: data UnstructuredValue
+ Language.Github.Actions.UnstructuredMap: gen :: MonadGen m => m UnstructuredMap
+ Language.Github.Actions.UnstructuredMap: instance Data.Aeson.Types.FromJSON.FromJSON Language.Github.Actions.UnstructuredMap.UnstructuredMap
+ Language.Github.Actions.UnstructuredMap: instance Data.Aeson.Types.FromJSON.FromJSON Language.Github.Actions.UnstructuredMap.UnstructuredValue
+ Language.Github.Actions.UnstructuredMap: instance Data.Aeson.Types.ToJSON.ToJSON Language.Github.Actions.UnstructuredMap.UnstructuredMap
+ Language.Github.Actions.UnstructuredMap: instance Data.Aeson.Types.ToJSON.ToJSON Language.Github.Actions.UnstructuredMap.UnstructuredValue
+ Language.Github.Actions.UnstructuredMap: instance GHC.Classes.Eq Language.Github.Actions.UnstructuredMap.UnstructuredMap
+ Language.Github.Actions.UnstructuredMap: instance GHC.Classes.Eq Language.Github.Actions.UnstructuredMap.UnstructuredValue
+ Language.Github.Actions.UnstructuredMap: instance GHC.Classes.Ord Language.Github.Actions.UnstructuredMap.UnstructuredMap
+ Language.Github.Actions.UnstructuredMap: instance GHC.Classes.Ord Language.Github.Actions.UnstructuredMap.UnstructuredValue
+ Language.Github.Actions.UnstructuredMap: instance GHC.Generics.Generic Language.Github.Actions.UnstructuredMap.UnstructuredMap
+ Language.Github.Actions.UnstructuredMap: instance GHC.Generics.Generic Language.Github.Actions.UnstructuredMap.UnstructuredValue
+ Language.Github.Actions.UnstructuredMap: instance GHC.Show.Show Language.Github.Actions.UnstructuredMap.UnstructuredMap
+ Language.Github.Actions.UnstructuredMap: instance GHC.Show.Show Language.Github.Actions.UnstructuredMap.UnstructuredValue
+ Language.Github.Actions.UnstructuredMap: newtype UnstructuredMap
+ Language.Github.Actions.UnstructuredMap: renderUnstructuredValue :: UnstructuredValue -> Text
+ Language.Github.Actions.Workflow.Trigger: WorkflowDispatchInput :: Maybe Text -> Maybe Value -> Maybe Bool -> Maybe WorkflowDispatchInputType -> WorkflowDispatchInput
+ Language.Github.Actions.Workflow.Trigger: WorkflowDispatchInputTypeBoolean :: WorkflowDispatchInputType
+ Language.Github.Actions.Workflow.Trigger: WorkflowDispatchInputTypeChoice :: NonEmpty Text -> WorkflowDispatchInputType
+ Language.Github.Actions.Workflow.Trigger: WorkflowDispatchInputTypeEnvironment :: WorkflowDispatchInputType
+ Language.Github.Actions.Workflow.Trigger: WorkflowDispatchInputTypeNumber :: WorkflowDispatchInputType
+ Language.Github.Actions.Workflow.Trigger: WorkflowDispatchInputTypeString :: WorkflowDispatchInputType
+ Language.Github.Actions.Workflow.Trigger: [default_] :: WorkflowDispatchInput -> Maybe Value
+ Language.Github.Actions.Workflow.Trigger: [description] :: WorkflowDispatchInput -> Maybe Text
+ Language.Github.Actions.Workflow.Trigger: [required] :: WorkflowDispatchInput -> Maybe Bool
+ Language.Github.Actions.Workflow.Trigger: [type_] :: WorkflowDispatchInput -> Maybe WorkflowDispatchInputType
+ Language.Github.Actions.Workflow.Trigger: data WorkflowDispatchInput
+ Language.Github.Actions.Workflow.Trigger: data WorkflowDispatchInputType
- Language.Github.Actions.Job: Job :: Maybe Concurrency -> Maybe JobContainer -> Maybe Bool -> Maybe Defaults -> Map Text Text -> Maybe JobEnvironment -> Maybe Text -> Maybe (NonEmpty JobId) -> Map Text Text -> Maybe Permissions -> Maybe Text -> Maybe Text -> Map Text Text -> Map ServiceId Service -> Maybe (NonEmpty Step) -> Maybe JobStrategy -> Maybe Int -> Maybe Text -> Map Text Text -> Job
+ Language.Github.Actions.Job: Job :: Maybe Concurrency -> Maybe JobContainer -> Maybe Bool -> Maybe Defaults -> Map Text Text -> Maybe JobEnvironment -> Maybe Text -> Maybe JobNeeds -> Map Text Text -> Maybe Permissions -> Maybe RunIf -> Maybe Text -> Map Text Text -> Map ServiceId Service -> Maybe (NonEmpty Step) -> Maybe JobStrategy -> Maybe Int -> Maybe Text -> Map Text Text -> Job
- Language.Github.Actions.Job: [needs] :: Job -> Maybe (NonEmpty JobId)
+ Language.Github.Actions.Job: [needs] :: Job -> Maybe JobNeeds
- Language.Github.Actions.Job: [runIf] :: Job -> Maybe Text
+ Language.Github.Actions.Job: [runIf] :: Job -> Maybe RunIf
- Language.Github.Actions.Step: Step :: Bool -> Map Text Text -> Maybe Text -> Maybe Text -> Maybe Text -> Maybe Shell -> Maybe StepId -> Maybe Int -> Maybe Text -> Maybe StepWith -> Maybe Text -> Step
+ Language.Github.Actions.Step: Step :: Bool -> Map Text Text -> Maybe Text -> Maybe Text -> Maybe RunIf -> Maybe Shell -> Maybe StepId -> Maybe Int -> Maybe Text -> Maybe StepWith -> Maybe Text -> Step
- Language.Github.Actions.Step: [runIf] :: Step -> Maybe Text
+ Language.Github.Actions.Step: [runIf] :: Step -> Maybe RunIf
- Language.Github.Actions.Step.With: StepWithEnv :: Map Text Text -> StepWith
+ Language.Github.Actions.Step.With: StepWithEnv :: UnstructuredMap -> StepWith

Files

+ CHANGELOG.md view
@@ -0,0 +1,5 @@+# CHANGELOG++## Version 0.1.0 (2025-07-01)++Released upon an unsuspecting world
github-actions.cabal view
@@ -1,6 +1,6 @@ cabal-version:      2.2 name:               github-actions-version:            0.1.0.0+version:            0.1.1.0 synopsis:           Github Actions description:   This library provides types and instances for serializing and deserializing@@ -15,8 +15,11 @@ copyright:          Copyright (C) 2025 Bellroy Pty Ltd category:           Language build-type:         Simple-extra-source-files:+extra-doc-files:+  CHANGELOG.md   README.md++extra-source-files:   test/golden/configuration-main.golden.yml   test/golden/configuration-main.hs.txt   test/golden/configuration-main.yml@@ -36,11 +39,11 @@   build-depends:     , aeson               ^>=2.2.3.0     , base                >=4.14      && <4.22-    , containers          ==0.6.7     || ==0.6.8 || ==0.7   || ==0.8+    , containers          ^>=0.6.7    || ^>=0.7   || ^>=0.8     , hedgehog            ^>=1.5     , hoist-error         ^>=0.3     , string-interpolate  ^>=0.3.3-    , text                ==1.2.4.1   || ==2.0.2 || ==2.1.1+    , text                ^>=1.2.4.1  || ^>=2.0.2 || ^>=2.1.1     , vector              ^>=0.13.0.0  library@@ -56,14 +59,17 @@     Language.Github.Actions.Job.Container     Language.Github.Actions.Job.Environment     Language.Github.Actions.Job.Id+    Language.Github.Actions.Job.Needs     Language.Github.Actions.Job.Strategy     Language.Github.Actions.Permissions+    Language.Github.Actions.RunIf     Language.Github.Actions.Service     Language.Github.Actions.Service.Id     Language.Github.Actions.Shell     Language.Github.Actions.Step     Language.Github.Actions.Step.Id     Language.Github.Actions.Step.With+    Language.Github.Actions.UnstructuredMap     Language.Github.Actions.Workflow     Language.Github.Actions.Workflow.Trigger 
src/Language/Github/Actions/Job.hs view
@@ -43,12 +43,14 @@ import qualified Language.Github.Actions.Job.Container as JobContainer import Language.Github.Actions.Job.Environment (JobEnvironment) import qualified Language.Github.Actions.Job.Environment as JobEnvironment-import Language.Github.Actions.Job.Id (JobId)-import qualified Language.Github.Actions.Job.Id as JobId+import Language.Github.Actions.Job.Needs (JobNeeds)+import qualified Language.Github.Actions.Job.Needs as JobNeeds import Language.Github.Actions.Job.Strategy (JobStrategy) import qualified Language.Github.Actions.Job.Strategy as JobStrategy import Language.Github.Actions.Permissions (Permissions) import qualified Language.Github.Actions.Permissions as Permissions+import Language.Github.Actions.RunIf (RunIf)+import qualified Language.Github.Actions.RunIf as RunIf import Language.Github.Actions.Service (Service) import qualified Language.Github.Actions.Service as Service import Language.Github.Actions.Service.Id (ServiceId)@@ -93,13 +95,13 @@     -- | Display name for the job     jobName :: Maybe Text,     -- | Jobs this job depends on-    needs :: Maybe (NonEmpty JobId),+    needs :: Maybe JobNeeds,     -- | Outputs from this job     outputs :: Map Text Text,     -- | Permissions for this job     permissions :: Maybe Permissions,     -- | Condition for running this job-    runIf :: Maybe Text,+    runIf :: Maybe RunIf,     -- | Runner type (e.g., "ubuntu-latest")     runsOn :: Maybe Text,     -- | Secrets available to this job@@ -179,10 +181,10 @@   env <- genTextMap   environment <- Gen.maybe JobEnvironment.gen   jobName <- Gen.maybe genText-  needs <- Gen.maybe (Gen.nonEmpty (Range.linear 1 5) JobId.gen)+  needs <- Gen.maybe JobNeeds.gen   outputs <- genTextMap   permissions <- Gen.maybe Permissions.gen-  runIf <- Gen.maybe genText+  runIf <- Gen.maybe RunIf.gen   runsOn <- Gen.maybe genText   secrets <- genTextMap   services <- Gen.map (Range.linear 1 5) $ liftA2 (,) ServiceId.gen Service.gen
+ src/Language/Github/Actions/Job/Needs.hs view
@@ -0,0 +1,78 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DerivingStrategies #-}++-- |+-- Module      : Language.Github.Actions.Job.Needs+-- Description : Job dependency specification for GitHub Actions+-- Copyright   : (c) 2025 Bellroy Pty Ltd+-- License     : BSD-3-Clause+-- Maintainer  : Bellroy Tech Team <haskell@bellroy.com>+--+-- This module provides the 'JobNeeds' type for representing job dependencies+-- in GitHub Actions workflows. GitHub Actions allows both strings and+-- lists of strings for the 'needs' field.+--+-- Examples of valid 'needs' specifications:+-- * @needs: build@ - Single job specified as a string+-- * @needs: [build]@ - Single job specified as a list of strings+-- * @needs: [build, test]@ - Multiple job dependencies specified as a list of strings+--+-- For more information about GitHub Actions job dependencies, see:+-- <https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idneeds>+module Language.Github.Actions.Job.Needs+  ( JobNeeds (..),+    gen,+  )+where++import Data.Aeson (FromJSON, ToJSON (..), Value (..))+import qualified Data.Aeson as Aeson+import Data.List.NonEmpty (NonEmpty)+import GHC.Generics (Generic)+import Hedgehog (MonadGen)+import qualified Hedgehog.Gen as Gen+import qualified Hedgehog.Range as Range+import Language.Github.Actions.Job.Id (JobId)+import qualified Language.Github.Actions.Job.Id as JobId++-- | Job dependency specification that preserves YAML representation.+--+-- GitHub Actions supports flexible job dependency specification:+--+-- * 'JobNeedsString' - Single job dependency as string like @needs: build@+-- * 'JobNeedsArray' - Multiple job dependencies as array like @needs: [build, test]@+--+-- Examples:+--+-- @+-- -- Single job dependency (string form)+-- stringDep :: JobNeeds+-- stringDep = JobNeedsString (JobId "build")+--+-- -- Multiple job dependencies (array form)+-- arrayDeps :: JobNeeds+-- arrayDeps = JobNeedsArray (JobId "build" :| [JobId "test", JobId "lint"])+-- @+--+-- The type preserves the original YAML format during round-trip serialization.+-- A string input will serialize back to a string, and an array input will+-- serialize back to an array, preventing information loss.+data JobNeeds+  = JobNeedsString JobId+  | JobNeedsArray (NonEmpty JobId)+  deriving stock (Eq, Generic, Ord, Show)++instance FromJSON JobNeeds where+  parseJSON v@(Array _) = JobNeedsArray <$> Aeson.parseJSON v+  parseJSON v = JobNeedsString <$> Aeson.parseJSON v++instance ToJSON JobNeeds where+  toJSON (JobNeedsString jobId) = toJSON jobId+  toJSON (JobNeedsArray jobIds) = toJSON jobIds++gen :: (MonadGen m) => m JobNeeds+gen =+  Gen.choice+    [ JobNeedsString <$> JobId.gen,+      JobNeedsArray <$> Gen.nonEmpty (Range.linear 1 5) JobId.gen+    ]
+ src/Language/Github/Actions/RunIf.hs view
@@ -0,0 +1,76 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE OverloadedStrings #-}++-- |+-- Module      : Language.Github.Actions.RunIf+-- Description : Flexible conditional expressions for GitHub Actions+-- Copyright   : (c) 2025 Bellroy Pty Ltd+-- License     : BSD-3-Clause+-- Maintainer  : Bellroy Tech Team <haskell@bellroy.com>+--+-- This module provides the 'RunIf' type for representing conditional expressions+-- in GitHub Actions workflows. GitHub Actions allows both boolean and string+-- expressions in 'if' conditions for jobs and steps.+--+-- Examples of valid 'if' conditions:+-- * @if: false@ - Boolean+-- * @if: "github.ref == 'refs/heads/main'"@ - String+--+-- For more information about GitHub Actions conditional expressions, see:+-- <https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idif>+module Language.Github.Actions.RunIf+  ( RunIf (..),+    gen,+  )+where++import Data.Aeson (FromJSON, ToJSON (..), Value (..))+import qualified Data.Aeson as Aeson+import Data.Text (Text)+import GHC.Generics (Generic)+import Hedgehog (MonadGen)+import qualified Hedgehog.Gen as Gen+import qualified Hedgehog.Range as Range++-- | A conditional expression that can be either a boolean or string.+--+-- GitHub Actions supports flexible 'if' conditions:+--+-- * 'RunIfBool' - Simple boolean values like @true@ or @false@+-- * 'RunIfString' - GitHub expressions like @"github.ref == 'refs/heads/main'"@+--+-- Examples:+--+-- @+-- -- Simple boolean condition+-- simpleFalse :: RunIf+-- simpleFalse = RunIfBool False+--+-- -- GitHub expression condition+-- branchCheck :: RunIf+-- branchCheck = RunIfString "github.ref == 'refs/heads/main'"+-- @+--+-- The type preserves the original format during round-trip serialization,+-- so a boolean input remains a boolean in the output YAML.+data RunIf+  = RunIfBool Bool+  | RunIfString Text+  deriving stock (Eq, Generic, Ord, Show)++instance FromJSON RunIf where+  parseJSON (Bool b) = pure $ RunIfBool b+  parseJSON (String s) = pure $ RunIfString s+  parseJSON v = fail $ "Expected Bool or String for RunIf, got: " ++ show v++instance ToJSON RunIf where+  toJSON (RunIfBool b) = Bool b+  toJSON (RunIfString s) = String s++gen :: (MonadGen m) => m RunIf+gen =+  Gen.choice+    [ RunIfBool <$> Gen.bool,+      RunIfString <$> Gen.text (Range.linear 5 50) Gen.alphaNum+    ]
src/Language/Github/Actions/Step.hs view
@@ -35,6 +35,8 @@ import Hedgehog (MonadGen) import qualified Hedgehog.Gen as Gen import qualified Hedgehog.Range as Range+import Language.Github.Actions.RunIf (RunIf)+import qualified Language.Github.Actions.RunIf as RunIf import Language.Github.Actions.Shell (Shell) import qualified Language.Github.Actions.Shell as Shell import Language.Github.Actions.Step.Id (StepId)@@ -82,7 +84,7 @@     -- | Command or script to run     run :: Maybe Text,     -- | Condition for running this step-    runIf :: Maybe Text,+    runIf :: Maybe RunIf,     -- | Shell to use for running commands     shell :: Maybe Shell,     -- | Unique identifier for this step@@ -139,7 +141,7 @@   env <- genTextMap   name <- Gen.maybe genText   run <- Gen.maybe genText-  runIf <- Gen.maybe genText+  runIf <- Gen.maybe RunIf.gen   shell <- Gen.maybe Shell.gen   stepId <- Gen.maybe StepId.gen   timeoutMinutes <- Gen.maybe $ Gen.int (Range.linear 1 120)
src/Language/Github/Actions/Step/With.hs view
@@ -30,7 +30,6 @@ import Data.Aeson (FromJSON, ToJSON (..), (.:), (.:?), (.=)) import qualified Data.Aeson as Aeson import qualified Data.Aeson.KeyMap as AesonKeyMap-import Data.Map (Map) import Data.Maybe (catMaybes) import qualified Data.Set as Set import Data.Text (Text)@@ -38,6 +37,8 @@ import Hedgehog (MonadGen) import qualified Hedgehog.Gen as Gen import qualified Hedgehog.Range as Range+import Language.Github.Actions.UnstructuredMap (UnstructuredMap)+import qualified Language.Github.Actions.UnstructuredMap as UnstructuredMap  -- | Docker container arguments for Docker actions. --@@ -95,14 +96,14 @@   = -- | Docker action arguments     StepWithDockerArgs StepWithDockerArgsAttrs   | -- | Environment variables/general inputs-    StepWithEnv (Map Text Text)+    StepWithEnv UnstructuredMap   deriving stock (Eq, Generic, Ord, Show)  instance FromJSON StepWith where   parseJSON = Aeson.withObject "StepWith" $ \o ->     let objectKeySet = Set.fromList (AesonKeyMap.keys o)         dockerKeySet = Set.fromList ["entryPoint", "args"]-     in if objectKeySet `Set.isSubsetOf` dockerKeySet+     in if not (null objectKeySet) && objectKeySet `Set.isSubsetOf` dockerKeySet           then do             entryPoint <- o .: "entryPoint"             args <- o .:? "args"@@ -127,8 +128,7 @@         entryPoint <- genText         args <- Gen.maybe genText         pure StepWithDockerArgsAttrs {..},-      StepWithEnv <$> genTextMap+      StepWithEnv <$> UnstructuredMap.gen     ]   where     genText = Gen.text (Range.linear 1 5) Gen.alphaNum-    genTextMap = Gen.map (Range.linear 1 5) $ liftA2 (,) genText genText
+ src/Language/Github/Actions/UnstructuredMap.hs view
@@ -0,0 +1,92 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE OverloadedStrings #-}++-- |+-- Module      : Language.Github.Actions.UnstructuredMap+-- Description : Flexible value types for GitHub Actions YAML parsing+-- Copyright   : (c) 2025 Bellroy Pty Ltd+-- License     : BSD-3-Clause+-- Maintainer  : Bellroy Tech Team <haskell@bellroy.com>+--+-- This module provides the 'UnstructuredMap' type for representing a map of+-- values that can be strings, numbers, or booleans in GitHub Actions YAML files.+--+-- GitHub Actions allows flexible typing in many contexts:+-- * @retention-days: 1@ (number)+-- * @retention-days: "1"@ (string)+-- * @should-retain: false@ (boolean)+--+-- This type preserves the original YAML type during round-trip parsing,+-- ensuring that numeric values remain numeric and strings remain strings.+module Language.Github.Actions.UnstructuredMap+  ( UnstructuredValue (..),+    UnstructuredMap (..),+    renderUnstructuredValue,+    gen,+  )+where++import Data.Aeson (FromJSON, ToJSON (..), Value (..))+import qualified Data.Aeson as Aeson+import Data.Map (Map)+import Data.Text (Text)+import qualified Data.Text as Text+import GHC.Generics (Generic)+import Hedgehog (MonadGen)+import qualified Hedgehog.Gen as Gen+import qualified Hedgehog.Range as Range++-- | A map that can have values of string, number, or boolean.+--+-- This type is designed to handle the flexible typing that GitHub Actions+-- allows in YAML files.+--+-- The type preserves the original format during round-trip serialization,+-- so numeric inputs remain numeric in the output YAML.+data UnstructuredValue+  = UnstructuredValueString Text+  | UnstructuredValueNumber Double+  | UnstructuredValueBool Bool+  deriving stock (Eq, Generic, Ord, Show)++instance FromJSON UnstructuredValue where+  parseJSON (String s) = pure $ UnstructuredValueString s+  parseJSON (Number n) = pure $ UnstructuredValueNumber (realToFrac n)+  parseJSON (Bool b) = pure $ UnstructuredValueBool b+  parseJSON v = fail $ "Expected String, Number, or Bool for UnstructuredValue, got: " ++ show v++instance ToJSON UnstructuredValue where+  toJSON (UnstructuredValueString s) = String s+  toJSON (UnstructuredValueNumber n) = Number (fromRational (toRational n))+  toJSON (UnstructuredValueBool b) = Bool b++renderUnstructuredValue :: UnstructuredValue -> Text+renderUnstructuredValue (UnstructuredValueString s) = s+renderUnstructuredValue (UnstructuredValueNumber n) =+  -- Format numbers nicely, avoiding unnecessary decimal places for integers+  if n == fromInteger (round n)+    then Text.pack (show (round n :: Integer))+    else Text.pack (show n)+renderUnstructuredValue (UnstructuredValueBool b) = if b then "true" else "false"++newtype UnstructuredMap = UnstructuredMap (Map Text UnstructuredValue)+  deriving stock (Eq, Generic, Ord, Show)+  deriving newtype (FromJSON, ToJSON)++genUnstructuredValue :: (MonadGen m) => m UnstructuredValue+genUnstructuredValue =+  Gen.choice+    [ UnstructuredValueString <$> Gen.text (Range.linear 1 20) Gen.alphaNum,+      UnstructuredValueNumber <$> Gen.realFloat (Range.linearFrac 0 1000),+      UnstructuredValueBool <$> Gen.bool+    ]++gen :: (MonadGen m) => m UnstructuredMap+gen = UnstructuredMap <$> Gen.map (Range.linear 0 10) genKeyValue+  where+    genKeyValue = do+      key <- Gen.text (Range.linear 1 20) Gen.alphaNum+      value <- genUnstructuredValue+      pure (key, value)
src/Language/Github/Actions/Workflow/Trigger.hs view
@@ -75,6 +75,8 @@     ReleaseActivityType (..),     WorkflowCallAttributes (..),     WorkflowDispatchAttributes (..),+    WorkflowDispatchInput (..),+    WorkflowDispatchInputType (..),     WorkflowRunActivityType (..),     WorkflowRunTriggerAttributes (..),     WorkflowTrigger (..),
test/Language/Github/Actions/WorkflowTest.hs view
@@ -50,7 +50,7 @@               putStrLn $ "roundtrip " <> takeBaseName testYamlFilePath               eitherWorkflow <- Yaml.decodeFileEither @Workflow testYamlFilePath               either-                (BS.writeFile outputFilePath >> (\_ -> fail "YAML decoding failed"))+                (BS.writeFile outputFilePath >> (\e -> fail $ "YAML decoding failed: " ++ show e))                 (\workflow -> writeOutputFiles outputFilePath haskellOutputFilePath workflow >> pure workflow)                 $ first (encodeUtf8 . Text.pack . Yaml.prettyPrintParseException) eitherWorkflow     writeOutputFiles :: FilePath -> FilePath -> Workflow -> IO ()
test/golden/configuration-main.hs.txt view
@@ -18,7 +18,7 @@               , env = fromList []               , environment = Nothing               , jobName = Nothing-              , needs = Just (JobId "validateContent" :| [])+              , needs = Just (JobNeedsArray (JobId "validateContent" :| []))               , outputs = fromList []               , permissions = Nothing               , runIf = Nothing@@ -34,7 +34,8 @@                        , run = Nothing                        , runIf =                            Just-                             "github.ref == 'refs/heads/master' || github.ref == 'refs/heads/stable'"+                             (RunIfString+                                "github.ref == 'refs/heads/master' || github.ref == 'refs/heads/stable'")                        , shell = Nothing                        , stepId = Nothing                        , timeoutMinutes = Nothing@@ -42,10 +43,11 @@                        , with =                            Just                              (StepWithEnv-                                (fromList-                                   [ ( "fetch-depth" , "0" )-                                   , ( "token" , "${{ env.GH_TOKEN }}" )-                                   ]))+                                (UnstructuredMap+                                   (fromList+                                      [ ( "fetch-depth" , UnstructuredValueString "0" )+                                      , ( "token" , UnstructuredValueString "${{ env.GH_TOKEN }}" )+                                      ])))                        , workingDirectory = Nothing                        } :|                        [ Step@@ -55,7 +57,8 @@                            , run = Nothing                            , runIf =                                Just-                                 "github.ref != 'refs/heads/master' && github.ref != 'refs/heads/stable'"+                                 (RunIfString+                                    "github.ref != 'refs/heads/master' && github.ref != 'refs/heads/stable'")                            , shell = Nothing                            , stepId = Nothing                            , timeoutMinutes = Nothing@@ -63,10 +66,13 @@                            , with =                                Just                                  (StepWithEnv-                                    (fromList-                                       [ ( "fetch-depth" , "1" )-                                       , ( "token" , "${{ env.GH_TOKEN }}" )-                                       ]))+                                    (UnstructuredMap+                                       (fromList+                                          [ ( "fetch-depth" , UnstructuredValueString "1" )+                                          , ( "token"+                                            , UnstructuredValueString "${{ env.GH_TOKEN }}"+                                            )+                                          ])))                            , workingDirectory = Nothing                            }                        , Step@@ -91,7 +97,7 @@                            , run =                                Just                                  "echo \"DESTINATION=Staging Environment\" >> $GITHUB_ENV\necho \"ENVIRONMENT=staging\" >> $GITHUB_ENV\n"-                           , runIf = Just "github.ref == 'refs/heads/master'"+                           , runIf = Just (RunIfString "github.ref == 'refs/heads/master'")                            , shell = Nothing                            , stepId = Nothing                            , timeoutMinutes = Nothing@@ -106,7 +112,7 @@                            , run =                                Just                                  "echo \"DESTINATION=Production Environment\" >> $GITHUB_ENV\necho \"ENVIRONMENT=production\" >> $GITHUB_ENV\n"-                           , runIf = Just "github.ref == 'refs/heads/stable'"+                           , runIf = Just (RunIfString "github.ref == 'refs/heads/stable'")                            , shell = Nothing                            , stepId = Nothing                            , timeoutMinutes = Nothing@@ -127,12 +133,16 @@                            , with =                                Just                                  (StepWithEnv-                                    (fromList-                                       [ ( "bellroy-nix-cache-access" , "none" )-                                       , ( "nix-config-access-tokens"-                                         , "github.com=${{ secrets.READ_HASKELL_REPO_PAT }}"-                                         )-                                       ]))+                                    (UnstructuredMap+                                       (fromList+                                          [ ( "bellroy-nix-cache-access"+                                            , UnstructuredValueString "none"+                                            )+                                          , ( "nix-config-access-tokens"+                                            , UnstructuredValueString+                                                "github.com=${{ secrets.READ_HASKELL_REPO_PAT }}"+                                            )+                                          ])))                            , workingDirectory = Nothing                            }                        , Step@@ -179,7 +189,13 @@                            , with =                                Just                                  (StepWithEnv-                                    (fromList [ ( "path" , "${{ env.GIT_REPOSITORY_PATH }}" ) ]))+                                    (UnstructuredMap+                                       (fromList+                                          [ ( "path"+                                            , UnstructuredValueString+                                                "${{ env.GIT_REPOSITORY_PATH }}"+                                            )+                                          ])))                            , workingDirectory = Nothing                            }                        , Step@@ -206,7 +222,8 @@                            , run =                                Just                                  "set +e\nIFS= output=$(./scripts/check_configuration_csvs_sorted 2>&1)\nstatus=$?\n\nif (($status)); then\n  echo \"Configuration CSV files are not sorted:\" >>/tmp/pr-comment.txt\n  echo \"\" >>/tmp/pr-comment.txt\n  echo \"\\`\\`\\`\" >>/tmp/pr-comment.txt\n  echo $output >>/tmp/pr-comment.txt\n  echo \"\\`\\`\\`\" >>/tmp/pr-comment.txt\n  exit $status\nelse\n  echo \"All configuration CSV files are sorted.\"\nfi\n"-                           , runIf = Just "steps.check_changes.outputs.csv_changed == '1'"+                           , runIf =+                               Just (RunIfString "steps.check_changes.outputs.csv_changed == '1'")                            , shell = Nothing                            , stepId = Nothing                            , timeoutMinutes = Nothing@@ -227,11 +244,15 @@                            , with =                                Just                                  (StepWithEnv-                                    (fromList-                                       [ ( "token" , "${{ secrets.BELLROY_DEPLOY_USER_TOKEN }}" )-                                       , ( "tool-name" , "byproduct" )-                                       , ( "tool-version" , "latest" )-                                       ]))+                                    (UnstructuredMap+                                       (fromList+                                          [ ( "token"+                                            , UnstructuredValueString+                                                "${{ secrets.BELLROY_DEPLOY_USER_TOKEN }}"+                                            )+                                          , ( "tool-name" , UnstructuredValueString "byproduct" )+                                          , ( "tool-version" , UnstructuredValueString "latest" )+                                          ])))                            , workingDirectory = Nothing                            }                        , Step@@ -247,11 +268,15 @@                            , with =                                Just                                  (StepWithEnv-                                    (fromList-                                       [ ( "token" , "${{ secrets.BELLROY_DEPLOY_USER_TOKEN }}" )-                                       , ( "tool-name" , "gitapult" )-                                       , ( "tool-version" , "latest" )-                                       ]))+                                    (UnstructuredMap+                                       (fromList+                                          [ ( "token"+                                            , UnstructuredValueString+                                                "${{ secrets.BELLROY_DEPLOY_USER_TOKEN }}"+                                            )+                                          , ( "tool-name" , UnstructuredValueString "gitapult" )+                                          , ( "tool-version" , UnstructuredValueString "latest" )+                                          ])))                            , workingDirectory = Nothing                            }                        , Step@@ -267,11 +292,17 @@                            , with =                                Just                                  (StepWithEnv-                                    (fromList-                                       [ ( "token" , "${{ secrets.BELLROY_DEPLOY_USER_TOKEN }}" )-                                       , ( "tool-name" , "config-rules-cli" )-                                       , ( "tool-version" , "latest" )-                                       ]))+                                    (UnstructuredMap+                                       (fromList+                                          [ ( "token"+                                            , UnstructuredValueString+                                                "${{ secrets.BELLROY_DEPLOY_USER_TOKEN }}"+                                            )+                                          , ( "tool-name"+                                            , UnstructuredValueString "config-rules-cli"+                                            )+                                          , ( "tool-version" , UnstructuredValueString "latest" )+                                          ])))                            , workingDirectory = Nothing                            }                        , Step@@ -287,11 +318,15 @@                            , with =                                Just                                  (StepWithEnv-                                    (fromList-                                       [ ( "token" , "${{ secrets.BELLROY_DEPLOY_USER_TOKEN }}" )-                                       , ( "tool-name" , "powerful-owl" )-                                       , ( "tool-version" , "latest" )-                                       ]))+                                    (UnstructuredMap+                                       (fromList+                                          [ ( "token"+                                            , UnstructuredValueString+                                                "${{ secrets.BELLROY_DEPLOY_USER_TOKEN }}"+                                            )+                                          , ( "tool-name" , UnstructuredValueString "powerful-owl" )+                                          , ( "tool-version" , UnstructuredValueString "latest" )+                                          ])))                            , workingDirectory = Nothing                            }                        , Step@@ -376,7 +411,7 @@                            , run =                                Just                                  "set +e\nIFS= output=$(./gitapult changeset --git_root \"$GITHUB_WORKSPACE\" --from \"${{ env.MERGE_BASE }}\" | ./byproduct validate --markdown 2>&1)\nstatus=$?\n\nif (($status)); then\n  echo $output >>/tmp/pr-comment.txt\n  exit $status\nelse\n  echo \"Byproduct validation succeeded.\"\nfi\n"-                           , runIf = Just "env.PR_NUMBER != 0"+                           , runIf = Just (RunIfString "env.PR_NUMBER != 0")                            , shell = Nothing                            , stepId = Nothing                            , timeoutMinutes = Nothing@@ -468,7 +503,8 @@                                  "if [ $(git status shipping/tests --porcelain=v1 | wc -l) -gt 0 ]\nthen\n  git add shipping/tests\n  git commit -m \"Update shipping tests\"\n  echo \"GENERATED_ANOTHER_COMMIT=true\" >> $GITHUB_ENV\nfi\n"                            , runIf =                                Just-                                 "env.PR_NUMBER != 0 && github.ref != 'refs/heads/stable' && github.ref != 'refs/heads/master'"+                                 (RunIfString+                                    "env.PR_NUMBER != 0 && github.ref != 'refs/heads/stable' && github.ref != 'refs/heads/master'")                            , shell = Nothing                            , stepId = Nothing                            , timeoutMinutes = Nothing@@ -515,7 +551,8 @@                                  "if [ $(git status promotions/tests --porcelain=v1 | wc -l) -gt 0 ]\nthen\n  git add promotions/tests\n  git commit -m \"Update promotion tests\"\n  echo \"GENERATED_ANOTHER_COMMIT=true\" >> $GITHUB_ENV\nfi\n"                            , runIf =                                Just-                                 "env.PR_NUMBER != 0 && github.ref != 'refs/heads/stable' && github.ref != 'refs/heads/master'"+                                 (RunIfString+                                    "env.PR_NUMBER != 0 && github.ref != 'refs/heads/stable' && github.ref != 'refs/heads/master'")                            , shell = Nothing                            , stepId = Nothing                            , timeoutMinutes = Nothing@@ -531,7 +568,8 @@                            , run =                                Just                                  "git push --set-upstream origin HEAD\n\ngh run cancel ${{ github.run_id }}\ngh run watch ${{ github.run_id }}\n"-                           , runIf = Just "env.GENERATED_ANOTHER_COMMIT == 'true'"+                           , runIf =+                               Just (RunIfString "env.GENERATED_ANOTHER_COMMIT == 'true'")                            , shell = Nothing                            , stepId = Nothing                            , timeoutMinutes = Nothing@@ -563,7 +601,8 @@                                  "echo \"COMMIT_CHANGES<<EOF\" >> $GITHUB_ENV\necho \"$(git log --oneline --format=\\\"%s\\\" $(git tag -l [0-9]*_${{ env.ENVIRONMENT }} | tail -n 1)..${{ steps.extract_branch.outputs.branch }} | grep -v 'Merge pull request' | grep -v \"Merge branch 'master' into\" | sed -e 's/^\"//' -e 's/\"$//' | cat)\" >> $GITHUB_ENV\necho \"EOF\" >> $GITHUB_ENV\n"                            , runIf =                                Just-                                 "github.ref == 'refs/heads/stable' || github.ref == 'refs/heads/master'"+                                 (RunIfString+                                    "github.ref == 'refs/heads/stable' || github.ref == 'refs/heads/master'")                            , shell = Just (Bash Nothing)                            , stepId = Just (StepId "extract_changes")                            , timeoutMinutes = Nothing@@ -580,7 +619,8 @@                            , run = Nothing                            , runIf =                                Just-                                 "github.ref == 'refs/heads/stable' || github.ref == 'refs/heads/master'"+                                 (RunIfString+                                    "github.ref == 'refs/heads/stable' || github.ref == 'refs/heads/master'")                            , shell = Nothing                            , stepId = Just (StepId "slack-deploy")                            , timeoutMinutes = Nothing@@ -590,12 +630,16 @@                            , with =                                Just                                  (StepWithEnv-                                    (fromList-                                       [ ( "channel-id" , "${{ env.SLACK_CHANNEL_ID }}" )-                                       , ( "payload"-                                         , "{\n  \"text\": \":loudspeaker: *${{ github.actor }}* is deploying *${{ env.SYSTEM_NAME }}* to *${{ env.DESTINATION }}* from *${{ steps.extract_branch.outputs.branch }}*\",\n  \"attachments\": [\n    {\n      \"fallback\": \"Deployment summary\",\n      \"color\": \"ffd966\",\n      \"fields\": [\n        {\n          \"title\": \"What is being deployed\",\n          \"value\": ${{ toJSON(env.COMMIT_CHANGES) }},\n          \"short\": false\n        },\n        {\n          \"title\": \"Status\",\n          \"short\": true,\n          \"value\": \"In Progress\"\n        }\n      ]\n    }\n  ]\n}\n"-                                         )-                                       ]))+                                    (UnstructuredMap+                                       (fromList+                                          [ ( "channel-id"+                                            , UnstructuredValueString "${{ env.SLACK_CHANNEL_ID }}"+                                            )+                                          , ( "payload"+                                            , UnstructuredValueString+                                                "{\n  \"text\": \":loudspeaker: *${{ github.actor }}* is deploying *${{ env.SYSTEM_NAME }}* to *${{ env.DESTINATION }}* from *${{ steps.extract_branch.outputs.branch }}*\",\n  \"attachments\": [\n    {\n      \"fallback\": \"Deployment summary\",\n      \"color\": \"ffd966\",\n      \"fields\": [\n        {\n          \"title\": \"What is being deployed\",\n          \"value\": ${{ toJSON(env.COMMIT_CHANGES) }},\n          \"short\": false\n        },\n        {\n          \"title\": \"Status\",\n          \"short\": true,\n          \"value\": \"In Progress\"\n        }\n      ]\n    }\n  ]\n}\n"+                                            )+                                          ])))                            , workingDirectory = Nothing                            }                        , Step@@ -605,7 +649,7 @@                            , run =                                Just                                  "set -e\n# XXX: Gitapult cannot distinguish datasets, so we push all data to\n# both endpoints and allow the server to decide what to do.\n# Kafka Webhooks accepts all datasets at once\n./gitapult run \\\n  --git_root \"$GITHUB_WORKSPACE\" \\\n  --authorization_token \"$KW_STAGING_API_KEY\" \\\n  --changeset_url \"$KW_STAGING_BASE_URL/gitapult/configuration/records\" \\\n  --revision_url \"$KW_STAGING_BASE_URL/gitapult/configuration/revision\" \\\n  --api_key \\\n  --compress \\\n  --max-payload-bytes 3000000\n\n# XXX: V3 doesn't care about rendering data, but it is too large to send.\n# As at 2024-02-14 gitapult does not support selecting which data to send.\ngit -C \"$GIT_REPOSITORY_PATH\" rm feeds/rendering_data/.gitapult.json\n./gitapult run \\\n  --git_root \"$GITHUB_WORKSPACE\" \\\n  --authorization_token \"$SF_STAGING_API_KEY\" \\\n  --changeset_url \"$SF_STAGING_BASE_URL/api/v1/configuration/shipping\" \\\n  --revision_url \"$SF_STAGING_BASE_URL/api/v1/configuration/shipping/revisions\" && \\\ngit -C \"$GIT_REPOSITORY_PATH\" restore --staged feeds/rendering_data/.gitapult.json\ngit -C \"$GIT_REPOSITORY_PATH\" restore feeds/rendering_data/.gitapult.json\n"-                           , runIf = Just "github.ref == 'refs/heads/master'"+                           , runIf = Just (RunIfString "github.ref == 'refs/heads/master'")                            , shell = Nothing                            , stepId = Nothing                            , timeoutMinutes = Nothing@@ -620,7 +664,8 @@                            , run =                                Just                                  "git tag \"${{ join(steps.current-time.outputs.*, '\\n') }}_staging\" && git push --tags\n"-                           , runIf = Just "success() && github.ref == 'refs/heads/master'"+                           , runIf =+                               Just (RunIfString "success() && github.ref == 'refs/heads/master'")                            , shell = Nothing                            , stepId = Nothing                            , timeoutMinutes = Nothing@@ -635,7 +680,7 @@                            , run =                                Just                                  "curl -X POST -H \"Accept: application/vnd.github.v3+json\" -H \"Authorization: token ${{ secrets.BELLROY_DEPLOY_USER_TOKEN }}\" https://api.github.com/repos/${{ github.repository }}/pulls -d '{\"head\":\"master\",\"base\":\"stable\",\"title\":\"Merge branch master into stable\",\"body\":\"This PR was automatically generated by CI.\"}'\nPR_URL=$(curl -sX GET -H \"Accept: application/vnd.github.v3+json\" -H \"Authorization: token ${{ secrets.BELLROY_DEPLOY_USER_TOKEN }}\" https://api.github.com/repos/${{ github.repository }}/pulls\\?head\\=bellroy:master\\&base\\=stable | jq -r '.[0].html_url | select(length>0)')\necho \"PR_URL=$PR_URL\" >> $GITHUB_ENV\n"-                           , runIf = Just "github.ref == 'refs/heads/master'"+                           , runIf = Just (RunIfString "github.ref == 'refs/heads/master'")                            , shell = Nothing                            , stepId = Nothing                            , timeoutMinutes = Nothing@@ -650,7 +695,7 @@                            , run =                                Just                                  "PR_RESPONSE=$(curl -sX GET -H \"Accept: application/vnd.github.v3+json\" -H \"Authorization: token ${{ secrets.BELLROY_DEPLOY_USER_TOKEN }}\" https://api.github.com/repos/${{ github.repository }}/pulls\\?head\\=bellroy:master\\&base\\=stable | jq -r '.[0]')\nPR_NUM=$(echo \"$PR_RESPONSE\" | jq -r \".number\")\nPR_ASSIGNEE=$(echo \"$PR_RESPONSE\" | jq -r \".assignee\")\nCOMMITS_RESPONSE=$(curl -sX GET -H \"Accept: application/vnd.github.v3+json\" -H \"Authorization: token ${{ secrets.BELLROY_DEPLOY_USER_TOKEN }}\" https://api.github.com/repos/${{ github.repository }}/pulls/$PR_NUM/commits)\nFIRST_COMMITTER=$(curl -sX GET -H \"Accept: application/vnd.github.v3+json\" -H \"Authorization: token ${{ secrets.BELLROY_DEPLOY_USER_TOKEN }}\" https://api.github.com/repos/${{ github.repository }}/pulls/$PR_NUM/commits | jq -r '. | sort_by(.commit.author.date) | .[0] | .author.login')\n# special case for squashed PRs created by trike-deploy e.g. gha-workflows\nif [[ \"$FIRST_COMMITTER\" == \"trike-deploy\" ]]; then\n    FIRST_COMMITTER=$(echo \"$COMMITS_RESPONSE\" | jq -r '. | sort_by(.commit.author.date) | .[0]' | sed -En \"s/.*Co-authored-by: (.*) <.*@.*>\\\",/\\1/p\")\nfi\nif [[ \"$PR_ASSIGNEE\" == \"null\" ]]; then\n    curl -sX POST -H \"Accept: application/vnd.github.v3+json\" -H \"Authorization: token ${{ secrets.BELLROY_DEPLOY_USER_TOKEN }}\" https://api.github.com/repos/${{ github.repository }}/issues/$PR_NUM/assignees -d \"{\\\"assignees\\\":[\\\"$FIRST_COMMITTER\\\"]}\"\nfi\n"-                           , runIf = Just "github.ref == 'refs/heads/master'"+                           , runIf = Just (RunIfString "github.ref == 'refs/heads/master'")                            , shell = Nothing                            , stepId = Nothing                            , timeoutMinutes = Nothing@@ -665,7 +710,7 @@                            , run =                                Just                                  "set -e\n# XXX: Gitapult cannot distinguish datasets, so we push all data to\n# both endpoints and allow the server to decide what to do.\n# Kafka Webhooks accepts all datasets at once\n./gitapult run \\\n  --git_root \"$GITHUB_WORKSPACE\" \\\n  --authorization_token \"$KW_PRODUCTION_API_KEY\" \\\n  --changeset_url \"$KW_PRODUCTION_BASE_URL/gitapult/configuration/records\" \\\n  --revision_url \"$KW_PRODUCTION_BASE_URL/gitapult/configuration/revision\" \\\n  --api_key \\\n  --compress \\\n  --max-payload-bytes 3000000\n\n# XXX: V3 doesn't care about rendering data, but it is too large to send.\n# As at 2024-02-14 gitapult does not support selecting which data to send.\ngit -C \"$GIT_REPOSITORY_PATH\" rm feeds/rendering_data/.gitapult.json\n./gitapult run \\\n  --git_root \"$GITHUB_WORKSPACE\" \\\n  --authorization_token \"$SF_PRODUCTION_API_KEY\" \\\n  --changeset_url \"$SF_PRODUCTION_BASE_URL/api/v1/configuration/shipping\" \\\n  --revision_url \"$SF_PRODUCTION_BASE_URL/api/v1/configuration/shipping/revisions\" && \\\ngit -C \"$GIT_REPOSITORY_PATH\" restore --staged feeds/rendering_data/.gitapult.json\ngit -C \"$GIT_REPOSITORY_PATH\" restore feeds/rendering_data/.gitapult.json\n"-                           , runIf = Just "github.ref == 'refs/heads/stable'"+                           , runIf = Just (RunIfString "github.ref == 'refs/heads/stable'")                            , shell = Nothing                            , stepId = Nothing                            , timeoutMinutes = Nothing@@ -680,7 +725,8 @@                            , run =                                Just                                  "git tag \"${{ join(steps.current-time.outputs.*, '\\n') }}_production\" && git push --tags\n"-                           , runIf = Just "success() && github.ref == 'refs/heads/stable'"+                           , runIf =+                               Just (RunIfString "success() && github.ref == 'refs/heads/stable'")                            , shell = Nothing                            , stepId = Nothing                            , timeoutMinutes = Nothing@@ -695,7 +741,7 @@                            , run =                                Just                                  "echo \"SUCCESS_MESSAGE=:tada: \\nA PR has been opened against the stable branch: ${{ env.PR_URL }}\" >> $GITHUB_ENV\necho \"SUCCESS_MESSAGE_BODY=https://github.com/${{ github.repository }}/commit/${{ github.sha }}/checks\\n\\n*PR for Production:* ${{ env.PR_URL }}\" >> $GITHUB_ENV\n"-                           , runIf = Just "github.ref == 'refs/heads/master'"+                           , runIf = Just (RunIfString "github.ref == 'refs/heads/master'")                            , shell = Nothing                            , stepId = Nothing                            , timeoutMinutes = Nothing@@ -710,7 +756,7 @@                            , run =                                Just                                  "echo \"SUCCESS_MESSAGE=:tada:\" >> $GITHUB_ENV\necho \"SUCCESS_MESSAGE_BODY=https://github.com/${{ github.repository }}/commit/${{ github.sha }}/checks\" >> $GITHUB_ENV\n"-                           , runIf = Just "github.ref == 'refs/heads/stable'"+                           , runIf = Just (RunIfString "github.ref == 'refs/heads/stable'")                            , shell = Nothing                            , stepId = Nothing                            , timeoutMinutes = Nothing@@ -727,7 +773,8 @@                            , run = Nothing                            , runIf =                                Just-                                 "success() && (github.ref == 'refs/heads/stable' || github.ref == 'refs/heads/master')"+                                 (RunIfString+                                    "success() && (github.ref == 'refs/heads/stable' || github.ref == 'refs/heads/master')")                            , shell = Nothing                            , stepId = Just (StepId "slack-deploy-success")                            , timeoutMinutes = Nothing@@ -737,13 +784,20 @@                            , with =                                Just                                  (StepWithEnv-                                    (fromList-                                       [ ( "channel-id" , "${{ env.SLACK_CHANNEL_ID }}" )-                                       , ( "payload"-                                         , "{\n  \"text\": \":loudspeaker: *${{ github.actor }}* has deployed *${{ env.SYSTEM_NAME }}* to *${{ env.DESTINATION }}* from *${{ steps.extract_branch.outputs.branch }}* ${{ env.SUCCESS_MESSAGE }}\",\n  \"attachments\": [\n    {\n      \"fallback\": \"Deployment summary\",\n      \"color\": \"00ff00\",\n      \"fields\": [\n        {\n          \"title\": \"What was deployed\",\n          \"value\": ${{ toJSON(env.COMMIT_CHANGES) }},\n          \"short\": false\n        },\n        {\n          \"title\": \"Status\",\n          \"short\": true,\n          \"value\": \"Deployed\"\n        },\n        {\n          \"title\": \"Output\",\n          \"short\": false,\n          \"value\": \"${{ env.SUCCESS_MESSAGE_BODY }}\"\n        }\n      ]\n    }\n  ]\n}\n"-                                         )-                                       , ( "update-ts" , "${{ steps.slack-deploy.outputs.ts }}" )-                                       ]))+                                    (UnstructuredMap+                                       (fromList+                                          [ ( "channel-id"+                                            , UnstructuredValueString "${{ env.SLACK_CHANNEL_ID }}"+                                            )+                                          , ( "payload"+                                            , UnstructuredValueString+                                                "{\n  \"text\": \":loudspeaker: *${{ github.actor }}* has deployed *${{ env.SYSTEM_NAME }}* to *${{ env.DESTINATION }}* from *${{ steps.extract_branch.outputs.branch }}* ${{ env.SUCCESS_MESSAGE }}\",\n  \"attachments\": [\n    {\n      \"fallback\": \"Deployment summary\",\n      \"color\": \"00ff00\",\n      \"fields\": [\n        {\n          \"title\": \"What was deployed\",\n          \"value\": ${{ toJSON(env.COMMIT_CHANGES) }},\n          \"short\": false\n        },\n        {\n          \"title\": \"Status\",\n          \"short\": true,\n          \"value\": \"Deployed\"\n        },\n        {\n          \"title\": \"Output\",\n          \"short\": false,\n          \"value\": \"${{ env.SUCCESS_MESSAGE_BODY }}\"\n        }\n      ]\n    }\n  ]\n}\n"+                                            )+                                          , ( "update-ts"+                                            , UnstructuredValueString+                                                "${{ steps.slack-deploy.outputs.ts }}"+                                            )+                                          ])))                            , workingDirectory = Nothing                            }                        , Step@@ -755,7 +809,8 @@                            , run = Nothing                            , runIf =                                Just-                                 "failure() && (github.ref == 'refs/heads/stable' || github.ref == 'refs/heads/master')"+                                 (RunIfString+                                    "failure() && (github.ref == 'refs/heads/stable' || github.ref == 'refs/heads/master')")                            , shell = Nothing                            , stepId = Just (StepId "slack-deploy-failed")                            , timeoutMinutes = Nothing@@ -765,13 +820,20 @@                            , with =                                Just                                  (StepWithEnv-                                    (fromList-                                       [ ( "channel-id" , "${{ env.SLACK_CHANNEL_ID }}" )-                                       , ( "payload"-                                         , "{ \"text\": \":loudspeaker: *BUILD/DEPLOY FAILURE* of *${{ env.SYSTEM_NAME }}* to *${{ env.DESTINATION }}*\",\n  \"attachments\": [\n    {\n      \"fallback\": \"Deployment summary\",\n      \"color\": \"ff0000\",\n      \"fields\": [\n        {\n          \"title\": \"What was being deployed\",\n          \"value\": ${{ toJSON(env.COMMIT_CHANGES) }},\n          \"short\": false\n        },\n        {\n          \"title\": \"Status\",\n          \"short\": true,\n          \"value\": \"Failed\"\n        },\n        {\n          \"title\": \"Who broke it\",\n          \"value\": \"${{ github.actor }}\"\n        },\n        {\n          \"title\": \"Output\",\n          \"short\": false,\n          \"value\": \"https://github.com/${{ github.repository }}/commit/${{ github.sha }}/checks\"\n        }\n      ]\n    }\n  ]\n}\n"-                                         )-                                       , ( "update-ts" , "${{ steps.slack-deploy.outputs.ts }}" )-                                       ]))+                                    (UnstructuredMap+                                       (fromList+                                          [ ( "channel-id"+                                            , UnstructuredValueString "${{ env.SLACK_CHANNEL_ID }}"+                                            )+                                          , ( "payload"+                                            , UnstructuredValueString+                                                "{ \"text\": \":loudspeaker: *BUILD/DEPLOY FAILURE* of *${{ env.SYSTEM_NAME }}* to *${{ env.DESTINATION }}*\",\n  \"attachments\": [\n    {\n      \"fallback\": \"Deployment summary\",\n      \"color\": \"ff0000\",\n      \"fields\": [\n        {\n          \"title\": \"What was being deployed\",\n          \"value\": ${{ toJSON(env.COMMIT_CHANGES) }},\n          \"short\": false\n        },\n        {\n          \"title\": \"Status\",\n          \"short\": true,\n          \"value\": \"Failed\"\n        },\n        {\n          \"title\": \"Who broke it\",\n          \"value\": \"${{ github.actor }}\"\n        },\n        {\n          \"title\": \"Output\",\n          \"short\": false,\n          \"value\": \"https://github.com/${{ github.repository }}/commit/${{ github.sha }}/checks\"\n        }\n      ]\n    }\n  ]\n}\n"+                                            )+                                          , ( "update-ts"+                                            , UnstructuredValueString+                                                "${{ steps.slack-deploy.outputs.ts }}"+                                            )+                                          ])))                            , workingDirectory = Nothing                            }                        , Step@@ -781,7 +843,7 @@                            , run =                                Just                                  "gh pr comment ${{ env.PR_NUMBER }} --body-file /tmp/pr-comment.txt || true"-                           , runIf = Just "always() && env.PR_NUMBER != 0"+                           , runIf = Just (RunIfString "always() && env.PR_NUMBER != 0")                            , shell = Nothing                            , stepId = Nothing                            , timeoutMinutes = Nothing@@ -827,11 +889,17 @@                        , with =                            Just                              (StepWithEnv-                                (fromList-                                   [ ( "fetch-depth" , "1" )-                                   , ( "path" , "${{ env.GITHUB_WORKSPACE }}" )-                                   , ( "token" , "${{ secrets.BELLROY_DEPLOY_USER_TOKEN }}" )-                                   ]))+                                (UnstructuredMap+                                   (fromList+                                      [ ( "fetch-depth" , UnstructuredValueString "1" )+                                      , ( "path"+                                        , UnstructuredValueString "${{ env.GITHUB_WORKSPACE }}"+                                        )+                                      , ( "token"+                                        , UnstructuredValueString+                                            "${{ secrets.BELLROY_DEPLOY_USER_TOKEN }}"+                                        )+                                      ])))                        , workingDirectory = Nothing                        } :|                        [ Step@@ -861,7 +929,12 @@                            , with =                                Just                                  (StepWithEnv-                                    (fromList [ ( "bellroy-nix-cache-access" , "none" ) ]))+                                    (UnstructuredMap+                                       (fromList+                                          [ ( "bellroy-nix-cache-access"+                                            , UnstructuredValueString "none"+                                            )+                                          ])))                            , workingDirectory = Nothing                            }                        , Step@@ -877,11 +950,15 @@                            , with =                                Just                                  (StepWithEnv-                                    (fromList-                                       [ ( "token" , "${{ secrets.BELLROY_DEPLOY_USER_TOKEN }}" )-                                       , ( "tool-name" , "gitapult" )-                                       , ( "tool-version" , "latest" )-                                       ]))+                                    (UnstructuredMap+                                       (fromList+                                          [ ( "token"+                                            , UnstructuredValueString+                                                "${{ secrets.BELLROY_DEPLOY_USER_TOKEN }}"+                                            )+                                          , ( "tool-name" , UnstructuredValueString "gitapult" )+                                          , ( "tool-version" , UnstructuredValueString "latest" )+                                          ])))                            , workingDirectory = Nothing                            }                        , Step@@ -897,11 +974,15 @@                            , with =                                Just                                  (StepWithEnv-                                    (fromList-                                       [ ( "token" , "${{ secrets.BELLROY_DEPLOY_USER_TOKEN }}" )-                                       , ( "tool-name" , "powerful-owl" )-                                       , ( "tool-version" , "latest" )-                                       ]))+                                    (UnstructuredMap+                                       (fromList+                                          [ ( "token"+                                            , UnstructuredValueString+                                                "${{ secrets.BELLROY_DEPLOY_USER_TOKEN }}"+                                            )+                                          , ( "tool-name" , UnstructuredValueString "powerful-owl" )+                                          , ( "tool-version" , UnstructuredValueString "latest" )+                                          ])))                            , workingDirectory = Nothing                            }                        , Step@@ -968,7 +1049,7 @@                            , run =                                Just                                  "gh pr comment ${{ env.PR_NUMBER }} --body-file /tmp/pr-comment.txt || true"-                           , runIf = Just "always() && env.PR_NUMBER != 0"+                           , runIf = Just (RunIfString "always() && env.PR_NUMBER != 0")                            , shell = Nothing                            , stepId = Nothing                            , timeoutMinutes = Nothing@@ -983,7 +1064,8 @@                            , run = Nothing                            , runIf =                                Just-                                 "failure() && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/stable')"+                                 (RunIfString+                                    "failure() && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/stable')")                            , shell = Nothing                            , stepId = Nothing                            , timeoutMinutes = Nothing@@ -991,17 +1073,24 @@                            , with =                                Just                                  (StepWithEnv-                                    (fromList-                                       [ ( "branch"-                                         , "${{ steps.checkout_branch.outputs.branch }}"-                                         )-                                       , ( "channel-id" , "${{ env.SLACK_CHANNEL_ID }}" )-                                       , ( "message-type" , "build-fail" )-                                       , ( "show-authors" , "true" )-                                       , ( "slack-bot-token"-                                         , "${{ secrets.BELLROY_SLACK_TOKEN }}"-                                         )-                                       ]))+                                    (UnstructuredMap+                                       (fromList+                                          [ ( "branch"+                                            , UnstructuredValueString+                                                "${{ steps.checkout_branch.outputs.branch }}"+                                            )+                                          , ( "channel-id"+                                            , UnstructuredValueString "${{ env.SLACK_CHANNEL_ID }}"+                                            )+                                          , ( "message-type"+                                            , UnstructuredValueString "build-fail"+                                            )+                                          , ( "show-authors" , UnstructuredValueString "true" )+                                          , ( "slack-bot-token"+                                            , UnstructuredValueString+                                                "${{ secrets.BELLROY_SLACK_TOKEN }}"+                                            )+                                          ])))                            , workingDirectory = Nothing                            }                        ])