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
1,452 changes: 1,452 additions & 0 deletions .github/workflows/agentic-campaign-generator.lock.yml

Large diffs are not rendered by default.

30 changes: 14 additions & 16 deletions pkg/workflow/bundler_script_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,48 +197,46 @@ const repo = github.context.repo;
}

func TestScriptRegistry_RegisterWithMode_Validation(t *testing.T) {
t.Run("GitHub Script mode with execSync should panic", func(t *testing.T) {
t.Run("GitHub Script mode with execSync should return error", func(t *testing.T) {
registry := NewScriptRegistry()
invalidScript := `
const { execSync } = require("child_process");
execSync("ls -la");
`
assert.Panics(t, func() {
registry.RegisterWithMode("invalid_script", invalidScript, RuntimeModeGitHubScript)
}, "Should panic when registering GitHub Script with execSync")
err := registry.RegisterWithMode("invalid_script", invalidScript, RuntimeModeGitHubScript)
require.Error(t, err, "Should return error when registering GitHub Script with execSync")
assert.Contains(t, err.Error(), "execSync", "Error should mention execSync")
})

t.Run("Node.js mode with GitHub Actions globals should panic", func(t *testing.T) {
t.Run("Node.js mode with GitHub Actions globals should return error", func(t *testing.T) {
registry := NewScriptRegistry()
invalidScript := `
const fs = require("fs");
core.info("This should not be here");
`
assert.Panics(t, func() {
registry.RegisterWithMode("invalid_script", invalidScript, RuntimeModeNodeJS)
}, "Should panic when registering Node.js script with GitHub Actions globals")
err := registry.RegisterWithMode("invalid_script", invalidScript, RuntimeModeNodeJS)
require.Error(t, err, "Should return error when registering Node.js script with GitHub Actions globals")
assert.Contains(t, err.Error(), "GitHub Actions global", "Error should mention GitHub Actions globals")
})

t.Run("Valid GitHub Script mode should not panic", func(t *testing.T) {
t.Run("Valid GitHub Script mode should not return error", func(t *testing.T) {
registry := NewScriptRegistry()
validScript := `
const { exec } = require("@actions/exec");
core.info("This is valid for GitHub Script mode");
`
assert.NotPanics(t, func() {
registry.RegisterWithMode("valid_script", validScript, RuntimeModeGitHubScript)
}, "Should not panic with valid GitHub Script")
err := registry.RegisterWithMode("valid_script", validScript, RuntimeModeGitHubScript)
assert.NoError(t, err, "Should not return error with valid GitHub Script")
})

t.Run("Valid Node.js mode should not panic", func(t *testing.T) {
t.Run("Valid Node.js mode should not return error", func(t *testing.T) {
registry := NewScriptRegistry()
validScript := `
const fs = require("fs");
const { execSync } = require("child_process");
console.log("This is valid for Node.js mode");
`
assert.NotPanics(t, func() {
registry.RegisterWithMode("valid_script", validScript, RuntimeModeNodeJS)
}, "Should not panic with valid Node.js script")
err := registry.RegisterWithMode("valid_script", validScript, RuntimeModeNodeJS)
assert.NoError(t, err, "Should not return error with valid Node.js script")
})
}
15 changes: 9 additions & 6 deletions pkg/workflow/compiler_action_mode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"testing"

"github.com/githubnext/gh-aw/pkg/stringutil"
"github.com/stretchr/testify/require"
)

// TestActionModeDetection tests the DetectActionMode function
Expand Down Expand Up @@ -321,19 +322,20 @@ Test workflow with release mode.

// Register test script with action path
testScript := `const { core } = require('@actions/core'); core.info('test');`
DefaultScriptRegistry.RegisterWithAction(
err := DefaultScriptRegistry.RegisterWithAction(
"create_issue",
testScript,
RuntimeModeGitHubScript,
"./actions/create-issue",
)
require.NoError(t, err)

// Restore after test
defer func() {
if origActionPath != "" {
DefaultScriptRegistry.RegisterWithAction("create_issue", origScript, RuntimeModeGitHubScript, origActionPath)
_ = DefaultScriptRegistry.RegisterWithAction("create_issue", origScript, RuntimeModeGitHubScript, origActionPath)
} else {
DefaultScriptRegistry.RegisterWithMode("create_issue", origScript, RuntimeModeGitHubScript)
_ = DefaultScriptRegistry.RegisterWithMode("create_issue", origScript, RuntimeModeGitHubScript)
}
}()

Expand Down Expand Up @@ -408,13 +410,14 @@ Test
origActionPath := DefaultScriptRegistry.GetActionPath("create_issue")

testScript := `const { core } = require('@actions/core'); core.info('test');`
DefaultScriptRegistry.RegisterWithAction("create_issue", testScript, RuntimeModeGitHubScript, "./actions/create-issue")
err := DefaultScriptRegistry.RegisterWithAction("create_issue", testScript, RuntimeModeGitHubScript, "./actions/create-issue")
require.NoError(t, err)

defer func() {
if origActionPath != "" {
DefaultScriptRegistry.RegisterWithAction("create_issue", origScript, RuntimeModeGitHubScript, origActionPath)
_ = DefaultScriptRegistry.RegisterWithAction("create_issue", origScript, RuntimeModeGitHubScript, origActionPath)
} else {
DefaultScriptRegistry.RegisterWithMode("create_issue", origScript, RuntimeModeGitHubScript)
_ = DefaultScriptRegistry.RegisterWithMode("create_issue", origScript, RuntimeModeGitHubScript)
}
}()

Expand Down
18 changes: 11 additions & 7 deletions pkg/workflow/compiler_custom_actions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"testing"

"github.com/githubnext/gh-aw/pkg/stringutil"
"github.com/stretchr/testify/require"
)

// TestActionModeValidation tests the ActionMode type validation
Expand Down Expand Up @@ -105,7 +106,8 @@ func TestScriptRegistryWithAction(t *testing.T) {
testScript := `console.log('test');`
actionPath := "./actions/test-action"

registry.RegisterWithAction("test_script", testScript, RuntimeModeGitHubScript, actionPath)
err := registry.RegisterWithAction("test_script", testScript, RuntimeModeGitHubScript, actionPath)
require.NoError(t, err)

if !registry.Has("test_script") {
t.Error("Script should be registered")
Expand Down Expand Up @@ -163,20 +165,21 @@ Test workflow with safe-outputs.
const { core } = require('@actions/core');
core.info('Creating issue');
`
DefaultScriptRegistry.RegisterWithAction(
err := DefaultScriptRegistry.RegisterWithAction(
"create_issue",
testScript,
RuntimeModeGitHubScript,
"./actions/create-issue",
)
require.NoError(t, err)

// Restore after test
defer func() {
if origSource != "" {
if origActionPath != "" {
DefaultScriptRegistry.RegisterWithAction("create_issue", origSource, RuntimeModeGitHubScript, origActionPath)
_ = DefaultScriptRegistry.RegisterWithAction("create_issue", origSource, RuntimeModeGitHubScript, origActionPath)
} else {
DefaultScriptRegistry.RegisterWithMode("create_issue", origSource, RuntimeModeGitHubScript)
_ = DefaultScriptRegistry.RegisterWithMode("create_issue", origSource, RuntimeModeGitHubScript)
}
}
}()
Expand Down Expand Up @@ -305,15 +308,16 @@ Test fallback to inline mode.
origActionPath := DefaultScriptRegistry.GetActionPath("create_issue")

testScript := `console.log('test');`
DefaultScriptRegistry.RegisterWithMode("create_issue", testScript, RuntimeModeGitHubScript)
err := DefaultScriptRegistry.RegisterWithMode("create_issue", testScript, RuntimeModeGitHubScript)
require.NoError(t, err)

// Restore after test
defer func() {
if origSource != "" {
if origActionPath != "" {
DefaultScriptRegistry.RegisterWithAction("create_issue", origSource, RuntimeModeGitHubScript, origActionPath)
_ = DefaultScriptRegistry.RegisterWithAction("create_issue", origSource, RuntimeModeGitHubScript, origActionPath)
} else {
DefaultScriptRegistry.RegisterWithMode("create_issue", origSource, RuntimeModeGitHubScript)
_ = DefaultScriptRegistry.RegisterWithMode("create_issue", origSource, RuntimeModeGitHubScript)
}
}
}()
Expand Down
4 changes: 3 additions & 1 deletion pkg/workflow/custom_action_copilot_token_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

// TestCustomActionCopilotTokenFallback tests that custom actions use the correct
Expand All @@ -15,7 +16,8 @@ func TestCustomActionCopilotTokenFallback(t *testing.T) {
// Register a test custom action
testScript := `console.log('test');`
actionPath := "./actions/test-action"
DefaultScriptRegistry.RegisterWithAction("test_handler", testScript, RuntimeModeGitHubScript, actionPath)
err := DefaultScriptRegistry.RegisterWithAction("test_handler", testScript, RuntimeModeGitHubScript, actionPath)
require.NoError(t, err)

workflowData := &WorkflowData{
Name: "Test Workflow",
Expand Down
31 changes: 19 additions & 12 deletions pkg/workflow/script_registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,10 @@ func NewScriptRegistry() *ScriptRegistry {
//
// If a script with the same name already exists, it will be overwritten.
// This is useful for testing but should be avoided in production.
func (r *ScriptRegistry) Register(name string, source string) {
r.RegisterWithMode(name, source, RuntimeModeGitHubScript)
//
// Returns an error if validation fails.
func (r *ScriptRegistry) Register(name string, source string) error {
return r.RegisterWithMode(name, source, RuntimeModeGitHubScript)
}

// RegisterWithMode adds a script source to the registry with a specific runtime mode.
Expand All @@ -125,9 +127,9 @@ func (r *ScriptRegistry) Register(name string, source string) {
// - GitHub Script mode: validates no execSync usage (should use exec instead)
// - Node.js mode: validates no GitHub Actions globals (core.*, exec.*, github.*)
//
// Panics if validation fails, as this indicates a programming error that should be
// caught during development and testing.
func (r *ScriptRegistry) RegisterWithMode(name string, source string, mode RuntimeMode) {
// Returns an error if validation fails, allowing the caller to handle gracefully
// instead of crashing the process.
func (r *ScriptRegistry) RegisterWithMode(name string, source string, mode RuntimeMode) error {
r.mu.Lock()
defer r.mu.Unlock()

Expand All @@ -137,20 +139,20 @@ func (r *ScriptRegistry) RegisterWithMode(name string, source string, mode Runti

// Perform compile-time validation based on runtime mode
if err := validateNoExecSync(name, source, mode); err != nil {
// This is a programming error that should be caught during development
panic(fmt.Sprintf("Script registration validation failed: %v", err))
return fmt.Errorf("script registration validation failed for %q: %w", name, err)
}

if err := validateNoGitHubScriptGlobals(name, source, mode); err != nil {
// This is a programming error that should be caught during development
panic(fmt.Sprintf("Script registration validation failed: %v", err))
return fmt.Errorf("script registration validation failed for %q: %w", name, err)
}

r.scripts[name] = &scriptEntry{
source: source,
mode: mode,
actionPath: "", // No custom action by default
}

return nil
}

// RegisterWithAction registers a script with both inline code and a custom action path.
Expand All @@ -166,7 +168,10 @@ func (r *ScriptRegistry) RegisterWithMode(name string, source string, mode Runti
// The actionPath should be a relative path from the repository root for development mode.
// In the future, this can be extended to support versioned references like
// "githubnext/gh-aw/.github/actions/create-issue@SHA" for release mode.
func (r *ScriptRegistry) RegisterWithAction(name string, source string, mode RuntimeMode, actionPath string) {
//
// Returns an error if validation fails, allowing the caller to handle gracefully
// instead of crashing the process.
func (r *ScriptRegistry) RegisterWithAction(name string, source string, mode RuntimeMode, actionPath string) error {
r.mu.Lock()
defer r.mu.Unlock()

Expand All @@ -177,18 +182,20 @@ func (r *ScriptRegistry) RegisterWithAction(name string, source string, mode Run

// Perform compile-time validation based on runtime mode
if err := validateNoExecSync(name, source, mode); err != nil {
panic(fmt.Sprintf("Script registration validation failed: %v", err))
return fmt.Errorf("script registration validation failed for %q: %w", name, err)
}

if err := validateNoGitHubScriptGlobals(name, source, mode); err != nil {
panic(fmt.Sprintf("Script registration validation failed: %v", err))
return fmt.Errorf("script registration validation failed for %q: %w", name, err)
}

r.scripts[name] = &scriptEntry{
source: source,
mode: mode,
actionPath: actionPath,
}

return nil
}

// GetActionPath retrieves the custom action path for a script, if registered.
Expand Down
Loading
Loading