diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index ea106a7..d244b1c 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -133,6 +133,27 @@ jobs: shell: pwsh run: __tests__/verify-dotnet.ps1 -Patterns "^2.2", "^3.1" + test-ABCxx-syntax: + 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 6.0.4xx + uses: ./ + with: + dotnet-version: '6.0.4xx' + - name: Verify dotnet + shell: pwsh + run: __tests__/verify-dotnet.ps1 -Patterns "^6\.0\.4\d{2}" + test-setup-with-wildcard: runs-on: ${{ matrix.operating-system }} strategy: @@ -183,6 +204,31 @@ jobs: shell: pwsh run: __tests__/verify-dotnet.ps1 -Patterns "^2.2", "^3.1" + test-setup-global-json-only: + 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: Write global.json + shell: bash + run: | + mkdir subdirectory + echo '{"sdk":{"version": "2.2.207","rollForward": "latestFeature"}}' > ./subdirectory/global.json + - name: Setup dotnet + uses: ./ + with: + global-json-file: ./subdirectory/global.json + - name: Verify dotnet + shell: pwsh + run: __tests__/verify-dotnet.ps1 -Patterns "^2.2" + test-setup-with-dotnet-quality: runs-on: ${{ matrix.operating-system }} strategy: diff --git a/README.md b/README.md index 01e70f1..99a5a51 100644 --- a/README.md +++ b/README.md @@ -49,12 +49,13 @@ The `dotnet-version` input supports following syntax: - **A.B.C** (e.g 6.0.400, 7.0.100-preview.7.22377.5) - installs exact version of .NET SDK - **A.B** or **A.B.x** (e.g. 3.1, 3.1.x) - installs the latest patch version of .NET SDK on the channel `3.1`, including prerelease versions (preview, rc) - **A** or **A.x** (e.g. 3, 3.x) - installs the latest minor version of the specified major tag, including prerelease versions (preview, rc) +- **A.B.Cxx** (e.g. 6.0.4xx) - available since `.NET 5.0` release. Installs the latest version of the specific SDK release, including prerelease versions (preview, rc). ## Using the `dotnet-quality` input This input sets up the action to install the latest build of the specified quality in the channel. The possible values of `dotnet-quality` are: **daily**, **signed**, **validated**, **preview**, **ga**. -> **Note**: `dotnet-quality` input can be used only with .NET SDK version in 'A.B', 'A.B.x', 'A' and 'A.x' formats where the major version is higher than 5. In other cases, `dotnet-quality` input will be ignored. +> **Note**: `dotnet-quality` input can be used only with .NET SDK version in 'A.B', 'A.B.x', 'A', 'A.x' and 'A.B.Cxx' formats where the major version is higher than 5. In other cases, `dotnet-quality` input will be ignored. ```yml steps: diff --git a/__tests__/installation-scripts.test.ts b/__tests__/installation-scripts.test.ts index e309a98..ce71ea7 100644 --- a/__tests__/installation-scripts.test.ts +++ b/__tests__/installation-scripts.test.ts @@ -2,46 +2,59 @@ import path from 'path'; import fs from 'fs'; import * as hc from '@actions/http-client'; -describe('Dotnet installation scripts tests', () => { - it('Uses an up to date bash download script', async () => { - const httpCallbackClient = new hc.HttpClient('setup-dotnet-test', [], { - allowRetries: true, - maxRetries: 3 - }); - const response: hc.HttpClientResponse = await httpCallbackClient.get( - 'https://dot.net/v1/dotnet-install.sh' - ); - expect(response.message.statusCode).toBe(200); - const upToDateContents: string = await response.readBody(); - const currentContents: string = fs - .readFileSync( - path.join(__dirname, '..', 'externals', 'install-dotnet.sh') - ) - .toString(); - expect(normalizeFileContents(currentContents)).toBe( - normalizeFileContents(upToDateContents) - ); - }, 30000); +const HTTP_CLIENT_OPTIONS = {allowRetries: true, maxRetries: 10} as const; +const TEST_TIMEOUT = 30000; - it('Uses an up to date powershell download script', async () => { - const httpCallbackClient = new hc.HttpClient('setup-dotnet-test', [], { - allowRetries: true, - maxRetries: 3 - }); - const response: hc.HttpClientResponse = await httpCallbackClient.get( - 'https://dot.net/v1/dotnet-install.ps1' - ); - expect(response.message.statusCode).toBe(200); - const upToDateContents: string = await response.readBody(); - const currentContents: string = fs - .readFileSync( - path.join(__dirname, '..', 'externals', 'install-dotnet.ps1') - ) - .toString(); - expect(normalizeFileContents(currentContents)).toBe( - normalizeFileContents(upToDateContents) - ); - }, 30000); +describe('Dotnet installation scripts tests', () => { + it( + 'Uses an up to date bash download script', + async () => { + const httpCallbackClient = new hc.HttpClient( + 'setup-dotnet-test', + [], + HTTP_CLIENT_OPTIONS + ); + const response: hc.HttpClientResponse = await httpCallbackClient.get( + 'https://dot.net/v1/dotnet-install.sh' + ); + expect(response.message.statusCode).toBe(200); + const upToDateContents: string = await response.readBody(); + const currentContents: string = fs + .readFileSync( + path.join(__dirname, '..', 'externals', 'install-dotnet.sh') + ) + .toString(); + expect(normalizeFileContents(currentContents)).toBe( + normalizeFileContents(upToDateContents) + ); + }, + TEST_TIMEOUT + ); + + it( + 'Uses an up to date powershell download script', + async () => { + const httpCallbackClient = new hc.HttpClient( + 'setup-dotnet-test', + [], + HTTP_CLIENT_OPTIONS + ); + const response: hc.HttpClientResponse = await httpCallbackClient.get( + 'https://dot.net/v1/dotnet-install.ps1' + ); + expect(response.message.statusCode).toBe(200); + const upToDateContents: string = await response.readBody(); + const currentContents: string = fs + .readFileSync( + path.join(__dirname, '..', 'externals', 'install-dotnet.ps1') + ) + .toString(); + expect(normalizeFileContents(currentContents)).toBe( + normalizeFileContents(upToDateContents) + ); + }, + TEST_TIMEOUT + ); }); function normalizeFileContents(contents: string): string { diff --git a/__tests__/installer.test.ts b/__tests__/installer.test.ts index f92f74a..013bcce 100644 --- a/__tests__/installer.test.ts +++ b/__tests__/installer.test.ts @@ -62,8 +62,13 @@ describe('installer tests', () => { it('should return version of .NET SDK after installation complete', async () => { const inputVersion = '3.1.100'; const inputQuality = '' as QualityOptions; + const stdout = `Fictitious dotnet version ${inputVersion} is installed`; getExecOutputSpy.mockImplementation(() => { - return Promise.resolve({exitCode: 0, stdout: '', stderr: ''}); + return Promise.resolve({ + exitCode: 0, + stdout: `${stdout}`, + stderr: '' + }); }); maxSatisfyingSpy.mockImplementation(() => inputVersion); @@ -79,9 +84,14 @@ describe('installer tests', () => { it(`should supply 'version' argument to the installation script if supplied version is in A.B.C syntax`, async () => { const inputVersion = '6.0.300'; const inputQuality = '' as QualityOptions; + const stdout = `Fictitious dotnet version ${inputVersion} is installed`; getExecOutputSpy.mockImplementation(() => { - return Promise.resolve({exitCode: 0, stdout: '', stderr: ''}); + return Promise.resolve({ + exitCode: 0, + stdout: `${stdout}`, + stderr: '' + }); }); maxSatisfyingSpy.mockImplementation(() => inputVersion); @@ -105,9 +115,13 @@ describe('installer tests', () => { it(`should warn if the 'quality' input is set and the supplied version is in A.B.C syntax`, async () => { const inputVersion = '6.0.300'; const inputQuality = 'ga' as QualityOptions; - + const stdout = `Fictitious dotnet version ${inputVersion} is installed`; getExecOutputSpy.mockImplementation(() => { - return Promise.resolve({exitCode: 0, stdout: '', stderr: ''}); + return Promise.resolve({ + exitCode: 0, + stdout: `${stdout}`, + stderr: '' + }); }); maxSatisfyingSpy.mockImplementation(() => inputVersion); @@ -119,16 +133,21 @@ describe('installer tests', () => { await dotnetInstaller.installDotnet(); expect(warningSpy).toHaveBeenCalledWith( - `'dotnet-quality' input can be used only with .NET SDK version in A.B, A.B.x, A and A.x formats where the major tag is higher than 5. You specified: ${inputVersion}. 'dotnet-quality' input is ignored.` + `The 'dotnet-quality' input can be used only with .NET SDK version in A.B, A.B.x, A, A.x and A.B.Cxx formats where the major tag is higher than 5. You specified: ${inputVersion}. 'dotnet-quality' input is ignored.` ); }); it(`should warn if the 'quality' input is set and version isn't in A.B.C syntax but major tag is lower then 6`, async () => { const inputVersion = '3.1'; const inputQuality = 'ga' as QualityOptions; + const stdout = `Fictitious dotnet version 3.1.100 is installed`; getExecOutputSpy.mockImplementation(() => { - return Promise.resolve({exitCode: 0, stdout: '', stderr: ''}); + return Promise.resolve({ + exitCode: 0, + stdout: `${stdout}`, + stderr: '' + }); }); maxSatisfyingSpy.mockImplementation(() => inputVersion); @@ -140,7 +159,7 @@ describe('installer tests', () => { await dotnetInstaller.installDotnet(); expect(warningSpy).toHaveBeenCalledWith( - `'dotnet-quality' input can be used only with .NET SDK version in A.B, A.B.x, A and A.x formats where the major tag is higher than 5. You specified: ${inputVersion}. 'dotnet-quality' input is ignored.` + `The 'dotnet-quality' input can be used only with .NET SDK version in A.B, A.B.x, A, A.x and A.B.Cxx formats where the major tag is higher than 5. You specified: ${inputVersion}. 'dotnet-quality' input is ignored.` ); }); @@ -149,10 +168,11 @@ describe('installer tests', () => { async inputVersion => { const inputQuality = 'ga' as QualityOptions; const exitCode = 0; + const stdout = `Fictitious dotnet version 6.0.0 is installed`; getExecOutputSpy.mockImplementation(() => { return Promise.resolve({ exitCode: exitCode, - stdout: '', + stdout: `${stdout}`, stderr: '' }); }); @@ -181,10 +201,11 @@ describe('installer tests', () => { async inputVersion => { const inputQuality = '' as QualityOptions; const exitCode = 0; + const stdout = `Fictitious dotnet version 6.0.0 is installed`; getExecOutputSpy.mockImplementation(() => { return Promise.resolve({ exitCode: exitCode, - stdout: '', + stdout: `${stdout}`, stderr: '' }); }); @@ -213,9 +234,14 @@ describe('installer tests', () => { process.env['https_proxy'] = 'https://proxy.com'; const inputVersion = '6.0.100'; const inputQuality = '' as QualityOptions; + const stdout = `Fictitious dotnet version ${inputVersion} is installed`; getExecOutputSpy.mockImplementation(() => { - return Promise.resolve({exitCode: 0, stdout: '', stderr: ''}); + return Promise.resolve({ + exitCode: 0, + stdout: `${stdout}`, + stderr: '' + }); }); maxSatisfyingSpy.mockImplementation(() => inputVersion); @@ -239,9 +265,14 @@ describe('installer tests', () => { process.env['no_proxy'] = 'first.url,second.url'; const inputVersion = '6.0.100'; const inputQuality = '' as QualityOptions; + const stdout = `Fictitious dotnet version 6.0.0 is installed`; getExecOutputSpy.mockImplementation(() => { - return Promise.resolve({exitCode: 0, stdout: '', stderr: ''}); + return Promise.resolve({ + exitCode: 0, + stdout: `${stdout}`, + stderr: '' + }); }); maxSatisfyingSpy.mockImplementation(() => inputVersion); @@ -289,7 +320,8 @@ describe('installer tests', () => { '3.1.*', '3.1.X', '3.1.2', - '3.1.0-preview1' + '3.1.0-preview1', + '6.0.2xx' ]).test( 'if valid version is supplied (%s), it should return version object with some value', async version => { @@ -341,7 +373,7 @@ describe('installer tests', () => { } ); - each(['3', '3.1', '3.1.x', '3.1.*', '3.1.X']).test( + each(['3', '3.1', '3.1.x', '3.1.*', '3.1.X', '6.0.2xx']).test( "if version that can be resolved to 'channel' option is supplied (%s), it should set type to 'channel' in version object", async version => { const dotnetVersionResolver = new installer.DotnetVersionResolver( @@ -356,7 +388,7 @@ describe('installer tests', () => { } ); - each(['6.0', '6.0.x', '6.0.*', '6.0.X']).test( + each(['6.0', '6.0.x', '6.0.*', '6.0.X', '6.0.2xx']).test( "if version that can be resolved to 'channel' option is supplied and its major tag is >= 6 (%s), it should set type to 'channel' and qualityFlag to 'true' in version object", async version => { const dotnetVersionResolver = new installer.DotnetVersionResolver( @@ -408,6 +440,18 @@ describe('installer tests', () => { } } ); + + it(`should throw if dotnet-version is supplied in A.B.Cxx syntax with major tag lower that 5`, async () => { + const version = '3.0.1xx'; + const dotnetVersionResolver = new installer.DotnetVersionResolver( + version + ); + await expect( + async () => await dotnetVersionResolver.createDotNetVersion() + ).rejects.toThrow( + `'dotnet-version' was supplied in invalid format: ${version}! The A.B.Cxx syntax is available since the .NET 5.0 release.` + ); + }); }); }); }); diff --git a/__tests__/setup-dotnet.test.ts b/__tests__/setup-dotnet.test.ts index fb1ebda..568ff45 100644 --- a/__tests__/setup-dotnet.test.ts +++ b/__tests__/setup-dotnet.test.ts @@ -15,6 +15,7 @@ describe('setup-dotnet tests', () => { const getMultilineInputSpy = jest.spyOn(core, 'getMultilineInput'); const getBooleanInputSpy = jest.spyOn(core, 'getBooleanInput'); const setFailedSpy = jest.spyOn(core, 'setFailed'); + const warningSpy = jest.spyOn(core, 'warning'); const debugSpy = jest.spyOn(core, 'debug'); const infoSpy = jest.spyOn(core, 'info'); const setOutputSpy = jest.spyOn(core, 'setOutput'); @@ -66,7 +67,7 @@ describe('setup-dotnet tests', () => { const expectedDebugMessage = 'No version found, trying to find version from global.json'; - const expectedInfoMessage = `global.json wasn't found in the root directory. No .NET version will be installed.`; + const expectedInfoMessage = `The global.json wasn't found in the root directory. No .NET version will be installed.`; await setup.run(); @@ -80,7 +81,7 @@ describe('setup-dotnet tests', () => { inputs['dotnet-version'] = ['6.0']; inputs['dotnet-quality'] = 'fictitiousQuality'; - const expectedErrorMessage = `${inputs['dotnet-quality']} is not a supported value for 'dotnet-quality' option. Supported values are: daily, signed, validated, preview, ga.`; + const expectedErrorMessage = `Value '${inputs['dotnet-quality']}' is not supported for the 'dotnet-quality' option. Supported values are: daily, signed, validated, preview, ga.`; await setup.run(); expect(setFailedSpy).toHaveBeenCalledWith(expectedErrorMessage); @@ -141,16 +142,42 @@ describe('setup-dotnet tests', () => { ); }); - it('should call setOutput() after installation complete', async () => { + it('should call setOutput() after installation complete successfully', async () => { inputs['dotnet-version'] = ['6.0.300']; - installDotnetSpy.mockImplementation(() => Promise.resolve('')); + installDotnetSpy.mockImplementation(() => + Promise.resolve(`${inputs['dotnet-version']}`) + ); addToPathSpy.mockImplementation(() => {}); await setup.run(); expect(setOutputSpy).toHaveBeenCalledTimes(1); }); + it(`shouldn't call setOutput() if parsing dotnet-installer logs failed`, async () => { + inputs['dotnet-version'] = ['6.0.300']; + const warningMessage = `Failed to output the installed version of .NET. The 'dotnet-version' output will not be set.`; + + installDotnetSpy.mockImplementation(() => Promise.resolve(null)); + addToPathSpy.mockImplementation(() => {}); + + await setup.run(); + expect(warningSpy).toHaveBeenCalledWith(warningMessage); + expect(setOutputSpy).not.toHaveBeenCalled(); + }); + + it(`shouldn't call setOutput() if actions didn't install .NET`, async () => { + inputs['dotnet-version'] = []; + const warningMessage = `The 'dotnet-version' output will not be set.`; + + addToPathSpy.mockImplementation(() => {}); + + await setup.run(); + + expect(infoSpy).toHaveBeenCalledWith(warningMessage); + expect(setOutputSpy).not.toHaveBeenCalled(); + }); + it(`should get 'cache-dependency-path' and call restoreCache() if input cache is set to true and cache feature is available`, async () => { inputs['dotnet-version'] = ['6.0.300']; inputs['dotnet-quality'] = ''; diff --git a/action.yml b/action.yml index 5614f00..bf24aca 100644 --- a/action.yml +++ b/action.yml @@ -6,7 +6,7 @@ branding: color: green inputs: dotnet-version: - 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' + 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.' global-json-file: diff --git a/src/installer.ts b/src/installer.ts index 8dc26bc..2a9db2c 100644 --- a/src/installer.ts +++ b/src/installer.ts @@ -4,7 +4,6 @@ import * as exec from '@actions/exec'; import * as io from '@actions/io'; import * as hc from '@actions/http-client'; import {chmodSync} from 'fs'; -import {readdir} from 'fs/promises'; import path from 'path'; import os from 'os'; import semver from 'semver'; @@ -17,6 +16,8 @@ export interface DotnetVersion { qualityFlag: boolean; } +const QUALITY_INPUT_MINIMAL_MAJOR_TAG = 6; +const LATEST_PATCH_SYNTAX_MINIMAL_MAJOR_TAG = 5; export class DotnetVersionResolver { private inputVersion: string; private resolvedArgument: DotnetVersion; @@ -27,33 +28,15 @@ export class DotnetVersionResolver { } private async resolveVersionInput(): Promise { - if (!semver.validRange(this.inputVersion)) { + if (!semver.validRange(this.inputVersion) && !this.isLatestPatchSyntax()) { throw new Error( - `'dotnet-version' was supplied in invalid format: ${this.inputVersion}! Supported syntax: A.B.C, A.B, A.B.x, A, A.x` + `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.resolvedArgument.type = 'version'; - this.resolvedArgument.value = this.inputVersion; + this.createVersionArgument(); } else { - const [major, minor] = this.inputVersion.split('.'); - - if (this.isNumericTag(major)) { - this.resolvedArgument.type = 'channel'; - if (this.isNumericTag(minor)) { - this.resolvedArgument.value = `${major}.${minor}`; - } else { - const httpClient = new hc.HttpClient('actions/setup-dotnet', [], { - allowRetries: true, - maxRetries: 3 - }); - this.resolvedArgument.value = await this.getLatestVersion( - httpClient, - [major, minor] - ); - } - } - this.resolvedArgument.qualityFlag = +major >= 6 ? true : false; + await this.createChannelArgument(); } } @@ -61,11 +44,44 @@ export class DotnetVersionResolver { return /^\d+$/.test(versionTag); } - public async createDotNetVersion(): Promise<{ - type: string; - value: string; - qualityFlag: boolean; - }> { + private isLatestPatchSyntax() { + const majorTag = this.inputVersion.match( + /^(?\d+)\.\d+\.\d{1}x{2}$/ + )?.groups?.majorTag; + if ( + majorTag && + parseInt(majorTag) < LATEST_PATCH_SYNTAX_MINIMAL_MAJOR_TAG + ) { + throw new Error( + `The 'dotnet-version' was supplied in invalid format: ${this.inputVersion}! The A.B.Cxx syntax is available since the .NET 5.0 release.` + ); + } + return majorTag ? true : false; + } + + private createVersionArgument() { + this.resolvedArgument.type = 'version'; + this.resolvedArgument.value = this.inputVersion; + } + + private async createChannelArgument() { + this.resolvedArgument.type = 'channel'; + const [major, minor] = this.inputVersion.split('.'); + if (this.isLatestPatchSyntax()) { + this.resolvedArgument.value = this.inputVersion; + } else if (this.isNumericTag(major) && this.isNumericTag(minor)) { + this.resolvedArgument.value = `${major}.${minor}`; + } else if (this.isNumericTag(major)) { + this.resolvedArgument.value = await this.getLatestByMajorTag(major); + } else { + // If "dotnet-version" is specified as *, x or X resolve latest version of .NET explicitly from LTS channel. The version argument will default to "latest" by install-dotnet script. + this.resolvedArgument.value = 'LTS'; + } + this.resolvedArgument.qualityFlag = + parseInt(major) >= QUALITY_INPUT_MINIMAL_MAJOR_TAG ? true : false; + } + + public async createDotNetVersion(): Promise { await this.resolveVersionInput(); if (!this.resolvedArgument.type) { return this.resolvedArgument; @@ -80,10 +96,11 @@ export class DotnetVersionResolver { return this.resolvedArgument; } - private async getLatestVersion( - httpClient: hc.HttpClient, - versionParts: string[] - ): Promise { + private async getLatestByMajorTag(majorTag: string): Promise { + const httpClient = new hc.HttpClient('actions/setup-dotnet', [], { + allowRetries: true, + maxRetries: 3 + }); const response = await httpClient.getJson( DotnetVersionResolver.DotNetCoreIndexUrl ); @@ -92,14 +109,12 @@ export class DotnetVersionResolver { const releaseInfo = releasesInfo.find(info => { const sdkParts: string[] = info['channel-version'].split('.'); - return sdkParts[0] === versionParts[0]; + return sdkParts[0] === majorTag; }); if (!releaseInfo) { throw new Error( - `Could not find info for version ${versionParts.join('.')} at ${ - DotnetVersionResolver.DotNetCoreIndexUrl - }` + `Could not find info for version with major tag: "${majorTag}" at ${DotnetVersionResolver.DotNetCoreIndexUrl}` ); } @@ -171,12 +186,12 @@ export class DotnetCoreInstaller { scriptArguments.push(option, this.quality); } else { core.warning( - `'dotnet-quality' input can be used only with .NET SDK version in A.B, A.B.x, A and A.x formats where the major tag is higher than 5. You specified: ${this.version}. 'dotnet-quality' input is ignored.` + `The 'dotnet-quality' input can be used only with .NET SDK version in A.B, A.B.x, A, A.x and A.B.Cxx formats where the major tag is higher than 5. You specified: ${this.version}. 'dotnet-quality' input is ignored.` ); } } - public async installDotnet(): Promise { + public async installDotnet(): Promise { const windowsDefaultOptions = [ '-NoLogo', '-Sta', @@ -236,7 +251,7 @@ export class DotnetCoreInstaller { ignoreReturnCode: true, env: process.env as {string: string} }; - const {exitCode, stderr} = await exec.getExecOutput( + const {exitCode, stdout, stderr} = await exec.getExecOutput( `"${scriptPath}"`, scriptArguments, getExecOutputOptions @@ -247,19 +262,17 @@ export class DotnetCoreInstaller { ); } - return this.outputDotnetVersion(dotnetVersion.value); + return this.parseInstalledVersion(stdout); } - private async outputDotnetVersion(version): Promise { - const installationPath = process.env['DOTNET_INSTALL_DIR']!; - const versionsOnRunner: string[] = await readdir( - path.join(installationPath.replace(/'/g, ''), 'sdk') - ); + private parseInstalledVersion(stdout: string): string | null { + const regex = /(?\d+\.\d+\.\d+[a-z0-9._-]*)/gm; + const matchedResult = regex.exec(stdout); - const installedVersion = semver.maxSatisfying(versionsOnRunner, version, { - includePrerelease: true - })!; - - return installedVersion; + if (!matchedResult) { + core.warning(`Failed to parse installed by the script version of .NET`); + return null; + } + return matchedResult.groups!.version; } } diff --git a/src/setup-dotnet.ts b/src/setup-dotnet.ts index e8a1076..4ac6451 100644 --- a/src/setup-dotnet.ts +++ b/src/setup-dotnet.ts @@ -30,7 +30,7 @@ export async function run() { // Proxy, auth, (etc) are still set up, even if no version is identified // const versions = core.getMultilineInput('dotnet-version'); - const installedDotnetVersions: string[] = []; + const installedDotnetVersions: (string | null)[] = []; const globalJsonFileInput = core.getInput('global-json-file'); if (globalJsonFileInput) { @@ -51,7 +51,7 @@ export async function run() { versions.push(getVersionFromGlobalJson(globalJsonPath)); } else { core.info( - `global.json wasn't found in the root directory. No .NET version will be installed.` + `The global.json wasn't found in the root directory. No .NET version will be installed.` ); } } @@ -61,7 +61,7 @@ export async function run() { if (quality && !qualityOptions.includes(quality)) { throw new Error( - `${quality} is not a supported value for 'dotnet-quality' option. Supported values are: daily, signed, validated, preview, ga.` + `Value '${quality}' is not supported for the 'dotnet-quality' option. Supported values are: daily, signed, validated, preview, ga.` ); } @@ -81,19 +81,7 @@ export async function run() { auth.configAuthentication(sourceUrl, configFile); } - const comparisonRange: string = globalJsonFileInput - ? versions[versions.length - 1]! - : '*'; - - const versionToOutput = semver.maxSatisfying( - installedDotnetVersions, - comparisonRange, - { - includePrerelease: true - } - ); - - core.setOutput(Outputs.DotnetVersion, versionToOutput); + outputInstalledVersion(installedDotnetVersions, globalJsonFileInput); if (core.getBooleanInput('cache') && isCacheFeatureAvailable()) { const cacheDependencyPath = core.getInput('cache-dependency-path'); @@ -124,4 +112,37 @@ function getVersionFromGlobalJson(globalJsonPath: string): string { return version; } +function outputInstalledVersion( + installedVersions: (string | null)[], + globalJsonFileInput: string +): void { + if (!installedVersions.length) { + core.info(`The '${Outputs.DotnetVersion}' output will not be set.`); + return; + } + + if (installedVersions.includes(null)) { + core.warning( + `Failed to output the installed version of .NET. The '${Outputs.DotnetVersion}' output will not be set.` + ); + return; + } + + if (globalJsonFileInput) { + const versionToOutput = installedVersions.at(-1); // .NET SDK version parsed from the global.json file is installed last + core.setOutput(Outputs.DotnetVersion, versionToOutput); + return; + } + + const versionToOutput = semver.maxSatisfying( + installedVersions as string[], + '*', + { + includePrerelease: true + } + ); + + core.setOutput(Outputs.DotnetVersion, versionToOutput); +} + run();