Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/patch-fix-safe-outputs-staged-handlers.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions .github/workflows/daily-choice-test.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

234 changes: 234 additions & 0 deletions pkg/cli/compile_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -802,6 +802,240 @@ This workflow tests that invalid schedule strings in array format fail compilati
t.Logf("Integration test passed - invalid schedule in array format correctly failed compilation\nOutput: %s", outputStr)
}

// TestCompileStagedSafeOutputsCreateIssue verifies that a workflow with staged: true
// and a create-issue handler compiles without error and emits GH_AW_SAFE_OUTPUTS_STAGED.
// Prior to the schema fix, staged was not listed in the create-issue schema
// (additionalProperties: false), so the frontmatter validator would reject the workflow.
func TestCompileStagedSafeOutputsCreateIssue(t *testing.T) {
setup := setupIntegrationTest(t)
defer setup.cleanup()

testWorkflow := `---
name: Staged Create Issue
on:
workflow_dispatch:
permissions: read-all
engine: copilot
safe-outputs:
staged: true
create-issue:
title-prefix: "[staged] "
max: 1
---

Verify staged safe-outputs with create-issue.
`
testWorkflowPath := filepath.Join(setup.workflowsDir, "staged-create-issue.md")
if err := os.WriteFile(testWorkflowPath, []byte(testWorkflow), 0644); err != nil {
t.Fatalf("Failed to write test workflow file: %v", err)
}

cmd := exec.Command(setup.binaryPath, "compile", testWorkflowPath)
output, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("CLI compile command failed: %v\nOutput: %s", err, string(output))
}

lockFilePath := filepath.Join(setup.workflowsDir, "staged-create-issue.lock.yml")
lockContent, err := os.ReadFile(lockFilePath)
if err != nil {
t.Fatalf("Failed to read lock file: %v", err)
}
lockContentStr := string(lockContent)

if !strings.Contains(lockContentStr, `GH_AW_SAFE_OUTPUTS_STAGED: "true"`) {
t.Errorf("Lock file should contain GH_AW_SAFE_OUTPUTS_STAGED: \"true\"\nLock file content:\n%s", lockContentStr)
}
}

// TestCompileStagedSafeOutputsAddComment verifies that a workflow with staged: true
// and an add-comment handler compiles and emits GH_AW_SAFE_OUTPUTS_STAGED.
// Prior to the schema fix, staged was not listed in the add-comment handler schema.
func TestCompileStagedSafeOutputsAddComment(t *testing.T) {
setup := setupIntegrationTest(t)
defer setup.cleanup()

testWorkflow := `---
name: Staged Add Comment
on:
workflow_dispatch:
permissions: read-all
engine: copilot
safe-outputs:
staged: true
add-comment:
max: 1
---

Verify staged safe-outputs with add-comment.
`
testWorkflowPath := filepath.Join(setup.workflowsDir, "staged-add-comment.md")
if err := os.WriteFile(testWorkflowPath, []byte(testWorkflow), 0644); err != nil {
t.Fatalf("Failed to write test workflow file: %v", err)
}

cmd := exec.Command(setup.binaryPath, "compile", testWorkflowPath)
output, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("CLI compile command failed: %v\nOutput: %s", err, string(output))
}

lockFilePath := filepath.Join(setup.workflowsDir, "staged-add-comment.lock.yml")
lockContent, err := os.ReadFile(lockFilePath)
if err != nil {
t.Fatalf("Failed to read lock file: %v", err)
}
lockContentStr := string(lockContent)

if !strings.Contains(lockContentStr, `GH_AW_SAFE_OUTPUTS_STAGED: "true"`) {
t.Errorf("Lock file should contain GH_AW_SAFE_OUTPUTS_STAGED: \"true\"\nLock file content:\n%s", lockContentStr)
}
}

// TestCompileStagedSafeOutputsCreateDiscussion verifies that a workflow with staged: true
// and a create-discussion handler compiles and emits GH_AW_SAFE_OUTPUTS_STAGED.
// Prior to the schema fix, staged was not listed in the create-discussion handler schema.
func TestCompileStagedSafeOutputsCreateDiscussion(t *testing.T) {
setup := setupIntegrationTest(t)
defer setup.cleanup()

testWorkflow := `---
name: Staged Create Discussion
on:
workflow_dispatch:
permissions:
contents: read
engine: copilot
safe-outputs:
staged: true
create-discussion:
max: 1
category: general
---

Verify staged safe-outputs with create-discussion.
`
testWorkflowPath := filepath.Join(setup.workflowsDir, "staged-create-discussion.md")
if err := os.WriteFile(testWorkflowPath, []byte(testWorkflow), 0644); err != nil {
t.Fatalf("Failed to write test workflow file: %v", err)
}

cmd := exec.Command(setup.binaryPath, "compile", testWorkflowPath)
output, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("CLI compile command failed: %v\nOutput: %s", err, string(output))
}

lockFilePath := filepath.Join(setup.workflowsDir, "staged-create-discussion.lock.yml")
lockContent, err := os.ReadFile(lockFilePath)
if err != nil {
t.Fatalf("Failed to read lock file: %v", err)
}
lockContentStr := string(lockContent)

if !strings.Contains(lockContentStr, `GH_AW_SAFE_OUTPUTS_STAGED: "true"`) {
t.Errorf("Lock file should contain GH_AW_SAFE_OUTPUTS_STAGED: \"true\"\nLock file content:\n%s", lockContentStr)
}
}

