Compare commits

..

15 Commits

Author SHA1 Message Date
Philip Gai
a98b56852c chore(deps): bump @actions/cache to 6.2.0 (#756)
* chore(deps): bump @actions/cache to 5.2.0

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a4005a74-ab24-4f47-a559-e9caf4870437

* chore(deps): update licensed record for @actions/cache 5.2.0

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a4005a74-ab24-4f47-a559-e9caf4870437

* chore(deps): bump @actions/cache to 6.2.0

Bump @actions/cache from ^6.1.0 to ^6.2.0, refresh the lockfile,
rebuild the dist bundle, and update the license record.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 0025a82b-5990-4c08-851c-4570aa7f7bbf

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-07-15 21:52:42 -05:00
dependabot[bot]
afb2931642 Bump actions/checkout from 6.0.3 to 7.0.0 (#751)
* Bump actions/checkout from 6.0.3 to 7.0.0

Bumps [actions/checkout](https://github.com/actions/checkout) from 6.0.3 to 7.0.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](df4cb1c069...9c091bb21b)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 7.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

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

* Update actions/checkout to v7 and actions/setup-dotnet to v6 in README examples

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: priya-kinthali <priya-kinthali@github.com>
2026-07-15 21:45:27 -05:00
Priya Gupta
6df8cefd14 Migrate to ESM and upgrade dependencies (#752)
* Migrate to ESM and upgrade dependencies

* Address review: use type-only import for QualityOptions

* update version to 6.0.0 in package.json and package-lock.json

* Add ESM migration note to README for V6

* Update test imports for ESM and clean up devDependencies (ts-node, @types/jest)

* Pin @types/node to 24.x and refine tsconfig/README wording
2026-07-15 15:41:57 -05:00
Stephen Cleary
26b0ec14cb Expand the CSC problem matcher to light up more errors on GitHub. (#717)
Expand csc problem matcher with more real-world scenarios.

Co-authored-by: Stephen Cleary <900597+StephenCleary@users.noreply.github.com>
2026-06-25 14:00:19 -05:00
Kranthi Poturaju
da5e5482f2 docs(action): explicitly mark all optional inputs with required: false (#737)
Co-authored-by: Kranthi Poturaju <Kranthi.Poturaju1@aexp.com>
2026-06-23 12:45:52 -05:00
Michal Domanski
9bd3b44355 Improve readability of global.json creation command (#694)
Updated the command to create global.json for better readability and execution.
2026-06-23 12:35:56 -05:00
Jason Ginchereau
4406a635cd Bump @actions/cache to 5.1.0, log cache write denied (#746)
* Bump @actions/cache to 5.1.0, log cache write denied

* Add cache save failure test case

* Update dist

* Resolve high-severity audit issues
2026-06-22 13:33:08 -05:00
priya-kinthali
dc3262dda8 pin actions to commit SHAs in workflows (#744)
* fix basic validation with npm command

* Revert "fix basic validation with npm command"

This reverts commit 27a0803a2a.

* pin workflow actions to full commit SHAs with patch

* update cooldown

* update redundant global.json test and rollForward fields

* update cooldown for npm section
2026-06-22 11:59:12 -05:00
Priya Gupta
95a3f8b067 Validate global.json SDK version before rollForward optimization (#742) 2026-06-16 12:26:42 -05:00
Priya Gupta
9a946fdbd5 Add rollForward note in README, improve proxy health check in e2e tests and bump version to v5.3.0 (#738)
* docs: add note about latest* rollForward support in global.json

* update health check options for squid proxy in e2e tests
2026-05-27 21:45:27 -05:00
js6pak
98af08bf64 Support global.json's rollForward latest* variants (#538)
* Rename toolcache directory instead of moving to tmp

This further enhances time savings seen in #213, down from a few minutes to seconds.

* Remove branch constraint on workflows used for testing

This means you are going to be notified of CI issues quicker, especially if you have to wait for a confirmation before running the workflows on your PR.

* Support global.json's rollForward latest* variants

* Remove e2e-test's dependency on Microsoft.NET.Test.Sdk

The newer versions are net8.0/net462+ only, making older .NET sdks more annoying to test

* Revert "Remove e2e-test's dependency on Microsoft.NET.Test.Sdk"

This reverts commit 14839a662649cdea79ccecc3a7ce9a91386185b4.

* Don't run unsupported versions in global.json tests

* Revert "Remove branch constraint on workflows used for testing"

This reverts commit d9dae35030.
2026-05-26 17:43:27 -05:00
Michael Simons
8404272362 Update install scripts to v2026.05.19 (#736)
Update externals/install-dotnet.ps1 and externals/install-dotnet.sh to
the v2026.05.19 release of dotnet/install-scripts. This uptakes the
changes from dotnet/install-scripts#697 which preserves archive links
during installation (hardlinks on Windows, symlinks on Linux) as part
of dotnet/sdk#52182.

On Windows, .NET 11.0+ is now installed via tar.gz archives which
preserves hardlinks, significantly reducing disk space usage.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-26 12:22:17 -05:00
Alexander Köplinger
f1970f5ca3 Don't download releases-index.json to resolve major version (#560)
* Don't download releases-index.json to resolving major version

Starting with .NET 5 the minor version is always zero.
The earlier releases don't get new versions anymore so we can hardcode the minor version instead of downloading releases-index.json to do the lookup.

* Remove unused variable

* Apply suggestions from code review

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Commit dist/setup/index.js

* Refresh after copilot commits

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-05-11 12:11:45 -05:00
mahabaleshwars
af9211b136 Add dotnet-version: latest support with dotnet-channel input (#730)
* feat: add dotnet-version: latest keyword with dotnet-channel support (#497)

* restore test-proxy container image

* update e2e-tests.yml and documentation

* fix(tests): correct release-type and support-phase values in latest-version test mocks
2026-04-27 13:24:18 -05:00
Copilot
df991aeaf2 chore: bump @actions/* and fast-xml-parser dependencies (#728)
* chore: update dependencies and license cache

Agent-Logs-Url: https://github.com/actions/setup-dotnet/sessions/e045029b-9f7d-4035-8714-f2c0a75f22f0

* fix: revert .licensed.yml change and set http-client license to mit

Agent-Logs-Url: https://github.com/actions/setup-dotnet/sessions/df77fea2-a442-4d42-9d49-a8617aa773a8

* Revert "fix: revert .licensed.yml change and set http-client license to mit"

This reverts commit 80db01fc60.

Co-authored-by: priyagupta108 <147705955+priyagupta108@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: priyagupta108 <147705955+priyagupta108@users.noreply.github.com>
2026-04-15 12:38:47 -05:00
102 changed files with 148715 additions and 132475 deletions

View File

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

View File

@@ -1,51 +0,0 @@
// 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-node', '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}],
'node/no-extraneous-import': 'error'
},
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
}
};

11
.github/csc.json vendored
View File

@@ -4,13 +4,14 @@
"owner": "csc", "owner": "csc",
"pattern": [ "pattern": [
{ {
"regexp": "^([^\\s].*)\\((\\d+)(?:,\\d+|,\\d+,\\d+)?\\):\\s+(error|warning)\\s+([a-zA-Z]+(?<!MSB)\\d+):\\s*(.*?)\\s+\\[(.*?)\\]$", "regexp": "^\\s*(?:\\d+>\\s*)?([^\\s].*)\\((\\d+)(?:,(\\d+))?(?:,\\d+)*\\):\\s+(error|warning)\\s+([a-zA-Z]+(?<!MSB)\\d*):\\s*(.*?)\\s+\\[(.*?)\\]$",
"file": 1, "file": 1,
"line": 2, "line": 2,
"severity": 3, "column": 3,
"code": 4, "severity": 4,
"message": 5, "code": 5,
"fromPath": 6 "message": 6,
"fromPath": 7
} }
] ]
} }

View File

@@ -12,6 +12,8 @@ updates:
# Check the npm registry for updates every day (weekdays) # Check the npm registry for updates every day (weekdays)
schedule: schedule:
interval: 'weekly' interval: 'weekly'
cooldown:
default-days: 7
# Enable version updates for GitHub Actions # Enable version updates for GitHub Actions
- package-ecosystem: 'github-actions' - package-ecosystem: 'github-actions'
@@ -20,3 +22,5 @@ updates:
directory: '/' directory: '/'
schedule: schedule:
interval: 'weekly' interval: 'weekly'
cooldown:
default-days: 7

View File

@@ -27,7 +27,7 @@ jobs:
] ]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Clear toolcache - name: Clear toolcache
shell: pwsh shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }} run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
@@ -57,7 +57,7 @@ jobs:
] ]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Clear toolcache - name: Clear toolcache
shell: pwsh shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }} run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
@@ -92,7 +92,7 @@ jobs:
] ]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Clear toolcache - name: Clear toolcache
shell: pwsh shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }} run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
@@ -123,7 +123,7 @@ jobs:
] ]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Clear toolcache - name: Clear toolcache
shell: pwsh shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }} run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
@@ -149,7 +149,7 @@ jobs:
] ]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Clear toolcache - name: Clear toolcache
shell: pwsh shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }} run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
@@ -180,7 +180,7 @@ jobs:
] ]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Clear toolcache - name: Clear toolcache
shell: pwsh shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }} run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
@@ -208,7 +208,7 @@ jobs:
] ]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Clear toolcache - name: Clear toolcache
shell: pwsh shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }} run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
@@ -239,7 +239,7 @@ jobs:
] ]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Clear toolcache - name: Clear toolcache
shell: pwsh shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }} run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
@@ -247,7 +247,7 @@ jobs:
shell: bash shell: bash
run: | run: |
mkdir subdirectory mkdir subdirectory
echo '{"sdk":{"version": "9.0.308","rollForward": "latestFeature"}}' > ./subdirectory/global.json echo '{"sdk":{"version": "9.0.308"}}' > ./subdirectory/global.json
- name: Setup dotnet - name: Setup dotnet
uses: ./ uses: ./
with: with:
@@ -257,22 +257,15 @@ jobs:
shell: pwsh shell: pwsh
run: __tests__/verify-dotnet.ps1 -Patterns "^9.0", "^10.0" run: __tests__/verify-dotnet.ps1 -Patterns "^9.0", "^10.0"
test-setup-global-json-only: test-setup-global-json-rollforward-latestmajor:
runs-on: ${{ matrix.operating-system }} runs-on: ${{ matrix.operating-system }}
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
operating-system: operating-system: [ubuntu-latest, windows-latest, macos-latest]
[
ubuntu-22.04,
ubuntu-latest,
windows-latest,
macos-latest,
macos-15-intel
]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Clear toolcache - name: Clear toolcache
shell: pwsh shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }} run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
@@ -280,14 +273,64 @@ jobs:
shell: bash shell: bash
run: | run: |
mkdir subdirectory mkdir subdirectory
echo '{"sdk":{"version": "10.0.101","rollForward": "latestFeature"}}' > ./subdirectory/global.json echo '{"sdk":{"version": "8.0.100","rollForward": "latestMajor"}}' > ./subdirectory/global.json
- name: Setup dotnet - name: Setup dotnet
uses: ./ uses: ./
with: with:
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 -Patterns "^10.0" run: __tests__/verify-dotnet.ps1 -Patterns "^(?!8)"
test-setup-global-json-rollforward-latestfeature:
runs-on: ${{ matrix.operating-system }}
strategy:
fail-fast: false
matrix:
operating-system: [ubuntu-latest, windows-latest, macos-latest]
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Clear toolcache
shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
- name: Write global.json
shell: bash
run: |
mkdir subdirectory
echo '{"sdk":{"version": "10.0.100","rollForward": "latestFeature"}}' > ./subdirectory/global.json
- name: Setup dotnet
uses: ./
with:
global-json-file: ./subdirectory/global.json
- name: Verify dotnet
shell: pwsh
run: __tests__/verify-dotnet.ps1 -Patterns "^10.0.(?!1)"
test-setup-global-json-rollforward-latestpatch:
runs-on: ${{ matrix.operating-system }}
strategy:
fail-fast: false
matrix:
operating-system: [ubuntu-latest, windows-latest, macos-latest]
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Clear toolcache
shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
- name: Write global.json
shell: bash
run: |
mkdir subdirectory
echo '{"sdk":{"version": "10.0.100","rollForward": "latestPatch"}}' > ./subdirectory/global.json
- name: Setup dotnet
uses: ./
with:
global-json-file: ./subdirectory/global.json
- name: Verify dotnet
shell: pwsh
run: __tests__/verify-dotnet.ps1 -Patterns "^10.0.1(?!00)"
test-global-json-with-comments: test-global-json-with-comments:
runs-on: ${{ matrix.operating-system }} runs-on: ${{ matrix.operating-system }}
@@ -304,7 +347,7 @@ jobs:
] ]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Clear toolcache - name: Clear toolcache
shell: pwsh shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }} run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
@@ -312,7 +355,7 @@ jobs:
shell: bash shell: bash
run: | run: |
mkdir subdirectory mkdir subdirectory
echo '/* should support comments */ {"sdk":{"version": "10.0.101","rollForward": "latestFeature"}} // should support comments' > ./subdirectory/global.json echo '/* should support comments */ {"sdk":{"version": "10.0.101"}} // should support comments' > ./subdirectory/global.json
- name: Setup dotnet - name: Setup dotnet
uses: ./ uses: ./
with: with:
@@ -336,7 +379,7 @@ jobs:
] ]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Clear toolcache - name: Clear toolcache
shell: pwsh shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }} run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
@@ -367,7 +410,7 @@ jobs:
NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Clear toolcache - name: Clear toolcache
shell: pwsh shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }} run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
@@ -405,7 +448,7 @@ jobs:
NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Clear toolcache - name: Clear toolcache
shell: pwsh shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }} run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
@@ -439,7 +482,7 @@ jobs:
] ]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Clear toolcache - name: Clear toolcache
shell: pwsh shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }} run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
@@ -472,7 +515,7 @@ jobs:
] ]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Clear toolcache - name: Clear toolcache
shell: pwsh shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }} run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
@@ -502,12 +545,17 @@ jobs:
image: ubuntu/squid:latest image: ubuntu/squid:latest
ports: ports:
- 3128:3128 - 3128:3128
options: >-
--health-cmd "bash -c '</dev/tcp/localhost/3128'"
--health-interval 5s
--health-timeout 3s
--health-retries 10
env: env:
https_proxy: http://squid-proxy:3128 https_proxy: http://squid-proxy:3128
http_proxy: http://squid-proxy:3128 http_proxy: http://squid-proxy:3128
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Clear toolcache - name: Clear toolcache
shell: pwsh shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }} run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
@@ -530,7 +578,7 @@ jobs:
no_proxy: github.com,download.visualstudio.microsoft.com,api.nuget.org,builds.dotnet.microsoft.com,ci.dot.net no_proxy: github.com,download.visualstudio.microsoft.com,api.nuget.org,builds.dotnet.microsoft.com,ci.dot.net
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Clear toolcache - name: Clear toolcache
shell: pwsh shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }} run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
@@ -562,7 +610,7 @@ jobs:
higher-version: ['10.0.101'] higher-version: ['10.0.101']
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Clear toolcache - name: Clear toolcache
shell: pwsh shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }} run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
@@ -591,7 +639,7 @@ jobs:
[ubuntu-latest, windows-latest, macos-15-intel, macos-latest] [ubuntu-latest, windows-latest, macos-15-intel, macos-latest]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Clear toolcache - name: Clear toolcache
shell: pwsh shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }} run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
@@ -630,7 +678,7 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Clear toolcache - name: Clear toolcache
shell: pwsh shell: pwsh
@@ -649,3 +697,44 @@ jobs:
- name: Verify dotnet - name: Verify dotnet
shell: pwsh shell: pwsh
run: __tests__/verify-dotnet.ps1 -Patterns "^8.0.416$", "^9.0.308$", "^10.0.101$", "^8.0" run: __tests__/verify-dotnet.ps1 -Patterns "^8.0.416$", "^9.0.308$", "^10.0.101$", "^8.0"
test-setup-latest-version:
runs-on: ${{ matrix.operating-system }}
strategy:
fail-fast: false
matrix:
operating-system: [ubuntu-latest]
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Clear toolcache
shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
- name: Setup dotnet latest
uses: ./
with:
dotnet-version: latest
- name: Verify dotnet
shell: pwsh
run: __tests__/verify-dotnet.ps1 -Patterns "^\d+\.\d+\.\d+"
test-setup-latest-with-channel-abcxx:
runs-on: ${{ matrix.operating-system }}
strategy:
fail-fast: false
matrix:
operating-system: [macos-latest]
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Clear toolcache
shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
- name: Setup dotnet latest with A.B.Cxx channel
uses: ./
with:
dotnet-version: latest
dotnet-channel: '9.0.1xx'
- name: Verify dotnet
shell: pwsh
run: __tests__/verify-dotnet.ps1 -Patterns "^9\.0\.1\d{2}"

View File

@@ -14,7 +14,7 @@ jobs:
steps: steps:
- name: Checking out - name: Checking out
uses: actions/checkout@v6 uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Publish - name: Publish
id: publish id: publish
uses: actions/publish-immutable-action@v0.0.4 uses: actions/publish-immutable-action@4bc8754ffc40f27910afb20287dbbbb675a4e978 # v0.0.4

View File

@@ -23,7 +23,7 @@ jobs:
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.4.0 uses: actions/publish-action@23f4c6f12633a2da8f44938b71fde9afec138fb4 # v0.4.0
with: with:
source-tag: ${{ env.TAG_NAME }} source-tag: ${{ env.TAG_NAME }}
slack-webhook: ${{ secrets.SLACK_WEBHOOK }} slack-webhook: ${{ secrets.SLACK_WEBHOOK }}

View File

@@ -28,7 +28,7 @@ jobs:
dotnet-version: ['8.0', '9.0', '10.0'] dotnet-version: ['8.0', '9.0', '10.0']
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Clear toolcache - name: Clear toolcache
shell: pwsh shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }} run: __tests__/clear-toolcache.ps1 ${{ runner.os }}

View File

@@ -10,7 +10,11 @@ allowed:
- mit - mit
- cc0-1.0 - cc0-1.0
- unlicense - unlicense
- blueoak-1.0.0
reviewed: reviewed:
npm: npm:
- sax # ISC + MIT - sax # ISC + MIT
- "@actions/http-client" # MIT
- balanced-match # MIT
- brace-expansion # MIT

View File

@@ -1,6 +1,6 @@
--- ---
name: "@actions/cache" name: "@actions/cache"
version: 5.0.1 version: 6.2.0
type: npm type: npm
summary: Actions cache lib summary: Actions cache lib
homepage: https://github.com/actions/toolkit/tree/main/packages/cache homepage: https://github.com/actions/toolkit/tree/main/packages/cache

View File

@@ -1,20 +0,0 @@
---
name: "@actions/core"
version: 1.11.1
type: npm
summary: Actions core lib
homepage: https://github.com/actions/toolkit/tree/main/packages/core
license: mit
licenses:
- sources: LICENSE.md
text: |-
The MIT License (MIT)
Copyright 2019 GitHub
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
notices: []

View File

@@ -1,6 +1,6 @@
--- ---
name: "@actions/core" name: "@actions/core"
version: 2.0.1 version: 3.0.1
type: npm type: npm
summary: Actions core lib summary: Actions core lib
homepage: https://github.com/actions/toolkit/tree/main/packages/core homepage: https://github.com/actions/toolkit/tree/main/packages/core

View File

@@ -1,6 +1,6 @@
--- ---
name: "@actions/exec" name: "@actions/exec"
version: 1.1.1 version: 3.0.0
type: npm type: npm
summary: Actions exec lib summary: Actions exec lib
homepage: https://github.com/actions/toolkit/tree/main/packages/exec homepage: https://github.com/actions/toolkit/tree/main/packages/exec

View File

@@ -1,6 +1,6 @@
--- ---
name: "@actions/github" name: "@actions/github"
version: 6.0.0 version: 9.1.1
type: npm type: npm
summary: Actions github lib summary: Actions github lib
homepage: https://github.com/actions/toolkit/tree/main/packages/github homepage: https://github.com/actions/toolkit/tree/main/packages/github

View File

@@ -1,6 +1,6 @@
--- ---
name: "@actions/glob" name: "@actions/glob"
version: 0.5.0 version: 0.6.1
type: npm type: npm
summary: Actions glob lib summary: Actions glob lib
homepage: https://github.com/actions/toolkit/tree/main/packages/glob homepage: https://github.com/actions/toolkit/tree/main/packages/glob

View File

@@ -1,9 +1,9 @@
--- ---
name: "@actions/exec" name: "@actions/glob"
version: 2.0.0 version: 0.7.0
type: npm type: npm
summary: Actions exec lib summary: Actions glob lib
homepage: https://github.com/actions/toolkit/tree/main/packages/exec homepage: https://github.com/actions/toolkit/tree/main/packages/glob
license: mit license: mit
licenses: licenses:
- sources: LICENSE.md - sources: LICENSE.md

View File

@@ -1,10 +1,10 @@
--- ---
name: "@actions/http-client" name: "@actions/http-client"
version: 2.2.3 version: 3.0.2
type: npm type: npm
summary: Actions Http Client summary: Actions Http Client
homepage: https://github.com/actions/toolkit/tree/main/packages/http-client homepage: https://github.com/actions/toolkit/tree/main/packages/http-client
license: mit license: other
licenses: licenses:
- sources: LICENSE - sources: LICENSE
text: | text: |

View File

