parseGitHubURL attempts to parse a GitHub URL and extract workflow specification components Supports URLs like: - https://github.com/owner/repo/blob/branch/path/to/workflow.md - https://github.com/owner/repo/blob/main/workflows/workflow.md - https://github.com/owner/repo/tree/branch/path/to/workflow
(spec string)
| 177 | // - https://raw.githubusercontent.com/owner/repo/refs/tags/tag/path/to/workflow.md |
| 178 | // - https://myorg.ghe.com/owner/repo/blob/branch/path/to/workflow.md (GHE) |
| 179 | func parseGitHubURL(spec string) (*WorkflowSpec, error) { |
| 180 | specLog.Printf("Parsing GitHub URL: %s", spec) |
| 181 | parsedURL, err := url.Parse(spec) |
| 182 | if err != nil { |
| 183 | specLog.Printf("Failed to parse URL: %v", err) |
| 184 | return nil, fmt.Errorf("invalid URL: %w", err) |
| 185 | } |
| 186 | |
| 187 | if parsedURL.Host == "" { |
| 188 | return nil, fmt.Errorf("URL must include a host: %s", spec) |
| 189 | } |
| 190 | |
| 191 | if !isGitHubHost(parsedURL.Host) { |
| 192 | return nil, fmt.Errorf("URL must be from github.com or a GitHub Enterprise host (*.ghe.com), got %q", parsedURL.Host) |
| 193 | } |
| 194 | |
| 195 | owner, repo, ref, filePath, err := parser.ParseRepoFileURL(spec) |
| 196 | if err != nil { |
| 197 | specLog.Printf("Failed to parse repo file URL: %v", err) |
| 198 | return nil, err |
| 199 | } |
| 200 | |
| 201 | specLog.Printf("Parsed GitHub URL: owner=%s, repo=%s, ref=%s, path=%s, host=%s", owner, repo, ref, filePath, parsedURL.Host) |
| 202 | |
| 203 | // Ensure the file path ends with .md |
| 204 | if !strings.HasSuffix(filePath, ".md") { |
| 205 | return nil, errors.New("GitHub URL must point to a .md file") |
| 206 | } |
| 207 | |
| 208 | // Validate owner and repo |
| 209 | if !parser.IsValidGitHubIdentifier(owner) || !parser.IsValidGitHubRepositoryName(repo) { |
| 210 | return nil, fmt.Errorf("invalid GitHub URL: '%s/%s' does not look like a valid GitHub repository", owner, repo) |
| 211 | } |
| 212 | |
| 213 | // For raw.githubusercontent.com content, the API host is github.com. |
| 214 | // For all other hosts (github.com, GHE), use the URL's host as-is. |
| 215 | host := parsedURL.Host |
| 216 | if host == "raw.githubusercontent.com" { |
| 217 | host = "github.com" |
| 218 | } |
| 219 | |
| 220 | return &WorkflowSpec{ |
| 221 | RepoSpec: RepoSpec{ |
| 222 | RepoSlug: fmt.Sprintf("%s/%s", owner, repo), |
| 223 | Version: ref, |
| 224 | }, |
| 225 | WorkflowPath: filePath, |
| 226 | WorkflowName: normalizeWorkflowID(filePath), |
| 227 | Host: host, |
| 228 | }, nil |
| 229 | } |
| 230 | |
| 231 | // parseWorkflowSpec parses a workflow specification in the new format |
| 232 | // Format: owner/repo/workflows/workflow-name[@version] or owner/repo/workflow-name[@version] |