diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index dc6f3e8..d670813 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -257,81 +257,6 @@ jobs: shell: pwsh run: __tests__/verify-dotnet.ps1 -Patterns "^9.0", "^10.0" - test-setup-global-json-rollforward-latestmajor: - runs-on: ${{ matrix.operating-system }} - strategy: - fail-fast: false - matrix: - operating-system: [ubuntu-latest, windows-latest, macos-latest] - steps: - - name: Checkout - uses: actions/checkout@v6 - - name: Clear toolcache - shell: pwsh - run: __tests__/clear-toolcache.ps1 ${{ runner.os }} - - name: Write global.json - shell: bash - run: | - mkdir subdirectory - echo '{"sdk":{"version": "3.1.0","rollForward": "latestMajor"}}' > ./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 "^(?!3)" - - test-setup-global-json-rollforward-latestfeature: - runs-on: ${{ matrix.operating-system }} - strategy: - fail-fast: false - matrix: - operating-system: [ubuntu-latest, windows-latest, macos-latest] - steps: - - name: Checkout - uses: actions/checkout@v6 - - name: Clear toolcache - shell: pwsh - run: __tests__/clear-toolcache.ps1 ${{ runner.os }} - - name: Write global.json - shell: bash - run: | - mkdir subdirectory - echo '{"sdk":{"version": "10.0.100","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 "^10.0.(?!1)" - - test-setup-global-json-rollforward-latestpatch: - runs-on: ${{ matrix.operating-system }} - strategy: - fail-fast: false - matrix: - operating-system: [ubuntu-latest, windows-latest, macos-latest] - steps: - - name: Checkout - uses: actions/checkout@v6 - - name: Clear toolcache - shell: pwsh - run: __tests__/clear-toolcache.ps1 ${{ runner.os }} - - name: Write global.json - shell: bash - run: | - mkdir subdirectory - echo '{"sdk":{"version": "10.0.100","rollForward": "latestPatch"}}' > ./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 "^10.0.1(?!00)" - test-setup-global-json-only: runs-on: ${{ matrix.operating-system }} strategy: @@ -577,11 +502,6 @@ jobs: image: ubuntu/squid:latest ports: - 3128:3128 - options: >- - --health-cmd "bash -c ' **Note**: The `dotnet-channel` input is only applied when `dotnet-version` is set to `latest`. If used with a specific version, a warning will be logged and the channel input will be ignored. - -**Install latest LTS version:** -```yaml -steps: -- uses: actions/checkout@v6 -- uses: actions/setup-dotnet@v5 - with: - dotnet-version: latest - dotnet-channel: LTS -``` ## Using the `architecture` input Using the architecture input, it is possible to specify the required .NET SDK architecture. Possible values: `x64`, `x86`, `arm64`, `amd64`, `arm`, `s390x`, `ppc64le`, `riscv64`. If the input is not specified, the architecture defaults to the host OS architecture (not all of the architectures are available on all platforms). @@ -100,10 +77,9 @@ steps: ``` ## 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**. -The `dotnet-quality` input installs the latest build of the specified quality in the channel. Supported values: `daily`, `preview`, `ga`. - -> **Note**: When used with a specific SDK version, `dotnet-quality` supports only `A.B`, `A.B.x`, `A`, `A.x`, and `A.B.Cxx` formats where the major version is higher than 5. For all other formats, `dotnet-quality` 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: @@ -115,18 +91,6 @@ steps: - run: dotnet build ``` -`dotnet-quality` can also be combined with `dotnet-version: latest` and `dotnet-channel` to target specific builds such as the latest `daily` build from the `LTS` channel. - -```yaml -steps: -- uses: actions/checkout@v6 -- uses: actions/setup-dotnet@v5 - with: - dotnet-version: latest - dotnet-channel: LTS - dotnet-quality: daily -``` - ## Using the `global-json-file` input `setup-dotnet` action can read .NET SDK version from a `global.json` file. Input `global-json-file` is used for specifying the path to the `global.json`. If the file that was supplied to `global-json-file` input doesn't exist, the action will fail with error. @@ -142,8 +106,6 @@ steps: working-directory: csharp ``` -> **Note**: The action supports `latest*` variants of the [rollForward](https://learn.microsoft.com/en-us/dotnet/core/tools/global-json#rollforward) field in `global.json`. When set to `latestPatch`, `latestFeature`, `latestMinor`, or `latestMajor`, the action installs the appropriate SDK version. - ## Caching NuGet Packages The action has a built-in functionality for caching and restoring dependencies. It uses [toolkit/cache](https://github.com/actions/toolkit/tree/main/packages/cache) under the hood for caching global packages data but requires less configuration settings. The `cache` input is optional, and caching is turned off by default. @@ -409,4 +371,4 @@ The scripts and documentation in this project are released under the [MIT Licens ## Contributions -Contributions are welcome! See [Contributor's Guide](docs/contributors.md) \ No newline at end of file +Contributions are welcome! See [Contributor's Guide](docs/contributors.md) diff --git a/__tests__/clear-toolcache.ps1 b/__tests__/clear-toolcache.ps1 index a8bd902..5589ec1 100644 --- a/__tests__/clear-toolcache.ps1 +++ b/__tests__/clear-toolcache.ps1 @@ -6,8 +6,8 @@ $dotnetPaths = @{ foreach ($srcPath in $dotnetPaths[$args[0]]) { if (Test-Path $srcPath) { - $dstPath = "$srcPath-" + [IO.Path]::GetRandomFileName() - Write-Host "Moving $srcPath to $dstPath" + Write-Host "Move $srcPath path" + $dstPath = Join-Path ([IO.Path]::GetTempPath()) ([IO.Path]::GetRandomFileName()) Move-Item -Path $srcPath -Destination $dstPath } } \ No newline at end of file diff --git a/__tests__/installer.test.ts b/__tests__/installer.test.ts index 851c327..11a57be 100644 --- a/__tests__/installer.test.ts +++ b/__tests__/installer.test.ts @@ -9,6 +9,7 @@ import * as io from '@actions/io'; import * as installer from '../src/installer'; import {IS_WINDOWS} from '../src/utils'; +import {QualityOptions} from '../src/setup-dotnet'; describe('installer tests', () => { const env = process.env; @@ -39,7 +40,7 @@ describe('installer tests', () => { it('should throw the error in case of non-zero exit code of the installation script. The error message should contain logs.', async () => { const inputVersion = '10.0.101'; - const inputQuality = ''; + const inputQuality = '' as QualityOptions; const errorMessage = 'fictitious error message!'; getExecOutputSpy.mockImplementation(() => { @@ -61,7 +62,7 @@ describe('installer tests', () => { it('should return version of .NET SDK after installation complete', async () => { const inputVersion = '10.0.101'; - const inputQuality = ''; + const inputQuality = '' as QualityOptions; const stdout = `Fictitious dotnet version ${inputVersion} is installed`; getExecOutputSpy.mockImplementation(() => { return Promise.resolve({ @@ -83,7 +84,7 @@ describe('installer tests', () => { it(`should supply 'version' argument to the installation script if supplied version is in A.B.C syntax`, async () => { const inputVersion = '10.0.101'; - const inputQuality = ''; + const inputQuality = '' as QualityOptions; const stdout = `Fictitious dotnet version ${inputVersion} is installed`; getExecOutputSpy.mockImplementation(() => { @@ -121,7 +122,7 @@ 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 = '10.0.101'; - const inputQuality = 'ga'; + const inputQuality = 'ga' as QualityOptions; const stdout = `Fictitious dotnet version ${inputVersion} is installed`; getExecOutputSpy.mockImplementation(() => { return Promise.resolve({ @@ -146,7 +147,7 @@ describe('installer tests', () => { 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'; + const inputQuality = 'ga' as QualityOptions; const stdout = `Fictitious dotnet version ${inputVersion} is installed`; getExecOutputSpy.mockImplementation(() => { @@ -173,7 +174,7 @@ describe('installer tests', () => { each(['10', '10.0', '10.0.x', '10.0.*', '10.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 inputQuality = 'ga'; + const inputQuality = 'ga' as QualityOptions; const exitCode = 0; const stdout = `Fictitious dotnet version ${inputVersion} is installed`; getExecOutputSpy.mockImplementation(() => { @@ -213,7 +214,7 @@ describe('installer tests', () => { each(['10', '10.0', '10.0.x', '10.0.*', '10.0.X']).test( `should supply 'channel' argument to the installation script if version (%s) isn't in A.B.C syntax`, async inputVersion => { - const inputQuality = ''; + const inputQuality = '' as QualityOptions; const exitCode = 0; const stdout = `Fictitious dotnet version ${inputVersion} is installed`; getExecOutputSpy.mockImplementation(() => { @@ -254,7 +255,7 @@ describe('installer tests', () => { it(`should supply '-ProxyAddress' argument to the installation script if env.variable 'https_proxy' is set`, async () => { process.env['https_proxy'] = 'https://proxy.com'; const inputVersion = '10.0.101'; - const inputQuality = ''; + const inputQuality = '' as QualityOptions; const stdout = `Fictitious dotnet version ${inputVersion} is installed`; getExecOutputSpy.mockImplementation(() => { @@ -292,7 +293,7 @@ describe('installer tests', () => { it(`should supply '-ProxyBypassList' argument to the installation script if env.variable 'no_proxy' is set`, async () => { process.env['no_proxy'] = 'first.url,second.url'; const inputVersion = '10.0.101'; - const inputQuality = ''; + const inputQuality = '' as QualityOptions; const stdout = `Fictitious dotnet version ${inputVersion} is installed`; getExecOutputSpy.mockImplementation(() => { @@ -330,7 +331,7 @@ describe('installer tests', () => { it(`should supply 'architecture' argument to the installation script when architecture is provided`, async () => { const inputVersion = '10.0.101'; - const inputQuality = ''; + const inputQuality = '' as QualityOptions; const inputArchitecture = 'x64'; const stdout = `Fictitious dotnet version ${inputVersion} is installed`; @@ -364,7 +365,7 @@ describe('installer tests', () => { it(`should NOT supply 'architecture' argument when architecture is not provided`, async () => { const inputVersion = '10.0.101'; - const inputQuality = ''; + const inputQuality = '' as QualityOptions; const stdout = `Fictitious dotnet version ${inputVersion} is installed`; getExecOutputSpy.mockImplementation(() => { @@ -394,7 +395,7 @@ describe('installer tests', () => { it(`should supply 'install-dir' with arch subdirectory for cross-arch install`, async () => { const inputVersion = '10.0.101'; - const inputQuality = ''; + const inputQuality = '' as QualityOptions; const inputArchitecture = 'x64'; const stdout = `Fictitious dotnet version ${inputVersion} is installed`; @@ -435,7 +436,7 @@ describe('installer tests', () => { it(`should NOT supply 'install-dir' when architecture matches runner's native arch`, async () => { const inputVersion = '10.0.101'; - const inputQuality = ''; + const inputQuality = '' as QualityOptions; const nativeArch = os.arch().toLowerCase(); const stdout = `Fictitious dotnet version ${inputVersion} is installed`; diff --git a/__tests__/latest-version.test.ts b/__tests__/latest-version.test.ts deleted file mode 100644 index e5e637a..0000000 --- a/__tests__/latest-version.test.ts +++ /dev/null @@ -1,223 +0,0 @@ -import {DotnetVersionResolver} from '../src/installer'; -import * as hc from '@actions/http-client'; -import * as core from '@actions/core'; - -// Mock http-client -jest.mock('@actions/http-client'); - -describe('DotnetVersionResolver with latest', () => { - let getJsonMock: jest.Mock; - let warningSpy: jest.SpyInstance; - - beforeEach(() => { - getJsonMock = jest.fn(); - (hc.HttpClient as any).mockImplementation(() => { - return { - getJson: getJsonMock - }; - }); - warningSpy = jest.spyOn(core, 'warning').mockImplementation(() => {}); - }); - - afterEach(() => { - jest.clearAllMocks(); - jest.restoreAllMocks(); - }); - - const mockReleases = { - 'releases-index': [ - { - 'channel-version': '10.0', - 'support-phase': 'preview', - 'release-type': 'lts' - }, - { - 'channel-version': '9.0', - 'support-phase': 'active', - 'release-type': 'sts' - }, - { - 'channel-version': '8.0', - 'support-phase': 'active', - 'release-type': 'lts' - }, - { - 'channel-version': '7.0', - 'support-phase': 'eol', - 'release-type': 'sts' - } - ] - }; - - it('should resolve "latest" to highest stable version by default', async () => { - getJsonMock.mockResolvedValue({result: mockReleases}); - - const resolver = new DotnetVersionResolver('latest'); - const version = await resolver.createDotnetVersion(); - - expect(version.value).toBe('9.0'); - expect(version.type.toLowerCase()).toContain('channel'); - expect(version.qualityFlag).toBe(true); - }); - - it('should resolve "LATEST" (uppercase) to highest stable version', async () => { - getJsonMock.mockResolvedValue({result: mockReleases}); - - const resolver = new DotnetVersionResolver('LATEST'); - const version = await resolver.createDotnetVersion(); - - expect(version.value).toBe('9.0'); - expect(version.type.toLowerCase()).toContain('channel'); - expect(version.qualityFlag).toBe(true); - }); - - it('should resolve "latest" to highest preview version if quality is preview', async () => { - getJsonMock.mockResolvedValue({result: mockReleases}); - - const resolver = new DotnetVersionResolver('latest', 'preview'); - const version = await resolver.createDotnetVersion(); - - expect(version.value).toBe('10.0'); - }); - - it('should resolve "latest" with channel filter LTS', async () => { - getJsonMock.mockResolvedValue({result: mockReleases}); - - const resolver = new DotnetVersionResolver('latest', '', 'LTS'); - const version = await resolver.createDotnetVersion(); - - expect(version.value).toBe('8.0'); - }); - - it('should resolve "latest" with channel filter STS', async () => { - getJsonMock.mockResolvedValue({result: mockReleases}); - - const resolver = new DotnetVersionResolver('latest', '', 'STS'); - const version = await resolver.createDotnetVersion(); - - expect(version.value).toBe('9.0'); - }); - - it('should resolve "latest" with channel filter STS and preview quality', async () => { - getJsonMock.mockResolvedValue({result: mockReleases}); - - const resolver = new DotnetVersionResolver('latest', 'preview', 'STS'); - const version = await resolver.createDotnetVersion(); - - // preview quality includes all support-phases; STS filter → 9.0 (active, sts) - expect(version.value).toBe('9.0'); - }); - - it('should warn if channel is provided but version is not latest', async () => { - getJsonMock.mockResolvedValue({result: mockReleases}); - - const resolver = new DotnetVersionResolver('8.0', '', 'LTS'); - await resolver.createDotnetVersion(); - - expect(warningSpy).toHaveBeenCalledWith( - `The 'dotnet-channel' input is only supported when 'dotnet-version' is set to 'latest'.` - ); - }); - - it('should throw when releases-index API returns empty active releases', async () => { - const emptyReleases = { - 'releases-index': [ - { - 'channel-version': '7.0', - 'support-phase': 'eol', - 'release-type': 'sts' - } - ] - }; - getJsonMock.mockResolvedValue({result: emptyReleases}); - - const resolver = new DotnetVersionResolver('latest'); - - await expect(resolver.createDotnetVersion()).rejects.toThrow( - /Could not find any active releases/ - ); - }); - - it('should throw when releases-index response has unexpected format', async () => { - getJsonMock.mockResolvedValue({result: {}}); - - const resolver = new DotnetVersionResolver('latest'); - - await expect(resolver.createDotnetVersion()).rejects.toThrow( - /Unexpected response format/ - ); - }); - - it('should throw when releases-index response is null', async () => { - getJsonMock.mockResolvedValue({result: null}); - - const resolver = new DotnetVersionResolver('latest'); - - await expect(resolver.createDotnetVersion()).rejects.toThrow( - /Unexpected response format/ - ); - }); - - it('should resolve "latest" with ga quality same as default (no previews)', async () => { - getJsonMock.mockResolvedValue({result: mockReleases}); - - const resolver = new DotnetVersionResolver('latest', 'ga'); - const version = await resolver.createDotnetVersion(); - - // ga should behave like no quality — skip preview (10.0), pick 9.0 - expect(version.value).toBe('9.0'); - }); - - it('should resolve "latest" with LTS channel and daily quality', async () => { - getJsonMock.mockResolvedValue({result: mockReleases}); - - const resolver = new DotnetVersionResolver('latest', 'daily', 'LTS'); - const version = await resolver.createDotnetVersion(); - - // daily allows previews, LTS filter applies — 10.0 (preview, lts) is the highest LTS - expect(version.value).toBe('10.0'); - expect(version.qualityFlag).toBe(true); - }); - - it('should resolve "latest" with A.B channel directly without API call', async () => { - const resolver = new DotnetVersionResolver('latest', '', '8.0'); - const version = await resolver.createDotnetVersion(); - - expect(version.value).toBe('8.0'); - expect(version.type.toLowerCase()).toContain('channel'); - expect(version.qualityFlag).toBe(true); - // Should NOT call the API - expect(getJsonMock).not.toHaveBeenCalled(); - }); - - it('should resolve "latest" with A.B.Cxx channel directly without API call', async () => { - const resolver = new DotnetVersionResolver('latest', '', '8.0.1xx'); - const version = await resolver.createDotnetVersion(); - - expect(version.value).toBe('8.0.1xx'); - expect(version.type.toLowerCase()).toContain('channel'); - expect(version.qualityFlag).toBe(true); - // Should NOT call the API - expect(getJsonMock).not.toHaveBeenCalled(); - }); - - it('should resolve "latest" with A.B channel for older version with qualityFlag false', async () => { - const resolver = new DotnetVersionResolver('latest', '', '3.1'); - const version = await resolver.createDotnetVersion(); - - expect(version.value).toBe('3.1'); - expect(version.type.toLowerCase()).toContain('channel'); - // major 3 < 6 → qualityFlag false - expect(version.qualityFlag).toBe(false); - expect(getJsonMock).not.toHaveBeenCalled(); - }); - - it('should resolve "latest" with A.B.Cxx channel and quality', async () => { - const resolver = new DotnetVersionResolver('latest', 'ga', '8.0.2xx'); - const version = await resolver.createDotnetVersion(); - - expect(version.value).toBe('8.0.2xx'); - expect(version.qualityFlag).toBe(true); - expect(getJsonMock).not.toHaveBeenCalled(); - }); -}); diff --git a/__tests__/setup-dotnet.test.ts b/__tests__/setup-dotnet.test.ts index 7c05d38..5f01d55 100644 --- a/__tests__/setup-dotnet.test.ts +++ b/__tests__/setup-dotnet.test.ts @@ -84,7 +84,7 @@ describe('setup-dotnet tests', () => { inputs['dotnet-version'] = ['10.0']; inputs['dotnet-quality'] = 'fictitiousQuality'; - const expectedErrorMessage = `Value '${inputs['dotnet-quality']}' is not supported for the 'dotnet-quality' option. Supported values are: daily, 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); @@ -256,95 +256,5 @@ describe('setup-dotnet tests', () => { await setup.run(); expect(setFailedSpy).toHaveBeenCalledWith(expectedErrorMessage); }); - - it('should fail the action if unsupported dotnet-channel value is provided with latest', async () => { - inputs['dotnet-version'] = ['latest']; - inputs['dotnet-quality'] = ''; - inputs['dotnet-channel'] = 'invalid'; - inputs['architecture'] = ''; - - const expectedErrorMessage = `Value 'invalid' is not supported for the 'dotnet-channel' option. Supported values are: LTS, STS, A.B (e.g. 8.0), A.B.Cxx (e.g. 8.0.1xx).`; - - await setup.run(); - expect(setFailedSpy).toHaveBeenCalledWith(expectedErrorMessage); - }); - - it('should warn but not fail if unsupported dotnet-channel value is provided with a specific version', async () => { - inputs['dotnet-version'] = ['8.0.x']; - inputs['dotnet-quality'] = ''; - inputs['dotnet-channel'] = 'invalid'; - inputs['architecture'] = ''; - - installDotnetSpy.mockImplementation(() => Promise.resolve('')); - - await setup.run(); - expect(setFailedSpy).not.toHaveBeenCalled(); - expect(warningSpy).toHaveBeenCalledWith( - `Value 'invalid' is not supported for the 'dotnet-channel' option and will be ignored because 'dotnet-version' is not set to 'latest'. Supported values are: LTS, STS, A.B (e.g. 8.0), A.B.Cxx (e.g. 8.0.1xx).` - ); - }); - - it('should pass valid dotnet-channel value through without error', async () => { - inputs['dotnet-version'] = ['latest']; - inputs['dotnet-quality'] = ''; - inputs['dotnet-channel'] = 'LTS'; - inputs['architecture'] = ''; - - installDotnetSpy.mockImplementation(() => Promise.resolve('')); - - await setup.run(); - expect(setFailedSpy).not.toHaveBeenCalled(); - }); - - it('should pass A.B channel value through without error when used with latest', async () => { - inputs['dotnet-version'] = ['latest']; - inputs['dotnet-quality'] = ''; - inputs['dotnet-channel'] = '8.0'; - inputs['architecture'] = ''; - - installDotnetSpy.mockImplementation(() => Promise.resolve('')); - - await setup.run(); - expect(setFailedSpy).not.toHaveBeenCalled(); - }); - - it('should pass A.B.Cxx channel value through without error when used with latest', async () => { - inputs['dotnet-version'] = ['latest']; - inputs['dotnet-quality'] = ''; - inputs['dotnet-channel'] = '8.0.1xx'; - inputs['architecture'] = ''; - - installDotnetSpy.mockImplementation(() => Promise.resolve('')); - - await setup.run(); - expect(setFailedSpy).not.toHaveBeenCalled(); - }); - - it('should fail with A.B.Cxx channel if major version is below 5', async () => { - inputs['dotnet-version'] = ['latest']; - inputs['dotnet-quality'] = ''; - inputs['dotnet-channel'] = '3.1.1xx'; - inputs['architecture'] = ''; - - const expectedErrorMessage = `Value '3.1.1xx' is not supported for the 'dotnet-channel' option. Supported values are: LTS, STS, A.B (e.g. 8.0), A.B.Cxx (e.g. 8.0.1xx).`; - - await setup.run(); - expect(setFailedSpy).toHaveBeenCalledWith(expectedErrorMessage); - }); - - it('should warn and not fail if valid dotnet-channel is provided with a non-latest version', async () => { - inputs['dotnet-version'] = ['8.0.x']; - inputs['dotnet-quality'] = ''; - inputs['dotnet-channel'] = 'LTS'; - inputs['architecture'] = ''; - - installDotnetSpy.mockImplementation(() => Promise.resolve('')); - - await setup.run(); - expect(setFailedSpy).not.toHaveBeenCalled(); - expect(warningSpy).toHaveBeenCalledWith( - `The 'dotnet-channel' input is only supported when 'dotnet-version' is set to 'latest'.` - ); - }); }); }); diff --git a/action.yml b/action.yml index 315bc8f..861fb9d 100644 --- a/action.yml +++ b/action.yml @@ -6,11 +6,9 @@ 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, 6.0.2xx, latest' + 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, preview, ga.' - dotnet-channel: - description: 'Optional channel for the installation. The possible values are: STS, LTS, A.B (e.g. 8.0), A.B.Cxx (e.g. 8.0.1xx, available since 5.0). To be used with "dotnet-version: latest".' + description: 'Optional quality of the build. The possible values are: daily, signed, validated, preview, ga.' global-json-file: description: 'Optional global.json location, if your global.json isn''t located in the root of the repo.' source-url: @@ -41,4 +39,4 @@ runs: using: 'node24' main: 'dist/setup/index.js' post: 'dist/cache-save/index.js' - post-if: success() \ No newline at end of file + post-if: success() diff --git a/dist/setup/index.js b/dist/setup/index.js index 3b3ad2c..b6aece1 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -78690,51 +78690,15 @@ const utils_1 = __nccwpck_require__(71314); const QUALITY_INPUT_MINIMAL_MAJOR_TAG = 6; const LATEST_PATCH_SYNTAX_MINIMAL_MAJOR_TAG = 5; class DotnetVersionResolver { - quality; - dotnetChannel; inputVersion; resolvedArgument; - constructor(version, quality = '', dotnetChannel) { - this.quality = quality; - this.dotnetChannel = dotnetChannel; + constructor(version) { this.inputVersion = version.trim(); this.resolvedArgument = { type: '', value: '', qualityFlag: false }; } - isVersionChannel(channel) { - // A.B format (e.g., 3.1, 8.0) - if (/^\d+\.\d+$/.test(channel)) - return true; - // A.B.Cxx format (e.g., 8.0.1xx) is supported only for .NET 5.0+ - const latestPatchMatch = channel.match(/^(\d+)\.\d+\.\d{1}xx$/); - if (latestPatchMatch) { - const major = Number(latestPatchMatch[1]); - return (!Number.isNaN(major) && major >= LATEST_PATCH_SYNTAX_MINIMAL_MAJOR_TAG); - } - return false; - } async resolveVersionInput() { - if (this.inputVersion.toLowerCase() === 'latest') { - const channel = this.dotnetChannel || ''; - if (this.isVersionChannel(channel)) { - // A.B or A.B.Cxx channels are passed directly to the install script - this.resolvedArgument.value = channel; - } - else { - // LTS, STS, or empty — resolve via releases index API - this.resolvedArgument.value = await this.getLatestVersion(channel); - } - this.resolvedArgument.type = 'channel'; - const latestChannelMajorTag = Number(this.resolvedArgument.value.split('.')[0]); - this.resolvedArgument.qualityFlag = - !Number.isNaN(latestChannelMajorTag) && - latestChannelMajorTag >= QUALITY_INPUT_MINIMAL_MAJOR_TAG; - return; - } - if (this.dotnetChannel) { - core.warning(`The 'dotnet-channel' input is only supported when 'dotnet-version' is set to 'latest'.`); - } if (!semver_1.default.validRange(this.inputVersion) && !this.isLatestPatchSyntax()) { - 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, latest`); + 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(); @@ -78768,22 +78732,7 @@ class DotnetVersionResolver { this.resolvedArgument.value = `${major}.${minor}`; } else if (this.isNumericTag(major)) { - // Starting with .NET 5, the minor version is always zero. - // Hardcode the earlier versions because they will not get new releases. - switch (major) { - case '1': - this.resolvedArgument.value = '1.1'; - break; - case '2': - this.resolvedArgument.value = '2.2'; - break; - case '3': - this.resolvedArgument.value = '3.1'; - break; - default: - this.resolvedArgument.value = `${major}.0`; - break; - } + 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. @@ -78807,45 +78756,22 @@ class DotnetVersionResolver { } return this.resolvedArgument; } - async getLatestVersion(channelFilter) { + async getLatestByMajorTag(majorTag) { const httpClient = new hc.HttpClient('actions/setup-dotnet', [], { allowRetries: true, maxRetries: 3 }); const response = await httpClient.getJson(DotnetVersionResolver.DotnetCoreIndexUrl); - const result = response.result; - const rawReleasesInfo = result?.['releases-index']; - if (!Array.isArray(rawReleasesInfo)) { - throw new Error('Unexpected response format from .NET releases index.'); - } - let releasesInfo = rawReleasesInfo; - // Filter out EOL versions - releasesInfo = releasesInfo.filter(info => info['support-phase'] !== 'eol'); - // Filter out preview versions if quality is not 'preview' or 'daily' - // If quality is not specified, we assume strict stability (GA only) - const normalizedQuality = (this.quality || '').toLowerCase(); - if (!['preview', 'daily'].includes(normalizedQuality)) { - releasesInfo = releasesInfo.filter(info => info['support-phase'] !== 'preview'); - } - // Apply channel filter (LTS/STS) - if (channelFilter) { - const type = channelFilter.toLowerCase(); - releasesInfo = releasesInfo.filter(info => info['release-type'] === type); - } - releasesInfo.sort((a, b) => { - const partsA = a['channel-version'].split('.').map(Number); - const partsB = b['channel-version'].split('.').map(Number); - for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) { - const diff = (partsB[i] || 0) - (partsA[i] || 0); - if (diff !== 0) - return diff; - } - return 0; + const result = response.result || {}; + const releasesInfo = result['releases-index']; + const releaseInfo = releasesInfo.find(info => { + const sdkParts = info['channel-version'].split('.'); + return sdkParts[0] === majorTag; }); - if (releasesInfo.length === 0) { - throw new Error(`Could not find any active releases matching channel '${channelFilter || 'any'}'`); + if (!releaseInfo) { + throw new Error(`Could not find info for version with major tag: "${majorTag}" at ${DotnetVersionResolver.DotnetCoreIndexUrl}`); } - return releasesInfo[0]['channel-version']; + return releaseInfo['channel-version']; } static DotnetCoreIndexUrl = 'https://builds.dotnet.microsoft.com/dotnet/release-metadata/releases-index.json'; } @@ -78965,18 +78891,16 @@ class DotnetCoreInstaller { version; quality; architecture; - dotnetChannel; static { DotnetInstallDir.setEnvironmentVariable(); } - constructor(version, quality, architecture, dotnetChannel) { + constructor(version, quality, architecture) { this.version = version; this.quality = quality; this.architecture = architecture; - this.dotnetChannel = dotnetChannel; } async installDotnet() { - const versionResolver = new DotnetVersionResolver(this.version, this.quality, this.dotnetChannel); + const versionResolver = new DotnetVersionResolver(this.version); const dotnetVersion = await versionResolver.createDotnetVersion(); const architectureArguments = this.architecture && normalizeArch(this.architecture) !== normalizeArch(os_1.default.arch()) @@ -79095,7 +79019,13 @@ const cache_utils_1 = __nccwpck_require__(41678); const cache_restore_1 = __nccwpck_require__(19517); const constants_1 = __nccwpck_require__(69042); const json5_1 = __importDefault(__nccwpck_require__(86904)); -const qualityOptions = ['daily', 'preview', 'ga']; +const qualityOptions = [ + 'daily', + 'signed', + 'validated', + 'preview', + 'ga' +]; const supportedArchitectures = [ 'x64', 'x86', @@ -79106,19 +79036,6 @@ const supportedArchitectures = [ 'ppc64le', 'riscv64' ]; -function isValidChannel(channel) { - const upper = channel.toUpperCase(); - if (upper === 'LTS' || upper === 'STS') - return true; - // A.B format (e.g., 3.1, 8.0) - if (/^\d+\.\d+$/.test(channel)) - return true; - // A.B.Cxx format (e.g., 8.0.1xx) - available since 5.0 - const match = channel.match(/^(?\d+)\.\d+\.\d{1}xx$/); - if (match && parseInt(match.groups.major) >= 5) - return true; - return false; -} async function run() { try { // @@ -79133,21 +79050,6 @@ async function run() { const versions = core.getMultilineInput('dotnet-version'); const installedDotnetVersions = []; const architecture = getArchitectureInput(); - let dotnetChannel = core.getInput('dotnet-channel'); - const isLatestRequested = versions.some(version => version && version.toLowerCase() === 'latest'); - if (dotnetChannel && !isValidChannel(dotnetChannel)) { - if (isLatestRequested) { - throw new Error(`Value '${dotnetChannel}' is not supported for the 'dotnet-channel' option. Supported values are: LTS, STS, A.B (e.g. 8.0), A.B.Cxx (e.g. 8.0.1xx).`); - } - else { - core.warning(`Value '${dotnetChannel}' is not supported for the 'dotnet-channel' option and will be ignored because 'dotnet-version' is not set to 'latest'. Supported values are: LTS, STS, A.B (e.g. 8.0), A.B.Cxx (e.g. 8.0.1xx).`); - dotnetChannel = ''; - } - } - else if (dotnetChannel && !isLatestRequested) { - core.warning(`The 'dotnet-channel' input is only supported when 'dotnet-version' is set to 'latest'.`); - dotnetChannel = ''; - } const globalJsonFileInput = core.getInput('global-json-file'); if (globalJsonFileInput) { const globalJsonPath = path_1.default.resolve(process.cwd(), globalJsonFileInput); @@ -79170,12 +79072,12 @@ async function run() { if (versions.length) { const quality = core.getInput('dotnet-quality'); if (quality && !qualityOptions.includes(quality)) { - throw new Error(`Value '${quality}' is not supported for the 'dotnet-quality' option. Supported values are: daily, preview, ga.`); + throw new Error(`Value '${quality}' is not supported for the 'dotnet-quality' option. Supported values are: daily, signed, validated, preview, ga.`); } let dotnetInstaller; - const uniqueVersions = new Set(versions.map(v => (v.toLowerCase() === 'latest' ? 'latest' : v))); + const uniqueVersions = new Set(versions); for (const version of uniqueVersions) { - dotnetInstaller = new installer_1.DotnetCoreInstaller(version, quality, architecture, version.toLowerCase() === 'latest' ? dotnetChannel : undefined); + dotnetInstaller = new installer_1.DotnetCoreInstaller(version, quality, architecture); const installedVersion = await dotnetInstaller.installDotnet(); installedDotnetVersions.push(installedVersion); } @@ -79244,23 +79146,9 @@ function getVersionFromGlobalJson(globalJsonPath) { if (globalJson.sdk && globalJson.sdk.version) { version = globalJson.sdk.version; const rollForward = globalJson.sdk.rollForward; - if (rollForward) { - const [major, minor, featurePatch] = version.split('.'); - const feature = featurePatch.substring(0, 1); - switch (rollForward) { - case 'latestMajor': - version = ''; - break; - case 'latestMinor': - version = `${major}`; - break; - case 'latestFeature': - version = `${major}.${minor}`; - break; - case 'latestPatch': - version = `${major}.${minor}.${feature}xx`; - break; - } + if (rollForward && rollForward === 'latestFeature') { + const [major, minor] = version.split('.'); + version = `${major}.${minor}`; } } return version; diff --git a/externals/install-dotnet.ps1 b/externals/install-dotnet.ps1 index 3fa9025..8537c60 100644 --- a/externals/install-dotnet.ps1 +++ b/externals/install-dotnet.ps1 @@ -94,9 +94,9 @@ Determines timeout duration in seconds for downloading of the SDK file Default: 1200 seconds (20 minutes) .PARAMETER KeepZip - If set, downloaded archive file is kept. Applies to both .zip and .tar.gz formats. + If set, downloaded file is kept .PARAMETER ZipPath - Use that path to store the downloaded archive, generated by default. Applies to both .zip and .tar.gz formats. + Use that path to store installer, generated by default .EXAMPLE dotnet-install.ps1 -Version 7.0.401 Installs the .NET SDK version 7.0.401 @@ -341,36 +341,6 @@ function Get-NormalizedProduct([string]$Runtime) { } } -function Test-TarAvailable { - if ($env:DOTNET_INSTALL_SKIP_TAR -eq "1") { - Say-Verbose "Skipping tar detection due to DOTNET_INSTALL_SKIP_TAR environment variable." - return $false - } - $tarCommand = Get-Command -Name "tar" -ErrorAction SilentlyContinue - return $null -ne $tarCommand -} - -function Get-FileExtension-For-Version([string]$VersionOrChannel) { - if (-not $script:TarAvailable) { - return ".zip" - } - if ($VersionOrChannel -match '^(\d+)\.') { - $majorVersion = [int]$Matches[1] - if ($majorVersion -ge 11) { - # Windows tarballs are only available starting with 11.0 preview 3 - if ($VersionOrChannel -match 'preview\.(\d+)') { - $previewNum = [int]$Matches[1] - if ($majorVersion -eq 11 -and $previewNum -lt 3) { - Say-Verbose "Version '$VersionOrChannel' predates tar.gz availability; using zip." - return ".zip" - } - } - Say-Verbose "Using tar.gz archive format for version/channel '$VersionOrChannel'." - return ".tar.gz" - } - } - return ".zip" -} # The version text returned from the feeds is a 1-line or 2-line string: # For the SDK and the dotnet runtime (2 lines): @@ -624,27 +594,24 @@ function Get-Download-Link([string]$AzureFeed, [string]$SpecificVersion, [string # If anything fails in this lookup it will default to $SpecificVersion $SpecificProductVersion = Get-Product-Version -AzureFeed $AzureFeed -SpecificVersion $SpecificVersion - # For .NET 11.0+, tar.gz is the preferred archive format on Windows - $ext = Get-FileExtension-For-Version $SpecificVersion - if ($Runtime -eq "dotnet") { - $PayloadURL = "$AzureFeed/Runtime/$SpecificVersion/dotnet-runtime-$SpecificProductVersion-win-$CLIArchitecture$ext" + $PayloadURL = "$AzureFeed/Runtime/$SpecificVersion/dotnet-runtime-$SpecificProductVersion-win-$CLIArchitecture.zip" } elseif ($Runtime -eq "aspnetcore") { - $PayloadURL = "$AzureFeed/aspnetcore/Runtime/$SpecificVersion/aspnetcore-runtime-$SpecificProductVersion-win-$CLIArchitecture$ext" + $PayloadURL = "$AzureFeed/aspnetcore/Runtime/$SpecificVersion/aspnetcore-runtime-$SpecificProductVersion-win-$CLIArchitecture.zip" } elseif ($Runtime -eq "windowsdesktop") { # The windows desktop runtime is part of the core runtime layout prior to 5.0 - $PayloadURL = "$AzureFeed/Runtime/$SpecificVersion/windowsdesktop-runtime-$SpecificProductVersion-win-$CLIArchitecture$ext" + $PayloadURL = "$AzureFeed/Runtime/$SpecificVersion/windowsdesktop-runtime-$SpecificProductVersion-win-$CLIArchitecture.zip" if ($SpecificVersion -match '^(\d+)\.(.*)$') { $majorVersion = [int]$Matches[1] if ($majorVersion -ge 5) { - $PayloadURL = "$AzureFeed/WindowsDesktop/$SpecificVersion/windowsdesktop-runtime-$SpecificProductVersion-win-$CLIArchitecture$ext" + $PayloadURL = "$AzureFeed/WindowsDesktop/$SpecificVersion/windowsdesktop-runtime-$SpecificProductVersion-win-$CLIArchitecture.zip" } } } elseif (-not $Runtime) { - $PayloadURL = "$AzureFeed/Sdk/$SpecificVersion/dotnet-sdk-$SpecificProductVersion-win-$CLIArchitecture$ext" + $PayloadURL = "$AzureFeed/Sdk/$SpecificVersion/dotnet-sdk-$SpecificProductVersion-win-$CLIArchitecture.zip" } else { throw "Invalid value for `$Runtime" @@ -910,80 +877,6 @@ function Extract-Dotnet-Package([string]$ZipPath, [string]$OutPath) { } } -function Extract-Dotnet-Package-Tar([string]$TarPath, [string]$OutPath) { - Say-Invocation $MyInvocation - - New-Item -ItemType Directory -Force -Path $OutPath | Out-Null - - # Build an exclude list of versioned directories that already exist locally. - # This matches the zip extraction behavior: if shared/Microsoft.NETCore.App/11.0.0/ - # already exists, we skip all files under that version to avoid overwriting a - # previously installed version. - $excludeArgs = @() - $versionRegex = '.*/\d+\.\d+[^/]+/' - - # Read the tarball's file listing to discover versioned directory prefixes - $tarListing = & tar -tzf $TarPath 2>$null - $versionedDirs = @{} - foreach ($entry in $tarListing) { - $normalizedEntry = $entry.Replace('\', '/') - $match = [regex]::Match($normalizedEntry, $versionRegex) - if ($match.Success) { - $versionedPrefix = $normalizedEntry.Substring(0, $match.Index + $match.Length) - if (-not $versionedDirs.ContainsKey($versionedPrefix)) { - $localDir = Join-Path -Path $OutPath -ChildPath $versionedPrefix - $versionedDirs[$versionedPrefix] = (Test-Path $localDir -PathType Container) - } - } - } - - foreach ($dir in $versionedDirs.GetEnumerator()) { - if ($dir.Value) { - # This versioned directory already exists — exclude it from extraction - $pattern = $dir.Key + '*' - $excludeArgs += '--exclude' - $excludeArgs += $pattern - Say-Verbose "Excluding pre-existing versioned directory: $($dir.Key)" - } - } - - # Handle non-versioned file override logic. - # If OverrideNonVersionedFiles is false, exclude non-versioned files that already exist. - if (-not $OverrideNonVersionedFiles) { - foreach ($entry in $tarListing) { - $normalizedEntry = $entry.Replace('\', '/') - # Skip directory entries (end with /) - if ($normalizedEntry.EndsWith('/')) { continue } - $match = [regex]::Match($normalizedEntry, $versionRegex) - if (-not $match.Success) { - # Non-versioned file — exclude if it already exists locally - $localPath = Join-Path -Path $OutPath -ChildPath $normalizedEntry - if (Test-Path $localPath) { - $excludeArgs += '--exclude' - $excludeArgs += $normalizedEntry - Say-Verbose "Excluding pre-existing non-versioned file: $normalizedEntry" - } - } - } - } - - try { - # Extract directly to install root, preserving hard links - $tarOutput = & tar -xzf $TarPath -C $OutPath @excludeArgs 2>&1 - if ($LASTEXITCODE -ne 0) { - $tarOutputText = ($tarOutput | Out-String).Trim() - if ([string]::IsNullOrWhiteSpace($tarOutputText)) { - throw "Tar extraction failed with exit code $LASTEXITCODE." - } - throw "Tar extraction failed with exit code $LASTEXITCODE. tar output: $tarOutputText" - } - } - catch { - Say-Error "Failed to extract tar package. Exception: $_" - throw - } -} - function DownloadFile($Source, [string]$OutPath) { if ($Source -notlike "http*") { # Using System.IO.Path.GetFullPath to get the current directory @@ -1098,7 +991,7 @@ function PrintDryRunOutput($Invocation, $DownloadLinks) { } } -function Get-AkaMSDownloadLink([string]$Channel, [string]$Quality, [bool]$Internal, [string]$Product, [string]$Architecture, [string]$FileExtension = ".zip") { +function Get-AkaMSDownloadLink([string]$Channel, [string]$Quality, [bool]$Internal, [string]$Product, [string]$Architecture) { Say-Invocation $MyInvocation #quality is not supported for LTS or STS channel @@ -1117,7 +1010,7 @@ function Get-AkaMSDownloadLink([string]$Channel, [string]$Quality, [bool]$Intern if (-not [string]::IsNullOrEmpty($Quality)) { $akaMsLink += "/$Quality" } - $akaMsLink += "/$Product-win-$Architecture$FileExtension" + $akaMsLink += "/$Product-win-$Architecture.zip" Say-Verbose "Constructed aka.ms link: '$akaMsLink'." $akaMsDownloadLink = $null @@ -1169,23 +1062,7 @@ function Get-AkaMSDownloadLink([string]$Channel, [string]$Quality, [bool]$Intern } function Get-AkaMsLink-And-Version([string] $NormalizedChannel, [string] $NormalizedQuality, [bool] $Internal, [string] $ProductName, [string] $Architecture) { - - # When tar is available, try .tar.gz first via aka.ms, then fall back to .zip. - # This handles symbolic channels (STS/LTS) and numeric channels where tar.gz - # may not yet be available (e.g. pre-11.0 versions). - $extensionsToTry = @(".zip") - if ($script:TarAvailable) { - $extensionsToTry = @(".tar.gz", ".zip") - } - - $AkaMsDownloadLink = $null - foreach ($ext in $extensionsToTry) { - $AkaMsDownloadLink = Get-AkaMSDownloadLink -Channel $NormalizedChannel -Quality $NormalizedQuality -Internal $Internal -Product $ProductName -Architecture $Architecture -FileExtension $ext - if (-not [string]::IsNullOrEmpty($AkaMsDownloadLink)) { - break - } - Say-Verbose "aka.ms link resolution with '$ext' extension failed, trying next format." - } + $AkaMsDownloadLink = Get-AkaMSDownloadLink -Channel $NormalizedChannel -Quality $NormalizedQuality -Internal $Internal -Product $ProductName -Architecture $Architecture if ([string]::IsNullOrEmpty($AkaMsDownloadLink)) { if (-not [string]::IsNullOrEmpty($NormalizedQuality)) { @@ -1314,8 +1191,6 @@ Measure-Action "Product discovery" { $script:NormalizedProduct = Get-NormalizedProduct $Runtime Say-Verbose "Normalized product: '$NormalizedProduct'" $script:FeedCredential = ValidateFeedCredential $FeedCredential - $script:TarAvailable = Test-TarAvailable - Say-Verbose "Tar available: '$TarAvailable'" } $InstallRoot = Resolve-Installation-Path $InstallDir @@ -1444,12 +1319,7 @@ if (-not $DownloadSucceeded) { } Say "Extracting the archive." -if ($DownloadedLink.downloadLink.EndsWith(".tar.gz")) { - Measure-Action "Package extraction" { Extract-Dotnet-Package-Tar -TarPath $ZipPath -OutPath $InstallRoot } -} -else { - Measure-Action "Package extraction" { Extract-Dotnet-Package -ZipPath $ZipPath -OutPath $InstallRoot } -} +Measure-Action "Package extraction" { Extract-Dotnet-Package -ZipPath $ZipPath -OutPath $InstallRoot } # Check if the SDK version is installed; if not, fail the installation. $isAssetInstalled = $false @@ -1485,215 +1355,219 @@ Say "Installed version is $($DownloadedLink.effectiveVersion)" Say "Installation finished" # SIG # Begin signature block -# MIIncQYJKoZIhvcNAQcCoIInYjCCJ14CAQExDzANBglghkgBZQMEAgEFADB5Bgor +# MIIoKgYJKoZIhvcNAQcCoIIoGzCCKBcCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG -# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDmNI7+9b4lSWsX -# 2kYVvqQHpZeXB6Kt5BepNOcmIqEK1aCCDMkwggYEMIID7KADAgECAhMzAAACHPrN -# xZvoL37EAAAAAAIcMA0GCSqGSIb3DQEBCwUAMFcxCzAJBgNVBAYTAlVTMR4wHAYD -# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBD -# b2RlIFNpZ25pbmcgUENBIDIwMjQwHhcNMjYwNDE2MTg1OTQxWhcNMjcwNDE1MTg1 -# OTQxWjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE -# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYD -# VQQDExVNaWNyb3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IB -# DwAwggEKAoIBAQDVsZfgOKmM31HPfoWOoNEiw0SlCiIxUMC0I9NMWbucKOw/e9lP -# oAoehQVu6SG65V4EPzrYsnBnFPNoi4/HoOdjhz1qkrEt4I6tEcxXU6oOeY9zGveC -# /3iBeuhLYxM3M/PkcUoebF+Nednm8OkdSPoDu8imViHPQq/8CQUu0WRR4rE+dMRf -# rpVqfmNi2qWCX94T4MsepijGVkwE//tJg0ryAiYdHT34LSnlG/RSBZmQRGWZ5g8j -# qnKjRParSqMft1gvjuUTVgtWNZfgcLFSK5Wa0myrq8OPcgTGGsRgun+tnSS+IxDT -# xVsAPH1OzvPjwomguByhUe/OcvUN0D5Wmp7xAgMBAAGjggGqMIIBpjAOBgNVHQ8B -# Af8EBAMCB4AwHwYDVR0lBBgwFgYKKwYBBAGCN0wIAQYIKwYBBQUHAwMwHQYDVR0O -# BBYEFNoH7a2YDjOSwpkp6DHcmUS7J+0yMFQGA1UdEQRNMEukSTBHMS0wKwYDVQQL -# EyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxFjAUBgNVBAUT -# DTIzMDAxMis1MDc1NjkwHwYDVR0jBBgwFoAUf1k/VCHarU/vBeXmo9ctBpQSCDEw -# YAYDVR0fBFkwVzBVoFOgUYZPaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9w -# cy9jcmwvTWljcm9zb2Z0JTIwQ29kZSUyMFNpZ25pbmclMjBQQ0ElMjAyMDI0LmNy -# bDBtBggrBgEFBQcBAQRhMF8wXQYIKwYBBQUHMAKGUWh0dHA6Ly93d3cubWljcm9z -# b2Z0LmNvbS9wa2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwQ29kZSUyMFNpZ25pbmcl -# MjBQQ0ElMjAyMDI0LmNydDAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4IC -# AQAUnEqhaRXe0T3hIJjvdQErEkrA/7bByjn6t5IArODkkRjzkYwtKMc2yYj2quaN -# rLutWw2YZcngKPy1b71YyDJQTy4NDRwaSh9Tw5thrk3NmcPrAHia5vtcBJ1CgtKK -# 7mQbIcQ22d/N3813ayCDDFewu1+jsZmX+r/aTEqaOM4TVxVtRSkuCy8nAXKuChOK -# Li/zA4XuH8iEYqIsj2YoNaeSxVmeGiERXpKdo3dDmYi0kO5w2D8VS4c3+9h6gElY -# BaAAg/dYErBg27qT3vv0zRDJhJufvCNylA8S7/+8H5E/PV5cng6na9VV/w9OV3qu -# uND6zdGa2EX38Glp50F9AIQk3p2xXmcvorDeM4XJ7UlWYBi6g80J1SSOQnInCYFE -# msfUNn3+1AaTJKSJL83quKArTac2pKhu0Yzzzrzo6HrsRiQKzpnRBb1/dMa6P3hz -# 75XbMRBctNsFhZC07WCmjExdLg2eHW5uV0TY8D5+6wozJf7vF3+WHkYPO85Z+BC6 -# U4FkNbYNycZ9cE4j1tXRdyDCfml6c0HWPHjNVDObrv9lKt3qUqFpX38VCqVCyNOO -# 1UcXfQiVjJw32U2WUKZjt/neJKHEBsm9kFsLuWzkQ53+qcaSaytmsCnk2gOglrlD -# 5d3kKyvvAw+rzm0lT8K38P6PLxfZQHhu4W8dV7Av8N2ZmDCCBr0wggSloAMCAQIC -# EzMAAAA5O7Y3Gb8GHWcAAAAAADkwDQYJKoZIhvcNAQEMBQAwgYgxCzAJBgNVBAYT -# AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD -# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBS -# b290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDExMB4XDTI0MDgwODIwNTQxOFoX -# DTM2MDMyMjIyMTMwNFowVzELMAkGA1UEBhMCVVMxHjAcBgNVBAoTFU1pY3Jvc29m -# dCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9zb2Z0IENvZGUgU2lnbmluZyBQ -# Q0EgMjAyNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANgBnB7jOMeq -# lRYHNa265v4IY9fH8TKhemHfPINe1gpLaV3dhg324WwH06LcHbpnsBukCDNitryo -# 0dtS/EW6I/yEL/bLSY8hKpbfQuWusBPr9qazYcDxCW/qnjb5JsI1s8bNOg3bVATv -# QVL4tcf03aTycsz8QeCdM0l/yHRObJ9QqazM1r6VPEOJ7LL+uEEb73w6QCuhs89a -# 1uv1zerOYMnsneRRwCbpyW11IcggU0cRKDDq1pjVJzIbIF6+oiXXbReOsgeI8zu1 -# FyQfK0fVkaya8SmVHQ/tOf23mZ4W9k0Ri22QW9p3UgSC5OUDktKxxcCmGL6tXLfO -# GSWHIIV4YrTJTT6PNty5REojHJuZHArkF9VnHTERWoTjAzfI3kP+5b4alUdhgAZ7 -# ttOu1bVnXfHaqPYl2rPs20ji03LOVWsh/radgE17es5hL+t6lV0eVHrVhsssROWJ -# uz2MXMCt7iw7lFPG9LXKGjsmonn2gotGdHIuEg5JnJMJVmixd5LRlkmgYRZKzhxS -# CwyoGIq0PhaA7Y+VPct5pCHkijcIIDm0nlkK+0KyepolcqGm0T/GYQRMhHJlGOOm -# VQop36wUVUYklUy++vDWeEgEo4s7hxN6mIbf2MSIQ/iIfMZgJxC69oukMUXCrOC3 -# SkE/xIkgpfl22MM1itkZ35nNXkMolU1lAgMBAAGjggFOMIIBSjAOBgNVHQ8BAf8E -# BAMCAYYwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFH9ZP1Qh2q1P7wXl5qPX -# LQaUEggxMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMA8GA1UdEwEB/wQFMAMB -# Af8wHwYDVR0jBBgwFoAUci06AjGQQ7kUBU7h6qfHMdEjiTQwWgYDVR0fBFMwUTBP -# oE2gS4ZJaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMv -# TWljUm9vQ2VyQXV0MjAxMV8yMDExXzAzXzIyLmNybDBeBggrBgEFBQcBAQRSMFAw -# TgYIKwYBBQUHMAKGQmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMv -# TWljUm9vQ2VyQXV0MjAxMV8yMDExXzAzXzIyLmNydDANBgkqhkiG9w0BAQwFAAOC -# AgEAFJQfOChP7onn6fLIMKrSlN1WYKwDFgAddymOUO3FrM8d7B/W/iQ6DxXsDn7D -# 5W4wMwYeLystcEqfkjz4NURRgazyMu5yRzQh4LqjA4tStTcJh1opExo7nn5PuPBY -# nbu0+THSuVHTe0VTTPVhily/piFrDo3axQ9P4C+Ol5yet+2gTfekICS5xS+cYfSI -# vgn0JksVBVMYVI5QFu/qhnLhsEFEUzG8fvv0hjgkO+lkpV9ty6GkN4vdnd7ya6Q6 -# aR9y34aiM1qmxaxBi6OUnyNl6fkuun/diTFnYDLTppOkr/mg5WSfCiDVMNCxtj4w -# PKC5OmHm1DQIt/MNokbbH3UGsFP1QbzsLocuSqLCvH09Io3fDPTmscR9Y75G4qX7 -# RTX8AdBPo0I6OEojf39zuFZt0qOHm65YWQE69cZM2ueE1MB05dNNgHK9gTE7zKvK -# /fg8B2qjW88MT/WF5V5uvZGtqa9FSL2RazArA+rDPuf6JGYz4HpgMZHB4S6szWSK -# YBv0VisCzfxgeU+dquXW9bd0auYlOB58DPcOYKdc3Se94g+xL4pcEhbB54JOgAkw -# YTu/9dLeH2pDqeJZAABVDWRQCaXfO5LgyKwKCLYXpigrZYCjUSBcr+Ve8PFWMhVT -# Ql0v4q8J/AUmQN5W4n101cY2L4A7GTQG1h32HHAvfQESWP0xghn+MIIZ+gIBATBu -# MFcxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x -# KDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMjQCEzMAAAIc -# +s3Fm+gvfsQAAAAAAhwwDQYJYIZIAWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwG -# CisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZI -# hvcNAQkEMSIEIB5Na9P++kVnSeIy27N4IyRD4N0sjfWHRS1ht0NHdoK/MEIGCisG -# AQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3 -# Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEBBQAEggEAx05EI+Kk4hoAKG/8oVdr -# UIq/+sv+E0vpSyWQMTq/yrkfnPWqT2TxNVtcG3ZcQT5/36cjMe1umi1zzy178VRP -# HKH2jxMOzMqtJPsAI/yAd46EOyTBwayS+27NOvz2r3v8WV0YjuplJb1teHmJtoIf -# UVC1va6H668UMrR2Mm5l8GvtcrGmDLjq3FLTLeidco7aj7xtkwUhyXvCS/HzoF9G -# yrYW3NKqIy+KbNXTD8RbwDxKvx3ED9C1SfZi3fi/t1a2wjNsxVqf4XNsO2+JvuuO -# SGTZOoo/3jqtS22IcV5Z3In0D3+C/rigq0179KhwPtztz4+J9MU5gtGmVayF1MH1 -# M6GCF7AwghesBgorBgEEAYI3AwMBMYIXnDCCF5gGCSqGSIb3DQEHAqCCF4kwgheF -# AgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFaBgsqhkiG9w0BCRABBKCCAUkEggFFMIIB -# QQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFlAwQCAQUABCDhOUY7jtQW2mTHjZaf -# zhylI7IWKlybklUpfwDq0GnW8AIGaeuhCndSGBMyMDI2MDUxNzAwNDk0OC4wNzFa -# MASAAgH0oIHZpIHWMIHTMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv -# bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0 -# aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExpbWl0 -# ZWQxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVTTjoyQTFBLTA1RTAtRDk0NzElMCMG -# A1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaCCEf4wggcoMIIFEKAD -# AgECAhMzAAACEKvN5BYY7zmwAAEAAAIQMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNV -# BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4w -# HAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29m -# dCBUaW1lLVN0YW1wIFBDQSAyMDEwMB4XDTI1MDgxNDE4NDgxMloXDTI2MTExMzE4 -# NDgxMlowgdMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYD -# VQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLTAr -# BgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJhdGlvbnMgTGltaXRlZDEnMCUG -# A1UECxMeblNoaWVsZCBUU1MgRVNOOjJBMUEtMDVFMC1EOTQ3MSUwIwYDVQQDExxN -# aWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNlMIICIjANBgkqhkiG9w0BAQEFAAOC -# Ag8AMIICCgKCAgEAjcc4q057ZwIgpKu4pTXWLejvYEduRf+1mIpbiJEMFWWmU2xp -# ip+zK7xFxKGB1CclUXBU0/ZQZ6LG8H0gI7yvosrsPEI1DPB/XccGCvswKbAKckng -# OuGTEPGk7K/vEZa9h0Xt02b7m2n9MdIjkLrFl0pDriKyz0QHGpdh93X6+NApfE1T -# L24Vo0xkeoFGpL3rX9gXhIOF59EMnTd2o45FW/oxMgY9q0y0jGO0HrCLTCZr50e7 -# TZRSNYAy2lyKbvKI2MKlN1wLzJvZbbc//L3s1q3J6KhS0KC2VNEImYdFgVkJej4z -# ZqHfScTbx9hjFgFpVkJl4xH5VJ8tyJdXE9+vU0k9AaT2QP1Zm3WQmXedSoLjjI7L -# WznuHwnoGIXLiJMQzPqKqRIFL3wzcrDrZeWgtAdBPbipglZ5CQns6Baj5Mb6a/EZ -# C9G3faJYK5QVHeE6eLoSEwp1dz5WurLXNPsp0VWplpl/FJb8jrRT/jOoHu85qRcd -# YpgByU9W7IWPdrthmyfqeAw0omVWN5JxcogYbLo2pANJHlsMdWnxIpN5YwHbGEPC -# uosBHPk2Xd9+E/pZPQUR6v+D85eEN5A/ZM/xiPpxa8dJZ87BpTvui7/2uflUMJf2 -# Yc9ZLPgEdhQQo0LwMDSTDT48y3sV7Pdo+g5q+MqnJztN/6qt1cgUTe9u+ykCAwEA -# AaOCAUkwggFFMB0GA1UdDgQWBBSe42+FrpdF2avbUhlk86BLSH5kejAfBgNVHSME -# GDAWgBSfpxVdAF5iXYP05dJlpxtTNRnpcjBfBgNVHR8EWDBWMFSgUqBQhk5odHRw -# Oi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNyb3NvZnQlMjBUaW1l -# LVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcmwwbAYIKwYBBQUHAQEEYDBeMFwGCCsG -# AQUFBzAChlBodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01p -# Y3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNydDAMBgNVHRMB -# Af8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMA4GA1UdDwEB/wQEAwIHgDAN -# BgkqhkiG9w0BAQsFAAOCAgEAvs4rO3oo8czOrxPqnnSEkUVq718QzlrIiy7/EW7J -# mQXsJoFxHWUF0Ux0PDyKFDRXPJVv29F7kpJkBJJmcQg5HQV7blUXIMWQ1qX0KdtF -# QXI/MRL77Z+pK5x1jX+tbRkA7a5Ft7vWuRoAEi02HpFH5m/Akh/dfsbx8wOpecJb -# YvuHuy4aG0/tGzOWFCxMMNhGAIJ4qdV87JnY/uMBmiodlm+Gz357XWW5tg3HrtNZ -# XuQ0tWUv26ud4nGKJo/oLZHP75p4Rpt7dMdYKUF9AuVFBwxYZYpvgk12tfK+/yOw -# q84/fjXVCdM83Qnawtbenbk/lnbc9KsZom+GnvA4itAMUpSXFWrcRkqdUQLN+JrG -# 6fPBoV8+D8U2Q2F4XkiCR6EU9JzYKwTuvL6t3nFuxnkLdNjbTg2/yv2j3WaDuCK5 -# lSPgsndIiH6Bku2Ui3A0aUo6D9z9v+XEuBs9ioVJaOjf/z+Urqg7ESnxG0/T1dKc -# i7vLQ2XNgWFYO+/OlDjtGoma1ijX4m14N9qgrXTuWEGwgC7hhBgp3id/LAOf9BST -# WA5lBrilsEoexXBrOn/1wM3rjG0hIsxvF5/YOK78mVRGY6Y7zYJ+uXt4OTOFBwad -# Pv8MklreQZLPnQPtiwop4rlLUYaPCiD4YUqRNbLp8Sgyo9g0iAcZYznTuc+8Q8ZI -# rgwwggdxMIIFWaADAgECAhMzAAAAFcXna54Cm0mZAAAAAAAVMA0GCSqGSIb3DQEB -# CwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE -# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTIwMAYD -# VQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxMDAe -# Fw0yMTA5MzAxODIyMjVaFw0zMDA5MzAxODMyMjVaMHwxCzAJBgNVBAYTAlVTMRMw +# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCyKn7B6ieM6Y2C +# rr9TCFvTSv2mMIh9mBGXh4z2gOksEqCCDXYwggX0MIID3KADAgECAhMzAAAEhV6Z +# 7A5ZL83XAAAAAASFMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD +# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p +# bmcgUENBIDIwMTEwHhcNMjUwNjE5MTgyMTM3WhcNMjYwNjE3MTgyMTM3WjB0MQsw +# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u +# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +# AQDASkh1cpvuUqfbqxele7LCSHEamVNBfFE4uY1FkGsAdUF/vnjpE1dnAD9vMOqy +# 5ZO49ILhP4jiP/P2Pn9ao+5TDtKmcQ+pZdzbG7t43yRXJC3nXvTGQroodPi9USQi +# 9rI+0gwuXRKBII7L+k3kMkKLmFrsWUjzgXVCLYa6ZH7BCALAcJWZTwWPoiT4HpqQ +# hJcYLB7pfetAVCeBEVZD8itKQ6QA5/LQR+9X6dlSj4Vxta4JnpxvgSrkjXCz+tlJ +# 67ABZ551lw23RWU1uyfgCfEFhBfiyPR2WSjskPl9ap6qrf8fNQ1sGYun2p4JdXxe +# UAKf1hVa/3TQXjvPTiRXCnJPAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE +# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUuCZyGiCuLYE0aU7j5TFqY05kko0w +# RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW +# MBQGA1UEBRMNMjMwMDEyKzUwNTM1OTAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci +# tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j +# b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG +# CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu +# Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0 +# MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBACjmqAp2Ci4sTHZci+qk +# tEAKsFk5HNVGKyWR2rFGXsd7cggZ04H5U4SV0fAL6fOE9dLvt4I7HBHLhpGdE5Uj +# Ly4NxLTG2bDAkeAVmxmd2uKWVGKym1aarDxXfv3GCN4mRX+Pn4c+py3S/6Kkt5eS +# DAIIsrzKw3Kh2SW1hCwXX/k1v4b+NH1Fjl+i/xPJspXCFuZB4aC5FLT5fgbRKqns +# WeAdn8DsrYQhT3QXLt6Nv3/dMzv7G/Cdpbdcoul8FYl+t3dmXM+SIClC3l2ae0wO +# lNrQ42yQEycuPU5OoqLT85jsZ7+4CaScfFINlO7l7Y7r/xauqHbSPQ1r3oIC+e71 +# 5s2G3ClZa3y99aYx2lnXYe1srcrIx8NAXTViiypXVn9ZGmEkfNcfDiqGQwkml5z9 +# nm3pWiBZ69adaBBbAFEjyJG4y0a76bel/4sDCVvaZzLM3TFbxVO9BQrjZRtbJZbk +# C3XArpLqZSfx53SuYdddxPX8pvcqFuEu8wcUeD05t9xNbJ4TtdAECJlEi0vvBxlm +# M5tzFXy2qZeqPMXHSQYqPgZ9jvScZ6NwznFD0+33kbzyhOSz/WuGbAu4cHZG8gKn +# lQVT4uA2Diex9DMs2WHiokNknYlLoUeWXW1QrJLpqO82TLyKTbBM/oZHAdIc0kzo +# STro9b3+vjn2809D0+SOOCVZMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq +# hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x +# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv +# bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 +# IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG +# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG +# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg +# Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC +# CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03 +# a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr +# rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg +# OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy +# 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9 +# sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh +# dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k +# A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB +# w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn +# Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90 +# lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w +# ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o +# ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD +# VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa +# BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny +# bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG +# AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t +# L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV +# HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3 +# dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG +# AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl +# AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb +# C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l +# hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6 +# I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0 +# wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560 +# STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam +# ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa +# J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah +# XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA +# 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt +# Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr +# /Xmfwb1tbWrJUnMTDXpQzTGCGgowghoGAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw # EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN -# aWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0 -# YW1wIFBDQSAyMDEwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA5OGm -# TOe0ciELeaLL1yR5vQ7VgtP97pwHB9KpbE51yMo1V/YBf2xK4OK9uT4XYDP/XE/H -# ZveVU3Fa4n5KWv64NmeFRiMMtY0Tz3cywBAY6GB9alKDRLemjkZrBxTzxXb1hlDc -# wUTIcVxRMTegCjhuje3XD9gmU3w5YQJ6xKr9cmmvHaus9ja+NSZk2pg7uhp7M62A -# W36MEBydUv626GIl3GoPz130/o5Tz9bshVZN7928jaTjkY+yOSxRnOlwaQ3KNi1w -# jjHINSi947SHJMPgyY9+tVSP3PoFVZhtaDuaRr3tpK56KTesy+uDRedGbsoy1cCG -# MFxPLOJiss254o2I5JasAUq7vnGpF1tnYN74kpEeHT39IM9zfUGaRnXNxF803RKJ -# 1v2lIH1+/NmeRd+2ci/bfV+AutuqfjbsNkz2K26oElHovwUDo9Fzpk03dJQcNIIP -# 8BDyt0cY7afomXw/TNuvXsLz1dhzPUNOwTM5TI4CvEJoLhDqhFFG4tG9ahhaYQFz -# ymeiXtcodgLiMxhy16cg8ML6EgrXY28MyTZki1ugpoMhXV8wdJGUlNi5UPkLiWHz -# NgY1GIRH29wb0f2y1BzFa/ZcUlFdEtsluq9QBXpsxREdcu+N+VLEhReTwDwV2xo3 -# xwgVGD94q0W29R6HXtqPnhZyacaue7e3PmriLq0CAwEAAaOCAd0wggHZMBIGCSsG -# AQQBgjcVAQQFAgMBAAEwIwYJKwYBBAGCNxUCBBYEFCqnUv5kxJq+gpE8RjUpzxD/ -# LwTuMB0GA1UdDgQWBBSfpxVdAF5iXYP05dJlpxtTNRnpcjBcBgNVHSAEVTBTMFEG -# DCsGAQQBgjdMg30BATBBMD8GCCsGAQUFBwIBFjNodHRwOi8vd3d3Lm1pY3Jvc29m -# dC5jb20vcGtpb3BzL0RvY3MvUmVwb3NpdG9yeS5odG0wEwYDVR0lBAwwCgYIKwYB -# BQUHAwgwGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwCwYDVR0PBAQDAgGGMA8G -# A1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU1fZWy4/oolxiaNE9lJBb186aGMQw -# VgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9j -# cmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3JsMFoGCCsGAQUF -# BwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3Br -# aS9jZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcnQwDQYJKoZIhvcNAQEL -# BQADggIBAJ1VffwqreEsH2cBMSRb4Z5yS/ypb+pcFLY+TkdkeLEGk5c9MTO1OdfC -# cTY/2mRsfNB1OW27DzHkwo/7bNGhlBgi7ulmZzpTTd2YurYeeNg2LpypglYAA7AF -# vonoaeC6Ce5732pvvinLbtg/SHUB2RjebYIM9W0jVOR4U3UkV7ndn/OOPcbzaN9l -# 9qRWqveVtihVJ9AkvUCgvxm2EhIRXT0n4ECWOKz3+SmJw7wXsFSFQrP8DJ6LGYnn -# 8AtqgcKBGUIZUnWKNsIdw2FzLixre24/LAl4FOmRsqlb30mjdAy87JGA0j3mSj5m -# O0+7hvoyGtmW9I/2kQH2zsZ0/fZMcm8Qq3UwxTSwethQ/gpY3UA8x1RtnWN0SCyx -# TkctwRQEcb9k+SS+c23Kjgm9swFXSVRk2XPXfx5bRAGOWhmRaw2fpCjcZxkoJLo4 -# S5pu+yFUa2pFEUep8beuyOiJXk+d0tBMdrVXVAmxaQFEfnyhYWxz/gq77EFmPWn9 -# y8FBSX5+k77L+DvktxW/tM4+pTFRhLy/AsGConsXHRWJjXD+57XQKBqJC4822rpM -# +Zv/Cuk0+CQ1ZyvgDbjmjJnW4SLq8CdCPSWU5nR0W2rRnj7tfqAxM328y+l7vzhw -# RNGQ8cirOoo6CGJ/2XBjU02N7oJtpQUQwXEGahC0HVUzWLOhcGbyoYIDWTCCAkEC -# AQEwggEBoYHZpIHWMIHTMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv -# bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0 -# aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExpbWl0 -# ZWQxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVTTjoyQTFBLTA1RTAtRDk0NzElMCMG -# A1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaIjCgEBMAcGBSsOAwIa -# AxUAOsyf2b6riPKnnXlIgIL2f53PUsKggYMwgYCkfjB8MQswCQYDVQQGEwJVUzET -# MBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV -# TWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1T -# dGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQsFAAIFAO2zHvgwIhgPMjAyNjA1MTYx -# NjUxMDRaGA8yMDI2MDUxNzE2NTEwNFowdzA9BgorBgEEAYRZCgQBMS8wLTAKAgUA -# 7bMe+AIBADAKAgEAAgIhcwIB/zAHAgEAAgISqjAKAgUA7bRweAIBADA2BgorBgEE -# AYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIBAAIDB6EgoQowCAIBAAIDAYag -# MA0GCSqGSIb3DQEBCwUAA4IBAQBjb6eELCy7c+QKNzPmI4O0riW248wi0uSI0DKx -# /ZRwZgOShcyQUcjn6Q/SGMTQ9cqeJe/IhyZbzUCTATtImhIybXreibtT2cAMO4M6 -# 3gGHvd96iu8Q49zOYR182At7wmlUTeMrsj51LpZkWCUy4ZSeOSri83Aong5MKGMF -# Kt2xnKiSYL2tXIm0zcTpX8xjXMMWFb7wa0CBnHn5ZMYJVtuntxE1B4wJyMZktBS6 -# WkP9XpGCXB1X7VhYM3vvtBFq2vA5neloI7waPPPTU+0hK6IX7+c1U776yDnJW4jc -# OUJsj8rjwrS+Gc/gPt98V3kMoGvxG0eGEzdjkmblUlhT1FwpMYIEDTCCBAkCAQEw -# gZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcT -# B1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UE -# AxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAIQq83kFhjvObAA -# AQAAAhAwDQYJYIZIAWUDBAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0B -# CRABBDAvBgkqhkiG9w0BCQQxIgQgn7/AUpcQFhG3OcHi0Aeq56NC1uclpcmcCkGu -# +HK5un4wgfoGCyqGSIb3DQEJEAIvMYHqMIHnMIHkMIG9BCDD1SHufsjzY59S1iHU -# QY9hnsKSrJPg5a9Mc4YnGmPHxjCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD +# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp +# Z25pbmcgUENBIDIwMTECEzMAAASFXpnsDlkvzdcAAAAABIUwDQYJYIZIAWUDBAIB +# BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO +# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEID8/Z0hz8wCpH2YjVYR3wACO +# qi7toMi0S892RCpCiXnDMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A +# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB +# BQAEggEAR3ofVJe8H1PSnVv5GEV/iNRKzDHBYXTNKjw6gaEwywGiLnvok4fIy+o/ +# pgoyuM4RLT6jq9o/62LWZPnRCXiQiidnt9u6BtjAQFoy9Hyz39SnG3SIfcXwQU6S +# Kn6sdIdkCnp9zgCw0A1um1l9ZESP36cub7lCkog6Qd1N+d5KAMuDMHX4MybWYjva +# YmW+c3RMH4HoBd6igF/hUaz0VTf+yrdIUaBIJ9UlWTMVkwokmQ9I79IwPU5hHnRu +# Ao8D6p++BagDKmVHo4bY/ADy4GDn4nrLA09mwd0YQPDZvb3K3Z2rIABM0UdS4+lG +# c/pZsaRUT7TE8NzWXP+vWQ9bdkhNbaGCF5QwgheQBgorBgEEAYI3AwMBMYIXgDCC +# F3wGCSqGSIb3DQEHAqCCF20wghdpAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFSBgsq +# hkiG9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl +# AwQCAQUABCAd+KomD6n/vMp0PpchU0Vc9uK1oIZ/s0smWP9W6KAY4QIGaSc7gduW +# GBMyMDI1MTIxMDIyNDQ0NC41NjdaMASAAgH0oIHRpIHOMIHLMQswCQYDVQQGEwJV +# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE +# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1l +# cmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046REMwMC0w +# NUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2Wg +# ghHqMIIHIDCCBQigAwIBAgITMwAAAgO7HlwAOGx0ygABAAACAzANBgkqhkiG9w0B +# AQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE +# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD +# VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAeFw0yNTAxMzAxOTQy +# NDZaFw0yNjA0MjIxOTQyNDZaMIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz +# aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv +# cnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25z +# MScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046REMwMC0wNUUwLUQ5NDcxJTAjBgNV +# BAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0GCSqGSIb3DQEB +# AQUAA4ICDwAwggIKAoICAQChl0MH5wAnOx8Uh8RtidF0J0yaFDHJYHTpPvRR16X1 +# KxGDYfT8PrcGjCLCiaOu3K1DmUIU4Rc5olndjappNuOgzwUoj43VbbJx5PFTY/a1 +# Z80tpqVP0OoKJlUkfDPSBLFgXWj6VgayRCINtLsUasy0w5gysD7ILPZuiQjace5K +# xASjKf2MVX1qfEzYBbTGNEijSQCKwwyc0eavr4Fo3X/+sCuuAtkTWissU64k8rK6 +# 0jsGRApiESdfuHr0yWAmc7jTOPNeGAx6KCL2ktpnGegLDd1IlE6Bu6BSwAIFHr7z +# OwIlFqyQuCe0SQALCbJhsT9y9iy61RJAXsU0u0TC5YYmTSbEI7g10dYx8Uj+vh9I +# nLoKYC5DpKb311bYVd0bytbzlfTRslRTJgotnfCAIGMLqEqk9/2VRGu9klJi1j9n +# VfqyYHYrMPOBXcrQYW0jmKNjOL47CaEArNzhDBia1wXdJANKqMvJ8pQe2m8/ciby +# DM+1BVZquNAov9N4tJF4ACtjX0jjXNDUMtSZoVFQH+FkWdfPWx1uBIkc97R+xRLu +# PjUypHZ5A3AALSke4TaRBvbvTBYyW2HenOT7nYLKTO4jw5Qq6cw3Z9zTKSPQ6D5l +# yiYpes5RR2MdMvJS4fCcPJFeaVOvuWFSQ/EGtVBShhmLB+5ewzFzdpf1UuJmuOQT +# TwIDAQABo4IBSTCCAUUwHQYDVR0OBBYEFLIpWUB+EeeQ29sWe0VdzxWQGJJ9MB8G +# A1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRYMFYwVKBSoFCG +# Tmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUy +# MFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEFBQcBAQRgMF4w +# XAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2Vy +# dHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3J0MAwG +# A1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwDgYDVR0PAQH/BAQD +# AgeAMA0GCSqGSIb3DQEBCwUAA4ICAQCQEMbesD6TC08R0oYCdSC452AQrGf/O89G +# Q54CtgEsbxzwGDVUcmjXFcnaJSTNedBKVXkBgawRonP1LgxH4bzzVj2eWNmzGIwO +# 1FlhldAPOHAzLBEHRoSZ4pddFtaQxoabU/N1vWyICiN60It85gnF5JD4MMXyd6pS +# 8eADIi6TtjfgKPoumWa0BFQ/aEzjUrfPN1r7crK+qkmLztw/ENS7zemfyx4kGRgw +# Y1WBfFqm/nFlJDPQBicqeU3dOp9hj7WqD0Rc+/4VZ6wQjesIyCkv5uhUNy2LhNDi +# 2leYtAiIFpmjfNk4GngLvC2Tj9IrOMv20Srym5J/Fh7yWAiPeGs3yA3QapjZTtfr +# 7NfzpBIJQ4xT/ic4WGWqhGlRlVBI5u6Ojw3ZxSZCLg3vRC4KYypkh8FdIWoKirji +# dEGlXsNOo+UP/YG5KhebiudTBxGecfJCuuUspIdRhStHAQsjv/dAqWBLlhorq2OC +# aP+wFhE3WPgnnx5pflvlujocPgsN24++ddHrl3O1FFabW8m0UkDHSKCh8QTwTkYO +# wu99iExBVWlbYZRz2qOIBjL/ozEhtCB0auKhfTLLeuNGBUaBz+oZZ+X9UAECoMhk +# ETjb6YfNaI1T7vVAaiuhBoV/JCOQT+RYZrgykyPpzpmwMNFBD1vdW/29q9nkTWoE +# hcEOO0L9NzCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZI +# hvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw +# DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x +# MjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAy +# MDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMC +# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV +# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp +# bWUtU3RhbXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC +# AQDk4aZM57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg4r25Phdg +# M/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPF +# dvWGUNzBRMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6 +# GnszrYBbfowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBp +# Dco2LXCOMcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2krnopN6zL64NF50Zu +# yjLVwIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3E +# XzTdEonW/aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0 +# lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1q +# GFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ +# +QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PA +# PBXbGjfHCBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkw +# EgYJKwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxG +# NSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARV +# MFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWlj +# cm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAK +# BggrBgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC +# AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX +# zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v +# cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI +# KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j +# b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG +# 9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0x +# M7U518JxNj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmC +# VgADsAW+iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449 +# xvNo32X2pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wM +# nosZiefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDS +# PeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2d +# Y3RILLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxn +# GSgkujhLmm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+Crvs +# QWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokL +# jzbaukz5m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL +# 6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggNN +# MIICNQIBATCB+aGB0aSBzjCByzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp +# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw +# b3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEn +# MCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOkRDMDAtMDVFMC1EOTQ3MSUwIwYDVQQD +# ExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQDN +# rxRX/iz6ss1lBCXG8P1LFxD0e6CBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w -# IFBDQSAyMDEwAhMzAAACEKvN5BYY7zmwAAEAAAIQMCIEIC5b102d224evVIH68x9 -# lEf8Au5vChR0bDNP4SViDwl6MA0GCSqGSIb3DQEBCwUABIICAHMgBiQOozDAIR+S -# O28IjquhCukrxIucbxjLUxu4ltpxavA+cedbwkdNPAMFAYEaLHeEYutciK6N5f4R -# VIJX/OSx2ZL6ac/xB5A/7rdoaHV7PK+F1y807INXMA2HPnJGDVbi7HD7nJp26Fuz -# P2K3aGE3kK06C2jXsjdqWsAZv32k1daX+OqFGJdOsmmua/qlF0AMWTXL7rsBStOH -# 5YI/KXtAsd9vvUbqcCMGeGmnRk6V7NmsKbQIkZ3kDk6YfKoZWmSknCKHF6LzCPkI -# bZSI8PNhcn19VUl9VS4WXGyWeGG5zBkOXnImAIKlaLMxR8Z9/kQVx84TrsDBl6Yh -# 34t6ISVDkQtre8SBG6fdjyWKmCOSntciVUlEom8ZwkadixIaf8nOoUJSYq5fl+4J -# HkcUaY79lJKL5DiENwdC4Lt6eNabRo3SPbQE7IQWbWx5h3et1QDitsZ7Gg9yAHaZ -# LS/t/SIAy+OXLXNxyttcyrbDvFI8A8tFE9P1w+3MC1n2cveqd6Wu07Z4l74Mj0LX -# yR+0h/KjfE6SPaL+N20E4CjM0bxRKZ+CuFj/A3tykEADdzFtsEfzUzTDkD3cJpOj -# fojWQNX1ly8Tu3G/WdAFqzrln2y8jCbVdPIqo1ntbu+BbYom1VQqAihYQB855+pG -# D8bOWbnrvhUua7gDFFDA762iZMxD +# IFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA7OQtPTAiGA8yMDI1MTIxMDE3MzI0 +# NVoYDzIwMjUxMjExMTczMjQ1WjB0MDoGCisGAQQBhFkKBAExLDAqMAoCBQDs5C09 +# AgEAMAcCAQACAgjlMAcCAQACAhNOMAoCBQDs5X69AgEAMDYGCisGAQQBhFkKBAIx +# KDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJKoZI +# hvcNAQELBQADggEBAFXCcBVLkxGEigIad7gAMsj2+SQdBANpzq4qPJXOu81TM3HC +# rAkCUTm3FRNc6YPdpfvl07lGlv/NHFCLyXL20d6PZ/1wlF5+WR2OvWjrktwDxYv8 +# cZqk7BrV9SB8xBe/GwVi7smKmlXhznqA6lFPO+VNfOwWcxn0H2yxEsAJKyDmgx/7 +# M8xnMTKeK8ulgSy4EoyGgFIO+nGHqxS0yaXe+OgzErkaavB1Qw7jfmm5/wlBCnwz +# 0UsbaequeL9UjA6FUw3Cc3F+3/D38BzyjJtTxjUVn+QiVWwOfikRJ2F7oZwpsJo3 +# yNIVpwJFpIV6VsqtxzaF0KQZBpS2lBGxVA17pFcxggQNMIIECQIBATCBkzB8MQsw +# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u +# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNy +# b3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAgO7HlwAOGx0ygABAAACAzAN +# BglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMC8G +# CSqGSIb3DQEJBDEiBCApggCahSc04fWyIz1KF4aeejwqHefyj2gzz7p9QsluFTCB +# +gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EIEsD3RtxlvaTxFOZZnpQw0DksPmV +# duo5SyK9h9w++hMtMIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldh +# c2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBD +# b3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIw +# MTACEzMAAAIDux5cADhsdMoAAQAAAgMwIgQgwIRXpw5w7cRbWSfOFZ15Z2Nf90Hi +# Ms/hZSpx+kv4aHcwDQYJKoZIhvcNAQELBQAEggIAM+zwgqPLhBOAumqVnUO/YRh7 +# sePgzUWqveSw/J01TAD8JVXufiGmu4neLrGFki/Nz8ytun2DhJP/3xDRu39y/9Pb +# t2qKabzaoASwH95fTjHLYEp0PhqkEZ1hkaaYjVC3TAG0LgU2mrvkEjL3doD5MXu8 +# WWQGcnB0Wera/3POf4ylyQbUUnzo/Pl9qUbjPVW/JouzzDzijObLcYp7IDgIDxGL +# sVJqMgzP1ZWBWsjjx4J0YiYORUnIVKWKPXt/0O3X9VO3zDfOnWRLF8mJj+ybEnqa +# Wd8LxLJnCxpmTAjtELLgC46UB0N4GHR0+ymSba35Ciz4Kzc+7R9E1Ajy1yd2rmGR +# M2u/eAV8MvKybIzgTd9Lukk9KJ5lvzV52CuYyzHOzYgcNt/mFgvM6gfMAef3CeN0 +# EU7ECvTEYqno7krSRi6+HD+R14+7EwXbiR0E+KAB2Ppgj7GqHWKeL/Owyv0A1oEa +# 4ocdqMApLcY908U7IzNu5qo7PPas/RBsB9J52++fyZ/9RyP31IYKu8/5xI5Ef7aH +# XIopbEpuMHpHeuWlYWlfkULa5tjk4iPVCTRVsgn7IimLY/wgVOLL4ueOzZZ6aNws +# Q37w/ocvXIH/qXUllulfh5vINVYqXK3d+l0QT8LCMIxXpJSSgtcFcPJG6aSdOFRQ +# r6EOj+C9DH5MueMd9SY= # SIG # End signature block diff --git a/externals/install-dotnet.sh b/externals/install-dotnet.sh index bd13ffa..c442946 100755 --- a/externals/install-dotnet.sh +++ b/externals/install-dotnet.sh @@ -1003,12 +1003,12 @@ copy_files_or_dirs_from_list() { cat | uniq | while read -r file_path; do local path="$(remove_beginning_slash "${file_path#$root_path}")" local target="$out_path/$path" - if [ "$override" = true ] || (! ([ -d "$target" ] || [ -e "$target" ] || [ -L "$target" ])); then + if [ "$override" = true ] || (! ([ -d "$target" ] || [ -e "$target" ])); then mkdir -p "$out_path/$(dirname "$path")" - if [ -d "$target" ] || [ -L "$target" ]; then + if [ -d "$target" ]; then rm -rf "$target" fi - cp -RP $override_switch "$root_path/$path" "$target" + cp -R $override_switch "$root_path/$path" "$target" fi done } @@ -1053,8 +1053,8 @@ extract_dotnet_package() { tar -xzf "$zip_path" -C "$temp_out_path" > /dev/null || failed=true local folders_with_version_regex='^.*/[0-9]+\.[0-9]+[^/]+/' - find "$temp_out_path" \( -type f -o -type l \) | grep -Eo "$folders_with_version_regex" | sort | copy_files_or_dirs_from_list "$temp_out_path" "$out_path" false - find "$temp_out_path" \( -type f -o -type l \) | grep -Ev "$folders_with_version_regex" | copy_files_or_dirs_from_list "$temp_out_path" "$out_path" "$override_non_versioned_files" + find "$temp_out_path" -type f | grep -Eo "$folders_with_version_regex" | sort | copy_files_or_dirs_from_list "$temp_out_path" "$out_path" false + find "$temp_out_path" -type f | grep -Ev "$folders_with_version_regex" | copy_files_or_dirs_from_list "$temp_out_path" "$out_path" "$override_non_versioned_files" validate_remote_local_file_sizes "$zip_path" "$remote_file_size" diff --git a/package-lock.json b/package-lock.json index 7c22f58..2f50c9d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "setup-dotnet", - "version": "5.3.0", + "version": "5.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "setup-dotnet", - "version": "5.3.0", + "version": "5.0.1", "license": "MIT", "dependencies": { "@actions/cache": "^5.0.5", diff --git a/package.json b/package.json index d56a35e..a7b0afb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "setup-dotnet", - "version": "5.3.0", + "version": "5.0.1", "private": true, "description": "setup dotnet action", "main": "dist/setup/index.js", diff --git a/src/installer.ts b/src/installer.ts index 85a2d82..451c66c 100644 --- a/src/installer.ts +++ b/src/installer.ts @@ -16,74 +16,21 @@ export interface DotnetVersion { qualityFlag: boolean; } -interface ReleaseIndexEntry { - 'channel-version': string; - 'support-phase': string; - 'release-type': string; -} - -interface ReleaseIndexResponse { - 'releases-index': ReleaseIndexEntry[]; -} - const QUALITY_INPUT_MINIMAL_MAJOR_TAG = 6; const LATEST_PATCH_SYNTAX_MINIMAL_MAJOR_TAG = 5; export class DotnetVersionResolver { private inputVersion: string; private resolvedArgument: DotnetVersion; - constructor( - version: string, - private quality: QualityOptions = '', - private dotnetChannel?: string - ) { + constructor(version: string) { this.inputVersion = version.trim(); this.resolvedArgument = {type: '', value: '', qualityFlag: false}; } - private isVersionChannel(channel: string): boolean { - // A.B format (e.g., 3.1, 8.0) - if (/^\d+\.\d+$/.test(channel)) return true; - // A.B.Cxx format (e.g., 8.0.1xx) is supported only for .NET 5.0+ - const latestPatchMatch = channel.match(/^(\d+)\.\d+\.\d{1}xx$/); - if (latestPatchMatch) { - const major = Number(latestPatchMatch[1]); - return ( - !Number.isNaN(major) && major >= LATEST_PATCH_SYNTAX_MINIMAL_MAJOR_TAG - ); - } - return false; - } - private async resolveVersionInput(): Promise { - if (this.inputVersion.toLowerCase() === 'latest') { - const channel = this.dotnetChannel || ''; - if (this.isVersionChannel(channel)) { - // A.B or A.B.Cxx channels are passed directly to the install script - this.resolvedArgument.value = channel; - } else { - // LTS, STS, or empty — resolve via releases index API - this.resolvedArgument.value = await this.getLatestVersion(channel); - } - this.resolvedArgument.type = 'channel'; - const latestChannelMajorTag = Number( - this.resolvedArgument.value.split('.')[0] - ); - this.resolvedArgument.qualityFlag = - !Number.isNaN(latestChannelMajorTag) && - latestChannelMajorTag >= QUALITY_INPUT_MINIMAL_MAJOR_TAG; - return; - } - - if (this.dotnetChannel) { - core.warning( - `The 'dotnet-channel' input is only supported when 'dotnet-version' is set to 'latest'.` - ); - } - if (!semver.validRange(this.inputVersion) && !this.isLatestPatchSyntax()) { 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, latest` + `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)) { @@ -125,22 +72,7 @@ export class DotnetVersionResolver { } else if (this.isNumericTag(major) && this.isNumericTag(minor)) { this.resolvedArgument.value = `${major}.${minor}`; } else if (this.isNumericTag(major)) { - // Starting with .NET 5, the minor version is always zero. - // Hardcode the earlier versions because they will not get new releases. - switch (major) { - case '1': - this.resolvedArgument.value = '1.1'; - break; - case '2': - this.resolvedArgument.value = '2.2'; - break; - case '3': - this.resolvedArgument.value = '3.1'; - break; - default: - this.resolvedArgument.value = `${major}.0`; - break; - } + 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'; @@ -164,62 +96,31 @@ export class DotnetVersionResolver { return this.resolvedArgument; } - private async getLatestVersion(channelFilter: 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( + const response = await httpClient.getJson( DotnetVersionResolver.DotnetCoreIndexUrl ); - const result = response.result; - const rawReleasesInfo = result?.['releases-index']; + const result = response.result || {}; + const releasesInfo: any[] = result['releases-index']; - if (!Array.isArray(rawReleasesInfo)) { - throw new Error('Unexpected response format from .NET releases index.'); - } - - let releasesInfo = rawReleasesInfo; - - // Filter out EOL versions - releasesInfo = releasesInfo.filter(info => info['support-phase'] !== 'eol'); - - // Filter out preview versions if quality is not 'preview' or 'daily' - // If quality is not specified, we assume strict stability (GA only) - const normalizedQuality = (this.quality || '').toLowerCase(); - if (!['preview', 'daily'].includes(normalizedQuality)) { - releasesInfo = releasesInfo.filter( - info => info['support-phase'] !== 'preview' - ); - } - - // Apply channel filter (LTS/STS) - if (channelFilter) { - const type = channelFilter.toLowerCase(); - releasesInfo = releasesInfo.filter(info => info['release-type'] === type); - } - - releasesInfo.sort((a, b) => { - const partsA = a['channel-version'].split('.').map(Number); - const partsB = b['channel-version'].split('.').map(Number); - for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) { - const diff = (partsB[i] || 0) - (partsA[i] || 0); - if (diff !== 0) return diff; - } - return 0; + const releaseInfo = releasesInfo.find(info => { + const sdkParts: string[] = info['channel-version'].split('.'); + return sdkParts[0] === majorTag; }); - if (releasesInfo.length === 0) { + if (!releaseInfo) { throw new Error( - `Could not find any active releases matching channel '${ - channelFilter || 'any' - }'` + `Could not find info for version with major tag: "${majorTag}" at ${DotnetVersionResolver.DotnetCoreIndexUrl}` ); } - return releasesInfo[0]['channel-version']; + return releaseInfo['channel-version']; } static DotnetCoreIndexUrl = @@ -378,16 +279,11 @@ export class DotnetCoreInstaller { constructor( private version: string, private quality: QualityOptions, - private architecture?: string, - private dotnetChannel?: string + private architecture?: string ) {} public async installDotnet(): Promise { - const versionResolver = new DotnetVersionResolver( - this.version, - this.quality, - this.dotnetChannel - ); + const versionResolver = new DotnetVersionResolver(this.version); const dotnetVersion = await versionResolver.createDotnetVersion(); const architectureArguments = diff --git a/src/setup-dotnet.ts b/src/setup-dotnet.ts index 969adbc..5a57680 100644 --- a/src/setup-dotnet.ts +++ b/src/setup-dotnet.ts @@ -15,7 +15,13 @@ import {restoreCache} from './cache-restore'; import {Outputs} from './constants'; import JSON5 from 'json5'; -const qualityOptions = ['daily', 'preview', 'ga'] as const; +const qualityOptions = [ + 'daily', + 'signed', + 'validated', + 'preview', + 'ga' +] as const; const supportedArchitectures = [ 'x64', 'x86', @@ -28,18 +34,7 @@ const supportedArchitectures = [ ] as const; type SupportedArchitecture = (typeof supportedArchitectures)[number]; -export type QualityOptions = (typeof qualityOptions)[number] | ''; - -function isValidChannel(channel: string): boolean { - const upper = channel.toUpperCase(); - if (upper === 'LTS' || upper === 'STS') return true; - // A.B format (e.g., 3.1, 8.0) - if (/^\d+\.\d+$/.test(channel)) return true; - // A.B.Cxx format (e.g., 8.0.1xx) - available since 5.0 - const match = channel.match(/^(?\d+)\.\d+\.\d{1}xx$/); - if (match && parseInt(match.groups!.major) >= 5) return true; - return false; -} +export type QualityOptions = (typeof qualityOptions)[number]; export async function run() { try { @@ -55,28 +50,6 @@ export async function run() { const versions = core.getMultilineInput('dotnet-version'); const installedDotnetVersions: (string | null)[] = []; const architecture = getArchitectureInput(); - let dotnetChannel = core.getInput('dotnet-channel'); - - const isLatestRequested = versions.some( - version => version && version.toLowerCase() === 'latest' - ); - if (dotnetChannel && !isValidChannel(dotnetChannel)) { - if (isLatestRequested) { - throw new Error( - `Value '${dotnetChannel}' is not supported for the 'dotnet-channel' option. Supported values are: LTS, STS, A.B (e.g. 8.0), A.B.Cxx (e.g. 8.0.1xx).` - ); - } else { - core.warning( - `Value '${dotnetChannel}' is not supported for the 'dotnet-channel' option and will be ignored because 'dotnet-version' is not set to 'latest'. Supported values are: LTS, STS, A.B (e.g. 8.0), A.B.Cxx (e.g. 8.0.1xx).` - ); - dotnetChannel = ''; - } - } else if (dotnetChannel && !isLatestRequested) { - core.warning( - `The 'dotnet-channel' input is only supported when 'dotnet-version' is set to 'latest'.` - ); - dotnetChannel = ''; - } const globalJsonFileInput = core.getInput('global-json-file'); if (globalJsonFileInput) { @@ -107,20 +80,17 @@ export async function run() { if (quality && !qualityOptions.includes(quality)) { throw new Error( - `Value '${quality}' is not supported for the 'dotnet-quality' option. Supported values are: daily, preview, ga.` + `Value '${quality}' is not supported for the 'dotnet-quality' option. Supported values are: daily, signed, validated, preview, ga.` ); } let dotnetInstaller: DotnetCoreInstaller; - const uniqueVersions = new Set( - versions.map(v => (v.toLowerCase() === 'latest' ? 'latest' : v)) - ); + const uniqueVersions = new Set(versions); for (const version of uniqueVersions) { dotnetInstaller = new DotnetCoreInstaller( version, quality, - architecture, - version.toLowerCase() === 'latest' ? dotnetChannel : undefined + architecture ); const installedVersion = await dotnetInstaller.installDotnet(); installedDotnetVersions.push(installedVersion); @@ -207,27 +177,9 @@ function getVersionFromGlobalJson(globalJsonPath: string): string { if (globalJson.sdk && globalJson.sdk.version) { version = globalJson.sdk.version; const rollForward = globalJson.sdk.rollForward; - if (rollForward) { - const [major, minor, featurePatch] = version.split('.'); - const feature = featurePatch.substring(0, 1); - - switch (rollForward) { - case 'latestMajor': - version = ''; - break; - - case 'latestMinor': - version = `${major}`; - break; - - case 'latestFeature': - version = `${major}.${minor}`; - break; - - case 'latestPatch': - version = `${major}.${minor}.${feature}xx`; - break; - } + if (rollForward && rollForward === 'latestFeature') { + const [major, minor] = version.split('.'); + version = `${major}.${minor}`; } } return version;