@@ -1,10 +1,10 @@
--- ---
name: "@actions/http-client" name: "@actions/http-client"
version: 3.0.0 version: 4.0.1
type: npm type: npm
summary: Actions Http Client summary: Actions Http Client
homepage: https://github.com/actions/toolkit/tree/main/packages/http-client homepage: https://github.com/actions/toolkit/tree/main/packages/http-client
license: mit license: other
licenses: licenses:
- sources: LICENSE - sources: LICENSE
text: | text: |

View File

@@ -1,20 +0,0 @@
---
name: "@actions/io"
version: 2.0.0
type: npm
summary: Actions io lib
homepage: https://github.com/actions/toolkit/tree/main/packages/io
license: mit
licenses:
- sources: LICENSE.md
text: |-
The MIT License (MIT)
Copyright 2019 GitHub
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
notices: []

View File

@@ -1,6 +1,6 @@
--- ---
name: "@actions/io" name: "@actions/io"
version: 1.1.3 version: 3.0.2
type: npm type: npm
summary: Actions io lib summary: Actions io lib
homepage: https://github.com/actions/toolkit/tree/main/packages/io homepage: https://github.com/actions/toolkit/tree/main/packages/io

View File

@@ -1,6 +1,6 @@
--- ---
name: "@azure/core-client" name: "@azure/core-client"
version: 1.10.1 version: 1.10.2
type: npm type: npm
summary: Core library for interfacing with AutoRest generated code summary: Core library for interfacing with AutoRest generated code
homepage: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/core/core-client/ homepage: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/core/core-client/

View File

@@ -1,6 +1,6 @@
--- ---
name: "@azure/core-http-compat" name: "@azure/core-http-compat"
version: 2.3.1 version: 2.4.0
type: npm type: npm
summary: Core HTTP Compatibility Library to bridge the gap between Core V1 & V2 packages. summary: Core HTTP Compatibility Library to bridge the gap between Core V1 & V2 packages.
homepage: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/core/core-compat/ homepage: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/core/core-compat/

View File

@@ -1,9 +1,9 @@
--- ---
name: "@azure/core-rest-pipeline" name: "@azure/core-rest-pipeline"
version: 1.22.2 version: 1.24.0
type: npm type: npm
summary: Isomorphic client library for making HTTP requests in node.js and browser. summary: Isomorphic client library for making HTTP requests in node.js and browser.
homepage: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/core/core-rest-pipeline/ homepage: https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/core/core-rest-pipeline/README.md
license: mit license: mit
licenses: licenses:
- sources: LICENSE - sources: LICENSE

View File

@@ -1,6 +1,6 @@
--- ---
name: "@azure/core-xml" name: "@azure/core-xml"
version: 1.5.0 version: 1.5.1
type: npm type: npm
summary: Core library for interacting with XML payloads summary: Core library for interacting with XML payloads
homepage: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/core/core-xml/ homepage: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/core/core-xml/

View File

@@ -1,9 +1,9 @@
--- ---
name: "@azure/storage-blob" name: "@azure/storage-blob"
version: 12.29.1 version: 12.32.0
type: npm type: npm
summary: Microsoft Azure Storage SDK for JavaScript - Blob summary: Microsoft Azure Storage SDK for JavaScript - Blob
homepage: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/storage/storage-blob/ homepage: https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/storage/storage-blob/README.md
license: mit license: mit
licenses: licenses:
- sources: LICENSE - sources: LICENSE

View File

@@ -1,9 +1,9 @@
--- ---
name: "@azure/storage-common" name: "@azure/storage-common"
version: 12.1.1 version: 12.4.1
type: npm type: npm
summary: Azure Storage Common Client Library for JavaScript summary: Azure Storage Common Client Library for JavaScript
homepage: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/storage/storage-internal-avro/ homepage: https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/storage/storage-common/README.md
license: mit license: mit
licenses: licenses:
- sources: LICENSE - sources: LICENSE

11
.licenses/npm/@nodable/entities.dep.yml generated Normal file
View File

@@ -0,0 +1,11 @@
---
name: "@nodable/entities"
version: 2.2.0
type: npm
summary: Entity parser for XML, HTML, External entites with security and NCR control
homepage:
license: mit
licenses:
- sources: README.md
text: MIT
notices: []

View File

@@ -1,6 +1,6 @@
--- ---
name: "@octokit/auth-token" name: "@octokit/auth-token"
version: 4.0.0 version: 6.0.0
type: npm type: npm
summary: GitHub API token authentication for browsers and Node.js summary: GitHub API token authentication for browsers and Node.js
homepage: homepage:

View File

@@ -1,6 +1,6 @@
--- ---
name: "@octokit/core" name: "@octokit/core"
version: 5.0.1 version: 7.0.6
type: npm type: npm
summary: Extendable client for GitHub's REST & GraphQL APIs summary: Extendable client for GitHub's REST & GraphQL APIs
homepage: homepage:

View File

@@ -1,6 +1,6 @@
--- ---
name: "@octokit/endpoint" name: "@octokit/endpoint"
version: 9.0.6 version: 11.0.3
type: npm type: npm
summary: Turns REST API endpoints into generic request options summary: Turns REST API endpoints into generic request options
homepage: homepage:

View File

@@ -1,6 +1,6 @@
--- ---
name: "@octokit/graphql" name: "@octokit/graphql"
version: 7.0.2 version: 9.0.3
type: npm type: npm
summary: GitHub GraphQL API client for browsers and Node summary: GitHub GraphQL API client for browsers and Node
homepage: homepage:

View File

@@ -1,20 +0,0 @@
---
name: "@octokit/openapi-types"
version: 20.0.0
type: npm
summary: Generated TypeScript definitions based on GitHub's OpenAPI spec for api.github.com
homepage:
license: mit
licenses:
- sources: LICENSE
text: |-
Copyright 2020 Gregor Martynus
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- sources: README.md
text: "[MIT](LICENSE)"
notices: []

View File

@@ -1,20 +0,0 @@
---
name: "@octokit/openapi-types"
version: 24.2.0
type: npm
summary: Generated TypeScript definitions based on GitHub's OpenAPI spec for api.github.com
homepage:
license: mit
licenses:
- sources: LICENSE
text: |-
Copyright 2020 Gregor Martynus
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- sources: README.md
text: "[MIT](LICENSE)"
notices: []

View File

@@ -1,14 +1,14 @@
--- ---
name: "@octokit/openapi-types" name: "@octokit/openapi-types"
version: 19.0.2 version: 27.0.0
type: npm type: npm
summary: Generated TypeScript definitions based on GitHub's OpenAPI spec for api.github.com summary: Generated TypeScript definitions based on GitHub's OpenAPI spec for api.github.com
homepage: homepage:
license: mit license: mit
licenses: licenses:
- sources: LICENSE - sources: LICENSE
text: |- text: |
Copyright 2020 Gregor Martynus Copyright (c) GitHub 2025 - Licensed as MIT.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

View File

@@ -1,6 +1,6 @@
--- ---
name: "@octokit/plugin-paginate-rest" name: "@octokit/plugin-paginate-rest"
version: 9.2.2 version: 14.0.0
type: npm type: npm
summary: Octokit plugin to paginate REST API endpoint responses summary: Octokit plugin to paginate REST API endpoint responses
homepage: homepage:

View File

@@ -1,6 +1,6 @@
--- ---
name: "@octokit/plugin-rest-endpoint-methods" name: "@octokit/plugin-rest-endpoint-methods"
version: 10.1.5 version: 17.0.0
type: npm type: npm
summary: Octokit plugin adding one method for all of api.github.com REST API endpoints summary: Octokit plugin adding one method for all of api.github.com REST API endpoints
homepage: homepage:

View File

@@ -1,6 +1,6 @@
--- ---
name: "@octokit/request-error" name: "@octokit/request-error"
version: 5.1.1 version: 7.1.0
type: npm type: npm
summary: Error class for Octokit request errors summary: Error class for Octokit request errors
homepage: homepage:

View File

@@ -1,6 +1,6 @@
--- ---
name: "@octokit/request" name: "@octokit/request"
version: 8.4.1 version: 10.0.10
type: npm type: npm
summary: Send parameterized requests to GitHub's APIs with sensible defaults in browsers summary: Send parameterized requests to GitHub's APIs with sensible defaults in browsers
and Node and Node

View File

@@ -1,20 +0,0 @@
---
name: "@octokit/types"
version: 12.3.0
type: npm
summary: Shared TypeScript definitions for Octokit projects
homepage:
license: mit
licenses:
- sources: LICENSE
text: |
MIT License Copyright (c) 2019 Octokit contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- sources: README.md
text: "[MIT](LICENSE)"
notices: []

View File

@@ -1,20 +0,0 @@
---
name: "@octokit/types"
version: 13.10.0
type: npm
summary: Shared TypeScript definitions for Octokit projects
homepage:
license: mit
licenses:
- sources: LICENSE
text: |
MIT License Copyright (c) 2019 Octokit contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- sources: README.md
text: "[MIT](LICENSE)"
notices: []

View File

@@ -1,6 +1,6 @@
--- ---
name: "@octokit/types" name: "@octokit/types"
version: 12.6.0 version: 16.0.0
type: npm type: npm
summary: Shared TypeScript definitions for Octokit projects summary: Shared TypeScript definitions for Octokit projects
homepage: homepage:

View File

@@ -1,9 +1,9 @@
--- ---
name: "@typespec/ts-http-runtime" name: "@typespec/ts-http-runtime"
version: 0.3.2 version: 0.3.6
type: npm type: npm
summary: Isomorphic client library for making HTTP requests in node.js and browser. summary: Isomorphic client library for making HTTP requests in node.js and browser.
homepage: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/core/ts-http-runtime/ homepage: https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/core/ts-http-runtime/README.md
license: mit license: mit
licenses: licenses:
- sources: LICENSE - sources: LICENSE

35
.licenses/npm/anynum.dep.yml generated Normal file
View File

@@ -0,0 +1,35 @@
---
name: anynum
version: 1.0.1
type: npm
summary: Normalize all Unicode decimal digits (Devanagari, Arabic, Thai, etc.) to
ASCII numerals. Zero dependencies, performance-first.
homepage:
license: mit
licenses:
- sources: LICENSE
text: |
MIT License
Copyright (c) 2026 Natural Intelligence
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
- sources: README.md
text: MIT
notices: []

View File

@@ -0,0 +1,34 @@
---
name: balanced-match
version: 4.0.4
type: npm
summary: Match balanced character pairs, like "{" and "}"
homepage:
license: other
licenses:
- sources: LICENSE.md
text: |
(MIT)
Original code Copyright Julian Gruber <julian@juliangruber.com>
Port to TypeScript Copyright Isaac Z. Schlueter <i@izs.me>
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
notices: []

View File

@@ -1,6 +1,6 @@
--- ---
name: before-after-hook name: before-after-hook
version: 2.2.3 version: 4.0.0
type: npm type: npm
summary: asynchronous before/error/after hooks for internal functionality summary: asynchronous before/error/after hooks for internal functionality
homepage: homepage:

View File

@@ -1,6 +1,6 @@
--- ---
name: brace-expansion name: brace-expansion
version: 1.1.12 version: 1.1.15
type: npm type: npm
summary: Brace expansion as known from sh/bash summary: Brace expansion as known from sh/bash
homepage: https://github.com/juliangruber/brace-expansion homepage: https://github.com/juliangruber/brace-expansion

View File

@@ -0,0 +1,34 @@
---
name: brace-expansion
version: 5.0.6
type: npm
summary: Brace expansion as known from sh/bash
homepage:
license: other
licenses:
- sources: LICENSE
text: |
MIT License
Copyright Julian Gruber <julian@juliangruber.com>
TypeScript port Copyright Isaac Z. Schlueter <i@izs.me>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
notices: []

47
.licenses/npm/content-type.dep.yml generated Normal file
View File

@@ -0,0 +1,47 @@
---
name: content-type
version: 2.0.0
type: npm
summary: Create and parse HTTP Content-Type header
homepage:
license: mit
licenses:
- sources: LICENSE
text: |
(The MIT License)
Copyright (c) 2015 Douglas Christopher Wilson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- sources: README.md
text: |-
[MIT](LICENSE)
[npm-image]: https://img.shields.io/npm/v/content-type
[npm-url]: https://npmjs.org/package/content-type
[downloads-image]: https://img.shields.io/npm/dm/content-type
[downloads-url]: https://npmjs.org/package/content-type
[build-image]: https://img.shields.io/github/actions/workflow/status/jshttp/content-type/ci.yml?branch=master
[build-url]: https://github.com/jshttp/content-type/actions/workflows/ci.yml?query=branch%3Amaster
[coverage-image]: https://img.shields.io/codecov/c/gh/jshttp/content-type
[coverage-url]: https://codecov.io/gh/jshttp/content-type
[license-image]: http://img.shields.io/npm/l/content-type.svg?style=flat
[license-url]: LICENSE
notices: []

View File

@@ -1,6 +1,6 @@
--- ---
name: debug name: debug
version: 4.3.4 version: 4.4.3
type: npm type: npm
summary: Lightweight debugging utility for Node.js and the browser summary: Lightweight debugging utility for Node.js and the browser
homepage: homepage:

View File

@@ -1,28 +0,0 @@
---
name: deprecation
version: 2.3.1
type: npm
summary: Log a deprecation message with stack
homepage: https://github.com/gr2m/deprecation#readme
license: isc
licenses:
- sources: LICENSE
text: |
The ISC License
Copyright (c) Gregor Martynus and contributors
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- sources: README.md
text: "[ISC](LICENSE)"
notices: []

View File

@@ -1,16 +1,16 @@
--- ---
name: "@azure/abort-controller" name: fast-xml-builder
version: 1.1.0 version: 1.2.0
type: npm type: npm
summary: Microsoft Azure SDK for JavaScript - Aborter summary: Build XML from JSON without C/C++ based libraries
homepage: https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/core/abort-controller/README.md homepage:
license: mit license: mit
licenses: licenses:
- sources: LICENSE - sources: LICENSE
text: | text: |
The MIT License (MIT) MIT License
Copyright (c) 2020 Microsoft Copyright (c) 2026 Natural Intelligence
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,6 +1,6 @@
--- ---
name: fast-xml-parser name: fast-xml-parser
version: 5.3.6 version: 5.9.3
type: npm type: npm
summary: Validate XML, Parse XML, Build XML without C/C++ based libraries summary: Validate XML, Parse XML, Build XML without C/C++ based libraries
homepage: homepage:

35
.licenses/npm/is-unsafe.dep.yml generated Normal file
View File

@@ -0,0 +1,35 @@
---
name: is-unsafe
version: 1.0.1
type: npm
summary: Zero-dependency, DOM-free, pure predicate for detecting unsafe strings across
HTML, XML, SVG, SQL, SHELL, and REGEX contexts
homepage:
license: mit
licenses:
- sources: LICENSE
text: |
MIT License
Copyright (c) 2026 Natural Intelligence
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
- sources: README.md
text: MIT
notices: []

View File

@@ -1,30 +1,33 @@
--- ---
name: "@fastify/busboy" name: json-with-bigint
version: 2.1.1 version: 3.5.8
type: npm type: npm
summary: A streaming parser for HTML form data for node.js summary: JS library that allows you to easily serialize and deserialize data with
BigInt values
homepage: homepage:
license: mit license: mit
licenses: licenses:
- sources: LICENSE - sources: LICENSE
text: |- text: |
Copyright Brian White. All rights reserved. MIT License
Copyright (c) 2023 Ivan Korolenko
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to of this software and associated documentation files (the "Software"), to deal
deal in the Software without restriction, including without limitation the in the Software without restriction, including without limitation the rights
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
sell copies of the Software, and to permit persons to whom the Software is copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions: furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in The above copyright notice and this permission notice shall be included in all
all copies or substantial portions of the Software. copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
IN THE SOFTWARE. SOFTWARE.
notices: [] notices: []

View File

@@ -1,26 +0,0 @@
---
name: lru-cache
version: 6.0.0
type: npm
summary: A cache object that deletes the least-recently-used items.
homepage:
license: isc
licenses:
- sources: LICENSE
text: |
The ISC License
Copyright (c) Isaac Z. Schlueter and Contributors
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
notices: []

66
.licenses/npm/minimatch-10.2.5.dep.yml generated Normal file
View File

