From 224716f8225fbb06b8f859f3b28872153f511800 Mon Sep 17 00:00:00 2001 From: Thomas Hauschild <7961978+Morgy93@users.noreply.github.com> Date: Sat, 27 Dec 2025 21:00:18 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat:=20refactor=20Ddev=20command?= =?UTF-8?q?=20execution=20to=20use=20execDdev=20wrapper?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/shared/utils/ddev-utils.ts | 31 +++++++++---------------------- src/test/ddev-utils.test.ts | 33 ++++++++++++++++++++++++++------- 2 files changed, 35 insertions(+), 29 deletions(-) diff --git a/src/shared/utils/ddev-utils.ts b/src/shared/utils/ddev-utils.ts index bb61cce..7ef83f9 100644 --- a/src/shared/utils/ddev-utils.ts +++ b/src/shared/utils/ddev-utils.ts @@ -64,10 +64,7 @@ export class DdevUtils { */ public static isDdevRunning(workspacePath: string): boolean { try { - execSync('ddev exec echo "test"', { - cwd: workspacePath, - stdio: 'ignore' - }); + this.execDdev('echo "test"', workspacePath); return true; } catch (error) { return false; @@ -83,10 +80,7 @@ export class DdevUtils { */ public static isToolInstalled(toolName: string, workspacePath: string): boolean { try { - execSync(`ddev exec ${toolName} --version`, { - cwd: workspacePath, - stdio: 'ignore' - }); + this.execDdev(`${toolName} --version`, workspacePath); return true; } catch (error) { return false; @@ -112,25 +106,14 @@ export class DdevUtils { // Try to run the tool try { - execSync(`ddev exec ${toolName} --version`, { - cwd: workspacePath, - stdio: 'ignore' - }); + this.execDdev(`${toolName} --version`, workspacePath); return { isValid: true }; } catch (error: any) { // Try to get more specific error information - let errorDetails = ''; - try { - execSync(`ddev exec ${toolName} --version`, { - cwd: workspacePath, - encoding: 'utf-8' - }); - } catch (execError: any) { - errorDetails = execError.message || execError.stderr || ''; - } + const errorDetails = error.message || error.stderr || ''; // Build concise but informative error message let userMessage = `${toolName.toUpperCase()} not available`; @@ -201,8 +184,12 @@ export class DdevUtils { * @throws Error if the command fails */ public static execDdev(command: string, workspacePath: string): string { + // Escape single quotes in the command to prevent breaking the bash -c string + const escapedCommand = command.replace(/'/g, "'\\''"); + const wrappedCommand = `bash -c 'XDEBUG_MODE=off ${escapedCommand}'`; + try { - return execSync(`ddev exec ${command}`, { + return execSync(`ddev exec ${wrappedCommand}`, { cwd: workspacePath, encoding: 'utf-8' }); diff --git a/src/test/ddev-utils.test.ts b/src/test/ddev-utils.test.ts index de722e4..a8e5f24 100644 --- a/src/test/ddev-utils.test.ts +++ b/src/test/ddev-utils.test.ts @@ -61,6 +61,9 @@ suite('DdevUtils Test Suite', () => { assert.strictEqual(result, true); assert.strictEqual(execSyncStub.calledOnce, true); + // Verify it uses execDdev wrapper + const callArgs = execSyncStub.firstCall.args; + assert.ok(callArgs[0].includes("bash -c 'XDEBUG_MODE=off echo \"test\"'")); }); test('isDdevRunning returns false when DDEV container is not running', () => { @@ -78,6 +81,9 @@ suite('DdevUtils Test Suite', () => { assert.strictEqual(result, true); assert.strictEqual(execSyncStub.calledOnce, true); + // Verify it uses execDdev wrapper + const callArgs = execSyncStub.firstCall.args; + assert.ok(callArgs[0].includes("bash -c 'XDEBUG_MODE=off phpmd --version'")); }); test('isToolInstalled returns false when tool is not available', () => { @@ -113,19 +119,17 @@ suite('DdevUtils Test Suite', () => { test('validateDdevTool returns error message for DDEV issues', () => { // First call (hasDdevProject) succeeds execSyncStub.onFirstCall().returns('exists\n'); - // Second call (tool version check) fails - execSyncStub.onSecondCall().throws(new Error('Tool not available')); - // Third call (error details) returns error - execSyncStub.onThirdCall().throws({ - message: 'DDEV project not currently running', - stderr: 'not currently running' - }); + // Second call (tool version check) fails with specific error + const error = new Error('DDEV project not currently running') as any; + error.stderr = 'not currently running'; + execSyncStub.onSecondCall().throws(error); const result = DdevUtils.validateDdevTool('phpmd', '/test/workspace'); assert.strictEqual(result.isValid, false); assert.strictEqual(result.errorType, 'unknown'); assert.ok(result.userMessage?.includes('PHPMD not available')); + assert.ok(result.userMessage?.includes('DDEV appears to be stopped')); }); test('execDdev returns output when command succeeds', () => { @@ -136,6 +140,9 @@ suite('DdevUtils Test Suite', () => { assert.strictEqual(result, expectedOutput); assert.strictEqual(execSyncStub.calledOnce, true); + + const callArgs = execSyncStub.firstCall.args; + assert.ok(callArgs[0].includes("bash -c 'XDEBUG_MODE=off phpmd test.php json cleancode'")); }); test('execDdev returns stdout when command fails but has output', () => { @@ -156,4 +163,16 @@ suite('DdevUtils Test Suite', () => { DdevUtils.execDdev('phpmd test.php json cleancode', '/test/workspace'); }, /Command failed/); }); + + test('execDdev escapes single quotes in command', () => { + execSyncStub.returns('output'); + + DdevUtils.execDdev("echo 'hello'", '/test/workspace'); + + assert.strictEqual(execSyncStub.calledOnce, true); + const callArgs = execSyncStub.firstCall.args; + // echo 'hello' -> echo '\''hello'\'' + // wrapped: bash -c 'XDEBUG_MODE=off echo '\''hello'\''' + assert.ok(callArgs[0].includes("echo '\\''hello'\\''")); + }); });