diff --git a/.aiox-core/core/orchestration/workflow-executor.js b/.aiox-core/core/orchestration/workflow-executor.js index 9c2558cdf8..8ae22e2bb4 100644 --- a/.aiox-core/core/orchestration/workflow-executor.js +++ b/.aiox-core/core/orchestration/workflow-executor.js @@ -775,9 +775,9 @@ class WorkflowExecutor { */ async runCodeRabbitAnalysis(coderabbitConfig) { try { - const { exec } = require('child_process'); + const childProcess = require('child_process'); const { promisify } = require('util'); - const execAsync = promisify(exec); + const execAsync = promisify(childProcess.exec); // Build command for current platform. // - Explicit installation_mode: 'wsl' | 'native' wins (lets ops override). @@ -793,6 +793,20 @@ class WorkflowExecutor { (process.platform === 'win32' ? 'wsl' : 'native'); let command; if (mode === 'wsl') { + // Probe WSL availability before building the command. Gives a clearer + // diagnostic than cmd.exe's generic "'wsl' is not recognized" when WSL + // is not installed. ENOENT (binary missing) and non-zero exit (WSL + // feature present but no distribution installed) both fail this check. + const wslProbe = childProcess.spawnSync('wsl', ['-l'], { encoding: 'utf8' }); + if (wslProbe.error || wslProbe.status !== 0) { + throw new Error( + 'CodeRabbit CLI requires WSL on Windows hosts. Install WSL via ' + + '`wsl --install` (https://learn.microsoft.com/windows/wsl/install), ' + + 'then install the CodeRabbit CLI inside the WSL distribution. ' + + 'See docs/guides/installation-troubleshooting.md Issue 10. ' + + `To bypass this check, set coderabbit.installation_mode='native' in your config.` + ); + } const wslPath = this.projectRoot .replace(/^([A-Za-z]):/, (_, drive) => `/mnt/${drive.toLowerCase()}`) .replace(/\\/g, '/'); diff --git a/.aiox-core/core/quality-gates/layer2-pr-automation.js b/.aiox-core/core/quality-gates/layer2-pr-automation.js index 5350adeb96..65e477757f 100644 --- a/.aiox-core/core/quality-gates/layer2-pr-automation.js +++ b/.aiox-core/core/quality-gates/layer2-pr-automation.js @@ -10,7 +10,7 @@ * @story 2.10 - Quality Gate Manager */ -const { spawn } = require('child_process'); +const childProcess = require('child_process'); const fs = require('fs').promises; const os = require('os'); const path = require('path'); @@ -117,6 +117,21 @@ class Layer2PRAutomation extends BaseLayer { this.coderabbit.installation_mode || (process.platform === 'win32' ? 'wsl' : 'native'); if (mode === 'wsl') { + // Probe WSL availability before building the command. Gives a clearer + // diagnostic than cmd.exe's generic "'wsl' is not recognized" when + // WSL is not installed. ENOENT (binary missing) and non-zero exit + // (WSL feature present but no distribution installed) both fail this + // check. + const wslProbe = childProcess.spawnSync('wsl', ['-l'], { encoding: 'utf8' }); + if (wslProbe.error || wslProbe.status !== 0) { + throw new Error( + 'CodeRabbit CLI requires WSL on Windows hosts. Install WSL via ' + + '`wsl --install` (https://learn.microsoft.com/windows/wsl/install), ' + + 'then install the CodeRabbit CLI inside the WSL distribution. ' + + 'See docs/guides/installation-troubleshooting.md Issue 10. ' + + 'To bypass this check, set coderabbit.installation_mode=\'native\' in your config.', + ); + } const projectRoot = this.coderabbit.projectRoot || process.cwd(); const wslProjectPath = projectRoot .replace(/\\/g, '/') @@ -322,7 +337,7 @@ class Layer2PRAutomation extends BaseLayer { env: { ...process.env }, }; - const child = spawn(command, [], options); + const child = childProcess.spawn(command, [], options); let stdout = ''; let stderr = ''; diff --git a/.aiox-core/install-manifest.yaml b/.aiox-core/install-manifest.yaml index 5e1db41642..99a43e2c78 100644 --- a/.aiox-core/install-manifest.yaml +++ b/.aiox-core/install-manifest.yaml @@ -8,7 +8,7 @@ # - File types for categorization # version: 5.2.7 -generated_at: "2026-05-18T05:37:21.641Z" +generated_at: "2026-05-18T11:40:45.332Z" generator: scripts/generate-install-manifest.js file_count: 1128 files: @@ -989,9 +989,9 @@ files: type: core size: 31469 - path: core/orchestration/workflow-executor.js - hash: sha256:ddc880683df489ec7203ab7ecd8e066a08e2f72b011fe0dfe578d31155eb513b + hash: sha256:5bc1c72b7565f3ae5794d4d379d2090c605ca6c383ebbb94a042c5f96b2b9102 type: core - size: 37348 + size: 38289 - path: core/orchestration/workflow-orchestrator.js hash: sha256:82816bd5e6fecc9bbb77292444e53254c72bf93e5c04260784743354c6a58627 type: core @@ -1037,9 +1037,9 @@ files: type: core size: 9393 - path: core/quality-gates/layer2-pr-automation.js - hash: sha256:53e1f05e3ece4731848ad818f13a97ce18f553327b6f2e385e4d299492cc890a + hash: sha256:19e52369efe5cf0df9d0b6a9675dd555fdd46c1ab19cf6dc45d9173ad6e6524e type: core - size: 10923 + size: 11907 - path: core/quality-gates/layer3-human-review.js hash: sha256:9bf79d5adfddae55d7dddfda777cd2775aa76f82204ddd0f660f6edbd093b16b type: core diff --git a/tests/unit/quality-gates/cross-platform-coderabbit.test.js b/tests/unit/quality-gates/cross-platform-coderabbit.test.js index 861ebe511b..1d90b98660 100644 --- a/tests/unit/quality-gates/cross-platform-coderabbit.test.js +++ b/tests/unit/quality-gates/cross-platform-coderabbit.test.js @@ -17,6 +17,7 @@ const os = require('os'); const path = require('path'); +const childProcess = require('child_process'); const { Layer2PRAutomation } = require('../../../.aiox-core/core/quality-gates/layer2-pr-automation'); @@ -27,11 +28,21 @@ describe('Cross-platform CodeRabbit invocation (Issue #731)', () => { let originalCwd; let layer; let capturedCommand; + let spawnSyncSpy; beforeEach(() => { originalPlatform = process.platform; originalCwd = process.cwd; capturedCommand = null; + // Mock the WSL availability probe added for #757 so legacy command-shape + // tests don't fail on macOS/Linux dev boxes where `wsl` isn't installed. + // Tests covering the probe failure path override this on a per-test basis. + spawnSyncSpy = jest.spyOn(childProcess, 'spawnSync').mockImplementation((cmd) => { + if (cmd === 'wsl') { + return { status: 0, stdout: 'Ubuntu\n', stderr: '' }; + } + return { status: 0, stdout: '', stderr: '' }; + }); layer = new Layer2PRAutomation({ enabled: true, coderabbit: { enabled: true }, @@ -47,6 +58,7 @@ describe('Cross-platform CodeRabbit invocation (Issue #731)', () => { afterEach(() => { Object.defineProperty(process, 'platform', { value: originalPlatform }); process.cwd = originalCwd; + spawnSyncSpy.mockRestore(); }); const setPlatform = (value) => { @@ -148,4 +160,53 @@ describe('Cross-platform CodeRabbit invocation (Issue #731)', () => { // that WSL cannot resolve. expect(capturedCommand).toContain('~/custom/coderabbit --prompt-only -t uncommitted'); }); + + // Issue #757 — WSL availability probe before invoking CodeRabbit on Windows + describe('WSL availability probe (Issue #757)', () => { + it('surfaces a clear error when WSL binary is missing (ENOENT)', async () => { + setPlatform('win32'); + spawnSyncSpy.mockImplementation((cmd) => { + if (cmd === 'wsl') { + return { error: Object.assign(new Error('ENOENT'), { code: 'ENOENT' }), status: null }; + } + return { status: 0, stdout: '', stderr: '' }; + }); + const result = await layer.runCodeRabbit(); + expect(spawnSyncSpy).toHaveBeenCalledWith('wsl', ['-l'], expect.any(Object)); + expect(result.pass).toBe(false); + expect(result.error).toMatch(/CodeRabbit CLI requires WSL/); + expect(result.error).toMatch(/wsl --install/); + expect(result.error).toMatch(/installation-troubleshooting/); + }); + + it('surfaces a clear error when WSL is installed but has no distribution (exit != 0)', async () => { + setPlatform('win32'); + spawnSyncSpy.mockImplementation((cmd) => { + if (cmd === 'wsl') { + return { status: 1, stdout: '', stderr: 'no distros\n' }; + } + return { status: 0, stdout: '', stderr: '' }; + }); + const result = await layer.runCodeRabbit(); + expect(spawnSyncSpy).toHaveBeenCalledWith('wsl', ['-l'], expect.any(Object)); + expect(result.pass).toBe(false); + expect(result.error).toMatch(/CodeRabbit CLI requires WSL/); + }); + + it('does NOT probe WSL when installation_mode=native on Windows', async () => { + setPlatform('win32'); + layer.coderabbit.installation_mode = 'native'; + await layer.runCodeRabbit(); + // The probe MUST NOT have been called when native mode is explicit. + const wslProbeCalls = spawnSyncSpy.mock.calls.filter((c) => c[0] === 'wsl'); + expect(wslProbeCalls).toHaveLength(0); + }); + + it('does NOT probe WSL on macOS native invocation', async () => { + setPlatform('darwin'); + await layer.runCodeRabbit(); + const wslProbeCalls = spawnSyncSpy.mock.calls.filter((c) => c[0] === 'wsl'); + expect(wslProbeCalls).toHaveLength(0); + }); + }); });