Compare commits

...

9 Commits

Author SHA1 Message Date
v-mahabaleshwars
074373ff88 Add check-latest input to reuse a locally installed .NET SDK and skip network calls (#774)
* check-latest-input feature implemented

* e2e test update

* e2e-tests.yml updated

* update documentation

* fix: align local SDK matching with the online resolver

* implement review comments

* implement review comments

* implement review comments

* implement review comments

* implement review comments

* implement review comments

* implement review comments

---------

Co-authored-by: mahabaleshwars <147705296+mahabaleshwars@users.noreply.github.com>
2026-09-28 09:46:01 -05:00
v-mahabaleshwars
4d6fc87ace Clarify dotnet-quality behavior for multiple SDK versions (#780)
* docs: clarify dotnet-quality behavior

* implement copilot review

---------

Co-authored-by: mahabaleshwars <147705296+mahabaleshwars@users.noreply.github.com>
2026-09-23 11:24:40 -05:00
dependabot[bot]
9b203d2b06 Bump browserslist from 4.28.2 to 4.28.8 (#781)
* Bump browserslist from 4.28.2 to 4.28.8

Bumps [browserslist](https://github.com/browserslist/browserslist) from 4.28.2 to 4.28.8.
- [Release notes](https://github.com/browserslist/browserslist/releases)
- [Changelog](https://github.com/browserslist/browserslist/blob/main/CHANGELOG.md)
- [Commits](https://github.com/browserslist/browserslist/compare/4.28.2...4.28.8)

---
updated-dependencies:
- dependency-name: browserslist
  dependency-version: 4.28.8
  dependency-type: indirect
...

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

* feat: add support for .NET 11.0 in verify-dotnet script and update package-lock.json for peer dependencies

* Revert "feat: add support for .NET 11.0 in verify-dotnet script and update package-lock.json for peer dependencies"

This reverts commit b7ba059f41.

* feat: add support for .NET 11.0 in verify-dotnet script and update js-yaml version in package-lock.json

---------

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>
Co-authored-by: priyagupta108 <priyagupta108@github.com>
2026-09-15 16:27:47 -05:00
lmvysakh
bbbb8c9ab6 Docs: Updating Documentation in README (#776)
* Documentation updation

* doc update

* Documentation updated

---------

Co-authored-by: lmvysakh <lmvysakh@github.com>
2026-08-24 14:58:40 -05:00
dependabot[bot]
4713b0c4d9 Bump actions/checkout from 7.0.0 to 7.0.1 (#764)
Bumps [actions/checkout](https://github.com/actions/checkout) from 7.0.0 to 7.0.1.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](9c091bb21b...3d3c42e5aa)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-20 11:35:24 -05:00
Philip Gai
9eae3fe338 ci: make validation caches read-only (#777) 2026-08-18 10:38:43 -05:00
Priya Gupta
c97148f473 docs: add reference link for dotnet-quality options (#773) 2026-08-09 21:41:34 -05:00
Haritha
e92112b014 Fix security vulnerabilities (#767)
* fix: resolve npm audit vulnerabilities, refresh license cache, rebuild dist

- npm audit fix resolves brace-expansion (high), fast-xml-parser (high),
  and undici (moderate) advisories via transitive dependency updates
- Regenerated .licenses/npm cache to match updated dependency tree
- Rebuilt dist/setup and dist/cache-save bundles

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

* fix: sync vendored install-dotnet.ps1 with upstream dot.net script

Basic Validation was failing because the vendored copy of
externals/install-dotnet.ps1 was out of date compared to the live
script served from https://dot.net/v1/dotnet-install.ps1, which
installation-scripts.test.ts compares against on every run.

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

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-08-05 16:49:13 -05:00
Priya Gupta
5e931af6be Document sdk.version validation for rollForward in global.json (#763)
* docs: update README to include validation for sdk.version in global.json with rollForward

* docs: clarify sdk.version must be fully-qualified for rollForward in global.json
2026-07-22 09:48:54 -05:00
25 changed files with 4603 additions and 1157 deletions

View File

@@ -14,6 +14,7 @@ on:
jobs:
call-basic-validation:
name: Basic validation
cache-mode: read
uses: actions/reusable-workflows/.github/workflows/basic-validation.yml@main
with:
node-version: '24'

View File

@@ -14,6 +14,7 @@ on:
jobs:
call-check-dist:
name: Check dist/
cache-mode: read
uses: actions/reusable-workflows/.github/workflows/check-dist.yml@main
with:
node-version: '24'

View File

@@ -27,7 +27,7 @@ jobs:
]
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Clear toolcache
shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
@@ -57,7 +57,7 @@ jobs:
]
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Clear toolcache
shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
@@ -92,7 +92,7 @@ jobs:
]
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Clear toolcache
shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
@@ -123,7 +123,7 @@ jobs:
]
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Clear toolcache
shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
@@ -149,7 +149,7 @@ jobs:
]
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Clear toolcache
shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
@@ -180,7 +180,7 @@ jobs:
]
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Clear toolcache
shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
@@ -208,7 +208,7 @@ jobs:
]
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Clear toolcache
shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
@@ -239,7 +239,7 @@ jobs:
]
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Clear toolcache
shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
@@ -265,7 +265,7 @@ jobs:
operating-system: [ubuntu-latest, windows-latest, macos-latest]
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Clear toolcache
shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
@@ -290,7 +290,7 @@ jobs:
operating-system: [ubuntu-latest, windows-latest, macos-latest]
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Clear toolcache
shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
@@ -315,7 +315,7 @@ jobs:
operating-system: [ubuntu-latest, windows-latest, macos-latest]
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Clear toolcache
shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
@@ -347,7 +347,7 @@ jobs:
]
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Clear toolcache
shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
@@ -379,7 +379,7 @@ jobs:
]
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Clear toolcache
shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
@@ -410,7 +410,7 @@ jobs:
NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Clear toolcache
shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
@@ -448,7 +448,7 @@ jobs:
NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Clear toolcache
shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
@@ -482,7 +482,7 @@ jobs:
]
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Clear toolcache
shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
@@ -515,7 +515,7 @@ jobs:
]
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Clear toolcache
shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
@@ -555,7 +555,7 @@ jobs:
http_proxy: http://squid-proxy:3128
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Clear toolcache
shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
@@ -578,7 +578,7 @@ jobs:
no_proxy: github.com,download.visualstudio.microsoft.com,api.nuget.org,builds.dotnet.microsoft.com,ci.dot.net
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Clear toolcache
shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
@@ -610,7 +610,7 @@ jobs:
higher-version: ['10.0.101']
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Clear toolcache
shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
@@ -639,7 +639,7 @@ jobs:
[ubuntu-latest, windows-latest, macos-15-intel, macos-latest]
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Clear toolcache
shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
@@ -678,7 +678,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Clear toolcache
shell: pwsh
@@ -706,7 +706,7 @@ jobs:
operating-system: [ubuntu-latest]
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Clear toolcache
shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
@@ -726,7 +726,7 @@ jobs:
operating-system: [macos-latest]
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Clear toolcache
shell: pwsh
run: __tests__/clear-toolcache.ps1 ${{ runner.os }}
@@ -738,3 +738,34 @@ jobs:
- name: Verify dotnet
shell: pwsh
run: __tests__/verify-dotnet.ps1 -Patterns "^9\.0\.1\d{2}"
test-check-latest-false:
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 }}
# Oldest 9.0.1xx SDK: resolving '9.0.1xx' online would return a newer patch.
- name: Setup dotnet 9.0.100
uses: ./
with:
dotnet-version: '9.0.100'
- name: Setup dotnet 9.0.1xx with check-latest false
id: reuse
uses: ./
with:
dotnet-version: '9.0.1xx'
check-latest: false
- name: Verify dotnet
shell: pwsh
run: |
if ('${{ steps.reuse.outputs.dotnet-version }}' -ne '9.0.100') {
throw "Expected 9.0.100 to be reused, got '${{ steps.reuse.outputs.dotnet-version }}'."
}
__tests__/verify-dotnet.ps1 -Patterns "^9\.0\.100$"

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
---
name: brace-expansion
version: 5.0.6
version: 5.0.9
type: npm
summary: Brace expansion as known from sh/bash
homepage:

View File

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

View File

@@ -1,10 +1,10 @@
---
name: is-unsafe
version: 1.0.1
version: 2.0.0
type: npm
summary: Zero-dependency, DOM-free, pure predicate for detecting unsafe strings across
HTML, XML, SVG, SQL, SHELL, and REGEX contexts
homepage:
homepage:
license: mit
licenses:
- sources: LICENSE

View File

@@ -1,6 +1,6 @@
---
name: path-expression-matcher
version: 1.5.0
version: 1.6.2
type: npm
summary: Efficient path tracking and pattern matching for XML/JSON parsers
homepage: https://github.com/NaturalIntelligence/path-expression-matcher#readme

View File

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

View File

@@ -4,7 +4,7 @@ version: 0.1.0
type: npm
summary: Validates XML name productions — Name, NCName, QName, NMToken, NMTokens —
for XML 1.0 and 1.1
homepage:
homepage:
license: mit
licenses:
- sources: README.md

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

@@ -0,0 +1,35 @@
---
name: xml-naming
version: 0.3.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: 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

