Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/patch-fix-body-import-path-resolution.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 6 additions & 3 deletions pkg/cli/add_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -416,13 +416,16 @@ func addWorkflowWithTracking(resolved *ResolvedWorkflow, tracker *FileTracker, o
// fetchAndSaveRemoteFrontmatterImports already downloaded those files locally, so
// the compiler can resolve them from disk without any GitHub API calls.

// Process @include directives and replace with workflowspec
// For local workflows, use the workflow's directory as the base path
// Process @include directives and replace with workflowspec.
// For local workflows, use the workflow's directory as the package source path.
// Pass githubWorkflowsDir as localWorkflowDir so that any body-level import
// whose target already exists locally is preserved as a local reference rather
// than being rewritten to a cross-repo workflowspec.
includeSourceDir := ""
if sourceInfo != nil && sourceInfo.IsLocal {
includeSourceDir = filepath.Dir(workflowSpec.WorkflowPath)
}
processedContent, err := processIncludesWithWorkflowSpec(content, workflowSpec, commitSHA, includeSourceDir, opts.Verbose)
processedContent, err := processIncludesWithWorkflowSpec(content, workflowSpec, commitSHA, includeSourceDir, githubWorkflowsDir, opts.Verbose)
if err != nil {
if opts.Verbose {
fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Failed to process includes: %v", err)))
Expand Down
49 changes: 33 additions & 16 deletions pkg/cli/imports.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,9 +182,11 @@ func reconstructWorkflowFileFromMap(frontmatter map[string]any, markdown string)
}

// 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
func processIncludesWithWorkflowSpec(content string, workflow *WorkflowSpec, commitSHA, packagePath string, verbose bool) (string, error) {
importsLog.Printf("Processing @include directives: repo=%s, sha=%s, package=%s", workflow.RepoSlug, commitSHA, packagePath)
// 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 as a local relative path rather than being rewritten to a cross-repo reference.
func processIncludesWithWorkflowSpec(content string, workflow *WorkflowSpec, commitSHA, packagePath, localWorkflowDir string, verbose bool) (string, error) {
importsLog.Printf("Processing @include directives: repo=%s, sha=%s, package=%s, localWorkflowDir=%s", workflow.RepoSlug, commitSHA, packagePath, localWorkflowDir)
if verbose {
fmt.Fprintln(os.Stderr, console.FormatVerboseMessage("Processing @include directives to replace with workflowspec"))
}
Expand All @@ -211,6 +213,12 @@ func processIncludesWithWorkflowSpec(content string, workflow *WorkflowSpec, com
isOptional := directive.IsOptional
includePath := directive.Path

// Skip if it's already a workflowspec (owner/repo/path@sha format)
if isWorkflowSpecFormat(includePath) {
result.WriteString(line + "\n")
continue
}

// Handle section references (file.md#Section)
var filePath, sectionName string
if strings.Contains(includePath, "#") {
Expand All @@ -230,34 +238,43 @@ func processIncludesWithWorkflowSpec(content string, workflow *WorkflowSpec, com
continue
}

// Check for cycle detection
if visited[filePath] {
if verbose {
fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Cycle detected for include: %s, skipping", filePath)))
// Preserve relative {{#import}} paths whose files exist in the local workflow directory.
if localWorkflowDir != "" && !strings.HasPrefix(filePath, "/") {
if isLocalFileForUpdate(localWorkflowDir, filePath) {
importsLog.Printf("Include path exists locally, preserving: %s", filePath)
result.WriteString(line + "\n")
// Add file to queue for processing nested includes (first visit only)
if !visited[filePath] {
visited[filePath] = true
queue = append(queue, fileToProcess{path: filePath})
}
continue
}
continue
}

// Mark as visited
visited[filePath] = true
// Resolve the file path relative to the workflow file's directory
resolvedPath := resolveImportPath(filePath, workflow.WorkflowPath)

// Build workflowspec for this include
workflowSpec := buildWorkflowSpecRef(workflow.RepoSlug, filePath, commitSHA, workflow.Version)
workflowSpec := buildWorkflowSpecRef(workflow.RepoSlug, resolvedPath, commitSHA, workflow.Version)

// Add section if present
if sectionName != "" {
workflowSpec += "#" + sectionName
}

// Write the updated @include directive
// Write the updated @include directive (even for duplicate occurrences)
if isOptional {
result.WriteString("{{#import? " + workflowSpec + "}}\n")
} else {
result.WriteString("{{#import " + workflowSpec + "}}\n")
}

// Add file to queue for processing nested includes
queue = append(queue, fileToProcess{path: filePath})
// Only enqueue for nested-include processing on the first visit to prevent cycles
if !visited[filePath] {
visited[filePath] = true
queue = append(queue, fileToProcess{path: filePath})
}
} else {
// Regular line, pass through
result.WriteString(line + "\n")
Expand Down Expand Up @@ -360,7 +377,7 @@ func processIncludesInContent(content string, workflow *WorkflowSpec, commitSHA
isOptional := directive.IsOptional
includePath := directive.Path

// Skip if it's already a workflowspec (contains repo/path format)
// Skip if it's already a workflowspec (owner/repo/path@sha format)
if isWorkflowSpecFormat(includePath) {
result.WriteString(line + "\n")
continue
Expand All @@ -385,7 +402,7 @@ func processIncludesInContent(content string, workflow *WorkflowSpec, commitSHA
continue
}

// Preserve relative @include paths whose files exist in the local workflow directory.
// Preserve relative {{#import}} paths whose files exist in the local workflow directory.
if localWorkflowDir != "" && !strings.HasPrefix(filePath, "/") {
if isLocalFileForUpdate(localWorkflowDir, filePath) {
importsLog.Printf("Include path exists locally, preserving: %s", filePath)
Expand Down
Loading
Loading