diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 332eebc..0c9146c 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -481,3 +481,31 @@ jobs: - name: Verify dotnet (higher version) shell: pwsh run: __tests__/verify-dotnet.ps1 -Patterns "^${{ matrix.lower-version }}$", "^${{ matrix.higher-version }}$" + + test-version-already-installed: + runs-on: ${{ matrix.operating-system }} + strategy: + fail-fast: false + matrix: + operating-system: [ubuntu-latest, windows-latest, macos-latest] + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Clear toolcache + shell: pwsh + run: __tests__/clear-toolcache.ps1 ${{ runner.os }} + - name: Setup dotnet (first) + uses: ./ + with: + dotnet-version: '6.0' + - name: Verify dotnet (first) + shell: pwsh + run: __tests__/verify-dotnet.ps1 -Patterns "^6.0" + - name: Setup dotnet (same major, any minor) + uses: ./ + with: + dotnet-version: '6.x' + prefer-installed: true + - name: Verify dotnet (same major, any minor) + shell: pwsh + run: __tests__/verify-dotnet.ps1 -Patterns "^6.0" diff --git a/__tests__/dotnet-utils.test.ts b/__tests__/dotnet-utils.test.ts new file mode 100644 index 0000000..7305985 --- /dev/null +++ b/__tests__/dotnet-utils.test.ts @@ -0,0 +1,79 @@ +import * as dotnetUtils from '../src/dotnet-utils'; +import * as exec from '@actions/exec'; + +describe('dotnet-utils', () => { + describe('findMatchingVersion', () => { + it('matches all versions with all syntaxes correctly', () => { + expect( + dotnetUtils.findMatchingVersion('3.1', ['3.1.201', '6.0.402']) + ).toEqual('3.1.201'); + expect( + dotnetUtils.findMatchingVersion('3.1.x', ['3.1.201', '6.0.402']) + ).toEqual('3.1.201'); + expect( + dotnetUtils.findMatchingVersion('3', ['3.1.201', '6.0.402']) + ).toEqual('3.1.201'); + expect( + dotnetUtils.findMatchingVersion('3.x', ['3.1.201', '6.0.402']) + ).toEqual('3.1.201'); + expect( + dotnetUtils.findMatchingVersion('6.0.4xx', ['3.1.201', '6.0.402']) + ).toEqual('6.0.402'); + }); + + it('returns undefined if no version is matched', () => { + expect( + dotnetUtils.findMatchingVersion('6.0.5xx', ['3.1.201', '6.0.403']) + ).toEqual(undefined); + expect(dotnetUtils.findMatchingVersion('6.0.5xx', [])).toEqual(undefined); + }); + + it("returns the first version if 'x' or '*' version is provided", () => { + expect( + dotnetUtils.findMatchingVersion('x', ['3.1.201', '6.0.403']) + ).toEqual('3.1.201'); + expect( + dotnetUtils.findMatchingVersion('*', ['3.1.201', '6.0.403']) + ).toEqual('3.1.201'); + }); + + it('returns undefined if empty version list is provided', () => { + expect(dotnetUtils.findMatchingVersion('6.0.4xx', [])).toEqual(undefined); + }); + }); + + describe('listSdks', () => { + const execSpy = jest.spyOn(exec, 'getExecOutput'); + + it('correctly parses versions from output and sorts them from newest to oldest', async () => { + const stdout = ` + 2.2.207 [C:\\Users\\User_Name\\AppData\\Local\\Microsoft\\dotnet\\sdk] + 6.0.413 [C:\\Users\\User_Name\\AppData\\Local\\Microsoft\\dotnet\\sdk] + 6.0.414 [C:\\Users\\User_Name\\AppData\\Local\\Microsoft\\dotnet\\sdk] + `; + + execSpy.mockImplementationOnce(() => + Promise.resolve({stdout, exitCode: 0, stderr: ''}) + ); + expect(await dotnetUtils.listSdks()).toEqual([ + '6.0.414', + '6.0.413', + '2.2.207' + ]); + }); + + it('returns empty array if exit code is not 0', async () => { + execSpy.mockImplementationOnce(() => + Promise.resolve({stdout: '', exitCode: 1, stderr: 'arbitrary error'}) + ); + expect(await dotnetUtils.listSdks()).toEqual([]); + }); + + it('returns empty array on error', async () => { + execSpy.mockImplementationOnce(() => + Promise.reject(new Error('arbitrary error')) + ); + expect(await dotnetUtils.listSdks()).toEqual([]); + }); + }); +}); diff --git a/__tests__/installer.test.ts b/__tests__/installer.test.ts index 84aea32..e1bcf28 100644 --- a/__tests__/installer.test.ts +++ b/__tests__/installer.test.ts @@ -42,6 +42,10 @@ describe('installer tests', () => { const inputQuality = '' as QualityOptions; const errorMessage = 'fictitious error message!'; + const resolvedVersion = await new installer.DotnetVersionResolver( + inputVersion + ).createDotnetVersion(); + getExecOutputSpy.mockImplementation(() => { return Promise.resolve({ exitCode: 1, @@ -51,7 +55,7 @@ describe('installer tests', () => { }); const dotnetInstaller = new installer.DotnetCoreInstaller( - inputVersion, + resolvedVersion, inputQuality ); await expect(dotnetInstaller.installDotnet()).rejects.toThrow( @@ -63,6 +67,11 @@ describe('installer tests', () => { const inputVersion = '3.1.100'; const inputQuality = '' as QualityOptions; const stdout = `Fictitious dotnet version ${inputVersion} is installed`; + + const resolvedVersion = await new installer.DotnetVersionResolver( + inputVersion + ).createDotnetVersion(); + getExecOutputSpy.mockImplementation(() => { return Promise.resolve({ exitCode: 0, @@ -73,7 +82,7 @@ describe('installer tests', () => { maxSatisfyingSpy.mockImplementation(() => inputVersion); const dotnetInstaller = new installer.DotnetCoreInstaller( - inputVersion, + resolvedVersion, inputQuality ); const installedVersion = await dotnetInstaller.installDotnet(); @@ -86,6 +95,10 @@ describe('installer tests', () => { const inputQuality = '' as QualityOptions; const stdout = `Fictitious dotnet version ${inputVersion} is installed`; + const resolvedVersion = await new installer.DotnetVersionResolver( + inputVersion + ).createDotnetVersion(); + getExecOutputSpy.mockImplementation(() => { return Promise.resolve({ exitCode: 0, @@ -96,7 +109,7 @@ describe('installer tests', () => { maxSatisfyingSpy.mockImplementation(() => inputVersion); const dotnetInstaller = new installer.DotnetCoreInstaller( - inputVersion, + resolvedVersion, inputQuality ); @@ -123,6 +136,11 @@ describe('installer tests', () => { const inputVersion = '6.0.300'; const inputQuality = 'ga' as QualityOptions; const stdout = `Fictitious dotnet version ${inputVersion} is installed`; + + const resolvedVersion = await new installer.DotnetVersionResolver( + inputVersion + ).createDotnetVersion(); + getExecOutputSpy.mockImplementation(() => { return Promise.resolve({ exitCode: 0, @@ -133,7 +151,7 @@ describe('installer tests', () => { maxSatisfyingSpy.mockImplementation(() => inputVersion); const dotnetInstaller = new installer.DotnetCoreInstaller( - inputVersion, + resolvedVersion, inputQuality ); @@ -149,6 +167,10 @@ describe('installer tests', () => { const inputQuality = 'ga' as QualityOptions; const stdout = `Fictitious dotnet version 3.1.100 is installed`; + const resolvedVersion = await new installer.DotnetVersionResolver( + inputVersion + ).createDotnetVersion(); + getExecOutputSpy.mockImplementation(() => { return Promise.resolve({ exitCode: 0, @@ -159,7 +181,7 @@ describe('installer tests', () => { maxSatisfyingSpy.mockImplementation(() => inputVersion); const dotnetInstaller = new installer.DotnetCoreInstaller( - inputVersion, + resolvedVersion, inputQuality ); @@ -173,6 +195,10 @@ describe('installer tests', () => { each(['6', '6.0', '6.0.x', '6.0.*', '6.0.X']).test( `should supply 'quality' argument to the installation script if quality input is set and version (%s) is not in A.B.C syntax`, async inputVersion => { + const resolvedVersion = await new installer.DotnetVersionResolver( + inputVersion + ).createDotnetVersion(); + const inputQuality = 'ga' as QualityOptions; const exitCode = 0; const stdout = `Fictitious dotnet version 6.0.0 is installed`; @@ -186,7 +212,7 @@ describe('installer tests', () => { maxSatisfyingSpy.mockImplementation(() => inputVersion); const dotnetInstaller = new installer.DotnetCoreInstaller( - inputVersion, + resolvedVersion, inputQuality ); @@ -216,6 +242,11 @@ describe('installer tests', () => { const inputQuality = '' as QualityOptions; const exitCode = 0; const stdout = `Fictitious dotnet version 6.0.0 is installed`; + + const resolvedVersion = await new installer.DotnetVersionResolver( + inputVersion + ).createDotnetVersion(); + getExecOutputSpy.mockImplementation(() => { return Promise.resolve({ exitCode: exitCode, @@ -226,7 +257,7 @@ describe('installer tests', () => { maxSatisfyingSpy.mockImplementation(() => inputVersion); const dotnetInstaller = new installer.DotnetCoreInstaller( - inputVersion, + resolvedVersion, inputQuality ); @@ -257,6 +288,10 @@ describe('installer tests', () => { const inputQuality = '' as QualityOptions; const stdout = `Fictitious dotnet version ${inputVersion} is installed`; + const resolvedVersion = await new installer.DotnetVersionResolver( + inputVersion + ).createDotnetVersion(); + getExecOutputSpy.mockImplementation(() => { return Promise.resolve({ exitCode: 0, @@ -267,7 +302,7 @@ describe('installer tests', () => { maxSatisfyingSpy.mockImplementation(() => inputVersion); const dotnetInstaller = new installer.DotnetCoreInstaller( - inputVersion, + resolvedVersion, inputQuality ); @@ -295,6 +330,10 @@ describe('installer tests', () => { const inputQuality = '' as QualityOptions; const stdout = `Fictitious dotnet version 6.0.0 is installed`; + const resolvedVersion = await new installer.DotnetVersionResolver( + inputVersion + ).createDotnetVersion(); + getExecOutputSpy.mockImplementation(() => { return Promise.resolve({ exitCode: 0, @@ -305,7 +344,7 @@ describe('installer tests', () => { maxSatisfyingSpy.mockImplementation(() => inputVersion); const dotnetInstaller = new installer.DotnetCoreInstaller( - inputVersion, + resolvedVersion, inputQuality ); diff --git a/action.yml b/action.yml index bf24aca..825a18e 100644 --- a/action.yml +++ b/action.yml @@ -9,6 +9,10 @@ inputs: description: 'Optional SDK version(s) to use. If not provided, will install global.json version when available. Examples: 2.2.104, 3.1, 3.1.x, 3.x, 6.0.2xx' dotnet-quality: description: 'Optional quality of the build. The possible values are: daily, signed, validated, preview, ga.' + prefer-installed: + description: 'Optional flag to prefer an already installed version of the SDK (when partial version syntax is used). If not provided, latest version with specified quality will be installed.' + required: false + default: false global-json-file: description: 'Optional global.json location, if your global.json isn''t located in the root of the repo.' source-url: diff --git a/dist/setup/index.js b/dist/setup/index.js index 7567a1b..2f20ef3 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -72787,6 +72787,86 @@ var Outputs; })(Outputs = exports.Outputs || (exports.Outputs = {})); +/***/ }), + +/***/ 2971: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.findMatchingVersion = exports.listSdks = void 0; +const exec = __importStar(__nccwpck_require__(1514)); +const listSdks = () => __awaiter(void 0, void 0, void 0, function* () { + const { stdout, exitCode } = yield exec + .getExecOutput('dotnet', ['--list-sdks'], { + ignoreReturnCode: true + }) + .catch(() => ({ stdout: '', exitCode: 1 })); + if (exitCode) { + return []; + } + return (stdout + .trim() + .split('\n') + .map(versionInfo => versionInfo.trim()) + .map(versionInfo => versionInfo.split(' ')[0]) + // reverses output so newer versions are first + .reverse()); +}); +exports.listSdks = listSdks; +/** + * Function that matches string like that + * '3.1', '3.1.x', '3', '3.x', '6.0.4xx' to + * correct version number like '3.1.201', '3.1.201', '3.1.201', '3.1.201', '6.0.402' + */ +const findMatchingVersion = (versionPattern, versions) => { + if (!versionPattern || versionPattern === 'x' || versionPattern === '*') { + return versions.at(0); + } + const versionArray = versionPattern.split('.'); + if (versionArray.length < 3) { + versionArray.push(...Array(3 - versionArray.length).fill('x')); + } + const normalizedVersion = versionArray.join('.'); + const versionRegex = new RegExp(`^${normalizedVersion.replace(/x/g, '\\d+')}`); + return versions.find(v => versionRegex.test(v)); +}; +exports.findMatchingVersion = findMatchingVersion; + + /***/ }), /***/ 2574: @@ -72841,10 +72921,12 @@ const path_1 = __importDefault(__nccwpck_require__(1017)); const os_1 = __importDefault(__nccwpck_require__(2037)); const semver_1 = __importDefault(__nccwpck_require__(5911)); const utils_1 = __nccwpck_require__(1314); +const dotnet_utils_1 = __nccwpck_require__(2971); const QUALITY_INPUT_MINIMAL_MAJOR_TAG = 6; const LATEST_PATCH_SYNTAX_MINIMAL_MAJOR_TAG = 5; class DotnetVersionResolver { - constructor(version) { + constructor(version, preferInstalled = false) { + this.preferInstalled = preferInstalled; this.inputVersion = version.trim(); this.resolvedArgument = { type: '', value: '', qualityFlag: false }; } @@ -72854,11 +72936,21 @@ class DotnetVersionResolver { throw new Error(`The 'dotnet-version' was supplied in invalid format: ${this.inputVersion}! Supported syntax: A.B.C, A.B, A.B.x, A, A.x, A.B.Cxx`); } if (semver_1.default.valid(this.inputVersion)) { - this.createVersionArgument(); + this.createVersionArgument(this.inputVersion); + return; } - else { + if (!this.preferInstalled) { yield this.createChannelArgument(); + return; } + const requestedVersion = this.inputVersion; + const installedVersions = yield (0, dotnet_utils_1.listSdks)(); + const matchingInstalledVersion = (0, dotnet_utils_1.findMatchingVersion)(requestedVersion, installedVersions); + if (matchingInstalledVersion) { + this.createVersionArgument(matchingInstalledVersion); + return; + } + this.createChannelArgument(); }); } isNumericTag(versionTag) { @@ -72873,9 +72965,9 @@ class DotnetVersionResolver { } return majorTag ? true : false; } - createVersionArgument() { + createVersionArgument(version) { this.resolvedArgument.type = 'version'; - this.resolvedArgument.value = this.inputVersion; + this.resolvedArgument.value = version; } createChannelArgument() { return __awaiter(this, void 0, void 0, function* () { @@ -73035,14 +73127,12 @@ DotnetInstallDir.dirPath = process.env['DOTNET_INSTALL_DIR'] ? DotnetInstallDir.convertInstallPathToAbsolute(process.env['DOTNET_INSTALL_DIR']) : DotnetInstallDir.default[utils_1.PLATFORM]; class DotnetCoreInstaller { - constructor(version, quality) { - this.version = version; + constructor(dotnetVersion, quality) { + this.dotnetVersion = dotnetVersion; this.quality = quality; } installDotnet() { return __awaiter(this, void 0, void 0, function* () { - const versionResolver = new DotnetVersionResolver(this.version); - const dotnetVersion = yield versionResolver.createDotnetVersion(); /** * Install dotnet runitme first in order to get * the latest stable version of dotnet CLI @@ -73070,7 +73160,7 @@ class DotnetCoreInstaller { // Don't overwrite CLI because it should be already installed .useArguments(utils_1.IS_WINDOWS ? '-SkipNonVersionedFiles' : '--skip-non-versioned-files') // Use version provided by user - .useVersion(dotnetVersion, this.quality) + .useVersion(this.dotnetVersion, this.quality) .execute(); if (dotnetInstallOutput.exitCode) { throw new Error(`Failed to install dotnet, exit code: ${dotnetInstallOutput.exitCode}. ${dotnetInstallOutput.stderr}`); @@ -73190,13 +73280,16 @@ function run() { } if (versions.length) { const quality = core.getInput('dotnet-quality'); + const preferInstalled = core.getBooleanInput('prefer-installed'); if (quality && !qualityOptions.includes(quality)) { throw new Error(`Value '${quality}' is not supported for the 'dotnet-quality' option. Supported values are: daily, signed, validated, preview, ga.`); } let dotnetInstaller; + let dotnetVersionResolver; const uniqueVersions = new Set(versions); for (const version of uniqueVersions) { - dotnetInstaller = new installer_1.DotnetCoreInstaller(version, quality); + dotnetVersionResolver = new installer_1.DotnetVersionResolver(version, preferInstalled); + dotnetInstaller = new installer_1.DotnetCoreInstaller(yield dotnetVersionResolver.createDotnetVersion(), quality); const installedVersion = yield dotnetInstaller.installDotnet(); installedDotnetVersions.push(installedVersion); } diff --git a/src/dotnet-utils.ts b/src/dotnet-utils.ts new file mode 100644 index 0000000..bb15f4d --- /dev/null +++ b/src/dotnet-utils.ts @@ -0,0 +1,51 @@ +import * as exec from '@actions/exec'; + +export const listSdks = async () => { + const {stdout, exitCode} = await exec + .getExecOutput('dotnet', ['--list-sdks'], { + ignoreReturnCode: true + }) + .catch(() => ({stdout: '', exitCode: 1})); + + if (exitCode) { + return []; + } + + return ( + stdout + .trim() + .split('\n') + .map(versionInfo => versionInfo.trim()) + .map(versionInfo => versionInfo.split(' ')[0]) + // reverses output so newer versions are first + .reverse() + ); +}; + +/** + * Function that matches string like that + * '3.1', '3.1.x', '3', '3.x', '6.0.4xx' to + * correct version number like '3.1.201', '3.1.201', '3.1.201', '3.1.201', '6.0.402' + */ +export const findMatchingVersion = ( + versionPattern: string, + versions: string[] +): string | undefined => { + if (!versionPattern || versionPattern === 'x' || versionPattern === '*') { + return versions.at(0); + } + + const versionArray = versionPattern.split('.'); + + if (versionArray.length < 3) { + versionArray.push(...Array(3 - versionArray.length).fill('x')); + } + + const normalizedVersion = versionArray.join('.'); + + const versionRegex = new RegExp( + `^${normalizedVersion.replace(/x/g, '\\d+')}` + ); + + return versions.find(v => versionRegex.test(v)); +}; diff --git a/src/installer.ts b/src/installer.ts index 4900afa..365956e 100644 --- a/src/installer.ts +++ b/src/installer.ts @@ -9,6 +9,7 @@ import os from 'os'; import semver from 'semver'; import {IS_WINDOWS, PLATFORM} from './utils'; import {QualityOptions} from './setup-dotnet'; +import {listSdks, findMatchingVersion} from './dotnet-utils'; export interface DotnetVersion { type: string; @@ -22,7 +23,7 @@ export class DotnetVersionResolver { private inputVersion: string; private resolvedArgument: DotnetVersion; - constructor(version: string) { + constructor(version: string, private preferInstalled = false) { this.inputVersion = version.trim(); this.resolvedArgument = {type: '', value: '', qualityFlag: false}; } @@ -33,11 +34,30 @@ export class DotnetVersionResolver { `The 'dotnet-version' was supplied in invalid format: ${this.inputVersion}! Supported syntax: A.B.C, A.B, A.B.x, A, A.x, A.B.Cxx` ); } + if (semver.valid(this.inputVersion)) { - this.createVersionArgument(); - } else { - await this.createChannelArgument(); + this.createVersionArgument(this.inputVersion); + return; } + + if (!this.preferInstalled) { + await this.createChannelArgument(); + return; + } + + const requestedVersion = this.inputVersion; + const installedVersions = await listSdks(); + const matchingInstalledVersion = findMatchingVersion( + requestedVersion, + installedVersions + ); + + if (matchingInstalledVersion) { + this.createVersionArgument(matchingInstalledVersion); + return; + } + + this.createChannelArgument(); } private isNumericTag(versionTag): boolean { @@ -59,9 +79,9 @@ export class DotnetVersionResolver { return majorTag ? true : false; } - private createVersionArgument() { + private createVersionArgument(version: string) { this.resolvedArgument.type = 'version'; - this.resolvedArgument.value = this.inputVersion; + this.resolvedArgument.value = version; } private async createChannelArgument() { @@ -253,12 +273,12 @@ export class DotnetCoreInstaller { DotnetInstallDir.setEnvironmentVariable(); } - constructor(private version: string, private quality: QualityOptions) {} + constructor( + private readonly dotnetVersion: DotnetVersion, + private readonly quality: QualityOptions + ) {} public async installDotnet(): Promise { - const versionResolver = new DotnetVersionResolver(this.version); - const dotnetVersion = await versionResolver.createDotnetVersion(); - /** * Install dotnet runitme first in order to get * the latest stable version of dotnet CLI @@ -294,7 +314,7 @@ export class DotnetCoreInstaller { IS_WINDOWS ? '-SkipNonVersionedFiles' : '--skip-non-versioned-files' ) // Use version provided by user - .useVersion(dotnetVersion, this.quality) + .useVersion(this.dotnetVersion, this.quality) .execute(); if (dotnetInstallOutput.exitCode) { diff --git a/src/setup-dotnet.ts b/src/setup-dotnet.ts index 2a628a5..2b8c6a6 100644 --- a/src/setup-dotnet.ts +++ b/src/setup-dotnet.ts @@ -1,5 +1,9 @@ import * as core from '@actions/core'; -import {DotnetCoreInstaller, DotnetInstallDir} from './installer'; +import { + DotnetCoreInstaller, + DotnetInstallDir, + DotnetVersionResolver +} from './installer'; import * as fs from 'fs'; import path from 'path'; import semver from 'semver'; @@ -59,6 +63,7 @@ export async function run() { if (versions.length) { const quality = core.getInput('dotnet-quality') as QualityOptions; + const preferInstalled = core.getBooleanInput('prefer-installed'); if (quality && !qualityOptions.includes(quality)) { throw new Error( @@ -67,9 +72,18 @@ export async function run() { } let dotnetInstaller: DotnetCoreInstaller; + let dotnetVersionResolver: DotnetVersionResolver; + const uniqueVersions = new Set(versions); for (const version of uniqueVersions) { - dotnetInstaller = new DotnetCoreInstaller(version, quality); + dotnetVersionResolver = new DotnetVersionResolver( + version, + preferInstalled + ); + dotnetInstaller = new DotnetCoreInstaller( + await dotnetVersionResolver.createDotnetVersion(), + quality + ); const installedVersion = await dotnetInstaller.installDotnet(); installedDotnetVersions.push(installedVersion); }