-
Notifications
You must be signed in to change notification settings - Fork 307
Fix: actions-lock.json created relative to CWD instead of repository root #14727
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
39748ac
621afdd
58ef6cf
f463697
69c18a3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -801,3 +801,95 @@ This workflow tests that invalid schedule strings in array format fail compilati | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| t.Logf("Integration test passed - invalid schedule in array format correctly failed compilation\nOutput: %s", outputStr) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // TestCompileFromSubdirectoryCreatesActionsLockAtRoot tests that actions-lock.json | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // is created at the repository root when compiling from a subdirectory | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func TestCompileFromSubdirectoryCreatesActionsLockAtRoot(t *testing.T) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setup := setupIntegrationTest(t) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| defer setup.cleanup() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Initialize git repository (required for git root detection) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| gitInitCmd := exec.Command("git", "init") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| gitInitCmd.Dir = setup.tempDir | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if output, err := gitInitCmd.CombinedOutput(); err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| t.Fatalf("Failed to initialize git repository: %v\nOutput: %s", err, string(output)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Configure git user for the repository | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| gitConfigEmail := exec.Command("git", "config", "user.email", "test@test.com") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| gitConfigEmail.Dir = setup.tempDir | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if output, err := gitConfigEmail.CombinedOutput(); err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| t.Fatalf("Failed to configure git user email: %v\nOutput: %s", err, string(output)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| gitConfigName := exec.Command("git", "config", "user.name", "Test User") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| gitConfigName.Dir = setup.tempDir | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if output, err := gitConfigName.CombinedOutput(); err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| t.Fatalf("Failed to configure git user name: %v\nOutput: %s", err, string(output)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Create a simple test workflow | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Note: actions-lock.json is only created when actions need to be pinned, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // so it may or may not exist. The test verifies it's NOT created in the wrong location. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| testWorkflow := `--- | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: Test Workflow | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| on: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| workflow_dispatch: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| permissions: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| contents: read | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| engine: copilot | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| --- | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Test Workflow | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Test workflow to verify actions-lock.json path handling when compiling from subdirectory. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| testWorkflowPath := filepath.Join(setup.workflowsDir, "test-action.md") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err := os.WriteFile(testWorkflowPath, []byte(testWorkflow), 0644); err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| t.Fatalf("Failed to write test workflow file: %v", err) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Change to the .github/workflows subdirectory | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err := os.Chdir(setup.workflowsDir); err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| t.Fatalf("Failed to change to workflows subdirectory: %v", err) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Run the compile command from the subdirectory using a relative path | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cmd := exec.Command(setup.binaryPath, "compile", "test-action.md") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| output, err := cmd.CombinedOutput() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| t.Fatalf("CLI compile command failed: %v\nOutput: %s", err, string(output)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Change back to the temp directory root | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err := os.Chdir(setup.tempDir); err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| t.Fatalf("Failed to change back to temp directory: %v", err) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Verify actions-lock.json is created at the repository root (.github/aw/actions-lock.json) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // NOT at .github/workflows/.github/aw/actions-lock.json | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expectedLockPath := filepath.Join(setup.tempDir, ".github", "aw", "actions-lock.json") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| wrongLockPath := filepath.Join(setup.workflowsDir, ".github", "aw", "actions-lock.json") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+853
to
+873
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Change to the .github/workflows subdirectory | |
| if err := os.Chdir(setup.workflowsDir); err != nil { | |
| t.Fatalf("Failed to change to workflows subdirectory: %v", err) | |
| } | |
| // Run the compile command from the subdirectory using a relative path | |
| cmd := exec.Command(setup.binaryPath, "compile", "test-action.md") | |
| output, err := cmd.CombinedOutput() | |
| if err != nil { | |
| t.Fatalf("CLI compile command failed: %v\nOutput: %s", err, string(output)) | |
| } | |
| // Change back to the temp directory root | |
| if err := os.Chdir(setup.tempDir); err != nil { | |
| t.Fatalf("Failed to change back to temp directory: %v", err) | |
| } | |
| // Verify actions-lock.json is created at the repository root (.github/aw/actions-lock.json) | |
| // NOT at .github/workflows/.github/aw/actions-lock.json | |
| expectedLockPath := filepath.Join(setup.tempDir, ".github", "aw", "actions-lock.json") | |
| wrongLockPath := filepath.Join(setup.workflowsDir, ".github", "aw", "actions-lock.json") | |
| // Run the compile command as if from the .github/workflows subdirectory using a relative path | |
| cmd := exec.Command(setup.binaryPath, "compile", "test-action.md") | |
| cmd.Dir = setup.workflowsDir | |
| output, err := cmd.CombinedOutput() | |
| if err != nil { | |
| t.Fatalf("CLI compile command failed: %v\nOutput: %s", err, string(output)) | |
| } | |
| // Verify actions-lock.json is created at the repository root (.github/aw/actions-lock.json) | |
| // NOT at .github/workflows/.github/aw/actions-lock.json | |
| expectedLockPath := filepath.Join(setup.tempDir, ".github", "aw", "actions-lock.json") | |
| wrongLockPath := filepath.Join(setup.workflowsDir, ".github", "aw", "actions-lock.json") | |
| // NOT at .github/workflows/.github/aw/actions-lock.json | |
| expectedLockPath := filepath.Join(setup.tempDir, ".github", "aw", "actions-lock.json") | |
| wrongLockPath := filepath.Join(setup.workflowsDir, ".github", "aw", "actions-lock.json") |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -72,6 +72,11 @@ func WithRepositorySlug(slug string) CompilerOption { | |
| return func(c *Compiler) { c.repositorySlug = slug } | ||
| } | ||
|
|
||
| // WithGitRoot sets the git repository root directory for action cache path | ||
| func WithGitRoot(gitRoot string) CompilerOption { | ||
| return func(c *Compiler) { c.gitRoot = gitRoot } | ||
| } | ||
|
|
||
| // FileTracker interface for tracking files created during compilation | ||
| type FileTracker interface { | ||
| TrackCreated(filePath string) | ||
|
|
@@ -125,6 +130,7 @@ type Compiler struct { | |
| repositorySlug string // Repository slug (owner/repo) used as seed for scattering | ||
| artifactManager *ArtifactManager // Tracks artifact uploads/downloads for validation | ||
| scheduleFriendlyFormats map[int]string // Maps schedule item index to friendly format string for current workflow | ||
| gitRoot string // Git repository root directory (if set, used for action cache path) | ||
| } | ||
|
|
||
| // NewCompiler creates a new workflow compiler with functional options. | ||
|
|
@@ -134,6 +140,10 @@ func NewCompiler(opts ...CompilerOption) *Compiler { | |
| // Get default version | ||
| version := defaultVersion | ||
|
|
||
| // Auto-detect git repository root for action cache path resolution | ||
| // This ensures actions-lock.json is created at repo root regardless of CWD | ||
| gitRoot := findGitRoot() | ||
|
|
||
|
Comment on lines
+143
to
+146
|
||
| // Create compiler with defaults | ||
| c := &Compiler{ | ||
| verbose: false, | ||
|
|
@@ -146,6 +156,7 @@ func NewCompiler(opts ...CompilerOption) *Compiler { | |
| stepOrderTracker: NewStepOrderTracker(), | ||
| artifactManager: NewArtifactManager(), | ||
| actionPinWarnings: make(map[string]bool), // Initialize warning cache | ||
| gitRoot: gitRoot, // Auto-detected git root | ||
| } | ||
|
|
||
| // Apply functional options | ||
|
|
@@ -283,11 +294,16 @@ func (c *Compiler) GetScheduleWarnings() []string { | |
| func (c *Compiler) getSharedActionResolver() (*ActionCache, *ActionResolver) { | ||
| if c.actionCache == nil { | ||
| // Initialize cache and resolver on first use | ||
| cwd, err := os.Getwd() | ||
| if err != nil { | ||
| cwd = "." | ||
| // Use git root if provided, otherwise fall back to current working directory | ||
| baseDir := c.gitRoot | ||
| if baseDir == "" { | ||
| cwd, err := os.Getwd() | ||
| if err != nil { | ||
| cwd = "." | ||
| } | ||
| baseDir = cwd | ||
| } | ||
| c.actionCache = NewActionCache(cwd) | ||
| c.actionCache = NewActionCache(baseDir) | ||
|
|
||
| // Load existing cache unless force refresh is enabled | ||
| if !c.forceRefreshActionPins { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -39,6 +39,22 @@ import ( | |
|
|
||
| var gitHelpersLog = logger.New("workflow:git_helpers") | ||
|
|
||
| // findGitRoot attempts to find the git repository root directory. | ||
| // Returns empty string if not in a git repository or if git command fails. | ||
| // This function is safe to call from any context and won't cause errors if git is not available. | ||
| func findGitRoot() string { | ||
| gitHelpersLog.Print("Attempting to find git root directory") | ||
| cmd := exec.Command("git", "rev-parse", "--show-toplevel") | ||
| output, err := cmd.Output() | ||
| if err != nil { | ||
| gitHelpersLog.Printf("Could not find git root (not a git repo or git not available): %v", err) | ||
| return "" | ||
| } | ||
| gitRoot := strings.TrimSpace(string(output)) | ||
| gitHelpersLog.Printf("Found git root: %s", gitRoot) | ||
| return gitRoot | ||
|
Comment on lines
+42
to
+55
|
||
| } | ||
|
|
||
| // GetCurrentGitTag returns the current git tag if available. | ||
| // Returns empty string if not on a tag. | ||
| func GetCurrentGitTag() string { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test is non-deterministic: it explicitly notes that actions-lock.json “may or may not exist”, and then only asserts the wrong path doesn’t exist. If the compile run doesn’t ever write the action cache, the test will pass even if the original bug regresses. Make the test force creation/update of actions-lock.json (e.g., ensure a code path that marks the ActionCache dirty and saves it) so it fails when the cache is written relative to CWD.