processIncludesWithWorkflowSpec processes @include directives in content and replaces local file references with workflowspec format (owner/repo/path@sha) for all includes found in the package. If localWorkflowDir is non-empty, any relative import path whose file exists under that directory is left
(content string, workflow *WorkflowSpec, commitSHA, packagePath, localWorkflowDir string, verbose bool)
| 151 | // If localWorkflowDir is non-empty, any relative import path whose file exists under that directory is |
| 152 | // left as a local relative path rather than being rewritten to a cross-repo reference. |
| 153 | func processIncludesWithWorkflowSpec(content string, workflow *WorkflowSpec, commitSHA, packagePath, localWorkflowDir string, verbose bool) (string, error) { |
| 154 | importsLog.Printf("Processing @include directives: repo=%s, sha=%s, package=%s, localWorkflowDir=%s", workflow.RepoSlug, commitSHA, packagePath, localWorkflowDir) |
| 155 | if verbose { |
| 156 | fmt.Fprintln(os.Stderr, console.FormatVerboseMessage("Processing @include directives to replace with workflowspec")) |
| 157 | } |
| 158 | |
| 159 | // Track visited includes to prevent cycles |
| 160 | visited := make(map[string]struct { |
| 161 | }) |
| 162 | |
| 163 | // Use a queue to process files iteratively instead of recursion |
| 164 | queue := []string{} |
| 165 | |
| 166 | // Process the main content first |
| 167 | scanner := bufio.NewScanner(strings.NewReader(content)) |
| 168 | var result strings.Builder |
| 169 | |
| 170 | for scanner.Scan() { |
| 171 | line := scanner.Text() |
| 172 | |
| 173 | // Parse import directive using the helper function that handles both syntaxes |
| 174 | directive := parser.ParseImportDirective(line) |
| 175 | if directive != nil { |
| 176 | isOptional := directive.IsOptional |
| 177 | includePath := directive.Path |
| 178 | |
| 179 | // Skip if it's already a workflowspec (owner/repo/path@sha format) |
| 180 | if isWorkflowSpecFormat(includePath) { |
| 181 | result.WriteString(line + "\n") |
| 182 | continue |
| 183 | } |
| 184 | |
| 185 | // Handle section references (file.md#Section) |
| 186 | filePath, sectionName := splitImportPath(includePath) |
| 187 | |
| 188 | // Skip if filePath is empty (e.g., section-only reference like "#Section") |
| 189 | if filePath == "" { |
| 190 | if verbose { |
| 191 | fmt.Fprintln(os.Stderr, console.FormatWarningMessage("Skipping include with empty file path: "+line)) |
| 192 | } |
| 193 | result.WriteString(line + "\n") |
| 194 | continue |
| 195 | } |
| 196 | |
| 197 | // Preserve relative {{#import}} paths whose files exist in the local workflow directory. |
| 198 | if localWorkflowDir != "" && !strings.HasPrefix(filePath, "/") { |
| 199 | if isLocalFileForUpdate(localWorkflowDir, filePath) { |
| 200 | importsLog.Printf("Include path exists locally, preserving: %s", filePath) |
| 201 | result.WriteString(line + "\n") |
| 202 | // Add file to queue for processing nested includes (first visit only) |
| 203 | if !setutil.Contains(visited, filePath) { |
| 204 | visited[filePath] = struct { |
| 205 | }{} |
| 206 | queue = append(queue, filePath) |
| 207 | } |
| 208 | continue |
| 209 | } |
| 210 | } |