parseRemoteOrigin extracts the remote origin (owner, repo, ref, basePath) from a workflowspec path. Returns nil if the path is not a valid workflowspec. Format: owner/repo/path[@ref] where ref defaults to "main" if not specified. BasePath is derived from the parent workflowspec path and used for res
(spec string)
| 39 | // For example, "elastic/ai-github-actions/gh-agent-workflows/gh-aw-workflows/file.md@main" |
| 40 | // produces BasePath="gh-agent-workflows" so nested imports resolve relative to that directory. |
| 41 | func parseRemoteOrigin(spec string) *remoteImportOrigin { |
| 42 | importRemoteLog.Printf("Parsing remote import origin from spec: %q", spec) |
| 43 | // Remove section reference if present |
| 44 | cleanSpec := spec |
| 45 | if before, _, ok := strings.Cut(spec, "#"); ok { |
| 46 | cleanSpec = before |
| 47 | } |
| 48 | |
| 49 | // Split on @ to get path and ref |
| 50 | parts := strings.SplitN(cleanSpec, "@", 2) |
| 51 | pathPart := parts[0] |
| 52 | ref := "main" |
| 53 | if len(parts) == 2 { |
| 54 | ref = parts[1] |
| 55 | } |
| 56 | |
| 57 | // Parse path: owner/repo/path/to/file.md |
| 58 | slashParts := strings.Split(pathPart, "/") |
| 59 | if len(slashParts) < 3 { |
| 60 | importRemoteLog.Printf("Spec %q has fewer than 3 path components; not a valid workflowspec", spec) |
| 61 | return nil |
| 62 | } |
| 63 | |
| 64 | // Derive BasePath: everything between owner/repo and the last component (filename) |
| 65 | // Since imports are always 2-level (dir/file.md), the base is everything before the filename |
| 66 | // Examples: |
| 67 | // - "owner/repo/.github/workflows/file.md" -> BasePath = ".github/workflows" |
| 68 | // - "owner/repo/gh-agent-workflows/gh-aw-workflows/file.md" -> BasePath = "gh-agent-workflows/gh-aw-workflows" |
| 69 | // - "owner/repo/a/b/c/d/file.md" -> BasePath = "a/b/c/d" |
| 70 | var basePath string |
| 71 | repoRelativeParts := slashParts[2:] // Everything after owner/repo |
| 72 | if len(repoRelativeParts) >= 2 { |
| 73 | // Take everything except the last component (the file itself) |
| 74 | // For nested imports, we want the directory containing the file |
| 75 | baseDirParts := repoRelativeParts[:len(repoRelativeParts)-1] |
| 76 | if len(baseDirParts) > 0 { |
| 77 | // Clean the path to normalize it (remove ./ and resolve ..) |
| 78 | basePath = path.Clean(strings.Join(baseDirParts, "/")) |
| 79 | importRemoteLog.Printf("Derived BasePath=%q from spec=%q (owner=%s, repo=%s, ref=%s)", |
| 80 | basePath, spec, slashParts[0], slashParts[1], ref) |
| 81 | } |
| 82 | } else { |
| 83 | importRemoteLog.Printf("No BasePath derived from spec=%q (file at repo root)", spec) |
| 84 | } |
| 85 | |
| 86 | return &remoteImportOrigin{ |
| 87 | Owner: slashParts[0], |
| 88 | Repo: slashParts[1], |
| 89 | Ref: ref, |
| 90 | BasePath: basePath, |
| 91 | } |
| 92 | } |