diff --git a/github-actions.cabal b/github-actions.cabal index 2a7ad52..1bebe54 100644 --- a/github-actions.cabal +++ b/github-actions.cabal @@ -59,14 +59,17 @@ library 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 diff --git a/src/Language/Github/Actions/Job.hs b/src/Language/Github/Actions/Job.hs index bd54897..9960981 100644 --- a/src/Language/Github/Actions/Job.hs +++ b/src/Language/Github/Actions/Job.hs @@ -43,12 +43,14 @@ import Language.Github.Actions.Job.Container (JobContainer) 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 @@ data Job = Job -- | 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 @@ gen = do 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 diff --git a/src/Language/Github/Actions/Job/Needs.hs b/src/Language/Github/Actions/Job/Needs.hs new file mode 100644 index 0000000..3682dd5 --- /dev/null +++ b/src/Language/Github/Actions/Job/Needs.hs @@ -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 +-- +-- 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: +-- +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 + ] diff --git a/src/Language/Github/Actions/RunIf.hs b/src/Language/Github/Actions/RunIf.hs new file mode 100644 index 0000000..3486c9d --- /dev/null +++ b/src/Language/Github/Actions/RunIf.hs @@ -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 +-- +-- 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: +-- +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 + ] diff --git a/src/Language/Github/Actions/Step.hs b/src/Language/Github/Actions/Step.hs index a6daada..e56e79d 100644 --- a/src/Language/Github/Actions/Step.hs +++ b/src/Language/Github/Actions/Step.hs @@ -35,6 +35,8 @@ import GHC.Generics (Generic) 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 @@ data Step = Step -- | 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 @@ gen = do 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) diff --git a/src/Language/Github/Actions/Step/With.hs b/src/Language/Github/Actions/Step/With.hs index 7241563..af9d765 100644 --- a/src/Language/Github/Actions/Step/With.hs +++ b/src/Language/Github/Actions/Step/With.hs @@ -30,7 +30,6 @@ where 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 GHC.Generics (Generic) 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 @@ data StepWith = -- | 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 @@ gen = 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 diff --git a/src/Language/Github/Actions/UnstructuredMap.hs b/src/Language/Github/Actions/UnstructuredMap.hs new file mode 100644 index 0000000..5abb4ab --- /dev/null +++ b/src/Language/Github/Actions/UnstructuredMap.hs @@ -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 +-- +-- 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) diff --git a/test/Language/Github/Actions/WorkflowTest.hs b/test/Language/Github/Actions/WorkflowTest.hs index 334c663..e177c97 100644 --- a/test/Language/Github/Actions/WorkflowTest.hs +++ b/test/Language/Github/Actions/WorkflowTest.hs @@ -50,7 +50,7 @@ test_goldenWorkflowFromYaml = do 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 () diff --git a/test/golden/configuration-main.hs.txt b/test/golden/configuration-main.hs.txt index cf237b3..4ffa06f 100644 --- a/test/golden/configuration-main.hs.txt +++ b/test/golden/configuration-main.hs.txt @@ -18,7 +18,7 @@ Workflow , 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 @@ Workflow , 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 @@ Workflow , 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 @@ Workflow , 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 @@ Workflow , 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 @@ Workflow , 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 @@ Workflow , 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 @@ Workflow , 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 @@ Workflow , with = Just (StepWithEnv - (fromList [ ( "path" , "${{ env.GIT_REPOSITORY_PATH }}" ) ])) + (UnstructuredMap + (fromList + [ ( "path" + , UnstructuredValueString + "${{ env.GIT_REPOSITORY_PATH }}" + ) + ]))) , workingDirectory = Nothing } , Step @@ -206,7 +222,8 @@ Workflow , 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 @@ Workflow , 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 @@ Workflow , 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 @@ Workflow , 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 @@ Workflow , 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 @@ Workflow , 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 @@ Workflow "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 @@ Workflow "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 @@ Workflow , 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 @@ Workflow "echo \"COMMIT_CHANGES<> $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 @@ Workflow , 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 @@ Workflow , 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 @@ Workflow , 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 @@ Workflow , 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 @@ Workflow , 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 @@ Workflow , 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 @@ Workflow , 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 @@ Workflow , 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 @@ Workflow , 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 @@ Workflow , 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 @@ Workflow , 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 @@ Workflow , 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 @@ Workflow , 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 @@ Workflow , 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 @@ Workflow , 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 @@ Workflow , 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 @@ Workflow , with = Just (StepWithEnv - (fromList [ ( "bellroy-nix-cache-access" , "none" ) ])) + (UnstructuredMap + (fromList + [ ( "bellroy-nix-cache-access" + , UnstructuredValueString "none" + ) + ]))) , workingDirectory = Nothing } , Step @@ -877,11 +950,15 @@ Workflow , 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 @@ Workflow , 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 @@ Workflow , 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 @@ Workflow , 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 @@ Workflow , 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 } ]) diff --git a/test/golden/issue-8-boolean-if.golden.yml b/test/golden/issue-8-boolean-if.golden.yml new file mode 100644 index 0000000..0b9ee6e --- /dev/null +++ b/test/golden/issue-8-boolean-if.golden.yml @@ -0,0 +1,13 @@ +jobs: + test: + runs-on: ubuntu-latest + steps: + - if: false + name: Test step + run: echo "test" +name: Test Boolean If +'on': + push: + branches-ignore: + - refs/tags/*_staging + - refs/tags/*_production diff --git a/test/golden/issue-8-boolean-if.hs.txt b/test/golden/issue-8-boolean-if.hs.txt new file mode 100644 index 0000000..40097cb --- /dev/null +++ b/test/golden/issue-8-boolean-if.hs.txt @@ -0,0 +1,61 @@ +Workflow + { concurrency = Nothing + , defaults = Nothing + , env = fromList [] + , jobs = + fromList + [ ( JobId "test" + , Job + { concurrency = Nothing + , container = Nothing + , continueOnError = Nothing + , defaults = Nothing + , env = fromList [] + , environment = Nothing + , jobName = Nothing + , needs = Nothing + , outputs = fromList [] + , permissions = Nothing + , runIf = Nothing + , runsOn = Just "ubuntu-latest" + , secrets = fromList [] + , services = fromList [] + , steps = + Just + (Step + { continueOnError = False + , env = fromList [] + , name = Just "Test step" + , run = Just "echo \"test\"" + , runIf = Just (RunIfBool False) + , shell = Nothing + , stepId = Nothing + , timeoutMinutes = Nothing + , uses = Nothing + , with = Nothing + , workingDirectory = Nothing + } :| + []) + , strategy = Nothing + , timeoutMinutes = Nothing + , uses = Nothing + , with = fromList [] + } + ) + ] + , on = + fromList + [ PushTrigger + PushTriggerAttributes + { branches = Nothing + , branchesIgnore = + Just ("refs/tags/*_staging" :| [ "refs/tags/*_production" ]) + , paths = Nothing + , pathsIgnore = Nothing + , tags = Nothing + } + ] + , permissions = Nothing + , runName = Nothing + , workflowName = Just "Test Boolean If" + } \ No newline at end of file diff --git a/test/golden/issue-8-boolean-if.yml b/test/golden/issue-8-boolean-if.yml new file mode 100644 index 0000000..1b01c03 --- /dev/null +++ b/test/golden/issue-8-boolean-if.yml @@ -0,0 +1,14 @@ +name: Test Boolean If +on: + push: + branches-ignore: + - "refs/tags/*_staging" + - "refs/tags/*_production" + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Test step + if: false + run: echo "test" diff --git a/test/golden/issue-8-numeric-retention.golden.yml b/test/golden/issue-8-numeric-retention.golden.yml new file mode 100644 index 0000000..dc4bb8e --- /dev/null +++ b/test/golden/issue-8-numeric-retention.golden.yml @@ -0,0 +1,16 @@ +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Upload artifact + uses: actions/upload-artifact@v4.2.0 + with: + name: test-artifact + path: ./test + retention-days: 1 +name: Test Numeric Retention +'on': + push: + branches-ignore: + - refs/tags/*_staging + - refs/tags/*_production diff --git a/test/golden/issue-8-numeric-retention.hs.txt b/test/golden/issue-8-numeric-retention.hs.txt new file mode 100644 index 0000000..84287d7 --- /dev/null +++ b/test/golden/issue-8-numeric-retention.hs.txt @@ -0,0 +1,69 @@ +Workflow + { concurrency = Nothing + , defaults = Nothing + , env = fromList [] + , jobs = + fromList + [ ( JobId "test" + , Job + { concurrency = Nothing + , container = Nothing + , continueOnError = Nothing + , defaults = Nothing + , env = fromList [] + , environment = Nothing + , jobName = Nothing + , needs = Nothing + , outputs = fromList [] + , permissions = Nothing + , runIf = Nothing + , runsOn = Just "ubuntu-latest" + , secrets = fromList [] + , services = fromList [] + , steps = + Just + (Step + { continueOnError = False + , env = fromList [] + , name = Just "Upload artifact" + , run = Nothing + , runIf = Nothing + , shell = Nothing + , stepId = Nothing + , timeoutMinutes = Nothing + , uses = Just "actions/upload-artifact@v4.2.0" + , with = + Just + (StepWithEnv + (UnstructuredMap + (fromList + [ ( "name" , UnstructuredValueString "test-artifact" ) + , ( "path" , UnstructuredValueString "./test" ) + , ( "retention-days" , UnstructuredValueNumber 1.0 ) + ]))) + , workingDirectory = Nothing + } :| + []) + , strategy = Nothing + , timeoutMinutes = Nothing + , uses = Nothing + , with = fromList [] + } + ) + ] + , on = + fromList + [ PushTrigger + PushTriggerAttributes + { branches = Nothing + , branchesIgnore = + Just ("refs/tags/*_staging" :| [ "refs/tags/*_production" ]) + , paths = Nothing + , pathsIgnore = Nothing + , tags = Nothing + } + ] + , permissions = Nothing + , runName = Nothing + , workflowName = Just "Test Numeric Retention" + } \ No newline at end of file diff --git a/test/golden/issue-8-numeric-retention.yml b/test/golden/issue-8-numeric-retention.yml new file mode 100644 index 0000000..c49aa6d --- /dev/null +++ b/test/golden/issue-8-numeric-retention.yml @@ -0,0 +1,17 @@ +name: Test Numeric Retention +on: + push: + branches-ignore: + - "refs/tags/*_staging" + - "refs/tags/*_production" + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Upload artifact + uses: actions/upload-artifact@v4.2.0 + with: + name: test-artifact + path: ./test + retention-days: 1 diff --git a/test/golden/issue-8-string-needs.golden.yml b/test/golden/issue-8-string-needs.golden.yml new file mode 100644 index 0000000..13949c5 --- /dev/null +++ b/test/golden/issue-8-string-needs.golden.yml @@ -0,0 +1,16 @@ +jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "build" + test: + needs: build + runs-on: ubuntu-latest + steps: + - run: echo "test" +name: Test String Needs +'on': + push: + branches-ignore: + - refs/tags/*_staging + - refs/tags/*_production diff --git a/test/golden/issue-8-string-needs.hs.txt b/test/golden/issue-8-string-needs.hs.txt new file mode 100644 index 0000000..73e5bb4 --- /dev/null +++ b/test/golden/issue-8-string-needs.hs.txt @@ -0,0 +1,99 @@ +Workflow + { concurrency = Nothing + , defaults = Nothing + , env = fromList [] + , jobs = + fromList + [ ( JobId "build" + , Job + { concurrency = Nothing + , container = Nothing + , continueOnError = Nothing + , defaults = Nothing + , env = fromList [] + , environment = Nothing + , jobName = Nothing + , needs = Nothing + , outputs = fromList [] + , permissions = Nothing + , runIf = Nothing + , runsOn = Just "ubuntu-latest" + , secrets = fromList [] + , services = fromList [] + , steps = + Just + (Step + { continueOnError = False + , env = fromList [] + , name = Nothing + , run = Just "echo \"build\"" + , runIf = Nothing + , shell = Nothing + , stepId = Nothing + , timeoutMinutes = Nothing + , uses = Nothing + , with = Nothing + , workingDirectory = Nothing + } :| + []) + , strategy = Nothing + , timeoutMinutes = Nothing + , uses = Nothing + , with = fromList [] + } + ) + , ( JobId "test" + , Job + { concurrency = Nothing + , container = Nothing + , continueOnError = Nothing + , defaults = Nothing + , env = fromList [] + , environment = Nothing + , jobName = Nothing + , needs = Just (JobNeedsString (JobId "build")) + , outputs = fromList [] + , permissions = Nothing + , runIf = Nothing + , runsOn = Just "ubuntu-latest" + , secrets = fromList [] + , services = fromList [] + , steps = + Just + (Step + { continueOnError = False + , env = fromList [] + , name = Nothing + , run = Just "echo \"test\"" + , runIf = Nothing + , shell = Nothing + , stepId = Nothing + , timeoutMinutes = Nothing + , uses = Nothing + , with = Nothing + , workingDirectory = Nothing + } :| + []) + , strategy = Nothing + , timeoutMinutes = Nothing + , uses = Nothing + , with = fromList [] + } + ) + ] + , on = + fromList + [ PushTrigger + PushTriggerAttributes + { branches = Nothing + , branchesIgnore = + Just ("refs/tags/*_staging" :| [ "refs/tags/*_production" ]) + , paths = Nothing + , pathsIgnore = Nothing + , tags = Nothing + } + ] + , permissions = Nothing + , runName = Nothing + , workflowName = Just "Test String Needs" + } \ No newline at end of file diff --git a/test/golden/issue-8-string-needs.yml b/test/golden/issue-8-string-needs.yml new file mode 100644 index 0000000..c7dd5e8 --- /dev/null +++ b/test/golden/issue-8-string-needs.yml @@ -0,0 +1,17 @@ +name: Test String Needs +on: + push: + branches-ignore: + - "refs/tags/*_staging" + - "refs/tags/*_production" + +jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "build" + test: + needs: build + runs-on: ubuntu-latest + steps: + - run: echo "test"