31 Commits
v3.3 ... v4.2.0

Author SHA1 Message Date
fe7b79cd5e build 2024-04-15 19:42:43 +00:00
df50d879fa Release v4.2.0 (#134)
* Update CHANGELOG.md

* Update package.json

* Update README.md
2024-04-15 15:41:59 -04:00
08d7123a4a Bump undici from 5.28.2 to 5.28.4 (#133)
Bumps [undici](https://github.com/nodejs/undici) from 5.28.2 to 5.28.4.
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v5.28.2...v5.28.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-12 16:23:04 -04:00
0a0c55a4c3 Fix os detection and archive extension (#124) 2024-04-12 15:46:47 -04:00
d00ce1cb5e update to release workflow major version tag (#132) 2024-03-08 13:33:11 -05:00
4c255dde26 publish version 4.1.0 (#131)
* publish version 4.1.0

* update version
2024-03-01 11:45:44 -05:00
ec8dd7c209 switching to fetching latest version from the dedicated file (#130) 2024-03-01 10:15:56 -06:00
efbd96d464 Fix action version in README.md (#129)
`v4` is not a valid tag, `v4.0.0` is.

See https://github.com/Azure/setup-helm/tags
2024-02-27 17:32:24 -05:00
859dc38e9a v4 readme update (#127) 2024-02-13 16:11:44 -05:00
0788eb3317 v4 release and required workflow updates (#125)
* v4 release and workflow update

* Update release-pr.yml

* Update release-pr.yml

* Update package.json

* format
2024-02-12 18:30:35 -05:00
208de6bd49 Upgrade action to use node20 (#121)
* Action update:

- Bump all dependencies
- Rewrite `getLatestHelmVersion()` function without graphql

* Bump stableHelmVersion

* Update readme and action.yaml

* Revert and rewrite with @octokit/action

* Add latest to integration test

* Bump action's versions

* Set github.token as default input

* Replace deprecated jest methods

* Add prettier to dev dependencies, fix prettier issues
2024-02-08 13:49:56 -05:00
1f87a575d0 Bump @babel/traverse from 7.18.6 to 7.23.2 (#118)
Bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 7.18.6 to 7.23.2.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.23.2/packages/babel-traverse)

---
updated-dependencies:
- dependency-name: "@babel/traverse"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-04 12:16:17 -05:00
d336b89352 Add action input to set download base url (#113)
* feat: add downloadBaseURL action input

* feat: downloadHelm and getHelmDownloadURL added baseURL function argument

* refactor: building the helm download url

* chore: format code

---------

Co-authored-by: Paul Vollmer <mail@paulvollmer.net>
2024-01-02 09:30:48 -05:00
ac5ee1fca8 Update helm to v3.11.1 (#108) 2023-02-16 19:41:09 -05:00
638a523e0c Bump decode-uri-component from 0.2.0 to 0.2.2 (#105)
Bumps [decode-uri-component](https://github.com/SamVerschueren/decode-uri-component) from 0.2.0 to 0.2.2.
- [Release notes](https://github.com/SamVerschueren/decode-uri-component/releases)
- [Commits](https://github.com/SamVerschueren/decode-uri-component/compare/v0.2.0...v0.2.2)

---
updated-dependencies:
- dependency-name: decode-uri-component
  dependency-type: indirect
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-10 13:29:26 -05:00
0a7ec47357 Bump json5 from 2.2.1 to 2.2.3 (#107)
Bumps [json5](https://github.com/json5/json5) from 2.2.1 to 2.2.3.
- [Release notes](https://github.com/json5/json5/releases)
- [Changelog](https://github.com/json5/json5/blob/main/CHANGELOG.md)
- [Commits](https://github.com/json5/json5/compare/v2.2.1...v2.2.3)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-10 13:29:07 -05:00
3419a8fa8f Update README to reflect current default helm version (#106) 2023-01-10 13:26:20 -05:00
f77071b246 minor nit , upgrade helm and GH action version updates (#102) 2022-12-27 09:28:27 -05:00
a4617735aa Added support message (#95) 2022-11-28 13:51:20 -05:00
2dafda840c refactor: cleanup GH gql query (#100) 2022-10-27 09:44:25 -04:00
b70d33f56d Update actions/toolkit versions. (#94)
Update actions/core to version 1.10.0 to avoid the warning:
```
The `set-output` command is deprecated and will be disabled soon.
Please upgrade to using Environment Files. For more information see:
https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/
```
2022-10-17 14:07:50 -04:00
3c00c0152f syntax error fixes (#93) 2022-09-21 11:12:26 -07:00
22d14750db Add the bug report and feature request form (#92) 2022-09-06 13:09:28 -04:00
a22741c887 Bump @actions/core from 1.9.0 to 1.9.1 (#91)
Bumps [@actions/core](https://github.com/actions/toolkit/tree/HEAD/packages/core) from 1.9.0 to 1.9.1.
- [Release notes](https://github.com/actions/toolkit/releases)
- [Changelog](https://github.com/actions/toolkit/blob/main/packages/core/RELEASES.md)
- [Commits](https://github.com/actions/toolkit/commits/HEAD/packages/core)

---
updated-dependencies:
- dependency-name: "@actions/core"
  dependency-type: direct:production
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-08-22 13:44:48 -04:00
f850d12cb3 Fix [object Object] version bug (#88)
* fix auth

* fix tag bug
2022-07-25 13:53:22 -04:00
17cd77473c fix auth (#86) 2022-07-25 13:13:50 -04:00
484a64052d Swap to GraphQL and add logging (#83)
* swap to graphql

* add logs

* swap to group log

* fix test
2022-07-11 10:12:11 -04:00
6e32762c2e Minor spelling correction in README.md (#81) 2022-07-06 10:45:43 -04:00
17c21ab68c upgrade ncc version (#82)
* upgrade ncc version

* Clear and verify npm cache

Co-authored-by: Vidya Reddy <vidyareddy@microsoft.com>
2022-07-05 10:17:02 -07:00
7f0153c54c Vidya reddy prettier (#75)
* upgraded to Node16

* Enforce Prettier

* code fix

* Update package-lock.json

resolved the conflicts.

* executed prettier

Co-authored-by: Vidya Reddy <vidyareddy@microsoft.com>
2022-06-27 18:27:44 -04:00
a14110f6e6 increment version in README to v3 (#79) 2022-06-27 14:20:10 -04:00
26 changed files with 35736 additions and 11908 deletions

View File

@ -0,0 +1,36 @@
name: Bug Report
description: File a bug report specifying all inputs you provided for the action, we will respond to this thread with any questions.
title: 'Bug: '
labels: ['bug', 'triage']
assignees: '@Azure/aks-atlanta'
body:
- type: textarea
id: What-happened
attributes:
label: What happened?
description: Tell us what happened and how is it different from the expected?
placeholder: Tell us what you see!
validations:
required: true
- type: checkboxes
id: Version
attributes:
label: Version
options:
- label: I am using the latest version
required: true
- type: input
id: Runner
attributes:
label: Runner
description: What runner are you using?
placeholder: Mention the runner info (self-hosted, operating system)
validations:
required: true
- type: textarea
id: Logs
attributes:
label: Relevant log output
description: Run in debug mode for the most verbose logs. Please feel free to attach a screenshot of the logs
validations:
required: true

6
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1,6 @@
blank_issues_enabled: false
contact_links:
- name: GitHub Action "setup-helm" Support
url: https://github.com/Azure/setup-helm
security: https://github.com/Azure/setup-helm/blob/main/SECURITY.md
about: Please ask and answer questions here.

View File

@ -0,0 +1,13 @@
name: Feature Request
description: File a Feature Request form, we will respond to this thread with any questions.
title: 'Feature Request: '
labels: ['Feature']
assignees: '@Azure/aks-atlanta'
body:
- type: textarea
id: Feature_request
attributes:
label: Feature request
description: Provide example functionality and links to relevant docs
validations:
required: true

View File

@ -4,7 +4,4 @@ about: Create a report to help us improve
title: '' title: ''
labels: need-to-triage labels: need-to-triage
assignees: '' assignees: ''
--- ---

View File

@ -2,34 +2,34 @@ name: setting-default-labels
# Controls when the action will run. # Controls when the action will run.
on: on:
schedule: schedule:
- cron: "0 0/3 * * *" - cron: '0 0/3 * * *'
# A workflow run is made up of one or more jobs that can run sequentially or in parallel # A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs: jobs:
build: build:
# The type of runner that the job will run on # The type of runner that the job will run on
runs-on: ubuntu-latest runs-on: ubuntu-latest
# Steps represent a sequence of tasks that will be executed as part of the job # Steps represent a sequence of tasks that will be executed as part of the job
steps: steps:
- uses: actions/stale@v3 - uses: actions/stale@v9
name: Setting issue as idle name: Setting issue as idle
with: with:
repo-token: ${{ secrets.GITHUB_TOKEN }} repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: "This issue is idle because it has been open for 14 days with no activity." stale-issue-message: 'This issue is idle because it has been open for 14 days with no activity.'
stale-issue-label: "idle" stale-issue-label: 'idle'
days-before-stale: 14 days-before-stale: 14
days-before-close: -1 days-before-close: -1
operations-per-run: 100 operations-per-run: 100
exempt-issue-labels: "backlog" exempt-issue-labels: 'backlog'
- uses: actions/stale@v3 - uses: actions/stale@v9
name: Setting PR as idle name: Setting PR as idle
with: with:
repo-token: ${{ secrets.GITHUB_TOKEN }} repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-pr-message: "This PR is idle because it has been open for 14 days with no activity." stale-pr-message: 'This PR is idle because it has been open for 14 days with no activity.'
stale-pr-label: "idle" stale-pr-label: 'idle'
days-before-stale: 14 days-before-stale: 14
days-before-close: -1 days-before-close: -1
operations-per-run: 100 operations-per-run: 100

View File

@ -1,65 +1,87 @@
name: "Trigger Integration tests" name: 'Trigger Integration tests'
on: on:
pull_request: pull_request:
branches: branches:
- main - main
- "releases/*" - 'releases/*'
jobs: jobs:
trigger-integration-tests: trigger-integration-tests:
name: Trigger Integration tests name: Trigger Integration tests
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
HELM_3_8_0: "v3.8.0" HELM_3_8_0: 'v3.8.0'
HELM_3_7_2: "v3.7.2" HELM_3_7_2: 'v3.7.2'
HELM_NO_V: "3.5.0" HELM_NO_V: '3.5.0'
PR_BASE_REF: ${{ github.event.pull_request.base.ref }} PR_BASE_REF: ${{ github.event.pull_request.base.ref }}
steps: steps:
- name: Check out repository - name: Check out repository
uses: actions/checkout@v2 uses: actions/checkout@v4
- name: npm install and build - name: npm install and build
id: action-npm-build id: action-npm-build
run: | run: |
echo $PR_BASE_REF echo $PR_BASE_REF
if [[ $PR_BASE_REF != releases/* ]]; then if [[ $PR_BASE_REF != releases/* ]]; then
npm install npm install
npm run build npm run build
fi fi
- name: Setup helm - name: Setup helm
uses: ./ uses: ./
with: with:
version: ${{ env.HELM_3_8_0 }} version: ${{ env.HELM_3_8_0 }}
- name: Validate helm 3.8.0 - name: Validate helm 3.8.0
run: | run: |
if [[ $(helm version) != *$HELM_3_8_0* ]]; then if [[ $(helm version) != *$HELM_3_8_0* ]]; then
echo "HELM VERSION INCORRECT: HELM VERSION DOES NOT CONTAIN v3.8.0" echo "HELM VERSION INCORRECT: HELM VERSION DOES NOT CONTAIN v3.8.0"
echo "HELM VERSION OUTPUT: $(helm version)" echo "HELM VERSION OUTPUT: $(helm version)"
exit 1 exit 1
else else
echo "HELM VERSION $HELM_3_8_0 INSTALLED SUCCESSFULLY" echo "HELM VERSION $HELM_3_8_0 INSTALLED SUCCESSFULLY"
fi fi
- name: Setup helm 3.7.2 - name: Setup helm 3.7.2
uses: ./ uses: ./
with: with:
version: ${{ env.HELM_3_7_2 }} version: ${{ env.HELM_3_7_2 }}
- name: Validate 3.7.2 - name: Validate 3.7.2
run: | run: |
if [[ $(helm version) != *$HELM_3_7_2* ]]; then if [[ $(helm version) != *$HELM_3_7_2* ]]; then
echo "HELM VERSION INCORRECT: HELM VERSION DOES NOT CONTAIN v3.7.2" echo "HELM VERSION INCORRECT: HELM VERSION DOES NOT CONTAIN v3.7.2"
echo "HELM VERSION OUTPUT: $(helm version)" echo "HELM VERSION OUTPUT: $(helm version)"
exit 1 exit 1
else else
echo "HELM VERSION $HELM_3_7_2 INSTALLED SUCCESSFULLY" echo "HELM VERSION $HELM_3_7_2 INSTALLED SUCCESSFULLY"
fi fi
- name: Setup helm 3.5.0 with no v in version - name: Setup helm 3.5.0 with no v in version
uses: ./ uses: ./
with: with:
version: ${{ env.HELM_NO_V }} version: ${{ env.HELM_NO_V }}
- name: Validate 3.5.0 without v in version - name: Validate 3.5.0 without v in version
run: | run: |
if [[ $(helm version) != *$HELM_NO_V* ]]; then if [[ $(helm version) != *$HELM_NO_V* ]]; then
echo "HELM VERSION INCORRECT: HELM VERSION DOES NOT CONTAIN v3.5.0" echo "HELM VERSION INCORRECT: HELM VERSION DOES NOT CONTAIN v3.5.0"
echo "HELM VERSION OUTPUT: $(helm version)" echo "HELM VERSION OUTPUT: $(helm version)"
exit 1 exit 1
else else
echo "HELM VERSION $HELM_3_5_0 INSTALLED SUCCESSFULLY" echo "HELM VERSION $HELM_3_5_0 INSTALLED SUCCESSFULLY"
fi fi
- name: Setup helm latest version
uses: ./
with:
version: latest
token: ${{ secrets.GITHUB_TOKEN }}
- name: Validate latest
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
HELM_LATEST=$(gh release list \
--repo helm/helm \
--exclude-drafts \
--exclude-pre-releases \
--limit 1 | awk '{print $4}')
if [[ $(helm version) != *$HELM_LATEST* ]]; then
echo "HELM VERSION INCORRECT: HELM VERSION DOES NOT CONTAIN $HELM_LATEST"
echo "HELM VERSION OUTPUT: $(helm version)"
exit 1
else
echo "HELM VERSION $HELM_LATEST INSTALLED SUCCESSFULLY"
fi

18
.github/workflows/prettify-code.yml vendored Normal file
View File

@ -0,0 +1,18 @@
name: 'Run prettify'
on:
pull_request:
push:
branches: [main]
jobs:
prettier:
name: Prettier Check
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Enforce Prettier
uses: actionsx/prettier@v3
with:
args: --check .

View File

@ -1,14 +1,18 @@
name: Create release PR name: Release Project
on: on:
workflow_dispatch: push:
inputs: branches:
release: - main
description: "Define release version (ex: v1, v2, v3)" paths:
required: true - CHANGELOG.md
workflow_dispatch:
jobs: jobs:
release-pr: release:
uses: OliverMKing/javascript-release-workflow/.github/workflows/release-pr.yml@main permissions:
with: actions: read
release: ${{ github.event.inputs.release }} contents: write
uses: Azure/action-release-workflows/.github/workflows/release_js_project.yaml@v1
with:
changelogPath: ./CHANGELOG.md

View File

@ -1,10 +1,10 @@
name: Tag and create release draft name: Tag and create release draft
on: on:
push: push:
branches: branches:
- releases/* - releases/*
jobs: jobs:
tag-and-release: tag-and-release:
uses: OliverMKing/javascript-release-workflow/.github/workflows/tag-and-release.yml@main uses: OliverMKing/javascript-release-workflow/.github/workflows/tag-and-release.yml@main

View File

@ -1,21 +1,21 @@
name: "Run unit tests." name: 'Run unit tests.'
on: # rebuild any PRs and main branch changes on: # rebuild any PRs and main branch changes
pull_request: pull_request:
branches: branches:
- main - main
- "releases/*" - 'releases/*'
push: push:
branches: branches:
- main - main
- "releases/*" - 'releases/*'
jobs: jobs:
build: # make sure build/ci works properly build: # make sure build/ci works properly
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v4
- name: Run L0 tests. - name: Run L0 tests.
run: | run: |
npm install npm install
npm test npm test

3
.gitignore vendored
View File

@ -11,8 +11,6 @@ pids
*.seed *.seed
*.pid.lock *.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul # Coverage directory used by tools like istanbul
coverage coverage
@ -64,4 +62,3 @@ node_modules
coverage coverage
# Transpiled JS # Transpiled JS
lib/

4
.prettierignore Normal file
View File

@ -0,0 +1,4 @@
# dependencies
/node_modules
coverage
/lib

8
.prettierrc.json Normal file
View File

@ -0,0 +1,8 @@
{
"trailingComma": "none",
"bracketSpacing": false,
"semi": false,
"tabWidth": 3,
"singleQuote": true,
"printWidth": 80
}

13
CHANGELOG.md Normal file
View File

@ -0,0 +1,13 @@
# Change Log
## [4.2.0] - 2024-04-15
- #124 Fix OS detection and download OS-native archive extension
## [4.1.0] - 2024-03-01
- #130 switches to use Helm published file to read latest version instead of using GitHub releases
## [4.0.0] - 2024-02-12
- #121 update to node20 as node16 is deprecated

View File

@ -1,9 +1,9 @@
# Microsoft Open Source Code of Conduct # Microsoft Open Source Code of Conduct
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
Resources: Resources:
- [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/)
- [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
- Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns

View File

@ -1,6 +1,6 @@
# Contributing # Contributing
This project welcomes contributions and suggestions. Most contributions require you to agree to a This project welcomes contributions and suggestions. Most contributions require you to agree to a
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.

View File

@ -1,29 +1,38 @@
# Setup Helm # Setup Helm
Install a specific version of helm binary on the runner.
## Example Install a specific version of helm binary on the runner.
Acceptable values are latest or any semantic version string like v3.5.0 Use this action in workflow to define which version of helm will be used. v2 of this action only supports Helm3. ## Example
```yaml Acceptable values are latest or any semantic version string like v3.5.0 Use this action in workflow to define which version of helm will be used. v2+ of this action only support Helm3.
- uses: azure/setup-helm@v1
with: ```yaml
version: '<version>' # default is latest stable - uses: azure/setup-helm@v4.2.0
id: install with:
``` version: '<version>' # default is latest (stable)
id: install
The cached helm binary path is prepended to the PATH environment variable as well as stored in the helm-path output variable. ```
Refer to the action metadata file for details about all the inputs https://github.com/Azure/setup-helm/blob/master/action.yml
> [!NOTE]
# Contributing > If something goes wrong with fetching the latest version the action will use the hardcoded default stable version (currently v3.13.3). If you rely on a certain version higher than the default, you should explicitly use that version instead of latest.
This project welcomes contributions and suggestions. Most contributions require you to agree to a The cached helm binary path is prepended to the PATH environment variable as well as stored in the helm-path output variable.
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us Refer to the action metadata file for details about all the inputs https://github.com/Azure/setup-helm/blob/master/action.yml
the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.
## Contributing
When you submit a pull request, a CLA bot will automatically determine whether you need to provide
a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions This project welcomes contributions and suggestions. Most contributions require you to agree to a
provided by the bot. You will only need to do this once across all repos using our CLA. Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or When you submit a pull request, a CLA bot will automatically determine whether you need to provide
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions
provided by the bot. You will only need to do this once across all repos using our CLA.
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
## Support
setup-helm is an open source project that is [**not** covered by the Microsoft Azure support policy](https://support.microsoft.com/en-us/help/2941892/support-for-linux-and-open-source-technology-in-azure). [Please search open issues here](https://github.com/Azure/setup-helm/issues), and if your issue isn't already represented please [open a new one](https://github.com/Azure/setup-helm/issues/new/choose). The project maintainers will respond to the best of their abilities.

View File

@ -1,41 +1,41 @@
<!-- BEGIN MICROSOFT SECURITY.MD V0.0.2 BLOCK --> <!-- BEGIN MICROSOFT SECURITY.MD V0.0.2 BLOCK -->
## Security ## Security
Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [many more](https://opensource.microsoft.com/). Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [many more](https://opensource.microsoft.com/).
If you believe you have found a security vulnerability in any Microsoft-owned repository that meets Microsoft's [definition](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)) of a security vulnerability, please report it to us as described below. If you believe you have found a security vulnerability in any Microsoft-owned repository that meets Microsoft's [definition](<https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)>) of a security vulnerability, please report it to us as described below.
## Reporting Security Issues ## Reporting Security Issues
**Please do not report security vulnerabilities through public GitHub issues.** **Please do not report security vulnerabilities through public GitHub issues.**
Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report).
If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc). If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc).
You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc).
Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
* Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) - Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
* Full paths of source file(s) related to the manifestation of the issue - Full paths of source file(s) related to the manifestation of the issue
* The location of the affected source code (tag/branch/commit or direct URL) - The location of the affected source code (tag/branch/commit or direct URL)
* Any special configuration required to reproduce the issue - Any special configuration required to reproduce the issue
* Step-by-step instructions to reproduce the issue - Step-by-step instructions to reproduce the issue
* Proof-of-concept or exploit code (if possible) - Proof-of-concept or exploit code (if possible)
* Impact of the issue, including how an attacker might exploit the issue - Impact of the issue, including how an attacker might exploit the issue
This information will help us triage your report more quickly. This information will help us triage your report more quickly.
If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs. If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs.
## Preferred Languages ## Preferred Languages
We prefer all communications to be in English. We prefer all communications to be in English.
## Policy ## Policy
Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd).
<!-- END MICROSOFT SECURITY.MD BLOCK --> <!-- END MICROSOFT SECURITY.MD BLOCK -->

View File

@ -1,15 +1,24 @@
name: "Helm tool installer" name: 'Helm tool installer'
description: "Install a specific version of helm binary. Acceptable values are latest or any semantic version string like 1.15.0" description: 'Install a specific version of helm binary. Acceptable values are latest or any semantic version string like 1.15.0'
inputs: inputs:
version: version:
description: "Version of helm" description: 'Version of helm'
required: true required: true
default: "latest" default: 'latest'
token:
description: GitHub token. Used to be required to fetch the latest version
required: false
deprecationMessage: 'GitHub token is no longer required'
default: '${{ github.token }}'
downloadBaseURL:
description: 'Set the download base URL'
required: false
default: 'https://get.helm.sh'
outputs: outputs:
helm-path: helm-path:
description: "Path to the cached helm binary" description: 'Path to the cached helm binary'
branding: branding:
color: "blue" color: 'blue'
runs: runs:
using: "node16" using: 'node20'
main: "lib/index.js" main: 'lib/index.js'

View File

@ -1,18 +1,18 @@
module.exports = { module.exports = {
clearMocks: true, clearMocks: true,
moduleFileExtensions: ['js', 'ts'], moduleFileExtensions: ['js', 'ts'],
testEnvironment: 'node', testEnvironment: 'node',
testMatch: ['**/*.test.ts'], testMatch: ['**/*.test.ts'],
transform: { transform: {
'^.+\\.ts$': 'ts-jest' '^.+\\.ts$': 'ts-jest'
}, },
verbose: true, verbose: true,
coverageThreshold: { coverageThreshold: {
"global": { global: {
"branches": 0, branches: 0,
"functions": 14, functions: 14,
"lines": 27, lines: 27,
"statements": 27 statements: 27
} }
} }
} }

30846
lib/index.js Normal file

File diff suppressed because one or more lines are too long

15223
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,30 +1,35 @@
{ {
"name": "setuphelm", "name": "setuphelm",
"version": "0.0.0", "version": "4.2.0",
"private": true, "private": true,
"description": "Setup helm", "description": "Setup helm",
"author": "Anumita Shenoy", "author": "Anumita Shenoy",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@actions/core": "^1.2.6", "@actions/core": "^1.10.0",
"@actions/exec": "^1.0.0", "@actions/exec": "^1.1.1",
"@actions/io": "^1.0.0", "@actions/io": "^1.1.2",
"@actions/tool-cache": "1.1.2", "@actions/tool-cache": "2.0.1",
"@octokit/graphql": "^4.6.1", "@octokit/action": "^6.0.7",
"semver": "^6.1.0" "ncc": "^0.3.6",
}, "semver": "^7.5.4"
"main": "lib/index.js", },
"scripts": { "main": "lib/index.js",
"build": "ncc build src/run.ts -o lib", "scripts": {
"test": "jest", "prebuild": "npm i ncc",
"test-coverage": "jest --coverage" "build": "ncc build src/run.ts -o lib",
}, "test": "jest",
"devDependencies": { "test-coverage": "jest --coverage",
"@types/jest": "^26.0.0", "format": "prettier --write .",
"@types/node": "^12.0.10", "format-check": "prettier --check ."
"@vercel/ncc": "^0.33.1", },
"jest": "^26.0.1", "devDependencies": {
"ts-jest": "^26.0.0", "@types/jest": "^29.5.11",
"typescript": "^3.5.2" "@types/node": "^20.11.8",
} "@vercel/ncc": "^0.38.1",
"jest": "^29.7.0",
"prettier": "^3.2.5",
"ts-jest": "^29.1.2",
"typescript": "^5.3.3"
}
} }

View File

@ -1,268 +1,299 @@
import * as run from "./run"; import * as run from './run'
import * as os from "os"; import * as os from 'os'
import * as toolCache from "@actions/tool-cache"; import * as toolCache from '@actions/tool-cache'
import * as fs from "fs"; import * as fs from 'fs'
import * as path from "path"; import * as path from 'path'
import * as core from "@actions/core"; import * as core from '@actions/core'
describe("run.ts", () => { describe('run.ts', () => {
test("getExecutableExtension() - return .exe when os is Windows", () => { const downloadBaseURL = 'https://test.tld'
jest.spyOn(os, "type").mockReturnValue("Windows_NT");
expect(run.getExecutableExtension()).toBe(".exe"); // Cleanup mocks after each test to ensure that subsequent tests are not affected by the mocks.
expect(os.type).toBeCalled(); afterEach(() => {
}); jest.restoreAllMocks()
})
test("getExecutableExtension() - return empty string for non-windows OS", () => { test('getExecutableExtension() - return .exe when os is Windows', () => {
jest.spyOn(os, "type").mockReturnValue("Darwin"); jest.spyOn(os, 'platform').mockReturnValue('win32')
expect(run.getExecutableExtension()).toBe(""); expect(run.getExecutableExtension()).toBe('.exe')
expect(os.type).toBeCalled(); expect(os.platform).toHaveBeenCalled()
}); })
test("getHelmDownloadURL() - return the URL to download helm for Linux", () => { test('getExecutableExtension() - return empty string for non-windows OS', () => {
jest.spyOn(os, "type").mockReturnValue("Linux"); jest.spyOn(os, 'platform').mockReturnValue('darwin')
jest.spyOn(os, "arch").mockReturnValueOnce("unknown");
const kubectlLinuxUrl = "https://get.helm.sh/helm-v3.8.0-linux-amd64.zip";
expect(run.getHelmDownloadURL("v3.8.0")).toBe(kubectlLinuxUrl); expect(run.getExecutableExtension()).toBe('')
expect(os.type).toBeCalled(); expect(os.platform).toHaveBeenCalled()
expect(os.arch).toBeCalled(); })
// arm64 test('getHelmDownloadURL() - return the URL to download helm for Linux amd64', () => {
jest.spyOn(os, "type").mockReturnValue("Linux"); jest.spyOn(os, 'platform').mockReturnValue('linux')
jest.spyOn(os, "arch").mockReturnValueOnce("arm64"); jest.spyOn(os, 'arch').mockReturnValue('x64')
const kubectlLinuxArm64Url = const expected = 'https://test.tld/helm-v3.8.0-linux-amd64.tar.gz'
"https://get.helm.sh/helm-v3.8.0-linux-arm64.zip";
expect(run.getHelmDownloadURL("v3.8.0")).toBe(kubectlLinuxArm64Url); expect(run.getHelmDownloadURL(downloadBaseURL, 'v3.8.0')).toBe(expected)
expect(os.type).toBeCalled(); expect(os.platform).toHaveBeenCalled()
expect(os.arch).toBeCalled(); expect(os.arch).toHaveBeenCalled()
}); })
test("getHelmDownloadURL() - return the URL to download helm for Darwin", () => { test('getHelmDownloadURL() - return the URL to download helm for Linux arm64', () => {
jest.spyOn(os, "type").mockReturnValue("Darwin"); jest.spyOn(os, 'platform').mockReturnValue('linux')
jest.spyOn(os, "arch").mockReturnValueOnce("unknown"); jest.spyOn(os, 'arch').mockReturnValue('arm64')
const kubectlDarwinUrl = "https://get.helm.sh/helm-v3.8.0-darwin-amd64.zip"; const expected = 'https://test.tld/helm-v3.8.0-linux-arm64.tar.gz'
expect(run.getHelmDownloadURL("v3.8.0")).toBe(kubectlDarwinUrl); expect(run.getHelmDownloadURL(downloadBaseURL, 'v3.8.0')).toBe(expected)
expect(os.type).toBeCalled(); expect(os.platform).toHaveBeenCalled()
expect(os.arch).toBeCalled(); expect(os.arch).toHaveBeenCalled()
})
// arm64 test('getHelmDownloadURL() - return the URL to download helm for Darwin x64', () => {
jest.spyOn(os, "type").mockReturnValue("Darwin"); jest.spyOn(os, 'platform').mockReturnValue('darwin')
jest.spyOn(os, "arch").mockReturnValueOnce("arm64"); jest.spyOn(os, 'arch').mockReturnValue('x64')
const kubectlDarwinArm64Url = const expected = 'https://test.tld/helm-v3.8.0-darwin-amd64.tar.gz'
"https://get.helm.sh/helm-v3.8.0-darwin-arm64.zip";
expect(run.getHelmDownloadURL("v3.8.0")).toBe(kubectlDarwinArm64Url); expect(run.getHelmDownloadURL(downloadBaseURL, 'v3.8.0')).toBe(expected)
expect(os.type).toBeCalled(); expect(os.platform).toHaveBeenCalled()
expect(os.arch).toBeCalled(); expect(os.arch).toHaveBeenCalled()
}); })
test("getValidVersion() - return version with v prepended", () => { test('getHelmDownloadURL() - return the URL to download helm for Darwin arm64', () => {
expect(run.getValidVersion("3.8.0")).toBe("v3.8.0"); jest.spyOn(os, 'platform').mockReturnValue('darwin')
}); jest.spyOn(os, 'arch').mockReturnValue('arm64')
const expected = 'https://test.tld/helm-v3.8.0-darwin-arm64.tar.gz'
test("getHelmDownloadURL() - return the URL to download helm for Windows", () => {
jest.spyOn(os, "type").mockReturnValue("Windows_NT");
const kubectlWindowsUrl = expect(run.getHelmDownloadURL(downloadBaseURL, 'v3.8.0')).toBe(expected)
"https://get.helm.sh/helm-v3.8.0-windows-amd64.zip"; expect(os.platform).toHaveBeenCalled()
expect(run.getHelmDownloadURL("v3.8.0")).toBe(kubectlWindowsUrl); expect(os.arch).toHaveBeenCalled()
expect(os.type).toBeCalled(); })
});
test("getLatestHelmVersion() - return the latest version of HELM", async () => { test('getHelmDownloadURL() - return the URL to download helm for Windows', () => {
try { jest.spyOn(os, 'platform').mockReturnValue('win32')
expect(await run.getLatestHelmVersion()).toBe("v3.8.0"); jest.spyOn(os, 'arch').mockReturnValue('x64')
} catch (e) {
return e;
}
});
test("walkSync() - return path to the all files matching fileToFind in dir", () => { const expected = 'https://test.tld/helm-v3.8.0-windows-amd64.zip'
jest.spyOn(fs, "readdirSync").mockImplementation((file, _) => { expect(run.getHelmDownloadURL(downloadBaseURL, 'v3.8.0')).toBe(expected)
if (file == "mainFolder") expect(os.platform).toHaveBeenCalled()
return [ })
"file1" as unknown as fs.Dirent,
"file2" as unknown as fs.Dirent,
"folder1" as unknown as fs.Dirent,
"folder2" as unknown as fs.Dirent,
];
if (file == path.join("mainFolder", "folder1"))
return [
"file11" as unknown as fs.Dirent,
"file12" as unknown as fs.Dirent,
];
if (file == path.join("mainFolder", "folder2"))
return [
"file21" as unknown as fs.Dirent,
"file22" as unknown as fs.Dirent,
];
});
jest.spyOn(core, "debug").mockImplementation();
jest.spyOn(fs, "statSync").mockImplementation((file) => {
const isDirectory =
(file as string).toLowerCase().indexOf("file") == -1 ? true : false;
return { isDirectory: () => isDirectory } as fs.Stats;
});
expect(run.walkSync("mainFolder", null, "file21")).toEqual([ test('getLatestHelmVersion() - return the latest version of HELM', async () => {
path.join("mainFolder", "folder2", "file21"), const res = {
]); status: 200,
expect(fs.readdirSync).toBeCalledTimes(3); text: async () => 'v9.99.999'
expect(fs.statSync).toBeCalledTimes(8); } as Response
}); global.fetch = jest.fn().mockReturnValue(res)
expect(await run.getLatestHelmVersion()).toBe('v9.99.999')
})
test("walkSync() - return empty array if no file with name fileToFind exists", () => { test('getLatestHelmVersion() - return the stable version of HELM when simulating a network error', async () => {
jest.spyOn(fs, "readdirSync").mockImplementation((file, _) => { const errorMessage: string = 'Network Error'
if (file == "mainFolder") global.fetch = jest.fn().mockRejectedValueOnce(new Error(errorMessage))
return [ expect(await run.getLatestHelmVersion()).toBe('v3.13.3')
"file1" as unknown as fs.Dirent, })
"file2" as unknown as fs.Dirent,
"folder1" as unknown as fs.Dirent,
"folder2" as unknown as fs.Dirent,
];
if (file == path.join("mainFolder", "folder1"))
return [
"file11" as unknown as fs.Dirent,
"file12" as unknown as fs.Dirent,
];
if (file == path.join("mainFolder", "folder2"))
return [
"file21" as unknown as fs.Dirent,
"file22" as unknown as fs.Dirent,
];
});
jest.spyOn(core, "debug").mockImplementation();
jest.spyOn(fs, "statSync").mockImplementation((file) => {
const isDirectory =
(file as string).toLowerCase().indexOf("file") == -1 ? true : false;
return { isDirectory: () => isDirectory } as fs.Stats;
});
expect(run.walkSync("mainFolder", null, "helm.exe")).toEqual([]); test('getValidVersion() - return version with v prepended', () => {
expect(fs.readdirSync).toBeCalledTimes(3); expect(run.getValidVersion('3.8.0')).toBe('v3.8.0')
expect(fs.statSync).toBeCalledTimes(8); })
});
test("findHelm() - change access permissions and find the helm in given directory", () => { test('walkSync() - return path to the all files matching fileToFind in dir', () => {
jest.spyOn(fs, "chmodSync").mockImplementation(() => {}); jest.spyOn(fs, 'readdirSync').mockImplementation((file, _) => {
jest.spyOn(fs, "readdirSync").mockImplementation((file, _) => { if (file == 'mainFolder')
if (file == "mainFolder") return ["helm.exe" as unknown as fs.Dirent]; return [
}); 'file1' as unknown as fs.Dirent,
jest.spyOn(fs, "statSync").mockImplementation((file) => { 'file2' as unknown as fs.Dirent,
const isDirectory = 'folder1' as unknown as fs.Dirent,
(file as string).indexOf("folder") == -1 ? false : true; 'folder2' as unknown as fs.Dirent
return { isDirectory: () => isDirectory } as fs.Stats; ]
}); if (file == path.join('mainFolder', 'folder1'))
jest.spyOn(os, "type").mockReturnValue("Windows_NT"); return [
'file11' as unknown as fs.Dirent,
'file12' as unknown as fs.Dirent
]
if (file == path.join('mainFolder', 'folder2'))
return [
'file21' as unknown as fs.Dirent,
'file22' as unknown as fs.Dirent
]
return []
})
jest.spyOn(core, 'debug').mockImplementation()
jest.spyOn(fs, 'statSync').mockImplementation((file) => {
const isDirectory =
(file as string).toLowerCase().indexOf('file') == -1 ? true : false
return {isDirectory: () => isDirectory} as fs.Stats
})
expect(run.findHelm("mainFolder")).toBe( expect(run.walkSync('mainFolder', null, 'file21')).toEqual([
path.join("mainFolder", "helm.exe") path.join('mainFolder', 'folder2', 'file21')
); ])
}); expect(fs.readdirSync).toHaveBeenCalledTimes(3)
expect(fs.statSync).toHaveBeenCalledTimes(8)
})
test("findHelm() - throw error if executable not found", () => { test('walkSync() - return empty array if no file with name fileToFind exists', () => {
jest.spyOn(fs, "chmodSync").mockImplementation(() => {}); jest.spyOn(fs, 'readdirSync').mockImplementation((file, _) => {
jest.spyOn(fs, "readdirSync").mockImplementation((file, _) => { if (file == 'mainFolder')
if (file == "mainFolder") return []; return [
}); 'file1' as unknown as fs.Dirent,
jest.spyOn(fs, "statSync").mockImplementation((file) => { 'file2' as unknown as fs.Dirent,
return { isDirectory: () => true } as fs.Stats; 'folder1' as unknown as fs.Dirent,
}); 'folder2' as unknown as fs.Dirent
jest.spyOn(os, "type").mockReturnValue("Windows_NT"); ]
expect(() => run.findHelm("mainFolder")).toThrow( if (file == path.join('mainFolder', 'folder1'))
"Helm executable not found in path mainFolder" return [
); 'file11' as unknown as fs.Dirent,
}); 'file12' as unknown as fs.Dirent
]
if (file == path.join('mainFolder', 'folder2'))
return [
'file21' as unknown as fs.Dirent,
'file22' as unknown as fs.Dirent
]
return []
})
jest.spyOn(core, 'debug').mockImplementation()
jest.spyOn(fs, 'statSync').mockImplementation((file) => {
const isDirectory =
(file as string).toLowerCase().indexOf('file') == -1 ? true : false
return {isDirectory: () => isDirectory} as fs.Stats
})
test("downloadHelm() - download helm and return path to it", async () => { expect(run.walkSync('mainFolder', null, 'helm.exe')).toEqual([])
jest.spyOn(toolCache, "find").mockReturnValue(""); expect(fs.readdirSync).toHaveBeenCalledTimes(3)
jest.spyOn(toolCache, "downloadTool").mockResolvedValue("pathToTool"); expect(fs.statSync).toHaveBeenCalledTimes(8)
const response = JSON.stringify([{ tag_name: "v4.0.0" }]); })
jest.spyOn(fs, "readFileSync").mockReturnValue(response);
jest.spyOn(os, "type").mockReturnValue("Windows_NT");
jest.spyOn(fs, "chmodSync").mockImplementation(() => {});
jest.spyOn(toolCache, "extractZip").mockResolvedValue("pathToUnzippedHelm");
jest.spyOn(toolCache, "cacheDir").mockResolvedValue("pathToCachedDir");
jest
.spyOn(fs, "readdirSync")
.mockImplementation((file, _) => ["helm.exe" as unknown as fs.Dirent]);
jest.spyOn(fs, "statSync").mockImplementation((file) => {
const isDirectory =
(file as string).indexOf("folder") == -1 ? false : true;
return { isDirectory: () => isDirectory } as fs.Stats;
});
expect(await run.downloadHelm("v4.0.0")).toBe( test('findHelm() - change access permissions and find the helm in given directory', () => {
path.join("pathToCachedDir", "helm.exe") jest.spyOn(fs, 'chmodSync').mockImplementation(() => {})
); jest.spyOn(fs, 'readdirSync').mockImplementation((file, _) => {
expect(toolCache.find).toBeCalledWith("helm", "v4.0.0"); if (file == 'mainFolder') return ['helm.exe' as unknown as fs.Dirent]
expect(toolCache.downloadTool).toBeCalledWith( return []
"https://get.helm.sh/helm-v4.0.0-windows-amd64.zip" })
); jest.spyOn(fs, 'statSync').mockImplementation((file) => {
expect(fs.chmodSync).toBeCalledWith("pathToTool", "777"); const isDirectory =
expect(toolCache.extractZip).toBeCalledWith("pathToTool"); (file as string).indexOf('folder') == -1 ? false : true
expect(fs.chmodSync).toBeCalledWith( return {isDirectory: () => isDirectory} as fs.Stats
path.join("pathToCachedDir", "helm.exe"), })
"777" jest.spyOn(os, 'platform').mockReturnValue('win32')
);
});
test("downloadHelm() - throw error if unable to download", async () => { expect(run.findHelm('mainFolder')).toBe(
jest.spyOn(toolCache, "find").mockReturnValue(""); path.join('mainFolder', 'helm.exe')
jest.spyOn(toolCache, "downloadTool").mockImplementation(async () => { )
throw "Unable to download"; })
});
jest.spyOn(os, "type").mockReturnValue("Windows_NT");
await expect(run.downloadHelm("v3.2.1")).rejects.toThrow( test('findHelm() - throw error if executable not found', () => {
"Failed to download Helm from location https://get.helm.sh/helm-v3.2.1-windows-amd64.zip" jest.spyOn(fs, 'chmodSync').mockImplementation(() => {})
); jest.spyOn(fs, 'readdirSync').mockImplementation((file, _) => {
expect(toolCache.find).toBeCalledWith("helm", "v3.2.1"); if (file == 'mainFolder') return []
expect(toolCache.downloadTool).toBeCalledWith( return []
"https://get.helm.sh/helm-v3.2.1-windows-amd64.zip" })
); jest.spyOn(fs, 'statSync').mockImplementation((file) => {
}); return {isDirectory: () => true} as fs.Stats
})
jest.spyOn(os, 'platform').mockReturnValue('win32')
test("downloadHelm() - return path to helm tool with same version from toolCache", async () => { expect(() => run.findHelm('mainFolder')).toThrow(
jest.spyOn(toolCache, "find").mockReturnValue("pathToCachedDir"); 'Helm executable not found in path mainFolder'
jest.spyOn(fs, "chmodSync").mockImplementation(() => {}); )
})
expect(await run.downloadHelm("v3.2.1")).toBe( test('downloadHelm() - download helm and return path to it', async () => {
path.join("pathToCachedDir", "helm.exe") jest.spyOn(toolCache, 'find').mockReturnValue('')
); jest.spyOn(toolCache, 'downloadTool').mockResolvedValue('pathToTool')
expect(toolCache.find).toBeCalledWith("helm", "v3.2.1"); const response = JSON.stringify([{tag_name: 'v4.0.0'}])
expect(fs.chmodSync).toBeCalledWith( jest.spyOn(fs, 'readFileSync').mockReturnValue(response)
path.join("pathToCachedDir", "helm.exe"), jest.spyOn(os, 'platform').mockReturnValue('win32')
"777" jest.spyOn(fs, 'chmodSync').mockImplementation(() => {})
); jest.spyOn(toolCache, 'extractZip').mockResolvedValue('extractedPath')
}); jest.spyOn(toolCache, 'cacheDir').mockResolvedValue('pathToCachedDir')
jest
.spyOn(fs, 'readdirSync')
.mockImplementation((file, _) => ['helm.exe' as unknown as fs.Dirent])
jest.spyOn(fs, 'statSync').mockImplementation((file) => {
const isDirectory =
(file as string).indexOf('folder') == -1 ? false : true
return {isDirectory: () => isDirectory} as fs.Stats
})
test("downloadHelm() - throw error is helm is not found in path", async () => { expect(await run.downloadHelm(downloadBaseURL, 'v4.0.0')).toBe(
jest.spyOn(toolCache, "find").mockReturnValue(""); path.join('pathToCachedDir', 'helm.exe')
jest.spyOn(toolCache, "downloadTool").mockResolvedValue("pathToTool"); )
jest.spyOn(os, "type").mockReturnValue("Windows_NT"); expect(toolCache.find).toHaveBeenCalledWith('helm', 'v4.0.0')
jest.spyOn(fs, "chmodSync").mockImplementation(); expect(toolCache.downloadTool).toHaveBeenCalledWith(
jest.spyOn(toolCache, "extractZip").mockResolvedValue("pathToUnzippedHelm"); 'https://test.tld/helm-v4.0.0-windows-amd64.zip'
jest.spyOn(toolCache, "cacheDir").mockResolvedValue("pathToCachedDir"); )
jest.spyOn(fs, "readdirSync").mockImplementation((file, _) => []); expect(fs.chmodSync).toHaveBeenCalledWith('pathToTool', '777')
jest.spyOn(fs, "statSync").mockImplementation((file) => { expect(toolCache.extractZip).toHaveBeenCalledWith('pathToTool')
const isDirectory = expect(fs.chmodSync).toHaveBeenCalledWith(
(file as string).indexOf("folder") == -1 ? false : true; path.join('pathToCachedDir', 'helm.exe'),
return { isDirectory: () => isDirectory } as fs.Stats; '777'
}); )
})
await expect(run.downloadHelm("v3.2.1")).rejects.toThrow( test('downloadHelm() - throw error if unable to download', async () => {
"Helm executable not found in path pathToCachedDir" jest.spyOn(toolCache, 'find').mockReturnValue('')
); jest.spyOn(toolCache, 'downloadTool').mockImplementation(async () => {
expect(toolCache.find).toBeCalledWith("helm", "v3.2.1"); throw 'Unable to download'
expect(toolCache.downloadTool).toBeCalledWith( })
"https://get.helm.sh/helm-v3.2.1-windows-amd64.zip" jest.spyOn(os, 'platform').mockReturnValue('win32')
);
expect(fs.chmodSync).toBeCalledWith("pathToTool", "777"); const downloadUrl = 'https://test.tld/helm-v3.2.1-windows-amd64.zip'
expect(toolCache.extractZip).toBeCalledWith("pathToTool"); await expect(run.downloadHelm(downloadBaseURL, 'v3.2.1')).rejects.toThrow(
}); `Failed to download Helm from location ${downloadUrl}`
}); )
expect(toolCache.find).toHaveBeenCalledWith('helm', 'v3.2.1')
expect(toolCache.downloadTool).toHaveBeenCalledWith(`${downloadUrl}`)
})
test('downloadHelm() - return path to helm tool with same version from toolCache', async () => {
jest.spyOn(toolCache, 'find').mockReturnValue('pathToCachedDir')
jest.spyOn(toolCache, 'cacheDir').mockResolvedValue('pathToCachedDir')
jest.spyOn(toolCache, 'downloadTool').mockResolvedValue('pathToTool')
jest.spyOn(toolCache, 'extractZip').mockResolvedValue('extractedPath')
jest.spyOn(os, 'platform').mockReturnValue('win32')
jest.spyOn(fs, 'chmodSync').mockImplementation(() => {})
jest
.spyOn(fs, 'readdirSync')
.mockReturnValue(['helm.exe' as unknown as fs.Dirent])
jest.spyOn(fs, 'statSync').mockImplementation((file) => {
const isDirectory =
(file as string).indexOf('folder') == -1 ? false : true
return {isDirectory: () => isDirectory} as fs.Stats
})
expect(await run.downloadHelm(downloadBaseURL, 'v3.2.1')).toBe(
path.join('pathToCachedDir', 'helm.exe')
)
expect(toolCache.find).toHaveBeenCalledWith('helm', 'v3.2.1')
expect(fs.chmodSync).toHaveBeenCalledWith(
path.join('pathToCachedDir', 'helm.exe'),
'777'
)
})
test('downloadHelm() - throw error is helm is not found in path', async () => {
jest.spyOn(toolCache, 'find').mockReturnValue('')
jest.spyOn(toolCache, 'downloadTool').mockResolvedValue('pathToTool')
jest.spyOn(toolCache, 'cacheDir').mockResolvedValue('pathToCachedDir')
jest.spyOn(toolCache, 'downloadTool').mockResolvedValue('pathToTool')
jest.spyOn(toolCache, 'extractZip').mockResolvedValue('extractedPath')
jest.spyOn(os, 'platform').mockReturnValue('win32')
jest.spyOn(fs, 'chmodSync').mockImplementation()
jest.spyOn(fs, 'readdirSync').mockImplementation((file, _) => [])
jest.spyOn(fs, 'statSync').mockImplementation((file) => {
const isDirectory =
(file as string).indexOf('folder') == -1 ? false : true
return {isDirectory: () => isDirectory} as fs.Stats
})
await expect(run.downloadHelm(downloadBaseURL, 'v3.2.1')).rejects.toThrow(
'Helm executable not found in path pathToCachedDir'
)
expect(toolCache.find).toHaveBeenCalledWith('helm', 'v3.2.1')
expect(toolCache.downloadTool).toHaveBeenCalledWith(
'https://test.tld/helm-v3.2.1-windows-amd64.zip'
)
expect(fs.chmodSync).toHaveBeenCalledWith('pathToTool', '777')
expect(toolCache.extractZip).toHaveBeenCalledWith('pathToTool')
})
})

View File

@ -2,193 +2,158 @@
// Copyright (c) Microsoft Corporation. // Copyright (c) Microsoft Corporation.
// Licensed under the MIT license. // Licensed under the MIT license.
import * as os from "os"; import * as os from 'os'
import * as path from "path"; import * as path from 'path'
import * as util from "util"; import * as util from 'util'
import * as fs from "fs"; import * as fs from 'fs'
import * as toolCache from "@actions/tool-cache"; import * as toolCache from '@actions/tool-cache'
import * as core from "@actions/core"; import * as core from '@actions/core'
const helmToolName = "helm"; const helmToolName = 'helm'
const stableHelmVersion = "v3.8.0"; const stableHelmVersion = 'v3.13.3'
const helmAllReleasesUrl = "https://api.github.com/repos/helm/helm/releases";
export async function run() { export async function run() {
let version = core.getInput("version", { required: true }); let version = core.getInput('version', {required: true})
if(version !== "latest" && version[0] !== "v"){ if (version !== 'latest' && version[0] !== 'v') {
version = getValidVersion(version); core.info('Getting latest Helm version')
} version = getValidVersion(version)
if (version.toLocaleLowerCase() === "latest") { }
version = await getLatestHelmVersion(); if (version.toLocaleLowerCase() === 'latest') {
} version = await getLatestHelmVersion()
}
core.debug(util.format("Downloading %s", version)); const downloadBaseURL = core.getInput('downloadBaseURL', {required: false})
let cachedPath = await downloadHelm(version);
try { core.startGroup(`Downloading ${version}`)
if (!process.env["PATH"].startsWith(path.dirname(cachedPath))) { const cachedPath = await downloadHelm(downloadBaseURL, version)
core.addPath(path.dirname(cachedPath)); core.endGroup()
}
} catch {
//do nothing, set as output variable
}
console.log( try {
`Helm tool version: '${version}' has been cached at ${cachedPath}` if (!process.env['PATH'].startsWith(path.dirname(cachedPath))) {
); core.addPath(path.dirname(cachedPath))
core.setOutput("helm-path", cachedPath);
}
//Returns version with proper v before it
export function getValidVersion(version: string): string {
return "v" + version;
}
// Downloads the helm releases JSON and parses all the recent versions of helm from it.
// Defaults to sending stable helm version if none are valid or if it fails
export async function getLatestHelmVersion(): Promise<string> {
const helmJSONPath: string = await toolCache.downloadTool(helmAllReleasesUrl);
try {
const helmJSON = JSON.parse(fs.readFileSync(helmJSONPath, "utf-8"));
for (let i in helmJSON) {
if (isValidVersion(helmJSON[i].tag_name)) {
return helmJSON[i].tag_name;
} }
} } catch {
} catch (err) { //do nothing, set as output variable
core.warning( }
util.format(
"Error while fetching the latest Helm release. Error: %s. Using default Helm version %s",
err.toString(),
stableHelmVersion
)
);
return stableHelmVersion;
}
return stableHelmVersion; core.info(`Helm tool version '${version}' has been cached at ${cachedPath}`)
core.setOutput('helm-path', cachedPath)
} }
// isValidVersion checks if verison is a stable release // Prefixes version with v
function isValidVersion(version: string): boolean { export function getValidVersion(version: string): string {
return version.indexOf("rc") == -1; return 'v' + version
}
// Gets the latest helm version or returns a default stable if getting latest fails
export async function getLatestHelmVersion(): Promise<string> {
try {
const response = await fetch('https://get.helm.sh/helm-latest-version')
const release = (await response.text()).trim()
return release
} catch (err) {
core.warning(
`Error while fetching latest Helm release: ${err.toString()}. Using default version ${stableHelmVersion}`
)
return stableHelmVersion
}
}
export function getArch(): string {
return os.arch() === 'x64' ? 'amd64' : os.arch()
}
export function getPlatform(): string {
return os.platform() === 'win32' ? 'windows' : os.platform()
}
export function getArchiveExtension(): string {
return os.platform() === 'win32' ? 'zip' : 'tar.gz'
} }
export function getExecutableExtension(): string { export function getExecutableExtension(): string {
if (os.type().match(/^Win/)) { return os.platform() === 'win32' ? '.exe' : ''
return ".exe";
}
return "";
} }
const LINUX = "Linux"; export function getHelmDownloadURL(baseURL: string, version: string): string {
const MAC_OS = "Darwin"; const urlPath = `helm-${version}-${getPlatform()}-${getArch()}.${getArchiveExtension()}`
const WINDOWS = "Windows_NT"; const url = new URL(urlPath, baseURL)
const ARM64 = "arm64"; return url.toString()
export function getHelmDownloadURL(version: string): string {
const arch = os.arch();
const operatingSystem = os.type();
switch (true) {
case operatingSystem == LINUX && arch == ARM64:
return util.format(
"https://get.helm.sh/helm-%s-linux-arm64.zip",
version
);
case operatingSystem == LINUX:
return util.format(
"https://get.helm.sh/helm-%s-linux-amd64.zip",
version
);
case operatingSystem == MAC_OS && arch == ARM64:
return util.format(
"https://get.helm.sh/helm-%s-darwin-arm64.zip",
version
);
case operatingSystem == MAC_OS:
return util.format(
"https://get.helm.sh/helm-%s-darwin-amd64.zip",
version
);
case operatingSystem == WINDOWS:
default:
return util.format(
"https://get.helm.sh/helm-%s-windows-amd64.zip",
version
);
}
} }
export async function downloadHelm(version: string): Promise<string> { export async function downloadHelm(
let cachedToolpath = toolCache.find(helmToolName, version); baseURL: string,
if (!cachedToolpath) { version: string
let helmDownloadPath; ): Promise<string> {
try { let cachedToolpath = toolCache.find(helmToolName, version)
helmDownloadPath = await toolCache.downloadTool( if (!cachedToolpath) {
getHelmDownloadURL(version) let helmDownloadPath
); try {
} catch (exception) { helmDownloadPath = await toolCache.downloadTool(
getHelmDownloadURL(baseURL, version)
)
} catch (exception) {
throw new Error(
`Failed to download Helm from location ${getHelmDownloadURL(
baseURL,
version
)}`
)
}
fs.chmodSync(helmDownloadPath, '777')
const extractedPath =
getPlatform() === 'windows'
? await toolCache.extractZip(helmDownloadPath)
: await toolCache.extractTar(helmDownloadPath)
cachedToolpath = await toolCache.cacheDir(
extractedPath,
helmToolName,
version
)
}
const helmpath = findHelm(cachedToolpath)
if (!helmpath) {
throw new Error( throw new Error(
util.format( util.format('Helm executable not found in path', cachedToolpath)
"Failed to download Helm from location", )
getHelmDownloadURL(version) }
)
);
}
fs.chmodSync(helmDownloadPath, "777"); fs.chmodSync(helmpath, '777')
const unzipedHelmPath = await toolCache.extractZip(helmDownloadPath); return helmpath
cachedToolpath = await toolCache.cacheDir(
unzipedHelmPath,
helmToolName,
version
);
}
const helmpath = findHelm(cachedToolpath);
if (!helmpath) {
throw new Error(
util.format("Helm executable not found in path", cachedToolpath)
);
}
fs.chmodSync(helmpath, "777");
return helmpath;
} }
export function findHelm(rootFolder: string): string { export function findHelm(rootFolder: string): string {
fs.chmodSync(rootFolder, "777"); fs.chmodSync(rootFolder, '777')
var filelist: string[] = []; var filelist: string[] = []
walkSync(rootFolder, filelist, helmToolName + getExecutableExtension()); walkSync(rootFolder, filelist, helmToolName + getExecutableExtension())
if (!filelist || filelist.length == 0) { if (!filelist || filelist.length == 0) {
throw new Error( throw new Error(
util.format("Helm executable not found in path", rootFolder) util.format('Helm executable not found in path', rootFolder)
); )
} else { } else {
return filelist[0]; return filelist[0]
} }
} }
export var walkSync = function (dir, filelist, fileToFind) { export var walkSync = function (dir, filelist, fileToFind) {
var files = fs.readdirSync(dir); var files = fs.readdirSync(dir)
filelist = filelist || []; filelist = filelist || []
files.forEach(function (file) { files.forEach(function (file) {
if (fs.statSync(path.join(dir, file)).isDirectory()) { if (fs.statSync(path.join(dir, file)).isDirectory()) {
filelist = walkSync(path.join(dir, file), filelist, fileToFind); filelist = walkSync(path.join(dir, file), filelist, fileToFind)
} else { } else {
core.debug(file); core.debug(file)
if (file == fileToFind) { if (file == fileToFind) {
filelist.push(path.join(dir, file)); filelist.push(path.join(dir, file))
}
} }
} })
}); return filelist
return filelist; }
};
run().catch(core.setFailed); run().catch(core.setFailed)

View File

@ -1,64 +1,64 @@
{ {
"compilerOptions": { "compilerOptions": {
/* Basic Options */ /* Basic Options */
// "incremental": true, /* Enable incremental compilation */ // "incremental": true, /* Enable incremental compilation */
"target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ "target": "es6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */,
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
// "allowJs": true, /* Allow javascript files to be compiled. */ // "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */ // "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */ // "declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */ // "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */ // "outFile": "./", /* Concatenate and emit output to single file. */
"outDir": "./lib", /* Redirect output structure to the directory. */ "outDir": "./lib" /* Redirect output structure to the directory. */,
"rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ "rootDir": "./src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */,
// "composite": true, /* Enable project compilation */ // "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
// "removeComments": true, /* Do not emit comments to output. */ // "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */ // "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */ // "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */ /* Strict Type-Checking Options */
//"strict": true, /* Enable all strict type-checking options. */ //"strict": true, /* Enable all strict type-checking options. */
"noImplicitAny": false, /* Raise error on expressions and declarations with an implied 'any' type. */ "noImplicitAny": false /* Raise error on expressions and declarations with an implied 'any' type. */,
// "strictNullChecks": true, /* Enable strict null checks. */ // "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */ // "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */ /* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */ // "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */ // "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
/* Module Resolution Options */ /* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */ // "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */ // "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
// "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ // "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
/* Source Map Options */ /* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */ /* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
"skipLibCheck": true "skipLibCheck": true
}, },
"exclude": ["node_modules", "**/*.test.ts"] "exclude": ["node_modules", "**/*.test.ts"]
} }