Skip to content

Normalize file paths to Unix format in compiled .lock.yml files#13853

Merged
pelikhan merged 5 commits intomainfrom
copilot/fix-unix-compatible-file-paths
Feb 5, 2026
Merged

Normalize file paths to Unix format in compiled .lock.yml files#13853
pelikhan merged 5 commits intomainfrom
copilot/fix-unix-compatible-file-paths

Conversation

Copy link
Contributor

Copilot AI commented Feb 5, 2026

The compiler generates platform-specific paths in .lock.yml files. On Windows, this produces backslash separators which breaks cross-platform workflows.

Changes

Applied filepath.ToSlash() normalization at all YAML output points:

  • pkg/workflow/compiler_yaml.go: Normalize Source, ImportedFiles, IncludedFiles, and runtime-import paths before writing to YAML header and prompt
  • pkg/parser/include_expander.go: Normalize paths when building the included files manifest

Example

Before (Windows):

# Resolved workflow manifest:
#   Imports:
#     - shared\jqschema.md
#     - shared\reporting.md

# Prompt section:
{{#runtime-import workflows\test.md}}

After (all platforms):

# Resolved workflow manifest:
#   Imports:
#     - shared/jqschema.md
#     - shared/reporting.md

# Prompt section:
{{#runtime-import workflows/test.md}}

Added test coverage in compiler_path_normalization_test.go for imports, includes, source paths, and nested directory structures.

Original prompt

Review the compiler such that it always generates unix compatible file path in the compiled .lock.yml file. This means that when the compiler is running on windows is should NOT generate path with forward /.


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI and others added 3 commits February 5, 2026 05:43
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix compiler to generate unix compatible file paths in .lock.yml Normalize file paths to Unix format in compiled .lock.yml files Feb 5, 2026
Copilot AI requested a review from pelikhan February 5, 2026 05:51
@pelikhan
Copy link
Contributor

pelikhan commented Feb 5, 2026

@copilot recompile

@pelikhan
Copy link
Contributor

pelikhan commented Feb 5, 2026

@copilot make sure the paths passed in runtime-import is using ToSlash.

Copy link
Contributor Author

Copilot AI commented Feb 5, 2026

@copilot recompile

Recompilation completed successfully. All 145 workflows compiled with no changes to existing .lock.yml files. The path normalization is working as expected - on Unix systems, paths already use forward slashes, so no modifications were needed. On Windows, the new code will normalize backslashes to forward slashes.

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Feb 5, 2026

@copilot make sure the paths passed in runtime-import is using ToSlash.

Added filepath.ToSlash() normalization to the runtime-import path in compiler_yaml.go (line 303). The workflow file path is now normalized to use forward slashes before being used in the {{#runtime-import}} macro. All 145 workflows recompiled successfully. Commit: f1f9758

@pelikhan pelikhan marked this pull request as ready for review February 5, 2026 06:07
Copilot AI review requested due to automatic review settings February 5, 2026 06:07
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +18 to +177
func TestCompilerGeneratesUnixPaths(t *testing.T) {
tests := []struct {
name string
markdownContent string
expectedImportPaths []string
expectedIncludePaths []string
expectedSourcePath string
}{
{
name: "imports with forward slashes",
markdownContent: `---
on: issues
imports:
- shared/common.md
- shared/reporting.md
source: workflows/test-workflow.md
---

# Test Workflow

This is a test workflow with imports.`,
expectedImportPaths: []string{
"shared/common.md",
"shared/reporting.md",
},
expectedSourcePath: "workflows/test-workflow.md",
},
{
name: "includes with forward slashes",
markdownContent: `---
on: pull_request
---

# Test Include Workflow

{{#import shared/tools.md}}

This workflow includes external tools.`,
expectedIncludePaths: []string{
"shared/tools.md",
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Create temporary directory for test
tmpDir := t.TempDir()

// Create shared directory and files for imports/includes
sharedDir := filepath.Join(tmpDir, "shared")
err := os.MkdirAll(sharedDir, 0755)
require.NoError(t, err, "Failed to create shared directory")

// Create shared/common.md (shared workflow - minimal valid content)
commonContent := `# Common Shared Workflow

This is a shared workflow.`
commonFile := filepath.Join(sharedDir, "common.md")
err = os.WriteFile(commonFile, []byte(commonContent), 0644)
require.NoError(t, err, "Failed to create common.md")

// Create shared/reporting.md (shared workflow - minimal valid content)
reportingContent := `# Reporting Shared Workflow

This is a shared workflow.`
reportingFile := filepath.Join(sharedDir, "reporting.md")
err = os.WriteFile(reportingFile, []byte(reportingContent), 0644)
require.NoError(t, err, "Failed to create reporting.md")

// Create shared/tools.md (shared workflow - minimal valid content)
toolsContent := `# Tools Shared Workflow

This is a shared workflow.`
toolsFile := filepath.Join(sharedDir, "tools.md")
err = os.WriteFile(toolsFile, []byte(toolsContent), 0644)
require.NoError(t, err, "Failed to create tools.md")

// Create workflows directory for source path
workflowsDir := filepath.Join(tmpDir, "workflows")
err = os.MkdirAll(workflowsDir, 0755)
require.NoError(t, err, "Failed to create workflows directory")

// Write markdown file
markdownPath := filepath.Join(tmpDir, "test-workflow.md")
err = os.WriteFile(markdownPath, []byte(tt.markdownContent), 0644)
require.NoError(t, err, "Failed to write markdown file")

// Compile the workflow
compiler := NewCompiler()
err = compiler.CompileWorkflow(markdownPath)
require.NoError(t, err, "Compilation should succeed")

// Read the generated .lock.yml file
lockFile := filepath.Join(tmpDir, "test-workflow.lock.yml")
lockContent, err := os.ReadFile(lockFile)
require.NoError(t, err, "Failed to read lock file")

lockYAML := string(lockContent)

// Verify that file paths in the manifest use forward slashes (Unix-compatible)
// Note: The ASCII art header contains backslashes, so we only check the manifest section
manifestStart := strings.Index(lockYAML, "# Resolved workflow manifest:")
sourceStart := strings.Index(lockYAML, "# Source:")

// Verify expected import paths are present with forward slashes
for _, importPath := range tt.expectedImportPaths {
expectedLine := "# - " + importPath
assert.Contains(t, lockYAML, expectedLine, "Lock file should contain import path: %s", importPath)

// Ensure no backslash version exists
backslashPath := strings.ReplaceAll(importPath, "/", "\\")
backslashLine := "# - " + backslashPath
assert.NotContains(t, lockYAML, backslashLine, "Lock file should not contain backslash version of: %s", importPath)
}

// Verify expected include paths are present with forward slashes
for _, includePath := range tt.expectedIncludePaths {
expectedLine := "# - " + includePath
assert.Contains(t, lockYAML, expectedLine, "Lock file should contain include path: %s", includePath)

// Ensure no backslash version exists
backslashPath := strings.ReplaceAll(includePath, "/", "\\")
backslashLine := "# - " + backslashPath
assert.NotContains(t, lockYAML, backslashLine, "Lock file should not contain backslash version of: %s", includePath)
}

// Verify source path uses forward slashes
if tt.expectedSourcePath != "" {
expectedLine := "# Source: " + tt.expectedSourcePath
assert.Contains(t, lockYAML, expectedLine, "Lock file should contain source path: %s", tt.expectedSourcePath)

// Ensure no backslash version exists
backslashPath := strings.ReplaceAll(tt.expectedSourcePath, "/", "\\")
backslashLine := "# Source: " + backslashPath
assert.NotContains(t, lockYAML, backslashLine, "Lock file should not contain backslash version of source: %s", tt.expectedSourcePath)
}

// Verify that manifest section does not contain backslashes in file paths
if manifestStart >= 0 {
manifestEnd := strings.Index(lockYAML[manifestStart:], "\n\n")
if manifestEnd >= 0 {
manifest := lockYAML[manifestStart : manifestStart+manifestEnd]
assert.NotContains(t, manifest, "\\", "Lock file manifest should not contain backslashes in file paths")
}
}

// Verify that source section does not contain backslashes in file paths
if sourceStart >= 0 && tt.expectedSourcePath != "" {
sourceEnd := strings.Index(lockYAML[sourceStart:], "\n")
if sourceEnd >= 0 {
sourceLine := lockYAML[sourceStart : sourceStart+sourceEnd]
// Check that the source line doesn't contain a Windows path
backslashPath := strings.ReplaceAll(tt.expectedSourcePath, "/", "\\")
assert.NotContains(t, sourceLine, backslashPath, "Source line should not contain Windows-style path")
}
}
})
}
}
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test suite doesn't verify the runtime-import path normalization. Consider adding a test case that checks the generated runtime-import macro path in the lock file contains forward slashes. For example, verify that a workflow file at workflows\test.md on Windows generates {{#runtime-import workflows/test.md}} instead of {{#runtime-import workflows\test.md}}.

Copilot uses AI. Check for mistakes.
@pelikhan pelikhan merged commit c6d30be into main Feb 5, 2026
131 checks passed
@pelikhan pelikhan deleted the copilot/fix-unix-compatible-file-paths branch February 5, 2026 06:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants