Repository: nektos/act (language: go)

Issue:
Add support for `>> $GITHUB_OUTPUT` because `set-output` will be deprectated.
### Act version

0.2.32.r13.gff5e289

### Feature description

I think this functionality isn't yet part of act, which isn't strange since i think it is just announced.
But it looks like they are going to deprecate the `set-output` and `set-state` options and replace them with `>> $GITHUB_OUTPUT` and `>> $GITHUB_STATE`

See: https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/
And also: https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-an-output-parameter



Discussion / hints:
I'm waiting for #1293 to be merged before looking into implementing this here, because this requires refactoring of the `GITHUB_ENV` logic to be shared with `GITHUB_STATE` , `GITHUB_OUTPUT` and the Host Environment otherwise this creates merge conflics for me.
I currently have a workaround which looks like this:
```bash
          # Special function to either use set-output or echo >> GITHUB_OUTPUT
          function set_output () {
            if [[ -z "${ACT}" ]]; then
              echo "${1}=${2}" | tee -a "${GITHUB_OUTPUT}"
            else
              echo "set-output name=${1}::${2}"
              echo "::set-output name=${1}::${2}"
            fi
          }

          set_output "MY_OUTPUT_VAR" "The output value"
```

In case someone is bumping into this.
This saves at least a lot of messages, because it looks like Github is just checking if there is `set-output` somewhere.
After checking the code, the only merge conflict would be due to actPath.
I opened a PR for this, https://github.com/nektos/act/pull/1391 you can use the binary from CI checks in the meantime. This simply redirects the env files to the old command logic.


Fix patch:
diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml
index 4a32128538e..baf5196ab8d 100644
--- a/.github/workflows/checks.yml
+++ b/.github/workflows/checks.yml
@@ -50,7 +50,7 @@ jobs:
           key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
           restore-keys: |
             ${{ runner.os }}-go-
-      - run: go test -v -cover -coverprofile=coverage.txt -covermode=atomic ./...
+      - run: go test -v -cover -coverprofile=coverage.txt -covermode=atomic -timeout 15m ./...
       - name: Upload Codecov report
         uses: codecov/codecov-action@v3.1.1
         with:
diff --git a/pkg/runner/step.go b/pkg/runner/step.go
index 4f7803e1950..8a9d2b6d797 100644
--- a/pkg/runner/step.go
+++ b/pkg/runner/step.go
@@ -3,9 +3,11 @@ package runner
 import (
 	"context"
 	"fmt"
+	"path"
 	"strings"
 
 	"github.com/nektos/act/pkg/common"
+	"github.com/nektos/act/pkg/container"
 	"github.com/nektos/act/pkg/exprparser"
 	"github.com/nektos/act/pkg/model"
 )
@@ -94,6 +96,20 @@ func runStepExecutor(step step, stage stepStage, executor common.Executor) commo
 		}
 		logger.Infof("\u2B50 Run %s %s", stage, stepString)
 
+		// Prepare and clean Runner File Commands
+		actPath := rc.JobContainer.GetActPath()
+		outputFileCommand := path.Join("workflow", "outputcmd.txt")
+		stateFileCommand := path.Join("workflow", "statecmd.txt")
+		(*step.getEnv())["GITHUB_OUTPUT"] = path.Join(actPath, outputFileCommand)
+		(*step.getEnv())["GITHUB_STATE"] = path.Join(actPath, stateFileCommand)
+		_ = rc.JobContainer.Copy(actPath, &container.FileEntry{
+			Name: outputFileCommand,
+			Mode: 0666,
+		}, &container.FileEntry{
+			Name: stateFileCommand,
+			Mode: 0666,
+		})(ctx)
+
 		err = executor(ctx)
 
 		if err == nil {
@@ -117,6 +133,27 @@ func runStepExecutor(step step, stage stepStage, executor common.Executor) commo
 
 			logger.WithField("stepResult", rc.StepResults[rc.CurrentStep].Outcome).Errorf("  \u274C  Failure - %s %s", stage, stepString)
 		}
