Repository: microsoft/vscode-js-debug (TypeScript)

Issue:
Support FNM Node Version Manager in Node configuration provider
Support FNM Node Version Manager (https://github.com/Schniz/fnm) in Node configuration provider.

The change would be in this method:
https://github.com/Microsoft/vscode-node-debug/blob/master/src/node/extension/configurationProvider.ts#L222

The location for each `$VERSION` is here: `$HOME/.fnm/node-versions/$VERSION/installation/bin/node`


Discussion: A workaround:

```js
{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "type": "node",
            "request": "launch",
            "name": "Launch Program (with FNM)",
            "skipFiles": [
                "<node_internals>/**"
            ],
            "console": "integratedTerminal",
            "internalConsoleOptions":"neverOpen",
            "program": "${workspaceFolder}/index.js",
            "runtimeExecutable": "fnm",
            "runtimeArgs": [
                "exec",
                "--using-file",
                "--",
                "node"
            ],
            "restart": true
        }
    ]
}
```
Is there interest from the maintainers to officially support this?
PR's welcome :)
I would be more then happy to contribute:

- Could you provide me how with you would see this to work from an API POV? So that I make sure we're aligned on its implementation?
- Could you give me any pointers you can, as it would be my first contribution to this plugin or any vscode plugin for that matter.

Want to make sure I know what I need to know and that we're aligned prior to me starting on its support. And thanks for the flexibility on your end!
This is currently handled by the [nvmResolver.ts](https://github.com/microsoft/vscode-js-debug/blob/main/src/targets/node/nvmResolver.ts). This could be refactored a little bit since this one class handles nvs and nvm on both Windows and Unix. Adding FNM would be yet another overload of that. I don't think there'd be any API changes really, we can still use the same `runtimeVersion` string, but just add support for searching for FNM.

For a build setup, check out [CONTRIBUTING.md](https://github.com/microsoft/vscode-js-debug/blob/main/CONTRIBUTING.md#development)
Great. Will try to make time for it this week, looks like a fun first contribution :)



Fix (patch):
diff --git a/src/dap/errors.ts b/src/dap/errors.ts
index c8ba52af2..9abf2dae2 100644
--- a/src/dap/errors.ts
+++ b/src/dap/errors.ts
@@ -65,7 +65,7 @@ export function createUserError(text: string, code = ErrorCodes.UserError): Dap.
 export const nvmNotFound = () =>
   createUserError(
     l10n.t(
-      "Attribute 'runtimeVersion' requires Node.js version manager 'nvs' or 'nvm' to be installed.",
+      "Attribute 'runtimeVersion' requires Node.js version manager 'nvs', 'nvm' or 'fnm' to be installed.",
     ),
     ErrorCodes.NvmOrNvsNotFound,
   );
diff --git a/src/targets/node/nvmResolver.ts b/src/targets/node/nvmResolver.ts
index 8909a326c..eafe4a378 100644
--- a/src/targets/node/nvmResolver.ts
+++ b/src/targets/node/nvmResolver.ts
@@ -36,6 +36,7 @@ const enum Vars {
   NvsHome = 'NVS_HOME',
   WindowsNvmHome = 'NVM_HOME',
   UnixNvmHome = 'NVM_DIR',
+  FnmHome = 'FNM_DIR',
 }
 
 @injectable()
@@ -49,6 +50,10 @@ export class NvmResolver implements INvmResolver {
   ) {}
 
   public async resolveNvmVersionPath(version: string) {
+    let directory: string | undefined = undefined;
+    const versionManagers: string[] = [];
+    const versionData = this.parseVersionString(version);
+
     let nvsHome = this.env[Vars.NvsHome];
     if (!nvsHome) {
       // NVS_HOME is not always set. Probe for 'nvs' directory instead
@@ -61,9 +66,6 @@ export class NvmResolver implements INvmResolver {
       }
     }
 
-    let directory: string | undefined = undefined;
-    const versionManagers: string[] = [];
-    const versionData = this.parseVersionString(version);
     if (versionData.nvsFormat || nvsHome) {
       directory = await this.resolveNvs(nvsHome, versionData);
       if (!directory && versionData.nvsFormat) {
@@ -87,6 +89,17 @@ export class NvmResolver implements INvmResolver {
       }
     }
 
+    if (!directory) {
+      const fnmDir =
+        this.platform === 'win32'
+          ? this.env[Vars.FnmHome] || path.join(this.env['APPDATA'] || '', 'fnm')
+          : this.env[Vars.FnmHome] || path.join(this.homedir, '.fnm');
+      if (await this.fsUtils.exists(fnmDir)) {
+        directory = await this.resolveFnm(version, fnmDir);
+        versionManagers.push('fnm');
+      }
+    }
+
     if (!versionManagers.length) {
       throw new ProtocolError(nvmNotFound());
     }
@@ -160,6 +173,19 @@ export class NvmResolver implements INvmResolver {
     return this.findBinFolderForVersion(nvmHome, `v${version}`);
   }
 
+  private async resolveFnm(version: string, fnmHome: string) {
+    const directory = this.findBinFolderForVersion(
+      path.join(fnmHome, 'node-versions'),
+      `v${version}`,
+    );
+
+    if (!directory) return;
+
+    return this.platform === 'win32'
+      ? path.join(directory, 'installation')
+      : path.join(directory, 'installation', 'bin');
+  }
+
   private findBinFolderForVersion(
     dir: string,
     version: string,