From 5e03b0a1ae88e45a344acac6845f603ec157ddc4 Mon Sep 17 00:00:00 2001 From: "Rossdan Craig rossdan@lastmileai.dev" <> Date: Sun, 25 Feb 2024 17:16:22 -0500 Subject: [PATCH] [vscode][8/n] Setup Env Variables: Use lowest common ancestor path for multiple workspaces Somewhat slightly checked out in https://gitlab.com/sebdeckers/lowest-common-ancestor/-/blob/master/src/index.js?ref_type=heads, but didn't want to use this package since: 1. It doesn't have exact functionality I want (ex: if only 1 path provided, it returns empty string) 2. Don't want something small to depend on a package when we can control it ourselves Some VS Code setups can have multiple workspaces, in which case we should take the lowest common ancestor path that is shared across all of them so that the same .env file can be used for multiple AIConfig files. Initial comment from Sarmad in https://github.com/lastmile-ai/aiconfig/pull/1291#discussion_r1499876794 ## Test Plan Tested it with my vscode settings (only 1 workspace path) and no functional changes https://github.com/lastmile-ai/aiconfig/assets/151060367/a82eb30f-c951-4f27-aa8f-2b9353089c4e To test, I also hardcoded some paths to ensure that this works with multiple workspaces https://github.com/lastmile-ai/aiconfig/assets/151060367/3110c87c-d3db-40bb-8dc2-6f0f15544d32 The nothing in common use case returns empty string, so no validation will occur in that case https://github.com/lastmile-ai/aiconfig/assets/151060367/e4b6fad8-feea-40b2-a339-973acf279520 I'd also love to test this in Windows just to 100% sanity check, though the `path.sep` should ensure that it still works --- vscode-extension/src/util.ts | 51 +++++++++++++++++++++++++++++------- 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/vscode-extension/src/util.ts b/vscode-extension/src/util.ts index aab45e262..754fbba28 100644 --- a/vscode-extension/src/util.ts +++ b/vscode-extension/src/util.ts @@ -310,17 +310,12 @@ export async function setupEnvironmentVariables( ) { const homedir = require("os").homedir(); // This is cross-platform: https://stackoverflow.com/a/9081436 const defaultEnvPath = path.join(homedir, ".env"); - - // TODO: If there are multiple workspace folders, use common lowest - // ancestor as workspacePath: https://github.com/lastmile-ai/aiconfig/issues/1299 - const workspacePath = vscode.workspace.workspaceFolders - ? vscode.workspace.workspaceFolders[0].uri.fsPath - : null; + const lowestCommonWorkspacePath = getLowestCommonAncestorAcrossWorkspaces(); const envPath = await vscode.window.showInputBox({ prompt: "Enter the path of your .env file", value: defaultEnvPath, - validateInput: (input) => validateEnvPath(input, workspacePath), + validateInput: (input) => validateEnvPath(input, lowestCommonWorkspacePath), }); if (!envPath) { @@ -403,15 +398,53 @@ export function validateNewConfigName(name: string, mode: "json" | "yaml") { return null; } +/** + * Some VS Code setups can have multiple workspaces, in which + * case we should take the lowest common ancestor path that is shared + * across all of them so that the same .env file can be used for multiple + * AIConfig files + * @returns lowestCommonAncestorPath (string | undefined) + * -> string of path to lowest common ancestor: empty means no shared path + * -> undefined if no workspaces are defined in VS Code session + */ +function getLowestCommonAncestorAcrossWorkspaces(): string | undefined { + const workspaceFolders = vscode.workspace.workspaceFolders; + if (workspaceFolders === undefined || workspaceFolders.length === 0) { + return undefined; + } + + const workspacePaths = workspaceFolders.map((folder) => + path.normalize(folder.uri.fsPath) + ); + let lowestCommonAncestorPath: string; + const separator = path.sep; // Handles Windows and Linux + lowestCommonAncestorPath = workspacePaths.reduce( + (currLowestCommonAncestorPath, currPath) => { + const ancestorFolders = currLowestCommonAncestorPath.split(separator); + const currPathFolders = currPath.split(separator); + const commonPathFolders: Array = []; + for (var i = 0; i < ancestorFolders.length; i++) { + if (ancestorFolders[i] === currPathFolders[i]) { + commonPathFolders.push(ancestorFolders[i]); + } else { + break; + } + } + return commonPathFolders.join(separator); + } + ); + return lowestCommonAncestorPath; +} + function validateEnvPath( inputPath: string, - workspacePath: string | null + workspacePath: string | undefined ): string | null { if (!inputPath) { return "File path is required"; } else if (path.basename(inputPath) !== ".env") { return 'Filename must be ".env"'; - } else if (workspacePath !== null) { + } else if (workspacePath != null && workspacePath !== "") { // loadenv() from Python checks each folder from the file/program where // it's invoked for the presence of an `.env` file. Therefore, the `.env // file must be saved either at the top-level directory of the workspace