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
2 changes: 1 addition & 1 deletion .github/workflows/weekly-safe-outputs-spec-review.lock.yml

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

3 changes: 2 additions & 1 deletion actions/setup/js/add_comment.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const { getRepositoryUrl } = require("./get_repository_url.cjs");
const { replaceTemporaryIdReferences, loadTemporaryIdMapFromResolved, resolveRepoIssueTarget } = require("./temporary_id.cjs");
const { getTrackerID } = require("./get_tracker_id.cjs");
const { getErrorMessage } = require("./error_helpers.cjs");
const { parseBoolTemplatable } = require("./templatable.cjs");
const { resolveTarget } = require("./safe_output_helpers.cjs");
const { resolveTargetRepoConfig, resolveAndValidateRepo } = require("./repo_helpers.cjs");
const { getMissingInfoSections } = require("./missing_messages_helper.cjs");
Expand Down Expand Up @@ -280,7 +281,7 @@ async function commentOnDiscussion(github, owner, repo, discussionNumber, messag
*/
async function main(config = {}) {
// Extract configuration
const hideOlderCommentsEnabled = config.hide_older_comments === true;
const hideOlderCommentsEnabled = parseBoolTemplatable(config.hide_older_comments, false);
const commentTarget = config.target || "triggering";
const maxCount = config.max || 20;
const { defaultTargetRepo, allowedRepos } = resolveTargetRepoConfig(config);
Expand Down
3 changes: 2 additions & 1 deletion actions/setup/js/assign_to_user.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const { getErrorMessage } = require("./error_helpers.cjs");
const { resolveTargetRepoConfig, resolveAndValidateRepo } = require("./repo_helpers.cjs");
const { resolveIssueNumber, extractAssignees } = require("./safe_output_helpers.cjs");
const { logStagedPreviewInfo } = require("./staged_preview.cjs");
const { parseBoolTemplatable } = require("./templatable.cjs");

/** @type {string} Safe output type handled by this module */
const HANDLER_TYPE = "assign_to_user";
Expand All @@ -24,7 +25,7 @@ async function main(config = {}) {
const allowedAssignees = config.allowed || [];
const blockedAssignees = config.blocked || [];
const maxCount = config.max || 10;
const unassignFirst = config.unassign_first || false;
const unassignFirst = parseBoolTemplatable(config.unassign_first, false);
const { defaultTargetRepo, allowedRepos } = resolveTargetRepoConfig(config);

// Check if we're in staged mode
Expand Down
5 changes: 3 additions & 2 deletions actions/setup/js/create_discussion.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const { generateWorkflowIdMarker } = require("./generate_footer.cjs");
const { sanitizeLabelContent } = require("./sanitize_label_content.cjs");
const { tryEnforceArrayLimit } = require("./limit_enforcement_helpers.cjs");
const { logStagedPreviewInfo } = require("./staged_preview.cjs");
const { parseBoolTemplatable } = require("./templatable.cjs");

/**
* Maximum limits for discussion parameters to prevent resource exhaustion.
Expand Down Expand Up @@ -306,8 +307,8 @@ async function main(config = {}) {
const maxCount = config.max || 10;
const expiresHours = config.expires ? parseInt(String(config.expires), 10) : 0;
const fallbackToIssue = config.fallback_to_issue !== false; // Default to true
const closeOlderDiscussions = config.close_older_discussions === true || config.close_older_discussions === "true";
const includeFooter = config.footer !== false; // Default to true (include footer)
const closeOlderDiscussions = parseBoolTemplatable(config.close_older_discussions, false);
const includeFooter = parseBoolTemplatable(config.footer, true);

// Check if we're in staged mode
const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true";
Expand Down
7 changes: 4 additions & 3 deletions actions/setup/js/create_issue.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const { renderTemplate } = require("./messages_core.cjs");
const { createExpirationLine, addExpirationToFooter } = require("./ephemerals.cjs");
const { MAX_SUB_ISSUES, getSubIssueCount } = require("./sub_issue_helpers.cjs");
const { closeOlderIssues } = require("./close_older_issues.cjs");
const { parseBoolTemplatable } = require("./templatable.cjs");
const { tryEnforceArrayLimit } = require("./limit_enforcement_helpers.cjs");
const fs = require("fs");
const { logStagedPreviewInfo } = require("./staged_preview.cjs");
Expand Down Expand Up @@ -209,9 +210,9 @@ async function main(config = {}) {
const expiresHours = config.expires ? parseInt(String(config.expires), 10) : 0;
const maxCount = config.max ?? 10;
const { defaultTargetRepo, allowedRepos } = resolveTargetRepoConfig(config);
const groupEnabled = config.group === true || config.group === "true";
const closeOlderIssuesEnabled = config.close_older_issues === true || config.close_older_issues === "true";
const includeFooter = config.footer !== false; // Default to true (include footer)
const groupEnabled = parseBoolTemplatable(config.group, false);
const closeOlderIssuesEnabled = parseBoolTemplatable(config.close_older_issues, false);
const includeFooter = parseBoolTemplatable(config.footer, true);

// Check if copilot assignment is enabled
const assignCopilot = process.env.GH_AW_ASSIGN_COPILOT === "true";
Expand Down
6 changes: 3 additions & 3 deletions actions/setup/js/create_pull_request.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -95,14 +95,14 @@ async function main(config = {}) {
const envLabels = config.labels ? (Array.isArray(config.labels) ? config.labels : config.labels.split(",")).map(label => String(label).trim()).filter(label => label) : [];
const draftDefault = parseBoolTemplatable(config.draft, true);
const ifNoChanges = config.if_no_changes || "warn";
const allowEmpty = config.allow_empty || false;
const autoMerge = config.auto_merge || false;
const allowEmpty = parseBoolTemplatable(config.allow_empty, false);
const autoMerge = parseBoolTemplatable(config.auto_merge, false);
const expiresHours = config.expires ? parseInt(String(config.expires), 10) : 0;
const maxCount = config.max || 1; // PRs are typically limited to 1
let baseBranch = config.base_branch || "";
const maxSizeKb = config.max_patch_size ? parseInt(String(config.max_patch_size), 10) : 1024;
const { defaultTargetRepo, allowedRepos } = resolveTargetRepoConfig(config);
const includeFooter = config.footer !== false; // Default to true (include footer)
const includeFooter = parseBoolTemplatable(config.footer, true);
const fallbackAsIssue = config.fallback_as_issue !== false; // Default to true (fallback enabled)

// Environment validation - fail early if required variables are missing
Expand Down
3 changes: 2 additions & 1 deletion actions/setup/js/reply_to_pr_review_comment.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const { generateFooterWithMessages } = require("./messages_footer.cjs");
const { sanitizeContent } = require("./sanitize_content.cjs");
const { getPRNumber } = require("./update_context_helpers.cjs");
const { logStagedPreviewInfo } = require("./staged_preview.cjs");
const { parseBoolTemplatable } = require("./templatable.cjs");

/**
* Type constant for handler identification
Expand All @@ -30,7 +31,7 @@ async function main(config = {}) {
// Extract configuration
const maxCount = config.max || 10;
const replyTarget = config.target || "triggering";
const includeFooter = config.footer !== false; // Default to true
const includeFooter = parseBoolTemplatable(config.footer, true);
const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true";
const { defaultTargetRepo, allowedRepos } = resolveTargetRepoConfig(config);

Expand Down
3 changes: 2 additions & 1 deletion actions/setup/js/update_discussion.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const { isDiscussionContext, getDiscussionNumber } = require("./update_context_h
const { createUpdateHandlerFactory, createStandardFormatResult } = require("./update_handler_factory.cjs");
const { sanitizeTitle } = require("./sanitize_title.cjs");
const { ERR_NOT_FOUND } = require("./error_codes.cjs");
const { parseBoolTemplatable } = require("./templatable.cjs");

/**
* Execute the discussion update API call using GraphQL
Expand Down Expand Up @@ -136,7 +137,7 @@ function buildDiscussionUpdateData(item, config) {
}

// Pass footer config to executeUpdate (default to true)
updateData._includeFooter = config.footer !== false;
updateData._includeFooter = parseBoolTemplatable(config.footer, true);

return { success: true, data: updateData };
}
Expand Down
3 changes: 2 additions & 1 deletion actions/setup/js/update_issue.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const { loadTemporaryProjectMap, replaceTemporaryProjectReferences } = require("
const { sanitizeTitle } = require("./sanitize_title.cjs");
const { tryEnforceArrayLimit } = require("./limit_enforcement_helpers.cjs");
const { ERR_VALIDATION } = require("./error_codes.cjs");
const { parseBoolTemplatable } = require("./templatable.cjs");

/**
* Maximum limits for issue update parameters to prevent resource exhaustion.
Expand Down Expand Up @@ -169,7 +170,7 @@ function buildIssueUpdateData(item, config) {
}

// Pass footer config to executeUpdate (default to true)
updateData._includeFooter = config.footer !== false;
updateData._includeFooter = parseBoolTemplatable(config.footer, true);

// Store title prefix for validation in executeIssueUpdate
if (config.title_prefix) {
Expand Down
3 changes: 2 additions & 1 deletion actions/setup/js/update_pull_request.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const { updateBody } = require("./update_pr_description_helpers.cjs");
const { resolveTarget } = require("./safe_output_helpers.cjs");
const { createUpdateHandlerFactory, createStandardResolveNumber, createStandardFormatResult } = require("./update_handler_factory.cjs");
const { sanitizeTitle } = require("./sanitize_title.cjs");
const { parseBoolTemplatable } = require("./templatable.cjs");

/**
* Execute the pull request update API call
Expand Down Expand Up @@ -132,7 +133,7 @@ function buildPRUpdateData(item, config) {
}

// Pass footer config to executeUpdate (default to true)
updateData._includeFooter = config.footer !== false;
updateData._includeFooter = parseBoolTemplatable(config.footer, true);

return { success: true, data: updateData };
}
Expand Down
3 changes: 2 additions & 1 deletion actions/setup/js/update_release.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const { getErrorMessage } = require("./error_helpers.cjs");
const { updateBody } = require("./update_pr_description_helpers.cjs");
const { logStagedPreviewInfo } = require("./staged_preview.cjs");
const { ERR_API, ERR_CONFIG, ERR_VALIDATION } = require("./error_codes.cjs");
const { parseBoolTemplatable } = require("./templatable.cjs");
// Content sanitization: message.body is sanitized by updateBody() helper

/**
Expand All @@ -27,7 +28,7 @@ async function main(config = {}) {
// Check if we're in staged mode
const isStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED === "true";
const workflowName = process.env.GH_AW_WORKFLOW_NAME || "GitHub Agentic Workflow";
const includeFooter = config.footer !== false; // Default to true (include footer)
const includeFooter = parseBoolTemplatable(config.footer, true);

/**
* Process a single update-release message
Expand Down
15 changes: 11 additions & 4 deletions pkg/workflow/add_comment.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ type AddCommentsConfig struct {
TargetRepoSlug string `yaml:"target-repo,omitempty"` // Target repository in format "owner/repo" for cross-repository comments
AllowedRepos []string `yaml:"allowed-repos,omitempty"` // List of additional repositories that comments can be added to (additionally to the target-repo)
Discussion *bool `yaml:"discussion,omitempty"` // Target discussion comments instead of issue/PR comments. Must be true if present.
HideOlderComments bool `yaml:"hide-older-comments,omitempty"` // When true, minimizes/hides all previous comments from the same workflow before creating the new comment
HideOlderComments *string `yaml:"hide-older-comments,omitempty"` // When true, minimizes/hides all previous comments from the same workflow before creating the new comment
AllowedReasons []string `yaml:"allowed-reasons,omitempty"` // List of allowed reasons for hiding older comments (default: all reasons allowed)
Discussions *bool `yaml:"discussions,omitempty"` // When false, excludes discussions:write permission. Default (nil or true) includes discussions:write for GitHub Apps with Discussions permission.
}
Expand Down Expand Up @@ -55,9 +55,7 @@ func (c *Compiler) buildCreateOutputAddCommentJob(data *WorkflowData, mainJobNam
customEnvVars = append(customEnvVars, " GITHUB_AW_COMMENT_DISCUSSION: \"true\"\n")
}
// Pass the hide-older-comments flag configuration
if data.SafeOutputs.AddComments.HideOlderComments {
customEnvVars = append(customEnvVars, " GH_AW_HIDE_OLDER_COMMENTS: \"true\"\n")
}
customEnvVars = append(customEnvVars, buildTemplatableBoolEnvVar("GH_AW_HIDE_OLDER_COMMENTS", data.SafeOutputs.AddComments.HideOlderComments)...)
// Pass the allowed-reasons list configuration
if len(data.SafeOutputs.AddComments.AllowedReasons) > 0 {
reasonsJSON, err := json.Marshal(data.SafeOutputs.AddComments.AllowedReasons)
Expand Down Expand Up @@ -151,6 +149,15 @@ func (c *Compiler) parseCommentsConfig(outputMap map[string]any) *AddCommentsCon

addCommentLog.Print("Parsing add-comment configuration")

// Get config data for pre-processing before YAML unmarshaling
configData, _ := outputMap["add-comment"].(map[string]any)

// Pre-process templatable bool fields
if err := preprocessBoolFieldAsString(configData, "hide-older-comments", addCommentLog); err != nil {
addCommentLog.Printf("Invalid hide-older-comments value: %v", err)
return nil
}

// Unmarshal into typed config struct
var config AddCommentsConfig
if err := unmarshalConfig(outputMap, "add-comment", &config, addCommentLog); err != nil {
Expand Down
24 changes: 16 additions & 8 deletions pkg/workflow/add_comment_target_repo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ func TestAddCommentsConfigHideOlderComments(t *testing.T) {
tests := []struct {
name string
configMap map[string]any
expectedHideOlderComments bool
expectedHideOlderComments *string
}{
{
name: "hide-older-comments enabled",
Expand All @@ -108,7 +108,7 @@ func TestAddCommentsConfigHideOlderComments(t *testing.T) {
"hide-older-comments": true,
},
},
expectedHideOlderComments: true,
expectedHideOlderComments: testStringPtr("true"),
},
{
name: "hide-older-comments disabled",
Expand All @@ -118,16 +118,16 @@ func TestAddCommentsConfigHideOlderComments(t *testing.T) {
"hide-older-comments": false,
},
},
expectedHideOlderComments: false,
expectedHideOlderComments: testStringPtr("false"),
},
{
name: "hide-older-comments not specified (default false)",
name: "hide-older-comments not specified (default nil)",
configMap: map[string]any{
"add-comment": map[string]any{
"max": 1,
},
},
expectedHideOlderComments: false,
expectedHideOlderComments: nil,
},
{
name: "hide-older-comments with other fields",
Expand All @@ -139,7 +139,7 @@ func TestAddCommentsConfigHideOlderComments(t *testing.T) {
"hide-older-comments": true,
},
},
expectedHideOlderComments: true,
expectedHideOlderComments: testStringPtr("true"),
},
}

Expand All @@ -151,8 +151,16 @@ func TestAddCommentsConfigHideOlderComments(t *testing.T) {
t.Fatal("Expected valid config, but got nil")
}

if config.HideOlderComments != tt.expectedHideOlderComments {
t.Errorf("Expected HideOlderComments = %v, got %v", tt.expectedHideOlderComments, config.HideOlderComments)
if tt.expectedHideOlderComments == nil {
if config.HideOlderComments != nil {
t.Errorf("Expected HideOlderComments = nil, got %v", *config.HideOlderComments)
}
} else {
if config.HideOlderComments == nil {
t.Errorf("Expected HideOlderComments = %v, got nil", *tt.expectedHideOlderComments)
} else if *config.HideOlderComments != *tt.expectedHideOlderComments {
t.Errorf("Expected HideOlderComments = %v, got %v", *tt.expectedHideOlderComments, *config.HideOlderComments)
}
}
})
}
Expand Down
11 changes: 10 additions & 1 deletion pkg/workflow/assign_to_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ type AssignToUserConfig struct {
SafeOutputTargetConfig `yaml:",inline"`
Allowed []string `yaml:"allowed,omitempty"` // Optional list of allowed usernames. If omitted, any users are allowed.
Blocked []string `yaml:"blocked,omitempty"` // Optional list of blocked usernames or patterns (e.g., "copilot", "*[bot]")
UnassignFirst bool `yaml:"unassign-first,omitempty"` // If true, unassign all current assignees before assigning new ones
UnassignFirst *string `yaml:"unassign-first,omitempty"` // If true, unassign all current assignees before assigning new ones
}

// parseAssignToUserConfig handles assign-to-user configuration
Expand All @@ -24,6 +24,15 @@ func (c *Compiler) parseAssignToUserConfig(outputMap map[string]any) *AssignToUs

assignToUserLog.Print("Parsing assign-to-user configuration")

// Get config data for pre-processing before YAML unmarshaling
configData, _ := outputMap["assign-to-user"].(map[string]any)

// Pre-process templatable bool fields
if err := preprocessBoolFieldAsString(configData, "unassign-first", assignToUserLog); err != nil {
assignToUserLog.Printf("Invalid unassign-first value: %v", err)
return nil
}

// Unmarshal into typed config struct
var config AssignToUserConfig
if err := unmarshalConfig(outputMap, "assign-to-user", &config, assignToUserLog); err != nil {
Expand Down
2 changes: 1 addition & 1 deletion pkg/workflow/compile_outputs_pr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -649,7 +649,7 @@ This test verifies that auto-merge configuration is properly handled.
}

// Verify auto-merge is set to true
if !workflowData.SafeOutputs.CreatePullRequests.AutoMerge {
if workflowData.SafeOutputs.CreatePullRequests.AutoMerge == nil || *workflowData.SafeOutputs.CreatePullRequests.AutoMerge != "true" {
t.Error("Expected auto-merge to be true")
}

Expand Down
Loading
Loading