+		// Process Runner File Commands
+		orgerr := err
+		state := map[string]string{}
+		err = rc.JobContainer.UpdateFromEnv(path.Join(actPath, stateFileCommand), &state)(ctx)
+		if err != nil {
+			return err
+		}
+		for k, v := range state {
+			rc.saveState(ctx, map[string]string{"name": k}, v)
+		}
+		output := map[string]string{}
+		err = rc.JobContainer.UpdateFromEnv(path.Join(actPath, outputFileCommand), &output)(ctx)
+		if err != nil {
+			return err
+		}
+		for k, v := range output {
+			rc.setOutput(ctx, map[string]string{"name": k}, v)
+		}
+		if orgerr != nil {
+			return orgerr
+		}
 		return err
 	}
 }


Test patch:
diff --git a/pkg/runner/step_action_local_test.go b/pkg/runner/step_action_local_test.go
index 8180d6defca..6b14f3d2399 100644
--- a/pkg/runner/step_action_local_test.go
+++ b/pkg/runner/step_action_local_test.go
@@ -2,6 +2,7 @@ package runner
 
 import (
 	"context"
+	"path/filepath"
 	"strings"
 	"testing"
 
@@ -63,7 +64,7 @@ func TestStepActionLocalTest(t *testing.T) {
 		},
 	}
 
-	salm.On("readAction", sal.Step, "/tmp/path/to/action", "", mock.Anything, mock.Anything).
+	salm.On("readAction", sal.Step, filepath.Clean("/tmp/path/to/action"), "", mock.Anything, mock.Anything).
 		Return(&model.Action{}, nil)
 
 	cm.On("UpdateFromImageEnv", mock.AnythingOfType("*map[string]string")).Return(func(ctx context.Context) error {
@@ -78,7 +79,19 @@ func TestStepActionLocalTest(t *testing.T) {
 		return nil
 	})
 
-	salm.On("runAction", sal, "/tmp/path/to/action", (*remoteAction)(nil)).Return(func(ctx context.Context) error {
+	cm.On("Copy", "/var/run/act", mock.AnythingOfType("[]*container.FileEntry")).Return(func(ctx context.Context) error {
+		return nil
+	})
+
+	cm.On("UpdateFromEnv", "/var/run/act/workflow/statecmd.txt", mock.AnythingOfType("*map[string]string")).Return(func(ctx context.Context) error {
+		return nil
+	})
+
+	cm.On("UpdateFromEnv", "/var/run/act/workflow/outputcmd.txt", mock.AnythingOfType("*map[string]string")).Return(func(ctx context.Context) error {
+		return nil
+	})
+
+	salm.On("runAction", sal, filepath.Clean("/tmp/path/to/action"), (*remoteAction)(nil)).Return(func(ctx context.Context) error {
 		return nil
 	})
 
@@ -275,6 +288,18 @@ func TestStepActionLocalPost(t *testing.T) {
 					})
 				}
 				cm.On("Exec", suffixMatcher("pkg/runner/local/action/post.js"), sal.env, "", "").Return(func(ctx context.Context) error { return tt.err })
+
+				cm.On("Copy", "/var/run/act", mock.AnythingOfType("[]*container.FileEntry")).Return(func(ctx context.Context) error {
+					return nil
+				})
+
+				cm.On("UpdateFromEnv", "/var/run/act/workflow/statecmd.txt", mock.AnythingOfType("*map[string]string")).Return(func(ctx context.Context) error {
+					return nil
+				})
+
+				cm.On("UpdateFromEnv", "/var/run/act/workflow/outputcmd.txt", mock.AnythingOfType("*map[string]string")).Return(func(ctx context.Context) error {
+					return nil
+				})
 			}
 
 			err := sal.post()(ctx)
