Skip to content

Commit 7b14ee8

Browse files
committed
feat: support env in path mapping
1 parent bcb2ba1 commit 7b14ee8

File tree

3 files changed

+123
-20
lines changed

3 files changed

+123
-20
lines changed

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,19 @@ To make VS Code map the files on the server to the right files on your local mac
140140
}
141141
```
142142

143+
### Environment Variables in Path Mappings
144+
145+
You can use environment variables in `pathMappings` using the `${env:VARIABLE_NAME}` syntax. This is useful for dynamic paths that vary between environments or users:
146+
147+
```json
148+
"pathMappings": {
149+
"${env:DOCKER_WEB_ROOT}": "${workspaceFolder}",
150+
"/app": "${env:PROJECT_PATH}"
151+
}
152+
```
153+
154+
The environment variables are resolved when the debug session starts. If a variable is not defined, the literal string (e.g., `${env:UNDEFINED_VAR}`) will be kept as-is.
155+
143156
Please also note that setting any of the CLI debugging options will not work with remote host debugging, because the script is always launched locally. If you want to debug a CLI script on a remote host, you need to launch it manually from the command line.
144157

145158
## Proxy support

src/extension.ts

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,18 @@ import { LaunchRequestArguments } from './phpDebug'
44
import * as which from 'which'
55
import * as path from 'path'
66

7+
/**
8+
* Resolves environment variables in a string
9+
* Supports: ${env:VAR_NAME}
10+
*/
11+
export function resolveEnvVariables(value: string): string {
12+
// Replace ${env:VAR_NAME} with environment variable values
13+
return value.replace(/\$\{env:([^}]+)\}/g, (match, envVar) => {
14+
const envValue = process.env[envVar]
15+
return envValue !== undefined ? envValue : match
16+
})
17+
}
18+
719
export function activate(context: vscode.ExtensionContext) {
820
context.subscriptions.push(
921
vscode.debug.registerDebugConfigurationProvider('php', {
@@ -71,29 +83,32 @@ export function activate(context: vscode.ExtensionContext) {
7183
}
7284
}
7385
}
74-
if (folder && folder.uri.scheme !== 'file') {
75-
// replace
76-
if (debugConfiguration.pathMappings) {
77-
for (const key in debugConfiguration.pathMappings) {
78-
debugConfiguration.pathMappings[key] = debugConfiguration.pathMappings[key].replace(
79-
'${workspaceFolder}',
80-
folder.uri.toString()
81-
)
86+
if (debugConfiguration.pathMappings) {
87+
const resolvedMappings: { [index: string]: string } = {}
88+
for (const [serverPath, localPath] of Object.entries(debugConfiguration.pathMappings)) {
89+
const resolvedServerPath = resolveEnvVariables(serverPath)
90+
let resolvedLocalPath = resolveEnvVariables(localPath)
91+
92+
if (folder && folder.uri.scheme !== 'file') {
93+
resolvedLocalPath = resolvedLocalPath.replace('${workspaceFolder}', folder.uri.toString())
8294
}
95+
96+
resolvedMappings[resolvedServerPath] = resolvedLocalPath
8397
}
84-
// The following path are currently NOT mapped
85-
/*
86-
debugConfiguration.skipEntryPaths = debugConfiguration.skipEntryPaths?.map(v =>
87-
v.replace('${workspaceFolder}', folder.uri.toString())
88-
)
89-
debugConfiguration.skipFiles = debugConfiguration.skipFiles?.map(v =>
90-
v.replace('${workspaceFolder}', folder.uri.toString())
91-
)
92-
debugConfiguration.ignore = debugConfiguration.ignore?.map(v =>
93-
v.replace('${workspaceFolder}', folder.uri.toString())
94-
)
95-
*/
98+
debugConfiguration.pathMappings = resolvedMappings
9699
}
100+
// The following path are currently NOT mapped
101+
/*
102+
debugConfiguration.skipEntryPaths = debugConfiguration.skipEntryPaths?.map(v =>
103+
v.replace('${workspaceFolder}', folder.uri.toString())
104+
)
105+
debugConfiguration.skipFiles = debugConfiguration.skipFiles?.map(v =>
106+
v.replace('${workspaceFolder}', folder.uri.toString())
107+
)
108+
debugConfiguration.ignore = debugConfiguration.ignore?.map(v =>
109+
v.replace('${workspaceFolder}', folder.uri.toString())
110+
)
111+
*/
97112
return debugConfiguration
98113
},
99114
})

src/test/envResolver.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { assert } from 'chai'
2+
import { describe, it, beforeEach, afterEach } from 'mocha'
3+
4+
// Inline the function for testing without vscode dependency
5+
function resolveEnvVariables(value: string): string {
6+
return value.replace(/\$\{env:([^}]+)\}/g, (match, envVar) => {
7+
const envValue = process.env[envVar]
8+
return envValue !== undefined ? envValue : match
9+
})
10+
}
11+
12+
describe('Environment Variable Resolution', () => {
13+
let originalEnv: NodeJS.ProcessEnv
14+
15+
beforeEach(() => {
16+
originalEnv = { ...process.env }
17+
})
18+
19+
afterEach(() => {
20+
process.env = originalEnv
21+
})
22+
23+
it('should resolve ${env:VAR_NAME} with existing environment variable', () => {
24+
process.env.TEST_VAR = '/test/path'
25+
const result = resolveEnvVariables('${env:TEST_VAR}/subdir')
26+
assert.equal(result, '/test/path/subdir')
27+
})
28+
29+
it('should keep ${env:VAR_NAME} if environment variable does not exist', () => {
30+
delete process.env.NONEXISTENT_VAR
31+
const result = resolveEnvVariables('${env:NONEXISTENT_VAR}/subdir')
32+
assert.equal(result, '${env:NONEXISTENT_VAR}/subdir')
33+
})
34+
35+
it('should resolve multiple environment variables', () => {
36+
process.env.VAR1 = '/path1'
37+
process.env.VAR2 = '/path2'
38+
const result = resolveEnvVariables('${env:VAR1}/${env:VAR2}')
39+
assert.equal(result, '/path1//path2')
40+
})
41+
42+
it('should handle text without environment variables', () => {
43+
const result = resolveEnvVariables('/var/www/html')
44+
assert.equal(result, '/var/www/html')
45+
})
46+
47+
it('should handle environment variables with underscores and numbers', () => {
48+
process.env.MY_VAR_123 = '/custom/path'
49+
const result = resolveEnvVariables('${env:MY_VAR_123}')
50+
assert.equal(result, '/custom/path')
51+
})
52+
53+
it('should handle empty environment variable value', () => {
54+
process.env.EMPTY_VAR = ''
55+
const result = resolveEnvVariables('${env:EMPTY_VAR}/test')
56+
assert.equal(result, '/test')
57+
})
58+
59+
it('should handle mixed content', () => {
60+
process.env.DOCKER_ROOT = '/var/www/html'
61+
const result = resolveEnvVariables('prefix/${env:DOCKER_ROOT}/suffix')
62+
assert.equal(result, 'prefix//var/www/html/suffix')
63+
})
64+
65+
it('should handle pathMapping use case', () => {
66+
process.env.DOCKER_WEB_ROOT = '/var/www/html'
67+
process.env.LOCAL_PROJECT = '/Users/developer/myproject'
68+
69+
const serverPath = '${env:DOCKER_WEB_ROOT}'
70+
const localPath = '${env:LOCAL_PROJECT}'
71+
72+
assert.equal(resolveEnvVariables(serverPath), '/var/www/html')
73+
assert.equal(resolveEnvVariables(localPath), '/Users/developer/myproject')
74+
})
75+
})

0 commit comments

Comments
 (0)