@@ -0,0 +1,66 @@
---
name: minimatch
version: 10.2.5
type: npm
summary: a glob matcher in javascript
homepage:
license: blueoak-1.0.0
licenses:
- sources: LICENSE.md
text: |
# Blue Oak Model License
Version 1.0.0
## Purpose
This license gives everyone as much permission to work with
this software as possible, while protecting contributors
from liability.
## Acceptance
In order to receive this license, you must agree to its
rules. The rules of this license are both obligations
under that agreement and conditions to your license.
You must not do anything with this software that triggers
a rule that you cannot or will not follow.
## Copyright
Each contributor licenses you to do everything with this
software that would otherwise infringe that contributor's
copyright in it.
## Notices
You must ensure that everyone who gets a copy of
any part of this software from you, with or without
changes, also gets the text of this license or a link to
<https://blueoakcouncil.org/license/1.0.0>.
## Excuse
If anyone notifies you in writing that you have not
complied with [Notices](#notices), you can keep your
license by taking all practical steps to comply within 30
days after the notice. If you do not do so, your license
ends immediately.
## Patent
Each contributor licenses you to do everything with this
software that would otherwise infringe any patent claims
they can license or become able to license.
## Reliability
No contributor can revoke this license.
## No Liability
**_As far as the law allows, this software comes as is,
without any warranty or condition, and no contributor
will be liable to anyone for any damages related to this
software or this license, under any kind of legal claim._**
notices: []

View File

@@ -1,6 +1,6 @@
--- ---
name: ms name: ms
version: 2.1.2 version: 2.1.3
type: npm type: npm
summary: Tiny millisecond conversion utility summary: Tiny millisecond conversion utility
homepage: homepage:
@@ -10,7 +10,7 @@ licenses:
text: | text: |
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2016 Zeit, Inc. Copyright (c) 2020 Vercel, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,26 +0,0 @@
---
name: once
version: 1.4.0
type: npm
summary: Run a function exactly one time
homepage: https://github.com/isaacs/once#readme
license: isc
licenses:
- sources: LICENSE
text: |
The ISC License
Copyright (c) Isaac Z. Schlueter and Contributors
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
notices: []

View File

@@ -0,0 +1,32 @@
---
name: path-expression-matcher
version: 1.5.0
type: npm
summary: Efficient path tracking and pattern matching for XML/JSON parsers
homepage: https://github.com/NaturalIntelligence/path-expression-matcher#readme
license: mit
licenses:
- sources: LICENSE
text: |
MIT License
Copyright (c) 2024
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
notices: []

View File

@@ -1,26 +0,0 @@
---
name: semver
version: 7.6.0
type: npm
summary: The semantic version parser used by npm.
homepage:
license: isc
licenses:
- sources: LICENSE
text: |
The ISC License
Copyright (c) Isaac Z. Schlueter and Contributors
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
notices: []

View File

@@ -1,6 +1,6 @@
--- ---
name: semver name: semver
version: 6.3.1 version: 7.8.5
type: npm type: npm
summary: The semantic version parser used by npm. summary: The semantic version parser used by npm.
homepage: homepage:

View File

@@ -1,9 +1,9 @@
--- ---
name: strnum name: strnum
version: 2.1.2 version: 2.4.1
type: npm type: npm
summary: Parse String to Number based on configuration summary: Parse String to Number based on configuration
homepage: homepage:
license: mit license: mit
licenses: licenses:
- sources: LICENSE - sources: LICENSE

View File

@@ -1,6 +1,6 @@
--- ---
name: undici name: undici
version: 5.29.0 version: 6.27.0
type: npm type: npm
summary: An HTTP/1.1 client, written from scratch for Node.js summary: An HTTP/1.1 client, written from scratch for Node.js
homepage: https://undici.nodejs.org homepage: https://undici.nodejs.org

View File

@@ -1,8 +1,8 @@
--- ---
name: universal-user-agent name: universal-user-agent
version: 6.0.1 version: 7.0.3
type: npm type: npm
summary: Get a user agent string in both browser and node summary: Get a user agent string across all JavaScript Runtime Environments
homepage: homepage:
license: isc license: isc
licenses: licenses:
@@ -10,7 +10,7 @@ licenses:
text: | text: |
# [ISC License](https://spdx.org/licenses/ISC) # [ISC License](https://spdx.org/licenses/ISC)
Copyright (c) 2018, Gregor Martynus (https://github.com/gr2m) Copyright (c) 2018-2021, Gregor Martynus (https://github.com/gr2m)
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.

View File

@@ -1,26 +0,0 @@
---
name: wrappy
version: 1.0.2
type: npm
summary: Callback wrapping utility
homepage: https://github.com/npm/wrappy
license: isc
licenses:
- sources: LICENSE
text: |
The ISC License
Copyright (c) Isaac Z. Schlueter and Contributors
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
notices: []

12
.licenses/npm/xml-naming.dep.yml generated Normal file
View File

@@ -0,0 +1,12 @@
---
name: xml-naming
version: 0.1.0
type: npm
summary: Validates XML name productions — Name, NCName, QName, NMToken, NMTokens —
for XML 1.0 and 1.1
homepage:
license: mit
licenses:
- sources: README.md
text: MIT
notices: []

View File

@@ -1,26 +0,0 @@
---
name: yallist
version: 4.0.0
type: npm
summary: Yet Another Linked List
homepage:
license: isc
licenses:
- sources: LICENSE
text: |
The ISC License
Copyright (c) Isaac Z. Schlueter and Contributors
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
notices: []

View File

@@ -1,11 +0,0 @@
// 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'
};

10
.prettierrc.json Normal file
View File

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

109
README.md
View File

@@ -15,6 +15,10 @@ documentation:
[Software installed on github hosted runners](https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#supported-software) [Software installed on github hosted runners](https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#supported-software)
for .NET SDK versions that are currently available. for .NET SDK versions that are currently available.
## What's new in V6
- Migrated action internals to ESM for compatibility with latest `@actions/*` packages. No changes to action inputs, outputs, or behavior.
## Breaking changes in V5 ## Breaking changes in V5
- Upgraded action from node20 to node24 - Upgraded action from node20 to node24
@@ -29,8 +33,8 @@ See [action.yml](action.yml)
**Basic**: **Basic**:
```yaml ```yaml
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v7
- uses: actions/setup-dotnet@v5 - uses: actions/setup-dotnet@v6
with: with:
dotnet-version: '8.0.x' dotnet-version: '8.0.x'
- run: dotnet build <my project> - run: dotnet build <my project>
@@ -40,9 +44,9 @@ steps:
**Multiple version installation**: **Multiple version installation**:
```yml ```yml
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v7
- name: Setup dotnet - name: Setup dotnet
uses: actions/setup-dotnet@v5 uses: actions/setup-dotnet@v6
with: with:
dotnet-version: | dotnet-version: |
8.0.x 8.0.x
@@ -57,7 +61,30 @@ The `dotnet-version` input supports following syntax:
- **A.B** or **A.B.x** (e.g. 8.0, 8.0.x) - installs the latest patch version of .NET SDK on the channel `8.0`, including prerelease versions (preview, rc) - **A.B** or **A.B.x** (e.g. 8.0, 8.0.x) - installs the latest patch version of .NET SDK on the channel `8.0`, including prerelease versions (preview, rc)
- **A** or **A.x** (e.g. 8, 8.x) - installs the latest minor version of the specified major tag, including prerelease versions (preview, rc) - **A** or **A.x** (e.g. 8, 8.x) - installs the latest minor version of the specified major tag, including prerelease versions (preview, rc)
- **A.B.Cxx** (e.g. 8.0.4xx) - available since `.NET 5.0` release. Installs the latest version of the specific SDK release, including prerelease versions (preview, rc). - **A.B.Cxx** (e.g. 8.0.4xx) - available since `.NET 5.0` release. Installs the latest version of the specific SDK release, including prerelease versions (preview, rc).
- **latest** - dynamically resolves to the highest active .NET SDK version. By default, it installs the latest **stable (GA)** version (excluding previews and end-of-life releases). Can be combined with `dotnet-channel` and `dotnet-quality`.
## Using with `dotnet-channel` input
The optional `dotnet-channel` input specifies the source channel for the installation. Supported values:
| Value | Description |
|-------|-------------|
| `STS` | The most recent Standard Term Support release |
| `LTS` | The most recent Long Term Support release |
| `A.B` (e.g. `8.0`) | A specific release channel |
| `A.B.Cxx` (e.g. `8.0.1xx`) | A specific SDK release (available since 5.0) |
> **Note**: The `dotnet-channel` input is only applied when `dotnet-version` is set to `latest`. If used with a specific version, a warning will be logged and the channel input will be ignored.
**Install latest LTS version:**
```yaml
steps:
- uses: actions/checkout@v7
- uses: actions/setup-dotnet@v6
with:
dotnet-version: latest
dotnet-channel: LTS
```
## Using the `architecture` input ## Using the `architecture` input
Using the architecture input, it is possible to specify the required .NET SDK architecture. Possible values: `x64`, `x86`, `arm64`, `amd64`, `arm`, `s390x`, `ppc64le`, `riscv64`. If the input is not specified, the architecture defaults to the host OS architecture (not all of the architectures are available on all platforms). Using the architecture input, it is possible to specify the required .NET SDK architecture. Possible values: `x64`, `x86`, `arm64`, `amd64`, `arm`, `s390x`, `ppc64le`, `riscv64`. If the input is not specified, the architecture defaults to the host OS architecture (not all of the architectures are available on all platforms).
@@ -65,9 +92,9 @@ Using the architecture input, it is possible to specify the required .NET SDK ar
**Example: Install multiple SDK versions for a specific architecture** **Example: Install multiple SDK versions for a specific architecture**
```yml ```yml
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v7
- name: Setup dotnet (x86) - name: Setup dotnet (x86)
uses: actions/setup-dotnet@v5 uses: actions/setup-dotnet@v6
with: with:
dotnet-version: | dotnet-version: |
8.0.x 8.0.x
@@ -77,20 +104,33 @@ steps:
``` ```
## 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**.
> **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. The `dotnet-quality` input installs the latest build of the specified quality in the channel. Supported values: `daily`, `preview`, `ga`.
> **Note**: When used with a specific SDK version, `dotnet-quality` supports only `A.B`, `A.B.x`, `A`, `A.x`, and `A.B.Cxx` formats where the major version is higher than 5. For all other formats, `dotnet-quality` will be ignored.
```yml ```yml
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v7
- uses: actions/setup-dotnet@v5 - uses: actions/setup-dotnet@v6
with: with:
dotnet-version: '8.0.x' dotnet-version: '8.0.x'
dotnet-quality: 'preview' dotnet-quality: 'preview'
- run: dotnet build <my project> - run: dotnet build <my project>
``` ```
`dotnet-quality` can also be combined with `dotnet-version: latest` and `dotnet-channel` to target specific builds such as the latest `daily` build from the `LTS` channel.
```yaml
steps:
- uses: actions/checkout@v7
- uses: actions/setup-dotnet@v6
with:
dotnet-version: latest
dotnet-channel: LTS
dotnet-quality: daily
```
## Using the `global-json-file` input ## Using the `global-json-file` input
`setup-dotnet` action can read .NET SDK version from a `global.json` file. Input `global-json-file` is used for specifying the path to the `global.json`. If the file that was supplied to `global-json-file` input doesn't exist, the action will fail with error. `setup-dotnet` action can read .NET SDK version from a `global.json` file. Input `global-json-file` is used for specifying the path to the `global.json`. If the file that was supplied to `global-json-file` input doesn't exist, the action will fail with error.
@@ -98,14 +138,16 @@ steps:
```yml ```yml
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v7
- uses: actions/setup-dotnet@v5 - uses: actions/setup-dotnet@v6
with: with:
global-json-file: csharp/global.json global-json-file: csharp/global.json
- run: dotnet build <my project> - run: dotnet build <my project>
working-directory: csharp working-directory: csharp
``` ```
> **Note**: The action supports `latest*` variants of the [rollForward](https://learn.microsoft.com/en-us/dotnet/core/tools/global-json#rollforward) field in `global.json`. When set to `latestPatch`, `latestFeature`, `latestMinor`, or `latestMajor`, the action installs the appropriate SDK version. For prerelease versions, the exact pinned version is always installed regardless of the `rollForward` setting.
## Caching NuGet Packages ## Caching NuGet Packages
The action has a built-in functionality for caching and restoring dependencies. It uses [toolkit/cache](https://github.com/actions/toolkit/tree/main/packages/cache) under the hood for caching global packages data but requires less configuration settings. The `cache` input is optional, and caching is turned off by default. The action has a built-in functionality for caching and restoring dependencies. It uses [toolkit/cache](https://github.com/actions/toolkit/tree/main/packages/cache) under the hood for caching global packages data but requires less configuration settings. The `cache` input is optional, and caching is turned off by default.
@@ -115,8 +157,8 @@ The action searches for [NuGet Lock files](https://learn.microsoft.com/nuget/con
```yaml ```yaml
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v7
- uses: actions/setup-dotnet@v5 - uses: actions/setup-dotnet@v6
with: with:
dotnet-version: 8.x dotnet-version: 8.x
cache: true cache: true
@@ -140,8 +182,8 @@ steps:
env: env:
NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v7
- uses: actions/setup-dotnet@v5 - uses: actions/setup-dotnet@v6
with: with:
dotnet-version: 8.x dotnet-version: 8.x
cache: true cache: true
@@ -154,8 +196,8 @@ steps:
env: env:
NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v7
- uses: actions/setup-dotnet@v5 - uses: actions/setup-dotnet@v6
with: with:
dotnet-version: 8.x dotnet-version: 8.x
cache: true cache: true
@@ -174,9 +216,9 @@ jobs:
dotnet: [ '8.0.x', '9.0.x', '10.0.x' ] dotnet: [ '8.0.x', '9.0.x', '10.0.x' ]
name: Dotnet ${{ matrix.dotnet }} sample name: Dotnet ${{ matrix.dotnet }} sample
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v7
- name: Setup dotnet - name: Setup dotnet
uses: actions/setup-dotnet@v5 uses: actions/setup-dotnet@v6
with: with:
dotnet-version: ${{ matrix.dotnet }} dotnet-version: ${{ matrix.dotnet }}
- name: Execute dotnet - name: Execute dotnet
@@ -194,14 +236,15 @@ jobs:
dotnet: [ '8.0.x', '9.0.x', '10.0.x' ] dotnet: [ '8.0.x', '9.0.x', '10.0.x' ]
name: Dotnet ${{ matrix.dotnet }} sample name: Dotnet ${{ matrix.dotnet }} sample
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v7
- name: Setup dotnet - name: Setup dotnet
uses: actions/setup-dotnet@v5 uses: actions/setup-dotnet@v6
id: stepid id: stepid
with: with:
dotnet-version: ${{ matrix.dotnet }} dotnet-version: ${{ matrix.dotnet }}
- name: Create temporary global.json - name: Create temporary global.json
run: echo '{"sdk":{"version": "${{ steps.stepid.outputs.dotnet-version }}"}}' > ./global.json run: |
echo '{"sdk":{"version": "${{ steps.stepid.outputs.dotnet-version }}"}}' > ./global.json
- name: Execute dotnet - name: Execute dotnet
run: dotnet build <my project> run: dotnet build <my project>
``` ```
@@ -211,8 +254,8 @@ jobs:
### Github Package Registry (GPR) ### Github Package Registry (GPR)
```yml ```yml
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v7
- uses: actions/setup-dotnet@v5 - uses: actions/setup-dotnet@v6
with: with:
dotnet-version: '8.0.x' dotnet-version: '8.0.x'
source-url: https://nuget.pkg.github.com/<owner>/index.json source-url: https://nuget.pkg.github.com/<owner>/index.json
@@ -227,7 +270,7 @@ steps:
### Azure Artifacts ### Azure Artifacts
```yml ```yml
- uses: actions/setup-dotnet@v5 - uses: actions/setup-dotnet@v6
with: with:
source-url: https://pkgs.dev.azure.com/<your-organization>/_packaging/<your-feed-name>/nuget/v3/index.json source-url: https://pkgs.dev.azure.com/<your-organization>/_packaging/<your-feed-name>/nuget/v3/index.json
env: env:
@@ -238,7 +281,7 @@ steps:
### nuget.org ### nuget.org
```yml ```yml
- uses: actions/setup-dotnet@v5 - uses: actions/setup-dotnet@v6
with: with:
dotnet-version: 8.0.x dotnet-version: 8.0.x
- name: Publish the package to nuget.org - name: Publish the package to nuget.org
@@ -255,7 +298,7 @@ The `workloads` input allows you to install .NET workloads as part of the SDK se
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v5
- name: Setup .NET with workloads - name: Setup .NET with workloads
uses: actions/setup-dotnet@v5 uses: actions/setup-dotnet@v6
with: with:
dotnet-version: '9.0.x' dotnet-version: '9.0.x'
workloads: workload1, workload2 # Specify the workloads required for the project, such as wasm-tools, maui, etc. workloads: workload1, workload2 # Specify the workloads required for the project, such as wasm-tools, maui, etc.
@@ -277,7 +320,7 @@ Using the **dotnet-version** output it's possible to get the installed by the ac
In case of a single version installation, the `dotnet-version` output contains the version that is installed by the action. In case of a single version installation, the `dotnet-version` output contains the version that is installed by the action.
```yaml ```yaml
- uses: actions/setup-dotnet@v5 - uses: actions/setup-dotnet@v6
id: stepid id: stepid
with: with:
dotnet-version: 8.0.416 dotnet-version: 8.0.416
@@ -289,7 +332,7 @@ In case of a single version installation, the `dotnet-version` output contains t
In case of a multiple version installation, the `dotnet-version` output contains the latest version that is installed by the action. In case of a multiple version installation, the `dotnet-version` output contains the latest version that is installed by the action.
```yaml ```yaml
- uses: actions/setup-dotnet@v5 - uses: actions/setup-dotnet@v6
id: stepid id: stepid
with: with:
dotnet-version: | dotnet-version: |
@@ -302,7 +345,7 @@ In case of a multiple version installation, the `dotnet-version` output contains
When the `dotnet-version` input is used along with the `global-json-file` input, the `dotnet-version` output contains the version resolved from the `global.json`. When the `dotnet-version` input is used along with the `global-json-file` input, the `dotnet-version` output contains the version resolved from the `global.json`.
```yaml ```yaml
- uses: actions/setup-dotnet@v5 - uses: actions/setup-dotnet@v6
id: stepid id: stepid
with: with:
dotnet-version: | dotnet-version: |
@@ -342,8 +385,8 @@ build:
DOTNET_INSTALL_DIR: "path/to/directory" DOTNET_INSTALL_DIR: "path/to/directory"
NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v7
- uses: actions/setup-dotnet@v5 - uses: actions/setup-dotnet@v6
with: with:
dotnet-version: '8.0.x' dotnet-version: '8.0.x'
cache: true cache: true

View File

@@ -1,4 +1,4 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
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"?>

View File

@@ -1,6 +1,10 @@
import {afterAll, beforeEach, describe, expect, it} from '@jest/globals';
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';
import {fileURLToPath} from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const fakeSourcesDirForTesting = path.join( const fakeSourcesDirForTesting = path.join(
__dirname, __dirname,
@@ -71,7 +75,7 @@ const azureartifactsnugetorgNuGetConfig = `<?xml version="1.0" encoding="utf-8"?
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.js';
describe('authutil tests', () => { describe('authutil tests', () => {
beforeEach(async () => { beforeEach(async () => {

View File

@@ -1,16 +1,40 @@
import {readdir} from 'node:fs/promises'; import {
import * as cache from '@actions/cache'; afterEach,
import * as core from '@actions/core'; beforeAll,
import * as glob from '@actions/glob'; beforeEach,
import {restoreCache} from '../src/cache-restore'; describe,
import {getNuGetFolderPath} from '../src/cache-utils'; expect,
import {lockFilePatterns} from '../src/constants'; it,
jest
} from '@jest/globals';
jest.mock('node:fs/promises'); jest.unstable_mockModule('node:fs/promises', () => ({
jest.mock('@actions/cache'); readdir: jest.fn()
jest.mock('@actions/core'); }));
jest.mock('@actions/glob'); jest.unstable_mockModule('@actions/cache', () => ({
jest.mock('../src/cache-utils'); restoreCache: jest.fn()
}));
jest.unstable_mockModule('@actions/core', () => ({
saveState: jest.fn(),
setOutput: jest.fn(),
info: jest.fn(),
warning: jest.fn(),
debug: jest.fn()
}));
jest.unstable_mockModule('@actions/glob', () => ({
hashFiles: jest.fn()
}));
jest.unstable_mockModule('../src/cache-utils', () => ({
getNuGetFolderPath: jest.fn()
}));
const {readdir} = await import('node:fs/promises');
const cache = await import('@actions/cache');
const core = await import('@actions/core');
const glob = await import('@actions/glob');
const {restoreCache} = await import('../src/cache-restore.js');
const {getNuGetFolderPath} = await import('../src/cache-utils.js');
const {lockFilePatterns} = await import('../src/constants.js');
describe('cache-restore tests', () => { describe('cache-restore tests', () => {
describe.each(lockFilePatterns)('restoreCache("%s")', lockFilePattern => { describe.each(lockFilePatterns)('restoreCache("%s")', lockFilePattern => {
@@ -32,7 +56,9 @@ describe('cache-restore tests', () => {
jest.mocked(core.setOutput).mockClear(); jest.mocked(core.setOutput).mockClear();
jest.mocked(cache.restoreCache).mockClear(); jest.mocked(cache.restoreCache).mockClear();
}); });
afterEach(() => (process.env.GITHUB_WORKSPACE = githubWorkspace)); afterEach(() => {
process.env.GITHUB_WORKSPACE = githubWorkspace;
});
it('throws error when lock file is not found', async () => { it('throws error when lock file is not found', async () => {
jest.mocked(glob.hashFiles).mockResolvedValue(''); jest.mocked(glob.hashFiles).mockResolvedValue('');

View File

@@ -1,13 +1,32 @@
import * as cache from '@actions/cache'; import {beforeAll, beforeEach, describe, expect, it, jest} from '@jest/globals';
import * as core from '@actions/core';
import fs from 'node:fs';
import {run} from '../src/cache-save';
import {getNuGetFolderPath} from '../src/cache-utils';
jest.mock('@actions/cache'); jest.unstable_mockModule('@actions/cache', () => ({
jest.mock('@actions/core'); saveCache: jest.fn()
jest.mock('node:fs'); }));
jest.mock('../src/cache-utils'); jest.unstable_mockModule('@actions/core', () => ({
setFailed: jest.fn(),
getState: jest.fn(),
setOutput: jest.fn(),
getBooleanInput: jest.fn(),
debug: jest.fn(),
info: jest.fn(),
warning: jest.fn()
}));
jest.unstable_mockModule('node:fs', () => ({
default: {
existsSync: jest.fn()
}
}));
jest.unstable_mockModule('../src/cache-utils', () => ({
getNuGetFolderPath: jest.fn()
}));
const cache = await import('@actions/cache');
const core = await import('@actions/core');
const fs = (await import('node:fs')).default;
const {run} = await import('../src/cache-save.js');
const {getNuGetFolderPath} = await import('../src/cache-utils.js');
const {State} = await import('../src/constants.js');
describe('cache-save tests', () => { describe('cache-save tests', () => {
beforeAll(() => { beforeAll(() => {
@@ -84,4 +103,22 @@ describe('cache-save tests', () => {
expect(jest.mocked(core.getState)).toHaveBeenCalledTimes(2); expect(jest.mocked(core.getState)).toHaveBeenCalledTimes(2);
expect(jest.mocked(cache.saveCache)).toHaveBeenCalled(); expect(jest.mocked(cache.saveCache)).toHaveBeenCalled();
}); });
it('does not fail and traces when saveCache returns -1', async () => {
jest.mocked(core.getBooleanInput).mockReturnValue(true);
jest.mocked(core.getState).mockImplementation(s => s);
jest.mocked(fs.existsSync).mockReturnValue(true);
jest.mocked(cache.saveCache).mockResolvedValue(-1);
await run();
expect(jest.mocked(core.setFailed)).not.toHaveBeenCalled();
expect(jest.mocked(cache.saveCache)).toHaveBeenCalled();
expect(jest.mocked(core.debug)).toHaveBeenCalledWith(
`Cache was not saved for the key: ${State.CachePrimaryKey}`
);
expect(jest.mocked(core.info)).not.toHaveBeenCalledWith(
`Cache saved with the key: ${State.CachePrimaryKey}`
);
});
}); });

View File

@@ -1,11 +1,29 @@
import * as cache from '@actions/cache'; import {
import * as exec from '@actions/exec'; afterEach,
beforeAll,
beforeEach,
describe,
expect,
it,
jest
} from '@jest/globals';
import {getNuGetFolderPath, isCacheFeatureAvailable} from '../src/cache-utils'; jest.unstable_mockModule('@actions/cache', () => ({
isFeatureAvailable: jest.fn()
}));
jest.unstable_mockModule('@actions/core', () => ({
warning: jest.fn(),
info: jest.fn(),
debug: jest.fn()
}));
jest.unstable_mockModule('@actions/exec', () => ({
getExecOutput: jest.fn()
}));
jest.mock('@actions/cache'); const cache = await import('@actions/cache');
jest.mock('@actions/core'); const exec = await import('@actions/exec');
jest.mock('@actions/exec'); const {getNuGetFolderPath, isCacheFeatureAvailable} =
await import('../src/cache-utils.js');
describe('cache-utils tests', () => { describe('cache-utils tests', () => {
describe('getNuGetFolderPath()', () => { describe('getNuGetFolderPath()', () => {
@@ -104,9 +122,15 @@ Options:
url => { url => {
// Save & Restore env // Save & Restore env
let serverUrlEnv: string | undefined; let serverUrlEnv: string | undefined;
beforeAll(() => (serverUrlEnv = process.env['GITHUB_SERVER_URL'])); beforeAll(() => {
beforeEach(() => (process.env['GITHUB_SERVER_URL'] = url)); serverUrlEnv = process.env['GITHUB_SERVER_URL'];
afterEach(() => (process.env['GITHUB_SERVER_URL'] = serverUrlEnv)); });
beforeEach(() => {
process.env['GITHUB_SERVER_URL'] = url;
});
afterEach(() => {
process.env['GITHUB_SERVER_URL'] = serverUrlEnv;
});
it('returns true when cache.isFeatureAvailable() === true', () => { it('returns true when cache.isFeatureAvailable() === true', () => {
jest.mocked(cache.isFeatureAvailable).mockReturnValue(true); jest.mocked(cache.isFeatureAvailable).mockReturnValue(true);

View File

@@ -6,8 +6,8 @@ $dotnetPaths = @{
foreach ($srcPath in $dotnetPaths[$args[0]]) { foreach ($srcPath in $dotnetPaths[$args[0]]) {
if (Test-Path $srcPath) { if (Test-Path $srcPath) {
Write-Host "Move $srcPath path" $dstPath = "$srcPath-" + [IO.Path]::GetRandomFileName()
$dstPath = Join-Path ([IO.Path]::GetTempPath()) ([IO.Path]::GetRandomFileName()) Write-Host "Moving $srcPath to $dstPath"
Move-Item -Path $srcPath -Destination $dstPath Move-Item -Path $srcPath -Destination $dstPath
} }
} }

View File

@@ -1,45 +1,97 @@
import cscFile from '../.github/csc.json'; import {describe, expect, test} from '@jest/globals';
import cscFile from '../.github/csc.json' with {type: 'json'};
describe('csc tests', () => { describe('csc tests', () => {
test('regular expression in csc.json is valid', async () => { const regexPattern = cscFile['problemMatcher'][0]['pattern'][0]['regexp'];
const regexPattern = cscFile['problemMatcher'][0]['pattern'][0]['regexp']; const regexResultsMap = cscFile['problemMatcher'][0]['pattern'][0];
const regexResultsMap = cscFile['problemMatcher'][0]['pattern'][0]; const regex = new RegExp(regexPattern);
const regex = new RegExp(regexPattern); const testCases: Array<{input: string; results: Record<string, string>}> = [
{
const stringsToMatch = [ input:
'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]" results: {
];
// Expected results are calculated according to the csc matcher located in csc.json file
const expectedResults = [
{
file: 'Program.cs', file: 'Program.cs',
line: '10', line: '10',
column: '79',
severity: 'error', severity: 'error',
code: 'CS1002', code: 'CS1002',
message: '; expected', message: '; expected',
fromPath: fromPath:
'/Users/zacharyeisinger/Documents/repo/setup-dotnet/__tests__/sample-broken-csproj/sample.csproj' '/Users/zacharyeisinger/Documents/repo/setup-dotnet/__tests__/sample-broken-csproj/sample.csproj'
}, }
{ },
{
input:
"S:\\Msbuild\\src\\Build\\Evaluation\\ExpressionShredder.cs(33,7): error CS1003: Syntax error, ',' expected [S:\\msbuild\\src\\Build\\Microsoft.Build.csproj > Properties:prop]",
results: {
file: 'S:\\Msbuild\\src\\Build\\Evaluation\\ExpressionShredder.cs', file: 'S:\\Msbuild\\src\\Build\\Evaluation\\ExpressionShredder.cs',
line: '33', line: '33',
column: '7',
severity: 'error', severity: 'error',
code: 'CS1003', code: 'CS1003',
message: "Syntax error, ',' expected", message: "Syntax error, ',' expected",
fromPath: fromPath:
'S:\\msbuild\\src\\Build\\Microsoft.Build.csproj > Properties:prop' 'S:\\msbuild\\src\\Build\\Microsoft.Build.csproj > Properties:prop'
} }
]; },
{
// `dotnet format` style error
input:
'C:\\actions-runner\\_work\\Some\\Folder\\SomeFile.cs(222,8): error WHITESPACE: Fix whitespace formatting. Delete 1 characters. [C:\\actions-runner\\_work\\Some\\Folder\\SomeProject.csproj]',
results: {
file: 'C:\\actions-runner\\_work\\Some\\Folder\\SomeFile.cs',
line: '222',
column: '8',
severity: 'error',
code: 'WHITESPACE',
message: 'Fix whitespace formatting. Delete 1 characters.',
fromPath: 'C:\\actions-runner\\_work\\Some\\Folder\\SomeProject.csproj'
}
},
{
// CSC error with whitespace prefix
input:
' /Volumes/Code/ghe_actions-2.301.1/Some/Folder/SomeFile.cs(10,8): error CS1014: A get or set accessor expected [/Volumes/Code/ghe_actions-2.301.1/Some/Folder/SomeProject.csproj]',
results: {
file: '/Volumes/Code/ghe_actions-2.301.1/Some/Folder/SomeFile.cs',
line: '10',
column: '8',
severity: 'error',
code: 'CS1014',
message: 'A get or set accessor expected',
fromPath:
'/Volumes/Code/ghe_actions-2.301.1/Some/Folder/SomeProject.csproj'
}
},
{
// CSC error with MSBuild prefix
input:
' 20>C:\\actions-runner\\_work\\Some\\Folder\\SomeFile.cs(8,2): error CS1014: A get or set accessor expected [C:\\actions-runner\\_work\\Some\\Folder\\SomeProject.csproj]',
results: {
file: 'C:\\actions-runner\\_work\\Some\\Folder\\SomeFile.cs',
line: '8',
column: '2',
severity: 'error',
code: 'CS1014',
message: 'A get or set accessor expected',
fromPath: 'C:\\actions-runner\\_work\\Some\\Folder\\SomeProject.csproj'
}
}
];
stringsToMatch.map((string, index) => { test.each(testCases)(
const matchedResultsArray = string.match(regex); 'regex matches and parses: $input',
for (const propName in expectedResults[index]) { ({input, results}: {input: string; results: Record<string, string>}) => {
const propertyIndex = regexResultsMap[propName]; const matchedResultsArray = input.match(regex);
const expectedPropValue = expectedResults[index][propName]; expect(matchedResultsArray).not.toBeNull();
for (const propName in results) {
const propertyIndex = (regexResultsMap as any)[propName];
const expectedPropValue = results[propName];
const matchedPropValue = matchedResultsArray![propertyIndex]; const matchedPropValue = matchedResultsArray![propertyIndex];
expect(matchedPropValue).toEqual(expectedPropValue); expect(matchedPropValue).toEqual(expectedPropValue);
} }
}); },
}, 10000); 10000
);
}); });

View File

@@ -1,7 +1,11 @@
import {describe, expect, it} from '@jest/globals';
import path from 'path'; import path from 'path';
import fs from 'fs'; import fs from 'fs';
import {fileURLToPath} from 'url';
import * as hc from '@actions/http-client'; import * as hc from '@actions/http-client';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const HTTP_CLIENT_OPTIONS = {allowRetries: true, maxRetries: 10} as const; const HTTP_CLIENT_OPTIONS = {allowRetries: true, maxRetries: 10} as const;
const TEST_TIMEOUT = 30000; const TEST_TIMEOUT = 30000;

View File

@@ -1,15 +1,52 @@
import {
afterAll,
beforeAll,
beforeEach,
describe,
expect,
it,
jest
} from '@jest/globals';
import each from 'jest-each'; import each from 'jest-each';
import semver from 'semver'; import semver from 'semver';
import fs from 'fs';
import fspromises from 'fs/promises'; import fspromises from 'fs/promises';
import os from 'os'; import os from 'os';
import * as exec from '@actions/exec'; import path from 'path';
import * as core from '@actions/core';
import * as io from '@actions/io';
import * as installer from '../src/installer';
import {IS_WINDOWS} from '../src/utils'; jest.unstable_mockModule('@actions/exec', () => ({
import {QualityOptions} from '../src/setup-dotnet'; getExecOutput: jest.fn()
}));
jest.unstable_mockModule('@actions/core', () => ({
warning: jest.fn(),
info: jest.fn(),
debug: jest.fn(),
setOutput: jest.fn(),
exportVariable: jest.fn((name: string, val: string) => {
process.env[name] = val;
}),
addPath: jest.fn((p: string) => {
process.env['PATH'] = `${p}${path.delimiter}${process.env['PATH']}`;
})
}));
jest.unstable_mockModule('@actions/io', () => ({
which: jest.fn()
}));
jest.unstable_mockModule('fs', () => {
const actual = jest.requireActual('fs') as typeof import('fs');
const chmodSync = jest.fn();
return {
...actual,
chmodSync,
default: {...actual, chmodSync}
};
});
const exec = await import('@actions/exec');
const core = await import('@actions/core');
const io = await import('@actions/io');
const fs = await import('fs');
const installer = await import('../src/installer.js');
const {IS_WINDOWS} = await import('../src/utils.js');
describe('installer tests', () => { describe('installer tests', () => {
const env = process.env; const env = process.env;
@@ -17,14 +54,24 @@ describe('installer tests', () => {
beforeEach(() => { beforeEach(() => {
jest.resetModules(); jest.resetModules();
process.env = {...env}; process.env = {...env};
(core.exportVariable as jest.Mock).mockImplementation(
(...args: unknown[]) => {
const [name, val] = args as [string, string];
process.env[name] = val;
}
);
(core.addPath as jest.Mock).mockImplementation((...args: unknown[]) => {
const [p] = args as [string];
process.env['PATH'] = `${p}${path.delimiter}${process.env['PATH']}`;
});
}); });
describe('DotnetCoreInstaller tests', () => { describe('DotnetCoreInstaller tests', () => {
const getExecOutputSpy = jest.spyOn(exec, 'getExecOutput'); const getExecOutputSpy = exec.getExecOutput as jest.Mock;
const warningSpy = jest.spyOn(core, 'warning'); const warningSpy = core.warning as jest.Mock;
const whichSpy = jest.spyOn(io, 'which'); const whichSpy = io.which as jest.Mock;
const maxSatisfyingSpy = jest.spyOn(semver, 'maxSatisfying'); const maxSatisfyingSpy = jest.spyOn(semver, 'maxSatisfying');
const chmodSyncSpy = jest.spyOn(fs, 'chmodSync'); const chmodSyncSpy = fs.chmodSync as jest.Mock;
const readdirSpy = jest.spyOn(fspromises, 'readdir'); const readdirSpy = jest.spyOn(fspromises, 'readdir');
describe('installDotnet() tests', () => { describe('installDotnet() tests', () => {
@@ -40,7 +87,7 @@ describe('installer tests', () => {
it('should throw the error in case of non-zero exit code of the installation script. The error message should contain logs.', async () => { it('should throw the error in case of non-zero exit code of the installation script. The error message should contain logs.', async () => {
const inputVersion = '10.0.101'; const inputVersion = '10.0.101';
const inputQuality = '' as QualityOptions; const inputQuality = '';
const errorMessage = 'fictitious error message!'; const errorMessage = 'fictitious error message!';
getExecOutputSpy.mockImplementation(() => { getExecOutputSpy.mockImplementation(() => {
@@ -62,7 +109,7 @@ describe('installer tests', () => {
it('should return version of .NET SDK after installation complete', async () => { it('should return version of .NET SDK after installation complete', async () => {
const inputVersion = '10.0.101'; const inputVersion = '10.0.101';
const inputQuality = '' as QualityOptions; const inputQuality = '';
const stdout = `Fictitious dotnet version ${inputVersion} is installed`; const stdout = `Fictitious dotnet version ${inputVersion} is installed`;
getExecOutputSpy.mockImplementation(() => { getExecOutputSpy.mockImplementation(() => {
return Promise.resolve({ return Promise.resolve({
@@ -84,7 +131,7 @@ describe('installer tests', () => {
it(`should supply 'version' argument to the installation script if supplied version is in A.B.C syntax`, async () => { it(`should supply 'version' argument to the installation script if supplied version is in A.B.C syntax`, async () => {
const inputVersion = '10.0.101'; const inputVersion = '10.0.101';
const inputQuality = '' as QualityOptions; const inputQuality = '';
const stdout = `Fictitious dotnet version ${inputVersion} is installed`; const stdout = `Fictitious dotnet version ${inputVersion} is installed`;
getExecOutputSpy.mockImplementation(() => { getExecOutputSpy.mockImplementation(() => {
@@ -122,7 +169,7 @@ describe('installer tests', () => {
it(`should warn if the 'quality' input is set and the supplied version is in A.B.C syntax`, async () => { it(`should warn if the 'quality' input is set and the supplied version is in A.B.C syntax`, async () => {
const inputVersion = '10.0.101'; const inputVersion = '10.0.101';
const inputQuality = 'ga' as QualityOptions; const inputQuality = 'ga';
const stdout = `Fictitious dotnet version ${inputVersion} is installed`; const stdout = `Fictitious dotnet version ${inputVersion} is installed`;
getExecOutputSpy.mockImplementation(() => { getExecOutputSpy.mockImplementation(() => {
return Promise.resolve({ return Promise.resolve({
@@ -147,7 +194,7 @@ describe('installer tests', () => {
it(`should warn if the 'quality' input is set and version isn't in A.B.C syntax but major tag is lower then 6`, async () => { it(`should warn if the 'quality' input is set and version isn't in A.B.C syntax but major tag is lower then 6`, async () => {
const inputVersion = '3.1'; const inputVersion = '3.1';
const inputQuality = 'ga' as QualityOptions; const inputQuality = 'ga';
const stdout = `Fictitious dotnet version ${inputVersion} is installed`; const stdout = `Fictitious dotnet version ${inputVersion} is installed`;
getExecOutputSpy.mockImplementation(() => { getExecOutputSpy.mockImplementation(() => {
@@ -174,7 +221,7 @@ describe('installer tests', () => {
each(['10', '10.0', '10.0.x', '10.0.*', '10.0.X']).test( each(['10', '10.0', '10.0.x', '10.0.*', '10.0.X']).test(
`should supply 'quality' argument to the installation script if quality input is set and version (%s) is not in A.B.C syntax`, `should supply 'quality' argument to the installation script if quality input is set and version (%s) is not in A.B.C syntax`,
async inputVersion => { async inputVersion => {
const inputQuality = 'ga' as QualityOptions; const inputQuality = 'ga';
const exitCode = 0; const exitCode = 0;
const stdout = `Fictitious dotnet version ${inputVersion} is installed`; const stdout = `Fictitious dotnet version ${inputVersion} is installed`;
getExecOutputSpy.mockImplementation(() => { getExecOutputSpy.mockImplementation(() => {
@@ -214,7 +261,7 @@ describe('installer tests', () => {
each(['10', '10.0', '10.0.x', '10.0.*', '10.0.X']).test( each(['10', '10.0', '10.0.x', '10.0.*', '10.0.X']).test(
`should supply 'channel' argument to the installation script if version (%s) isn't in A.B.C syntax`, `should supply 'channel' argument to the installation script if version (%s) isn't in A.B.C syntax`,
async inputVersion => { async inputVersion => {
const inputQuality = '' as QualityOptions; const inputQuality = '';
const exitCode = 0; const exitCode = 0;
const stdout = `Fictitious dotnet version ${inputVersion} is installed`; const stdout = `Fictitious dotnet version ${inputVersion} is installed`;
getExecOutputSpy.mockImplementation(() => { getExecOutputSpy.mockImplementation(() => {
@@ -255,7 +302,7 @@ describe('installer tests', () => {
it(`should supply '-ProxyAddress' argument to the installation script if env.variable 'https_proxy' is set`, async () => { it(`should supply '-ProxyAddress' argument to the installation script if env.variable 'https_proxy' is set`, async () => {
process.env['https_proxy'] = 'https://proxy.com'; process.env['https_proxy'] = 'https://proxy.com';
const inputVersion = '10.0.101'; const inputVersion = '10.0.101';
const inputQuality = '' as QualityOptions; const inputQuality = '';
const stdout = `Fictitious dotnet version ${inputVersion} is installed`; const stdout = `Fictitious dotnet version ${inputVersion} is installed`;
getExecOutputSpy.mockImplementation(() => { getExecOutputSpy.mockImplementation(() => {
@@ -293,7 +340,7 @@ describe('installer tests', () => {
it(`should supply '-ProxyBypassList' argument to the installation script if env.variable 'no_proxy' is set`, async () => { 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'; process.env['no_proxy'] = 'first.url,second.url';
const inputVersion = '10.0.101'; const inputVersion = '10.0.101';
const inputQuality = '' as QualityOptions; const inputQuality = '';
const stdout = `Fictitious dotnet version ${inputVersion} is installed`; const stdout = `Fictitious dotnet version ${inputVersion} is installed`;
getExecOutputSpy.mockImplementation(() => { getExecOutputSpy.mockImplementation(() => {
@@ -331,7 +378,7 @@ describe('installer tests', () => {
it(`should supply 'architecture' argument to the installation script when architecture is provided`, async () => { it(`should supply 'architecture' argument to the installation script when architecture is provided`, async () => {
const inputVersion = '10.0.101'; const inputVersion = '10.0.101';
const inputQuality = '' as QualityOptions; const inputQuality = '';
const inputArchitecture = 'x64'; const inputArchitecture = 'x64';
const stdout = `Fictitious dotnet version ${inputVersion} is installed`; const stdout = `Fictitious dotnet version ${inputVersion} is installed`;
@@ -365,7 +412,7 @@ describe('installer tests', () => {
it(`should NOT supply 'architecture' argument when architecture is not provided`, async () => { it(`should NOT supply 'architecture' argument when architecture is not provided`, async () => {
const inputVersion = '10.0.101'; const inputVersion = '10.0.101';
const inputQuality = '' as QualityOptions; const inputQuality = '';
const stdout = `Fictitious dotnet version ${inputVersion} is installed`; const stdout = `Fictitious dotnet version ${inputVersion} is installed`;
getExecOutputSpy.mockImplementation(() => { getExecOutputSpy.mockImplementation(() => {
@@ -395,7 +442,7 @@ describe('installer tests', () => {
it(`should supply 'install-dir' with arch subdirectory for cross-arch install`, async () => { it(`should supply 'install-dir' with arch subdirectory for cross-arch install`, async () => {
const inputVersion = '10.0.101'; const inputVersion = '10.0.101';
const inputQuality = '' as QualityOptions; const inputQuality = '';
const inputArchitecture = 'x64'; const inputArchitecture = 'x64';
const stdout = `Fictitious dotnet version ${inputVersion} is installed`; const stdout = `Fictitious dotnet version ${inputVersion} is installed`;
@@ -436,7 +483,7 @@ describe('installer tests', () => {
it(`should NOT supply 'install-dir' when architecture matches runner's native arch`, async () => { it(`should NOT supply 'install-dir' when architecture matches runner's native arch`, async () => {
const inputVersion = '10.0.101'; const inputVersion = '10.0.101';
const inputQuality = '' as QualityOptions; const inputQuality = '';
const nativeArch = os.arch().toLowerCase(); const nativeArch = os.arch().toLowerCase();
const stdout = `Fictitious dotnet version ${inputVersion} is installed`; const stdout = `Fictitious dotnet version ${inputVersion} is installed`;

View File

@@ -0,0 +1,232 @@
import {afterEach, beforeEach, describe, expect, it, jest} from '@jest/globals';
jest.unstable_mockModule('@actions/http-client', () => ({
HttpClient: jest.fn()
}));
jest.unstable_mockModule('@actions/core', () => ({
warning: jest.fn(),
info: jest.fn(),
debug: jest.fn()
}));
const hc = await import('@actions/http-client');
const core = await import('@actions/core');
const {DotnetVersionResolver} = await import('../src/installer.js');
describe('DotnetVersionResolver with latest', () => {
let getJsonMock: jest.Mock<(...args: any[]) => Promise<any>>;
let warningSpy: jest.Mock;
beforeEach(() => {
getJsonMock = jest.fn<(...args: any[]) => Promise<any>>();
(hc.HttpClient as jest.Mock).mockImplementation(() => {
return {
getJson: getJsonMock
};
});
warningSpy = core.warning as jest.Mock;
warningSpy.mockImplementation(() => {});
});
afterEach(() => {
jest.clearAllMocks();
jest.restoreAllMocks();
});
const mockReleases = {
'releases-index': [
{
'channel-version': '10.0',
'support-phase': 'preview',
'release-type': 'lts'
},
{
'channel-version': '9.0',
'support-phase': 'active',
'release-type': 'sts'
},
{
'channel-version': '8.0',
'support-phase': 'active',
'release-type': 'lts'
},
{
'channel-version': '7.0',
'support-phase': 'eol',
'release-type': 'sts'
}
]
};
it('should resolve "latest" to highest stable version by default', async () => {
getJsonMock.mockResolvedValue({result: mockReleases});
const resolver = new DotnetVersionResolver('latest');
const version = await resolver.createDotnetVersion();
expect(version.value).toBe('9.0');
expect(version.type.toLowerCase()).toContain('channel');
expect(version.qualityFlag).toBe(true);
});
it('should resolve "LATEST" (uppercase) to highest stable version', async () => {
getJsonMock.mockResolvedValue({result: mockReleases});
const resolver = new DotnetVersionResolver('LATEST');
const version = await resolver.createDotnetVersion();
expect(version.value).toBe('9.0');
expect(version.type.toLowerCase()).toContain('channel');
expect(version.qualityFlag).toBe(true);
});
it('should resolve "latest" to highest preview version if quality is preview', async () => {
getJsonMock.mockResolvedValue({result: mockReleases});
const resolver = new DotnetVersionResolver('latest', 'preview');
const version = await resolver.createDotnetVersion();
expect(version.value).toBe('10.0');
});
it('should resolve "latest" with channel filter LTS', async () => {
getJsonMock.mockResolvedValue({result: mockReleases});
const resolver = new DotnetVersionResolver('latest', '', 'LTS');
const version = await resolver.createDotnetVersion();
expect(version.value).toBe('8.0');
});
it('should resolve "latest" with channel filter STS', async () => {
getJsonMock.mockResolvedValue({result: mockReleases});
const resolver = new DotnetVersionResolver('latest', '', 'STS');
const version = await resolver.createDotnetVersion();
expect(version.value).toBe('9.0');
});
it('should resolve "latest" with channel filter STS and preview quality', async () => {
getJsonMock.mockResolvedValue({result: mockReleases});
const resolver = new DotnetVersionResolver('latest', 'preview', 'STS');
const version = await resolver.createDotnetVersion();
// preview quality includes all support-phases; STS filter → 9.0 (active, sts)
expect(version.value).toBe('9.0');
});
it('should warn if channel is provided but version is not latest', async () => {
getJsonMock.mockResolvedValue({result: mockReleases});
const resolver = new DotnetVersionResolver('8.0', '', 'LTS');
await resolver.createDotnetVersion();
expect(warningSpy).toHaveBeenCalledWith(
`The 'dotnet-channel' input is only supported when 'dotnet-version' is set to 'latest'.`
);
});
it('should throw when releases-index API returns empty active releases', async () => {
const emptyReleases = {
'releases-index': [
{
'channel-version': '7.0',
'support-phase': 'eol',
'release-type': 'sts'
}
]
};
getJsonMock.mockResolvedValue({result: emptyReleases});
const resolver = new DotnetVersionResolver('latest');
await expect(resolver.createDotnetVersion()).rejects.toThrow(
/Could not find any active releases/
);
});
it('should throw when releases-index response has unexpected format', async () => {
getJsonMock.mockResolvedValue({result: {}});
const resolver = new DotnetVersionResolver('latest');
await expect(resolver.createDotnetVersion()).rejects.toThrow(
/Unexpected response format/
);
});
it('should throw when releases-index response is null', async () => {
getJsonMock.mockResolvedValue({result: null});
const resolver = new DotnetVersionResolver('latest');
await expect(resolver.createDotnetVersion()).rejects.toThrow(
/Unexpected response format/
);
});
it('should resolve "latest" with ga quality same as default (no previews)', async () => {
getJsonMock.mockResolvedValue({result: mockReleases});
const resolver = new DotnetVersionResolver('latest', 'ga');
const version = await resolver.createDotnetVersion();
// ga should behave like no quality — skip preview (10.0), pick 9.0
expect(version.value).toBe('9.0');
});
it('should resolve "latest" with LTS channel and daily quality', async () => {
getJsonMock.mockResolvedValue({result: mockReleases});
const resolver = new DotnetVersionResolver('latest', 'daily', 'LTS');
const version = await resolver.createDotnetVersion();
// daily allows previews, LTS filter applies — 10.0 (preview, lts) is the highest LTS
expect(version.value).toBe('10.0');
expect(version.qualityFlag).toBe(true);
});
it('should resolve "latest" with A.B channel directly without API call', async () => {
const resolver = new DotnetVersionResolver('latest', '', '8.0');
const version = await resolver.createDotnetVersion();
expect(version.value).toBe('8.0');
expect(version.type.toLowerCase()).toContain('channel');
expect(version.qualityFlag).toBe(true);
// Should NOT call the API
expect(getJsonMock).not.toHaveBeenCalled();
});
it('should resolve "latest" with A.B.Cxx channel directly without API call', async () => {
const resolver = new DotnetVersionResolver('latest', '', '8.0.1xx');
const version = await resolver.createDotnetVersion();
expect(version.value).toBe('8.0.1xx');
expect(version.type.toLowerCase()).toContain('channel');
expect(version.qualityFlag).toBe(true);
// Should NOT call the API
expect(getJsonMock).not.toHaveBeenCalled();
});
it('should resolve "latest" with A.B channel for older version with qualityFlag false', async () => {
const resolver = new DotnetVersionResolver('latest', '', '3.1');
const version = await resolver.createDotnetVersion();
expect(version.value).toBe('3.1');
expect(version.type.toLowerCase()).toContain('channel');
// major 3 < 6 → qualityFlag false
expect(version.qualityFlag).toBe(false);
expect(getJsonMock).not.toHaveBeenCalled();
});
it('should resolve "latest" with A.B.Cxx channel and quality', async () => {
const resolver = new DotnetVersionResolver('latest', 'ga', '8.0.2xx');
const version = await resolver.createDotnetVersion();
expect(version.value).toBe('8.0.2xx');
expect(version.qualityFlag).toBe(true);
expect(getJsonMock).not.toHaveBeenCalled();
});
});

View File

@@ -1,26 +1,71 @@
import * as core from '@actions/core'; import {
import fs from 'fs'; afterEach,
beforeEach,
describe,
expect,
it,
jest,
test
} from '@jest/globals';
import semver from 'semver'; import semver from 'semver';
import * as auth from '../src/authutil';
import os from 'os'; import os from 'os';
import * as setup from '../src/setup-dotnet';
import {DotnetCoreInstaller, DotnetInstallDir} from '../src/installer'; jest.unstable_mockModule('@actions/core', () => ({
import * as cacheUtils from '../src/cache-utils'; getInput: jest.fn(),
import * as cacheRestore from '../src/cache-restore'; getMultilineInput: jest.fn(),
getBooleanInput: jest.fn(),
setFailed: jest.fn(),
warning: jest.fn(),
debug: jest.fn(),
info: jest.fn(),
setOutput: jest.fn(),
addPath: jest.fn(),
exportVariable: jest.fn()
}));
jest.unstable_mockModule('fs', () => {
const actual = jest.requireActual('fs') as typeof import('fs');
const existsSync = jest.fn(actual.existsSync);
const readFileSync = jest.fn(actual.readFileSync);
return {
...actual,
existsSync,
readFileSync,
default: {...actual, existsSync, readFileSync}
};
});
jest.unstable_mockModule('../src/authutil', () => ({
configAuthentication: jest.fn()
}));
jest.unstable_mockModule('../src/cache-utils', () => ({
isCacheFeatureAvailable: jest.fn(),
getNuGetFolderPath: jest.fn()
}));
jest.unstable_mockModule('../src/cache-restore', () => ({
restoreCache: jest.fn()
}));
const core = await import('@actions/core');
const fs = await import('fs');
const auth = await import('../src/authutil.js');
const cacheUtils = await import('../src/cache-utils.js');
const cacheRestore = await import('../src/cache-restore.js');
const setup = await import('../src/setup-dotnet.js');
const {DotnetCoreInstaller, DotnetInstallDir} =
await import('../src/installer.js');
describe('setup-dotnet tests', () => { describe('setup-dotnet tests', () => {
const inputs = {} as any; const inputs = {} as any;
const getInputSpy = jest.spyOn(core, 'getInput'); const getInputSpy = core.getInput as jest.Mock;
const getMultilineInputSpy = jest.spyOn(core, 'getMultilineInput'); const getMultilineInputSpy = core.getMultilineInput as jest.Mock;
const getBooleanInputSpy = jest.spyOn(core, 'getBooleanInput'); const getBooleanInputSpy = core.getBooleanInput as jest.Mock;
const setFailedSpy = jest.spyOn(core, 'setFailed'); const setFailedSpy = core.setFailed as jest.Mock;
const warningSpy = jest.spyOn(core, 'warning'); const warningSpy = core.warning as jest.Mock;
const debugSpy = jest.spyOn(core, 'debug'); const debugSpy = core.debug as jest.Mock;
const infoSpy = jest.spyOn(core, 'info'); const infoSpy = core.info as jest.Mock;
const setOutputSpy = jest.spyOn(core, 'setOutput'); const setOutputSpy = core.setOutput as jest.Mock;
const existsSyncSpy = jest.spyOn(fs, 'existsSync'); const existsSyncSpy = fs.existsSync as jest.Mock;
const maxSatisfyingSpy = jest.spyOn(semver, 'maxSatisfying'); const maxSatisfyingSpy = jest.spyOn(semver, 'maxSatisfying');
@@ -29,12 +74,10 @@ describe('setup-dotnet tests', () => {
'installDotnet' 'installDotnet'
); );
const isCacheFeatureAvailableSpy = jest.spyOn( const isCacheFeatureAvailableSpy =
cacheUtils, cacheUtils.isCacheFeatureAvailable as jest.Mock;
'isCacheFeatureAvailable' const restoreCacheSpy = cacheRestore.restoreCache as jest.Mock;
); const configAuthenticationSpy = auth.configAuthentication as jest.Mock;
const restoreCacheSpy = jest.spyOn(cacheRestore, 'restoreCache');
const configAuthenticationSpy = jest.spyOn(auth, 'configAuthentication');
const addToPathOriginal = DotnetInstallDir.addToPath; const addToPathOriginal = DotnetInstallDir.addToPath;
describe('run() tests', () => { describe('run() tests', () => {
@@ -84,7 +127,7 @@ describe('setup-dotnet tests', () => {
inputs['dotnet-version'] = ['10.0']; inputs['dotnet-version'] = ['10.0'];
inputs['dotnet-quality'] = 'fictitiousQuality'; 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.`; const expectedErrorMessage = `Value '${inputs['dotnet-quality']}' is not supported for the 'dotnet-quality' option. Supported values are: daily, preview, ga.`;
await setup.run(); await setup.run();
expect(setFailedSpy).toHaveBeenCalledWith(expectedErrorMessage); expect(setFailedSpy).toHaveBeenCalledWith(expectedErrorMessage);
@@ -256,5 +299,95 @@ describe('setup-dotnet tests', () => {
await setup.run(); await setup.run();
expect(setFailedSpy).toHaveBeenCalledWith(expectedErrorMessage); expect(setFailedSpy).toHaveBeenCalledWith(expectedErrorMessage);
}); });
it('should fail the action if unsupported dotnet-channel value is provided with latest', async () => {
inputs['dotnet-version'] = ['latest'];
inputs['dotnet-quality'] = '';
inputs['dotnet-channel'] = 'invalid';
inputs['architecture'] = '';
const expectedErrorMessage = `Value 'invalid' is not supported for the 'dotnet-channel' option. Supported values are: LTS, STS, A.B (e.g. 8.0), A.B.Cxx (e.g. 8.0.1xx).`;
await setup.run();
expect(setFailedSpy).toHaveBeenCalledWith(expectedErrorMessage);
});
it('should warn but not fail if unsupported dotnet-channel value is provided with a specific version', async () => {
inputs['dotnet-version'] = ['8.0.x'];
inputs['dotnet-quality'] = '';
inputs['dotnet-channel'] = 'invalid';
inputs['architecture'] = '';
installDotnetSpy.mockImplementation(() => Promise.resolve(''));
await setup.run();
expect(setFailedSpy).not.toHaveBeenCalled();
expect(warningSpy).toHaveBeenCalledWith(
`Value 'invalid' is not supported for the 'dotnet-channel' option and will be ignored because 'dotnet-version' is not set to 'latest'. Supported values are: LTS, STS, A.B (e.g. 8.0), A.B.Cxx (e.g. 8.0.1xx).`
);
});
it('should pass valid dotnet-channel value through without error', async () => {
inputs['dotnet-version'] = ['latest'];
inputs['dotnet-quality'] = '';
inputs['dotnet-channel'] = 'LTS';
inputs['architecture'] = '';
installDotnetSpy.mockImplementation(() => Promise.resolve(''));
await setup.run();
expect(setFailedSpy).not.toHaveBeenCalled();
});
it('should pass A.B channel value through without error when used with latest', async () => {
inputs['dotnet-version'] = ['latest'];
inputs['dotnet-quality'] = '';
inputs['dotnet-channel'] = '8.0';
inputs['architecture'] = '';
installDotnetSpy.mockImplementation(() => Promise.resolve(''));
await setup.run();
expect(setFailedSpy).not.toHaveBeenCalled();
});
it('should pass A.B.Cxx channel value through without error when used with latest', async () => {
inputs['dotnet-version'] = ['latest'];
inputs['dotnet-quality'] = '';
inputs['dotnet-channel'] = '8.0.1xx';
inputs['architecture'] = '';
installDotnetSpy.mockImplementation(() => Promise.resolve(''));
await setup.run();
expect(setFailedSpy).not.toHaveBeenCalled();
});
it('should fail with A.B.Cxx channel if major version is below 5', async () => {
inputs['dotnet-version'] = ['latest'];
inputs['dotnet-quality'] = '';
inputs['dotnet-channel'] = '3.1.1xx';
inputs['architecture'] = '';
const expectedErrorMessage = `Value '3.1.1xx' is not supported for the 'dotnet-channel' option. Supported values are: LTS, STS, A.B (e.g. 8.0), A.B.Cxx (e.g. 8.0.1xx).`;
await setup.run();
expect(setFailedSpy).toHaveBeenCalledWith(expectedErrorMessage);
});
it('should warn and not fail if valid dotnet-channel is provided with a non-latest version', async () => {
inputs['dotnet-version'] = ['8.0.x'];
inputs['dotnet-quality'] = '';
inputs['dotnet-channel'] = 'LTS';
inputs['architecture'] = '';
installDotnetSpy.mockImplementation(() => Promise.resolve(''));
await setup.run();
expect(setFailedSpy).not.toHaveBeenCalled();
expect(warningSpy).toHaveBeenCalledWith(
`The 'dotnet-channel' input is only supported when 'dotnet-version' is set to 'latest'.`
);
});
}); });
}); });

