Skip to content

Commit 922add3

Browse files
authored
feat: Implement envFile support (#837)
* Implement envFile support. * Test envfile. * Add extra test for missing env file. * README and CHANGELOG.
1 parent 8fa60e3 commit 922add3

File tree

9 files changed

+132
-1
lines changed

9 files changed

+132
-1
lines changed

.eslintrc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ module.exports = {
6666
'prefer-rest-params': 'off',
6767
'@typescript-eslint/no-inferrable-types': ['error', { ignoreParameters: true }],
6868
'@typescript-eslint/no-non-null-assertion': 'off',
69+
'@typescript-eslint/no-unsafe-assignment': 'off',
6970
/*
7071
"@typescript-eslint/indent": "off",
7172
"@typescript-eslint/member-delimiter-style": [

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file.
44

55
The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/).
66

7+
## [1.28.0]
8+
9+
- Support for envFile.
10+
- Migrated from tslint to eslint.
11+
712
## [1.27.0]
813

914
- Variable paging with VSCode indexedVariables.

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ Options specific to CLI debugging:
100100
- `runtimeArgs`: Additional arguments to pass to the PHP binary
101101
- `externalConsole`: Launches the script in an external console window instead of the debug console (default: `false`)
102102
- `env`: Environment variables to pass to the script
103+
- `envFile`: Optional path to a file containing environment variable definitions
103104

104105
## Features
105106

package-lock.json

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
"@vscode/debugadapter": "^1.57.0",
4747
"@vscode/debugprotocol": "^1.55.1",
4848
"@xmldom/xmldom": "^0.8.2",
49+
"dotenv": "^16.0.1",
4950
"file-url": "^3.0.0",
5051
"iconv-lite": "^0.6.3",
5152
"minimatch": "^5.1.0",
@@ -230,6 +231,10 @@
230231
"description": "Environment variables passed to the program.",
231232
"default": {}
232233
},
234+
"envFile": {
235+
"type": "string",
236+
"description": "Absolute path to a file containing environment variable definitions."
237+
},
233238
"hostname": {
234239
"type": "string",
235240
"description": "Address to bind to when listening for Xdebug or Unix domain socket (start with unix://)",

src/envfile.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import * as fs from 'fs'
2+
import { LaunchRequestArguments } from './phpDebug'
3+
import * as dotenv from 'dotenv'
4+
5+
/**
6+
* Returns the user-configured portion of the environment variables.
7+
*/
8+
export function getConfiguredEnvironment(args: LaunchRequestArguments): { [key: string]: string } {
9+
if (args.envFile) {
10+
try {
11+
return merge(readEnvFile(args.envFile), args.env || {})
12+
} catch (e) {
13+
throw new Error('Failed reading envFile')
14+
}
15+
}
16+
return args.env || {}
17+
}
18+
19+
function readEnvFile(file: string): { [key: string]: string } {
20+
if (!fs.existsSync(file)) {
21+
return {}
22+
}
23+
const buffer = stripBOM(fs.readFileSync(file, 'utf8'))
24+
const env = dotenv.parse(Buffer.from(buffer))
25+
return env
26+
}
27+
28+
function stripBOM(s: string): string {
29+
if (s && s[0] === '\uFEFF') {
30+
s = s.substring(1)
31+
}
32+
return s
33+
}
34+
35+
function merge(...vars: { [key: string]: string }[]): { [key: string]: string } {
36+
if (process.platform === 'win32') {
37+
return caseInsensitiveMerge(...vars)
38+
}
39+
return Object.assign({}, ...vars) as { [key: string]: string }
40+
}
41+
42+
/**
43+
* Performs a case-insenstive merge of the list of objects.
44+
*/
45+
function caseInsensitiveMerge<V>(...objs: ReadonlyArray<Readonly<{ [key: string]: V }> | undefined | null>) {
46+
if (objs.length === 0) {
47+
return {}
48+
}
49+
const out: { [key: string]: V } = {}
50+
const caseMapping: { [key: string]: string } = Object.create(null) // prototype-free object
51+
for (const obj of objs) {
52+
if (!obj) {
53+
continue
54+
}
55+
for (const key of Object.keys(obj)) {
56+
const normalized = key.toLowerCase()
57+
if (caseMapping[normalized]) {
58+
out[caseMapping[normalized]] = obj[key]
59+
} else {
60+
caseMapping[normalized] = key
61+
out[key] = obj[key]
62+
}
63+
}
64+
}
65+
return out
66+
}

src/phpDebug.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import * as semver from 'semver'
1616
import { LogPointManager } from './logpoint'
1717
import { ProxyConnect } from './proxyConnect'
1818
import { randomUUID } from 'crypto'
19+
import { getConfiguredEnvironment } from './envfile'
1920

2021
if (process.env['VSCODE_NLS_CONFIG']) {
2122
try {
@@ -100,6 +101,8 @@ export interface LaunchRequestArguments extends VSCodeDebugProtocol.LaunchReques
100101
runtimeArgs?: string[]
101102
/** Optional environment variables to pass to the debuggee. The string valued properties of the 'environmentVariables' are used as key/value pairs. */
102103
env?: { [key: string]: string }
104+
/** Absolute path to a file containing environment variable definitions. */
105+
envFile?: string
103106
/** If true launch the target in an external console. */
104107
externalConsole?: boolean
105108
/** Maximum allowed parallel debugging sessions */
@@ -268,7 +271,7 @@ class PhpDebugSession extends vscode.DebugSession {
268271
const program = args.program ? [args.program] : []
269272
const cwd = args.cwd || process.cwd()
270273
const env = Object.fromEntries(
271-
Object.entries({ ...process.env, ...args.env }).map(v => [
274+
Object.entries({ ...process.env, ...getConfiguredEnvironment(args) }).map(v => [
272275
v[0],
273276
v[1]?.replace('${port}', port.toString()),
274277
])

src/test/envfile.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { assert } from 'chai'
2+
import { describe, it } from 'mocha'
3+
import { getConfiguredEnvironment } from '../envfile'
4+
5+
describe('EnvFile', () => {
6+
it('should work without envfile', () => {
7+
const ret = getConfiguredEnvironment({ env: { TEST: 'TEST' } })
8+
assert.deepEqual(ret, { TEST: 'TEST' })
9+
})
10+
it('should work with missing envfile', () => {
11+
const ret = getConfiguredEnvironment({ env: { TEST: 'TEST' }, envFile: 'NONEXISTINGFILE' })
12+
assert.deepEqual(ret, { TEST: 'TEST' })
13+
})
14+
it('should merge envfile', () => {
15+
const ret = getConfiguredEnvironment({ env: { TEST: 'TEST' }, envFile: 'testproject/envfile' })
16+
assert.deepEqual(ret, { TEST: 'TEST', TEST1: 'VALUE1', Test2: 'Value2' })
17+
})
18+
;(process.platform === 'win32' ? it : it.skip)('should merge envfile on win32', () => {
19+
const ret = getConfiguredEnvironment({ env: { TEST1: 'TEST' }, envFile: 'testproject/envfile' })
20+
assert.deepEqual(ret, { TEST1: 'TEST', Test2: 'Value2' })
21+
})
22+
;(process.platform === 'win32' ? it : it.skip)('should merge envfile on win32 case insensitive', () => {
23+
const ret = getConfiguredEnvironment({ env: { Test1: 'TEST' }, envFile: 'testproject/envfile' })
24+
assert.deepEqual(ret, { TEST1: 'TEST', Test2: 'Value2' })
25+
})
26+
;(process.platform !== 'win32' ? it : it.skip)('should merge envfile on unix', () => {
27+
const ret = getConfiguredEnvironment({ env: { TEST1: 'TEST' }, envFile: 'testproject/envfile' })
28+
assert.deepEqual(ret, { TEST1: 'TEST', Test2: 'Value2' })
29+
})
30+
;(process.platform !== 'win32' ? it : it.skip)('should merge envfile on unix case insensitive', () => {
31+
const ret = getConfiguredEnvironment({ env: { Test1: 'TEST' }, envFile: 'testproject/envfile' })
32+
assert.deepEqual(ret, { Test1: 'TEST', TEST1: 'VALUE1', Test2: 'Value2' })
33+
})
34+
})

testproject/envfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
TEST1=VALUE1
2+
Test2=Value2

0 commit comments

Comments
 (0)