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

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

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

Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ name: "Go File Size Reduction Campaign (Project 64)"
description: "Systematically reduce oversized Go files to improve maintainability. Success: all files ≤800 LOC, maintain coverage, no regressions."

project-url: "https://github.com/orgs/githubnext/projects/64"
project-github-token: "${{ secrets.GH_AW_PROJECT_GITHUB_TOKEN }}"

workflows:
- daily-file-diet
Expand Down

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/go-file-size-reduction.campaign.g.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/go-file-size-reduction.campaign.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ name: "Go File Size Reduction Campaign"
description: "Reduce oversized non-test Go files under pkg/ to ≤800 LOC via tracked refactors, with daily metrics snapshots and a GitHub Projects dashboard."

project-url: "https://github.com/orgs/githubnext/projects/60"
project-github-token: "${{ secrets.GH_AW_PROJECT_GITHUB_TOKEN }}"

workflows:
- daily-file-diet
Expand Down
9 changes: 8 additions & 1 deletion pkg/campaign/orchestrator.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,14 @@ func BuildOrchestrator(spec *CampaignSpec, campaignFilePath string) (*workflow.W
// Always allow commenting on tracker issues (or other issues/PRs if needed).
safeOutputs.AddComments = &workflow.AddCommentsConfig{BaseSafeOutputConfig: workflow.BaseSafeOutputConfig{Max: 10}}
// Allow updating the campaign's GitHub Project dashboard.
safeOutputs.UpdateProjects = &workflow.UpdateProjectConfig{BaseSafeOutputConfig: workflow.BaseSafeOutputConfig{Max: 10}}
updateProjectConfig := &workflow.UpdateProjectConfig{BaseSafeOutputConfig: workflow.BaseSafeOutputConfig{Max: 10}}
// If the campaign spec specifies a custom GitHub token for Projects v2 operations,
// pass it to the update-project configuration.
if strings.TrimSpace(spec.ProjectGitHubToken) != "" {
updateProjectConfig.GitHubToken = strings.TrimSpace(spec.ProjectGitHubToken)
orchestratorLog.Printf("Campaign orchestrator '%s' configured with custom GitHub token for update-project", spec.ID)
}
safeOutputs.UpdateProjects = updateProjectConfig

orchestratorLog.Printf("Campaign orchestrator '%s' built successfully with safe outputs enabled", spec.ID)

Expand Down
73 changes: 73 additions & 0 deletions pkg/campaign/orchestrator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,3 +160,76 @@ func TestBuildOrchestrator_TrackerIDMonitoring(t *testing.T) {
t.Errorf("expected markdown to contain Phase 4: Report, got: %q", data.MarkdownContent)
}
}