View File

@@ -6,17 +6,26 @@ 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, 6.0.2xx' description: 'Optional SDK version(s) to use. If not provided, will install global.json version when available. Examples: 2.2.104, 3.1, 3.1.x, 3.x, 6.0.2xx, latest'
required: false
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, preview, ga.'
required: false
dotnet-channel:
description: 'Optional channel for the installation. The possible values are: STS, LTS, A.B (e.g. 8.0), A.B.Cxx (e.g. 8.0.1xx, available since 5.0). To be used with "dotnet-version: latest".'
required: false
global-json-file: global-json-file:
description: 'Optional global.json location, if your global.json isn''t located in the root of the repo.' description: 'Optional global.json location, if your global.json isn''t located in the root of the repo.'
required: false
source-url: source-url:
description: 'Optional package source for which to set up authentication. Will consult any existing NuGet.config in the root of the repo and provide a temporary NuGet.config using the NUGET_AUTH_TOKEN environment variable as a ClearTextPassword' description: 'Optional package source for which to set up authentication. Will consult any existing NuGet.config in the root of the repo and provide a temporary NuGet.config using the NUGET_AUTH_TOKEN environment variable as a ClearTextPassword'
required: false
owner: owner:
description: 'Optional OWNER for using packages from GitHub Package Registry organizations/users other than the current repository''s owner. Only used if a GPR URL is also provided in source-url' description: 'Optional OWNER for using packages from GitHub Package Registry organizations/users other than the current repository''s owner. Only used if a GPR URL is also provided in source-url'
required: false
config-file: config-file:
description: 'Optional NuGet.config location, if your NuGet.config isn''t located in the root of the repo.' description: 'Optional NuGet.config location, if your NuGet.config isn''t located in the root of the repo.'
required: false
cache: cache:
description: 'Optional input to enable caching of the NuGet global-packages folder' description: 'Optional input to enable caching of the NuGet global-packages folder'
required: false required: false
@@ -39,4 +48,4 @@ runs:
using: 'node24' using: 'node24'
main: 'dist/setup/index.js' main: 'dist/setup/index.js'
post: 'dist/cache-save/index.js' post: 'dist/cache-save/index.js'
post-if: success() post-if: success()

