Skip to content

Commit 224716f

Browse files
committed
✨ feat: refactor Ddev command execution to use execDdev wrapper
1 parent 5e25e9c commit 224716f

File tree

2 files changed

+35
-29
lines changed

2 files changed

+35
-29
lines changed

src/shared/utils/ddev-utils.ts

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,7 @@ export class DdevUtils {
6464
*/
6565
public static isDdevRunning(workspacePath: string): boolean {
6666
try {
67-
execSync('ddev exec echo "test"', {
68-
cwd: workspacePath,
69-
stdio: 'ignore'
70-
});
67+
this.execDdev('echo "test"', workspacePath);
7168
return true;
7269
} catch (error) {
7370
return false;
@@ -83,10 +80,7 @@ export class DdevUtils {
8380
*/
8481
public static isToolInstalled(toolName: string, workspacePath: string): boolean {
8582
try {
86-
execSync(`ddev exec ${toolName} --version`, {
87-
cwd: workspacePath,
88-
stdio: 'ignore'
89-
});
83+
this.execDdev(`${toolName} --version`, workspacePath);
9084
return true;
9185
} catch (error) {
9286
return false;
@@ -112,25 +106,14 @@ export class DdevUtils {
112106

113107
// Try to run the tool
114108
try {
115-
execSync(`ddev exec ${toolName} --version`, {
116-
cwd: workspacePath,
117-
stdio: 'ignore'
118-
});
109+
this.execDdev(`${toolName} --version`, workspacePath);
119110

120111
return {
121112
isValid: true
122113
};
123114
} catch (error: any) {
124115
// Try to get more specific error information
125-
let errorDetails = '';
126-
try {
127-
execSync(`ddev exec ${toolName} --version`, {
128-
cwd: workspacePath,
129-
encoding: 'utf-8'
130-
});
131-
} catch (execError: any) {
132-
errorDetails = execError.message || execError.stderr || '';
133-
}
116+
const errorDetails = error.message || error.stderr || '';
134117

135118
// Build concise but informative error message
136119
let userMessage = `${toolName.toUpperCase()} not available`;
@@ -201,8 +184,12 @@ export class DdevUtils {
201184
* @throws Error if the command fails
202185
*/
203186
public static execDdev(command: string, workspacePath: string): string {
187+
// Escape single quotes in the command to prevent breaking the bash -c string
188+
const escapedCommand = command.replace(/'/g, "'\\''");
189+
const wrappedCommand = `bash -c 'XDEBUG_MODE=off ${escapedCommand}'`;
190+
204191
try {
205-
return execSync(`ddev exec ${command}`, {
192+
return execSync(`ddev exec ${wrappedCommand}`, {
206193
cwd: workspacePath,
207194
encoding: 'utf-8'
208195
});

src/test/ddev-utils.test.ts

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ suite('DdevUtils Test Suite', () => {
6161

6262
assert.strictEqual(result, true);
6363
assert.strictEqual(execSyncStub.calledOnce, true);
64+
// Verify it uses execDdev wrapper
65+
const callArgs = execSyncStub.firstCall.args;
66+
assert.ok(callArgs[0].includes("bash -c 'XDEBUG_MODE=off echo \"test\"'"));
6467
});
6568

6669
test('isDdevRunning returns false when DDEV container is not running', () => {
@@ -78,6 +81,9 @@ suite('DdevUtils Test Suite', () => {
7881

7982
assert.strictEqual(result, true);
8083
assert.strictEqual(execSyncStub.calledOnce, true);
84+
// Verify it uses execDdev wrapper
85+
const callArgs = execSyncStub.firstCall.args;
86+
assert.ok(callArgs[0].includes("bash -c 'XDEBUG_MODE=off phpmd --version'"));
8187
});
8288

8389
test('isToolInstalled returns false when tool is not available', () => {
@@ -113,19 +119,17 @@ suite('DdevUtils Test Suite', () => {
113119
test('validateDdevTool returns error message for DDEV issues', () => {
114120
// First call (hasDdevProject) succeeds
115121
execSyncStub.onFirstCall().returns('exists\n');
116-
// Second call (tool version check) fails
117-
execSyncStub.onSecondCall().throws(new Error('Tool not available'));
118-
// Third call (error details) returns error
119-
execSyncStub.onThirdCall().throws({
120-
message: 'DDEV project not currently running',
121-
stderr: 'not currently running'
122-
});
122+
// Second call (tool version check) fails with specific error
123+
const error = new Error('DDEV project not currently running') as any;
124+
error.stderr = 'not currently running';
125+
execSyncStub.onSecondCall().throws(error);
123126

124127
const result = DdevUtils.validateDdevTool('phpmd', '/test/workspace');
125128

126129
assert.strictEqual(result.isValid, false);
127130
assert.strictEqual(result.errorType, 'unknown');
128131
assert.ok(result.userMessage?.includes('PHPMD not available'));
132+
assert.ok(result.userMessage?.includes('DDEV appears to be stopped'));
129133
});
130134

131135
test('execDdev returns output when command succeeds', () => {
@@ -136,6 +140,9 @@ suite('DdevUtils Test Suite', () => {
136140

137141
assert.strictEqual(result, expectedOutput);
138142
assert.strictEqual(execSyncStub.calledOnce, true);
143+
144+
const callArgs = execSyncStub.firstCall.args;
145+
assert.ok(callArgs[0].includes("bash -c 'XDEBUG_MODE=off phpmd test.php json cleancode'"));
139146
});
140147

141148
test('execDdev returns stdout when command fails but has output', () => {
@@ -156,4 +163,16 @@ suite('DdevUtils Test Suite', () => {
156163
DdevUtils.execDdev('phpmd test.php json cleancode', '/test/workspace');
157164
}, /Command failed/);
158165
});
166+
167+
test('execDdev escapes single quotes in command', () => {
168+
execSyncStub.returns('output');
169+
170+
DdevUtils.execDdev("echo 'hello'", '/test/workspace');
171+
172+
assert.strictEqual(execSyncStub.calledOnce, true);
173+
const callArgs = execSyncStub.firstCall.args;
174+
// echo 'hello' -> echo '\''hello'\''
175+
// wrapped: bash -c 'XDEBUG_MODE=off echo '\''hello'\'''
176+
assert.ok(callArgs[0].includes("echo '\\''hello'\\''"));
177+
});
159178
});

0 commit comments

Comments
 (0)