func TestBuildOrchestrator_GitHubToken(t *testing.T) {
t.Run("with custom github token", func(t *testing.T) {
spec := &CampaignSpec{
ID: "test-campaign-with-token",
Name: "Test Campaign",
Description: "A test campaign with custom GitHub token",
ProjectURL: "https://github.com/orgs/test/projects/1",
Workflows: []string{"test-workflow"},
TrackerLabel: "campaign:test",
ProjectGitHubToken: "${{ secrets.GH_AW_PROJECT_GITHUB_TOKEN }}",
}

mdPath := ".github/workflows/test-campaign.campaign.md"
data, _ := BuildOrchestrator(spec, mdPath)

if data == nil {
t.Fatalf("expected non-nil WorkflowData")
}

// Verify that SafeOutputs is configured
if data.SafeOutputs == nil {
t.Fatalf("expected SafeOutputs to be configured")
}

// Verify that UpdateProjects is configured
if data.SafeOutputs.UpdateProjects == nil {
t.Fatalf("expected UpdateProjects to be configured")
}

// Verify that the GitHubToken is set
if data.SafeOutputs.UpdateProjects.GitHubToken != "${{ secrets.GH_AW_PROJECT_GITHUB_TOKEN }}" {
t.Errorf("expected GitHubToken to be %q, got %q",
"${{ secrets.GH_AW_PROJECT_GITHUB_TOKEN }}",
data.SafeOutputs.UpdateProjects.GitHubToken)
}
})

t.Run("without custom github token", func(t *testing.T) {
spec := &CampaignSpec{
ID: "test-campaign-no-token",
Name: "Test Campaign",
Description: "A test campaign without custom GitHub token",
ProjectURL: "https://github.com/orgs/test/projects/1",
Workflows: []string{"test-workflow"},
TrackerLabel: "campaign:test",
// ProjectGitHubToken is intentionally omitted
}

mdPath := ".github/workflows/test-campaign.campaign.md"
data, _ := BuildOrchestrator(spec, mdPath)

if data == nil {
t.Fatalf("expected non-nil WorkflowData")
}

// Verify that SafeOutputs is configured
if data.SafeOutputs == nil {
t.Fatalf("expected SafeOutputs to be configured")
}

// Verify that UpdateProjects is configured
if data.SafeOutputs.UpdateProjects == nil {
t.Fatalf("expected UpdateProjects to be configured")
}

// Verify that the GitHubToken is empty when not specified
if data.SafeOutputs.UpdateProjects.GitHubToken != "" {
t.Errorf("expected GitHubToken to be empty when not specified, got %q",
data.SafeOutputs.UpdateProjects.GitHubToken)
}
})
}
6 changes: 6 additions & 0 deletions pkg/campaign/spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ type CampaignSpec struct {
// enforced by validation in the future.
AllowedSafeOutputs []string `yaml:"allowed-safe-outputs,omitempty" json:"allowed_safe_outputs,omitempty" console:"header:Allowed Safe Outputs,omitempty,maxlen:30"`

// ProjectGitHubToken is an optional GitHub token expression (e.g.,
// ${{ secrets.GH_AW_PROJECT_GITHUB_TOKEN }}) used for GitHub Projects v2
// operations. When specified, this token is passed to the update-project
// safe output configuration in the generated orchestrator workflow.
ProjectGitHubToken string `yaml:"project-github-token,omitempty" json:"project_github_token,omitempty" console:"header:Project Token,omitempty,maxlen:30"`

// ApprovalPolicy describes high-level approval expectations for this
// campaign (for example: number of approvals and required roles).
ApprovalPolicy *CampaignApprovalPolicy `yaml:"approval-policy,omitempty" json:"approval-policy,omitempty"`
Expand Down
110 changes: 110 additions & 0 deletions pkg/cli/compile_campaign_orchestrator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -234,3 +234,113 @@ func extractSourcePath(t *testing.T, content string) string {

return strings.TrimSpace(content[startIdx : startIdx+endIdx])
}

// TestCampaignOrchestratorGitHubToken verifies that when a campaign spec includes
// a project-github-token field, it is properly serialized into the generated
// .g.campaign.md file's safe-outputs configuration
func TestCampaignOrchestratorGitHubToken(t *testing.T) {
tmpDir := t.TempDir()
campaignSpecPath := filepath.Join(tmpDir, "test-campaign-with-token.campaign.md")

// Test case 1: Campaign with custom GitHub token
t.Run("with custom token", func(t *testing.T) {
spec := &campaign.CampaignSpec{
ID: "test-campaign-with-token",
Name: "Test Campaign With Token",
Description: "A test campaign with custom GitHub token",
Workflows: []string{"example-workflow"},
TrackerLabel: "campaign:test-campaign-with-token",
MemoryPaths: []string{"memory/campaigns/test-campaign-with-token-*/**"},
ProjectGitHubToken: "${{ secrets.GH_AW_PROJECT_GITHUB_TOKEN }}",
}

compiler := workflow.NewCompiler(false, "", GetVersion())
compiler.SetSkipValidation(true)
compiler.SetNoEmit(false)
compiler.SetStrictMode(false)

orchestratorPath, err := generateAndCompileCampaignOrchestrator(
compiler,
spec,
campaignSpecPath,
false, false, false, false, false, false, false,
)
if err != nil {
t.Fatalf("generateAndCompileCampaignOrchestrator() error: %v", err)
}

// Read the generated markdown file
mdContent, err := os.ReadFile(orchestratorPath)
if err != nil {
t.Fatalf("failed to read generated markdown: %v", err)
}
mdStr := string(mdContent)

// Verify the github-token is present in the safe-outputs configuration
if !strings.Contains(mdStr, "github-token:") {
t.Errorf("expected generated markdown to contain 'github-token:' field")
}

if !strings.Contains(mdStr, "${{ secrets.GH_AW_PROJECT_GITHUB_TOKEN }}") {
t.Errorf("expected generated markdown to contain the token expression")
}

// Verify the safe-outputs structure
if !strings.Contains(mdStr, "safe-outputs:") {
t.Errorf("expected generated markdown to contain 'safe-outputs:' section")
}

if !strings.Contains(mdStr, "update-project:") {
t.Errorf("expected generated markdown to contain 'update-project:' section")
}
})

// Test case 2: Campaign without custom GitHub token
t.Run("without custom token", func(t *testing.T) {
spec := &campaign.CampaignSpec{
ID: "test-campaign-no-token",
Name: "Test Campaign Without Token",
Description: "A test campaign without custom GitHub token",
Workflows: []string{"example-workflow"},
TrackerLabel: "campaign:test-campaign-no-token",
MemoryPaths: []string{"memory/campaigns/test-campaign-no-token-*/**"},
// ProjectGitHubToken is intentionally omitted
}

compiler := workflow.NewCompiler(false, "", GetVersion())
compiler.SetSkipValidation(true)
compiler.SetNoEmit(false)
compiler.SetStrictMode(false)

orchestratorPath, err := generateAndCompileCampaignOrchestrator(
compiler,
spec,
filepath.Join(tmpDir, "test-campaign-no-token.campaign.md"),
false, false, false, false, false, false, false,
)
if err != nil {
t.Fatalf("generateAndCompileCampaignOrchestrator() error: %v", err)
}

// Read the generated markdown file
mdContent, err := os.ReadFile(orchestratorPath)
if err != nil {
t.Fatalf("failed to read generated markdown: %v", err)
}
mdStr := string(mdContent)

// Verify the github-token is NOT present when not configured
if strings.Contains(mdStr, "github-token:") {
t.Errorf("expected generated markdown to NOT contain 'github-token:' field when not configured")
}

// But safe-outputs and update-project should still be present
if !strings.Contains(mdStr, "safe-outputs:") {
t.Errorf("expected generated markdown to contain 'safe-outputs:' section")
}

if !strings.Contains(mdStr, "update-project:") {
t.Errorf("expected generated markdown to contain 'update-project:' section")
}
})
}
7 changes: 6 additions & 1 deletion pkg/cli/compile_orchestrator.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,14 @@ func renderGeneratedCampaignOrchestratorMarkdown(data *workflow.WorkflowData, so
}
}
if data.SafeOutputs.UpdateProjects != nil {
outputs["update-project"] = map[string]any{
updateProjectConfig := map[string]any{
"max": data.SafeOutputs.UpdateProjects.Max,
}
// Include github-token if specified
if strings.TrimSpace(data.SafeOutputs.UpdateProjects.GitHubToken) != "" {
updateProjectConfig["github-token"] = data.SafeOutputs.UpdateProjects.GitHubToken
}
outputs["update-project"] = updateProjectConfig
}
if len(outputs) > 0 {
payload := map[string]any{"safe-outputs": outputs}
Expand Down
Loading