130361
dist/cache-save/index.js vendored

File diff suppressed because one or more lines are too long

3
dist/cache-save/package.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"type": "module"
}

142158
dist/setup/index.js vendored

File diff suppressed because one or more lines are too long

3
dist/setup/package.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"type": "module"
}

74
eslint.config.mjs Normal file
View File

@@ -0,0 +1,74 @@
// 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.
import js from '@eslint/js';
import tsParser from '@typescript-eslint/parser';
import tsPlugin from '@typescript-eslint/eslint-plugin';
import jest from 'eslint-plugin-jest';
import n from 'eslint-plugin-n';
import prettier from 'eslint-config-prettier';
import globals from 'globals';
export default [
{
ignores: ['**/*', '!src/**', '!__tests__/**']
},
js.configs.recommended,
{
files: ['**/*.ts'],
languageOptions: {
parser: tsParser,
ecmaVersion: 2022,
sourceType: 'module',
globals: {
...globals.node,
...globals.es2015
}
},
plugins: {
'@typescript-eslint': tsPlugin,
n
},
rules: {
...tsPlugin.configs.recommended.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}],
'no-undef': 'off',
'no-useless-assignment': 'off',
'n/no-extraneous-import': 'error'
}
},
{
files: ['**/*{test,spec}.ts'],
plugins: {jest},
languageOptions: {
globals: {
...globals.jest
}
},
rules: {
...jest.configs['flat/recommended'].rules,
'@typescript-eslint/no-unused-vars': 'off',
'jest/no-standalone-expect': 'off',
'jest/no-conditional-expect': 'off',
'no-console': 'off'
}
},
prettier
];

View File

