fetchAndSaveDispatchWorkflowsFromParsedFile parses a locally-saved workflow file to obtain the fully merged safe-outputs configuration (including dispatch workflows that originate from imported shared workflows), then fetches any referenced dispatch workflow files that don't already exist locally.
(destFile string, spec *WorkflowSpec, targetDir string, verbose bool, force bool, tracker *FileTracker)
| 269 | // Parse failures are logged at debug level so they can be investigated when needed. |
| 270 | // Source conflicts are reported as warnings (not errors) because the main file is already written. |
| 271 | func fetchAndSaveDispatchWorkflowsFromParsedFile(destFile string, spec *WorkflowSpec, targetDir string, verbose bool, force bool, tracker *FileTracker) { |
| 272 | remoteWorkflowLog.Printf("Fetching import-derived dispatch workflows from parsed file: %s, repo=%s", destFile, spec.RepoSlug) |
| 273 | if spec.RepoSlug == "" { |
| 274 | return |
| 275 | } |
| 276 | |
| 277 | parts := strings.SplitN(spec.RepoSlug, "/", 2) |
| 278 | if len(parts) != 2 { |
| 279 | return |
| 280 | } |
| 281 | owner, repo := parts[0], parts[1] |
| 282 | ref := spec.Version |
| 283 | if ref == "" { |
| 284 | ref = "main" |
| 285 | } |
| 286 | |
| 287 | // Parse the locally-saved workflow to get the full merged safe-outputs config. |
| 288 | compiler := workflow.NewCompiler() |
| 289 | data, err := compiler.ParseWorkflowFile(destFile) |
| 290 | if err != nil { |
| 291 | remoteWorkflowLog.Printf("Failed to parse workflow file %s for import-derived dispatch workflows: %v", destFile, err) |
| 292 | return |
| 293 | } |
| 294 | if data == nil || data.SafeOutputs == nil || data.SafeOutputs.DispatchWorkflow == nil { |
| 295 | return |
| 296 | } |
| 297 | |
| 298 | workflowNames := data.SafeOutputs.DispatchWorkflow.Workflows |
| 299 | if len(workflowNames) == 0 { |
| 300 | return |
| 301 | } |
| 302 | |
| 303 | // Filter out GitHub Actions expression syntax |
| 304 | filtered := make([]string, 0, len(workflowNames)) |
| 305 | for _, name := range workflowNames { |
| 306 | if !strings.Contains(name, "${{") { |
| 307 | filtered = append(filtered, name) |
| 308 | } |
| 309 | } |
| 310 | if len(filtered) == 0 { |
| 311 | return |
| 312 | } |
| 313 | |
| 314 | remoteWorkflowLog.Printf("Processing %d import-derived dispatch workflow(s) (filtered from %d)", len(filtered), len(workflowNames)) |
| 315 | |
| 316 | workflowBaseDir := getParentDir(spec.WorkflowPath) |
| 317 | |
| 318 | absTargetDir, absErr := filepath.Abs(targetDir) |
| 319 | if absErr != nil { |
| 320 | remoteWorkflowLog.Printf("Failed to resolve absolute path for target directory %s: %v", targetDir, absErr) |
| 321 | return |
| 322 | } |
| 323 | |
| 324 | for _, workflowName := range filtered { |
| 325 | // Early rejection of path traversal patterns (authoritative check is filepath.Rel below). |
| 326 | if strings.Contains(workflowName, "..") { |
| 327 | if verbose { |
| 328 | fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Skipping dispatch workflow with unsafe name: %q", workflowName))) |