From 0a79426185c8af41ba0dddb1f5e584cfc86e4630 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Wed, 4 Feb 2026 09:13:17 +0000 Subject: [PATCH 1/5] chore: add `.env` to `.gitignore` to exclude environment variables file --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index af4080c..98e2308 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ node_modules package-lock.json *.vsix .vscode/settings.json +.env From 9254b984d7155a96b93bbbaa7b9eb9b048feb79f Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Wed, 4 Feb 2026 09:27:48 +0000 Subject: [PATCH 2/5] feat: add util function to load dev environment variables from .env - Added `addDevEnvVariables` util function to allow the extension to load the local `.env` file from the project root if it exists, using Node's `loadEnvFile` API, and load the environment variables into `process.env`. The `.env` file will only exist on the development machine and is crucial to properly test the extension in vscode's extension testing environment. This fixes the ability to mistakenly commit the dev testing path to git. As seen in commit be9d972 and removed in commit 9ff0857 of PR #20. With the .env file, this will never happen again. - Added the `addDevEnvVariables` function call to the top of the `extension.ts` file so it is the first thing it does and the env variables will be available in all files in development. - Added the usage of the `DEV_USER_EXTENSIONS_PATH` env variable to `ExtensionData::setExtensionDiscoveryPaths` method. So that when its available to use, it will override the `userExtensionsPath` variable with the path set in the env variable. --- src/extension.ts | 3 +++ src/extensionData.ts | 11 ++++++++++- src/utils.ts | 13 +++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/extension.ts b/src/extension.ts index a9a945e..9453e5f 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -5,6 +5,9 @@ import * as vscode from "vscode"; import {Configuration} from "./configuration"; import {logger} from "./logger"; import {ExtensionData} from "./extensionData"; +import {addDevEnvVariables} from "./utils"; + +addDevEnvVariables(); const extensionData = new ExtensionData(); logger.setupOutputChannel(); diff --git a/src/extensionData.ts b/src/extensionData.ts index c9db9c4..6a37b5e 100644 --- a/src/extensionData.ts +++ b/src/extensionData.ts @@ -94,11 +94,20 @@ export class ExtensionData { * Set the extension discovery paths into the extensionDiscoveryPaths Map. */ private setExtensionDiscoveryPaths() { + // Get the DEV_USER_EXTENSIONS_PATH env variable if it exists. + const devUserExtensionsPath = process.env.DEV_USER_EXTENSIONS_PATH; + // The path to the user extensions. // // On Windows/Linux/Mac: ~/.vscode[-server|remote]/extensions // On WSL: ~/.vscode-[server|remote]/extensions - const userExtensionsPath = isWsl ? path.join(vscode.env.appRoot, "../../", "extensions") : path.join(this.extensionPath, "../"); + // + // Because the extensionPath is created from __dirname and retrieves where this extension + // is located, in extension testing/development mode, this path will point to the local + // development path, not the actual user extensions path. So we use the custom + // DEV_USER_EXTENSIONS_PATH env variable to override it. + const userExtensionsPath = + devUserExtensionsPath || (isWsl ? path.join(vscode.env.appRoot, "../../", "extensions") : path.join(this.extensionPath, "../")); this.extensionDiscoveryPaths.set("userExtensionsPath", userExtensionsPath); // The path to the built-in extensions. diff --git a/src/utils.ts b/src/utils.ts index d1388ac..bdbbdf4 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,4 +1,5 @@ import * as fs from "node:fs"; +import * as path from "node:path"; import * as jsonc from "jsonc-parser"; import {logger} from "./logger"; import {window} from "vscode"; @@ -237,3 +238,15 @@ export function mergeArraysBy(primaryArray: T[], secondaryArray: T[], key: ke return merged; } + +/** + * Add development environment variables from a local .env file located in the project root. + */ +export function addDevEnvVariables() { + // Try to load the local .env file from the project root. + try { + process.loadEnvFile(path.join(__dirname, "../../.env")); + } catch (error) { + // Ignore errors if the .env file doesn't exist + } +} From fff3c4091ef18f6db8baf0a4f92fa2d040e45e28 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Fri, 6 Feb 2026 02:54:29 +0000 Subject: [PATCH 3/5] fix: duplicate output channel. A duplicate output channel is sometimes created when logging. Possibly because it is setup after `ExtensionData`, and any logs from that class or earlier in the run, will create additional channels, without realising there's one already created. - Fixed by moving `logger.setupOutputChannel` call up the run order to before the `addDevEnvVariables` call in the extension file. This should help prevent duplicate channels, and log to only one channel. --- src/extension.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/extension.ts b/src/extension.ts index 9453e5f..95c8bb7 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -7,10 +7,10 @@ import {logger} from "./logger"; import {ExtensionData} from "./extensionData"; import {addDevEnvVariables} from "./utils"; +logger.setupOutputChannel(); addDevEnvVariables(); const extensionData = new ExtensionData(); -logger.setupOutputChannel(); let configuration = new Configuration(); const disposables: vscode.Disposable[] = []; From 0f7a9370d1a8a9b5fdfa270f9fcbdd42e2698827 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Fri, 6 Feb 2026 03:17:51 +0000 Subject: [PATCH 4/5] fix: validate and sanitize the dev env variable - Added `validateDevEnvVariables` util function to validate and sanitize the `DEV_USER_EXTENSIONS_PATH` env variable value. Added it's function call to `addDevEnvVariables` util function. This function is based on the code Copilot created in PR #23, but with significant improvement. --- src/utils.ts | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/src/utils.ts b/src/utils.ts index bdbbdf4..a6b0de2 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -249,4 +249,64 @@ export function addDevEnvVariables() { } catch (error) { // Ignore errors if the .env file doesn't exist } + + // Validate the loaded environment variables + validateDevEnvVariables(); +} + +/** + * Validate and sanitize the `DEV_USER_EXTENSIONS_PATH` environment variable. + * Removes invalid paths from the environment with logged errors. + */ +function validateDevEnvVariables() { + // Validate DEV_USER_EXTENSIONS_PATH if it was loaded + if (process.env.DEV_USER_EXTENSIONS_PATH) { + // Trim whitespace and resolve the path to an absolute path + let devPath = path.resolve(process.env.DEV_USER_EXTENSIONS_PATH.trim()); + + let stats: fs.Stats; + let errorMsg: string; + let errorData: Error; + + // Get the file system stats for the path to check if it exists. + // statSync throws an exception if the no file system data exists for the path, + // so we catch it to handle errors gracefully. + try { + stats = fs.statSync(devPath); + } catch (error) { + const nodeError = error as NodeJS.ErrnoException; + const errorCode = nodeError.code || "UNKNOWN"; + + // Handle specific file system errors with user-friendly messages. + const errorMessages = { + ENOENT: "Path from env variable 'DEV_USER_EXTENSIONS_PATH' does not exist", + EACCES: "Permission denied accessing path from env variable 'DEV_USER_EXTENSIONS_PATH'", + UNKNOWN: "Unknown error accessing the path from env variable 'DEV_USER_EXTENSIONS_PATH'", + }; + + errorMsg = `${errorCode}: ${errorMessages[errorCode]}: "${devPath}". Removing from environment.`; + + errorData = error as Error; + + delete process.env.DEV_USER_EXTENSIONS_PATH; + } + + // If stats has data AND the path is NOT a directory + if (stats && !stats.isDirectory()) { + errorMsg = `DEV_USER_EXTENSIONS_PATH is not a directory: "${devPath}". Removing from environment.`; + } + + // If there was an error... + if (errorMsg.length > 0) { + // Delete the environment variable to prevent issues later on and log the error. + delete process.env.DEV_USER_EXTENSIONS_PATH; + logger.error(errorMsg, errorData); + } + // Otherwise, if there are no errors... + else { + // Update the environment variable with the sanitized path and log a success message. + process.env.DEV_USER_EXTENSIONS_PATH = devPath; + logger.info(`Loaded DEV_USER_EXTENSIONS_PATH: "${devPath}"`); + } + } } From d9d08308726455b7cf3efe424baf10df2ac32973 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Fri, 6 Feb 2026 04:25:09 +0000 Subject: [PATCH 5/5] fix: initialise `errorMsg` variable as an empty string. - Fixed `errorMsg` variable in `validateDevEnvVariables` util function to be initialised as an empty string. This avoids errors when trying to call `.length` on it later. --- src/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils.ts b/src/utils.ts index a6b0de2..5d6fe71 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -265,7 +265,7 @@ function validateDevEnvVariables() { let devPath = path.resolve(process.env.DEV_USER_EXTENSIONS_PATH.trim()); let stats: fs.Stats; - let errorMsg: string; + let errorMsg: string = ""; let errorData: Error; // Get the file system stats for the path to check if it exists.