@@ -802,6 +802,240 @@ This workflow tests that invalid schedule strings in array format fail compilati
802802 t .Logf ("Integration test passed - invalid schedule in array format correctly failed compilation\n Output: %s" , outputStr )
803803}
804804
805+ // TestCompileStagedSafeOutputsCreateIssue verifies that a workflow with staged: true
806+ // and a create-issue handler compiles without error and emits GH_AW_SAFE_OUTPUTS_STAGED.
807+ // Prior to the schema fix, staged was not listed in the create-issue schema
808+ // (additionalProperties: false), so the frontmatter validator would reject the workflow.
809+ func TestCompileStagedSafeOutputsCreateIssue (t * testing.T ) {
810+ setup := setupIntegrationTest (t )
811+ defer setup .cleanup ()
812+
813+ testWorkflow := `---
814+ name: Staged Create Issue
815+ on:
816+ workflow_dispatch:
817+ permissions: read-all
818+ engine: copilot
819+ safe-outputs:
820+ staged: true
821+ create-issue:
822+ title-prefix: "[staged] "
823+ max: 1
824+ ---
825+
826+ Verify staged safe-outputs with create-issue.
827+ `
828+ testWorkflowPath := filepath .Join (setup .workflowsDir , "staged-create-issue.md" )
829+ if err := os .WriteFile (testWorkflowPath , []byte (testWorkflow ), 0644 ); err != nil {
830+ t .Fatalf ("Failed to write test workflow file: %v" , err )
831+ }
832+
833+ cmd := exec .Command (setup .binaryPath , "compile" , testWorkflowPath )
834+ output , err := cmd .CombinedOutput ()
835+ if err != nil {
836+ t .Fatalf ("CLI compile command failed: %v\n Output: %s" , err , string (output ))
837+ }
838+
839+ lockFilePath := filepath .Join (setup .workflowsDir , "staged-create-issue.lock.yml" )
840+ lockContent , err := os .ReadFile (lockFilePath )
841+ if err != nil {
842+ t .Fatalf ("Failed to read lock file: %v" , err )
843+ }
844+ lockContentStr := string (lockContent )
845+
846+ if ! strings .Contains (lockContentStr , `GH_AW_SAFE_OUTPUTS_STAGED: "true"` ) {
847+ t .Errorf ("Lock file should contain GH_AW_SAFE_OUTPUTS_STAGED: \" true\" \n Lock file content:\n %s" , lockContentStr )
848+ }
849+ }
850+
851+ // TestCompileStagedSafeOutputsAddComment verifies that a workflow with staged: true
852+ // and an add-comment handler compiles and emits GH_AW_SAFE_OUTPUTS_STAGED.
853+ // Prior to the schema fix, staged was not listed in the add-comment handler schema.
854+ func TestCompileStagedSafeOutputsAddComment (t * testing.T ) {
855+ setup := setupIntegrationTest (t )
856+ defer setup .cleanup ()
857+
858+ testWorkflow := `---
859+ name: Staged Add Comment
860+ on:
861+ workflow_dispatch:
862+ permissions: read-all
863+ engine: copilot
864+ safe-outputs:
865+ staged: true
866+ add-comment:
867+ max: 1
868+ ---
869+
870+ Verify staged safe-outputs with add-comment.
871+ `
872+ testWorkflowPath := filepath .Join (setup .workflowsDir , "staged-add-comment.md" )
873+ if err := os .WriteFile (testWorkflowPath , []byte (testWorkflow ), 0644 ); err != nil {
874+ t .Fatalf ("Failed to write test workflow file: %v" , err )
875+ }
876+
877+ cmd := exec .Command (setup .binaryPath , "compile" , testWorkflowPath )
878+ output , err := cmd .CombinedOutput ()
879+ if err != nil {
880+ t .Fatalf ("CLI compile command failed: %v\n Output: %s" , err , string (output ))
881+ }
882+
883+ lockFilePath := filepath .Join (setup .workflowsDir , "staged-add-comment.lock.yml" )
884+ lockContent , err := os .ReadFile (lockFilePath )
885+ if err != nil {
886+ t .Fatalf ("Failed to read lock file: %v" , err )
887+ }
888+ lockContentStr := string (lockContent )
889+
890+ if ! strings .Contains (lockContentStr , `GH_AW_SAFE_OUTPUTS_STAGED: "true"` ) {
891+ t .Errorf ("Lock file should contain GH_AW_SAFE_OUTPUTS_STAGED: \" true\" \n Lock file content:\n %s" , lockContentStr )
892+ }
893+ }
894+
895+ // TestCompileStagedSafeOutputsCreateDiscussion verifies that a workflow with staged: true
896+ // and a create-discussion handler compiles and emits GH_AW_SAFE_OUTPUTS_STAGED.
897+ // Prior to the schema fix, staged was not listed in the create-discussion handler schema.
898+ func TestCompileStagedSafeOutputsCreateDiscussion (t * testing.T ) {
899+ setup := setupIntegrationTest (t )
900+ defer setup .cleanup ()
901+
902+ testWorkflow := `---
903+ name: Staged Create Discussion
904+ on:
905+ workflow_dispatch:
906+ permissions:
907+ contents: read
908+ engine: copilot
909+ safe-outputs:
910+ staged: true
911+ create-discussion:
912+ max: 1
913+ category: general
914+ ---
915+
916+ Verify staged safe-outputs with create-discussion.
917+ `
918+ testWorkflowPath := filepath .Join (setup .workflowsDir , "staged-create-discussion.md" )
919+ if err := os .WriteFile (testWorkflowPath , []byte (testWorkflow ), 0644 ); err != nil {
920+ t .Fatalf ("Failed to write test workflow file: %v" , err )
921+ }
922+
923+ cmd := exec .Command (setup .binaryPath , "compile" , testWorkflowPath )
924+ output , err := cmd .CombinedOutput ()
925+ if err != nil {
926+ t .Fatalf ("CLI compile command failed: %v\n Output: %s" , err , string (output ))
927+ }
928+
929+ lockFilePath := filepath .Join (setup .workflowsDir , "staged-create-discussion.lock.yml" )
930+ lockContent , err := os .ReadFile (lockFilePath )
931+ if err != nil {
932+ t .Fatalf ("Failed to read lock file: %v" , err )
933+ }
934+ lockContentStr := string (lockContent )
935+
936+ if ! strings .Contains (lockContentStr , `GH_AW_SAFE_OUTPUTS_STAGED: "true"` ) {
937+ t .Errorf ("Lock file should contain GH_AW_SAFE_OUTPUTS_STAGED: \" true\" \n Lock file content:\n %s" , lockContentStr )
938+ }
939+ }
940+
941+ // TestCompileStagedSafeOutputsWithTargetRepo verifies that staged: true emits
942+ // GH_AW_SAFE_OUTPUTS_STAGED even when a target-repo is specified on the handler.
943+ // Staged mode is independent of target-repo.
944+ func TestCompileStagedSafeOutputsWithTargetRepo (t * testing.T ) {
945+ setup := setupIntegrationTest (t )
946+ defer setup .cleanup ()
947+
948+ testWorkflow := `---
949+ name: Staged Cross-Repo
950+ on:
951+ workflow_dispatch:
952+ permissions: read-all
953+ engine: copilot
954+ safe-outputs:
955+ staged: true
956+ create-issue:
957+ title-prefix: "[cross-repo staged] "
958+ max: 1
959+ target-repo: org/other-repo
960+ ---
961+
962+ Verify that staged mode is independent of target-repo.
963+ `
964+ testWorkflowPath := filepath .Join (setup .workflowsDir , "staged-cross-repo.md" )
965+ if err := os .WriteFile (testWorkflowPath , []byte (testWorkflow ), 0644 ); err != nil {
966+ t .Fatalf ("Failed to write test workflow file: %v" , err )
967+ }
968+
969+ cmd := exec .Command (setup .binaryPath , "compile" , testWorkflowPath )
970+ output , err := cmd .CombinedOutput ()
971+ if err != nil {
972+ t .Fatalf ("CLI compile command failed: %v\n Output: %s" , err , string (output ))
973+ }
974+
975+ lockFilePath := filepath .Join (setup .workflowsDir , "staged-cross-repo.lock.yml" )
976+ lockContent , err := os .ReadFile (lockFilePath )
977+ if err != nil {
978+ t .Fatalf ("Failed to read lock file: %v" , err )
979+ }
980+ lockContentStr := string (lockContent )
981+
982+ // staged is independent of target-repo: env var must be present
983+ if ! strings .Contains (lockContentStr , `GH_AW_SAFE_OUTPUTS_STAGED: "true"` ) {
984+ t .Errorf ("Lock file should contain GH_AW_SAFE_OUTPUTS_STAGED: \" true\" even with target-repo set\n Lock file content:\n %s" , lockContentStr )
985+ }
986+ }
987+
988+ // TestCompileStagedSafeOutputsMultipleHandlers verifies that staged: true with
989+ // multiple handler types compiles and emits GH_AW_SAFE_OUTPUTS_STAGED exactly once.
990+ // Previously, adding staged to most handler types caused a schema validation error.
991+ func TestCompileStagedSafeOutputsMultipleHandlers (t * testing.T ) {
992+ setup := setupIntegrationTest (t )
993+ defer setup .cleanup ()
994+
995+ testWorkflow := `---
996+ name: Staged Multiple Handlers
997+ on:
998+ workflow_dispatch:
999+ permissions: read-all
1000+ engine: copilot
1001+ safe-outputs:
1002+ staged: true
1003+ create-issue:
1004+ title-prefix: "[staged] "
1005+ max: 1
1006+ add-comment:
1007+ max: 2
1008+ add-labels:
1009+ allowed:
1010+ - bug
1011+ update-issue:
1012+ ---
1013+
1014+ Verify staged safe-outputs with multiple handler types.
1015+ `
1016+ testWorkflowPath := filepath .Join (setup .workflowsDir , "staged-multi-handler.md" )
1017+ if err := os .WriteFile (testWorkflowPath , []byte (testWorkflow ), 0644 ); err != nil {
1018+ t .Fatalf ("Failed to write test workflow file: %v" , err )
1019+ }
1020+
1021+ cmd := exec .Command (setup .binaryPath , "compile" , testWorkflowPath )
1022+ output , err := cmd .CombinedOutput ()
1023+ if err != nil {
1024+ t .Fatalf ("CLI compile command failed: %v\n Output: %s" , err , string (output ))
1025+ }
1026+
1027+ lockFilePath := filepath .Join (setup .workflowsDir , "staged-multi-handler.lock.yml" )
1028+ lockContent , err := os .ReadFile (lockFilePath )
1029+ if err != nil {
1030+ t .Fatalf ("Failed to read lock file: %v" , err )
1031+ }
1032+ lockContentStr := string (lockContent )
1033+
1034+ if ! strings .Contains (lockContentStr , `GH_AW_SAFE_OUTPUTS_STAGED: "true"` ) {
1035+ t .Errorf ("Lock file should contain GH_AW_SAFE_OUTPUTS_STAGED: \" true\" \n Lock file content:\n %s" , lockContentStr )
1036+ }
1037+ }
1038+
8051039// TestCompileFromSubdirectoryCreatesActionsLockAtRoot tests that actions-lock.json
8061040// is created at the repository root when compiling from a subdirectory
8071041func TestCompileFromSubdirectoryCreatesActionsLockAtRoot (t * testing.T ) {
0 commit comments