Compare commits

..

50 Commits

Author SHA1 Message Date
aa983c550d Merge pull request #428 from akv-platform/add-latest-patch-syntax
Add latest patch syntax
2023-05-24 08:41:30 +02:00
b891376106 Merge branch 'main' into add-latest-patch-syntax 2023-05-22 12:29:35 +02:00
b05a3f26b3 Fix review points, rebuild solution 2023-05-22 12:27:33 +02:00
5fdecd2063 Increase amount of retries for Dotnet installation scripts tests (#427)
* Increase amount of retries for Dotnet installation scripts tests

* Format: Increase amount of retries for Dotnet installation scripts tests
2023-05-19 08:50:54 +02:00
38b49fb717 Fix informational and debug messages 2023-05-18 12:39:22 +02:00
3cf3e230c1 Fix informational message 2023-05-18 12:01:55 +02:00
83a1653fa3 Update regular expresiion for isLatestPatchSyntax() 2023-05-18 11:46:40 +02:00
898aa0ce4d Fix typo 2023-05-18 11:16:22 +02:00
2f028bc044 update unit and e2e tests 2023-05-18 11:11:51 +02:00
21cf89aa73 Remove commented lines of code 2023-05-16 15:13:18 +02:00
fefaa59d2e update logic of outputting dotnet-version 2023-05-16 14:58:18 +02:00
e8501859aa Update unit tests 2023-05-15 14:24:28 +02:00
426d75d071 Merge branch 'main' into add-latest-patch-syntax 2023-05-15 14:13:18 +02:00
0f534f5829 Refactor and update unit-tests (#418)
* Update unit-tests and dotnet-install scripts
2023-05-15 14:09:29 +02:00
fbdbede901 Rebuild solution 2023-05-15 11:49:01 +02:00
0bc43909e0 Update mechanic of outputting installed dotnet version 2023-05-15 11:45:07 +02:00
c5a57b219c Update unit-tests 2023-05-05 10:44:54 +02:00
6adeb768ce Update docs 2023-05-05 10:43:09 +02:00
f425be78f5 Merge branch 'main' of https://github.com/actions/setup-dotnet
into add-latest-patch-syntax
2023-04-20 14:47:10 +02:00
fc8786b149 Update e2e tests and e2e test infrastructure (#414) 2023-04-20 14:42:27 +02:00
7d08dc7593 Add e2e test 2023-04-19 16:48:00 +02:00
e0a32d6459 Merge branch 'update-e2e-tests' into add-latest-patch-syntax 2023-04-19 16:43:50 +02:00
255362be61 Silent dotnet new globaljson command 2023-04-19 16:32:38 +02:00
50b46b3b1d Update verify-dotnet.ps1 2023-04-19 16:24:27 +02:00
e8ac21d503 Fix typos 2023-04-19 15:41:28 +02:00
a79ce57e6b Update contribution documentation 2023-04-19 13:43:18 +02:00
180a15970f Update e2e test infrastructure 2023-04-19 13:40:44 +02:00
b72f430d36 Update e2e tests 2023-04-13 17:28:59 +02:00
559e47b01b Fix review points 2023-04-13 10:33:52 +02:00
7358a44590 Update unit tests 2023-04-12 16:44:51 +02:00
34c30d0e81 Refactor logic 2023-04-12 15:44:03 +02:00
5f570676c2 Add unit and e2e tests 2023-04-11 15:53:11 +02:00
aa34a3ceaa Fix typo 2023-04-11 13:44:35 +02:00
12f70884d7 Fix typo 2023-04-11 13:23:50 +02:00
0318091611 Update resolveVersionInput() 2023-04-11 13:20:34 +02:00
f199d27aa1 Update solution 2023-04-10 16:58:35 +02:00
660c25a321 Rebuild action 2023-04-10 16:24:56 +02:00
4f6b2f576a Change the logic to support A.B.Cxx 2023-04-10 16:23:29 +02:00
920b830bd1 Fix glob bug in package.json scripts section (#401) 2023-04-03 12:48:02 +02:00
abdd14ee80 Update configuration files (#398)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-03-15 00:33:04 +01:00
1d9f0dad5b Accept absolute paths for 'global-json-file' input (#396) 2023-03-13 12:34:56 +01:00
2699274f6e Merge pull request #395 from akv-platform/reusable-config
Turn on automatic update of ESLint and Prettier configuration files
2023-03-10 10:56:01 +01:00
ca579e0fb2 Add update-config-files.yml 2023-03-09 13:47:42 +01:00
c82240598b Add and configure ESLint and update configuration for Prettier (#391)
* Apply ESLint config and update Prettier

* Update dependencies and rebuild

* Update docs
2023-03-09 14:43:05 +02:00
926f442022 Update workflow badges (#377) 2023-01-31 10:29:16 +02:00
c41fd15071 Merge pull request #375 from akv-platform/fix-error-message
Make throwing errors in setup-dotnet more informative
2023-01-30 14:21:47 +01:00
0c8652569e Fix error message 2023-01-26 10:35:10 +01:00
3cf27f13bb Update release-new-action-version.yml (#368) 2023-01-12 13:51:42 +01:00
ae8edb8fff Bump json5 from 2.1.3 to 2.2.3 (#365)
Bumps [json5](https://github.com/json5/json5) from 2.1.3 to 2.2.3.
- [Release notes](https://github.com/json5/json5/releases)
- [Changelog](https://github.com/json5/json5/blob/main/CHANGELOG.md)
- [Commits](https://github.com/json5/json5/compare/v2.1.3...v2.2.3)

---
updated-dependencies:
- dependency-name: json5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-06 17:20:11 +01:00
82b2b40816 Merge pull request #364 from actions/update-team-setup-actions-team
Update CODEOWNERS
2022-12-27 09:27:52 +01:00
41 changed files with 4381 additions and 1761 deletions

6
.eslintignore Normal file
View File

@ -0,0 +1,6 @@
# Ignore list
/*
# Do not ignore these folders:
!__tests__/
!src/

50
.eslintrc.js Normal file
View File

@ -0,0 +1,50 @@
// This is a reusable configuration file copied from https://github.com/actions/reusable-workflows/tree/main/reusable-configurations. Please don't make changes to this file as it's the subject of an automatic update.
module.exports = {
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:eslint-plugin-jest/recommended',
'eslint-config-prettier'
],
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint', 'eslint-plugin-jest'],
rules: {
'@typescript-eslint/no-require-imports': 'error',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/ban-ts-comment': [
'error',
{
'ts-ignore': 'allow-with-description'
}
],
'no-console': 'error',
'yoda': 'error',
'prefer-const': [
'error',
{
destructuring: 'all'
}
],
'no-control-regex': 'off',
'no-constant-condition': ['error', {checkLoops: false}]
},
overrides: [
{
files: ['**/*{test,spec}.ts'],
rules: {
'@typescript-eslint/no-unused-vars': 'off',
'jest/no-standalone-expect': 'off',
'jest/no-conditional-expect': 'off',
'no-console': 'off',
}
}
],
env: {
node: true,
es6: true,
'jest/globals': true
}
};

1
.gitattributes vendored
View File

@ -1 +1,2 @@
* text=auto eol=lf
.licenses/** -diff linguist-generated=true .licenses/** -diff linguist-generated=true

View File

@ -14,4 +14,4 @@ on:
jobs: jobs:
call-basic-validation: call-basic-validation:
name: Basic validation name: Basic validation
uses: actions/reusable-workflows/.github/workflows/basic-validation.yml@main uses: actions/reusable-workflows/.github/workflows/basic-validation.yml@main

View File

@ -14,4 +14,4 @@ on:
jobs: jobs:
call-check-dist: call-check-dist:
name: Check dist/ name: Check dist/
uses: actions/reusable-workflows/.github/workflows/check-dist.yml@main uses: actions/reusable-workflows/.github/workflows/check-dist.yml@main

View File

@ -2,13 +2,13 @@ name: CodeQL analysis
on: on:
push: push:
branches: [ main ] branches: [main]
pull_request: pull_request:
branches: [ main ] branches: [main]
schedule: schedule:
- cron: '0 3 * * 0' - cron: '0 3 * * 0'
jobs: jobs:
call-codeQL-analysis: call-codeQL-analysis:
name: CodeQL analysis name: CodeQL analysis
uses: actions/reusable-workflows/.github/workflows/codeql-analysis.yml@main uses: actions/reusable-workflows/.github/workflows/codeql-analysis.yml@main

View File

@ -24,7 +24,7 @@ jobs:
- name: Clear toolcache - name: Clear toolcache
shell: pwsh shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }} run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
- name: Setup dotnet 2.2.402 and 3.1.404 - name: Setup dotnet 2.2.402, 3.1.404 and 3.0.x
uses: ./ uses: ./
with: with:
dotnet-version: | dotnet-version: |
@ -33,7 +33,7 @@ jobs:
3.0.x 3.0.x
- name: Verify dotnet - name: Verify dotnet
shell: pwsh shell: pwsh
run: __tests__/verify-dotnet.ps1 2.2.402 3.1.404 '3.0' run: __tests__/verify-dotnet.ps1 -Patterns "^2.2.402$", "^3.1.404$", "^3.0"
test-setup-full-version: test-setup-full-version:
runs-on: ${{ matrix.operating-system }} runs-on: ${{ matrix.operating-system }}
@ -60,13 +60,9 @@ jobs:
source-url: https://api.nuget.org/v3/index.json source-url: https://api.nuget.org/v3/index.json
env: env:
NUGET_AUTH_TOKEN: NOTATOKEN NUGET_AUTH_TOKEN: NOTATOKEN
- name: Verify nuget config file
shell: pwsh
run: |
if (-Not (Test-Path "../nuget.config")) { throw "nuget file not generated correctly" }
- name: Verify dotnet - name: Verify dotnet
shell: pwsh shell: pwsh
run: __tests__/verify-dotnet.ps1 3.1.201 2.2.402 run: __tests__/verify-dotnet.ps1 -Patterns "^3.1.201$", "^2.2.402$" -CheckNugetConfig
test-setup-without-patch-version: test-setup-without-patch-version:
runs-on: ${{ matrix.operating-system }} runs-on: ${{ matrix.operating-system }}
@ -91,7 +87,7 @@ jobs:
dotnet-version: '2.2' dotnet-version: '2.2'
- name: Verify dotnet - name: Verify dotnet
shell: pwsh shell: pwsh
run: __tests__/verify-dotnet.ps1 3.1 2.2 run: __tests__/verify-dotnet.ps1 -Patterns "^3.1", "^2.2"
test-setup-prerelease-version: test-setup-prerelease-version:
runs-on: ${{ matrix.operating-system }} runs-on: ${{ matrix.operating-system }}
@ -105,17 +101,13 @@ jobs:
- name: Clear toolcache - name: Clear toolcache
shell: pwsh shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }} run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
- name: Setup dotnet '2.2'
uses: ./
with:
dotnet-version: '2.2'
- name: Setup dotnet '3.1.100-preview1-014459' - name: Setup dotnet '3.1.100-preview1-014459'
uses: ./ uses: ./
with: with:
dotnet-version: '3.1.100-preview1-014459' dotnet-version: '3.1.100-preview1-014459'
- name: Verify dotnet - name: Verify dotnet
shell: pwsh shell: pwsh
run: __tests__/verify-dotnet.ps1 3.1.100-preview1-014459 run: __tests__/verify-dotnet.ps1 -Patterns "3.1.100-preview1-014459"
test-setup-latest-patch-version: test-setup-latest-patch-version:
runs-on: ${{ matrix.operating-system }} runs-on: ${{ matrix.operating-system }}
@ -139,7 +131,28 @@ jobs:
dotnet-version: 2.2.X dotnet-version: 2.2.X
- name: Verify dotnet - name: Verify dotnet
shell: pwsh shell: pwsh
run: __tests__/verify-dotnet.ps1 '2.2' '3.1' 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: test-setup-with-wildcard:
runs-on: ${{ matrix.operating-system }} runs-on: ${{ matrix.operating-system }}
@ -163,7 +176,7 @@ jobs:
dotnet-version: 2.2.* dotnet-version: 2.2.*
- name: Verify dotnet - name: Verify dotnet
shell: pwsh shell: pwsh
run: __tests__/verify-dotnet.ps1 3.1 2.2 run: __tests__/verify-dotnet.ps1 -Patterns "^3.1", "^2.2"
test-setup-global-json-specified-and-version: test-setup-global-json-specified-and-version:
runs-on: ${{ matrix.operating-system }} runs-on: ${{ matrix.operating-system }}
@ -181,7 +194,7 @@ jobs:
shell: bash shell: bash
run: | run: |
mkdir subdirectory mkdir subdirectory
echo '{"sdk":{"version": "2.2","rollForward": "latestFeature"}}' > ./subdirectory/global.json echo '{"sdk":{"version": "2.2.207","rollForward": "latestFeature"}}' > ./subdirectory/global.json
- name: Setup dotnet - name: Setup dotnet
uses: ./ uses: ./
with: with:
@ -189,91 +202,113 @@ jobs:
global-json-file: ./subdirectory/global.json global-json-file: ./subdirectory/global.json
- name: Verify dotnet - name: Verify dotnet
shell: pwsh shell: pwsh
run: __tests__/verify-dotnet.ps1 2.2 3.1 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: test-setup-with-dotnet-quality:
runs-on: ${{ matrix.operating-system }} runs-on: ${{ matrix.operating-system }}
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
operating-system: [ubuntu-latest, windows-latest, macOS-latest] operating-system: [ubuntu-latest, windows-latest, macOS-latest]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Clear toolcache - name: Clear toolcache
shell: pwsh shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }} run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
- name: Setup dotnet 7.0 with preview quality - name: Setup dotnet 7.0 with preview quality
uses: ./ uses: ./
with: with:
dotnet-version: "7.0" dotnet-version: '7.0'
dotnet-quality: "preview" dotnet-quality: 'preview'
- name: Verify preview version - name: Verify dotnet
shell: pwsh shell: pwsh
run: | run: __tests__/verify-dotnet.ps1 -Patterns "^7\.0\.\d+-"
$version = & dotnet --version
Write-Host "Installed version: $version"
if (-not ($version.Contains("preview") -or $version.Contains("rc"))) { throw "Unexpected version" }
test-dotnet-version-output-during-single-version-installation: test-dotnet-version-output-during-single-version-installation:
runs-on: ${{ matrix.operating-system }} runs-on: ${{ matrix.operating-system }}
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
operating-system: [ubuntu-latest, windows-latest, macOS-latest] operating-system: [ubuntu-latest, windows-latest, macOS-latest]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Clear toolcache - name: Clear toolcache
shell: pwsh shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }} run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
- name: Setup dotnet 6.0.401 - name: Setup dotnet 6.0.401
uses: ./ uses: ./
id: step1 id: step1
with: with:
dotnet-version: "6.0.401" dotnet-version: '6.0.401'
- name: Verify value of the dotnet-version output
shell: pwsh
run: |
$version = & dotnet --version
Write-Host "Installed version: $version"
if (-not ($version -eq '${{steps.step1.outputs.dotnet-version}}')) { throw "Unexpected output value" }
- name: Verify value of the dotnet-version output
shell: pwsh
run: |
$version = & dotnet --version
Write-Host "Installed version: $version"
if (-not ($version -eq '${{steps.step1.outputs.dotnet-version}}')) { throw "Unexpected output value" }
test-dotnet-version-output-during-multiple-version-installation: test-dotnet-version-output-during-multiple-version-installation:
runs-on: ${{ matrix.operating-system }} runs-on: ${{ matrix.operating-system }}
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
operating-system: [ubuntu-latest, windows-latest, macOS-latest] operating-system: [ubuntu-latest, windows-latest, macOS-latest]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Clear toolcache - name: Clear toolcache
shell: pwsh shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }} run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
- name: Setup dotnet 6.0.401, 5.0.408, 7.0.100-rc.1.22431.12
uses: ./
id: step2
with:
dotnet-version: |
7.0.100-rc.1.22431.12
6.0.401
5.0.408
- name: Verify value of the dotnet-version output - name: Setup dotnet 6.0.401, 5.0.408, 7.0.100-rc.1.22431.12
shell: pwsh uses: ./
run: | id: step2
$version = "7.0.100-rc.1.22431.12" with:
if (-not ($version -eq '${{steps.step2.outputs.dotnet-version}}')) { throw "Unexpected output value" } dotnet-version: |
7.0.100-rc.1.22431.12
6.0.401
5.0.408
- name: Verify value of the dotnet-version output
shell: pwsh
run: |
$version = "7.0.100-rc.1.22431.12"
if (-not ($version -eq '${{steps.step2.outputs.dotnet-version}}')) { throw "Unexpected output value" }
test-proxy: test-proxy:
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: container:
image: mcr.microsoft.com/dotnet/core/runtime-deps:3.0-bionic image: ubuntu:latest
options: --dns 127.0.0.1 options: --dns 127.0.0.1
services: services:
squid-proxy: squid-proxy:
@ -286,21 +321,29 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Clear tool cache - name: Install Powershell
run: rm -rf "/usr/share/dotnet"
- name: Install curl
run: | run: |
apt update apt-get update
apt -y install curl apt-get install -y wget apt-transport-https software-properties-common
- name: Setup dotnet 3.1.201 wget -q "https://packages.microsoft.com/config/ubuntu/$(lsb_release -rs)/packages-microsoft-prod.deb"
dpkg -i packages-microsoft-prod.deb
rm packages-microsoft-prod.deb
apt-get update
apt-get install -y powershell
- name: Clear toolcache
shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
- name: Setup dotnet 6.0
uses: ./ uses: ./
with: with:
dotnet-version: 3.1.201 dotnet-version: 6.0
source-url: https://api.nuget.org/v3/index.json source-url: https://api.nuget.org/v3/index.json
env: env:
NUGET_AUTH_TOKEN: NOTATOKEN NUGET_AUTH_TOKEN: NOTATOKEN
- name: Verify dotnet - name: Verify dotnet
run: __tests__/verify-dotnet.sh 3.1.201 shell: pwsh
run: |
__tests__/verify-dotnet.ps1 -Patterns "^6.0" -CheckNugetConfig
test-bypass-proxy: test-bypass-proxy:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -310,8 +353,9 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Clear tool cache - name: Clear toolcache
run: rm -rf "/usr/share/dotnet" shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
- name: Setup dotnet 3.1.201 - name: Setup dotnet 3.1.201
uses: ./ uses: ./
with: with:
@ -320,4 +364,5 @@ jobs:
env: env:
NUGET_AUTH_TOKEN: NOTATOKEN NUGET_AUTH_TOKEN: NOTATOKEN
- name: Verify dotnet - name: Verify dotnet
run: __tests__/verify-dotnet.sh 3.1.201 shell: pwsh
run: __tests__/verify-dotnet.ps1 -Patterns "^3.1.201$" -CheckNugetConfig

View File

@ -12,4 +12,4 @@ on:
jobs: jobs:
call-licensed: call-licensed:
name: Licensed name: Licensed
uses: actions/reusable-workflows/.github/workflows/licensed.yml@main uses: actions/reusable-workflows/.github/workflows/licensed.yml@main

View File

@ -21,9 +21,9 @@ jobs:
name: releaseNewActionVersion name: releaseNewActionVersion
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Update the ${{ env.TAG_NAME }} tag - name: Update the ${{ env.TAG_NAME }} tag
id: update-major-tag id: update-major-tag
uses: actions/publish-action@v0.2.1 uses: actions/publish-action@v0.2.2
with: with:
source-tag: ${{ env.TAG_NAME }} source-tag: ${{ env.TAG_NAME }}
slack-webhook: ${{ secrets.SLACK_WEBHOOK }} slack-webhook: ${{ secrets.SLACK_WEBHOOK }}

View File

@ -18,7 +18,7 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
operating-system: [ubuntu-latest, windows-latest, macOS-latest] operating-system: [ubuntu-latest, windows-latest, macOS-latest]
dotnet-version: ['2.1', '2.2', '3.0', '3.1', '5.0'] dotnet-version: ['2.1', '2.2', '3.0', '3.1', '5.0', '6.0', '7.0', '8.0']
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
@ -29,9 +29,7 @@ jobs:
uses: ./ uses: ./
with: with:
dotnet-version: ${{ matrix.dotnet-version }} dotnet-version: ${{ matrix.dotnet-version }}
- name: Check installed version - name: Verify installed version
shell: pwsh shell: pwsh
run: | run: |
$version = & dotnet --version __tests__/verify-dotnet.ps1 -Patterns "^${{ matrix.dotnet-version }}"
Write-Host "Installed version: $version"
if (-not $version.StartsWith("${{ matrix.dotnet-version }}")) { throw "Unexpected version" }

View File

@ -0,0 +1,11 @@
name: Update configuration files
on:
schedule:
- cron: '0 3 * * 0'
workflow_dispatch:
jobs:
call-update-configuration-files:
name: Update configuration files
uses: actions/reusable-workflows/.github/workflows/update-config-files.yml@main

4
.gitignore vendored
View File

@ -3,8 +3,8 @@ global.json
lib/ lib/
node_modules/ node_modules/
__tests__/runner/* __tests__/runner/*
__tests__/sample-csproj/bin/ __tests__/e2e-test-csproj/bin/
__tests__/sample-csproj/obj/ __tests__/e2e-test-csproj/obj/
# Rest of the file pulled from https://github.com/github/gitignore/blob/master/Node.gitignore # Rest of the file pulled from https://github.com/github/gitignore/blob/master/Node.gitignore
# Logs # Logs

View File

@ -2,3 +2,4 @@
. "$(dirname -- "$0")/_/husky.sh" . "$(dirname -- "$0")/_/husky.sh"
npm run format npm run format
npm run lint:fix

View File

@ -3,3 +3,4 @@
# Tests are not run at push time since they can take 2-4 minutes to complete # Tests are not run at push time since they can take 2-4 minutes to complete
npm run format-check npm run format-check
npm run lint

7
.prettierignore Normal file
View File

@ -0,0 +1,7 @@
# Ignore list
/*
# Do not ignore these folders:
!__tests__/
!.github/
!src/

11
.prettierrc.js Normal file
View File

@ -0,0 +1,11 @@
// This is a reusable configuration file copied from https://github.com/actions/reusable-workflows/tree/main/reusable-configurations. Please don't make changes to this file as it's the subject of an automatic update.
module.exports = {
printWidth: 80,
tabWidth: 2,
useTabs: false,
semi: true,
singleQuote: true,
trailingComma: 'none',
bracketSpacing: false,
arrowParens: 'avoid'
};

View File

@ -1,12 +0,0 @@
{
"printWidth": 80,
"tabWidth": 2,
"useTabs": false,
"semi": true,
"singleQuote": true,
"trailingComma": "none",
"bracketSpacing": false,
"arrowParens": "avoid",
"parser": "typescript",
"endOfLine": "auto"
}

View File

@ -1,6 +1,7 @@
# setup-dotnet # setup-dotnet
[![GitHub Actions Status](https://github.com/actions/setup-dotnet/workflows/Main%20workflow/badge.svg)](https://github.com/actions/setup-dotnet) [![Basic validation](https://github.com/actions/setup-dotnet/actions/workflows/basic-validation.yml/badge.svg?branch=main)](https://github.com/actions/setup-dotnet/actions/workflows/basic-validation.yml)
[![e2e tests](https://github.com/actions/setup-dotnet/actions/workflows/e2e-tests.yml/badge.svg?branch=main)](https://github.com/actions/setup-dotnet/actions/workflows/e2e-tests.yml)
This action sets up a [.NET CLI](https://github.com/dotnet/sdk) environment for use in actions by: This action sets up a [.NET CLI](https://github.com/dotnet/sdk) environment for use in actions by:
@ -48,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.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.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** 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 ## 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**. 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 ```yml
steps: steps:

View File

@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`authutil tests Existing config not in repo root, sets up a partial NuGet.config user/PAT for GPR 1`] = ` exports[`authutil tests existing config not in repo root, sets up a partial NuGet.config user/PAT for GPR 1`] = `
"<?xml version=\\"1.0\\"?> "<?xml version=\\"1.0\\"?>
<configuration> <configuration>
<config> <config>
@ -15,7 +15,7 @@ exports[`authutil tests Existing config not in repo root, sets up a partial NuGe
</configuration>" </configuration>"
`; `;
exports[`authutil tests Existing config w/ Azure Artifacts source and NuGet.org, sets up a partial NuGet.config user/PAT for GPR 1`] = ` exports[`authutil tests existing config w/ Azure Artifacts source and NuGet.org, sets up a partial NuGet.config user/PAT for GPR 1`] = `
"<?xml version=\\"1.0\\"?> "<?xml version=\\"1.0\\"?>
<configuration> <configuration>
<config> <config>
@ -30,7 +30,7 @@ exports[`authutil tests Existing config w/ Azure Artifacts source and NuGet.org,
</configuration>" </configuration>"
`; `;
exports[`authutil tests Existing config w/ GPR source and NuGet.org, sets up a partial NuGet.config user/PAT for GPR 1`] = ` exports[`authutil tests existing config w/ GPR source and NuGet.org, sets up a partial NuGet.config user/PAT for GPR 1`] = `
"<?xml version=\\"1.0\\"?> "<?xml version=\\"1.0\\"?>
<configuration> <configuration>
<config> <config>
@ -45,7 +45,7 @@ exports[`authutil tests Existing config w/ GPR source and NuGet.org, sets up a p
</configuration>" </configuration>"
`; `;
exports[`authutil tests Existing config w/ no GPR sources, sets up a full NuGet.config with URL and user/PAT for GPR 1`] = ` exports[`authutil tests existing config w/ no GPR sources, sets up a full NuGet.config with URL and user/PAT for GPR 1`] = `
"<?xml version=\\"1.0\\"?> "<?xml version=\\"1.0\\"?>
<configuration> <configuration>
<config> <config>
@ -63,7 +63,7 @@ exports[`authutil tests Existing config w/ no GPR sources, sets up a full NuGet.
</configuration>" </configuration>"
`; `;
exports[`authutil tests Existing config w/ no sources, sets up a full NuGet.config with URL and user/PAT for GPR 1`] = ` exports[`authutil tests existing config w/ no sources, sets up a full NuGet.config with URL and user/PAT for GPR 1`] = `
"<?xml version=\\"1.0\\"?> "<?xml version=\\"1.0\\"?>
<configuration> <configuration>
<config> <config>
@ -81,7 +81,7 @@ exports[`authutil tests Existing config w/ no sources, sets up a full NuGet.conf
</configuration>" </configuration>"
`; `;
exports[`authutil tests Existing config w/ only Azure Artifacts source, sets up a partial NuGet.config user/PAT for GPR 1`] = ` exports[`authutil tests existing config w/ only Azure Artifacts source, sets up a partial NuGet.config user/PAT for GPR 1`] = `
"<?xml version=\\"1.0\\"?> "<?xml version=\\"1.0\\"?>
<configuration> <configuration>
<config> <config>
@ -96,7 +96,7 @@ exports[`authutil tests Existing config w/ only Azure Artifacts source, sets up
</configuration>" </configuration>"
`; `;
exports[`authutil tests Existing config w/ only GPR source, sets up a partial NuGet.config user/PAT for GPR 1`] = ` exports[`authutil tests existing config w/ only GPR source, sets up a partial NuGet.config user/PAT for GPR 1`] = `
"<?xml version=\\"1.0\\"?> "<?xml version=\\"1.0\\"?>
<configuration> <configuration>
<config> <config>
@ -111,7 +111,7 @@ exports[`authutil tests Existing config w/ only GPR source, sets up a partial Nu
</configuration>" </configuration>"
`; `;
exports[`authutil tests Existing config w/ two GPR sources, sets up a partial NuGet.config user/PAT for GPR 1`] = ` exports[`authutil tests existing config w/ two GPR sources, sets up a partial NuGet.config user/PAT for GPR 1`] = `
"<?xml version=\\"1.0\\"?> "<?xml version=\\"1.0\\"?>
<configuration> <configuration>
<config> <config>
@ -130,7 +130,7 @@ exports[`authutil tests Existing config w/ two GPR sources, sets up a partial Nu
</configuration>" </configuration>"
`; `;
exports[`authutil tests No existing config, sets up a full NuGet.config with URL and other owner/PAT for GPR 1`] = ` exports[`authutil tests no existing config, sets up a full NuGet.config with URL and other owner/PAT for GPR 1`] = `
"<?xml version=\\"1.0\\"?> "<?xml version=\\"1.0\\"?>
<configuration> <configuration>
<config> <config>
@ -148,7 +148,7 @@ exports[`authutil tests No existing config, sets up a full NuGet.config with URL
</configuration>" </configuration>"
`; `;
exports[`authutil tests No existing config, sets up a full NuGet.config with URL and token for other source 1`] = ` exports[`authutil tests no existing config, sets up a full NuGet.config with URL and token for other source 1`] = `
"<?xml version=\\"1.0\\"?> "<?xml version=\\"1.0\\"?>
<configuration> <configuration>
<config> <config>
@ -166,7 +166,7 @@ exports[`authutil tests No existing config, sets up a full NuGet.config with URL
</configuration>" </configuration>"
`; `;
exports[`authutil tests No existing config, sets up a full NuGet.config with URL and user/PAT for GPR 1`] = ` exports[`authutil tests no existing config, sets up a full NuGet.config with URL and user/PAT for GPR 1`] = `
"<?xml version=\\"1.0\\"?> "<?xml version=\\"1.0\\"?>
<configuration> <configuration>
<config> <config>

View File

@ -1,336 +1,336 @@
import * as io from '@actions/io'; import * as io from '@actions/io';
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
const fakeSourcesDirForTesting = path.join( const fakeSourcesDirForTesting = path.join(
__dirname, __dirname,
'runner', 'runner',
path.join(Math.random().toString(36).substring(7)), path.join(Math.random().toString(36).substring(7)),
's' 's'
); );
const invalidNuGetConfig: string = `<?xml version="1.0" encoding="utf-8"?>`; const invalidNuGetConfig = `<?xml version="1.0" encoding="utf-8"?>`;
const emptyNuGetConfig: string = `<?xml version="1.0" encoding="utf-8"?> const emptyNuGetConfig = `<?xml version="1.0" encoding="utf-8"?>
<configuration> <configuration>
</configuration>`; </configuration>`;
const nugetorgNuGetConfig: string = `<?xml version="1.0" encoding="utf-8"?> const nugetorgNuGetConfig = `<?xml version="1.0" encoding="utf-8"?>
<configuration> <configuration>
<packageSources> <packageSources>
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" /> <add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
</packageSources> </packageSources>
</configuration>`; </configuration>`;
const gprnugetorgNuGetConfig: string = `<?xml version="1.0" encoding="utf-8"?> const gprnugetorgNuGetConfig = `<?xml version="1.0" encoding="utf-8"?>
<configuration> <configuration>
<packageSources> <packageSources>
<add key="GPR" value="https://nuget.pkg.github.com/OwnerName/index.json" protocolVersion="3" /> <add key="GPR" value="https://nuget.pkg.github.com/OwnerName/index.json" protocolVersion="3" />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" /> <add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
</packageSources> </packageSources>
</configuration>`; </configuration>`;
const gprNuGetConfig: string = `<?xml version="1.0" encoding="utf-8"?> const gprNuGetConfig = `<?xml version="1.0" encoding="utf-8"?>
<configuration> <configuration>
<packageSources> <packageSources>
<add key="GPR" value="https://nuget.pkg.github.com/OwnerName/index.json" protocolVersion="3" /> <add key="GPR" value="https://nuget.pkg.github.com/OwnerName/index.json" protocolVersion="3" />
</packageSources> </packageSources>
</configuration>`; </configuration>`;
const twogprNuGetConfig: string = `<?xml version="1.0" encoding="utf-8"?> const twogprNuGetConfig = `<?xml version="1.0" encoding="utf-8"?>
<configuration> <configuration>
<packageSources> <packageSources>
<add key="GPR-GitHub" value="https://nuget.pkg.github.com/OwnerName/index.json" protocolVersion="3" /> <add key="GPR-GitHub" value="https://nuget.pkg.github.com/OwnerName/index.json" protocolVersion="3" />
<add key="GPR-Actions" value="https://nuget.pkg.github.com/actions/index.json" protocolVersion="3" /> <add key="GPR-Actions" value="https://nuget.pkg.github.com/actions/index.json" protocolVersion="3" />
</packageSources> </packageSources>
</configuration>`; </configuration>`;
const spaceNuGetConfig: string = `<?xml version="1.0" encoding="utf-8"?> const spaceNuGetConfig = `<?xml version="1.0" encoding="utf-8"?>
<configuration> <configuration>
<packageSources> <packageSources>
<add key="GPR GitHub" value="https://nuget.pkg.github.com/OwnerName/index.json" protocolVersion="3" /> <add key="GPR GitHub" value="https://nuget.pkg.github.com/OwnerName/index.json" protocolVersion="3" />
</packageSources> </packageSources>
</configuration>`; </configuration>`;
const azureartifactsNuGetConfig: string = `<?xml version="1.0" encoding="utf-8"?> const azureartifactsNuGetConfig = `<?xml version="1.0" encoding="utf-8"?>
<configuration> <configuration>
<packageSources> <packageSources>
<add key="AzureArtifacts" value="https://pkgs.dev.azure.com/amullans/_packaging/GitHubBuilds/nuget/v3/index.json" protocolVersion="3" /> <add key="AzureArtifacts" value="https://pkgs.dev.azure.com/amullans/_packaging/GitHubBuilds/nuget/v3/index.json" protocolVersion="3" />
</packageSources> </packageSources>
</configuration>`; </configuration>`;
const azureartifactsnugetorgNuGetConfig: string = `<?xml version="1.0" encoding="utf-8"?> const azureartifactsnugetorgNuGetConfig = `<?xml version="1.0" encoding="utf-8"?>
<configuration> <configuration>
<packageSources> <packageSources>
<add key="AzureArtifacts" value="https://pkgs.dev.azure.com/amullans/_packaging/GitHubBuilds/nuget/v3/index.json" protocolVersion="3" /> <add key="AzureArtifacts" value="https://pkgs.dev.azure.com/amullans/_packaging/GitHubBuilds/nuget/v3/index.json" protocolVersion="3" />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" /> <add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
</packageSources> </packageSources>
</configuration>`; </configuration>`;
// We want a NuGet.config one level above the sources directory, so it doesn't trample a user's NuGet.config but is still picked up by NuGet/dotnet. // We want a NuGet.config one level above the sources directory, so it doesn't trample a user's NuGet.config but is still picked up by NuGet/dotnet.
const nugetConfigFile = path.join(fakeSourcesDirForTesting, '../nuget.config'); const nugetConfigFile = path.join(fakeSourcesDirForTesting, '../nuget.config');
process.env['GITHUB_REPOSITORY'] = 'OwnerName/repo'; process.env['GITHUB_REPOSITORY'] = 'OwnerName/repo';
import * as auth from '../src/authutil'; import * as auth from '../src/authutil';
describe('authutil tests', () => { describe('authutil tests', () => {
beforeEach(async () => { beforeEach(async () => {
await io.rmRF(fakeSourcesDirForTesting); await io.rmRF(fakeSourcesDirForTesting);
await io.mkdirP(fakeSourcesDirForTesting); await io.mkdirP(fakeSourcesDirForTesting);
}, 30000); }, 30000);
afterAll(async () => { afterAll(async () => {
await io.rmRF(fakeSourcesDirForTesting); await io.rmRF(fakeSourcesDirForTesting);
}, 30000); }, 30000);
beforeEach(() => { beforeEach(() => {
if (fs.existsSync(nugetConfigFile)) { if (fs.existsSync(nugetConfigFile)) {
fs.unlinkSync(nugetConfigFile); fs.unlinkSync(nugetConfigFile);
} }
process.env['INPUT_OWNER'] = ''; process.env['INPUT_OWNER'] = '';
process.env['NUGET_AUTH_TOKEN'] = ''; process.env['NUGET_AUTH_TOKEN'] = '';
}); });
it('No existing config, sets up a full NuGet.config with URL and user/PAT for GPR', async () => { it('no existing config, sets up a full NuGet.config with URL and user/PAT for GPR', async () => {
process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN'; process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN';
await auth.configAuthentication( auth.configAuthentication(
'https://nuget.pkg.github.com/OwnerName/index.json', 'https://nuget.pkg.github.com/OwnerName/index.json',
'', '',
fakeSourcesDirForTesting fakeSourcesDirForTesting
); );
expect(fs.existsSync(nugetConfigFile)).toBe(true); expect(fs.existsSync(nugetConfigFile)).toBe(true);
expect( expect(
fs.readFileSync(nugetConfigFile, {encoding: 'utf8'}) fs.readFileSync(nugetConfigFile, {encoding: 'utf8'})
).toMatchSnapshot(); ).toMatchSnapshot();
}); });
it('No existing config, auth token environment variable not provided, throws', async () => { it('no existing config, auth token environment variable not provided, throws', async () => {
let thrown = false; let thrown = false;
try { try {
await auth.configAuthentication( auth.configAuthentication(
'https://nuget.pkg.github.com/OwnerName/index.json', 'https://nuget.pkg.github.com/OwnerName/index.json',
'', '',
fakeSourcesDirForTesting fakeSourcesDirForTesting
); );
} catch { } catch {
thrown = true; thrown = true;
} }
expect(thrown).toBe(true); expect(thrown).toBe(true);
}); });
it('No existing config, sets up a full NuGet.config with URL and other owner/PAT for GPR', async () => { it('no existing config, sets up a full NuGet.config with URL and other owner/PAT for GPR', async () => {
process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN'; process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN';
process.env['INPUT_OWNER'] = 'otherorg'; process.env['INPUT_OWNER'] = 'otherorg';
await auth.configAuthentication( auth.configAuthentication(
'https://nuget.pkg.github.com/otherorg/index.json', 'https://nuget.pkg.github.com/otherorg/index.json',
'', '',
fakeSourcesDirForTesting fakeSourcesDirForTesting
); );
expect(fs.existsSync(nugetConfigFile)).toBe(true); expect(fs.existsSync(nugetConfigFile)).toBe(true);
expect( expect(
fs.readFileSync(nugetConfigFile, {encoding: 'utf8'}) fs.readFileSync(nugetConfigFile, {encoding: 'utf8'})
).toMatchSnapshot(); ).toMatchSnapshot();
}); });
it('Existing config (invalid), tries to parse an invalid NuGet.config and throws', async () => { it('existing config (invalid), tries to parse an invalid NuGet.config and throws', async () => {
process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN'; process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN';
const inputNuGetConfigPath: string = path.join( const inputNuGetConfigPath: string = path.join(
fakeSourcesDirForTesting, fakeSourcesDirForTesting,
'nuget.config' 'nuget.config'
); );
fs.writeFileSync(inputNuGetConfigPath, invalidNuGetConfig); fs.writeFileSync(inputNuGetConfigPath, invalidNuGetConfig);
let thrown = false; let thrown = false;
try { try {
await auth.configAuthentication( auth.configAuthentication(
'https://nuget.pkg.github.com/OwnerName/index.json', 'https://nuget.pkg.github.com/OwnerName/index.json',
'', '',
fakeSourcesDirForTesting fakeSourcesDirForTesting
); );
} catch { } catch {
thrown = true; thrown = true;
} }
expect(thrown).toBe(true); expect(thrown).toBe(true);
}); });
it('Existing config w/ no sources, sets up a full NuGet.config with URL and user/PAT for GPR', async () => { it('existing config w/ no sources, sets up a full NuGet.config with URL and user/PAT for GPR', async () => {
process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN'; process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN';
const inputNuGetConfigPath: string = path.join( const inputNuGetConfigPath: string = path.join(
fakeSourcesDirForTesting, fakeSourcesDirForTesting,
'nuget.config' 'nuget.config'
); );
fs.writeFileSync(inputNuGetConfigPath, emptyNuGetConfig); fs.writeFileSync(inputNuGetConfigPath, emptyNuGetConfig);
await auth.configAuthentication( auth.configAuthentication(
'https://nuget.pkg.github.com/OwnerName/index.json', 'https://nuget.pkg.github.com/OwnerName/index.json',
'', '',
fakeSourcesDirForTesting fakeSourcesDirForTesting
); );
expect(fs.existsSync(nugetConfigFile)).toBe(true); expect(fs.existsSync(nugetConfigFile)).toBe(true);
expect( expect(
fs.readFileSync(nugetConfigFile, {encoding: 'utf8'}) fs.readFileSync(nugetConfigFile, {encoding: 'utf8'})
).toMatchSnapshot(); ).toMatchSnapshot();
}); });
it('Existing config w/ no GPR sources, sets up a full NuGet.config with URL and user/PAT for GPR', async () => { it('existing config w/ no GPR sources, sets up a full NuGet.config with URL and user/PAT for GPR', async () => {
process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN'; process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN';
const inputNuGetConfigPath: string = path.join( const inputNuGetConfigPath: string = path.join(
fakeSourcesDirForTesting, fakeSourcesDirForTesting,
'nuget.config' 'nuget.config'
); );
fs.writeFileSync(inputNuGetConfigPath, nugetorgNuGetConfig); fs.writeFileSync(inputNuGetConfigPath, nugetorgNuGetConfig);
await auth.configAuthentication( auth.configAuthentication(
'https://nuget.pkg.github.com/OwnerName/index.json', 'https://nuget.pkg.github.com/OwnerName/index.json',
'', '',
fakeSourcesDirForTesting fakeSourcesDirForTesting
); );
expect(fs.existsSync(nugetConfigFile)).toBe(true); expect(fs.existsSync(nugetConfigFile)).toBe(true);
expect( expect(
fs.readFileSync(nugetConfigFile, {encoding: 'utf8'}) fs.readFileSync(nugetConfigFile, {encoding: 'utf8'})
).toMatchSnapshot(); ).toMatchSnapshot();
}); });
it('Existing config w/ only GPR source, sets up a partial NuGet.config user/PAT for GPR', async () => { it('existing config w/ only GPR source, sets up a partial NuGet.config user/PAT for GPR', async () => {
process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN'; process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN';
const inputNuGetConfigPath: string = path.join( const inputNuGetConfigPath: string = path.join(
fakeSourcesDirForTesting, fakeSourcesDirForTesting,
'nuget.config' 'nuget.config'
); );
fs.writeFileSync(inputNuGetConfigPath, gprNuGetConfig); fs.writeFileSync(inputNuGetConfigPath, gprNuGetConfig);
await auth.configAuthentication( auth.configAuthentication(
'https://nuget.pkg.github.com/OwnerName/index.json', 'https://nuget.pkg.github.com/OwnerName/index.json',
'', '',
fakeSourcesDirForTesting fakeSourcesDirForTesting
); );
expect(fs.existsSync(nugetConfigFile)).toBe(true); expect(fs.existsSync(nugetConfigFile)).toBe(true);
expect( expect(
fs.readFileSync(nugetConfigFile, {encoding: 'utf8'}) fs.readFileSync(nugetConfigFile, {encoding: 'utf8'})
).toMatchSnapshot(); ).toMatchSnapshot();
}); });
it('Existing config w/ GPR source and NuGet.org, sets up a partial NuGet.config user/PAT for GPR', async () => { it('existing config w/ GPR source and NuGet.org, sets up a partial NuGet.config user/PAT for GPR', async () => {
process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN'; process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN';
const inputNuGetConfigPath: string = path.join( const inputNuGetConfigPath: string = path.join(
fakeSourcesDirForTesting, fakeSourcesDirForTesting,
'nuget.config' 'nuget.config'
); );
fs.writeFileSync(inputNuGetConfigPath, gprnugetorgNuGetConfig); fs.writeFileSync(inputNuGetConfigPath, gprnugetorgNuGetConfig);
await auth.configAuthentication( auth.configAuthentication(
'https://nuget.pkg.github.com/OwnerName/index.json', 'https://nuget.pkg.github.com/OwnerName/index.json',
'', '',
fakeSourcesDirForTesting fakeSourcesDirForTesting
); );
expect(fs.existsSync(nugetConfigFile)).toBe(true); expect(fs.existsSync(nugetConfigFile)).toBe(true);
expect( expect(
fs.readFileSync(nugetConfigFile, {encoding: 'utf8'}) fs.readFileSync(nugetConfigFile, {encoding: 'utf8'})
).toMatchSnapshot(); ).toMatchSnapshot();
}); });
it('Existing config w/ two GPR sources, sets up a partial NuGet.config user/PAT for GPR', async () => { it('existing config w/ two GPR sources, sets up a partial NuGet.config user/PAT for GPR', async () => {
process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN'; process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN';
const inputNuGetConfigPath: string = path.join( const inputNuGetConfigPath: string = path.join(
fakeSourcesDirForTesting, fakeSourcesDirForTesting,
'nuget.config' 'nuget.config'
); );
fs.writeFileSync(inputNuGetConfigPath, twogprNuGetConfig); fs.writeFileSync(inputNuGetConfigPath, twogprNuGetConfig);
await auth.configAuthentication( auth.configAuthentication(
'https://nuget.pkg.github.com', 'https://nuget.pkg.github.com',
'', '',
fakeSourcesDirForTesting fakeSourcesDirForTesting
); );
expect(fs.existsSync(nugetConfigFile)).toBe(true); expect(fs.existsSync(nugetConfigFile)).toBe(true);
expect( expect(
fs.readFileSync(nugetConfigFile, {encoding: 'utf8'}) fs.readFileSync(nugetConfigFile, {encoding: 'utf8'})
).toMatchSnapshot(); ).toMatchSnapshot();
}); });
it('Existing config w/ spaces in key, throws for now', async () => { it('existing config w/ spaces in key, throws for now', async () => {
process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN'; process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN';
const inputNuGetConfigPath: string = path.join( const inputNuGetConfigPath: string = path.join(
fakeSourcesDirForTesting, fakeSourcesDirForTesting,
'nuget.config' 'nuget.config'
); );
fs.writeFileSync(inputNuGetConfigPath, spaceNuGetConfig); fs.writeFileSync(inputNuGetConfigPath, spaceNuGetConfig);
let thrown = false; let thrown = false;
try { try {
await auth.configAuthentication( auth.configAuthentication(
'https://nuget.pkg.github.com/OwnerName/index.json', 'https://nuget.pkg.github.com/OwnerName/index.json',
'', '',
fakeSourcesDirForTesting fakeSourcesDirForTesting
); );
} catch { } catch {
thrown = true; thrown = true;
} }
expect(thrown).toBe(true); expect(thrown).toBe(true);
}); });
it('Existing config not in repo root, sets up a partial NuGet.config user/PAT for GPR', async () => { it('existing config not in repo root, sets up a partial NuGet.config user/PAT for GPR', async () => {
process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN'; process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN';
const inputNuGetConfigDirectory: string = path.join( const inputNuGetConfigDirectory: string = path.join(
fakeSourcesDirForTesting, fakeSourcesDirForTesting,
'subfolder' 'subfolder'
); );
const inputNuGetConfigPath: string = path.join( const inputNuGetConfigPath: string = path.join(
inputNuGetConfigDirectory, inputNuGetConfigDirectory,
'nuget.config' 'nuget.config'
); );
fs.mkdirSync(inputNuGetConfigDirectory, {recursive: true}); fs.mkdirSync(inputNuGetConfigDirectory, {recursive: true});
fs.writeFileSync(inputNuGetConfigPath, gprNuGetConfig); fs.writeFileSync(inputNuGetConfigPath, gprNuGetConfig);
await auth.configAuthentication( auth.configAuthentication(
'https://nuget.pkg.github.com/OwnerName/index.json', 'https://nuget.pkg.github.com/OwnerName/index.json',
'subfolder/nuget.config', 'subfolder/nuget.config',
fakeSourcesDirForTesting fakeSourcesDirForTesting
); );
expect(fs.existsSync(nugetConfigFile)).toBe(true); expect(fs.existsSync(nugetConfigFile)).toBe(true);
expect( expect(
fs.readFileSync(nugetConfigFile, {encoding: 'utf8'}) fs.readFileSync(nugetConfigFile, {encoding: 'utf8'})
).toMatchSnapshot(); ).toMatchSnapshot();
}); });
it('Existing config w/ only Azure Artifacts source, sets up a partial NuGet.config user/PAT for GPR', async () => { it('existing config w/ only Azure Artifacts source, sets up a partial NuGet.config user/PAT for GPR', async () => {
process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN'; process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN';
const inputNuGetConfigPath: string = path.join( const inputNuGetConfigPath: string = path.join(
fakeSourcesDirForTesting, fakeSourcesDirForTesting,
'nuget.config' 'nuget.config'
); );
fs.writeFileSync(inputNuGetConfigPath, azureartifactsNuGetConfig); fs.writeFileSync(inputNuGetConfigPath, azureartifactsNuGetConfig);
await auth.configAuthentication( auth.configAuthentication(
'https://pkgs.dev.azure.com/amullans/_packaging/GitHubBuilds/nuget/v3/index.json', 'https://pkgs.dev.azure.com/amullans/_packaging/GitHubBuilds/nuget/v3/index.json',
'', '',
fakeSourcesDirForTesting fakeSourcesDirForTesting
); );
expect(fs.existsSync(nugetConfigFile)).toBe(true); expect(fs.existsSync(nugetConfigFile)).toBe(true);
expect( expect(
fs.readFileSync(nugetConfigFile, {encoding: 'utf8'}) fs.readFileSync(nugetConfigFile, {encoding: 'utf8'})
).toMatchSnapshot(); ).toMatchSnapshot();
}); });
it('Existing config w/ Azure Artifacts source and NuGet.org, sets up a partial NuGet.config user/PAT for GPR', async () => { it('existing config w/ Azure Artifacts source and NuGet.org, sets up a partial NuGet.config user/PAT for GPR', async () => {
process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN'; process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN';
const inputNuGetConfigPath: string = path.join( const inputNuGetConfigPath: string = path.join(
fakeSourcesDirForTesting, fakeSourcesDirForTesting,
'nuget.config' 'nuget.config'
); );
fs.writeFileSync(inputNuGetConfigPath, azureartifactsnugetorgNuGetConfig); fs.writeFileSync(inputNuGetConfigPath, azureartifactsnugetorgNuGetConfig);
await auth.configAuthentication( auth.configAuthentication(
'https://pkgs.dev.azure.com/amullans/_packaging/GitHubBuilds/nuget/v3/index.json', 'https://pkgs.dev.azure.com/amullans/_packaging/GitHubBuilds/nuget/v3/index.json',
'', '',
fakeSourcesDirForTesting fakeSourcesDirForTesting
); );
expect(fs.existsSync(nugetConfigFile)).toBe(true); expect(fs.existsSync(nugetConfigFile)).toBe(true);
expect( expect(
fs.readFileSync(nugetConfigFile, {encoding: 'utf8'}) fs.readFileSync(nugetConfigFile, {encoding: 'utf8'})
).toMatchSnapshot(); ).toMatchSnapshot();
}); });
it('No existing config, sets up a full NuGet.config with URL and token for other source', async () => { it('no existing config, sets up a full NuGet.config with URL and token for other source', async () => {
process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN'; process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN';
await auth.configAuthentication( auth.configAuthentication(
'https://pkgs.dev.azure.com/amullans/_packaging/GitHubBuilds/nuget/v3/index.json', 'https://pkgs.dev.azure.com/amullans/_packaging/GitHubBuilds/nuget/v3/index.json',
'', '',
fakeSourcesDirForTesting fakeSourcesDirForTesting
); );
expect(fs.existsSync(nugetConfigFile)).toBe(true); expect(fs.existsSync(nugetConfigFile)).toBe(true);
expect( expect(
fs.readFileSync(nugetConfigFile, {encoding: 'utf8'}) fs.readFileSync(nugetConfigFile, {encoding: 'utf8'})
).toMatchSnapshot(); ).toMatchSnapshot();
}); });
}); });

View File

@ -1,21 +1,45 @@
import cscFile from '../.github/csc.json';
describe('csc tests', () => { describe('csc tests', () => {
it('Valid regular expression', async () => { test('regular expression in csc.json is valid', async () => {
var cscFile = require('../.github/csc.json'); const regexPattern = cscFile['problemMatcher'][0]['pattern'][0]['regexp'];
var regex = cscFile['problemMatcher'][0]['pattern'][0]['regexp']; const regexResultsMap = cscFile['problemMatcher'][0]['pattern'][0];
console.log(regex); const regex = new RegExp(regexPattern);
var re = new RegExp(regex);
// Ideally we would verify that this const stringsToMatch = [
var stringsToMatch = [
'Program.cs(10,79): error CS1002: ; expected [/Users/zacharyeisinger/Documents/repo/setup-dotnet/__tests__/sample-broken-csproj/sample.csproj]', 'Program.cs(10,79): error CS1002: ; expected [/Users/zacharyeisinger/Documents/repo/setup-dotnet/__tests__/sample-broken-csproj/sample.csproj]',
"S:\\Msbuild\\src\\Build\\Evaluation\\ExpressionShredder.cs(33,7): error CS1003: Syntax error, ',' expected [S:\\msbuild\\src\\Build\\Microsoft.Build.csproj > Properties:prop]" "S:\\Msbuild\\src\\Build\\Evaluation\\ExpressionShredder.cs(33,7): error CS1003: Syntax error, ',' expected [S:\\msbuild\\src\\Build\\Microsoft.Build.csproj > Properties:prop]"
]; ];
// Expected results are calculated according to the csc matcher located in csc.json file
const expectedResults = [
{
file: 'Program.cs',
line: '10',
severity: 'error',
code: 'CS1002',
message: '; expected',
fromPath:
'/Users/zacharyeisinger/Documents/repo/setup-dotnet/__tests__/sample-broken-csproj/sample.csproj'
},
{
file: 'S:\\Msbuild\\src\\Build\\Evaluation\\ExpressionShredder.cs',
line: '33',
severity: 'error',
code: 'CS1003',
message: "Syntax error, ',' expected",
fromPath:
'S:\\msbuild\\src\\Build\\Microsoft.Build.csproj > Properties:prop'
}
];
stringsToMatch.forEach(string => { stringsToMatch.map((string, index) => {
var matchStr = string.match(re); const matchedResultsArray = string.match(regex);
console.log(matchStr); for (const propName in expectedResults[index]) {
expect(matchStr).toEqual(expect.anything()); const propertyIndex = regexResultsMap[propName];
const expectedPropValue = expectedResults[index][propName];
const matchedPropValue = matchedResultsArray![propertyIndex];
expect(matchedPropValue).toEqual(expectedPropValue);
}
}); });
}, 10000); }, 10000);
}); });

View File

@ -0,0 +1,18 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
namespace test_csproj
{
[TestClass]
public class Test
{
[TestMethod]
public void TestMethod()
{
Console.WriteLine("TestMethod");
int calculatedResult = 1000 / 25;
int expectedResult = 40;
Assert.AreEqual(calculatedResult, expectedResult);
}
}
}

View File

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>$(TEST_TARGET_FRAMEWORK)</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<!-- These packages will be downloaded over the network for testing proxy settings -->
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0-preview-20170810-02" />
<PackageReference Include="MSTest.TestAdapter" Version="1.1.18" />
<PackageReference Include="MSTest.TestFramework" Version="1.1.18" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,65 @@
import path from 'path';
import fs from 'fs';
import * as hc from '@actions/http-client';
const HTTP_CLIENT_OPTIONS = {allowRetries: true, maxRetries: 10} as const;
const TEST_TIMEOUT = 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 {
return contents
.trim()
.replace(new RegExp('\r\n', 'g'), '\n')
.replace(new RegExp('\r', 'g'), '\n');
}

View File

@ -1,290 +1,443 @@
import * as io from '@actions/io';
import * as os from 'os';
import fs from 'fs';
import path from 'path';
import each from 'jest-each'; import each from 'jest-each';
import * as hc from '@actions/http-client'; import semver from 'semver';
import * as exec from '@actions/exec';
import * as core from '@actions/core';
import * as io from '@actions/io';
import * as installer from '../src/installer'; import * as installer from '../src/installer';
import {QualityOptions} from '../src/setup-dotnet';
import {IS_WINDOWS} from '../src/utils'; import {IS_WINDOWS} from '../src/utils';
import {IS_LINUX} from '../src/utils'; import {QualityOptions} from '../src/setup-dotnet';
let toolDir: string; describe('installer tests', () => {
const env = process.env;
if (IS_WINDOWS) { beforeEach(() => {
toolDir = path.join(process.env['PROGRAMFILES'] + '', 'dotnet'); jest.resetModules();
} else if (IS_LINUX) { process.env = {...env};
toolDir = '/usr/share/dotnet'; });
} else {
toolDir = path.join(process.env['HOME'] + '', '.dotnet');
}
const tempDir = path.join(__dirname, 'runner', 'temp');
process.env['RUNNER_TOOL_CACHE'] = toolDir; describe('DotnetCoreInstaller tests', () => {
process.env['RUNNER_TEMP'] = tempDir; const getExecOutputSpy = jest.spyOn(exec, 'getExecOutput');
const warningSpy = jest.spyOn(core, 'warning');
const whichSpy = jest.spyOn(io, 'which');
const maxSatisfyingSpy = jest.spyOn(semver, 'maxSatisfying');
describe('DotnetCoreInstaller tests', () => { describe('installDotnet() tests', () => {
beforeAll(async () => { whichSpy.mockImplementation(() => Promise.resolve('PathToShell'));
process.env.RUNNER_TOOL_CACHE = toolDir;
process.env.DOTNET_INSTALL_DIR = toolDir;
process.env.RUNNER_TEMP = tempDir;
process.env.DOTNET_ROOT = '';
try {
await io.rmRF(`${toolDir}/*`);
await io.rmRF(`${tempDir}/*`);
} catch (err) {
console.log(
`Failed to remove test directories, check the error message:${os.EOL}`,
err.message
);
}
}, 30000);
afterEach(async () => { it('should throw the error in case of non-zero exit code of the installation script. The error message should contain logs.', async () => {
try { const inputVersion = '3.1.100';
await io.rmRF(`${toolDir}/*`); const inputQuality = '' as QualityOptions;
await io.rmRF(`${tempDir}/*`); const errorMessage = 'fictitious error message!';
} catch (err) { getExecOutputSpy.mockImplementation(() => {
console.log( return Promise.resolve({
`Failed to remove test directories, check the error message:${os.EOL}`, exitCode: 1,
err.message stdout: '',
); stderr: errorMessage
} });
}, 30000); });
const dotnetInstaller = new installer.DotnetCoreInstaller(
inputVersion,
inputQuality
);
await expect(dotnetInstaller.installDotnet()).rejects.toThrow(
`Failed to install dotnet, exit code: 1. ${errorMessage}`
);
});
it('Aquires multiple versions of dotnet', async () => { it('should return version of .NET SDK after installation complete', async () => {
const versions = ['2.2.207', '3.1.120']; 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: `${stdout}`,
stderr: ''
});
});
maxSatisfyingSpy.mockImplementation(() => inputVersion);
for (const version of versions) { const dotnetInstaller = new installer.DotnetCoreInstaller(
await getDotnet(version); inputVersion,
} inputQuality
expect(fs.existsSync(path.join(toolDir, 'sdk', '2.2.207'))).toBe(true); );
expect(fs.existsSync(path.join(toolDir, 'sdk', '3.1.120'))).toBe(true); const installedVersion = await dotnetInstaller.installDotnet();
if (IS_WINDOWS) { expect(installedVersion).toBe(inputVersion);
expect(fs.existsSync(path.join(toolDir, 'dotnet.exe'))).toBe(true); });
} else {
expect(fs.existsSync(path.join(toolDir, 'dotnet'))).toBe(true);
}
expect(process.env.DOTNET_ROOT).toBeDefined; it(`should supply 'version' argument to the installation script if supplied version is in A.B.C syntax`, async () => {
expect(process.env.PATH).toBeDefined; const inputVersion = '6.0.300';
expect(process.env.DOTNET_ROOT).toBe(toolDir); const inputQuality = '' as QualityOptions;
expect(process.env.PATH?.startsWith(toolDir)).toBe(true); const stdout = `Fictitious dotnet version ${inputVersion} is installed`;
}, 600000);
it('Acquires version of dotnet if no matching version is installed', async () => { getExecOutputSpy.mockImplementation(() => {
await getDotnet('3.1.201'); return Promise.resolve({
expect(fs.existsSync(path.join(toolDir, 'sdk', '3.1.201'))).toBe(true); exitCode: 0,
if (IS_WINDOWS) { stdout: `${stdout}`,
expect(fs.existsSync(path.join(toolDir, 'dotnet.exe'))).toBe(true); stderr: ''
} else { });
expect(fs.existsSync(path.join(toolDir, 'dotnet'))).toBe(true); });
} maxSatisfyingSpy.mockImplementation(() => inputVersion);
expect(process.env.DOTNET_ROOT).toBeDefined; const dotnetInstaller = new installer.DotnetCoreInstaller(
expect(process.env.PATH).toBeDefined; inputVersion,
expect(process.env.DOTNET_ROOT).toBe(toolDir); inputQuality
expect(process.env.PATH?.startsWith(toolDir)).toBe(true); );
}, 600000); //This needs some time to download on "slower" internet connections
it('Acquires generic version of dotnet if no matching version is installed', async () => { await dotnetInstaller.installDotnet();
await getDotnet('3.1');
var directory = fs
.readdirSync(path.join(toolDir, 'sdk'))
.filter(fn => fn.startsWith('3.1.'));
expect(directory.length > 0).toBe(true);
if (IS_WINDOWS) {
expect(fs.existsSync(path.join(toolDir, 'dotnet.exe'))).toBe(true);
} else {
expect(fs.existsSync(path.join(toolDir, 'dotnet'))).toBe(true);
}
expect(process.env.DOTNET_ROOT).toBeDefined; const scriptArguments = (
expect(process.env.PATH).toBeDefined; getExecOutputSpy.mock.calls[0][1] as string[]
expect(process.env.DOTNET_ROOT).toBe(toolDir); ).join(' ');
expect(process.env.PATH?.startsWith(toolDir)).toBe(true); const expectedArgument = IS_WINDOWS
}, 600000); //This needs some time to download on "slower" internet connections ? `-Version ${inputVersion}`
: `--version ${inputVersion}`;
it('Returns string with installed SDK version', async () => { expect(scriptArguments).toContain(expectedArgument);
const version = '3.1.120'; });
let installedVersion: string;
installedVersion = await getDotnet(version); 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: `${stdout}`,
stderr: ''
});
});
maxSatisfyingSpy.mockImplementation(() => inputVersion);
expect(installedVersion).toBe('3.1.120'); const dotnetInstaller = new installer.DotnetCoreInstaller(
}, 600000); inputVersion,
inputQuality
);
it('Throws if no location contains correct dotnet version', async () => { await dotnetInstaller.installDotnet();
await expect(async () => {
await getDotnet('1000.0.0');
}).rejects.toThrow();
}, 30000);
it('Uses an up to date bash download script', async () => { expect(warningSpy).toHaveBeenCalledWith(
const httpCallbackClient = new hc.HttpClient('setup-dotnet-test', [], { `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.`
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);
it('Uses an up to date powershell download script', async () => { 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 () => {
var httpCallbackClient = new hc.HttpClient('setup-dotnet-test', [], { const inputVersion = '3.1';
allowRetries: true, const inputQuality = 'ga' as QualityOptions;
maxRetries: 3 const stdout = `Fictitious dotnet version 3.1.100 is installed`;
});
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('DotnetVersionResolver tests', () => { getExecOutputSpy.mockImplementation(() => {
each([ return Promise.resolve({
'3.1', exitCode: 0,
'3.x', stdout: `${stdout}`,
'3.1.x', stderr: ''
'3.1.*', });
'3.1.X', });
'3.1.2', maxSatisfyingSpy.mockImplementation(() => inputVersion);
'3.1.0-preview1'
]).test(
"if valid version: '%s' is supplied, it should return version object with some value",
async version => {
const dotnetVersionResolver = new installer.DotnetVersionResolver(
version
);
const versionObject = await dotnetVersionResolver.createDotNetVersion();
expect(!!versionObject.value).toBeTruthy; const dotnetInstaller = new installer.DotnetCoreInstaller(
} inputVersion,
); inputQuality
);
each([ await dotnetInstaller.installDotnet();
'.',
'..', expect(warningSpy).toHaveBeenCalledWith(
' . ', `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.`
'. ', );
' .', });
' . . ',
' .. ', each(['6', '6.0', '6.0.x', '6.0.*', '6.0.X']).test(
' . ', `should supply 'quality' argument to the installation script if quality input is set and version (%s) is not in A.B.C syntax`,
'-1.-1', async inputVersion => {
'-1', const inputQuality = 'ga' as QualityOptions;
'-1.-1.-1', const exitCode = 0;
'..3', const stdout = `Fictitious dotnet version 6.0.0 is installed`;
'1..3', getExecOutputSpy.mockImplementation(() => {
'1..', return Promise.resolve({
'.2.3', exitCode: exitCode,
'.2.x', stdout: `${stdout}`,
'*.', stderr: ''
'1.2.', });
'1.2.-abc', });
'a.b', maxSatisfyingSpy.mockImplementation(() => inputVersion);
'a.b.c',
'a.b.c-preview', const dotnetInstaller = new installer.DotnetCoreInstaller(
' 0 . 1 . 2 ', inputVersion,
'invalid' inputQuality
]).test( );
"if invalid version: '%s' is supplied, it should throw",
async version => { await dotnetInstaller.installDotnet();
const dotnetVersionResolver = new installer.DotnetVersionResolver(
version const scriptArguments = (
getExecOutputSpy.mock.calls[0][1] as string[]
).join(' ');
const expectedArgument = IS_WINDOWS
? `-Quality ${inputQuality}`
: `--quality ${inputQuality}`;
expect(scriptArguments).toContain(expectedArgument);
}
); );
await expect( each(['6', '6.0', '6.0.x', '6.0.*', '6.0.X']).test(
async () => await dotnetVersionResolver.createDotNetVersion() `should supply 'channel' argument to the installation script if version (%s) isn't in A.B.C syntax`,
).rejects.toThrow(); 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}`,
stderr: ''
});
});
maxSatisfyingSpy.mockImplementation(() => inputVersion);
each(['3.1', '3.1.x', '3.1.*', '3.1.X']).test( const dotnetInstaller = new installer.DotnetCoreInstaller(
"if version: '%s' that can be resolved to 'channel' option is supplied, it should set quality flag to 'true' and type to 'channel' in version object", inputVersion,
async version => { inputQuality
const dotnetVersionResolver = new installer.DotnetVersionResolver( );
version
await dotnetInstaller.installDotnet();
const scriptArguments = (
getExecOutputSpy.mock.calls[0][1] as string[]
).join(' ');
const expectedArgument = IS_WINDOWS
? `-Channel 6.0`
: `--channel 6.0`;
expect(scriptArguments).toContain(expectedArgument);
}
); );
const versionObject = await dotnetVersionResolver.createDotNetVersion();
expect(versionObject.type.toLowerCase().includes('channel')).toBeTruthy;
expect(versionObject.qualityFlag).toBeTruthy;
}
);
each(['3.1.2', '3.1.0-preview1']).test(
"if version: '%s' that can be resolved to 'version' option is supplied, it should set quality flag to 'false' and type to 'version' in version object",
async version => {
const dotnetVersionResolver = new installer.DotnetVersionResolver(
version
);
const versionObject = await dotnetVersionResolver.createDotNetVersion();
expect(versionObject.type.toLowerCase().includes('version')).toBeTruthy;
expect(versionObject.qualityFlag).toBeFalsy;
}
);
each(['3.1.2', '3.1']).test(
'it should create proper line arguments for powershell/bash installation scripts',
async version => {
const dotnetVersionResolver = new installer.DotnetVersionResolver(
version
);
const versionObject = await dotnetVersionResolver.createDotNetVersion();
const windowsRegEx = new RegExp(/^-[VC]/);
const nonWindowsRegEx = new RegExp(/^--[vc]/);
if (IS_WINDOWS) { if (IS_WINDOWS) {
expect(windowsRegEx.test(versionObject.type)).toBeTruthy; it(`should supply '-ProxyAddress' argument to the installation script if env.variable 'https_proxy' is set`, async () => {
expect(nonWindowsRegEx.test(versionObject.type)).toBeFalsy; process.env['https_proxy'] = 'https://proxy.com';
} else { const inputVersion = '6.0.100';
expect(nonWindowsRegEx.test(versionObject.type)).toBeTruthy; const inputQuality = '' as QualityOptions;
expect(windowsRegEx.test(versionObject.type)).toBeFalsy; const stdout = `Fictitious dotnet version ${inputVersion} is installed`;
getExecOutputSpy.mockImplementation(() => {
return Promise.resolve({
exitCode: 0,
stdout: `${stdout}`,
stderr: ''
});
});
maxSatisfyingSpy.mockImplementation(() => inputVersion);
const dotnetInstaller = new installer.DotnetCoreInstaller(
inputVersion,
inputQuality
);
await dotnetInstaller.installDotnet();
const scriptArguments = (
getExecOutputSpy.mock.calls[0][1] as string[]
).join(' ');
expect(scriptArguments).toContain(
`-ProxyAddress ${process.env['https_proxy']}`
);
});
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 = '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: `${stdout}`,
stderr: ''
});
});
maxSatisfyingSpy.mockImplementation(() => inputVersion);
const dotnetInstaller = new installer.DotnetCoreInstaller(
inputVersion,
inputQuality
);
await dotnetInstaller.installDotnet();
const scriptArguments = (
getExecOutputSpy.mock.calls[0][1] as string[]
).join(' ');
expect(scriptArguments).toContain(
`-ProxyBypassList ${process.env['no_proxy']}`
);
});
} }
} });
);
describe('addToPath() tests', () => {
it(`should export DOTNET_ROOT env.var with value from DOTNET_INSTALL_DIR env.var`, async () => {
process.env['DOTNET_INSTALL_DIR'] = 'fictitious/dotnet/install/dir';
installer.DotnetCoreInstaller.addToPath();
const dotnet_root = process.env['DOTNET_ROOT'];
expect(dotnet_root).toBe(process.env['DOTNET_INSTALL_DIR']);
});
it(`should export value from DOTNET_INSTALL_DIR env.var to the PATH`, async () => {
process.env['DOTNET_INSTALL_DIR'] = 'fictitious/dotnet/install/dir';
installer.DotnetCoreInstaller.addToPath();
const path = process.env['PATH'];
expect(path).toContain(process.env['DOTNET_INSTALL_DIR']);
});
});
});
describe('DotnetVersionResolver tests', () => {
describe('createDotNetVersion() tests', () => {
each([
'3.1',
'3.x',
'3.1.x',
'3.1.*',
'3.1.X',
'3.1.2',
'3.1.0-preview1',
'6.0.2xx'
]).test(
'if valid version is supplied (%s), it should return version object with some value',
async version => {
const dotnetVersionResolver = new installer.DotnetVersionResolver(
version
);
const versionObject =
await dotnetVersionResolver.createDotNetVersion();
expect(!!versionObject.value).toBe(true);
}
);
each([
'.',
'..',
' . ',
'. ',
' .',
' . . ',
' .. ',
' . ',
'-1.-1',
'-1',
'-1.-1.-1',
'..3',
'1..3',
'1..',
'.2.3',
'.2.x',
'*.',
'1.2.',
'1.2.-abc',
'a.b',
'a.b.c',
'a.b.c-preview',
' 0 . 1 . 2 ',
'invalid'
]).test(
'if invalid version is supplied (%s), it should throw',
async version => {
const dotnetVersionResolver = new installer.DotnetVersionResolver(
version
);
await expect(
async () => await dotnetVersionResolver.createDotNetVersion()
).rejects.toThrow();
}
);
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(
version
);
const versionObject =
await dotnetVersionResolver.createDotNetVersion();
expect(versionObject.type.toLowerCase().includes('channel')).toBe(
true
);
}
);
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(
version
);
const versionObject =
await dotnetVersionResolver.createDotNetVersion();
expect(versionObject.type.toLowerCase().includes('channel')).toBe(
true
);
expect(versionObject.qualityFlag).toBe(true);
}
);
each(['3.1.2', '3.1.0-preview1']).test(
"if version that can be resolved to 'version' option is supplied (%s), it should set quality flag to 'false' and type to 'version' in version object",
async version => {
const dotnetVersionResolver = new installer.DotnetVersionResolver(
version
);
const versionObject =
await dotnetVersionResolver.createDotNetVersion();
expect(versionObject.type.toLowerCase().includes('version')).toBe(
true
);
expect(versionObject.qualityFlag).toBe(false);
}
);
each(['3.1.2', '3.1']).test(
'it should create proper line arguments for powershell/bash installation scripts',
async version => {
const dotnetVersionResolver = new installer.DotnetVersionResolver(
version
);
const versionObject =
await dotnetVersionResolver.createDotNetVersion();
const windowsRegEx = new RegExp(/^-(Version|Channel)/);
const nonWindowsRegEx = new RegExp(/^--(version|channel)/);
if (IS_WINDOWS) {
expect(windowsRegEx.test(versionObject.type)).toBe(true);
expect(nonWindowsRegEx.test(versionObject.type)).toBe(false);
} else {
expect(nonWindowsRegEx.test(versionObject.type)).toBe(true);
expect(windowsRegEx.test(versionObject.type)).toBe(false);
}
}
);
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.`
);
});
});
});
}); });
function normalizeFileContents(contents: string): string {
return contents
.trim()
.replace(new RegExp('\r\n', 'g'), '\n')
.replace(new RegExp('\r', 'g'), '\n');
}
async function getDotnet(
version: string,
quality: string = ''
): Promise<string> {
const dotnetInstaller = new installer.DotnetCoreInstaller(
version,
quality as QualityOptions
);
const installedVersion = await dotnetInstaller.installDotnet();
installer.DotnetCoreInstaller.addToPath();
return installedVersion;
}

View File

@ -1,15 +0,0 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
namespace sample_csproj
{
[TestClass]
public class Program
{
[TestMethod]
public void TestMethod1()
{
Console.WriteLine("Hello, World!");
}
}
}

View File

@ -1,18 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp3.1;netcoreapp3.0;netcoreapp2.2</TargetFrameworks>
<RootNamespace>sample_csproj</RootNamespace>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<!-- These packages will be downloaded over the network for testing proxy settings -->
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="MSTest.TestAdapter" Version="2.1.0" />
<PackageReference Include="MSTest.TestFramework" Version="2.1.0" />
<PackageReference Include="coverlet.collector" Version="1.2.0" />
</ItemGroup>
</Project>

View File

@ -1,98 +1,173 @@
import * as io from '@actions/io';
import * as core from '@actions/core'; import * as core from '@actions/core';
import fs from 'fs'; import fs from 'fs';
import os from 'os'; import semver from 'semver';
import path from 'path'; import * as auth from '../src/authutil';
import * as setup from '../src/setup-dotnet'; import * as setup from '../src/setup-dotnet';
import {IS_WINDOWS} from '../src/utils'; import {DotnetCoreInstaller} from '../src/installer';
import {IS_LINUX} from '../src/utils';
let toolDir: string;
if (IS_WINDOWS) {
toolDir = path.join(process.env['PROGRAMFILES'] + '', 'dotnet');
} else if (IS_LINUX) {
toolDir = '/usr/share/dotnet';
} else {
toolDir = path.join(process.env['HOME'] + '', '.dotnet');
}
const tempDir = path.join(__dirname, 'runner', 'temp2');
describe('setup-dotnet tests', () => { describe('setup-dotnet tests', () => {
let getInputSpy = jest.spyOn(core, 'getInput'); const inputs = {} as any;
let getMultilineInputSpy = jest.spyOn(core, 'getMultilineInput');
let setOutputSpy = jest.spyOn(core, 'setOutput');
let inputs = {} as any; const getInputSpy = jest.spyOn(core, 'getInput');
const getMultilineInputSpy = jest.spyOn(core, 'getMultilineInput');
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');
beforeAll(async () => { const existsSyncSpy = jest.spyOn(fs, 'existsSync');
process.env.RUNNER_TOOL_CACHE = toolDir;
process.env.DOTNET_INSTALL_DIR = toolDir;
process.env.RUNNER_TEMP = tempDir;
try {
await io.rmRF(`${toolDir}/*`);
await io.rmRF(`${tempDir}/*`);
} catch (err) {
console.log(err.message);
console.log('Failed to remove test directories');
}
}, 30000);
afterEach(async () => { const maxSatisfyingSpy = jest.spyOn(semver, 'maxSatisfying');
try {
await io.rmRF(path.join(process.cwd(), 'global.json'));
await io.rmRF(`${toolDir}/*`);
await io.rmRF(`${tempDir}/*`);
} catch (err) {
console.log(err.message);
console.log('Failed to remove test directories');
}
}, 30000);
it('Acquires version of dotnet from global.json if no matching version is installed', async () => { const installDotnetSpy = jest.spyOn(
const globalJsonPath = path.join(process.cwd(), 'global.json'); DotnetCoreInstaller.prototype,
const jsonContents = `{${os.EOL}"sdk": {${os.EOL}"version": "3.1.201"${os.EOL}}${os.EOL}}`; 'installDotnet'
if (!fs.existsSync(globalJsonPath)) { );
fs.writeFileSync(globalJsonPath, jsonContents); const addToPathSpy = jest.spyOn(DotnetCoreInstaller, 'addToPath');
}
await setup.run();
expect(fs.existsSync(path.join(toolDir, 'sdk', '3.1.201'))).toBe(true); const configAuthenticationSpy = jest.spyOn(auth, 'configAuthentication');
if (IS_WINDOWS) {
expect(fs.existsSync(path.join(toolDir, 'dotnet.exe'))).toBe(true);
} else {
expect(fs.existsSync(path.join(toolDir, 'dotnet'))).toBe(true);
}
}, 400000);
it("Sets output with the latest installed by action version if global.json file isn't specified", async () => { describe('run() tests', () => {
inputs['dotnet-version'] = ['3.1.201', '6.0.401']; beforeEach(() => {
getMultilineInputSpy.mockImplementation(input => inputs[input as string]);
getInputSpy.mockImplementation(input => inputs[input as string]);
});
getMultilineInputSpy.mockImplementation(input => inputs[input]); afterEach(() => {
jest.clearAllMocks();
jest.resetAllMocks();
});
await setup.run(); it('should fail the action if global-json-file input is present, but the file does not exist in the file system', async () => {
inputs['global-json-file'] = 'fictitious.json';
inputs['dotnet-version'] = [];
expect(setOutputSpy).toBeCalledWith('dotnet-version', '6.0.401'); const expectedErrorMessage = `The specified global.json file '${inputs['global-json-file']}' does not exist`;
}, 400000);
it("Sets output with the version specified in global.json, if it's present", async () => { await setup.run();
const globalJsonPath = path.join(process.cwd(), 'global.json'); expect(setFailedSpy).toHaveBeenCalledWith(expectedErrorMessage);
const jsonContents = `{${os.EOL}"sdk": {${os.EOL}"version": "3.0.103"${os.EOL}}${os.EOL}}`; });
if (!fs.existsSync(globalJsonPath)) {
fs.writeFileSync(globalJsonPath, jsonContents);
}
inputs['dotnet-version'] = ['3.1.201', '6.0.401']; test(`if 'dotnet-version' and 'global-json-file' inputs aren't present, should log into debug output, try to find global.json in the repo root, fail and log message into info output`, async () => {
inputs['global-json-file'] = './global.json'; inputs['global-json-file'] = '';
inputs['dotnet-version'] = [];
getMultilineInputSpy.mockImplementation(input => inputs[input]); maxSatisfyingSpy.mockImplementation(() => null);
setOutputSpy.mockImplementation(() => {});
getInputSpy.mockImplementation(input => inputs[input]); const expectedDebugMessage =
'No version found, trying to find version from global.json';
const expectedInfoMessage = `The global.json wasn't found in the root directory. No .NET version will be installed.`;
await setup.run(); await setup.run();
expect(setOutputSpy).toBeCalledWith('dotnet-version', '3.0.103'); expect(debugSpy).toHaveBeenCalledWith(expectedDebugMessage);
}, 400000); expect(existsSyncSpy).toHaveBeenCalled();
expect(infoSpy).toHaveBeenCalledWith(expectedInfoMessage);
});
it('should fail the action if quality is supplied but its value is not supported', async () => {
inputs['global-json-file'] = '';
inputs['dotnet-version'] = ['6.0'];
inputs['dotnet-quality'] = 'fictitiousQuality';
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);
});
it('should call installDotnet() multiple times if dotnet-version multiline input is provided', async () => {
inputs['global-json-file'] = '';
inputs['dotnet-version'] = ['6.0', '7.0'];
inputs['dotnet-quality'] = '';
installDotnetSpy.mockImplementation(() => Promise.resolve(''));
await setup.run();
expect(installDotnetSpy).toHaveBeenCalledTimes(2);
});
it('should call addToPath() after installation complete', async () => {
inputs['global-json-file'] = '';
inputs['dotnet-version'] = ['6.0', '7.0'];
inputs['dotnet-quality'] = '';
installDotnetSpy.mockImplementation(() => Promise.resolve(''));
addToPathSpy.mockImplementation(() => {});
await setup.run();
expect(addToPathSpy).toHaveBeenCalledTimes(1);
});
it('should call auth.configAuthentication() if source-url input is provided', async () => {
inputs['global-json-file'] = '';
inputs['dotnet-version'] = [];
inputs['dotnet-quality'] = '';
inputs['source-url'] = 'fictitious.source.url';
configAuthenticationSpy.mockImplementation(() => {});
await setup.run();
expect(configAuthenticationSpy).toHaveBeenCalledWith(
inputs['source-url'],
undefined
);
});
it('should call auth.configAuthentication() with proper parameters if source-url and config-file inputs are provided', async () => {
inputs['global-json-file'] = '';
inputs['dotnet-version'] = [];
inputs['dotnet-quality'] = '';
inputs['source-url'] = 'fictitious.source.url';
inputs['config-file'] = 'fictitious.path';
configAuthenticationSpy.mockImplementation(() => {});
setOutputSpy.mockImplementation(() => {});
await setup.run();
expect(configAuthenticationSpy).toHaveBeenCalledWith(
inputs['source-url'],
inputs['config-file']
);
});
it('should call setOutput() after installation complete successfully', async () => {
inputs['dotnet-version'] = ['6.0.300'];
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();
});
});
}); });

View File

@ -1,73 +1,117 @@
if (!$args[0]) <#
{ .DESCRIPTION
throw "Must supply dotnet version argument" Verifies that installed on the machine .NET SDK versions match the input patterns.
Optionally checks that the nuget.config file is generated correctly.
.PARAMETER Patterns
Specifies the regular expression patterns that should be matched with the installed
on the machine .NET SDK versions. The number of patterns should be equal to the number
of installed .NET versions.
.PARAMETER CheckNugetConfig
Switches the check for the existence of the nuget.config file.
.EXAMPLE
PS> .\verify-dotnet.ps1 -Paterns "^3.1.200$", "^6.0" -CheckNugetConfig
#>
param(
[ValidateNotNullOrEmpty()]
[Parameter(Mandatory=$true)]
[string[]]$Patterns,
[switch]$CheckNugetConfig
)
$PatternsList = [System.Collections.ArrayList]($Patterns)
if ($CheckNugetConfig.IsPresent -and !(Test-Path "../nuget.config")) {
throw "The nuget.config file is not generated correctly."
} }
Write-Host "These patterns were supplied to the script: $($PatternsList -join ', ')."
$dotnet = Get-Command dotnet | Select-Object -First 1 | ForEach-Object { $_.Path } $dotnet = Get-Command dotnet | Select-Object -First 1 | ForEach-Object { $_.Path }
Write-Host "Found '$dotnet'" Write-Host "Found: '$dotnet'"
if($args.count -eq 1) # SDKs are listed on multiple lines with the path afterwards in square brackets
$Versions = & $dotnet --list-sdks | ForEach-Object { $_.SubString(0, $_.IndexOf('[')).Trim() }
Write-Host "Found installed versions: $($Versions -join ', ')."
$InstalledVersionCount = $Versions.Count
foreach($version in $Versions)
{ {
$version = & $dotnet --version | Out-String | ForEach-Object { $_.Trim() } foreach($pattern in $PatternsList)
Write-Host "Version $version"
if (-not ($version.StartsWith($args[0].ToString())))
{ {
Write-Host "PATH='$env:PATH'" if ($version -match $pattern)
throw "Unexpected version" {
} $PatternsList.Remove($pattern)
} $InstalledVersionCount--
break
if ($args[1])
{
# SDKs are listed on multiple lines with the path afterwards in square brackets
$versions = & $dotnet --list-sdks | ForEach-Object { $_.SubString(0, $_.IndexOf('[')).Trim() }
Write-Host "Installed versions: $versions"
$InstalledVersionCount = 0
foreach($arg in $args){
foreach ($version in $versions)
{
if ($version.StartsWith($arg.ToString()))
{
$InstalledVersionCount++
}
} }
}
if ( $InstalledVersionCount -ne $args.Count)
{
Write-Host "PATH='$env:PATH'"
throw "Unexpected version"
} }
} }
Write-Host "Building sample csproj" if ( $InstalledVersionCount -ne 0)
& $dotnet build __tests__/sample-csproj/ --no-cache
if ($LASTEXITCODE -ne 0)
{ {
throw "Unexpected exit code $LASTEXITCODE" throw "An unexpected version of Dotnet is found on the machine, please check the correctness of the -Patterns input."
} }
Write-Host "Testing compiled app" $workingDir = Get-Location
$sample_output = "$(dotnet test __tests__/sample-csproj/ --no-build)" $testProjectDir = "./__tests__/e2e-test-csproj"
Write-Host "Sample output: $sample_output" Write-Host "Changing directory to the $testProjectDir"
# For Side-by-Side installs we want to run the tests twice, for a single install the tests will run once Set-Location $testProjectDir
if ($args[1])
$targetFrameworkVersionMap = @{
"1.0" = "netcoreapp1.0";
"1.1" = "netcoreapp1.1";
"2.0" = "netcoreapp2.0";
"2.1" = "netcoreapp2.1";
"2.2" = "netcoreapp2.2";
"3.0" = "netcoreapp3.0";
"3.1" = "netcoreapp3.1";
"5.0" = "net5.0";
"6.0" = "net6.0";
"7.0" = "net7.0";
"8.0" = "net8.0";
}
foreach ($version in $Versions)
{ {
if ($sample_output -notlike "*Test Run Successful.*Test Run Successful.*") # Creating temporary global.json file inside e2e-test-csproj dir and setting exact version of .NET inside allows to override default behavior of .NET and run build and tests on that exact version.
Write-Host "Creating temporary global.json file for $version .NET version."
& $dotnet new globaljson --sdk-version $version --force | Out-Null
if (!(Test-Path "./global.json"))
{ {
throw "Unexpected output" throw "An error occured while creating the global.json file. Exit code: $LASTEXITCODE"
} }
} Write-Host "The global.json file for the version $version is created. Currently used .NET version is: $(& $dotnet --version)."
if ($args[2])
{ # Environment variable TEST_TARGET_FRAMEWORK is used inside the test.csproj file to target required framework version
if ($sample_output -notlike "*Test Run Successful.*Test Run Successful.*Test Run Successful.*") $version -match "^(?<key>\d+\.\d+)" | Out-Null
if (!($targetFrameworkVersionMap.ContainsKey($Matches.key)))
{ {
throw "Unexpected output" throw "The map with the framework targets doesn't contain a target name for the version $version."
} }
} Write-Host "Setting the TEST_TARGET_FRAMEWORK environment variable to $($targetFrameworkVersionMap[$Matches.key])"
else [Environment]::SetEnvironmentVariable('TEST_TARGET_FRAMEWORK', $($targetFrameworkVersionMap[$Matches.key]))
{
if ($sample_output -notlike "*Test Run Successful.*") Write-Host "Building test C# project with $version .NET version."
& $dotnet build --no-cache
if ($LASTEXITCODE -ne 0)
{ {
throw "Unexpected output" throw "Building process is not successful, exit code: $LASTEXITCODE"
} }
Write-Host "Testing compiled C# project with $version .NET version."
& $dotnet test --no-build
if ($LASTEXITCODE -ne 0)
{
throw "Testing process is not successful, exit code: $LASTEXITCODE"
}
Write-Host "Tests are completed successfully!"
Write-Host "Removing temporary global.json file."
Remove-Item ./global.json
} }
Set-Location $workingDir

View File

@ -1,44 +0,0 @@
if [ -z "$1" ]; then
echo "Must supply dotnet version argument"
exit 1
fi
if [ ! -f "../nuget.config" ]; then
echo "nuget file not generated correctly"
exit 1
fi
dotnet_version="$(dotnet --version)"
echo "Found dotnet version '$dotnet_version'"
if [ -z "$(echo $dotnet_version | grep $1)" ]; then
echo "Unexpected version"
exit 1
fi
if [ -n "$2" ]; then
dotnet_version="$(dotnet --list-sdks)"
echo "Found dotnet version '$dotnet_version'"
if [ -z "$(echo $dotnet_version | grep $2)" ]; then
echo "Unexpected version"
exit 1
fi
fi
echo "Building sample csproj"
dotnet build __tests__/sample-csproj/ --no-cache || exit 1
echo "Testing compiled app"
sample_output=$(dotnet test __tests__/sample-csproj/ --no-build)
echo "Sample output: $sample_output"
# For Side-by-Side installs we want to run the tests twice, for a single install the tests will run once
if [ -n "$2" ]; then
if [ -z "$(echo $sample_output | grep "Test Run Successful.*Test Run Successful.")" ]; then
echo "Unexpected output"
exit 1
fi
else
if [ -z "$(echo $sample_output | grep "Test Run Successful.")" ]; then
echo "Unexpected output"
exit 1
fi
fi

View File

@ -6,7 +6,7 @@ branding:
color: green color: green
inputs: inputs:
dotnet-version: 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: dotnet-quality:
description: 'Optional quality of the build. The possible values are: daily, signed, validated, preview, ga.' description: 'Optional quality of the build. The possible values are: daily, signed, validated, preview, ga.'
global-json-file: global-json-file:

150
dist/index.js vendored
View File

@ -45,7 +45,7 @@ function configAuthentication(feedUrl, existingFileLocation = '', processRoot =
} }
exports.configAuthentication = configAuthentication; exports.configAuthentication = configAuthentication;
function isValidKey(key) { function isValidKey(key) {
return /^[\w\-\.]+$/i.test(key); return /^[\w\-.]+$/i.test(key);
} }
function getExistingNugetConfig(processRoot) { function getExistingNugetConfig(processRoot) {
const defaultConfigName = 'nuget.config'; const defaultConfigName = 'nuget.config';
@ -60,9 +60,9 @@ function getExistingNugetConfig(processRoot) {
function writeFeedToFile(feedUrl, existingFileLocation, tempFileLocation) { function writeFeedToFile(feedUrl, existingFileLocation, tempFileLocation) {
var _a, _b; var _a, _b;
core.info(`dotnet-auth: Finding any source references in ${existingFileLocation}, writing a new temporary configuration file with credentials to ${tempFileLocation}`); core.info(`dotnet-auth: Finding any source references in ${existingFileLocation}, writing a new temporary configuration file with credentials to ${tempFileLocation}`);
let sourceKeys = []; const sourceKeys = [];
let owner = core.getInput('owner'); let owner = core.getInput('owner');
let sourceUrl = feedUrl; const sourceUrl = feedUrl;
if (!owner) { if (!owner) {
owner = github.context.repo.owner; owner = github.context.repo.owner;
} }
@ -132,7 +132,7 @@ function writeFeedToFile(feedUrl, existingFileLocation, tempFileLocation) {
} }
]; ];
if (!sourceKeys.length) { if (!sourceKeys.length) {
let keystring = 'Source'; const keystring = 'Source';
xmlSource[1].configuration.push({ xmlSource[1].configuration.push({
packageSources: [ packageSources: [
{ {
@ -238,11 +238,12 @@ const exec = __importStar(__nccwpck_require__(1514));
const io = __importStar(__nccwpck_require__(7436)); const io = __importStar(__nccwpck_require__(7436));
const hc = __importStar(__nccwpck_require__(6255)); const hc = __importStar(__nccwpck_require__(6255));
const fs_1 = __nccwpck_require__(7147); const fs_1 = __nccwpck_require__(7147);
const promises_1 = __nccwpck_require__(3292);
const path_1 = __importDefault(__nccwpck_require__(1017)); const path_1 = __importDefault(__nccwpck_require__(1017));
const os_1 = __importDefault(__nccwpck_require__(2037)); const os_1 = __importDefault(__nccwpck_require__(2037));
const semver_1 = __importDefault(__nccwpck_require__(5911)); const semver_1 = __importDefault(__nccwpck_require__(5911));
const utils_1 = __nccwpck_require__(918); const utils_1 = __nccwpck_require__(918);
const QUALITY_INPUT_MINIMAL_MAJOR_TAG = 6;
const LATEST_PATCH_SYNTAX_MINIMAL_MAJOR_TAG = 5;
class DotnetVersionResolver { class DotnetVersionResolver {
constructor(version) { constructor(version) {
this.inputVersion = version.trim(); this.inputVersion = version.trim();
@ -250,35 +251,54 @@ class DotnetVersionResolver {
} }
resolveVersionInput() { resolveVersionInput() {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
if (!semver_1.default.validRange(this.inputVersion)) { if (!semver_1.default.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`); 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)) { if (semver_1.default.valid(this.inputVersion)) {
this.resolvedArgument.type = 'version'; this.createVersionArgument();
this.resolvedArgument.value = this.inputVersion;
} }
else { else {
const [major, minor] = this.inputVersion.split('.'); yield this.createChannelArgument();
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 = yield this.getLatestVersion(httpClient, [major, minor]);
}
}
this.resolvedArgument.qualityFlag = +major >= 6 ? true : false;
} }
}); });
} }
isNumericTag(versionTag) { isNumericTag(versionTag) {
return /^\d+$/.test(versionTag); return /^\d+$/.test(versionTag);
} }
isLatestPatchSyntax() {
var _b, _c;
const majorTag = (_c = (_b = this.inputVersion.match(/^(?<majorTag>\d+)\.\d+\.\d{1}x{2}$/)) === null || _b === void 0 ? void 0 : _b.groups) === null || _c === void 0 ? void 0 : _c.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;
}
createVersionArgument() {
this.resolvedArgument.type = 'version';
this.resolvedArgument.value = this.inputVersion;
}
createChannelArgument() {
return __awaiter(this, void 0, void 0, function* () {
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 = yield 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;
});
}
createDotNetVersion() { createDotNetVersion() {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
yield this.resolveVersionInput(); yield this.resolveVersionInput();
@ -296,17 +316,21 @@ class DotnetVersionResolver {
return this.resolvedArgument; return this.resolvedArgument;
}); });
} }
getLatestVersion(httpClient, versionParts) { getLatestByMajorTag(majorTag) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
const httpClient = new hc.HttpClient('actions/setup-dotnet', [], {
allowRetries: true,
maxRetries: 3
});
const response = yield httpClient.getJson(DotnetVersionResolver.DotNetCoreIndexUrl); const response = yield httpClient.getJson(DotnetVersionResolver.DotNetCoreIndexUrl);
const result = response.result || {}; const result = response.result || {};
let releasesInfo = result['releases-index']; const releasesInfo = result['releases-index'];
let releaseInfo = releasesInfo.find(info => { const releaseInfo = releasesInfo.find(info => {
let sdkParts = info['channel-version'].split('.'); const sdkParts = info['channel-version'].split('.');
return sdkParts[0] === versionParts[0]; return sdkParts[0] === majorTag;
}); });
if (!releaseInfo) { if (!releaseInfo) {
throw new Error(`Could not find info for version ${versionParts.join('.')} at ${DotnetVersionResolver.DotNetCoreIndexUrl}`); throw new Error(`Could not find info for version with major tag: "${majorTag}" at ${DotnetVersionResolver.DotNetCoreIndexUrl}`);
} }
return releaseInfo['channel-version']; return releaseInfo['channel-version'];
}); });
@ -341,7 +365,7 @@ class DotnetCoreInstaller {
scriptArguments.push(option, this.quality); scriptArguments.push(option, this.quality);
} }
else { 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.`); core.warning(`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.`);
} }
} }
installDotnet() { installDotnet() {
@ -398,22 +422,21 @@ class DotnetCoreInstaller {
ignoreReturnCode: true, ignoreReturnCode: true,
env: process.env env: process.env
}; };
const { exitCode, stdout } = yield exec.getExecOutput(`"${scriptPath}"`, scriptArguments, getExecOutputOptions); const { exitCode, stdout, stderr } = yield exec.getExecOutput(`"${scriptPath}"`, scriptArguments, getExecOutputOptions);
if (exitCode) { if (exitCode) {
throw new Error(`Failed to install dotnet ${exitCode}. ${stdout}`); throw new Error(`Failed to install dotnet, exit code: ${exitCode}. ${stderr}`);
} }
return this.outputDotnetVersion(dotnetVersion.value); return this.parseInstalledVersion(stdout);
}); });
} }
outputDotnetVersion(version) { parseInstalledVersion(stdout) {
return __awaiter(this, void 0, void 0, function* () { const regex = /(?<version>\d+\.\d+\.\d+[a-z0-9._-]*)/gm;
const installationPath = process.env['DOTNET_INSTALL_DIR']; const matchedResult = regex.exec(stdout);
let versionsOnRunner = yield (0, promises_1.readdir)(path_1.default.join(installationPath.replace(/'/g, ''), 'sdk')); if (!matchedResult) {
let installedVersion = semver_1.default.maxSatisfying(versionsOnRunner, version, { core.warning(`Failed to parse installed by the script version of .NET`);
includePrerelease: true return null;
}); }
return installedVersion; return matchedResult.groups.version;
});
} }
} }
exports.DotnetCoreInstaller = DotnetCoreInstaller; exports.DotnetCoreInstaller = DotnetCoreInstaller;
@ -513,7 +536,7 @@ function run() {
const installedDotnetVersions = []; const installedDotnetVersions = [];
const globalJsonFileInput = core.getInput('global-json-file'); const globalJsonFileInput = core.getInput('global-json-file');
if (globalJsonFileInput) { if (globalJsonFileInput) {
const globalJsonPath = path_1.default.join(process.cwd(), globalJsonFileInput); const globalJsonPath = path_1.default.resolve(process.cwd(), globalJsonFileInput);
if (!fs.existsSync(globalJsonPath)) { if (!fs.existsSync(globalJsonPath)) {
throw new Error(`The specified global.json file '${globalJsonFileInput}' does not exist`); throw new Error(`The specified global.json file '${globalJsonFileInput}' does not exist`);
} }
@ -527,13 +550,13 @@ function run() {
versions.push(getVersionFromGlobalJson(globalJsonPath)); versions.push(getVersionFromGlobalJson(globalJsonPath));
} }
else { else {
core.info(`global.json wasn't found in the root directory. No .NET version will be installed.`); core.info(`The global.json wasn't found in the root directory. No .NET version will be installed.`);
} }
} }
if (versions.length) { if (versions.length) {
const quality = core.getInput('dotnet-quality'); const quality = core.getInput('dotnet-quality');
if (quality && !qualityOptions.includes(quality)) { 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.`); throw new Error(`Value '${quality}' is not supported for the 'dotnet-quality' option. Supported values are: daily, signed, validated, preview, ga.`);
} }
let dotnetInstaller; let dotnetInstaller;
const uniqueVersions = new Set(versions); const uniqueVersions = new Set(versions);
@ -549,13 +572,7 @@ function run() {
if (sourceUrl) { if (sourceUrl) {
auth.configAuthentication(sourceUrl, configFile); auth.configAuthentication(sourceUrl, configFile);
} }
const comparisonRange = globalJsonFileInput outputInstalledVersion(installedDotnetVersions, globalJsonFileInput);
? versions[versions.length - 1]
: '*';
const versionToOutput = semver_1.default.maxSatisfying(installedDotnetVersions, comparisonRange, {
includePrerelease: true
});
core.setOutput('dotnet-version', versionToOutput);
const matchersPath = path_1.default.join(__dirname, '..', '.github'); const matchersPath = path_1.default.join(__dirname, '..', '.github');
core.info(`##[add-matcher]${path_1.default.join(matchersPath, 'csc.json')}`); core.info(`##[add-matcher]${path_1.default.join(matchersPath, 'csc.json')}`);
} }
@ -580,6 +597,25 @@ function getVersionFromGlobalJson(globalJsonPath) {
} }
return version; return version;
} }
function outputInstalledVersion(installedVersions, globalJsonFileInput) {
if (!installedVersions.length) {
core.info(`The 'dotnet-version' output will not be set.`);
return;
}
if (installedVersions.includes(null)) {
core.warning(`Failed to output the installed version of .NET. The 'dotnet-version' 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('dotnet-version', versionToOutput);
return;
}
const versionToOutput = semver_1.default.maxSatisfying(installedVersions, '*', {
includePrerelease: true
});
core.setOutput('dotnet-version', versionToOutput);
}
run(); run();
@ -21084,14 +21120,6 @@ module.exports = require("fs");
/***/ }), /***/ }),
/***/ 3292:
/***/ ((module) => {
"use strict";
module.exports = require("fs/promises");
/***/ }),
/***/ 3685: /***/ 3685:
/***/ ((module) => { /***/ ((module) => {

View File

@ -61,12 +61,13 @@ Pull requests are the easiest way to contribute changes to git repos at GitHub.
- To implement new features or fix bugs, you need to make changes to the `.ts` files, which are located in the `src` folder - To implement new features or fix bugs, you need to make changes to the `.ts` files, which are located in the `src` folder
- To comply with the code style, **you need to run the `format` script** - To comply with the code style, **you need to run the `format` script**
- To lint the code, **you need to run the `lint:fix` script**
- To transpile source code to `javascript` we use [NCC](https://github.com/vercel/ncc). **It is very important to run the `build` script after making changes**, otherwise your changes will not get into the final `javascript` build - To transpile source code to `javascript` we use [NCC](https://github.com/vercel/ncc). **It is very important to run the `build` script after making changes**, otherwise your changes will not get into the final `javascript` build
**Learn more about how to implement tests:** **Learn more about how to implement tests:**
Adding or changing tests is an integral part of making a change to the code. Adding or changing tests is an integral part of making a change to the code.
Unit tests are in the `__tests__` folder, and end-to-end tests are in the `workflows` folder, particularly in the [workflow.yml](https://github.com/actions/setup-dotnet/blob/main/.github/workflows/workflow.yml). Unit tests are in the `__tests__` folder, and end-to-end tests are in the `workflows` folder, particularly in the [e2e-tests.yml](https://github.com/actions/setup-dotnet/blob/main/.github/workflows/e2e-tests.yml).
- The contributor can add various types of tests (like unit tests or end-to-end tests), which, in his opinion, will be necessary and sufficient for testing new or changed functionality - The contributor can add various types of tests (like unit tests or end-to-end tests), which, in his opinion, will be necessary and sufficient for testing new or changed functionality
- Tests should cover a successful execution, as well as some edge cases and possible errors - Tests should cover a successful execution, as well as some edge cases and possible errors

View File

@ -9,6 +9,12 @@
.DESCRIPTION .DESCRIPTION
Installs dotnet cli. If dotnet installation already exists in the given directory Installs dotnet cli. If dotnet installation already exists in the given directory
it will update it only if the requested version differs from the one already installed. it will update it only if the requested version differs from the one already installed.
Note that the intended use of this script is for Continuous Integration (CI) scenarios, where:
- The SDK needs to be installed without user interaction and without admin rights.
- The SDK installation doesn't need to persist across multiple CI runs.
To set up a development environment or to run apps, use installers rather than this script. Visit https://dotnet.microsoft.com/download to get the installer.
.PARAMETER Channel .PARAMETER Channel
Default: LTS Default: LTS
Download from the Channel specified. Possible values: Download from the Channel specified. Possible values:
@ -164,6 +170,12 @@ function Say-Verbose($str) {
} }
} }
function Measure-Action($name, $block) {
$time = Measure-Command $block
$totalSeconds = $time.TotalSeconds
Say-Verbose "⏱ Action '$name' took $totalSeconds seconds"
}
function Say-Invocation($Invocation) { function Say-Invocation($Invocation) {
$command = $Invocation.MyCommand; $command = $Invocation.MyCommand;
$args = (($Invocation.BoundParameters.Keys | foreach { "-$_ `"$($Invocation.BoundParameters[$_])`"" }) -join " ") $args = (($Invocation.BoundParameters.Keys | foreach { "-$_ `"$($Invocation.BoundParameters[$_])`"" }) -join " ")
@ -1104,10 +1116,10 @@ function Prepare-Install-Directory {
} }
} }
Say "Note that the intended use of this script is for Continuous Integration (CI) scenarios, where:" Say-Verbose "Note that the intended use of this script is for Continuous Integration (CI) scenarios, where:"
Say "- The SDK needs to be installed without user interaction and without admin rights." Say-Verbose "- The SDK needs to be installed without user interaction and without admin rights."
Say "- The SDK installation doesn't need to persist across multiple CI runs." Say-Verbose "- The SDK installation doesn't need to persist across multiple CI runs."
Say "To set up a development environment or to run apps, use installers rather than this script. Visit https://dotnet.microsoft.com/download to get the installer.`r`n" Say-Verbose "To set up a development environment or to run apps, use installers rather than this script. Visit https://dotnet.microsoft.com/download to get the installer.`r`n"
if ($SharedRuntime -and (-not $Runtime)) { if ($SharedRuntime -and (-not $Runtime)) {
$Runtime = "dotnet" $Runtime = "dotnet"
@ -1115,14 +1127,16 @@ if ($SharedRuntime -and (-not $Runtime)) {
$OverrideNonVersionedFiles = !$SkipNonVersionedFiles $OverrideNonVersionedFiles = !$SkipNonVersionedFiles
$CLIArchitecture = Get-CLIArchitecture-From-Architecture $Architecture Measure-Action "Product discovery" {
$NormalizedQuality = Get-NormalizedQuality $Quality $script:CLIArchitecture = Get-CLIArchitecture-From-Architecture $Architecture
Say-Verbose "Normalized quality: '$NormalizedQuality'" $script:NormalizedQuality = Get-NormalizedQuality $Quality
$NormalizedChannel = Get-NormalizedChannel $Channel Say-Verbose "Normalized quality: '$NormalizedQuality'"
Say-Verbose "Normalized channel: '$NormalizedChannel'" $script:NormalizedChannel = Get-NormalizedChannel $Channel
$NormalizedProduct = Get-NormalizedProduct $Runtime Say-Verbose "Normalized channel: '$NormalizedChannel'"
Say-Verbose "Normalized product: '$NormalizedProduct'" $script:NormalizedProduct = Get-NormalizedProduct $Runtime
$FeedCredential = ValidateFeedCredential $FeedCredential Say-Verbose "Normalized product: '$NormalizedProduct'"
$script:FeedCredential = ValidateFeedCredential $FeedCredential
}
$InstallRoot = Resolve-Installation-Path $InstallDir $InstallRoot = Resolve-Installation-Path $InstallDir
Say-Verbose "InstallRoot: $InstallRoot" Say-Verbose "InstallRoot: $InstallRoot"
@ -1200,7 +1214,7 @@ if ($DryRun) {
return return
} }
Prepare-Install-Directory Measure-Action "Installation directory preparation" { Prepare-Install-Directory }
$ZipPath = [System.IO.Path]::combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()) $ZipPath = [System.IO.Path]::combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName())
Say-Verbose "Zip path: $ZipPath" Say-Verbose "Zip path: $ZipPath"
@ -1214,7 +1228,7 @@ foreach ($link in $DownloadLinks)
Say-Verbose "Downloading `"$($link.type)`" link $($link.downloadLink)" Say-Verbose "Downloading `"$($link.type)`" link $($link.downloadLink)"
try { try {
DownloadFile -Source $link.downloadLink -OutPath $ZipPath Measure-Action "Package download" { DownloadFile -Source $link.downloadLink -OutPath $ZipPath }
Say-Verbose "Download succeeded." Say-Verbose "Download succeeded."
$DownloadSucceeded = $true $DownloadSucceeded = $true
$DownloadedLink = $link $DownloadedLink = $link
@ -1251,7 +1265,7 @@ if (-not $DownloadSucceeded) {
} }
Say "Extracting the archive." Say "Extracting the archive."
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. # Check if the SDK version is installed; if not, fail the installation.
$isAssetInstalled = $false $isAssetInstalled = $false
@ -1277,225 +1291,224 @@ if (!$isAssetInstalled) {
SafeRemoveFile -Path $ZipPath SafeRemoveFile -Path $ZipPath
Prepend-Sdk-InstallRoot-To-Path -InstallRoot $InstallRoot Measure-Action "Setting up shell environment" { Prepend-Sdk-InstallRoot-To-Path -InstallRoot $InstallRoot }
Say "Note that the script does not resolve dependencies during installation." Say "Note that the script does not resolve dependencies during installation."
Say "To check the list of dependencies, go to https://learn.microsoft.com/dotnet/core/install/windows#dependencies" Say "To check the list of dependencies, go to https://learn.microsoft.com/dotnet/core/install/windows#dependencies"
Say "Installed version is $($DownloadedLink.effectiveVersion)" Say "Installed version is $($DownloadedLink.effectiveVersion)"
Say "Installation finished" Say "Installation finished"
# SIG # Begin signature block # SIG # Begin signature block
# MIInzgYJKoZIhvcNAQcCoIInvzCCJ7sCAQExDzANBglghkgBZQMEAgEFADB5Bgor # MIInvwYJKoZIhvcNAQcCoIInsDCCJ6wCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCB7pzZ0nuEMd30h # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBhfTi3SRn7+vyy
# n1EcAYUQN+1clltqaLf9611TDrw/laCCDYUwggYDMIID66ADAgECAhMzAAACzfNk # uCXKPjhiawegWZ493EcaOEycbgkZcKCCDXYwggX0MIID3KADAgECAhMzAAACy7d1
# v/jUTF1RAAAAAALNMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # OfsCcUI2AAAAAALLMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjIwNTEyMjA0NjAyWhcNMjMwNTExMjA0NjAyWjB0MQsw # bmcgUENBIDIwMTEwHhcNMjIwNTEyMjA0NTU5WhcNMjMwNTExMjA0NTU5WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDrIzsY62MmKrzergm7Ucnu+DuSHdgzRZVCIGi9CalFrhwtiK+3FIDzlOYbs/zz # AQC3sN0WcdGpGXPZIb5iNfFB0xZ8rnJvYnxD6Uf2BHXglpbTEfoe+mO//oLWkRxA
# HwuLC3hir55wVgHoaC4liQwQ60wVyR17EZPa4BQ28C5ARlxqftdp3H8RrXWbVyvQ # wppditsSVOD0oglKbtnh9Wp2DARLcxbGaW4YanOWSB1LyLRpHnnQ5POlh2U5trg4
# aUnBQVZM73XDyGV1oUPZGHGWtgdqtBUd60VjnFPICSf8pnFiit6hvSxH5IVWI0iO # 3gQjvlNZlQB3lL+zrPtbNvMA7E0Wkmo+Z6YFnsf7aek+KGzaGboAeFO4uKZjQXY5
# nfqdXYoPWUtVUMmVqW1yBX0NtbQlSHIU6hlPvo9/uqKvkjFUFA2LbC9AWQbJmH+1 # RmMzE70Bwaz7hvA05jDURdRKH0i/1yK96TDuP7JyRFLOvA3UXNWz00R9w7ppMDcN
# uM0l4nDSKfCqccvdI5l3zjEk9yUSUmh1IQhDFn+5SL2JmnCF0jZEZ4f5HE7ykDP+ # lXtrmbPigv3xE9FfpfmJRtiOZQKd73K72Wujmj6/Su3+DBTpOq7NgdntW2lJfX3X
# oiA3Q+fhKCseg+0aEHi+DRPZAgMBAAGjggGCMIIBfjAfBgNVHSUEGDAWBgorBgEE # a6oe4F9Pk9xRhkwHsk7Ju9E/AgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQU0WymH4CP7s1+yQktEwbcLQuR9Zww # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUrg/nt/gj+BBLd1jZWYhok7v5/w4w
# VAYDVR0RBE0wS6RJMEcxLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJh # RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW
# dGlvbnMgTGltaXRlZDEWMBQGA1UEBRMNMjMwMDEyKzQ3MDUzMDAfBgNVHSMEGDAW # MBQGA1UEBRMNMjMwMDEyKzQ3MDUyODAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci
# gBRIbmTlUAXTgqoXNzcitW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8v # tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# d3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIw # b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG
# MTEtMDctMDguY3JsMGEGCCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDov # CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu
# L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDEx # Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0
# XzIwMTEtMDctMDguY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIB # MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAJL5t6pVjIRlQ8j4dAFJ
# AE7LSuuNObCBWYuttxJAgilXJ92GpyV/fTiyXHZ/9LbzXs/MfKnPwRydlmA2ak0r # ZnMke3rRHeQDOPFxswM47HRvgQa2E1jea2aYiMk1WmdqWnYw1bal4IzRlSVf4czf
# GWLDFh89zAWHFI8t9JLwpd/VRoVE3+WyzTIskdbBnHbf1yjo/+0tpHlnroFJdcDS # zx2vjOIOiaGllW2ByHkfKApngOzJmAQ8F15xSHPRvNMmvpC3PFLvKMf3y5SyPJxh
# MIsH+T7z3ClY+6WnjSTetpg1Y/pLOLXZpZjYeXQiFwo9G5lzUcSd8YVQNPQAGICl # 922TTq0q5epJv1SgZDWlUlHL/Ex1nX8kzBRhHvc6D6F5la+oAO4A3o/ZC05OOgm4
# 2JRSaCNlzAdIFCF5PNKoXbJtEqDcPZ8oDrM9KdO7TqUE5VqeBe6DggY1sZYnQD+/ # EJxZP9MqUi5iid2dw4Jg/HvtDpCcLj1GLIhCDaebKegajCJlMhhxnDXrGFLJfX8j
# LWlz5D0wCriNgGQ/TWWexMwwnEqlIwfkIcNFxo0QND/6Ya9DTAUykk2SKGSPt0kL # 7k7LUvrZDsQniJZ3D66K+3SZTLhvwK7dMGVFuUUJUfDifrlCTjKG9mxsPDllfyck
# tHxNEn2GJvcNtfohVY/b0tuyF05eXE3cdtYZbeGoU1xQixPZAlTdtLmeFNly82uB # 4zGnRZv8Jw9RgE1zAghnU14L0vVUNOzi/4bE7wIsiRyIcCcVoXRneBA3n/frLXvd
# VbybAZ4Ut18F//UrugVQ9UUdK1uYmc+2SdRQQCccKwXGOuYgZ1ULW2u5PyfWxzo4 # jDsbb2lpGu78+s1zbO5N0bhHWq4j5WMutrspBxEhqG2PSBjC5Ypi+jhtfu3+x76N
# BR++53OB/tZXQpz4OkgBZeqs9YaYLFfKRlQHVtmQghFHzB5v/WFonxDVlvPxy2go # mBvsyKuxx9+Hm/ALnlzKxr4KyMR3/z4IRMzA1QyppNk65Ui+jB14g+w4vole33M1
# a0u9Z+ZlIpvooZRvm6OtXxdAjMBcWBAsnBRr/Oj5s356EDdf2l/sLwLFYE61t+ME # pVqVckrmSebUkmjnCshCiH12IFgHZF7gRwE4YZrJ7QjxZeoZqHaKsQLRMp653beB
# iNYdy0pXL6gN3DxTVf2qjJxXFkFfjjTisndudHsguEMk8mEtnvwo9fOSKT6oRHhM # fHfeva9zJPhBSdVcCW7x9q0c2HVPLJHX9YCUU714I+qtLpDGrdbZxD9mikPqL/To
# 9sZ4HTg/TTMjUljmN3mBYWAWI5ExdC1inuog0xrKmOWVMIIHejCCBWKgAwIBAgIK # /1lDZ0ch8FtePhME7houuoPcMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq
# YQ6Q0gAAAAAAAzANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNV # 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
# /Xmfwb1tbWrJUnMTDXpQzTGCGZ8wghmbAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp
# Z25pbmcgUENBIDIwMTECEzMAAALLt3U5+wJxQjYAAAAAAsswDQYJYIZIAWUDBAIB
# BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO
# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIFmuaTXYQ37AFvsEol24fdW+
# nRqHcc1fr+VQVdqhXc/vMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A
# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB
# BQAEggEAjY5XW5Ly7TJ1OTbeIR98xU+2dmtw7L71ws+ICnQCGhj2xJDUK+5yrTfO
# 8C98l/P4ynFi33Dl8z2YElqUCuqEXbiCzz06lIL4NuibC5DV/X80ZmICR/NYd2v1
# ww7IH+7dpsHAowBBindCYpVwQ3Ea3kDWgsjPAinAysFFushSOnNWFvrF6vi2smrs
# smbrAAhEhSfLd1Pxxdw73hQ0YjM/D3F3opaybMQ0blpHhOaqtbiyYzvk0doIzBEc
# trSH4NDIc3yLNj5VbjSczpexE+hyQNY4xCtwco4bVtXhONUihv08AIKR8+sIaI7A
# mM/SWrrwGYSSSxydKqDei7biKG4jDqGCFykwghclBgorBgEEAYI3AwMBMYIXFTCC
# FxEGCSqGSIb3DQEHAqCCFwIwghb+AgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFZBgsq
# hkiG9w0BCRABBKCCAUgEggFEMIIBQAIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl
# AwQCAQUABCB6Hzt2gUb/WZK8fvVnOocriE4rYr6mscZi3gZnBCpiigIGZBr2iMZU
# GBMyMDIzMDMzMTE1MjEwNi41MTZaMASAAgH0oIHYpIHVMIHSMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJl
# bGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNO
# OjA4NDItNEJFNi1DMjlBMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBT
# ZXJ2aWNloIIReDCCBycwggUPoAMCAQICEzMAAAGybkADf26plJIAAQAAAbIwDQYJ
# KoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwHhcNMjIw
# OTIwMjAyMjAxWhcNMjMxMjE0MjAyMjAxWjCB0jELMAkGA1UEBhMCVVMxEzARBgNV
# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv # BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv
# c29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlm # c29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9zb2Z0IElyZWxhbmQgT3Bl
# aWNhdGUgQXV0aG9yaXR5IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEw # cmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjowODQyLTRC
# OTA5WjB+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # RTYtQzI5QTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZTCC
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYD # AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMqiZTIde/lQ4rC+Bml5f/Wu
# VQQDEx9NaWNyb3NvZnQgQ29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG # q/xKTxrfbG23HofmQ+qZAN4GyO73PF3y9OAfpt7Qf2jcldWOGUB+HzBuwllYyP3f
# 9w0BAQEFAAOCAg8AMIICCgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+la # x4MY8zvuAuB37FvoytnNC2DKnVrVlHOVcGUL9CnmhDNMA2/nskjIf2IoiG9J0qLY
# UKq4BjgaBEm6f8MMHt03a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc # r8duvHdQJ9Li2Pq9guySb9mvUL60ogslCO9gkh6FiEDwMrwUr8Wja6jFpUTny8tg
# 6Whe0t+bU7IKLMOv2akrrnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4D # 0N0cnCN2w4fKkp5qZcbUYFYicLSb/6A7pHCtX6xnjqwhmJoib3vkKJyVxbuFLRhV
# dato88tt8zpcoRb0RrrgOGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+ # XxH95b0LHeNhifn3jvo2j+/4QV10jEpXVW+iC9BsTtR69xvTjU51ZgP7BR4YDEWq
# lD3v++MrWhAfTVYoonpy4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nk # 7JsylSOv5B5THTDXRf184URzFhTyb8OZQKY7mqMh7c8J8w1sEM4XDUF2UZNy829N
# kDstrjNYxbc+/jLTswM9sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6 # VCzG2tfdEXZaHxF8RmxpQYBxyhZwY1rotuIS+gfN2eq+hkAT3ipGn8/KmDwDtzAb
# A4aN91/w0FK/jJSHvMAhdCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmd # nfuXjApgeZqwgcYJ8pDJ+y/xU6ouzJz1Bve5TTihkiA7wQsQe6R60Zk9dPdNzw0M
# X4jiJV3TIUs+UsS1Vz8kA/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL # K5niRzuQZAt4GI96FhjhlUWcUZOCkv/JXM/OGu/rgSplYwdmPLzzfDtXyuy/GCU5
# 5zmhD+kjSbwYuER8ReTBw3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zd # I4l08g6iifXypMgoYkkceOAAz4vx1x0BOnZWfI3fSwqNUvoN7ncTT+MB4Vpvf1QB
# sGbiwZeBe+3W7UvnSSmnEyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3 # ppjBAQUuvui6eCG0MCVNAgMBAAGjggFJMIIBRTAdBgNVHQ4EFgQUmfIngFzZEZlP
# T8HhhUSJxAlMxdSlQy90lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS # kjDOVluBSDDaanEwHwYDVR0jBBgwFoAUn6cVXQBeYl2D9OXSZacbUzUZ6XIwXwYD
# 4NaIjAsCAwEAAaOCAe0wggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRI # VR0fBFgwVjBUoFKgUIZOaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9j
# bmTlUAXTgqoXNzcitW2oynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTAL # cmwvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3JsMGwG
# BgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBD # CCsGAQUFBwEBBGAwXjBcBggrBgEFBQcwAoZQaHR0cDovL3d3dy5taWNyb3NvZnQu
# uRQFTuHqp8cx0SOJNDBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jv # Y29tL3BraW9wcy9jZXJ0cy9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIw
# c29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf # MjAxMCgxKS5jcnQwDAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcD
# MDNfMjIuY3JsMF4GCCsGAQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3 # CDAOBgNVHQ8BAf8EBAMCB4AwDQYJKoZIhvcNAQELBQADggIBANxHtu3FzIabaDbW
# dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf # qswdKBlAhKXRCN+5CSMiv2TYa4i2QuWIm+99piwAhDhADfbqor1zyLi95Y6GQnvI
# MDNfMjIuY3J0MIGfBgNVHSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEF # WUgdeC7oL1ZtZye92zYK+EIfwYZmhS+CH4infAzUvscHZF3wlrJUfPUIDGVP0lCY
# BQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1h # Vse9mguvG0dqkY4ayQPEHOvJubgZZaOdg/N8dInd6fGeOc+0DoGzB+LieObJ2Q0A
# cnljcHMuaHRtMEAGCCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkA # tEt3XN3iX8Cp6+dZTX8xwE/LvhRwPpb/+nKshO7TVuvenwdTwqB/LT6CNPaElwFe
# YwB5AF8AcwB0AGEAdABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn # KxKrqRTPMbHeg+i+KnBLfwmhEXsMg2s1QX7JIxfvT96md0eiMjiMEO22LbOzmLMN
# 8oalmOBUeRou09h0ZyKbC5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7 # d3LINowAnRBAJtX+3/e390B9sMGMHp+a1V+hgs62AopBl0p/00li30DN5wEQ5If3
# v0epo/Np22O/IjWll11lhJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0b # 5Zk7b/T6pEx6rJUDYCti7zCbikjKTanBnOc99zGMlej5X+fC/k5ExUCrOs3/VzGR
# pdS1HXeUOeLpZMlEPXh6I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/ # CZt5LvVQSdWqq/QMzTEmim4sbzASK9imEkjNtZZyvC1CsUcD1voFktld4mKMjE+u
# KmtYSWMfCWluWpiW5IP0wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvy # DEV3IddD+DrRk94nVzNPSuZXewfVOnXHSeqG7xM3V7fl2aL4v1OhL2+JwO1Tx3B0
# CInWH8MyGOLwxS3OW560STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBp # irO1O9qbNdJk355bntd1RSVKgM22KFBHnoL7Js7pRhBiaKmVTQGoOb+j1Qa7q+ci
# mLJZiWhub6e3dMNABQamASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJi # xGo48Vh9k35BDsJS/DLoXFSPDl4mMIIHcTCCBVmgAwIBAgITMwAAABXF52ueAptJ
# hsMdYzaXht/a8/jyFqGaJ+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYb # mQAAAAAAFTANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT
# BL7fQccOKO7eZS/sl/ahXJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbS # Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m
# oqKfenoi+kiVH6v7RyOA9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sL # dCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNh
# gOppO6/8MO0ETI7f33VtY5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtX # dGUgQXV0aG9yaXR5IDIwMTAwHhcNMjEwOTMwMTgyMjI1WhcNMzAwOTMwMTgzMjI1
# cVZOSEXAQsmbdlsKgEhr/Xmfwb1tbWrJUnMTDXpQzTGCGZ8wghmbAgEBMIGVMH4x # WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt # UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD
# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01p # Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCAiIwDQYJKoZIhvcNAQEB
# Y3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTECEzMAAALN82S/+NRMXVEAAAAA # BQADggIPADCCAgoCggIBAOThpkzntHIhC3miy9ckeb0O1YLT/e6cBwfSqWxOdcjK
# As0wDQYJYIZIAWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQw # NVf2AX9sSuDivbk+F2Az/1xPx2b3lVNxWuJ+Slr+uDZnhUYjDLWNE893MsAQGOhg
# HAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEINK7 # fWpSg0S3po5GawcU88V29YZQ3MFEyHFcUTE3oAo4bo3t1w/YJlN8OWECesSq/XJp
# cJe0KVfbcXchjID30U/cUg7pWAQUa3+n8JuhjLCLMEIGCisGAQQBgjcCAQwxNDAy # rx2rrPY2vjUmZNqYO7oaezOtgFt+jBAcnVL+tuhiJdxqD89d9P6OU8/W7IVWTe/d
# oBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5j # vI2k45GPsjksUZzpcGkNyjYtcI4xyDUoveO0hyTD4MmPfrVUj9z6BVWYbWg7mka9
# b20wDQYJKoZIhvcNAQEBBQAEggEAODLxcflOtjpIXXIhbYyQ0wFeBx0NrmoMU/Ri # 7aSueik3rMvrg0XnRm7KMtXAhjBcTyziYrLNueKNiOSWrAFKu75xqRdbZ2De+JKR
# e7CRrAieAbG4iLJzs4DhUov5iuMHY6AAbLWAH54QlSkd4XNp6POsE7lSzN9yjlVw # Hh09/SDPc31BmkZ1zcRfNN0Sidb9pSB9fvzZnkXftnIv231fgLrbqn427DZM9itu
# V/e0XCaYeXIbtd75hGd5P7wAhM4m2ViDI4IRHyQtjysW0U0F6YiqNlFm7Fzo5Si6 # qBJR6L8FA6PRc6ZNN3SUHDSCD/AQ8rdHGO2n6Jl8P0zbr17C89XYcz1DTsEzOUyO
# l2XxvuEDSdyJcEN70wHQajx6bKfnI/oMY59iGjDXvDP/6cQV9NI0gPHFTwPKA7vg # ArxCaC4Q6oRRRuLRvWoYWmEBc8pnol7XKHYC4jMYctenIPDC+hIK12NvDMk2ZItb
# PySyVFEG7dQMoEwAWy9GHbcS//RulgUwBhWcrtUP411XLSO6is2VTknwbdglc9HZ # oKaDIV1fMHSRlJTYuVD5C4lh8zYGNRiER9vcG9H9stQcxWv2XFJRXRLbJbqvUAV6
# zViuS4C1ujHlPrlMzm8Y5iGVIQCna5w2NU/kGsSK5+dMkovomKGCFykwghclBgor # bMURHXLvjflSxIUXk8A8FdsaN8cIFRg/eKtFtvUeh17aj54WcmnGrnu3tz5q4i6t
# BgEEAYI3AwMBMYIXFTCCFxEGCSqGSIb3DQEHAqCCFwIwghb+AgEDMQ8wDQYJYIZI # AgMBAAGjggHdMIIB2TASBgkrBgEEAYI3FQEEBQIDAQABMCMGCSsGAQQBgjcVAgQW
# AWUDBAIBBQAwggFZBgsqhkiG9w0BCRABBKCCAUgEggFEMIIBQAIBAQYKKwYBBAGE # BBQqp1L+ZMSavoKRPEY1Kc8Q/y8E7jAdBgNVHQ4EFgQUn6cVXQBeYl2D9OXSZacb
# WQoDATAxMA0GCWCGSAFlAwQCAQUABCDRz6ce9oWlH6+o0BtjmAjtvEMN1hfhIA5v # UzUZ6XIwXAYDVR0gBFUwUzBRBgwrBgEEAYI3TIN9AQEwQTA/BggrBgEFBQcCARYz
# +wTZHvB4XgIGY2PeyIloGBMyMDIyMTExMDE1MDUxNi43MzRaMASAAgH0oIHYpIHV # aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9Eb2NzL1JlcG9zaXRvcnku
# MIHSMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH # aHRtMBMGA1UdJQQMMAoGCCsGAQUFBwMIMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIA
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQL # QwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNX2
# EyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJjAkBgNVBAsT # VsuP6KJcYmjRPZSQW9fOmhjEMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwu
# HVRoYWxlcyBUU1MgRVNOOkEyNDAtNEI4Mi0xMzBFMSUwIwYDVQQDExxNaWNyb3Nv # bWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEw
# ZnQgVGltZS1TdGFtcCBTZXJ2aWNloIIReDCCBycwggUPoAMCAQICEzMAAAG4CNTB # LTA2LTIzLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93
# uHngUUkAAQAAAbgwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNV # d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYt
# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv # MjMuY3J0MA0GCSqGSIb3DQEBCwUAA4ICAQCdVX38Kq3hLB9nATEkW+Geckv8qW/q
# c29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAg # XBS2Pk5HZHixBpOXPTEztTnXwnE2P9pkbHzQdTltuw8x5MKP+2zRoZQYIu7pZmc6
# UENBIDIwMTAwHhcNMjIwOTIwMjAyMjE2WhcNMjMxMjE0MjAyMjE2WjCB0jELMAkG # U03dmLq2HnjYNi6cqYJWAAOwBb6J6Gngugnue99qb74py27YP0h1AdkY3m2CDPVt
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx # I1TkeFN1JFe53Z/zjj3G82jfZfakVqr3lbYoVSfQJL1AoL8ZthISEV09J+BAljis
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9z # 9/kpicO8F7BUhUKz/AyeixmJ5/ALaoHCgRlCGVJ1ijbCHcNhcy4sa3tuPywJeBTp
# b2Z0IElyZWxhbmQgT3BlcmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMg # kbKpW99Jo3QMvOyRgNI95ko+ZjtPu4b6MhrZlvSP9pEB9s7GdP32THJvEKt1MMU0
# VFNTIEVTTjpBMjQwLTRCODItMTMwRTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt # sHrYUP4KWN1APMdUbZ1jdEgssU5HLcEUBHG/ZPkkvnNtyo4JvbMBV0lUZNlz138e
# U3RhbXAgU2VydmljZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAJwb # W0QBjloZkWsNn6Qo3GcZKCS6OEuabvshVGtqRRFHqfG3rsjoiV5PndLQTHa1V1QJ
# sfwRHERn5C95QPGn37tJ5vOiY9aWjeIDxpgaXaYGiqsw0G0cvCK3YulrqemEf2Ck # sWkBRH58oWFsc/4Ku+xBZj1p/cvBQUl+fpO+y/g75LcVv7TOPqUxUYS8vwLBgqJ7
# GSdcOJAF++EqhOSqrO13nGcjqw6hFNnsGwKANyzddwnOO0jz1lfBIIu77TbfNvna # Fx0ViY1w/ue10CgaiQuPNtq6TPmb/wrpNPgkNWcr4A245oyZ1uEi6vAnQj0llOZ0
# WbwSRu0DTGHA7n7PR0MYJ9bC/HopStpbFf606LKcTWnwaUuEdAhx6FAqg1rkgugi # dFtq0Z4+7X6gMTN9vMvpe784cETRkPHIqzqKOghif9lwY1NNje6CbaUFEMFxBmoQ
# uuaaxKyxRkdjFZLKFXEXL9p01PtwS0fG6vZiRVnEKgeal2TeLvdAIqapBwltPYif # tB1VM1izoXBm8qGCAtQwggI9AgEBMIIBAKGB2KSB1TCB0jELMAkGA1UEBhMCVVMx
# gqnp7Z4VJMcPo0TWmRNVFOcHRNwWHehN9xg6ugIGXPo7hMpWrPgg4moHO2epc0T3 # EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT
# 6rgm9hlDrl28bG5TakmV7NJ98kbF5lgtlrowT6ecwEVtuLd4a0gzYqhanW7zaFZn # FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9zb2Z0IElyZWxh
# Dft5yMexy59ifETdzpwArj2nJAyIsiq1PY3XPm2mUMLlACksqelHKfWihK/Fehw/ # bmQgT3BlcmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjow
# mziovBVwkkr/G0F19OWgR+MBUKifwpOyQiLAxrqvVnfCY4QjJCZiHIuS15HCQ/TI # ODQyLTRCRTYtQzI5QTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy
# t/Qj4x1WvRa1UqjnmpLu4/yBYWZsdvZoq8SXI7iOs7muecAJeEkYlM6iOkMighzE # dmljZaIjCgEBMAcGBSsOAwIaAxUAjhJ+EeySRfn2KCNsjn9cF9AUSTqggYMwgYCk
# hjQK9ThPpoAtluXbL7qIHGrfFlHmX/4soc7jj1j8uB31U34gJlB2XphjMaT+E+O9 # fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# SImk/6GRV9Sm8C88Fnmm2VdwMluCNAUzPFjfvHx3AgMBAAGjggFJMIIBRTAdBgNV # UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD
# HQ4EFgQUxP1HJTeFwzNYo1njfucXuUfQaW4wHwYDVR0jBBgwFoAUn6cVXQBeYl2D # Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF
# 9OXSZacbUzUZ6XIwXwYDVR0fBFgwVjBUoFKgUIZOaHR0cDovL3d3dy5taWNyb3Nv # AOfRUdUwIhgPMjAyMzAzMzEyMDM0MjlaGA8yMDIzMDQwMTIwMzQyOVowdDA6Bgor
# ZnQuY29tL3BraW9wcy9jcmwvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUy # BgEEAYRZCgQBMSwwKjAKAgUA59FR1QIBADAHAgEAAgIKJDAHAgEAAgIRLzAKAgUA
# MDIwMTAoMSkuY3JsMGwGCCsGAQUFBwEBBGAwXjBcBggrBgEFBQcwAoZQaHR0cDov # 59KjVQIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIBAAID
# L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNyb3NvZnQlMjBUaW1l # B6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBAJlOESCa/uRR1x6GunE8
# LVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcnQwDAYDVR0TAQH/BAIwADAWBgNVHSUB # K/WgHWTpSE31EITDOfTMvDcF4ptngCS5aOc4gfzmhNNehWfP6EOrgoSQzJYZ4YCh
# Af8EDDAKBggrBgEFBQcDCDAOBgNVHQ8BAf8EBAMCB4AwDQYJKoZIhvcNAQELBQAD # fYbHNMk56f18sq8t7y2hgR7KixcEo/4HVzeSdaOclHNc4Gn7kCGpMvpT3Xz9Lzc7
# ggIBAJ9uk8miwpMoKw3D996piEzbegAGxkABHYn2vP2hbqnkS9U97s/6QlyZOhGF # UKWDZ0zkNKnbS8TZLNueVQwfMYIEDTCCBAkCAQEwgZMwfDELMAkGA1UEBhMCVVMx
# sVudaiLeRZZTsaG5hR0oCuBINZ/lelo5xzHc+mBOpBXpxSaW1hqoxaCLsVH1EBtz # EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT
# 7in25Hjy+ejuBcilH6EZ0ZtNxmWGIQz8R0AuS0Tj4VgJXHIlXP9dVOiyGo9Velrk # FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUt
# +FGx/BC+iEuCaKd/IsypHPiCUCh52DGc91s2S7ldQx1H4CljOAtanDfbvSejASWL # U3RhbXAgUENBIDIwMTACEzMAAAGybkADf26plJIAAQAAAbIwDQYJYIZIAWUDBAIB
# o/s3w0XMAbDurWNns0XidAF2RnL1PaxoOyz9VYakNGK4F3/uJRZnVgbsCYuwNX1B # BQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQx
# mSwM1ZbPSnggNSGTZx/FQ20Jj/ulrK0ryAbvNbNb4kkaS4a767ifCqvUOFLlUT8P # IgQgXhJRuHCXk3arJvifIY3DBe9Ce9EmlP1y6U4XkgL31DkwgfoGCyqGSIb3DQEJ
# N43hhldxI6yHPMOWItJpEHIZBiTNKblBsYbIrghb1Ym9tfSsLa5ZJDzVZNndRfhU # EAIvMYHqMIHnMIHkMIG9BCBTeM485+E+t4PEVieUoFKX7PVyLo/nzu+htJPCG04+
# qJOyXF+CVm9OtVmFDG9kIwM6QAX8Q0if721z4VOzZNvD8ktg1lI+XjXgXDJVs3h4 # NTCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw
# 7sMu9GXSYzky+7dtgmc3iRPkda3YVRdmPJtNFN0NLybcssE7vhFCij75eDGQBFq0 # DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x
# A4KVG6uBdr6UTWwE0VKHxBz2BpGvn7BCs+5yxnF+HV6CUickDqqPi/II7Zssd9Eb # JjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAABsm5A
# P9uzj4luldXDAPrWGtdGq+wK0odlGNVuCMxsL3hn8+KiO9UiMIIHcTCCBVmgAwIB # A39uqZSSAAEAAAGyMCIEIGGWlnNnYHrB5HguWG0/nJd/WvSrCogze+QCpenu3IM5
# AgITMwAAABXF52ueAptJmQAAAAAAFTANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UE # MA0GCSqGSIb3DQEBCwUABIICADVOLTuNxeEnBOfZpb7Nv4uf91W/Ho5i99zenDSJ
# BhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAc # x5QHVs+bKXmgc3a7/SSsliAT3zygHc7cH4zARbCZePLTivByKmeG08Ka35eyR+FK
# BgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0 # awSNrI/X+eVIC6nw/egCwviBC1NAG8jHGkuScbHeiiGajvS6lp3ORML7UexMuE4w
# IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMjEwOTMwMTgyMjI1 # 9SEumoghljCLZMwCSvw+3WxhQoBEZroR8u+PID2RdD0vi85FjKPWcZZijVLqHeFi
# WhcNMzAwOTMwMTgzMjI1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu # TnuFqwRCLTV0MV+dDCbjwXneIqV+AVlnqb9iDMr3ZhISlRcy9XJNpY5vQBj/wqUW
# Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv # vefrmpdz0LNkdtXYThPkyl3mha2KsoQi5SA9zSjlAjFgY3ppmXvi3Frbfqk+iL+f
# cmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCC # l/Qc4+B71jG4t28lTWKteJiHqo+6AUXK2rlAl0d74yvhO6N8lMMtXhdJc8JABYn1
# AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAOThpkzntHIhC3miy9ckeb0O # v2/KKZn5RvPFF8QP7Ac1saIe1+gUFNcsYOLaMm/xl8E6kefWwZnm5Rhm606g1AC/
# 1YLT/e6cBwfSqWxOdcjKNVf2AX9sSuDivbk+F2Az/1xPx2b3lVNxWuJ+Slr+uDZn # N5Wo08aAs0ymTPH91dEbmOURXLbA3vCyG7kbfgnhCs/j7oQHWaFDzEYuXDIA4ICT
# hUYjDLWNE893MsAQGOhgfWpSg0S3po5GawcU88V29YZQ3MFEyHFcUTE3oAo4bo3t # dxPUTltbq3OWdp0PAS8JSEKPQFaOoQEnPa4adrXWxMvOmel8IGqJiQ+BPOaLQG64
# 1w/YJlN8OWECesSq/XJprx2rrPY2vjUmZNqYO7oaezOtgFt+jBAcnVL+tuhiJdxq # Qu2tMkH/5szb1fsEnCe8SJmy5ESF+kmpnLBtJ17Y9o+9nJHF5ddFmvzy+LUaIqDN
# D89d9P6OU8/W7IVWTe/dvI2k45GPsjksUZzpcGkNyjYtcI4xyDUoveO0hyTD4MmP # cOfH
# frVUj9z6BVWYbWg7mka97aSueik3rMvrg0XnRm7KMtXAhjBcTyziYrLNueKNiOSW
# rAFKu75xqRdbZ2De+JKRHh09/SDPc31BmkZ1zcRfNN0Sidb9pSB9fvzZnkXftnIv
# 231fgLrbqn427DZM9ituqBJR6L8FA6PRc6ZNN3SUHDSCD/AQ8rdHGO2n6Jl8P0zb
# r17C89XYcz1DTsEzOUyOArxCaC4Q6oRRRuLRvWoYWmEBc8pnol7XKHYC4jMYcten
# IPDC+hIK12NvDMk2ZItboKaDIV1fMHSRlJTYuVD5C4lh8zYGNRiER9vcG9H9stQc
# xWv2XFJRXRLbJbqvUAV6bMURHXLvjflSxIUXk8A8FdsaN8cIFRg/eKtFtvUeh17a
# j54WcmnGrnu3tz5q4i6tAgMBAAGjggHdMIIB2TASBgkrBgEEAYI3FQEEBQIDAQAB
# MCMGCSsGAQQBgjcVAgQWBBQqp1L+ZMSavoKRPEY1Kc8Q/y8E7jAdBgNVHQ4EFgQU
# n6cVXQBeYl2D9OXSZacbUzUZ6XIwXAYDVR0gBFUwUzBRBgwrBgEEAYI3TIN9AQEw
# QTA/BggrBgEFBQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9E
# b2NzL1JlcG9zaXRvcnkuaHRtMBMGA1UdJQQMMAoGCCsGAQUFBwMIMBkGCSsGAQQB
# gjcUAgQMHgoAUwB1AGIAQwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/
# MB8GA1UdIwQYMBaAFNX2VsuP6KJcYmjRPZSQW9fOmhjEMFYGA1UdHwRPME0wS6BJ
# oEeGRWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01p
# Y1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYB
# BQUHMAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljUm9v
# Q2VyQXV0XzIwMTAtMDYtMjMuY3J0MA0GCSqGSIb3DQEBCwUAA4ICAQCdVX38Kq3h
# LB9nATEkW+Geckv8qW/qXBS2Pk5HZHixBpOXPTEztTnXwnE2P9pkbHzQdTltuw8x
# 5MKP+2zRoZQYIu7pZmc6U03dmLq2HnjYNi6cqYJWAAOwBb6J6Gngugnue99qb74p
# y27YP0h1AdkY3m2CDPVtI1TkeFN1JFe53Z/zjj3G82jfZfakVqr3lbYoVSfQJL1A
# oL8ZthISEV09J+BAljis9/kpicO8F7BUhUKz/AyeixmJ5/ALaoHCgRlCGVJ1ijbC
# HcNhcy4sa3tuPywJeBTpkbKpW99Jo3QMvOyRgNI95ko+ZjtPu4b6MhrZlvSP9pEB
# 9s7GdP32THJvEKt1MMU0sHrYUP4KWN1APMdUbZ1jdEgssU5HLcEUBHG/ZPkkvnNt
# yo4JvbMBV0lUZNlz138eW0QBjloZkWsNn6Qo3GcZKCS6OEuabvshVGtqRRFHqfG3
# rsjoiV5PndLQTHa1V1QJsWkBRH58oWFsc/4Ku+xBZj1p/cvBQUl+fpO+y/g75LcV
# v7TOPqUxUYS8vwLBgqJ7Fx0ViY1w/ue10CgaiQuPNtq6TPmb/wrpNPgkNWcr4A24
# 5oyZ1uEi6vAnQj0llOZ0dFtq0Z4+7X6gMTN9vMvpe784cETRkPHIqzqKOghif9lw
# Y1NNje6CbaUFEMFxBmoQtB1VM1izoXBm8qGCAtQwggI9AgEBMIIBAKGB2KSB1TCB
# 0jELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl
# ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMk
# TWljcm9zb2Z0IElyZWxhbmQgT3BlcmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1U
# aGFsZXMgVFNTIEVTTjpBMjQwLTRCODItMTMwRTElMCMGA1UEAxMcTWljcm9zb2Z0
# IFRpbWUtU3RhbXAgU2VydmljZaIjCgEBMAcGBSsOAwIaAxUAcGteVqFx/IbTKXHL
# euXCPRPMD7uggYMwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu
# Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv
# cmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAN
# BgkqhkiG9w0BAQUFAAIFAOcW7qowIhgPMjAyMjExMTAxMTI5NDZaGA8yMDIyMTEx
# MTExMjk0NlowdDA6BgorBgEEAYRZCgQBMSwwKjAKAgUA5xbuqgIBADAHAgEAAgIE
# qTAHAgEAAgIRVjAKAgUA5xhAKgIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEE
# AYRZCgMCoAowCAIBAAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GB
# AGsU2HTQg158bHX+QngoY7NVfCbGRaLjQOi8geKi26qQWAxll9QLFg4+epiG2nZB
# eQvhxeNmIzounhWfJ+gfhFMi8aBT5z4dLK9iBtmpG1Y14RmSS4andiUlS6bVNVNe
# WGObqHijMVeMOphiTaAfzR6zSASDaG0CfVm9bNBOnZZsMYIEDTCCBAkCAQEwgZMw
# fDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl
# ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMd
# TWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAG4CNTBuHngUUkAAQAA
# AbgwDQYJYIZIAWUDBAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRAB
# BDAvBgkqhkiG9w0BCQQxIgQg578XwPrBwneU95xu1sHFncHeCC0UPQ7QK7PvSSby
# VpwwgfoGCyqGSIb3DQEJEAIvMYHqMIHnMIHkMIG9BCAo69Y4oHA7Q4pS+Y1NsBfr
# pIYTeWsPeGTami0X0PD7HzCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQI
# EwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3Nv
# ZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBD
# QSAyMDEwAhMzAAABuAjUwbh54FFJAAEAAAG4MCIEIJVlMK4mQfdJaAZx7Unfka/I
# D4Wbw5edh/SR7TTptzRqMA0GCSqGSIb3DQEBCwUABIICAA1QlR3ywR7e+jqZ++NC
# xsIwREiwVS70CEkbH8XpPRS0mFS0SHcCfpwymGfdep3D0CWk0PIfMhXq0SD97iBI
# rOLdHglVBkMYTjGEBHyBzv/LevAZUuzoc5aqyIF4Ywa5KS4PGbMSuRK5CKAojOzH
# A/vp2/KYuADmf9kOOgOfDVicyfoqZ+3W+QaUI/k0KKV4dPLF55+y18C+Td6sR60Y
# AkcvGZObuj/OkREhdTJ1qJ2E/4RKG8gtGY1DfluLon7+UvS/ciWDWrJnHMmkxM11
# cYuRIvLArIdq/YS2bcSnY6AVO2zYjj7gCqDN9GuCurstUKC5uxVl3VNxntC0u3Le
# BoI/R5uMYlTXodW8ukLNL6zHrQ4wI4udgW77KJref+3E1PEpZBRMxwose7Vt8lDc
# sW1vdM+eZzUXRLhDR8a0Nai7+PaNoukoGf4pvwsu8Mkeji5a0hWtU9lUVRv6nzue
# 3L2olhsbiHhAET7N6Rj0kzEhbUgfVUJrGvNlWOfN7MDr+OpArGXMPLtovbKTLtXF
# v/GrJo9wQuyqUmY6KQSRDZgOw1CcoZpJcy40HG/aOlJwk03N13OZD5H3KfHwEphR
# YnbGwGq9zUId5druSr5s40Yyl3idAkqmI5SXAm9v/gRq2X9vMU0a7KqXet9wO62F
# TqxV+7Qp48Vw6hW1g+Q7oWoc
# SIG # End signature block # SIG # End signature block

View File

@ -1617,6 +1617,10 @@ do
echo " $script_name -h|-?|--help" echo " $script_name -h|-?|--help"
echo "" echo ""
echo "$script_name is a simple command line interface for obtaining dotnet cli." echo "$script_name is a simple command line interface for obtaining dotnet cli."
echo " Note that the intended use of this script is for Continuous Integration (CI) scenarios, where:"
echo " - The SDK needs to be installed without user interaction and without admin rights."
echo " - The SDK installation doesn't need to persist across multiple CI runs."
echo " To set up a development environment or to run apps, use installers rather than this script. Visit https://dotnet.microsoft.com/download to get the installer."
echo "" echo ""
echo "Options:" echo "Options:"
echo " -c,--channel <CHANNEL> Download from the channel specified, Defaults to \`$channel\`." echo " -c,--channel <CHANNEL> Download from the channel specified, Defaults to \`$channel\`."
@ -1694,10 +1698,10 @@ do
shift shift
done done
say "Note that the intended use of this script is for Continuous Integration (CI) scenarios, where:" say_verbose "Note that the intended use of this script is for Continuous Integration (CI) scenarios, where:"
say "- The SDK needs to be installed without user interaction and without admin rights." say_verbose "- The SDK needs to be installed without user interaction and without admin rights."
say "- The SDK installation doesn't need to persist across multiple CI runs." say_verbose "- The SDK installation doesn't need to persist across multiple CI runs."
say "To set up a development environment or to run apps, use installers rather than this script. Visit https://dotnet.microsoft.com/download to get the installer.\n" say_verbose "To set up a development environment or to run apps, use installers rather than this script. Visit https://dotnet.microsoft.com/download to get the installer.\n"
if [ "$internal" = true ] && [ -z "$(echo $feed_credential)" ]; then if [ "$internal" = true ] && [ -z "$(echo $feed_credential)" ]; then
message="Provide credentials via --feed-credential parameter." message="Provide credentials via --feed-credential parameter."
@ -1731,4 +1735,4 @@ fi
say "Note that the script does not resolve dependencies during installation." say "Note that the script does not resolve dependencies during installation."
say "To check the list of dependencies, go to https://learn.microsoft.com/dotnet/core/install, select your operating system and check the \"Dependencies\" section." say "To check the list of dependencies, go to https://learn.microsoft.com/dotnet/core/install, select your operating system and check the \"Dependencies\" section."
say "Installation finished successfully." say "Installation finished successfully."

2157
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -6,9 +6,10 @@
"main": "lib/setup-dotnet.js", "main": "lib/setup-dotnet.js",
"scripts": { "scripts": {
"build": "tsc && ncc build", "build": "tsc && ncc build",
"format": "prettier --write **/*.ts", "format": "prettier --no-error-on-unmatched-pattern --config ./.prettierrc.js --write \"**/*.{ts,yml,yaml}\"",
"format-check": "prettier --check **/*.ts", "format-check": "prettier --no-error-on-unmatched-pattern --config ./.prettierrc.js --check \"**/*.{ts,yml,yaml}\"",
"lint": "echo \"Fake command that does nothing. It is used in reusable workflows\"", "lint": "eslint --config ./.eslintrc.js \"**/*.ts\"",
"lint:fix": "eslint --config ./.eslintrc.js \"**/*.ts\" --fix",
"prepare": "husky install", "prepare": "husky install",
"test": "jest --coverage --config ./jest.config.js", "test": "jest --coverage --config ./jest.config.js",
"update-installers": "nwget https://dot.net/v1/dotnet-install.ps1 -O externals/install-dotnet.ps1 && nwget https://dot.net/v1/dotnet-install.sh -O externals/install-dotnet.sh" "update-installers": "nwget https://dot.net/v1/dotnet-install.ps1 -O externals/install-dotnet.ps1 && nwget https://dot.net/v1/dotnet-install.sh -O externals/install-dotnet.sh"
@ -37,11 +38,16 @@
"@types/jest": "^27.0.2", "@types/jest": "^27.0.2",
"@types/node": "^16.11.25", "@types/node": "^16.11.25",
"@types/semver": "^6.2.2", "@types/semver": "^6.2.2",
"@typescript-eslint/eslint-plugin": "^5.54.0",
"@typescript-eslint/parser": "^5.54.0",
"@vercel/ncc": "^0.33.4", "@vercel/ncc": "^0.33.4",
"eslint": "^8.35.0",
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-jest": "^27.2.1",
"husky": "^8.0.1", "husky": "^8.0.1",
"jest": "^27.2.5", "jest": "^27.2.5",
"jest-circus": "^27.2.5", "jest-circus": "^27.2.5",
"prettier": "^2.7.1", "prettier": "^2.8.4",
"ts-jest": "^27.0.5", "ts-jest": "^27.0.5",
"typescript": "^4.8.4", "typescript": "^4.8.4",
"wget-improved": "^3.2.1" "wget-improved": "^3.2.1"

View File

@ -1,196 +1,196 @@
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import * as core from '@actions/core'; import * as core from '@actions/core';
import * as github from '@actions/github'; import * as github from '@actions/github';
import {XMLParser, XMLBuilder} from 'fast-xml-parser'; import {XMLParser, XMLBuilder} from 'fast-xml-parser';
export function configAuthentication( export function configAuthentication(
feedUrl: string, feedUrl: string,
existingFileLocation: string = '', existingFileLocation = '',
processRoot: string = process.cwd() processRoot: string = process.cwd()
) { ) {
const existingNuGetConfig: string = path.resolve( const existingNuGetConfig: string = path.resolve(
processRoot, processRoot,
existingFileLocation === '' existingFileLocation === ''
? getExistingNugetConfig(processRoot) ? getExistingNugetConfig(processRoot)
: existingFileLocation : existingFileLocation
); );
const tempNuGetConfig: string = path.resolve( const tempNuGetConfig: string = path.resolve(
processRoot, processRoot,
'../', '../',
'nuget.config' 'nuget.config'
); );
writeFeedToFile(feedUrl, existingNuGetConfig, tempNuGetConfig); writeFeedToFile(feedUrl, existingNuGetConfig, tempNuGetConfig);
} }
function isValidKey(key: string): boolean { function isValidKey(key: string): boolean {
return /^[\w\-\.]+$/i.test(key); return /^[\w\-.]+$/i.test(key);
} }
function getExistingNugetConfig(processRoot: string) { function getExistingNugetConfig(processRoot: string) {
const defaultConfigName = 'nuget.config'; const defaultConfigName = 'nuget.config';
const configFileNames = fs const configFileNames = fs
.readdirSync(processRoot) .readdirSync(processRoot)
.filter(filename => filename.toLowerCase() === defaultConfigName); .filter(filename => filename.toLowerCase() === defaultConfigName);
if (configFileNames.length) { if (configFileNames.length) {
return configFileNames[0]; return configFileNames[0];
} }
return defaultConfigName; return defaultConfigName;
} }
function writeFeedToFile( function writeFeedToFile(
feedUrl: string, feedUrl: string,
existingFileLocation: string, existingFileLocation: string,
tempFileLocation: string tempFileLocation: string
) { ) {
core.info( core.info(
`dotnet-auth: Finding any source references in ${existingFileLocation}, writing a new temporary configuration file with credentials to ${tempFileLocation}` `dotnet-auth: Finding any source references in ${existingFileLocation}, writing a new temporary configuration file with credentials to ${tempFileLocation}`
); );
let sourceKeys: string[] = []; const sourceKeys: string[] = [];
let owner: string = core.getInput('owner'); let owner: string = core.getInput('owner');
let sourceUrl: string = feedUrl; const sourceUrl: string = feedUrl;
if (!owner) { if (!owner) {
owner = github.context.repo.owner; owner = github.context.repo.owner;
} }
if (!process.env.NUGET_AUTH_TOKEN) { if (!process.env.NUGET_AUTH_TOKEN) {
throw new Error( throw new Error(
'The NUGET_AUTH_TOKEN environment variable was not provided. In this step, add the following: \r\nenv:\r\n NUGET_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}}' 'The NUGET_AUTH_TOKEN environment variable was not provided. In this step, add the following: \r\nenv:\r\n NUGET_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}}'
); );
} }
if (fs.existsSync(existingFileLocation)) { if (fs.existsSync(existingFileLocation)) {
// get key from existing NuGet.config so NuGet/dotnet can match credentials // get key from existing NuGet.config so NuGet/dotnet can match credentials
const curContents: string = fs.readFileSync(existingFileLocation, 'utf8'); const curContents: string = fs.readFileSync(existingFileLocation, 'utf8');
const parserOptions = { const parserOptions = {
ignoreAttributes: false ignoreAttributes: false
}; };
const parser = new XMLParser(parserOptions); const parser = new XMLParser(parserOptions);
const json = parser.parse(curContents); const json = parser.parse(curContents);
if (typeof json.configuration === 'undefined') { if (typeof json.configuration === 'undefined') {
throw new Error(`The provided NuGet.config seems invalid.`); throw new Error(`The provided NuGet.config seems invalid.`);
} }
if (json.configuration?.packageSources?.add) { if (json.configuration?.packageSources?.add) {
const packageSources = json.configuration.packageSources.add; const packageSources = json.configuration.packageSources.add;
if (Array.isArray(packageSources)) { if (Array.isArray(packageSources)) {
packageSources.forEach(source => { packageSources.forEach(source => {
const value = source['@_value']; const value = source['@_value'];
core.debug(`source '${value}'`); core.debug(`source '${value}'`);
if (value.toLowerCase().includes(feedUrl.toLowerCase())) { if (value.toLowerCase().includes(feedUrl.toLowerCase())) {
const key = source['@_key']; const key = source['@_key'];
sourceKeys.push(key); sourceKeys.push(key);
core.debug(`Found a URL with key ${key}`); core.debug(`Found a URL with key ${key}`);
} }
}); });
} else { } else {
if ( if (
packageSources['@_value'] packageSources['@_value']
.toLowerCase() .toLowerCase()
.includes(feedUrl.toLowerCase()) .includes(feedUrl.toLowerCase())
) { ) {
const key = packageSources['@_key']; const key = packageSources['@_key'];
sourceKeys.push(key); sourceKeys.push(key);
core.debug(`Found a URL with key ${key}`); core.debug(`Found a URL with key ${key}`);
} }
} }
} }
} }
const xmlSource: any[] = [ const xmlSource: any[] = [
{ {
'?xml': [ '?xml': [
{ {
'#text': '' '#text': ''
} }
], ],
':@': { ':@': {
'@_version': '1.0' '@_version': '1.0'
} }
}, },
{ {
configuration: [ configuration: [
{ {
config: [ config: [
{ {
add: [], add: [],
':@': { ':@': {
'@_key': 'defaultPushSource', '@_key': 'defaultPushSource',
'@_value': sourceUrl '@_value': sourceUrl
} }
} }
] ]
} }
] ]
} }
]; ];
if (!sourceKeys.length) { if (!sourceKeys.length) {
let keystring = 'Source'; const keystring = 'Source';
xmlSource[1].configuration.push({ xmlSource[1].configuration.push({
packageSources: [ packageSources: [
{ {
add: [], add: [],
':@': { ':@': {
'@_key': keystring, '@_key': keystring,
'@_value': sourceUrl '@_value': sourceUrl
} }
} }
] ]
}); });
sourceKeys.push(keystring); sourceKeys.push(keystring);
} }
const packageSourceCredentials: any[] = []; const packageSourceCredentials: any[] = [];
sourceKeys.forEach(key => { sourceKeys.forEach(key => {
if (!isValidKey(key)) { if (!isValidKey(key)) {
throw new Error( throw new Error(
"Source name can contain letters, numbers, and '-', '_', '.' symbols only. Please, fix source name in NuGet.config and try again." "Source name can contain letters, numbers, and '-', '_', '.' symbols only. Please, fix source name in NuGet.config and try again."
); );
} }
packageSourceCredentials.push({ packageSourceCredentials.push({
[key]: [ [key]: [
{ {
add: [], add: [],
':@': { ':@': {
'@_key': 'Username', '@_key': 'Username',
'@_value': owner '@_value': owner
} }
}, },
{ {
add: [], add: [],
':@': { ':@': {
'@_key': 'ClearTextPassword', '@_key': 'ClearTextPassword',
'@_value': process.env.NUGET_AUTH_TOKEN '@_value': process.env.NUGET_AUTH_TOKEN
} }
} }
] ]
}); });
}); });
xmlSource[1].configuration.push({ xmlSource[1].configuration.push({
packageSourceCredentials packageSourceCredentials
}); });
const xmlBuilderOptions = { const xmlBuilderOptions = {
format: true, format: true,
ignoreAttributes: false, ignoreAttributes: false,
preserveOrder: true, preserveOrder: true,
allowBooleanAttributes: true, allowBooleanAttributes: true,
suppressBooleanAttributes: true, suppressBooleanAttributes: true,
suppressEmptyNode: true suppressEmptyNode: true
}; };
const builder = new XMLBuilder(xmlBuilderOptions); const builder = new XMLBuilder(xmlBuilderOptions);
const output = builder.build(xmlSource).trim(); const output = builder.build(xmlSource).trim();
fs.writeFileSync(tempFileLocation, output); fs.writeFileSync(tempFileLocation, output);
} }

View File

@ -1,263 +1,278 @@
// Load tempDirectory before it gets wiped by tool-cache // Load tempDirectory before it gets wiped by tool-cache
import * as core from '@actions/core'; import * as core from '@actions/core';
import * as exec from '@actions/exec'; import * as exec from '@actions/exec';
import * as io from '@actions/io'; import * as io from '@actions/io';
import * as hc from '@actions/http-client'; import * as hc from '@actions/http-client';
import {chmodSync} from 'fs'; import {chmodSync} from 'fs';
import {readdir} from 'fs/promises'; import path from 'path';
import path from 'path'; import os from 'os';
import os from 'os'; import semver from 'semver';
import semver from 'semver'; import {IS_LINUX, IS_WINDOWS} from './utils';
import {IS_LINUX, IS_WINDOWS} from './utils'; import {QualityOptions} from './setup-dotnet';
import {QualityOptions} from './setup-dotnet';
export interface DotnetVersion {
export interface DotnetVersion { type: string;
type: string; value: string;
value: string; qualityFlag: boolean;
qualityFlag: boolean; }
}
const QUALITY_INPUT_MINIMAL_MAJOR_TAG = 6;
export class DotnetVersionResolver { const LATEST_PATCH_SYNTAX_MINIMAL_MAJOR_TAG = 5;
private inputVersion: string; export class DotnetVersionResolver {
private resolvedArgument: DotnetVersion; private inputVersion: string;
private resolvedArgument: DotnetVersion;
constructor(version: string) {
this.inputVersion = version.trim(); constructor(version: string) {
this.resolvedArgument = {type: '', value: '', qualityFlag: false}; this.inputVersion = version.trim();
} this.resolvedArgument = {type: '', value: '', qualityFlag: false};
}
private async resolveVersionInput(): Promise<void> {
if (!semver.validRange(this.inputVersion)) { private async resolveVersionInput(): Promise<void> {
throw new Error( if (!semver.validRange(this.inputVersion) && !this.isLatestPatchSyntax()) {
`'dotnet-version' was supplied in invalid format: ${this.inputVersion}! Supported syntax: A.B.C, A.B, A.B.x, A, A.x` 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.valid(this.inputVersion)) { }
this.resolvedArgument.type = 'version'; if (semver.valid(this.inputVersion)) {
this.resolvedArgument.value = this.inputVersion; this.createVersionArgument();
} else { } else {
const [major, minor] = this.inputVersion.split('.'); await this.createChannelArgument();
}
if (this.isNumericTag(major)) { }
this.resolvedArgument.type = 'channel';
if (this.isNumericTag(minor)) { private isNumericTag(versionTag): boolean {
this.resolvedArgument.value = `${major}.${minor}`; return /^\d+$/.test(versionTag);
} else { }
const httpClient = new hc.HttpClient('actions/setup-dotnet', [], {
allowRetries: true, private isLatestPatchSyntax() {
maxRetries: 3 const majorTag = this.inputVersion.match(
}); /^(?<majorTag>\d+)\.\d+\.\d{1}x{2}$/
this.resolvedArgument.value = await this.getLatestVersion( )?.groups?.majorTag;
httpClient, if (
[major, minor] majorTag &&
); parseInt(majorTag) < LATEST_PATCH_SYNTAX_MINIMAL_MAJOR_TAG
} ) {
} throw new Error(
this.resolvedArgument.qualityFlag = +major >= 6 ? true : false; `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 isNumericTag(versionTag): boolean { }
return /^\d+$/.test(versionTag);
} private createVersionArgument() {
this.resolvedArgument.type = 'version';
public async createDotNetVersion(): Promise<{ this.resolvedArgument.value = this.inputVersion;
type: string; }
value: string;
qualityFlag: boolean; private async createChannelArgument() {
}> { this.resolvedArgument.type = 'channel';
await this.resolveVersionInput(); const [major, minor] = this.inputVersion.split('.');
if (!this.resolvedArgument.type) { if (this.isLatestPatchSyntax()) {
return this.resolvedArgument; this.resolvedArgument.value = this.inputVersion;
} } else if (this.isNumericTag(major) && this.isNumericTag(minor)) {
if (IS_WINDOWS) { this.resolvedArgument.value = `${major}.${minor}`;
this.resolvedArgument.type = } else if (this.isNumericTag(major)) {
this.resolvedArgument.type === 'channel' ? '-Channel' : '-Version'; this.resolvedArgument.value = await this.getLatestByMajorTag(major);
} else { } else {
this.resolvedArgument.type = // 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.type === 'channel' ? '--channel' : '--version'; this.resolvedArgument.value = 'LTS';
} }
return this.resolvedArgument; this.resolvedArgument.qualityFlag =
} parseInt(major) >= QUALITY_INPUT_MINIMAL_MAJOR_TAG ? true : false;
}
private async getLatestVersion(
httpClient: hc.HttpClient, public async createDotNetVersion(): Promise<DotnetVersion> {
versionParts: string[] await this.resolveVersionInput();
): Promise<string> { if (!this.resolvedArgument.type) {
const response = await httpClient.getJson<any>( return this.resolvedArgument;
DotnetVersionResolver.DotNetCoreIndexUrl }
); if (IS_WINDOWS) {
const result = response.result || {}; this.resolvedArgument.type =
let releasesInfo: any[] = result['releases-index']; this.resolvedArgument.type === 'channel' ? '-Channel' : '-Version';
} else {
let releaseInfo = releasesInfo.find(info => { this.resolvedArgument.type =
let sdkParts: string[] = info['channel-version'].split('.'); this.resolvedArgument.type === 'channel' ? '--channel' : '--version';
return sdkParts[0] === versionParts[0]; }
}); return this.resolvedArgument;
}
if (!releaseInfo) {
throw new Error( private async getLatestByMajorTag(majorTag: string): Promise<string> {
`Could not find info for version ${versionParts.join('.')} at ${ const httpClient = new hc.HttpClient('actions/setup-dotnet', [], {
DotnetVersionResolver.DotNetCoreIndexUrl allowRetries: true,
}` maxRetries: 3
); });
} const response = await httpClient.getJson<any>(
DotnetVersionResolver.DotNetCoreIndexUrl
return releaseInfo['channel-version']; );
} const result = response.result || {};
const releasesInfo: any[] = result['releases-index'];
static DotNetCoreIndexUrl: string =
'https://dotnetcli.azureedge.net/dotnet/release-metadata/releases-index.json'; const releaseInfo = releasesInfo.find(info => {
} const sdkParts: string[] = info['channel-version'].split('.');
return sdkParts[0] === majorTag;
export class DotnetCoreInstaller { });
private version: string;
private quality: QualityOptions; if (!releaseInfo) {
throw new Error(
static { `Could not find info for version with major tag: "${majorTag}" at ${DotnetVersionResolver.DotNetCoreIndexUrl}`
const installationDirectoryWindows = path.join( );
process.env['PROGRAMFILES'] + '', }
'dotnet'
); return releaseInfo['channel-version'];
const installationDirectoryLinux = '/usr/share/dotnet'; }
const installationDirectoryMac = path.join(
process.env['HOME'] + '', static DotNetCoreIndexUrl =
'.dotnet' 'https://dotnetcli.azureedge.net/dotnet/release-metadata/releases-index.json';
); }
const dotnetInstallDir: string | undefined =
process.env['DOTNET_INSTALL_DIR']; export class DotnetCoreInstaller {
if (dotnetInstallDir) { private version: string;
process.env['DOTNET_INSTALL_DIR'] = private quality: QualityOptions;
this.convertInstallPathToAbsolute(dotnetInstallDir);
} else { static {
if (IS_WINDOWS) { const installationDirectoryWindows = path.join(
process.env['DOTNET_INSTALL_DIR'] = installationDirectoryWindows; process.env['PROGRAMFILES'] + '',
} else { 'dotnet'
process.env['DOTNET_INSTALL_DIR'] = IS_LINUX );
? installationDirectoryLinux const installationDirectoryLinux = '/usr/share/dotnet';
: installationDirectoryMac; const installationDirectoryMac = path.join(
} process.env['HOME'] + '',
} '.dotnet'
} );
const dotnetInstallDir: string | undefined =
constructor(version: string, quality: QualityOptions) { process.env['DOTNET_INSTALL_DIR'];
this.version = version; if (dotnetInstallDir) {
this.quality = quality; process.env['DOTNET_INSTALL_DIR'] =
} this.convertInstallPathToAbsolute(dotnetInstallDir);
} else {
private static convertInstallPathToAbsolute(installDir: string): string { if (IS_WINDOWS) {
let transformedPath; process.env['DOTNET_INSTALL_DIR'] = installationDirectoryWindows;
if (path.isAbsolute(installDir)) { } else {
transformedPath = installDir; process.env['DOTNET_INSTALL_DIR'] = IS_LINUX
} else { ? installationDirectoryLinux
transformedPath = installDir.startsWith('~') : installationDirectoryMac;
? path.join(os.homedir(), installDir.slice(1)) }
: (transformedPath = path.join(process.cwd(), installDir)); }
} }
return path.normalize(transformedPath);
} constructor(version: string, quality: QualityOptions) {
this.version = version;
static addToPath() { this.quality = quality;
core.addPath(process.env['DOTNET_INSTALL_DIR']!); }
core.exportVariable('DOTNET_ROOT', process.env['DOTNET_INSTALL_DIR']);
} private static convertInstallPathToAbsolute(installDir: string): string {
let transformedPath;
private setQuality( if (path.isAbsolute(installDir)) {
dotnetVersion: DotnetVersion, transformedPath = installDir;
scriptArguments: string[] } else {
): void { transformedPath = installDir.startsWith('~')
const option = IS_WINDOWS ? '-Quality' : '--quality'; ? path.join(os.homedir(), installDir.slice(1))
if (dotnetVersion.qualityFlag) { : (transformedPath = path.join(process.cwd(), installDir));
scriptArguments.push(option, this.quality); }
} else { return path.normalize(transformedPath);
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.`
); static addToPath() {
} core.addPath(process.env['DOTNET_INSTALL_DIR']!);
} core.exportVariable('DOTNET_ROOT', process.env['DOTNET_INSTALL_DIR']);
}
public async installDotnet(): Promise<string> {
const windowsDefaultOptions = [ private setQuality(
'-NoLogo', dotnetVersion: DotnetVersion,
'-Sta', scriptArguments: string[]
'-NoProfile', ): void {
'-NonInteractive', const option = IS_WINDOWS ? '-Quality' : '--quality';
'-ExecutionPolicy', if (dotnetVersion.qualityFlag) {
'Unrestricted', scriptArguments.push(option, this.quality);
'-Command' } else {
]; core.warning(
const scriptName = IS_WINDOWS ? 'install-dotnet.ps1' : 'install-dotnet.sh'; `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.`
const escapedScript = path );
.join(__dirname, '..', 'externals', scriptName) }
.replace(/'/g, "''"); }
let scriptArguments: string[];
let scriptPath = ''; public async installDotnet(): Promise<string | null> {
const windowsDefaultOptions = [
const versionResolver = new DotnetVersionResolver(this.version); '-NoLogo',
const dotnetVersion = await versionResolver.createDotNetVersion(); '-Sta',
'-NoProfile',
if (IS_WINDOWS) { '-NonInteractive',
scriptArguments = ['&', `'${escapedScript}'`]; '-ExecutionPolicy',
'Unrestricted',
if (dotnetVersion.type) { '-Command'
scriptArguments.push(dotnetVersion.type, dotnetVersion.value); ];
} const scriptName = IS_WINDOWS ? 'install-dotnet.ps1' : 'install-dotnet.sh';
const escapedScript = path
if (this.quality) { .join(__dirname, '..', 'externals', scriptName)
this.setQuality(dotnetVersion, scriptArguments); .replace(/'/g, "''");
} let scriptArguments: string[];
let scriptPath = '';
if (process.env['https_proxy'] != null) {
scriptArguments.push(`-ProxyAddress ${process.env['https_proxy']}`); const versionResolver = new DotnetVersionResolver(this.version);
} const dotnetVersion = await versionResolver.createDotNetVersion();
// This is not currently an option
if (process.env['no_proxy'] != null) { if (IS_WINDOWS) {
scriptArguments.push(`-ProxyBypassList ${process.env['no_proxy']}`); scriptArguments = ['&', `'${escapedScript}'`];
}
if (dotnetVersion.type) {
scriptPath = scriptArguments.push(dotnetVersion.type, dotnetVersion.value);
(await io.which('pwsh', false)) || (await io.which('powershell', true)); }
scriptArguments = windowsDefaultOptions.concat(scriptArguments);
} else { if (this.quality) {
chmodSync(escapedScript, '777'); this.setQuality(dotnetVersion, scriptArguments);
scriptPath = await io.which(escapedScript, true); }
scriptArguments = [];
if (process.env['https_proxy'] != null) {
if (dotnetVersion.type) { scriptArguments.push(`-ProxyAddress ${process.env['https_proxy']}`);
scriptArguments.push(dotnetVersion.type, dotnetVersion.value); }
} // This is not currently an option
if (process.env['no_proxy'] != null) {
if (this.quality) { scriptArguments.push(`-ProxyBypassList ${process.env['no_proxy']}`);
this.setQuality(dotnetVersion, scriptArguments); }
}
} scriptPath =
// process.env must be explicitly passed in for DOTNET_INSTALL_DIR to be used (await io.which('pwsh', false)) || (await io.which('powershell', true));
const getExecOutputOptions = { scriptArguments = windowsDefaultOptions.concat(scriptArguments);
ignoreReturnCode: true, } else {
env: process.env as {string: string} chmodSync(escapedScript, '777');
}; scriptPath = await io.which(escapedScript, true);
const {exitCode, stdout} = await exec.getExecOutput( scriptArguments = [];
`"${scriptPath}"`,
scriptArguments, if (dotnetVersion.type) {
getExecOutputOptions scriptArguments.push(dotnetVersion.type, dotnetVersion.value);
); }
if (exitCode) {
throw new Error(`Failed to install dotnet ${exitCode}. ${stdout}`); if (this.quality) {
} this.setQuality(dotnetVersion, scriptArguments);
}
return this.outputDotnetVersion(dotnetVersion.value); }
} // process.env must be explicitly passed in for DOTNET_INSTALL_DIR to be used
const getExecOutputOptions = {
private async outputDotnetVersion(version): Promise<string> { ignoreReturnCode: true,
const installationPath = process.env['DOTNET_INSTALL_DIR']!; env: process.env as {string: string}
let versionsOnRunner: string[] = await readdir( };
path.join(installationPath.replace(/'/g, ''), 'sdk') const {exitCode, stdout, stderr} = await exec.getExecOutput(
); `"${scriptPath}"`,
scriptArguments,
let installedVersion = semver.maxSatisfying(versionsOnRunner, version, { getExecOutputOptions
includePrerelease: true );
})!; if (exitCode) {
throw new Error(
return installedVersion; `Failed to install dotnet, exit code: ${exitCode}. ${stderr}`
} );
} }
return this.parseInstalledVersion(stdout);
}
private parseInstalledVersion(stdout: string): string | null {
const regex = /(?<version>\d+\.\d+\.\d+[a-z0-9._-]*)/gm;
const matchedResult = regex.exec(stdout);
if (!matchedResult) {
core.warning(`Failed to parse installed by the script version of .NET`);
return null;
}
return matchedResult.groups!.version;
}
}

View File

@ -13,7 +13,7 @@ const qualityOptions = [
'ga' 'ga'
] as const; ] as const;
export type QualityOptions = typeof qualityOptions[number]; export type QualityOptions = (typeof qualityOptions)[number];
export async function run() { export async function run() {
try { try {
@ -27,11 +27,11 @@ export async function run() {
// Proxy, auth, (etc) are still set up, even if no version is identified // Proxy, auth, (etc) are still set up, even if no version is identified
// //
const versions = core.getMultilineInput('dotnet-version'); const versions = core.getMultilineInput('dotnet-version');
const installedDotnetVersions: string[] = []; const installedDotnetVersions: (string | null)[] = [];
const globalJsonFileInput = core.getInput('global-json-file'); const globalJsonFileInput = core.getInput('global-json-file');
if (globalJsonFileInput) { if (globalJsonFileInput) {
const globalJsonPath = path.join(process.cwd(), globalJsonFileInput); const globalJsonPath = path.resolve(process.cwd(), globalJsonFileInput);
if (!fs.existsSync(globalJsonPath)) { if (!fs.existsSync(globalJsonPath)) {
throw new Error( throw new Error(
`The specified global.json file '${globalJsonFileInput}' does not exist` `The specified global.json file '${globalJsonFileInput}' does not exist`
@ -48,7 +48,7 @@ export async function run() {
versions.push(getVersionFromGlobalJson(globalJsonPath)); versions.push(getVersionFromGlobalJson(globalJsonPath));
} else { } else {
core.info( 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.`
); );
} }
} }
@ -58,7 +58,7 @@ export async function run() {
if (quality && !qualityOptions.includes(quality)) { if (quality && !qualityOptions.includes(quality)) {
throw new Error( 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.`
); );
} }
@ -78,19 +78,7 @@ export async function run() {
auth.configAuthentication(sourceUrl, configFile); auth.configAuthentication(sourceUrl, configFile);
} }
const comparisonRange: string = globalJsonFileInput outputInstalledVersion(installedDotnetVersions, globalJsonFileInput);
? versions[versions.length - 1]!
: '*';
const versionToOutput = semver.maxSatisfying(
installedDotnetVersions,
comparisonRange,
{
includePrerelease: true
}
);
core.setOutput('dotnet-version', versionToOutput);
const matchersPath = path.join(__dirname, '..', '.github'); const matchersPath = path.join(__dirname, '..', '.github');
core.info(`##[add-matcher]${path.join(matchersPath, 'csc.json')}`); core.info(`##[add-matcher]${path.join(matchersPath, 'csc.json')}`);
@ -100,7 +88,7 @@ export async function run() {
} }
function getVersionFromGlobalJson(globalJsonPath: string): string { function getVersionFromGlobalJson(globalJsonPath: string): string {
let version: string = ''; let version = '';
const globalJson = JSON.parse( const globalJson = JSON.parse(
// .trim() is necessary to strip BOM https://github.com/nodejs/node/issues/20649 // .trim() is necessary to strip BOM https://github.com/nodejs/node/issues/20649
fs.readFileSync(globalJsonPath, {encoding: 'utf8'}).trim() fs.readFileSync(globalJsonPath, {encoding: 'utf8'}).trim()
@ -116,4 +104,37 @@ function getVersionFromGlobalJson(globalJsonPath: string): string {
return version; return version;
} }
function outputInstalledVersion(
installedVersions: (string | null)[],
globalJsonFileInput: string
): void {
if (!installedVersions.length) {
core.info(`The 'dotnet-version' output will not be set.`);
return;
}
if (installedVersions.includes(null)) {
core.warning(
`Failed to output the installed version of .NET. The 'dotnet-version' 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('dotnet-version', versionToOutput);
return;
}
const versionToOutput = semver.maxSatisfying(
installedVersions as string[],
'*',
{
includePrerelease: true
}
);
core.setOutput('dotnet-version', versionToOutput);
}
run(); run();

View File

@ -49,7 +49,8 @@
// "typeRoots": [], /* List of folders to include type definitions from. */ // "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */ // "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
"resolveJsonModule": true, /* Allows importing modules with a '.json' extension, which is a common practice in node projects. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */