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, diff --git a/src/test/node/runtimeVersion.test.ts b/src/test/node/runtimeVersion.test.ts index 1aab9a076..da6db7bc9 100644 --- a/src/test/node/runtimeVersion.test.ts +++ b/src/test/node/runtimeVersion.test.ts @@ -30,11 +30,16 @@ describe('runtimeVersion', () => { 'nvs/node/13.12.0/x64/bin/node': '', 'nvs/node/13.11.0/x86/bin/node': '', 'nvm/versions/node/v13.11.0/bin/node': '', + 'fnm/node-versions/v13.10.0/installation/bin/node': '', }); resolver = new NvmResolver( fsUtils, - { NVS_HOME: path.join(testFixturesDir, 'nvs'), NVM_DIR: path.join(testFixturesDir, 'nvm') }, + { + NVS_HOME: path.join(testFixturesDir, 'nvs'), + NVM_DIR: path.join(testFixturesDir, 'nvm'), + FNM_DIR: path.join(testFixturesDir, 'fnm'), + }, 'x64', 'linux', testWorkspace, @@ -48,9 +53,12 @@ describe('runtimeVersion', () => { const { directory: b } = await resolver.resolveNvmVersionPath('13.11'); expect(b).to.equal(path.join(testFixturesDir, 'nvm/versions/node/v13.11.0/bin')); + const { directory: c } = await resolver.resolveNvmVersionPath('13.10'); + expect(c).to.equal(path.join(testFixturesDir, 'fnm/node-versions/v13.10.0/installation/bin')); + await expect(resolver.resolveNvmVersionPath('14')).to.eventually.be.rejectedWith( ProtocolError, - /not installed using version manager nvs\/nvm/, + /not installed using version manager nvs\/nvm\/fnm/, ); }); @@ -229,4 +237,44 @@ describe('runtimeVersion', () => { ); }); }); + + describe('fnm', () => { + beforeEach(() => { + createFileTree(testFixturesDir, { + 'node-versions/v13.12.0/installation/bin/node': '', + 'node-versions/v13.3.0/installation/bin/node': '', + 'node-versions/v13.invalid/installation/bin/node': '', + }); + + resolver = new NvmResolver( + fsUtils, + { FNM_DIR: testFixturesDir }, + 'x64', + 'linux', + testWorkspace, + ); + }); + + it('gets an exact match', async () => { + const { directory, binary } = await resolver.resolveNvmVersionPath('13.3.0'); + expect(directory).to.equal( + path.join(testFixturesDir, 'node-versions/v13.3.0/installation/bin'), + ); + expect(binary).to.equal('node'); + }); + + it('gets the best matching version', async () => { + const { directory } = await resolver.resolveNvmVersionPath('13'); + expect(directory).to.equal( + path.join(testFixturesDir, 'node-versions/v13.12.0/installation/bin'), + ); + }); + + it('throws if no version match', async () => { + await expect(resolver.resolveNvmVersionPath('14')).to.eventually.be.rejectedWith( + ProtocolError, + /not installed/, + ); + }); + }); });