@@ -5,7 +5,7 @@
This action sets up a [.NET CLI](https://github.com/dotnet/sdk) environment for use in actions by:
- optionally downloading and caching a version(s) of dotnet by SDK version(s) and adding to PATH
- optionally download and install the requested .NET SDK version(s) and add them to the PATH
- registering problem matchers for error output
- setting up authentication to private package sources like GitHub Packages
@@ -24,7 +24,7 @@ for .NET SDK versions that are currently available.
- Upgraded action from node20 to node24
> Make sure your runner is on version v2.327.1 or later to ensure compatibility with this release. see [Release Notes](https://github.com/actions/runner/releases/tag/v2.327.1)
For more details, see the full release notes on the [release page](https://github.com/actions/setup-dotnet/releases/tag/v5.0.0)
For more details, see the full release notes on the [release page](https://github.com/actions/setup-dotnet/releases)
## Usage
@@ -105,7 +105,21 @@ steps:
## Using the `dotnet-quality` input
The `dotnet-quality` input installs the latest build of the specified quality in the channel. Supported values: `daily`, `preview`, `ga`.
The `dotnet-quality` input installs the latest build of the specified quality in the channel. Supported values: `daily`, `preview`, `ga`. For more details about quality options, see the [official .NET documentation](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-install-script#options).
> **Important**: `dotnet-quality` is a single input shared across all versions listed in `dotnet-version` for the same step (and is only applied where supported by the version format), so per-version quality is not supported. For example, setting `dotnet-quality: preview` for `10.0.x` and `11.0.x` resolves both versions as previews. To install a released SDK alongside a preview SDK, use separate steps:
```yaml
steps:
- uses: actions/setup-dotnet@v6
with:
dotnet-version: '10.0.x'
- uses: actions/setup-dotnet@v6
with:
dotnet-version: '11.0.x'
dotnet-quality: 'preview'
```
> **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.
@@ -148,6 +162,8 @@ steps:
> **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.
> **Important**: When using `rollForward` in `global.json`, the `sdk.version` field must be a fully-qualified SDK version (e.g., `8.0.100`, `10.0.100`). Wildcard versions (e.g., `10.0.*`) and runtime-style versions (e.g., `8.0.0`) are not supported. See the [.NET SDK version specification](https://learn.microsoft.com/en-us/dotnet/core/tools/global-json#version) for details.
## 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.
@@ -307,6 +323,29 @@ steps:
> **Note**: Ensure workloads are compatible with your runner's OS, architecture, and .NET SDK version before enabling workload installation. Some workloads may require additional installation time due to large toolchain downloads.
## Using the `check-latest` input
By default (`check-latest: true`) the action resolves and installs the latest version matching the request online, which is the historical behavior. With `check-latest: false` it first looks for an SDK already installed under [`DOTNET_INSTALL_DIR`](#environment-variables) and reuses it, skipping SDK resolution and download — useful for air-gapped or preloaded runners (the `cache` and `workloads` inputs still use the network). If none matches, it installs online as usual.
```yaml
steps:
- uses: actions/checkout@v7
- name: Reuse a preinstalled SDK when available
uses: actions/setup-dotnet@v6
with:
dotnet-version: '8.0.x'
check-latest: false
- run: dotnet build <my project>
```
An installed SDK is reused only when it matches the requested spec (`A.B.C`, `A.B`, `A.B.x`, `A.B.Cxx`, `A`, `A.x` or `latest`; `x`, `X` and `*` interchange except in a feature band, which is lowercase `Cxx`), satisfies `dotnet-quality`, is not older than the `sdk.version` declared in `global.json`, and sits next to a runnable `dotnet` executable. As online, `dotnet-quality` is ignored for a full `A.B.C` request, so such a request always resolves to a GA SDK. Cross-architecture requests, `latest` with an `LTS` or `STS` channel, and a bare wildcard always install online.
When the request comes from `global.json`, the SDK is picked the way the .NET resolver picks one: `latestPatch`, `latestFeature`, `latestMinor` and `latestMajor` take the **highest** SDK the policy allows, while `feature`, `minor` and `major` roll forward to the **nearest** higher feature band, minor or major and then take its latest patch. `patch` — the default when `rollForward` is omitted — prefers the declared version, otherwise the latest patch in its band. `disable` and a prerelease `sdk.version` require an exact match, and `allowPrerelease` is not used: `dotnet-quality` is the only prerelease control.
### Setting `check-latest` from the environment
When the input is not set, the action reads the `DOTNET_CHECK_LATEST` environment variable (`true` or `false`, case-insensitive; an unsupported value warns and is ignored), which covers workflows you cannot edit. Setting it on a self-hosted runner alongside SDKs preinstalled under [`DOTNET_INSTALL_DIR`](#environment-variables) lets those workflows run without reaching `aka.ms`, `builds.dotnet.microsoft.com` or `ci.dot.net`. It applies to every job on that runner, so a dedicated runner is recommended; any workflow can still override it with an explicit `check-latest` input.
# Outputs and environment variables
## Outputs
@@ -365,6 +404,7 @@ Some environment variables may be necessary for your particular case or to impro
| **Env.variable** | **Description** | **Default value** |
| ----------- | ----------- | ----------- |
| DOTNET_INSTALL_DIR |Specifies a directory where .NET SDKs should be installed by the action.|*default value for each OS* |
| DOTNET_CHECK_LATEST |Fallback for the [`check-latest`](#setting-check-latest-from-the-environment) input, for workflows that cannot set it. Ignored when the input is set.|*true*|
| DOTNET_NOLOGO |Removes logo and telemetry message from first run of dotnet cli|*false*|
| DOTNET_CLI_TELEMETRY_OPTOUT |Opt-out of telemetry being sent to Microsoft|*false*|
| DOTNET_MULTILEVEL_LOOKUP |Configures whether the global install location is used as a fall-back|*true*|

View File

@@ -1,5 +1,6 @@
import {
afterAll,
afterEach,
beforeAll,
beforeEach,
describe,
@@ -34,10 +35,25 @@ jest.unstable_mockModule('@actions/io', () => ({
jest.unstable_mockModule('fs', () => {
const actual = jest.requireActual('fs') as typeof import('fs');
const chmodSync = jest.fn();
const readdirSync = jest.fn();
const existsSync = jest.fn(() => true);
const statSync = jest.fn(() => ({isFile: () => true}));
const accessSync = jest.fn();
return {
...actual,
chmodSync,
default: {...actual, chmodSync}
readdirSync,
existsSync,
statSync,
accessSync,
default: {
...actual,
chmodSync,
readdirSync,
existsSync,
statSync,
accessSync
}
};
});
@@ -514,6 +530,696 @@ describe('installer tests', () => {
});
});
describe('check-latest: false (local SDK reuse) tests', () => {
const readdirSyncSpy = fs.readdirSync as unknown as jest.Mock;
const statSyncSpy = fs.statSync as unknown as jest.Mock;
const accessSyncSpy = fs.accessSync as unknown as jest.Mock;
const asFile = {isFile: () => true};
const muxerName = IS_WINDOWS ? 'dotnet.exe' : 'dotnet';
const isMuxer = (target: unknown) => String(target).endsWith(muxerName);
const throwEnoent = () => {
throw new Error('ENOENT');
};
const makeDirents = (names: string[]): any =>
names.map(name => ({
name,
isDirectory: () => true,
isSymbolicLink: () => false
}));
const makeSymlinks = (names: string[]): any =>
names.map(name => ({
name,
isDirectory: () => false,
isSymbolicLink: () => true
}));
beforeEach(() => {
getExecOutputSpy.mockClear();
getExecOutputSpy.mockImplementation(() =>
Promise.resolve({
exitCode: 0,
stdout: 'Fictitious dotnet version 1.2.3 is installed',
stderr: ''
})
);
statSyncSpy.mockReturnValue(asFile);
accessSyncSpy.mockImplementation(() => undefined);
});
afterEach(() => {
readdirSyncSpy.mockReset();
statSyncSpy.mockReset();
accessSyncSpy.mockReset();
});
interface LocalReuseCase {
name: string;
version: string;
quality?: string;
dotnetChannel?: string;
minimumVersion?: string;
installed: string[];
expected: string | null;
}
const localReuseCases: LocalReuseCase[] = [
{
name: 'reuses a locally installed pinned SDK and skips all install scripts',
version: '8.0.422',
installed: ['8.0.100', '8.0.422'],
expected: '8.0.422'
},
{
name: 'falls back to online install when no local SDK matches the pinned request',
version: '8.0.422',
installed: ['8.0.100'],
expected: null
},
{
name: 'reuses the highest matching patch for a floating A.B.x request',
version: '8.0.x',
installed: ['8.0.100', '8.0.412', '8.0.205'],
expected: '8.0.412'
},
{
name: 'matches the correct feature band for an A.B.Cxx request',
version: '8.0.1xx',
installed: ['8.0.100', '8.0.105', '8.0.203'],
expected: '8.0.105'
},
{
name: 'picks the highest installed SDK for a channel-less latest request',
version: 'latest',
installed: ['8.0.412', '9.0.101'],
expected: '9.0.101'
},
{
name: 'ignores prerelease SDKs when quality is not preview/daily',
version: '8.0.x',
installed: ['8.0.100-preview.1', '8.0.100'],
expected: '8.0.100'
},
{
name: 'reuses the highest installed SDK for a major-only request',
version: '8',
installed: ['8.0.100', '8.0.412', '8.0.205'],
expected: '8.0.412'
},
{
name: 'reuses a prerelease SDK when quality is preview',
version: '8.0.x',
quality: 'preview',
installed: ['8.0.100-preview.1', '8.0.100-preview.2'],
expected: '8.0.100-preview.2'
},
{
name: 'falls back to online install when no local SDK matches a floating request',
version: '9.0.x',
installed: ['8.0.412'],
expected: null
},
{
name: 'installs online for a latest request with an LTS channel',
version: 'latest',
dotnetChannel: 'LTS',
installed: ['8.0.412', '9.0.101'],
expected: null
},
{
name: 'reuses the SDK of the requested channel for a latest request',
version: 'latest',
dotnetChannel: '8.0',
installed: ['8.0.412', '9.0.101'],
expected: '8.0.412'
},
{
name: 'installs online for a wildcard request instead of guessing',
version: 'x',
installed: ['8.0.412', '9.0.101'],
expected: null
},
{
name: 'does not reuse a local SDK below the global.json minimum version',
version: '8.0',
minimumVersion: '8.0.400',
installed: ['8.0.100', '8.0.205'],
expected: null
},
{
name: 'reuses a local SDK at or above the global.json minimum version',
version: '8.0',
minimumVersion: '8.0.400',
installed: ['8.0.100', '8.0.412'],
expected: '8.0.412'
},
{
name: 'installs online when quality is preview but only GA SDKs are local',
version: '8.0.x',
quality: 'preview',
installed: ['8.0.412'],
expected: null
},
{
name: 'ignores the quality input for majors below 6 when matching locally',
version: '3.1',
quality: 'preview',
installed: ['3.1.426', '3.1.500-preview.1'],
expected: '3.1.426'
}
];
each(localReuseCases).it(
'$name',
async ({
version,
quality = '',
dotnetChannel,
minimumVersion,
installed,
expected
}: LocalReuseCase) => {
readdirSyncSpy.mockReturnValue(makeDirents(installed));
maxSatisfyingSpy.mockImplementation(() => '8.0.412');
const installedVersion = await new installer.DotnetCoreInstaller(
version,
quality as any,
undefined,
dotnetChannel,
false,
minimumVersion
).installDotnet();
if (expected === null) {
expect(getExecOutputSpy).toHaveBeenCalledTimes(2);
} else {
expect(installedVersion).toBe(expected);
expect(getExecOutputSpy).not.toHaveBeenCalled();
}
}
);
it('rejects an A.B.Cxx request below .NET 5 instead of reusing a local SDK', async () => {
readdirSyncSpy.mockReturnValue(makeDirents(['3.1.100']));
const dotnetInstaller = new installer.DotnetCoreInstaller(
'3.1.1xx',
'',
undefined,
undefined,
false
);
await expect(dotnetInstaller.installDotnet()).rejects.toThrow(
`The 'dotnet-version' was supplied in invalid format: 3.1.1xx! The A.B.Cxx syntax is available since the .NET 5.0 release.`
);
expect(getExecOutputSpy).not.toHaveBeenCalled();
});
each(['preview', 'daily'] as const).it(
"reuses the highest local prerelease SDK for a channel-less latest request with '%s' quality",
async quality => {
readdirSyncSpy.mockReturnValue(
makeDirents(['8.0.412', '9.0.100-preview.1', '9.0.100-preview.2'])
);
const dotnetInstaller = new installer.DotnetCoreInstaller(
'latest',
quality,
undefined,
undefined,
false
);
const installedVersion = await dotnetInstaller.installDotnet();
expect(installedVersion).toBe('9.0.100-preview.2');
expect(getExecOutputSpy).not.toHaveBeenCalled();
}
);
it('does NOT reuse a local SDK for a cross-architecture request', async () => {
readdirSyncSpy.mockReturnValue(makeDirents(['8.0.412']));
maxSatisfyingSpy.mockImplementation(() => '8.0.412');
const archSpy = jest.spyOn(os, 'arch').mockReturnValue('arm64');
const dotnetInstaller = new installer.DotnetCoreInstaller(
'8.0.x',
'',
'x64',
undefined,
false
);
await dotnetInstaller.installDotnet();
expect(getExecOutputSpy).toHaveBeenCalledTimes(2);
archSpy.mockRestore();
});
it('installs online when check-latest defaults to true even if SDK is local', async () => {
readdirSyncSpy.mockReturnValue(makeDirents(['8.0.422']));
maxSatisfyingSpy.mockImplementation(() => '8.0.422');
const dotnetInstaller = new installer.DotnetCoreInstaller(
'8.0.422',
''
);
await dotnetInstaller.installDotnet();
expect(getExecOutputSpy).toHaveBeenCalledTimes(2);
});
it('installs online when the dotnet muxer is missing', async () => {
readdirSyncSpy.mockReturnValue(makeDirents(['8.0.412']));
statSyncSpy.mockImplementation((target: string) => {
if (isMuxer(target)) {
throwEnoent();
}
return asFile;
});
maxSatisfyingSpy.mockImplementation(() => '8.0.412');
const dotnetInstaller = new installer.DotnetCoreInstaller(
'8.0.x',
'',
undefined,
undefined,
false
);
await dotnetInstaller.installDotnet();
expect(getExecOutputSpy).toHaveBeenCalledTimes(2);
});
const itOnPosix = IS_WINDOWS ? it.skip : it;
itOnPosix(
'installs online when the dotnet muxer is not executable',
async () => {
readdirSyncSpy.mockReturnValue(makeDirents(['8.0.412']));
accessSyncSpy.mockImplementation((target: string) => {
if (isMuxer(target)) {
throw new Error('EACCES');
}
});
maxSatisfyingSpy.mockImplementation(() => '8.0.412');
const dotnetInstaller = new installer.DotnetCoreInstaller(
'8.0.x',
'',
undefined,
undefined,
false
);
await dotnetInstaller.installDotnet();
expect(getExecOutputSpy).toHaveBeenCalledTimes(2);
}
);
it('installs online when the dotnet muxer path is a directory', async () => {
readdirSyncSpy.mockReturnValue(makeDirents(['8.0.412']));
statSyncSpy.mockImplementation((target: string) =>
isMuxer(target) ? {isFile: () => false} : asFile
);
maxSatisfyingSpy.mockImplementation(() => '8.0.412');
const dotnetInstaller = new installer.DotnetCoreInstaller(
'8.0.x',
'',
undefined,
undefined,
false
);
await dotnetInstaller.installDotnet();
expect(getExecOutputSpy).toHaveBeenCalledTimes(2);
});
it('reuses an SDK exposed as a symbolic link', async () => {
readdirSyncSpy.mockReturnValue(makeSymlinks(['8.0.412']));
const dotnetInstaller = new installer.DotnetCoreInstaller(
'8.0.x',
'',
undefined,
undefined,
false
);
const installedVersion = await dotnetInstaller.installDotnet();
expect(installedVersion).toBe('8.0.412');
expect(getExecOutputSpy).not.toHaveBeenCalled();
});
it('matches the online channel mapping for legacy major-only requests', async () => {
readdirSyncSpy.mockReturnValue(makeDirents(['3.0.103', '3.1.426']));
const dotnetInstaller = new installer.DotnetCoreInstaller(
'3',
'',
undefined,
undefined,
false
);
const installedVersion = await dotnetInstaller.installDotnet();
expect(installedVersion).toBe('3.1.426');
expect(getExecOutputSpy).not.toHaveBeenCalled();
});
each(['8.0.X', '8.0.x', '8.0.*', '8.0']).it(
'reuses a local SDK for the floating request %s',
async (version: string) => {
readdirSyncSpy.mockReturnValue(makeDirents(['8.0.100', '8.0.412']));
const dotnetInstaller = new installer.DotnetCoreInstaller(
version,
'',
undefined,
undefined,
false
);
const installedVersion = await dotnetInstaller.installDotnet();
expect(installedVersion).toBe('8.0.412');
expect(getExecOutputSpy).not.toHaveBeenCalled();
}
);
each(['8.X', '8.x', '8.*', '8']).it(
'reuses a local SDK for the major-only request %s',
async (version: string) => {
readdirSyncSpy.mockReturnValue(makeDirents(['8.0.100', '8.0.412']));
const dotnetInstaller = new installer.DotnetCoreInstaller(
version,
'',
undefined,
undefined,
false
);
const installedVersion = await dotnetInstaller.installDotnet();
expect(installedVersion).toBe('8.0.412');
expect(getExecOutputSpy).not.toHaveBeenCalled();
}
);
it('does not reuse a local SDK for an uppercase feature band request', async () => {
readdirSyncSpy.mockReturnValue(makeDirents(['8.0.105']));
const dotnetInstaller = new installer.DotnetCoreInstaller(
'8.0.1XX',
'',
undefined,
undefined,
false
);
await expect(dotnetInstaller.installDotnet()).rejects.toThrow(
`The 'dotnet-version' was supplied in invalid format: 8.0.1XX!`
);
});
each(['08.0.x', '8.00.x', '08']).it(
'does not reuse a local SDK for the invalid request %s',
async (version: string) => {
readdirSyncSpy.mockReturnValue(makeDirents(['8.0.412']));
const dotnetInstaller = new installer.DotnetCoreInstaller(
version,
'',
undefined,
undefined,
false
);
await expect(dotnetInstaller.installDotnet()).rejects.toThrow(
`The 'dotnet-version' was supplied in invalid format: ${version}!`
);
}
);
it('ignores an sdk folder that does not contain an SDK', async () => {
readdirSyncSpy.mockReturnValue(makeDirents(['8.0.412']));
statSyncSpy.mockImplementation((target: string) => {
if (String(target).includes('dotnet.dll')) {
throwEnoent();
}
return asFile;
});
maxSatisfyingSpy.mockImplementation(() => '8.0.412');
const dotnetInstaller = new installer.DotnetCoreInstaller(
'8.0.x',
'',
undefined,
undefined,
false
);
await dotnetInstaller.installDotnet();
expect(getExecOutputSpy).toHaveBeenCalledTimes(2);
});
describe('global.json rollForward tests', () => {
interface RollForwardCase {
name: string;
version?: string;
quality?: string;
minimumVersion?: string;
rollForward?: string;
installed: string[];
expected: string | null;
}
const rollForwardCases: RollForwardCase[] = [
{
name: 'latestMajor reuses the highest local SDK across majors',
minimumVersion: '8.0.100',
rollForward: 'latestMajor',
installed: ['8.0.412', '9.0.101'],
expected: '9.0.101'
},
{
name: 'latestMajor ignores local SDKs below the declared version',
minimumVersion: '8.0.412',
rollForward: 'latestMajor',
installed: ['8.0.100', '7.0.400'],
expected: null
},
{
name: 'latestMajor keeps requiring GA builds when quality is preview',
quality: 'preview',
minimumVersion: '8.0.100',
rollForward: 'latestMajor',
installed: ['9.0.200-preview.1'],
expected: null
},
{
name: 'latestMinor reuses a higher local minor of the same major',
version: '8',
minimumVersion: '8.1.100',
rollForward: 'latestMinor',
installed: ['8.1.200', '8.2.300'],
expected: '8.2.300'
},
{
name: 'latestMinor does not cross into another major',
version: '8',
minimumVersion: '8.1.100',
rollForward: 'latestMinor',
installed: ['9.0.101'],
expected: null
},
{
name: 'latestMinor ignores local SDKs below the declared version',
version: '8',
minimumVersion: '8.1.100',
rollForward: 'latestMinor',
installed: ['8.0.412'],
expected: null
},
{
name: 'latestFeature takes the highest band of the declared major.minor',
version: '8.0',
minimumVersion: '8.0.100',
rollForward: 'latestFeature',
installed: ['8.0.105', '8.0.400'],
expected: '8.0.400'
},
{
name: 'latestPatch stays inside the declared feature band',
version: '8.0.2xx',
minimumVersion: '8.0.200',
rollForward: 'latestPatch',
installed: ['8.0.205', '8.0.412'],
expected: '8.0.205'
},
{
name: 'latestPatch takes the highest patch even when the declared version is installed',
version: '8.0.1xx',
minimumVersion: '8.0.100',
rollForward: 'latestPatch',
installed: ['8.0.100', '8.0.105'],
expected: '8.0.105'
},
{
name: 'patch prefers the exact declared version',
version: '8.0.100',
minimumVersion: '8.0.100',
rollForward: 'patch',
installed: ['8.0.100', '8.0.105'],
expected: '8.0.100'
},
{
name: 'patch stays inside the declared feature band',
version: '8.0.100',
minimumVersion: '8.0.100',
rollForward: 'patch',
installed: ['8.0.105', '8.0.200'],
expected: '8.0.105'
},
{
name: 'feature prefers the declared band over a higher band',
version: '8.0.100',
minimumVersion: '8.0.100',
rollForward: 'feature',
installed: ['8.0.105', '8.0.400'],
expected: '8.0.105'
},
{
name: 'feature rolls to the next higher band, not the highest',
version: '8.0.100',
minimumVersion: '8.0.100',
rollForward: 'feature',
installed: ['8.0.200', '8.0.300', '8.0.400'],
expected: '8.0.200'
},
{
name: 'feature takes the latest patch of the selected band',
version: '8.0.100',
minimumVersion: '8.0.100',
rollForward: 'feature',
installed: ['8.0.201', '8.0.205', '8.0.400'],
expected: '8.0.205'
},
{
name: 'feature does not cross into another minor',
version: '8.0.100',
minimumVersion: '8.0.100',
rollForward: 'feature',
installed: ['8.1.100'],
expected: null
},
{
name: 'minor prefers a higher band of the declared minor over a higher minor',
version: '8.0.100',
minimumVersion: '8.0.100',
rollForward: 'minor',
installed: ['8.0.400', '8.1.100'],
expected: '8.0.400'
},
{
name: 'minor rolls to the next higher minor, not the highest',
version: '8.0.100',
minimumVersion: '8.0.100',
rollForward: 'minor',
installed: ['8.1.100', '8.2.100'],
expected: '8.1.100'
},
{
name: 'major prefers the declared major over a higher major',
version: '8.0.100',
minimumVersion: '8.0.100',
rollForward: 'major',
installed: ['8.0.400', '9.0.100'],
expected: '8.0.400'
},
{
name: 'major rolls to the next higher major, not the highest',
version: '8.0.100',
minimumVersion: '8.0.100',
rollForward: 'major',
installed: ['9.0.100', '10.0.100'],
expected: '9.0.100'
},
{
name: 'non-latest policies ignore dotnet-quality, as the online path does',
version: '8.0.100',
quality: 'preview',
minimumVersion: '8.0.100',
rollForward: 'patch',
installed: ['8.0.105'],
expected: '8.0.105'
},
{
name: 'non-latest policies never prefer a prerelease over a GA build',
version: '8.0.100',
quality: 'preview',
minimumVersion: '8.0.100',
rollForward: 'major',
installed: ['8.0.100', '9.0.100-rc.2'],
expected: '8.0.100'
},
{
name: 'roll-forward policies never select below the declared version',
version: '8.0.200',
minimumVersion: '8.0.200',
rollForward: 'major',
installed: ['8.0.100'],
expected: null
},
{
name: 'disable requires the exact declared version',
version: '8.0.100',
installed: ['8.0.200'],
expected: null
},
{
name: 'installs online for an empty version without a rollForward floor',
installed: ['8.0.412'],
expected: null
}
];
each(rollForwardCases).it(
'$name',
async ({
version = '',
quality = '',
minimumVersion,
rollForward,
installed,
expected
}: RollForwardCase) => {
readdirSyncSpy.mockReturnValue(makeDirents(installed));
maxSatisfyingSpy.mockImplementation(() => '8.0.412');
const installedVersion = await new installer.DotnetCoreInstaller(
version,
quality as any,
undefined,
undefined,
false,
minimumVersion,
rollForward
).installDotnet();
if (expected === null) {
expect(getExecOutputSpy).toHaveBeenCalledTimes(2);
} else {
expect(installedVersion).toBe(expected);
expect(getExecOutputSpy).not.toHaveBeenCalled();
}
}
);
});
});
describe('addToPath() tests', () => {
it(`should export DOTNET_ROOT env.var with value from DOTNET_INSTALL_DIR env.var`, async () => {
process.env['DOTNET_INSTALL_DIR'] = 'fictitious/dotnet/install/dir';

View File

@@ -66,6 +66,7 @@ describe('setup-dotnet tests', () => {
const setOutputSpy = core.setOutput as jest.Mock;
const existsSyncSpy = fs.existsSync as jest.Mock;
const readFileSyncSpy = fs.readFileSync as jest.Mock;
const maxSatisfyingSpy = jest.spyOn(semver, 'maxSatisfying');
@@ -85,11 +86,17 @@ describe('setup-dotnet tests', () => {
DotnetInstallDir.addToPath = jest.fn();
getMultilineInputSpy.mockImplementation(input => inputs[input as string]);
getInputSpy.mockImplementation(input => inputs[input as string]);
getBooleanInputSpy.mockImplementation(input => inputs[input as string]);
getBooleanInputSpy.mockImplementation(input => {
const value = inputs[input as string];
return typeof value === 'string'
? value.toLowerCase() === 'true'
: !!value;
});
});
afterEach(() => {
DotnetInstallDir.addToPath = addToPathOriginal;
delete process.env['DOTNET_CHECK_LATEST'];
jest.clearAllMocks();
jest.resetAllMocks();
});
@@ -289,6 +296,77 @@ describe('setup-dotnet tests', () => {
expect(DotnetInstallDir.addToPath).toHaveBeenCalledTimes(1);
});
it("should read the 'check-latest' input and pass it to DotnetCoreInstaller", async () => {
inputs['dotnet-version'] = ['10.0.101'];
inputs['dotnet-quality'] = '';
inputs['architecture'] = '';
inputs['check-latest'] = 'false';
let capturedCheckLatest: boolean | undefined;
installDotnetSpy.mockImplementation(function (this: any) {
capturedCheckLatest = this.checkLatest;
return Promise.resolve('');
});
await setup.run();
expect(getBooleanInputSpy).toHaveBeenCalledWith('check-latest');
expect(installDotnetSpy).toHaveBeenCalledTimes(1);
expect(capturedCheckLatest).toBe(false);
});
describe("'check-latest' resolution", () => {
const captureCheckLatest = async () => {
inputs['dotnet-version'] = ['10.0.101'];
inputs['dotnet-quality'] = '';
inputs['architecture'] = '';
let capturedCheckLatest: boolean | undefined;
installDotnetSpy.mockImplementation(function (this: any) {
capturedCheckLatest = this.checkLatest;
return Promise.resolve('');
});
await setup.run();
return capturedCheckLatest;
};
it('defaults to true when neither the input nor the environment variable is set', async () => {
inputs['check-latest'] = '';
expect(await captureCheckLatest()).toBe(true);
expect(getBooleanInputSpy).not.toHaveBeenCalledWith('check-latest');
});
it.each(['false', 'FALSE', 'False'])(
"reads 'check-latest' from the DOTNET_CHECK_LATEST environment variable ('%s')",
async envValue => {
inputs['check-latest'] = '';
process.env['DOTNET_CHECK_LATEST'] = envValue;
expect(await captureCheckLatest()).toBe(false);
}
);
it('lets an explicit input win over the environment variable', async () => {
inputs['check-latest'] = 'true';
process.env['DOTNET_CHECK_LATEST'] = 'false';
expect(await captureCheckLatest()).toBe(true);
});
it('warns and falls back to true when the environment variable is not a boolean', async () => {
inputs['check-latest'] = '';
process.env['DOTNET_CHECK_LATEST'] = 'yes';
expect(await captureCheckLatest()).toBe(true);
expect(warningSpy).toHaveBeenCalledWith(
`Value 'yes' is not supported for the DOTNET_CHECK_LATEST environment variable. Supported values are: true, false. The 'check-latest' option falls back to 'true'.`
);
expect(setFailedSpy).not.toHaveBeenCalled();
});
});
it('should fail the action if unsupported architecture is provided', async () => {
inputs['dotnet-version'] = ['10.0.101'];
inputs['dotnet-quality'] = '';
@@ -389,5 +467,241 @@ describe('setup-dotnet tests', () => {
`The 'dotnet-channel' input is only supported when 'dotnet-version' is set to 'latest'.`
);
});
it('should pass the global.json rollForward minimum version to DotnetCoreInstaller', async () => {
inputs['dotnet-version'] = [];
inputs['dotnet-quality'] = '';
inputs['dotnet-channel'] = '';
inputs['architecture'] = '';
inputs['check-latest'] = 'false';
inputs['global-json-file'] = 'global.json';
existsSyncSpy.mockReturnValue(true);
readFileSyncSpy.mockReturnValue(
JSON.stringify({
sdk: {version: '8.0.400', rollForward: 'latestFeature'}
})
);
let capturedVersion: string | undefined;
let capturedMinimumVersion: string | undefined;
installDotnetSpy.mockImplementation(function (this: any) {
capturedVersion = this.version;
capturedMinimumVersion = this.minimumVersion;
return Promise.resolve('8.0.412');
});
await setup.run();
inputs['global-json-file'] = '';
expect(capturedVersion).toBe('8.0');
expect(capturedMinimumVersion).toBe('8.0.400');
});
it('should pass non-latest rollForward policies to DotnetCoreInstaller', async () => {
inputs['dotnet-version'] = [];
inputs['dotnet-quality'] = '';
inputs['dotnet-channel'] = '';
inputs['architecture'] = '';
inputs['check-latest'] = 'false';
inputs['global-json-file'] = 'global.json';
existsSyncSpy.mockReturnValue(true);
readFileSyncSpy.mockReturnValue(
JSON.stringify({sdk: {version: '8.0.100', rollForward: 'feature'}})
);
let capturedVersion: string | undefined;
let capturedMinimumVersion: string | undefined;
let capturedRollForward: string | undefined;
installDotnetSpy.mockImplementation(function (this: any) {
capturedVersion = this.version;
capturedMinimumVersion = this.minimumVersion;
capturedRollForward = this.rollForward;
return Promise.resolve('8.0.200');
});
await setup.run();
inputs['global-json-file'] = '';
expect(capturedVersion).toBe('8.0.100');
expect(capturedMinimumVersion).toBe('8.0.100');
expect(capturedRollForward).toBe('feature');
});
it('should default an omitted rollForward to patch', async () => {
inputs['dotnet-version'] = [];
inputs['dotnet-quality'] = '';
inputs['dotnet-channel'] = '';
inputs['architecture'] = '';
inputs['check-latest'] = 'false';
inputs['global-json-file'] = 'global.json';
existsSyncSpy.mockReturnValue(true);
readFileSyncSpy.mockReturnValue(
JSON.stringify({sdk: {version: '8.0.100'}})
);
let capturedVersion: string | undefined;
let capturedMinimumVersion: string | undefined;
let capturedRollForward: string | undefined;
installDotnetSpy.mockImplementation(function (this: any) {
capturedVersion = this.version;
capturedMinimumVersion = this.minimumVersion;
capturedRollForward = this.rollForward;
return Promise.resolve('8.0.100');
});
await setup.run();
inputs['global-json-file'] = '';
expect(capturedVersion).toBe('8.0.100');
expect(capturedMinimumVersion).toBe('8.0.100');
expect(capturedRollForward).toBe('patch');
});
it('should not default to patch for a prerelease sdk version', async () => {
inputs['dotnet-version'] = [];
inputs['dotnet-quality'] = '';
inputs['dotnet-channel'] = '';
inputs['architecture'] = '';
inputs['check-latest'] = 'false';
inputs['global-json-file'] = 'global.json';
existsSyncSpy.mockReturnValue(true);
readFileSyncSpy.mockReturnValue(
JSON.stringify({sdk: {version: '8.0.100-preview.1'}})
);
let capturedMinimumVersion: string | undefined;
let capturedRollForward: string | undefined;
installDotnetSpy.mockImplementation(function (this: any) {
capturedMinimumVersion = this.minimumVersion;
capturedRollForward = this.rollForward;
return Promise.resolve('8.0.100-preview.1');
});
await setup.run();
inputs['global-json-file'] = '';
expect(capturedMinimumVersion).toBeUndefined();
expect(capturedRollForward).toBeUndefined();
});
it('should not default to patch for a version that is not a full SDK version', async () => {
inputs['dotnet-version'] = [];
inputs['dotnet-quality'] = '';
inputs['dotnet-channel'] = '';
inputs['architecture'] = '';
inputs['check-latest'] = 'false';
inputs['global-json-file'] = 'global.json';
existsSyncSpy.mockReturnValue(true);
readFileSyncSpy.mockReturnValue(
JSON.stringify({sdk: {version: '8.0.0'}})
);
let capturedMinimumVersion: string | undefined;
let capturedRollForward: string | undefined;
installDotnetSpy.mockImplementation(function (this: any) {
capturedMinimumVersion = this.minimumVersion;
capturedRollForward = this.rollForward;
return Promise.resolve('8.0.0');
});
await setup.run();
inputs['global-json-file'] = '';
expect(capturedMinimumVersion).toBeUndefined();
expect(capturedRollForward).toBeUndefined();
});
it('should not roll forward a version pinned by the dotnet-version input', async () => {
inputs['dotnet-version'] = ['8.0.100'];
inputs['dotnet-quality'] = '';
inputs['dotnet-channel'] = '';
inputs['architecture'] = '';
inputs['check-latest'] = 'false';
inputs['global-json-file'] = 'global.json';
existsSyncSpy.mockReturnValue(true);
readFileSyncSpy.mockReturnValue(
JSON.stringify({sdk: {version: '8.0.100'}})
);
let capturedMinimumVersion: string | undefined;
let capturedRollForward: string | undefined;
installDotnetSpy.mockImplementation(function (this: any) {
capturedMinimumVersion = this.minimumVersion;
capturedRollForward = this.rollForward;
return Promise.resolve('8.0.100');
});
await setup.run();
inputs['global-json-file'] = '';
expect(installDotnetSpy).toHaveBeenCalledTimes(1);
expect(capturedMinimumVersion).toBeUndefined();
expect(capturedRollForward).toBeUndefined();
});
it('should keep the global.json floor when a floating dotnet-version matches', async () => {
inputs['dotnet-version'] = ['8.0'];
inputs['dotnet-quality'] = '';
inputs['dotnet-channel'] = '';
inputs['architecture'] = '';
inputs['check-latest'] = 'false';
inputs['global-json-file'] = 'global.json';
existsSyncSpy.mockReturnValue(true);
readFileSyncSpy.mockReturnValue(
JSON.stringify({
sdk: {version: '8.0.400', rollForward: 'latestFeature'}
})
);
let capturedMinimumVersion: string | undefined;
let capturedRollForward: string | undefined;
installDotnetSpy.mockImplementation(function (this: any) {
capturedMinimumVersion = this.minimumVersion;
capturedRollForward = this.rollForward;
return Promise.resolve('8.0.400');
});
await setup.run();
inputs['global-json-file'] = '';
expect(installDotnetSpy).toHaveBeenCalledTimes(1);
expect(capturedMinimumVersion).toBe('8.0.400');
expect(capturedRollForward).toBe('latestFeature');
});
it('should not set a rollForward policy for disable', async () => {
inputs['dotnet-version'] = [];
inputs['dotnet-quality'] = '';
inputs['dotnet-channel'] = '';
inputs['architecture'] = '';
inputs['check-latest'] = 'false';
inputs['global-json-file'] = 'global.json';
existsSyncSpy.mockReturnValue(true);
readFileSyncSpy.mockReturnValue(
JSON.stringify({sdk: {version: '8.0.100', rollForward: 'disable'}})
);
let capturedMinimumVersion: string | undefined;
let capturedRollForward: string | undefined;
installDotnetSpy.mockImplementation(function (this: any) {
capturedMinimumVersion = this.minimumVersion;
capturedRollForward = this.rollForward;
return Promise.resolve('8.0.100');
});
await setup.run();
inputs['global-json-file'] = '';
expect(capturedMinimumVersion).toBeUndefined();
expect(capturedRollForward).toBeUndefined();
});
});
});

View File

@@ -74,6 +74,7 @@ $targetFrameworkVersionMap = @{
"8.0" = "net8.0";
"9.0" = "net9.0";
"10.0" = "net10.0";
"11.0" = "net11.0";
}
foreach ($version in $Versions)

View File

@@ -39,6 +39,9 @@ inputs:
architecture:
description: 'Optional architecture for the .NET install. Supported values: x64, x86, arm64, amd64, arm, s390x, ppc64le, riscv64. If not set, the installer auto-detects the current system architecture.'
required: false
check-latest:
description: 'Optional, defaults to true. When false, a locally installed SDK that satisfies the requested version is reused without any network calls, otherwise the latest matching version is installed online. Cross-architecture requests always install online. Falls back to the DOTNET_CHECK_LATEST environment variable when this input is not set.'
required: false
outputs:
cache-hit:
description: 'A boolean value to indicate if a cache was hit.'

1593
dist/cache-save/index.js vendored

File diff suppressed because it is too large Load Diff

2197
dist/setup/index.js vendored

File diff suppressed because it is too large Load Diff

View File

@@ -939,7 +939,7 @@ function Extract-Dotnet-Package-Tar([string]$TarPath, [string]$OutPath) {
foreach ($dir in $versionedDirs.GetEnumerator()) {
if ($dir.Value) {
# This versioned directory already exists — exclude it from extraction
# This versioned directory already exists - exclude it from extraction
$pattern = $dir.Key + '*'
$excludeArgs += '--exclude'
$excludeArgs += $pattern
@@ -956,7 +956,7 @@ function Extract-Dotnet-Package-Tar([string]$TarPath, [string]$OutPath) {
if ($normalizedEntry.EndsWith('/')) { continue }
$match = [regex]::Match($normalizedEntry, $versionRegex)
if (-not $match.Success) {
# Non-versioned file — exclude if it already exists locally
# Non-versioned file - exclude if it already exists locally
$localPath = Join-Path -Path $OutPath -ChildPath $normalizedEntry
if (Test-Path $localPath) {
$excludeArgs += '--exclude'
@@ -1487,8 +1487,8 @@ Say "Installation finished"
# SIG # Begin signature block
# MIIncQYJKoZIhvcNAQcCoIInYjCCJ14CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDmNI7+9b4lSWsX
# 2kYVvqQHpZeXB6Kt5BepNOcmIqEK1aCCDMkwggYEMIID7KADAgECAhMzAAACHPrN
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDbixcRHZx4cmqV
# 58QdPY+XiwQw34JjAyv8LsJIGTKYs6CCDMkwggYEMIID7KADAgECAhMzAAACHPrN
# xZvoL37EAAAAAAIcMA0GCSqGSIb3DQEBCwUAMFcxCzAJBgNVBAYTAlVTMR4wHAYD
# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBD
# b2RlIFNpZ25pbmcgUENBIDIwMjQwHhcNMjYwNDE2MTg1OTQxWhcNMjcwNDE1MTg1
@@ -1561,62 +1561,62 @@ Say "Installation finished"
# KDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMjQCEzMAAAIc
# +s3Fm+gvfsQAAAAAAhwwDQYJYIZIAWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwG
# CisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZI
# hvcNAQkEMSIEIB5Na9P++kVnSeIy27N4IyRD4N0sjfWHRS1ht0NHdoK/MEIGCisG
# hvcNAQkEMSIEIPGYOwqjbYcPpGhrXbJoGwiSMzNy0DFg1wPuMKjzUFU3MEIGCisG
# AQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3
# Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEBBQAEggEAx05EI+Kk4hoAKG/8oVdr
# UIq/+sv+E0vpSyWQMTq/yrkfnPWqT2TxNVtcG3ZcQT5/36cjMe1umi1zzy178VRP
# HKH2jxMOzMqtJPsAI/yAd46EOyTBwayS+27NOvz2r3v8WV0YjuplJb1teHmJtoIf
# UVC1va6H668UMrR2Mm5l8GvtcrGmDLjq3FLTLeidco7aj7xtkwUhyXvCS/HzoF9G
# yrYW3NKqIy+KbNXTD8RbwDxKvx3ED9C1SfZi3fi/t1a2wjNsxVqf4XNsO2+JvuuO
# SGTZOoo/3jqtS22IcV5Z3In0D3+C/rigq0179KhwPtztz4+J9MU5gtGmVayF1MH1
# M6GCF7AwghesBgorBgEEAYI3AwMBMYIXnDCCF5gGCSqGSIb3DQEHAqCCF4kwgheF
# Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEBBQAEggEATEc7CdUQlF9ad/L6EImG
# GP+g++vMy0R/Hg4c+MlO/1U5MYfOdh4p0OSk0R3D9KhIvGy0pmPFjbCovy8OT1o0
# /SmlfLvdZi71NTu7USS9IBaGa34XbhtETx7bqyEuFVyJi3Q6kem+/FX4X9Yy1uKa
# raasZebqi0W5Bwmm/l2W2vJoRejOf73LY/JVDJDoqC6Euy0ygFMRcXKuy0JapfNW
# uzhOnMSTeEhxSnJulIJT9WJ27C0C3xCs15Zxog1pTCIyrUdZ+1wztImKRG6ckOMs
# stSyISi+BDDDZlEIrqW28uhS3y1NamvJ8ParfgUUMWE6IVMfXyU3UNX9CffZlZUu
# G6GCF7AwghesBgorBgEEAYI3AwMBMYIXnDCCF5gGCSqGSIb3DQEHAqCCF4kwgheF
# AgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFaBgsqhkiG9w0BCRABBKCCAUkEggFFMIIB
# QQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFlAwQCAQUABCDhOUY7jtQW2mTHjZaf
# zhylI7IWKlybklUpfwDq0GnW8AIGaeuhCndSGBMyMDI2MDUxNzAwNDk0OC4wNzFa
# QQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFlAwQCAQUABCAKISNs7txQuaBSV3EX
# ZialoNXK4+PNZXnxI6CUNBDKJwIGajVNcfmeGBMyMDI2MDcxOTAxMTk1MS40NTVa
# MASAAgH0oIHZpIHWMIHTMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv
# bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0
# aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExpbWl0
# ZWQxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVTTjoyQTFBLTA1RTAtRDk0NzElMCMG
# ZWQxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVTTjo0MDFBLTA1RTAtRDk0NzElMCMG
# A1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaCCEf4wggcoMIIFEKAD
# AgECAhMzAAACEKvN5BYY7zmwAAEAAAIQMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNV
# AgECAhMzAAACGV6y2FR19LGNAAEAAAIZMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNV
# BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4w
# HAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29m
# dCBUaW1lLVN0YW1wIFBDQSAyMDEwMB4XDTI1MDgxNDE4NDgxMloXDTI2MTExMzE4
# NDgxMlowgdMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYD
# dCBUaW1lLVN0YW1wIFBDQSAyMDEwMB4XDTI1MDgxNDE4NDgyNloXDTI2MTExMzE4
# NDgyNlowgdMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYD
# VQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLTAr
# BgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJhdGlvbnMgTGltaXRlZDEnMCUG
# A1UECxMeblNoaWVsZCBUU1MgRVNOOjJBMUEtMDVFMC1EOTQ3MSUwIwYDVQQDExxN
# A1UECxMeblNoaWVsZCBUU1MgRVNOOjQwMUEtMDVFMC1EOTQ3MSUwIwYDVQQDExxN
# 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
# Ag8AMIICCgKCAgEApqFIyUkzIyxpL3Q03WmLuy4G9YIUScznhKr+cHOT+/u7Parx
# I96gxxb1WrWuAxB8qjGLfsbImx8V3ouK1nUcf+R/nsnXas5/iTgV/Tl3QTRGT0De
# uXBNbpHqc+wC1NiTyA76gLnirvSBEoBzlrpNQFEnuwdbPLCLpTS3KWSCu5J02b+R
# FWR/kcFzVxnhoE3gIaeURtrGKGBZGKLBXvqggkDENtKkvtvRT32xLvAvL/RpReu5
# z18ZojCs72ZSoa74Dy8YbaWsDm3OZOpJRZxZsPKCHZ6xNqgFKf0xNHj0t9v0Q3W+
# 2z5gAVaasJJCvR52Sl0XJ2AOf3l0LSetXgUA5gD5IQ1RvEslTmNnSouTrGID3D1n
# jY7mBu0puiIdPK2jK/1Weef2+YR4cQpWQkeBZmXidh9AuWdlwxKQL15LJ6K2dw8y
# /t/PBhmLyt6QAf0CepWRdgZnMytVAUuWHwlZRV9JLY7aX8D55eL9+cOLpX3bGNOm
# N24UpIW8qtZaqXaesFvIOW23JNLhaaQVvObr1eu7GE/5Mn43e+/DbtdYl/bLP2IQ
# 1xYEJdSbcUkDFfW3KlZEh+nBKDtaRnNRkbgIgxIbKdT38OKQwZ/aA4uSsiAg6nEP
# iWBHGuytIo5wU75M5VdjhEqqTHfXYu8BJi6GTzvWT+9ekfMXezqCkksxaG8CAwEA
# AaOCAUkwggFFMB0GA1UdDgQWBBSAaOo5HWatNzqZn1IF1fcD6nr3ITAfBgNVHSME
# 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
# BgkqhkiG9w0BAQsFAAOCAgEAXxzVZLLXBFfoCCCTiY7MHXdb7civJSTfrHYJC5Ok
# 2NN75NpzTMT9V2TcIQjfQ3AFUbh1NBAYtMUuwxC6D4ceEXG5lXAnbvkC9YjeLVDR
# yImXYYmft7z+Qpl9t3C/8a0tiqnOz8Ue8/DYLtMTgvWMnsqLNjILDaImOfnHI36T
# LCjGFe8RYLXGdCUdOLlfAdMGePxSTA3TAAOc+GQbmPWjrguLWbxvnl3NVjRvrBZV
# kxFMoVZH0f7qGwDOShjpnv5nYnQ48ufL0uBz52RbPGdX4Fv9+UGOrBprmcHzmIut
# FtJec2Y4kujNtTK2wBGgWscEOVhFiaVdje8VLJ7MVNKE5TmsuGM3jTLr1nuR5AFG
# s3UKkP7g3cQD4cHK7XdLiTm7e606QJ+WqeQsADYE9dvU9wIUbI9Dl4UcIErFw+FH
# aWSTrkfJ4SvLmhKnl5khhpJ1sF3z6e1BxepUliXHqzRLiHWihWIWESF8IHElF3PO
# xbP4VJqHBiYvaXMV0SyRgwoD6zXddbUnX9WR6JL2BlqAjjHxINwelsp/VhxAWThz
# uMA58LxvE/VAzjfFF4Wm7a1ZALmJVw3oL/s/uxo1Op4tcT+hfZ9uN1htC1JN4DuR
# qFfLttjuoAmUQobO5zUFRzvCn8Ck/hiO+bzR15sqkjlxLMyMjpkc/ef4SUUikD46
# 8vUwggdxMIIFWaADAgECAhMzAAAAFcXna54Cm0mZAAAAAAAVMA0GCSqGSIb3DQEB
# CwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTIwMAYD
# VQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxMDAe
@@ -1659,41 +1659,41 @@ Say "Installation finished"
# AQEwggEBoYHZpIHWMIHTMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv
# bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0
# aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExpbWl0
# ZWQxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVTTjoyQTFBLTA1RTAtRDk0NzElMCMG
# ZWQxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVTTjo0MDFBLTA1RTAtRDk0NzElMCMG
# A1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaIjCgEBMAcGBSsOAwIa
# AxUAOsyf2b6riPKnnXlIgIL2f53PUsKggYMwgYCkfjB8MQswCQYDVQQGEwJVUzET
# AxUAMXYp/Wqqdyb0enigrLfxl0InAz6ggYMwgYCkfjB8MQswCQYDVQQGEwJVUzET
# MBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV
# TWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1T
# dGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQsFAAIFAO2zHvgwIhgPMjAyNjA1MTYx
# NjUxMDRaGA8yMDI2MDUxNzE2NTEwNFowdzA9BgorBgEEAYRZCgQBMS8wLTAKAgUA
# 7bMe+AIBADAKAgEAAgIhcwIB/zAHAgEAAgISqjAKAgUA7bRweAIBADA2BgorBgEE
# dGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQsFAAIFAO4FXKYwIhgPMjAyNjA3MTgw
# MjAwMDZaGA8yMDI2MDcxOTAyMDAwNlowdzA9BgorBgEEAYRZCgQBMS8wLTAKAgUA
# 7gVcpgIBADAKAgEAAgIYywIB/zAHAgEAAgITSDAKAgUA7gauJgIBADA2BgorBgEE
# AYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIBAAIDB6EgoQowCAIBAAIDAYag
# MA0GCSqGSIb3DQEBCwUAA4IBAQBjb6eELCy7c+QKNzPmI4O0riW248wi0uSI0DKx
# /ZRwZgOShcyQUcjn6Q/SGMTQ9cqeJe/IhyZbzUCTATtImhIybXreibtT2cAMO4M6
# 3gGHvd96iu8Q49zOYR182At7wmlUTeMrsj51LpZkWCUy4ZSeOSri83Aong5MKGMF
# Kt2xnKiSYL2tXIm0zcTpX8xjXMMWFb7wa0CBnHn5ZMYJVtuntxE1B4wJyMZktBS6
# WkP9XpGCXB1X7VhYM3vvtBFq2vA5neloI7waPPPTU+0hK6IX7+c1U776yDnJW4jc
# OUJsj8rjwrS+Gc/gPt98V3kMoGvxG0eGEzdjkmblUlhT1FwpMYIEDTCCBAkCAQEw
# MA0GCSqGSIb3DQEBCwUAA4IBAQBq2r0gOT1OxJNWazOFyx2xCpcFbnegtTJkQ4PQ
# VcbdTaxlE7ksYBeZ0zXLWiv/fSnhonUmp9ORGV6+2phlj6xeV4AyH4dVsmyRBiLN
# +Fb7Q5qNxhG3ciablIePFBPSFxOxJ33x6wjJF9CLx7iuhmaykYlp5jhWQ3OQerd2
# FB5vO417JsgGhZscrvZeeamXMWU6LvuPSX02oWth8FbfnIuty1OAtQh9iQwmllq4
# T+dLJJSkC9Mh0gqKo+v5EBAIQ1fS5TsvfMW73Pt3lydMrZjRhdnTsQ4nge+iE8CN
# XrKtIO0esovriWQVcDVYcu9DM/2P1UovTKoZ44pcxDz1zk4VMYIEDTCCBAkCAQEw
# gZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcT
# B1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UE
# AxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAIQq83kFhjvObAA
# AQAAAhAwDQYJYIZIAWUDBAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0B
# CRABBDAvBgkqhkiG9w0BCQQxIgQgn7/AUpcQFhG3OcHi0Aeq56NC1uclpcmcCkGu
# +HK5un4wgfoGCyqGSIb3DQEJEAIvMYHqMIHnMIHkMIG9BCDD1SHufsjzY59S1iHU
# QY9hnsKSrJPg5a9Mc4YnGmPHxjCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD
# AxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAIZXrLYVHX0sY0A
# AQAAAhkwDQYJYIZIAWUDBAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0B
# CRABBDAvBgkqhkiG9w0BCQQxIgQg1hCY9TskS/ocjoNgKVJVguEsAjqzxBlDPM+r
# Bb91Lt8wgfoGCyqGSIb3DQEJEAIvMYHqMIHnMIHkMIG9BCDckX633E1y1EF32V18
# zQcrsgjzI9+3Le7mlvk2OebthjCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w
# IFBDQSAyMDEwAhMzAAACEKvN5BYY7zmwAAEAAAIQMCIEIC5b102d224evVIH68x9
# lEf8Au5vChR0bDNP4SViDwl6MA0GCSqGSIb3DQEBCwUABIICAHMgBiQOozDAIR+S
# O28IjquhCukrxIucbxjLUxu4ltpxavA+cedbwkdNPAMFAYEaLHeEYutciK6N5f4R
# VIJX/OSx2ZL6ac/xB5A/7rdoaHV7PK+F1y807INXMA2HPnJGDVbi7HD7nJp26Fuz
# P2K3aGE3kK06C2jXsjdqWsAZv32k1daX+OqFGJdOsmmua/qlF0AMWTXL7rsBStOH
# 5YI/KXtAsd9vvUbqcCMGeGmnRk6V7NmsKbQIkZ3kDk6YfKoZWmSknCKHF6LzCPkI
# bZSI8PNhcn19VUl9VS4WXGyWeGG5zBkOXnImAIKlaLMxR8Z9/kQVx84TrsDBl6Yh
# 34t6ISVDkQtre8SBG6fdjyWKmCOSntciVUlEom8ZwkadixIaf8nOoUJSYq5fl+4J
# HkcUaY79lJKL5DiENwdC4Lt6eNabRo3SPbQE7IQWbWx5h3et1QDitsZ7Gg9yAHaZ
# LS/t/SIAy+OXLXNxyttcyrbDvFI8A8tFE9P1w+3MC1n2cveqd6Wu07Z4l74Mj0LX
# yR+0h/KjfE6SPaL+N20E4CjM0bxRKZ+CuFj/A3tykEADdzFtsEfzUzTDkD3cJpOj
# fojWQNX1ly8Tu3G/WdAFqzrln2y8jCbVdPIqo1ntbu+BbYom1VQqAihYQB855+pG
# D8bOWbnrvhUua7gDFFDA762iZMxD
# IFBDQSAyMDEwAhMzAAACGV6y2FR19LGNAAEAAAIZMCIEIDjz74g3CBfNc6GVJRjf
# Y0YVIg76oCasifagFFulpBDbMA0GCSqGSIb3DQEBCwUABIICADak0YxE5whyHvYr
# Zukn9brxBTp9erdA6poINujmoiK5ZrTv6SZYaJbSI0HnDxbxp9QchY3BwpI19ix5
# +excveLH8i0oQWql3XyZcBDcwBi9sfFDv+yuJfd1CE27X8YxGy5OJeKCK9QoQiXs
# b0MgT35WaxPxvRwcUArXf8qwkP+CO4EkL9x+ZCznyvutSebHKS5fRRPgVreJKRg2
# vNh4J2lujjEIsOduZs8o5U7woRCBgN/v6lDl9x35WHgoHSzxSwQz7HZea/gcbXa6
# GRezjA7bCzS1H99feDX3KiTi1kIxtKilQOVzOO9XqrTYoZGak7NeNW12IqbmeYHF
# ZVW8XkBg5U8BdPpS/3/wD7Y8K1429YSIrJLdccR16TCwlItiqbnFrPt1+qPUH64K
# okrklXX5tYZMe9cXKCC6GlP8oSw1mBf3i85h7YkQf7MgLMbhuBKPBndY7PYfeXNY
# zjiH4lwKswYlJHqagmw2xYvT00Ekchjo9k6HGaUK1gzCK/ISGOiNb1pr6xf5hK6K
# TL9zvNOvRxOr6L/fBLjzRFCy4TiBmOAn6uq50hl/7H7hwuoxIXVHcPqZbgRfng7z
# PXEyYpUtVfNMlrFfD0u6mXTp9Ez+1W0hs55SVN4CuwKAO9g1nVBc5i+7KwEpYVfU
# kJuzixuLkM8/Hhm7X6wRAwAqpA7P
# SIG # End signature block

155
package-lock.json generated
View File

@@ -1634,9 +1634,9 @@
}
},
"node_modules/@nodable/entities": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.2.0.tgz",
"integrity": "sha512-9uGyhaQavEUMC8AIddIjau4NsnsXhou+j5sBAGojCM1oxmQpVKTWR/9JxABD6UAv12vpIms55fPZKFQEhG6uBg==",
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-3.0.0.tgz",
"integrity": "sha512-8L9xFeTYKhm49xfIypoe2W5wV1m/3Z58kT+7kR9A8OyFxcPduI4VmxaUMQyKYrRjUoLLSXv6EKKID5Tvj9cUVw==",
"funding": [
{
"type": "github",
@@ -2367,9 +2367,6 @@
"arm64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -2384,9 +2381,6 @@
"arm64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -2401,9 +2395,6 @@
"loong64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -2418,9 +2409,6 @@
"loong64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -2435,9 +2423,6 @@
"ppc64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -2452,9 +2437,6 @@
"riscv64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -2469,9 +2451,6 @@
"riscv64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -2486,9 +2465,6 @@
"s390x"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -2503,9 +2479,6 @@
"x64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -2520,9 +2493,6 @@
"x64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -2851,9 +2821,9 @@
}
},
"node_modules/baseline-browser-mapping": {
"version": "2.10.38",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.38.tgz",
"integrity": "sha512-31/02mVB4yuQU6adKk5SlY6m+mxDwUq5KZkyYgnLrrKl7TEm1+3PyDtDBz2kOv/wxZz41GHsvV1A/u6RmiyBvw==",
"version": "2.11.20",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.20.tgz",
"integrity": "sha512-H0ulySigv6icDJ1F7SjtdCD6PrhTpdYCmP0CactWy1+ekh0AFd0o1Wn5T8b+hnTmdBx19u9yhL6wvCylXMY7zw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
@@ -2870,21 +2840,21 @@
"license": "Apache-2.0"
},
"node_modules/brace-expansion": {
"version": "5.0.6",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
"integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
"version": "5.0.9",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz",
"integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==",
"license": "MIT",
"dependencies": {
"balanced-match": "^4.0.2"
},
"engines": {
"node": "18 || 20 || >=22"
"node": "20 || >=22"
}
},
"node_modules/browserslist": {
"version": "4.28.2",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz",
"integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==",
"version": "4.28.8",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz",
"integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==",
"dev": true,
"funding": [
{
@@ -2902,11 +2872,11 @@
],
"license": "MIT",
"dependencies": {
"baseline-browser-mapping": "^2.10.12",
"caniuse-lite": "^1.0.30001782",
"electron-to-chromium": "^1.5.328",
"node-releases": "^2.0.36",
"update-browserslist-db": "^1.2.3"
"baseline-browser-mapping": "^2.11.12",
"caniuse-lite": "^1.0.30001809",
"electron-to-chromium": "^1.5.402",
"node-releases": "^2.0.53",
"update-browserslist-db": "^1.3.0"
},
"bin": {
"browserslist": "cli.js"
@@ -2964,9 +2934,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001799",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz",
"integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==",
"version": "1.0.30001810",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz",
"integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==",
"dev": true,
"funding": [
{
@@ -3252,9 +3222,9 @@
"license": "MIT"
},
"node_modules/electron-to-chromium": {
"version": "1.5.375",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.375.tgz",
"integrity": "sha512-ZWP5eB4BVPW/ZYo9252hQZHZ5XavtsTgpbhcmMmRwymavC5AsLWQWBPaKMeNd2LW0KGby5HPXvj7+sr4ta5j/Q==",
"version": "1.5.420",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.420.tgz",
"integrity": "sha512-2yD6XreGusOfNV+dUcvipJEXc3n/n7fgr7996aszTG+YY5E4mqM4tOq/3uhP129cazL9YHbVWSpc79ePotWtPA==",
"dev": true,
"license": "ISC"
},
@@ -3764,9 +3734,9 @@
}
},
"node_modules/fast-xml-parser": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.9.3.tgz",
"integrity": "sha512-brCNCeScma/kqa54J4PIDriSSSLssRkuYaUCpvHJulGc3HGI/xxKUCTDcYkAdqJsyb//ydpbxecjC3hB9+tb/g==",
"version": "5.10.1",
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.10.1.tgz",
"integrity": "sha512-IEMIf7298kXuZSRFoGfMYrl7is8LpavODgbNz1cwIudv7KwVFnuU+UsMporfq6PD6aXSlawZlARiA3UywCTfMw==",
"funding": [
{
"type": "github",
@@ -3775,17 +3745,32 @@
],
"license": "MIT",
"dependencies": {
"@nodable/entities": "^2.2.0",
"@nodable/entities": "^3.0.0",
"fast-xml-builder": "^1.2.0",
"is-unsafe": "^1.0.1",
"path-expression-matcher": "^1.5.0",
"is-unsafe": "^2.0.0",
"path-expression-matcher": "^1.6.2",
"strnum": "^2.4.1",
"xml-naming": "^0.1.0"
"xml-naming": "^0.3.0"
},
"bin": {
"fxparser": "src/cli/cli.js"
}
},
"node_modules/fast-xml-parser/node_modules/xml-naming": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.3.0.tgz",
"integrity": "sha512-ghig2TBE/H11aOVgmahA3MhimvkBr6JIYknH/Dhdk10nXwdbIqBJsbfMxpvFPG8bAw77gN29aQWvKpmVoPlvPQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/NaturalIntelligence"
}
],
"license": "MIT",
"engines": {
"node": ">=16.0.0"
}
},
"node_modules/fb-watchman": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz",
@@ -3993,9 +3978,9 @@
"license": "MIT"
},
"node_modules/glob/node_modules/brace-expansion": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz",
"integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==",
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz",
"integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4254,9 +4239,9 @@
}
},
"node_modules/is-unsafe": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-unsafe/-/is-unsafe-1.0.1.tgz",
"integrity": "sha512-CLK2+VdgERgD96EYm5lUQssZYlRg2tkZnbsxZoacmSiRxiFJ4Nk4SzjCl+Ur+v3kXIY9dTIdb3IH22y1mZ56LA==",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-unsafe/-/is-unsafe-2.0.0.tgz",
"integrity": "sha512-2LdV822R+wmI86unXA93WCFpL6g+av8ynWk0nrHyJqGop5VoocYsSLFgN8jrfalT6iGeLNM4KXuVSsULP53kEA==",
"funding": [
{
"type": "github",
@@ -4984,9 +4969,9 @@
"license": "MIT"
},
"node_modules/js-yaml": {
"version": "3.15.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.0.tgz",
"integrity": "sha512-ttBQIIQPDeLjpPOohtUdXuXUVoA2uIB6fEH9HyJ7234s5mBJ5wTx20njxplLZQgLaOfpmPQA7X2t5AX6tIPbog==",
"version": "3.15.2",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.2.tgz",
"integrity": "sha512-6EuL879VkRA+1Cz578mKMiKvjPNEuk6+r1JaFzoSWejZmtf7xWbIyw1e3KkxlkzTIt9Taw6JBhEppG7utc1P+w==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -5191,9 +5176,9 @@
"license": "MIT"
},
"node_modules/minimatch/node_modules/brace-expansion": {
"version": "1.1.15",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz",
"integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==",
"version": "1.1.18",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz",
"integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
@@ -5258,9 +5243,9 @@
"dev": true
},
"node_modules/node-releases": {
"version": "2.0.48",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.48.tgz",
"integrity": "sha512-1uz8041X6LoI6ZSdZacM9lVY28vuzDlSKitnpbSNK0RfKoIJkX29NBPVEFXhnuSuEOA9Ww0xnPJ+ILWbGAv8DA==",
"version": "2.0.54",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.54.tgz",
"integrity": "sha512-YHs7BmmcsdAI5Ozuf8JZo6PT0mv2GIWC9vMfvUC3dp65M8hn7Ux8CPL+2oBI7juNuj9d0ndhTcznq2ODBps9cQ==",
"dev": true,
"license": "MIT",
"engines": {
@@ -5407,9 +5392,9 @@
}
},
"node_modules/path-expression-matcher": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.5.0.tgz",
"integrity": "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==",
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.6.2.tgz",
"integrity": "sha512-enSlaiat05iasnzmgNxRj8reFdj3puY2QpNgP1aPIaVfT6nn9ICuPoFlKHk8EN22HcwewshO+mN2DGbkCEOtqQ==",
"funding": [
{
"type": "github",
@@ -6281,9 +6266,9 @@
}
},
"node_modules/undici": {
"version": "6.27.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.27.0.tgz",
"integrity": "sha512-YmfV3YnEDzXRC5lZ2jWtWWHKGUm1zIt8AhesR1tens+HTNv+YZlN/dp6G727LOvMJ8xjP9Be7Y2Sdr96LDm+pg==",
"version": "6.28.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.28.0.tgz",
"integrity": "sha512-LIY910g9TI13YS95lrMFrs8Rm/u/irgHeTWoKCoteeJ04CUJ92eEfj0rVn+7VKMPBpUPiUoBKfhNyLI23EE/KA==",
"license": "MIT",
"engines": {
"node": ">=18.17"
@@ -6341,9 +6326,9 @@
}
},
"node_modules/update-browserslist-db": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
"integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.2.tgz",
"integrity": "sha512-UQ+MSxlhRm1bzjhU+DcuXfjFO1FzNtqhK5+9Yvlp90ItDLk5vT932A0rFu619nf7RVS+Y/VeaUW1jaRDqZ8VJw==",
"dev": true,
"funding": [
{

View File

@@ -3,7 +3,13 @@ import * as core from '@actions/core';
import * as exec from '@actions/exec';
import * as io from '@actions/io';
import * as hc from '@actions/http-client';
import {chmodSync} from 'fs';
import {
accessSync,
chmodSync,
constants as fsConstants,
readdirSync,
statSync
} from 'fs';
import path from 'path';
import {fileURLToPath} from 'url';
import os from 'os';
@@ -29,6 +35,22 @@ interface ReleaseIndexResponse {
const QUALITY_INPUT_MINIMAL_MAJOR_TAG = 6;
const LATEST_PATCH_SYNTAX_MINIMAL_MAJOR_TAG = 5;
function channelForMajor(major: string): string {
// 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':
return '1.1';
case '2':
return '2.2';
case '3':
return '3.1';
default:
return `${major}.0`;
}
}
export class DotnetVersionResolver {
private inputVersion: string;
private resolvedArgument: DotnetVersion;
@@ -126,22 +148,7 @@ export class DotnetVersionResolver {
} else if (this.isNumericTag(major) && this.isNumericTag(minor)) {
this.resolvedArgument.value = `${major}.${minor}`;
} else if (this.isNumericTag(major)) {
// Starting with .NET 5, the minor version is always zero.
// Hardcode the earlier versions because they will not get new releases.
switch (major) {
case '1':
this.resolvedArgument.value = '1.1';
break;
case '2':
this.resolvedArgument.value = '2.2';
break;
case '3':
this.resolvedArgument.value = '3.1';
break;
default:
this.resolvedArgument.value = `${major}.0`;
break;
}
this.resolvedArgument.value = channelForMajor(major);
} else {
// If "dotnet-version" is specified as *, x or X resolve latest version of .NET explicitly from LTS channel. The version argument will default to "latest" by install-dotnet script.
this.resolvedArgument.value = 'LTS';
@@ -377,7 +384,32 @@ export function normalizeArch(arch: string): string {
}
}
function isFile(filePath: string): boolean {
try {
return statSync(filePath).isFile();
} catch {
return false;
}
}
function isExecutableFile(filePath: string): boolean {
if (!isFile(filePath)) {
return false;
}
if (IS_WINDOWS) {
return true;
}
try {
accessSync(filePath, fsConstants.X_OK);
return true;
} catch {
return false;
}
}
export class DotnetCoreInstaller {
private static readonly FeatureBandSyntax = /^(\d+)\.(\d+)\.(\d)xx$/;
static {
DotnetInstallDir.setEnvironmentVariable();
}
@@ -386,10 +418,291 @@ export class DotnetCoreInstaller {
private version: string,
private quality: QualityOptions,
private architecture?: string,
private dotnetChannel?: string
) {}
private dotnetChannel?: string,
private checkLatest: boolean = true,
private minimumVersion?: string,
private rollForward?: string
) {
this.version = version.trim();
}
private getInstalledSdkVersions(): string[] {
const sdkDir = path.join(DotnetInstallDir.dirPath, 'sdk');
try {
const versions = readdirSync(sdkDir, {withFileTypes: true})
.filter(entry => entry.isDirectory() || entry.isSymbolicLink())
.map(entry => entry.name)
.filter(name => semver.valid(name) !== null)
.filter(name => isFile(path.join(sdkDir, name, 'dotnet.dll')));
core.debug(
`Locally installed .NET SDKs in '${sdkDir}': ${
versions.join(', ') || '<none>'
}`
);
return versions;
} catch {
core.debug(`Unable to read the SDK directory '${sdkDir}'.`);
return [];
}
}
private hasDotnetMuxer(): boolean {
return isExecutableFile(
path.join(DotnetInstallDir.dirPath, IS_WINDOWS ? 'dotnet.exe' : 'dotnet')
);
}
private qualityApplies(): boolean {
if (this.version.toLowerCase() === 'latest') {
const major = (this.dotnetChannel || '').trim().match(/^(\d+)/)?.[1];
return major ? Number(major) >= QUALITY_INPUT_MINIMAL_MAJOR_TAG : true;
}
if (semver.valid(this.version)) {
return false;
}
const major = this.version.match(/^(\d+)/)?.[1];
return major ? Number(major) >= QUALITY_INPUT_MINIMAL_MAJOR_TAG : false;
}
private findByScope(
candidates: string[],
major: string,
minor: string,
band?: string
): string | null {
return (
candidates.find(version => {
const parsed = semver.parse(version);
return (
parsed &&
parsed.major === Number(major) &&
parsed.minor === Number(minor) &&
(band === undefined ||
Math.floor(parsed.patch / 100) === Number(band))
);
}) ?? null
);
}
private static isInRollForwardScope(
version: string,
policy: string,
declared: semver.SemVer
): boolean {
const parsed = semver.parse(version);
if (!parsed) {
return false;
}
switch (policy) {
case 'patch':
case 'latestPatch':
return (
parsed.major === declared.major &&
parsed.minor === declared.minor &&
Math.floor(parsed.patch / 100) === Math.floor(declared.patch / 100)
);
case 'feature':
case 'latestFeature':
return (
parsed.major === declared.major && parsed.minor === declared.minor
);
case 'minor':
case 'latestMinor':
return parsed.major === declared.major;
case 'major':
case 'latestMajor':
return true;
default:
return false;
}
}
private static highestVersion(versions: string[]): string {
return versions.reduce((best, version) =>
semver.gt(version, best) ? version : best
);
}
private static nearestBandVersion(versions: string[]): string {
return versions.reduce((best, version) => {
const delta =
semver.major(version) - semver.major(best) ||
semver.minor(version) - semver.minor(best) ||
Math.floor(semver.patch(version) / 100) -
Math.floor(semver.patch(best) / 100);
return delta < 0 || (delta === 0 && semver.gt(version, best))
? version
: best;
});
}
private findByRollForward(
candidates: string[],
policy: string,
declaredVersion: string
): string | null {
const declared = semver.parse(declaredVersion);
if (!declared) {
return null;
}
const scoped = candidates.filter(version =>
DotnetCoreInstaller.isInRollForwardScope(version, policy, declared)
);
if (!scoped.length) {
return null;
}
switch (policy) {
case 'patch':
return (
scoped.find(version => semver.eq(version, declared)) ??
DotnetCoreInstaller.highestVersion(scoped)
);
case 'feature':
case 'minor':
case 'major':
return DotnetCoreInstaller.nearestBandVersion(scoped);
default:
return DotnetCoreInstaller.highestVersion(scoped);
}
}
private filterByQuality(allowed: string[]): string[] {
const wantsPrerelease =
['preview', 'daily'].includes((this.quality || '').toLowerCase()) &&
this.qualityApplies();
return allowed
.filter(version =>
wantsPrerelease
? semver.prerelease(version) !== null
: semver.prerelease(version) === null
)
.sort(semver.rcompare);
}
private findLocalSdkVersion(): string | null {
const installed = this.getInstalledSdkVersions();
if (!installed.length) {
return null;
}
if (!this.hasDotnetMuxer()) {
core.debug(
`The 'dotnet' executable was not found in '${DotnetInstallDir.dirPath}'. Locally installed SDKs are ignored.`
);
return null;
}
const minimumVersion = this.minimumVersion;
const allowed = minimumVersion
? installed.filter(version => semver.gte(version, minimumVersion))
: installed;
if (!allowed.length) {
core.debug(
`No locally installed .NET SDK satisfies the global.json minimum version '${minimumVersion}'.`
);
return null;
}
if (this.rollForward && minimumVersion) {
return this.findByRollForward(
this.filterByQuality(allowed),
this.rollForward,
minimumVersion
);
}
if (semver.valid(this.version)) {
return allowed.find(version => version === this.version) ?? null;
}
if (
this.version.toLowerCase() !== 'latest' &&
!DotnetCoreInstaller.FeatureBandSyntax.test(this.version) &&
!semver.validRange(this.version)
) {
core.debug(
`The requested version '${this.version}' is not a valid version spec. Locally installed SDKs are ignored.`
);
return null;
}
const candidates = this.filterByQuality(allowed);
if (!candidates.length) {
return null;
}
const input = this.version.toLowerCase();
if (input === 'latest') {
const channel = (this.dotnetChannel || '').trim();
if (!channel) {
return candidates[0];
}
const channelMinor = channel.match(/^(\d+)\.(\d+)$/);
if (channelMinor) {
return this.findByScope(candidates, channelMinor[1], channelMinor[2]);
}
const channelBand = channel.match(/^(\d+)\.(\d+)\.(\d)xx$/);
if (channelBand) {
return this.findByScope(
candidates,
channelBand[1],
channelBand[2],
channelBand[3]
);
}
return null;
}
const bandMatch = this.version.match(DotnetCoreInstaller.FeatureBandSyntax);
if (bandMatch) {
if (Number(bandMatch[1]) < LATEST_PATCH_SYNTAX_MINIMAL_MAJOR_TAG) {
return null;
}
return this.findByScope(
candidates,
bandMatch[1],
bandMatch[2],
bandMatch[3]
);
}
const minorMatch = this.version.match(/^(\d+)\.(\d+)(?:\.[xX*])?$/);
if (minorMatch) {
return this.findByScope(candidates, minorMatch[1], minorMatch[2]);
}
const majorMatch = this.version.match(/^(\d+)(?:\.[xX*])?$/);
if (majorMatch) {
const [major, minor] = channelForMajor(majorMatch[1]).split('.');
return this.findByScope(candidates, major, minor);
}
return null;
}
public async installDotnet(): Promise<string | null> {
const isCrossArch =
!!this.architecture &&
normalizeArch(this.architecture) !== normalizeArch(os.arch());
if (!this.checkLatest && !isCrossArch) {
const localVersion = this.findLocalSdkVersion();
if (localVersion) {
core.info(
`'check-latest' is false and a locally installed .NET SDK (${localVersion}) satisfies the '${this.version}' request. Skipping download.`
);
return localVersion;
}
core.info(
`'check-latest' is false but no locally installed .NET SDK satisfies the '${this.version}' request. Falling back to online installation.`
);
}
const versionResolver = new DotnetVersionResolver(
this.version,
this.quality,
@@ -397,16 +710,14 @@ export class DotnetCoreInstaller {
);
const dotnetVersion = await versionResolver.createDotnetVersion();
const architectureArguments =
this.architecture &&
normalizeArch(this.architecture) !== normalizeArch(os.arch())
? [
IS_WINDOWS ? '-InstallDir' : '--install-dir',
IS_WINDOWS
? `"${path.join(DotnetInstallDir.dirPath, this.architecture)}"`
: path.join(DotnetInstallDir.dirPath, this.architecture)
]
: [];
const architectureArguments = isCrossArch
? [
IS_WINDOWS ? '-InstallDir' : '--install-dir',
IS_WINDOWS
? `"${path.join(DotnetInstallDir.dirPath, this.architecture!)}"`
: path.join(DotnetInstallDir.dirPath, this.architecture!)
]
: [];
/**
* Install dotnet runtime first in order to get
* the latest stable version of dotnet CLI

View File

@@ -31,6 +31,8 @@ type SupportedArchitecture = (typeof supportedArchitectures)[number];
export type QualityOptions = (typeof qualityOptions)[number] | '';
const CHECK_LATEST_ENV_VAR = 'DOTNET_CHECK_LATEST';
function isValidChannel(channel: string): boolean {
const upper = channel.toUpperCase();
if (upper === 'LTS' || upper === 'STS') return true;
@@ -54,8 +56,24 @@ export async function run() {
// Proxy, auth, (etc) are still set up, even if no version is identified
//
const versions = core.getMultilineInput('dotnet-version');
const explicitVersions = new Set(versions);
const globalJsonConstraints = new Map<
string,
{minimumVersion: string; rollForward?: string}
>();
const addVersionFromGlobalJson = (globalJsonPath: string) => {
const {version, minimumVersion, rollForward} =
getVersionFromGlobalJson(globalJsonPath);
versions.push(version);
const isRedundantExplicitPin =
explicitVersions.has(version) && version === minimumVersion;
if (minimumVersion && !isRedundantExplicitPin) {
globalJsonConstraints.set(version, {minimumVersion, rollForward});
}
};
const installedDotnetVersions: (string | null)[] = [];
const architecture = getArchitectureInput();
const checkLatest = getCheckLatestInput();
let dotnetChannel = core.getInput('dotnet-channel');
const isLatestRequested = versions.some(
@@ -87,7 +105,7 @@ export async function run() {
`The specified global.json file '${globalJsonFileInput}' does not exist`
);
}
versions.push(getVersionFromGlobalJson(globalJsonPath));
addVersionFromGlobalJson(globalJsonPath);
}
if (!versions.length) {
@@ -95,7 +113,7 @@ export async function run() {
core.debug('No version found, trying to find version from global.json');
const globalJsonPath = path.join(process.cwd(), 'global.json');
if (fs.existsSync(globalJsonPath)) {
versions.push(getVersionFromGlobalJson(globalJsonPath));
addVersionFromGlobalJson(globalJsonPath);
} else {
core.info(
`The global.json wasn't found in the root directory. No .NET version will be installed.`
@@ -117,11 +135,15 @@ export async function run() {
versions.map(v => (v.toLowerCase() === 'latest' ? 'latest' : v))
);
for (const version of uniqueVersions) {
const constraint = globalJsonConstraints.get(version);
dotnetInstaller = new DotnetCoreInstaller(
version,
quality,
architecture,
version.toLowerCase() === 'latest' ? dotnetChannel : undefined
version.toLowerCase() === 'latest' ? dotnetChannel : undefined,
checkLatest,
constraint?.minimumVersion,
constraint?.rollForward
);
const installedVersion = await dotnetInstaller.installDotnet();
installedDotnetVersions.push(installedVersion);
@@ -200,8 +222,51 @@ function getArchitectureInput(): SupportedArchitecture | '' {
);
}
function getVersionFromGlobalJson(globalJsonPath: string): string {
function getCheckLatestInput(): boolean {
if ((core.getInput('check-latest') || '').trim()) {
return core.getBooleanInput('check-latest');
}
const rawEnvValue = (process.env[CHECK_LATEST_ENV_VAR] || '').trim();
if (rawEnvValue) {
const envValue = rawEnvValue.toLowerCase();
if (envValue === 'true' || envValue === 'false') {
core.debug(
`The 'check-latest' option is set to '${envValue}' by the ${CHECK_LATEST_ENV_VAR} environment variable.`
);
return envValue === 'true';
}
core.warning(
`Value '${rawEnvValue}' is not supported for the ${CHECK_LATEST_ENV_VAR} environment variable. Supported values are: true, false. The 'check-latest' option falls back to 'true'.`
);
}
return true;
}
interface GlobalJsonVersion {
version: string;
minimumVersion?: string;
rollForward?: string;
}
const ROLL_FORWARD_POLICIES = [
'patch',
'feature',
'minor',
'major',
'latestPatch',
'latestFeature',
'latestMinor',
'latestMajor'
];
const versionPattern = /^\d+\.\d+\.[1-9]\d{2,}$/;
function getVersionFromGlobalJson(globalJsonPath: string): GlobalJsonVersion {
let version = '';
let minimumVersion: string | undefined;
let rollForwardPolicy: string | undefined;
const globalJson = JSON5.parse(
// .trim() is necessary to strip BOM https://github.com/nodejs/node/issues/20649
fs.readFileSync(globalJsonPath, {encoding: 'utf8'}).trim(),
@@ -215,7 +280,6 @@ function getVersionFromGlobalJson(globalJsonPath: string): string {
version = globalJson.sdk.version;
const rollForward = globalJson.sdk.rollForward;
if (rollForward && !semver.prerelease(version)) {
const versionPattern = /^\d+\.\d+\.[1-9]\d{2,}$/;
if (!versionPattern.test(version)) {
throw new Error(
`Version '${version}' is not valid for the 'sdk.version' value in global.json. ` +
@@ -244,9 +308,21 @@ function getVersionFromGlobalJson(globalJsonPath: string): string {
version = `${major}.${minor}.${feature}xx`;
break;
}
if (ROLL_FORWARD_POLICIES.includes(rollForward)) {
minimumVersion = globalJson.sdk.version;
rollForwardPolicy = rollForward;
}
} else if (
!rollForward &&
!semver.prerelease(version) &&
versionPattern.test(version)
) {
minimumVersion = version;
rollForwardPolicy = 'patch';
}
}
return version;
return {version, minimumVersion, rollForward: rollForwardPolicy};
}
function outputInstalledVersion(