diff --git a/.github/workflows/smoke-crush.lock.yml b/.github/workflows/smoke-crush.lock.yml index 892a6e6afb..07b8857103 100644 --- a/.github/workflows/smoke-crush.lock.yml +++ b/.github/workflows/smoke-crush.lock.yml @@ -445,7 +445,9 @@ jobs: - name: Install AWF binary run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.29 - name: Install Crush CLI - run: npm install --ignore-scripts -g @charmland/crush@0.59.0 + run: npm install -g @charmland/crush@0.59.0 + - name: Verify Crush CLI installation + run: crush --version - name: Determine automatic lockdown mode for GitHub MCP Server id: determine-automatic-lockdown uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9 @@ -1347,7 +1349,9 @@ jobs: - name: Install AWF binary run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.29 - name: Install Crush CLI - run: npm install --ignore-scripts -g @charmland/crush@0.59.0 + run: npm install -g @charmland/crush@0.59.0 + - name: Verify Crush CLI installation + run: crush --version - name: Write Crush Config if: always() && steps.detection_guard.outputs.run_detection == 'true' continue-on-error: true diff --git a/pkg/workflow/crush_engine.go b/pkg/workflow/crush_engine.go index c72e94cb76..4491386262 100644 --- a/pkg/workflow/crush_engine.go +++ b/pkg/workflow/crush_engine.go @@ -59,13 +59,34 @@ func (e *CrushEngine) GetInstallationSteps(workflowData *WorkflowData) []GitHubA return []GitHubActionStep{} } - npmSteps := BuildStandardNpmEngineInstallSteps( + // Use version from engine config if provided, otherwise default to pinned version + version := string(constants.DefaultCrushVersion) + if workflowData.EngineConfig != nil && workflowData.EngineConfig.Version != "" { + version = workflowData.EngineConfig.Version + } + + // Crush requires post-install scripts (native binaries) so --ignore-scripts must + // NOT be passed. This is intentionally different from other engine installs. + npmSteps := GenerateNpmInstallSteps( "@charmland/crush", - string(constants.DefaultCrushVersion), + version, "Install Crush CLI", "crush", - workflowData, + true, // Include Node.js setup + true, // Crush requires post-install scripts for native binaries ) + + // Run crush --version to verify the installation and force any deferred binary downloads + commandName := "crush" + if workflowData.EngineConfig != nil && workflowData.EngineConfig.Command != "" { + commandName = workflowData.EngineConfig.Command + } + versionStep := GitHubActionStep{ + " - name: Verify Crush CLI installation", + " run: " + commandName + " --version", + } + npmSteps = append(npmSteps, versionStep) + return BuildNpmEngineInstallStepsWithAWF(npmSteps, workflowData) } diff --git a/pkg/workflow/crush_engine_test.go b/pkg/workflow/crush_engine_test.go index ef931cf2ca..dde1eab279 100644 --- a/pkg/workflow/crush_engine_test.go +++ b/pkg/workflow/crush_engine_test.go @@ -161,8 +161,32 @@ func TestCrushEngineInstallation(t *testing.T) { steps := engine.GetInstallationSteps(workflowData) require.NotEmpty(t, steps, "Should generate installation steps") - // Should have at least: Node.js setup + Install Crush - assert.GreaterOrEqual(t, len(steps), 2, "Should have at least 2 installation steps") + // Should have at least: Node.js setup + Install Crush + Verify Crush CLI installation + assert.GreaterOrEqual(t, len(steps), 3, "Should have at least 3 installation steps") + + // Find install step and verify --ignore-scripts is NOT present (Crush needs post-install scripts for native binaries) + var installStep string + for _, step := range steps { + content := strings.Join(step, "\n") + if strings.Contains(content, "@charmland/crush@") { + installStep = content + break + } + } + require.NotEmpty(t, installStep, "Should find a step installing @charmland/crush") + assert.NotContains(t, installStep, "--ignore-scripts", "Should not use --ignore-scripts for Crush (requires post-install scripts for native binaries)") + + // Find crush --version step to confirm binary download is forced + var versionStep string + for _, step := range steps { + content := strings.Join(step, "\n") + if strings.Contains(content, "crush --version") { + versionStep = content + break + } + } + require.NotEmpty(t, versionStep, "Should find crush --version step to force binary download") + assert.Contains(t, versionStep, "Verify Crush CLI installation", "Should have a descriptive step name") }) t.Run("custom command skips installation", func(t *testing.T) {