From a7f0c828ac76e0d049e34c920172c60f579f9eb3 Mon Sep 17 00:00:00 2001 From: Tom Keller <1083460+kellertk@users.noreply.github.com> Date: Fri, 3 Apr 2026 16:27:00 -0700 Subject: [PATCH] feat: Support usage of AWS Profiles (#1696) * Support usage of AWS Profiles * squash merge main updates w feature branch Squashed commit of the following: commit ef2df4679f908ff30d5a711258ace2fa906c4bf3 Author: Michael Lehmann Date: Tue Mar 17 11:24:04 2026 -0700 dist update commit db3779a0e9d3ffe1c63ae0251d05c765062183e8 Author: Jan Feddern Date: Sun Dec 21 11:28:36 2025 +0100 Support usage of AWS Profiles * chore: Update dist * consistent outputEnvCredentials * take out tests temporarily * chore: Update dist * debug changes for static creds * remove debug and only cleanup profile if it was set * formatting fixes + remove profile from cleanup test * feat: Support usage of AWS Profiles Adds a config option to support writing to profile files instead of exporting environment variables. Closes #1594. Closes #1586. Closes #112. * chore: fix failing test case and windows path * chore: lint project markdown files * chore: update scripts in package.json and tsconfig update * make env vars consistent, readme linting * debug for profile path env vars * remove debug * remove profile backups * error if we try to overwrite * add option to overwrite existing profiles * tests for overwrite option * default to no env vars * remove default from action file * add static credential env var support * validation fix for static creds multi profile * debug sleep for static creds validation * wait syntax * undo sleep for creds validate * test coverage, readme/action yml updates, validate creds later on self-hosted runner * security dependency updates * chore(deps-dev): bump @biomejs/biome from 2.4.8 to 2.4.10 (#1709) Bumps [@biomejs/biome](https://github.com/biomejs/biome/tree/HEAD/packages/@biomejs/biome) from 2.4.8 to 2.4.10. - [Release notes](https://github.com/biomejs/biome/releases) - [Changelog](https://github.com/biomejs/biome/blob/main/packages/@biomejs/biome/CHANGELOG.md) - [Commits](https://github.com/biomejs/biome/commits/@biomejs/biome@2.4.10/packages/@biomejs/biome) --- updated-dependencies: - dependency-name: "@biomejs/biome" dependency-version: 2.4.10 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): bump @aws-sdk/credential-provider-env (#1713) Bumps [@aws-sdk/credential-provider-env](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/packages-internal/credential-provider-env) from 3.972.22 to 3.972.24. - [Release notes](https://github.com/aws/aws-sdk-js-v3/releases) - [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/packages-internal/credential-provider-env/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-js-v3/commits/HEAD/packages-internal/credential-provider-env) --- updated-dependencies: - dependency-name: "@aws-sdk/credential-provider-env" dependency-version: 3.972.24 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore: Update dist * chore(deps): bump @aws-sdk/client-sts from 3.1015.0 to 3.1020.0 (#1710) Bumps [@aws-sdk/client-sts](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/clients/client-sts) from 3.1015.0 to 3.1020.0. - [Release notes](https://github.com/aws/aws-sdk-js-v3/releases) - [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/clients/client-sts/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.1020.0/clients/client-sts) --- updated-dependencies: - dependency-name: "@aws-sdk/client-sts" dependency-version: 3.1020.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore: Update dist * fix: do not write empty profile files Also cleanup fix, additional test, README typo cleanup * linting fix * chore: linting fix --------- Signed-off-by: dependabot[bot] Co-authored-by: Jan Feddern Co-authored-by: Michael Lehmann Co-authored-by: GitHub Actions Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/PULL_REQUEST_TEMPLATE.md | 10 +- .github/workflows/tests-integ-release.yml | 47 + .markdownlint.yaml | 17 + CODE_OF_CONDUCT.md | 12 +- CONTRIBUTING.md | 98 +- README.md | 659 +++++++---- action.yml | 7 +- dist/cleanup/index.js | 31 +- dist/index.js | 1206 ++++++++++++--------- examples/README.md | 9 +- examples/cfn-deploy-example/README.md | 26 +- examples/federated-setup/README.md | 6 +- package-lock.json | 1145 +++++++++++++++++-- package.json | 5 +- src/cleanup/index.ts | 6 +- src/helpers.ts | 1 + src/index.ts | 49 +- src/profileManager.ts | 214 ++++ test/cleanup.test.ts | 20 + test/helpers.test.ts | 5 +- test/index.test.ts | 199 ++++ test/profileManager.test.ts | 724 +++++++++++++ tsconfig.json | 3 +- 23 files changed, 3579 insertions(+), 920 deletions(-) create mode 100644 .markdownlint.yaml create mode 100644 src/profileManager.ts create mode 100644 test/profileManager.test.ts diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 854c889..0242ddc 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,9 +1,11 @@ -*Issue #, if available:* +_Issue #, if available:_ -*Description of changes:* +_Description of changes:_ --- -* [ ] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws-actions/configure-aws-credentials/blob/main/CONTRIBUTING.md) +- [ ] Have you followed the guidelines in our + [Contributing guide?](https://github.com/aws-actions/configure-aws-credentials/blob/main/CONTRIBUTING.md) -By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. +By submitting this pull request, I confirm that you can use, modify, copy, and +redistribute this contribution, under the terms of your choice. diff --git a/.github/workflows/tests-integ-release.yml b/.github/workflows/tests-integ-release.yml index 56ee836..2584ff6 100644 --- a/.github/workflows/tests-integ-release.yml +++ b/.github/workflows/tests-integ-release.yml @@ -232,3 +232,50 @@ jobs: retry-max-attempts: 4 - name: check creds run: aws sts get-caller-identity + + profile-oidc: + if: ${{ github.event_name == 'workflow_dispatch' || (github.event.pull_request.user.login == 'aws-sdk-osds' && github.repository == 'aws-actions/configure-aws-credentials') }} + permissions: + id-token: write + strategy: + fail-fast: false + matrix: + os: [windows-latest, ubuntu-latest, macos-latest] + runs-on: ${{ matrix.os }} + name: OIDC profile writing test + steps: + - name: checkout + uses: actions/checkout@v5 + with: + fetch-depth: 0 + persist-credentials: false + + - name: make profile 1 + uses: ./ + with: + aws-region: us-east-1 + role-to-assume: ${{ secrets.INTEG_PROFILE_ROLE_1 }} + aws-profile: TestProfile1 + + - name: make profile 2 + uses: ./ + with: + aws-region: us-east-2 + role-to-assume: ${{ secrets.INTEG_PROFILE_ROLE_2 }} + aws-profile: TestProfile2 + + - name: check profile1 + run: aws sts get-caller-identity --profile TestProfile1 + + - name: check profile2 + run: aws sts get-caller-identity --profile TestProfile2 + + - name: Get Caller Identity + id: no-creds-sts-step + continue-on-error: true + run: | + aws sts get-caller-identity + + - name: fail if we got caller id with env creds + if: steps.no-creds-sts-step.outcome == 'success' + run: exit 1 diff --git a/.markdownlint.yaml b/.markdownlint.yaml new file mode 100644 index 0000000..f3d46f7 --- /dev/null +++ b/.markdownlint.yaml @@ -0,0 +1,17 @@ +default: true +MD007: + indent: 4 +heading-style: + style: atx +ul-style: + style: dash +line-length: + stern: true + tables: false + code_blocks: false +no-inline-html: + allowed_elements: + - details + - summary +code-block-style: + style: fenced diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 5b627cf..ae44d67 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,4 +1,8 @@ -## Code of Conduct -This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). -For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact -opensource-codeofconduct@amazon.com with any additional questions or comments. +# Code of Conduct + +This project has adopted the +[Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). For +more information see the +[Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact + with any additional questions or +comments. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fc6672e..d0810b4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,57 +1,90 @@ # Contributing Guidelines -Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional -documentation, we greatly value feedback and contributions from our community. - -Please read through this document before submitting any issues or pull requests to ensure we have all the necessary -information to effectively respond to your bug report or contribution. +Thank you for your interest in contributing to our project. Whether it's a bug +report, new feature, correction, or additional documentation, we greatly value +feedback and contributions from our community. +Please read through this document before submitting any issues or pull requests +to ensure we have all the necessary information to effectively respond to your +bug report or contribution. ## Reporting Bugs/Feature Requests -We welcome you to use the GitHub issue tracker to report bugs or suggest features. +We welcome you to use the GitHub issue tracker to report bugs or suggest +features. -When filing an issue, please check [existing open](https://github.com/aws-actions/configure-aws-credentials/issues), or [recently closed](https://github.com/aws-actions/configure-aws-credentials/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), issues to make sure somebody else hasn't already -reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: +When filing an issue, please check [existing open][open-issues], +or [recently closed][closed-issues], issues to make sure somebody +else hasn't already reported the issue. -* A reproducible test case or series of steps -* The version of our code being used -* Any modifications you've made relevant to the bug -* Anything unusual about your environment or deployment +[open-issues]: https://github.com/aws-actions/configure-aws-credentials/issues +[closed-issues]: https://github.com/aws-actions/configure-aws-credentials/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20 +Please try to include as much information as you can. Details like +these are incredibly useful: + +- A reproducible test case or series of steps +- The version of our code being used +- Any modifications you've made relevant to the bug +- Anything unusual about your environment or deployment ## Contributing via Pull Requests -Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: -1. You are working against the latest source on the *main* branch. -2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. -3. You open an issue to discuss any significant work - we would hate for your time to be wasted. +Contributions via pull requests are much appreciated. Before sending us a pull +request, please ensure that: + +1. You are working against the latest source on the _main_ branch. +2. You check existing open, and recently merged, pull requests to make sure + someone else hasn't addressed the problem already. +3. You open an issue to discuss any significant work - we would hate for your + time to be wasted. To send us a pull request, please: 1. Fork the repository. -2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. +2. Modify the source; please focus on the specific change you are contributing. + If you also reformat all the code, it will be hard for us to focus on your + change. 3. Ensure local tests pass. 4. Commit to your fork using clear commit messages. -5. Send us a pull request, answering any default questions in the pull request interface. -6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. +5. Send us a pull request, answering any default questions in the pull request + interface. +6. Pay attention to any automated CI failures reported in the pull request, and + stay involved in the conversation. -GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and -[creating a pull request](https://help.github.com/articles/creating-a-pull-request/). +GitHub provides additional document on +[forking a repository](https://help.github.com/articles/fork-a-repo/) and +[creating a pull request][create-pr]. +[create-pr]: + https://help.github.com/articles/creating-a-pull-request/ ## Finding contributions to work on -Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any ['help wanted'](https://github.com/aws-actions/configure-aws-credentials/labels/help%20wanted) issues is a great place to start. +Looking at the existing issues is a great way to find something to contribute +on. As our projects, by default, use the default GitHub issue labels +(enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any +['help wanted'](https://github.com/aws-actions/configure-aws-credentials/labels/help%20wanted) +issues is a great place to start. ## Code of Conduct -This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). -For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact -opensource-codeofconduct@amazon.com with any additional questions or comments. +This project has adopted the +[Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). For +more information see the +[Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact + with any additional questions or +comments. ## Security issue notifications -If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. + +If you discover a potential security issue in this project we ask that you +notify AWS/Amazon Security via our +[vulnerability reporting page][vuln-report]. + +[vuln-report]: + http://aws.amazon.com/security/vulnerability-reporting/ +Please do **not** create a public github issue. ## Automated Tools @@ -62,7 +95,7 @@ the following rules: - All issue and pull request submissions to this repository that are sourced by AI must first be reviewed by a human before submitting to the repository. Items reviewed in this way must include a statement like "generated by AI - tools, and reviewed by " + tools, and reviewed by [person]" - Please ensure that your submissions are actually improvements. While we are grateful for any proposed fixes, even if they are very small, behavior that looks like creating noisy PRs or artificially inflating submission counts @@ -70,10 +103,15 @@ the following rules: - We may close issues or pull requests, or limit your ability to interact with this repository, for behavior that in our estimation violates these rules or any of the other rules in this repository's - [Code of Conduct][CODE_OF_CONDUCT.md]. + [Code of Conduct](CODE_OF_CONDUCT.md). ## Licensing -See the [LICENSE](https://github.com/aws-actions/configure-aws-credentials/blob/main/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. +See the +[LICENSE](https://github.com/aws-actions/configure-aws-credentials/blob/main/LICENSE) +file for our project's licensing. We will ask you to confirm the licensing of +your contribution. -We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. +We may ask you to sign a +[Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) +for larger changes. diff --git a/README.md b/README.md index b353d4d..f53c131 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,17 @@ -Configure AWS Credentials -========================= -Authenticate to AWS in GitHub Actions! Works especially well with [AWS Secrets -Manager](https://github.com/aws-actions/aws-secretsmanager-get-secrets). +# Configure AWS Credentials -Quick Start (OIDC, recommended) -------------------------------- -1. Create an IAM Identity Provider in your AWS account for GitHub OIDC. (See -[OIDC configuration](#oidc-configuration) below for details.) -2. Create an IAM Role in your AWS account with a trust policy that allows GitHub -Actions to assume it: -
+Authenticate to AWS in GitHub Actions! Works especially well with +[AWS Secrets Manager][secretsmanager]. + +[secretsmanager]: + https://github.com/aws-actions/aws-secretsmanager-get-secrets + +## Quick Start (OIDC, recommended) + +1. Create an IAM Identity Provider in your AWS account for GitHub OIDC. (See + [OIDC configuration](#oidc-configuration-details) below for details.) +2. Create an IAM Role in your AWS account with a trust policy that allows + GitHub Actions to assume it. (Expand the sections below)
GitHub OIDC Trust Policy ```json @@ -32,11 +34,12 @@ Actions to assume it: ] } ``` +
-3. Attach permissions to the IAM Role that allow it to access the AWS resources -you need. -4. Add the following to your GitHub Actions workflow: -
+ +3. Attach permissions to the IAM Role that allow it to access the AWS resources + you need. +4. Add the following to your GitHub Actions workflow:
Example Workflow ```yaml @@ -48,39 +51,47 @@ you need. runs-on: ubuntu-latest steps: - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@main # Or a specific version + uses: aws-actions/configure-aws-credentials@v6.1.0 with: role-to-assume: aws-region: - name: Additional steps run: | # Your commands that require AWS credentials - aws sts get-caller-identity + aws sts get-caller-identity ``` -
-That's it! Your GitHub Actions workflow can now access AWS resources using the -IAM Role you created. Other authentication scenarios are also supported (see -below). -Security Recommendations ------------------------- -* Use temporary credentials when possible. OIDC is recommended because it +
+ + That's it! Your GitHub Actions workflow can now access AWS resources using + the IAM Role you created. Other authentication scenarios are also supported + (see below). + +## Security Recommendations + +- Use temporary credentials when possible. OIDC is recommended because it provides temporary credentials and it's easy to set up. -* Do not store credentials in your repository's code. Consider using +- Do not store credentials in your repository's code. Consider using [git-secrets](https://github.com/awslabs/git-secrets) to prevent committing secrets to your repository. -* [Grant least privilege](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege) +- [Grant least privilege](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege) to your workflows. Grant only those permissions that are necessary for the workflow to run. -* [Monitor the activity](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#keep-a-log) +- [Monitor the activity](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#keep-a-log) of the credentials used in workflows. -* Periodically rotate any long-lived credentials that you use. -* Store sensitive information in a secure way, such as using +- Periodically rotate any long-lived credentials that you use. +- Store sensitive information in a secure way, such as using [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/) or - [GitHub Secrets](https://docs.github.com/en/actions/security-guides/encrypted-secrets). + [GitHub Secrets][gh-secrets]. +- Be especially careful about running Actions in non-ephemeral environments, or + [triggering workflows on `pull_request_target`](https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows#pull_request_target) + events. + +[gh-secrets]: + https://docs.github.com/en/actions/security-guides/encrypted-secrets + +## Non-OIDC Authentication Options -Other Authentication Scenarios ------------------------------- This action supports five different authentication methods that are configured by specifying different inputs. @@ -94,85 +105,178 @@ by specifying different inputs. 5. Use credentials stored in the Action environment to fetch temporary credentials via STS AssumeRole. -Because we use the AWS JavaScript SDK, we always will use the [credential -resolution flow for Node.js](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/setting-credentials-node.html). +Because we use the AWS JavaScript SDK, we always will use the +[credential resolution flow for Node.js][cred-resolution]. + +[cred-resolution]: + https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/setting-credentials-node.html Depending on your inputs, the action might override parts of this flow.
Inputs and their effects on the credential resolution flow -| **Identity Used** | `aws-access-key-id` | `role-to-assume` | `web-identity-token-file` | `role-chaining` | -| ---------------------------------------- | ------------------- | ---------------- | ------------------------- | --------------- | -| [✅ Recommended] GitHub OIDC | | ✔ | | | -| IAM User (no AssumeRole) | ✔ | | | | -| AssumeRole using static IAM credentials | ✔ | ✔ | | | -| AssumeWithWebIdentity use a token file | | ✔ | ✔ | | -| AssumeRole using existing credentials | | ✔ | | ✔ | +| **Identity Used** | `aws-access-key-id` | `role-to-assume` | `web-identity-token-file` | `role-chaining` | +| --------------------------------------- | ------------------- | ---------------- | ------------------------- | --------------- | +| [✅ Recommended] GitHub OIDC | | ✔ | | | +| IAM User (no AssumeRole) | ✔ | | | | +| AssumeRole using static IAM credentials | ✔ | ✔ | | | +| AssumeWithWebIdentity use a token file | | ✔ | ✔ | | +| AssumeRole using existing credentials | | ✔ | | ✔ | + +_Note: `role-chaining` is not always necessary to use existing credentials. If +you're getting a "Credentials loaded by the SDK do not match" error, try +enabling this option._ -*Note: `role-chaining` is not always necessary to use existing credentials. -If you're getting a "Credentials loaded by the SDK do not match" error, -try enabling this option.*
Additionally, **`aws-region`** is always required. -*Note: If you use GitHub Enterprise Server, you must use the you may need to -adjust examples here to match your environment.* +_Note: If you use GitHub Enterprise Server, you may need to adjust examples +here to match your environment._ + +## Additional Options -Additional Options ------------------- ### Options -See [action.yml](./action.yml) for more detail. + +The options list can be expanded below. See [action.yml](./action.yml) for more +detail. +
Options list and descriptions -| Option | Description | Required | -|---------------------------|---------------------------------------------------------------------------------------------------|----------| -| aws-region | Which AWS region to use | Yes | -| role-to-assume | Role for which to fetch credentials. Only required for some authentication types. | No | -| aws-access-key-id | AWS access key to use. Only required for some authentication types. | No | -| aws-secret-access-key | AWS secret key to use. Only required for some authentication types. | No | -| aws-session-token | AWS session token to use. Used in uncommon authentication scenarios. | No | -| role-chaining | Use existing credentials from the environment to assume a new role. | No | -| audience | The JWT audience when using OIDC. Used in non-default AWS partitions, like China regions. | No | -| http-proxy | An HTTP proxy to use for API calls. | No | -| mask-aws-account-id | AWS account IDs are not considered secret. Setting this will hide account IDs from output anyway. | No | -| role-duration-seconds | The assumed role duration in seconds, if assuming a role. Defaults to 1 hour (3600 seconds). Acceptable values range from 15 minutes (900 seconds) to 12 hours (43200 seconds). | No | -| role-external-id | The external ID of the role to assume. Only needed if your role requires it. | No | -| role-session-name | Defaults to "GitHubActions", but may be changed if required. | No | -| role-skip-session-tagging | Skips session tagging if set. | No | -| transitive-tag-keys | Define a list of transitive tag keys to pass when assuming a role. | No | -| inline-session-policy | You may further restrict the assumed role policy by defining an inline policy here. | No | -| managed-session-policies | You may further restrict the assumed role policy by specifying a managed policy here. | No | -| output-credentials | When set, outputs fetched credentials as action step output. (Outputs aws-access-key-id, aws-secret-access-key, aws-session-token, aws-account-id, authenticated-arn, and aws-expiration). Defaults to false. | No | -| output-env-credentials | When set, outputs fetched credentials as environment variables (AWS_REGION, AWS_DEFAULT_REGION, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN). Defaults to true. Set to false if you need to avoid setting/changing env variables. You'd probably want to use output-credentials if you disable this. (NOTE: Setting to false will prevent the aws-account-id from being exported as a step output). | No | -| unset-current-credentials | When set, attempts to unset any existing credentials in your action runner. | No | -| disable-retry | Disabled retry/backoff logic for assume role calls. By default, retries are enabled. | No | -| retry-max-attempts | Limits the number of retry attempts before giving up. Defaults to 12. | No | -| special-characters-workaround | Uncommonly, some environments cannot tolerate special characters in a secret key. This option will retry fetching credentials until the secret access key does not contain special characters. This option overrides disable-retry and retry-max-attempts. | No | -| use-existing-credentials | When set, the action will check if existing credentials are valid and exit if they are. Defaults to false. | No | -| allowed-account-ids | A comma-delimited list of expected AWS account IDs. The action will fail if we receive credentials for the wrong account. | No | -| force-skip-oidc | When set, the action will skip using GitHub OIDC provider even if the id-token permission is set. | No | -| action-timeout-s | Global timeout for the action in seconds. If set to a value greater than 0, the action will fail if it takes longer than this time to complete. | No | +| Option | Description | Required | +| ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | +| aws-region | Which AWS region to use | Yes | +| aws-profile | Name of the AWS profile to configure. When provided, credentials are written to `~/.aws/credentials` and `~/.aws/config` files. This enables configuring multiple profiles in a single workflow. Name cannot contain whitespace, square brackets, or slashes. When set, credentials will not be exported as environment variables unless `output-env-credentials` is manually set to true. | No | +| overwrite-aws-profile | Overwrite the given AWS profile if it already exists. When set to false or not set, an error will be thrown if the profile already exists. | No | +| role-to-assume | Role for which to fetch credentials. Only required for some authentication types. | No | +| aws-access-key-id | AWS access key to use. Only required for some authentication types. | No | +| aws-secret-access-key | AWS secret key to use. Only required for some authentication types. | No | +| aws-session-token | AWS session token to use. Used in uncommon authentication scenarios. | No | +| role-chaining | Use existing credentials from the environment to assume a new role. | No | +| audience | The JWT audience when using OIDC. Used in non-default AWS partitions, like China regions. | No | +| http-proxy | An HTTP proxy to use for API calls. | No | +| mask-aws-account-id | AWS account IDs are not considered secret. Setting this will hide account IDs from output anyway. | No | +| role-duration-seconds | The assumed role duration in seconds, if assuming a role. Defaults to 1 hour (3600 seconds). Acceptable values range from 15 minutes (900 seconds) to 12 hours (43200 seconds). | No | +| role-external-id | The external ID of the role to assume. Only needed if your role requires it. | No | +| role-session-name | Defaults to "GitHubActions", but may be changed if required. | No | +| role-skip-session-tagging | Skips session tagging if set. | No | +| transitive-tag-keys | Define a list of transitive tag keys to pass when assuming a role. | No | +| inline-session-policy | You may further restrict the assumed role policy by defining an inline policy here. | No | +| managed-session-policies | You may further restrict the assumed role policy by specifying a managed policy here. | No | +| output-credentials | When set, outputs fetched credentials as action step output. (Outputs aws-access-key-id, aws-secret-access-key, aws-session-token, aws-account-id, authenticated-arn, and aws-expiration). Defaults to false. | No | +| output-env-credentials | When set, outputs fetched credentials as environment variables (AWS_REGION, AWS_DEFAULT_REGION, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN, and AWS_PROFILE (if profile option is used)). Defaults to true when `aws-profile` is not set, and false when `aws-profile` is set. Set to false to avoid setting env variables. (NOTE: Setting to false will prevent aws-account-id from being exported as a step output). | No | +| unset-current-credentials | When set, attempts to unset any existing credentials in your action runner. | No | +| disable-retry | Disabled retry/backoff logic for assume role calls. By default, retries are enabled. | No | +| retry-max-attempts | Limits the number of retry attempts before giving up. Defaults to 12. | No | +| special-characters-workaround | Uncommonly, some environments cannot tolerate special characters in a secret key. This option will retry fetching credentials until the secret access key does not contain special characters. This option overrides disable-retry and retry-max-attempts. | No | +| use-existing-credentials | When set, the action will check if existing credentials are valid and exit if they are. Defaults to false. | No | +| allowed-account-ids | A comma-delimited list of expected AWS account IDs. The action will fail if we receive credentials for the wrong account. | No | +| force-skip-oidc | When set, the action will skip using GitHub OIDC provider even if the id-token permission is set. | No | +| action-timeout-s | Global timeout for the action in seconds. If set to a value greater than 0, the action will fail if it takes longer than this time to complete. | No | +
#### Adjust the retry mechanism -You can configure retry settings for if the STS call fails. By default, we -retry with exponential backoff `12` times. You can disable this behavior -altogether by setting the `disable-retry` input to `true`, or you can configure -the number of times it retries with the `retry-max-attempts` input. + +You can configure retry settings for if the STS call fails. By default, we retry +with exponential backoff `12` times. You can disable this behavior altogether by +setting the `disable-retry` input to `true`, or you can configure the number of +times it retries with the `retry-max-attempts` input. #### Mask account ID + Your account ID is not masked by default in workflow logs. You can set the `mask-aws-account-id` input to `true` to mask your account ID in workflow logs if desired. #### Unset current credentials + Sometimes, existing credentials in your runner can get in the way of the intended outcome. You can set the `unset-current-credentials` input to `true` to work around this issue. -#### Skip the cleanup step +#### Configure named AWS profiles + +By default, this action exports credentials as environment variables +(AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, etc.). However, you can use the +`aws-profile` input to configure named AWS profiles. When `aws-profile` is +provided, credentials are written to `~/.aws/credentials` and `~/.aws/config` +files (which are created if they don't already exist). The default locations of +these files will be overridden if the `AWS_SHARED_CREDENTIALS_FILE` and +`AWS_CONFIG_FILE` environment variables are present. + +Profile names may not contain whitespace, square brackets, or forward or +backslashes. + +Writing to a profile will prevent credentials being written to the environment +by default. Use `output-env-credentials: true` if you would like the +credentials to also be exported as environment variables. + +By default, the action will not overwrite existing profiles. If you would like +to overwrite a profile, set the `overwrite-aws-profile` input to `true`. + +_Note: When writing profiles, the action will preserve existing profile sections +in the credentials and config files. However, comments in these files will not +be preserved._ + +_Caution: Writing to the AWS configuration file means that credentials will +persist in the execution environment even after the action cleanup step. Use +extreme care to ensure that this is safe in your environment and you do not leak +valid credentials unintentionally. Writing to configuration files is intended +for unusual authentication scenarios._ + +For using profiles with static IAM User Credentials or when using one +role to assume another, role chaining is needed: + +
+ +If using static credentials, it's necessary to set `role-chaining: true` and +specify the profile name as an environment variable in the job step: + +```yaml +- name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v6.1.0 + with: + aws-region: us-east-1 + role-to-assume: arn:aws:iam::123456789100:role/my-role + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-profile: MyProfile1 + role-chaining: true + env: + AWS_PROFILE: MyProfile1 +``` + +If you are using one role to assume another while using profiles, the +subsequent steps must set `role-chaining: true` and specify the prior profile's +name as step environment variables: + +```yaml +- name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v6.1.0 + with: + aws-region: us-east-1 + role-to-assume: arn:aws:iam::123456789100:role/my-first-role + aws-profile: firstRoleInChain + +- name: assume second role + uses: aws-actions/configure-aws-credentials@v6.1.0 + with: + aws-region: us-east-2 + role-to-assume: arn:aws:iam::987654321000:role/my-second-role + role-chaining: true + aws-profile: secondRoleInChain + env: + AWS_PROFILE: firstRoleInChain +``` + +
+ +See the [Examples](#examples) section for more usage examples. + +#### Skip the cleanup + By default, this action runs a post-job cleanup step that removes credentials from the environment. To skip this step, set the `AWS_SKIP_CLEANUP_STEP` environment variable to `true`: @@ -191,8 +295,9 @@ this action will always consider the `HTTP_PROXY` environment variable. Proxy configuration Manually configured proxy: + ```yaml -uses: aws-actions/configure-aws-credentials@v6.0.0 +uses: aws-actions/configure-aws-credentials@v6.1.0 with: aws-region: us-east-2 role-to-assume: my-github-actions-role @@ -200,16 +305,22 @@ with: ``` Proxy configured in the environment variable: + ```bash # Your environment configuration HTTP_PROXY="http://companydomain.com:3128" ``` +
#### Special characters in AWS_SECRET_ACCESS_KEY + Some edge cases are unable to properly parse an `AWS_SECRET_ACCESS_KEY` if it contains special characters. For more information, please see the -[AWS CLI documentation](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-troubleshooting.html#tshoot-signature-does-not-match). +[AWS CLI documentation][aws-cli-troubleshooting]. + +[aws-cli-troubleshooting]: + https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-troubleshooting.html#tshoot-signature-does-not-match If you set the `special-characters-workaround` option, this action will continually retry fetching credentials until we get one that does not have special characters. This option overrides the `disable-retry` and @@ -217,18 +328,21 @@ special characters. This option overrides the `disable-retry` and unless required, because retrying APIs infinitely until they succeed is not best practice. -Session Naming and Policies ---------------------------- +## Session Naming and Policies + The default session name is "GitHubActions", and you can modify it by specifying the desired name in `role-session-name`. -*Note: you might find it helpful to set the `role-session-name` to `${{ github.run_id }}` -so as to clarify in audit logs which AWS actions were performed by which workflow -run.* +_Note: you might find it helpful to set the `role-session-name` to +`${{ github.run_id }}` so as to clarify in audit logs which AWS actions were +performed by which workflow run._ -The session will be tagged with the -following tags: (Refer to [GitHub's documentation for `GITHUB_` environment -variable definitions](https://docs.github.com/en/actions/reference/workflows-and-actions/variables#default-environment-variables)) +The session will be tagged with the following tags: (Refer to +[GitHub's documentation for `GITHUB_` environment variable +definitions][gh-env-vars]) + +[gh-env-vars]: + https://docs.github.com/en/actions/reference/workflows-and-actions/variables#default-environment-variables | Key | Value | | ---------- | ----------------- | @@ -244,31 +358,39 @@ _Note: all tag values must conform to [the tag requirements](https://docs.aws.amazon.com/STS/latest/APIReference/API_Tag.html). Particularly, `GITHUB_WORKFLOW` will be truncated if it's too long. If `GITHUB_ACTOR` or `GITHUB_WORKFLOW` contain invalid characters, the characters -will be replaced with an '*'._ +will be replaced with an '\*'._ The action will use session tagging by default unless you are using OIDC. -To [forward session tags to subsequent sessions in a role chain](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_session-tags.html#id_session-tags_role-chaining), -you can use the `transitive-tag-keys` input to specify the keys of the tags to be passed. +To [forward session tags to subsequent sessions in a role +chain][session-tag-chaining], you can use + +[session-tag-chaining]: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_session-tags.html#id_session-tags_role-chaining + +the `transitive-tag-keys` input to specify the keys of the tags to be passed. + +_Note that all subsequent roles in the chain must have +`role-skip-session-tagging` set to `true`_ -_Note that all subsequent roles in the chain must have `role-skip-session-tagging` set to `true`_ ```yaml - uses: aws-actions/configure-aws-credentials@v6 - with: - transitive-tag-keys: | - Repository - Workflow - Action - Actor +uses: aws-actions/configure-aws-credentials@v6 +with: + transitive-tag-keys: | + Repository + Workflow + Action + Actor ``` ### Session policies + Session policies are not required, but they allow you to limit the scope of the fetched credentials without making changes to IAM roles. You can specify inline session policies right in your workflow file, or refer to an existing managed session policy by its ARN. #### Inline session policies + An IAM policy in stringified JSON format that you want to use as an inline session policy. Depending on preferences, the JSON could be written on a single line. @@ -277,30 +399,34 @@ line. Inline session policy examples ```yaml - uses: aws-actions/configure-aws-credentials@v6.0.0 - with: - inline-session-policy: '{"Version":"2012-10-17","Statement":[{"Sid":"Stmt1","Effect":"Allow","Action":"s3:List*","Resource":"*"}]}' +uses: aws-actions/configure-aws-credentials@v6.1.0 +with: + inline-session-policy: '{"Version":"2012-10-17","Statement":[{"Sid":"Stmt1","Effect":"Allow","Action":"s3:List*","Resource":"*"}]}' ``` + Or we can have a nicely formatted JSON as well: + ```yaml - uses: aws-actions/configure-aws-credentials@v6.0.0 - with: - inline-session-policy: >- - { - "Version": "2012-10-17", - "Statement": [ - { - "Sid":"Stmt1", - "Effect":"Allow", - "Action":"s3:List*", - "Resource":"*" - } - ] - } +uses: aws-actions/configure-aws-credentials@v6.1.0 +with: + inline-session-policy: >- + { + "Version": "2012-10-17", + "Statement": [ + { + "Sid":"Stmt1", + "Effect":"Allow", + "Action":"s3:List*", + "Resource":"*" + } + ] + } ``` + #### Managed session policies + The Amazon Resource Names (ARNs) of the IAM managed policies that you want to use as managed session policies. The policies must exist in the same account as the role. @@ -309,36 +435,40 @@ the role. Managed session policy examples ```yaml - uses: aws-actions/configure-aws-credentials@v6.0.0 - with: - managed-session-policies: arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess +uses: aws-actions/configure-aws-credentials@v6.1.0 +with: + managed-session-policies: arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess ``` + And we can pass multiple managed policies likes this: + ```yaml - uses: aws-actions/configure-aws-credentials@v6.0.0 - with: - managed-session-policies: | - arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess - arn:aws:iam::aws:policy/AmazonS3OutpostsReadOnlyAccess +uses: aws-actions/configure-aws-credentials@v6.1.0 +with: + managed-session-policies: | + arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess + arn:aws:iam::aws:policy/AmazonS3OutpostsReadOnlyAccess ``` + -OIDC Configuration -------------------- -We recommend using [GitHub's OIDC -provider](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services) +## OIDC Configuration Details + +We recommend using +[GitHub's OIDC provider](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services) to get short-lived AWS credentials needed for your actions. When using OIDC, you -configure IAM to accept JWTs from GitHub's OIDC endpoint. This action will -then create a JWT unique to the workflow run using the OIDC endpoint, and it -will use the JWT to assume the specified role with short-term credentials. +configure IAM to accept JWTs from GitHub's OIDC endpoint. This action will then +create a JWT unique to the workflow run using the OIDC endpoint, and it will use +the JWT to assume the specified role with short-term credentials. To get this to work + 1. Configure your workflow to use the `id-token: write` permission. 2. Configure your audience, if required. 3. In your AWS account, configure IAM to trust GitHub's OIDC identity provider. 4. Configure an IAM role with appropriate claim limits and permission scope. - *Note*: Naming your role "GitHubActions" has been reported to not work. See + _Note_: Naming your role "GitHubActions" has been reported to not work. See [#953](https://github.com/aws-actions/configure-aws-credentials/issues/953). 5. Specify that role's ARN when setting up this action. @@ -347,31 +477,33 @@ To get this to work When the JWT is created, an audience needs to be specified. Normally, you would use `sts.amazonaws.com`, and this action uses this by default if you don't -specify one. This will work for most cases. Changing the default audience may -be necessary when using non-default AWS partitions, such as China regions. -You can specify the audience through the `audience` input: +specify one. This will work for most cases. Changing the default audience may be +necessary when using non-default AWS partitions, such as China regions. You can +specify the audience through the `audience` input: ```yaml - - name: Configure AWS Credentials for China region audience - uses: aws-actions/configure-aws-credentials@v6.0.0 - with: - audience: sts.amazonaws.com.cn - aws-region: cn-northwest-1 - role-to-assume: arn:aws-cn:iam::123456789100:role/my-github-actions-role +- name: Configure AWS Credentials for China region audience + uses: aws-actions/configure-aws-credentials@v6.1.0 + with: + audience: sts.amazonaws.com.cn + aws-region: cn-northwest-1 + role-to-assume: arn:aws-cn:iam::123456789100:role/my-github-actions-role ``` ### Configuring IAM to trust GitHub + To use GitHub's OIDC provider, you must first set up federation in your AWS account. This involves creating an IAM Identity Provider that trusts GitHub's -OIDC endpoint. You can create an IAM Identity Provider in the AWS Management +OIDC endpoint. You can create an IAM Identity Provider in the AWS Management Console by specifying the following details: + - **Provider Type**: OIDC - **Provider URL**: `https://token.actions.githubusercontent.com` - **Audience**: `sts.amazonaws.com` (or your custom audience if you specified one in the `audience` input) Prior versions of this documentation gave instructions for specifying the -certificate fingerprint, but this is no longer necessary. The thumbprint, if +certificate fingerprint, but this is no longer necessary. The thumbprint, if specified, will be ignored. You can also create the IAM Identity Provider using the AWS CLI: @@ -379,18 +511,22 @@ You can also create the IAM Identity Provider using the AWS CLI: ```bash aws iam create-open-id-connect-provider \ --url https://token.actions.githubusercontent.com \ - --client-id-list sts.amazonaws.com + --client-id-list sts.amazonaws.com ``` ### Claims and scoping permissions -To align with the Amazon IAM best practice of [granting least -privilege](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege), + +To align with the Amazon IAM best practice of +[granting least privilege][least-privilege], + +[least-privilege]: + https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege the assume role policy document should contain a [`Condition`](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_condition.html) -that specifies a subject (`sub`) allowed to assume the role. [GitHub also -recommends](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#defining-trust-conditions-on-cloud-roles-using-oidc-claims) -filtering for the correct audience (`aud`). See [AWS IAM -documentation](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_iam-condition-keys.html#condition-keys-wif) +that specifies a subject (`sub`) allowed to assume the role. +[GitHub also recommends](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#defining-trust-conditions-on-cloud-roles-using-oidc-claims) +filtering for the correct audience (`aud`). See +[AWS IAM documentation](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_iam-condition-keys.html#condition-keys-wif) on which claims you can filter for in your trust policies. Without a subject (`sub`) condition, any GitHub user or repository could @@ -399,7 +535,8 @@ and repository as shown in the CloudFormation template. However, scoping it down to your org and repo may cause the role assumption to fail in some cases. See [Example subject claims](https://docs.github.com/en/actions/reference/security/oidc#example-subject-claims) for specific details on what the subject value will be depending on your -workflow. You can also [customize your subject claim](https://docs.github.com/en/actions/reference/security/oidc#customizing-the-token-claims) +workflow. You can also +[customize your subject claim](https://docs.github.com/en/actions/reference/security/oidc#customizing-the-token-claims) if you want full control over the information you can filter for in your trust policy. If you aren't sure what your subject (`sub`) key is, you can add the [`actions-oidc-debugger`](https://github.com/github/actions-oidc-debugger) @@ -407,7 +544,11 @@ action to your workflow to see the value of the subject (`sub`) key, as well as other claims. Additional claim conditions can be added for higher specificity as explained in -the [GitHub documentation](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect). +the +[GitHub documentation][gh-oidc-hardening]. + +[gh-oidc-hardening]: + https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect Due to implementation details, not every OIDC claim is presently supported by IAM. @@ -415,113 +556,165 @@ IAM. For further information on OIDC and GitHub Actions, please see: -* [AWS docs: Creating OpenID Connect (OIDC) identity providers](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc.html) -* [AWS docs: IAM JSON policy elements: Condition](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_condition.html) -* [GitHub docs: About security hardening with OpenID Connect](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect) -* [GitHub docs: Configuring OpenID Connect in Amazon Web Services](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services) -* [GitHub changelog: GitHub Actions: Secure cloud deployments with OpenID Connect](https://github.blog/changelog/2021-10-27-github-actions-secure-cloud-deployments-with-openid-connect/) +- [AWS docs: Creating OpenID Connect (OIDC) identity providers](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc.html) +- [AWS docs: IAM JSON policy elements: Condition](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_condition.html) +- [GitHub docs: About security hardening with OpenID Connect](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect) +- [GitHub docs: Configuring OpenID Connect in Amazon Web Services](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services) +- [GitHub changelog: GitHub Actions: Secure cloud deployments with OpenID Connect](https://github.blog/changelog/2021-10-27-github-actions-secure-cloud-deployments-with-openid-connect/) -Examples --------- +## Examples ### AssumeRoleWithWebIdentity + ```yaml - - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v6.0.0 - with: - aws-region: us-east-2 - role-to-assume: arn:aws:iam::123456789100:role/my-github-actions-role - role-session-name: MySessionName +- name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v6.1.0 + with: + aws-region: us-east-2 + role-to-assume: arn:aws:iam::123456789100:role/my-github-actions-role + role-session-name: MySessionName ``` + In this example, the Action will load the OIDC token from the GitHub-provided environment variable and use it to assume the role `arn:aws:iam::123456789100:role/my-github-actions-role` with the session name `MySessionName`. ### AssumeRole with role previously assumed by action in same workflow + ```yaml - - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v6.0.0 - with: - aws-region: us-east-2 - role-to-assume: arn:aws:iam::123456789100:role/my-github-actions-role - role-session-name: MySessionName - - name: Configure other AWS Credentials - uses: aws-actions/configure-aws-credentials@v6.0.0 - with: - aws-region: us-east-2 - role-to-assume: arn:aws:iam::987654321000:role/my-second-role - role-session-name: MySessionName - role-chaining: true +- name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v6.1.0 + with: + aws-region: us-east-2 + role-to-assume: arn:aws:iam::123456789100:role/my-github-actions-role + role-session-name: MySessionName +- name: Configure other AWS Credentials + uses: aws-actions/configure-aws-credentials@v6.1.0 + with: + aws-region: us-east-2 + role-to-assume: arn:aws:iam::987654321000:role/my-second-role + role-session-name: MySessionName + role-chaining: true ``` + In this two-step example, the first step will use OIDC to assume the role `arn:aws:iam::123456789100:role/my-github-actions-role` just as in the prior example. Following that, a second step will use this role to assume a different role, `arn:aws:iam::987654321000:role/my-second-role`. -Note that the trust relationship/trust policy of the second role must grant the permissions `sts:AssumeRole` and `sts:TagSession` to the first role. (Or, alternatively, the `TagSession` permission can be omitted if you are using the `role-skip-session-tagging: true` flag for the second step.) +Note that the trust relationship/trust policy of the second role must grant the +permissions `sts:AssumeRole` and `sts:TagSession` to the first role. (Or, +alternatively, the `TagSession` permission can be omitted if you are using the +`role-skip-session-tagging: true` flag for the second step.) ### AssumeRole with static IAM credentials in repository secrets -```yaml - - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v6.0.0 - with: - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws-region: us-east-2 - role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} - role-external-id: ${{ secrets.AWS_ROLE_EXTERNAL_ID }} - role-duration-seconds: 1200 - role-session-name: MySessionName -``` -In this example, the secret `AWS_ROLE_TO_ASSUME` contains a string like -`arn:aws:iam::123456789100:role/my-github-actions-role`. To assume a role in -the same account as the static credentials, you can simply specify the role -name, like `role-to-assume: my-github-actions-role`. -### Retrieving credentials from step output, AssumeRole with temporary credentials ```yaml - - name: Configure AWS Credentials 1 - id: creds - uses: aws-actions/configure-aws-credentials@v6.0.0 - with: - aws-region: us-east-2 - role-to-assume: arn:aws:iam::123456789100:role/my-github-actions-role - output-credentials: true - - name: get caller identity 1 - run: | - aws sts get-caller-identity - - name: Configure AWS Credentials 2 - uses: aws-actions/configure-aws-credentials@v6.0.0 - with: - aws-region: us-east-2 - aws-access-key-id: ${{ steps.creds.outputs.aws-access-key-id }} - aws-secret-access-key: ${{ steps.creds.outputs.aws-secret-access-key }} - aws-session-token: ${{ steps.creds.outputs.aws-session-token }} - role-to-assume: arn:aws:iam::123456789100:role/my-other-github-actions-role - - name: get caller identity2 - run: | - aws sts get-caller-identity +- name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v6.1.0 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: us-east-2 + role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} + role-external-id: ${{ secrets.AWS_ROLE_EXTERNAL_ID }} + role-duration-seconds: 1200 + role-session-name: MySessionName ``` + +In this example, the secret `AWS_ROLE_TO_ASSUME` contains a string like +`arn:aws:iam::123456789100:role/my-github-actions-role`. To assume a role in the +same account as the static credentials, you can simply specify the role name, +like `role-to-assume: my-github-actions-role`. + +### Retrieving credentials from step output + +```yaml +- name: Configure AWS Credentials 1 + id: creds + uses: aws-actions/configure-aws-credentials@v6.1.0 + with: + aws-region: us-east-2 + role-to-assume: arn:aws:iam::123456789100:role/my-github-actions-role + output-credentials: true +- name: get caller identity 1 + run: | + aws sts get-caller-identity +- name: Configure AWS Credentials 2 + uses: aws-actions/configure-aws-credentials@v6.1.0 + with: + aws-region: us-east-2 + aws-access-key-id: ${{ steps.creds.outputs.aws-access-key-id }} + aws-secret-access-key: ${{ steps.creds.outputs.aws-secret-access-key }} + aws-session-token: ${{ steps.creds.outputs.aws-session-token }} + role-to-assume: arn:aws:iam::123456789100:role/my-other-github-actions-role +- name: get caller identity2 + run: | + aws sts get-caller-identity +``` + This example shows that you can reference the fetched credentials as outputs if `output-credentials` is set to true. This example also shows that you can use the `aws-session-token` input in a situation where session tokens are fetched and passed to this action. -Versioning ----------- -Starting with version 5.0.0, this action uses semantic-style release tags and -[immutable releases](https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/immutable-releases). -A floating version tag (vN) is also provided for convenience: this tag will -move to the latest major version (vN -> vN.2.1, vM -> vM.0.0, etc.). +### Configure multiple AWS profiles in a single workflow + +```yaml +- name: Configure AWS Credentials for Dev + uses: aws-actions/configure-aws-credentials@v6.1.0 + with: + aws-region: us-east-1 + role-to-assume: arn:aws:iam::111111111111:role/dev-role + aws-profile: dev + +- name: Configure AWS Credentials for Prod + uses: aws-actions/configure-aws-credentials@v6.1.0 + with: + aws-region: us-west-2 + role-to-assume: arn:aws:iam::222222222222:role/prod-role + aws-profile: prod + +- name: Use multiple profiles + run: | + # Check caller identity for dev account + aws sts get-caller-identity --profile dev + + # Check caller identity for prod account + aws sts get-caller-identity --profile prod + + # Deploy to dev using CDK + cdk deploy --profile dev +``` + +This example shows how to configure multiple named AWS profiles in a single +workflow. When using the `aws-profile` input, credentials are written to +`~/.aws/credentials` and `~/.aws/config` files, allowing you to reference +different profiles using the `--profile` flag with AWS CLI, SDKs, CDK, and +other tools. + +Each profile is independent and can authenticate to different AWS accounts or +use different roles. This is particularly useful for multi-account deployments +or when you need to interact with multiple AWS environments in a single job. + +## Versioning + +Starting with version 5.0.0, this action uses semantic-style release tags and +[immutable releases][immutable-releases]. + +[immutable-releases]: + https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/immutable-releases +A floating version tag (vN) is also provided for convenience: this tag will move +to the latest major version (vN -> vN.2.1, vM -> vM.0.0, etc.). + +## License -License -------- This code is made available under the MIT license. -Security Disclosures --------------------- +## Security Disclosures + If you would like to report a potential security issue in this project, please do not create a GitHub issue. Instead, please follow the instructions -[here](https://aws.amazon.com/security/vulnerability-reporting/) or -[email AWS security](mailto:aws-security@amazon.com) directly. +[on the vulnerability reporting page](https://aws.amazon.com/security/vulnerability-reporting/) +or [email AWS security](mailto:aws-security@amazon.com) directly. diff --git a/action.yml b/action.yml index cdba191..e6448c7 100644 --- a/action.yml +++ b/action.yml @@ -13,6 +13,12 @@ inputs: aws-region: description: AWS Region, e.g. us-east-2 required: true + aws-profile: + description: Name of the AWS profile to configure. When provided, credentials are written to ~/.aws/credentials and ~/.aws/config files instead of env variables (unless output-env-credentials is manually set to true). Name cannot contain whitespace, square brackets, or slashes. + required: false + overwrite-aws-profile: + description: Overwrite the given AWS profile if it already exists. Requires aws-profile. When set to false or not set, an error will be thrown if the profile already exists. + required: false role-to-assume: description: The Amazon Resource Name (ARN) of the role to assume. Use the provided credentials to assume an IAM role and configure the Actions environment with the assumed role credentials rather than with the provided credentials. required: false @@ -71,7 +77,6 @@ inputs: output-env-credentials: description: Whether to export credentials as environment variables. If you set this to false, you probably want to use output-credentials. required: false - default: "true" unset-current-credentials: description: Whether to unset the existing credentials in your runner. May be useful if you run this action multiple times in the same job required: false diff --git a/dist/cleanup/index.js b/dist/cleanup/index.js index 5c28d64..4d2a6e4 100644 --- a/dist/cleanup/index.js +++ b/dist/cleanup/index.js @@ -21418,9 +21418,9 @@ var require_core = __commonJS({ exports2.exportVariable = exportVariable3; exports2.setSecret = setSecret2; exports2.addPath = addPath; - exports2.getInput = getInput2; + exports2.getInput = getInput3; exports2.getMultilineInput = getMultilineInput; - exports2.getBooleanInput = getBooleanInput2; + exports2.getBooleanInput = getBooleanInput; exports2.setOutput = setOutput2; exports2.setCommandEcho = setCommandEcho; exports2.setFailed = setFailed2; @@ -21468,7 +21468,7 @@ var require_core = __commonJS({ } process.env["PATH"] = `${inputPath}${path.delimiter}${process.env["PATH"]}`; } - function getInput2(name, options) { + function getInput3(name, options) { const val = process.env[`INPUT_${name.replace(/ /g, "_").toUpperCase()}`] || ""; if (options && options.required && !val) { throw new Error(`Input required and not supplied: ${name}`); @@ -21479,16 +21479,16 @@ var require_core = __commonJS({ return val.trim(); } function getMultilineInput(name, options) { - const inputs = getInput2(name, options).split("\n").filter((x) => x !== ""); + const inputs = getInput3(name, options).split("\n").filter((x) => x !== ""); if (options && options.trimWhitespace === false) { return inputs; } return inputs.map((input) => input.trim()); } - function getBooleanInput2(name, options) { + function getBooleanInput(name, options) { const trueValue = ["true", "True", "TRUE"]; const falseValue = ["false", "False", "FALSE"]; - const val = getInput2(name, options); + const val = getInput3(name, options); if (trueValue.includes(val)) return true; if (falseValue.includes(val)) @@ -21597,30 +21597,19 @@ var core = __toESM(require_core()); function errorMessage(error) { return error instanceof Error ? error.message : String(error); } -function getBooleanInput(name, options) { - const trueValue = ["true", "True", "TRUE"]; - const falseValue = ["false", "False", "FALSE"]; - const optionsWithoutDefault = { ...options }; - delete optionsWithoutDefault.default; - const val = core.getInput(name, optionsWithoutDefault); - if (trueValue.includes(val)) return true; - if (falseValue.includes(val)) return false; - if (val === "") return options?.default ?? false; - throw new TypeError( - `Input does not meet YAML 1.2 "Core Schema" specification: ${name} -Support boolean input list: \`true | True | TRUE | false | False | FALSE\`` - ); -} // src/cleanup/index.ts function cleanup() { - if (getBooleanInput("output-env-credentials", { required: false, default: true })) { + if (core2.getInput("output-env-credentials") !== "false") { try { core2.exportVariable("AWS_ACCESS_KEY_ID", ""); core2.exportVariable("AWS_SECRET_ACCESS_KEY", ""); core2.exportVariable("AWS_SESSION_TOKEN", ""); core2.exportVariable("AWS_DEFAULT_REGION", ""); core2.exportVariable("AWS_REGION", ""); + if (core2.getInput("aws-profile")) { + core2.exportVariable("AWS_PROFILE", ""); + } } catch (error) { core2.setFailed(errorMessage(error)); } diff --git a/dist/index.js b/dist/index.js index 92ca640..ad4f30d 100644 --- a/dist/index.js +++ b/dist/index.js @@ -108,11 +108,11 @@ var require_command = __commonJS({ Object.defineProperty(exports2, "__esModule", { value: true }); exports2.issueCommand = issueCommand; exports2.issue = issue; - var os = __importStar2(require("os")); + var os2 = __importStar2(require("os")); var utils_1 = require_utils(); function issueCommand(command, properties, message) { const cmd = new Command(command, properties, message); - process.stdout.write(cmd.toString() + os.EOL); + process.stdout.write(cmd.toString() + os2.EOL); } function issue(name, message = "") { issueCommand(name, {}, message); @@ -204,18 +204,18 @@ var require_file_command = __commonJS({ exports2.issueFileCommand = issueFileCommand; exports2.prepareKeyValueMessage = prepareKeyValueMessage; var crypto2 = __importStar2(require("crypto")); - var fs2 = __importStar2(require("fs")); - var os = __importStar2(require("os")); + var fs3 = __importStar2(require("fs")); + var os2 = __importStar2(require("os")); var utils_1 = require_utils(); function issueFileCommand(command, message) { const filePath = process.env[`GITHUB_${command}`]; if (!filePath) { throw new Error(`Unable to find environment variable for file command ${command}`); } - if (!fs2.existsSync(filePath)) { + if (!fs3.existsSync(filePath)) { throw new Error(`Missing file at path: ${filePath}`); } - fs2.appendFileSync(filePath, `${(0, utils_1.toCommandValue)(message)}${os.EOL}`, { + fs3.appendFileSync(filePath, `${(0, utils_1.toCommandValue)(message)}${os2.EOL}`, { encoding: "utf8" }); } @@ -228,7 +228,7 @@ var require_file_command = __commonJS({ if (convertedValue.includes(delimiter)) { throw new Error(`Unexpected input: value should not contain the delimiter "${delimiter}"`); } - return `${key}<<${delimiter}${os.EOL}${convertedValue}${os.EOL}${delimiter}`; + return `${key}<<${delimiter}${os2.EOL}${convertedValue}${os2.EOL}${delimiter}`; } } }); @@ -417,7 +417,7 @@ var require_tunnel = __commonJS({ connectOptions.headers = connectOptions.headers || {}; connectOptions.headers["Proxy-Authorization"] = "Basic " + new Buffer(connectOptions.proxyAuth).toString("base64"); } - debug3("making CONNECT request"); + debug4("making CONNECT request"); var connectReq = self2.request(connectOptions); connectReq.useChunkedEncodingByDefault = false; connectReq.once("response", onResponse); @@ -437,7 +437,7 @@ var require_tunnel = __commonJS({ connectReq.removeAllListeners(); socket.removeAllListeners(); if (res.statusCode !== 200) { - debug3( + debug4( "tunneling socket could not be established, statusCode=%d", res.statusCode ); @@ -449,7 +449,7 @@ var require_tunnel = __commonJS({ return; } if (head.length > 0) { - debug3("got illegal response body from proxy"); + debug4("got illegal response body from proxy"); socket.destroy(); var error2 = new Error("got illegal response body from proxy"); error2.code = "ECONNRESET"; @@ -457,13 +457,13 @@ var require_tunnel = __commonJS({ self2.removeSocket(placeholder); return; } - debug3("tunneling connection has established"); + debug4("tunneling connection has established"); self2.sockets[self2.sockets.indexOf(placeholder)] = socket; return cb(socket); } function onError(cause) { connectReq.removeAllListeners(); - debug3( + debug4( "tunneling socket could not be established, cause=%s\n", cause.message, cause.stack @@ -525,9 +525,9 @@ var require_tunnel = __commonJS({ } return target; } - var debug3; + var debug4; if (process.env.NODE_DEBUG && /\btunnel\b/.test(process.env.NODE_DEBUG)) { - debug3 = function() { + debug4 = function() { var args = Array.prototype.slice.call(arguments); if (typeof args[0] === "string") { args[0] = "TUNNEL: " + args[0]; @@ -537,10 +537,10 @@ var require_tunnel = __commonJS({ console.error.apply(console, args); }; } else { - debug3 = function() { + debug4 = function() { }; } - exports2.debug = debug3; + exports2.debug = debug4; } }); @@ -1353,14 +1353,14 @@ var require_util = __commonJS({ } const port = url.port != null ? url.port : url.protocol === "https:" ? 443 : 80; let origin = url.origin != null ? url.origin : `${url.protocol || ""}//${url.hostname || ""}:${port}`; - let path2 = url.path != null ? url.path : `${url.pathname || ""}${url.search || ""}`; + let path3 = url.path != null ? url.path : `${url.pathname || ""}${url.search || ""}`; if (origin[origin.length - 1] === "/") { origin = origin.slice(0, origin.length - 1); } - if (path2 && path2[0] !== "/") { - path2 = `/${path2}`; + if (path3 && path3[0] !== "/") { + path3 = `/${path3}`; } - return new URL(`${origin}${path2}`); + return new URL(`${origin}${path3}`); } if (!isHttpOrHttpsPrefixed(url.origin || url.protocol)) { throw new InvalidArgumentError("Invalid URL protocol: the URL must start with `http:` or `https:`."); @@ -1811,39 +1811,39 @@ var require_diagnostics = __commonJS({ }); diagnosticsChannel.channel("undici:client:sendHeaders").subscribe((evt) => { const { - request: { method, path: path2, origin } + request: { method, path: path3, origin } } = evt; - debuglog("sending request to %s %s/%s", method, origin, path2); + debuglog("sending request to %s %s/%s", method, origin, path3); }); diagnosticsChannel.channel("undici:request:headers").subscribe((evt) => { const { - request: { method, path: path2, origin }, + request: { method, path: path3, origin }, response: { statusCode } } = evt; debuglog( "received response to %s %s/%s - HTTP %d", method, origin, - path2, + path3, statusCode ); }); diagnosticsChannel.channel("undici:request:trailers").subscribe((evt) => { const { - request: { method, path: path2, origin } + request: { method, path: path3, origin } } = evt; - debuglog("trailers received from %s %s/%s", method, origin, path2); + debuglog("trailers received from %s %s/%s", method, origin, path3); }); diagnosticsChannel.channel("undici:request:error").subscribe((evt) => { const { - request: { method, path: path2, origin }, + request: { method, path: path3, origin }, error: error2 } = evt; debuglog( "request to %s %s/%s errored - %s", method, origin, - path2, + path3, error2.message ); }); @@ -1892,9 +1892,9 @@ var require_diagnostics = __commonJS({ }); diagnosticsChannel.channel("undici:client:sendHeaders").subscribe((evt) => { const { - request: { method, path: path2, origin } + request: { method, path: path3, origin } } = evt; - debuglog("sending request to %s %s/%s", method, origin, path2); + debuglog("sending request to %s %s/%s", method, origin, path3); }); } diagnosticsChannel.channel("undici:websocket:open").subscribe((evt) => { @@ -1957,7 +1957,7 @@ var require_request = __commonJS({ var kHandler = /* @__PURE__ */ Symbol("handler"); var Request2 = class { constructor(origin, { - path: path2, + path: path3, method, body, headers, @@ -1972,11 +1972,11 @@ var require_request = __commonJS({ expectContinue, servername }, handler) { - if (typeof path2 !== "string") { + if (typeof path3 !== "string") { throw new InvalidArgumentError("path must be a string"); - } else if (path2[0] !== "/" && !(path2.startsWith("http://") || path2.startsWith("https://")) && method !== "CONNECT") { + } else if (path3[0] !== "/" && !(path3.startsWith("http://") || path3.startsWith("https://")) && method !== "CONNECT") { throw new InvalidArgumentError("path must be an absolute URL or start with a slash"); - } else if (invalidPathRegex.test(path2)) { + } else if (invalidPathRegex.test(path3)) { throw new InvalidArgumentError("invalid request path"); } if (typeof method !== "string") { @@ -2042,7 +2042,7 @@ var require_request = __commonJS({ this.completed = false; this.aborted = false; this.upgrade = upgrade || null; - this.path = query ? buildURL(path2, query) : path2; + this.path = query ? buildURL(path3, query) : path3; this.origin = origin; this.idempotent = idempotent == null ? method === "HEAD" || method === "GET" : idempotent; this.blocking = blocking == null ? false : blocking; @@ -6561,7 +6561,7 @@ var require_client_h1 = __commonJS({ return method !== "GET" && method !== "HEAD" && method !== "OPTIONS" && method !== "TRACE" && method !== "CONNECT"; } function writeH1(client, request) { - const { method, path: path2, host, upgrade, blocking, reset } = request; + const { method, path: path3, host, upgrade, blocking, reset } = request; let { body, headers, contentLength } = request; const expectsPayload = method === "PUT" || method === "POST" || method === "PATCH" || method === "QUERY" || method === "PROPFIND" || method === "PROPPATCH"; if (util.isFormDataLike(body)) { @@ -6627,7 +6627,7 @@ var require_client_h1 = __commonJS({ if (blocking) { socket[kBlocking] = true; } - let header = `${method} ${path2} HTTP/1.1\r + let header = `${method} ${path3} HTTP/1.1\r `; if (typeof host === "string") { header += `host: ${host}\r @@ -7153,7 +7153,7 @@ var require_client_h2 = __commonJS({ } function writeH2(client, request) { const session = client[kHTTP2Session]; - const { method, path: path2, host, upgrade, expectContinue, signal, headers: reqHeaders } = request; + const { method, path: path3, host, upgrade, expectContinue, signal, headers: reqHeaders } = request; let { body } = request; if (upgrade) { util.errorRequest(client, request, new Error("Upgrade not supported for H2")); @@ -7220,7 +7220,7 @@ var require_client_h2 = __commonJS({ }); return true; } - headers[HTTP2_HEADER_PATH] = path2; + headers[HTTP2_HEADER_PATH] = path3; headers[HTTP2_HEADER_SCHEME] = "https"; const expectsPayload = method === "PUT" || method === "POST" || method === "PATCH"; if (body && typeof body.read === "function") { @@ -7573,9 +7573,9 @@ var require_redirect_handler = __commonJS({ return this.handler.onHeaders(statusCode, headers, resume, statusText); } const { origin, pathname, search } = util.parseURL(new URL(this.location, this.opts.origin && new URL(this.opts.path, this.opts.origin))); - const path2 = search ? `${pathname}${search}` : pathname; + const path3 = search ? `${pathname}${search}` : pathname; this.opts.headers = cleanRequestHeaders(this.opts.headers, statusCode === 303, this.opts.origin !== origin); - this.opts.path = path2; + this.opts.path = path3; this.opts.origin = origin; this.opts.maxRedirections = 0; this.opts.query = null; @@ -8809,10 +8809,10 @@ var require_proxy_agent = __commonJS({ }; const { origin, - path: path2 = "/", + path: path3 = "/", headers = {} } = opts; - opts.path = origin + path2; + opts.path = origin + path3; if (!("host" in headers) && !("Host" in headers)) { const { host } = new URL2(origin); headers.host = host; @@ -10733,20 +10733,20 @@ var require_mock_utils = __commonJS({ } return true; } - function safeUrl(path2) { - if (typeof path2 !== "string") { - return path2; + function safeUrl(path3) { + if (typeof path3 !== "string") { + return path3; } - const pathSegments = path2.split("?"); + const pathSegments = path3.split("?"); if (pathSegments.length !== 2) { - return path2; + return path3; } const qp = new URLSearchParams(pathSegments.pop()); qp.sort(); return [...pathSegments, qp.toString()].join("?"); } - function matchKey(mockDispatch2, { path: path2, method, body, headers }) { - const pathMatch = matchValue(mockDispatch2.path, path2); + function matchKey(mockDispatch2, { path: path3, method, body, headers }) { + const pathMatch = matchValue(mockDispatch2.path, path3); const methodMatch = matchValue(mockDispatch2.method, method); const bodyMatch = typeof mockDispatch2.body !== "undefined" ? matchValue(mockDispatch2.body, body) : true; const headersMatch = matchHeaders(mockDispatch2, headers); @@ -10768,7 +10768,7 @@ var require_mock_utils = __commonJS({ function getMockDispatch(mockDispatches, key) { const basePath = key.query ? buildURL(key.path, key.query) : key.path; const resolvedPath2 = typeof basePath === "string" ? safeUrl(basePath) : basePath; - let matchedMockDispatches = mockDispatches.filter(({ consumed }) => !consumed).filter(({ path: path2 }) => matchValue(safeUrl(path2), resolvedPath2)); + let matchedMockDispatches = mockDispatches.filter(({ consumed }) => !consumed).filter(({ path: path3 }) => matchValue(safeUrl(path3), resolvedPath2)); if (matchedMockDispatches.length === 0) { throw new MockNotMatchedError(`Mock dispatch not matched for path '${resolvedPath2}'`); } @@ -10806,9 +10806,9 @@ var require_mock_utils = __commonJS({ } } function buildKey(opts) { - const { path: path2, method, body, headers, query } = opts; + const { path: path3, method, body, headers, query } = opts; return { - path: path2, + path: path3, method, body, headers, @@ -11271,10 +11271,10 @@ var require_pending_interceptors_formatter = __commonJS({ } format(pendingInterceptors) { const withPrettyHeaders = pendingInterceptors.map( - ({ method, path: path2, data: { statusCode }, persist, times, timesInvoked, origin }) => ({ + ({ method, path: path3, data: { statusCode }, persist, times, timesInvoked, origin }) => ({ Method: method, Origin: origin, - Path: path2, + Path: path3, "Status code": statusCode, Persistent: persist ? PERSISTENT : NOT_PERSISTENT, Invocations: timesInvoked, @@ -16155,9 +16155,9 @@ var require_util6 = __commonJS({ } } } - function validateCookiePath(path2) { - for (let i5 = 0; i5 < path2.length; ++i5) { - const code = path2.charCodeAt(i5); + function validateCookiePath(path3) { + for (let i5 = 0; i5 < path3.length; ++i5) { + const code = path3.charCodeAt(i5); if (code < 32 || // exclude CTLs (0-31) code === 127 || // DEL code === 59) { @@ -18823,11 +18823,11 @@ var require_undici = __commonJS({ if (typeof opts.path !== "string") { throw new InvalidArgumentError("invalid opts.path"); } - let path2 = opts.path; + let path3 = opts.path; if (!opts.path.startsWith("/")) { - path2 = `/${path2}`; + path3 = `/${path3}`; } - url = new URL(util.parseOrigin(url).origin + path2); + url = new URL(util.parseOrigin(url).origin + path3); } else { if (!opts) { opts = typeof url === "object" ? url : {}; @@ -19205,12 +19205,12 @@ var require_lib = __commonJS({ throw new Error("Client has already been disposed."); } const parsedUrl = new URL(requestUrl); - let info4 = this._prepareRequest(verb, parsedUrl, headers); + let info5 = this._prepareRequest(verb, parsedUrl, headers); const maxTries = this._allowRetries && RetryableHttpVerbs.includes(verb) ? this._maxRetries + 1 : 1; let numTries = 0; let response; do { - response = yield this.requestRaw(info4, data2); + response = yield this.requestRaw(info5, data2); if (response && response.message && response.message.statusCode === HttpCodes.Unauthorized) { let authenticationHandler; for (const handler of this.handlers) { @@ -19220,7 +19220,7 @@ var require_lib = __commonJS({ } } if (authenticationHandler) { - return authenticationHandler.handleAuthentication(this, info4, data2); + return authenticationHandler.handleAuthentication(this, info5, data2); } else { return response; } @@ -19243,8 +19243,8 @@ var require_lib = __commonJS({ } } } - info4 = this._prepareRequest(verb, parsedRedirectUrl, headers); - response = yield this.requestRaw(info4, data2); + info5 = this._prepareRequest(verb, parsedRedirectUrl, headers); + response = yield this.requestRaw(info5, data2); redirectsRemaining--; } if (!response.message.statusCode || !HttpResponseRetryCodes.includes(response.message.statusCode)) { @@ -19273,7 +19273,7 @@ var require_lib = __commonJS({ * @param info * @param data */ - requestRaw(info4, data2) { + requestRaw(info5, data2) { return __awaiter2(this, void 0, void 0, function* () { return new Promise((resolve, reject) => { function callbackForResult(err, res) { @@ -19285,7 +19285,7 @@ var require_lib = __commonJS({ resolve(res); } } - this.requestRawWithCallback(info4, data2, callbackForResult); + this.requestRawWithCallback(info5, data2, callbackForResult); }); }); } @@ -19295,12 +19295,12 @@ var require_lib = __commonJS({ * @param data * @param onResult */ - requestRawWithCallback(info4, data2, onResult) { + requestRawWithCallback(info5, data2, onResult) { if (typeof data2 === "string") { - if (!info4.options.headers) { - info4.options.headers = {}; + if (!info5.options.headers) { + info5.options.headers = {}; } - info4.options.headers["Content-Length"] = Buffer.byteLength(data2, "utf8"); + info5.options.headers["Content-Length"] = Buffer.byteLength(data2, "utf8"); } let callbackCalled = false; function handleResult(err, res) { @@ -19309,7 +19309,7 @@ var require_lib = __commonJS({ onResult(err, res); } } - const req = info4.httpModule.request(info4.options, (msg) => { + const req = info5.httpModule.request(info5.options, (msg) => { const res = new HttpClientResponse(msg); handleResult(void 0, res); }); @@ -19321,7 +19321,7 @@ var require_lib = __commonJS({ if (socket) { socket.end(); } - handleResult(new Error(`Request timeout: ${info4.options.path}`)); + handleResult(new Error(`Request timeout: ${info5.options.path}`)); }); req.on("error", function(err) { handleResult(err); @@ -19357,27 +19357,27 @@ var require_lib = __commonJS({ return this._getProxyAgentDispatcher(parsedUrl, proxyUrl); } _prepareRequest(method, requestUrl, headers) { - const info4 = {}; - info4.parsedUrl = requestUrl; - const usingSsl = info4.parsedUrl.protocol === "https:"; - info4.httpModule = usingSsl ? https : http; + const info5 = {}; + info5.parsedUrl = requestUrl; + const usingSsl = info5.parsedUrl.protocol === "https:"; + info5.httpModule = usingSsl ? https : http; const defaultPort = usingSsl ? 443 : 80; - info4.options = {}; - info4.options.host = info4.parsedUrl.hostname; - info4.options.port = info4.parsedUrl.port ? parseInt(info4.parsedUrl.port) : defaultPort; - info4.options.path = (info4.parsedUrl.pathname || "") + (info4.parsedUrl.search || ""); - info4.options.method = method; - info4.options.headers = this._mergeHeaders(headers); + info5.options = {}; + info5.options.host = info5.parsedUrl.hostname; + info5.options.port = info5.parsedUrl.port ? parseInt(info5.parsedUrl.port) : defaultPort; + info5.options.path = (info5.parsedUrl.pathname || "") + (info5.parsedUrl.search || ""); + info5.options.method = method; + info5.options.headers = this._mergeHeaders(headers); if (this.userAgent != null) { - info4.options.headers["user-agent"] = this.userAgent; + info5.options.headers["user-agent"] = this.userAgent; } - info4.options.agent = this._getAgent(info4.parsedUrl); + info5.options.agent = this._getAgent(info5.parsedUrl); if (this.handlers) { for (const handler of this.handlers) { - handler.prepareRequest(info4.options); + handler.prepareRequest(info5.options); } } - return info4; + return info5; } _mergeHeaders(headers) { if (this.requestOptions && this.requestOptions.headers) { @@ -20130,7 +20130,7 @@ var require_path_utils = __commonJS({ exports2.toPosixPath = toPosixPath; exports2.toWin32Path = toWin32Path; exports2.toPlatformPath = toPlatformPath; - var path2 = __importStar2(require("path")); + var path3 = __importStar2(require("path")); function toPosixPath(pth) { return pth.replace(/[\\]/g, "/"); } @@ -20138,7 +20138,7 @@ var require_path_utils = __commonJS({ return pth.replace(/[/]/g, "\\"); } function toPlatformPath(pth) { - return pth.replace(/[/\\]/g, path2.sep); + return pth.replace(/[/\\]/g, path3.sep); } } }); @@ -20220,13 +20220,13 @@ var require_io_util = __commonJS({ exports2.isRooted = isRooted; exports2.tryGetExecutablePath = tryGetExecutablePath; exports2.getCmdPath = getCmdPath; - var fs2 = __importStar2(require("fs")); - var path2 = __importStar2(require("path")); - _a2 = fs2.promises, exports2.chmod = _a2.chmod, exports2.copyFile = _a2.copyFile, exports2.lstat = _a2.lstat, exports2.mkdir = _a2.mkdir, exports2.open = _a2.open, exports2.readdir = _a2.readdir, exports2.rename = _a2.rename, exports2.rm = _a2.rm, exports2.rmdir = _a2.rmdir, exports2.stat = _a2.stat, exports2.symlink = _a2.symlink, exports2.unlink = _a2.unlink; + var fs3 = __importStar2(require("fs")); + var path3 = __importStar2(require("path")); + _a2 = fs3.promises, exports2.chmod = _a2.chmod, exports2.copyFile = _a2.copyFile, exports2.lstat = _a2.lstat, exports2.mkdir = _a2.mkdir, exports2.open = _a2.open, exports2.readdir = _a2.readdir, exports2.rename = _a2.rename, exports2.rm = _a2.rm, exports2.rmdir = _a2.rmdir, exports2.stat = _a2.stat, exports2.symlink = _a2.symlink, exports2.unlink = _a2.unlink; exports2.IS_WINDOWS = process.platform === "win32"; function readlink(fsPath) { return __awaiter2(this, void 0, void 0, function* () { - const result = yield fs2.promises.readlink(fsPath); + const result = yield fs3.promises.readlink(fsPath); if (exports2.IS_WINDOWS && !result.endsWith("\\")) { return `${result}\\`; } @@ -20234,7 +20234,7 @@ var require_io_util = __commonJS({ }); } exports2.UV_FS_O_EXLOCK = 268435456; - exports2.READONLY = fs2.constants.O_RDONLY; + exports2.READONLY = fs3.constants.O_RDONLY; function exists(fsPath) { return __awaiter2(this, void 0, void 0, function* () { try { @@ -20276,7 +20276,7 @@ var require_io_util = __commonJS({ } if (stats && stats.isFile()) { if (exports2.IS_WINDOWS) { - const upperExt = path2.extname(filePath).toUpperCase(); + const upperExt = path3.extname(filePath).toUpperCase(); if (extensions.some((validExt) => validExt.toUpperCase() === upperExt)) { return filePath; } @@ -20300,11 +20300,11 @@ var require_io_util = __commonJS({ if (stats && stats.isFile()) { if (exports2.IS_WINDOWS) { try { - const directory = path2.dirname(filePath); - const upperName = path2.basename(filePath).toUpperCase(); + const directory = path3.dirname(filePath); + const upperName = path3.basename(filePath).toUpperCase(); for (const actualName of yield (0, exports2.readdir)(directory)) { if (upperName === actualName.toUpperCase()) { - filePath = path2.join(directory, actualName); + filePath = path3.join(directory, actualName); break; } } @@ -20416,7 +20416,7 @@ var require_io = __commonJS({ exports2.which = which; exports2.findInPath = findInPath; var assert_1 = require("assert"); - var path2 = __importStar2(require("path")); + var path3 = __importStar2(require("path")); var ioUtil = __importStar2(require_io_util()); function cp(source_1, dest_1) { return __awaiter2(this, arguments, void 0, function* (source, dest, options = {}) { @@ -20425,7 +20425,7 @@ var require_io = __commonJS({ if (destStat && destStat.isFile() && !force) { return; } - const newDest = destStat && destStat.isDirectory() && copySourceDirectory ? path2.join(dest, path2.basename(source)) : dest; + const newDest = destStat && destStat.isDirectory() && copySourceDirectory ? path3.join(dest, path3.basename(source)) : dest; if (!(yield ioUtil.exists(source))) { throw new Error(`no such file or directory: ${source}`); } @@ -20437,7 +20437,7 @@ var require_io = __commonJS({ yield cpDirRecursive(source, newDest, 0, force); } } else { - if (path2.relative(source, newDest) === "") { + if (path3.relative(source, newDest) === "") { throw new Error(`'${newDest}' and '${source}' are the same file`); } yield copyFile(source, newDest, force); @@ -20449,7 +20449,7 @@ var require_io = __commonJS({ if (yield ioUtil.exists(dest)) { let destExists = true; if (yield ioUtil.isDirectory(dest)) { - dest = path2.join(dest, path2.basename(source)); + dest = path3.join(dest, path3.basename(source)); destExists = yield ioUtil.exists(dest); } if (destExists) { @@ -20460,7 +20460,7 @@ var require_io = __commonJS({ } } } - yield mkdirP(path2.dirname(dest)); + yield mkdirP(path3.dirname(dest)); yield ioUtil.rename(source, dest); }); } @@ -20519,7 +20519,7 @@ var require_io = __commonJS({ } const extensions = []; if (ioUtil.IS_WINDOWS && process.env["PATHEXT"]) { - for (const extension of process.env["PATHEXT"].split(path2.delimiter)) { + for (const extension of process.env["PATHEXT"].split(path3.delimiter)) { if (extension) { extensions.push(extension); } @@ -20532,12 +20532,12 @@ var require_io = __commonJS({ } return []; } - if (tool.includes(path2.sep)) { + if (tool.includes(path3.sep)) { return []; } const directories = []; if (process.env.PATH) { - for (const p5 of process.env.PATH.split(path2.delimiter)) { + for (const p5 of process.env.PATH.split(path3.delimiter)) { if (p5) { directories.push(p5); } @@ -20545,7 +20545,7 @@ var require_io = __commonJS({ } const matches = []; for (const directory of directories) { - const filePath = yield ioUtil.tryGetExecutablePath(path2.join(directory, tool), extensions); + const filePath = yield ioUtil.tryGetExecutablePath(path3.join(directory, tool), extensions); if (filePath) { matches.push(filePath); } @@ -20672,10 +20672,10 @@ var require_toolrunner = __commonJS({ Object.defineProperty(exports2, "__esModule", { value: true }); exports2.ToolRunner = void 0; exports2.argStringToArray = argStringToArray; - var os = __importStar2(require("os")); + var os2 = __importStar2(require("os")); var events = __importStar2(require("events")); var child = __importStar2(require("child_process")); - var path2 = __importStar2(require("path")); + var path3 = __importStar2(require("path")); var io = __importStar2(require_io()); var ioUtil = __importStar2(require_io_util()); var timers_1 = require("timers"); @@ -20727,12 +20727,12 @@ var require_toolrunner = __commonJS({ _processLineBuffer(data2, strBuffer, onLine) { try { let s5 = strBuffer + data2.toString(); - let n5 = s5.indexOf(os.EOL); + let n5 = s5.indexOf(os2.EOL); while (n5 > -1) { const line = s5.substring(0, n5); onLine(line); - s5 = s5.substring(n5 + os.EOL.length); - n5 = s5.indexOf(os.EOL); + s5 = s5.substring(n5 + os2.EOL.length); + n5 = s5.indexOf(os2.EOL); } return s5; } catch (err) { @@ -20890,7 +20890,7 @@ var require_toolrunner = __commonJS({ exec() { return __awaiter2(this, void 0, void 0, function* () { if (!ioUtil.isRooted(this.toolPath) && (this.toolPath.includes("/") || IS_WINDOWS && this.toolPath.includes("\\"))) { - this.toolPath = path2.resolve(process.cwd(), this.options.cwd || process.cwd(), this.toolPath); + this.toolPath = path3.resolve(process.cwd(), this.options.cwd || process.cwd(), this.toolPath); } this.toolPath = yield io.which(this.toolPath, true); return new Promise((resolve, reject) => __awaiter2(this, void 0, void 0, function* () { @@ -20901,7 +20901,7 @@ var require_toolrunner = __commonJS({ } const optionsNonNull = this._cloneExecOptions(this.options); if (!optionsNonNull.silent && optionsNonNull.outStream) { - optionsNonNull.outStream.write(this._getCommandString(optionsNonNull) + os.EOL); + optionsNonNull.outStream.write(this._getCommandString(optionsNonNull) + os2.EOL); } const state2 = new ExecState(optionsNonNull, this.toolPath); state2.on("debug", (message) => { @@ -21418,7 +21418,7 @@ var require_core = __commonJS({ }; Object.defineProperty(exports2, "__esModule", { value: true }); exports2.platform = exports2.toPlatformPath = exports2.toWin32Path = exports2.toPosixPath = exports2.markdownSummary = exports2.summary = exports2.ExitCode = void 0; - exports2.exportVariable = exportVariable2; + exports2.exportVariable = exportVariable3; exports2.setSecret = setSecret2; exports2.addPath = addPath; exports2.getInput = getInput3; @@ -21428,11 +21428,11 @@ var require_core = __commonJS({ exports2.setCommandEcho = setCommandEcho; exports2.setFailed = setFailed2; exports2.isDebug = isDebug; - exports2.debug = debug3; + exports2.debug = debug4; exports2.error = error2; exports2.warning = warning; exports2.notice = notice2; - exports2.info = info4; + exports2.info = info5; exports2.startGroup = startGroup; exports2.endGroup = endGroup; exports2.group = group; @@ -21442,15 +21442,15 @@ var require_core = __commonJS({ var command_1 = require_command(); var file_command_1 = require_file_command(); var utils_1 = require_utils(); - var os = __importStar2(require("os")); - var path2 = __importStar2(require("path")); + var os2 = __importStar2(require("os")); + var path3 = __importStar2(require("path")); var oidc_utils_1 = require_oidc_utils(); var ExitCode; (function(ExitCode2) { ExitCode2[ExitCode2["Success"] = 0] = "Success"; ExitCode2[ExitCode2["Failure"] = 1] = "Failure"; })(ExitCode || (exports2.ExitCode = ExitCode = {})); - function exportVariable2(name, val) { + function exportVariable3(name, val) { const convertedVal = (0, utils_1.toCommandValue)(val); process.env[name] = convertedVal; const filePath = process.env["GITHUB_ENV"] || ""; @@ -21469,7 +21469,7 @@ var require_core = __commonJS({ } else { (0, command_1.issueCommand)("add-path", {}, inputPath); } - process.env["PATH"] = `${inputPath}${path2.delimiter}${process.env["PATH"]}`; + process.env["PATH"] = `${inputPath}${path3.delimiter}${process.env["PATH"]}`; } function getInput3(name, options) { const val = process.env[`INPUT_${name.replace(/ /g, "_").toUpperCase()}`] || ""; @@ -21504,7 +21504,7 @@ Support boolean input list: \`true | True | TRUE | false | False | FALSE\``); if (filePath) { return (0, file_command_1.issueFileCommand)("OUTPUT", (0, file_command_1.prepareKeyValueMessage)(name, value)); } - process.stdout.write(os.EOL); + process.stdout.write(os2.EOL); (0, command_1.issueCommand)("set-output", { name }, (0, utils_1.toCommandValue)(value)); } function setCommandEcho(enabled) { @@ -21517,7 +21517,7 @@ Support boolean input list: \`true | True | TRUE | false | False | FALSE\``); function isDebug() { return process.env["RUNNER_DEBUG"] === "1"; } - function debug3(message) { + function debug4(message) { (0, command_1.issueCommand)("debug", {}, message); } function error2(message, properties = {}) { @@ -21529,8 +21529,8 @@ Support boolean input list: \`true | True | TRUE | false | False | FALSE\``); function notice2(message, properties = {}) { (0, command_1.issueCommand)("notice", (0, utils_1.toCommandProperties)(properties), message instanceof Error ? message.toString() : message); } - function info4(message) { - process.stdout.write(message + os.EOL); + function info5(message) { + process.stdout.write(message + os2.EOL); } function startGroup(name) { (0, command_1.issue)("group", name); @@ -22378,9 +22378,9 @@ var init_createPaginator = __esm({ command = withCommand(command) ?? command; return await client.send(command, ...args); }; - get = (fromObject, path2) => { + get = (fromObject, path3) => { let cursor2 = fromObject; - const pathComponents = path2.split("."); + const pathComponents = path3.split("."); for (const step of pathComponents) { if (!cursor2 || typeof cursor2 !== "object") { return void 0; @@ -23398,12 +23398,12 @@ or increase socketAcquisitionWarningTimeout=(millis) in the NodeHttpHandler conf const password = request.password ?? ""; auth = `${username}:${password}`; } - let path2 = request.path; + let path3 = request.path; if (queryString) { - path2 += `?${queryString}`; + path3 += `?${queryString}`; } if (request.fragment) { - path2 += `#${request.fragment}`; + path3 += `#${request.fragment}`; } let hostname = request.hostname ?? ""; if (hostname[0] === "[" && hostname.endsWith("]")) { @@ -23415,7 +23415,7 @@ or increase socketAcquisitionWarningTimeout=(millis) in the NodeHttpHandler conf headers: request.headers, host: hostname, method: request.method, - path: path2, + path: path3, port: request.port, agent, auth @@ -23698,16 +23698,16 @@ or increase socketAcquisitionWarningTimeout=(millis) in the NodeHttpHandler conf reject(err); }; const queryString = querystringBuilder.buildQueryString(query || {}); - let path2 = request.path; + let path3 = request.path; if (queryString) { - path2 += `?${queryString}`; + path3 += `?${queryString}`; } if (request.fragment) { - path2 += `#${request.fragment}`; + path3 += `#${request.fragment}`; } const req = session.request({ ...request.headers, - [http2.constants.HTTP2_HEADER_PATH]: path2, + [http2.constants.HTTP2_HEADER_PATH]: path3, [http2.constants.HTTP2_HEADER_METHOD]: method }); session.ref(); @@ -23894,13 +23894,13 @@ var require_dist_cjs14 = __commonJS({ const abortError = buildAbortError(abortSignal); return Promise.reject(abortError); } - let path2 = request.path; + let path3 = request.path; const queryString = querystringBuilder.buildQueryString(request.query || {}); if (queryString) { - path2 += `?${queryString}`; + path3 += `?${queryString}`; } if (request.fragment) { - path2 += `#${request.fragment}`; + path3 += `#${request.fragment}`; } let auth = ""; if (request.username != null || request.password != null) { @@ -23909,7 +23909,7 @@ var require_dist_cjs14 = __commonJS({ auth = `${username}:${password}@`; } const { port, method } = request; - const url = `${request.protocol}//${auth}${request.hostname}${port ? `:${port}` : ""}${path2}`; + const url = `${request.protocol}//${auth}${request.hostname}${port ? `:${port}` : ""}${path3}`; const body = method === "GET" || method === "HEAD" ? void 0 : request.body; const requestOptions = { body, @@ -26141,13 +26141,13 @@ function __disposeResources(env) { } return next(); } -function __rewriteRelativeImportExtension(path2, preserveJsx) { - if (typeof path2 === "string" && /^\.\.?\//.test(path2)) { - return path2.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function(m5, tsx, d5, ext, cm) { +function __rewriteRelativeImportExtension(path3, preserveJsx) { + if (typeof path3 === "string" && /^\.\.?\//.test(path3)) { + return path3.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function(m5, tsx, d5, ext, cm) { return tsx ? preserveJsx ? ".jsx" : ".js" : d5 && (!ext || !cm) ? m5 : d5 + ext + "." + cm.toLowerCase() + "js"; }); } - return path2; + return path3; } var extendStatics, __assign, __createBinding, __setModuleDefault, ownKeys, _SuppressedError, tslib_es6_default; var init_tslib_es6 = __esm({ @@ -27043,11 +27043,11 @@ var init_HttpBindingProtocol = __esm({ const opTraits = translateTraits(operationSchema.traits); if (opTraits.http) { request.method = opTraits.http[0]; - const [path2, search] = opTraits.http[1].split("?"); + const [path3, search] = opTraits.http[1].split("?"); if (request.path == "/") { - request.path = path2; + request.path = path3; } else { - request.path += path2; + request.path += path3; } const traitSearchParams = new URLSearchParams(search ?? ""); Object.assign(query, Object.fromEntries(traitSearchParams)); @@ -27443,8 +27443,8 @@ var init_requestBuilder = __esm({ return this; } p(memberName, labelValueProvider, uriLabel, isGreedyLabel) { - this.resolvePathStack.push((path2) => { - this.path = resolvedPath(path2, this.input, memberName, labelValueProvider, uriLabel, isGreedyLabel); + this.resolvePathStack.push((path3) => { + this.path = resolvedPath(path3, this.input, memberName, labelValueProvider, uriLabel, isGreedyLabel); }); return this; } @@ -28094,18 +28094,18 @@ var require_dist_cjs20 = __commonJS({ } }; var booleanEquals = (value1, value2) => value1 === value2; - var getAttrPathList = (path2) => { - const parts = path2.split("."); + var getAttrPathList = (path3) => { + const parts = path3.split("."); const pathList = []; for (const part of parts) { const squareBracketIndex = part.indexOf("["); if (squareBracketIndex !== -1) { if (part.indexOf("]") !== part.length - 1) { - throw new EndpointError(`Path: '${path2}' does not end with ']'`); + throw new EndpointError(`Path: '${path3}' does not end with ']'`); } const arrayIndex = part.slice(squareBracketIndex + 1, -1); if (Number.isNaN(parseInt(arrayIndex))) { - throw new EndpointError(`Invalid array index: '${arrayIndex}' in path: '${path2}'`); + throw new EndpointError(`Invalid array index: '${arrayIndex}' in path: '${path3}'`); } if (squareBracketIndex !== 0) { pathList.push(part.slice(0, squareBracketIndex)); @@ -28117,9 +28117,9 @@ var require_dist_cjs20 = __commonJS({ } return pathList; }; - var getAttr = (value, path2) => getAttrPathList(path2).reduce((acc, index) => { + var getAttr = (value, path3) => getAttrPathList(path3).reduce((acc, index) => { if (typeof acc !== "object") { - throw new EndpointError(`Index '${index}' in '${path2}' not found in '${JSON.stringify(value)}'`); + throw new EndpointError(`Index '${index}' in '${path3}' not found in '${JSON.stringify(value)}'`); } else if (Array.isArray(acc)) { return acc[parseInt(index)]; } @@ -28138,8 +28138,8 @@ var require_dist_cjs20 = __commonJS({ return value; } if (typeof value === "object" && "hostname" in value) { - const { hostname: hostname2, port, protocol: protocol2 = "", path: path2 = "", query = {} } = value; - const url = new URL(`${protocol2}//${hostname2}${port ? `:${port}` : ""}${path2}`); + const { hostname: hostname2, port, protocol: protocol2 = "", path: path3 = "", query = {} } = value; + const url = new URL(`${protocol2}//${hostname2}${port ? `:${port}` : ""}${path3}`); url.search = Object.entries(query).map(([k5, v5]) => `${k5}=${v5}`).join("&"); return url; } @@ -32300,10 +32300,10 @@ ${longDate} ${credentialScope} ${utilHexEncoding.toHex(hashedRequest)}`; } - getCanonicalPath({ path: path2 }) { + getCanonicalPath({ path: path3 }) { if (this.uriEscapePath) { const normalizedPathSegments = []; - for (const pathSegment of path2.split("/")) { + for (const pathSegment of path3.split("/")) { if (pathSegment?.length === 0) continue; if (pathSegment === ".") @@ -32314,11 +32314,11 @@ ${utilHexEncoding.toHex(hashedRequest)}`; normalizedPathSegments.push(pathSegment); } } - const normalizedPath = `${path2?.startsWith("/") ? "/" : ""}${normalizedPathSegments.join("/")}${normalizedPathSegments.length > 0 && path2?.endsWith("/") ? "/" : ""}`; + const normalizedPath = `${path3?.startsWith("/") ? "/" : ""}${normalizedPathSegments.join("/")}${normalizedPathSegments.length > 0 && path3?.endsWith("/") ? "/" : ""}`; const doubleEncoded = utilUriEscape.escapeUri(normalizedPath); return doubleEncoded.replace(/%2F/g, "/"); } - return path2; + return path3; } validateResolvedCredentials(credentials) { if (typeof credentials !== "object" || typeof credentials.accessKeyId !== "string" || typeof credentials.secretAccessKey !== "string") { @@ -34981,9 +34981,9 @@ var init_SmithyRpcV2CborProtocol = __esm({ const { service, operation: operation2 } = (0, import_util_middleware6.getSmithyContext)(context); const path2 = `/service/${service}/operation/${operation2}`; if (request.path.endsWith("/")) { - request.path += path2.slice(1); + request.path += path3.slice(1); } else { - request.path += path2; + request.path += path3; } return request; } @@ -45909,11 +45909,11 @@ var require_common = __commonJS({ let enableOverride = null; let namespacesCache; let enabledCache; - function debug3(...args) { - if (!debug3.enabled) { + function debug4(...args) { + if (!debug4.enabled) { return; } - const self2 = debug3; + const self2 = debug4; const curr = Number(/* @__PURE__ */ new Date()); const ms = curr - (prevTime || curr); self2.diff = ms; @@ -45943,12 +45943,12 @@ var require_common = __commonJS({ const logFn = self2.log || createDebug.log; logFn.apply(self2, args); } - debug3.namespace = namespace; - debug3.useColors = createDebug.useColors(); - debug3.color = createDebug.selectColor(namespace); - debug3.extend = extend; - debug3.destroy = createDebug.destroy; - Object.defineProperty(debug3, "enabled", { + debug4.namespace = namespace; + debug4.useColors = createDebug.useColors(); + debug4.color = createDebug.selectColor(namespace); + debug4.extend = extend; + debug4.destroy = createDebug.destroy; + Object.defineProperty(debug4, "enabled", { enumerable: true, configurable: false, get: () => { @@ -45966,9 +45966,9 @@ var require_common = __commonJS({ } }); if (typeof createDebug.init === "function") { - createDebug.init(debug3); + createDebug.init(debug4); } - return debug3; + return debug4; } function extend(namespace, delimiter) { const newDebug = createDebug(this.namespace + (typeof delimiter === "undefined" ? ":" : delimiter) + namespace); @@ -46241,7 +46241,7 @@ var require_has_flag = __commonJS({ var require_supports_color = __commonJS({ "node_modules/supports-color/index.js"(exports2, module2) { "use strict"; - var os = require("os"); + var os2 = require("os"); var tty = require("tty"); var hasFlag = require_has_flag(); var { env } = process; @@ -46289,7 +46289,7 @@ var require_supports_color = __commonJS({ return min; } if (process.platform === "win32") { - const osRelease = os.release().split("."); + const osRelease = os2.release().split("."); if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) { return Number(osRelease[2]) >= 14931 ? 3 : 2; } @@ -46493,11 +46493,11 @@ var require_node = __commonJS({ function load() { return process.env.DEBUG; } - function init(debug3) { - debug3.inspectOpts = {}; + function init(debug4) { + debug4.inspectOpts = {}; const keys = Object.keys(exports2.inspectOpts); for (let i5 = 0; i5 < keys.length; i5++) { - debug3.inspectOpts[keys[i5]] = exports2.inspectOpts[keys[i5]]; + debug4.inspectOpts[keys[i5]] = exports2.inspectOpts[keys[i5]]; } } module2.exports = require_common()(exports2); @@ -46636,13 +46636,13 @@ var require_dist2 = __commonJS({ var events_1 = require("events"); var agent_base_1 = require_dist(); var url_1 = require("url"); - var debug3 = (0, debug_1.default)("http-proxy-agent"); + var debug4 = (0, debug_1.default)("http-proxy-agent"); var HttpProxyAgent = class extends agent_base_1.Agent { constructor(proxy, opts) { super(opts); this.proxy = typeof proxy === "string" ? new url_1.URL(proxy) : proxy; this.proxyHeaders = opts?.headers ?? {}; - debug3("Creating new HttpProxyAgent instance: %o", this.proxy.href); + debug4("Creating new HttpProxyAgent instance: %o", this.proxy.href); const host = (this.proxy.hostname || this.proxy.host).replace(/^\[|\]$/g, ""); const port = this.proxy.port ? parseInt(this.proxy.port, 10) : this.proxy.protocol === "https:" ? 443 : 80; this.connectOpts = { @@ -46688,21 +46688,21 @@ var require_dist2 = __commonJS({ } let first; let endOfHeaders; - debug3("Regenerating stored HTTP header string for request"); + debug4("Regenerating stored HTTP header string for request"); req._implicitHeader(); if (req.outputData && req.outputData.length > 0) { - debug3("Patching connection write() output buffer with updated header"); + debug4("Patching connection write() output buffer with updated header"); first = req.outputData[0].data; endOfHeaders = first.indexOf("\r\n\r\n") + 4; req.outputData[0].data = req._header + first.substring(endOfHeaders); - debug3("Output buffer: %o", req.outputData[0].data); + debug4("Output buffer: %o", req.outputData[0].data); } let socket; if (this.proxy.protocol === "https:") { - debug3("Creating `tls.Socket`: %o", this.connectOpts); + debug4("Creating `tls.Socket`: %o", this.connectOpts); socket = tls.connect(this.connectOpts); } else { - debug3("Creating `net.Socket`: %o", this.connectOpts); + debug4("Creating `net.Socket`: %o", this.connectOpts); socket = net.connect(this.connectOpts); } await (0, events_1.once)(socket, "connect"); @@ -46734,7 +46734,7 @@ var require_parse_proxy_response = __commonJS({ Object.defineProperty(exports2, "__esModule", { value: true }); exports2.parseProxyResponse = void 0; var debug_1 = __importDefault2(require_src()); - var debug3 = (0, debug_1.default)("https-proxy-agent:parse-proxy-response"); + var debug4 = (0, debug_1.default)("https-proxy-agent:parse-proxy-response"); function parseProxyResponse(socket) { return new Promise((resolve, reject) => { let buffersLength = 0; @@ -46753,12 +46753,12 @@ var require_parse_proxy_response = __commonJS({ } function onend() { cleanup(); - debug3("onend"); + debug4("onend"); reject(new Error("Proxy connection ended before receiving CONNECT response")); } function onerror(err) { cleanup(); - debug3("onerror %o", err); + debug4("onerror %o", err); reject(err); } function ondata(b5) { @@ -46767,7 +46767,7 @@ var require_parse_proxy_response = __commonJS({ const buffered = Buffer.concat(buffers, buffersLength); const endOfHeaders = buffered.indexOf("\r\n\r\n"); if (endOfHeaders === -1) { - debug3("have not received end of HTTP headers yet..."); + debug4("have not received end of HTTP headers yet..."); read(); return; } @@ -46800,7 +46800,7 @@ var require_parse_proxy_response = __commonJS({ headers[key] = value; } } - debug3("got proxy server response: %o %o", firstLine, headers); + debug4("got proxy server response: %o %o", firstLine, headers); cleanup(); resolve({ connect: { @@ -46863,7 +46863,7 @@ var require_dist3 = __commonJS({ var agent_base_1 = require_dist(); var url_1 = require("url"); var parse_proxy_response_1 = require_parse_proxy_response(); - var debug3 = (0, debug_1.default)("https-proxy-agent"); + var debug4 = (0, debug_1.default)("https-proxy-agent"); var setServernameFromNonIpHost = (options) => { if (options.servername === void 0 && options.host && !net.isIP(options.host)) { return { @@ -46879,7 +46879,7 @@ var require_dist3 = __commonJS({ this.options = { path: void 0 }; this.proxy = typeof proxy === "string" ? new url_1.URL(proxy) : proxy; this.proxyHeaders = opts?.headers ?? {}; - debug3("Creating new HttpsProxyAgent instance: %o", this.proxy.href); + debug4("Creating new HttpsProxyAgent instance: %o", this.proxy.href); const host = (this.proxy.hostname || this.proxy.host).replace(/^\[|\]$/g, ""); const port = this.proxy.port ? parseInt(this.proxy.port, 10) : this.proxy.protocol === "https:" ? 443 : 80; this.connectOpts = { @@ -46901,10 +46901,10 @@ var require_dist3 = __commonJS({ } let socket; if (proxy.protocol === "https:") { - debug3("Creating `tls.Socket`: %o", this.connectOpts); + debug4("Creating `tls.Socket`: %o", this.connectOpts); socket = tls.connect(setServernameFromNonIpHost(this.connectOpts)); } else { - debug3("Creating `net.Socket`: %o", this.connectOpts); + debug4("Creating `net.Socket`: %o", this.connectOpts); socket = net.connect(this.connectOpts); } const headers = typeof this.proxyHeaders === "function" ? this.proxyHeaders() : { ...this.proxyHeaders }; @@ -46932,7 +46932,7 @@ var require_dist3 = __commonJS({ if (connect.statusCode === 200) { req.once("socket", resume); if (opts.secureEndpoint) { - debug3("Upgrading socket connection to TLS"); + debug4("Upgrading socket connection to TLS"); return tls.connect({ ...omit(setServernameFromNonIpHost(opts), "host", "path", "port"), socket @@ -46944,7 +46944,7 @@ var require_dist3 = __commonJS({ const fakeSocket = new net.Socket({ writable: false }); fakeSocket.readable = true; req.once("socket", (s5) => { - debug3("Replaying proxy buffer for failed request"); + debug4("Replaying proxy buffer for failed request"); (0, assert_1.default)(s5.listenerCount("data") > 0); s5.push(buffered); s5.push(null); @@ -50095,13 +50095,13 @@ var require_socksclient = __commonJS({ } const client = new _SocksClient(options); client.connect(options.existing_socket); - client.once("established", (info4) => { + client.once("established", (info5) => { client.removeAllListeners(); if (typeof callback === "function") { - callback(null, info4); - resolve(info4); + callback(null, info5); + resolve(info5); } else { - resolve(info4); + resolve(info5); } }); client.once("error", (err) => { @@ -50261,13 +50261,13 @@ var require_socksclient = __commonJS({ this.socket.setNoDelay(!!this.options.set_tcp_nodelay); } } - this.prependOnceListener("established", (info4) => { + this.prependOnceListener("established", (info5) => { setImmediate(() => { if (this.receiveBuffer.length > 0) { const excessData = this.receiveBuffer.get(this.receiveBuffer.length); - info4.socket.emit("data", excessData); + info5.socket.emit("data", excessData); } - info4.socket.resume(); + info5.socket.resume(); }); }); } @@ -50769,7 +50769,7 @@ var require_dist4 = __commonJS({ var net = __importStar2(require("net")); var tls = __importStar2(require("tls")); var url_1 = require("url"); - var debug3 = (0, debug_1.default)("socks-proxy-agent"); + var debug4 = (0, debug_1.default)("socks-proxy-agent"); var setServernameFromNonIpHost = (options) => { if (options.servername === void 0 && options.host && !net.isIP(options.host)) { return { @@ -50876,21 +50876,21 @@ var require_dist4 = __commonJS({ if (tlsSocket) tlsSocket.destroy(); }; - debug3("Creating socks proxy connection: %o", socksOpts); + debug4("Creating socks proxy connection: %o", socksOpts); const { socket } = await socks_1.SocksClient.createConnection(socksOpts); - debug3("Successfully created socks proxy connection"); + debug4("Successfully created socks proxy connection"); if (timeout !== null) { socket.setTimeout(timeout); socket.on("timeout", () => cleanup()); } if (opts.secureEndpoint) { - debug3("Upgrading socket connection to TLS"); + debug4("Upgrading socket connection to TLS"); const tlsSocket = tls.connect({ ...omit(setServernameFromNonIpHost(opts), "host", "path", "port"), socket }); tlsSocket.once("error", (error2) => { - debug3("Socket TLS error", error2.message); + debug4("Socket TLS error", error2.message); cleanup(tlsSocket); }); return tlsSocket; @@ -51022,7 +51022,7 @@ var require_data = __commonJS({ var crypto_1 = require("crypto"); var data_uri_to_buffer_1 = require_node2(); var notmodified_1 = __importDefault2(require_notmodified()); - var debug3 = (0, debug_1.default)("get-uri:data"); + var debug4 = (0, debug_1.default)("get-uri:data"); var DataReadable = class extends stream_1.Readable { constructor(hash, buf) { super(); @@ -51035,12 +51035,12 @@ var require_data = __commonJS({ const shasum = (0, crypto_1.createHash)("sha1"); shasum.update(uri); const hash = shasum.digest("hex"); - debug3('generated SHA1 hash for "data:" URI: %o', hash); + debug4('generated SHA1 hash for "data:" URI: %o', hash); if (cache5?.hash === hash) { - debug3("got matching cache SHA1 hash: %o", hash); + debug4("got matching cache SHA1 hash: %o", hash); throw new notmodified_1.default(); } else { - debug3('creating Readable stream from "data:" URI buffer'); + debug4('creating Readable stream from "data:" URI buffer'); const { buffer } = (0, data_uri_to_buffer_1.dataUriToBuffer)(uri); return new DataReadable(hash, Buffer.from(buffer)); } @@ -51078,7 +51078,7 @@ var require_file2 = __commonJS({ var notfound_1 = __importDefault2(require_notfound()); var notmodified_1 = __importDefault2(require_notmodified()); var url_1 = require("url"); - var debug3 = (0, debug_1.default)("get-uri:file"); + var debug4 = (0, debug_1.default)("get-uri:file"); var file = async ({ href: uri }, opts = {}) => { const { cache: cache5, @@ -51088,7 +51088,7 @@ var require_file2 = __commonJS({ } = opts; try { const filepath = (0, url_1.fileURLToPath)(uri); - debug3("Normalized pathname: %o", filepath); + debug4("Normalized pathname: %o", filepath); const fdHandle = await fs_1.promises.open(filepath, flags, mode); const fd = fdHandle.fd; const stat = await fdHandle.stat(); @@ -51682,36 +51682,36 @@ var require_parseListMLSD = __commonJS({ exports2.transformList = transformList; exports2.parseMLSxDate = parseMLSxDate; var FileInfo_1 = require_FileInfo(); - function parseSize(value, info4) { - info4.size = parseInt(value, 10); + function parseSize(value, info5) { + info5.size = parseInt(value, 10); } var factHandlersByName = { "size": parseSize, // File size "sizd": parseSize, // Directory size - "unique": (value, info4) => { - info4.uniqueID = value; + "unique": (value, info5) => { + info5.uniqueID = value; }, - "modify": (value, info4) => { - info4.modifiedAt = parseMLSxDate(value); - info4.rawModifiedAt = info4.modifiedAt.toISOString(); + "modify": (value, info5) => { + info5.modifiedAt = parseMLSxDate(value); + info5.rawModifiedAt = info5.modifiedAt.toISOString(); }, - "type": (value, info4) => { + "type": (value, info5) => { if (value.startsWith("OS.unix=slink")) { - info4.type = FileInfo_1.FileType.SymbolicLink; - info4.link = value.substr(value.indexOf(":") + 1); + info5.type = FileInfo_1.FileType.SymbolicLink; + info5.link = value.substr(value.indexOf(":") + 1); return 1; } switch (value) { case "file": - info4.type = FileInfo_1.FileType.File; + info5.type = FileInfo_1.FileType.File; break; case "dir": - info4.type = FileInfo_1.FileType.Directory; + info5.type = FileInfo_1.FileType.Directory; break; case "OS.unix=symlink": - info4.type = FileInfo_1.FileType.SymbolicLink; + info5.type = FileInfo_1.FileType.SymbolicLink; break; case "cdir": // Current directory being listed @@ -51719,34 +51719,34 @@ var require_parseListMLSD = __commonJS({ return 2; // Don't include these entries in the listing default: - info4.type = FileInfo_1.FileType.Unknown; + info5.type = FileInfo_1.FileType.Unknown; } return 1; }, - "unix.mode": (value, info4) => { + "unix.mode": (value, info5) => { const digits = value.substr(-3); - info4.permissions = { + info5.permissions = { user: parseInt(digits[0], 10), group: parseInt(digits[1], 10), world: parseInt(digits[2], 10) }; }, - "unix.ownername": (value, info4) => { - info4.user = value; + "unix.ownername": (value, info5) => { + info5.user = value; }, - "unix.owner": (value, info4) => { - if (info4.user === void 0) - info4.user = value; + "unix.owner": (value, info5) => { + if (info5.user === void 0) + info5.user = value; }, get "unix.uid"() { return this["unix.owner"]; }, - "unix.groupname": (value, info4) => { - info4.group = value; + "unix.groupname": (value, info5) => { + info5.group = value; }, - "unix.group": (value, info4) => { - if (info4.group === void 0) - info4.group = value; + "unix.group": (value, info5) => { + if (info5.group === void 0) + info5.group = value; }, get "unix.gid"() { return this["unix.group"]; @@ -51776,7 +51776,7 @@ var require_parseListMLSD = __commonJS({ if (name === "" || name === "." || name === "..") { return void 0; } - const info4 = new FileInfo_1.FileInfo(name); + const info5 = new FileInfo_1.FileInfo(name); const facts = packedFacts.split(";"); for (const fact of facts) { const [factName, factValue] = splitStringOnce(fact, "="); @@ -51787,12 +51787,12 @@ var require_parseListMLSD = __commonJS({ if (!factHandler) { continue; } - const result = factHandler(factValue, info4); + const result = factHandler(factValue, info5); if (result === 2) { return void 0; } } - return info4; + return info5; } function transformList(files) { const nonLinksByID = /* @__PURE__ */ new Map(); @@ -51909,7 +51909,7 @@ var require_parseList = __commonJS({ if (!parser) { throw new Error("This library only supports MLSD, Unix- or DOS-style directory listing. Your FTP server seems to be using another format. You can see the transmitted listing when setting `client.ftp.verbose = true`. You can then provide a custom parser to `client.parseList`, see the documentation for details."); } - const files = lines.map(parser.parseLine).filter((info4) => info4 !== void 0); + const files = lines.map(parser.parseLine).filter((info5) => info5 !== void 0); return parser.transformList(files); } } @@ -52565,8 +52565,8 @@ var require_Client = __commonJS({ /** * Set the working directory. */ - async cd(path2) { - const validPath = await this.protectWhitespace(path2); + async cd(path3) { + const validPath = await this.protectWhitespace(path3); return this.send("CWD " + validPath); } /** @@ -52579,8 +52579,8 @@ var require_Client = __commonJS({ * Get the last modified time of a file. This is not supported by every FTP server, in which case * calling this method will throw an exception. */ - async lastMod(path2) { - const validPath = await this.protectWhitespace(path2); + async lastMod(path3) { + const validPath = await this.protectWhitespace(path3); const res = await this.send(`MDTM ${validPath}`); const date2 = res.message.slice(4); return (0, parseListMLSD_1.parseMLSxDate)(date2); @@ -52588,8 +52588,8 @@ var require_Client = __commonJS({ /** * Get the size of a file. */ - async size(path2) { - const validPath = await this.protectWhitespace(path2); + async size(path3) { + const validPath = await this.protectWhitespace(path3); const command = `SIZE ${validPath}`; const res = await this.send(command); const size = parseInt(res.message.slice(4), 10); @@ -52616,8 +52616,8 @@ var require_Client = __commonJS({ * You can ignore FTP error return codes which won't throw an exception if e.g. * the file doesn't exist. */ - async remove(path2, ignoreErrorCodes = false) { - const validPath = await this.protectWhitespace(path2); + async remove(path3, ignoreErrorCodes = false) { + const validPath = await this.protectWhitespace(path3); if (ignoreErrorCodes) { return this.sendIgnoringError(`DELE ${validPath}`); } @@ -52771,8 +52771,8 @@ var require_Client = __commonJS({ * * @param [path] Path to remote file or directory. */ - async list(path2 = "") { - const validPath = await this.protectWhitespace(path2); + async list(path3 = "") { + const validPath = await this.protectWhitespace(path3); let lastError; for (const candidate of this.availableListCommands) { const command = validPath === "" ? candidate : `${candidate} ${validPath}`; @@ -52942,21 +52942,21 @@ var require_Client = __commonJS({ /** * Remove an empty directory, will fail if not empty. */ - async removeEmptyDir(path2) { - const validPath = await this.protectWhitespace(path2); + async removeEmptyDir(path3) { + const validPath = await this.protectWhitespace(path3); return this.send(`RMD ${validPath}`); } /** * FTP servers can't handle filenames that have leading whitespace. This method transforms * a given path to fix that issue for most cases. */ - async protectWhitespace(path2) { - if (!path2.startsWith(" ")) { - return path2; + async protectWhitespace(path3) { + if (!path3.startsWith(" ")) { + return path3; } const pwd = await this.pwd(); const absolutePathPrefix = pwd.endsWith("/") ? pwd : pwd + "/"; - return absolutePathPrefix + path2; + return absolutePathPrefix + path3; } async _exitAtCurrentDirectory(func) { const userDir = await this.pwd(); @@ -53033,11 +53033,11 @@ var require_Client = __commonJS({ } }; exports2.Client = Client; - async function ensureLocalDirectory(path2) { + async function ensureLocalDirectory(path3) { try { - await fsStat(path2); + await fsStat(path3); } catch (_a2) { - await fsMkDir(path2, { recursive: true }); + await fsMkDir(path3, { recursive: true }); } } async function ignoreError(func) { @@ -53110,7 +53110,7 @@ var require_ftp = __commonJS({ var debug_1 = __importDefault2(require_src()); var notfound_1 = __importDefault2(require_notfound()); var notmodified_1 = __importDefault2(require_notmodified()); - var debug3 = (0, debug_1.default)("get-uri:ftp"); + var debug4 = (0, debug_1.default)("get-uri:ftp"); var ftp = async (url, opts = {}) => { const { cache: cache5 } = opts; const filepath = decodeURIComponent(url.pathname); @@ -53156,7 +53156,7 @@ var require_ftp = __commonJS({ const stream = new stream_1.PassThrough(); const rs = stream; client.downloadTo(stream, filepath).then((result) => { - debug3(result.message); + debug4(result.message); client.close(); }); rs.lastModified = lastModified; @@ -53209,27 +53209,27 @@ var require_http = __commonJS({ var http_error_1 = __importDefault2(require_http_error()); var notfound_1 = __importDefault2(require_notfound()); var notmodified_1 = __importDefault2(require_notmodified()); - var debug3 = (0, debug_1.default)("get-uri:http"); + var debug4 = (0, debug_1.default)("get-uri:http"); var http = async (url, opts = {}) => { - debug3("GET %o", url.href); + debug4("GET %o", url.href); const cache5 = getCache(url, opts.cache); if (cache5 && isFresh(cache5) && typeof cache5.statusCode === "number") { const type2 = cache5.statusCode / 100 | 0; if (type2 === 3 && cache5.headers.location) { - debug3("cached redirect"); + debug4("cached redirect"); throw new Error("TODO: implement cached redirects!"); } throw new notmodified_1.default(); } const maxRedirects = typeof opts.maxRedirects === "number" ? opts.maxRedirects : 5; - debug3("allowing %o max redirects", maxRedirects); + debug4("allowing %o max redirects", maxRedirects); let mod; if (opts.http) { mod = opts.http; - debug3("using secure `https` core module"); + debug4("using secure `https` core module"); } else { mod = http_1.default; - debug3("using `http` core module"); + debug4("using `http` core module"); } const options = { ...opts }; if (cache5) { @@ -53239,12 +53239,12 @@ var require_http = __commonJS({ const lastModified = cache5.headers["last-modified"]; if (lastModified) { options.headers["If-Modified-Since"] = lastModified; - debug3('added "If-Modified-Since" request header: %o', lastModified); + debug4('added "If-Modified-Since" request header: %o', lastModified); } const etag = cache5.headers.etag; if (etag) { options.headers["If-None-Match"] = etag; - debug3('added "If-None-Match" request header: %o', etag); + debug4('added "If-None-Match" request header: %o', etag); } } const req = mod.get(url, options); @@ -53252,7 +53252,7 @@ var require_http = __commonJS({ const code = res.statusCode || 0; res.date = Date.now(); res.parsed = url; - debug3("got %o response status code", code); + debug4("got %o response status code", code); const type = code / 100 | 0; const location = res.headers.location; if (type === 3 && location) { @@ -53260,13 +53260,13 @@ var require_http = __commonJS({ opts.redirects = []; const redirects = opts.redirects; if (redirects.length < maxRedirects) { - debug3('got a "redirect" status code with Location: %o', location); + debug4('got a "redirect" status code with Location: %o', location); res.resume(); redirects.push(res); const newUri = new URL(location, url.href); - debug3("resolved redirect URL: %o", newUri.href); + debug4("resolved redirect URL: %o", newUri.href); const left = maxRedirects - redirects.length; - debug3("%o more redirects allowed after this one", left); + debug4("%o more redirects allowed after this one", left); if (newUri.protocol !== url.protocol) { opts.http = newUri.protocol === "https:" ? https_1.default : void 0; } @@ -53293,7 +53293,7 @@ var require_http = __commonJS({ let expires = parseInt(cache5.headers.expires || "", 10); const cacheControl = cache5.headers["cache-control"]; if (cacheControl) { - debug3("Cache-Control: %o", cacheControl); + debug4("Cache-Control: %o", cacheControl); const parts = cacheControl.split(/,\s*?\b/); for (let i5 = 0; i5 < parts.length; i5++) { const part = parts[i5]; @@ -53304,24 +53304,24 @@ var require_http = __commonJS({ expires = (cache5.date || 0) + parseInt(subparts[1], 10) * 1e3; fresh = Date.now() < expires; if (fresh) { - debug3('cache is "fresh" due to previous %o Cache-Control param', part); + debug4('cache is "fresh" due to previous %o Cache-Control param', part); } return fresh; case "must-revalidate": break; case "no-cache": case "no-store": - debug3('cache is "stale" due to explicit %o Cache-Control param', name); + debug4('cache is "stale" due to explicit %o Cache-Control param', name); return false; default: break; } } } else if (expires) { - debug3("Expires: %o", expires); + debug4("Expires: %o", expires); fresh = Date.now() < expires; if (fresh) { - debug3('cache is "fresh" due to previous Expires response header'); + debug4('cache is "fresh" due to previous Expires response header'); } return fresh; } @@ -53379,7 +53379,7 @@ var require_dist6 = __commonJS({ var ftp_1 = require_ftp(); var http_1 = require_http(); var https_1 = require_https(); - var debug3 = (0, debug_1.default)("get-uri"); + var debug4 = (0, debug_1.default)("get-uri"); exports2.protocols = { data: data_1.data, file: file_1.file, @@ -53393,7 +53393,7 @@ var require_dist6 = __commonJS({ } exports2.isValidProtocol = isValidProtocol; async function getUri(uri, opts) { - debug3("getUri(%o)", uri); + debug4("getUri(%o)", uri); if (!uri) { throw new TypeError('Must pass in a URI to "getUri()"'); } @@ -53631,23 +53631,23 @@ var require_estraverse = __commonJS({ return false; } }; - function Element(node, path2, wrap, ref) { + function Element(node, path3, wrap, ref) { this.node = node; - this.path = path2; + this.path = path3; this.wrap = wrap; this.ref = ref; } function Controller() { } - Controller.prototype.path = function path2() { + Controller.prototype.path = function path3() { var i5, iz, j5, jz, result, element; - function addToPath(result2, path3) { - if (Array.isArray(path3)) { - for (j5 = 0, jz = path3.length; j5 < jz; ++j5) { - result2.push(path3[j5]); + function addToPath(result2, path4) { + if (Array.isArray(path4)) { + for (j5 = 0, jz = path4.length; j5 < jz; ++j5) { + result2.push(path4[j5]); } } else { - result2.push(path3); + result2.push(path4); } } if (!this.__current.path) { @@ -54532,16 +54532,16 @@ var require_util10 = __commonJS({ } exports2.urlGenerate = urlGenerate; function normalize(aPath) { - var path2 = aPath; + var path3 = aPath; var url = urlParse(aPath); if (url) { if (!url.path) { return aPath; } - path2 = url.path; + path3 = url.path; } - var isAbsolute = exports2.isAbsolute(path2); - var parts = path2.split(/\/+/); + var isAbsolute = exports2.isAbsolute(path3); + var parts = path3.split(/\/+/); for (var part, up = 0, i5 = parts.length - 1; i5 >= 0; i5--) { part = parts[i5]; if (part === ".") { @@ -54558,18 +54558,18 @@ var require_util10 = __commonJS({ } } } - path2 = parts.join("/"); - if (path2 === "") { - path2 = isAbsolute ? "/" : "."; + path3 = parts.join("/"); + if (path3 === "") { + path3 = isAbsolute ? "/" : "."; } if (url) { - url.path = path2; + url.path = path3; return urlGenerate(url); } - return path2; + return path3; } exports2.normalize = normalize; - function join(aRoot, aPath) { + function join2(aRoot, aPath) { if (aRoot === "") { aRoot = "."; } @@ -54601,7 +54601,7 @@ var require_util10 = __commonJS({ } return joined; } - exports2.join = join; + exports2.join = join2; exports2.isAbsolute = function(aPath) { return aPath.charAt(0) === "/" || urlRegexp.test(aPath); }; @@ -54774,7 +54774,7 @@ var require_util10 = __commonJS({ parsed.path = parsed.path.substring(0, index + 1); } } - sourceURL = join(urlGenerate(parsed), sourceURL); + sourceURL = join2(urlGenerate(parsed), sourceURL); } return normalize(sourceURL); } @@ -56576,7 +56576,7 @@ var require_escodegen = __commonJS({ function noEmptySpace() { return space ? space : " "; } - function join(left, right) { + function join2(left, right) { var leftSource, rightSource, leftCharCode, rightCharCode; leftSource = toSourceNodeWhenNeeded(left).toString(); if (leftSource.length === 0) { @@ -56907,8 +56907,8 @@ var require_escodegen = __commonJS({ } else { result.push(that.generateExpression(stmt.left, Precedence.Call, E_TTT)); } - result = join(result, operator); - result = [join( + result = join2(result, operator); + result = [join2( result, that.generateExpression(stmt.right, Precedence.Assignment, E_TTT) ), ")"]; @@ -57051,11 +57051,11 @@ var require_escodegen = __commonJS({ var result, fragment; result = ["class"]; if (stmt.id) { - result = join(result, this.generateExpression(stmt.id, Precedence.Sequence, E_TTT)); + result = join2(result, this.generateExpression(stmt.id, Precedence.Sequence, E_TTT)); } if (stmt.superClass) { - fragment = join("extends", this.generateExpression(stmt.superClass, Precedence.Unary, E_TTT)); - result = join(result, fragment); + fragment = join2("extends", this.generateExpression(stmt.superClass, Precedence.Unary, E_TTT)); + result = join2(result, fragment); } result.push(space); result.push(this.generateStatement(stmt.body, S_TFFT)); @@ -57068,9 +57068,9 @@ var require_escodegen = __commonJS({ return escapeDirective(stmt.directive) + this.semicolon(flags); }, DoWhileStatement: function(stmt, flags) { - var result = join("do", this.maybeBlock(stmt.body, S_TFFF)); + var result = join2("do", this.maybeBlock(stmt.body, S_TFFF)); result = this.maybeBlockSuffix(stmt.body, result); - return join(result, [ + return join2(result, [ "while" + space + "(", this.generateExpression(stmt.test, Precedence.Sequence, E_TTT), ")" + this.semicolon(flags) @@ -57106,11 +57106,11 @@ var require_escodegen = __commonJS({ ExportDefaultDeclaration: function(stmt, flags) { var result = ["export"], bodyFlags; bodyFlags = flags & F_SEMICOLON_OPT ? S_TFFT : S_TFFF; - result = join(result, "default"); + result = join2(result, "default"); if (isStatement(stmt.declaration)) { - result = join(result, this.generateStatement(stmt.declaration, bodyFlags)); + result = join2(result, this.generateStatement(stmt.declaration, bodyFlags)); } else { - result = join(result, this.generateExpression(stmt.declaration, Precedence.Assignment, E_TTT) + this.semicolon(flags)); + result = join2(result, this.generateExpression(stmt.declaration, Precedence.Assignment, E_TTT) + this.semicolon(flags)); } return result; }, @@ -57118,15 +57118,15 @@ var require_escodegen = __commonJS({ var result = ["export"], bodyFlags, that = this; bodyFlags = flags & F_SEMICOLON_OPT ? S_TFFT : S_TFFF; if (stmt.declaration) { - return join(result, this.generateStatement(stmt.declaration, bodyFlags)); + return join2(result, this.generateStatement(stmt.declaration, bodyFlags)); } if (stmt.specifiers) { if (stmt.specifiers.length === 0) { - result = join(result, "{" + space + "}"); + result = join2(result, "{" + space + "}"); } else if (stmt.specifiers[0].type === Syntax.ExportBatchSpecifier) { - result = join(result, this.generateExpression(stmt.specifiers[0], Precedence.Sequence, E_TTT)); + result = join2(result, this.generateExpression(stmt.specifiers[0], Precedence.Sequence, E_TTT)); } else { - result = join(result, "{"); + result = join2(result, "{"); withIndent(function(indent2) { var i5, iz; result.push(newline); @@ -57144,7 +57144,7 @@ var require_escodegen = __commonJS({ result.push(base + "}"); } if (stmt.source) { - result = join(result, [ + result = join2(result, [ "from" + space, // ModuleSpecifier this.generateExpression(stmt.source, Precedence.Sequence, E_TTT), @@ -57232,7 +57232,7 @@ var require_escodegen = __commonJS({ ]; cursor2 = 0; if (stmt.specifiers[cursor2].type === Syntax.ImportDefaultSpecifier) { - result = join(result, [ + result = join2(result, [ this.generateExpression(stmt.specifiers[cursor2], Precedence.Sequence, E_TTT) ]); ++cursor2; @@ -57242,7 +57242,7 @@ var require_escodegen = __commonJS({ result.push(","); } if (stmt.specifiers[cursor2].type === Syntax.ImportNamespaceSpecifier) { - result = join(result, [ + result = join2(result, [ space, this.generateExpression(stmt.specifiers[cursor2], Precedence.Sequence, E_TTT) ]); @@ -57271,7 +57271,7 @@ var require_escodegen = __commonJS({ } } } - result = join(result, [ + result = join2(result, [ "from" + space, // ModuleSpecifier this.generateExpression(stmt.source, Precedence.Sequence, E_TTT), @@ -57325,7 +57325,7 @@ var require_escodegen = __commonJS({ return result; }, ThrowStatement: function(stmt, flags) { - return [join( + return [join2( "throw", this.generateExpression(stmt.argument, Precedence.Sequence, E_TTT) ), this.semicolon(flags)]; @@ -57336,7 +57336,7 @@ var require_escodegen = __commonJS({ result = this.maybeBlockSuffix(stmt.block, result); if (stmt.handlers) { for (i5 = 0, iz = stmt.handlers.length; i5 < iz; ++i5) { - result = join(result, this.generateStatement(stmt.handlers[i5], S_TFFF)); + result = join2(result, this.generateStatement(stmt.handlers[i5], S_TFFF)); if (stmt.finalizer || i5 + 1 !== iz) { result = this.maybeBlockSuffix(stmt.handlers[i5].body, result); } @@ -57344,7 +57344,7 @@ var require_escodegen = __commonJS({ } else { guardedHandlers = stmt.guardedHandlers || []; for (i5 = 0, iz = guardedHandlers.length; i5 < iz; ++i5) { - result = join(result, this.generateStatement(guardedHandlers[i5], S_TFFF)); + result = join2(result, this.generateStatement(guardedHandlers[i5], S_TFFF)); if (stmt.finalizer || i5 + 1 !== iz) { result = this.maybeBlockSuffix(guardedHandlers[i5].body, result); } @@ -57352,13 +57352,13 @@ var require_escodegen = __commonJS({ if (stmt.handler) { if (Array.isArray(stmt.handler)) { for (i5 = 0, iz = stmt.handler.length; i5 < iz; ++i5) { - result = join(result, this.generateStatement(stmt.handler[i5], S_TFFF)); + result = join2(result, this.generateStatement(stmt.handler[i5], S_TFFF)); if (stmt.finalizer || i5 + 1 !== iz) { result = this.maybeBlockSuffix(stmt.handler[i5].body, result); } } } else { - result = join(result, this.generateStatement(stmt.handler, S_TFFF)); + result = join2(result, this.generateStatement(stmt.handler, S_TFFF)); if (stmt.finalizer) { result = this.maybeBlockSuffix(stmt.handler.body, result); } @@ -57366,7 +57366,7 @@ var require_escodegen = __commonJS({ } } if (stmt.finalizer) { - result = join(result, ["finally", this.maybeBlock(stmt.finalizer, S_TFFF)]); + result = join2(result, ["finally", this.maybeBlock(stmt.finalizer, S_TFFF)]); } return result; }, @@ -57400,7 +57400,7 @@ var require_escodegen = __commonJS({ withIndent(function() { if (stmt.test) { result = [ - join("case", that.generateExpression(stmt.test, Precedence.Sequence, E_TTT)), + join2("case", that.generateExpression(stmt.test, Precedence.Sequence, E_TTT)), ":" ]; } else { @@ -57448,9 +57448,9 @@ var require_escodegen = __commonJS({ result.push(this.maybeBlock(stmt.consequent, S_TFFF)); result = this.maybeBlockSuffix(stmt.consequent, result); if (stmt.alternate.type === Syntax.IfStatement) { - result = join(result, ["else ", this.generateStatement(stmt.alternate, bodyFlags)]); + result = join2(result, ["else ", this.generateStatement(stmt.alternate, bodyFlags)]); } else { - result = join(result, join("else", this.maybeBlock(stmt.alternate, bodyFlags))); + result = join2(result, join2("else", this.maybeBlock(stmt.alternate, bodyFlags))); } } else { result.push(this.maybeBlock(stmt.consequent, bodyFlags)); @@ -57551,7 +57551,7 @@ var require_escodegen = __commonJS({ }, ReturnStatement: function(stmt, flags) { if (stmt.argument) { - return [join( + return [join2( "return", this.generateExpression(stmt.argument, Precedence.Sequence, E_TTT) ), this.semicolon(flags)]; @@ -57640,14 +57640,14 @@ var require_escodegen = __commonJS({ if (leftSource.charCodeAt(leftSource.length - 1) === 47 && esutils.code.isIdentifierPartES5(expr.operator.charCodeAt(0))) { result = [fragment, noEmptySpace(), expr.operator]; } else { - result = join(fragment, expr.operator); + result = join2(fragment, expr.operator); } fragment = this.generateExpression(expr.right, rightPrecedence, flags); if (expr.operator === "/" && fragment.toString().charAt(0) === "/" || expr.operator.slice(-1) === "<" && fragment.toString().slice(0, 3) === "!--") { result.push(noEmptySpace()); result.push(fragment); } else { - result = join(result, fragment); + result = join2(result, fragment); } if (expr.operator === "in" && !(flags & F_ALLOW_IN)) { return ["(", result, ")"]; @@ -57687,7 +57687,7 @@ var require_escodegen = __commonJS({ var result, length, i5, iz, itemFlags; length = expr["arguments"].length; itemFlags = flags & F_ALLOW_UNPARATH_NEW && !parentheses && length === 0 ? E_TFT : E_TFF; - result = join( + result = join2( "new", this.generateExpression(expr.callee, Precedence.New, itemFlags) ); @@ -57737,11 +57737,11 @@ var require_escodegen = __commonJS({ var result, fragment, rightCharCode, leftSource, leftCharCode; fragment = this.generateExpression(expr.argument, Precedence.Unary, E_TTT); if (space === "") { - result = join(expr.operator, fragment); + result = join2(expr.operator, fragment); } else { result = [expr.operator]; if (expr.operator.length > 2) { - result = join(result, fragment); + result = join2(result, fragment); } else { leftSource = toSourceNodeWhenNeeded(result).toString(); leftCharCode = leftSource.charCodeAt(leftSource.length - 1); @@ -57764,7 +57764,7 @@ var require_escodegen = __commonJS({ result = "yield"; } if (expr.argument) { - result = join( + result = join2( result, this.generateExpression(expr.argument, Precedence.Yield, E_TTT) ); @@ -57772,7 +57772,7 @@ var require_escodegen = __commonJS({ return parenthesize(result, Precedence.Yield, precedence); }, AwaitExpression: function(expr, precedence, flags) { - var result = join( + var result = join2( expr.all ? "await*" : "await", this.generateExpression(expr.argument, Precedence.Await, E_TTT) ); @@ -57855,11 +57855,11 @@ var require_escodegen = __commonJS({ var result, fragment; result = ["class"]; if (expr.id) { - result = join(result, this.generateExpression(expr.id, Precedence.Sequence, E_TTT)); + result = join2(result, this.generateExpression(expr.id, Precedence.Sequence, E_TTT)); } if (expr.superClass) { - fragment = join("extends", this.generateExpression(expr.superClass, Precedence.Unary, E_TTT)); - result = join(result, fragment); + fragment = join2("extends", this.generateExpression(expr.superClass, Precedence.Unary, E_TTT)); + result = join2(result, fragment); } result.push(space); result.push(this.generateStatement(expr.body, S_TFFT)); @@ -57874,7 +57874,7 @@ var require_escodegen = __commonJS({ } if (expr.kind === "get" || expr.kind === "set") { fragment = [ - join(expr.kind, this.generatePropertyKey(expr.key, expr.computed)), + join2(expr.kind, this.generatePropertyKey(expr.key, expr.computed)), this.generateFunctionBody(expr.value) ]; } else { @@ -57884,7 +57884,7 @@ var require_escodegen = __commonJS({ this.generateFunctionBody(expr.value) ]; } - return join(result, fragment); + return join2(result, fragment); }, Property: function(expr, precedence, flags) { if (expr.kind === "get" || expr.kind === "set") { @@ -58079,7 +58079,7 @@ var require_escodegen = __commonJS({ for (i5 = 0, iz = expr.blocks.length; i5 < iz; ++i5) { fragment = that.generateExpression(expr.blocks[i5], Precedence.Sequence, E_TTT); if (i5 > 0 || extra.moz.comprehensionExpressionStartsWithAssignment) { - result = join(result, fragment); + result = join2(result, fragment); } else { result.push(fragment); } @@ -58087,13 +58087,13 @@ var require_escodegen = __commonJS({ }); } if (expr.filter) { - result = join(result, "if" + space); + result = join2(result, "if" + space); fragment = this.generateExpression(expr.filter, Precedence.Sequence, E_TTT); - result = join(result, ["(", fragment, ")"]); + result = join2(result, ["(", fragment, ")"]); } if (!extra.moz.comprehensionExpressionStartsWithAssignment) { fragment = this.generateExpression(expr.body, Precedence.Assignment, E_TTT); - result = join(result, fragment); + result = join2(result, fragment); } result.push(expr.type === Syntax.GeneratorExpression ? ")" : "]"); return result; @@ -58109,8 +58109,8 @@ var require_escodegen = __commonJS({ } else { fragment = this.generateExpression(expr.left, Precedence.Call, E_TTT); } - fragment = join(fragment, expr.of ? "of" : "in"); - fragment = join(fragment, this.generateExpression(expr.right, Precedence.Sequence, E_TTT)); + fragment = join2(fragment, expr.of ? "of" : "in"); + fragment = join2(fragment, this.generateExpression(expr.right, Precedence.Sequence, E_TTT)); return ["for" + space + "(", fragment, ")"]; }, SpreadElement: function(expr, precedence, flags) { @@ -65238,16 +65238,16 @@ var require_path = __commonJS({ this.__childCache = null; }; var Pp = Path.prototype; - function getChildCache(path2) { - return path2.__childCache || (path2.__childCache = /* @__PURE__ */ Object.create(null)); + function getChildCache(path3) { + return path3.__childCache || (path3.__childCache = /* @__PURE__ */ Object.create(null)); } - function getChildPath(path2, name) { - var cache5 = getChildCache(path2); - var actualChildValue = path2.getValueProperty(name); + function getChildPath(path3, name) { + var cache5 = getChildCache(path3); + var actualChildValue = path3.getValueProperty(name); var childPath = cache5[name]; if (!hasOwn.call(cache5, name) || // Ensure consistency between cache and reality. childPath.value !== actualChildValue) { - childPath = cache5[name] = new path2.constructor(actualChildValue, path2, name); + childPath = cache5[name] = new path3.constructor(actualChildValue, path3, name); } return childPath; } @@ -65259,12 +65259,12 @@ var require_path = __commonJS({ for (var _i = 0; _i < arguments.length; _i++) { names[_i] = arguments[_i]; } - var path2 = this; + var path3 = this; var count = names.length; for (var i5 = 0; i5 < count; ++i5) { - path2 = getChildPath(path2, names[i5]); + path3 = getChildPath(path3, names[i5]); } - return path2; + return path3; }; Pp.each = function each(callback, context) { var childPaths = []; @@ -65300,12 +65300,12 @@ var require_path = __commonJS({ }; function emptyMoves() { } - function getMoves(path2, offset, start, end) { - isArray.assert(path2.value); + function getMoves(path3, offset, start, end) { + isArray.assert(path3.value); if (offset === 0) { return emptyMoves; } - var length = path2.value.length; + var length = path3.value.length; if (length < 1) { return emptyMoves; } @@ -65323,10 +65323,10 @@ var require_path = __commonJS({ isNumber.assert(start); isNumber.assert(end); var moves = /* @__PURE__ */ Object.create(null); - var cache5 = getChildCache(path2); + var cache5 = getChildCache(path3); for (var i5 = start; i5 < end; ++i5) { - if (hasOwn.call(path2.value, i5)) { - var childPath = path2.get(i5); + if (hasOwn.call(path3.value, i5)) { + var childPath = path3.get(i5); if (childPath.name !== i5) { throw new Error(""); } @@ -65344,7 +65344,7 @@ var require_path = __commonJS({ throw new Error(""); } cache5[newIndex2] = childPath2; - path2.value[newIndex2] = childPath2.value; + path3.value[newIndex2] = childPath2.value; } }; } @@ -65419,34 +65419,34 @@ var require_path = __commonJS({ } return pp.insertAt.apply(pp, insertAtArgs); }; - function repairRelationshipWithParent(path2) { - if (!(path2 instanceof Path)) { + function repairRelationshipWithParent(path3) { + if (!(path3 instanceof Path)) { throw new Error(""); } - var pp = path2.parentPath; + var pp = path3.parentPath; if (!pp) { - return path2; + return path3; } var parentValue = pp.value; var parentCache = getChildCache(pp); - if (parentValue[path2.name] === path2.value) { - parentCache[path2.name] = path2; + if (parentValue[path3.name] === path3.value) { + parentCache[path3.name] = path3; } else if (isArray.check(parentValue)) { - var i5 = parentValue.indexOf(path2.value); + var i5 = parentValue.indexOf(path3.value); if (i5 >= 0) { - parentCache[path2.name = i5] = path2; + parentCache[path3.name = i5] = path3; } } else { - parentValue[path2.name] = path2.value; - parentCache[path2.name] = path2; + parentValue[path3.name] = path3.value; + parentCache[path3.name] = path3; } - if (parentValue[path2.name] !== path2.value) { + if (parentValue[path3.name] !== path3.value) { throw new Error(""); } - if (path2.parentPath.get(path2.name) !== path2) { + if (path3.parentPath.get(path3.name) !== path3) { throw new Error(""); } - return path2; + return path3; } Pp.replace = function replace(replacement) { var results = []; @@ -65526,11 +65526,11 @@ var require_scope = __commonJS({ var Expression = namedTypes.Expression; var isArray = types.builtInTypes.array; var b5 = types.builders; - var Scope = function Scope2(path2, parentScope) { + var Scope = function Scope2(path3, parentScope) { if (!(this instanceof Scope2)) { throw new Error("Scope constructor cannot be invoked without 'new'"); } - ScopeType.assert(path2.value); + ScopeType.assert(path3.value); var depth; if (parentScope) { if (!(parentScope instanceof Scope2)) { @@ -65542,8 +65542,8 @@ var require_scope = __commonJS({ depth = 0; } Object.defineProperties(this, { - path: { value: path2 }, - node: { value: path2.value }, + path: { value: path3 }, + node: { value: path3.value }, isGlobal: { value: !parentScope, enumerable: true }, depth: { value: depth }, parent: { value: parentScope }, @@ -65618,50 +65618,50 @@ var require_scope = __commonJS({ this.scan(); return this.types; }; - function scanScope(path2, bindings, scopeTypes2) { - var node = path2.value; + function scanScope(path3, bindings, scopeTypes2) { + var node = path3.value; ScopeType.assert(node); if (namedTypes.CatchClause.check(node)) { - var param = path2.get("param"); + var param = path3.get("param"); if (param.value) { addPattern(param, bindings); } } else { - recursiveScanScope(path2, bindings, scopeTypes2); + recursiveScanScope(path3, bindings, scopeTypes2); } } - function recursiveScanScope(path2, bindings, scopeTypes2) { - var node = path2.value; - if (path2.parent && namedTypes.FunctionExpression.check(path2.parent.node) && path2.parent.node.id) { - addPattern(path2.parent.get("id"), bindings); + function recursiveScanScope(path3, bindings, scopeTypes2) { + var node = path3.value; + if (path3.parent && namedTypes.FunctionExpression.check(path3.parent.node) && path3.parent.node.id) { + addPattern(path3.parent.get("id"), bindings); } if (!node) { } else if (isArray.check(node)) { - path2.each(function(childPath) { + path3.each(function(childPath) { recursiveScanChild(childPath, bindings, scopeTypes2); }); } else if (namedTypes.Function.check(node)) { - path2.get("params").each(function(paramPath) { + path3.get("params").each(function(paramPath) { addPattern(paramPath, bindings); }); - recursiveScanChild(path2.get("body"), bindings, scopeTypes2); + recursiveScanChild(path3.get("body"), bindings, scopeTypes2); } else if (namedTypes.TypeAlias && namedTypes.TypeAlias.check(node) || namedTypes.InterfaceDeclaration && namedTypes.InterfaceDeclaration.check(node) || namedTypes.TSTypeAliasDeclaration && namedTypes.TSTypeAliasDeclaration.check(node) || namedTypes.TSInterfaceDeclaration && namedTypes.TSInterfaceDeclaration.check(node)) { - addTypePattern(path2.get("id"), scopeTypes2); + addTypePattern(path3.get("id"), scopeTypes2); } else if (namedTypes.VariableDeclarator.check(node)) { - addPattern(path2.get("id"), bindings); - recursiveScanChild(path2.get("init"), bindings, scopeTypes2); + addPattern(path3.get("id"), bindings); + recursiveScanChild(path3.get("init"), bindings, scopeTypes2); } else if (node.type === "ImportSpecifier" || node.type === "ImportNamespaceSpecifier" || node.type === "ImportDefaultSpecifier") { addPattern( // Esprima used to use the .name field to refer to the local // binding identifier for ImportSpecifier nodes, but .id for // ImportNamespaceSpecifier and ImportDefaultSpecifier nodes. // ESTree/Acorn/ESpree use .local for all three node types. - path2.get(node.local ? "local" : node.name ? "name" : "id"), + path3.get(node.local ? "local" : node.name ? "name" : "id"), bindings ); } else if (Node.check(node) && !Expression.check(node)) { types.eachField(node, function(name, child) { - var childPath = path2.get(name); + var childPath = path3.get(name); if (!pathHasValue(childPath, child)) { throw new Error(""); } @@ -65669,34 +65669,34 @@ var require_scope = __commonJS({ }); } } - function pathHasValue(path2, value) { - if (path2.value === value) { + function pathHasValue(path3, value) { + if (path3.value === value) { return true; } - if (Array.isArray(path2.value) && path2.value.length === 0 && Array.isArray(value) && value.length === 0) { + if (Array.isArray(path3.value) && path3.value.length === 0 && Array.isArray(value) && value.length === 0) { return true; } return false; } - function recursiveScanChild(path2, bindings, scopeTypes2) { - var node = path2.value; + function recursiveScanChild(path3, bindings, scopeTypes2) { + var node = path3.value; if (!node || Expression.check(node)) { } else if (namedTypes.FunctionDeclaration.check(node) && node.id !== null) { - addPattern(path2.get("id"), bindings); + addPattern(path3.get("id"), bindings); } else if (namedTypes.ClassDeclaration && namedTypes.ClassDeclaration.check(node)) { - addPattern(path2.get("id"), bindings); + addPattern(path3.get("id"), bindings); } else if (ScopeType.check(node)) { if (namedTypes.CatchClause.check(node) && // TODO Broaden this to accept any pattern. namedTypes.Identifier.check(node.param)) { var catchParamName = node.param.name; var hadBinding = hasOwn.call(bindings, catchParamName); - recursiveScanScope(path2.get("body"), bindings, scopeTypes2); + recursiveScanScope(path3.get("body"), bindings, scopeTypes2); if (!hadBinding) { delete bindings[catchParamName]; } } } else { - recursiveScanScope(path2, bindings, scopeTypes2); + recursiveScanScope(path3, bindings, scopeTypes2); } } function addPattern(patternPath, bindings) { @@ -66032,53 +66032,53 @@ var require_node_path = __commonJS({ NPp.firstInStatement = function() { return firstInStatement(this); }; - function firstInStatement(path2) { - for (var node, parent; path2.parent; path2 = path2.parent) { - node = path2.node; - parent = path2.parent.node; - if (n5.BlockStatement.check(parent) && path2.parent.name === "body" && path2.name === 0) { + function firstInStatement(path3) { + for (var node, parent; path3.parent; path3 = path3.parent) { + node = path3.node; + parent = path3.parent.node; + if (n5.BlockStatement.check(parent) && path3.parent.name === "body" && path3.name === 0) { if (parent.body[0] !== node) { throw new Error("Nodes must be equal"); } return true; } - if (n5.ExpressionStatement.check(parent) && path2.name === "expression") { + if (n5.ExpressionStatement.check(parent) && path3.name === "expression") { if (parent.expression !== node) { throw new Error("Nodes must be equal"); } return true; } - if (n5.SequenceExpression.check(parent) && path2.parent.name === "expressions" && path2.name === 0) { + if (n5.SequenceExpression.check(parent) && path3.parent.name === "expressions" && path3.name === 0) { if (parent.expressions[0] !== node) { throw new Error("Nodes must be equal"); } continue; } - if (n5.CallExpression.check(parent) && path2.name === "callee") { + if (n5.CallExpression.check(parent) && path3.name === "callee") { if (parent.callee !== node) { throw new Error("Nodes must be equal"); } continue; } - if (n5.MemberExpression.check(parent) && path2.name === "object") { + if (n5.MemberExpression.check(parent) && path3.name === "object") { if (parent.object !== node) { throw new Error("Nodes must be equal"); } continue; } - if (n5.ConditionalExpression.check(parent) && path2.name === "test") { + if (n5.ConditionalExpression.check(parent) && path3.name === "test") { if (parent.test !== node) { throw new Error("Nodes must be equal"); } continue; } - if (isBinary(parent) && path2.name === "left") { + if (isBinary(parent) && path3.name === "left") { if (parent.left !== node) { throw new Error("Nodes must be equal"); } continue; } - if (n5.UnaryExpression.check(parent) && !parent.prefix && path2.name === "argument") { + if (n5.UnaryExpression.check(parent) && !parent.prefix && path3.name === "argument") { if (parent.argument !== node) { throw new Error("Nodes must be equal"); } @@ -66248,36 +66248,36 @@ var require_path_visitor = __commonJS({ }; PVp.reset = function(_path) { }; - PVp.visitWithoutReset = function(path2) { + PVp.visitWithoutReset = function(path3) { if (this instanceof this.Context) { - return this.visitor.visitWithoutReset(path2); + return this.visitor.visitWithoutReset(path3); } - if (!(path2 instanceof NodePath)) { + if (!(path3 instanceof NodePath)) { throw new Error(""); } - var value = path2.value; + var value = path3.value; var methodName = value && typeof value === "object" && typeof value.type === "string" && this._methodNameTable[value.type]; if (methodName) { - var context = this.acquireContext(path2); + var context = this.acquireContext(path3); try { return context.invokeVisitorMethod(methodName); } finally { this.releaseContext(context); } } else { - return visitChildren(path2, this); + return visitChildren(path3, this); } }; - function visitChildren(path2, visitor) { - if (!(path2 instanceof NodePath)) { + function visitChildren(path3, visitor) { + if (!(path3 instanceof NodePath)) { throw new Error(""); } if (!(visitor instanceof PathVisitor)) { throw new Error(""); } - var value = path2.value; + var value = path3.value; if (isArray.check(value)) { - path2.each(visitor.visitWithoutReset, visitor); + path3.each(visitor.visitWithoutReset, visitor); } else if (!isObject.check(value)) { } else { var childNames = types.getFieldNames(value); @@ -66291,19 +66291,19 @@ var require_path_visitor = __commonJS({ if (!hasOwn.call(value, childName)) { value[childName] = types.getFieldValue(value, childName); } - childPaths.push(path2.get(childName)); + childPaths.push(path3.get(childName)); } for (var i5 = 0; i5 < childCount; ++i5) { visitor.visitWithoutReset(childPaths[i5]); } } - return path2.value; + return path3.value; } - PVp.acquireContext = function(path2) { + PVp.acquireContext = function(path3) { if (this._reusableContextStack.length === 0) { - return new this.Context(path2); + return new this.Context(path3); } - return this._reusableContextStack.pop().reset(path2); + return this._reusableContextStack.pop().reset(path3); }; PVp.releaseContext = function(context) { if (!(context instanceof this.Context)) { @@ -66319,14 +66319,14 @@ var require_path_visitor = __commonJS({ return this._changeReported; }; function makeContextConstructor(visitor) { - function Context(path2) { + function Context(path3) { if (!(this instanceof Context)) { throw new Error(""); } if (!(this instanceof PathVisitor)) { throw new Error(""); } - if (!(path2 instanceof NodePath)) { + if (!(path3 instanceof NodePath)) { throw new Error(""); } Object.defineProperty(this, "visitor", { @@ -66335,7 +66335,7 @@ var require_path_visitor = __commonJS({ enumerable: true, configurable: false }); - this.currentPath = path2; + this.currentPath = path3; this.needToCallTraverse = true; Object.seal(this); } @@ -66348,14 +66348,14 @@ var require_path_visitor = __commonJS({ return Context; } var sharedContextProtoMethods = /* @__PURE__ */ Object.create(null); - sharedContextProtoMethods.reset = function reset(path2) { + sharedContextProtoMethods.reset = function reset(path3) { if (!(this instanceof this.Context)) { throw new Error(""); } - if (!(path2 instanceof NodePath)) { + if (!(path3 instanceof NodePath)) { throw new Error(""); } - this.currentPath = path2; + this.currentPath = path3; this.needToCallTraverse = true; return this; }; @@ -66378,34 +66378,34 @@ var require_path_visitor = __commonJS({ if (this.needToCallTraverse !== false) { throw new Error("Must either call this.traverse or return false in " + methodName); } - var path2 = this.currentPath; - return path2 && path2.value; + var path3 = this.currentPath; + return path3 && path3.value; }; - sharedContextProtoMethods.traverse = function traverse(path2, newVisitor) { + sharedContextProtoMethods.traverse = function traverse(path3, newVisitor) { if (!(this instanceof this.Context)) { throw new Error(""); } - if (!(path2 instanceof NodePath)) { + if (!(path3 instanceof NodePath)) { throw new Error(""); } if (!(this.currentPath instanceof NodePath)) { throw new Error(""); } this.needToCallTraverse = false; - return visitChildren(path2, PathVisitor.fromMethodsObject(newVisitor || this.visitor)); + return visitChildren(path3, PathVisitor.fromMethodsObject(newVisitor || this.visitor)); }; - sharedContextProtoMethods.visit = function visit(path2, newVisitor) { + sharedContextProtoMethods.visit = function visit(path3, newVisitor) { if (!(this instanceof this.Context)) { throw new Error(""); } - if (!(path2 instanceof NodePath)) { + if (!(path3 instanceof NodePath)) { throw new Error(""); } if (!(this.currentPath instanceof NodePath)) { throw new Error(""); } this.needToCallTraverse = false; - return PathVisitor.fromMethodsObject(newVisitor || this.visitor).visitWithoutReset(path2); + return PathVisitor.fromMethodsObject(newVisitor || this.visitor).visitWithoutReset(path3); }; sharedContextProtoMethods.reportChanged = function reportChanged() { this.visitor.reportChanged(); @@ -67619,10 +67619,10 @@ var require_degenerator = __commonJS({ do { lastNamesLength = names.length; (0, ast_types_1.visit)(ast, { - visitVariableDeclaration(path2) { - if (path2.node.declarations) { - for (let i5 = 0; i5 < path2.node.declarations.length; i5++) { - const declaration = path2.node.declarations[i5]; + visitVariableDeclaration(path3) { + if (path3.node.declarations) { + for (let i5 = 0; i5 < path3.node.declarations.length; i5++) { + const declaration = path3.node.declarations[i5]; if (ast_types_1.namedTypes.VariableDeclarator.check(declaration) && ast_types_1.namedTypes.Identifier.check(declaration.init) && ast_types_1.namedTypes.Identifier.check(declaration.id) && checkName(declaration.init.name, names) && !checkName(declaration.id.name, names)) { names.push(declaration.id.name); } @@ -67630,18 +67630,18 @@ var require_degenerator = __commonJS({ } return false; }, - visitAssignmentExpression(path2) { - if (ast_types_1.namedTypes.Identifier.check(path2.node.left) && ast_types_1.namedTypes.Identifier.check(path2.node.right) && checkName(path2.node.right.name, names) && !checkName(path2.node.left.name, names)) { - names.push(path2.node.left.name); + visitAssignmentExpression(path3) { + if (ast_types_1.namedTypes.Identifier.check(path3.node.left) && ast_types_1.namedTypes.Identifier.check(path3.node.right) && checkName(path3.node.right.name, names) && !checkName(path3.node.left.name, names)) { + names.push(path3.node.left.name); } return false; }, - visitFunction(path2) { - if (path2.node.id) { + visitFunction(path3) { + if (path3.node.id) { let shouldDegenerate = false; - (0, ast_types_1.visit)(path2.node, { - visitCallExpression(path3) { - if (checkNames(path3.node, names)) { + (0, ast_types_1.visit)(path3.node, { + visitCallExpression(path4) { + if (checkNames(path4.node, names)) { shouldDegenerate = true; } return false; @@ -67650,28 +67650,28 @@ var require_degenerator = __commonJS({ if (!shouldDegenerate) { return false; } - path2.node.async = true; - if (!checkName(path2.node.id.name, names)) { - names.push(path2.node.id.name); + path3.node.async = true; + if (!checkName(path3.node.id.name, names)) { + names.push(path3.node.id.name); } } - this.traverse(path2); + this.traverse(path3); } }); } while (lastNamesLength !== names.length); (0, ast_types_1.visit)(ast, { - visitCallExpression(path2) { - if (checkNames(path2.node, names)) { + visitCallExpression(path3) { + if (checkNames(path3.node, names)) { const delegate = false; - const { name, parent: { node: pNode } } = path2; - const expr = ast_types_1.builders.awaitExpression(path2.node, delegate); + const { name, parent: { node: pNode } } = path3; + const expr = ast_types_1.builders.awaitExpression(path3.node, delegate); if (ast_types_1.namedTypes.CallExpression.check(pNode)) { pNode.arguments[name] = expr; } else { pNode[name] = expr; } } - this.traverse(path2); + this.traverse(path3); } }); return (0, escodegen_1.generate)(ast); @@ -70411,14 +70411,14 @@ var require_emscripten_module_WASM_RELEASE_SYNC = __commonJS({ }); var p5 = Object.assign({}, a5), t5 = "./this.program", u5 = "object" == typeof window, v5 = "function" == typeof importScripts, w5 = "object" == typeof process && "object" == typeof process.versions && "string" == typeof process.versions.node, x5 = "", y2, z2, A2; if (w5) { - var fs2 = require("fs"), B2 = require("path"); + var fs3 = require("fs"), B2 = require("path"); x5 = v5 ? B2.dirname(x5) + "/" : __dirname + "/"; y2 = (b5, c5) => { var d5 = C2(b5); if (d5) return c5 ? d5 : d5.toString(); b5 = b5.startsWith("file://") ? new URL(b5) : B2.normalize(b5); - return fs2.readFileSync(b5, c5 ? void 0 : "utf8"); + return fs3.readFileSync(b5, c5 ? void 0 : "utf8"); }; A2 = (b5) => { b5 = y2(b5, true); @@ -70429,7 +70429,7 @@ var require_emscripten_module_WASM_RELEASE_SYNC = __commonJS({ var e5 = C2(b5); e5 && c5(e5); b5 = b5.startsWith("file://") ? new URL(b5) : B2.normalize(b5); - fs2.readFile(b5, function(f5, g5) { + fs3.readFile(b5, function(f5, g5) { f5 ? d5(f5) : c5(g5.buffer); }); }; @@ -71467,7 +71467,7 @@ var require_dist10 = __commonJS({ var get_uri_1 = require_dist6(); var pac_resolver_1 = require_dist8(); var quickjs_emscripten_1 = require_dist9(); - var debug3 = (0, debug_1.default)("pac-proxy-agent"); + var debug4 = (0, debug_1.default)("pac-proxy-agent"); var setServernameFromNonIpHost = (options) => { if (options.servername === void 0 && options.host && !net.isIP(options.host)) { return { @@ -71485,7 +71485,7 @@ var require_dist10 = __commonJS({ }; const uriStr = typeof uri === "string" ? uri : uri.href; this.uri = new url_1.URL(uriStr.replace(/^pac\+/i, "")); - debug3("Creating PacProxyAgent with URI %o", this.uri.href); + debug4("Creating PacProxyAgent with URI %o", this.uri.href); this.opts = { ...opts }; this.cache = void 0; this.resolver = void 0; @@ -71514,16 +71514,16 @@ var require_dist10 = __commonJS({ ]); const hash = crypto2.createHash("sha1").update(code).digest("hex"); if (this.resolver && this.resolverHash === hash) { - debug3("Same sha1 hash for code - contents have not changed, reusing previous proxy resolver"); + debug4("Same sha1 hash for code - contents have not changed, reusing previous proxy resolver"); return this.resolver; } - debug3("Creating new proxy resolver instance"); + debug4("Creating new proxy resolver instance"); this.resolver = (0, pac_resolver_1.createPacResolver)(qjs, code, this.opts); this.resolverHash = hash; return this.resolver; } catch (err) { if (this.resolver && err.code === "ENOTMODIFIED") { - debug3("Got ENOTMODIFIED response, reusing previous proxy resolver"); + debug4("Got ENOTMODIFIED response, reusing previous proxy resolver"); return this.resolver; } throw err; @@ -71535,12 +71535,12 @@ var require_dist10 = __commonJS({ * @api private */ async loadPacFile() { - debug3("Loading PAC file: %o", this.uri); + debug4("Loading PAC file: %o", this.uri); const rs = await (0, get_uri_1.getUri)(this.uri, { ...this.opts, cache: this.cache }); - debug3("Got `Readable` instance for URI"); + debug4("Got `Readable` instance for URI"); this.cache = rs; const buf = await (0, agent_base_1.toBuffer)(rs); - debug3("Read %o byte PAC file from URI", buf.length); + debug4("Read %o byte PAC file from URI", buf.length); return buf.toString("utf8"); } /** @@ -71554,7 +71554,7 @@ var require_dist10 = __commonJS({ const host = opts.host && net.isIPv6(opts.host) ? `[${opts.host}]` : opts.host; const defaultPort = secureEndpoint ? 443 : 80; const url = Object.assign(new url_1.URL(req.path, `${protocol}//${host}`), defaultPort ? void 0 : { port: opts.port }); - debug3("url: %s", url); + debug4("url: %s", url); let result = await resolver(url); if (!result) { result = "DIRECT"; @@ -71567,7 +71567,7 @@ var require_dist10 = __commonJS({ let agent = null; let socket = null; const [type, target] = proxy.split(/\s+/); - debug3("Attempting to use proxy: %o", proxy); + debug4("Attempting to use proxy: %o", proxy); if (type === "DIRECT") { if (secureEndpoint) { socket = tls.connect(setServernameFromNonIpHost(opts)); @@ -71606,7 +71606,7 @@ var require_dist10 = __commonJS({ } throw new Error(`Could not determine proxy type for: ${proxy}`); } catch (err) { - debug3("Got error for proxy %o: %o", proxy, err); + debug4("Got error for proxy %o: %o", proxy, err); req.emit("proxy", { proxy, error: err }); } } @@ -71667,7 +71667,7 @@ var require_dist11 = __commonJS({ var agent_base_1 = require_dist(); var debug_1 = __importDefault2(require_src()); var proxy_from_env_1 = require_proxy_from_env(); - var debug3 = (0, debug_1.default)("proxy-agent"); + var debug4 = (0, debug_1.default)("proxy-agent"); var wellKnownAgents = { http: async () => (await Promise.resolve().then(() => __importStar2(require_dist2()))).HttpProxyAgent, https: async () => (await Promise.resolve().then(() => __importStar2(require_dist3()))).HttpsProxyAgent, @@ -71698,7 +71698,7 @@ var require_dist11 = __commonJS({ max: 20, dispose: (agent) => agent.destroy() }); - debug3("Creating new ProxyAgent instance: %o", opts); + debug4("Creating new ProxyAgent instance: %o", opts); this.connectOpts = opts; this.httpAgent = opts?.httpAgent || new http.Agent(opts); this.httpsAgent = opts?.httpsAgent || new https.Agent(opts); @@ -71712,11 +71712,11 @@ var require_dist11 = __commonJS({ const url = new url_1.URL(req.path, `${protocol}//${host}`).href; const proxy = await this.getProxyForUrl(url, req); if (!proxy) { - debug3("Proxy not enabled for URL: %o", url); + debug4("Proxy not enabled for URL: %o", url); return secureEndpoint ? this.httpsAgent : this.httpAgent; } - debug3("Request URL: %o", url); - debug3("Proxy URL: %o", proxy); + debug4("Request URL: %o", url); + debug4("Proxy URL: %o", proxy); const cacheKey = `${protocol}+${proxy}`; let agent = this.cache.get(cacheKey); if (!agent) { @@ -71729,7 +71729,7 @@ var require_dist11 = __commonJS({ agent = new ctor(proxy, this.connectOpts); this.cache.set(cacheKey, agent); } else { - debug3("Cache hit for proxy URL: %o", proxy); + debug4("Cache hit for proxy URL: %o", proxy); } return agent; } @@ -71750,7 +71750,7 @@ __export(index_exports, { run: () => run }); module.exports = __toCommonJS(index_exports); -var core3 = __toESM(require_core()); +var core4 = __toESM(require_core()); // src/assumeRole.ts var import_node_assert = __toESM(require("node:assert")); @@ -71787,7 +71787,8 @@ function translateEnvVariables() { "RETRY_MAX_ATTEMPTS", "SPECIAL_CHARACTERS_WORKAROUND", "USE_EXISTING_CREDENTIALS", - "NO_PROXY" + "NO_PROXY", + "OVERWRITE_AWS_PROFILE" ]; if (process.env.HTTPS_PROXY) process.env.HTTP_PROXY = process.env.HTTPS_PROXY; for (const envVar of envVars) { @@ -72215,6 +72216,136 @@ var CredentialsClient = class { } }; +// src/profileManager.ts +var fs2 = __toESM(require("node:fs")); +var os = __toESM(require("node:os")); +var path2 = __toESM(require("node:path")); +var core3 = __toESM(require_core()); +function parseIni(iniData) { + const result = {}; + let currentSection; + for (const line of iniData.split(/\r?\n/)) { + const trimmed = line.trim(); + if (!trimmed || trimmed.startsWith(";") || trimmed.startsWith("#")) { + continue; + } + const sectionMatch = trimmed.match(/^\[([^\]]*)\]$/); + if (sectionMatch) { + currentSection = sectionMatch[1]; + if (currentSection === "__proto__") { + currentSection = void 0; + continue; + } + result[currentSection] = result[currentSection] || {}; + continue; + } + if (currentSection) { + const eqIndex = trimmed.indexOf("="); + if (eqIndex > 0) { + const key = trimmed.substring(0, eqIndex).trim(); + const value = trimmed.substring(eqIndex + 1).trim(); + if (key !== "__proto__") { + const section = result[currentSection]; + if (section) { + section[key] = value; + } + } + } + } + } + return result; +} +function stringifyIni(data2) { + const sections = []; + for (const [sectionName, sectionData] of Object.entries(data2)) { + const lines = [`[${sectionName}]`]; + for (const [key, value] of Object.entries(sectionData)) { + lines.push(`${key} = ${value}`); + } + sections.push(lines.join("\n")); + } + return `${sections.join("\n\n")} +`; +} +function getProfileFilePaths() { + const credentialsPath = process.env.AWS_SHARED_CREDENTIALS_FILE || path2.join(os.homedir(), ".aws", "credentials"); + const configPath = process.env.AWS_CONFIG_FILE || path2.join(os.homedir(), ".aws", "config"); + return { + credentials: credentialsPath, + config: configPath + }; +} +function ensureAwsDirectoryExists(filePath) { + const dir = path2.dirname(filePath); + if (!fs2.existsSync(dir)) { + core3.debug(`Creating directory: ${dir}`); + fs2.mkdirSync(dir, { recursive: true, mode: 448 }); + } +} +function validateProfileName(profileName) { + if (!profileName || profileName.trim() === "") { + throw new Error("aws-profile must not be empty"); + } + if (/\s/.test(profileName)) { + throw new Error("aws-profile must not contain whitespace"); + } + if (/[[\]]/.test(profileName)) { + throw new Error("aws-profile must not contain brackets"); + } + if (profileName.includes("/") || profileName.includes("\\")) { + throw new Error("aws-profile must not contain path separators"); + } +} +function mergeProfileSection(filePath, sectionName, data2, overwriteAwsProfile) { + let existingContent = {}; + if (fs2.existsSync(filePath)) { + core3.debug(`Reading existing file: ${filePath}`); + const fileContent = fs2.readFileSync(filePath, "utf-8"); + existingContent = parseIni(fileContent); + } + if (existingContent[sectionName] && !overwriteAwsProfile) { + throw new Error( + `Profile with name "${sectionName}" already exists. Please use the overwrite-aws-profile input if you want to overwrite existing profiles.` + ); + } + existingContent[sectionName] = data2; + const content = stringifyIni(existingContent); + core3.debug(`Writing profile to ${filePath}`); + fs2.writeFileSync(filePath, content, { mode: 384 }); +} +function writeProfileFiles(profileName, credentials, region, overwriteAwsProfile) { + try { + validateProfileName(profileName); + const paths = getProfileFilePaths(); + ensureAwsDirectoryExists(paths.credentials); + ensureAwsDirectoryExists(paths.config); + const credentialsData = {}; + if (credentials.AccessKeyId) { + credentialsData.aws_access_key_id = credentials.AccessKeyId; + } + if (credentials.SecretAccessKey) { + credentialsData.aws_secret_access_key = credentials.SecretAccessKey; + } + if (credentials.SessionToken) { + credentialsData.aws_session_token = credentials.SessionToken; + } + const credsSectionName = profileName; + const configSectionName = profileName === "default" ? "default" : `profile ${profileName}`; + const configData = { + region + }; + core3.info(`Writing credentials to profile: ${profileName}`); + mergeProfileSection(paths.credentials, credsSectionName, credentialsData, overwriteAwsProfile); + core3.info(`Writing config to profile: ${profileName}`); + mergeProfileSection(paths.config, configSectionName, configData, overwriteAwsProfile); + core3.info(`\u2713 Successfully configured AWS profile: ${profileName}`); + } catch (error2) { + throw new Error( + `Failed to write AWS profile '${profileName}': ${error2 instanceof Error ? error2.message : String(error2)}` + ); + } +} + // src/index.ts var DEFAULT_ROLE_DURATION = 3600; var ROLE_SESSION_NAME = "GitHubActions"; @@ -72222,42 +72353,44 @@ var REGION_REGEX = /^[a-z0-9-]+$/g; async function run() { try { translateEnvVariables(); - const AccessKeyId = core3.getInput("aws-access-key-id", { required: false }); - const SecretAccessKey = core3.getInput("aws-secret-access-key", { required: false }); - const sessionTokenInput = core3.getInput("aws-session-token", { required: false }); + const AccessKeyId = core4.getInput("aws-access-key-id", { required: false }); + const SecretAccessKey = core4.getInput("aws-secret-access-key", { required: false }); + const sessionTokenInput = core4.getInput("aws-session-token", { required: false }); const SessionToken = sessionTokenInput === "" ? void 0 : sessionTokenInput; - const region = core3.getInput("aws-region", { required: true }); - const roleToAssume = core3.getInput("role-to-assume", { required: false }); - const audience = core3.getInput("audience", { required: false }); + const region = core4.getInput("aws-region", { required: true }); + const awsProfile = core4.getInput("aws-profile", { required: false }); + const overwriteAwsProfile = getBooleanInput("overwrite-aws-profile", { required: false }); + const roleToAssume = core4.getInput("role-to-assume", { required: false }); + const audience = core4.getInput("audience", { required: false }); const maskAccountId = getBooleanInput("mask-aws-account-id", { required: false }); - const roleExternalId = core3.getInput("role-external-id", { required: false }); - const webIdentityTokenFile = core3.getInput("web-identity-token-file", { required: false }); - const roleDuration = Number.parseInt(core3.getInput("role-duration-seconds", { required: false })) || DEFAULT_ROLE_DURATION; - const roleSessionName = core3.getInput("role-session-name", { required: false }) || ROLE_SESSION_NAME; + const roleExternalId = core4.getInput("role-external-id", { required: false }); + const webIdentityTokenFile = core4.getInput("web-identity-token-file", { required: false }); + const roleDuration = Number.parseInt(core4.getInput("role-duration-seconds", { required: false })) || DEFAULT_ROLE_DURATION; + const roleSessionName = core4.getInput("role-session-name", { required: false }) || ROLE_SESSION_NAME; const roleSkipSessionTagging = getBooleanInput("role-skip-session-tagging", { required: false }); - const transitiveTagKeys = core3.getMultilineInput("transitive-tag-keys", { required: false }); - const proxyServer = core3.getInput("http-proxy", { required: false }) || process.env.HTTP_PROXY; - const inlineSessionPolicy = core3.getInput("inline-session-policy", { required: false }); - const managedSessionPolicies = core3.getMultilineInput("managed-session-policies", { required: false }).map((p5) => { + const transitiveTagKeys = core4.getMultilineInput("transitive-tag-keys", { required: false }); + const proxyServer = core4.getInput("http-proxy", { required: false }) || process.env.HTTP_PROXY; + const inlineSessionPolicy = core4.getInput("inline-session-policy", { required: false }); + const managedSessionPolicies = core4.getMultilineInput("managed-session-policies", { required: false }).map((p5) => { return { arn: p5 }; }); const roleChaining = getBooleanInput("role-chaining", { required: false }); const outputCredentials = getBooleanInput("output-credentials", { required: false }); - const outputEnvCredentials = getBooleanInput("output-env-credentials", { required: false, default: true }); + const outputEnvCredentials = getBooleanInput("output-env-credentials", { required: false, default: !awsProfile }); const unsetCurrentCredentials = getBooleanInput("unset-current-credentials", { required: false }); let disableRetry = getBooleanInput("disable-retry", { required: false }); const specialCharacterWorkaround = getBooleanInput("special-characters-workaround", { required: false }); - const useExistingCredentials = core3.getInput("use-existing-credentials", { required: false }); - let maxRetries = Number.parseInt(core3.getInput("retry-max-attempts", { required: false })) || 12; - const expectedAccountIds = core3.getInput("allowed-account-ids", { required: false }).split(",").map((s5) => s5.trim()); + const useExistingCredentials = core4.getInput("use-existing-credentials", { required: false }); + let maxRetries = Number.parseInt(core4.getInput("retry-max-attempts", { required: false })) || 12; + const expectedAccountIds = core4.getInput("allowed-account-ids", { required: false }).split(",").map((s5) => s5.trim()); const forceSkipOidc = getBooleanInput("force-skip-oidc", { required: false }); - const noProxy = core3.getInput("no-proxy", { required: false }); - const globalTimeout = Number.parseInt(core3.getInput("action-timeout-s", { required: false })) || 0; + const noProxy = core4.getInput("no-proxy", { required: false }); + const globalTimeout = Number.parseInt(core4.getInput("action-timeout-s", { required: false })) || 0; let timeoutId; if (globalTimeout > 0) { - core3.info(`Setting a global timeout of ${globalTimeout} seconds for the action`); + core4.info(`Setting a global timeout of ${globalTimeout} seconds for the action`); timeoutId = setTimeout(() => { - core3.setFailed(`Action timed out after ${globalTimeout} seconds`); + core4.setFailed(`Action timed out after ${globalTimeout} seconds`); process.exit(1); }, globalTimeout * 1e3); } @@ -72275,7 +72408,7 @@ async function run() { const useGitHubOIDCProvider = () => { if (forceSkipOidc) return false; if (!!roleToAssume && !webIdentityTokenFile && !AccessKeyId && !process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN && !roleChaining) { - core3.info( + core4.info( "It looks like you might be trying to authenticate with OIDC. Did you mean to set the `id-token` permission? If you are not trying to authenticate with OIDC and the action is working successfully, you can ignore this message." ); } @@ -72300,17 +72433,17 @@ async function run() { if (useExistingCredentials) { const validCredentials = await areCredentialsValid(credentialsClient); if (validCredentials) { - core3.notice("Pre-existing credentials are valid. No need to generate new ones."); + core4.notice("Pre-existing credentials are valid. No need to generate new ones."); if (timeoutId) clearTimeout(timeoutId); return; } - core3.notice("No valid credentials exist. Running as normal."); + core4.notice("No valid credentials exist. Running as normal."); } if (useGitHubOIDCProvider()) { try { webIdentityToken = await retryAndBackoff( async () => { - return core3.getIDToken(audience); + return core4.getIDToken(audience); }, !disableRetry, maxRetries @@ -72323,6 +72456,9 @@ async function run() { throw new Error("'aws-secret-access-key' must be provided if 'aws-access-key-id' is provided"); } exportCredentials({ AccessKeyId, SecretAccessKey, SessionToken }, outputCredentials, outputEnvCredentials); + if (awsProfile) { + writeProfileFiles(awsProfile, { AccessKeyId, SecretAccessKey, SessionToken }, region, overwriteAwsProfile); + } } else if (!webIdentityTokenFile && !roleChaining) { await credentialsClient.validateCredentials(void 0, roleChaining, expectedAccountIds); sourceAccountId = await exportAccountId(credentialsClient, maskAccountId); @@ -72355,9 +72491,9 @@ async function run() { maxRetries ); } while (specialCharacterWorkaround && !verifyKeys(roleCredentials.Credentials)); - core3.info(`Authenticated as assumedRoleId ${roleCredentials.AssumedRoleUser?.AssumedRoleId}`); + core4.info(`Authenticated as assumedRoleId ${roleCredentials.AssumedRoleUser?.AssumedRoleId}`); exportCredentials(roleCredentials.Credentials, outputCredentials, outputEnvCredentials); - if (!process.env.GITHUB_ACTIONS || AccessKeyId) { + if ((!process.env.GITHUB_ACTIONS || AccessKeyId) && !awsProfile) { await credentialsClient.validateCredentials( roleCredentials.Credentials?.AccessKeyId, roleChaining, @@ -72367,12 +72503,32 @@ async function run() { if (outputEnvCredentials) { await exportAccountId(credentialsClient, maskAccountId); } + if (awsProfile) { + if (AccessKeyId || !process.env.GITHUB_ACTIONS) { + writeProfileFiles(awsProfile, roleCredentials.Credentials || {}, region, true); + await credentialsClient.validateCredentials( + roleCredentials.Credentials?.AccessKeyId, + roleChaining, + expectedAccountIds + ); + } else { + writeProfileFiles(awsProfile, roleCredentials.Credentials || {}, region, overwriteAwsProfile); + } + if (outputEnvCredentials) { + core4.exportVariable("AWS_PROFILE", awsProfile); + } + } } else { - core3.info("Proceeding with IAM user credentials"); + core4.info("Proceeding with IAM user credentials"); + if (awsProfile) { + if (outputEnvCredentials) { + core4.exportVariable("AWS_PROFILE", awsProfile); + } + } } if (timeoutId) clearTimeout(timeoutId); } catch (error2) { - core3.setFailed(errorMessage(error2)); + core4.setFailed(errorMessage(error2)); const showStackTrace = process.env.SHOW_STACK_TRACE; if (showStackTrace === "true") { throw error2; @@ -72383,7 +72539,7 @@ if (require.main === module) { (async () => { await run(); })().catch((error2) => { - core3.setFailed(errorMessage(error2)); + core4.setFailed(errorMessage(error2)); }); } // Annotate the CommonJS export names for ESM import in node: diff --git a/examples/README.md b/examples/README.md index 7992b82..e494f10 100644 --- a/examples/README.md +++ b/examples/README.md @@ -2,8 +2,13 @@ ## [federated-setup](./federated-setup/README.md) -The directory contains templates for setting up the `configure-aws-credentials` federation between your GitHub Organization/repository and your AWS account. +The directory contains templates for setting up the `configure-aws-credentials` +federation between your GitHub Organization/repository and your AWS account. ## [cfn-deploy-example](./cfn-deploy-example/README.md) -Repository example uses aws-action `configure-aws-credentials` with OIDC federation template [github-actions-oidc-federation-and-role](./github-actions-oidc-federation-and-role.yml). Example demonstrates a repository that deploys AWS CloudFormation template using cfn-deploy GitHub Action. +Repository example uses aws-action `configure-aws-credentials` with OIDC +federation template +[github-actions-oidc-federation-and-role](./github-actions-oidc-federation-and-role.yml). +Example demonstrates a repository that deploys AWS CloudFormation template using +cfn-deploy GitHub Action. diff --git a/examples/cfn-deploy-example/README.md b/examples/cfn-deploy-example/README.md index 4deedcc..7f68737 100644 --- a/examples/cfn-deploy-example/README.md +++ b/examples/cfn-deploy-example/README.md @@ -1,14 +1,24 @@ # cfn-deploy example -Example uses aws-action `configure-aws-credentials` with OIDC federation. Prior to using this example project, the user needs to deploy the [github-actions-oidc-federation-and-role](../federated-setup/github-actions-oidc-federation-and-role.yml) template in the AWS account they want to deploy the CloudFormation template into. Specify the GitHub Organization name, repository name, and the specific branch you want to deploy on. +Example uses aws-action `configure-aws-credentials` with OIDC federation. Prior +to using this example project, the user needs to deploy the +[github-actions-oidc-federation-and-role](../federated-setup/github-actions-oidc-federation-and-role.yml) +template in the AWS account they want to deploy the CloudFormation template +into. Specify the GitHub Organization name, repository name, and the specific +branch you want to deploy on. -Within the [github/workflows](./.github/workflows/) directory there is a [compliance.yml](./.github/workflows/compliance.yml) and a [deploy.yml](./.github/workflows/deploy.yml). The deploy.yml file leverages the aws-action `configure-aws-credentials` and accesses GitHub Action Secrets for some of the variables. The compliance.yml runs static application security testing using cfn-guard. +Within the [github/workflows](./.github/workflows/) directory there is a +[compliance.yml](./.github/workflows/compliance.yml) and a +[deploy.yml](./.github/workflows/deploy.yml). The deploy.yml file leverages the +aws-action `configure-aws-credentials` and accesses GitHub Action Secrets for +some of the variables. The compliance.yml runs static application security +testing using cfn-guard. To use the example you will need to set the following GitHub Action Secrets: -| Secret Key | Used With | Description | -| --------- | -------- | -----------| -| AWS_ACCOUNT_ID | configure-aws-credentials | The AWS account ID | -| AWS_DEPLOY_ROLE | configure-aws-credentials | The name of the IAM role | -| VPC_ID | aws-cloudformation-github-deploy | VPC ID the EC2 Bastion is deployed to | -| SUBNET_ID | aws-cloudformation-github-deploy | Subnet ID the EC2 Bastion is deployed to | +| Secret Key | Used With | Description | +| --------------- | -------------------------------- | ---------------------------------------- | +| AWS_ACCOUNT_ID | configure-aws-credentials | The AWS account ID | +| AWS_DEPLOY_ROLE | configure-aws-credentials | The name of the IAM role | +| VPC_ID | aws-cloudformation-github-deploy | VPC ID the EC2 Bastion is deployed to | +| SUBNET_ID | aws-cloudformation-github-deploy | Subnet ID the EC2 Bastion is deployed to | diff --git a/examples/federated-setup/README.md b/examples/federated-setup/README.md index 80b302d..684488c 100644 --- a/examples/federated-setup/README.md +++ b/examples/federated-setup/README.md @@ -2,8 +2,10 @@ ## [github-action-oidc-federation](./github-actions-oidc-federation.yml) -Setup of the OIDC federation between your GitHub Organization/repository and your AWS account. +Setup of the OIDC federation between your GitHub Organization/repository and +your AWS account. ## [github-actions-oidc-federation-and-role](./github-actions-oidc-federation-and-role.yml) -Setup of the OIDC federation between your GitHub Organization/repository and your AWS account along with a role that only executes on specific branch. +Setup of the OIDC federation between your GitHub Organization/repository and +your AWS account along with a role that only executes on specific branch. diff --git a/package-lock.json b/package-lock.json index 3d8047a..89a4158 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "esbuild": "^0.27.4", "generate-license-file": "^4.1.1", "json-schema": "^0.4.0", + "markdownlint-cli": "^0.48.0", "memfs": "^4.57.1", "standard-version": "^9.5.0", "typescript": "^6.0.2", @@ -1991,9 +1992,9 @@ } }, "node_modules/@npmcli/arborist/node_modules/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2165,9 +2166,9 @@ } }, "node_modules/@npmcli/map-workspaces/node_modules/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2315,9 +2316,9 @@ } }, "node_modules/@npmcli/package-json/node_modules/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3598,9 +3599,9 @@ } }, "node_modules/@tufjs/models/node_modules/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3636,6 +3637,16 @@ "@types/deep-eql": "*" } }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, "node_modules/@types/deep-eql": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", @@ -3650,6 +3661,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/katex": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.8.tgz", + "integrity": "sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/minimist": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz", @@ -3657,6 +3675,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "25.5.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", @@ -3691,6 +3716,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "dev": true, + "license": "MIT" + }, "node_modules/@vitest/coverage-v8": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.4.tgz", @@ -4035,9 +4067,9 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", "dev": true, "license": "MIT", "dependencies": { @@ -4121,9 +4153,9 @@ } }, "node_modules/cacache/node_modules/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4287,6 +4319,39 @@ "node": ">=4" } }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/check-error": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", @@ -5533,6 +5598,20 @@ "node": ">=0.10.0" } }, + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/deep-eql": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", @@ -5543,6 +5622,16 @@ "node": ">=6" } }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/defaults": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", @@ -5570,6 +5659,16 @@ "node": ">= 14" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/detect-indent": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", @@ -5590,10 +5689,24 @@ "node": ">=8" } }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/diff": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", - "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.2.tgz", + "integrity": "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -5744,6 +5857,19 @@ "node": ">=8" } }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/env-paths": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", @@ -6086,9 +6212,9 @@ } }, "node_modules/generate-license-file/node_modules/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6192,6 +6318,19 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-east-asian-width": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", + "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-pkg-repo": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/get-pkg-repo/-/get-pkg-repo-4.2.1.tgz", @@ -6710,6 +6849,13 @@ "ini": "^1.3.2" } }, + "node_modules/gitconfiglocal/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, "node_modules/glob": { "version": "10.5.0", "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", @@ -6749,9 +6895,9 @@ } }, "node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", "dev": true, "license": "MIT", "dependencies": { @@ -6938,6 +7084,16 @@ ], "license": "BSD-3-Clause" }, + "node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/ignore-walk": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-8.0.0.tgz", @@ -6962,9 +7118,9 @@ } }, "node_modules/ignore-walk/node_modules/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, "license": "MIT", "dependencies": { @@ -7035,11 +7191,14 @@ "license": "ISC" }, "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz", + "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==", "dev": true, - "license": "ISC" + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } }, "node_modules/ip-address": { "version": "10.0.1", @@ -7050,6 +7209,32 @@ "node": ">= 12" } }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -7073,6 +7258,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -7083,6 +7279,17 @@ "node": ">=8" } }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-interactive": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", @@ -7294,6 +7501,13 @@ "node": ">=6" } }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "dev": true, + "license": "MIT" + }, "node_modules/jsonparse": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", @@ -7304,6 +7518,16 @@ ], "license": "MIT" }, + "node_modules/jsonpointer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", + "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/JSONStream": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", @@ -7342,6 +7566,33 @@ "dev": true, "license": "MIT" }, + "node_modules/katex": { + "version": "0.16.38", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.38.tgz", + "integrity": "sha512-cjHooZUmIAUmDsHBN+1n8LaZdpmbj03LtYeYPyuYB7OuloiaeaV6N4LcfjcnHVzGWjVQmKrxxTrpDcmSzEZQwQ==", + "dev": true, + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "license": "MIT", + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/katex/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -7359,6 +7610,16 @@ "dev": true, "license": "MIT" }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, "node_modules/load-json-file": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", @@ -7579,6 +7840,138 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/markdown-it": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz", + "integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/markdownlint": { + "version": "0.40.0", + "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.40.0.tgz", + "integrity": "sha512-UKybllYNheWac61Ia7T6fzuQNDZimFIpCg2w6hHjgV1Qu0w1TV0LlSgryUGzM0bkKQCBhy2FDhEELB73Kb0kAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "micromark": "4.0.2", + "micromark-core-commonmark": "2.0.3", + "micromark-extension-directive": "4.0.0", + "micromark-extension-gfm-autolink-literal": "2.1.0", + "micromark-extension-gfm-footnote": "2.1.0", + "micromark-extension-gfm-table": "2.1.1", + "micromark-extension-math": "3.1.0", + "micromark-util-types": "2.0.2", + "string-width": "8.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/DavidAnson" + } + }, + "node_modules/markdownlint-cli": { + "version": "0.48.0", + "resolved": "https://registry.npmjs.org/markdownlint-cli/-/markdownlint-cli-0.48.0.tgz", + "integrity": "sha512-NkZQNu2E0Q5qLEEHwWj674eYISTLD4jMHkBzDobujXd1kv+yCxi8jOaD/rZoQNW1FBBMMGQpuW5So8B51N/e0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "~14.0.3", + "deep-extend": "~0.6.0", + "ignore": "~7.0.5", + "js-yaml": "~4.1.1", + "jsonc-parser": "~3.3.1", + "jsonpointer": "~5.0.1", + "markdown-it": "~14.1.1", + "markdownlint": "~0.40.0", + "minimatch": "~10.2.4", + "run-con": "~1.3.2", + "smol-toml": "~1.6.0", + "tinyglobby": "~0.2.15" + }, + "bin": { + "markdownlint": "markdownlint.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/markdownlint-cli/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/markdownlint-cli/node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/markdownlint-cli/node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/markdownlint/node_modules/string-width": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz", + "integrity": "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "dev": true, + "license": "MIT" + }, "node_modules/memfs": { "version": "4.57.1", "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.57.1.tgz", @@ -7609,6 +8002,542 @@ "tslib": "2" } }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-directive": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-4.0.0.tgz", + "integrity": "sha512-/C2nqVmXXmiseSSuCdItCMho7ybwwop6RrrRPk0KbOHW21JKoCldC+8rFOaundDoRBUWBnJJcxeA/Kvi34WQXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "parse-entities": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "dev": true, + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-math": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-3.1.0.tgz", + "integrity": "sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/katex": "^0.16.0", + "devlop": "^1.0.0", + "katex": "^0.16.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, "node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -8369,6 +9298,26 @@ "node": "^20.17.0 || >=22.9.0" } }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", @@ -8450,13 +9399,14 @@ "license": "ISC" }, "node_modules/path-to-regexp": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", - "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.1.tgz", + "integrity": "sha512-fvU78fIjZ+SBM9YwCknCvKOUKkLVqtWDVctl0s7xIqfmfb38t2TT4ZU2gHm+Z8xGwgW+QWEU3oQSAzIbo89Ggw==", "dev": true, "license": "MIT", - "engines": { - "node": ">=16" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/pathe": { @@ -8483,6 +9433,19 @@ "dev": true, "license": "ISC" }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", @@ -8641,6 +9604,16 @@ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "license": "MIT" }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/q": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", @@ -8974,6 +9947,22 @@ "fsevents": "~2.3.2" } }, + "node_modules/run-con": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/run-con/-/run-con-1.3.2.tgz", + "integrity": "sha512-CcfE+mYiTcKEzg0IqS08+efdnH0oJ3zV0wSUFBNrMHMuxCtXvBCLzCJHatwuXDcu/RlhjTziTo/a1ruQik6/Yg==", + "dev": true, + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~4.1.0", + "minimist": "^1.2.8", + "strip-json-comments": "~3.1.1" + }, + "bin": { + "run-con": "cli.js" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -9106,6 +10095,19 @@ "npm": ">= 3.0.0" } }, + "node_modules/smol-toml": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.6.1.tgz", + "integrity": "sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 18" + }, + "funding": { + "url": "https://github.com/sponsors/cyyynthia" + } + }, "node_modules/socks": { "version": "2.8.7", "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", @@ -9414,6 +10416,19 @@ "node": ">=8" } }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/strip-literal": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.0.0.tgz", @@ -9508,9 +10523,9 @@ } }, "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", "dev": true, "license": "MIT", "dependencies": { @@ -9626,19 +10641,6 @@ } } }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/tinypool": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", @@ -9780,6 +10782,13 @@ "node": ">=14.17" } }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true, + "license": "MIT" + }, "node_modules/uglify-js": { "version": "3.19.3", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", @@ -10464,19 +11473,6 @@ } } }, - "node_modules/vite/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/vitest": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", @@ -10550,19 +11546,6 @@ } } }, - "node_modules/vitest/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/walk-up-path": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-4.0.0.tgz", diff --git a/package.json b/package.json index b6a2c54..366a0e1 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,8 @@ "version": "6.0.0", "scripts": { "build": "tsc", - "lint": "biome check --error-on-warnings ./src", - "lint:fix": "biome check --write ./src", + "lint": "biome check --error-on-warnings ./src && markdownlint -i node_modules -i CHANGELOG.md '**/*.md'", + "lint:fix": "biome check --write ./src && markdownlint -i node_modules -i CHANGELOG.md -f '**/*.md'", "package": "esbuild src/index.ts --bundle --platform=node --target=node24 --outfile=dist/index.js && esbuild src/cleanup/index.ts --bundle --platform=node --target=node24 --outfile=dist/cleanup/index.js && npm run license", "test": "npm run lint && vitest run && npm run build", "clean": "del-cli coverage test-reports node_modules", @@ -26,6 +26,7 @@ "esbuild": "^0.27.4", "generate-license-file": "^4.1.1", "json-schema": "^0.4.0", + "markdownlint-cli": "^0.48.0", "memfs": "^4.57.1", "standard-version": "^9.5.0", "typescript": "^6.0.2", diff --git a/src/cleanup/index.ts b/src/cleanup/index.ts index 81bbdb5..9b1fdcb 100644 --- a/src/cleanup/index.ts +++ b/src/cleanup/index.ts @@ -14,7 +14,8 @@ import { errorMessage, getBooleanInput } from '../helpers'; export function cleanup() { // Only attempt to change environment variables if we changed them in the first place - if (getBooleanInput('output-env-credentials', { required: false, default: true })) { + const awsProfile = core.getInput('aws-profile', { required: false }); + if (getBooleanInput('output-env-credentials', { required: false, default: !awsProfile })) { try { // The GitHub Actions toolkit does not have an option to completely unset // environment variables, so we overwrite the current value with an empty @@ -25,6 +26,9 @@ export function cleanup() { core.exportVariable('AWS_SESSION_TOKEN', ''); core.exportVariable('AWS_DEFAULT_REGION', ''); core.exportVariable('AWS_REGION', ''); + if (core.getInput('aws-profile')) { + core.exportVariable('AWS_PROFILE', ''); + } } catch (error) { core.setFailed(errorMessage(error)); } diff --git a/src/helpers.ts b/src/helpers.ts index a54befb..82e25d5 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -30,6 +30,7 @@ export function translateEnvVariables() { 'SPECIAL_CHARACTERS_WORKAROUND', 'USE_EXISTING_CREDENTIALS', 'NO_PROXY', + 'OVERWRITE_AWS_PROFILE', ]; // Treat HTTPS_PROXY as HTTP_PROXY. Precedence is HTTPS_PROXY > HTTP_PROXY if (process.env.HTTPS_PROXY) process.env.HTTP_PROXY = process.env.HTTPS_PROXY; diff --git a/src/index.ts b/src/index.ts index 541352c..5ea2a32 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,6 +14,7 @@ import { unsetCredentials, verifyKeys, } from './helpers'; +import { writeProfileFiles } from './profileManager'; const DEFAULT_ROLE_DURATION = 3600; // One hour (seconds) const ROLE_SESSION_NAME = 'GitHubActions'; @@ -29,6 +30,8 @@ export async function run() { const sessionTokenInput = core.getInput('aws-session-token', { required: false }); const SessionToken = sessionTokenInput === '' ? undefined : sessionTokenInput; const region = core.getInput('aws-region', { required: true }); + const awsProfile = core.getInput('aws-profile', { required: false }); + const overwriteAwsProfile = getBooleanInput('overwrite-aws-profile', { required: false }); const roleToAssume = core.getInput('role-to-assume', { required: false }); const audience = core.getInput('audience', { required: false }); const maskAccountId = getBooleanInput('mask-aws-account-id', { required: false }); @@ -46,7 +49,9 @@ export async function run() { }); const roleChaining = getBooleanInput('role-chaining', { required: false }); const outputCredentials = getBooleanInput('output-credentials', { required: false }); - const outputEnvCredentials = getBooleanInput('output-env-credentials', { required: false, default: true }); + // Default to always outputting environment credentials unless profile is specified. If profile is specified, default to + // no environment credentials (but still output them if the user specifically requests it). + const outputEnvCredentials = getBooleanInput('output-env-credentials', { required: false, default: !awsProfile }); const unsetCurrentCredentials = getBooleanInput('unset-current-credentials', { required: false }); let disableRetry = getBooleanInput('disable-retry', { required: false }); const specialCharacterWorkaround = getBooleanInput('special-characters-workaround', { required: false }); @@ -118,6 +123,7 @@ export async function run() { if (!region.match(REGION_REGEX)) { throw new Error(`Region is not valid: ${region}`); } + exportRegion(region, outputEnvCredentials); // Instantiate credentials client @@ -165,6 +171,12 @@ export async function run() { // the source credentials to already be masked as secrets // in any error messages. exportCredentials({ AccessKeyId, SecretAccessKey, SessionToken }, outputCredentials, outputEnvCredentials); + + // If using IAM User Credentials, write to profile now so that the assumeRole call can succeed (and also for + // credential validation before role assumption). + if (awsProfile) { + writeProfileFiles(awsProfile, { AccessKeyId, SecretAccessKey, SessionToken }, region, overwriteAwsProfile); + } } else if (!webIdentityTokenFile && !roleChaining) { // Proceed only if credentials can be picked up await credentialsClient.validateCredentials(undefined, roleChaining, expectedAccountIds); @@ -178,7 +190,6 @@ export async function run() { await credentialsClient.validateCredentials(AccessKeyId, roleChaining, expectedAccountIds); sourceAccountId = await exportAccountId(credentialsClient, maskAccountId); } - // Get role credentials if configured to do so if (roleToAssume) { let roleCredentials: AssumeRoleCommandOutput; @@ -210,7 +221,9 @@ export async function run() { // First: self-hosted runners. If the GITHUB_ACTIONS environment variable // is set to `true` then we are NOT in a self-hosted runner. // Second: Customer provided credentials manually (IAM User keys stored in GH Secrets) - if (!process.env.GITHUB_ACTIONS || AccessKeyId) { + // If we are using a profile, don't validate credentials yet (since they most likely won't be in the environment). + // Wait until after creds are written to the profile file to try validation. + if ((!process.env.GITHUB_ACTIONS || AccessKeyId) && !awsProfile) { await credentialsClient.validateCredentials( roleCredentials.Credentials?.AccessKeyId, roleChaining, @@ -220,8 +233,38 @@ export async function run() { if (outputEnvCredentials) { await exportAccountId(credentialsClient, maskAccountId); } + + // Write profile files if profile mode is enabled + if (awsProfile) { + if (!roleCredentials.Credentials) { + throw new Error('AssumeRole call succeeded but returned no credentials'); + } + // If user provided IAM User Credentials and then we assumed a role, overwrite the profile file to add + // the session token. (this only overwrites the profile within a single run of the action). + // We then validate the credentials to make sure they work. + if (AccessKeyId || !process.env.GITHUB_ACTIONS) { + writeProfileFiles(awsProfile, roleCredentials.Credentials, region, true); + await credentialsClient.validateCredentials( + roleCredentials.Credentials.AccessKeyId, + roleChaining, + expectedAccountIds, + ); + } else { + writeProfileFiles(awsProfile, roleCredentials.Credentials, region, overwriteAwsProfile); + } + + if (outputEnvCredentials) { + core.exportVariable('AWS_PROFILE', awsProfile); + } + } } else { core.info('Proceeding with IAM user credentials'); + + if (awsProfile) { + if (outputEnvCredentials) { + core.exportVariable('AWS_PROFILE', awsProfile); + } + } } // Clear timeout on successful completion diff --git a/src/profileManager.ts b/src/profileManager.ts new file mode 100644 index 0000000..d98db43 --- /dev/null +++ b/src/profileManager.ts @@ -0,0 +1,214 @@ +import * as fs from 'node:fs'; +import * as os from 'node:os'; +import * as path from 'node:path'; +import * as core from '@actions/core'; +import type { Credentials } from '@aws-sdk/client-sts'; + +/** + * Parse an INI-format string into a nested object. + * Preserves literal section names (e.g. "profile dev" stays as-is). + */ +export function parseIni(iniData: string): Record> { + const result: Record> = {}; + let currentSection: string | undefined; + + for (const line of iniData.split(/\r?\n/)) { + const trimmed = line.trim(); + if (!trimmed || trimmed.startsWith(';') || trimmed.startsWith('#')) { + continue; + } + + const sectionMatch = trimmed.match(/^\[([^\]]*)\]$/); + if (sectionMatch) { + currentSection = sectionMatch[1] as string; + if (currentSection === '__proto__') { + currentSection = undefined; + continue; + } + result[currentSection] = result[currentSection] || {}; + continue; + } + + if (currentSection) { + const eqIndex = trimmed.indexOf('='); + if (eqIndex > 0) { + const key = trimmed.substring(0, eqIndex).trim(); + const value = trimmed.substring(eqIndex + 1).trim(); + if (key !== '__proto__') { + const section = result[currentSection]; + if (section) { + section[key] = value; + } + } + } + } + } + + return result; +} + +/** + * Serialize a nested object into INI-format string. + */ +export function stringifyIni(data: Record>): string { + const sections: string[] = []; + for (const [sectionName, sectionData] of Object.entries(data)) { + const lines: string[] = [`[${sectionName}]`]; + for (const [key, value] of Object.entries(sectionData)) { + lines.push(`${key} = ${value}`); + } + sections.push(lines.join('\n')); + } + return `${sections.join('\n\n')}\n`; +} + +interface ProfileFilePaths { + credentials: string; + config: string; +} + +/** + * Get the file paths for AWS credentials and config files + * Respects AWS_SHARED_CREDENTIALS_FILE and AWS_CONFIG_FILE environment variables + */ +export function getProfileFilePaths(): ProfileFilePaths { + const credentialsPath = process.env.AWS_SHARED_CREDENTIALS_FILE || path.join(os.homedir(), '.aws', 'credentials'); + const configPath = process.env.AWS_CONFIG_FILE || path.join(os.homedir(), '.aws', 'config'); + + return { + credentials: credentialsPath, + config: configPath, + }; +} + +/** + * Ensure the AWS directory exists with secure permissions + * Creates the directory with 700 permissions (rwx for owner only) + */ +export function ensureAwsDirectoryExists(filePath: string): void { + const dir = path.dirname(filePath); + if (!fs.existsSync(dir)) { + core.debug(`Creating directory: ${dir}`); + fs.mkdirSync(dir, { recursive: true, mode: 0o700 }); + } +} + +/** + * Validate profile name format + * Profile names must be non-empty, contain no whitespace, brackets, or path separators + */ +export function validateProfileName(profileName: string): void { + if (!profileName || profileName.trim() === '') { + throw new Error('aws-profile must not be empty'); + } + + if (/\s/.test(profileName)) { + throw new Error('aws-profile must not contain whitespace'); + } + + // INI section names can't contain brackets + if (/[[\]]/.test(profileName)) { + throw new Error('aws-profile must not contain brackets'); + } + + // Prevent path traversal + if (profileName.includes('/') || profileName.includes('\\')) { + throw new Error('aws-profile must not contain path separators'); + } +} + +/** + * Merge a profile section into an INI file + * Reads existing file, updates the specified section, and writes back + */ +export function mergeProfileSection( + filePath: string, + sectionName: string, + data: Record, + overwriteAwsProfile: boolean, +): void { + let existingContent: Record> = {}; + + // Read existing file if it exists + if (fs.existsSync(filePath)) { + core.debug(`Reading existing file: ${filePath}`); + const fileContent = fs.readFileSync(filePath, 'utf-8'); + existingContent = parseIni(fileContent); + } + + if (existingContent[sectionName] && !overwriteAwsProfile) { + throw new Error( + `Profile with name "${sectionName}" already exists. Please use the overwrite-aws-profile input if you want to overwrite existing profiles.`, + ); + } + // Merge: update existing profile or add new one + existingContent[sectionName] = data; + + const content = stringifyIni(existingContent); + + core.debug(`Writing profile to ${filePath}`); + fs.writeFileSync(filePath, content, { mode: 0o600 }); +} + +/** + * Write AWS profile files with credentials and configuration + * This is the main entry point for profile file operations + * + * @param profileName - Name of the AWS profile to configure + * @param credentials - AWS credentials (access key, secret key, session token) + * @param region - AWS region + */ +export function writeProfileFiles( + profileName: string, + credentials: Partial, + region: string, + overwriteAwsProfile: boolean, +): void { + try { + // Validate profile name + validateProfileName(profileName); + + const paths = getProfileFilePaths(); + + // Ensure .aws directory exists + ensureAwsDirectoryExists(paths.credentials); + ensureAwsDirectoryExists(paths.config); + + // Prepare credentials data + const credentialsData: Record = {}; + if (credentials.AccessKeyId) { + credentialsData.aws_access_key_id = credentials.AccessKeyId; + } + if (credentials.SecretAccessKey) { + credentialsData.aws_secret_access_key = credentials.SecretAccessKey; + } + if (credentials.SessionToken) { + credentialsData.aws_session_token = credentials.SessionToken; + } + + // Credentials file uses [profileName] syntax + const credsSectionName = profileName; + + // Config file uses [profile profileName] syntax, except for 'default' + const configSectionName = profileName === 'default' ? 'default' : `profile ${profileName}`; + + // Prepare config data + const configData: Record = { + region: region, + }; + + // Write to credentials file + core.info(`Writing credentials to profile: ${profileName}`); + mergeProfileSection(paths.credentials, credsSectionName, credentialsData, overwriteAwsProfile); + + // Write to config file + core.info(`Writing config to profile: ${profileName}`); + mergeProfileSection(paths.config, configSectionName, configData, overwriteAwsProfile); + + core.info(`✓ Successfully configured AWS profile: ${profileName}`); + } catch (error) { + throw new Error( + `Failed to write AWS profile '${profileName}': ${error instanceof Error ? error.message : String(error)}`, + ); + } +} diff --git a/test/cleanup.test.ts b/test/cleanup.test.ts index 02b2468..74da1e7 100644 --- a/test/cleanup.test.ts +++ b/test/cleanup.test.ts @@ -38,6 +38,26 @@ describe('Configure AWS Credentials cleanup', {}, () => { expect(core.exportVariable).toHaveBeenCalledWith('AWS_DEFAULT_REGION', ''); expect(core.exportVariable).toHaveBeenCalledWith('AWS_REGION', ''); }); + it('also clears AWS_PROFILE when aws-profile was set', {}, () => { + vi.spyOn(core, 'getInput').mockImplementation((name: string) => { + if (name === 'aws-profile') return 'my-profile'; + if (name === 'output-env-credentials') return 'true'; + return ''; + }); + cleanup(); + expect(core.setFailed).toHaveBeenCalledTimes(0); + expect(core.exportVariable).toHaveBeenCalledTimes(6); + expect(core.exportVariable).toHaveBeenCalledWith('AWS_PROFILE', ''); + }); + it('skips env cleanup when aws-profile is set without output-env-credentials', {}, () => { + vi.spyOn(core, 'getInput').mockImplementation((name: string) => { + if (name === 'aws-profile') return 'my-profile'; + return ''; + }); + cleanup(); + expect(core.setFailed).toHaveBeenCalledTimes(0); + expect(core.exportVariable).toHaveBeenCalledTimes(0); + }); it('handles errors', {}, () => { vi.spyOn(core, 'exportVariable').mockImplementationOnce(() => { throw new Error('Test error'); diff --git a/test/helpers.test.ts b/test/helpers.test.ts index 7e96f04..98f8b71 100644 --- a/test/helpers.test.ts +++ b/test/helpers.test.ts @@ -1,11 +1,11 @@ -import { beforeEach } from 'node:test'; import * as core from '@actions/core'; -import { describe, expect, it, vi } from 'vitest'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; import * as helpers from '../src/helpers'; describe('Configure AWS Credentials helpers', {}, () => { beforeEach(() => { vi.restoreAllMocks(); + vi.spyOn(core, 'debug').mockImplementation(() => {}); }); it('removes brackets from GitHub Actor', {}, () => { const actor = 'actor[bot]'; @@ -91,6 +91,7 @@ describe('Configure AWS Credentials helpers', {}, () => { }); it('clears session token when not provided', {}, () => { + vi.spyOn(core, 'setSecret').mockImplementation(() => {}); vi.spyOn(core, 'exportVariable').mockImplementation(() => {}); process.env.AWS_SESSION_TOKEN = 'old-token'; helpers.exportCredentials({ AccessKeyId: 'test', SecretAccessKey: 'test' }, false, true); diff --git a/test/index.test.ts b/test/index.test.ts index 6df229d..19f8b93 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -10,6 +10,7 @@ import { fs, vol } from 'memfs'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { CredentialsClient } from '../src/CredentialsClient'; import { run } from '../src/index'; +import * as profileManager from '../src/profileManager'; import mocks from './mockinputs.test'; const mockedSTSClient = mockClient(STSClient); @@ -822,4 +823,202 @@ describe('Configure AWS Credentials', {}, () => { expect(core.setFailed).not.toHaveBeenCalled(); }); }); + + describe('AWS Profile Support', {}, () => { + beforeEach(() => { + vi.clearAllMocks(); + mockedSTSClient.reset(); + vi.mock('node:fs'); + vol.reset(); + }); + + it('writes profile files with OIDC authentication', async () => { + vi.spyOn(core, 'getInput').mockImplementation( + mocks.getInput({ + ...mocks.GH_OIDC_INPUTS, + 'aws-profile': 'dev', + }), + ); + vi.spyOn(core, 'getIDToken').mockResolvedValue('testoidctoken'); + mockedSTSClient.on(AssumeRoleWithWebIdentityCommand).resolves(mocks.outputs.STS_CREDENTIALS); + mockedSTSClient.on(GetCallerIdentityCommand).resolves({ ...mocks.outputs.GET_CALLER_IDENTITY }); + process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN = 'fake-token'; + + await run(); + + // Verify credentials were NOT exported to environment variables + expect(core.exportVariable).not.toHaveBeenCalledWith('AWS_ACCESS_KEY_ID', expect.anything()); + expect(core.exportVariable).not.toHaveBeenCalledWith('AWS_SECRET_ACCESS_KEY', expect.anything()); + expect(core.exportVariable).not.toHaveBeenCalledWith('AWS_SESSION_TOKEN', expect.anything()); + expect(core.exportVariable).not.toHaveBeenCalledWith('AWS_PROFILE', expect.anything()); + expect(core.exportVariable).not.toHaveBeenCalledWith('AWS_REGION', expect.anything()); + expect(core.exportVariable).not.toHaveBeenCalledWith('AWS_DEFAULT_REGION', expect.anything()); + + // Verify profile files were written + expect(core.info).toHaveBeenCalledWith('Writing credentials to profile: dev'); + expect(core.info).toHaveBeenCalledWith('Writing config to profile: dev'); + expect(core.info).toHaveBeenCalledWith('✓ Successfully configured AWS profile: dev'); + expect(core.setFailed).not.toHaveBeenCalled(); + }); + + it('writes profile files with IAM user credentials', async () => { + vi.spyOn(core, 'getInput').mockImplementation( + mocks.getInput({ + ...mocks.IAM_USER_INPUTS, + 'aws-profile': 'production', + }), + ); + mockedSTSClient.on(GetCallerIdentityCommand).resolves({ ...mocks.outputs.GET_CALLER_IDENTITY }); + // biome-ignore lint/suspicious/noExplicitAny: any required to mock private method + vi.spyOn(CredentialsClient.prototype as any, 'loadCredentials').mockResolvedValue({ + accessKeyId: 'MYAWSACCESSKEYID', + }); + + await run(); + + // Verify credentials were NOT exported to environment variables + expect(core.exportVariable).not.toHaveBeenCalledWith('AWS_ACCESS_KEY_ID', expect.anything()); + expect(core.exportVariable).not.toHaveBeenCalledWith('AWS_SECRET_ACCESS_KEY', expect.anything()); + expect(core.exportVariable).not.toHaveBeenCalledWith('AWS_SESSION_TOKEN', expect.anything()); + expect(core.exportVariable).not.toHaveBeenCalledWith('AWS_PROFILE', expect.anything()); + expect(core.exportVariable).not.toHaveBeenCalledWith('AWS_REGION', expect.anything()); + expect(core.exportVariable).not.toHaveBeenCalledWith('AWS_DEFAULT_REGION', expect.anything()); + + // Verify profile files were written + expect(core.info).toHaveBeenCalledWith('Writing credentials to profile: production'); + expect(core.info).toHaveBeenCalledWith('Writing config to profile: production'); + expect(core.info).toHaveBeenCalledWith('✓ Successfully configured AWS profile: production'); + expect(core.setFailed).not.toHaveBeenCalled(); + }); + + it('writes profile files with IAM user role assumption', async () => { + vi.spyOn(core, 'getInput').mockImplementation( + mocks.getInput({ + ...mocks.IAM_ASSUMEROLE_INPUTS, + 'aws-profile': 'assumed-role', + }), + ); + mockedSTSClient.on(AssumeRoleCommand).resolves(mocks.outputs.STS_CREDENTIALS); + mockedSTSClient.on(GetCallerIdentityCommand).resolves({ ...mocks.outputs.GET_CALLER_IDENTITY }); + // biome-ignore lint/suspicious/noExplicitAny: any required to mock private method + vi.spyOn(CredentialsClient.prototype as any, 'loadCredentials') + .mockResolvedValueOnce({ accessKeyId: 'MYAWSACCESSKEYID' }) + .mockResolvedValueOnce({ accessKeyId: 'STSAWSACCESSKEYID' }); + + vi.spyOn(profileManager, 'writeProfileFiles'); + await run(); + + // Verify credentials were NOT exported to environment variables + expect(core.exportVariable).not.toHaveBeenCalledWith('AWS_ACCESS_KEY_ID', expect.anything()); + expect(core.exportVariable).not.toHaveBeenCalledWith('AWS_SECRET_ACCESS_KEY', expect.anything()); + expect(core.exportVariable).not.toHaveBeenCalledWith('AWS_SESSION_TOKEN', expect.anything()); + expect(core.exportVariable).not.toHaveBeenCalledWith('AWS_PROFILE', expect.anything()); + expect(core.exportVariable).not.toHaveBeenCalledWith('AWS_REGION', expect.anything()); + expect(core.exportVariable).not.toHaveBeenCalledWith('AWS_DEFAULT_REGION', expect.anything()); + + // Verify profile files were written + expect(core.info).toHaveBeenCalledWith('Writing credentials to profile: assumed-role'); + expect(core.info).toHaveBeenCalledWith('Writing config to profile: assumed-role'); + expect(core.info).toHaveBeenCalledWith('✓ Successfully configured AWS profile: assumed-role'); + + // Verify profile files were written twice (first to write access key id and access key, second to write + // actual session token after role assumption + expect(profileManager.writeProfileFiles).toHaveBeenCalledTimes(2); + + expect(core.setFailed).not.toHaveBeenCalled(); + }); + + it('respects output-env-credentials=true with profiles', async () => { + vi.spyOn(core, 'getInput').mockImplementation( + mocks.getInput({ + ...mocks.GH_OIDC_INPUTS, + 'aws-profile': 'dev', + 'output-env-credentials': 'true', + }), + ); + vi.spyOn(core, 'getIDToken').mockResolvedValue('testoidctoken'); + mockedSTSClient.on(AssumeRoleWithWebIdentityCommand).resolves(mocks.outputs.STS_CREDENTIALS); + mockedSTSClient.on(GetCallerIdentityCommand).resolves({ ...mocks.outputs.GET_CALLER_IDENTITY }); + process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN = 'fake-token'; + + await run(); + + // verify that env vars were exported + expect(core.exportVariable).toHaveBeenCalledWith('AWS_ACCESS_KEY_ID', 'STSAWSACCESSKEYID'); + expect(core.exportVariable).toHaveBeenCalledWith('AWS_SECRET_ACCESS_KEY', 'STSAWSSECRETACCESSKEY'); + expect(core.exportVariable).toHaveBeenCalledWith('AWS_SESSION_TOKEN', 'STSAWSSESSIONTOKEN'); + expect(core.exportVariable).toHaveBeenCalledWith('AWS_PROFILE', 'dev'); + expect(core.exportVariable).toHaveBeenCalledWith('AWS_REGION', 'fake-region-1'); + expect(core.exportVariable).toHaveBeenCalledWith('AWS_DEFAULT_REGION', 'fake-region-1'); + + // Verify profile files were still written + expect(core.info).toHaveBeenCalledWith('Writing credentials to profile: dev'); + expect(core.info).toHaveBeenCalledWith('Writing config to profile: dev'); + expect(core.info).toHaveBeenCalledWith('✓ Successfully configured AWS profile: dev'); + expect(core.setFailed).not.toHaveBeenCalled(); + }); + + it('maintains backward compatibility when aws-profile is not specified', async () => { + vi.spyOn(core, 'getInput').mockImplementation(mocks.getInput(mocks.GH_OIDC_INPUTS)); + vi.spyOn(core, 'getIDToken').mockResolvedValue('testoidctoken'); + mockedSTSClient.on(AssumeRoleWithWebIdentityCommand).resolves(mocks.outputs.STS_CREDENTIALS); + mockedSTSClient.on(GetCallerIdentityCommand).resolves({ ...mocks.outputs.GET_CALLER_IDENTITY }); + process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN = 'fake-token'; + + await run(); + + // Verify credentials WERE exported to environment variables (backward compatibility) + expect(core.exportVariable).toHaveBeenCalledWith('AWS_ACCESS_KEY_ID', 'STSAWSACCESSKEYID'); + expect(core.exportVariable).toHaveBeenCalledWith('AWS_SECRET_ACCESS_KEY', 'STSAWSSECRETACCESSKEY'); + expect(core.exportVariable).toHaveBeenCalledWith('AWS_SESSION_TOKEN', 'STSAWSSESSIONTOKEN'); + expect(core.exportVariable).toHaveBeenCalledWith('AWS_REGION', 'fake-region-1'); + expect(core.exportVariable).toHaveBeenCalledWith('AWS_DEFAULT_REGION', 'fake-region-1'); + + // Verify AWS_PROFILE was NOT exported + expect(core.exportVariable).not.toHaveBeenCalledWith('AWS_PROFILE', expect.anything()); + + // Verify profile files were NOT written + expect(core.info).not.toHaveBeenCalledWith(expect.stringContaining('Writing credentials to profile')); + expect(core.info).not.toHaveBeenCalledWith(expect.stringContaining('✓ Successfully configured AWS profile:')); + expect(core.setFailed).not.toHaveBeenCalled(); + }); + + it('handles default profile correctly', async () => { + vi.spyOn(core, 'getInput').mockImplementation( + mocks.getInput({ + ...mocks.GH_OIDC_INPUTS, + 'aws-profile': 'default', + }), + ); + vi.spyOn(core, 'getIDToken').mockResolvedValue('testoidctoken'); + mockedSTSClient.on(AssumeRoleWithWebIdentityCommand).resolves(mocks.outputs.STS_CREDENTIALS); + mockedSTSClient.on(GetCallerIdentityCommand).resolves({ ...mocks.outputs.GET_CALLER_IDENTITY }); + process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN = 'fake-token'; + + await run(); + + // Verify profile files were written for 'default' profile + expect(core.info).toHaveBeenCalledWith('Writing credentials to profile: default'); + expect(core.info).toHaveBeenCalledWith('Writing config to profile: default'); + expect(core.info).toHaveBeenCalledWith('✓ Successfully configured AWS profile: default'); + expect(core.setFailed).not.toHaveBeenCalled(); + }); + + it('rejects invalid profile names with whitespace', async () => { + vi.spyOn(core, 'getInput').mockImplementation( + mocks.getInput({ + ...mocks.GH_OIDC_INPUTS, + 'aws-profile': 'invalid profile', + }), + ); + vi.spyOn(core, 'getIDToken').mockResolvedValue('testoidctoken'); + mockedSTSClient.on(AssumeRoleWithWebIdentityCommand).resolves(mocks.outputs.STS_CREDENTIALS); + mockedSTSClient.on(GetCallerIdentityCommand).resolves({ ...mocks.outputs.GET_CALLER_IDENTITY }); + process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN = 'fake-token'; + + await run(); + + expect(core.setFailed).toHaveBeenCalledWith(expect.stringContaining('whitespace')); + }); + }); }); diff --git a/test/profileManager.test.ts b/test/profileManager.test.ts new file mode 100644 index 0000000..8c935e6 --- /dev/null +++ b/test/profileManager.test.ts @@ -0,0 +1,724 @@ +import * as core from '@actions/core'; +import { fs, vol } from 'memfs'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { + ensureAwsDirectoryExists, + getProfileFilePaths, + mergeProfileSection, + parseIni, + stringifyIni, + validateProfileName, + writeProfileFiles, +} from '../src/profileManager'; + +describe('Profile Manager', {}, () => { + beforeEach(() => { + vi.restoreAllMocks(); + vi.mock('node:fs'); + vol.reset(); + vi.spyOn(core, 'debug').mockImplementation(() => {}); + vi.spyOn(core, 'info').mockImplementation(() => {}); + }); + + describe('parseIni', {}, () => { + it('parses a single section', {}, () => { + const result = parseIni('[dev]\naws_access_key_id=AKIA\naws_secret_access_key=secret\n'); + expect(result.dev).toEqual({ aws_access_key_id: 'AKIA', aws_secret_access_key: 'secret' }); + }); + + it('parses multiple sections', {}, () => { + const result = parseIni('[dev]\nkey=dev_val\n\n[prod]\nkey=prod_val\n'); + expect(result.dev.key).toBe('dev_val'); + expect(result.prod.key).toBe('prod_val'); + }); + + it('skips comments and empty lines', {}, () => { + const result = parseIni('# comment\n; another comment\n\n[dev]\nkey=val\n'); + expect(result.dev).toEqual({ key: 'val' }); + }); + + it('trims whitespace around keys and values', {}, () => { + const result = parseIni('[dev]\n key = val \n'); + expect(result.dev.key).toBe('val'); + }); + + it('preserves section names with spaces (e.g. profile prefix)', {}, () => { + const result = parseIni('[profile dev]\nregion=us-east-1\n'); + expect(result['profile dev']).toEqual({ region: 'us-east-1' }); + }); + + it('guards against __proto__ section pollution', {}, () => { + const result = parseIni('[__proto__]\npolluted=true\n[safe]\nkey=val\n'); + expect(result.__proto__).not.toHaveProperty('polluted'); + expect(result.safe).toEqual({ key: 'val' }); + }); + + it('guards against __proto__ key pollution', {}, () => { + const result = parseIni('[dev]\n__proto__=evil\naws_access_key_id=AKIA\n'); + expect(result.dev).toEqual({ aws_access_key_id: 'AKIA' }); + expect(result.dev).not.toHaveProperty('__proto__', 'evil'); + }); + + it('handles values containing equals signs', {}, () => { + const result = parseIni('[dev]\naws_session_token=FwoGZXIvYXdzEBYa/base64==\n'); + expect(result.dev.aws_session_token).toBe('FwoGZXIvYXdzEBYa/base64=='); + }); + + it('handles empty values', {}, () => { + const result = parseIni('[dev]\ncli_pager=\n'); + expect(result.dev.cli_pager).toBe(''); + }); + + it('returns empty object for empty input', {}, () => { + expect(parseIni('')).toEqual({}); + }); + + it('returns empty object for whitespace-only input', {}, () => { + expect(parseIni(' \n\n \n')).toEqual({}); + }); + + it('handles Windows line endings (CRLF)', {}, () => { + const result = parseIni('[dev]\r\naws_access_key_id=AKIA\r\naws_secret_access_key=secret\r\n'); + expect(result.dev).toEqual({ aws_access_key_id: 'AKIA', aws_secret_access_key: 'secret' }); + }); + }); + + describe('stringifyIni', {}, () => { + it('serializes a single section', {}, () => { + const result = stringifyIni({ dev: { key: 'val' } }); + expect(result).toBe('[dev]\nkey = val\n'); + }); + + it('serializes multiple sections with blank line separator', {}, () => { + const result = stringifyIni({ dev: { a: '1' }, prod: { b: '2' } }); + expect(result).toBe('[dev]\na = 1\n\n[prod]\nb = 2\n'); + }); + + it('round-trips through parseIni', {}, () => { + const data = { dev: { aws_access_key_id: 'AKIA', aws_secret_access_key: 'secret' }, 'profile prod': { region: 'us-west-2' } }; + const roundTripped = parseIni(stringifyIni(data)); + expect(roundTripped).toEqual(data); + }); + + it('handles empty data object', {}, () => { + const result = stringifyIni({}); + expect(result).toBe('\n'); + expect(parseIni(result)).toEqual({}); + }); + + it('handles section with no keys', {}, () => { + const result = stringifyIni({ dev: {} }); + expect(result).toBe('[dev]\n'); + }); + }); + + describe('validateProfileName', {}, () => { + it('accepts valid profile names', {}, () => { + expect(() => validateProfileName('dev')).not.toThrow(); + expect(() => validateProfileName('production')).not.toThrow(); + expect(() => validateProfileName('my-profile-123')).not.toThrow(); + expect(() => validateProfileName('default')).not.toThrow(); + }); + + it('rejects empty profile names', {}, () => { + expect(() => validateProfileName('')).toThrow('aws-profile must not be empty'); + expect(() => validateProfileName(' ')).toThrow('aws-profile must not be empty'); + }); + + it('rejects profile names with whitespace', {}, () => { + expect(() => validateProfileName('my profile')).toThrow('aws-profile must not contain whitespace'); + expect(() => validateProfileName('dev\ntest')).toThrow('aws-profile must not contain whitespace'); + expect(() => validateProfileName('prod\tenv')).toThrow('aws-profile must not contain whitespace'); + }); + + it('rejects profile names with brackets', {}, () => { + expect(() => validateProfileName('dev[test]')).toThrow('aws-profile must not contain brackets'); + expect(() => validateProfileName('[profile]')).toThrow('aws-profile must not contain brackets'); + }); + + it('rejects profile names with path separators', {}, () => { + expect(() => validateProfileName('dev/test')).toThrow('aws-profile must not contain path separators'); + expect(() => validateProfileName('dev\\test')).toThrow('aws-profile must not contain path separators'); + expect(() => validateProfileName('../etc/passwd')).toThrow('aws-profile must not contain path separators'); + }); + }); + + describe('getProfileFilePaths', {}, () => { + it('returns default paths when env vars not set', {}, () => { + delete process.env.AWS_SHARED_CREDENTIALS_FILE; + delete process.env.AWS_CONFIG_FILE; + + const paths = getProfileFilePaths(); + + expect(paths.credentials).toMatch(/\.aws[/\\]credentials$/); + expect(paths.config).toMatch(/\.aws[/\\]config$/); + }); + + it('respects AWS_SHARED_CREDENTIALS_FILE env var', {}, () => { + process.env.AWS_SHARED_CREDENTIALS_FILE = '/custom/path/credentials'; + process.env.AWS_CONFIG_FILE = '/custom/path/config'; + + const paths = getProfileFilePaths(); + + expect(paths.credentials).toBe('/custom/path/credentials'); + expect(paths.config).toBe('/custom/path/config'); + }); + }); + + describe('ensureAwsDirectoryExists', {}, () => { + it('creates directory if it does not exist', {}, () => { + const filePath = '/home/runner/.aws/credentials'; + + ensureAwsDirectoryExists(filePath); + + expect(fs.existsSync('/home/runner/.aws')).toBe(true); + }); + + it('does not error if directory already exists', {}, () => { + const filePath = '/home/runner/.aws/credentials'; + + fs.mkdirSync('/home/runner/.aws', { recursive: true }); + + expect(() => ensureAwsDirectoryExists(filePath)).not.toThrow(); + }); + + it('creates nested directories', {}, () => { + const filePath = '/home/runner/custom/path/.aws/credentials'; + + ensureAwsDirectoryExists(filePath); + + expect(fs.existsSync('/home/runner/custom/path/.aws')).toBe(true); + }); + }); + + describe('mergeProfileSection', {}, () => { + it('creates new file with profile section', {}, () => { + const filePath = '/home/runner/.aws/credentials'; + fs.mkdirSync('/home/runner/.aws', { recursive: true }); + + mergeProfileSection(filePath, 'dev', { + aws_access_key_id: 'AKIAIOSFODNN7EXAMPLE', + aws_secret_access_key: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY', + }, false); + + const content = fs.readFileSync(filePath, 'utf-8'); + const parsed = parseIni(content); + + expect(parsed.dev).toBeDefined(); + expect(parsed.dev.aws_access_key_id).toBe('AKIAIOSFODNN7EXAMPLE'); + expect(parsed.dev.aws_secret_access_key).toBe('wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY'); + }); + + it('merges with existing profiles', {}, () => { + const filePath = '/home/runner/.aws/credentials'; + fs.mkdirSync('/home/runner/.aws', { recursive: true }); + + // Create initial profile + mergeProfileSection(filePath, 'dev', { + aws_access_key_id: 'AKIAIOSFODNN7EXAMPLE', + aws_secret_access_key: 'devSecretKey', + }, false); + + // Add second profile + mergeProfileSection(filePath, 'prod', { + aws_access_key_id: 'AKIAPRODEXAMPLE', + aws_secret_access_key: 'prodSecretKey', + }, false); + + const content = fs.readFileSync(filePath, 'utf-8'); + const parsed = parseIni(content); + + expect(parsed.dev).toBeDefined(); + expect(parsed.dev.aws_access_key_id).toBe('AKIAIOSFODNN7EXAMPLE'); + expect(parsed.prod).toBeDefined(); + expect(parsed.prod.aws_access_key_id).toBe('AKIAPRODEXAMPLE'); + }); + + it('overwrites existing profile with same name', {}, () => { + const filePath = '/home/runner/.aws/credentials'; + fs.mkdirSync('/home/runner/.aws', { recursive: true }); + + // Create initial profile + mergeProfileSection(filePath, 'dev', { + aws_access_key_id: 'OLD_KEY', + aws_secret_access_key: 'oldSecretKey', + aws_session_token: 'oldSessionToken' + }, false); + + // Overwrite with new credentials + mergeProfileSection(filePath, 'dev', { + aws_access_key_id: 'NEW_KEY', + aws_secret_access_key: 'newSecretKey', + aws_session_token: 'newSessionToken', + }, true); + + const content = fs.readFileSync(filePath, 'utf-8'); + const parsed = parseIni(content); + + expect(parsed.dev.aws_access_key_id).toBe('NEW_KEY'); + expect(parsed.dev.aws_secret_access_key).toBe('newSecretKey'); + expect(parsed.dev.aws_session_token).toBe('newSessionToken'); + }); + + it('overwriting a profile removes stale keys', {}, () => { + const filePath = '/home/runner/.aws/credentials'; + fs.mkdirSync('/home/runner/.aws', { recursive: true }); + + // Create profile with session token + mergeProfileSection(filePath, 'dev', { + aws_access_key_id: 'AKIA', + aws_secret_access_key: 'secret', + aws_session_token: 'old-token', + }, false); + + // Overwrite without session token + mergeProfileSection(filePath, 'dev', { + aws_access_key_id: 'AKIA2', + aws_secret_access_key: 'secret2', + }, true); + + const content = fs.readFileSync(filePath, 'utf-8'); + const parsed = parseIni(content); + + expect(parsed.dev.aws_access_key_id).toBe('AKIA2'); + expect(parsed.dev.aws_secret_access_key).toBe('secret2'); + expect(parsed.dev.aws_session_token).toBeUndefined(); + }); + + it('handles empty existing file', {}, () => { + const filePath = '/home/runner/.aws/credentials'; + fs.mkdirSync('/home/runner/.aws', { recursive: true }); + fs.writeFileSync(filePath, '', { mode: 0o600 }); + + mergeProfileSection(filePath, 'dev', { + aws_access_key_id: 'AKIA', + aws_secret_access_key: 'secret', + }, false); + + const content = fs.readFileSync(filePath, 'utf-8'); + const parsed = parseIni(content); + + expect(parsed.dev.aws_access_key_id).toBe('AKIA'); + expect(parsed.dev.aws_secret_access_key).toBe('secret'); + }); + + it('errors if profile name already exists but overwrite flag is false', {}, () => { + const filePath = '/home/runner/.aws/credentials'; + fs.mkdirSync('/home/runner/.aws', { recursive: true }); + + // Create initial profile + mergeProfileSection(filePath, 'dev', { + aws_access_key_id: 'OLD_KEY', + aws_secret_access_key: 'oldSecretKey', + }, false); + + // Overwrite with new credentials + expect(() => mergeProfileSection(filePath, 'dev', { + aws_access_key_id: 'NEW_KEY', + aws_secret_access_key: 'newSecretKey', + aws_session_token: 'sessionToken', + }, false)).toThrow(`Profile with name "dev" already exists. Please use the overwrite-aws-profile input if you want to overwrite existing profiles.`); + + const content = fs.readFileSync(filePath, 'utf-8'); + const parsed = parseIni(content); + + expect(parsed.dev.aws_access_key_id).toBe('OLD_KEY'); + expect(parsed.dev.aws_secret_access_key).toBe('oldSecretKey'); + expect(parsed.dev.aws_session_token).toBeUndefined(); + }); + }); + + describe('writeProfileFiles', {}, () => { + beforeEach(() => { + delete process.env.AWS_SHARED_CREDENTIALS_FILE; + delete process.env.AWS_CONFIG_FILE; + }); + + it('writes credentials and config for new profile', {}, () => { + writeProfileFiles( + 'dev', + { + AccessKeyId: 'AKIAIOSFODNN7EXAMPLE', + SecretAccessKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY', + SessionToken: 'FwoGZXIvYXdzEBYaDEXAMPLE', + }, + 'us-east-1', + false, + ); + + // Check credentials file + const credsPath = getProfileFilePaths().credentials; + const credContent = fs.readFileSync(credsPath, 'utf-8'); + const credParsed = parseIni(credContent); + + expect(credParsed.dev).toBeDefined(); + expect(credParsed.dev.aws_access_key_id).toBe('AKIAIOSFODNN7EXAMPLE'); + expect(credParsed.dev.aws_secret_access_key).toBe('wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY'); + expect(credParsed.dev.aws_session_token).toBe('FwoGZXIvYXdzEBYaDEXAMPLE'); + + // Check config file + const configPath = getProfileFilePaths().config; + const configContent = fs.readFileSync(configPath, 'utf-8'); + const configParsed = parseIni(configContent); + + expect(configParsed['profile dev']).toBeDefined(); + expect(configParsed['profile dev'].region).toBe('us-east-1'); + }); + + it('uses correct section naming for default profile', {}, () => { + writeProfileFiles( + 'default', + { + AccessKeyId: 'AKIAIOSFODNN7EXAMPLE', + SecretAccessKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY', + }, + 'us-west-2', + false, + ); + + // Check credentials file uses [default] + const credsPath = getProfileFilePaths().credentials; + const credContent = fs.readFileSync(credsPath, 'utf-8'); + const credParsed = parseIni(credContent); + + expect(credParsed.default).toBeDefined(); + expect(credParsed['profile default']).toBeUndefined(); + + // Check config file uses [default] (not [profile default]) + const configPath = getProfileFilePaths().config; + const configContent = fs.readFileSync(configPath, 'utf-8'); + const configParsed = parseIni(configContent); + + expect(configParsed.default).toBeDefined(); + expect(configParsed['profile default']).toBeUndefined(); + }); + + it('supports multiple profiles', {}, () => { + // Write first profile + writeProfileFiles( + 'dev', + { + AccessKeyId: 'AKIADEV', + SecretAccessKey: 'devSecret', + }, + 'us-east-1', + false, + ); + + // Write second profile + writeProfileFiles( + 'prod', + { + AccessKeyId: 'AKIAPROD', + SecretAccessKey: 'prodSecret', + SessionToken: 'prodToken', + }, + 'us-west-2', + false, + ); + + // Verify both profiles exist + const credsPath = getProfileFilePaths().credentials; + const credContent = fs.readFileSync(credsPath, 'utf-8'); + const credParsed = parseIni(credContent); + + expect(credParsed.dev).toBeDefined(); + expect(credParsed.prod).toBeDefined(); + expect(credParsed.dev.aws_access_key_id).toBe('AKIADEV'); + expect(credParsed.prod.aws_access_key_id).toBe('AKIAPROD'); + }); + + it('handles credentials without session token', {}, () => { + writeProfileFiles( + 'dev', + { + AccessKeyId: 'AKIAIOSFODNN7EXAMPLE', + SecretAccessKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY', + }, + 'us-east-1', + false, + ); + + const credsPath = getProfileFilePaths().credentials; + const credContent = fs.readFileSync(credsPath, 'utf-8'); + const credParsed = parseIni(credContent); + + expect(credParsed.dev.aws_access_key_id).toBe('AKIAIOSFODNN7EXAMPLE'); + expect(credParsed.dev.aws_secret_access_key).toBe('wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY'); + expect(credParsed.dev.aws_session_token).toBeUndefined(); + }); + + it('throws error for invalid profile name', {}, () => { + expect(() => + writeProfileFiles( + 'invalid profile', + { + AccessKeyId: 'AKIA', + SecretAccessKey: 'secret', + }, + 'us-east-1', + false, + ), + ).toThrow('Failed to write AWS profile'); + expect(() => + writeProfileFiles( + 'invalid profile', + { + AccessKeyId: 'AKIA', + SecretAccessKey: 'secret', + }, + 'us-east-1', + false + ), + ).toThrow('whitespace'); + }); + + it('respects custom file paths from env vars', {}, () => { + process.env.AWS_SHARED_CREDENTIALS_FILE = '/custom/credentials'; + process.env.AWS_CONFIG_FILE = '/custom/config'; + + fs.mkdirSync('/custom', { recursive: true }); + + writeProfileFiles( + 'dev', + { + AccessKeyId: 'AKIA', + SecretAccessKey: 'secret', + }, + 'us-east-1', + false + ); + + expect(fs.existsSync('/custom/credentials')).toBe(true); + expect(fs.existsSync('/custom/config')).toBe(true); + }); + + it('logs info messages', {}, () => { + writeProfileFiles( + 'dev', + { + AccessKeyId: 'AKIA', + SecretAccessKey: 'secret', + }, + 'us-east-1', + false + ); + + expect(core.info).toHaveBeenCalledWith('Writing credentials to profile: dev'); + expect(core.info).toHaveBeenCalledWith('Writing config to profile: dev'); + expect(core.info).toHaveBeenCalledWith('✓ Successfully configured AWS profile: dev'); + }); + + it('preserves pre-existing unrelated profiles in credentials file', {}, () => { + const credsPath = getProfileFilePaths().credentials; + fs.mkdirSync(require('node:path').dirname(credsPath), { recursive: true }); + fs.writeFileSync( + credsPath, + '[personal]\naws_access_key_id=AKIAPERSONAL\naws_secret_access_key=personalSecret\naws_session_token=personalToken\n', + ); + + writeProfileFiles( + 'dev', + { AccessKeyId: 'AKIADEV', SecretAccessKey: 'devSecret' }, + 'us-east-1', + false, + ); + + const content = fs.readFileSync(credsPath, 'utf-8'); + const parsed = parseIni(content); + + // Pre-existing profile must be fully intact + expect(parsed.personal).toEqual({ + aws_access_key_id: 'AKIAPERSONAL', + aws_secret_access_key: 'personalSecret', + aws_session_token: 'personalToken', + }); + // New profile also present + expect(parsed.dev.aws_access_key_id).toBe('AKIADEV'); + }); + + it('preserves pre-existing config with extra keys', {}, () => { + const configPath = getProfileFilePaths().config; + fs.mkdirSync(require('node:path').dirname(configPath), { recursive: true }); + fs.writeFileSync( + configPath, + '[profile personal]\nregion=eu-west-1\noutput=json\ncli_pager=\n', + ); + + writeProfileFiles( + 'dev', + { AccessKeyId: 'AKIA', SecretAccessKey: 'secret' }, + 'us-east-1', + false + ); + + const content = fs.readFileSync(configPath, 'utf-8'); + const parsed = parseIni(content); + + expect(parsed['profile personal']).toEqual({ + region: 'eu-west-1', + output: 'json', + cli_pager: '', + }); + expect(parsed['profile dev'].region).toBe('us-east-1'); + }); + + it('preserves pre-existing default profile when writing a named profile', {}, () => { + const credsPath = getProfileFilePaths().credentials; + fs.mkdirSync(require('node:path').dirname(credsPath), { recursive: true }); + fs.writeFileSync( + credsPath, + '[default]\naws_access_key_id=AKIADEFAULT\naws_secret_access_key=defaultSecret\n', + ); + + writeProfileFiles( + 'dev', + { AccessKeyId: 'AKIADEV', SecretAccessKey: 'devSecret' }, + 'us-west-2', + false + ); + + const content = fs.readFileSync(credsPath, 'utf-8'); + const parsed = parseIni(content); + + expect(parsed.default).toEqual({ + aws_access_key_id: 'AKIADEFAULT', + aws_secret_access_key: 'defaultSecret', + }); + expect(parsed.dev.aws_access_key_id).toBe('AKIADEV'); + }); + + it('comments in pre-existing files are stripped on round-trip', {}, () => { + const credsPath = getProfileFilePaths().credentials; + fs.mkdirSync(require('node:path').dirname(credsPath), { recursive: true }); + fs.writeFileSync( + credsPath, + '# My important comment\n[personal]\naws_access_key_id=AKIA\naws_secret_access_key=secret\n', + ); + + writeProfileFiles( + 'dev', + { AccessKeyId: 'AKIADEV', SecretAccessKey: 'devSecret' }, + 'us-east-1', + false + ); + + const content = fs.readFileSync(credsPath, 'utf-8') as string; + + // Comment is lost (known trade-off), but profile data is preserved + expect(content).not.toContain('# My important comment'); + const parsed = parseIni(content); + expect(parsed.personal.aws_access_key_id).toBe('AKIA'); + expect(parsed.dev.aws_access_key_id).toBe('AKIADEV'); + }); + + it('writes empty section when credentials object has no keys', {}, () => { + writeProfileFiles('dev', {}, 'us-east-1', false); + + const credsPath = getProfileFilePaths().credentials; + const credContent = fs.readFileSync(credsPath, 'utf-8'); + const credParsed = parseIni(credContent); + + // Section exists but has no credential keys + expect(credParsed.dev).toEqual({}); + + // Config still gets region + const configPath = getProfileFilePaths().config; + const configContent = fs.readFileSync(configPath, 'utf-8'); + const configParsed = parseIni(configContent); + expect(configParsed['profile dev'].region).toBe('us-east-1'); + }); + + it('resolves credentials and config paths independently from env vars', {}, () => { + process.env.AWS_SHARED_CREDENTIALS_FILE = '/custom-creds/credentials'; + // AWS_CONFIG_FILE is NOT set — should use default path + + fs.mkdirSync('/custom-creds', { recursive: true }); + + writeProfileFiles( + 'dev', + { AccessKeyId: 'AKIA', SecretAccessKey: 'secret' }, + 'us-east-1', + false + ); + + expect(fs.existsSync('/custom-creds/credentials')).toBe(true); + // Config file should be at the default path (under homedir) + const defaultConfigPath = require('node:path').join(require('node:os').homedir(), '.aws', 'config'); + expect(fs.existsSync(defaultConfigPath)).toBe(true); + }); + + it('produces AWS CLI-compatible INI output (golden file)', {}, () => { + writeProfileFiles( + 'dev', + { + AccessKeyId: 'AKIAIOSFODNN7EXAMPLE', + SecretAccessKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY', + SessionToken: 'FwoGZXIvYXdzEBYaDEXAMPLE', + }, + 'us-east-1', + false + ); + + const credsPath = getProfileFilePaths().credentials; + const credContent = fs.readFileSync(credsPath, 'utf-8'); + + const configPath = getProfileFilePaths().config; + const configContent = fs.readFileSync(configPath, 'utf-8'); + + // Verify exact byte-for-byte format matching AWS CLI style: + // - [section] header on its own line + // - key = value with spaces around = + // - LF line endings, trailing newline + expect(credContent).toBe( + '[dev]\n' + + 'aws_access_key_id = AKIAIOSFODNN7EXAMPLE\n' + + 'aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\n' + + 'aws_session_token = FwoGZXIvYXdzEBYaDEXAMPLE\n', + ); + expect(configContent).toBe( + '[profile dev]\n' + + 'region = us-east-1\n', + ); + }); + + it('golden file for multi-profile output', {}, () => { + writeProfileFiles( + 'dev', + { AccessKeyId: 'AKIADEV', SecretAccessKey: 'devSecret' }, + 'us-east-1', + false + ); + writeProfileFiles( + 'prod', + { AccessKeyId: 'AKIAPROD', SecretAccessKey: 'prodSecret', SessionToken: 'prodToken' }, + 'us-west-2', + false + ); + + const credsPath = getProfileFilePaths().credentials; + const credContent = fs.readFileSync(credsPath, 'utf-8'); + + const configPath = getProfileFilePaths().config; + const configContent = fs.readFileSync(configPath, 'utf-8'); + + expect(credContent).toBe( + '[dev]\n' + + 'aws_access_key_id = AKIADEV\n' + + 'aws_secret_access_key = devSecret\n' + + '\n' + + '[prod]\n' + + 'aws_access_key_id = AKIAPROD\n' + + 'aws_secret_access_key = prodSecret\n' + + 'aws_session_token = prodToken\n', + ); + expect(configContent).toBe( + '[profile dev]\n' + + 'region = us-east-1\n' + + '\n' + + '[profile prod]\n' + + 'region = us-west-2\n', + ); + }); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index 9294dbf..f71566c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -23,7 +23,8 @@ "target": "ES2020", "noErrorTruncation": true, "esModuleInterop": true, - "rootDir": "src" + "rootDir": "src", + "skipLibCheck": true }, "include": ["src/**/*.ts"], "exclude": ["test/**/*.ts"]