// TestCompileStagedSafeOutputsWithTargetRepo verifies that staged: true emits
// GH_AW_SAFE_OUTPUTS_STAGED even when a target-repo is specified on the handler.
// Staged mode is independent of target-repo.
func TestCompileStagedSafeOutputsWithTargetRepo(t *testing.T) {
setup := setupIntegrationTest(t)
defer setup.cleanup()

testWorkflow := `---
name: Staged Cross-Repo
on:
workflow_dispatch:
permissions: read-all
engine: copilot
safe-outputs:
staged: true
create-issue:
title-prefix: "[cross-repo staged] "
max: 1
target-repo: org/other-repo
---

Verify that staged mode is independent of target-repo.
`
testWorkflowPath := filepath.Join(setup.workflowsDir, "staged-cross-repo.md")
if err := os.WriteFile(testWorkflowPath, []byte(testWorkflow), 0644); err != nil {
t.Fatalf("Failed to write test workflow file: %v", err)
}

cmd := exec.Command(setup.binaryPath, "compile", testWorkflowPath)
output, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("CLI compile command failed: %v\nOutput: %s", err, string(output))
}

lockFilePath := filepath.Join(setup.workflowsDir, "staged-cross-repo.lock.yml")
lockContent, err := os.ReadFile(lockFilePath)
if err != nil {
t.Fatalf("Failed to read lock file: %v", err)
}
lockContentStr := string(lockContent)

// staged is independent of target-repo: env var must be present
if !strings.Contains(lockContentStr, `GH_AW_SAFE_OUTPUTS_STAGED: "true"`) {
t.Errorf("Lock file should contain GH_AW_SAFE_OUTPUTS_STAGED: \"true\" even with target-repo set\nLock file content:\n%s", lockContentStr)
}
}

// TestCompileStagedSafeOutputsMultipleHandlers verifies that staged: true with
// multiple handler types compiles and emits GH_AW_SAFE_OUTPUTS_STAGED exactly once.
// Previously, adding staged to most handler types caused a schema validation error.
func TestCompileStagedSafeOutputsMultipleHandlers(t *testing.T) {
setup := setupIntegrationTest(t)
defer setup.cleanup()

testWorkflow := `---
name: Staged Multiple Handlers
on:
workflow_dispatch:
permissions: read-all
engine: copilot
safe-outputs:
staged: true
create-issue:
title-prefix: "[staged] "
max: 1
add-comment:
max: 2
add-labels:
allowed:
- bug
update-issue:
---

Verify staged safe-outputs with multiple handler types.
`
testWorkflowPath := filepath.Join(setup.workflowsDir, "staged-multi-handler.md")
if err := os.WriteFile(testWorkflowPath, []byte(testWorkflow), 0644); err != nil {
t.Fatalf("Failed to write test workflow file: %v", err)
}

cmd := exec.Command(setup.binaryPath, "compile", testWorkflowPath)
output, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("CLI compile command failed: %v\nOutput: %s", err, string(output))
}

lockFilePath := filepath.Join(setup.workflowsDir, "staged-multi-handler.lock.yml")
lockContent, err := os.ReadFile(lockFilePath)
if err != nil {
t.Fatalf("Failed to read lock file: %v", err)
}
lockContentStr := string(lockContent)

if !strings.Contains(lockContentStr, `GH_AW_SAFE_OUTPUTS_STAGED: "true"`) {
t.Errorf("Lock file should contain GH_AW_SAFE_OUTPUTS_STAGED: \"true\"\nLock file content:\n%s", lockContentStr)
}
}

// TestCompileFromSubdirectoryCreatesActionsLockAtRoot tests that actions-lock.json
// is created at the repository root when compiling from a subdirectory
func TestCompileFromSubdirectoryCreatesActionsLockAtRoot(t *testing.T) {
Expand Down
16 changes: 16 additions & 0 deletions pkg/cli/workflows/test-staged-add-comment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
on:
workflow_dispatch:
permissions: read-all
engine: copilot
safe-outputs:
staged: true
add-comment:
max: 1
---

# Test Staged Add Comment

Verify that `staged: true` works together with the `add-comment` handler.

Add a comment to issue #1 saying "Staged test comment."
18 changes: 18 additions & 0 deletions pkg/cli/workflows/test-staged-create-discussion.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
on:
workflow_dispatch:
permissions:
contents: read
engine: copilot
safe-outputs:
staged: true
create-discussion:
max: 1
category: general
---

# Test Staged Create Discussion

Verify that `staged: true` works together with the `create-discussion` handler.

Create a discussion titled "Staged test discussion" with the body "This discussion was created in staged mode."
17 changes: 17 additions & 0 deletions pkg/cli/workflows/test-staged-create-issue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
on:
workflow_dispatch:
permissions: read-all
engine: copilot
safe-outputs:
staged: true
create-issue:
title-prefix: "[staged] "
max: 1
---

# Test Staged Create Issue

Verify that `staged: true` works together with the `create-issue` handler.

Create an issue titled "Staged test issue" with the body "This issue was created in staged mode."
19 changes: 19 additions & 0 deletions pkg/cli/workflows/test-staged-cross-repo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
on:
workflow_dispatch:
permissions: read-all
engine: copilot
safe-outputs:
staged: true
create-issue:
title-prefix: "[cross-repo staged] "
max: 1
target-repo: org/other-repo
---

# Test Staged Create Issue Cross-Repo

Verify that `staged: true` is emitted even when a `target-repo` is configured.
`staged` mode is independent of the `target-repo` setting.

Create an issue in the target repository titled "Cross-repo staged test issue."
Loading
Loading