@@ -94,9 +94,9 @@
Determines timeout duration in seconds for downloading of the SDK file Determines timeout duration in seconds for downloading of the SDK file
Default: 1200 seconds (20 minutes) Default: 1200 seconds (20 minutes)
.PARAMETER KeepZip .PARAMETER KeepZip
If set, downloaded file is kept If set, downloaded archive file is kept. Applies to both .zip and .tar.gz formats.
.PARAMETER ZipPath .PARAMETER ZipPath
Use that path to store installer, generated by default Use that path to store the downloaded archive, generated by default. Applies to both .zip and .tar.gz formats.
.EXAMPLE .EXAMPLE
dotnet-install.ps1 -Version 7.0.401 dotnet-install.ps1 -Version 7.0.401
Installs the .NET SDK version 7.0.401 Installs the .NET SDK version 7.0.401
@@ -341,6 +341,36 @@ function Get-NormalizedProduct([string]$Runtime) {
} }
} }
function Test-TarAvailable {
if ($env:DOTNET_INSTALL_SKIP_TAR -eq "1") {
Say-Verbose "Skipping tar detection due to DOTNET_INSTALL_SKIP_TAR environment variable."
return $false
}
$tarCommand = Get-Command -Name "tar" -ErrorAction SilentlyContinue
return $null -ne $tarCommand
}
function Get-FileExtension-For-Version([string]$VersionOrChannel) {
if (-not $script:TarAvailable) {
return ".zip"
}
if ($VersionOrChannel -match '^(\d+)\.') {
$majorVersion = [int]$Matches[1]
if ($majorVersion -ge 11) {
# Windows tarballs are only available starting with 11.0 preview 3
if ($VersionOrChannel -match 'preview\.(\d+)') {
$previewNum = [int]$Matches[1]
if ($majorVersion -eq 11 -and $previewNum -lt 3) {
Say-Verbose "Version '$VersionOrChannel' predates tar.gz availability; using zip."
return ".zip"
}
}
Say-Verbose "Using tar.gz archive format for version/channel '$VersionOrChannel'."
return ".tar.gz"
}
}
return ".zip"
}
# The version text returned from the feeds is a 1-line or 2-line string: # The version text returned from the feeds is a 1-line or 2-line string:
# For the SDK and the dotnet runtime (2 lines): # For the SDK and the dotnet runtime (2 lines):
@@ -594,24 +624,27 @@ function Get-Download-Link([string]$AzureFeed, [string]$SpecificVersion, [string
# If anything fails in this lookup it will default to $SpecificVersion # If anything fails in this lookup it will default to $SpecificVersion
$SpecificProductVersion = Get-Product-Version -AzureFeed $AzureFeed -SpecificVersion $SpecificVersion $SpecificProductVersion = Get-Product-Version -AzureFeed $AzureFeed -SpecificVersion $SpecificVersion
# For .NET 11.0+, tar.gz is the preferred archive format on Windows
$ext = Get-FileExtension-For-Version $SpecificVersion
if ($Runtime -eq "dotnet") { if ($Runtime -eq "dotnet") {
$PayloadURL = "$AzureFeed/Runtime/$SpecificVersion/dotnet-runtime-$SpecificProductVersion-win-$CLIArchitecture.zip" $PayloadURL = "$AzureFeed/Runtime/$SpecificVersion/dotnet-runtime-$SpecificProductVersion-win-$CLIArchitecture$ext"
} }
elseif ($Runtime -eq "aspnetcore") { elseif ($Runtime -eq "aspnetcore") {
$PayloadURL = "$AzureFeed/aspnetcore/Runtime/$SpecificVersion/aspnetcore-runtime-$SpecificProductVersion-win-$CLIArchitecture.zip" $PayloadURL = "$AzureFeed/aspnetcore/Runtime/$SpecificVersion/aspnetcore-runtime-$SpecificProductVersion-win-$CLIArchitecture$ext"
} }
elseif ($Runtime -eq "windowsdesktop") { elseif ($Runtime -eq "windowsdesktop") {
# The windows desktop runtime is part of the core runtime layout prior to 5.0 # The windows desktop runtime is part of the core runtime layout prior to 5.0
$PayloadURL = "$AzureFeed/Runtime/$SpecificVersion/windowsdesktop-runtime-$SpecificProductVersion-win-$CLIArchitecture.zip" $PayloadURL = "$AzureFeed/Runtime/$SpecificVersion/windowsdesktop-runtime-$SpecificProductVersion-win-$CLIArchitecture$ext"
if ($SpecificVersion -match '^(\d+)\.(.*)$') { if ($SpecificVersion -match '^(\d+)\.(.*)$') {
$majorVersion = [int]$Matches[1] $majorVersion = [int]$Matches[1]
if ($majorVersion -ge 5) { if ($majorVersion -ge 5) {
$PayloadURL = "$AzureFeed/WindowsDesktop/$SpecificVersion/windowsdesktop-runtime-$SpecificProductVersion-win-$CLIArchitecture.zip" $PayloadURL = "$AzureFeed/WindowsDesktop/$SpecificVersion/windowsdesktop-runtime-$SpecificProductVersion-win-$CLIArchitecture$ext"
} }
} }
} }
elseif (-not $Runtime) { elseif (-not $Runtime) {
$PayloadURL = "$AzureFeed/Sdk/$SpecificVersion/dotnet-sdk-$SpecificProductVersion-win-$CLIArchitecture.zip" $PayloadURL = "$AzureFeed/Sdk/$SpecificVersion/dotnet-sdk-$SpecificProductVersion-win-$CLIArchitecture$ext"
} }
else { else {
throw "Invalid value for `$Runtime" throw "Invalid value for `$Runtime"
@@ -877,6 +910,80 @@ function Extract-Dotnet-Package([string]$ZipPath, [string]$OutPath) {
} }
} }
function Extract-Dotnet-Package-Tar([string]$TarPath, [string]$OutPath) {
Say-Invocation $MyInvocation
New-Item -ItemType Directory -Force -Path $OutPath | Out-Null
# Build an exclude list of versioned directories that already exist locally.
# This matches the zip extraction behavior: if shared/Microsoft.NETCore.App/11.0.0/
# already exists, we skip all files under that version to avoid overwriting a
# previously installed version.
$excludeArgs = @()
$versionRegex = '.*/\d+\.\d+[^/]+/'
# Read the tarball's file listing to discover versioned directory prefixes
$tarListing = & tar -tzf $TarPath 2>$null
$versionedDirs = @{}
foreach ($entry in $tarListing) {
$normalizedEntry = $entry.Replace('\', '/')
$match = [regex]::Match($normalizedEntry, $versionRegex)
if ($match.Success) {
$versionedPrefix = $normalizedEntry.Substring(0, $match.Index + $match.Length)
if (-not $versionedDirs.ContainsKey($versionedPrefix)) {
$localDir = Join-Path -Path $OutPath -ChildPath $versionedPrefix
$versionedDirs[$versionedPrefix] = (Test-Path $localDir -PathType Container)
}
}
}
foreach ($dir in $versionedDirs.GetEnumerator()) {
if ($dir.Value) {
# This versioned directory already exists — exclude it from extraction
$pattern = $dir.Key + '*'
$excludeArgs += '--exclude'
$excludeArgs += $pattern
Say-Verbose "Excluding pre-existing versioned directory: $($dir.Key)"
}
}
# Handle non-versioned file override logic.
# If OverrideNonVersionedFiles is false, exclude non-versioned files that already exist.
if (-not $OverrideNonVersionedFiles) {
foreach ($entry in $tarListing) {
$normalizedEntry = $entry.Replace('\', '/')
# Skip directory entries (end with /)
if ($normalizedEntry.EndsWith('/')) { continue }
$match = [regex]::Match($normalizedEntry, $versionRegex)
if (-not $match.Success) {
# Non-versioned file — exclude if it already exists locally
$localPath = Join-Path -Path $OutPath -ChildPath $normalizedEntry
if (Test-Path $localPath) {
$excludeArgs += '--exclude'
$excludeArgs += $normalizedEntry
Say-Verbose "Excluding pre-existing non-versioned file: $normalizedEntry"
}
}
}
}
try {
# Extract directly to install root, preserving hard links
$tarOutput = & tar -xzf $TarPath -C $OutPath @excludeArgs 2>&1
if ($LASTEXITCODE -ne 0) {
$tarOutputText = ($tarOutput | Out-String).Trim()
if ([string]::IsNullOrWhiteSpace($tarOutputText)) {
throw "Tar extraction failed with exit code $LASTEXITCODE."
}
throw "Tar extraction failed with exit code $LASTEXITCODE. tar output: $tarOutputText"
}
}
catch {
Say-Error "Failed to extract tar package. Exception: $_"
throw
}
}
function DownloadFile($Source, [string]$OutPath) { function DownloadFile($Source, [string]$OutPath) {
if ($Source -notlike "http*") { if ($Source -notlike "http*") {
# Using System.IO.Path.GetFullPath to get the current directory # Using System.IO.Path.GetFullPath to get the current directory
@@ -991,7 +1098,7 @@ function PrintDryRunOutput($Invocation, $DownloadLinks) {
} }
} }
function Get-AkaMSDownloadLink([string]$Channel, [string]$Quality, [bool]$Internal, [string]$Product, [string]$Architecture) { function Get-AkaMSDownloadLink([string]$Channel, [string]$Quality, [bool]$Internal, [string]$Product, [string]$Architecture, [string]$FileExtension = ".zip") {
Say-Invocation $MyInvocation Say-Invocation $MyInvocation
#quality is not supported for LTS or STS channel #quality is not supported for LTS or STS channel
@@ -1010,7 +1117,7 @@ function Get-AkaMSDownloadLink([string]$Channel, [string]$Quality, [bool]$Intern
if (-not [string]::IsNullOrEmpty($Quality)) { if (-not [string]::IsNullOrEmpty($Quality)) {
$akaMsLink += "/$Quality" $akaMsLink += "/$Quality"
} }
$akaMsLink += "/$Product-win-$Architecture.zip" $akaMsLink += "/$Product-win-$Architecture$FileExtension"
Say-Verbose "Constructed aka.ms link: '$akaMsLink'." Say-Verbose "Constructed aka.ms link: '$akaMsLink'."
$akaMsDownloadLink = $null $akaMsDownloadLink = $null
@@ -1062,7 +1169,23 @@ function Get-AkaMSDownloadLink([string]$Channel, [string]$Quality, [bool]$Intern
} }
function Get-AkaMsLink-And-Version([string] $NormalizedChannel, [string] $NormalizedQuality, [bool] $Internal, [string] $ProductName, [string] $Architecture) { function Get-AkaMsLink-And-Version([string] $NormalizedChannel, [string] $NormalizedQuality, [bool] $Internal, [string] $ProductName, [string] $Architecture) {
$AkaMsDownloadLink = Get-AkaMSDownloadLink -Channel $NormalizedChannel -Quality $NormalizedQuality -Internal $Internal -Product $ProductName -Architecture $Architecture
# When tar is available, try .tar.gz first via aka.ms, then fall back to .zip.
# This handles symbolic channels (STS/LTS) and numeric channels where tar.gz
# may not yet be available (e.g. pre-11.0 versions).
$extensionsToTry = @(".zip")
if ($script:TarAvailable) {
$extensionsToTry = @(".tar.gz", ".zip")
}
$AkaMsDownloadLink = $null
foreach ($ext in $extensionsToTry) {
$AkaMsDownloadLink = Get-AkaMSDownloadLink -Channel $NormalizedChannel -Quality $NormalizedQuality -Internal $Internal -Product $ProductName -Architecture $Architecture -FileExtension $ext
if (-not [string]::IsNullOrEmpty($AkaMsDownloadLink)) {
break
}
Say-Verbose "aka.ms link resolution with '$ext' extension failed, trying next format."
}
if ([string]::IsNullOrEmpty($AkaMsDownloadLink)) { if ([string]::IsNullOrEmpty($AkaMsDownloadLink)) {
if (-not [string]::IsNullOrEmpty($NormalizedQuality)) { if (-not [string]::IsNullOrEmpty($NormalizedQuality)) {
@@ -1191,6 +1314,8 @@ Measure-Action "Product discovery" {
$script:NormalizedProduct = Get-NormalizedProduct $Runtime $script:NormalizedProduct = Get-NormalizedProduct $Runtime
Say-Verbose "Normalized product: '$NormalizedProduct'" Say-Verbose "Normalized product: '$NormalizedProduct'"
$script:FeedCredential = ValidateFeedCredential $FeedCredential $script:FeedCredential = ValidateFeedCredential $FeedCredential
$script:TarAvailable = Test-TarAvailable
Say-Verbose "Tar available: '$TarAvailable'"
} }
$InstallRoot = Resolve-Installation-Path $InstallDir $InstallRoot = Resolve-Installation-Path $InstallDir
@@ -1319,7 +1444,12 @@ if (-not $DownloadSucceeded) {
} }
Say "Extracting the archive." Say "Extracting the archive."
Measure-Action "Package extraction" { Extract-Dotnet-Package -ZipPath $ZipPath -OutPath $InstallRoot } if ($DownloadedLink.downloadLink.EndsWith(".tar.gz")) {
Measure-Action "Package extraction" { Extract-Dotnet-Package-Tar -TarPath $ZipPath -OutPath $InstallRoot }
}
else {
Measure-Action "Package extraction" { Extract-Dotnet-Package -ZipPath $ZipPath -OutPath $InstallRoot }
}
# 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
@@ -1355,219 +1485,215 @@ Say "Installed version is $($DownloadedLink.effectiveVersion)"
Say "Installation finished" Say "Installation finished"
# SIG # Begin signature block # SIG # Begin signature block
# MIIoKgYJKoZIhvcNAQcCoIIoGzCCKBcCAQExDzANBglghkgBZQMEAgEFADB5Bgor # MIIncQYJKoZIhvcNAQcCoIInYjCCJ14CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCyKn7B6ieM6Y2C # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDmNI7+9b4lSWsX
# rr9TCFvTSv2mMIh9mBGXh4z2gOksEqCCDXYwggX0MIID3KADAgECAhMzAAAEhV6Z # 2kYVvqQHpZeXB6Kt5BepNOcmIqEK1aCCDMkwggYEMIID7KADAgECAhMzAAACHPrN
# 7A5ZL83XAAAAAASFMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # xZvoL37EAAAAAAIcMA0GCSqGSIb3DQEBCwUAMFcxCzAJBgNVBAYTAlVTMR4wHAYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBD
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # b2RlIFNpZ25pbmcgUENBIDIwMjQwHhcNMjYwNDE2MTg1OTQxWhcNMjcwNDE1MTg1
# bmcgUENBIDIwMTEwHhcNMjUwNjE5MTgyMTM3WhcNMjYwNjE3MTgyMTM3WjB0MQsw # OTQxWjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYD
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # VQQDExVNaWNyb3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IB
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # DwAwggEKAoIBAQDVsZfgOKmM31HPfoWOoNEiw0SlCiIxUMC0I9NMWbucKOw/e9lP
# AQDASkh1cpvuUqfbqxele7LCSHEamVNBfFE4uY1FkGsAdUF/vnjpE1dnAD9vMOqy # oAoehQVu6SG65V4EPzrYsnBnFPNoi4/HoOdjhz1qkrEt4I6tEcxXU6oOeY9zGveC
# 5ZO49ILhP4jiP/P2Pn9ao+5TDtKmcQ+pZdzbG7t43yRXJC3nXvTGQroodPi9USQi # /3iBeuhLYxM3M/PkcUoebF+Nednm8OkdSPoDu8imViHPQq/8CQUu0WRR4rE+dMRf
# 9rI+0gwuXRKBII7L+k3kMkKLmFrsWUjzgXVCLYa6ZH7BCALAcJWZTwWPoiT4HpqQ # rpVqfmNi2qWCX94T4MsepijGVkwE//tJg0ryAiYdHT34LSnlG/RSBZmQRGWZ5g8j
# hJcYLB7pfetAVCeBEVZD8itKQ6QA5/LQR+9X6dlSj4Vxta4JnpxvgSrkjXCz+tlJ # qnKjRParSqMft1gvjuUTVgtWNZfgcLFSK5Wa0myrq8OPcgTGGsRgun+tnSS+IxDT
# 67ABZ551lw23RWU1uyfgCfEFhBfiyPR2WSjskPl9ap6qrf8fNQ1sGYun2p4JdXxe # xVsAPH1OzvPjwomguByhUe/OcvUN0D5Wmp7xAgMBAAGjggGqMIIBpjAOBgNVHQ8B
# UAKf1hVa/3TQXjvPTiRXCnJPAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE # Af8EBAMCB4AwHwYDVR0lBBgwFgYKKwYBBAGCN0wIAQYIKwYBBQUHAwMwHQYDVR0O
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUuCZyGiCuLYE0aU7j5TFqY05kko0w # BBYEFNoH7a2YDjOSwpkp6DHcmUS7J+0yMFQGA1UdEQRNMEukSTBHMS0wKwYDVQQL
# RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW # EyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxFjAUBgNVBAUT
# MBQGA1UEBRMNMjMwMDEyKzUwNTM1OTAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci # DTIzMDAxMis1MDc1NjkwHwYDVR0jBBgwFoAUf1k/VCHarU/vBeXmo9ctBpQSCDEw
# tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j # YAYDVR0fBFkwVzBVoFOgUYZPaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9w
# b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG # cy9jcmwvTWljcm9zb2Z0JTIwQ29kZSUyMFNpZ25pbmclMjBQQ0ElMjAyMDI0LmNy
# CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu # bDBtBggrBgEFBQcBAQRhMF8wXQYIKwYBBQUHMAKGUWh0dHA6Ly93d3cubWljcm9z
# Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0 # b2Z0LmNvbS9wa2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwQ29kZSUyMFNpZ25pbmcl
# MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBACjmqAp2Ci4sTHZci+qk # MjBQQ0ElMjAyMDI0LmNydDAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4IC
# tEAKsFk5HNVGKyWR2rFGXsd7cggZ04H5U4SV0fAL6fOE9dLvt4I7HBHLhpGdE5Uj # AQAUnEqhaRXe0T3hIJjvdQErEkrA/7bByjn6t5IArODkkRjzkYwtKMc2yYj2quaN
# Ly4NxLTG2bDAkeAVmxmd2uKWVGKym1aarDxXfv3GCN4mRX+Pn4c+py3S/6Kkt5eS # rLutWw2YZcngKPy1b71YyDJQTy4NDRwaSh9Tw5thrk3NmcPrAHia5vtcBJ1CgtKK
# DAIIsrzKw3Kh2SW1hCwXX/k1v4b+NH1Fjl+i/xPJspXCFuZB4aC5FLT5fgbRKqns # 7mQbIcQ22d/N3813ayCDDFewu1+jsZmX+r/aTEqaOM4TVxVtRSkuCy8nAXKuChOK
# WeAdn8DsrYQhT3QXLt6Nv3/dMzv7G/Cdpbdcoul8FYl+t3dmXM+SIClC3l2ae0wO # Li/zA4XuH8iEYqIsj2YoNaeSxVmeGiERXpKdo3dDmYi0kO5w2D8VS4c3+9h6gElY
# lNrQ42yQEycuPU5OoqLT85jsZ7+4CaScfFINlO7l7Y7r/xauqHbSPQ1r3oIC+e71 # BaAAg/dYErBg27qT3vv0zRDJhJufvCNylA8S7/+8H5E/PV5cng6na9VV/w9OV3qu
# 5s2G3ClZa3y99aYx2lnXYe1srcrIx8NAXTViiypXVn9ZGmEkfNcfDiqGQwkml5z9 # uND6zdGa2EX38Glp50F9AIQk3p2xXmcvorDeM4XJ7UlWYBi6g80J1SSOQnInCYFE
# nm3pWiBZ69adaBBbAFEjyJG4y0a76bel/4sDCVvaZzLM3TFbxVO9BQrjZRtbJZbk # msfUNn3+1AaTJKSJL83quKArTac2pKhu0Yzzzrzo6HrsRiQKzpnRBb1/dMa6P3hz
# C3XArpLqZSfx53SuYdddxPX8pvcqFuEu8wcUeD05t9xNbJ4TtdAECJlEi0vvBxlm # 75XbMRBctNsFhZC07WCmjExdLg2eHW5uV0TY8D5+6wozJf7vF3+WHkYPO85Z+BC6
# M5tzFXy2qZeqPMXHSQYqPgZ9jvScZ6NwznFD0+33kbzyhOSz/WuGbAu4cHZG8gKn # U4FkNbYNycZ9cE4j1tXRdyDCfml6c0HWPHjNVDObrv9lKt3qUqFpX38VCqVCyNOO
# lQVT4uA2Diex9DMs2WHiokNknYlLoUeWXW1QrJLpqO82TLyKTbBM/oZHAdIc0kzo # 1UcXfQiVjJw32U2WUKZjt/neJKHEBsm9kFsLuWzkQ53+qcaSaytmsCnk2gOglrlD
# STro9b3+vjn2809D0+SOOCVZMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq # 5d3kKyvvAw+rzm0lT8K38P6PLxfZQHhu4W8dV7Av8N2ZmDCCBr0wggSloAMCAQIC
# hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x # EzMAAAA5O7Y3Gb8GHWcAAAAAADkwDQYJKoZIhvcNAQEMBQAwgYgxCzAJBgNVBAYT
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv # AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD
# bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 # VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBS
# IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG # b290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDExMB4XDTI0MDgwODIwNTQxOFoX
# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG # DTM2MDMyMjIyMTMwNFowVzELMAkGA1UEBhMCVVMxHjAcBgNVBAoTFU1pY3Jvc29m
# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg # dCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9zb2Z0IENvZGUgU2lnbmluZyBQ
# Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC # Q0EgMjAyNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANgBnB7jOMeq
# CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03 # lRYHNa265v4IY9fH8TKhemHfPINe1gpLaV3dhg324WwH06LcHbpnsBukCDNitryo
# a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr # 0dtS/EW6I/yEL/bLSY8hKpbfQuWusBPr9qazYcDxCW/qnjb5JsI1s8bNOg3bVATv
# rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg # QVL4tcf03aTycsz8QeCdM0l/yHRObJ9QqazM1r6VPEOJ7LL+uEEb73w6QCuhs89a
# OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy # 1uv1zerOYMnsneRRwCbpyW11IcggU0cRKDDq1pjVJzIbIF6+oiXXbReOsgeI8zu1
# 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9 # FyQfK0fVkaya8SmVHQ/tOf23mZ4W9k0Ri22QW9p3UgSC5OUDktKxxcCmGL6tXLfO
# sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh # GSWHIIV4YrTJTT6PNty5REojHJuZHArkF9VnHTERWoTjAzfI3kP+5b4alUdhgAZ7
# dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k # ttOu1bVnXfHaqPYl2rPs20ji03LOVWsh/radgE17es5hL+t6lV0eVHrVhsssROWJ
# A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB # uz2MXMCt7iw7lFPG9LXKGjsmonn2gotGdHIuEg5JnJMJVmixd5LRlkmgYRZKzhxS
# w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn # CwyoGIq0PhaA7Y+VPct5pCHkijcIIDm0nlkK+0KyepolcqGm0T/GYQRMhHJlGOOm
# Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90 # VQop36wUVUYklUy++vDWeEgEo4s7hxN6mIbf2MSIQ/iIfMZgJxC69oukMUXCrOC3
# lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w # SkE/xIkgpfl22MM1itkZ35nNXkMolU1lAgMBAAGjggFOMIIBSjAOBgNVHQ8BAf8E
# ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o # BAMCAYYwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFH9ZP1Qh2q1P7wXl5qPX
# ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD # LQaUEggxMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMA8GA1UdEwEB/wQFMAMB
# VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa # Af8wHwYDVR0jBBgwFoAUci06AjGQQ7kUBU7h6qfHMdEjiTQwWgYDVR0fBFMwUTBP
# BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny # oE2gS4ZJaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMv
# bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG # TWljUm9vQ2VyQXV0MjAxMV8yMDExXzAzXzIyLmNybDBeBggrBgEFBQcBAQRSMFAw
# AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t # TgYIKwYBBQUHMAKGQmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMv
# L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV # TWljUm9vQ2VyQXV0MjAxMV8yMDExXzAzXzIyLmNydDANBgkqhkiG9w0BAQwFAAOC
# HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3 # AgEAFJQfOChP7onn6fLIMKrSlN1WYKwDFgAddymOUO3FrM8d7B/W/iQ6DxXsDn7D
# dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG # 5W4wMwYeLystcEqfkjz4NURRgazyMu5yRzQh4LqjA4tStTcJh1opExo7nn5PuPBY
# AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl # nbu0+THSuVHTe0VTTPVhily/piFrDo3axQ9P4C+Ol5yet+2gTfekICS5xS+cYfSI
# AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb # vgn0JksVBVMYVI5QFu/qhnLhsEFEUzG8fvv0hjgkO+lkpV9ty6GkN4vdnd7ya6Q6
# C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l # aR9y34aiM1qmxaxBi6OUnyNl6fkuun/diTFnYDLTppOkr/mg5WSfCiDVMNCxtj4w
# hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6 # PKC5OmHm1DQIt/MNokbbH3UGsFP1QbzsLocuSqLCvH09Io3fDPTmscR9Y75G4qX7
# I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0 # RTX8AdBPo0I6OEojf39zuFZt0qOHm65YWQE69cZM2ueE1MB05dNNgHK9gTE7zKvK
# wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560 # /fg8B2qjW88MT/WF5V5uvZGtqa9FSL2RazArA+rDPuf6JGYz4HpgMZHB4S6szWSK
# STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam # YBv0VisCzfxgeU+dquXW9bd0auYlOB58DPcOYKdc3Se94g+xL4pcEhbB54JOgAkw
# ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa # YTu/9dLeH2pDqeJZAABVDWRQCaXfO5LgyKwKCLYXpigrZYCjUSBcr+Ve8PFWMhVT
# J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah # Ql0v4q8J/AUmQN5W4n101cY2L4A7GTQG1h32HHAvfQESWP0xghn+MIIZ+gIBATBu
# XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA # MFcxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x
# 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt # KDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMjQCEzMAAAIc
# Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr # +s3Fm+gvfsQAAAAAAhwwDQYJYIZIAWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwG
# /Xmfwb1tbWrJUnMTDXpQzTGCGgowghoGAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw # CisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZI
# hvcNAQkEMSIEIB5Na9P++kVnSeIy27N4IyRD4N0sjfWHRS1ht0NHdoK/MEIGCisG
# AQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3
# Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEBBQAEggEAx05EI+Kk4hoAKG/8oVdr
# UIq/+sv+E0vpSyWQMTq/yrkfnPWqT2TxNVtcG3ZcQT5/36cjMe1umi1zzy178VRP
# HKH2jxMOzMqtJPsAI/yAd46EOyTBwayS+27NOvz2r3v8WV0YjuplJb1teHmJtoIf
# UVC1va6H668UMrR2Mm5l8GvtcrGmDLjq3FLTLeidco7aj7xtkwUhyXvCS/HzoF9G
# yrYW3NKqIy+KbNXTD8RbwDxKvx3ED9C1SfZi3fi/t1a2wjNsxVqf4XNsO2+JvuuO
# SGTZOoo/3jqtS22IcV5Z3In0D3+C/rigq0179KhwPtztz4+J9MU5gtGmVayF1MH1
# M6GCF7AwghesBgorBgEEAYI3AwMBMYIXnDCCF5gGCSqGSIb3DQEHAqCCF4kwgheF
# AgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFaBgsqhkiG9w0BCRABBKCCAUkEggFFMIIB
# QQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFlAwQCAQUABCDhOUY7jtQW2mTHjZaf
# zhylI7IWKlybklUpfwDq0GnW8AIGaeuhCndSGBMyMDI2MDUxNzAwNDk0OC4wNzFa
# MASAAgH0oIHZpIHWMIHTMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv
# bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0
# aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExpbWl0
# ZWQxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVTTjoyQTFBLTA1RTAtRDk0NzElMCMG
# A1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaCCEf4wggcoMIIFEKAD
# AgECAhMzAAACEKvN5BYY7zmwAAEAAAIQMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNV
# BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4w
# HAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29m
# dCBUaW1lLVN0YW1wIFBDQSAyMDEwMB4XDTI1MDgxNDE4NDgxMloXDTI2MTExMzE4
# NDgxMlowgdMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYD
# VQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLTAr
# BgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJhdGlvbnMgTGltaXRlZDEnMCUG
# A1UECxMeblNoaWVsZCBUU1MgRVNOOjJBMUEtMDVFMC1EOTQ3MSUwIwYDVQQDExxN
# aWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNlMIICIjANBgkqhkiG9w0BAQEFAAOC
# Ag8AMIICCgKCAgEAjcc4q057ZwIgpKu4pTXWLejvYEduRf+1mIpbiJEMFWWmU2xp
# ip+zK7xFxKGB1CclUXBU0/ZQZ6LG8H0gI7yvosrsPEI1DPB/XccGCvswKbAKckng
# OuGTEPGk7K/vEZa9h0Xt02b7m2n9MdIjkLrFl0pDriKyz0QHGpdh93X6+NApfE1T
# L24Vo0xkeoFGpL3rX9gXhIOF59EMnTd2o45FW/oxMgY9q0y0jGO0HrCLTCZr50e7
# TZRSNYAy2lyKbvKI2MKlN1wLzJvZbbc//L3s1q3J6KhS0KC2VNEImYdFgVkJej4z
# ZqHfScTbx9hjFgFpVkJl4xH5VJ8tyJdXE9+vU0k9AaT2QP1Zm3WQmXedSoLjjI7L
# WznuHwnoGIXLiJMQzPqKqRIFL3wzcrDrZeWgtAdBPbipglZ5CQns6Baj5Mb6a/EZ
# C9G3faJYK5QVHeE6eLoSEwp1dz5WurLXNPsp0VWplpl/FJb8jrRT/jOoHu85qRcd
# YpgByU9W7IWPdrthmyfqeAw0omVWN5JxcogYbLo2pANJHlsMdWnxIpN5YwHbGEPC
# uosBHPk2Xd9+E/pZPQUR6v+D85eEN5A/ZM/xiPpxa8dJZ87BpTvui7/2uflUMJf2
# Yc9ZLPgEdhQQo0LwMDSTDT48y3sV7Pdo+g5q+MqnJztN/6qt1cgUTe9u+ykCAwEA
# AaOCAUkwggFFMB0GA1UdDgQWBBSe42+FrpdF2avbUhlk86BLSH5kejAfBgNVHSME
# GDAWgBSfpxVdAF5iXYP05dJlpxtTNRnpcjBfBgNVHR8EWDBWMFSgUqBQhk5odHRw
# Oi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNyb3NvZnQlMjBUaW1l
# LVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcmwwbAYIKwYBBQUHAQEEYDBeMFwGCCsG
# AQUFBzAChlBodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01p
# Y3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNydDAMBgNVHRMB
# Af8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMA4GA1UdDwEB/wQEAwIHgDAN
# BgkqhkiG9w0BAQsFAAOCAgEAvs4rO3oo8czOrxPqnnSEkUVq718QzlrIiy7/EW7J
# mQXsJoFxHWUF0Ux0PDyKFDRXPJVv29F7kpJkBJJmcQg5HQV7blUXIMWQ1qX0KdtF
# QXI/MRL77Z+pK5x1jX+tbRkA7a5Ft7vWuRoAEi02HpFH5m/Akh/dfsbx8wOpecJb
# YvuHuy4aG0/tGzOWFCxMMNhGAIJ4qdV87JnY/uMBmiodlm+Gz357XWW5tg3HrtNZ
# XuQ0tWUv26ud4nGKJo/oLZHP75p4Rpt7dMdYKUF9AuVFBwxYZYpvgk12tfK+/yOw
# q84/fjXVCdM83Qnawtbenbk/lnbc9KsZom+GnvA4itAMUpSXFWrcRkqdUQLN+JrG
# 6fPBoV8+D8U2Q2F4XkiCR6EU9JzYKwTuvL6t3nFuxnkLdNjbTg2/yv2j3WaDuCK5
# lSPgsndIiH6Bku2Ui3A0aUo6D9z9v+XEuBs9ioVJaOjf/z+Urqg7ESnxG0/T1dKc
# i7vLQ2XNgWFYO+/OlDjtGoma1ijX4m14N9qgrXTuWEGwgC7hhBgp3id/LAOf9BST
# WA5lBrilsEoexXBrOn/1wM3rjG0hIsxvF5/YOK78mVRGY6Y7zYJ+uXt4OTOFBwad
# Pv8MklreQZLPnQPtiwop4rlLUYaPCiD4YUqRNbLp8Sgyo9g0iAcZYznTuc+8Q8ZI
# rgwwggdxMIIFWaADAgECAhMzAAAAFcXna54Cm0mZAAAAAAAVMA0GCSqGSIb3DQEB
# CwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTIwMAYD
# VQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxMDAe
# Fw0yMTA5MzAxODIyMjVaFw0zMDA5MzAxODMyMjVaMHwxCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN # EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp # aWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0
# Z25pbmcgUENBIDIwMTECEzMAAASFXpnsDlkvzdcAAAAABIUwDQYJYIZIAWUDBAIB # YW1wIFBDQSAyMDEwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA5OGm
# BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO # TOe0ciELeaLL1yR5vQ7VgtP97pwHB9KpbE51yMo1V/YBf2xK4OK9uT4XYDP/XE/H
# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEID8/Z0hz8wCpH2YjVYR3wACO # ZveVU3Fa4n5KWv64NmeFRiMMtY0Tz3cywBAY6GB9alKDRLemjkZrBxTzxXb1hlDc
# qi7toMi0S892RCpCiXnDMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A # wUTIcVxRMTegCjhuje3XD9gmU3w5YQJ6xKr9cmmvHaus9ja+NSZk2pg7uhp7M62A
# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB # W36MEBydUv626GIl3GoPz130/o5Tz9bshVZN7928jaTjkY+yOSxRnOlwaQ3KNi1w
# BQAEggEAR3ofVJe8H1PSnVv5GEV/iNRKzDHBYXTNKjw6gaEwywGiLnvok4fIy+o/ # jjHINSi947SHJMPgyY9+tVSP3PoFVZhtaDuaRr3tpK56KTesy+uDRedGbsoy1cCG
# pgoyuM4RLT6jq9o/62LWZPnRCXiQiidnt9u6BtjAQFoy9Hyz39SnG3SIfcXwQU6S # MFxPLOJiss254o2I5JasAUq7vnGpF1tnYN74kpEeHT39IM9zfUGaRnXNxF803RKJ
# Kn6sdIdkCnp9zgCw0A1um1l9ZESP36cub7lCkog6Qd1N+d5KAMuDMHX4MybWYjva # 1v2lIH1+/NmeRd+2ci/bfV+AutuqfjbsNkz2K26oElHovwUDo9Fzpk03dJQcNIIP
# YmW+c3RMH4HoBd6igF/hUaz0VTf+yrdIUaBIJ9UlWTMVkwokmQ9I79IwPU5hHnRu # 8BDyt0cY7afomXw/TNuvXsLz1dhzPUNOwTM5TI4CvEJoLhDqhFFG4tG9ahhaYQFz
# Ao8D6p++BagDKmVHo4bY/ADy4GDn4nrLA09mwd0YQPDZvb3K3Z2rIABM0UdS4+lG # ymeiXtcodgLiMxhy16cg8ML6EgrXY28MyTZki1ugpoMhXV8wdJGUlNi5UPkLiWHz
# c/pZsaRUT7TE8NzWXP+vWQ9bdkhNbaGCF5QwgheQBgorBgEEAYI3AwMBMYIXgDCC # NgY1GIRH29wb0f2y1BzFa/ZcUlFdEtsluq9QBXpsxREdcu+N+VLEhReTwDwV2xo3
# F3wGCSqGSIb3DQEHAqCCF20wghdpAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFSBgsq # xwgVGD94q0W29R6HXtqPnhZyacaue7e3PmriLq0CAwEAAaOCAd0wggHZMBIGCSsG
# hkiG9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl # AQQBgjcVAQQFAgMBAAEwIwYJKwYBBAGCNxUCBBYEFCqnUv5kxJq+gpE8RjUpzxD/
# AwQCAQUABCAd+KomD6n/vMp0PpchU0Vc9uK1oIZ/s0smWP9W6KAY4QIGaSc7gduW # LwTuMB0GA1UdDgQWBBSfpxVdAF5iXYP05dJlpxtTNRnpcjBcBgNVHSAEVTBTMFEG
# GBMyMDI1MTIxMDIyNDQ0NC41NjdaMASAAgH0oIHRpIHOMIHLMQswCQYDVQQGEwJV # DCsGAQQBgjdMg30BATBBMD8GCCsGAQUFBwIBFjNodHRwOi8vd3d3Lm1pY3Jvc29m
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE # dC5jb20vcGtpb3BzL0RvY3MvUmVwb3NpdG9yeS5odG0wEwYDVR0lBAwwCgYIKwYB
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1l # BQUHAwgwGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwCwYDVR0PBAQDAgGGMA8G
# cmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046REMwMC0w # A1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU1fZWy4/oolxiaNE9lJBb186aGMQw
# NUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2Wg # VgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9j
# ghHqMIIHIDCCBQigAwIBAgITMwAAAgO7HlwAOGx0ygABAAACAzANBgkqhkiG9w0B # cmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3JsMFoGCCsGAQUF
# AQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3Br
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD # aS9jZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcnQwDQYJKoZIhvcNAQEL
# VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAeFw0yNTAxMzAxOTQy # BQADggIBAJ1VffwqreEsH2cBMSRb4Z5yS/ypb+pcFLY+TkdkeLEGk5c9MTO1OdfC
# NDZaFw0yNjA0MjIxOTQyNDZaMIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz # cTY/2mRsfNB1OW27DzHkwo/7bNGhlBgi7ulmZzpTTd2YurYeeNg2LpypglYAA7AF
# aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv # vonoaeC6Ce5732pvvinLbtg/SHUB2RjebYIM9W0jVOR4U3UkV7ndn/OOPcbzaN9l
# cnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25z # 9qRWqveVtihVJ9AkvUCgvxm2EhIRXT0n4ECWOKz3+SmJw7wXsFSFQrP8DJ6LGYnn
# MScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046REMwMC0wNUUwLUQ5NDcxJTAjBgNV # 8AtqgcKBGUIZUnWKNsIdw2FzLixre24/LAl4FOmRsqlb30mjdAy87JGA0j3mSj5m
# BAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0GCSqGSIb3DQEB # O0+7hvoyGtmW9I/2kQH2zsZ0/fZMcm8Qq3UwxTSwethQ/gpY3UA8x1RtnWN0SCyx
# AQUAA4ICDwAwggIKAoICAQChl0MH5wAnOx8Uh8RtidF0J0yaFDHJYHTpPvRR16X1 # TkctwRQEcb9k+SS+c23Kjgm9swFXSVRk2XPXfx5bRAGOWhmRaw2fpCjcZxkoJLo4
# KxGDYfT8PrcGjCLCiaOu3K1DmUIU4Rc5olndjappNuOgzwUoj43VbbJx5PFTY/a1 # S5pu+yFUa2pFEUep8beuyOiJXk+d0tBMdrVXVAmxaQFEfnyhYWxz/gq77EFmPWn9
# Z80tpqVP0OoKJlUkfDPSBLFgXWj6VgayRCINtLsUasy0w5gysD7ILPZuiQjace5K # y8FBSX5+k77L+DvktxW/tM4+pTFRhLy/AsGConsXHRWJjXD+57XQKBqJC4822rpM
# xASjKf2MVX1qfEzYBbTGNEijSQCKwwyc0eavr4Fo3X/+sCuuAtkTWissU64k8rK6 # +Zv/Cuk0+CQ1ZyvgDbjmjJnW4SLq8CdCPSWU5nR0W2rRnj7tfqAxM328y+l7vzhw
# 0jsGRApiESdfuHr0yWAmc7jTOPNeGAx6KCL2ktpnGegLDd1IlE6Bu6BSwAIFHr7z # RNGQ8cirOoo6CGJ/2XBjU02N7oJtpQUQwXEGahC0HVUzWLOhcGbyoYIDWTCCAkEC
# OwIlFqyQuCe0SQALCbJhsT9y9iy61RJAXsU0u0TC5YYmTSbEI7g10dYx8Uj+vh9I # AQEwggEBoYHZpIHWMIHTMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv
# nLoKYC5DpKb311bYVd0bytbzlfTRslRTJgotnfCAIGMLqEqk9/2VRGu9klJi1j9n # bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0
# VfqyYHYrMPOBXcrQYW0jmKNjOL47CaEArNzhDBia1wXdJANKqMvJ8pQe2m8/ciby # aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExpbWl0
# DM+1BVZquNAov9N4tJF4ACtjX0jjXNDUMtSZoVFQH+FkWdfPWx1uBIkc97R+xRLu # ZWQxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVTTjoyQTFBLTA1RTAtRDk0NzElMCMG
# PjUypHZ5A3AALSke4TaRBvbvTBYyW2HenOT7nYLKTO4jw5Qq6cw3Z9zTKSPQ6D5l # A1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaIjCgEBMAcGBSsOAwIa
# yiYpes5RR2MdMvJS4fCcPJFeaVOvuWFSQ/EGtVBShhmLB+5ewzFzdpf1UuJmuOQT # AxUAOsyf2b6riPKnnXlIgIL2f53PUsKggYMwgYCkfjB8MQswCQYDVQQGEwJVUzET
# TwIDAQABo4IBSTCCAUUwHQYDVR0OBBYEFLIpWUB+EeeQ29sWe0VdzxWQGJJ9MB8G # MBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV
# A1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRYMFYwVKBSoFCG # TWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1T
# Tmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUy # dGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQsFAAIFAO2zHvgwIhgPMjAyNjA1MTYx
# MFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEFBQcBAQRgMF4w # NjUxMDRaGA8yMDI2MDUxNzE2NTEwNFowdzA9BgorBgEEAYRZCgQBMS8wLTAKAgUA
# XAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2Vy # 7bMe+AIBADAKAgEAAgIhcwIB/zAHAgEAAgISqjAKAgUA7bRweAIBADA2BgorBgEE
# dHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3J0MAwG # AYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIBAAIDB6EgoQowCAIBAAIDAYag
# A1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwDgYDVR0PAQH/BAQD # MA0GCSqGSIb3DQEBCwUAA4IBAQBjb6eELCy7c+QKNzPmI4O0riW248wi0uSI0DKx
# AgeAMA0GCSqGSIb3DQEBCwUAA4ICAQCQEMbesD6TC08R0oYCdSC452AQrGf/O89G # /ZRwZgOShcyQUcjn6Q/SGMTQ9cqeJe/IhyZbzUCTATtImhIybXreibtT2cAMO4M6
# Q54CtgEsbxzwGDVUcmjXFcnaJSTNedBKVXkBgawRonP1LgxH4bzzVj2eWNmzGIwO # 3gGHvd96iu8Q49zOYR182At7wmlUTeMrsj51LpZkWCUy4ZSeOSri83Aong5MKGMF
# 1FlhldAPOHAzLBEHRoSZ4pddFtaQxoabU/N1vWyICiN60It85gnF5JD4MMXyd6pS # Kt2xnKiSYL2tXIm0zcTpX8xjXMMWFb7wa0CBnHn5ZMYJVtuntxE1B4wJyMZktBS6
# 8eADIi6TtjfgKPoumWa0BFQ/aEzjUrfPN1r7crK+qkmLztw/ENS7zemfyx4kGRgw # WkP9XpGCXB1X7VhYM3vvtBFq2vA5neloI7waPPPTU+0hK6IX7+c1U776yDnJW4jc
# Y1WBfFqm/nFlJDPQBicqeU3dOp9hj7WqD0Rc+/4VZ6wQjesIyCkv5uhUNy2LhNDi # OUJsj8rjwrS+Gc/gPt98V3kMoGvxG0eGEzdjkmblUlhT1FwpMYIEDTCCBAkCAQEw
# 2leYtAiIFpmjfNk4GngLvC2Tj9IrOMv20Srym5J/Fh7yWAiPeGs3yA3QapjZTtfr # gZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcT
# 7NfzpBIJQ4xT/ic4WGWqhGlRlVBI5u6Ojw3ZxSZCLg3vRC4KYypkh8FdIWoKirji # B1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UE
# dEGlXsNOo+UP/YG5KhebiudTBxGecfJCuuUspIdRhStHAQsjv/dAqWBLlhorq2OC # AxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAIQq83kFhjvObAA
# aP+wFhE3WPgnnx5pflvlujocPgsN24++ddHrl3O1FFabW8m0UkDHSKCh8QTwTkYO # AQAAAhAwDQYJYIZIAWUDBAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0B
# wu99iExBVWlbYZRz2qOIBjL/ozEhtCB0auKhfTLLeuNGBUaBz+oZZ+X9UAECoMhk # CRABBDAvBgkqhkiG9w0BCQQxIgQgn7/AUpcQFhG3OcHi0Aeq56NC1uclpcmcCkGu
# ETjb6YfNaI1T7vVAaiuhBoV/JCOQT+RYZrgykyPpzpmwMNFBD1vdW/29q9nkTWoE # +HK5un4wgfoGCyqGSIb3DQEJEAIvMYHqMIHnMIHkMIG9BCDD1SHufsjzY59S1iHU
# hcEOO0L9NzCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZI # QY9hnsKSrJPg5a9Mc4YnGmPHxjCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD
# hvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw
# DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x
# MjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAy
# MDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMC
# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV
# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp
# bWUtU3RhbXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC
# AQDk4aZM57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg4r25Phdg
# M/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPF
# dvWGUNzBRMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6
# GnszrYBbfowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBp
# Dco2LXCOMcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2krnopN6zL64NF50Zu
# yjLVwIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3E
# XzTdEonW/aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0
# lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1q
# GFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ
# +QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PA
# PBXbGjfHCBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkw
# EgYJKwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxG
# NSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARV
# MFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWlj
# cm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAK
# BggrBgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC
# AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX
# zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v
# cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI
# KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG
# 9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0x
# M7U518JxNj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmC
# VgADsAW+iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449
# xvNo32X2pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wM
# nosZiefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDS
# PeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2d
# Y3RILLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxn
# GSgkujhLmm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+Crvs
# QWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokL
# jzbaukz5m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL
# 6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggNN
# MIICNQIBATCB+aGB0aSBzjCByzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp
# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw
# b3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEn
# MCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOkRDMDAtMDVFMC1EOTQ3MSUwIwYDVQQD
# ExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQDN
# rxRX/iz6ss1lBCXG8P1LFxD0e6CBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w # b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w
# IFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA7OQtPTAiGA8yMDI1MTIxMDE3MzI0 # IFBDQSAyMDEwAhMzAAACEKvN5BYY7zmwAAEAAAIQMCIEIC5b102d224evVIH68x9
# NVoYDzIwMjUxMjExMTczMjQ1WjB0MDoGCisGAQQBhFkKBAExLDAqMAoCBQDs5C09 # lEf8Au5vChR0bDNP4SViDwl6MA0GCSqGSIb3DQEBCwUABIICAHMgBiQOozDAIR+S
# AgEAMAcCAQACAgjlMAcCAQACAhNOMAoCBQDs5X69AgEAMDYGCisGAQQBhFkKBAIx # O28IjquhCukrxIucbxjLUxu4ltpxavA+cedbwkdNPAMFAYEaLHeEYutciK6N5f4R
# KDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJKoZI # VIJX/OSx2ZL6ac/xB5A/7rdoaHV7PK+F1y807INXMA2HPnJGDVbi7HD7nJp26Fuz
# hvcNAQELBQADggEBAFXCcBVLkxGEigIad7gAMsj2+SQdBANpzq4qPJXOu81TM3HC # P2K3aGE3kK06C2jXsjdqWsAZv32k1daX+OqFGJdOsmmua/qlF0AMWTXL7rsBStOH
# rAkCUTm3FRNc6YPdpfvl07lGlv/NHFCLyXL20d6PZ/1wlF5+WR2OvWjrktwDxYv8 # 5YI/KXtAsd9vvUbqcCMGeGmnRk6V7NmsKbQIkZ3kDk6YfKoZWmSknCKHF6LzCPkI
# cZqk7BrV9SB8xBe/GwVi7smKmlXhznqA6lFPO+VNfOwWcxn0H2yxEsAJKyDmgx/7 # bZSI8PNhcn19VUl9VS4WXGyWeGG5zBkOXnImAIKlaLMxR8Z9/kQVx84TrsDBl6Yh
# M8xnMTKeK8ulgSy4EoyGgFIO+nGHqxS0yaXe+OgzErkaavB1Qw7jfmm5/wlBCnwz # 34t6ISVDkQtre8SBG6fdjyWKmCOSntciVUlEom8ZwkadixIaf8nOoUJSYq5fl+4J
# 0UsbaequeL9UjA6FUw3Cc3F+3/D38BzyjJtTxjUVn+QiVWwOfikRJ2F7oZwpsJo3 # HkcUaY79lJKL5DiENwdC4Lt6eNabRo3SPbQE7IQWbWx5h3et1QDitsZ7Gg9yAHaZ
# yNIVpwJFpIV6VsqtxzaF0KQZBpS2lBGxVA17pFcxggQNMIIECQIBATCBkzB8MQsw # LS/t/SIAy+OXLXNxyttcyrbDvFI8A8tFE9P1w+3MC1n2cveqd6Wu07Z4l74Mj0LX
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # yR+0h/KjfE6SPaL+N20E4CjM0bxRKZ+CuFj/A3tykEADdzFtsEfzUzTDkD3cJpOj
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNy # fojWQNX1ly8Tu3G/WdAFqzrln2y8jCbVdPIqo1ntbu+BbYom1VQqAihYQB855+pG
# b3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAgO7HlwAOGx0ygABAAACAzAN # D8bOWbnrvhUua7gDFFDA762iZMxD
# BglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMC8G
# CSqGSIb3DQEJBDEiBCApggCahSc04fWyIz1KF4aeejwqHefyj2gzz7p9QsluFTCB
# +gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EIEsD3RtxlvaTxFOZZnpQw0DksPmV
# duo5SyK9h9w++hMtMIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldh
# c2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBD
# b3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIw
# MTACEzMAAAIDux5cADhsdMoAAQAAAgMwIgQgwIRXpw5w7cRbWSfOFZ15Z2Nf90Hi
# Ms/hZSpx+kv4aHcwDQYJKoZIhvcNAQELBQAEggIAM+zwgqPLhBOAumqVnUO/YRh7
# sePgzUWqveSw/J01TAD8JVXufiGmu4neLrGFki/Nz8ytun2DhJP/3xDRu39y/9Pb
# t2qKabzaoASwH95fTjHLYEp0PhqkEZ1hkaaYjVC3TAG0LgU2mrvkEjL3doD5MXu8
# WWQGcnB0Wera/3POf4ylyQbUUnzo/Pl9qUbjPVW/JouzzDzijObLcYp7IDgIDxGL
# sVJqMgzP1ZWBWsjjx4J0YiYORUnIVKWKPXt/0O3X9VO3zDfOnWRLF8mJj+ybEnqa
# Wd8LxLJnCxpmTAjtELLgC46UB0N4GHR0+ymSba35Ciz4Kzc+7R9E1Ajy1yd2rmGR
# M2u/eAV8MvKybIzgTd9Lukk9KJ5lvzV52CuYyzHOzYgcNt/mFgvM6gfMAef3CeN0
# EU7ECvTEYqno7krSRi6+HD+R14+7EwXbiR0E+KAB2Ppgj7GqHWKeL/Owyv0A1oEa
# 4ocdqMApLcY908U7IzNu5qo7PPas/RBsB9J52++fyZ/9RyP31IYKu8/5xI5Ef7aH
# XIopbEpuMHpHeuWlYWlfkULa5tjk4iPVCTRVsgn7IimLY/wgVOLL4ueOzZZ6aNws
# Q37w/ocvXIH/qXUllulfh5vINVYqXK3d+l0QT8LCMIxXpJSSgtcFcPJG6aSdOFRQ
# r6EOj+C9DH5MueMd9SY=
# SIG # End signature block # SIG # End signature block

View File

@@ -1003,12 +1003,12 @@ copy_files_or_dirs_from_list() {
cat | uniq | while read -r file_path; do cat | uniq | while read -r file_path; do
local path="$(remove_beginning_slash "${file_path#$root_path}")" local path="$(remove_beginning_slash "${file_path#$root_path}")"
local target="$out_path/$path" local target="$out_path/$path"
if [ "$override" = true ] || (! ([ -d "$target" ] || [ -e "$target" ])); then if [ "$override" = true ] || (! ([ -d "$target" ] || [ -e "$target" ] || [ -L "$target" ])); then
mkdir -p "$out_path/$(dirname "$path")" mkdir -p "$out_path/$(dirname "$path")"
if [ -d "$target" ]; then if [ -d "$target" ] || [ -L "$target" ]; then
rm -rf "$target" rm -rf "$target"
fi fi
cp -R $override_switch "$root_path/$path" "$target" cp -RP $override_switch "$root_path/$path" "$target"
fi fi
done done
} }
@@ -1053,8 +1053,8 @@ extract_dotnet_package() {
tar -xzf "$zip_path" -C "$temp_out_path" > /dev/null || failed=true tar -xzf "$zip_path" -C "$temp_out_path" > /dev/null || failed=true
local folders_with_version_regex='^.*/[0-9]+\.[0-9]+[^/]+/' local folders_with_version_regex='^.*/[0-9]+\.[0-9]+[^/]+/'
find "$temp_out_path" -type f | grep -Eo "$folders_with_version_regex" | sort | copy_files_or_dirs_from_list "$temp_out_path" "$out_path" false find "$temp_out_path" \( -type f -o -type l \) | grep -Eo "$folders_with_version_regex" | sort | copy_files_or_dirs_from_list "$temp_out_path" "$out_path" false
find "$temp_out_path" -type f | grep -Ev "$folders_with_version_regex" | copy_files_or_dirs_from_list "$temp_out_path" "$out_path" "$override_non_versioned_files" find "$temp_out_path" \( -type f -o -type l \) | grep -Ev "$folders_with_version_regex" | copy_files_or_dirs_from_list "$temp_out_path" "$out_path" "$override_non_versioned_files"
validate_remote_local_file_sizes "$zip_path" "$remote_file_size" validate_remote_local_file_sizes "$zip_path" "$remote_file_size"

View File

@@ -1,11 +0,0 @@
module.exports = {
clearMocks: true,
moduleFileExtensions: ['js', 'ts'],
testEnvironment: 'node',
testMatch: ['**/*.test.ts'],
testRunner: 'jest-circus/runner',
transform: {
'^.+\\.ts$': 'ts-jest'
},
verbose: true
}

28
jest.config.ts Normal file
View File

@@ -0,0 +1,28 @@
import type {JestConfigWithTsJest} from 'ts-jest';
const jestConfig: JestConfigWithTsJest = {
clearMocks: true,
testEnvironment: 'node',
moduleFileExtensions: ['js', 'ts'],
testMatch: ['**/*.test.ts'],
testRunner: 'jest-circus/runner',
extensionsToTreatAsEsm: ['.ts'],
moduleNameMapper: {
'^(\\.{1,2}/.*)\\.js$': '$1'
},
transform: {
'^.+\\.ts$': [
'ts-jest',
{
useESM: true,
diagnostics: {
ignoreCodes: [151002]
}
}
]
},
transformIgnorePatterns: ['node_modules/(?!(@actions)/)'],
verbose: true
};
export default jestConfig;

5747
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,7 @@
{ {
"name": "setup-dotnet", "name": "setup-dotnet",
"version": "5.0.1", "version": "6.0.0",
"type": "module",
"private": true, "private": true,
"description": "setup dotnet action", "description": "setup dotnet action",
"main": "dist/setup/index.js", "main": "dist/setup/index.js",
@@ -9,12 +10,12 @@
}, },
"scripts": { "scripts": {
"build": "ncc build -o dist/setup src/setup-dotnet.ts && ncc build -o dist/cache-save src/cache-save.ts", "build": "ncc build -o dist/setup src/setup-dotnet.ts && ncc build -o dist/cache-save src/cache-save.ts",
"format": "prettier --no-error-on-unmatched-pattern --config ./.prettierrc.js --write \"**/*.{ts,yml,yaml}\"", "format": "prettier --no-error-on-unmatched-pattern --write \"**/*.{ts,yml,yaml}\"",
"format-check": "prettier --no-error-on-unmatched-pattern --config ./.prettierrc.js --check \"**/*.{ts,yml,yaml}\"", "format-check": "prettier --no-error-on-unmatched-pattern --check \"**/*.{ts,yml,yaml}\"",
"lint": "eslint --config ./.eslintrc.js \"**/*.ts\"", "lint": "eslint \"**/*.ts\"",
"lint:fix": "eslint --config ./.eslintrc.js \"**/*.ts\" --fix", "lint:fix": "eslint \"**/*.ts\" --fix",
"prepare": "husky install", "prepare": "husky install",
"test": "jest --coverage --config ./jest.config.js", "test": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js --coverage",
"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"
}, },
"repository": { "repository": {
@@ -29,38 +30,37 @@
"author": "GitHub", "author": "GitHub",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@actions/cache": "^5.0.1", "@actions/cache": "^6.2.0",
"@actions/core": "^2.0.0", "@actions/core": "^3.0.1",
"@actions/exec": "^2.0.0", "@actions/exec": "^3.0.0",
"@actions/github": "^6.0.0", "@actions/github": "^9.1.1",
"@actions/glob": "^0.5.0", "@actions/glob": "^0.7.0",
"@actions/http-client": "^3.0.0", "@actions/http-client": "^4.0.1",
"@actions/io": "^1.0.2", "@actions/io": "^3.0.2",
"fast-xml-parser": "^5.3.6", "fast-xml-parser": "^5.9.3",
"json5": "^2.2.3", "json5": "^2.2.3",
"semver": "^7.6.0" "semver": "^7.8.5"
}, },
"devDependencies": { "devDependencies": {
"@types/jest": "^29.5.12", "@eslint/js": "^10.0.1",
"@types/node": "^24.1.0", "@jest/globals": "^30.4.1",
"@types/semver": "^7.5.8", "@types/node": "^24.13.2",
"@typescript-eslint/eslint-plugin": "^8.0.0", "@types/semver": "^7.7.0",
"@typescript-eslint/parser": "^8.0.0", "@typescript-eslint/eslint-plugin": "^8.62.0",
"@vercel/ncc": "^0.38.1", "@typescript-eslint/parser": "^8.62.0",
"eslint": "^8.57.0", "@vercel/ncc": "^0.44.0",
"eslint-config-prettier": "^10.1.5", "eslint": "^10.5.0",
"eslint-plugin-jest": "^29.0.1", "eslint-config-prettier": "^10.1.8",
"eslint-plugin-node": "^11.1.0", "eslint-plugin-jest": "^29.15.2",
"eslint-plugin-n": "^18.1.0",
"globals": "^17.7.0",
"husky": "^9.1.7", "husky": "^9.1.7",
"jest": "^29.7.0", "jest": "^30.4.2",
"jest-circus": "^29.7.0", "jest-circus": "^30.4.2",
"jest-each": "^29.7.0", "jest-each": "^30.0.0",
"prettier": "^3.2.5", "prettier": "^3.8.4",
"ts-jest": "^29.1.2", "ts-jest": "^29.4.11",
"typescript": "^5.9.2", "typescript": "^6.0.3",
"wget-improved": "^3.2.1" "wget-improved": "^3.2.1"
},
"jest": {
"testEnvironment": "node"
} }
} }

View File

@@ -4,8 +4,8 @@ import * as cache from '@actions/cache';
import * as core from '@actions/core'; import * as core from '@actions/core';
import * as glob from '@actions/glob'; import * as glob from '@actions/glob';
import {getNuGetFolderPath} from './cache-utils'; import {getNuGetFolderPath} from './cache-utils.js';
import {lockFilePatterns, State, Outputs} from './constants'; import {lockFilePatterns, State, Outputs} from './constants.js';
export const restoreCache = async (cacheDependencyPath?: string) => { export const restoreCache = async (cacheDependencyPath?: string) => {
const lockFilePath = cacheDependencyPath || (await findLockFile()); const lockFilePath = cacheDependencyPath || (await findLockFile());

View File

@@ -1,8 +1,8 @@
import * as core from '@actions/core'; import * as core from '@actions/core';
import * as cache from '@actions/cache'; import * as cache from '@actions/cache';
import fs from 'node:fs'; import fs from 'node:fs';
import {getNuGetFolderPath} from './cache-utils'; import {getNuGetFolderPath} from './cache-utils.js';
import {State} from './constants'; import {State} from './constants.js';
// Catch and log any unhandled exceptions. These exceptions can leak out of the uploadChunk method in // Catch and log any unhandled exceptions. These exceptions can leak out of the uploadChunk method in
// @actions/toolkit when a failed upload closes the file descriptor causing any in-process reads to // @actions/toolkit when a failed upload closes the file descriptor causing any in-process reads to
@@ -47,7 +47,11 @@ const cachePackages = async () => {
} }
const cacheId = await cache.saveCache([cachePath], primaryKey); const cacheId = await cache.saveCache([cachePath], primaryKey);
if (cacheId == -1) { if (cacheId === -1) {
// saveCache returns -1 without throwing when the cache was not saved, e.g.
// a reserve collision or a read-only token (fork PR). @actions/cache has
// already logged the reason at the appropriate severity, so just trace it.
core.debug(`Cache was not saved for the key: ${primaryKey}`);
return; return;
} }

View File

@@ -2,7 +2,7 @@ import * as cache from '@actions/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 {cliCommand} from './constants'; import {cliCommand} from './constants.js';
type NuGetFolderName = type NuGetFolderName =
| 'http-cache' | 'http-cache'

View File

@@ -5,10 +5,11 @@ 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 path from 'path'; import path from 'path';
import {fileURLToPath} from 'url';
import os from 'os'; import os from 'os';
import semver from 'semver'; import semver from 'semver';
import {IS_WINDOWS, PLATFORM} from './utils'; import {IS_WINDOWS, PLATFORM} from './utils.js';
import {QualityOptions} from './setup-dotnet'; import type {QualityOptions} from './setup-dotnet.js';
export interface DotnetVersion { export interface DotnetVersion {
type: string; type: string;
@@ -16,21 +17,74 @@ export interface DotnetVersion {
qualityFlag: boolean; qualityFlag: boolean;
} }
interface ReleaseIndexEntry {
'channel-version': string;
'support-phase': string;
'release-type': string;
}
interface ReleaseIndexResponse {
'releases-index': ReleaseIndexEntry[];
}
const QUALITY_INPUT_MINIMAL_MAJOR_TAG = 6; const QUALITY_INPUT_MINIMAL_MAJOR_TAG = 6;
const LATEST_PATCH_SYNTAX_MINIMAL_MAJOR_TAG = 5; const LATEST_PATCH_SYNTAX_MINIMAL_MAJOR_TAG = 5;
export class DotnetVersionResolver { export class DotnetVersionResolver {
private inputVersion: string; private inputVersion: string;
private resolvedArgument: DotnetVersion; private resolvedArgument: DotnetVersion;
constructor(version: string) { constructor(
version: string,
private quality: QualityOptions = '',
private dotnetChannel?: string
) {
this.inputVersion = version.trim(); this.inputVersion = version.trim();
this.resolvedArgument = {type: '', value: '', qualityFlag: false}; this.resolvedArgument = {type: '', value: '', qualityFlag: false};
} }
private isVersionChannel(channel: string): boolean {
// A.B format (e.g., 3.1, 8.0)
if (/^\d+\.\d+$/.test(channel)) return true;
// A.B.Cxx format (e.g., 8.0.1xx) is supported only for .NET 5.0+
const latestPatchMatch = channel.match(/^(\d+)\.\d+\.\d{1}xx$/);
if (latestPatchMatch) {
const major = Number(latestPatchMatch[1]);
return (
!Number.isNaN(major) && major >= LATEST_PATCH_SYNTAX_MINIMAL_MAJOR_TAG
);
}
return false;
}
private async resolveVersionInput(): Promise<void> { private async resolveVersionInput(): Promise<void> {
if (this.inputVersion.toLowerCase() === 'latest') {
const channel = this.dotnetChannel || '';
if (this.isVersionChannel(channel)) {
// A.B or A.B.Cxx channels are passed directly to the install script
this.resolvedArgument.value = channel;
} else {
// LTS, STS, or empty — resolve via releases index API
this.resolvedArgument.value = await this.getLatestVersion(channel);
}
this.resolvedArgument.type = 'channel';
const latestChannelMajorTag = Number(
this.resolvedArgument.value.split('.')[0]
);
this.resolvedArgument.qualityFlag =
!Number.isNaN(latestChannelMajorTag) &&
latestChannelMajorTag >= QUALITY_INPUT_MINIMAL_MAJOR_TAG;
return;
}
if (this.dotnetChannel) {
core.warning(
`The 'dotnet-channel' input is only supported when 'dotnet-version' is set to 'latest'.`
);
}
if (!semver.validRange(this.inputVersion) && !this.isLatestPatchSyntax()) { if (!semver.validRange(this.inputVersion) && !this.isLatestPatchSyntax()) {
throw new Error( 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` `The 'dotnet-version' was supplied in invalid format: ${this.inputVersion}! Supported syntax: A.B.C, A.B, A.B.x, A, A.x, A.B.Cxx, latest`
); );
} }
if (semver.valid(this.inputVersion)) { if (semver.valid(this.inputVersion)) {
@@ -72,7 +126,22 @@ export class DotnetVersionResolver {
} else if (this.isNumericTag(major) && this.isNumericTag(minor)) { } else if (this.isNumericTag(major) && this.isNumericTag(minor)) {
this.resolvedArgument.value = `${major}.${minor}`; this.resolvedArgument.value = `${major}.${minor}`;
} else if (this.isNumericTag(major)) { } else if (this.isNumericTag(major)) {
this.resolvedArgument.value = await this.getLatestByMajorTag(major); // Starting with .NET 5, the minor version is always zero.
// Hardcode the earlier versions because they will not get new releases.
switch (major) {
case '1':
this.resolvedArgument.value = '1.1';
break;
case '2':
this.resolvedArgument.value = '2.2';
break;
case '3':
this.resolvedArgument.value = '3.1';
break;
default:
this.resolvedArgument.value = `${major}.0`;
break;
}
} else { } 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. // 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.value = 'LTS';
@@ -96,31 +165,62 @@ export class DotnetVersionResolver {
return this.resolvedArgument; return this.resolvedArgument;
} }
private async getLatestByMajorTag(majorTag: string): Promise<string> { private async getLatestVersion(channelFilter: string): Promise<string> {
const httpClient = new hc.HttpClient('actions/setup-dotnet', [], { const httpClient = new hc.HttpClient('actions/setup-dotnet', [], {
allowRetries: true, allowRetries: true,
maxRetries: 3 maxRetries: 3
}); });
const response = await httpClient.getJson<any>( const response = await httpClient.getJson<ReleaseIndexResponse>(
DotnetVersionResolver.DotnetCoreIndexUrl DotnetVersionResolver.DotnetCoreIndexUrl
); );
const result = response.result || {}; const result = response.result;
const releasesInfo: any[] = result['releases-index']; const rawReleasesInfo = result?.['releases-index'];
const releaseInfo = releasesInfo.find(info => { if (!Array.isArray(rawReleasesInfo)) {
const sdkParts: string[] = info['channel-version'].split('.'); throw new Error('Unexpected response format from .NET releases index.');
return sdkParts[0] === majorTag; }
});
if (!releaseInfo) { let releasesInfo = rawReleasesInfo;
throw new Error(
`Could not find info for version with major tag: "${majorTag}" at ${DotnetVersionResolver.DotnetCoreIndexUrl}` // Filter out EOL versions
releasesInfo = releasesInfo.filter(info => info['support-phase'] !== 'eol');
// Filter out preview versions if quality is not 'preview' or 'daily'
// If quality is not specified, we assume strict stability (GA only)
const normalizedQuality = (this.quality || '').toLowerCase();
if (!['preview', 'daily'].includes(normalizedQuality)) {
releasesInfo = releasesInfo.filter(
info => info['support-phase'] !== 'preview'
); );
} }
return releaseInfo['channel-version']; // Apply channel filter (LTS/STS)
if (channelFilter) {
const type = channelFilter.toLowerCase();
releasesInfo = releasesInfo.filter(info => info['release-type'] === type);
}
releasesInfo.sort((a, b) => {
const partsA = a['channel-version'].split('.').map(Number);
const partsB = b['channel-version'].split('.').map(Number);
for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
const diff = (partsB[i] || 0) - (partsA[i] || 0);
if (diff !== 0) return diff;
}
return 0;
});
if (releasesInfo.length === 0) {
throw new Error(
`Could not find any active releases matching channel '${
channelFilter || 'any'
}'`
);
}
return releasesInfo[0]['channel-version'];
} }
static DotnetCoreIndexUrl = static DotnetCoreIndexUrl =
@@ -134,7 +234,13 @@ export class DotnetInstallScript {
constructor() { constructor() {
this.escapedScript = path this.escapedScript = path
.join(__dirname, '..', '..', 'externals', this.scriptName) .join(
path.dirname(fileURLToPath(import.meta.url)),
'..',
'..',
'externals',
this.scriptName
)
.replace(/'/g, "''"); .replace(/'/g, "''");
if (IS_WINDOWS) { if (IS_WINDOWS) {
@@ -279,11 +385,16 @@ export class DotnetCoreInstaller {
constructor( constructor(
private version: string, private version: string,
private quality: QualityOptions, private quality: QualityOptions,
private architecture?: string private architecture?: string,
private dotnetChannel?: string
) {} ) {}
public async installDotnet(): Promise<string | null> { public async installDotnet(): Promise<string | null> {
const versionResolver = new DotnetVersionResolver(this.version); const versionResolver = new DotnetVersionResolver(
this.version,
this.quality,
this.dotnetChannel
);
const dotnetVersion = await versionResolver.createDotnetVersion(); const dotnetVersion = await versionResolver.createDotnetVersion();
const architectureArguments = const architectureArguments =

Some files were not shown because too many files have changed in this diff Show More