diff --git a/pkg/runner/step_action_remote_test.go b/pkg/runner/step_action_remote_test.go
index 1e117ea58fb..84fe082926d 100644
--- a/pkg/runner/step_action_remote_test.go
+++ b/pkg/runner/step_action_remote_test.go
@@ -172,6 +172,18 @@ func TestStepActionRemote(t *testing.T) {
 			}
 			if tt.mocks.run {
 				sarm.On("runAction", sar, suffixMatcher("act/remote-action@v1"), newRemoteAction(sar.Step.Uses)).Return(func(ctx context.Context) error { return tt.runError })
+
+				cm.On("Copy", "/var/run/act", mock.AnythingOfType("[]*container.FileEntry")).Return(func(ctx context.Context) error {
+					return nil
+				})
+
+				cm.On("UpdateFromEnv", "/var/run/act/workflow/statecmd.txt", mock.AnythingOfType("*map[string]string")).Return(func(ctx context.Context) error {
+					return nil
+				})
+
+				cm.On("UpdateFromEnv", "/var/run/act/workflow/outputcmd.txt", mock.AnythingOfType("*map[string]string")).Return(func(ctx context.Context) error {
+					return nil
+				})
 			}
 
 			err := sar.pre()(ctx)
@@ -582,6 +594,18 @@ func TestStepActionRemotePost(t *testing.T) {
 			}
 			if tt.mocks.exec {
 				cm.On("Exec", []string{"node", "/var/run/act/actions/remote-action@v1/post.js"}, sar.env, "", "").Return(func(ctx context.Context) error { return tt.err })
+
+				cm.On("Copy", "/var/run/act", mock.AnythingOfType("[]*container.FileEntry")).Return(func(ctx context.Context) error {
+					return nil
+				})
+
+				cm.On("UpdateFromEnv", "/var/run/act/workflow/statecmd.txt", mock.AnythingOfType("*map[string]string")).Return(func(ctx context.Context) error {
+					return nil
+				})
+
+				cm.On("UpdateFromEnv", "/var/run/act/workflow/outputcmd.txt", mock.AnythingOfType("*map[string]string")).Return(func(ctx context.Context) error {
+					return nil
+				})
 			}
 
 			err := sar.post()(ctx)
diff --git a/pkg/runner/step_docker_test.go b/pkg/runner/step_docker_test.go
index c6794b472e2..e0e8575f476 100644
--- a/pkg/runner/step_docker_test.go
+++ b/pkg/runner/step_docker_test.go
@@ -86,6 +86,18 @@ func TestStepDockerMain(t *testing.T) {
 		return nil
 	})
 
+	cm.On("Copy", "/var/run/act", mock.AnythingOfType("[]*container.FileEntry")).Return(func(ctx context.Context) error {
+		return nil
+	})
+
+	cm.On("UpdateFromEnv", "/var/run/act/workflow/statecmd.txt", mock.AnythingOfType("*map[string]string")).Return(func(ctx context.Context) error {
+		return nil
+	})
+
+	cm.On("UpdateFromEnv", "/var/run/act/workflow/outputcmd.txt", mock.AnythingOfType("*map[string]string")).Return(func(ctx context.Context) error {
+		return nil
+	})
+
 	err := sd.main()(ctx)
 	assert.Nil(t, err)
 
diff --git a/pkg/runner/step_run_test.go b/pkg/runner/step_run_test.go
index 081d86476ec..e5cde1238e9 100644
--- a/pkg/runner/step_run_test.go
+++ b/pkg/runner/step_run_test.go
@@ -65,6 +65,18 @@ func TestStepRun(t *testing.T) {
 		return nil
 	})
 
+	cm.On("Copy", "/var/run/act", mock.AnythingOfType("[]*container.FileEntry")).Return(func(ctx context.Context) error {
+		return nil
+	})
+
+	cm.On("UpdateFromEnv", "/var/run/act/workflow/statecmd.txt", mock.AnythingOfType("*map[string]string")).Return(func(ctx context.Context) error {
+		return nil
+	})
+
+	cm.On("UpdateFromEnv", "/var/run/act/workflow/outputcmd.txt", mock.AnythingOfType("*map[string]string")).Return(func(ctx context.Context) error {
+		return nil
+	})
+
 	ctx := context.Background()
 
 	err := sr.main()(ctx)