fetchWorkflowRuns retrieves all completed workflow runs within a date range. since is the oldest bound (inclusive); until is the newest bound (zero means open-ended). Implements proper pagination to fix the 100-run limit bug.
(ctx context.Context, repo string, workflowID int64, branch string, since, until time.Time)
| 17 | // since is the oldest bound (inclusive); until is the newest bound (zero means open-ended). |
| 18 | // Implements proper pagination to fix the 100-run limit bug. |
| 19 | func fetchWorkflowRuns(ctx context.Context, repo string, workflowID int64, branch string, since, until time.Time) ([]WorkflowRun, error) { |
| 20 | var allRuns []WorkflowRun |
| 21 | |
| 22 | createdFilter := ">=" + since.Format("2006-01-02") |
| 23 | if !until.IsZero() { |
| 24 | createdFilter = since.Format("2006-01-02") + ".." + until.Format("2006-01-02") |
| 25 | } |
| 26 | fmt.Printf("Fetching workflow runs created %s...\n", createdFilter) |
| 27 | |
| 28 | page := 1 |
| 29 | for { |
| 30 | ctxTimeout, cancel := context.WithTimeout(ctx, 30*time.Second) |
| 31 | |
| 32 | cmd := exec.CommandContext(ctxTimeout, "gh", "api", |
| 33 | fmt.Sprintf("/repos/%s/actions/workflows/%d/runs?branch=%s&created=%s&per_page=100&page=%d", |
| 34 | repo, workflowID, branch, createdFilter, page), |
| 35 | ) |
| 36 | |
| 37 | output, err := cmd.Output() |
| 38 | cancel() // Cancel context immediately after command completes |
| 39 | |
| 40 | if err != nil { |
| 41 | if exitErr, ok := err.(*exec.ExitError); ok { |
| 42 | return nil, fmt.Errorf("gh api failed (page %d): %w\nstderr: %s", page, err, string(exitErr.Stderr)) |
| 43 | } |
| 44 | return nil, fmt.Errorf("failed to execute gh command (page %d): %w", page, err) |
| 45 | } |
| 46 | |
| 47 | var response struct { |
| 48 | WorkflowRuns []WorkflowRun `json:"workflow_runs"` |
| 49 | } |
| 50 | |
| 51 | if err := json.Unmarshal(output, &response); err != nil { |
| 52 | return nil, fmt.Errorf("failed to parse workflow runs response (page %d): %w", page, err) |
| 53 | } |
| 54 | |
| 55 | if len(response.WorkflowRuns) == 0 { |
| 56 | break |
| 57 | } |
| 58 | |
| 59 | allRuns = append(allRuns, response.WorkflowRuns...) |
| 60 | fmt.Printf("Fetched page %d: %d runs (total: %d)\n", page, len(response.WorkflowRuns), len(allRuns)) |
| 61 | |
| 62 | // If we got fewer than 100 results, this is the last page |
| 63 | if len(response.WorkflowRuns) < 100 { |
| 64 | break |
| 65 | } |
| 66 | |
| 67 | page++ |
| 68 | } |
| 69 | |
| 70 | fmt.Printf("Total workflow runs fetched: %d\n", len(allRuns)) |
| 71 | return allRuns, nil |
| 72 | } |
| 73 | |
| 74 | // fetchRunArtifacts retrieves all artifacts for a specific workflow run |
| 75 | func fetchRunArtifacts(ctx context.Context, repo string, runID int64) ([]WorkflowArtifact, error) { |
no test coverage detected