diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..807cb6c --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @freckle/backenders diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 1230149..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,6 +0,0 @@ -version: 2 -updates: - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "daily" diff --git a/.github/workflows/add-asana-comment.yml b/.github/workflows/add-asana-comment.yml new file mode 100644 index 0000000..aaa3f6d --- /dev/null +++ b/.github/workflows/add-asana-comment.yml @@ -0,0 +1,16 @@ +name: Asana + +on: + pull_request: + types: [opened] + +jobs: + link-asana-task: + if: ${{ github.actor != 'dependabot[bot]' }} + runs-on: ubuntu-latest + steps: + - uses: Asana/create-app-attachment-github-action@v1.3 + id: postAttachment + with: + asana-secret: ${{ secrets.ASANA_API_ACCESS_KEY }} + - run: echo "Status is ${{ steps.postAttachment.outputs.status }}" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 38d308e..9a57472 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,7 @@ jobs: generate: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - id: generate uses: freckle/stack-action/generate-matrix@v5 outputs: @@ -29,20 +29,16 @@ jobs: fail-fast: false steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - id: stack uses: freckle/stack-action@v5 with: stack-arguments: --stack-yaml ${{ matrix.stack-yaml }} - - if: ${{ matrix.stack-yaml == 'stack.yaml' }} - uses: freckle/weeder-action@v2 - with: - ghc-version: ${{ steps.stack.outputs.compiler-version }} lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: haskell-actions/hlint-setup@v2 - uses: haskell-actions/hlint-run@v2 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 23313f8..8a6d0db 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,7 +8,7 @@ jobs: release: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - id: tag uses: freckle/haskell-tag-action@v1 diff --git a/CHANGELOG.md b/CHANGELOG.md index db30fce..20996d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,13 @@ -## [_Unreleased_](https://github.com/freckle/github-workflow-commands/compare/v0.0.0.0...main) +## [_Unreleased_](https://github.com/freckle/github-workflow-commands/compare/v0.0.1.0...main) + +## [v0.0.1.0](https://github.com/freckle/github-workflow-commands/compare/v0.0.0.0...v0.0.1.0) + +Additions: + +- Re-exporting overview module `GitHub.Workflow.Command`, which is now the + primary module for users to import and find documentation +- Class `MonadCommand` which is now the recommended way to execute commands +- Support for `group`, `add-mask`, `stop-commands` commands ## [v0.0.0.0](https://github.com/freckle/github-workflow-commands/tree/v0.0.0.0) diff --git a/README.lhs b/README.lhs index 6a36fe4..74a1789 100644 --- a/README.lhs +++ b/README.lhs @@ -14,13 +14,12 @@ module Main (main) where import Prelude -import Text.Markdown.Unlit () ``` --> ```haskell -import qualified GitHub.Workflow.Command.Annotation as GH -import Control.Lens +import qualified GitHub.Workflow.Command as GH +import Control.Lens ((&), (?~)) ``` An annotation is at minimum just a string. @@ -28,7 +27,7 @@ An annotation is at minimum just a string. ```haskell example1 :: IO () example1 = - GH.printByteStringLn $ + GH.executeCommand $ GH.error "Something failed." ``` @@ -47,7 +46,7 @@ someLocation = ```haskell example2 :: IO () example2 = - GH.printByteStringLn $ + GH.executeCommand $ GH.warning "Something seems amiss here." & GH.location ?~ someLocation ``` @@ -55,7 +54,7 @@ example2 = diff --git a/github-workflow-commands.cabal b/github-workflow-commands.cabal index d4ef1eb..c0818ed 100644 --- a/github-workflow-commands.cabal +++ b/github-workflow-commands.cabal @@ -1,11 +1,11 @@ cabal-version: 1.18 --- This file has been generated from package.yaml by hpack version 0.36.0. +-- This file has been generated from package.yaml by hpack version 0.38.1. -- -- see: https://github.com/sol/hpack name: github-workflow-commands -version: 0.0.0.0 +version: 0.0.1.0 synopsis: GitHub Actions workflow commands description: For printing workflow commands in GitHub Actions. . @@ -29,6 +29,7 @@ source-repository head library exposed-modules: + GitHub.Workflow.Command GitHub.Workflow.Command.Annotation GitHub.Workflow.Command.Annotation.Commands.Debug GitHub.Workflow.Command.Annotation.Commands.Error @@ -43,6 +44,10 @@ library GitHub.Workflow.Command.Annotation.Position.Extent GitHub.Workflow.Command.Annotation.Position.Line GitHub.Workflow.Command.Annotation.Properties + GitHub.Workflow.Command.Execution + GitHub.Workflow.Command.Grouping + GitHub.Workflow.Command.Masking + GitHub.Workflow.Command.Stopping GitHub.Workflow.Command.Syntax GitHub.Workflow.Command.Syntax.Command GitHub.Workflow.Command.Syntax.Key @@ -76,7 +81,8 @@ library TypeFamilies ghc-options: -fwrite-ide-info -Weverything -Wno-all-missed-specialisations -Wno-missed-specialisations -Wno-missing-exported-signatures -Wno-missing-import-lists -Wno-missing-local-signatures -Wno-monomorphism-restriction -Wno-safe -Wno-unsafe build-depends: - base <5 + MonadRandom + , base <5 , bytestring , containers , lens @@ -114,11 +120,12 @@ test-suite readme TemplateHaskell TypeFamilies ghc-options: -fwrite-ide-info -Weverything -Wno-all-missed-specialisations -Wno-missed-specialisations -Wno-missing-exported-signatures -Wno-missing-import-lists -Wno-missing-local-signatures -Wno-monomorphism-restriction -Wno-safe -Wno-unsafe -pgmL markdown-unlit + build-tool-depends: + markdown-unlit:markdown-unlit build-depends: base <5 , github-workflow-commands , lens - , markdown-unlit default-language: GHC2021 if impl(ghc >= 9.8) ghc-options: -Wno-missing-poly-kind-signatures -Wno-missing-role-annotations diff --git a/library/GitHub/Workflow/Command.hs b/library/GitHub/Workflow/Command.hs new file mode 100644 index 0000000..191761c --- /dev/null +++ b/library/GitHub/Workflow/Command.hs @@ -0,0 +1,99 @@ +-- | Programs run by GitHub Actions can use workflow commands to communicate with the runner. +-- +-- GitHub documentation: +-- +module GitHub.Workflow.Command + ( -- * Executing commands + MonadCommand (..) + , PrintCommands (..) + + -- * Commands + , ToCommand (..) + + -- ** Setting a debug message + , Debug (..) + , debug + + -- ** Setting a notice message + , Notice (..) + , notice + + -- ** Setting a warning message + , Warning (..) + , warning + + -- ** Setting an error message + , Error (..) + , error + + -- ** Grouping log lines + , group + , GroupStart (..) + , GroupEnd (..) + + -- ** Masking a value in a log + , AddMask (..) + + -- ** Stopping and starting workflow commands + , suspendCommands + , stopCommands + , resumeCommands + , SuspendToken + + -- * Location + , Location (..) + , HasLocationMaybe (..) + + -- ** File + , File (..) + , inFile + , file + + -- ** Position + , Position (..) + , position + , Extent (..) + , extent + , Columns (..) + , line + , startColumn + , endColumn + , Line (..) + , atLine + , Column (..) + , atColumn + + -- * Anatomy of a command + , Command + + -- ** Name + , Name (..) + , HasName (..) + + -- ** Message + , Message (..) + , HasMessage (..) + + -- ** Properties + , Properties + , HasProperties (..) + , Key (..) + , Value (..) + ) where + +import GitHub.Workflow.Command.Annotation.Commands.Debug +import GitHub.Workflow.Command.Annotation.Commands.Error +import GitHub.Workflow.Command.Annotation.Commands.Notice +import GitHub.Workflow.Command.Annotation.Commands.Warning +import GitHub.Workflow.Command.Annotation.File +import GitHub.Workflow.Command.Annotation.Location +import GitHub.Workflow.Command.Annotation.Position +import GitHub.Workflow.Command.Annotation.Position.Column +import GitHub.Workflow.Command.Annotation.Position.Columns +import GitHub.Workflow.Command.Annotation.Position.Extent +import GitHub.Workflow.Command.Annotation.Position.Line +import GitHub.Workflow.Command.Execution +import GitHub.Workflow.Command.Grouping +import GitHub.Workflow.Command.Masking +import GitHub.Workflow.Command.Stopping +import GitHub.Workflow.Command.Syntax diff --git a/library/GitHub/Workflow/Command/Annotation.hs b/library/GitHub/Workflow/Command/Annotation.hs index bb4a1c6..68eb51e 100644 --- a/library/GitHub/Workflow/Command/Annotation.hs +++ b/library/GitHub/Workflow/Command/Annotation.hs @@ -48,6 +48,7 @@ module GitHub.Workflow.Command.Annotation , atColumn -- * Output + , MonadCommand (..) , ToCommand (..) , toCommand , ToByteString (..) @@ -66,11 +67,7 @@ import GitHub.Workflow.Command.Annotation.Position.Columns import GitHub.Workflow.Command.Annotation.Position.Extent import GitHub.Workflow.Command.Annotation.Position.Line import GitHub.Workflow.Command.Annotation.Properties -import GitHub.Workflow.Command.Syntax - ( FromMessage (..) - , Message (..) - , ToByteString (..) - , ToCommand (..) - , printByteStringLn - , toCommand - ) +import GitHub.Workflow.Command.Execution +import GitHub.Workflow.Command.Syntax.Command +import GitHub.Workflow.Command.Syntax.Message +import GitHub.Workflow.Command.Syntax.ToByteString diff --git a/library/GitHub/Workflow/Command/Annotation/Commands/Debug.hs b/library/GitHub/Workflow/Command/Annotation/Commands/Debug.hs index 5c26e37..d0147fc 100644 --- a/library/GitHub/Workflow/Command/Annotation/Commands/Debug.hs +++ b/library/GitHub/Workflow/Command/Annotation/Commands/Debug.hs @@ -16,6 +16,10 @@ import GitHub.Workflow.Command.Syntax ) import GitHub.Workflow.Command.Syntax qualified as Syntax +-- | Prints a debug message to the log +-- +-- GitHub documentation: +-- newtype Debug = Debug { message :: Message } diff --git a/library/GitHub/Workflow/Command/Annotation/Commands/Error.hs b/library/GitHub/Workflow/Command/Annotation/Commands/Error.hs index 706b217..56a796d 100644 --- a/library/GitHub/Workflow/Command/Annotation/Commands/Error.hs +++ b/library/GitHub/Workflow/Command/Annotation/Commands/Error.hs @@ -18,6 +18,13 @@ import GitHub.Workflow.Command.Syntax ) import GitHub.Workflow.Command.Syntax qualified as Syntax +-- | Creates an error message and prints the message to the log +-- +-- The message can be associated with a particular file in your repository, +-- and optionally also a position within the file. See 'HasLocationMaybe'. +-- +-- GitHub documentation: +-- data Error = Error { message :: Message , properties :: Properties diff --git a/library/GitHub/Workflow/Command/Annotation/Commands/Notice.hs b/library/GitHub/Workflow/Command/Annotation/Commands/Notice.hs index babb4f6..cf01e4a 100644 --- a/library/GitHub/Workflow/Command/Annotation/Commands/Notice.hs +++ b/library/GitHub/Workflow/Command/Annotation/Commands/Notice.hs @@ -18,6 +18,13 @@ import GitHub.Workflow.Command.Syntax ) import GitHub.Workflow.Command.Syntax qualified as Syntax +-- | Creates a notice message and prints the message to the log +-- +-- The message can be associated with a particular file in your repository, +-- and optionally also a position within the file. See 'HasLocationMaybe'. +-- +-- GitHub documentation: +-- data Notice = Notice { message :: Message , properties :: Properties diff --git a/library/GitHub/Workflow/Command/Annotation/Commands/Warning.hs b/library/GitHub/Workflow/Command/Annotation/Commands/Warning.hs index c67aacc..a793d4c 100644 --- a/library/GitHub/Workflow/Command/Annotation/Commands/Warning.hs +++ b/library/GitHub/Workflow/Command/Annotation/Commands/Warning.hs @@ -18,6 +18,13 @@ import GitHub.Workflow.Command.Syntax ) import GitHub.Workflow.Command.Syntax qualified as Syntax +-- | Creates a warning message and prints the message to the log +-- +-- The message can be associated with a particular file in your repository, +-- and optionally also a position within the file. See 'HasLocationMaybe'. +-- +-- GitHub documentation: +-- data Warning = Warning { message :: Message , properties :: Properties diff --git a/library/GitHub/Workflow/Command/Execution.hs b/library/GitHub/Workflow/Command/Execution.hs new file mode 100644 index 0000000..9e4c5ef --- /dev/null +++ b/library/GitHub/Workflow/Command/Execution.hs @@ -0,0 +1,35 @@ +module GitHub.Workflow.Command.Execution + ( MonadCommand (..) + , PrintCommands (..) + ) where + +import Control.Applicative (Applicative) +import Control.Monad (Monad) +import Control.Monad.IO.Class (MonadIO, liftIO) +import Data.Function ((.)) +import Data.Functor (Functor) +import GitHub.Workflow.Command.Syntax +import System.IO (IO) + +-- | Monadic context in which GitHub workflow commands may be executed +-- +-- * For the most basic uses, use the 'IO' instance, which prints commands to 'System.IO.stdout'. +-- +-- * For custom monads that support 'MonadIO', you may derive 'MonadCommand' via 'PrintCommands' +-- to get the same behavior that 'IO' exhibits. +-- +-- * A program that wishes to accommodate running in both GitHub and non-GitHub contexts +-- may wish to define a more sophisicated 'MonadCommand' instance that prints GitHub +-- workflow commands only when the @GITHUB_ACTIONS@ environment variable is present, +-- and otherwise takes some other more context-appropriate action. +class Monad m => MonadCommand m where + executeCommand :: ToCommand a => a -> m () + +instance MonadCommand IO where + executeCommand = printByteStringLn . toCommand + +newtype PrintCommands m a = PrintCommands (m a) + deriving newtype (Functor, Applicative, Monad, MonadIO) + +instance MonadIO m => MonadCommand (PrintCommands m) where + executeCommand = liftIO . executeCommand diff --git a/library/GitHub/Workflow/Command/Grouping.hs b/library/GitHub/Workflow/Command/Grouping.hs new file mode 100644 index 0000000..4f2ee83 --- /dev/null +++ b/library/GitHub/Workflow/Command/Grouping.hs @@ -0,0 +1,42 @@ +module GitHub.Workflow.Command.Grouping + ( group + , GroupStart (..) + , GroupEnd (..) + ) where + +import Control.Applicative ((*>), (<*)) +import Control.Lens ((.~)) +import Data.Function ((.)) +import Data.Text (Text) +import GitHub.Workflow.Command.Execution +import GitHub.Workflow.Command.Syntax + +-- | Creates an expandable group in the log +-- +-- GitHub documentation: +-- +group + :: MonadCommand m + => Text + -- ^ Group title + -> m a + -- ^ Anything printed within this action will be + -- nested inside an expandable entry in the log + -> m a +group title x = + executeCommand GroupStart {title} + *> x + <* executeCommand GroupEnd + +-- | Starts a 'group' +newtype GroupStart = GroupStart {title :: Text} + +instance ToCommand GroupStart where + addToCommand GroupStart {title} = + (name .~ "group") . (message .~ Message title) + +-- | Ends a 'group' +data GroupEnd = GroupEnd + +instance ToCommand GroupEnd where + addToCommand GroupEnd = name .~ "endgroup" diff --git a/library/GitHub/Workflow/Command/Masking.hs b/library/GitHub/Workflow/Command/Masking.hs new file mode 100644 index 0000000..649cf53 --- /dev/null +++ b/library/GitHub/Workflow/Command/Masking.hs @@ -0,0 +1,21 @@ +module GitHub.Workflow.Command.Masking + ( AddMask (..) + ) where + +import Control.Lens ((.~)) +import Data.Function ((.)) +import Data.Text (Text) +import GitHub.Workflow.Command.Syntax + +-- | Prevents a string or variable from being printed in the log +-- +-- GitHub documentation: +-- +newtype AddMask = AddMask + { value :: Text + -- ^ An environment variable or string + } + +instance ToCommand AddMask where + addToCommand AddMask {value} = + (name .~ "add-mask") . (message .~ Message value) diff --git a/library/GitHub/Workflow/Command/Stopping.hs b/library/GitHub/Workflow/Command/Stopping.hs new file mode 100644 index 0000000..532a74f --- /dev/null +++ b/library/GitHub/Workflow/Command/Stopping.hs @@ -0,0 +1,83 @@ +module GitHub.Workflow.Command.Stopping + ( -- * Basic usage + suspendCommands + + -- * Stop and resume + , stopCommands + , resumeCommands + , SuspendToken (..) + + -- * Manual token management + , randomSuspendToken + , suspendCommandsWithToken + , stopCommandsWithToken + + -- * Command types + , StopCommands (..) + , ResumeCommands (..) + ) where + +import Control.Applicative ((*>), (<*)) +import Control.Lens ((.~)) +import Control.Monad.Random.Class (MonadRandom, getRandomRs) +import Data.Function ((.)) +import Data.Functor (Functor ((<$)), (<$>)) +import Data.List qualified as List +import Data.Text (Text) +import Data.Text qualified as T +import GitHub.Workflow.Command.Execution +import GitHub.Workflow.Command.Syntax + +-- | Run an action with processing of workflow commands suspended +-- +-- GitHub documentation: +-- +suspendCommands + :: (MonadCommand m, MonadRandom m) + => m a + -- ^ Commands issued by this action will have no effect + -> m a +suspendCommands x = do + token <- randomSuspendToken + suspendCommandsWithToken token x + +suspendCommandsWithToken :: MonadCommand m => SuspendToken -> m a -> m a +suspendCommandsWithToken token x = + stopCommandsWithToken token *> x <* resumeCommands token + +-- | Stops processing any workflow commands +-- +-- This special command allows you to log anything without accidentally running a workflow command. +stopCommands :: (MonadCommand m, MonadRandom m) => m SuspendToken +stopCommands = do + token <- randomSuspendToken + token <$ stopCommandsWithToken token + +stopCommandsWithToken :: MonadCommand m => SuspendToken -> m () +stopCommandsWithToken token = + executeCommand StopCommands {token} + +-- | Resume processing workflow commands +resumeCommands :: MonadCommand m => SuspendToken -> m () +resumeCommands token = executeCommand ResumeCommands {token} + +newtype SuspendToken = SuspendToken Text + +randomSuspendToken :: MonadRandom m => m SuspendToken +randomSuspendToken = SuspendToken . T.pack . List.take 20 <$> getRandomRs ('a', 'z') + +newtype StopCommands = StopCommands + { token :: SuspendToken + } + +instance ToCommand StopCommands where + addToCommand StopCommands {token = SuspendToken t} = + (name .~ "stop-commands") . (message .~ Message t) + +newtype ResumeCommands = ResumeCommands + { token :: SuspendToken + } + +instance ToCommand ResumeCommands where + addToCommand ResumeCommands {token = SuspendToken t} = + name .~ Name t diff --git a/library/GitHub/Workflow/Command/Syntax/Command.hs b/library/GitHub/Workflow/Command/Syntax/Command.hs index be77e7b..80c843e 100644 --- a/library/GitHub/Workflow/Command/Syntax/Command.hs +++ b/library/GitHub/Workflow/Command/Syntax/Command.hs @@ -19,10 +19,20 @@ import GitHub.Workflow.Command.Syntax.Properties qualified as Properties import GitHub.Workflow.Command.Syntax.ToByteString import Prelude (Eq, Maybe (..), Ord, Show, not, (<>)) +-- | A GitHub workflow command +-- +-- A 'Command' consists of: +-- +-- * 'Name' ('HasName') +-- * 'Message' ('HasMessage') +-- * 'Properties' ('HasProperties') +-- +-- Of these, only 'Name' is always required. Some particular types of command require +-- a message or have restrictions on what properties they support or require. data Command = Command { name :: Name - , properties :: Properties , message :: Message + , properties :: Properties } deriving stock (Eq, Ord, Show) diff --git a/package.yaml b/package.yaml index 1c69fbd..c7c9530 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: github-workflow-commands -version: 0.0.0.0 +version: 0.0.1.0 maintainer: Freckle Education category: GitHub github: freckle/github-workflow-commands @@ -72,6 +72,7 @@ library: - bytestring - containers - lens + - MonadRandom - text tests: @@ -90,4 +91,5 @@ tests: dependencies: - github-workflow-commands - lens + build-tools: - markdown-unlit diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..bd84589 --- /dev/null +++ b/renovate.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "local>freckle/renovate-config" + ], + "minimumReleaseAge": "0 days" +} diff --git a/weeder.toml b/weeder.toml deleted file mode 100644 index c9de85a..0000000 --- a/weeder.toml +++ /dev/null @@ -1,6 +0,0 @@ -roots = [ - "Main.main", - "^Paths_.*", - "GitHub.Workflow.Command.Syntax.ToByteString.printByteStringLn", -] -type-class-roots = true